From b721ab27e2e1951a48dd4ec58752f43af28c346b Mon Sep 17 00:00:00 2001
From: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Tue, 11 Apr 2023 18:19:11 -0700
Subject: [PATCH] Too many LSP server related commands.  Consolidate the LSP
 server commands into a single :LspServer command using sub-commands

---
 README.md                  |   3 +-
 autoload/lsp/handlers.vim  |  17 +----
 autoload/lsp/lsp.vim       | 110 ++++++++++++++++++++++++++----
 autoload/lsp/lspserver.vim |  13 ++++
 doc/lsp.txt                | 133 +++++++++++++++++++++----------------
 plugin/lsp.vim             |  40 +----------
 test/run_tests.sh          |  11 ++-
 7 files changed, 198 insertions(+), 129 deletions(-)

diff --git a/README.md b/README.md
index 9f66a90..2abe07e 100644
--- a/README.md
+++ b/README.md
@@ -187,9 +187,8 @@ Command|Description
 :LspSelectionExpand|Expand the current symbol range visual selection.
 :LspSelectionShrink|Shrink the current symbol range visual selection.
 :LspShowAllServers|Display information about all the registered language servers.
-:LspServerRestart|Restart the language server for the current buffer.
+:LspServer|Display the capabilities or messages or status of the language server for the current buffer or restart the server.
 :LspShowReferences|Display the list of references to the keyword under cursor in a new location list.
-:LspShowServer|Display the capabilities or messages or status of the language server for the current buffer.
 :LspShowSignature|Display the signature of the keyword under cursor.
 :LspSubTypeHierarchy|Display the sub type hierarchy in a popup window.
 :LspSuperTypeHierarchy|Display the super type hierarchy in a popup window.
diff --git a/autoload/lsp/handlers.vim b/autoload/lsp/handlers.vim
index 42c0cbd..b4219f5 100644
--- a/autoload/lsp/handlers.vim
+++ b/autoload/lsp/handlers.vim
@@ -50,27 +50,14 @@ enddef
 # Param: LogMessageParams
 def ProcessLogMsgNotif(lspserver: dict<any>, reply: dict<any>)
   var mtype = LspMsgTypeToString(reply.params.type)
-  var msgs = reply.params.message->split("\n")
-
-  lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{mtype}]: {msgs[0]}')
-  lspserver.messages->extend(msgs[1 : ])
-  # Keep only the last 500 messages to reduce the memory usage
-  if lspserver.messages->len() > 500
-    lspserver.messages = lspserver.messages[-500 : ]
-  endif
+  lspserver.addMessage(mtype, reply.params.message)
 enddef
 
 # process the log trace notification messages
 # Notification: $/logTrace
 # Param: LogTraceParams
 def ProcessLogTraceNotif(lspserver: dict<any>, reply: dict<any>)
-  var msgs = reply.params.message->split("\n")
-  lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [trace]: {msgs[0]}')
-  lspserver.messages->extend(msgs[1 : ])
-  # Keep only the last 500 messages to reduce the memory usage
-  if lspserver.messages->len() > 500
-    lspserver.messages = lspserver.messages[-500 : ]
-  endif
+  lspserver.addMessage('trace', reply.params.message)
 enddef
 
 # process unsupported notification messages
diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim
index 01f5043..8eb97a27 100644
--- a/autoload/lsp/lsp.vim
+++ b/autoload/lsp/lsp.vim
@@ -83,9 +83,9 @@ def LspAddServer(ftype: string, lspsrv: dict<any>)
 enddef
 
 # Enable/disable the logging of the language server protocol messages
-export def ServerDebug(arg: string)
-  if arg !=? 'errors' && arg !=? 'messages' && arg !=? 'on' && arg !=? 'off'
-    util.ErrMsg($'Error: Invalid argument ("{arg}") for LSP server debug')
+def ServerDebug(arg: string)
+  if ['errors', 'messages', 'off', 'on']->index(arg) == -1
+    util.ErrMsg($'Error: Unsupported argument "{arg}" for LspServer debug')
     return
   endif
 
