fix(lsp): multibyte fragment treated as commit char #41019

Problem:
commit_chars_str() reads a 0x80-0xBF byte as a 2-byte lead, so
"\xA9x" makes "x" a commit character. nvim_get_hl() returns
a dict, so `#` on it is 0 whether or not the group exists

Solution:
use vim.str_utf_end() and drop entries that are not one whole
character. use next check dict length.
This commit is contained in:
glepnir
2026-07-28 20:39:03 +08:00
committed by GitHub
parent 089c415cb2
commit 3fa5904cf2
2 changed files with 21 additions and 5 deletions

View File

@@ -377,7 +377,7 @@ local function generate_kind(item)
hex = hex:lower()
local group = ('@lsp.color.%s'):format(hex)
if #api.nvim_get_hl(0, { name = group }) == 0 then
if next(api.nvim_get_hl(0, { name = group })) == nil then
api.nvim_set_hl(0, group, { fg = '#' .. hex })
end
@@ -435,9 +435,12 @@ local function commit_chars_str(chars)
-- commit characters "should have `length=1`" and superfluous
-- characters are ignored. Keep the first codepoint.
local b = type(ch) == 'string' and ch:byte(1)
if b then
local len = b < 0x80 and 1 or b < 0xe0 and 2 or b < 0xf0 and 3 or 4
result[#result + 1] = ch:sub(1, len)
if b and b ~= 0 then
local n = vim.str_utf_end(ch, 1)
-- Only whole characters: multibyte fragments pair up across entries, NUL truncates.
if b < 0x80 or n > 0 then
result[#result + 1] = ch:sub(1, 1 + n)
end
end
end
return table.concat(result)

View File

@@ -1807,7 +1807,8 @@ describe('vim.lsp.completion: integration', function()
isIncomplete = false,
items = {
{
commitCharacters = { '.', ',', ';', '(' },
-- Only whole characters are kept: '\0' and '\169x' are dropped, '=>' becomes '='.
commitCharacters = { '.', ',', ';', '\0', '(', '=>', '\169x' },
data = {
cacheId = 1,
},
@@ -1842,6 +1843,18 @@ describe('vim.lsp.completion: integration', function()
wait_for_pum()
feed('(')
eq('f.(', n.api.nvim_get_current_line())
-- Test that a dropped fragment did not leak: 'x' must not commit, '=' must.
n.command('set completeopt=menuone,menu,noinsert')
feed('<ESC>Sf.')
wait_for_pum()
feed('x')
eq('f.x', n.api.nvim_get_current_line())
feed('<ESC>Sf.')
wait_for_pum()
feed('=')
eq('f.bar=', n.api.nvim_get_current_line())
end)
end)