diff options
| author | Justin M. Keyes <justinkz@gmail.com> | 2024-10-02 06:57:18 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-02 06:57:18 -0700 |
| commit | e6569c18c21be5166e4b9cc7530e828b8285c84e (patch) | |
| tree | 72801068988c64be91dd543cddc71ab226766551 /lua/lspconfig/ui | |
| parent | ci: refactor ci output (diff) | |
| download | nvim-lspconfig-e6569c18c21be5166e4b9cc7530e828b8285c84e.tar nvim-lspconfig-e6569c18c21be5166e4b9cc7530e828b8285c84e.tar.gz nvim-lspconfig-e6569c18c21be5166e4b9cc7530e828b8285c84e.tar.bz2 nvim-lspconfig-e6569c18c21be5166e4b9cc7530e828b8285c84e.tar.lz nvim-lspconfig-e6569c18c21be5166e4b9cc7530e828b8285c84e.tar.xz nvim-lspconfig-e6569c18c21be5166e4b9cc7530e828b8285c84e.tar.zst nvim-lspconfig-e6569c18c21be5166e4b9cc7530e828b8285c84e.zip | |
feat(lspinfo): replace :LspInfo with :checkhealth #3339
Problem:
:LspInfo has its own "inner platlform" of highlights, mappings etc. And
it doesn't integrate with :checkhealth.
Solution:
- Move the lspinfo code to a healthcheck.
- LspInfo features such as highlights, "floating window" presentation,
etc., should be added to :checkhealth in Nvim core, if they are really
needed.
- Define a "q" mapping until Nvim stable has that in :checkhealth.
Diffstat (limited to 'lua/lspconfig/ui')
| -rw-r--r-- | lua/lspconfig/ui/lspinfo.lua | 375 | ||||
| -rw-r--r-- | lua/lspconfig/ui/windows.lua | 124 |
2 files changed, 3 insertions, 496 deletions
diff --git a/lua/lspconfig/ui/lspinfo.lua b/lua/lspconfig/ui/lspinfo.lua deleted file mode 100644 index ac8e6c2b..00000000 --- a/lua/lspconfig/ui/lspinfo.lua +++ /dev/null @@ -1,375 +0,0 @@ -local api, fn = vim.api, vim.fn -local windows = require 'lspconfig.ui.windows' -local util = require 'lspconfig.util' - -local error_messages = { - cmd_not_found = 'Unable to find executable. Please check your path and ensure the server is installed', - no_filetype_defined = 'No filetypes defined, Please define filetypes in setup()', - root_dir_not_found = 'Not found.', - async_root_dir_function = 'Asynchronous root_dir functions are not supported in :LspInfo', -} - -local helptags = { - [error_messages.no_filetype_defined] = { 'lspconfig-setup' }, - [error_messages.root_dir_not_found] = { 'lspconfig-root-detection' }, -} - -local function trim_blankspace(cmd) - local trimmed_cmd = {} - for _, str in ipairs(cmd) do - trimmed_cmd[#trimmed_cmd + 1] = str:match '^%s*(.*)' - end - return trimmed_cmd -end - -local function indent_lines(lines, offset) - return vim.tbl_map(function(val) - return offset .. val - end, lines) -end - -local function remove_newlines(cmd) - cmd = trim_blankspace(cmd) - cmd = table.concat(cmd, ' ') - cmd = vim.split(cmd, '\n') - cmd = trim_blankspace(cmd) - cmd = table.concat(cmd, ' ') - return cmd -end - -local cmd_type = { - ['function'] = function(_) - return '<function>', 'NA' - end, - ['table'] = function(config) - local cmd = remove_newlines(config.cmd) - if vim.fn.executable(config.cmd[1]) == 1 then - return cmd, 'true' - end - return cmd, error_messages.cmd_not_found - end, -} - -local function make_config_info(config, bufnr) - local config_info = {} - config_info.name = config.name - config_info.helptags = {} - - if config.cmd then - config_info.cmd, config_info.cmd_is_executable = cmd_type[type(config.cmd)](config) - else - config_info.cmd = 'cmd not defined' - config_info.cmd_is_executable = 'NA' - end - - local buffer_dir = api.nvim_buf_call(bufnr, function() - return vim.fn.expand '%:p:h' - end) - - if config.get_root_dir then - local root_dir - local co = coroutine.create(function() - local status, err = pcall(function() - root_dir = config.get_root_dir(buffer_dir) - end) - if not status then - vim.notify(('[lspconfig] unhandled error: %s'):format(tostring(err), vim.log.levels.WARN)) - end - end) - coroutine.resume(co) - if root_dir then - config_info.root_dir = root_dir - elseif coroutine.status(co) == 'suspended' then - config_info.root_dir = error_messages.async_root_dir_function - else - config_info.root_dir = error_messages.root_dir_not_found - end - else - config_info.root_dir = error_messages.root_dir_not_found - vim.list_extend(config_info.helptags, helptags[error_messages.root_dir_not_found]) - end - - config_info.autostart = (config.autostart and 'true') or 'false' - config_info.handlers = table.concat(vim.tbl_keys(config.handlers), ', ') - config_info.filetypes = table.concat(config.filetypes or {}, ', ') - - local lines = { - 'Config: ' .. config_info.name, - } - - local info_lines = { - 'filetypes: ' .. config_info.filetypes, - 'root directory: ' .. config_info.root_dir, - 'cmd: ' .. config_info.cmd, - 'cmd is executable: ' .. config_info.cmd_is_executable, - 'autostart: ' .. config_info.autostart, - 'custom handlers: ' .. config_info.handlers, - } - - if vim.tbl_count(config_info.helptags) > 0 then - local help = vim.tbl_map(function(helptag) - return string.format(':h %s', helptag) - end, config_info.helptags) - info_lines = vim.list_extend({ - 'Refer to ' .. table.concat(help, ', ') .. ' for help.', - }, info_lines) - end - - vim.list_extend(lines, indent_lines(info_lines, '\t')) - - return lines -end - ----@param client vim.lsp.Client ----@param fname string -local function make_client_info(client, fname) - local client_info = {} - - client_info.cmd = cmd_type[type(client.config.cmd)](client.config) - local workspace_folders = fn.has 'nvim-0.9' == 1 and client.workspace_folders or client.workspaceFolders - local uv = vim.uv - local is_windows = uv.os_uname().version:match 'Windows' - fname = uv.fs_realpath(fname) or fn.fnamemodify(fn.resolve(fname), ':p') - if is_windows then - fname:gsub('%/', '%\\') - end - - if workspace_folders then - for _, schema in ipairs(workspace_folders) do - local matched = true - local root_dir = uv.fs_realpath(schema.name) - if root_dir == nil or fname:sub(1, root_dir:len()) ~= root_dir then - matched = false - end - - if matched then - client_info.root_dir = schema.name - break - end - end - end - - if not client_info.root_dir then - client_info.root_dir = 'Running in single file mode.' - end - client_info.filetypes = table.concat(client.config.filetypes or {}, ', ') - client_info.autostart = (client.config.autostart and 'true') or 'false' - client_info.attached_buffers_list = table.concat(vim.lsp.get_buffers_by_client_id(client.id), ', ') - - local lines = { - '', - 'Client: ' - .. client.name - .. ' (id: ' - .. tostring(client.id) - .. ', bufnr: [' - .. client_info.attached_buffers_list - .. '])', - } - - local info_lines = { - 'filetypes: ' .. client_info.filetypes, - 'autostart: ' .. client_info.autostart, - 'root directory: ' .. client_info.root_dir, - 'cmd: ' .. client_info.cmd, - } - - if client.config.lspinfo then - local server_specific_info = client.config.lspinfo(client.config) - info_lines = vim.list_extend(info_lines, server_specific_info) - end - - vim.list_extend(lines, indent_lines(info_lines, '\t')) - - return lines -end - -return function() - -- These options need to be cached before switching to the floating - -- buffer. - local original_bufnr = api.nvim_get_current_buf() - local buf_clients = util.get_lsp_clients { bufnr = original_bufnr } - local clients = util.get_lsp_clients() - local buffer_filetype = vim.bo.filetype - local fname = api.nvim_buf_get_name(original_bufnr) - - windows.default_options.wrap = true - windows.default_options.breakindent = true - windows.default_options.breakindentopt = 'shift:25' - windows.default_options.showbreak = 'NONE' - - local win_info = windows.percentage_range_window(0.8, 0.7) - local bufnr, win_id = win_info.bufnr, win_info.win_id - vim.bo.bufhidden = 'wipe' - - local buf_lines = {} - - local buf_client_ids = {} - for _, client in ipairs(buf_clients) do - buf_client_ids[#buf_client_ids + 1] = client.id - end - - local other_active_clients = {} - for _, client in ipairs(clients) do - if not vim.tbl_contains(buf_client_ids, client.id) then - other_active_clients[#other_active_clients + 1] = client - end - end - - -- insert the tips at the top of window - buf_lines[#buf_lines + 1] = 'Press q or <Esc> to close this window. Press <Tab> to view server doc.' - - local header = { - '', - 'Language client log: ' .. (vim.lsp.get_log_path()), - 'Detected filetype: ' .. buffer_filetype, - } - vim.list_extend(buf_lines, header) - - local buffer_clients_header = { - '', - tostring(#vim.tbl_keys(buf_clients)) .. ' client(s) attached to this buffer: ', - } - - vim.list_extend(buf_lines, buffer_clients_header) - for _, client in ipairs(buf_clients) do - local client_info = make_client_info(client, fname) - vim.list_extend(buf_lines, client_info) - end - - local other_active_section_header = { - '', - tostring(#other_active_clients) .. ' active client(s) not attached to this buffer: ', - } - if not vim.tbl_isempty(other_active_clients) then - vim.list_extend(buf_lines, other_active_section_header) - end - for _, client in ipairs(other_active_clients) do - local client_info = make_client_info(client, fname) - vim.list_extend(buf_lines, client_info) - end - - local other_matching_configs_header = { - '', - 'Other clients that match the filetype: ' .. buffer_filetype, - '', - } - - local other_matching_configs = util.get_other_matching_providers(buffer_filetype) - - if not vim.tbl_isempty(other_matching_configs) then - vim.list_extend(buf_lines, other_matching_configs_header) - for _, config in ipairs(other_matching_configs) do - vim.list_extend(buf_lines, make_config_info(config, original_bufnr)) - end - end - - local matching_config_header = { - '', - 'Configured servers list: ' .. table.concat(util.available_servers(), ', '), - } - local deprecated_servers = {} - vim.list_extend(buf_lines, matching_config_header) - for server_name, deprecate in pairs(require('lspconfig').server_aliases()) do - table.insert(deprecated_servers, ('%s -> %s'):format(server_name, deprecate.to)) - end - if #deprecated_servers > 0 then - vim.list_extend(buf_lines, { '', 'Deprecated servers: ' .. table.concat(deprecated_servers, ' ') }) - end - - local fmt_buf_lines = indent_lines(buf_lines, ' ') - - api.nvim_buf_set_lines(bufnr, 0, -1, true, fmt_buf_lines) - vim.bo.modifiable = false - vim.bo.filetype = 'lspinfo' - - local augroup = api.nvim_create_augroup('lspinfo', { clear = false }) - - local function close() - api.nvim_clear_autocmds { group = augroup, buffer = bufnr } - if api.nvim_win_is_valid(win_id) then - api.nvim_win_close(win_id, true) - end - end - - vim.keymap.set('n', '<ESC>', close, { buffer = bufnr, nowait = true }) - vim.keymap.set('n', 'q', close, { buffer = bufnr, nowait = true }) - api.nvim_create_autocmd({ 'BufDelete', 'BufHidden' }, { - once = true, - buffer = bufnr, - callback = close, - group = augroup, - }) - - vim.fn.matchadd( - 'Error', - error_messages.no_filetype_defined - .. '.\\|' - .. 'cmd not defined\\|' - .. error_messages.cmd_not_found - .. '\\|' - .. error_messages.root_dir_not_found - ) - - vim.cmd [[ - syn keyword String true - syn keyword Error false - syn match LspInfoFiletypeList /\<filetypes\?:\s*\zs.*\ze/ contains=LspInfoFiletype - syn match LspInfoFiletype /\k\+/ contained - syn match LspInfoTitle /^\s*\%(Client\|Config\):\s*\zs\S\+\ze/ - syn match LspInfoListList /^\s*Configured servers list:\s*\zs.*\ze/ contains=LspInfoList - syn match LspInfoList /\S\+/ contained - ]] - - api.nvim_buf_add_highlight(bufnr, 0, 'LspInfoTip', 0, 0, -1) - - local function show_doc() - local lines = {} - local function append_lines(config) - if not config then - return - end - local desc = vim.tbl_get(config, 'config_def', 'docs', 'description') - if desc then - lines[#lines + 1] = string.format('# %s', config.name) - lines[#lines + 1] = '' - vim.list_extend(lines, vim.split(desc, '\n')) - lines[#lines + 1] = '' - end - end - - lines[#lines + 1] = 'Press <Tab> to close server info.' - lines[#lines + 1] = '' - - for _, client in ipairs(buf_clients) do - local config = require('lspconfig.configs')[client.name] - append_lines(config) - end - - for _, config in ipairs(other_matching_configs) do - append_lines(config) - end - - local info = windows.percentage_range_window(0.8, 0.7) - lines = indent_lines(lines, ' ') - api.nvim_buf_set_lines(info.bufnr, 0, -1, false, lines) - api.nvim_buf_add_highlight(info.bufnr, 0, 'LspInfoTip', 0, 0, -1) - - vim.bo[info.bufnr].filetype = 'markdown' - vim.bo[info.bufnr].syntax = 'on' - vim.wo[info.win_id].concealcursor = 'niv' - vim.wo[info.win_id].conceallevel = 2 - vim.wo[info.win_id].breakindent = false - vim.wo[info.win_id].breakindentopt = '' - - local function close_doc_win() - if api.nvim_win_is_valid(info.win_id) then - api.nvim_win_close(info.win_id, true) - end - end - - vim.keymap.set('n', '<TAB>', close_doc_win, { buffer = info.bufnr }) - end - - vim.keymap.set('n', '<TAB>', show_doc, { buffer = true, nowait = true }) -end diff --git a/lua/lspconfig/ui/windows.lua b/lua/lspconfig/ui/windows.lua index dc02c9ef..616d30cd 100644 --- a/lua/lspconfig/ui/windows.lua +++ b/lua/lspconfig/ui/windows.lua @@ -1,129 +1,11 @@ --- The following is extracted and modified from plenary.vnim by --- TJ Devries. It is not a stable API, and is expected to change --- -local api = vim.api - -local function apply_defaults(original, defaults) - if original == nil then - original = {} - end - - original = vim.deepcopy(original) - - for k, v in pairs(defaults) do - if original[k] == nil then - original[k] = v - end - end - - return original -end - local win_float = {} win_float.default_options = {} -function win_float.default_opts(options) - options = apply_defaults(options, { percentage = 0.9 }) - - local width = math.floor(vim.o.columns * options.percentage) - local height = math.floor(vim.o.lines * options.percentage) - - local top = math.floor(((vim.o.lines - height) / 2)) - local left = math.floor((vim.o.columns - width) / 2) - - local opts = { - relative = 'editor', - row = top, - col = left, - width = width, - height = height, - style = 'minimal', - border = { - { ' ', 'NormalFloat' }, - { ' ', 'NormalFloat' }, - { ' ', 'NormalFloat' }, - { ' ', 'NormalFloat' }, - { ' ', 'NormalFloat' }, - { ' ', 'NormalFloat' }, - { ' ', 'NormalFloat' }, - { ' ', 'NormalFloat' }, - }, - } - - opts.border = options.border and options.border - - return opts +function win_float.default_opts() + return {} end ---- Create window that takes up certain percentags of the current screen. ---- ---- Works regardless of current buffers, tabs, splits, etc. ---@param col_range number | Table: --- If number, then center the window taking up this percentage of the screen. --- If table, first index should be start, second_index should be end ---@param row_range number | Table: --- If number, then center the window taking up this percentage of the screen. --- If table, first index should be start, second_index should be end -function win_float.percentage_range_window(col_range, row_range, options) - options = apply_defaults(options, win_float.default_options) - - local win_opts = win_float.default_opts(options) - win_opts.relative = 'editor' - - local height_percentage, row_start_percentage - if type(row_range) == 'number' then - assert(row_range <= 1) - assert(row_range > 0) - height_percentage = row_range - row_start_percentage = (1 - height_percentage) / 3 - elseif type(row_range) == 'table' then - height_percentage = row_range[2] - row_range[1] - row_start_percentage = row_range[1] - else - error(string.format("Invalid type for 'row_range': %p", row_range)) - end - - win_opts.height = math.ceil(vim.o.lines * height_percentage) - win_opts.row = math.ceil(vim.o.lines * row_start_percentage) - win_opts.border = options.border or 'none' - - local width_percentage, col_start_percentage - if type(col_range) == 'number' then - assert(col_range <= 1) - assert(col_range > 0) - width_percentage = col_range - col_start_percentage = (1 - width_percentage) / 2 - elseif type(col_range) == 'table' then - width_percentage = col_range[2] - col_range[1] - col_start_percentage = col_range[1] - else - error(string.format("Invalid type for 'col_range': %p", col_range)) - end - - win_opts.col = math.floor(vim.o.columns * col_start_percentage) - win_opts.width = math.floor(vim.o.columns * width_percentage) - - local bufnr = options.bufnr or api.nvim_create_buf(false, true) - local win_id = api.nvim_open_win(bufnr, true, win_opts) - api.nvim_win_set_option(win_id, 'winhl', 'FloatBorder:LspInfoBorder') - - for k, v in pairs(win_float.default_options) do - if k ~= 'border' then - vim.opt_local[k] = v - end - end - - api.nvim_win_set_buf(win_id, bufnr) - - api.nvim_win_set_option(win_id, 'cursorcolumn', false) - api.nvim_buf_set_option(bufnr, 'tabstop', 2) - api.nvim_buf_set_option(bufnr, 'shiftwidth', 2) - - return { - bufnr = bufnr, - win_id = win_id, - } -end +function win_float.percentage_range_window() end return win_float |