@@ -94,13 +94,13 @@ export def ServerDebug(arg: string)
     return
   endif
 
-  if arg ==? 'on'
+  if arg ==# 'on'
     util.ClearTraceLogs(lspserver.logfile)
     util.ClearTraceLogs(lspserver.errfile)
     lspserver.debug = true
-  elseif arg ==? 'off'
+  elseif arg ==# 'off'
     lspserver.debug = false
-  elseif arg ==? 'messages'
+  elseif arg ==# 'messages'
     util.ServerMessagesShow(lspserver.logfile)
   else
     util.ServerMessagesShow(lspserver.errfile)
@@ -173,7 +173,7 @@ def OpenScratchWindow(bname: string)
 enddef
 
 # Show the status of the LSP server for the current buffer
-export def ShowServer(arg: string)
+def ShowServer(arg: string)
   var lspserver: dict<any> = buf.CurbufGetServerChecked()
   if lspserver->empty()
     :echomsg "LSP Server not found"
@@ -182,7 +182,7 @@ export def ShowServer(arg: string)
 
   var windowName: string = ''
   var lines: list<string> = []
-  if arg == '' || arg ==? 'status'
+  if arg == '' || arg ==# 'status'
     windowName = $'LangServer-Status'
     var msg = $"LSP server '{lspserver.name}' is "
     if lspserver.running
@@ -191,13 +191,13 @@ export def ShowServer(arg: string)
       msg ..= 'not running'
     endif
     lines->add(msg)
-  elseif arg ==? 'capabilities'
+  elseif arg ==# 'capabilities'
     windowName = $'LangServer-Capabilities'
     lines->extend(lspserver.getCapabilities())
-  elseif arg ==? 'initializeRequest'
+  elseif arg ==# 'initializeRequest'
     windowName = $'LangServer-InitializeRequest'
     lines->extend(lspserver.getInitializeRequest())
-  elseif arg ==? 'messages'
+  elseif arg ==# 'messages'
     windowName = $'LangServer-Messages'
     lines->extend(lspserver.getMessages())
   else
@@ -497,7 +497,7 @@ def AddBuffersToLsp(ftype: string)
 enddef
 
 # Restart the LSP server for the current buffer
-export def RestartServer()
+def RestartServer()
   var lspserver: dict<any> = buf.CurbufGetServer()
   if lspserver->empty()
     return
@@ -636,7 +636,7 @@ enddef
 
 # set the LSP server trace level for the current buffer
 # Params: SetTraceParams
-export def ServerTraceSet(traceVal: string)
+def ServerTraceSet(traceVal: string)
   if ['off', 'messages', 'verbose']->index(traceVal) == -1
     util.ErrMsg($'Error: Unsupported LSP server trace value {traceVal}')
     return
@@ -1032,4 +1032,88 @@ export def RegisterCmdHandler(cmd: string, Handler: func)
   codeaction.RegisterCmdHandler(cmd, Handler)
 enddef
 
