diff options
| author | William Boman <william@redwill.se> | 2022-05-13 16:17:06 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-05-13 16:17:06 +0200 |
| commit | 09aea5d129c761a95f4bfaf80ec99eac2ab88983 (patch) | |
| tree | b9121fce8c3fe064fa2be8adb2165a6777d956f2 /lua | |
| parent | run autogen_metadata.lua (diff) | |
| download | mason-09aea5d129c761a95f4bfaf80ec99eac2ab88983.tar mason-09aea5d129c761a95f4bfaf80ec99eac2ab88983.tar.gz mason-09aea5d129c761a95f4bfaf80ec99eac2ab88983.tar.bz2 mason-09aea5d129c761a95f4bfaf80ec99eac2ab88983.tar.lz mason-09aea5d129c761a95f4bfaf80ec99eac2ab88983.tar.xz mason-09aea5d129c761a95f4bfaf80ec99eac2ab88983.tar.zst mason-09aea5d129c761a95f4bfaf80ec99eac2ab88983.zip | |
feat(ui): use diagnostics to show some messages (#694)
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/nvim-lsp-installer/core/ui/display.lua | 342 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/core/ui/init.lua | 13 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/ui/init.lua | 50 |
3 files changed, 224 insertions, 181 deletions
diff --git a/lua/nvim-lsp-installer/core/ui/display.lua b/lua/nvim-lsp-installer/core/ui/display.lua index cf99f30c..4e7e9692 100644 --- a/lua/nvim-lsp-installer/core/ui/display.lua +++ b/lua/nvim-lsp-installer/core/ui/display.lua @@ -4,18 +4,6 @@ local state = require "nvim-lsp-installer.core.ui.state" local M = {} -local function from_hex(str) - return (str:gsub("..", function(cc) - return string.char(tonumber(cc, 16)) - end)) -end - -local function to_hex(str) - return (str:gsub(".", function(c) - return string.format("%02X", string.byte(c)) - end)) -end - ---@param line string ---@param render_context RenderContext local function get_styles(line, render_context) @@ -64,17 +52,23 @@ local function render_node(viewport_context, node, _render_context, _output) ---@field effect string ---@field payload any + ---@class RenderDiagnostic + ---@field line number + ---@field diagnostic {message: string, severity: integer} + ---@class RenderOutput ---@field lines string[] @The buffer lines. ---@field virt_texts string[][] @List of (text, highlight) tuples. ---@field highlights RenderHighlight[] ---@field keybinds RenderKeybind[] + ---@field diagnostics RenderDiagnostic[] local output = _output or { lines = {}, virt_texts = {}, highlights = {}, keybinds = {}, + diagnostics = {}, } if node.type == "VIRTUAL_TEXT" then @@ -132,6 +126,13 @@ local function render_node(viewport_context, node, _render_context, _output) effect = node.effect, payload = node.payload, } + elseif node.type == "DIAGNOSTICS" then + output.diagnostics[#output.diagnostics + 1] = { + line = #output.lines, + message = node.diagnostic.message, + severity = node.diagnostic.severity, + source = node.diagnostic.source, + } end return output @@ -153,6 +154,7 @@ local function create_popup_window_opts(sizes_only) col = math.floor((win_width - width) / 2), relative = "editor", style = "minimal", + zindex = 1, } if not sizes_only then @@ -162,144 +164,50 @@ local function create_popup_window_opts(sizes_only) return popup_layout end -local registered_effect_handlers_by_bufnr = {} -local active_keybinds_by_bufnr = {} -local registered_keymaps_by_bufnr = {} -local redraw_by_win_id = {} - ----@param bufnr number ----@param line number ----@param key string -local function call_effect_handler(bufnr, line, key) - local line_keybinds = active_keybinds_by_bufnr[bufnr][line] - if line_keybinds then - local keybind = line_keybinds[key] - if keybind then - local effect_handler = registered_effect_handlers_by_bufnr[bufnr][keybind.effect] - if effect_handler then - log.fmt_trace("Calling handler for effect %s on line %d for key %s", keybind.effect, line, key) - effect_handler { payload = keybind.payload } - return true - end - end - end - return false -end - -M.dispatch_effect = function(bufnr, hex_key) - local key = from_hex(hex_key) - local line = vim.api.nvim_win_get_cursor(0)[1] - log.fmt_trace("Dispatching effect on line %d, key %s, bufnr %s", line, key, bufnr) - call_effect_handler(bufnr, line, key) -- line keybinds - call_effect_handler(bufnr, -1, key) -- global keybinds -end - -function M.redraw_win(win_id) - local fn = redraw_by_win_id[win_id] - if fn then - fn() - end -end - -function M.delete_win_buf(win_id, bufnr) - -- We queue the win_buf to be deleted in a schedule call, otherwise when used with folke/which-key (and - -- set timeoutlen=0) we run into a weird segfault. - -- It should probably be unnecessary once https://github.com/neovim/neovim/issues/15548 is resolved - vim.schedule(function() - if win_id and vim.api.nvim_win_is_valid(win_id) then - log.trace "Deleting window" - vim.api.nvim_win_close(win_id, true) - end - if bufnr and vim.api.nvim_buf_is_valid(bufnr) then - log.trace "Deleting buffer" - vim.api.nvim_buf_delete(bufnr, { force = true }) - end - if redraw_by_win_id[win_id] then - redraw_by_win_id[win_id] = nil - end - if active_keybinds_by_bufnr[bufnr] then - active_keybinds_by_bufnr[bufnr] = nil - end - if registered_effect_handlers_by_bufnr[bufnr] then - registered_effect_handlers_by_bufnr[bufnr] = nil - end - if registered_keymaps_by_bufnr[bufnr] then - registered_keymaps_by_bufnr[bufnr] = nil - end - end) -end - function M.new_view_only_win(name) local namespace = vim.api.nvim_create_namespace(("lsp_installer_%s"):format(name)) - local bufnr, renderer, mutate_state, get_state, unsubscribe, win_id + local bufnr, renderer, mutate_state, get_state, unsubscribe, win_id, window_mgmt_augroup, autoclose_augroup, registered_keymaps, registered_keybinds, registered_effect_handlers local has_initiated = false - ---@param opts DisplayOpenOpts - local function open(opts) - opts = opts or {} - local highlight_groups = opts.highlight_groups - bufnr = vim.api.nvim_create_buf(false, true) - win_id = vim.api.nvim_open_win(bufnr, true, create_popup_window_opts(false)) - - registered_effect_handlers_by_bufnr[bufnr] = {} - active_keybinds_by_bufnr[bufnr] = {} - registered_keymaps_by_bufnr[bufnr] = {} - - local buf_opts = { - modifiable = false, - swapfile = false, - textwidth = 0, - buftype = "nofile", - bufhidden = "wipe", - buflisted = false, - filetype = "lsp-installer", - } - - local win_opts = { - number = false, - relativenumber = false, - wrap = false, - spell = false, - foldenable = false, - signcolumn = "no", - colorcolumn = "", - cursorline = true, - } - - -- window options - for key, value in pairs(win_opts) do - vim.api.nvim_win_set_option(win_id, key, value) - end - - -- buffer options - for key, value in pairs(buf_opts) do - vim.api.nvim_buf_set_option(bufnr, key, value) - end - - vim.cmd [[ syntax clear ]] - - local resize_autocmd = ( - "autocmd VimResized <buffer> lua require('nvim-lsp-installer.core.ui.display').redraw_win(%d)" - ):format(win_id) - local autoclose_autocmd = ( - "autocmd WinLeave,BufHidden,BufLeave <buffer> ++once lua require('nvim-lsp-installer.core.ui.display').delete_win_buf(%d, %d)" - ):format(win_id, bufnr) - - vim.cmd(([[ - augroup LspInstallerWindow - autocmd! - %s - %s - augroup END - ]]):format(resize_autocmd, autoclose_autocmd)) + local function delete_win_buf() + -- We queue the win_buf to be deleted in a schedule call, otherwise when used with folke/which-key (and + -- set timeoutlen=0) we run into a weird segfault. + -- It should probably be unnecessary once https://github.com/neovim/neovim/issues/15548 is resolved + vim.schedule(function() + if win_id and vim.api.nvim_win_is_valid(win_id) then + log.trace "Deleting window" + vim.api.nvim_win_close(win_id, true) + end + if bufnr and vim.api.nvim_buf_is_valid(bufnr) then + log.trace "Deleting buffer" + vim.api.nvim_buf_delete(bufnr, { force = true }) + end + end) + end - if highlight_groups then - for i = 1, #highlight_groups do - vim.cmd(highlight_groups[i]) + ---@param line number + ---@param key string + local function call_effect_handler(line, key) + local line_keybinds = registered_keybinds[line] + if line_keybinds then + local keybind = line_keybinds[key] + if keybind then + local effect_handler = registered_effect_handlers[keybind.effect] + if effect_handler then + log.fmt_trace("Calling handler for effect %s on line %d for key %s", keybind.effect, line, key) + effect_handler { payload = keybind.payload } + return true + end end end + return false + end - return win_id + local function dispatch_effect(key) + local line = vim.api.nvim_win_get_cursor(0)[1] + log.fmt_trace("Dispatching effect on line %d, key %s, bufnr %s", line, key, bufnr) + call_effect_handler(line, key) -- line keybinds + call_effect_handler(-1, key) -- global keybinds end local draw = function(view) @@ -321,8 +229,8 @@ function M.new_view_only_win(name) win_width = win_width, } local output = render_node(viewport_context, view) - local lines, virt_texts, highlights, keybinds = - output.lines, output.virt_texts, output.highlights, output.keybinds + local lines, virt_texts, highlights, keybinds, diagnostics = + output.lines, output.virt_texts, output.highlights, output.keybinds, output.diagnostics -- set line contents vim.api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) @@ -330,6 +238,7 @@ function M.new_view_only_win(name) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_option(bufnr, "modifiable", false) + -- set virtual texts for i = 1, #virt_texts do local virt_text = virt_texts[i] vim.api.nvim_buf_set_extmark(bufnr, namespace, virt_text.line, 0, { @@ -337,6 +246,24 @@ function M.new_view_only_win(name) }) end + -- set diagnostics + vim.diagnostic.set( + namespace, + bufnr, + vim.tbl_map(function(diagnostic) + return { + lnum = diagnostic.line - 1, + col = 0, + message = diagnostic.message, + severity = diagnostic.severity, + source = diagnostic.source, + } + end, diagnostics), + { + signs = false, + } + ) + -- set highlights for i = 1, #highlights do local highlight = highlights[i] @@ -351,32 +278,108 @@ function M.new_view_only_win(name) end -- set keybinds - local buf_keybinds = {} - active_keybinds_by_bufnr[bufnr] = buf_keybinds + registered_keybinds = {} for i = 1, #keybinds do local keybind = keybinds[i] - if not buf_keybinds[keybind.line] then - buf_keybinds[keybind.line] = {} + if not registered_keybinds[keybind.line] then + registered_keybinds[keybind.line] = {} end - buf_keybinds[keybind.line][keybind.key] = keybind - if not registered_keymaps_by_bufnr[bufnr][keybind.key] then - vim.api.nvim_buf_set_keymap( - bufnr, - "n", - keybind.key, - ("<cmd>lua require('nvim-lsp-installer.core.ui.display').dispatch_effect(%d, %q)<cr>"):format( - bufnr, - -- We transfer the keybinding as hex to avoid issues with (neo)vim interpreting the key as a - -- literal input to the command. For example, "<CR>" would cause vim to issue an actual carriage - -- return - even if it's quoted as a string. - to_hex(keybind.key) - ), - { nowait = true, silent = true, noremap = true } - ) + registered_keybinds[keybind.line][keybind.key] = keybind + if not registered_keymaps[keybind.key] then + registered_keymaps[keybind.key] = true + vim.keymap.set("n", keybind.key, function() + dispatch_effect(keybind.key) + end, { + buffer = bufnr, + nowait = true, + silent = true, + }) end end end + ---@param opts DisplayOpenOpts + local function open(opts) + opts = opts or {} + local highlight_groups = opts.highlight_groups + bufnr = vim.api.nvim_create_buf(false, true) + win_id = vim.api.nvim_open_win(bufnr, true, create_popup_window_opts(false)) + + registered_effect_handlers = opts.effects + registered_keybinds = {} + registered_keymaps = {} + + local buf_opts = { + modifiable = false, + swapfile = false, + textwidth = 0, + buftype = "nofile", + bufhidden = "wipe", + buflisted = false, + filetype = "lsp-installer", + } + + local win_opts = { + number = false, + relativenumber = false, + wrap = false, + spell = false, + foldenable = false, + signcolumn = "no", + colorcolumn = "", + cursorline = true, + } + + -- window options + for key, value in pairs(win_opts) do + vim.api.nvim_win_set_option(win_id, key, value) + end + + -- buffer options + for key, value in pairs(buf_opts) do + vim.api.nvim_buf_set_option(bufnr, key, value) + end + + vim.cmd [[ syntax clear ]] + + window_mgmt_augroup = vim.api.nvim_create_augroup("LspInstallerWindowMgmt", {}) + autoclose_augroup = vim.api.nvim_create_augroup("LspInstallerWindow", {}) + + vim.api.nvim_create_autocmd({ "VimResized" }, { + group = window_mgmt_augroup, + buffer = bufnr, + callback = function() + if vim.api.nvim_win_is_valid(win_id) then + draw(renderer(get_state())) + vim.api.nvim_win_set_config(win_id, create_popup_window_opts(true)) + end + end, + }) + + local win_enter_aucmd + win_enter_aucmd = vim.api.nvim_create_autocmd({ "WinEnter" }, { + group = autoclose_augroup, + pattern = "*", + callback = function() + -- Only autoclose the popup window if the user enters a "normal" buffer. + -- This allows us to keep the popup window open for things like diagnostic popups, UI inputs รก la dressing.nvim, etc. + if vim.api.nvim_buf_get_option(0, "buftype") == "" then + delete_win_buf() + vim.api.nvim_del_autocmd(win_enter_aucmd) + end + end, + }) + + if highlight_groups then + for i = 1, #highlight_groups do + -- TODO use vim.api.nvim_set_hl() + vim.cmd(highlight_groups[i]) + end + end + + return win_id + end + return { ---@param _renderer fun(state: table): table view = function(_renderer) @@ -411,22 +414,17 @@ function M.new_view_only_win(name) return end unsubscribe(false) - local opened_win_id = open(opts) + open(opts) draw(renderer(get_state())) - registered_effect_handlers_by_bufnr[bufnr] = opts.effects - redraw_by_win_id[opened_win_id] = function() - if vim.api.nvim_win_is_valid(opened_win_id) then - draw(renderer(get_state())) - vim.api.nvim_win_set_config(opened_win_id, create_popup_window_opts(true)) - end - end end), ---@type fun() close = vim.schedule_wrap(function() assert(has_initiated, "Display has not been initiated, cannot close.") unsubscribe(true) log.fmt_trace("Closing window win_id=%s, bufnr=%s", win_id, bufnr) - M.delete_win_buf(win_id, bufnr) + delete_win_buf() + vim.api.nvim_del_augroup_by_id(window_mgmt_augroup) + vim.api.nvim_del_augroup_by_id(autoclose_augroup) end), ---@param pos number[] @(row, col) tuple set_cursor = function(pos) diff --git a/lua/nvim-lsp-installer/core/ui/init.lua b/lua/nvim-lsp-installer/core/ui/init.lua index 6baf5351..ee1e20d9 100644 --- a/lua/nvim-lsp-installer/core/ui/init.lua +++ b/lua/nvim-lsp-installer/core/ui/init.lua @@ -5,10 +5,11 @@ local M = {} ---| '"NODE"' ---| '"CASCADING_STYLE"' ---| '"VIRTUAL_TEXT"' +---| '"DIAGNOSTICS' ---| '"HL_TEXT"' ---| '"KEYBIND_HANDLER"' ----@alias INode Node | HlTextNode | CascadingStyleNode | VirtualTextNode | KeybindHandlerNode +---@alias INode Node | HlTextNode | CascadingStyleNode | VirtualTextNode | KeybindHandlerNode | DiagnosticsNode ---@param children INode[] function M.Node(children) @@ -67,6 +68,16 @@ function M.VirtualTextNode(virt_text) return node end +---@param diagnostic {message: string, severity: integer, source: string|nil} +function M.DiagnosticsNode(diagnostic) + ---@class DiagnosticsNode + local node = { + type = "DIAGNOSTICS", + diagnostic = diagnostic, + } + return node +end + ---@param condition boolean ---@param node INode | fun(): INode ---@param default_val any diff --git a/lua/nvim-lsp-installer/ui/init.lua b/lua/nvim-lsp-installer/ui/init.lua index 276941c0..847834ff 100644 --- a/lua/nvim-lsp-installer/ui/init.lua +++ b/lua/nvim-lsp-installer/ui/init.lua @@ -16,7 +16,7 @@ local HELP_KEYMAP = "?" local CLOSE_WINDOW_KEYMAP_1 = "<Esc>" local CLOSE_WINDOW_KEYMAP_2 = "q" ----@param props {title: string, subtitle: string[][], count: number} +---@param props {title: string, diagnostics: table|nil, subtitle: string[][], count: number} local function ServerGroupHeading(props) local line = { { props.title, props.highlight or "LspInstallerHeading" }, @@ -25,7 +25,10 @@ local function ServerGroupHeading(props) if props.subtitle then vim.list_extend(line, props.subtitle) end - return Ui.HlTextNode { line } + return Ui.Node { + Ui.HlTextNode { line }, + Ui.When(props.diagnostics, Ui.DiagnosticsNode(props.diagnostics)), + } end local function Indent(children) @@ -307,6 +310,16 @@ local function ServerMetadata(server) )) end +---@param packages OutdatedPackage[] +local function format_outdated_packages(packages) + return table.concat( + vim.tbl_map(function(package) + return ("%s %s -> %s"):format(package.name, package.current_version, package.latest_version) + end, packages), + "\n" + ) +end + ---@param servers ServerState[] ---@param props ServerGroupProps local function InstalledServers(servers, props) @@ -320,13 +333,19 @@ local function InstalledServers(servers, props) { settings.current.ui.icons.server_installed, "LspInstallerGreen" }, { " " .. server.name .. " ", "" }, { server.hints, "Comment" }, - functional.when(server.deprecated, { " deprecated", "LspInstallerOrange" }), - functional.when( - #server.metadata.outdated_packages > 0 and not is_expanded, - { " new version available", "LspInstallerGreen" } - ) + functional.when(server.deprecated, { " deprecated", "LspInstallerOrange" }) ), }, + Ui.When( + #server.metadata.outdated_packages > 0, + Ui.DiagnosticsNode { + message = ("new version available, press %s to update \n"):format( + settings.current.ui.keymaps.update_server + ) .. format_outdated_packages(server.metadata.outdated_packages), + severity = vim.diagnostic.severity.INFO, + source = server.name, + } + ), Ui.Keybind(settings.current.ui.keymaps.toggle_server_expand, "EXPAND_SERVER", { server.name }), Ui.Keybind(settings.current.ui.keymaps.update_server, "INSTALL_SERVER", { server.name }), Ui.Keybind(settings.current.ui.keymaps.check_server_version, "CHECK_SERVER_VERSION", { server.name }), @@ -429,7 +448,7 @@ local function UninstalledServers(servers, props) end, servers)) end ----@alias ServerGroupProps {title: string, subtitle: string|nil, hide_when_empty: boolean|nil, servers: ServerState[][], expanded_server: string|nil, renderer: fun(servers: ServerState[], props: ServerGroupProps)} +---@alias ServerGroupProps {title: string, title_diagnostics: table|nil, subtitle: string|nil, hide_when_empty: boolean|nil, servers: ServerState[][], expanded_server: string|nil, renderer: fun(servers: ServerState[], props: ServerGroupProps)} ---@param props ServerGroupProps local function ServerGroup(props) @@ -445,6 +464,7 @@ local function ServerGroup(props) Ui.EmptyLine(), ServerGroupHeading { title = props.title, + diagnostics = props.title_diagnostics, subtitle = props.subtitle, count = total_server_count, }, @@ -505,6 +525,12 @@ local function Servers(state) return Ui.Node { ServerGroup { title = "Installed servers", + title_diagnostics = state.has_outdated_servers and { + severity = vim.diagnostic.severity.INFO, + message = ("press %s to update all outdated servers"):format( + settings.current.ui.keymaps.update_all_servers + ), + } or nil, subtitle = state.server_version_check_completed_percentage ~= nil and { { "checking for new versions ", @@ -643,6 +669,7 @@ local function init(all_servers) server_name_order = server_name_order, servers = servers, server_version_check_completed_percentage = nil, + has_outdated_servers = false, is_showing_help = false, is_current_settings_expanded = false, prioritized_servers = {}, @@ -886,9 +913,11 @@ local function init(all_servers) end) if #servers > 0 then mutate_state(function(state) + state.has_outdated_servers = false state.server_version_check_completed_percentage = 0 end) end + local has_outdated_servers = false outdated_servers.identify_outdated_servers(servers, function(check_result, progress) mutate_state(function(state) local completed_percentage = progress.completed / progress.total @@ -896,12 +925,14 @@ local function init(all_servers) if completed_percentage == 1 then vim.defer_fn(function() mutate_state(function(state) + state.has_outdated_servers = has_outdated_servers state.server_version_check_completed_percentage = nil end) end, 700) end if check_result.success and check_result:has_outdated_packages() then + has_outdated_servers = true state.servers[check_result.server.name].metadata.outdated_packages = check_result.outdated_packages end end) @@ -1007,6 +1038,9 @@ local function init(all_servers) end end, ["UPDATE_ALL_SERVERS"] = function() + mutate_state(function(state) + state.has_outdated_servers = false + end) local installed_servers = lsp_servers.get_installed_servers() local state = get_state() local outdated_servers = vim.tbl_filter(function(server) |
