Problem:
With the typescript LSes typescript-language-server and vtsls,
omnicompletion on partial tokens for certain types, such as array
methods, and functions that are attached as attributes to other
functions, either results in no entries populated in the completion menu
(typescript-language-server), or an unfiltered completion menu with all
array methods included, even if they don't share the same prefix as the
partial token being completed (vtsls).
Solution:
Enable insertReplaceSupport and uses the insert portion of the lsp
completion response in adjust_start_col if it's included in the
response.
Completion results are still filtered client side.
Problem: No way to customize completion order across multiple servers.
Solution: Add `cmp` function to `vim.lsp.completion.enable()` options
for custom sorting logic.
Problem:
Users often jump and navigate through LSP windows to yank text.
Concealed markdown can make navigation through hyperlinks and code
blocks more difficult.
Solution:
Change 'concealcursor' from 'n' to '' to preserve clean display
while improving navigation and selection of the LSP response.
Closes#36537
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
accd392f4d/test/functional/plugin/lsp_spec.lua (L1950)
and tweaked the boilerplate in two other tests for reusability while keeping the final assertions the same.
accd392f4d/test/functional/plugin/lsp_spec.lua (L5704)accd392f4d/test/functional/plugin/lsp_spec.lua (L5721)
Problem: reuse_win will always jump to the first window containing the
target buffer rather even if the buffer is displayed in the current
window/tab
Solution: check to see if the buffer is already displayed in the
current window or any window of the current buffer
* feat(lua): `Range:is_empty()` to check vim.range emptiness
* fix(lsp): don't overlay insertion-style inline completions
**Problem:** Some servers commonly respond with an empty inline
completion range which acts as a position where text should be inserted.
However, the inline completion module assumes that all responses with a
range are deletions + insertions that thus require an `overlay` display
style. This causes an incorrect preview, because the virtual text should
have the `inline` display style (to reflect that this is purely an
insertion).
**Solution:** Only use `overlay` for non-empty replacement ranges.
**Problem:** When quickly entering and leaving insert mode, sometimes
inline completion requests are returned and handled in normal mode. This
causes an extmark to be set, which will not get cleared until the next
time entering & leaving insert mode.
**Solution:** Return early in the inline completion handler if we have
left insert mode.
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.
Problem:
Some LSP method handlers were making requests without specifying a
bufnr, defaulting to 0 (current). This works in most cases but
fails when client attaches to background buffers, causing
assertions in handlers to fail.
Solution:
Ensure bufnr is passed to Client.request for buffer-local methods.
The current implementation has a race condition where items are appended
to the completion list twice when a second completion runs while the
first is still going. This hotfix just deduplicates the entire list.
Co-authored-by: Tomasz N <przepompownia@users.noreply.github.com>
* fix(lsp): type of root_dir should be annotated with string|fun|nil
* feat(lsp): support root_dir as function in _get_workspace_folders
* feat(lsp): let checkhealth support root_dir() function
Examples:
vim.lsp: Active Clients ~
- lua_ls (id: 1)
- Version: <Unknown>
- Root directories:
~/foo/bar
~/dev/neovim
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
Problem:
Autocmds in inline_completion Completor are not scoped to specific
buffers. When multiple buffers have inline completion enabled, events
(InsertEnter, CursorMovedI, TextChangedP, InsertLeave) in any buffer
trigger callbacks for all Completor instances, causing incorrect
behavior across buffers.
Solution:
Add `buffer = bufnr` parameter to nvim_create_autocmd() calls to make
them buffer-local. Each Completor instance now only responds to events
in its own buffer.
Problem:
If there are 2 language servers with different trigger chars (`-` and
`>`), and a keymap inputs both simultaneously (`->`), then `>` doesn't
trigger. We get completion items from server1 only.
This happens because the `completion_timer` for the `-` trigger is still
pending.
Solution:
If the next character arrived enough quickly (< 25 ms), replace the
existing deferred autotrigger with a new one that matches this later
character.
This fixes a regression from #33796.
I tried for several hours and cannot write a working test for this, but
this does fix the following warning in tests run with ASAN or TSAN:
-------- Running tests from test/functional/plugin/lsp_spec.lua
RUN T4667 LSP server_name specified start_client(), stop_client(): 114.00 ms OK
RUN T4668 LSP server_name specified stop_client() also works on client objects: 97.00 ms OK
RUN T4669 LSP server_name specified does not reuse an already-stopping client #33616: 31.00 ms OK
nvim took 2022 milliseconds to exit after last test
This indicates a likely problem with the test even if it passed!
Problem: The registerCapability handler re-enables document_color,
making it impossible to disable it in LspAttach.
Solution: Enable it once on initialization and avoid re-enabling
on registerCapability.
This also fixes the following warning in tests with ASAN or TSAN:
-------- Running tests from test/functional/plugin/lsp/inline_completion_spec.lua
RUN T4604 vim.lsp.inline_completion enable() requests or abort when entered/left insert mode: 225.00 ms OK
RUN T4605 vim.lsp.inline_completion get() applies the current candidate: 212.00 ms OK
nvim took 2013 milliseconds to exit after last test
This indicates a likely problem with the test even if it passed!
RUN T4606 vim.lsp.inline_completion get() accepts on_accept callback: 212.00 ms OK
RUN T4607 vim.lsp.inline_completion select() selects the next candidate: 220.00 ms OK
-------- 4 tests from test/functional/plugin/lsp/inline_completion_spec.lua (3437.00 ms total)
-------- Running tests from test/functional/plugin/lsp/linked_editing_range_spec.lua
nvim took 2011 milliseconds to exit after last test
This indicates a likely problem with the test even if it passed!
- Problem: It's not clear for new plugin developers that `:help` uses
a help-tags file for searching the docs, generated by `:helptags`.
- Solution: Hint to the |:helptags| docs for regenerating the tags
file for their freshly written documentation.
Co-authored-by: Yochem van Rosmalen <git@yochem.nl>
Problem:
Hover response of MarkedString[] where the first element contains a
language identifier treated as empty.
Solution:
Fix empty check to handle case of MarkedString[] where the first element
is a pair of a language and value.