From 44229bb85b6cff00193164967126d85a7a785a7b Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 17 Nov 2024 12:31:32 -0800 Subject: [PATCH] feat(lsp): highlight hover target/range #31110 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem:** Despite the LSP providing the option for language servers to specify a range with a hover response (for highlighting), Neovim does not give the option to highlight this range. **Solution:** Add an option to `buf.hover()` which causes this range to be highlighted. Co-authored-by: Mathias Fußenegger --- runtime/doc/lsp.txt | 19 ++++++++++++---- runtime/doc/news.txt | 2 ++ runtime/lua/vim/lsp/buf.lua | 45 ++++++++++++++++++++++++++++++++++--- src/nvim/highlight_group.c | 1 + 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index a7d6844dd4..7d50cb52eb 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -338,13 +338,16 @@ Highlight groups that are meant to be used by |vim.lsp.buf.document_highlight()| You can see more about the differences in types here: https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight - *hl-LspReferenceText* + *hl-LspReferenceText* LspReferenceText used for highlighting "text" references - *hl-LspReferenceRead* + *hl-LspReferenceRead* LspReferenceRead used for highlighting "read" references - *hl-LspReferenceWrite* + *hl-LspReferenceWrite* LspReferenceWrite used for highlighting "write" references - *hl-LspInlayHint* + *hl-LspReferenceTarget* +LspReferenceTarget used for highlighting reference targets (e.g. in a + hover range) + *hl-LspInlayHint* LspInlayHint used for highlighting inlay hints @@ -1335,6 +1338,14 @@ hover({config}) *vim.lsp.buf.hover()* mappings are available as usual, except that "q" dismisses the window. You can scroll the contents the same as you would any other buffer. + Note: to disable hover highlights, add the following to your config: >lua + vim.api.nvim_create_autocmd('ColorScheme', { + callback = function() + vim.api.nvim_set_hl(0, 'LspReferenceTarget', {}) + end, + }) +< + Parameters: ~ • {config} (`vim.lsp.buf.hover.Opts?`) See |vim.lsp.buf.hover.Opts|. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index fc714526d4..d19df84023 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -216,6 +216,8 @@ LSP • |vim.lsp.buf.signature_help()| can now cycle through different signatures using `` and also support multiple clients. • The client now supports `'utf-8'` and `'utf-32'` position encodings. +• |vim.lsp.buf.hover()| now highlights hover ranges using the + |hl-LspReferenceTarget| highlight group. LUA diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 6d7597c5ff..a75e322e90 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -20,6 +20,8 @@ local function client_positional_params(params) end end +local hover_ns = api.nvim_create_namespace('vim_lsp_hover_range') + --- @class vim.lsp.buf.hover.Opts : vim.lsp.util.open_floating_preview.Opts --- @field silent? boolean @@ -30,13 +32,24 @@ end --- In the floating window, all commands and mappings are available as usual, --- except that "q" dismisses the window. --- You can scroll the contents the same as you would any other buffer. +--- +--- Note: to disable hover highlights, add the following to your config: +--- +--- ```lua +--- vim.api.nvim_create_autocmd('ColorScheme', { +--- callback = function() +--- vim.api.nvim_set_hl(0, 'LspReferenceTarget', {}) +--- end, +--- }) +--- ``` --- @param config? vim.lsp.buf.hover.Opts function M.hover(config) config = config or {} config.focus_id = ms.textDocument_hover lsp.buf_request_all(0, ms.textDocument_hover, client_positional_params(), function(results, ctx) - if api.nvim_get_current_buf() ~= ctx.bufnr then + local bufnr = assert(ctx.bufnr) + if api.nvim_get_current_buf() ~= bufnr then -- Ignore result since buffer changed. This happens for slow language servers. return end @@ -67,9 +80,10 @@ function M.hover(config) local format = 'markdown' for client_id, result in pairs(results1) do + local client = assert(lsp.get_client_by_id(client_id)) if nresults > 1 then -- Show client name if there are multiple clients - contents[#contents + 1] = string.format('# %s', lsp.get_client_by_id(client_id).name) + contents[#contents + 1] = string.format('# %s', client.name) end if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then if #results1 == 1 then @@ -87,6 +101,22 @@ function M.hover(config) else vim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents)) end + local range = result.range + if range then + local start = range.start + local end_ = range['end'] + local start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding) + local end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding) + + vim.hl.range( + bufnr, + hover_ns, + 'LspReferenceTarget', + { start.line, start_idx }, + { end_.line, end_idx }, + { priority = vim.hl.priorities.user } + ) + end contents[#contents + 1] = '---' end @@ -100,7 +130,16 @@ function M.hover(config) return end - lsp.util.open_floating_preview(contents, format, config) + local _, winid = lsp.util.open_floating_preview(contents, format, config) + + api.nvim_create_autocmd('WinClosed', { + pattern = tostring(winid), + once = true, + callback = function() + api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1) + return true + end, + }) end) end diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 65641f120f..b3c4aca1af 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -215,6 +215,7 @@ static const char *highlight_init_both[] = { "default link LspReferenceRead LspReferenceText", "default link LspReferenceText Visual", "default link LspReferenceWrite LspReferenceText", + "default link LspReferenceTarget LspReferenceText", "default link LspSignatureActiveParameter Visual", "default link SnippetTabstop Visual",