aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2024-10-06 12:20:40 -0700
committerGitHub <noreply@github.com>2024-10-06 12:20:40 -0700
commit27f3750817b188c9ababe94eade22c30d8819585 (patch)
treeb916d804083d1f5a0c991390d06c5bfd37f95339
parent5da2a171f72cb11f70cf5568d800a0d672b07548 (diff)
downloadrneovim-27f3750817b188c9ababe94eade22c30d8819585.tar.gz
rneovim-27f3750817b188c9ababe94eade22c30d8819585.tar.bz2
rneovim-27f3750817b188c9ababe94eade22c30d8819585.zip
feat(lsp): improve LSP doc hover rendering #30695
Problem: - Some servers like LuaLS add unwanted blank lines after multiline `@param` description. - List items do not wrap nicely. Solution: - When rendering the LSP doc hover, remove blank lines in each `@param` or `@return`. - But ensure exactly one empty line before each. - Set 'breakindent'.
-rw-r--r--runtime/lua/vim/lsp/util.lua36
-rw-r--r--test/functional/plugin/lsp/utils_spec.lua57
2 files changed, 82 insertions, 11 deletions
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 9ee7dc8df7..a08825b735 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -99,9 +99,26 @@ local function get_border_size(opts)
return { height = height, width = width }
end
-local function split_lines(value)
- value = string.gsub(value, '\r\n?', '\n')
- return split(value, '\n', { plain = true, trimempty = true })
+--- Splits string at newlines, optionally removing unwanted blank lines.
+---
+--- @param s string Multiline string
+--- @param no_blank boolean? Drop blank lines for each @param/@return (except one empty line
+--- separating each). Workaround for https://github.com/LuaLS/lua-language-server/issues/2333
+local function split_lines(s, no_blank)
+ s = string.gsub(s, '\r\n?', '\n')
+ local lines = {}
+ local in_desc = true -- Main description block, before seeing any @foo.
+ for line in vim.gsplit(s, '\n', { plain = true, trimempty = true }) do
+ local start_annotation = not not line:find('^ ?%@.?[pr]')
+ in_desc = (not start_annotation) and in_desc or false
+ if start_annotation and no_blank and not (lines[#lines] or ''):find('^%s*$') then
+ table.insert(lines, '') -- Separate each @foo with a blank line.
+ end
+ if in_desc or not no_blank or not line:find('^%s*$') then
+ table.insert(lines, line)
+ end
+ end
+ return lines
end
local function create_window_without_focus()
@@ -116,7 +133,7 @@ end
--- Convenience wrapper around vim.str_utfindex
---@param line string line to be indexed
---@param index integer|nil byte index (utf-8), or `nil` for length
----@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16
+---@param encoding 'utf-8'|'utf-16'|'utf-32'|nil defaults to utf-16
---@return integer `encoding` index of `index` in `line`
function M._str_utfindex_enc(line, index, encoding)
local len32, len16 = vim.str_utfindex(line)
@@ -735,13 +752,13 @@ function M.convert_input_to_markdown_lines(input, contents)
contents = contents or {}
-- MarkedString variation 1
if type(input) == 'string' then
- list_extend(contents, split_lines(input))
+ list_extend(contents, split_lines(input, true))
else
assert(type(input) == 'table', 'Expected a table for LSP input')
-- MarkupContent
if input.kind then
local value = input.value or ''
- list_extend(contents, split_lines(value))
+ list_extend(contents, split_lines(value, true))
-- MarkupString variation 2
elseif input.language then
table.insert(contents, '```' .. input.language)
@@ -1633,10 +1650,9 @@ function M.open_floating_preview(contents, syntax, opts)
if do_stylize then
vim.wo[floating_winnr].conceallevel = 2
end
- -- disable folding
- vim.wo[floating_winnr].foldenable = false
- -- soft wrapping
- vim.wo[floating_winnr].wrap = opts.wrap
+ vim.wo[floating_winnr].foldenable = false -- Disable folding.
+ vim.wo[floating_winnr].wrap = opts.wrap -- Soft wrapping.
+ vim.wo[floating_winnr].breakindent = true -- Slightly better list presentation.
vim.bo[floating_bufnr].modifiable = false
vim.bo[floating_bufnr].bufhidden = 'wipe'
diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua
index c1f56f2722..64d58eeffd 100644
--- a/test/functional/plugin/lsp/utils_spec.lua
+++ b/test/functional/plugin/lsp/utils_spec.lua
@@ -83,7 +83,62 @@ describe('vim.lsp.util', function()
end)
end)
- describe('normalize_markdown', function()
+ it('convert_input_to_markdown_lines', function()
+ local r = exec_lua(function()
+ local hover_data = {
+ kind = 'markdown',
+ value = '```lua\nfunction vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)\n -> boolean\n```\n\n---\n\n Activates buffer-update events. Example:\n\n\n\n ```lua\n events = {}\n vim.api.nvim_buf_attach(0, false, {\n on_lines = function(...)\n table.insert(events, {...})\n end,\n })\n ```\n\n\n @see `nvim_buf_detach()`\n @see `api-buffer-updates-lua`\n@*param* `buffer` — Buffer handle, or 0 for current buffer\n\n\n\n@*param* `send_buffer` — True if whole buffer.\n Else the first notification will be `nvim_buf_changedtick_event`.\n\n\n@*param* `opts` — Optional parameters.\n\n - on_lines: Lua callback. Args:\n - the string "lines"\n - buffer handle\n - b:changedtick\n@*return* — False if foo;\n\n otherwise True.\n\n@see foo\n@see bar\n\n',
+ }
+ return vim.lsp.util.convert_input_to_markdown_lines(hover_data)
+ end)
+ local expected = {
+ '```lua',
+ 'function vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)',
+ ' -> boolean',
+ '```',
+ '',
+ '---',
+ '',
+ ' Activates buffer-update events. Example:',
+ '',
+ '',
+ '',
+ ' ```lua',
+ ' events = {}',
+ ' vim.api.nvim_buf_attach(0, false, {',
+ ' on_lines = function(...)',
+ ' table.insert(events, {...})',
+ ' end,',
+ ' })',
+ ' ```',
+ '',
+ '',
+ ' @see `nvim_buf_detach()`',
+ ' @see `api-buffer-updates-lua`',
+ '',
+ -- For each @param/@return: #30695
+ -- - Separate each by one empty line.
+ -- - Remove all other blank lines.
+ '@*param* `buffer` — Buffer handle, or 0 for current buffer',
+ '',
+ '@*param* `send_buffer` — True if whole buffer.',
+ ' Else the first notification will be `nvim_buf_changedtick_event`.',
+ '',
+ '@*param* `opts` — Optional parameters.',
+ ' - on_lines: Lua callback. Args:',
+ ' - the string "lines"',
+ ' - buffer handle',
+ ' - b:changedtick',
+ '',
+ '@*return* — False if foo;',
+ ' otherwise True.',
+ '@see foo',
+ '@see bar',
+ }
+ eq(expected, r)
+ end)
+
+ describe('_normalize_markdown', function()
it('collapses consecutive blank lines', function()
local result = exec_lua(function()
local lines = {