feat(lsp): add support for completionItem.command resolving

`command` was already resolved via a `completionItem/resolve` request
but only if `additionalTextEdits` were also present, and the
`resolveSupport` capability wasn't listed.

Closes https://github.com/neovim/neovim/issues/32406
This commit is contained in:
Mathias Fussenegger
2025-02-14 14:06:47 +01:00
committed by Mathias Fußenegger
parent c091bc3b9a
commit f20335a54c
3 changed files with 69 additions and 9 deletions

View File

@@ -610,13 +610,14 @@ local function on_complete_done()
clear_word()
if err then
vim.notify_once(err.message, vim.log.levels.WARN)
elseif result and result.additionalTextEdits then
lsp.util.apply_text_edits(result.additionalTextEdits, bufnr, position_encoding)
elseif result then
if result.additionalTextEdits then
lsp.util.apply_text_edits(result.additionalTextEdits, bufnr, position_encoding)
end
if result.command then
completion_item.command = result.command
end
end
apply_snippet_and_command()
end, bufnr)
else

View File

@@ -458,6 +458,7 @@ function protocol.make_client_capabilities()
resolveSupport = {
properties = {
'additionalTextEdits',
'command',
},
},
tagSupport = {

View File

@@ -770,20 +770,25 @@ end)
--- @param name string
--- @param completion_result lsp.CompletionList
--- @param trigger_chars? string[]
--- @param opts? {trigger_chars?: string[], resolve_result?: lsp.CompletionItem}
--- @return integer
local function create_server(name, completion_result, trigger_chars)
local function create_server(name, completion_result, opts)
opts = opts or {}
return exec_lua(function()
local server = _G._create_server({
capabilities = {
completionProvider = {
triggerCharacters = trigger_chars or { '.' },
triggerCharacters = opts.trigger_chars or { '.' },
resolveProvider = opts.resolve_result ~= nil,
},
},
handlers = {
['textDocument/completion'] = function(_, _, callback)
callback(nil, completion_result)
end,
['completionItem/resolve'] = function(_, _, callback)
callback(nil, opts.resolve_result)
end,
},
})
@@ -794,7 +799,7 @@ local function create_server(name, completion_result, trigger_chars)
cmd = server.cmd,
on_attach = function(client, bufnr0)
vim.lsp.completion.enable(true, client.id, bufnr0, {
autotrigger = trigger_chars ~= nil,
autotrigger = opts.trigger_chars ~= nil,
convert = function(item)
return { abbr = item.label:gsub('%b()', '') }
end,
@@ -968,7 +973,7 @@ describe('vim.lsp.completion: protocol', function()
},
},
}
create_server('dummy1', results1, { 'e' })
create_server('dummy1', results1, { trigger_chars = { 'e' } })
local results2 = {
isIncomplete = false,
items = {
@@ -977,7 +982,7 @@ describe('vim.lsp.completion: protocol', function()
},
},
}
create_server('dummy2', results2, { 'h' })
create_server('dummy2', results2, { trigger_chars = { 'h' } })
feed('h')
exec_lua(function()
@@ -1042,6 +1047,59 @@ describe('vim.lsp.completion: protocol', function()
end)
end)
it('resolves and executes commands', function()
local completion_list = {
isIncomplete = false,
items = {
{
label = 'hello',
},
},
}
local client_id = create_server('dummy', completion_list, {
resolve_result = {
label = 'hello',
command = {
arguments = { '1', '0' },
command = 'dummy',
title = '',
},
},
})
exec_lua(function()
_G.called = false
local client = assert(vim.lsp.get_client_by_id(client_id))
client.commands.dummy = function()
_G.called = true
end
end)
feed('ih')
trigger_at_pos({ 1, 1 })
local item = completion_list.items[1]
exec_lua(function()
vim.v.completed_item = {
user_data = {
nvim = {
lsp = {
client_id = client_id,
completion_item = item,
},
},
},
}
end)
feed('<C-x><C-o><C-y>')
assert_matches(function(matches)
eq(1, #matches)
eq('hello', matches[1].word)
eq(true, exec_lua('return _G.called'))
end)
end)
it('enable(…,{convert=fn}) custom word/abbr format', function()
create_server('dummy', {
isIncomplete = false,