autoload/lsp/lsp.vim | 20 ++++++++++++++------ autoload/lsp/lspserver.vim | 32 +++++++++++++++++++++++++++++++- autoload/lsp/symbol.vim | 18 ++++++++++++++++++ doc/lsp.txt | 18 +++++++++++++++++- test/unit_tests.vim | 34 ++++++++++++++++++++++++++++++++++ diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index 5d5d6dc952c5b39e41aa38d233d54afb358a62aa..d7b3cd2d476ff7d6bef1987679e24fa17b75356b 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -201,7 +201,7 @@ # Return the diagnostic text from the LSP server for the current mouse line to # display in a balloon var lspDiagPopupID: number = 0 var lspDiagPopupInfo: dict = {} -def g:LspDiagExpr(): string +def g:LspDiagExpr(): any var lspserver: dict = buf.BufLspServerGet(v:beval_bufnr) if lspserver->empty() || !lspserver.running return '' @@ -209,10 +209,8 @@ endif # Display the diagnostic message only if the mouse is over the gutter for # the signs. - if opt.lspOptions.noDiagHoverOnLine - if v:beval_col >= 2 - return '' - endif + if opt.lspOptions.noDiagHoverOnLine && v:beval_col >= 2 + return '' endif var diagInfo: dict = lspserver.getDiagByLine(v:beval_bufnr, @@ -222,7 +220,7 @@ # No diagnostic for the current cursor location return '' endif - return diagInfo.message + return diagInfo.message->split("\n") enddef # Called after leaving insert mode. Used to process diag messages (if any) @@ -922,6 +920,16 @@ return endif lspserver.showCapabilities() +enddef + +# Function to use with the 'tagfunc' option. +export def TagFunc(pat: string, flags: string, info: dict): any + var lspserver: dict = CurbufGetServerChecked() + if lspserver->empty() + return v:null + endif + + return lspserver.tagFunc(pat, flags, info) enddef # vim: shiftwidth=2 softtabstop=2 diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index b562a2e5b06958f7cd0b18a443c8b36db12f6733..f53ab3c9f9bc3c61f8296602f8875b96689d9768 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -1114,6 +1114,35 @@ echo $'{k}: {lspserver.caps[k]->string()}' endfor enddef +# Send a 'textDocument/definition' request to the LSP server to get the +# location where the symbol under the cursor is defined and return a list of +# Dicts in a format accepted by the 'tagfunc' option. +# Returns null if the LSP server doesn't support getting the location of a +# symbol definition or the symbol is not defined. +def TagFunc(lspserver: dict, pat: string, flags: string, info: dict): any + # Check whether LSP server supports getting the location of a definition + if !lspserver.caps->has_key('definitionProvider') + || !lspserver.caps.definitionProvider + return null + endif + + # interface DefinitionParams + # interface TextDocumentPositionParams + var reply = lspserver.rpc('textDocument/definition', GetLspTextDocPosition()) + if reply->empty() || reply.result->empty() + return null + endif + + var taglocations: list> + if reply.result->type() == v:t_list + taglocations = reply.result + else + taglocations = [reply.result] + endif + + return symbol.TagFunc(lspserver, taglocations, pat) +enddef + export def NewLspServer(path: string, args: list, isSync: bool, initializationOptions: dict): dict var lspserver: dict = { path: path, @@ -1164,10 +1193,11 @@ sendInitializedNotif: function(SendInitializedNotif, [lspserver]), getCompletion: function(GetCompletion, [lspserver]), resolveCompletion: function(ResolveCompletion, [lspserver]), gotoDefinition: function(GotoDefinition, [lspserver]), - switchSourceHeader: function(SwitchSourceHeader, [lspserver]), gotoDeclaration: function(GotoDeclaration, [lspserver]), gotoTypeDef: function(GotoTypeDef, [lspserver]), gotoImplementation: function(GotoImplementation, [lspserver]), + tagFunc: function(TagFunc, [lspserver]), + switchSourceHeader: function(SwitchSourceHeader, [lspserver]), showSignature: function(ShowSignature, [lspserver]), didSaveFile: function(DidSaveFile, [lspserver]), hover: function(ShowHoverInfo, [lspserver]), diff --git a/autoload/lsp/symbol.vim b/autoload/lsp/symbol.vim index ae293dc0d6369548dc8cf6034789faa90ec651d7..9177b0083183cd1c3755b0479f4d0b9f3678e756 100644 --- a/autoload/lsp/symbol.vim +++ b/autoload/lsp/symbol.vim @@ -323,4 +323,22 @@ endif redraw! enddef +# Process the LSP server reply message for a 'textDocument/definition' request +# and return a list of Dicts in a format accepted by the 'tagfunc' option. +export def TagFunc(lspserver: dict, + taglocations: list>, + pat: string): list> + var retval: list> + + for tagloc in taglocations + var tagitem = {} + tagitem.name = pat + tagitem.filename = util.LspUriToFile(tagloc.uri) + tagitem.cmd = (tagloc.range.start.line + 1)->string() + retval->add(tagitem) + endfor + + return retval +enddef + # vim: shiftwidth=2 softtabstop=2 diff --git a/doc/lsp.txt b/doc/lsp.txt index 751c77ad13d1025f63d1ba6af7c3bbcdfd2623c5..78874bc3970c9f5c37352b394f1b4acbacca95af 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -678,7 +678,23 @@ message area instead, you can set the 'showDiagInPopup' option to false. By default this is set to true. ============================================================================== -8. Autocommands *lsp-autocmds* +8. Tag Function + +The |:LspGotoDefinition| command can be used jump to the location where a +symbol is defined. To jump to the symbol definition using the Vim +|tag-commands|, you can set the 'tagfunc' option to the 'lsp#lsp#TagFunc' +function: > + + setlocal tagfunc=lsp#lsp#TagFunc +< +After setting the above option, you can use |Ctrl-]| and other tag related +commands to jump to the symbol definition. + +Note that most of the language servers return only one symbol location even if +the symbol is defined in multiple places in the code. + +============================================================================== +9. Autocommands *lsp-autocmds* *LspAttached* LspAttached A |User| autocommand fired when the LSP client diff --git a/test/unit_tests.vim b/test/unit_tests.vim index 73f103ec16fdd8a2f46845f921eca9d4c82bf3da..5185de4e7feb19065a2122737c8f13279a10a00d 100644 --- a/test/unit_tests.vim +++ b/test/unit_tests.vim @@ -272,6 +272,7 @@ assert_equal([3, 14, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type]) assert_equal([5, 2, 'W'], [qfl[1].lnum, qfl[1].col, qfl[1].type]) assert_equal([7, 2, 'W'], [qfl[2].lnum, qfl[2].col, qfl[2].type]) close + g:LspOptionsSet({showDiagInPopup: false}) normal gg var output = execute('LspDiagCurrent')->split("\n") assert_equal('No diagnostic messages found for current line', output[0]) @@ -295,6 +296,7 @@ setline(1, ['void blueFunc()', '{', '}']) WaitForDiags(0) output = execute('LspDiagShow')->split("\n") assert_match('No diagnostic messages found for', output[0]) + g:LspOptionsSet({showDiagInPopup: true}) :%bw! enddef @@ -778,6 +780,38 @@ var bnum = winbufnr(1) assert_equal('LSP-Outline', bufname(bnum)) assert_equal(['Function', ' aFunc', ' bFunc'], getbufline(bnum, 4, '$')) :%bw! +enddef + +# Test for setting the 'tagfunc' +def Test_LspTagFunc() + var lines: list =<< trim END + void aFunc(void) + { + xFunc(); + } + + void bFunc(void) + { + xFunc(); + } + + void xFunc(void) + { + } + END + writefile(lines, 'Xtest.c') + :silent! edit Xtest.c + :sleep 1 + :setlocal tagfunc=lsp#lsp#TagFunc + cursor(3, 4) + :exe "normal \" + assert_equal([11, 1], [line('.'), col('.')]) + cursor(1, 1) + assert_fails('exe "normal \"', 'E433: No tags file') + + :set tagfunc& + :%bw! + delete('Xtest.c') enddef def LspRunTests()