From: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Tue, 18 Oct 2022 04:26:44 +0000 (-0700)
Subject: Use sync RPC for workspace symbol search
X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=97da893637254590715bdd3f0726f6cd23999a8d;p=vim-lsp.git

Use sync RPC for workspace symbol search
---

diff --git a/README.md b/README.md
index 8a94468..edc5ac1 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ The following language server protocol (LSP) features are supported:
 * Display code outline
 * Rename symbol
 * Display type and documentation on hover
-* Inlay hints
+* Signature help
 * Code action
 * Formatting code
 * Folding code
diff --git a/autoload/lsp/handlers.vim b/autoload/lsp/handlers.vim
index bdb2a5b..48cf696 100644
--- a/autoload/lsp/handlers.vim
+++ b/autoload/lsp/handlers.vim
@@ -284,19 +284,6 @@ def ProcessDocHighlightReply(lspserver: dict<any>, req: dict<any>, reply: dict<a
   endfor
 enddef
 
-# map the LSP symbol kind number to string
-def LspSymbolKindToName(symkind: number): string
-  var symbolMap: list<string> = ['', 'File', 'Module', 'Namespace', 'Package',
-	'Class', 'Method', 'Property', 'Field', 'Constructor', 'Enum',
-	'Interface', 'Function', 'Variable', 'Constant', 'String', 'Number',
-	'Boolean', 'Array', 'Object', 'Key', 'Null', 'EnumMember', 'Struct',
-	'Event', 'Operator', 'TypeParameter']
-  if symkind > 26
-    return ''
-  endif
-  return symbolMap[symkind]
-enddef
-
 # process SymbolInformation[]
 def ProcessSymbolInfoTable(symbolInfoTable: list<dict<any>>,
 				symbolTypeTable: dict<list<dict<any>>>,
@@ -309,7 +296,7 @@ def ProcessSymbolInfoTable(symbolInfoTable: list<dict<any>>,
 
   for symbol in symbolInfoTable
     fname = util.LspUriToFile(symbol.location.uri)
-    symbolType = LspSymbolKindToName(symbol.kind)
+    symbolType = symbol.SymbolKindToName(symbol.kind)
     name = symbol.name
     if symbol->has_key('containerName')
       if symbol.containerName != ''
@@ -340,7 +327,7 @@ def ProcessDocSymbolTable(docSymbolTable: list<dict<any>>,
 
   for symbol in docSymbolTable
     name = symbol.name
-    symbolType = LspSymbolKindToName(symbol.kind)
+    symbolType = symbol.SymbolKindToName(symbol.kind)
     r = symbol.range
     if symbol->has_key('detail')
       symbolDetail = symbol.detail
@@ -438,68 +425,6 @@ def ProcessWorkspaceExecuteReply(lspserver: dict<any>, req: dict<any>, reply: di
   # 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 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
-# Result: SymbolInformation[] | null
-def ProcessWorkspaceSymbolReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
-  var symbols: list<dict<any>> = []
-  var symbolType: string
-  var fileName: string
-  var r: dict<dict<number>>
-  var symName: string
-
-  if reply.result->empty()
-    return
-  endif
-
-  for symbol in reply.result
-    if !symbol->has_key('location')
-      # ignore entries without location information
-      continue
-    endif
-
-    # interface SymbolInformation
-    fileName = util.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 ..= ' ' .. MakeMenuName(
-		lspserver.workspaceSymbolPopup->popup_getpos().core_width,
-		fileName)
-
-    symbols->add({name: symName,
-			file: fileName,
-			pos: r.start})
-  endfor
-  symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable')
-  lspserver.workspaceSymbolPopup->popup_settext(
-				symbols->copy()->mapnew('v:val.name'))
-enddef
-
 # Process various reply messages from the LSP server
 export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
   var lsp_reply_handlers: dict<func> =
@@ -513,7 +438,6 @@ export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>):
       'textDocument/codeAction': ProcessCodeActionReply,
       'textDocument/foldingRange': ProcessFoldingRangeReply,
       'workspace/executeCommand': ProcessWorkspaceExecuteReply,
-      'workspace/symbol': ProcessWorkspaceSymbolReply,
     }
 
   if lsp_reply_handlers->has_key(req.method)
diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim
index 199a915..6fd28e2 100644
--- a/autoload/lsp/lsp.vim
+++ b/autoload/lsp/lsp.vim
@@ -764,11 +764,7 @@ export def SymbolSearch(queryArg: string)
   endif
   redraw!
 
-  symbol.ShowSymbolMenu(lspserver, query)
-
-  if !lspserver.workspaceQuery(query)
-    lspserver.workspaceSymbolPopup->popup_close()
-  endif
+  lspserver.workspaceQuery(query)
 enddef
 
 # Display the list of workspace folders
diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim
index 82a9e7f..e4fc267 100644
--- a/autoload/lsp/lspserver.vim
+++ b/autoload/lsp/lspserver.vim
@@ -917,19 +917,24 @@ enddef
 # List project-wide symbols matching query string
 # Request: "workspace/symbol"
 # Param: WorkspaceSymbolParams
-def WorkspaceQuerySymbols(lspserver: dict<any>, query: string): bool
+def WorkspaceQuerySymbols(lspserver: dict<any>, query: string)
   # Check whether the LSP server supports listing workspace symbols
   if !lspserver.caps->has_key('workspaceSymbolProvider')
 				|| !lspserver.caps.workspaceSymbolProvider
     util.ErrMsg("Error: LSP server does not support listing workspace symbols")
-    return false
+    return
   endif
 
-  var req = lspserver.createRequest('workspace/symbol')
-  req.params->extend({query: query})
-  lspserver.sendMessage(req)
+  # Param: WorkspaceSymbolParams
+  var param = {}
+  param.query = query
+  var reply = lspserver.rpc('workspace/symbol', param)
+  if reply->empty() || reply.result->empty()
+    util.WarnMsg($'Error: Symbol "{query}" is not found')
+    return
+  endif
 
-  return true
+  symbol.WorkspaceSymbolPopup(lspserver, query, reply.result)
 enddef
 
 # Add a workspace folder to the LSP server.
@@ -1099,7 +1104,7 @@ export def NewLspServer(path: string, args: list<string>, isSync: bool, initiali
     completionTriggerChars: [],
     signaturePopup: -1,
     diagsMap: {},
-    workspaceSymbolPopup: 0,
+    workspaceSymbolPopup: -1,
     workspaceSymbolQuery: '',
     callHierarchyType: '',
     selection: {}
diff --git a/autoload/lsp/symbol.vim b/autoload/lsp/symbol.vim
index 57e9809..ae293dc 100644
--- a/autoload/lsp/symbol.vim
+++ b/autoload/lsp/symbol.vim
@@ -103,6 +103,9 @@ def JumpToWorkspaceSymbol(popupID: number, result: number): void
     else
       winList[0]->win_gotoid()
     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,
 			symTbl[result - 1].pos.character + 1)
   catch
@@ -111,7 +114,7 @@ def JumpToWorkspaceSymbol(popupID: number, result: number): void
 enddef
 
 # display a list of symbols from the workspace
-export def ShowSymbolMenu(lspserver: dict<any>, query: string)
+def ShowSymbolMenu(lspserver: dict<any>, query: string)
   # Create the popup menu
   var lnum = &lines - &cmdheight - 2 - 10
   var popupAttr = {
@@ -138,6 +141,83 @@ export def ShowSymbolMenu(lspserver: dict<any>, query: string)
   echo $'Symbol: {query}'
 enddef
 
+# Convert a file name to <filename> (<dirname>) format.
+# Make sure the popup does't occupy the entire screen by reducing the width.
+def 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
+# Result: SymbolInformation[] | null
+export def WorkspaceSymbolPopup(lspserver: dict<any>, query: string,
+				symInfo: list<dict<any>>)
+  var symbols: list<dict<any>> = []
+  var symbolType: string
+  var fileName: string
+  var r: dict<dict<number>>
+  var symName: string
+
+  # Create a symbol popup menu if it is not present
+  if lspserver.workspaceSymbolPopup->winbufnr() == -1
+    ShowSymbolMenu(lspserver, query)
+  endif
+
+  for symbol in symInfo
+    if !symbol->has_key('location')
+      # ignore entries without location information
+      continue
+    endif
+
+    # interface SymbolInformation
+    fileName = util.LspUriToFile(symbol.location.uri)
+    r = symbol.location.range
+
+    symName = symbol.name
+    if symbol->has_key('containerName') && symbol.containerName != ''
+      symName = $'{symbol.containerName}::{symName}'
+    endif
+    symName ..= $' [{SymbolKindToName(symbol.kind)}]'
+    symName ..= ' ' .. MakeMenuName(
+		lspserver.workspaceSymbolPopup->popup_getpos().core_width,
+		fileName)
+
+    symbols->add({name: symName,
+			file: fileName,
+			pos: r.start})
+  endfor
+  symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable')
+  lspserver.workspaceSymbolPopup->popup_settext(
+				symbols->copy()->mapnew('v:val.name'))
+enddef
+
+# map the LSP symbol kind number to string
+export def SymbolKindToName(symkind: number): string
+  var symbolMap: list<string> = ['', 'File', 'Module', 'Namespace', 'Package',
+	'Class', 'Method', 'Property', 'Field', 'Constructor', 'Enum',
+	'Interface', 'Function', 'Variable', 'Constant', 'String', 'Number',
+	'Boolean', 'Array', 'Object', 'Key', 'Null', 'EnumMember', 'Struct',
+	'Event', 'Operator', 'TypeParameter']
+  if symkind > 26
+    return ''
+  endif
+  return symbolMap[symkind]
+enddef
+
 # Display or peek symbol references in a location list
 export def ShowReferences(lspserver: dict<any>, refs: list<dict<any>>, peekSymbol: bool)
   if refs->empty()
diff --git a/doc/lsp.txt b/doc/lsp.txt
index 3d07de7..22152a2 100644
--- a/doc/lsp.txt
+++ b/doc/lsp.txt
@@ -472,9 +472,11 @@ diagnostic messages, you can add the following line to your .vimrc file:
 			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. You can
-			close the popup menu by pressing the escape key or by
-			pressing CTRL-C.
+			characters to narrow down the list of matches. The
+			displayed symbol name can be erased by pressing
+			<Backspace> or <C-U> and a new symbol search pattern
+			can be entered.  You can close the popup menu by
+			pressing the escape key or by pressing CTRL-C.
 
 			In the popup menu, the following keys can be used:
 
diff --git a/test/unit_tests.vim b/test/unit_tests.vim
index 3abc4e5..54cf016 100644
--- a/test/unit_tests.vim
+++ b/test/unit_tests.vim
@@ -672,6 +672,44 @@ def Test_LspShowSignature()
   :%bw!
 enddef
 
+# Test for :LspSymbolSearch
+def Test_LspSymbolSearch()
+  silent! edit Xtest.c
+  sleep 200m
+  var lines: list<string> =<< trim END
+    void lsptest_funcA()
+    {
+    }
+
+    void lsptest_funcB()
+    {
+    }
+
+    void lsptest_funcC()
+    {
+    }
+  END
+  setline(1, lines)
+  :sleep 1
+
+  cursor(1, 1)
+  feedkeys(":LspSymbolSearch lsptest_funcB\<CR>\<CR>", "xt")
+  assert_equal([5, 6], [line('.'), col('.')])
+
+  cursor(1, 1)
+  feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt")
+  assert_equal([9, 6], [line('.'), col('.')])
+
+  cursor(1, 1)
+  feedkeys(":LspSymbolSearch lsptest_funcA\<CR>\<BS>B\<CR>", "xt")
+  assert_equal([5, 6], [line('.'), col('.')])
+
+  var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")
+  assert_equal(['Error: Symbol "lsptest_nonexist" is not found'], output)
+
+  :%bw!
+enddef
+
 # Test for :LspIncomingCalls
 def Test_LspIncomingCalls()
   silent! edit Xtest.c