autoload/lsp/lsp.vim | 4 ++-- autoload/lsp/lspserver.vim | 21 +++++++++++++++++++-- autoload/lsp/symbol.vim | 61 +++++++++++++++++++++++++++++------------------------ autoload/lsp/util.vim | 9 +++++++-- doc/lsp.txt | 16 +++++++++++++--- plugin/lsp.vim | 2 +- test/clangd_offsetencoding.vim | 6 +++--- test/clangd_tests.vim | 4 ++-- diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index a2d7bede2fd332cb434d4b52a07722cf93dd7dbe..730527a1bc906ed62ad4494ba6d57408143ea67f 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -952,7 +952,7 @@ enddef # Perform a workspace wide symbol lookup # Uses LSP "workspace/symbol" request -export def SymbolSearch(queryArg: string) +export def SymbolSearch(queryArg: string, cmdmods: string) var lspserver: dict = buf.CurbufGetServerChecked() if lspserver->empty() return @@ -967,7 +967,7 @@ endif endif :redraw! - lspserver.workspaceQuery(query) + lspserver.workspaceQuery(query, true, cmdmods) enddef # Display the list of workspace folders diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index 03d89bd8414953d862576c75b501adfa8752a645..3ef58dce10aa1679dae1368642521eb15cdda0e0 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -1433,7 +1433,7 @@ # List project-wide symbols matching query string # Request: "workspace/symbol" # Param: WorkspaceSymbolParams -def WorkspaceQuerySymbols(lspserver: dict, query: string) +def WorkspaceQuerySymbols(lspserver: dict, query: string, firstCall: bool, cmdmods: string = '') # Check whether the LSP server supports listing workspace symbols if !lspserver.isWorkspaceSymbolProvider util.ErrMsg('LSP server does not support listing workspace symbols') @@ -1449,7 +1449,24 @@ util.WarnMsg($'Symbol "{query}" is not found') return endif - symbol.WorkspaceSymbolPopup(lspserver, query, reply.result) + var symInfo: list> = reply.result + + symInfo->map((_, sym) => { + if sym->has_key('location') + lspserver.decodeLocation(sym.location) + endif + return sym + }) + + if firstCall && symInfo->len() == 1 + # If there is only one symbol, then jump to the symbol location + var symLoc: dict = symInfo[0]->get('location', {}) + if !symLoc->empty() + symbol.GotoSymbol(lspserver, symLoc, false, cmdmods) + endif + else + symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods) + endif enddef # Add a workspace folder to the language server. diff --git a/autoload/lsp/symbol.vim b/autoload/lsp/symbol.vim index 177c761a35bb26499fb74a00b27fd9c1ef90c86e..ae193877a398248f02c196fe4032716c39c2a9c3 100644 --- a/autoload/lsp/symbol.vim +++ b/autoload/lsp/symbol.vim @@ -54,7 +54,7 @@ if update_popup # Update the popup with the new list of symbol names popupID->popup_settext('') if query != '' - lspserver.workspaceQuery(query) + lspserver.workspaceQuery(query, false) else []->setwinvar(popupID, 'LspSymbolTable') endif @@ -72,7 +72,7 @@ return popupID->popup_filter_menu(key) enddef # Jump to the location of a symbol selected in the popup menu -def JumpToWorkspaceSymbol(popupID: number, result: number): void +def JumpToWorkspaceSymbol(cmdmods: string, popupID: number, result: number): void # clear the message displayed at the command-line :echo '' @@ -91,32 +91,40 @@ util.PushCursorToTagStack() # if the selected file is already present in a window, then jump to it var fname: string = symTbl[result - 1].file - var bufnum = fname->bufnr() - var winList: list = bufnum->win_findbuf() - if winList->empty() - # Not present in any window - if &modified || &buftype != '' - # the current buffer is modified or is not a normal buffer, then open - # the file in a new window - exe $'split {symTbl[result - 1].file}' + var bnr = fname->bufnr() + if cmdmods->empty() + var winList: list = bnr->win_findbuf() + if winList->empty() + # Not present in any window + if &modified || &buftype != '' + # the current buffer is modified or is not a normal buffer, then + # open the file in a new window + exe $'split {symTbl[result - 1].file}' + else + exe $'confirm edit {symTbl[result - 1].file}' + endif else - exe $'confirm edit {symTbl[result - 1].file}' + # If the target buffer is opened in the curent window, then don't + # change the window. + if bufnr() != bnr + # If the target buffer is opened in a window in the current tab + # page, then use it. + var winID = fname->bufwinid() + if winID == -1 + # not present in the current tab page.  Use the first window. + winID = winList[0] + endif + winID->win_gotoid() + endif endif else - if bufnr() != bufnum - var winID = fname->bufwinid() - if winID == -1 - # not present in the current tab page - winID = winList[0] - endif - winID->win_gotoid() - endif + exe $'{cmdmods} split {symTbl[result - 1].file}' 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(symTbl[result - 1].pos.line + 1, - util.GetCharIdxWithoutCompChar(bufnum, + util.GetCharIdxWithoutCompChar(bnr, symTbl[result - 1].pos) + 1) catch # ignore exceptions @@ -124,7 +132,7 @@ endtry enddef # display a list of symbols from the workspace -def ShowSymbolMenu(lspserver: dict, query: string) +def ShowSymbolMenu(lspserver: dict, query: string, cmdmods: string) # Create the popup menu var lnum = &lines - &cmdheight - 2 - 10 var popupAttr = { @@ -141,7 +149,7 @@ mapping: false, fixed: 1, close: 'button', filter: function(FilterSymbols, [lspserver]), - callback: JumpToWorkspaceSymbol + callback: function('JumpToWorkspaceSymbol', [cmdmods]) } lspserver.workspaceSymbolPopup = popup_menu([], popupAttr) lspserver.workspaceSymbolQuery = query @@ -175,16 +183,15 @@ # process the 'workspace/symbol' reply from the LSP server # Result: SymbolInformation[] | null export def WorkspaceSymbolPopup(lspserver: dict, query: string, - symInfo: list>) + symInfo: list>, cmdmods: string) var symbols: list> = [] var symbolType: string var fileName: string - var r: dict> var symName: string # Create a symbol popup menu if it is not present if lspserver.workspaceSymbolPopup->winbufnr() == -1 - ShowSymbolMenu(lspserver, query) + ShowSymbolMenu(lspserver, query, cmdmods) endif for symbol in symInfo @@ -195,8 +202,6 @@ endif # interface SymbolInformation fileName = util.LspUriToFile(symbol.location.uri) - r = symbol.location.range - lspserver.decodeRange(fileName->bufnr(), r) symName = symbol.name if symbol->has_key('containerName') && symbol.containerName != '' @@ -209,7 +214,7 @@ fileName) symbols->add({name: symName, file: fileName, - pos: r.start}) + pos: symbol.location.range.start}) endfor symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable') lspserver.workspaceSymbolPopup->popup_settext( diff --git a/autoload/lsp/util.vim b/autoload/lsp/util.vim index a63953c6a3660553f17ea607a7c1d2c918fd2f32..023c83553e85d471bfb8b45048c5c9b92fb613ae 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -261,8 +261,8 @@ var [uri, range] = LspLocationParse(location) var fname = LspUriToFile(uri) # jump to the file and line containing the symbol + var bnr: number = fname->bufnr() if cmdmods->empty() - var bnr: number = fname->bufnr() if bnr == bufnr() # Set the previous cursor location mark. Instead of using setpos(), m' is # used so that the current location is added to the jump list. @@ -294,7 +294,12 @@ endif endif endif else - exe $'{cmdmods} split {fname}' + if bnr == -1 + exe $'{cmdmods} split {fname}' + else + # Use "sbuffer" so that the 'switchbuf' option settings are used. + exe $'{cmdmods} sbuffer {bnr}' + endif endif setcursorcharpos(range.start.line + 1, GetCharIdxWithoutCompChar(bufnr(), range.start) + 1) diff --git a/doc/lsp.txt b/doc/lsp.txt index 6c8b47f290c0f1e495934054ae643261b9fb267c..11857564918316fa05f88b8244e991fdfd70876d 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -1047,9 +1047,11 @@ *:LspSymbolSearch* :LspSymbolSearch Perform a workspace wide search for the symbol . If is not supplied, then you will be prompted to enter the symbol name (the keyword under the cursor is - used as the default). A popup window is opened with - the list of matching symbols. You can enter a few - characters to narrow down the list of matches. The + used as the default). If there is only one matching + symbol, then the cursor will be positioned at the + symbol location. Otherwise a popup window is opened + with the list of matching symbols. You can enter a + few characters to narrow down the list of matches. The displayed symbol name can be erased by pressing or and a new symbol search pattern can be entered. You can close the popup menu by @@ -1082,6 +1084,14 @@ matching the string are displayed in the popup menu. You can enter a new search pattern to do a workspace wide symbol search. + This command accepts |:command-modifiers| which can be + used to jump to a symbol in a horizontally or + vertically split window or a new tab page: > + + :topleft LspSymbolSearch foo + :vert LspSymbolSearch bar + :tab LspSymbolSearch baz +< *:LspWorkspaceAddFolder* :LspWorkspaceAddFolder {folder} Add a folder to the workspace diff --git a/plugin/lsp.vim b/plugin/lsp.vim index cc9856ed8f08968fa9d4eac7249616f80d469898..f38f97ab029e24d2eefae28160bfc69f9ae92700 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -96,7 +96,7 @@ command! -nargs=0 -bar LspSuperTypeHierarchy lsp.TypeHierarchy(1) # Clangd specifc extension to switch from one C/C++ source file to a # corresponding header file command! -nargs=0 -bar LspSwitchSourceHeader lsp.SwitchSourceHeader() -command! -nargs=? -bar LspSymbolSearch lsp.SymbolSearch() +command! -nargs=? -bar LspSymbolSearch lsp.SymbolSearch(, ) command! -nargs=1 -bar -complete=dir LspWorkspaceAddFolder lsp.AddWorkspaceFolder() command! -nargs=0 -bar LspWorkspaceListFolders lsp.ListWorkspaceFolders() command! -nargs=1 -bar -complete=dir LspWorkspaceRemoveFolder lsp.RemoveWorkspaceFolder() diff --git a/test/clangd_offsetencoding.vim b/test/clangd_offsetencoding.vim index c61b37c331fac4562cca7f51bfbfa92d55747951..0918ab91a3dc77ca9749cdc4301667c8256a893d 100644 --- a/test/clangd_offsetencoding.vim +++ b/test/clangd_offsetencoding.vim @@ -378,13 +378,13 @@ setline(1, lines) g:WaitForServerFileLoad(0) cursor(1, 1) - feedkeys(":LspSymbolSearch Func1\\", "xt") + feedkeys(":LspSymbolSearch Func1\", "xt") assert_equal([5, 18], [line('.'), col('.')]) cursor(1, 1) - feedkeys(":LspSymbolSearch Func2\\", "xt") + feedkeys(":LspSymbolSearch Func2\", "xt") assert_equal([9, 14], [line('.'), col('.')]) cursor(1, 1) - feedkeys(":LspSymbolSearch Func3\\", "xt") + feedkeys(":LspSymbolSearch Func3\", "xt") assert_equal([13, 22], [line('.'), col('.')]) :%bw! diff --git a/test/clangd_tests.vim b/test/clangd_tests.vim index 3c88ff0a9bf306a13d192a9b9d6af71a5eafbf48..443ff987664667bb4d50df8ef1999ef029e9f361 100644 --- a/test/clangd_tests.vim +++ b/test/clangd_tests.vim @@ -1178,7 +1178,7 @@ setline(1, lines) g:WaitForServerFileLoad(0) cursor(1, 1) - feedkeys(":LspSymbolSearch lsptest_funcB\\", "xt") + feedkeys(":LspSymbolSearch lsptest_funcB\", "xt") assert_equal([5, 6], [line('.'), col('.')]) cursor(1, 1) @@ -1186,7 +1186,7 @@ feedkeys(":LspSymbolSearch lsptest_func\\\\", "xt") assert_equal([9, 6], [line('.'), col('.')]) cursor(1, 1) - feedkeys(":LspSymbolSearch lsptest_funcA\\B\", "xt") + feedkeys(":LspSymbolSearch lsptest_func\A\B\", "xt") assert_equal([5, 6], [line('.'), col('.')]) var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")