]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Use a popup menu for searching for workspace wide symbols
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 3 Jan 2021 06:31:40 +0000 (22:31 -0800)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 3 Jan 2021 06:31:40 +0000 (22:31 -0800)
autoload/handlers.vim
autoload/lsp.vim
autoload/lspserver.vim
plugin/lsp.vim

index 91ff702f6c76bb7f3ba2d987b1cef8095dedd619..c5a97a504ae2b914ca19914d0bda730ebc321d76 100644 (file)
@@ -778,14 +778,66 @@ def s:processWorkspaceExecuteReply(lspserver: dict<any>, req: dict<any>, reply:
   # Nothing to do for the reply
 enddef
 
+# Convert a file name <filename> (<dirname>) 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<any>, req: dict<any>, reply: dict<any>)
   if reply.result->empty()
-    WarnMsg('Error: Symbol not found')
     return
   endif
 
-  s:showSymbols(reply.result, '')
+  var symbols: list<dict<any>> = []
+  var symbolType: string
+  var fileName: string
+  var r: dict<dict<number>>
+  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
index 0309dd36222fccce60082864f9ca4b423516c122..044504dbb5566d84ea5e199aa5638308eaa65395 100644 (file)
@@ -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<any>, popupID: number, key: string): bool
+  var key_handled: bool = false
+  var update_popup: bool = false
+  var query: string = lspserver.workspaceSymbolQuery
+
+  if key == "\<BS>" || key == "\<C-H>"
+    # Erase one character from the filter text
+    if query->len() >= 1
+      query = query[: -2]
+      update_popup = true
+    endif
+    key_handled = true
+  elseif key == "\<C-U>"
+    # clear the filter text
+    query = ''
+    update_popup = true
+    key_handled = true
+  elseif key == "\<C-F>"
+        || key == "\<C-B>"
+        || key == "\<PageUp>"
+        || key == "\<PageDown>"
+        || key == "\<C-Home>"
+        || key == "\<C-End>"
+        || key == "\<C-N>"
+        || key == "\<C-P>"
+    # scroll the popup window
+    var cmd: string = 'normal! ' .. (key == "\<C-N>" ? 'j' : key == "\<C-P>" ? 'k' : key)
+    cmd->win_execute(popupID)
+    key_handled = true
+  elseif key == "\<Up>" || key == "\<Down>"
+    # Use native Vim handling for these keys
+    key_handled = false
+  elseif key =~ '^\f$' || key == "\<Space>"
+    # 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<dict<any>> = 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<number> = 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<any>, 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('<cword>'))
-  if sym == ''
-    return
+  var query: string = queryArg
+  if query == ''
+    query = input("Lookup symbol: ", expand('<cword>'))
+    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
index 218aa7d047461c2e6b647d6d0fc6df9b59a8450a..571779aca24b43c166b98c207f2c79ff313e2686 100644 (file)
@@ -87,16 +87,16 @@ def s:initServer(lspserver: dict<any>)
        },
        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<any>, fname_arg: string)
   lspserver.sendMessage(req)
 enddef
 
-def s:workspaceSymbols(lspserver: dict<any>, sym: string)
+def s:workspaceSymbols(lspserver: dict<any>, 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<any>, dirName: string): void
@@ -720,7 +722,9 @@ export def NewLspServer(path: string, args: list<string>): dict<any>
     caps: {},
     requests: {},
     completePending: v:false,
-    diagsMap: {}
+    diagsMap: {},
+    workspaceSymbolPopup: 0,
+    workspaceSymbolQuery: ''
   }
   # Add the LSP server functions
   lspserver->extend({
index b97d0f30492d5b2d92204f0ab9f04f8b133978ec..3f797094f36f18878f323e3234afee8e6727a760 100644 (file)
@@ -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(<range>, <line1>, <line2>)
 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(<q-args>)
 command! -nargs=0 -bar LspWorkspaceListFolders call lsp#listWorkspaceFolders()
 command! -nargs=1 -bar -complete=dir LspWorkspaceAddFolder call lsp#addWorkspaceFolder(<q-args>)
 command! -nargs=1 -bar -complete=dir LspWorkspaceRemoveFolder call lsp#removeWorkspaceFolder(<q-args>)