mirror of
https://github.com/neovim/neovim.git
synced 2026-04-25 08:44:06 +00:00
feat(lsp): do completionItem/resolve if completeopt=popup #32820
Problem: No completionItem/resolve handler. Solution: If completeopt=popup is set, invoke completionItem/resolve when a completion item is selected. Show resolved documentation in popup next to the completion menu.
This commit is contained in:
@@ -67,6 +67,8 @@ local Context = {
|
|||||||
last_request_time = nil, --- @type integer?
|
last_request_time = nil, --- @type integer?
|
||||||
pending_requests = {}, --- @type function[]
|
pending_requests = {}, --- @type function[]
|
||||||
isIncomplete = false,
|
isIncomplete = false,
|
||||||
|
-- Handles "completionItem/resolve".
|
||||||
|
resolve_handler = nil, --- @type CompletionResolver?
|
||||||
}
|
}
|
||||||
|
|
||||||
--- @nodoc
|
--- @nodoc
|
||||||
@@ -84,6 +86,10 @@ function Context:reset()
|
|||||||
self.isIncomplete = false
|
self.isIncomplete = false
|
||||||
self.last_request_time = nil
|
self.last_request_time = nil
|
||||||
self:cancel_pending()
|
self:cancel_pending()
|
||||||
|
if self.resolve_handler then
|
||||||
|
self.resolve_handler:cleanup()
|
||||||
|
self.resolve_handler = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @type uv.uv_timer_t?
|
--- @type uv.uv_timer_t?
|
||||||
@@ -105,7 +111,7 @@ end
|
|||||||
|
|
||||||
--- @param window integer
|
--- @param window integer
|
||||||
--- @param warmup integer
|
--- @param warmup integer
|
||||||
--- @return fun(sample: number): number
|
--- @return fun(sample: integer): integer
|
||||||
local function exp_avg(window, warmup)
|
local function exp_avg(window, warmup)
|
||||||
local count = 0
|
local count = 0
|
||||||
local sum = 0
|
local sum = 0
|
||||||
@@ -125,14 +131,23 @@ local function exp_avg(window, warmup)
|
|||||||
end
|
end
|
||||||
local compute_new_average = exp_avg(10, 10)
|
local compute_new_average = exp_avg(10, 10)
|
||||||
|
|
||||||
--- @return number
|
--- Calculates the adaptive debounce time based on the elapsed time since the last request.
|
||||||
local function next_debounce()
|
---
|
||||||
if not Context.last_request_time then
|
--- @param last_request_time integer?
|
||||||
return rtt_ms
|
--- @param current_rtt_ms number
|
||||||
|
--- @return integer
|
||||||
|
local function adaptive_debounce(last_request_time, current_rtt_ms)
|
||||||
|
if not last_request_time then
|
||||||
|
return current_rtt_ms
|
||||||
end
|
end
|
||||||
|
local ms_since_request = (vim.uv.hrtime() - last_request_time) * ns_to_ms
|
||||||
|
return math.max((ms_since_request - current_rtt_ms) * -1, 0)
|
||||||
|
end
|
||||||
|
|
||||||
local ms_since_request = (vim.uv.hrtime() - Context.last_request_time) * ns_to_ms
|
--- @param flag string
|
||||||
return math.max((ms_since_request - rtt_ms) * -1, 0)
|
--- @return boolean
|
||||||
|
local function has_completeopt(flag)
|
||||||
|
return vim.list_contains(vim.opt.completeopt:get(), flag)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param input string Unparsed snippet
|
--- @param input string Unparsed snippet
|
||||||
@@ -244,7 +259,7 @@ end
|
|||||||
---@return string
|
---@return string
|
||||||
local function get_doc(item)
|
local function get_doc(item)
|
||||||
if
|
if
|
||||||
vim.o.completeopt:find('popup')
|
has_completeopt('popup')
|
||||||
and item.insertTextFormat == protocol.InsertTextFormat.Snippet
|
and item.insertTextFormat == protocol.InsertTextFormat.Snippet
|
||||||
and #(item.documentation or '') == 0
|
and #(item.documentation or '') == 0
|
||||||
and vim.bo.filetype ~= ''
|
and vim.bo.filetype ~= ''
|
||||||
@@ -278,7 +293,7 @@ local function match_item_by_value(value, prefix)
|
|||||||
if prefix == '' then
|
if prefix == '' then
|
||||||
return true, nil
|
return true, nil
|
||||||
end
|
end
|
||||||
if vim.o.completeopt:find('fuzzy') ~= nil then
|
if has_completeopt('fuzzy') then
|
||||||
local score = vim.fn.matchfuzzypos({ value }, prefix)[3] ---@type table
|
local score = vim.fn.matchfuzzypos({ value }, prefix)[3] ---@type table
|
||||||
return #score > 0, score[1]
|
return #score > 0, score[1]
|
||||||
end
|
end
|
||||||
@@ -454,8 +469,8 @@ function M._lsp_to_complete_items(
|
|||||||
return (itema.sortText or itema.label) < (itemb.sortText or itemb.label)
|
return (itema.sortText or itema.label) < (itemb.sortText or itemb.label)
|
||||||
end
|
end
|
||||||
|
|
||||||
local use_fuzzy_sort = vim.o.completeopt:find('fuzzy') ~= nil
|
local use_fuzzy_sort = has_completeopt('fuzzy')
|
||||||
and vim.o.completeopt:find('nosort') == nil
|
and not has_completeopt('nosort')
|
||||||
and not result.isIncomplete
|
and not result.isIncomplete
|
||||||
and #prefix > 0
|
and #prefix > 0
|
||||||
|
|
||||||
@@ -594,6 +609,195 @@ local function request(clients, bufnr, win, ctx, callback)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
---@return string
|
||||||
|
local function get_augroup(bufnr)
|
||||||
|
return string.format('nvim.lsp.completion_%d', bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Updates the completion preview popup: configures conceal level, applies Treesitter or
|
||||||
|
--- fallback syntax, and resizes height to fit content
|
||||||
|
---
|
||||||
|
--- @param winid integer
|
||||||
|
--- @param bufnr integer
|
||||||
|
--- @param kind? string
|
||||||
|
local function update_popup_window(winid, bufnr, kind)
|
||||||
|
if winid and api.nvim_win_is_valid(winid) and bufnr and api.nvim_buf_is_valid(bufnr) then
|
||||||
|
if kind == lsp.protocol.MarkupKind.Markdown then
|
||||||
|
vim.wo[winid].conceallevel = 2
|
||||||
|
vim.treesitter.start(bufnr, kind)
|
||||||
|
end
|
||||||
|
local all = api.nvim_win_text_height(winid, {}).all
|
||||||
|
api.nvim_win_set_height(winid, all)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Handles the LSP "completionItem/resolve"
|
||||||
|
---
|
||||||
|
--- @nodoc
|
||||||
|
--- @class CompletionResolver
|
||||||
|
--- @field timer uv.uv_timer_t? Timer used for debouncing
|
||||||
|
--- @field bufnr integer? Buffer number for which the resolution is triggered
|
||||||
|
--- @field word string? Word being completed
|
||||||
|
--- @field last_request_time integer? Last request timestamp
|
||||||
|
--- @field doc_rtt_ms integer Last request timestamp
|
||||||
|
--- @field doc_compute_new_average fun(sample: integer): integer Last request timestamp
|
||||||
|
local CompletionResolver = {}
|
||||||
|
CompletionResolver.__index = CompletionResolver
|
||||||
|
|
||||||
|
--- @nodoc
|
||||||
|
---
|
||||||
|
--- Creates a new instance of `resolve_completion_item`.
|
||||||
|
---
|
||||||
|
--- @return CompletionResolver
|
||||||
|
function CompletionResolver.new()
|
||||||
|
local self = setmetatable({}, CompletionResolver)
|
||||||
|
self.timer = nil
|
||||||
|
self.bufnr = nil
|
||||||
|
self.word = nil
|
||||||
|
self.last_request_time = nil
|
||||||
|
self.doc_rtt_ms = 100
|
||||||
|
self.doc_compute_new_average = exp_avg(10, 5)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @nodoc
|
||||||
|
---
|
||||||
|
--- Cancels any pending requests.
|
||||||
|
function CompletionResolver:cancel_pending_requests()
|
||||||
|
if self.bufnr then
|
||||||
|
lsp.util._cancel_requests({
|
||||||
|
method = 'completionItem/resolve',
|
||||||
|
bufnr = self.bufnr,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @nodoc
|
||||||
|
---
|
||||||
|
--- Cleans up the timer and cancels any ongoing requests.
|
||||||
|
function CompletionResolver:cleanup()
|
||||||
|
if self.timer and not self.timer:is_closing() then
|
||||||
|
self.timer:stop()
|
||||||
|
self.timer:close()
|
||||||
|
end
|
||||||
|
self.timer = nil
|
||||||
|
self:cancel_pending_requests()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @nodoc
|
||||||
|
---
|
||||||
|
--- Checks if the completionItem/resolve request is valid by ensuring the buffer
|
||||||
|
--- and word are valid.
|
||||||
|
---
|
||||||
|
--- @return boolean, table Validity of the request and the completion info
|
||||||
|
function CompletionResolver:is_valid()
|
||||||
|
local cmp_info = vim.fn.complete_info({ 'selected', 'completed' })
|
||||||
|
return vim.api.nvim_buf_is_valid(self.bufnr)
|
||||||
|
and vim.api.nvim_get_current_buf() == self.bufnr
|
||||||
|
and vim.startswith(vim.api.nvim_get_mode().mode, 'i')
|
||||||
|
and tonumber(vim.fn.pumvisible()) == 1
|
||||||
|
and (vim.tbl_get(cmp_info, 'completed', 'word') or '') == self.word,
|
||||||
|
cmp_info
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @nodoc
|
||||||
|
---
|
||||||
|
--- Invokes and handles "completionItem/resolve".
|
||||||
|
---
|
||||||
|
--- @param bufnr integer The buffer number where the request is triggered
|
||||||
|
--- @param param table The parameters for the LSP request
|
||||||
|
--- @param selected_word string The word being completed
|
||||||
|
function CompletionResolver:request(bufnr, param, selected_word)
|
||||||
|
self:cleanup()
|
||||||
|
|
||||||
|
self.bufnr = bufnr
|
||||||
|
self.word = selected_word
|
||||||
|
local debounce_time = adaptive_debounce(self.last_request_time, self.doc_rtt_ms)
|
||||||
|
|
||||||
|
self.timer = vim.defer_fn(function()
|
||||||
|
local valid, cmp_info = self:is_valid()
|
||||||
|
if not valid then
|
||||||
|
self:cleanup()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self:cancel_pending_requests()
|
||||||
|
|
||||||
|
local client_id = vim.tbl_get(cmp_info.completed, 'user_data', 'nvim', 'lsp', 'client_id')
|
||||||
|
local client = client_id and vim.lsp.get_client_by_id(client_id)
|
||||||
|
if not client or not client:supports_method('completionItem/resolve') then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local start_time = vim.uv.hrtime()
|
||||||
|
self.last_request_time = start_time
|
||||||
|
|
||||||
|
client:request('completionItem/resolve', param, function(err, result)
|
||||||
|
local end_time = vim.uv.hrtime()
|
||||||
|
local response_time = (end_time - start_time) * ns_to_ms
|
||||||
|
self.doc_rtt_ms = self.doc_compute_new_average(response_time)
|
||||||
|
|
||||||
|
if err or not result or next(result) == nil then
|
||||||
|
if err then
|
||||||
|
vim.notify(err.message, vim.log.levels.WARN)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
valid, cmp_info = self:is_valid()
|
||||||
|
if not valid then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local value = vim.tbl_get(result, 'documentation', 'value')
|
||||||
|
if not value then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local windata = vim.api.nvim__complete_set(cmp_info.selected, {
|
||||||
|
info = value,
|
||||||
|
})
|
||||||
|
local kind = vim.tbl_get(result, 'documentation', 'kind')
|
||||||
|
update_popup_window(windata.winid, windata.bufnr, kind)
|
||||||
|
end, bufnr)
|
||||||
|
end, debounce_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Defines a CompleteChanged handler to request and display LSP completion item documentation
|
||||||
|
--- via completionItem/resolve
|
||||||
|
local function on_completechanged(group, bufnr)
|
||||||
|
api.nvim_create_autocmd('CompleteChanged', {
|
||||||
|
group = group,
|
||||||
|
buffer = bufnr,
|
||||||
|
callback = function(args)
|
||||||
|
local completed_item = vim.v.event.completed_item or {}
|
||||||
|
if (completed_item.info or '') ~= '' then
|
||||||
|
local data = vim.fn.complete_info({ 'selected' })
|
||||||
|
update_popup_window(data.preview_winid, data.preview_bufnr)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
#lsp.get_clients({
|
||||||
|
id = vim.tbl_get(completed_item, 'user_data', 'nvim', 'lsp', 'client_id'),
|
||||||
|
method = 'completionItem/resolve',
|
||||||
|
bufnr = args.buf,
|
||||||
|
}) == 0
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Retrieve the raw LSP completionItem from completed_item as the parameter for
|
||||||
|
-- the completionItem/resolve request
|
||||||
|
local param = vim.tbl_get(completed_item, 'user_data', 'nvim', 'lsp', 'completion_item')
|
||||||
|
if param then
|
||||||
|
Context.resolve_handler = Context.resolve_handler or CompletionResolver.new()
|
||||||
|
Context.resolve_handler:request(args.buf, param, completed_item.word)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
desc = 'Request and display LSP completion item documentation via completionItem/resolve',
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
--- @param bufnr integer
|
--- @param bufnr integer
|
||||||
--- @param clients vim.lsp.Client[]
|
--- @param clients vim.lsp.Client[]
|
||||||
--- @param ctx? lsp.CompletionContext
|
--- @param ctx? lsp.CompletionContext
|
||||||
@@ -683,6 +887,14 @@ local function trigger(bufnr, clients, ctx)
|
|||||||
|
|
||||||
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 }
|
||||||
|
if #matches > 0 and has_completeopt('popup') then
|
||||||
|
local group = get_augroup(bufnr)
|
||||||
|
if
|
||||||
|
#api.nvim_get_autocmds({ buffer = bufnr, event = 'CompleteChanged', group = group }) == 0
|
||||||
|
then
|
||||||
|
on_completechanged(group, bufnr)
|
||||||
|
end
|
||||||
|
end
|
||||||
vim.fn.complete(start_col, matches)
|
vim.fn.complete(start_col, matches)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -695,7 +907,7 @@ local function on_insert_char_pre(handle)
|
|||||||
if Context.isIncomplete then
|
if Context.isIncomplete then
|
||||||
reset_timer()
|
reset_timer()
|
||||||
|
|
||||||
local debounce_ms = next_debounce()
|
local debounce_ms = adaptive_debounce(Context.last_request_time, rtt_ms)
|
||||||
local ctx = { triggerKind = protocol.CompletionTriggerKind.TriggerForIncompleteCompletions }
|
local ctx = { triggerKind = protocol.CompletionTriggerKind.TriggerForIncompleteCompletions }
|
||||||
if debounce_ms == 0 then
|
if debounce_ms == 0 then
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
@@ -716,7 +928,7 @@ local function on_insert_char_pre(handle)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local char = api.nvim_get_vvar('char')
|
local char = vim.v.char
|
||||||
local matched_clients = handle.triggers[char]
|
local matched_clients = handle.triggers[char]
|
||||||
-- Discard pending trigger char, complete the "latest" one.
|
-- Discard pending trigger char, complete the "latest" one.
|
||||||
-- Can happen if a mapping inputs multiple trigger chars simultaneously.
|
-- Can happen if a mapping inputs multiple trigger chars simultaneously.
|
||||||
@@ -726,11 +938,10 @@ local function on_insert_char_pre(handle)
|
|||||||
completion_timer:start(25, 0, function()
|
completion_timer:start(25, 0, function()
|
||||||
reset_timer()
|
reset_timer()
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
trigger(
|
trigger(api.nvim_get_current_buf(), matched_clients, {
|
||||||
api.nvim_get_current_buf(),
|
triggerKind = protocol.CompletionTriggerKind.TriggerCharacter,
|
||||||
matched_clients,
|
triggerCharacter = char,
|
||||||
{ triggerKind = protocol.CompletionTriggerKind.TriggerCharacter, triggerCharacter = char }
|
})
|
||||||
)
|
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -831,12 +1042,6 @@ local function on_complete_done()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
|
||||||
---@return string
|
|
||||||
local function get_augroup(bufnr)
|
|
||||||
return string.format('nvim.lsp.completion_%d', bufnr)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param client_id integer
|
--- @param client_id integer
|
||||||
--- @param bufnr integer
|
--- @param bufnr integer
|
||||||
local function disable_completions(client_id, bufnr)
|
local function disable_completions(client_id, bufnr)
|
||||||
@@ -904,6 +1109,7 @@ local function enable_completions(client_id, bufnr, opts)
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
if opts.autotrigger then
|
if opts.autotrigger then
|
||||||
api.nvim_create_autocmd('InsertCharPre', {
|
api.nvim_create_autocmd('InsertCharPre', {
|
||||||
group = group,
|
group = group,
|
||||||
|
|||||||
@@ -482,6 +482,7 @@ function protocol.make_client_capabilities()
|
|||||||
properties = {
|
properties = {
|
||||||
'additionalTextEdits',
|
'additionalTextEdits',
|
||||||
'command',
|
'command',
|
||||||
|
'documentation',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tagSupport = {
|
tagSupport = {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ local neq = t.neq
|
|||||||
local exec_lua = n.exec_lua
|
local exec_lua = n.exec_lua
|
||||||
local feed = n.feed
|
local feed = n.feed
|
||||||
local retry = t.retry
|
local retry = t.retry
|
||||||
|
local Screen = require('test.functional.ui.screen')
|
||||||
|
|
||||||
local create_server_definition = t_lsp.create_server_definition
|
local create_server_definition = t_lsp.create_server_definition
|
||||||
|
|
||||||
@@ -1000,6 +1001,9 @@ describe('vim.lsp.completion: protocol', function()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
exec_lua(function()
|
||||||
|
vim.o.completeopt = 'menuone,noselect'
|
||||||
|
end)
|
||||||
local client_id = create_server('dummy', completion_list)
|
local client_id = create_server('dummy', completion_list)
|
||||||
|
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
@@ -1038,6 +1042,9 @@ describe('vim.lsp.completion: protocol', function()
|
|||||||
isIncomplete = false,
|
isIncomplete = false,
|
||||||
items = { { label = 'hello' } },
|
items = { { label = 'hello' } },
|
||||||
}
|
}
|
||||||
|
exec_lua(function()
|
||||||
|
vim.o.completeopt = 'menuone,noselect'
|
||||||
|
end)
|
||||||
local client_id = create_server('dummy', completion_list, {
|
local client_id = create_server('dummy', completion_list, {
|
||||||
resolve_result = {
|
resolve_result = {
|
||||||
label = 'hello',
|
label = 'hello',
|
||||||
@@ -1354,6 +1361,66 @@ describe('vim.lsp.completion: integration', function()
|
|||||||
feed('<C-y>')
|
feed('<C-y>')
|
||||||
eq('w-1/2', n.api.nvim_get_current_line())
|
eq('w-1/2', n.api.nvim_get_current_line())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('completionItem/resolve', function()
|
||||||
|
local screen = Screen.new(50, 20)
|
||||||
|
screen:add_extra_attr_ids({
|
||||||
|
[100] = { background = Screen.colors.Plum1, foreground = Screen.colors.Blue },
|
||||||
|
})
|
||||||
|
local completion_list = {
|
||||||
|
isIncomplete = false,
|
||||||
|
items = {
|
||||||
|
{
|
||||||
|
insertText = 'nvim__id_array',
|
||||||
|
insertTextFormat = 1,
|
||||||
|
kind = 3,
|
||||||
|
label = 'nvim__id_array(arr)',
|
||||||
|
sortText = '0002',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exec_lua(function()
|
||||||
|
vim.o.completeopt = 'menuone,popup'
|
||||||
|
end)
|
||||||
|
create_server('dummy', completion_list, {
|
||||||
|
resolve_result = {
|
||||||
|
detail = 'function',
|
||||||
|
documentation = {
|
||||||
|
kind = 'markdown',
|
||||||
|
value = [[```lua\nfunction vim.api.nvim__id_array(arr: any[])\n -> any[]\n```]],
|
||||||
|
},
|
||||||
|
insertText = 'nvim__id_array',
|
||||||
|
insertTextFormat = 1,
|
||||||
|
kind = 3,
|
||||||
|
label = 'nvim__id_array(arr)',
|
||||||
|
sortText = '0002',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
feed('Sapi.<C-X><C-O>')
|
||||||
|
retry(nil, nil, function()
|
||||||
|
eq(
|
||||||
|
{ true, true, [[```lua\nfunction vim.api.nvim__id_array(arr: any[])\n -> any[]\n```]] },
|
||||||
|
exec_lua(function()
|
||||||
|
local data = vim.fn.complete_info({ 'selected' })
|
||||||
|
return {
|
||||||
|
vim.api.nvim_win_is_valid(data.preview_winid),
|
||||||
|
vim.api.nvim_buf_is_valid(data.preview_bufnr),
|
||||||
|
vim.api.nvim_buf_get_lines(data.preview_bufnr, 0, -1, false)[1],
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
screen:expect([[
|
||||||
|
api.nvim__id_array^ |
|
||||||
|
{1:~ }{12: nvim__id_array Function }{100:lua\nfunction vim.}{4: }{1: }|
|
||||||
|
{1:~ }{100:api.nvim__id_array(ar}{1: }|
|
||||||
|
{1:~ }{100:r: any[])\n -> any[]}{1: }|
|
||||||
|
{1:~ }{100:\n}{4: }{1: }|
|
||||||
|
{1:~ }|*14
|
||||||
|
{5:-- INSERT --} |
|
||||||
|
]])
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("vim.lsp.completion: omnifunc + 'autocomplete'", function()
|
describe("vim.lsp.completion: omnifunc + 'autocomplete'", function()
|
||||||
|
|||||||
Reference in New Issue
Block a user