aboutsummaryrefslogtreecommitdiffstats
path: root/lua
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2022-05-13 16:17:06 +0200
committerGitHub <noreply@github.com>2022-05-13 16:17:06 +0200
commit09aea5d129c761a95f4bfaf80ec99eac2ab88983 (patch)
treeb9121fce8c3fe064fa2be8adb2165a6777d956f2 /lua
parentrun autogen_metadata.lua (diff)
downloadmason-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.lua342
-rw-r--r--lua/nvim-lsp-installer/core/ui/init.lua13
-rw-r--r--lua/nvim-lsp-installer/ui/init.lua50
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)