aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp/rpc.lua
Commit message (Collapse)AuthorAge
* fix(lua): enable param-type-mismatch diagnosticLewis Russell2026-09-28
| | | | | | | | | Problem: Argument type mismatches are excluded from EmmyLua checks. Solution: Correct runtime annotations and calls, document local checker workarounds, and enable the diagnostic. AI-assisted
* fix(lua): add missing param annotations (#41911)Lewis Russell2026-09-15
|
* 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'.
* docs(lsp): document rpc Client request/notify as fields #40573Barrett Ruth2026-07-04
| | | | | | | | | | Problem: `request()` and `notify()` are methods of the object returned by `vim.lsp.rpc.start()`/`connect()`, but were rendered with module-level helptags (`vim.lsp.rpc.request()`, `vim.lsp.rpc.notify()`) (erroneously implying module functions that do not exist). Solution: Mark the wrappers `@private` and describe them on `vim.lsp.rpc.Client` instead.
* fix(lsp): always respond to requests, even on handler error #40076Yi Ming2026-06-04
| | | | | | | | | Problem: We perform validations after the request handler is called. When these validations fail, `error()` and `assert()` will prevent the subsequent code from running, meaning the server will never receive a response. Solution: Always respond to requests.
* 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`.)
* refactor(net): parameterize transport logging #40022Yi Ming2026-06-02
|
* refactor: rename `vim.transport` to `vim.net.transport` #40021Yi Ming2026-05-28
|
* refactor(lsp): avoid using coroutine when parsing framesYi Ming2026-05-22
|
* refactor(lsp): message stream abstraction for message framingYi Ming2026-05-22
|
* refactor(lsp): move `vim.lsp._transport` to `vim.net._transport`Yi Ming2026-05-22
|
* docs: misc #39511Justin M. Keyes2026-04-29
|
* perf(lsp): avoid unnecessary string allocations when parsing content-lengthYi Ming2026-04-25
|
* 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.
* refactor(lsp): fix typing for LSP methods #39099Luis Calle2026-04-16
| | | | | | | | 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.
* refactor(lsp): remove implicit rpc error tostring #38707Yi Ming2026-04-02
| | | | | | | | | | | | | | | | | | 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.
* refactor(lsp): better encapsulation and readability, inline unnecessaryYi Ming2026-04-01
|
* refactor(lsp): merge `vim.lsp.rpc.Client` and `vim.lsp.rpc.PublicClient`Yi Ming2026-03-30
|
* refactor: rename _ensure_integer => _assert_integerJustin M. Keyes2026-03-13
|
* refactor: integer functions, optimize asserts #34112Lewis Russell2026-03-12
| | | | | | | | | | | | refactor(lua): add integer coercion helpers Add vim._tointeger() and vim._ensure_integer(), including optional base support, and switch integer-only tonumber()/assert call sites in the Lua runtime to use them. This also cleans up related integer parsing in LSP, health, loader, URI, tohtml, and Treesitter code. supported by AI
* 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 are merged with previous highlights to maintain order and the "marked" property - this allows the tokens to stop flickering once they've loaded once per document version * abandon the processing coroutine if the request_id has changed instead of relying only on the document version - this will improve efficiency if a new range request is made while a previous one was processing its result * apply new highlights from processing coroutine directly to the current result when the version hasn't changed - this allows new highlights to be immediately drawable once they've processed instead of waiting for the whole response to be processed at once * rpc layer was changed to provide the request ID back in success callbacks, which is then provided as a request_id field on the handler context to lsp handlers
* build: ship "_core/*" as bytecode (built-into Nvim binary)Justin M. Keyes2025-12-30
| | | | | | | | | | | | | | | | | | | | | | | | | | Problem: We want to encourage implementing core features in Lua instead of C, but it's clumsy because: - Core Lua code (built into `nvim` so it is available even if VIMRUNTIME is missing/invalid) requires manually updating CMakeLists.txt, or stuffing it into `_editor.lua`. - Core Lua modules are not organized similar to C modules, `_editor.lua` is getting too big. Solution: - Introduce `_core/` where core Lua code can live. All Lua modules added there will automatically be included as bytecode in the `nvim` binary. - Move these core modules into `_core/*`: ``` _defaults.lua _editor.lua _options.lua _system.lua shared.lua ``` TODO: - Move `_extui/ => _core/ui2/`
* docs: misc #36580Justin M. Keyes2025-11-16
| | | | | | | | Co-authored-by: nguyenkd27 <nguyenkd27@gmail.com> Co-authored-by: dundargoc <gocdundar@gmail.com> Co-authored-by: Yochem van Rosmalen <git@yochem.nl> Co-authored-by: Tuure Piitulainen <tuure.piitulainen@gmail.com> Co-authored-by: Maria Solano <majosolano99@gmail.com> Co-authored-by: tao <2471314@gmail.com>
* feat(lsp): skip invalid header lines #36402tao2025-11-16
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Some servers write log to stdout and there's no way to avoid it. See https://github.com/neovim/neovim/pull/35743#pullrequestreview-3379705828 Solution: We can extract `content-length` field byte by byte and skip invalid lines via a simple state machine (name/colon/value/invalid), with minimal performance impact. I chose byte parsing here instead of pattern. Although it's a bit more complex, it provides more stable performance and allows for more accurate error info when needed. Here is a bench result and script: parse header1 by pattern: 59.52377ms 45 parse header1 by byte: 7.531128ms 45 parse header2 by pattern: 26.06936ms 45 parse header2 by byte: 5.235724ms 45 parse header3 by pattern: 9.348495ms 45 parse header3 by byte: 3.452389ms 45 parse header4 by pattern: 9.73156ms 45 parse header4 by byte: 3.638386ms 45 Script: ```lua local strbuffer = require('string.buffer') --- @param header string local function get_content_length(header) for line in header:gmatch('(.-)\r?\n') do if line == '' then break end local key, value = line:match('^%s*(%S+)%s*:%s*(%d+)%s*$') if key and key:lower() == 'content-length' then return assert(tonumber(value)) end end error('Content-Length not found in header: ' .. header) end --- @param header string local function get_content_length_by_byte(header) local state = 'name' local i, len = 1, #header local j, name = 1, 'content-length' local buf = strbuffer.new() local digit = true while i <= len do local c = header:byte(i) if state == 'name' then if c >= 65 and c <= 90 then -- lower case c = c + 32 end if (c == 32 or c == 9) and j == 1 then -- skip OWS for compatibility only elseif c == name:byte(j) then j = j + 1 elseif c == 58 and j == 15 then state = 'colon' else state = 'invalid' end elseif state == 'colon' then if c ~= 32 and c ~= 9 then -- skip OWS normally state = 'value' i = i - 1 end elseif state == 'value' then if c == 13 and header:byte(i + 1) == 10 then -- must end with \r\n local value = buf:get() return assert(digit and tonumber(value), 'value of Content-Length is not number: ' .. value) else buf:put(string.char(c)) end if c < 48 and c ~= 32 and c ~= 9 or c > 57 then digit = false end elseif state == 'invalid' then if c == 10 then -- reset for next line state, j = 'name', 1 end end i = i + 1 end error('Content-Length not found in header: ' .. header) end --- @param fn fun(header: string): number local function bench(label, header, fn, count) local start = vim.uv.hrtime() local value --- @type number for _ = 1, count do value = fn(header) end local elapsed = (vim.uv.hrtime() - start) / 1e6 print(label .. ':', elapsed .. 'ms', value) end -- header starting with log lines local header1 = 'WARN: no common words file defined for Khmer - this language might not be correctly auto-detected\nWARN: no common words file defined for Japanese - this language might not be correctly auto-detected\nContent-Length: 45 \r\n\r\n' -- header starting with content-type local header2 = 'Content-Type: application/json-rpc; charset=utf-8\r\nContent-Length: 45 \r\n' -- regular header local header3 = ' Content-Length: 45\r\n' -- regular header ending with content-type local header4 = ' Content-Length: 45 \r\nContent-Type: application/json-rpc; charset=utf-8\r\n' local count = 10000 collectgarbage('collect') bench('parse header1 by pattern', header1, get_content_length, count) collectgarbage('collect') bench('parse header1 by byte', header1, get_content_length_by_byte, count) collectgarbage('collect') bench('parse header2 by pattern', header2, get_content_length, count) collectgarbage('collect') bench('parse header2 by byte', header2, get_content_length_by_byte, count) collectgarbage('collect') bench('parse header3 by pattern', header3, get_content_length, count) collectgarbage('collect') bench('parse header3 by byte', header3, get_content_length_by_byte, count) collectgarbage('collect') bench('parse header4 by pattern', header4, get_content_length, count) collectgarbage('collect') bench('parse header4 by byte', header4, get_content_length_by_byte, count) ``` Also, I removed an outdated test https://github.com/neovim/neovim/blob/accd392f4d14a114e378f84dc15cb24bc34a370a/test/functional/plugin/lsp_spec.lua#L1950 and tweaked the boilerplate in two other tests for reusability while keeping the final assertions the same. https://github.com/neovim/neovim/blob/accd392f4d14a114e378f84dc15cb24bc34a370a/test/functional/plugin/lsp_spec.lua#L5704 https://github.com/neovim/neovim/blob/accd392f4d14a114e378f84dc15cb24bc34a370a/test/functional/plugin/lsp_spec.lua#L5721
* fix(lsp): stop repeatedly resuming dead coroutine #35743tao2025-10-27
| | | | | | | | | | Problem: Error extracting content-length causes all future coroutine resumes to fail. Solution: Replace coroutine.wrap with coroutine.create in create_read_loop so that we can check its status and catch any errors, allowing us to stop the lsp client and avoid repeatedly resuming the dead coroutine.
* fix(lsp): replace `string` types with LSP method alias type annotations ↵David2025-10-16
| | | | | where appropriate. (#36180) fix(lsp): replace `string` types with LSP method alias where appropriate
* fix(lsp): use LSP method type in more places (#36069)Maria Solano2025-10-07
|
* refactor(lua): consistent use of local aliasesChristian Clason2025-08-28
|
* fix(lsp): decode 'null' in server responses as vim.NIL #34849skewb1k2025-08-03
| | | | | | | | | | | | | | | | Problem: Previously, 'null' value in LSP responses were decoded as 'nil'. This caused ambiguity for fields typed as '? | null' and led to loss of explicit 'null' values, particularly in 'data' parameters. Solution: Decode all JSON 'null' values as 'vim.NIL' and adjust handling where needed. This better aligns with the LSP specification, where 'null' and absent fields are distinct, and 'null' should not be used to represent missing values. This also enables proper validation of response messages to ensure that exactly one of 'result' or 'error' is present, as required by the JSON-RPC specification.
* fix: type fixesLewis Russell2025-06-06
| | | | Type fixes caught by emmylua
* refactor(docs): remove unnecessary `@private`/`@nodoc` annotations (#33951)Maria José Solano2025-05-10
| | | | | * refactor(docs): remove `@private` annotations from local functions * refactor(docs): remove unnecessary `@nodoc` annotations
* refactor(lsp): use method type annotations for parametersMaria José Solano2025-04-21
|
* refactor: add basic stringbuffer shimLewis Russell2025-03-31
|
* perf(lsp): use string.buffer for rpc loopMathias Fussenegger2025-03-31
| | | | | | | | | | Avoids some table allocations. In a quick test over 50000 iterations it reduces the time from 130ms to 74 ms For the test setup details see: https://github.com/mfussenegger/nvim-dap/pull/1394#issue-2725352391
* perf(lsp): optimize content length extraction from rpc headersMathias Fussenegger2025-03-31
| | | | | | - No redundant `:gsub` to turn `-` in `Content-Length` into `_` - No table allocations only to add and later get the content-length header
* perf(lsp): don't construct potentially expensive stringsRiley Bruins2025-03-16
|
* refactor(lsp/rpc): move transport logic to separate moduleLewis Russell2024-12-12
|
* feat(lsp): deprecate vim.lsp.start_client #31341Maria José Solano2024-12-04
| | | | | | | | Problem: LSP module has multiple "start" interfaces. Solution: - Enhance vim.lsp.start - Deprecate vim.lsp.start_client
* refactor(lsp): avoid redundant function wrappingNicolas Hillegeer2024-11-12
| | | | Leftover from #21026.
* perf(validate): use lighter versionLewis Russell2024-10-17
| | | | | - Also fix `vim.validate()` for PUC Lua when showing errors for values that aren't string or number.
* docs: improve luacats support #30580James Trew2024-10-03
| | | | | | | Some composite/compound types even as basic as `(string|number)[]` are not currently supported by the luacats LPEG grammar used by gen_vimdoc. It would be parsed & rendered as just `string|number`. Changeset adds better support for these types.
* fix(vim.fs): dirname() returns "." on mingw/msys2 #30480Justin M. Keyes2024-09-23
| | | | | | | | | | Problem: `vim.fs.dirname([[C:\User\XXX\AppData\Local]])` returns "." on mingw/msys2. Solution: - Check for "mingw" when deciding `iswin`. - Use `has("win32")` where possible, it works in "fast" contexts since b02eeb6a7281df0561a021d7ae595c84be9a01be.
* feat(lsp): support hostname in rpc.connect #30238Tristan Knight2024-09-03
| | | | | | | | | | | | | | | Updated the `rpc.connect` function to support connecting to LSP servers using hostnames, not just IP addresses. This change includes updates to the documentation and additional test cases to verify the new functionality. - Modified `connect` function to resolve hostnames. - Updated documentation to reflect the change. - Added test case for connecting using hostname. Added a TCP echo server utility function to the LSP test suite. This server echoes the first message it receives and is used in tests to verify LSP server connections via both IP address and hostname. Refactored existing tests to use the new utility function.
* docs: vim.lsp.rpc.connect() TCP requires IP address #30219Tristan Knight2024-09-01
| | | | | | "localhost" would work if we used [tcp_connect](https://github.com/luvit/luv/blob/ae0387742b460bc89ebddce33214ad65fffddba2/examples/echo-server-client.lua#L42), but that will require changes to [vim.lsp.rpc.connect](https://github.com/neovim/neovim/blob/318c0415d5b10b44fee4afa06994734f1beb7e71/runtime/lua/vim/lsp/rpc.lua#L638).
* fix(lsp): check for nil response from server (#29196)Tama McGlinn2024-06-14
| | | | | | | | this only changes the error message, so that it is clear that the error is with the LSP server, rather than being a crash inside nvim runtime scripts. We are already doing a lot of validation, it's just that nil was being overlooked here. This fixes issue #27395
* refactor(lua): improve type annotationsLewis Russell2024-06-11
|
* fix(lsp): add "silent" option to vim.lsp.start (#28478)Gregory Anders2024-04-26
| | | | | | | | | | | | | | | | | | vim.notify cannot be suppressed and it is not always necessary to display a visible warning to the user if the RPC process fails to start. For instance, a user may have the same LSP configuration across systems, some of which may not have all of the LSP server executables installed. In that case, the user receives a notification every time a file is opened that they cannot suppress. Instead of using vim.notify in vim.lsp.rpc, propagate a normal error up through the call stack and use vim.notify in vim.lsp.start() only if the "silent" option is not set. This also updates lsp.start_client() to return an error message as its second return value if an error occurred, rather than calling vim.notify directly. Callers of lsp.start_client() will need to update call sites appropriately if they wish to report errors to the user (or even better, switch to vim.lsp.start).
* fix(lsp): buffer messages until connected to server (#28507)Mathias Fußenegger2024-04-26
| | | | | | `handle:write(msg)` can fail if the socket is not yet connected to the server. Should address https://github.com/neovim/neovim/pull/28398#issuecomment-2078152491
* refactor(lsp): merge rpc.domain_socket_connect into rpc.connect (#28398)Mathias Fußenegger2024-04-18
| | | See discussion in https://github.com/neovim/neovim/pull/26850