lewis6991 / lewis6991/hover.nvim

Proposal: Allow provider to mess with the hover buffer/window

Open
#8 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Lua
Stars
744
Forks
58
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in lua/hover/actions.lua, especially show_hover and run_provider, and compare the proposed callback flow with the example provider's on_render function. Check how the rendered buffer and window are returned and passed to providers. Done means providers can receive those handles after rendering and use them for buffer and window customization.

Written by the indexing model from the issue text.

Assessment

Tech stack
lua
Domain
devtools
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.