local core = require "fzf-lua.core" local utils = require "fzf-lua.utils" local config = require "fzf-lua.config" local make_entry = require "fzf-lua.make_entry" local M = {} local function location_to_entry(location, enc) local item = vim.lsp.util.locations_to_items({ location }, enc)[1] return ('%s:%d:%d:'):format(item.filename, item.lnum, item.col) end local jump_to_location = function(opts, result, enc) local winid = vim.api.nvim_get_current_win() if opts.winid ~= winid then -- utils.send_ctrl_c() vim.api.nvim_win_close(0, false) end local action = opts.jump_to_single_result_action if action then local entry = location_to_entry(result, enc) return opts.jump_to_single_result_action({ entry }, opts) end return vim.lsp.util.jump_to_location(result, enc) end local function location_handler(opts, cb, _, result, ctx, _) local encoding = vim.lsp.get_client_by_id(ctx.client_id).offset_encoding result = vim.tbl_islist(result) and result or {result} -- Jump immediately if there is only one location if opts.jump_to_single_result and #result == 1 then jump_to_location(opts, result[1], encoding) end local items = vim.lsp.util.locations_to_items(result, encoding) for _, entry in ipairs(items) do if not opts.current_buffer_only or vim.api.nvim_buf_get_name(opts.bufnr) == entry.filename then entry = make_entry.lcol(entry, opts) entry = make_entry.file(entry, opts) if entry then cb(entry, function(err) if err then return end end) end end end end local function call_hierarchy_handler(opts, cb, _, result, _, _) for _, call_hierarchy_call in pairs(result) do --- "from" for incoming calls and "to" for outgoing calls local call_hierarchy_item = call_hierarchy_call.from or call_hierarchy_call.to for _, range in pairs(call_hierarchy_call.fromRanges) do local entry = { filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)), text = call_hierarchy_item.name, lnum = range.start.line + 1, col = range.start.character + 1, } entry = make_entry.lcol(entry, opts) entry = make_entry.file(entry, opts) if entry then cb(entry, function(err) if err then return end end) end end end end local function symbol_handler(opts, cb, _, result, _, _) result = vim.tbl_islist(result) and result or {result} local items = vim.lsp.util.symbols_to_items(result, 0) for _, entry in ipairs(items) do if opts.ignore_filename then entry.filename = opts.filename end if (not opts.current_buffer_only or vim.api.nvim_buf_get_name(opts.bufnr) == entry.filename) and (not opts.regex_filter or entry.text:match(opts.regex_filter)) then if M._sym2style then local kind = entry.text:match("%[(.-)%]") if kind and M._sym2style[kind] then entry.text = entry.text:gsub("%[.-%]", M._sym2style[kind]) end end entry = make_entry.lcol(entry, opts) entry = make_entry.file(entry, opts) if entry then cb(entry, function(err) if err then return end end) end end end end local function code_action_handler(opts, cb, _, code_actions, context, _) if not opts.code_actions then opts.code_actions = {} end local i = utils.tbl_length(opts.code_actions) + 1 for _, action in ipairs(code_actions) do local text = string.format("%s %s", utils.ansi_codes.magenta(string.format("%d:", i)), action.title) -- local client = vim.lsp.get_client_by_id(context.client_id) local entry = { client_id = context.client_id, -- client - client, -- client_name = client and client.name or "", command = action, } opts.code_actions[tostring(i)] = entry cb(text, function(err) if err then return end end) i = i + 1 end end local function diagnostics_handler(opts, cb, co, entry) local type = entry.type entry = make_entry.lcol(entry, opts) entry = make_entry.file(entry, opts) if not entry then return end if opts.lsp_icons and opts._severity_icons[type] then local severity = opts._severity_icons[type] local icon = severity.icon if opts.color_icons then icon = utils.ansi_codes[severity.color or "dark_grey"](icon) end entry = icon .. utils.nbsp .. utils.nbsp .. entry end cb(entry, function() coroutine.resume(co) end) end -- see neovim #15504 -- https://github.com/neovim/neovim/pull/15504#discussion_r698424017 local mk_handler = function(fn) return function(...) local is_new = not select(4, ...) or type(select(4, ...)) ~= 'number' if is_new then -- function(err, result, context, config) fn(...) else -- function(err, method, params, client_id, bufnr, config) local err = select(1, ...) local method = select(2, ...) local result = select(3, ...) local client_id = select(4, ...) local bufnr = select(5, ...) local lspcfg = select(6, ...) fn(err, result, { method = method, client_id = client_id, bufnr = bufnr }, lspcfg) end end end local function wrap_handler(handler, opts, cb, co) return mk_handler(function(err, result, context, lspcfg) -- increment callback & result counters opts.num_callbacks = opts.num_callbacks+1 opts.num_results = opts.num_results or 0 + result and utils.tbl_length(result) or 0 local ret if err then ret = err utils.err(string.format("Error executing '%s': %s", handler.method, err)) utils.send_ctrl_c() elseif not result or vim.tbl_isempty(result) then -- Only close the window if all clients sent their results if opts.num_callbacks == opts.num_clients and opts.num_results == 0 then ret = utils.info(string.format('No %s found', string.lower(handler.label))) utils.send_ctrl_c() end else ret = opts.lsp_handler.handler(opts, cb, co, result, context, lspcfg) if opts.num_callbacks == opts.num_clients then -- close the pipe to fzf, this -- removes the loading indicator in fzf cb(nil) end end return ret end) end local function set_lsp_fzf_fn(opts) -- we must make the params here while we're on -- our current buffer window, anything inside -- fzf_fn is run while fzf term is open opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf() opts.winid = opts.winid or vim.api.nvim_get_current_win() opts.filename = vim.api.nvim_buf_get_name(opts.bufnr) if not opts.lsp_params then opts.lsp_params = vim.lsp.util.make_position_params(0) opts.lsp_params.context = { includeDeclaration = true } end -- Save no of attached clients so we can determine -- if all callbacks were completed opts.num_results = 0 opts.num_callbacks = 0 opts.num_clients = utils.tbl_length(vim.lsp.buf_get_clients(0)) -- consider 'async_or_timeout' only if -- 'sync|async' wasn't manually set if opts.sync == nil and opts.async == nil then if type(opts.async_or_timeout) == 'number' then opts.async = false elseif type(opts.async_or_timeout) == 'boolean' then opts.async = opts.async_or_timeout end end if opts.sync or opts.async == false then local timeout = 5000 if type(opts.async_or_timeout) == "number" then timeout = opts.async_or_timeout end local lsp_results, err = vim.lsp.buf_request_sync(opts.bufnr, opts.lsp_handler.method, opts.lsp_params, timeout) if err then utils.err(string.format("Error executing '%s': %s", opts.lsp_handler.method, err)) else local results = {} local cb = function(text) table.insert(results, text) end for client_id, response in pairs(lsp_results) do if response.result then local context = { client_id = client_id } opts.lsp_handler.handler(opts, cb, opts.lsp_handler.method, response.result, context) end end if vim.tbl_isempty(results) then utils.info(string.format('No %s found', string.lower(opts.lsp_handler.label))) elseif not (opts.jump_to_single_result and #results == 1) then opts.fzf_fn = results end end return opts end opts.fzf_fn = function (cb) coroutine.wrap(function () local co = coroutine.running() -- reset number of callbacks incase -- we're being called from 'resume' opts.num_callbacks = 0 -- cancel all currently running requests -- can happen when using `live_ws_symbols` if opts._cancel_all then opts._cancel_all() opts._cancel_all = nil end -- local cancel_all = vim.lsp.buf_request_all(opts.bufnr, -- opts.lsp_handler.method, opts.lsp_params, -- wrap_request_all(opts.lsp_handler)) local _, cancel_all = vim.lsp.buf_request(opts.bufnr, opts.lsp_handler.method, opts.lsp_params, wrap_handler(opts.lsp_handler, opts, cb, co)) -- save this so we can cancel all requests -- when using `live_ws_symbols` opts._cancel_all = cancel_all -- cancel all remaining LSP requests -- once the user made their selection -- or closed the fzf popup opts._fn_post_fzf = function() if opts._cancel_all then opts._cancel_all() opts._cancel_all = nil end end end)() end return opts end local set_async_default = function(opts, bool) if not opts then opts = {} end if opts.sync == nil and opts.async == nil then opts.async = bool end return opts end local normalize_lsp_opts = function(opts, cfg) opts = config.normalize_opts(opts, cfg) if not opts then return end if not opts.prompt and opts.prompt_postfix then opts.prompt = opts.lsp_handler.label .. (opts.prompt_postfix or '') end opts.bufnr = nil opts.winid = nil opts.filename = nil opts.code_actions = nil opts.num_results = nil opts.num_callbacks = nil opts.num_clients = nil return opts end local function fzf_lsp_locations(opts) opts = normalize_lsp_opts(opts, config.globals.lsp) if not opts then return end opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_fzf_field_index(opts) if opts.force_uri == nil then opts.force_uri = true end opts = set_lsp_fzf_fn(opts) if not opts.fzf_fn then return end return core.fzf_exec(opts.fzf_fn, opts) end -- define the functions for wrap_module_fncs M.references = function(opts) return fzf_lsp_locations(opts) end M.definitions = function(opts) return fzf_lsp_locations(opts) end M.declarations = function(opts) return fzf_lsp_locations(opts) end M.typedefs = function(opts) return fzf_lsp_locations(opts) end M.implementations = function(opts) return fzf_lsp_locations(opts) end -- see $VIMRUNTIME/lua/vim/buf.lua:pick_call_hierarchy_item() M.call_hierarchy = function(opts) opts.lsp_params = vim.lsp.util.make_position_params(0) local method = "textDocument/prepareCallHierarchy" local res, err = vim.lsp.buf_request_sync( 0, method, opts.lsp_params, 2000) if err then utils.err(("Error executing '%s': %s"):format(method, err)) else local _, response = next(res) if vim.tbl_isempty(response) then utils.info(('No %s found'):format(opts.lsp_handler.label:lower())) return end assert(response.result and response.result[1]) local call_hierarchy_item = response.result[1] opts.lsp_params = { item = call_hierarchy_item } return fzf_lsp_locations(opts) end end M.incoming_calls = function(opts) return M.call_hierarchy(opts) end M.outgoing_calls = function(opts) return M.call_hierarchy(opts) end local function gen_sym2style_map(opts) assert(M._sym2style == nil) assert(opts.symbol_style ~= nil) M._sym2style = {} local colormap = vim.api.nvim_get_color_map() for k, v in pairs(vim.lsp.protocol.CompletionItemKind) do if type(k) == 'string' then local icon = vim.lsp.protocol.CompletionItemKind[v] -- style==1: " " -- style==2: "" -- style==3: "" local s = nil if opts.symbol_style == 1 then -- if icons weren't set by the user -- icon will match the kind if icon ~= k then s = ("%s %s"):format(icon, k) else s = k end elseif opts.symbol_style == 2 then s = icon elseif opts.symbol_style == 3 then s = k end if s and opts.symbol_hl_prefix then M._sym2style[k] = utils.ansi_from_hl(opts.symbol_hl_prefix .. k, s, colormap) elseif s then M._sym2style[k] = s else -- can get here when only 'opts.symbol_fmt' was set M._sym2style[k] = k end end end if type(opts.symbol_fmt) == 'function' then for k, v in pairs(M._sym2style) do M._sym2style[k] = opts.symbol_fmt(v) or v end end end M.document_symbols = function(opts) opts = set_async_default(opts, true) opts = normalize_lsp_opts(opts, config.globals.lsp) if not opts then return end opts = core.set_header(opts, opts.headers or {"cwd","regex_filter"}) opts = core.set_fzf_field_index(opts) if opts.force_uri == nil then opts.force_uri = true end if not opts.fzf_opts or opts.fzf_opts['--with-nth'] == nil then opts.fzf_opts = opts.fzf_opts or {} opts.fzf_opts["--with-nth"] = '2..' opts.fzf_opts["--tiebreak"] = 'index' end opts.ignore_filename = true opts = set_lsp_fzf_fn(opts) if not opts.fzf_fn then return end if opts.symbol_style or opts.symbol_fmt then opts.fn_pre_fzf = function() gen_sym2style_map(opts) end opts.fn_post_fzf = function() M._sym2style = nil end end return core.fzf_exec(opts.fzf_fn, opts) end M.workspace_symbols = function(opts) opts = set_async_default(opts, true) opts = normalize_lsp_opts(opts, config.globals.lsp) if not opts then return end opts.lsp_params = {query = opts.lsp_query or ''} opts = core.set_header(opts, opts.headers or {"cwd","query","regex_filter"}) opts = core.set_fzf_field_index(opts) if opts.force_uri == nil then opts.force_uri = true end opts = set_lsp_fzf_fn(opts) if not opts.fzf_fn then return end if opts.symbol_style or opts.symbol_fmt then opts.fn_pre_fzf = function() gen_sym2style_map(opts) end opts.fn_post_fzf = function() M._sym2style = nil end end return core.fzf_exec(opts.fzf_fn, opts) end -- Converts 'vim.diagnostic.get' to legacy style 'get_line_diagnostics()' local function get_line_diagnostics(opts) if not vim.diagnostic then return vim.lsp.diagnostic.get_line_diagnostics() end local diag = vim.diagnostic.get(opts.bufnr, {lnum = vim.api.nvim_win_get_cursor(0)[1]-1}) return diag and diag[1] and {{ source = diag[1].source, message = diag[1].message, severity = diag[1].severity, code = diag[1].user_data and diag[1].user_data.lsp and diag[1].user_data.lsp.code, codeDescription = diag[1].user_data and diag[1].user_data.lsp and diag[1].user_data.lsp.codeDescription, range = { ["start"] = { line = diag[1].lnum, character = diag[1].col, }, ["end"] = { line = diag[1].end_lnum, character = diag[1].end_col, } } }} or nil end ---Returns true if the client has code_action capability and its -- resolveProvider is true. -- @param client vim.lsp.buf.client -- @return boolean local function code_action_resolves(client) local code_action if vim.fn.has("nvim-0.8") == 1 then code_action = client.server_capabilities.codeActionProvider else code_action = client.resolved_capabilities.code_action end return type(code_action) == "table" and code_action.resolveProvider end M.code_actions = function(opts) opts = normalize_lsp_opts(opts, config.globals.lsp) if not opts then return end -- irrelevant for code actions and can cause -- single results to be skipped with 'async = false' opts.jump_to_single_result = false opts.lsp_params = vim.lsp.util.make_range_params(0) opts.lsp_params.context = { diagnostics = get_line_diagnostics(opts) } -- we use `vim.ui.select` for neovim > 0.6 -- so make sure 'set_lsp_fzf_fn' is run synchronously if vim.fn.has('nvim-0.6') == 1 then opts.sync, opts.async = true, false end -- when 'opts.sync == true' calls 'vim.lsp.buf_request_sync' -- so we can avoid calling 'ui_select.register' when no code -- actions are available opts = set_lsp_fzf_fn(opts) -- error or no sync request no results if not opts.fzf_fn then return end -- use `vim.ui.select` for neovim > 0.6 -- the original method is now deprecated if opts.ui_select and vim.fn.has('nvim-0.6') == 1 then local ui_select = require'fzf-lua.providers.ui_select' opts.previewer = false opts.actions = opts.actions or {} opts.actions.default = nil opts.post_action_cb = function() ui_select.deregister({}, true, true) end ui_select.register(opts, true) vim.lsp.buf.code_action() -- vim.defer_fn(function() -- ui_select.deregister({}, true, true) -- end, 100) return end -- see discussion in: -- https://github.com/nvim-telescope/telescope.nvim/pull/738 -- If the text document version is 0, set it to nil instead so that Neovim -- won't refuse to update a buffer that it believes is newer than edits. -- See: https://github.com/eclipse/eclipse.jdt.ls/issues/1695 -- Source: -- https://github.com/neovim/nvim-lspconfig/blob/486f72a25ea2ee7f81648fdfd8999a155049e466/lua/lspconfig/jdtls.lua#L62 local function fix_zero_version(workspace_edit) if workspace_edit and workspace_edit.documentChanges then for _, change in pairs(workspace_edit.documentChanges) do local text_document = change.textDocument if text_document and text_document.version and text_document.version == 0 then text_document.version = nil end end end return workspace_edit end local transform_action = opts.transform_action or function(action) -- Remove 0 -version from LSP codeaction request payload. -- Is only run on the "java.apply.workspaceEdit" codeaction. -- Fixed Java/jdtls compatibility with Telescope -- See fix_zero_version commentary for more information local command = (action.command and action.command.command) or action.command if not (command == "java.apply.workspaceEdit") then return action end local arguments = (action.command and action.command.arguments) or action.arguments action.edit = fix_zero_version(arguments[1]) return action end local execute_action = opts.execute_action or function(action, enc) if action.edit or type(action.command) == "table" then if action.edit then vim.lsp.util.apply_workspace_edit(action.edit, enc) end if type(action.command) == "table" then vim.lsp.buf.execute_command(action.command) end else vim.lsp.buf.execute_command(action) end end -- "apply action" as default function if not opts.actions then opts.actions = {} end opts.actions.default = (function(selected) local idx = selected[1]:match("(%d+)") local entry = opts.code_actions[idx] local action = entry.command local client = entry.client or vim.lsp.get_client_by_id(entry.client_id) local offset_encoding = client and client.offset_encoding if not action.edit and client and code_action_resolves(client) then local request = "codeAction/resolve" client.request(request, action, function(resolved_err, resolved_action) if resolved_err then utils.err(("Error %d executing '%s': %s") :format(resolved_err.code, request, resolved_err.message)) return end if resolved_action then execute_action(transform_action(resolved_action), offset_encoding) else execute_action(transform_action(action), offset_encoding) end end) else execute_action(transform_action(action), offset_encoding) end end) opts.previewer = false opts.fzf_opts["--no-multi"] = '' opts.fzf_opts["--preview-window"] = 'right:0' core.fzf_exec(opts.fzf_fn, opts) end local convert_diagnostic_type = function(severity) -- convert from string to int if type(severity) == "string" and not tonumber(severity) then -- make sure that e.g. error is uppercased to Error return vim.diagnostic and vim.diagnostic.severity[severity:upper()] or vim.lsp.protocol.DiagnosticSeverity[severity:gsub("^%l", string.upper)] else -- otherwise keep original value, incl. nil return tonumber(severity) end end local filter_diag_severity = function(opts, severity) if opts.severity_only ~= nil then return tonumber(opts.severity_only) == severity elseif opts.severity_limit ~= nil then return severity <= tonumber(opts.severity_limit) elseif opts.severity_bound ~= nil then return severity >= tonumber(opts.severity_bound) else return true end end M.diagnostics = function(opts) opts = normalize_lsp_opts(opts, config.globals.lsp) if not opts then return end if not vim.diagnostic then local lsp_clients = vim.lsp.buf_get_clients(0) if utils.tbl_isempty(lsp_clients) then utils.info("LSP: no client attached") return end end opts.winid = vim.api.nvim_get_current_win() local current_buf = vim.api.nvim_get_current_buf() -- normalize the LSP icons table opts._severity_icons = {} for k, v in pairs({ ["Error"] = 1, ["Warning"] = 2, ["Information"] = 3, ["Hint"] = 4 }) do if opts.icons and opts.icons[k] then opts._severity_icons[v] = opts.icons[k] end end -- hint = 4 -- information = 3 -- warning = 2 -- error = 1 -- severity_only: keep any matching exact severity -- severity_limit: keep any equal or more severe (lower) -- severity_bound: keep any equal or less severe (higher) opts.severity_only = convert_diagnostic_type(opts.severity_only) opts.severity_limit = convert_diagnostic_type(opts.severity_limit) opts.severity_bound = convert_diagnostic_type(opts.severity_bound) local diag_opts = { severity = {}, namespace = opts.namespace } if opts.severity_only ~= nil then if opts.severity_limit ~= nil or opts.severity_bound ~= nil then utils.warn("Invalid severity parameters. Both a specific severity and a limit/bound is not allowed") return {} end diag_opts.severity = opts.severity_only else if opts.severity_limit ~= nil then diag_opts.severity["min"] = opts.severity_limit end if opts.severity_bound ~= nil then diag_opts.severity["max"] = opts.severity_bound end end local diag_results = vim.diagnostic and vim.diagnostic.get(not opts.diag_all and current_buf or nil, diag_opts) or opts.diag_all and vim.lsp.diagnostic.get_all() or {[current_buf] = vim.lsp.diagnostic.get(current_buf, opts.client_id)} local has_diags = false if vim.diagnostic then -- format: { } has_diags = not vim.tbl_isempty(diag_results) else -- format: { [bufnr] = , ... } for _, diags in pairs(diag_results) do if #diags > 0 then has_diags = true end end end if not has_diags then utils.info(string.format('No %s found', string.lower(opts.lsp_handler.label))) return end local preprocess_diag = function(diag, bufnr) bufnr = bufnr or diag.bufnr local filename = vim.api.nvim_buf_get_name(bufnr) -- pre vim.diagnostic (vim.lsp.diagnostic) -- has 'start|finish' instead of 'end_col|end_lnum' local start = diag.range and diag.range['start'] -- local finish = diag.range and diag.range['end'] local row = diag.lnum or start.line local col = diag.col or start.character local buffer_diag = { bufnr = bufnr, filename = filename, lnum = row + 1, col = col + 1, text = vim.trim(diag.message:gsub("[\n]", "")), type = diag.severity or 1 } return buffer_diag end opts.fzf_fn = function (fzf_cb) coroutine.wrap(function () local co = coroutine.running() local function process_diagnostics(diags, bufnr) for _, diag in ipairs(diags) do -- workspace diagnostics may include -- empty tables for unused buffers if not vim.tbl_isempty(diag) then if filter_diag_severity(opts, diag.severity) then -- wrap with 'vim.scheudle' or calls to vim.{fn|api} fail: -- E5560: vimL function must not be called in a lua loop callback vim.schedule(function() diagnostics_handler(opts, fzf_cb, co, preprocess_diag(diag, bufnr)) end) -- wait here for 'diagnostics_handler' to return coroutine.yield() end end end end if vim.diagnostic then process_diagnostics(diag_results) else for bufnr, diags in pairs(diag_results) do process_diagnostics(diags, bufnr) end end -- close the pipe to fzf, this -- removes the loading indicator fzf_cb(nil) end)() end opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_fzf_field_index(opts) if opts.force_uri == nil then opts.force_uri = true end return core.fzf_exec(opts.fzf_fn, opts) end M.workspace_diagnostics = function(opts) if not opts then opts = {} end opts.diag_all = true return M.diagnostics(opts) end local last_search = {} M.live_workspace_symbols = function(opts) opts = normalize_lsp_opts(opts, config.globals.lsp) if not opts then return end -- exec empty query is the default here if opts.exec_empty_query == nil then opts.exec_empty_query = true end if not opts.query and opts.resume then opts.query = last_search.query end -- sent to the LSP server opts.lsp_params = {query = opts.query or ''} -- must get those here, otherwise we get the -- fzf terminal buffer and window IDs opts.bufnr = vim.api.nvim_get_current_buf() opts.winid = vim.api.nvim_get_current_win() opts.fn_reload = function(query) if query and not (opts.save_last_query == false) then last_search = { query = query } config.__resume_data.last_query = query end opts.sync = true opts.async = false opts.lsp_params = {query = query or ''} opts = set_lsp_fzf_fn(opts) return opts.fzf_fn end opts = core.set_fzf_field_index(opts) if opts.force_uri == nil then opts.force_uri = true end if opts.symbol_style or opts.symbol_fmt then opts.fn_pre_fzf = function() gen_sym2style_map(opts) end opts.fn_post_fzf = function() M._sym2style = nil end end core.fzf_exec(nil, opts) end local function check_capabilities(feature) local clients = vim.lsp.buf_get_clients(0) for _, client in pairs(clients) do if vim.fn.has("nvim-0.8") == 1 then if client.server_capabilities[feature] then return true end else if client.resolved_capabilities[feature] then return true end end end if utils.tbl_isempty(clients) then utils.info("LSP: no client attached") else utils.info("LSP: server does not support " .. feature) end return false end local handlers = { ["code_actions"] = { label = "Code Actions", resolved_capability = "code_action", server_capability = "codeActionProvider", method = "textDocument/codeAction", handler = code_action_handler }, ["references"] = { label = "References", resolved_capability = "find_references", server_capability = "referencesProvider", method = "textDocument/references", handler = location_handler }, ["definitions"] = { label = "Definitions", resolved_capability = "goto_definition", server_capability = "definitionProvider", method = "textDocument/definition", handler = location_handler }, ["declarations"] = { label = "Declarations", resolved_capability = "goto_declaration", server_capability = "declarationProvider", method = "textDocument/declaration", handler = location_handler }, ["typedefs"] = { label = "Type Definitions", resolved_capability = "type_definition", server_capability = "typeDefinitionProvider", method = "textDocument/typeDefinition", handler = location_handler }, ["implementations"] = { label = "Implementations", resolved_capability = "implementation", server_capability = "implementationProvider", method = "textDocument/implementation", handler = location_handler }, ["document_symbols"] = { label = "Document Symbols", resolved_capability = "document_symbol", server_capability = "documentSymbolProvider", method = "textDocument/documentSymbol", handler = symbol_handler }, ["workspace_symbols"] = { label = "Workspace Symbols", resolved_capability = "workspace_symbol", server_capability = "workspaceSymbolProvider", method = "workspace/symbol", handler = symbol_handler }, ["live_workspace_symbols"] = { label = "Workspace Symbols", resolved_capability = "workspace_symbol", server_capability = "workspaceSymbolProvider", method = "workspace/symbol", handler = symbol_handler }, ["diagnostics"] = { label = "Diagnostics", resolved_capability = nil, server_capability = nil, method = nil, handler = diagnostics_handler }, ["workspace_diagnostics"] = { label = "Workspace Diagnostics", resolved_capability = nil, server_capability = nil, method = nil, handler = diagnostics_handler }, ["incoming_calls"] = { label = "Incoming Calls", resolved_capability = "call_hierarchy", server_capability = "callHierarchyProvider", method = "callHierarchy/incomingCalls", handler = call_hierarchy_handler }, ["outgoing_calls"] = { label = "Outgoing Calls", resolved_capability = "call_hierarchy", server_capability = "callHierarchyProvider", method = "callHierarchy/outgoingCalls", handler = call_hierarchy_handler }, } local function wrap_module_fncs(mod) for k, v in pairs(mod) do mod[k] = function(opts) opts = opts or {} if not opts.lsp_handler then opts.lsp_handler = handlers[k] end if not opts.lsp_handler then utils.err(string.format("No LSP handler defined for %s", k)) return end -- We only need to set this once. if opts.lsp_handler and not opts.lsp_handler.capability then if vim.fn.has("nvim-0.8") == 1 then opts.lsp_handler.capability = opts.lsp_handler.server_capability else opts.lsp_handler.capability = opts.lsp_handler.resolved_capability end end if opts.lsp_handler.capability and not check_capabilities(opts.lsp_handler.capability) then return end v(opts) end end return mod end return wrap_module_fncs(M)