fix(lsp): use LSP textEdit range for completion start boundary (#37491)

Previously, adjust_start_col returned nil when completion items had
different start position from lsp textEdit range
This caused the completion to fall back to \k*$ which ignores the
non-keyword characters

Changes:
- adjust_start_col: now returns the minimum start postion among all
items instead of nil
- _lsp_to_complete_items - normalizes the items by adding the gap between
  current and minimum start

Fixes: https://github.com/neovim/neovim/issues/37441
This commit is contained in:
Harsh Kapse
2026-01-30 20:04:42 +05:30
committed by GitHub
parent 47ce93ad6d
commit 36db6ff2c1
2 changed files with 75 additions and 9 deletions

View File

@@ -282,9 +282,21 @@ end
--- @param result vim.lsp.CompletionResult Result of `textDocument/completion`
--- @param prefix string prefix to filter the completion items
--- @param client_id integer? Client ID
--- @param server_start_boundary integer? server start boundary
--- @param line string? current line content
--- @param lnum integer? 0-indexed line number
--- @param encoding string? encoding
--- @return table[]
--- @see complete-items
function M._lsp_to_complete_items(result, prefix, client_id)
function M._lsp_to_complete_items(
result,
prefix,
client_id,
server_start_boundary,
line,
lnum,
encoding
)
local items = get_items(result)
if vim.tbl_isempty(items) then
return {}
@@ -320,6 +332,25 @@ function M._lsp_to_complete_items(result, prefix, client_id)
local match, score = matches(item)
if match then
local word = get_completion_word(item, prefix, match_item_by_value)
if server_start_boundary and line and lnum and encoding and item.textEdit then
--- @type integer?
local item_start_char
if item.textEdit.range and item.textEdit.range.start.line == lnum then
item_start_char = item.textEdit.range.start.character
elseif item.textEdit.insert and item.textEdit.insert.start.line == lnum then
item_start_char = item.textEdit.insert.start.character
end
if item_start_char then
local item_start_byte = vim.str_byteindex(line, encoding, item_start_char, false)
if item_start_byte > server_start_boundary then
local missing_prefix = line:sub(server_start_boundary + 1, item_start_byte)
word = missing_prefix .. word
end
end
end
local hl_group = ''
if
item.deprecated
@@ -393,16 +424,16 @@ local function adjust_start_col(lnum, line, items, encoding)
local min_start_char = nil
for _, item in pairs(items) do
if item.textEdit then
local start_char = nil
if item.textEdit.range and item.textEdit.range.start.line == lnum then
if min_start_char and min_start_char ~= item.textEdit.range.start.character then
return nil
end
min_start_char = item.textEdit.range.start.character
start_char = item.textEdit.range.start.character
elseif item.textEdit.insert and item.textEdit.insert.start.line == lnum then
if min_start_char and min_start_char ~= item.textEdit.insert.start.character then
return nil
start_char = item.textEdit.insert.start.character
end
if start_char then
if not min_start_char or start_char < min_start_char then
min_start_char = start_char
end
min_start_char = item.textEdit.insert.start.character
end
end
end
@@ -456,7 +487,9 @@ function M._convert_results(
server_start_boundary = client_start_boundary
end
local prefix = line:sub((server_start_boundary or client_start_boundary) + 1, cursor_col)
local matches = M._lsp_to_complete_items(result, prefix, client_id)
local matches =
M._lsp_to_complete_items(result, prefix, client_id, server_start_boundary, line, lnum, encoding)
return matches, server_start_boundary
end