mirror of
https://github.com/neovim/neovim
synced 2025-07-15 16:51:49 +00:00
fix(lsp): handle multiline signature help labels #30460
This commit is contained in:
committed by
GitHub
parent
74a1614772
commit
e0a5c3bb58
@ -1855,7 +1855,7 @@ signature_help({_}, {result}, {ctx}, {config})
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
• {result} (`lsp.SignatureHelp`) Response from the language server
|
||||
• {result} (`lsp.SignatureHelp?`) Response from the language server
|
||||
• {ctx} (`lsp.HandlerContext`) Client context
|
||||
• {config} (`table`) Configuration table.
|
||||
• border: (default=nil)
|
||||
@ -1973,7 +1973,7 @@ convert_signature_help_to_markdown_lines({signature_help}, {ft}, {triggers})
|
||||
|
||||
Return (multiple): ~
|
||||
(`string[]?`) table list of lines of converted markdown.
|
||||
(`number[]?`) table of active hl
|
||||
(`Range4?`) table of active hl
|
||||
|
||||
See also: ~
|
||||
• https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
|
||||
|
@ -462,6 +462,8 @@ M[ms.textDocument_typeDefinition] = location_handler
|
||||
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
|
||||
M[ms.textDocument_implementation] = location_handler
|
||||
|
||||
local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help')
|
||||
|
||||
--- |lsp-handler| for the method "textDocument/signatureHelp".
|
||||
---
|
||||
--- The active parameter is highlighted with |hl-LspSignatureActiveParameter|.
|
||||
@ -476,7 +478,7 @@ M[ms.textDocument_implementation] = location_handler
|
||||
--- ```
|
||||
---
|
||||
---@param _ lsp.ResponseError?
|
||||
---@param result lsp.SignatureHelp Response from the language server
|
||||
---@param result lsp.SignatureHelp? Response from the language server
|
||||
---@param ctx lsp.HandlerContext Client context
|
||||
---@param config table Configuration table.
|
||||
--- - border: (default=nil)
|
||||
@ -509,10 +511,15 @@ function M.signature_help(_, result, ctx, config)
|
||||
return
|
||||
end
|
||||
local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
|
||||
-- Highlight the active parameter.
|
||||
if hl then
|
||||
-- Highlight the second line if the signature is wrapped in a Markdown code block.
|
||||
local line = vim.startswith(lines[1], '```') and 1 or 0
|
||||
api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', line, unpack(hl))
|
||||
vim.highlight.range(
|
||||
fbuf,
|
||||
sig_help_ns,
|
||||
'LspSignatureActiveParameter',
|
||||
{ hl[1], hl[2] },
|
||||
{ hl[3], hl[4] }
|
||||
)
|
||||
end
|
||||
return fbuf, fwin
|
||||
end
|
||||
|
@ -781,24 +781,37 @@ function M.convert_input_to_markdown_lines(input, contents)
|
||||
return contents
|
||||
end
|
||||
|
||||
--- Returns the line/column-based position in `contents` at the given offset.
|
||||
---
|
||||
---@param offset integer
|
||||
---@param contents string[]
|
||||
---@return { [1]: integer, [2]: integer }
|
||||
local function get_pos_from_offset(offset, contents)
|
||||
local i = 0
|
||||
for l, line in ipairs(contents) do
|
||||
if offset >= i and offset < i + #line then
|
||||
return { l - 1, offset - i + 1 }
|
||||
else
|
||||
i = i + #line + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts `textDocument/signatureHelp` response to markdown lines.
|
||||
---
|
||||
---@param signature_help lsp.SignatureHelp Response of `textDocument/SignatureHelp`
|
||||
---@param ft string|nil filetype that will be use as the `lang` for the label markdown code block
|
||||
---@param triggers table|nil list of trigger characters from the lsp server. used to better determine parameter offsets
|
||||
---@return string[]|nil table list of lines of converted markdown.
|
||||
---@return number[]|nil table of active hl
|
||||
---@return Range4|nil table of active hl
|
||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
|
||||
function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers)
|
||||
if not signature_help.signatures then
|
||||
return
|
||||
end
|
||||
--The active signature. If omitted or the value lies outside the range of
|
||||
--`signatures` the value defaults to zero or is ignored if `signatures.length == 0`.
|
||||
--Whenever possible implementors should make an active decision about
|
||||
--the active signature and shouldn't rely on a default value.
|
||||
local contents = {}
|
||||
local active_hl
|
||||
local active_offset ---@type [integer, integer]|nil
|
||||
local active_signature = signature_help.activeSignature or 0
|
||||
-- If the activeSignature is not inside the valid range, then clip it.
|
||||
-- In 3.15 of the protocol, activeSignature was allowed to be negative
|
||||
@ -806,9 +819,6 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
|
||||
active_signature = 0
|
||||
end
|
||||
local signature = signature_help.signatures[active_signature + 1]
|
||||
if not signature then
|
||||
return
|
||||
end
|
||||
local label = signature.label
|
||||
if ft then
|
||||
-- wrap inside a code block for proper rendering
|
||||
@ -825,6 +835,8 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
|
||||
M.convert_input_to_markdown_lines(signature.documentation, contents)
|
||||
end
|
||||
if signature.parameters and #signature.parameters > 0 then
|
||||
-- First check if the signature has an activeParameter. If it doesn't check if the response
|
||||
-- had that property instead. Else just default to 0.
|
||||
local active_parameter = (signature.activeParameter or signature_help.activeParameter or 0)
|
||||
if active_parameter < 0 then
|
||||
active_parameter = 0
|
||||
@ -837,8 +849,8 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
|
||||
end
|
||||
|
||||
local parameter = signature.parameters[active_parameter + 1]
|
||||
if parameter then
|
||||
--[=[
|
||||
local parameter_label = parameter.label
|
||||
--[=[
|
||||
--Represents a parameter of a callable-signature. A parameter can
|
||||
--have a label and a doc-comment.
|
||||
interface ParameterInformation {
|
||||
@ -856,36 +868,47 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
|
||||
documentation?: string | MarkupContent;
|
||||
}
|
||||
--]=]
|
||||
if parameter.label then
|
||||
if type(parameter.label) == 'table' then
|
||||
active_hl = parameter.label
|
||||
else
|
||||
local offset = 1
|
||||
-- try to set the initial offset to the first found trigger character
|
||||
for _, t in ipairs(triggers or {}) do
|
||||
local trigger_offset = signature.label:find(t, 1, true)
|
||||
if trigger_offset and (offset == 1 or trigger_offset < offset) then
|
||||
offset = trigger_offset
|
||||
end
|
||||
end
|
||||
for p, param in pairs(signature.parameters) do
|
||||
offset = signature.label:find(param.label, offset, true)
|
||||
if not offset then
|
||||
break
|
||||
end
|
||||
if p == active_parameter + 1 then
|
||||
active_hl = { offset - 1, offset + #parameter.label - 1 }
|
||||
break
|
||||
end
|
||||
offset = offset + #param.label + 1
|
||||
end
|
||||
if type(parameter_label) == 'table' then
|
||||
active_offset = parameter_label
|
||||
else
|
||||
local offset = 1 ---@type integer|nil
|
||||
-- try to set the initial offset to the first found trigger character
|
||||
for _, t in ipairs(triggers or {}) do
|
||||
local trigger_offset = signature.label:find(t, 1, true)
|
||||
if trigger_offset and (offset == 1 or trigger_offset < offset) then
|
||||
offset = trigger_offset
|
||||
end
|
||||
end
|
||||
if parameter.documentation then
|
||||
M.convert_input_to_markdown_lines(parameter.documentation, contents)
|
||||
for p, param in pairs(signature.parameters) do
|
||||
offset = signature.label:find(param.label, offset, true)
|
||||
if not offset then
|
||||
break
|
||||
end
|
||||
if p == active_parameter + 1 then
|
||||
active_offset = { offset - 1, offset + #parameter_label - 1 }
|
||||
break
|
||||
end
|
||||
offset = offset + #param.label + 1
|
||||
end
|
||||
end
|
||||
if parameter.documentation then
|
||||
M.convert_input_to_markdown_lines(parameter.documentation, contents)
|
||||
end
|
||||
end
|
||||
|
||||
local active_hl = nil
|
||||
if active_offset then
|
||||
active_hl = {}
|
||||
-- Account for the start of the markdown block.
|
||||
if ft then
|
||||
active_offset[1], active_offset[2] =
|
||||
active_offset[1] + #contents[1], active_offset[2] + #contents[1]
|
||||
end
|
||||
|
||||
list_extend(active_hl, get_pos_from_offset(active_offset[1], contents))
|
||||
list_extend(active_hl, get_pos_from_offset(active_offset[2], contents))
|
||||
end
|
||||
|
||||
return contents, active_hl
|
||||
end
|
||||
|
||||
|
@ -3519,6 +3519,30 @@ describe('LSP', function()
|
||||
local expected = { '```cs', 'TestEntity.TestEntity()', '```', 'some doc' }
|
||||
eq(expected, result)
|
||||
end)
|
||||
|
||||
it('highlights active parameters in multiline signature labels', function()
|
||||
local _, hl = exec_lua(function()
|
||||
local signature_help = {
|
||||
activeSignature = 0,
|
||||
signatures = {
|
||||
{
|
||||
activeParameter = 1,
|
||||
label = 'fn bar(\n _: void,\n _: void,\n) void',
|
||||
parameters = {
|
||||
{ label = '_: void' },
|
||||
{ label = '_: void' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'zig', { '(' })
|
||||
end)
|
||||
-- Note that although the higlight positions below are 0-indexed, the 2nd parameter
|
||||
-- corresponds to the 3rd line because the first line is the ``` from the
|
||||
-- Markdown block.
|
||||
local expected = { 3, 4, 3, 11 }
|
||||
eq(expected, hl)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('lsp.util.get_effective_tabstop', function()
|
||||
|
Reference in New Issue
Block a user