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: Relaxing minimal Git version did not fully preserve previous
behavior in case there no `git` executable. Instead it showed the same
error as if after `vim.system({ 'does_not_exist' })`.
Solution: Show a more direct "No `git` executable" error message.
Problem: No example workflow of how to revert after a bad update.
Solution: Add example workflow of how to revert after a bad update.
In future this might be improved by utilizing other `vim.pack`
features or via a dedicated function (like `vim.pack.restore()` that
restores all installed plugins to a state from the lockfile).
Problem: Changing `src` of an existing plugin cleanly requires manual
`vim.pack.del()` prior to executing `vim.pack.add()` with a new `src`.
Solution: Autodetect `src` change for an existing plugin (by comparing
against lockfile data). If different - properly delete immediately and
treat this as new plugin installation.
Alternative solution might be to update `origin` remote in the
installed plugin after calling `vim.pack.update()`. Although, doable,
this 1) requires more code; and 2) works only for Git plugins (which
might be not the only type of plugins in the future). Automatic
"delete and clean install" feels more robust.
Problem: Plain `vim.pack.add()` calls (with default `opts.load`) does
not fully work if called inside 'plugin/' runtime directory. In
particular, 'plugin/' files of newly added plugins are not sourced.
This is because `opts.load` is `false` during the whole startup, which
means `:packadd!` is used (modify 'runtimepath' but not force source
newly added 'plugin/' files).
This use case is common due to users organizing their config as
separate files in '~/.config/nvim/plugin/'.
Solution: Use newly added `v:vim_did_init` to decide default `opts.load`
value instead of `v:vim_did_enter`.
Problem: Current requirement is Git>=2.36 as `--also-filter-submodules`
flag for `git clone` was introduced there. This is problematic since
default Git version on Ubuntu 22.04 is 2.34.
Solution: Relax minimal Git version to be (at least) 2.0 by selectively
applying necessary flags based on the current Git version.
As 2.0.0 was released in 2014-05-28 (almost the same age as Neovim
project itself), it is reasonable to drop any mention and checks on
minimal version altogether.
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
`:trust` command calculated SHA-256 on file content reading it as a
text. While it doesn't matter on Unices, on Windows hash was calculated
incorectly. SHA-256 for buffer content was calculated fine though.
After this fix hashes in `%LOCALAPPDATA%/nvim-data/trust` are the same
as in output of `sha256sum -t`.
Problem:
- Exposing the raw config as table is a pattern not seen anywhere else
in the Nvim codebase.
- Old spellfile.vim docs still available, no new documentation
Solution:
- Exposing a `config()` function that both acts as "getter" and "setter"
is a much more common idiom (e.g. vim.lsp, vim.diagnostic).
- Add new documentation and link old docs to |spellfile.lua| instead of
|spellfile.vim|.
* 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: Resize events during startup may clear an active external
cmdline, which is then not redrawn.
UI2 VimResized autocommand does not work.
UI2 message appearance may be altered by inherited window
options. The message separator uses the wrong fillchar.
Solution: Unset cmdline_was_last_redrawn when clearing the screen, such
that cmdline_show is re-emitted.
Ensure set_pos function is called without arguments.
Ensure such options are unset. Use 'fillchars'->msgsep.
Problem: Confirmation buffer is named with `nvim-pack://` as scheme
prefix and uses buffer id (needed for in-process LSP) as one an entry
in the "hierarchical part".
Solution: Use `nvim://pack-confirm#<buf>` format with a more ubiquitous
`nvim://` prefix and buffer id at the end as the optional fragment.
Problem: In some areas plugin's revision is named "state". This might be
confusing for the users.
Solution: Consistently use "revision" to indicate "plugin's state on
disk".
Problem: Using abbreviated version of commit hashes might be unreliable
in the long term (although highly unlikely).
Solution: Use full hashes in lockfile and revision description (in
confirmation buffer and log). Keep abbreviated hashes when displaying
update changes (for brevity).
The iterator is meant to be fully reset in this code path, but only the
`next_row` state was being reset. This would only cause highlight
artifacts for very brief periods of time, though.
Problem: A plugin does not know when startup scripts were already
triggered. This is useful to determine if a function is
called inside vimrc or after (like when sourcing 'plugin/'
files).
Solution: Add the v:vim_did_init variable (Evgeni Chasnovski)
closes: vim/vim#18668294bce21ee
Nvim has two more steps between sourcing startup scripts and loading
plugins. Set this variable after these two steps.
Co-authored-by: Evgeni Chasnovski <evgeni.chasnovski@gmail.com>
Problem:
Previously, the fallback logic to ".conf" was located outside of
`vim.filetype.match()` and directly within the AutoCmd definition. As a
result, `vim.filetype.match()` would return nil instead of ".conf" for
fallback cases (#30100).
Solution:
Added a boolean return value to `vim.filetype.match()` that indicates
whether the match was the result of fallback. If true, the filetype will
be set using `setf FALLBACK <ft>` instead of `setf <ft>`.
Problem: Setting a filetype before configuring default options for ui2
buffers (pager, cmd, ...) prevents users from setting their own options.
Solution: Call nvim_set_option_value after defaults are set.
Closes#36314
Co-authored-by: Luuk van Baal <luukvbaal@gmail.com>
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: pre-inserted text not exposed in complete_info()
Solution: Add the pre-inserted text to the complete_info() Vim script
function (Girish Palya)
closes: vim/vim#18571
Feat: expose preinserted text in complete_info()
ef5bf58d8c
Co-authored-by: Girish Palya <girishji@gmail.com>
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.
**Problem:**
`vim.filetype.match({ filename = 'a.sh' })` returns `nil` because
an invalid buffer ID is passed to `vim.api.nvim_buf_get_lines()`.
For filetypes like `csh`, `txt`, or any other extensions that call
`_getlines()` or `_getline()` to detect their filetypes, the same
issue occurs.
When only the `filename` argument is passed, an error is raised
inside a `pcall()` that wraps the filetype detection function,
causing it to return no value without showing any error message.
**Solution:**
Validate the `bufnr` value in `_getlines()` and `_getline()`.