+# Command-line completion for the ":LspServer <cmd>" sub command
+def LspServerSubCmdComplete(cmds: list<string>, arglead: string, cmdline: string, cursorPos: number): list<string>
+  var wordBegin = cmdline->match('\s\+\zs\S', cursorPos)
+  if wordBegin == -1
+    return cmds
+  endif
+
+  # Make sure there are no additional sub-commands
+  var wordEnd = cmdline->stridx(' ', wordBegin)
+  if wordEnd == -1
+    return cmds->filter((_, val) => val =~ $'^{arglead}')
+  endif
+
+  return []
+enddef
+
+# Command-line completion for the ":LspServer debug" command
+def LspServerDebugComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+  return LspServerSubCmdComplete(['errors', 'messages', 'off', 'on'],
+				 arglead, cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer show" command
+def LspServerShowComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+  return LspServerSubCmdComplete(['capabilities', 'initializeRequest',
+				  'messages', 'status'], arglead, cmdline,
+				  cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer trace" command
+def LspServerTraceComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+  return LspServerSubCmdComplete(['messages', 'off', 'verbose'],
+				 arglead, cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer" command
+export def LspServerComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+  var wordBegin = -1
+  var wordEnd = -1
+  var l = ['debug', 'restart', 'show', 'trace']
+
+  # Skip the command name
+  var i = cmdline->stridx(' ', 0)
+  wordBegin = cmdline->match('\s\+\zs\S', i)
+  if wordBegin == -1
+    return l
+  endif
+
+  wordEnd = cmdline->stridx(' ', wordBegin)
+  if wordEnd == -1
+    return filter(l, (_, val) => val =~ $'^{arglead}')
+  endif
+
+  var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
+  if cmd ==# 'debug'
+    return LspServerDebugComplete(arglead, cmdline, wordEnd)
+  elseif cmd ==# 'restart'
+  elseif cmd ==# 'show'
+    return LspServerShowComplete(arglead, cmdline, wordEnd)
+  elseif cmd ==# 'trace'
+    return LspServerTraceComplete(arglead, cmdline, wordEnd)
+  endif
+
+  return []
+enddef
+
+# ":LspServer" command handler
+export def LspServerCmd(args: string)
+  if args->stridx('debug ') == 0
+    var subcmd = args[6 : ]->trim()
+    ServerDebug(subcmd)
+  elseif args ==# 'restart'
+    RestartServer()
+  elseif args->stridx('show ') == 0
+    var subcmd = args[5 : ]->trim()
+    ShowServer(subcmd)
+  elseif args->stridx('trace ') == 0
+    var subcmd = args[6 : ]->trim()
+    ServerTraceSet(subcmd)
+  else
+    util.ErrMsg($'Error: LspServer - Unsupported argument "{args}"')
+  endif
+enddef
+
 # vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim
index ba26d09..36bdd3f 100644
--- a/autoload/lsp/lspserver.vim
+++ b/autoload/lsp/lspserver.vim
@@ -1411,6 +1411,18 @@ def GetInitializeRequest(lspserver: dict<any>): list<string>
   return l
 enddef
 
+# Store a log or trace message received from the language server.
+def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
+  # A single message may contain multiple lines separate by newline
+  var msgs = newMsg->split("\n")
+  lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
+  lspserver.messages->extend(msgs[1 : ])
+  # Keep only the last 500 messages to reduce the memory usage
+  if lspserver.messages->len() >= 600
+    lspserver.messages = lspserver.messages[-500 : ]
+  endif
+enddef
+
 # Display the log messages received from the LSP server (window/logMessage)
 def GetMessages(lspserver: dict<any>): list<string>
   if lspserver.messages->empty()
@@ -1559,6 +1571,7 @@ export def NewLspServer(name_arg: string, path_arg: string, args: list<string>,
     workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
     getCapabilities: function(GetCapabilities, [lspserver]),
     getInitializeRequest: function(GetInitializeRequest, [lspserver]),
+    addMessage: function(AddMessage, [lspserver]),
     getMessages: function(GetMessages, [lspserver])
   })
 
diff --git a/doc/lsp.txt b/doc/lsp.txt
index d0fc36d..b97ae89 100644
--- a/doc/lsp.txt
+++ b/doc/lsp.txt
@@ -2,7 +2,7 @@
 
 Author: Yegappan Lakshmanan  (yegappan AT yahoo DOT com)
 For Vim version 9.0 and above
-Last change: April 7, 2023
+Last change: April 11, 2023
 
 ==============================================================================
 						*lsp-license*
@@ -121,15 +121,12 @@ The following commands are provided:
 :LspRename		Rename the current symbol
 :LspSelectionExpand	Expand the current symbol range visual selection
 :LspSelectionShrink	Shrink the current symbol range visual selection
-:LspServerDebug		Enable or disable the language server debug messages.
-:LspServerRestart	Restart the language server for the current buffer.
-:LspServerTrace		Set the language server debug trace value.
+:LspServer		Command to display the status and messages from a
+			language server and to restart the language server.
 :LspShowAllServers	Display the status of all the registered language
 			servers.
 :LspShowReferences	Display the list of references to the keyword under
 			cursor in a new location list.
-:LspShowServer		Display the capabilities, status and messages of the
-			language server for the current buffer.
 :LspShowSignature	Display the signature of the symbol under cursor.
 :LspSubTypeHierarchy	Display the sub type hierarchy in a popup window.
 :LspSuperTypeHierarchy	Display the super type hierarchy in a popup window.
@@ -743,36 +740,71 @@ can map these commands to keys and make it easier to invoke them.
 			successively to shrink the current symbol visual
 			region.
 
-						*:LspServerDebug*
-:LspServerDebug { on | off | messages | errors }
-			The following arguments are supported:
-
-			errors	Open the log file containing the language
-				server error messages.
-			messages
-				Open the log file containing the language
-				server debug messages.
-			off	Disable the logging of the language server
-				messages.
-			on	Enable the logging of the messages emitted
-				by the language server in the standard output
-				and standard error.
-			By default, the language server messages are not
-			logged.  On a Unix-like system, when enabled, these
-			messages are logged to the /tmp/lsp-<server-name>.log
-			and /tmp/lsp-<server-name>.err file respectively.  On
-			MS-Windows, the %TEMP%/lsp-<server-name>.log and
-			%TEMP%/lsp-<server-name>.err% files are used. See
-			|lsp-debug| for more information.
-
-						*:LspServerRestart*
-:LspServerRestart	Restart (stop and then start) the language server for
-			the current buffer. All the loaded buffers with the
-			same filetype as the current buffer are added back to
-			the server.
-
-:LspServerTrace { off | messages | verbose }	*:LspServerTrace*
-			Set the language server debug trace value.
+						*:LspServer*
+:LspServer { debug | restart | show | trace }
+			Command to display and control the language server for
+			the current buffer.  Each argument has additional
+			sub-commands which are described below.
+
+			debug { on | off | messages | errors }
+			    Command to enable or disable the language server
+			    debug messages and to display the debug messages
+			    and error messages received from the language
+			    server.  The following sub-commands are supported:
+				errors	Open the log file containing the
+					language server error messages.
+				messages
+					Open the log file containing the
+					language server debug messages.
+				off	Disable the logging of the language
+					server messages.
+				on	Enable the logging of the messages
+					emitted by the language server in the
+					standard output and standard error.
+			    By default, the language server messages are not
+			    logged.  On a Unix-like system, when enabled,
+			    these messages are logged to the
+			    /tmp/lsp-<server-name>.log and
+			    /tmp/lsp-<server-name>.err file respectively.  On
+			    MS-Windows, the %TEMP%/lsp-<server-name>.log and
+			    %TEMP%/lsp-<server-name>.err% files are used. See
+			    |lsp-debug| for more information.
+
+			restart
+			    Restart (stop and then start) the language server
+			    for the current buffer. All the loaded buffers
+			    with the same filetype as the current buffer are
+			    added back to the server.
+
+			show {capabilities | initializeRequest | messages
+								| status}
+			    The following sub-commands are supported:
+				capabilities
+					Display the list of language server
+					capabilities for the current buffer.
+					The server capabilities are described
+					in the LSP protocol specification
+					under the "ServerCapabilities"
+					interface.
+				initializeRequest
+					Display the contents of the language
+					server initialization request message
+					(initialize).
+				messages
+					Display the log messages received from
+					the language server.  This includes
+					the messages received using the
+					"window/logMessage" and "$/logTrace"
+					LSP notifications.
+				status
+					Display the language server status for
+					the current buffer.  The output shows
+					the path to the language server
+					executable and the server status.
+
+			trace { off | messages | verbose }
+			    Set the language server debug trace value using
+			    the "$/setTrace" command.
 
 						*:LspShowAllServers*
 :LspShowAllServers	Displays the list of registered language servers and
@@ -793,23 +825,6 @@ can map these commands to keys and make it easier to invoke them.
 
 				call LspOptionsSet({'useQuickfixForLocations': v:true})
 <
-						*:LspShowServer*
-:LspShowServer {capabilities | messages | status}
-			capabilities - Display the list of language server
-				       capabilities for the current buffer.
-				       The server capabilities are described
-				       in the LSP protocol specification under
-				       the "ServerCapabilities" interface.
-			messages     - Display the log messages received from
-				       the language server.  This includes the
-				       messages received using the
-				       "window/logMessage" and "$/logTrace"
-				       LSP notifications.
-			status       - Display the language server status for
-				       the current buffer.  The output shows
-				       the path to the language server
-				       executable and the server status.
-
 						*:LspShowSignature*
 :LspShowSignature	Displays the signature of the symbol (e.g. a function
 			or method) before the cursor in a popup.
@@ -1159,12 +1174,12 @@ and received by the plugin from the language server.  The following command
 enables the logging of the messages from the language server for the current
 buffer: >
 
-    :LspServerDebug on
+    :LspServer debug on
 <
 This command also clears the log files.  The following command disables the
 logging of the messages from the language server for the current buffer: >
 
-    :LspServerDebug off
+    :LspServer debug off
 <
 By default, the messages are not logged.  Another method to enable the debug
 is to set the "debug" field to v:true when adding a language server
@@ -1179,18 +1194,18 @@ created in the /tmp directory.  On MS-Windows, these files are created in the
 The following command opens the file containing the messages printed by the
 language server in the stdout: >
 
-    :LspServerDebug messages
+    :LspServer debug messages
 <
 The following command opens the file containing the messages printed by the
 language server in the stderr: >
 
-    :LspServerDebug errors
+    :LspServer debug errors
 <
 To debug language server initialization problems, after enabling the above
 server debug, you can restart the server for the file type in the current
 buffer using the following command: >
 
-    :LspServerRestart
+    :LspServer restart
 <
 The language servers typically support command line options to enable debug
 messages and to increase the verbosity of the messages.  You can refer to the
@@ -1200,7 +1215,7 @@ these options when registering the language server with this plugin.
 If a language server supports the "$/logTrace" LSP notification, then you can
 use the :LspServerTrace command to set the server trace value: >
 
-    :LspServerTrace { off | messages | verbose }
+    :LspServer trace { off | messages | verbose }
 <
 ==============================================================================
 14. Custom Command Handlers			*lsp-custom-commands*
diff --git a/plugin/lsp.vim b/plugin/lsp.vim
index 319a073..82650b2 100644
--- a/plugin/lsp.vim
+++ b/plugin/lsp.vim
@@ -7,12 +7,11 @@ vim9script
 
 # Language Server Protocol (LSP) plugin for vim
 
-g:loaded_lsp = 1
+g:loaded_lsp = true
 
 import '../autoload/lsp/options.vim'
 import autoload '../autoload/lsp/lsp.vim'
 
-
 # Set LSP plugin options from 'opts'.
 def g:LspOptionsSet(opts: dict<any>)
   options.OptionsSet(opts)
@@ -44,36 +43,6 @@ def g:LspServerRunning(ftype: string): bool
   return lsp.ServerRunning(ftype)
 enddef
 
-# Command line completion function for the LspServerTrace command.
-def LspServerTraceComplete(arglead: string, cmdline: string, cursorpos: number): list<string>
-  var l = ['off', 'messages', 'verbose']
-  if arglead->empty()
-    return l
-  else
-    return filter(l, (_, val) => val =~ arglead)
-  endif
-enddef
-
-# Command line completion function for the LspServerDebug command.
-def LspServerDebugComplete(arglead: string, cmdline: string, cursorpos: number): list<string>
-  var l = ['errors', 'messages', 'off', 'on']
-  if arglead->empty()
-    return l
-  else
-    return filter(l, (_, val) => val =~ arglead)
-  endif
-enddef
-
-# Command line completion function for the LspShowServer command.
-def LspShowServerComplete(arglead: string, cmdline: string, cursorpos: number): list<string>
-  var l = ['capabilities', 'initializeRequest', 'messages', 'status']
-  if arglead->empty()
-    return l
-  else
-    return filter(l, (_, val) => val =~ arglead)
-  endif
-enddef
-
 augroup LSPAutoCmds
   au!
   autocmd BufNewFile,BufReadPost * lsp.AddFile(expand('<abuf>')->str2nr())
@@ -118,13 +87,8 @@ command! -nargs=0 -bar LspPeekTypeDef lsp.GotoTypedef(v:true, <q-mods>)
 command! -nargs=? -bar LspRename lsp.Rename(<q-args>)
 command! -nargs=0 -bar LspSelectionExpand lsp.SelectionExpand()
 command! -nargs=0 -bar LspSelectionShrink lsp.SelectionShrink()
-command! -nargs=1 -complete=customlist,LspServerDebugComplete -bar LspServerDebug lsp.ServerDebug(<q-args>)
-command! -nargs=0 -bar LspServerRestart lsp.RestartServer()
-command! -nargs=1 -complete=customlist,LspServerTraceComplete -bar LspServerTrace lsp.ServerTraceSet(<q-args>)
+command! -nargs=+ -bar -complete=customlist,lsp.LspServerComplete LspServer lsp.LspServerCmd(<q-args>)
 command! -nargs=0 -bar LspShowReferences lsp.ShowReferences(v:false)
-# The :LspShowServerCapabilities command is retained for backward
-# compatibility.  Remove this in the future.
-command! -nargs=0 -bar LspShowServerCapabilities :LspShowServer capabilities
 command! -nargs=? -complete=customlist,LspShowServerComplete -bar LspShowServer lsp.ShowServer(<q-args>)
 command! -nargs=0 -bar LspShowAllServers lsp.ShowAllServers()
 command! -nargs=0 -bar LspShowSignature call LspShowSignature()
diff --git a/test/run_tests.sh b/test/run_tests.sh
index 151dcbd..45a19b7 100755
--- a/test/run_tests.sh
+++ b/test/run_tests.sh
@@ -2,7 +2,11 @@
 
 # Script to run the unit-tests for the LSP Vim plugin
 
-VIMPRG=${VIMPRG:=$(which vim)}
+#VIMPRG=${VIMPRG:=$(which vim)}
+export VIMPRG=/home/yega/bin/vim90/bin/vim
+export VIMRUNTIME=/home/yega/bin/vim90/share/vim/vim90
+#export VIMPRG=/home/yega/Documents/vim/vim9/vim/src/vim
+#export VIMRUNTIME=/home/yega/Documents/vim/vim9/vim/runtime
 if [ -z "$VIMPRG" ]; then
   echo "ERROR: vim (\$VIMPRG) is not found in PATH"
   exit 1
@@ -10,7 +14,10 @@ fi
 
 VIM_CMD="$VIMPRG -u NONE -U NONE -i NONE --noplugin -N --not-a-term"
 
-TESTS="clangd_tests.vim tsserver_tests.vim gopls_tests.vim not_lspserver_related_tests.vim"
+#TESTS="clangd_tests.vim tsserver_tests.vim gopls_tests.vim not_lspserver_related_tests.vim"
+TESTS="clangd_tests.vim"
+#TESTS="tsserver_tests.vim"
+#TESTS="gopls_tests.vim"
 
 for testfile in $TESTS
 do
-- 
2.51.0