From 99bd714568374a9417554df53bf2305fbfd64079 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Sat, 2 Jan 2021 22:31:40 -0800 Subject: [PATCH] Use a popup menu for searching for workspace wide symbols --- autoload/handlers.vim | 56 ++++++++++++++++- autoload/lsp.vim | 139 +++++++++++++++++++++++++++++++++++++++-- autoload/lspserver.vim | 18 +++--- plugin/lsp.vim | 4 +- 4 files changed, 201 insertions(+), 16 deletions(-) diff --git a/autoload/handlers.vim b/autoload/handlers.vim index 91ff702..c5a97a5 100644 --- a/autoload/handlers.vim +++ b/autoload/handlers.vim @@ -778,14 +778,66 @@ def s:processWorkspaceExecuteReply(lspserver: dict, req: dict, reply: # Nothing to do for the reply enddef +# Convert a file name () format. +# Make sure the popup does't occupy the entire screen by reducing the width. +def s:makeMenuName(popupWidth: number, fname: string): string + var filename: string = fname->fnamemodify(':t') + var flen: number = filename->len() + var dirname: string = fname->fnamemodify(':h') + + if fname->len() > popupWidth && flen < popupWidth + # keep the full file name and reduce directory name length + # keep some characters at the beginning and end (equally). + # 6 spaces are used for "..." and " ()" + var dirsz = (popupWidth - flen - 6) / 2 + dirname = dirname[:dirsz] .. '...' .. dirname[-dirsz:] + endif + var str: string = filename + if dirname != '.' + str ..= ' (' .. dirname .. '/)' + endif + return str +enddef + # process the 'workspace/symbol' reply from the LSP server def s:processWorkspaceSymbolReply(lspserver: dict, req: dict, reply: dict) if reply.result->empty() - WarnMsg('Error: Symbol not found') return endif - s:showSymbols(reply.result, '') + var symbols: list> = [] + var symbolType: string + var fileName: string + var r: dict> + var symName: string + + for symbol in reply.result + if !symbol->has_key('location') + # ignore entries without location information + continue + endif + + # interface SymbolInformation + fileName = LspUriToFile(symbol.location.uri) + r = symbol.location.range + + symName = symbol.name + if symbol->has_key('containerName') && symbol.containerName != '' + symName = symbol.containerName .. '::' .. symName + endif + symName ..= ' [' .. LspSymbolKindToName(symbol.kind) .. ']' + symName ..= ' ' .. s:makeMenuName( + lspserver.workspaceSymbolPopup->popup_getpos().core_width, + fileName) + + symbols->add({name: symName, + file: fileName, + lnum: r.start.line + 1, + col: r.start.character + 1}) + endfor + symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable') + lspserver.workspaceSymbolPopup->popup_settext( + symbols->copy()->map('v:val.name')) enddef # Process various reply messages from the LSP server diff --git a/autoload/lsp.vim b/autoload/lsp.vim index 0309dd3..044504d 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -639,9 +639,130 @@ def lsp#codeAction() lspserver.codeAction(fname) enddef +# Handle keys pressed when the workspace symbol popup menu is displayed +def LspFilterNames(lspserver: dict, popupID: number, key: string): bool + var key_handled: bool = false + var update_popup: bool = false + var query: string = lspserver.workspaceSymbolQuery + + if key == "\" || key == "\" + # Erase one character from the filter text + if query->len() >= 1 + query = query[: -2] + update_popup = true + endif + key_handled = true + elseif key == "\" + # clear the filter text + query = '' + update_popup = true + key_handled = true + elseif key == "\" + || key == "\" + || key == "\" + || key == "\" + || key == "\" + || key == "\" + || key == "\" + || key == "\" + # scroll the popup window + var cmd: string = 'normal! ' .. (key == "\" ? 'j' : key == "\" ? 'k' : key) + cmd->win_execute(popupID) + key_handled = true + elseif key == "\" || key == "\" + # Use native Vim handling for these keys + key_handled = false + elseif key =~ '^\f$' || key == "\" + # Filter the names based on the typed key and keys typed before + query ..= key + update_popup = true + key_handled = true + endif + + if update_popup + # Update the popup with the new list of symbol names + popupID->popup_settext('') + if query != '' + lspserver.workspaceSymbols(query) + endif + echo 'Symbol: ' .. query + endif + + # Update the workspace symbol query string + lspserver.workspaceSymbolQuery = query + + if key_handled + return v:true + endif + + return popupID->popup_filter_menu(key) +enddef + +# Jump to the location of a symbol selected in the popup menu +def LspJumpToSymbol(popupID: number, result: number): void + # clear the message displayed at the command-line + echo '' + + if result <= 0 + # popup is canceled + return + endif + + var symTbl: list> = popupID->getwinvar('LspSymbolTable') + echomsg symTbl + try + # if the selected file is already present in a window, then jump to it + var fname: string = symTbl[result - 1].file + var winList: list = fname->bufnr()->win_findbuf() + if winList->len() == 0 + # 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 + winList[0]->win_gotoid() + endif + cursor(symTbl[result - 1].lnum, symTbl[result - 1].col) + catch + # ignore exceptions + endtry +enddef + +# display a list of symbols from the workspace +def s:showSymbolMenu(lspserver: dict, query: string) + # Create the popup menu + var lnum = &lines - &cmdheight - 2 - 10 + var popupAttr = { + title: 'Workspace Symbol Search', + wrap: 0, + pos: 'topleft', + line: lnum, + col: 2, + minwidth: 60, + minheight: 10, + maxheight: 10, + maxwidth: 60, + mapping: false, + fixed: 1, + close: "button", + filter: function('s:LspFilterNames', [lspserver]), + callback: LspJumpToSymbol + } + lspserver.workspaceSymbolPopup = popup_menu([], popupAttr) + lspserver.workspaceSymbolQuery = query + prop_type_add('lspworkspacesymbol', + {bufnr: lspserver.workspaceSymbolPopup->winbufnr(), + highlight: 'Title'}) + echo 'Symbol: ' .. query +enddef + # Perform a workspace wide symbol lookup # Uses LSP "workspace/symbol" request -def lsp#showWorkspaceSymbols() +def lsp#showWorkspaceSymbols(queryArg: string) var ftype = &filetype if ftype == '' return @@ -662,12 +783,20 @@ def lsp#showWorkspaceSymbols() return endif - var sym: string = input("Lookup symbol: ", expand('')) - if sym == '' - return + var query: string = queryArg + if query == '' + query = input("Lookup symbol: ", expand('')) + if query == '' + return + endif endif + redraw! - lspserver.workspaceSymbols(sym) + s:showSymbolMenu(lspserver, query) + + if !lspserver.workspaceSymbols(query) + lspserver.workspaceSymbolPopup->popup_close() + endif enddef # Display the list of workspace folders diff --git a/autoload/lspserver.vim b/autoload/lspserver.vim index 218aa7d..571779a 100644 --- a/autoload/lspserver.vim +++ b/autoload/lspserver.vim @@ -87,16 +87,16 @@ def s:initServer(lspserver: dict) }, completionItemKind: {valueSet: range(1, 25)} }, + hover: { + contentFormat: ['plaintext', 'markdown'] + }, documentSymbol: { hierarchicalDocumentSymbolSupport: v:true, symbolKind: {valueSet: range(1, 25)} }, - hover: { - contentFormat: ['plaintext', 'markdown'] - } }, window: {}, - general: {}, + general: {} } # interface 'InitializeParams' @@ -616,17 +616,19 @@ def s:codeAction(lspserver: dict, fname_arg: string) lspserver.sendMessage(req) enddef -def s:workspaceSymbols(lspserver: dict, sym: string) +def s:workspaceSymbols(lspserver: dict, sym: string): bool # Check whether the LSP server supports listing workspace symbols if !lspserver.caps->has_key('workspaceSymbolProvider') || !lspserver.caps.workspaceSymbolProvider ErrMsg("Error: LSP server does not support listing workspace symbols") - return + return false endif var req = lspserver.createRequest('workspace/symbol') req.params->extend({query: sym}) lspserver.sendMessage(req) + + return true enddef def s:addWorkspaceFolder(lspserver: dict, dirName: string): void @@ -720,7 +722,9 @@ export def NewLspServer(path: string, args: list): dict caps: {}, requests: {}, completePending: v:false, - diagsMap: {} + diagsMap: {}, + workspaceSymbolPopup: 0, + workspaceSymbolQuery: '' } # Add the LSP server functions lspserver->extend({ diff --git a/plugin/lsp.vim b/plugin/lsp.vim index b97d0f3..3f79709 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -29,13 +29,13 @@ command! -nargs=0 -bar LspShowDiagnostics call lsp#showDiagnostics() command! -nargs=0 -bar LspShowReferences call lsp#showReferences() command! -nargs=0 -bar LspHighlight call lsp#docHighlight() command! -nargs=0 -bar LspHighlightClear call lsp#docHighlightClear() -command! -nargs=0 -bar LspShowSymbols call lsp#showDocSymbols() +command! -nargs=0 -bar LspShowDocSymbols call lsp#showDocSymbols() command! -nargs=0 -bar -range=% LspFormat call lsp#textDocFormat(, , ) command! -nargs=0 -bar LspCalledBy call lsp#incomingCalls() command! -nargs=0 -bar LspCalling call lsp#outgoingCalls() command! -nargs=0 -bar LspRename call lsp#rename() command! -nargs=0 -bar LspCodeAction call lsp#codeAction() -command! -nargs=0 -bar LspShowWorkspaceSymbols call lsp#showWorkspaceSymbols() +command! -nargs=? -bar LspShowWorkspaceSymbols call lsp#showWorkspaceSymbols() command! -nargs=0 -bar LspWorkspaceListFolders call lsp#listWorkspaceFolders() command! -nargs=1 -bar -complete=dir LspWorkspaceAddFolder call lsp#addWorkspaceFolder() command! -nargs=1 -bar -complete=dir LspWorkspaceRemoveFolder call lsp#removeWorkspaceFolder() -- 2.48.1