mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	fix(lsp): handle multiline signature help labels #30460
This commit is contained in:
		 Maria José Solano
					Maria José Solano
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						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,7 +849,7 @@ 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. | ||||
| @@ -856,11 +868,10 @@ 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 | ||||
|     if type(parameter_label) == 'table' then | ||||
|       active_offset = parameter_label | ||||
|     else | ||||
|           local offset = 1 | ||||
|       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) | ||||
| @@ -874,18 +885,30 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers | ||||
|           break | ||||
|         end | ||||
|         if p == active_parameter + 1 then | ||||
|               active_hl = { offset - 1, offset + #parameter.label - 1 } | ||||
|           active_offset = { offset - 1, offset + #parameter_label - 1 } | ||||
|           break | ||||
|         end | ||||
|         offset = offset + #param.label + 1 | ||||
|       end | ||||
|     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