mirror of
https://github.com/neovim/neovim.git
synced 2025-12-11 17:12:40 +00:00
feat(lsp): user-specified sorting of lsp.completion multi-server results #36401
Problem: No way to customize completion order across multiple servers. Solution: Add `cmp` function to `vim.lsp.completion.enable()` options for custom sorting logic.
This commit is contained in:
@@ -2040,6 +2040,8 @@ enable({enable}, {client_id}, {bufnr}, {opts})
|
|||||||
`triggerCharacters`.
|
`triggerCharacters`.
|
||||||
• {convert}? (`fun(item: lsp.CompletionItem): table`)
|
• {convert}? (`fun(item: lsp.CompletionItem): table`)
|
||||||
Transforms an LSP CompletionItem to |complete-items|.
|
Transforms an LSP CompletionItem to |complete-items|.
|
||||||
|
• {cmp}? (`fun(a: table, b: table): boolean`) Comparator
|
||||||
|
for sorting merged completion items from all servers.
|
||||||
|
|
||||||
get({opts}) *vim.lsp.completion.get()*
|
get({opts}) *vim.lsp.completion.get()*
|
||||||
Triggers LSP completion once in the current buffer, if LSP completion is
|
Triggers LSP completion once in the current buffer, if LSP completion is
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ LSP
|
|||||||
• The filter option of |vim.lsp.buf.code_action()| now receives the client ID as an argument.
|
• The filter option of |vim.lsp.buf.code_action()| now receives the client ID as an argument.
|
||||||
• |Client:stop()| now accepts a numerical `force` argument to be interpreted as the time to wait
|
• |Client:stop()| now accepts a numerical `force` argument to be interpreted as the time to wait
|
||||||
before forcing the shutdown.
|
before forcing the shutdown.
|
||||||
|
• Add cmp field to opts of |vim.lsp.completion.enable()| for custom completion ordering.
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ function M._lsp_to_complete_items(result, prefix, client_id)
|
|||||||
local candidates = {}
|
local candidates = {}
|
||||||
local bufnr = api.nvim_get_current_buf()
|
local bufnr = api.nvim_get_current_buf()
|
||||||
local user_convert = vim.tbl_get(buf_handles, bufnr, 'convert')
|
local user_convert = vim.tbl_get(buf_handles, bufnr, 'convert')
|
||||||
|
local user_cmp = vim.tbl_get(buf_handles, bufnr, 'cmp')
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
if matches(item) then
|
if matches(item) then
|
||||||
local word = get_completion_word(item, prefix, match_item_by_value)
|
local word = get_completion_word(item, prefix, match_item_by_value)
|
||||||
@@ -348,15 +349,16 @@ function M._lsp_to_complete_items(result, prefix, client_id)
|
|||||||
table.insert(candidates, completion_item)
|
table.insert(candidates, completion_item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
---@diagnostic disable-next-line: no-unknown
|
if not user_cmp then
|
||||||
table.sort(candidates, function(a, b)
|
---@diagnostic disable-next-line: no-unknown
|
||||||
---@type lsp.CompletionItem
|
table.sort(candidates, function(a, b)
|
||||||
local itema = a.user_data.nvim.lsp.completion_item
|
---@type lsp.CompletionItem
|
||||||
---@type lsp.CompletionItem
|
local itema = a.user_data.nvim.lsp.completion_item
|
||||||
local itemb = b.user_data.nvim.lsp.completion_item
|
---@type lsp.CompletionItem
|
||||||
return (itema.sortText or itema.label) < (itemb.sortText or itemb.label)
|
local itemb = b.user_data.nvim.lsp.completion_item
|
||||||
end)
|
return (itema.sortText or itema.label) < (itemb.sortText or itemb.label)
|
||||||
|
end)
|
||||||
|
end
|
||||||
return candidates
|
return candidates
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -551,6 +553,10 @@ local function trigger(bufnr, clients, ctx)
|
|||||||
end, prev_matches)
|
end, prev_matches)
|
||||||
|
|
||||||
matches = vim.list_extend(prev_matches, matches)
|
matches = vim.list_extend(prev_matches, matches)
|
||||||
|
local user_cmp = vim.tbl_get(buf_handles, bufnr, 'cmp')
|
||||||
|
if user_cmp then
|
||||||
|
table.sort(matches, user_cmp)
|
||||||
|
end
|
||||||
|
|
||||||
local start_col = (server_start_boundary or word_boundary) + 1
|
local start_col = (server_start_boundary or word_boundary) + 1
|
||||||
Context.cursor = { cursor_row, start_col }
|
Context.cursor = { cursor_row, start_col }
|
||||||
@@ -712,6 +718,7 @@ end
|
|||||||
--- @class vim.lsp.completion.BufferOpts
|
--- @class vim.lsp.completion.BufferOpts
|
||||||
--- @field autotrigger? boolean (default: false) When true, completion triggers automatically based on the server's `triggerCharacters`.
|
--- @field autotrigger? boolean (default: false) When true, completion triggers automatically based on the server's `triggerCharacters`.
|
||||||
--- @field convert? fun(item: lsp.CompletionItem): table Transforms an LSP CompletionItem to |complete-items|.
|
--- @field convert? fun(item: lsp.CompletionItem): table Transforms an LSP CompletionItem to |complete-items|.
|
||||||
|
--- @field cmp? fun(a: table, b: table): boolean Comparator for sorting merged completion items from all servers.
|
||||||
|
|
||||||
---@param client_id integer
|
---@param client_id integer
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
@@ -719,7 +726,7 @@ end
|
|||||||
local function enable_completions(client_id, bufnr, opts)
|
local function enable_completions(client_id, bufnr, opts)
|
||||||
local buf_handle = buf_handles[bufnr]
|
local buf_handle = buf_handles[bufnr]
|
||||||
if not buf_handle then
|
if not buf_handle then
|
||||||
buf_handle = { clients = {}, triggers = {}, convert = opts.convert }
|
buf_handle = { clients = {}, triggers = {}, convert = opts.convert, cmp = opts.cmp }
|
||||||
buf_handles[bufnr] = buf_handle
|
buf_handles[bufnr] = buf_handle
|
||||||
|
|
||||||
-- Attach to buffer events.
|
-- Attach to buffer events.
|
||||||
|
|||||||
@@ -810,7 +810,7 @@ end)
|
|||||||
|
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param completion_result lsp.CompletionList
|
--- @param completion_result lsp.CompletionList
|
||||||
--- @param opts? {trigger_chars?: string[], resolve_result?: lsp.CompletionItem, delay?: integer}
|
--- @param opts? {trigger_chars?: string[], resolve_result?: lsp.CompletionItem, delay?: integer, cmp?: string}
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function create_server(name, completion_result, opts)
|
local function create_server(name, completion_result, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
@@ -841,6 +841,10 @@ local function create_server(name, completion_result, opts)
|
|||||||
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
vim.api.nvim_win_set_buf(0, bufnr)
|
vim.api.nvim_win_set_buf(0, bufnr)
|
||||||
|
local cmp_fn
|
||||||
|
if opts.cmp then
|
||||||
|
cmp_fn = assert(loadstring(opts.cmp))
|
||||||
|
end
|
||||||
return vim.lsp.start({
|
return vim.lsp.start({
|
||||||
name = name,
|
name = name,
|
||||||
cmd = server.cmd,
|
cmd = server.cmd,
|
||||||
@@ -850,6 +854,7 @@ local function create_server(name, completion_result, opts)
|
|||||||
convert = function(item)
|
convert = function(item)
|
||||||
return { abbr = item.label:gsub('%b()', '') }
|
return { abbr = item.label:gsub('%b()', '') }
|
||||||
end,
|
end,
|
||||||
|
cmp = cmp_fn,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -1190,6 +1195,29 @@ describe('vim.lsp.completion: protocol', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('enable(…,{cmp=fn}) custom sort order', function()
|
||||||
|
create_server('dummy', {
|
||||||
|
isIncomplete = false,
|
||||||
|
items = {
|
||||||
|
{ label = 'zzz', sortText = 'a' },
|
||||||
|
{ label = 'aaa', sortText = 'z' },
|
||||||
|
{ label = 'mmm', sortText = 'm' },
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
cmp = string.dump(function(a, b)
|
||||||
|
return a.abbr < b.abbr
|
||||||
|
end),
|
||||||
|
})
|
||||||
|
feed('i')
|
||||||
|
trigger_at_pos({ 1, 0 })
|
||||||
|
assert_matches(function(matches)
|
||||||
|
eq(3, #matches)
|
||||||
|
eq('aaa', matches[1].abbr)
|
||||||
|
eq('mmm', matches[2].abbr)
|
||||||
|
eq('zzz', matches[3].abbr)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
it('sends completion context when invoked', function()
|
it('sends completion context when invoked', function()
|
||||||
local params = exec_lua(function()
|
local params = exec_lua(function()
|
||||||
local params
|
local params
|
||||||
|
|||||||
Reference in New Issue
Block a user