diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index cc9e4e3690..423100fae8 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -92,18 +92,29 @@ end --- --- @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 +--- separating each, and before a codeblock). +--- 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 raw = vim.split(s, '\n', { plain = true, trimempty = true }) + + --- @return boolean # true if line begins a (4-space-indented) codeblock. #40860 + local function codeblock(l) + return l:find('^ ') or l:find('^\t') + end + 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 + for i, line in ipairs(raw) 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 + + local blank = line:find('^%s*$') + local keep_blank = blank and codeblock(raw[i + 1] or '') -- Keep if it precedes a codeblock. #40860 + if in_desc or not no_blank or not blank or keep_blank then table.insert(lines, line) end end diff --git a/test/functional/plugin/lsp/util_spec.lua b/test/functional/plugin/lsp/util_spec.lua index dd3e44cad4..3373b6abe4 100644 --- a/test/functional/plugin/lsp/util_spec.lua +++ b/test/functional/plugin/lsp/util_spec.lua @@ -1209,7 +1209,7 @@ describe('vim.lsp.util', 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', + 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\nExample:\n\n iex> params = %{title: "", topics: []}\n\n iex> changeset.changes[:topics]\n', } return vim.lsp.util.convert_input_to_markdown_lines(hover_data) end) @@ -1256,6 +1256,12 @@ describe('vim.lsp.util', function() ' otherwise True.', '@see foo', '@see bar', + -- Blank line preceding a 4-space-indented codeblock is required, per Markdown spec. #40860 + 'Example:', + '', + ' iex> params = %{title: "", topics: []}', + '', + ' iex> changeset.changes[:topics]', } eq(expected, r) end)