aboutsummaryrefslogtreecommitdiff
path: root/test/functional/plugin/lsp_spec.lua
Commit message (Collapse)AuthorAge
* perf(lua): share compatible file watchersLewis Russell2026-09-24
| | | | | | | | | | | | | | | | | | Problem: LSP servers can register identical file watchers before removing old registrations. Each registration creates another watcher tree, making setup and teardown expensive in large workspaces. Solution: Move glob compilation into vim._watch and accept rules for multiple roots alongside existing single-path and LPeg inputs. Share compatible backends until their last subscription is cancelled. Ref: #23291 AI-assisted
* fix(lsp): stale client when rpc.connect fails #41792stenja2026-09-09
| | | | | | | | | | | | | | | | Problem: When vim.lsp.rpc.connect is used for an LSP and the connection fails (due to ECONNREFUSED, for example), the LSP client is left uninitialized. Subsequent calls to vim.lsp.start will reuse the uninitialized client, but no new connection will be attempted. Solution: When vim.lsp.rpc.connect encounters a connection error, terminate the client. This allows the user to do something in response to the failed connection in the on_exit callback, and also makes it so subsequent attempts to start the LSP will try to establish a new connection rather than using the previous uninitialized client. Co-authored-by: Jason Stenftenagel <jasonstenftenagel@gmail.com>
* feat(watch): add on_error callbackLewis Russell2026-09-09
| | | | | | | | | | | | | | Problem: Watcher failures are reported inconsistently, and callers cannot detect when a watcher is no longer usable. Solution: Route startup and event failures and unexpected inotifywait exits through an optional on_error callback. Log to nvim-watch.log by default. Ignore disappearing child directories and exits caused by cancellation. Let LSP log failures and notify once per message, using INFO for missing roots and ERROR otherwise, without interrupting registration. AI-assisted
* fix(lsp): completion dynamicRegistration breaks capability readers #41771glepnir2026-09-08
| | | | | | | | | | Problem: A server that supports dynamic registration must not also declare completionProvider at initialize, so server_capabilities.completionProvider is nil for anything reading it directly. Solution: Do not advertise it by default. A client that follows a registration can declare it itself.
* feat(lsp): support dynamic registration of completionProvider #41748glepnir2026-09-07
| | | | | | | | Problem: dynamicRegistration is declared false, and completionProvider is read from server_capabilities only. Solution: Declare it, and prefer registerOptions per field.
* fix(lsp): blank line before Content-Length breaks header parsing #41524Yanze Li2026-08-29
| | | | | | | | | | | | | | Problem: A blank line before the Content-Length header makes header parsing fail with "Content-Length not found in header". An LF in the 'name' state falls through to the 'invalid' state, which only return to 'name' at the *next* LF. That LF terminates the next line, so the line with Content-Length is swallowed. The same issue happens when a junk line is a partial match of the header name (e.g. "Cont\nContent-Length: ..."). Solution: When seeing an LF in the 'name' state, reset the cursor and stay in 'name' rather than entering 'invalid'.
* revert: "fix(lsp): do not expand $VAR in tagfunc filenames" #41489Justin M. Keyes2026-08-25
| | | revert c5cb0350a6dc578e6ca73eefdd58159e1e039cbf
* fix(lsp): do not expand $VAR in tagfunc filenames #41398Shubh2026-08-23
| | | | | | | | Problem: CTRL-] via lsp tagfunc passes URI paths through :tag, which expands \$VAR, so files like people_.\$personId.tsx fail with E429. Solution: fnameescape() the filename in tag items so \$ is treated as literal.
* feat(lsp): fallback to textDocument/formatting from vim.lsp.formatexpr #40079Marcus Caisey2026-08-18
| | | | | | | | | | | | | | | | Problem: With the addition of the `:help al` text object, you can now easily format the whole buffer with `gqal`. However, `vim.lsp.formatexpr` only uses `textDocument/rangeFormatting` which some language servers (like gopls) don't support. Solution: - Fall back to `textDocument/formatting` if the whole buffer is being formatted and the server doesn't support `textDocument/rangeFormatting`. - In theory, these two methods should return the same response if the whole buffer is being formatted, but I preserved the existing behaviour of prioritising `textDocument/rangeFormatting` in case that does not hold (i.e. LS bug). - Also: `vim.lsp.formatexpr` had no tests at all, so actually add tests for it.
* fix(lsp): ignore (and log once) invalid documentSelector globs #41055Rocco Vaccone2026-08-04
| | | | | | | | | | | | | Problem: Client:_get_registrations() errors if a documentSelector pattern is not a valid glob (e.g. "**/**.dart" from dartls). The error discards already-matched registrations and propagates to supports_method() and the client/registerCapability handler, disabling capabilities unrelated to the offending pattern. Solution: Skip invalid globs. Treat them as non-matching, and log once per client. (Similar approach was taken for filewatcher globs: 4d9e5acfb5ee081b5b0761205c8a8c36ed7e49e5)
* fix(lsp): only send `didSave` to clients attached to the buffer (#41126)Maria Solano2026-08-03
|
* feat(options): lua closure/function optionsJustin M. Keyes2026-07-31
| | | | | | | | | | | | | | | Problem: Cannot assign Lua functions/closures to "func" ('completefunc', 'tagfun', …) or "expr" ('foldexpr', 'indentexpr', …) options. Solution: - Store "func"/"expr" options as `Callback` instead of string. - Delete oceans of copy-pasted code. - BREAKING: LuaRef returned via RPC/Vimscript is now represented as `"<Lua N: file:line>"` (like what `:map` shows) instead of `nil`. - Note: `man.vim` still uses `v:lua` string, bc it's a vimscript ftplugin. Helped-by: Lewis Russell <lewis6991@gmail.com>
* test(harness): migrate away from magic globalsJustin M. Keyes2026-07-21
| | | | | | | | | | | | | | | | | | | | | Problem: The magic globals `it`, `describe`, etc., are more trouble than they are worth. - Hooking into `after_each` requires `getfenv()` hacks. - They confuse luals/emmylua, because the top-level `.luarc.json` isn't merged with `test/.luarc.json` (apparently a luals limitation?) - They totally defeat discoverability because the user just has to "know" about the various magic symbols. So they harm DX, which means they serve no purpose at all. Solution: - Expose the test API from `testutil`, so tests can call `t.it()`, `t.describe()`, etc., in the conventional way. - Drop `getfenv()` hacks. - Drop the `setfenv()` injection in `load_chunk`. - Drop `test/_meta.lua`.
* refactor(test): unify XDG path handling (cmake vs zig)bfredl2026-07-06
| | | | fixes #40381
* fix(lsp): use root_dir as cmd CWD #40331Barrett Ruth2026-06-26
| | | | | | | | | | | | Problem: cmd given as string[] always starts using Nvim's CWD, which is arbitrary. Solution: If cmd_cwd is not given, use root_dir as CWD. BREAKING CHANGE: LSP commands given as string arrays now use `root_dir` as the process working directory when `cmd_cwd` is unset. Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
* fix(lsp): skip invalid file watcher globs #40376 #40396Barrett Ruth2026-06-25
| | | | | Some servers register `workspace/didChangeWatchedFiles` watchers for URI schemes that cannot be watched locally. Skipping the unsupported glob and keep the rest of the registration batch active.
* fix(lsp): trailing blank line when edit inserts past end of buffer #40133Aaron Tinio2026-06-10
| | | | | | | | | | | | | | | | Problem: A text edit positioned entirely past the last buffer line, with newText ending in a newline, leaves a stray blank line: the past-the-end path appends the trailing empty fragment produced by vim.split() and does not set has_eol_text_edit, so the end-of-buffer cleanup is skipped. Formatting servers emit such edits whenever formatting moves text to the end of a document. Regression from ec94014cd1 (#20137), which split the past-the-end fast path off the clamp path that sets the flag. Solution: Set has_eol_text_edit in the past-the-end path, like the adjacent path that clamps end_row.
* fix(lsp): handle requests with null id #40073Yi Ming2026-06-02
| | | | | | | | | | | | | Problem: PR #38340 prevented messages we receive with id:null from being incorrectly classified as notifications, but caused us to ignore all messages with id:null, including requests. Solution: Handle requests with id:null. When we receive a request, we only need to respond based on the `method` and `param`. (The original so-called `notification_received` in the test was actually semantically `request_or_notification_received`.)
* feat(logging): vim.log #38906Yi Ming2026-05-10
|
* fix(lsp): support nested workspace registrations #39574Tristan Knight2026-05-05
| | | | | | | | | | | Problem: Nested workspace capabilities like workspace.fileOperations.didCreate and workspace.textDocumentContent are not handled consistently for dynamic and static registration provider lookup. Solution: Generate explicit registration-provider mappings from the LSP metadata and use them when registering and querying capabilities. Add coverage for dynamic and static nested workspace registrations.
* fix(lsp): dynamic registration for off-spec method #39544Tristan Knight2026-05-01
| | | | | | | | | | | | | Problem: LSP clients previously did not handle dynamic registration for off-spec methods Solution: Update the client logic to assume support for dynamic registration when the method is unknown. Adjust the registration provider fallback and enhance tests to verify correct behaviour for unknown methods and their registration options. This improves compatibility with servers using custom dynamic registrations. AI-assisted: OpenCode
* fix(lsp): send didClose, didOpen when languageId changes #39499phanium2026-04-30
| | | | | | | | | | | | | | | | | | | | | | Problem: If a buffer's filetype changes after the LSP client has already attached (e.g. from json to jsonc via a modeline), but the client supports both filetypes, it stays attached. It does not notify the server of the new languageId, causing the server to incorrectly process the file using the old languageId. Solution: Save the languageId used during textDocument/didOpen, and send textDocument/didClose + textDocument/didOpen when buffer's languageId changed. Lsp spec: https://github.com/microsoft/language-server-protocol/blob/0003fb53f18dc7a4ac7e51d0eb518deeea90fde5/_specifications/lsp/3.18/textDocument/didOpen.md#L5 > If the language id of a document changes, the client > needs to send a textDocument/didClose to the server followed by a > textDocument/didOpen with the new language id if the server handles > the new language id as well. AI-assisted: Gemini 3.1 Pro
* fix(lsp): handle self-mapped methods in supports_method #39383Tristan Knight2026-04-24
| | | | | | | | | | | | | | | 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.
* fix(lsp): more info in error msg, deduplicate test #39359Justin M. Keyes2026-04-24
|
* fix(lsp): handle null id in JSON-RPC responses #38340atusy2026-04-23
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* test(lsp): extract buf/util parts from lsp_spec.lua #39149Yi Ming2026-04-17
| | | | | | | | | Problem: `test/functional/plugin/lsp_spec.lua` had grown into a large catch-all file that mixed core LSP client lifecycle coverage, `vim.lsp.buf.*` behavior, and `vim.lsp.util.*` behavior in one place. Solution: Split the large tests into more focused test files without changing test coverage or intended behavior. After this change, `lsp_spec.lua` is more focused on core LSP client/config/dynamic-registration behavior.
* refactor: update usages of deprecated "buffer" param #39089Justin M. Keyes2026-04-15
|
* fix(lsp): show_document can't position cursor past EOL in insert-mode #38566Lars Debor2026-04-12
| | | | | | | | 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.
* fix(lsp): send didOpen on save to all clients+groups #37454Emilv22026-04-12
| | | | | | | 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.
* fix(lsp): apply_text_edits causes unwanted BufDelete events #38778glepnir2026-04-08
| | | | | | | | | | | | | | 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.
* feat(lsp): respect 'switchbuf' for jump commands, drop `reuse_win` #38510Yi Ming2026-03-30
| | | | | | | | | | | | | | | | | | 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.
* test: fix s390x failuresJustin M. Keyes2026-03-29
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: failures in s390x CI. Solution: - runtime/lua/man.lua: parse_path() can return nil but 3 callers didn't handle it. - skip some tests on s390x. TODO: - TODO: why "build/bin/xxd is not executable" on s390x? - TODO: other failures, not addressed (see below). OTHER FAILURES: FAILED test/functional/treesitter/fold_spec.lua @ 87: treesitter foldexpr recomputes fold levels after lines are added/removed test/functional/treesitter/fold_spec.lua:95: Expected objects to be the same. Passed in: (table: 0x4013c18940) { [1] = '0' [2] = '0' [3] = '0' *[4] = '0' [5] = '0' ... Expected: (table: 0x4005acf900) { [1] = '0' [2] = '0' [3] = '>1' *[4] = '1' [5] = '1' ... stack traceback: (tail call): ? test/functional/treesitter/fold_spec.lua:95: in function <test/functional/treesitter/fold_spec.lua:87> FAILED test/functional/treesitter/select_spec.lua @ 52: treesitter incremental-selection works test/functional/treesitter/select_spec.lua:63: Expected objects to be the same. Passed in: (string) 'bar(2)' Expected: (string) 'foo(1)' stack traceback: (tail call): ? test/functional/treesitter/select_spec.lua:63: in function <test/functional/treesitter/select_spec.lua:52> FAILED test/functional/treesitter/select_spec.lua @ 69: treesitter incremental-selection repeat test/functional/treesitter/select_spec.lua:82: Expected objects to be the same. Passed in: (string) '2' Expected: (string) '4' stack traceback: (tail call): ? test/functional/treesitter/select_spec.lua:82: in function <test/functional/treesitter/select_spec.lua:69> FAILED test/functional/treesitter/select_spec.lua @ 98: treesitter incremental-selection history test/functional/treesitter/select_spec.lua:111: Expected objects to be the same. Passed in: (string) 'bar(2)' Expected: (string) 'foo(1)' stack traceback: (tail call): ? test/functional/treesitter/select_spec.lua:111: in function <test/functional/treesitter/select_spec.lua:98> FAILED test/functional/treesitter/select_spec.lua @ 186: treesitter incremental-selection with injections works test/functional/treesitter/select_spec.lua:201: Expected objects to be the same. Passed in: (string) 'lua' Expected: (string) 'foo' stack traceback: (tail call): ? test/functional/treesitter/select_spec.lua:201: in function <test/functional/treesitter/select_spec.lua:186> FAILED test/functional/treesitter/select_spec.lua @ 216: treesitter incremental-selection with injections ignores overlapping nodes test/functional/treesitter/select_spec.lua:231: Expected objects to be the same. Passed in: (string) ' )' Expected: (string) ' foo(' stack traceback: (tail call): ? test/functional/treesitter/select_spec.lua:231: in function <test/functional/treesitter/select_spec.lua:216> FAILED test/functional/treesitter/select_spec.lua @ 307: treesitter incremental-selection with injections handles disjointed trees test/functional/treesitter/select_spec.lua:337: Expected objects to be the same. Passed in: (string) 'int' Expected: (string) '1}' stack traceback: (tail call): ? test/functional/treesitter/select_spec.lua:337: in function <test/functional/treesitter/select_spec.lua:307> ERROR test/functional/treesitter/parser_spec.lua @ 562: treesitter parser API can run async parses with string parsers test/functional/treesitter/parser_spec.lua:565: attempt to index a nil value stack traceback: test/functional/testnvim/exec_lua.lua:124: in function <test/functional/testnvim/exec_lua.lua:105> (tail call): ? (tail call): ? test/functional/treesitter/parser_spec.lua:563: in function <test/functional/treesitter/parser_spec.lua:562> FAILED test/functional/core/job_spec.lua @ 1157: jobs jobstop() kills entire process tree #6530 test/functional/core/job_spec.lua:1244: retry() attempts: 94 test/functional/core/job_spec.lua:1246: Expected objects to be the same. Passed in: (table: 0x401dd74b30) { [name] = 'sleep <defunct>' [pid] = 33579 [ppid] = 1 } Expected: (userdata) 'vim.NIL' stack traceback: test/testutil.lua:89: in function 'retry' test/functional/core/job_spec.lua:1244: in function <test/functional/core/job_spec.lua:1157>
* test(lsp): get_configs resolves only necessary configsJustin M. Keyes2026-03-24
|
* revert: "feat(lsp): add `buftypes` field to `vim.lsp.Config`" #38421Maria Solano2026-03-22
| | | | | revert: "feat(lsp): add `buftypes` field to `vim.lsp.Config` (#38380)" This reverts commit cfcdbcf638b5957213b3c1a55d5d1b5659a2fb3e.
* feat(lsp): add `buftypes` field to `vim.lsp.Config` (#38380)Barrett Ruth2026-03-21
| | | | | | Problem: `vim.lsp.enable()` skips buffers with `buftype` set, even when `filetype` matches. Solution: Add `buftypes` field to `vim.lsp.Config`.
* refactor(lsp): replace _provider_value_get with _provider_foreachtris2032026-03-19
| | | | | | | | Introduce _provider_foreach to iterate over all matching provider capabilities for a given LSP method, handling both static and dynamic registrations. Update diagnostic logic and tests to use the new iteration approach, simplifying capability access and improving consistency across features.
* feat(lsp): vim.lsp.get_configs() #37237Olivia Kinnear2026-03-19
| | | | | | | | | | Problem: No way to iterate configs. Users need to reach for `vim.lsp.config._configs`, an internal interface. Solution: Provide vim.lsp.get_configs(). Also indirectly improves :lsp enable/disable completion by discarding invalid configs from completion.
* docs: use "ev" convention in event-handlersJustin M. Keyes2026-03-12
| | | | | | | | | Problem: In autocmd examples, using "args" as the event-object name is vague and may be confused with a user-command. Solution: Use "ev" as the conventional event-object name.
* test(lsp): fix fake LSP server timeout not working (#37970)zeertzjq2026-02-19
| | | | | | | | | | Problem: Fake LSP server does not timeout or respond to SIGTERM as it does not run the event loop. Solution: Instead of io.read(), use stdioopen()'s on_stdin callback to accumulate input and use vim.wait() to wait for input. Also, in the test suite, don't stop a session when it's not running, as calling uv.stop() outside uv.run() will instead cause the next uv.run() to stop immediately, which cancels the next RPC request.
* fix(lsp): map all LSP methods to server capabilities (#37910)Tristan Knight2026-02-16
|
* fix(lsp): support workspace/configuation with no section #27510Tim Pope2026-02-15
| | | | | | | | | | | | | | | | | | | The [spec for `workspace/configuration`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration) marks the `section` property of each item in `items` optional. Therefore, I believe it violates the spec to skip over items without a section, because then the length of the results won't match the length of a valid `items` input. The spec does not elaborate on _what_ to return in this case, but I don't think it would be controversial to say that returning the full configuration, as done for an empty string, is the most natural interpretation. That empty string case, by the way, was initially [added in response](https://github.com/neovim/nvim-lspconfig/commit/5da124fc82c24592abc3a97c68363bb4e85e3c85) to a real world implementation requesting it. I don't have a similar real world implementation to point to for the omitted `section`, but I would note that `getConfiguration()` from `vscode-languageserver-node` [defaults to a request with no section](https://github.com/Microsoft/vscode-languageserver-node/blob/d859bb14d1bcb3923eecaf0ef587e55c48502ccc/server/src/common/configuration.ts#L24-L26) when called with no arguments. I surmise that this is intended as a way to retrieve the full configuration.
* refactor(lsp): centralize provider capability resolution #37221Tristan Knight2026-02-14
| | | | | | | | - Refactor LSP client to use unified provider-based capability lookup for diagnostics and other features. - Introduce `_provider_value_get` to abstract capability retrieval, supporting both static and dynamic registrations. - Update diagnostic handling and protocol mappings to leverage provider-centric logic.
* feat(lsp)!: reimplement `textDocument/codeLens` as decoration providerYi Ming2026-02-03
|
* fix: lsp.enable() don't work correctly inside FileType event #37538phanium2026-01-27
| | | | | | | | | | | | | | | | | | | | Problem: Two cases lsp.enable() won't work in the first FileType event 1. lsp.enable luals inside FileType or ftplugin/lua.lua, then: ``` nvim a.lua ``` 2. lsp.enable luals inside FileType or ftplugin/lua.lua, then: ``` nvim -> :edit a.lua -> :mksession! | restart +qa! so Session.vim ``` Solution: Currently `v:vim_did_enter` is used to detected two cases: 1. "maunally enabled" (lsp.enable() or `:lsp enable`) 2. "inside FileType event" To detect 2. correctly we use did_filetype().
* feat(lsp): semantic token range improvements #37451jdrouhard2026-01-27
| | | | | | | | | | | | | | | | | | | | * cache all tokens from various range requests for a given document version - all new token highlights a