From d6eb7d0e5ea932995765803dedb521efc06bd383 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Fri, 20 Sep 2024 08:03:04 +0200 Subject: [PATCH] feat(lsp): `` mappings for hover actions --- README.md | 35 ++++++++-- lua/haskell-tools/lsp/hover.lua | 111 ++++++++++++++++++++++++-------- 2 files changed, 114 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 9c9cbf9..2b04960 100644 --- a/README.md +++ b/README.md @@ -246,18 +246,41 @@ Start a GHCi repl for the current project / buffer. Inspired by [rust-tools.nvim](https://github.com/simrat39/rust-tools.nvim), this plugin adds the following hover actions (if available): -- Hoogle search. -- Open documentation in browser. -- Open source in browser. -- Go to definition. -- Go to type definition. -- Find references. +- Hoogle search + (no dedicated `` mapping + because there can be multiple signatures to search for). +- Open documentation in browser (`HaskellHoverActionDocs`). +- Open source in browser (`HaskellHoverActionSource`). +- Go to definition (`HaskellHoverActionDefinition`). +- Go to type definition (`HaskellHoverActionTypeDefinition`). +- Find references (`HaskellHoverActionReferences`). + +You can invoke them by switching to the hover window and entering `` +on the respective line, or with a keymap for the respective `` mapping. +If you do not wish to create a separate keymap for each `` mapping, +you can also create a keymap for `HaskellHoverAction`, which accepts +a `` prefix as the (1-based) index of the hover action to invoke. + +For example, if you set the following keymap: + +```lua +vim.keymap.set('n', 'a', 'HaskellHoverAction') +``` + +you can invoke the third hover action with `3a`. Additionally, the default behaviour of stylizing markdown is disabled. And the hover buffer's filetype is set to markdown, so that [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) users can benefit from syntax highlighting of code snippets. +For invoking hover actions with a keymap in normal mode, this plugin +provides the following `` mappings: + +- `HaskellHoverAction`: Takes a `` prefix for the hover action to invoke. +- `HaskellHoverActionHoogle`: Invokes the first Hoogle search hover action. +- `HaskellHoverActionOpenDocs`: Open + ![hoverActions](https://user-images.githubusercontent.com/12857160/219763211-61fc4207-4300-41f2-99c4-6a420cf940f2.gif) - [x] **Automatically generate tags** diff --git a/lua/haskell-tools/lsp/hover.lua b/lua/haskell-tools/lsp/hover.lua index 580938a..b241be3 100644 --- a/lua/haskell-tools/lsp/hover.lua +++ b/lua/haskell-tools/lsp/hover.lua @@ -34,12 +34,30 @@ local function close_hover() end end ----Execute the command at the cursor position ----@retrun nil -local function run_command() - local winnr = vim.api.nvim_get_current_win() - local line = vim.api.nvim_win_get_cursor(winnr)[1] +---@param bufnr number +local function delete_plug_mappings(bufnr) + vim + .iter({ + '', + 'Docs', + 'Source', + 'Definition', + 'TypeDefinition', + 'References', + }) + :map(function(suffix) + return 'HaskellHoverAction' .. suffix + end) + :each(function(mapping) + pcall(function() + vim.keymap.del('n', mapping, { buffer = bufnr }) + end) + end) +end +---Execute the command at the cursor position +---@param line integer +local function run_command(line) if line > #_state.commands then return end @@ -109,6 +127,13 @@ local function is_same_position(params, result) and params.position.character == range_start.character end +---@param suffix string +---@param action function +---@param bufnr integer +local function create_plug_mapping(suffix, action, bufnr) + vim.keymap.set('n', 'HaskellHoverAction' .. suffix, action, { buffer = bufnr, noremap = true, silent = true }) +end + ---LSP handler for textDocument/hover ---@param result table ---@param ctx table @@ -133,6 +158,7 @@ function hover.on_hover(_, result, ctx, config) vim.notify('No information available') return end + local current_bufnr = vim.api.nvim_get_current_buf() local to_remove = {} local actions = {} _state.commands = {} @@ -141,10 +167,12 @@ function hover.on_hover(_, result, ctx, config) local _, signatures = HtParser.try_get_signatures_from_markdown(func_name, result.contents.value) for _, signature in pairs(signatures) do table.insert(actions, 1, string.format('%d. Hoogle search: `%s`', #actions + 1, signature)) - table.insert(_state.commands, function() + local action = function() log.debug { 'Hover: Hoogle search for signature', signature } ht.hoogle.hoogle_signature { search_term = signature } - end) + close_hover() + end + table.insert(_state.commands, action) end local cword = vim.fn.expand('') table.insert(actions, 1, string.format('%d. Hoogle search: `%s`', #actions + 1, cword)) @@ -163,23 +191,31 @@ function hover.on_hover(_, result, ctx, config) table.insert(to_remove, 1, i) table.insert(actions, 1, string.format('%d. Open documentation in browser', #actions + 1)) local uri = string.match(value, '%[Documentation%]%((.+)%)') - table.insert(_state.commands, function() + local action = function() log.debug { 'Hover: Open documentation in browser', uri } OS.open_browser(uri) - end) + close_hover() + end + table.insert(_state.commands, action) + create_plug_mapping('Docs', action, current_bufnr) elseif vim.startswith(value, '[Source]') and not found_source then found_source = true table.insert(to_remove, 1, i) table.insert(actions, 1, string.format('%d. View source in browser', #actions + 1)) local uri = string.match(value, '%[Source%]%((.+)%)') - table.insert(_state.commands, function() + local action = function() log.debug { 'Hover: View source in browser', uri } OS.open_browser(uri) - end) + close_hover() + end + table.insert(_state.commands, action) + create_plug_mapping('Source', action, current_bufnr) end local location = string.match(value, '*Defined [ia][nt] (.+)') local current_file = params.textDocument.uri:gsub('file://', '') local results, err, definition_results + ---@type function + local references_action -- defined here because of the goto statements if location == nil or found_location then goto SkipDefinition end @@ -196,7 +232,7 @@ function hover.on_hover(_, result, ctx, config) if not is_same_position(params, definition_result) then log.debug { 'Hover: definition location', location_suffix } table.insert(actions, 1, string.format('%d. Go to definition at ' .. location_suffix, #actions + 1)) - table.insert(_state.commands, function() + local action = function() -- We don't call vim.lsp.buf.definition() because the location params may have changed local definition_ctx = vim.tbl_extend('force', ctx, { method = 'textDocument/definition', @@ -205,7 +241,10 @@ function hover.on_hover(_, result, ctx, config) ---Neovim 0.9 has a bug in the lua doc ---@diagnostic disable-next-line: param-type-mismatch vim.lsp.handlers['textDocument/definition'](nil, definition_result, definition_ctx) - end) + close_hover() + end + table.insert(_state.commands, action) + create_plug_mapping('Definition', action, current_bufnr) end else -- Display Hoogle search instead local pkg = location:match('‘(.+)’') @@ -217,13 +256,16 @@ function hover.on_hover(_, result, ctx, config) end) end table.insert(actions, 1, string.format('%d. Find references', #actions + 1)) - table.insert(_state.commands, function() + references_action = function() local reference_params = vim.tbl_deep_extend('force', params, { context = { includeDeclaration = true } }) log.debug { 'Hover: Find references', reference_params } -- We don't call vim.lsp.buf.references() because the location params may have changed ---@diagnostic disable-next-line: missing-parameter vim.lsp.buf_request(0, 'textDocument/references', reference_params) - end) + close_hover() + end + table.insert(_state.commands, references_action) + create_plug_mapping('Referenes', references_action, current_bufnr) ::SkipDefinition:: if found_type_definition then goto SkipTypeDefinition @@ -244,7 +286,7 @@ function hover.on_hover(_, result, ctx, config) end log.debug { 'Hover: type definition location', type_def_suffix } table.insert(actions, 1, string.format('%d. Go to type definition at ' .. type_def_suffix, #actions + 1)) - table.insert(_state.commands, function() + local typedef_action = function() -- We don't call vim.lsp.buf.typeDefinition() because the location params may have changed local type_definition_ctx = vim.tbl_extend('force', ctx, { method = 'textDocument/typeDefinition', @@ -253,7 +295,10 @@ function hover.on_hover(_, result, ctx, config) ---Neovim 0.9 has a bug in the lua doc ---@diagnostic disable-next-line: param-type-mismatch vim.lsp.handlers['textDocument/typeDefinition'](nil, type_definition_result, type_definition_ctx) - end) + close_hover() + end + table.insert(_state.commands, typedef_action) + create_plug_mapping('TypeDefinition', typedef_action, current_bufnr) ::SkipTypeDefinition:: end for _, pos in ipairs(to_remove) do @@ -275,38 +320,52 @@ function hover.on_hover(_, result, ctx, config) focus_id = 'haskell-tools-hover', close_events = { 'CursorMoved', 'BufHidden', 'InsertCharPre' }, }, config or {}) - local bufnr, winnr = lsp_util.open_floating_preview(markdown_lines, 'markdown', config) + local hover_bufnr, winnr = lsp_util.open_floating_preview(markdown_lines, 'markdown', config) if opts.stylize_markdown == false then - vim.bo[bufnr].filetype = 'markdown' + vim.bo[hover_bufnr].filetype = 'markdown' end if opts.auto_focus == true then vim.api.nvim_set_current_win(winnr) end if _state.winnr ~= nil then - return bufnr, winnr + return hover_bufnr, winnr end _state.winnr = winnr - vim.keymap.set('n', '', close_hover, { buffer = bufnr, noremap = true, silent = true }) - vim.api.nvim_buf_attach(bufnr, false, { + vim.keymap.set('n', '', close_hover, { buffer = hover_bufnr, noremap = true, silent = true }) + vim.api.nvim_buf_attach(hover_bufnr, false, { on_detach = function() _state.winnr = nil end, }) if #_state.commands == 0 then - return bufnr, winnr + return hover_bufnr, winnr end vim.api.nvim_set_option_value('cursorline', true, { win = winnr }) -- run the command under the cursor vim.keymap.set('n', '', function() - run_command() - end, { buffer = bufnr, noremap = true, silent = true }) + local win = vim.api.nvim_get_current_win() + local line = vim.api.nvim_win_get_cursor(win)[1] + run_command(line) + end, { buffer = hover_bufnr, noremap = true, silent = true }) + + local function run_command_mapping() + run_command(math.max(vim.v.count, 1)) + end + create_plug_mapping('', run_command_mapping, current_bufnr) + vim.api.nvim_create_autocmd('WinClosed', { + group = vim.api.nvim_create_augroup('HaskellHoverAction', { clear = true }), + pattern = tostring(winnr), + callback = function() + delete_plug_mappings(current_bufnr) + end, + }) - return bufnr, winnr + return hover_bufnr, winnr end return hover