autoload/lsp/lsp.vim | 30 +++++++++++++++--------------- autoload/lsp/lspserver.vim | 25 +++++++++++-------------- autoload/lsp/symbol.vim | 154 ++++++++++++++++++++++++++++++----------------------- doc/lsp.txt | 12 ++++++++++++ plugin/lsp.vim | 16 ++++++++-------- test/unit_tests.vim | 30 +++++++++++++++++++++++++----- diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index 97ef97663c3006111c7249be1f9c654c646089de..80a42bf017465022220ad046ec45cabc61af2057 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -134,54 +134,54 @@ return v:false enddef # Go to a definition using "textDocument/definition" LSP request -export def GotoDefinition(peek: bool) +export def GotoDefinition(peek: bool, cmdmods: string) var lspserver: dict = CurbufGetServerChecked() if lspserver->empty() return endif - lspserver.gotoDefinition(peek) + lspserver.gotoDefinition(peek, cmdmods) enddef -# Switch source header using "textDocument/switchSourceHeader" LSP request -# (Clangd specifc extension) -export def SwitchSourceHeader() +# Go to a declaration using "textDocument/declaration" LSP request +export def GotoDeclaration(peek: bool, cmdmods: string) var lspserver: dict = CurbufGetServerChecked() if lspserver->empty() return endif - lspserver.switchSourceHeader() + lspserver.gotoDeclaration(peek, cmdmods) enddef -# Go to a declaration using "textDocument/declaration" LSP request -export def GotoDeclaration(peek: bool) +# Go to a type definition using "textDocument/typeDefinition" LSP request +export def GotoTypedef(peek: bool, cmdmods: string) var lspserver: dict = CurbufGetServerChecked() if lspserver->empty() return endif - lspserver.gotoDeclaration(peek) + lspserver.gotoTypeDef(peek, cmdmods) enddef -# Go to a type definition using "textDocument/typeDefinition" LSP request -export def GotoTypedef(peek: bool) +# Go to a implementation using "textDocument/implementation" LSP request +export def GotoImplementation(peek: bool, cmdmods: string) var lspserver: dict = CurbufGetServerChecked() if lspserver->empty() return endif - lspserver.gotoTypeDef(peek) + lspserver.gotoImplementation(peek, cmdmods) enddef -# Go to a implementation using "textDocument/implementation" LSP request -export def GotoImplementation(peek: bool) +# Switch source header using "textDocument/switchSourceHeader" LSP request +# (Clangd specifc extension) +export def SwitchSourceHeader() var lspserver: dict = CurbufGetServerChecked() if lspserver->empty() return endif - lspserver.gotoImplementation(peek) + lspserver.switchSourceHeader() enddef # Show the signature using "textDocument/signatureHelp" LSP method diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index 14fb706a0e100764198e0964d71ec4df8503b006..b73d58eb8bcd522155a4307492228fa72d5441e9 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -801,7 +801,8 @@ # If 'peekSymbol' is true, then display the symbol location in the preview # window but don't jump to the symbol location. # # Result: Location | Location[] | LocationLink[] | null -def GotoSymbolLoc(lspserver: dict, msg: string, peekSymbol: bool) +def GotoSymbolLoc(lspserver: dict, msg: string, peekSymbol: bool, + cmdmods: string) var reply = lspserver.rpc(msg, GetLspTextDocPosition()) if reply->empty() || reply.result->empty() var emsg: string @@ -817,10 +818,6 @@ endif util.WarnMsg(emsg) return - endif - - if !peekSymbol - util.PushCursorToTagStack() endif var location: dict @@ -830,12 +827,12 @@ else location = reply.result endif - symbol.GotoSymbol(lspserver, location, peekSymbol) + symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods) enddef # Request: "textDocument/definition" # Param: DefinitionParams -def GotoDefinition(lspserver: dict, peek: bool) +def GotoDefinition(lspserver: dict, peek: bool, cmdmods: string) # Check whether LSP server supports jumping to a definition if !lspserver.caps->get('definitionProvider', false) util.ErrMsg("Error: Jumping to a symbol definition is not supported") @@ -844,12 +841,12 @@ endif # interface DefinitionParams # interface TextDocumentPositionParams - GotoSymbolLoc(lspserver, 'textDocument/definition', peek) + GotoSymbolLoc(lspserver, 'textDocument/definition', peek, cmdmods) enddef # Request: "textDocument/declaration" # Param: DeclarationParams -def GotoDeclaration(lspserver: dict, peek: bool): void +def GotoDeclaration(lspserver: dict, peek: bool, cmdmods: string) # Check whether LSP server supports jumping to a declaration if !lspserver.caps->get('declarationProvider', false) util.ErrMsg("Error: Jumping to a symbol declaration is not supported") @@ -858,12 +855,12 @@ endif # interface DeclarationParams # interface TextDocumentPositionParams - GotoSymbolLoc(lspserver, 'textDocument/declaration', peek) + GotoSymbolLoc(lspserver, 'textDocument/declaration', peek, cmdmods) enddef # Request: "textDocument/typeDefinition" # Param: TypeDefinitionParams -def GotoTypeDef(lspserver: dict, peek: bool): void +def GotoTypeDef(lspserver: dict, peek: bool, cmdmods: string) # Check whether LSP server supports jumping to a type definition if !lspserver.caps->get('typeDefinitionProvider', false) util.ErrMsg("Error: Jumping to a symbol type definition is not supported") @@ -872,12 +869,12 @@ endif # interface TypeDefinitionParams # interface TextDocumentPositionParams - GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek) + GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek, cmdmods) enddef # Request: "textDocument/implementation" # Param: ImplementationParams -def GotoImplementation(lspserver: dict, peek: bool): void +def GotoImplementation(lspserver: dict, peek: bool, cmdmods: string) # Check whether LSP server supports jumping to a implementation if !lspserver.caps->get('implementationProvider', false) util.ErrMsg("Error: Jumping to a symbol implementation is not supported") @@ -886,7 +883,7 @@ endif # interface ImplementationParams # interface TextDocumentPositionParams - GotoSymbolLoc(lspserver, 'textDocument/implementation', peek) + GotoSymbolLoc(lspserver, 'textDocument/implementation', peek, cmdmods) enddef # Request: "textDocument/switchSourceHeader" diff --git a/autoload/lsp/symbol.vim b/autoload/lsp/symbol.vim index d1282a66fd468474fbfdd4c64b8d46e1d1dbdce6..110170a04545bb4e40acac3433409d9584cff2a0 100644 --- a/autoload/lsp/symbol.vim +++ b/autoload/lsp/symbol.vim @@ -272,85 +272,103 @@ save_winid->win_gotoid() endif enddef -# Jump to the definition, declaration or implementation of a symbol. -# Also, used to peek at the definition, declaration or implementation of a -# symbol. -export def GotoSymbol(lspserver: dict, location: dict, peekSymbol: bool) - var fname = util.LspUriToFile(location.uri) - if peekSymbol - # open the definition/declaration in the preview window and highlight the - # matching symbol +# Display the symbol in file 'fname' at 'location' in a popup window. +def PeekSymbolLocation(lspserver: dict, fname: string, + location: dict) + var bnum = fname->bufadd() + if bnum == 0 + # Failed to create or find a buffer + return + endif + silent! bnum->bufload() - var bnum = bufadd(fname) - if bnum == 0 - # Failed to create or find a buffer - return - endif + if lspserver.peekSymbolPopup->winbufnr() != -1 + # If the symbol popup window is already present, close it. + lspserver.peekSymbolPopup->popup_close() + endif + var ptitle = $"{fnamemodify(fname, ':t')} ({fnamemodify(fname, ':h')})" + lspserver.peekSymbolPopup = popup_atcursor(bnum, {moved: 'any', + title: ptitle, + minwidth: 10, + maxwidth: 60, + minheight: 10, + maxheight: 10, + mapping: false, + wrap: false}) - if lspserver.peekSymbolPopup->winbufnr() != -1 - # If the symbol popup window is already present, close it. - lspserver.peekSymbolPopup->popup_close() - endif - var ptitle = $"{fnamemodify(fname, ':t')} ({fnamemodify(fname, ':h')})" - lspserver.peekSymbolPopup = popup_atcursor(bnum, {moved: 'any', - title: ptitle, - minwidth: 10, - maxwidth: 60, - minheight: 10, - maxheight: 10, - mapping: false, - wrap: false}) + # Highlight the symbol name and center the line in the popup + var pwid = lspserver.peekSymbolPopup + var pwbuf = winbufnr(pwid) + var pos: list = [] + var start_col: number + var end_col: number + start_col = util.GetLineByteFromPos(pwbuf, location.range.start) + 1 + end_col = util.GetLineByteFromPos(pwbuf, location.range.end) + 1 + pos->add(location.range.start.line + 1) + pos->extend([start_col, end_col - start_col]) + matchaddpos('Search', [pos], 10, 101, {window: pwid}) + var cmds =<< trim eval END + cursor({location.range.start.line + 1}, 1) + normal! z. + END + win_execute(pwid, cmds, 'silent!') +enddef - # Highlight the symbol name and center the line in the popup - var pwid = lspserver.peekSymbolPopup - var pwbuf = winbufnr(pwid) - var pos: list = [] - var start_col: number - var end_col: number - start_col = util.GetLineByteFromPos(pwbuf, location.range.start) + 1 - end_col = util.GetLineByteFromPos(pwbuf, location.range.end) + 1 - pos->add(location.range.start.line + 1) - pos->extend([start_col, end_col - start_col]) - matchaddpos('Search', [pos], 10, 101, {window: pwid}) - var cmds =<< trim eval END - cursor({location.range.start.line + 1}, 1) - normal! z. - END - win_execute(pwid, cmds, 'silent!') - else - # jump to the file and line containing the symbol +# Jump to the symbol in file 'fname' at 'location'. The user specified +# window command modifiers (e.g. topleft) are in 'cmdmods'. +def JumpToSymbolLocation(lspserver: dict, fname: string, + location: dict, cmdmods: string) + # jump to the file and line containing the symbol + if cmdmods == '' var bnr: number = fname->bufnr() if bnr != bufnr() var wid = fname->bufwinid() if wid != -1 - wid->win_gotoid() + wid->win_gotoid() else - if bnr != -1 - # Reuse an existing buffer. If the current buffer has unsaved changes - # and 'hidden' is not set or if the current buffer is a special - # buffer, then open the buffer in a new window. - if (&modified && !&hidden) || &buftype != '' - exe $'sbuffer {bnr}' - else - exe $'buf {bnr}' - endif - else - if (&modified && !&hidden) || &buftype != '' - # if the current buffer has unsaved changes and 'hidden' is not set, - # or if the current buffer is a special buffer, then open the file - # in a new window - exe $'split {fname}' - else - exe $'edit {fname}' - endif - endif + if bnr != -1 + # Reuse an existing buffer. If the current buffer has unsaved changes + # and 'hidden' is not set or if the current buffer is a special + # buffer, then open the buffer in a new window. + if (&modified && !&hidden) || &buftype != '' + exe $'sbuffer {bnr}' + else + exe $'buf {bnr}' + endif + else + if (&modified && !&hidden) || &buftype != '' + # if the current buffer has unsaved changes and 'hidden' is not set, + # or if the current buffer is a special buffer, then open the file + # in a new window + exe $'split {fname}' + else + exe $'edit {fname}' + endif + endif endif endif - # Set the previous cursor location mark. Instead of using setpos(), m' is - # used so that the current location is added to the jump list. - normal m' - setcursorcharpos(location.range.start.line + 1, + else + exe $'{cmdmods} split {fname}' + endif + # Set the previous cursor location mark. Instead of using setpos(), m' is + # used so that the current location is added to the jump list. + normal m' + setcursorcharpos(location.range.start.line + 1, location.range.start.character + 1) +enddef + +# Jump to the definition, declaration or implementation of a symbol. +# Also, used to peek at the definition, declaration or implementation of a +# symbol. +export def GotoSymbol(lspserver: dict, location: dict, + peekSymbol: bool, cmdmods: string) + var fname = util.LspUriToFile(location.uri) + if peekSymbol + PeekSymbolLocation(lspserver, fname, location) + else + # Save the current cursor location in the tag stack. + util.PushCursorToTagStack() + JumpToSymbolLocation(lspserver, fname, location, cmdmods) endif enddef diff --git a/doc/lsp.txt b/doc/lsp.txt index b987c3575d8667d891ccbf58070e672b30f7dc96..57c1e1155f06f7e6e68615c09e0f4b0b772f4276 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -304,9 +304,21 @@ is pushed onto the tag stack. The |CTRL-T| command can be used to go back up the tag stack. Also the |``| mark is set to the position before the jump. + This command supports |:command-modifiers|. You can + use the modifiers to specify whether a new window or + a new tab page is used and where the window is opened. + Example(s): > + # Open a horizontally split window + :topleft LspGotoDefinition + # Open a vertically split window + :vert LspGotoDefinition + # Open a new tab page + :tab LspGotoDefinition +< You may want to map a key to invoke this command: > nnoremap gd LspGotoDefinition + nnoremap gd topleft LspGotoDefinition < *:LspGotoDeclaration* :LspGotoDeclaration Jumps to the declaration of the symbol under the diff --git a/plugin/lsp.vim b/plugin/lsp.vim index d1d54b3ead1b8c7d09013e651fe9067205c94162..27b6250530d810c5e06333f5796d31b4d37abe01 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -70,14 +70,14 @@ command! -nargs=0 -bar LspShowServerCapabilities lsp.ShowServerCapabilities() command! -nargs=0 -bar LspServerRestart lsp.RestartServer() command! -nargs=1 -complete=customlist,LspServerTraceComplete -bar LspServerTrace lsp.ServerTraceSet() command! -nargs=1 -complete=customlist,LspServerDebugComplete -bar LspServerDebug lsp.ServerDebug() -command! -nargs=0 -bar LspGotoDefinition lsp.GotoDefinition(v:false) -command! -nargs=0 -bar LspGotoDeclaration lsp.GotoDeclaration(v:false) -command! -nargs=0 -bar LspGotoTypeDef lsp.GotoTypedef(v:false) -command! -nargs=0 -bar LspGotoImpl lsp.GotoImplementation(v:false) -command! -nargs=0 -bar LspPeekDefinition lsp.GotoDefinition(v:true) -command! -nargs=0 -bar LspPeekDeclaration lsp.GotoDeclaration(v:true) -command! -nargs=0 -bar LspPeekTypeDef lsp.GotoTypedef(v:true) -command! -nargs=0 -bar LspPeekImpl lsp.GotoImplementation(v:true) +command! -nargs=0 -bar LspGotoDefinition lsp.GotoDefinition(v:false, ) +command! -nargs=0 -bar LspGotoDeclaration lsp.GotoDeclaration(v:false, ) +command! -nargs=0 -bar LspGotoTypeDef lsp.GotoTypedef(v:false, ) +command! -nargs=0 -bar LspGotoImpl lsp.GotoImplementation(v:false, ) +command! -nargs=0 -bar LspPeekDefinition lsp.GotoDefinition(v:true, ) +command! -nargs=0 -bar LspPeekDeclaration lsp.GotoDeclaration(v:true, ) +command! -nargs=0 -bar LspPeekTypeDef lsp.GotoTypedef(v:true, ) +command! -nargs=0 -bar LspPeekImpl lsp.GotoImplementation(v:true, ) command! -nargs=0 -bar LspShowSignature call LspShowSignature() command! -nargs=0 -bar LspDiagShow lsp.ShowDiagnostics() command! -nargs=0 -bar LspDiagCurrent lsp.LspShowCurrentDiag() diff --git a/test/unit_tests.vim b/test/unit_tests.vim index 85ee435d5d7374f62e809fcb1606b3b84f805a02..3e8817a63508e22a4153f46b7e3717d31d75d00e 100644 --- a/test/unit_tests.vim +++ b/test/unit_tests.vim @@ -547,20 +547,37 @@ } END setline(1, lines) :sleep 1 + cursor(24, 6) :LspGotoDeclaration assert_equal([6, 19], [line('.'), col('.')]) exe "normal! \" assert_equal([24, 6], [line('.'), col('.')]) + assert_equal(1, winnr('$')) + :LspGotoDefinition assert_equal([9, 12], [line('.'), col('.')]) exe "normal! \" assert_equal([24, 6], [line('.'), col('.')]) - # FIXME: The following test is failing in Github CI - # :LspGotoImpl - # assert_equal([15, 11], [line('.'), col('.')]) - # exe "normal! \" - # assert_equal([24, 6], [line('.'), col('.')]) + assert_equal(1, winnr('$')) + + # Command modifiers + :topleft LspGotoDefinition + assert_equal([9, 12], [line('.'), col('.')]) + assert_equal([1, 2], [winnr(), winnr('$')]) + close + exe "normal! \" + assert_equal([24, 6], [line('.'), col('.')]) + + :tab LspGotoDefinition + assert_equal([9, 12], [line('.'), col('.')]) + assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')]) + tabclose + exe "normal! \" + assert_equal([24, 6], [line('.'), col('.')]) + + # FIXME: :LspGotoTypeDef and :LspGotoImpl are supported only with clang-14. + # This clangd version is not available in Github CI. # Error cases # FIXME: The following tests are failing in Github CI. Comment out for now. @@ -601,6 +618,9 @@ assert_equal(9, line('.', plist[0])) popup_clear() # tag stack should not be changed assert_fails("normal! \", 'E555:') + + # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14. + # This clangd version is not available in Github CI. :%bw!