Proposal: Allow provider to mess with the hover buffer/window
Opened this issue · 1 comments
Some hover providers (like rust-analyzer) allow clients to have certain actions embedded in the hover, which would need providers to be able to modify the buffer and window (set keymaps, change window options) etc. So, I think allowing providers the ability to do that is a good idea.
Proposed Change
Allow providers to define a on_render
function which is called when the hover is rendered.
Example Implementation
diff --git a/lua/hover/actions.lua b/lua/hover/actions.lua
index 65d8cc5..321c269 100644
--- a/lua/hover/actions.lua
+++ b/lua/hover/actions.lua
@@ -66,11 +66,13 @@ local function focus_or_close_hover()
end
local function show_hover(provider_id, config, result, opts)
- local _, winnr = util.open_floating_preview(result.lines, result.filetype, opts)
+ local bufnr, winnr = util.open_floating_preview(result.lines, result.filetype, opts)
if config.title then
add_title(winnr, provider_id)
end
+
+ return bufnr, winnr
end
-- Must be called in async context
@@ -89,7 +91,10 @@ local function run_provider(provider)
local result = provider.execute()
if result then
async.scheduler()
- show_hover(provider.id, config, result, opts)
+ local bufnr, winnr = show_hover(provider.id, config, result, opts)
+ if provider.on_render then
+ provider.on_render(bufnr, winnr)
+ end
return true
end
Example provider implementation (rust-tools hover actions)
---@diagnostic disable: missing-parameter, param-type-mismatch
local M = {}
M._state = { commands = nil }
local function execute_rust_analyzer_command(action, ctx)
local fn = vim.lsp.commands[action.command]
if fn then
fn(action, ctx)
end
end
-- run the command under the cursor, if the thing under the cursor is not the
-- command then do nothing
local function run_command(ctx)
local winnr = vim.api.nvim_get_current_win()
local line = vim.api.nvim_win_get_cursor(winnr)[1]
if line > #M._state.commands then
return
end
local action = M._state.commands[line]
vim.api.nvim_win_close(winnr, true)
execute_rust_analyzer_command(action, ctx)
end
local function parse_commands()
local prompt = {}
for i, value in ipairs(M._state.commands) do
if value.command == "rust-analyzer.gotoLocation" then
table.insert(
prompt,
string.format("%d. Go to %s (%s)", i, value.title, value.tooltip)
)
elseif value.command == "rust-analyzer.showReferences" then
table.insert(prompt, string.format("%d. %s", i, "Go to " .. value.title))
else
table.insert(prompt, string.format("%d. %s", i, value.title))
end
end
return prompt
end
require("hover").register({
name = "Rust Hover Actions",
enabled = function()
return true
end,
execute = function(done)
local util = require("vim.lsp.util")
local params = util.make_position_params()
vim.lsp.buf_request(
0,
"textDocument/hover",
params,
function(_, result, ctx)
if not result or not result.contents then
done()
return
end
M._state.commands = nil
local lines = util.convert_input_to_markdown_lines(result.contents)
if result.actions then
M._state.commands = result.actions[1].commands
local prompt = parse_commands()
local l = {}
for _, value in ipairs(prompt) do
table.insert(l, value)
end
lines = vim.list_extend(l, lines)
end
lines = util.trim_empty_lines(lines)
M._state.ctx = ctx
if vim.tbl_isempty(lines) then
done()
return
end
done({ lines = lines, filetype = "markdown" })
end
)
end,
on_render = function(bufnr, winnr)
if M._state.commands == nil then
return
end
-- makes more sense in a dropdown-ish ui
vim.api.nvim_win_set_option(winnr, "cursorline", true)
-- run the command under the cursor
vim.keymap.set("n", "<CR>", function()
run_command(M._state.ctx)
end, { buffer = bufnr, noremap = true, silent = true })
end,
})
return M
I had a use case for this where the hover output was colored using ANSI sequences and I wanted to render those correctly.
Here is how I did it, might be helpful:
When you call the done
callback pass a custom filetype
that ideally is not used anywhere else:
done { lines = job:result(), filetype = 'glow' }
Internally, the syntax will be set to this filetype, so using the following snippet you can run code once it is opened:
vim.api.nvim_create_autocmd('Syntax', {
pattern = "glow",
callback = function(ctx)
vim.schedule(function()
vim.api.nvim_buf_set_option(ctx.buf, 'modifiable', true)
-- Do your stuff here.
vim.api.nvim_buf_set_option(ctx.buf, 'modifiable', false)
end)
end,
})
Full working example of my use case (GitHub repos rendered with charmbracelet/glow, converted using m00qek/baleia.nvim):
local hover = require('hover')
local Job = require 'plenary.job'
local baleia = require('baleia').setup {}
local repo_pattern = '[^%s]+/[^%s]+'
vim.api.nvim_create_autocmd(
'Syntax', {
pattern = 'glow',
callback = function(ctx)
vim.schedule(
function()
vim.api.nvim_buf_set_option(ctx.buf, 'modifiable', true)
baleia.once(ctx.buf)
vim.api.nvim_buf_set_option(ctx.buf, 'modifiable', false)
end
)
end,
}
)
local function enabled()
return vim.fn.expand('<cfile>'):match(repo_pattern) ~= nil
end
local function execute(done)
local repo = vim.fn.expand('<cfile>'):match(repo_pattern)
Job:new(
{
command = 'glow',
args = { 'github.com/' .. repo, '-s', 'dark' },
on_exit = function(job)
done { lines = job:result(), filetype = 'glow' }
end,
}
):start()
end
hover.register({
name = "GitHub repos",
priority = 1050,
enabled = enabled,
execute = execute,
})