Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
simrat39 opened this issue Aug 20, 2022 · 1 comment
Open

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

simrat39 opened this issue Aug 20, 2022 · 1 comment
Labels
enhancement New feature or request

Comments

@simrat39
Copy link

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
@laytan
Copy link
Contributor

laytan commented Sep 24, 2022

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,
})

@lewis6991 lewis6991 added the enhancement New feature or request label Feb 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants