Description
hover.lua's content extraction logic silently drops the actual hover text when an LSP server returns result.contents as a single MarkupContent object ({kind = "markdown", value = "..."}) rather than an array of MarkedString. This is a very common shape — e.g. pyright returns exactly this for stdlib symbols.
https://github.com/patrickpichler/hovercraft.nvim/blob/main/lua/hovercraft/provider/lsp/hover.lua#L86-L93
local res = result.contents
if type(res) == "table" and #res > 0 then
vim.list_extend(contents, res)
elseif (type(res) == "table" and res.value) or type(res) then
contents[#contents + 1] = res
end
The bug is in the second branch's condition: type(res) on a Lua table always returns the string "table", which is truthy. This means (type(res) == "table" and res.value) or type(res) is unconditionally true whenever res is a table, regardless of whether res.value exists. As a result, contents[#contents + 1] = res pushes the whole table (e.g. {kind="markdown", value="```python\n...\n```"}) as a single "content line," rather than res.value (the actual markdown text).
Downstream in ui.lua, vim.lsp.util.convert_input_to_markdown_lines expects array items to be strings or MarkedString-shaped ({language=, value=}) — not MarkupContent-shaped ({kind=, value=}) — so the real text is silently dropped. The popup ends up rendering nothing but whatever fallback/footer UI hovercraft draws (in my case, just a language icon with no code/text beneath it).
Steps to reproduce
- Use pyright (or any server returning a single non-array
MarkupContent) as the LSP client
- Hover (
K) over a stdlib symbol with a minimal hover response, e.g. time.monotonic
- Confirm via raw LSP request that the server is returning content:
vim.lsp.buf_request(0, 'textDocument/hover', vim.lsp.util.make_position_params(0, 'utf-16'), function(err, result)
vim.print(result)
end)
This returns:
{
contents = {
kind = "markdown",
value = "```python\n(function) def monotonic() -> float\n```"
},
...
}
- Observe hovercraft's popup shows no text for this hover, despite the LSP response containing content.
Expected
The signature/docstring text should render in the popup.
Suggested fix
local res = result.contents
if type(res) == "table" and #res > 0 then
vim.list_extend(contents, res)
elseif type(res) == "table" and res.value then
vim.list_extend(contents, vim.split(res.value, '\n', { trimempty = true }))
elseif type(res) == "string" then
contents[#contents + 1] = res
end
This correctly extracts res.value and splits it into lines instead of pushing the raw table. Verified locally that this fixes the missing-content issue.
Description
hover.lua's content extraction logic silently drops the actual hover text when an LSP server returnsresult.contentsas a singleMarkupContentobject ({kind = "markdown", value = "..."}) rather than an array ofMarkedString. This is a very common shape — e.g. pyright returns exactly this for stdlib symbols.https://github.com/patrickpichler/hovercraft.nvim/blob/main/lua/hovercraft/provider/lsp/hover.lua#L86-L93
The bug is in the second branch's condition:
type(res)on a Lua table always returns the string"table", which is truthy. This means(type(res) == "table" and res.value) or type(res)is unconditionally true wheneverresis a table, regardless of whetherres.valueexists. As a result,contents[#contents + 1] = respushes the whole table (e.g.{kind="markdown", value="```python\n...\n```"}) as a single "content line," rather thanres.value(the actual markdown text).Downstream in
ui.lua,vim.lsp.util.convert_input_to_markdown_linesexpects array items to be strings orMarkedString-shaped ({language=, value=}) — notMarkupContent-shaped ({kind=, value=}) — so the real text is silently dropped. The popup ends up rendering nothing but whatever fallback/footer UI hovercraft draws (in my case, just a language icon with no code/text beneath it).Steps to reproduce
MarkupContent) as the LSP clientK) over a stdlib symbol with a minimal hover response, e.g.time.monotonic{ contents = { kind = "markdown", value = "```python\n(function) def monotonic() -> float\n```" }, ... }Expected
The signature/docstring text should render in the popup.
Suggested fix
This correctly extracts
res.valueand splits it into lines instead of pushing the raw table. Verified locally that this fixes the missing-content issue.