local cfg = require('outline.config') local jsx = require('outline.providers.jsx') local lsp_utils = require('outline.utils.lsp') local utils = require('outline.utils') local l = vim.lsp local M = { name = 'lsp', } local request_timeout = 2000 ---@param info table? Must be the table received from `supports_buffer` function M.get_status(info) if not info then return { 'No clients' } end return { 'client: ' .. info.client.name } end ---@param client vim.lsp.Client ---@param capability string ---@return boolean local function _check_client(client, capability) if cfg.is_client_blacklisted(client) then return false end return client.server_capabilities[capability] end ---@param bufnr integer ---@param capability string ---@return vim.lsp.Client? local function get_appropriate_client(bufnr, capability) local clients, use_client if _G._outline_nvim_has[8] then if _G._outline_nvim_has[10] then clients = l.get_clients({ bufnr = bufnr }) else ---@diagnostic disable-next-line: deprecated clients = l.get_active_clients({ bufnr = bufnr }) end for _, client in ipairs(clients) do if _check_client(client, capability) then use_client = client break end end else -- Returns client_id:client pairs ---@diagnostic disable-next-line clients = l.buf_get_clients(bufnr) for _, client in pairs(clients) do if _check_client(client, capability) then use_client = client break end end end return use_client end ---@return boolean, table? function M.supports_buffer(bufnr) local client = get_appropriate_client(bufnr, 'documentSymbolProvider') if not client then return false end return true, { client = client } end ---Include JSX symbols if applicable, and merge it with existing symbols ---@param symbols outline.ProviderSymbol[] ---@return outline.ProviderSymbol[] local function postprocess_symbols(symbols) local jsx_symbols = jsx.get_symbols() if #jsx_symbols > 0 then return lsp_utils.merge_symbols(symbols, jsx_symbols) else return symbols end end -- XXX: Only one LSP client is supported here, to prevent checking blacklisting -- over again ---@param on_symbols fun(symbols?:outline.ProviderSymbol[], opts?:table) ---@param opts table? ---@param info table? Must be the table received from `supports_buffer` function M.request_symbols(on_symbols, opts, info) if not info then return on_symbols(nil, opts) end local params = { textDocument = l.util.make_text_document_params(), } -- XXX: Is bufnr=0 ok here? local method = 'textDocument/documentSymbol' local callback = function(err, response) if err or not response then on_symbols(response, opts) else response = postprocess_symbols(response) on_symbols(response, opts) end end local bufnr = 0 local status if _G._outline_nvim_has[11] then status = info.client:request(method, params, callback, bufnr) else status = info.client.request(method, params, callback, bufnr) end if not status then on_symbols(nil, opts) end end -- No good way to update outline when LSP action complete for now ---@param sidebar outline.Sidebar ---@return boolean success function M.code_actions(sidebar) local client = get_appropriate_client(sidebar.code.buf, 'codeActionProvider') if not client then return false end -- NOTE: Unfortunately the code_action function provided by neovim does a -- lot, yet it doesn't let us filter clients. Since handling of code_actions -- is beyond the scope of outline.nvim itself, we will not respect -- blacklist_clients for code actions for now. Code actions feature would not -- actually be included if I were to write this plugin from scratch. However -- we still keep it because many people are moving here from -- symbols-outline.nvim, which happened to implement this feature. sidebar:wrap_goto_location(function() l.buf.code_action() end) return true end ---@see rename_symbol ---@param sidebar outline.Sidebar ---@param client vim.lsp.Client ---@param node outline.FlatSymbol ---@return boolean success local function legacy_rename(sidebar, client, node) -- Using fn.input so it's synchronous local new_name = vim.fn.input({ prompt = 'New Name: ', default = node.name }) if not new_name or new_name == '' or new_name == node.name then return true end local params = { textDocument = { uri = 'file://' .. vim.api.nvim_buf_get_name(sidebar.code.buf) }, position = { line = node.line, character = node.character }, bufnr = sidebar.code.buf, newName = new_name, } local status, err if _G._outline_nvim_has[11] then status, err = client:request_sync('textDocument/rename', params, request_timeout, sidebar.code.buf) else ---@diagnostic disable-next-line status, err = client.request_sync('textDocument/rename', params, request_timeout, sidebar.code.buf) end if status == nil or status.err or err or status.result == nil then return false end l.util.apply_workspace_edit(status.result, client.offset_encoding) node.name = new_name sidebar:_update_lines(false) return true end ---Synchronously request rename from LSP ---@param sidebar outline.Sidebar ---@return boolean success function M.rename_symbol(sidebar) local client = get_appropriate_client(sidebar.code.buf, 'renameProvider') if not client then return false end local node = sidebar:_current_node() if not node then return false end if _G._outline_nvim_has[8] then sidebar:wrap_goto_location(function() -- Options table with filter key only added in nvim-0.8 -- Use vim.lsp's function because it has better support. l.buf.rename(nil, { filter = function(cl) return not cfg.is_client_blacklisted(cl) end, }) end) return true else return legacy_rename(sidebar, client, node) end end ---Synchronously request and show hover info from LSP ---@param sidebar outline.Sidebar ---@return boolean success function M.show_hover(sidebar) local client = get_appropriate_client(sidebar.code.buf, 'hoverProvider') if not client then return false end local node = sidebar:_current_node() if not node then return false end local params = { textDocument = { uri = vim.uri_from_bufnr(sidebar.code.buf) }, position = { line = node.line, character = node.character }, bufnr = sidebar.code.buf, } local status, err if _G._outline_nvim_has[11] then status, err = client:request_sync('textDocument/hover', params, request_timeout) else status, err = client.request_sync('textDocument/hover', params, request_timeout) end if status == nil or status.err or err or not status.result or not status.result.contents then return false end local md_lines = l.util.convert_input_to_markdown_lines(status.result.contents) if _G._outline_nvim_has[10] then md_lines = vim.split(status.result.contents, '\n', { trimempty = true }) else ---@diagnostic disable-next-line:deprecated md_lines = l.util.trim_empty_lines(md_lines) end if vim.tbl_isempty(md_lines) then -- Request was successful, but there is no hover content return true end local code_width = vim.api.nvim_win_get_width(sidebar.code.win) local bufnr, winnr = l.util.open_floating_preview(md_lines, 'markdown', { border = cfg.o.preview_window.border, width = code_width, }) utils.win_set_option(winnr, 'winhighlight', cfg.o.preview_window.winhl) return true end return M