diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2024-11-25 19:15:05 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2024-11-25 19:27:38 +0000 |
commit | c5d770d311841ea5230426cc4c868e8db27300a8 (patch) | |
tree | dd21f70127b4b8b5f109baefc8ecc5016f507c91 /runtime/lua/vim/lsp/buf.lua | |
parent | 9be89f131f87608f224f0ee06d199fcd09d32176 (diff) | |
parent | 081beb3659bd6d8efc3e977a160b1e72becbd8a2 (diff) | |
download | rneovim-c5d770d311841ea5230426cc4c868e8db27300a8.tar.gz rneovim-c5d770d311841ea5230426cc4c868e8db27300a8.tar.bz2 rneovim-c5d770d311841ea5230426cc4c868e8db27300a8.zip |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'runtime/lua/vim/lsp/buf.lua')
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 702 |
1 files changed, 519 insertions, 183 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 301c1f0cb6..6383855a30 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -1,4 +1,5 @@ local api = vim.api +local lsp = vim.lsp local validate = vim.validate local util = require('vim.lsp.util') local npcall = vim.F.npcall @@ -6,28 +7,24 @@ local ms = require('vim.lsp.protocol').Methods local M = {} ---- Sends an async request to all active clients attached to the current ---- buffer. ---- ----@param method (string) LSP method name ----@param params (table|nil) Parameters to send to the server ----@param handler lsp.Handler? See |lsp-handler|. Follows |lsp-handler-resolution| ---- ----@return table<integer, integer> client_request_ids Map of client-id:request-id pairs ----for all successful requests. ----@return function _cancel_all_requests Function which can be used to ----cancel all the requests. You could instead ----iterate all clients and call their `cancel_request()` methods. ---- ----@see |vim.lsp.buf_request()| -local function request(method, params, handler) - validate({ - method = { method, 's' }, - handler = { handler, 'f', true }, - }) - return vim.lsp.buf_request(0, method, params, handler) +--- @param params? table +--- @return fun(client: vim.lsp.Client): lsp.TextDocumentPositionParams +local function client_positional_params(params) + local win = api.nvim_get_current_win() + return function(client) + local ret = util.make_position_params(win, client.offset_encoding) + if params then + ret = vim.tbl_extend('force', ret, params) + end + return ret + end end +local hover_ns = api.nvim_create_namespace('vim_lsp_hover_range') + +--- @class vim.lsp.buf.hover.Opts : vim.lsp.util.open_floating_preview.Opts +--- @field silent? boolean + --- Displays hover information about the symbol under the cursor in a floating --- window. The window will be dismissed on cursor move. --- Calling the function twice will jump into the floating window @@ -35,21 +32,210 @@ end --- In the floating window, all commands and mappings are available as usual, --- except that "q" dismisses the window. --- You can scroll the contents the same as you would any other buffer. -function M.hover() - local params = util.make_position_params() - request(ms.textDocument_hover, params) +--- +--- Note: to disable hover highlights, add the following to your config: +--- +--- ```lua +--- vim.api.nvim_create_autocmd('ColorScheme', { +--- callback = function() +--- vim.api.nvim_set_hl(0, 'LspReferenceTarget', {}) +--- end, +--- }) +--- ``` +--- @param config? vim.lsp.buf.hover.Opts +function M.hover(config) + config = config or {} + config.focus_id = ms.textDocument_hover + + lsp.buf_request_all(0, ms.textDocument_hover, client_positional_params(), function(results, ctx) + local bufnr = assert(ctx.bufnr) + if api.nvim_get_current_buf() ~= bufnr then + -- Ignore result since buffer changed. This happens for slow language servers. + return + end + + -- Filter errors from results + local results1 = {} --- @type table<integer,lsp.Hover> + + for client_id, resp in pairs(results) do + local err, result = resp.err, resp.result + if err then + lsp.log.error(err.code, err.message) + elseif result then + results1[client_id] = result + end + end + + if vim.tbl_isempty(results1) then + if config.silent ~= true then + vim.notify('No information available') + end + return + end + + local contents = {} --- @type string[] + + local nresults = #vim.tbl_keys(results1) + + local format = 'markdown' + + for client_id, result in pairs(results1) do + local client = assert(lsp.get_client_by_id(client_id)) + if nresults > 1 then + -- Show client name if there are multiple clients + contents[#contents + 1] = string.format('# %s', client.name) + end + if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then + if #results1 == 1 then + format = 'plaintext' + contents = vim.split(result.contents.value or '', '\n', { trimempty = true }) + else + -- Surround plaintext with ``` to get correct formatting + contents[#contents + 1] = '```' + vim.list_extend( + contents, + vim.split(result.contents.value or '', '\n', { trimempty = true }) + ) + contents[#contents + 1] = '```' + end + else + vim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents)) + end + local range = result.range + if range then + local start = range.start + local end_ = range['end'] + local start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding) + local end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding) + + vim.hl.range( + bufnr, + hover_ns, + 'LspReferenceTarget', + { start.line, start_idx }, + { end_.line, end_idx }, + { priority = vim.hl.priorities.user } + ) + end + contents[#contents + 1] = '---' + end + + -- Remove last linebreak ('---') + contents[#contents] = nil + + if vim.tbl_isempty(contents) then + if config.silent ~= true then + vim.notify('No information available') + end + return + end + + local _, winid = lsp.util.open_floating_preview(contents, format, config) + + api.nvim_create_autocmd('WinClosed', { + pattern = tostring(winid), + once = true, + callback = function() + api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1) + return true + end, + }) + end) end local function request_with_opts(name, params, opts) local req_handler --- @type function? if opts then req_handler = function(err, result, ctx, config) - local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) - local handler = client.handlers[name] or vim.lsp.handlers[name] + local client = assert(lsp.get_client_by_id(ctx.client_id)) + local handler = client.handlers[name] or lsp.handlers[name] handler(err, result, ctx, vim.tbl_extend('force', config or {}, opts)) end end - request(name, params, req_handler) + lsp.buf_request(0, name, params, req_handler) +end + +---@param method string +---@param opts? vim.lsp.LocationOpts +local function get_locations(method, opts) + opts = opts or {} + local bufnr = api.nvim_get_current_buf() + local clients = lsp.get_clients({ method = method, bufnr = bufnr }) + if not next(clients) then + vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN) + return + end + local win = api.nvim_get_current_win() + local from = vim.fn.getpos('.') + from[1] = bufnr + local tagname = vim.fn.expand('<cword>') + local remaining = #clients + + ---@type vim.quickfix.entry[] + local all_items = {} + + ---@param result nil|lsp.Location|lsp.Location[] + ---@param client vim.lsp.Client + local function on_response(_, result, client) + local locations = {} + if result then + locations = vim.islist(result) and result or { result } + end + local items = util.locations_to_items(locations, client.offset_encoding) + vim.list_extend(all_items, items) + remaining = remaining - 1 + if remaining == 0 then + if vim.tbl_isempty(all_items) then + vim.notify('No locations found', vim.log.levels.INFO) + return + end + + local title = 'LSP locations' + if opts.on_list then + assert(vim.is_callable(opts.on_list), 'on_list is not a function') + opts.on_list({ + title = title, + items = all_items, + context = { bufnr = bufnr, method = method }, + }) + return + end + + if #all_items == 1 then + local item = all_items[1] + local b = item.bufnr or vim.fn.bufadd(item.filename) + + -- Save position in jumplist + vim.cmd("normal! m'") + -- Push a new item into tagstack + local tagstack = { { tagname = tagname, from = from } } + vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't') + + vim.bo[b].buflisted = true + local w = opts.reuse_win and vim.fn.win_findbuf(b)[1] or win + api.nvim_win_set_buf(w, b) + api.nvim_win_set_cursor(w, { item.lnum, item.col - 1 }) + vim._with({ win = w }, function() + -- Open folds under the cursor + vim.cmd('normal! zv') + end) + return + end + if opts.loclist then + vim.fn.setloclist(0, {}, ' ', { title = title, items = all_items }) + vim.cmd.lopen() + else + vim.fn.setqflist({}, ' ', { title = title, items = all_items }) + vim.cmd('botright copen') + end + end + end + for _, client in ipairs(clients) do + local params = util.make_position_params(win, client.offset_encoding) + client.request(method, params, function(_, result) + on_response(_, result, client) + end) + end end --- @class vim.lsp.ListOpts @@ -89,39 +275,145 @@ end --- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead. --- @param opts? vim.lsp.LocationOpts function M.declaration(opts) - local params = util.make_position_params() - request_with_opts(ms.textDocument_declaration, params, opts) + get_locations(ms.textDocument_declaration, opts) end --- Jumps to the definition of the symbol under the cursor. --- @param opts? vim.lsp.LocationOpts function M.definition(opts) - local params = util.make_position_params() - request_with_opts(ms.textDocument_definition, params, opts) + get_locations(ms.textDocument_definition, opts) end --- Jumps to the definition of the type of the symbol under the cursor. --- @param opts? vim.lsp.LocationOpts function M.type_definition(opts) - local params = util.make_position_params() - request_with_opts(ms.textDocument_typeDefinition, params, opts) + get_locations(ms.textDocument_typeDefinition, opts) end --- Lists all the implementations for the symbol under the cursor in the --- quickfix window. --- @param opts? vim.lsp.LocationOpts function M.implementation(opts) - local params = util.make_position_params() - request_with_opts(ms.textDocument_implementation, params, opts) + get_locations(ms.textDocument_implementation, opts) end +--- @param results table<integer,{err: lsp.ResponseError?, result: lsp.SignatureHelp?}> +local function process_signature_help_results(results) + local signatures = {} --- @type [vim.lsp.Client,lsp.SignatureInformation][] + + -- Pre-process results + for client_id, r in pairs(results) do + local err = r.err + local client = assert(lsp.get_client_by_id(client_id)) + if err then + vim.notify( + client.name .. ': ' .. tostring(err.code) .. ': ' .. err.message, + vim.log.levels.ERROR + ) + api.nvim_command('redraw') + else + local result = r.result --- @type lsp.SignatureHelp + if result and result.signatures and result.signatures[1] then + for _, sig in ipairs(result.signatures) do + signatures[#signatures + 1] = { client, sig } + end + end + end + end + + return signatures +end + +local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help') + +--- @class vim.lsp.buf.signature_help.Opts : vim.lsp.util.open_floating_preview.Opts +--- @field silent? boolean + +-- TODO(lewis6991): support multiple clients --- Displays signature information about the symbol under the cursor in a --- floating window. -function M.signature_help() - local params = util.make_position_params() - request(ms.textDocument_signatureHelp, params) +--- @param config? vim.lsp.buf.signature_help.Opts +function M.signature_help(config) + local method = ms.textDocument_signatureHelp + + config = config and vim.deepcopy(config) or {} + config.focus_id = method + + lsp.buf_request_all(0, method, client_positional_params(), function(results, ctx) + if api.nvim_get_current_buf() ~= ctx.bufnr then + -- Ignore result since buffer changed. This happens for slow language servers. + return + end + + local signatures = process_signature_help_results(results) + + if not next(signatures) then + if config.silent ~= true then + print('No signature help available') + end + return + end + + local ft = vim.bo[ctx.bufnr].filetype + local total = #signatures + local idx = 0 + + --- @param update_win? integer + local function show_signature(update_win) + idx = (idx % total) + 1 + local client, result = signatures[idx][1], signatures[idx][2] + --- @type string[]? + local triggers = + vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters') + local lines, hl = + util.convert_signature_help_to_markdown_lines({ signatures = { result } }, ft, triggers) + if not lines then + return + end + + local sfx = total > 1 and string.format(' (%d/%d) (<C-s> to cycle)', idx, total) or '' + local title = string.format('Signature Help: %s%s', client.name, sfx) + if config.border then + config.title = title + else + table.insert(lines, 1, '# ' .. title) + if hl then + hl[1] = hl[1] + 1 + hl[3] = hl[3] + 1 + end + end + + config._update_win = update_win + + local buf, win = util.open_floating_preview(lines, 'markdown', config) + + if hl then + vim.api.nvim_buf_clear_namespace(buf, sig_help_ns, 0, -1) + vim.hl.range( + buf, + sig_help_ns, + 'LspSignatureActiveParameter', + { hl[1], hl[2] }, + { hl[3], hl[4] } + ) + end + return buf, win + end + + local fbuf, fwin = show_signature() + + if total > 1 then + vim.keymap.set('n', '<C-s>', function() + show_signature(fwin) + end, { + buffer = fbuf, + desc = 'Cycle next signature', + }) + end + end) end +--- @deprecated --- Retrieves the completion items at the current cursor position. Can only be --- called in Insert mode. --- @@ -131,9 +423,14 @@ end --- ---@see vim.lsp.protocol.CompletionTriggerKind function M.completion(context) - local params = util.make_position_params() - params.context = context - return request(ms.textDocument_completion, params) + vim.depends('vim.lsp.buf.completion', 'vim.lsp.commpletion.trigger', '0.12') + return lsp.buf_request( + 0, + ms.textDocument_completion, + client_positional_params({ + context = context, + }) + ) end ---@param bufnr integer @@ -240,7 +537,7 @@ function M.format(opts) method = ms.textDocument_formatting end - local clients = vim.lsp.get_clients({ + local clients = lsp.get_clients({ id = opts.id, bufnr = bufnr, name = opts.name, @@ -277,7 +574,7 @@ function M.format(opts) end local params = set_range(client, util.make_formatting_params(opts.formatting_options)) client.request(method, params, function(...) - local handler = client.handlers[method] or vim.lsp.handlers[method] + local handler = client.handlers[method] or lsp.handlers[method] handler(...) do_format(next(clients, idx)) end, bufnr) @@ -319,7 +616,7 @@ end function M.rename(new_name, opts) opts = opts or {} local bufnr = opts.bufnr or api.nvim_get_current_buf() - local clients = vim.lsp.get_clients({ + local clients = lsp.get_clients({ bufnr = bufnr, name = opts.name, -- Clients must at least support rename, prepareRename is optional @@ -338,6 +635,8 @@ function M.rename(new_name, opts) -- Compute early to account for cursor movements after going async local cword = vim.fn.expand('<cword>') + --- @param range lsp.Range + --- @param offset_encoding string local function get_text_at_range(range, offset_encoding) return api.nvim_buf_get_text( bufnr, @@ -359,7 +658,7 @@ function M.rename(new_name, opts) local params = util.make_position_params(win, client.offset_encoding) params.newName = name local handler = client.handlers[ms.textDocument_rename] - or vim.lsp.handlers[ms.textDocument_rename] + or lsp.handlers[ms.textDocument_rename] client.request(ms.textDocument_rename, params, function(...) handler(...) try_use_client(next(clients, idx)) @@ -437,12 +736,60 @@ end ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references ---@param opts? vim.lsp.ListOpts function M.references(context, opts) - validate({ context = { context, 't', true } }) - local params = util.make_position_params() - params.context = context or { - includeDeclaration = true, - } - request_with_opts(ms.textDocument_references, params, opts) + validate('context', context, 'table', true) + local bufnr = api.nvim_get_current_buf() + local clients = lsp.get_clients({ method = ms.textDocument_references, bufnr = bufnr }) + if not next(clients) then + return + end + local win = api.nvim_get_current_win() + opts = opts or {} + + local all_items = {} + local title = 'References' + + local function on_done() + if not next(all_items) then + vim.notify('No references found') + else + local list = { + title = title, + items = all_items, + context = { + method = ms.textDocument_references, + bufnr = bufnr, + }, + } + if opts.loclist then + vim.fn.setloclist(0, {}, ' ', list) + vim.cmd.lopen() + elseif opts.on_list then + assert(vim.is_callable(opts.on_list), 'on_list is not a function') + opts.on_list(list) + else + vim.fn.setqflist({}, ' ', list) + vim.cmd('botright copen') + end + end + end + + local remaining = #clients + for _, client in ipairs(clients) do + local params = util.make_position_params(win, client.offset_encoding) + + ---@diagnostic disable-next-line: inject-field + params.context = context or { + includeDeclaration = true, + } + client.request(ms.textDocument_references, params, function(_, result) + local items = util.locations_to_items(result or {}, client.offset_encoding) + vim.list_extend(all_items, items) + remaining = remaining - 1 + if remaining == 0 then + on_done() + end + end) + end end --- Lists all symbols in the current buffer in the quickfix window. @@ -452,65 +799,116 @@ function M.document_symbol(opts) request_with_opts(ms.textDocument_documentSymbol, params, opts) end ---- @param call_hierarchy_items lsp.CallHierarchyItem[] ---- @return lsp.CallHierarchyItem? -local function pick_call_hierarchy_item(call_hierarchy_items) - if #call_hierarchy_items == 1 then - return call_hierarchy_items[1] - end - local items = {} - for i, item in pairs(call_hierarchy_items) do - local entry = item.detail or item.name - table.insert(items, string.format('%d. %s', i, entry)) - end - local choice = vim.fn.inputlist(items) - if choice < 1 or choice > #items then +--- @param client_id integer +--- @param method string +--- @param params table +--- @param handler? lsp.Handler +--- @param bufnr? integer +local function request_with_id(client_id, method, params, handler, bufnr) + local client = lsp.get_client_by_id(client_id) + if not client then + vim.notify( + string.format('Client with id=%d disappeared during hierarchy request', client_id), + vim.log.levels.WARN + ) return end - return call_hierarchy_items[choice] + client.request(method, params, handler, bufnr) +end + +--- @param item lsp.TypeHierarchyItem|lsp.CallHierarchyItem +local function format_hierarchy_item(item) + if not item.detail or #item.detail == 0 then + return item.name + end + return string.format('%s %s', item.name, item.detail) end +local hierarchy_methods = { + [ms.typeHierarchy_subtypes] = 'type', + [ms.typeHierarchy_supertypes] = 'type', + [ms.callHierarchy_incomingCalls] = 'call', + [ms.callHierarchy_outgoingCalls] = 'call', +} + --- @param method string -local function call_hierarchy(method) - local params = util.make_position_params() - --- @param result lsp.CallHierarchyItem[]? - request(ms.textDocument_prepareCallHierarchy, params, function(err, result, ctx) - if err then - vim.notify(err.message, vim.log.levels.WARN) - return - end - if not result or vim.tbl_isempty(result) then +local function hierarchy(method) + local kind = hierarchy_methods[method] + if not kind then + error('unsupported method ' .. method) + end + + local prepare_method = kind == 'type' and ms.textDocument_prepareTypeHierarchy + or ms.textDocument_prepareCallHierarchy + + local bufnr = api.nvim_get_current_buf() + local clients = lsp.get_clients({ bufnr = bufnr, method = prepare_method }) + if not next(clients) then + vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN) + return + end + + local win = api.nvim_get_current_win() + + --- @param results [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][] + local function on_response(results) + if #results == 0 then vim.notify('No item resolved', vim.log.levels.WARN) - return - end - local call_hierarchy_item = pick_call_hierarchy_item(result) - if not call_hierarchy_item then - return - end - local client = vim.lsp.get_client_by_id(ctx.client_id) - if client then - client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr) + elseif #results == 1 then + local client_id, item = results[1][1], results[1][2] + request_with_id(client_id, method, { item = item }, nil, bufnr) else - vim.notify( - string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id), - vim.log.levels.WARN - ) + vim.ui.select(results, { + prompt = string.format('Select a %s hierarchy item:', kind), + kind = kind .. 'hierarchy', + format_item = function(x) + return format_hierarchy_item(x[2]) + end, + }, function(x) + if x then + local client_id, item = x[1], x[2] + request_with_id(client_id, method, { item = item }, nil, bufnr) + end + end) end - end) + end + + local results = {} --- @type [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][] + + local remaining = #clients + + for _, client in ipairs(clients) do + local params = util.make_position_params(win, client.offset_encoding) + --- @param result lsp.CallHierarchyItem[]|lsp.TypeHierarchyItem[]? + client.request(prepare_method, params, function(err, result, ctx) + if err then + vim.notify(err.message, vim.log.levels.WARN) + elseif result then + for _, item in ipairs(result) do + results[#results + 1] = { ctx.client_id, item } + end + end + + remaining = remaining - 1 + if remaining == 0 then + on_response(results) + end + end, bufnr) + end end --- Lists all the call sites of the symbol under the cursor in the --- |quickfix| window. If the symbol can resolve to multiple --- items, the user can pick one in the |inputlist()|. function M.incoming_calls() - call_hierarchy(ms.callHierarchy_incomingCalls) + hierarchy(ms.callHierarchy_incomingCalls) end --- Lists all the items that are called by the symbol under the --- cursor in the |quickfix| window. If the symbol can resolve to --- multiple items, the user can pick one in the |inputlist()|. function M.outgoing_calls() - call_hierarchy(ms.callHierarchy_outgoingCalls) + hierarchy(ms.callHierarchy_outgoingCalls) end --- Lists all the subtypes or supertypes of the symbol under the @@ -519,79 +917,14 @@ end ---@param kind "subtypes"|"supertypes" function M.typehierarchy(kind) local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes - - --- Merge results from multiple clients into a single table. Client-ID is preserved. - --- - --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}> - --- @return [integer, lsp.TypeHierarchyItem][] - local function merge_results(results) - local merged_results = {} - for client_id, client_result in pairs(results) do - if client_result.error then - vim.notify(client_result.error.message, vim.log.levels.WARN) - elseif client_result.result then - for _, item in pairs(client_result.result) do - table.insert(merged_results, { client_id, item }) - end - end - end - return merged_results - end - - local bufnr = api.nvim_get_current_buf() - local params = util.make_position_params() - --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}> - vim.lsp.buf_request_all(bufnr, ms.textDocument_prepareTypeHierarchy, params, function(results) - local merged_results = merge_results(results) - if #merged_results == 0 then - vim.notify('No items resolved', vim.log.levels.INFO) - return - end - - if #merged_results == 1 then - local item = merged_results[1] - local client = vim.lsp.get_client_by_id(item[1]) - if client then - client.request(method, { item = item[2] }, nil, bufnr) - else - vim.notify( - string.format('Client with id=%d disappeared during call hierarchy request', item[1]), - vim.log.levels.WARN - ) - end - else - local select_opts = { - prompt = 'Select a type hierarchy item:', - kind = 'typehierarchy', - format_item = function(item) - if not item[2].detail or #item[2].detail == 0 then - return item[2].name - end - return string.format('%s %s', item[2].name, item[2].detail) - end, - } - - vim.ui.select(merged_results, select_opts, function(item) - local client = vim.lsp.get_client_by_id(item[1]) - if client then - --- @type lsp.TypeHierarchyItem - client.request(method, { item = item[2] }, nil, bufnr) - else - vim.notify( - string.format('Client with id=%d disappeared during call hierarchy request', item[1]), - vim.log.levels.WARN - ) - end - end) - end - end) + hierarchy(method) end --- List workspace folders. --- function M.list_workspace_folders() local workspace_folders = {} - for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do + for _, client in pairs(lsp.get_clients({ bufnr = 0 })) do for _, folder in pairs(client.workspace_folders or {}) do table.insert(workspace_folders, folder.name) end @@ -614,7 +947,7 @@ function M.add_workspace_folder(workspace_folder) return end local bufnr = api.nvim_get_current_buf() - for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do + for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do client:_add_workspace_folder(workspace_folder) end end @@ -631,7 +964,7 @@ function M.remove_workspace_folder(workspace_folder) return end local bufnr = api.nvim_get_current_buf() - for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do + for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do client:_remove_workspace_folder(workspace_folder) end print(workspace_folder, 'is not currently part of the workspace') @@ -670,8 +1003,7 @@ end --- |hl-LspReferenceRead| --- |hl-LspReferenceWrite| function M.document_highlight() - local params = util.make_position_params() - request(ms.textDocument_documentHighlight, params) + lsp.buf_request(0, ms.textDocument_documentHighlight, client_positional_params()) end --- Removes document highlights from current buffer. @@ -773,7 +1105,8 @@ local function on_code_action_results(results, opts) local a_cmd = action.command if a_cmd then local command = type(a_cmd) == 'table' and a_cmd or action - client:_exec_cmd(command, ctx) + --- @cast command lsp.Command + client:exec_cmd(command, ctx) end end @@ -794,16 +1127,11 @@ local function on_code_action_results(results, opts) -- command: string -- arguments?: any[] -- - local client = assert(vim.lsp.get_client_by_id(choice.ctx.client_id)) + local client = assert(lsp.get_client_by_id(choice.ctx.client_id)) local action = choice.action local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number') - local reg = client.dynamic_capabilities:get(ms.textDocument_codeAction, { bufnr = bufnr }) - - local supports_resolve = vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider') - or client.supports_method(ms.codeAction_resolve) - - if not action.edit and client and supports_resolve then + if not action.edit and client.supports_method(ms.codeAction_resolve) then client.request(ms.codeAction_resolve, action, function(err, resolved_action) if err then if action.command then @@ -827,11 +1155,19 @@ local function on_code_action_results(results, opts) return end - ---@param item {action: lsp.Command|lsp.CodeAction} + ---@param item {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext} local function format_item(item) - local title = item.action.title:gsub('\r\n', '\\r\\n') - return title:gsub('\n', '\\n') + local clients = lsp.get_clients({ bufnr = item.ctx.bufnr }) + local title = item.action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n') + + if #clients == 1 then + return title + end + + local source = lsp.get_client_by_id(item.ctx.client_id).name + return ('%s [%s]'):format(title, source) end + local select_opts = { prompt = 'Code actions:', kind = 'codeaction', @@ -847,7 +1183,7 @@ end ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction ---@see vim.lsp.protocol.CodeActionTriggerKind function M.code_action(opts) - validate({ options = { opts, 't', true } }) + validate('options', opts, 'table', true) opts = opts or {} -- Detect old API call code_action(context) which should now be -- code_action({ context = context} ) @@ -857,16 +1193,16 @@ function M.code_action(opts) end local context = opts.context and vim.deepcopy(opts.context) or {} if not context.triggerKind then - context.triggerKind = vim.lsp.protocol.CodeActionTriggerKind.Invoked + context.triggerKind = lsp.protocol.CodeActionTriggerKind.Invoked end local mode = api.nvim_get_mode().mode local bufnr = api.nvim_get_current_buf() local win = api.nvim_get_current_win() - local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction }) + local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction }) local remaining = #clients if remaining == 0 then - if next(vim.lsp.get_clients({ bufnr = bufnr })) then - vim.notify(vim.lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN) + if next(lsp.get_clients({ bufnr = bufnr })) then + vim.notify(lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN) end return end @@ -903,8 +1239,8 @@ function M.code_action(opts) if context.diagnostics then params.context = context else - local ns_push = vim.lsp.diagnostic.get_namespace(client.id, false) - local ns_pull = vim.lsp.diagnostic.get_namespace(client.id, true) + local ns_push = lsp.diagnostic.get_namespace(client.id, false) + local ns_pull = lsp.diagnostic.get_namespace(client.id, true) local diagnostics = {} local lnum = api.nvim_win_get_cursor(0)[1] - 1 vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_pull, lnum = lnum })) @@ -921,20 +1257,20 @@ function M.code_action(opts) end end +--- @deprecated --- Executes an LSP server command. --- @param command_params lsp.ExecuteCommandParams --- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand function M.execute_command(command_params) - validate({ - command = { command_params.command, 's' }, - arguments = { command_params.arguments, 't', true }, - }) + validate('command', command_params.command, 'string') + validate('arguments', command_params.arguments, 'table', true) + vim.deprecate('execute_command', 'client:exec_cmd', '0.12') command_params = { command = command_params.command, arguments = command_params.arguments, workDoneToken = command_params.workDoneToken, } - request(ms.workspace_executeCommand, command_params) + lsp.buf_request(0, ms.workspace_executeCommand, command_params) end return M |