]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Use callback functions for async RPC requests to simplify the code
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sat, 29 Oct 2022 19:35:21 +0000 (12:35 -0700)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Sat, 29 Oct 2022 19:35:21 +0000 (12:35 -0700)
autoload/lsp/diag.vim
autoload/lsp/handlers.vim
autoload/lsp/lsp.vim
autoload/lsp/lspserver.vim
autoload/lsp/symbol.vim
plugin/lsp.vim
test/unit_tests.vim

index acd64a568a3b0ef1aff9b5f8ec6f3c8fb48dc189..85870c974153cb92805f5574cbc1c1faae376b92 100644 (file)
@@ -112,26 +112,28 @@ enddef
 
 # get the count of error in the current buffer
 export def DiagsGetErrorCount(lspserver: dict<any>): dict<number>
-  var res = {'Error': 0, 'Warn': 0, 'Info': 0, 'Hint': 0}
+  var errCount = 0
+  var warnCount = 0
+  var infoCount = 0
+  var hintCount = 0
 
   var bnr: number = bufnr()
   if lspserver.diagsMap->has_key(bnr)
-      for item in lspserver.diagsMap[bnr]->values()
-          if item->has_key('severity')
-              if item.severity == 1
-                  res.Error = res.Error + 1
-              elseif item.severity == 2
-                  res.Warn = res.Warn + 1
-              elseif item.severity == 3
-                  res.Info = res.Info + 1
-              elseif item.severity == 4
-                  res.Hint = res.Hint + 1
-              endif
-          endif
-      endfor
+    for item in lspserver.diagsMap[bnr]->values()
+      var severity = item->get('severity', -1)
+      if severity == 1
+       errCount += 1
+      elseif severity == 2
+       warnCount += 1
+      elseif severity == 3
+       infoCount += 1
+      elseif severity == 4
+       hintCount += 1
+      endif
+    endfor
   endif
 
-  return res
+  return {'Error': errCount, 'Warn': warnCount, 'Info': infoCount, 'Hint': hintCount}
 enddef
 
 # Map the LSP DiagnosticSeverity to a quickfix type character
index 6d19b771736413422d488dbbd01e7e8a0ef75360..0c9a704096b079131f46632ebcce940a7900845a 100644 (file)
@@ -10,448 +10,10 @@ import './diag.vim'
 import './outline.vim'
 import './textedit.vim'
 import './symbol.vim'
-import './codeaction.vim'
-import './callhierarchy.vim' as callhier
-
-# process the 'initialize' method reply from the LSP server
-# Result: InitializeResult
-def ProcessInitializeReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
-  if reply.result->empty()
-    return
-  endif
-
-  var caps: dict<any> = reply.result.capabilities
-  lspserver.caps = caps
-
-  if opt.lspOptions.autoComplete && caps->has_key('completionProvider')
-    lspserver.completionTriggerChars = caps.completionProvider->get('triggerCharacters', [])
-    lspserver.completionLazyDoc =
-               lspserver.caps.completionProvider->has_key('resolveProvider')
-               && lspserver.caps.completionProvider.resolveProvider
-  endif
-
-  # send a "initialized" notification to server
-  lspserver.sendInitializedNotif()
-  lspserver.ready = true
-  if exists($'#User#LspServerReady{lspserver.name}')
-    exe $'doautocmd <nomodeline> User LspServerReady{lspserver.name}'
-  endif
-
-  # if the outline window is opened, then request the symbols for the current
-  # buffer
-  if bufwinid('LSP-Outline') != -1
-    lspserver.getDocSymbols(@%)
-  endif
-enddef
-
-# Map LSP complete item kind to a character
-def LspCompleteItemKindChar(kind: number): string
-  var kindMap: list<string> = ['',
-               't', # Text
-               'm', # Method
-               'f', # Function
-               'C', # Constructor
-               'F', # Field
-               'v', # Variable
-               'c', # Class
-               'i', # Interface
-               'M', # Module
-               'p', # Property
-               'u', # Unit
-               'V', # Value
-               'e', # Enum
-               'k', # Keyword
-               'S', # Snippet
-               'C', # Color
-               'f', # File
-               'r', # Reference
-               'F', # Folder
-               'E', # EnumMember
-               'd', # Contant
-               's', # Struct
-               'E', # Event
-               'o', # Operator
-               'T'  # TypeParameter
-       ]
-  if kind > 25
-    return ''
-  endif
-  return kindMap[kind]
-enddef
-
-# process the 'textDocument/completion' reply from the LSP server
-# Result: CompletionItem[] | CompletionList | null
-def ProcessCompletionReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
-  if reply.result->empty()
-    return
-  endif
-
-  var items: list<dict<any>>
-  if reply.result->type() == v:t_list
-    items = reply.result
-  else
-    items = reply.result.items
-  endif
-
-  var completeItems: list<dict<any>> = []
-  for item in items
-    var d: dict<any> = {}
-    if item->has_key('textEdit') && item.textEdit->has_key('newText')
-      d.word = item.textEdit.newText
-    elseif item->has_key('insertText')
-      d.word = item.insertText
-    else
-      d.word = item.label
-    endif
-    d.abbr = item.label
-    d.dup = 1
-    if item->has_key('kind')
-      # namespace CompletionItemKind
-      # map LSP kind to complete-item-kind
-      d.kind = LspCompleteItemKindChar(item.kind)
-    endif
-    if lspserver.completionLazyDoc
-      d.info = 'Lazy doc'
-    else
-      if item->has_key('detail')
-        # Solve a issue where if a server send a detail field
-        # with a "\n", on the menu will be everything joined with
-        # a "^@" separating it. (example: clangd)
-        d.menu = item.detail->split("\n")[0]
-      endif
-      if item->has_key('documentation')
-        if item.documentation->type() == v:t_string && item.documentation != ''
-          d.info = item.documentation
-        elseif item.documentation->type() == v:t_dict
-            && item.documentation.value->type() == v:t_string
-          d.info = item.documentation.value
-        endif
-      endif
-    endif
-    d.user_data = item
-    completeItems->add(d)
-  endfor
-
-  if opt.lspOptions.autoComplete && !lspserver.omniCompletePending
-    if completeItems->empty()
-      # no matches
-      return
-    endif
-
-    var m = mode()
-    if m != 'i' && m != 'R' && m != 'Rv'
-      # If not in insert or replace mode, then don't start the completion
-      return
-    endif
-
-    if completeItems->len() == 1
-       && getline('.')->matchstr(completeItems[0].word .. '\>') != ''
-      # only one complete match. No need to show the completion popup
-      return
-    endif
-
-    var start_col: number = 0
-
-    # FIXME: The following doesn't work with typescript as one of the
-    # completion item has a start column that is before the special character.
-    # For example, when completing the methods for "str.", the dot is removed.
-    #
-    # # Find the start column for the completion.  If any of the entries
-    # # returned by the LSP server has a starting position, then use that.
-    # for item in items
-    #   if item->has_key('textEdit')
-    #     start_col = item.textEdit.range.start.character + 1
-    #     break
-    #   endif
-    # endfor
-
-    # LSP server didn't return a starting position for completion, search
-    # backwards from the current cursor position for a non-keyword character.
-    if start_col == 0
-      var line: string = getline('.')
-      var start = col('.') - 1
-      while start > 0 && line[start - 1] =~ '\k'
-       start -= 1
-      endwhile
-      start_col = start + 1
-    endif
-
-    complete(start_col, completeItems)
-  else
-    lspserver.completeItems = completeItems
-    lspserver.omniCompletePending = false
-  endif
-enddef
-
-# process the 'completionItem/resolve' reply from the LSP server
-# Result: CompletionItem
-def ProcessResolveReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
-  if reply.result->empty()
-    return
-  endif
-
-  # check if completion item is still selected
-  var cInfo = complete_info()
-  if cInfo->empty()
-      || !cInfo.pum_visible
-      || cInfo.selected == -1
-      || cInfo.items[cInfo.selected].user_data.label != reply.result.label
-    return
-  endif
-
-  var infoText: list<string>
-  var infoKind: string
-
-  if reply.result->has_key('detail')
-    # Solve a issue where if a server send the detail field with "\n",
-    # on the completion popup, everything will be joined with "^@"
-    # (example: typescript-language-server)
-    infoText->extend(reply.result.detail->split("\n"))
-  endif
-
-  if reply.result->has_key('documentation')
-    if !infoText->empty()
-      infoText->extend(['- - -'])
-    endif
-    if reply.result.documentation->type() == v:t_dict
-      # MarkupContent
-      if reply.result.documentation.kind == 'plaintext'
-        infoText->extend(reply.result.documentation.value->split("\n"))
-        infoKind = 'text'
-      elseif reply.result.documentation.kind == 'markdown'
-        infoText->extend(reply.result.documentation.value->split("\n"))
-        infoKind = 'markdown'
-      else
-        util.ErrMsg($'Error: Unsupported documentation type ({reply.result.documentation.kind})')
-        return
-      endif
-    elseif reply.result.documentation->type() == v:t_string
-      infoText->extend(reply.result.documentation->split("\n"))
-    else
-      util.ErrMsg($'Error: Unsupported documentation ({reply.result.documentation->string()})')
-      return
-    endif
-  endif
-
-  if infoText->empty()
-    return
-  endif
-
-  # check if completion item is changed in meantime
-  cInfo = complete_info()
-  if cInfo->empty()
-      || !cInfo.pum_visible
-      || cInfo.selected == -1
-      || cInfo.items[cInfo.selected].user_data.label != reply.result.label
-    return
-  endif
-
-  var id = popup_findinfo()
-  if id > 0
-    var bufnr = id->winbufnr()
-    infoKind->setbufvar(bufnr, '&ft')
-    if infoKind == 'markdown'
-      3->setwinvar(id, '&conceallevel')
-    else
-      0->setwinvar(id, '&conceallevel')
-    endif
-    id->popup_settext(infoText)
-    id->popup_show()
-  endif
-enddef
-
-# process the 'textDocument/documentHighlight' reply from the LSP server
-# Result: DocumentHighlight[] | null
-def ProcessDocHighlightReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
-  if reply.result->empty()
-    return
-  endif
-
-  var fname: string = util.LspUriToFile(req.params.textDocument.uri)
-  var bnr = fname->bufnr()
-
-  for docHL in reply.result
-    var kind: number = docHL->get('kind', 1)
-    var propName: string
-    if kind == 2
-      # Read-access
-      propName = 'LspReadRef'
-    elseif kind == 3
-      # Write-access
-      propName = 'LspWriteRef'
-    else
-      # textual reference
-      propName = 'LspTextRef'
-    endif
-    prop_add(docHL.range.start.line + 1,
-               util.GetLineByteFromPos(bnr, docHL.range.start) + 1,
-               {end_lnum: docHL.range.end.line + 1,
-                 end_col: util.GetLineByteFromPos(bnr, docHL.range.end) + 1,
-                 bufnr: bnr,
-                 type: propName})
-  endfor
-enddef
-
-# process SymbolInformation[]
-def ProcessSymbolInfoTable(symbolInfoTable: list<dict<any>>,
-                               symbolTypeTable: dict<list<dict<any>>>,
-                               symbolLineTable: list<dict<any>>)
-  var fname: string
-  var symbolType: string
-  var name: string
-  var r: dict<dict<number>>
-  var symInfo: dict<any>
-
-  for syminfo in symbolInfoTable
-    fname = util.LspUriToFile(syminfo.location.uri)
-    symbolType = symbol.SymbolKindToName(syminfo.kind)
-    name = syminfo.name
-    if syminfo->has_key('containerName')
-      if syminfo.containerName != ''
-       name ..= $' [{syminfo.containerName}]'
-      endif
-    endif
-    r = syminfo.location.range
-
-    if !symbolTypeTable->has_key(symbolType)
-      symbolTypeTable[symbolType] = []
-    endif
-    symInfo = {name: name, range: r}
-    symbolTypeTable[symbolType]->add(symInfo)
-    symbolLineTable->add(symInfo)
-  endfor
-enddef
-
-# process DocumentSymbol[]
-def ProcessDocSymbolTable(docSymbolTable: list<dict<any>>,
-                               symbolTypeTable: dict<list<dict<any>>>,
-                               symbolLineTable: list<dict<any>>)
-  var symbolType: string
-  var name: string
-  var r: dict<dict<number>>
-  var symInfo: dict<any>
-  var symbolDetail: string
-  var childSymbols: dict<list<dict<any>>>
-
-  for syminfo in docSymbolTable
-    name = syminfo.name
-    symbolType = symbol.SymbolKindToName(syminfo.kind)
-    r = syminfo.range
-    if syminfo->has_key('detail')
-      symbolDetail = syminfo.detail
-    endif
-    if !symbolTypeTable->has_key(symbolType)
-      symbolTypeTable[symbolType] = []
-    endif
-    childSymbols = {}
-    if syminfo->has_key('children')
-      ProcessDocSymbolTable(syminfo.children, childSymbols, symbolLineTable)
-    endif
-    symInfo = {name: name, range: r, detail: symbolDetail,
-                                               children: childSymbols}
-    symbolTypeTable[symbolType]->add(symInfo)
-    symbolLineTable->add(symInfo)
-  endfor
-enddef
-
-# process the 'textDocument/documentSymbol' reply from the LSP server
-# Open a symbols window and display the symbols as a tree
-# Result: DocumentSymbol[] | SymbolInformation[] | null
-def ProcessDocSymbolReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
-  var fname: string
-  var symbolTypeTable: dict<list<dict<any>>> = {}
-  var symbolLineTable: list<dict<any>> = []
-
-  if req.params.textDocument.uri != ''
-    fname = util.LspUriToFile(req.params.textDocument.uri)
-  endif
-
-  if reply.result->empty()
-    # No symbols defined for this file. Clear the outline window.
-    outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
-    return
-  endif
-
-  if reply.result[0]->has_key('location')
-    # SymbolInformation[]
-    ProcessSymbolInfoTable(reply.result, symbolTypeTable, symbolLineTable)
-  else
-    # DocumentSymbol[]
-    ProcessDocSymbolTable(reply.result, symbolTypeTable, symbolLineTable)
-  endif
-
-  # sort the symbols by line number
-  symbolLineTable->sort((a, b) => a.range.start.line - b.range.start.line)
-  outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
-enddef
-
-# process the 'textDocument/codeAction' reply from the LSP server
-# Result: (Command | CodeAction)[] | null
-def ProcessCodeActionReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
-  if reply.result->empty()
-    # no action can be performed
-    util.WarnMsg('No code action is available')
-    return
-  endif
-
-  codeaction.ApplyCodeAction(lspserver, reply.result)
-enddef
-
-# Reply: 'textDocument/foldingRange'
-# Result: FoldingRange[] | null
-def ProcessFoldingRangeReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
-  if reply.result->empty()
-    return
-  endif
-
-  # result: FoldingRange[]
-  var end_lnum: number
-  var last_lnum: number = line('$')
-  for foldRange in reply.result
-    end_lnum = foldRange.endLine + 1
-    if end_lnum < foldRange.startLine + 2
-      end_lnum = foldRange.startLine + 2
-    endif
-    exe $':{foldRange.startLine + 2}, {end_lnum}fold'
-    # Open all the folds, otherwise the subsequently created folds are not
-    # correct.
-    :silent! foldopen!
-  endfor
-
-  if &foldcolumn == 0
-    :setlocal foldcolumn=2
-  endif
-enddef
-
-# process the 'workspace/executeCommand' reply from the LSP server
-# Result: any | null
-def ProcessWorkspaceExecuteReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
-  if reply.result->empty()
-    return
-  endif
-
-  # Nothing to do for the reply
-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> =
-    {
-      'initialize': ProcessInitializeReply,
-      'textDocument/completion': ProcessCompletionReply,
-      'completionItem/resolve': ProcessResolveReply,
-      'textDocument/documentHighlight': ProcessDocHighlightReply,
-      'textDocument/documentSymbol': ProcessDocSymbolReply,
-      'textDocument/codeAction': ProcessCodeActionReply,
-      'textDocument/foldingRange': ProcessFoldingRangeReply,
-      'workspace/executeCommand': ProcessWorkspaceExecuteReply,
-    }
-
-  if lsp_reply_handlers->has_key(req.method)
-    lsp_reply_handlers[req.method](lspserver, req, reply)
-  else
-    util.ErrMsg($'Error: Unsupported reply received from LSP server: {reply->string()} for request: {req->string()}')
-  endif
+  util.ErrMsg($'Error: Unsupported reply received from LSP server: {reply->string()} for request: {req->string()}')
 enddef
 
 # process a diagnostic notification message from the LSP server
index 373ff050e6298dcaf713b1c7881f68df2b70cb4d..919df5654f17bdee903733e7010845eb9afed24e 100644 (file)
@@ -61,15 +61,15 @@ def CurbufGetServerChecked(): dict<any>
 
   var lspserver: dict<any> = buf.CurbufGetServer()
   if lspserver->empty()
-    util.ErrMsg($'Error: LSP server for "{fname}" is not found')
+    util.ErrMsg($'Error: Language server not found for "{&filetype}" file type')
     return {}
   endif
   if !lspserver.running
-    util.ErrMsg($'Error: LSP server for "{fname}" is not running')
+    util.ErrMsg($'Error: Language server not running for "{&filetype}" file type')
     return {}
   endif
   if !lspserver.ready
-    util.ErrMsg($'Error: LSP server for "{fname}" is not ready')
+    util.ErrMsg($'Error: Language server not ready for "{&filetype}" file type')
     return {}
   endif
 
@@ -312,8 +312,7 @@ def BufferInit(bnr: number): void
     exe $'autocmd InsertLeave <buffer={bnr}> call LspLeftInsertMode()'
 
     if opt.lspOptions.autoHighlight &&
-                       lspserver.caps->has_key('documentHighlightProvider')
-                       && lspserver.caps.documentHighlightProvider
+                       lspserver.caps->get('documentHighlightProvider', false)
       # Highlight all the occurrences of the current keyword
       exe $'autocmd CursorMoved <buffer={bnr}> call LspDocHighlightClear() | call LspDocHighlight()'
     endif
index 6ef13f63d0685cc40d8c73d6366b5209b0f2e846..de7b22cc67bb2cac4becf76632952730aa74eacb 100644 (file)
@@ -13,6 +13,7 @@ import './symbol.vim'
 import './textedit.vim'
 import './hover.vim'
 import './signature.vim'
+import './codeaction.vim'
 import './callhierarchy.vim' as callhier
 
 # LSP server standard output handler
@@ -81,12 +82,39 @@ def StartServer(lspserver: dict<any>): number
   return 0
 enddef
 
+# process the 'initialize' method reply from the LSP server
+# Result: InitializeResult
+def ServerInitReply(lspserver: dict<any>, initResult: dict<any>): void
+  if initResult->empty()
+    return
+  endif
+
+  var caps: dict<any> = initResult.capabilities
+  lspserver.caps = caps
+
+  if opt.lspOptions.autoComplete && caps->has_key('completionProvider')
+    lspserver.completionTriggerChars = caps.completionProvider->get('triggerCharacters', [])
+    lspserver.completionLazyDoc =
+               lspserver.caps.completionProvider->get('resolveProvider', false)
+  endif
+
+  # send a "initialized" notification to server
+  lspserver.sendInitializedNotif()
+  lspserver.ready = true
+  if exists($'#User#LspServerReady{lspserver.name}')
+    exe $'doautocmd <nomodeline> User LspServerReady{lspserver.name}'
+  endif
+
+  # if the outline window is opened, then request the symbols for the current
+  # buffer
+  if bufwinid('LSP-Outline') != -1
+    lspserver.getDocSymbols(@%)
+  endif
+enddef
+
 # Request: 'initialize'
 # Param: InitializeParams
-#
 def InitServer(lspserver: dict<any>)
-  var req = lspserver.createRequest('initialize')
-
   # client capabilities (ClientCapabilities)
   var clientCaps: dict<any> = {
     workspace: {
@@ -134,12 +162,8 @@ def InitServer(lspserver: dict<any>)
   if !empty(lspserver.initializationOptions)
     initparams.initializationOptions = lspserver.initializationOptions
   endif
-  req.params->extend(initparams)
 
-  lspserver.sendMessage(req)
-  if lspserver.syncInit
-    lspserver.waitForResponse(req)
-  endif
+  lspserver.rpc_a('initialize', initparams, ServerInitReply)
 enddef
 
 # Send a "initialized" LSP notification
@@ -486,12 +510,151 @@ def GetLspTextDocPosition(): dict<dict<any>>
          position: GetLspPosition()}
 enddef
 
+# Map LSP complete item kind to a character
+def LspCompleteItemKindChar(kind: number): string
+  var kindMap: list<string> = ['',
+               't', # Text
+               'm', # Method
+               'f', # Function
+               'C', # Constructor
+               'F', # Field
+               'v', # Variable
+               'c', # Class
+               'i', # Interface
+               'M', # Module
+               'p', # Property
+               'u', # Unit
+               'V', # Value
+               'e', # Enum
+               'k', # Keyword
+               'S', # Snippet
+               'C', # Color
+               'f', # File
+               'r', # Reference
+               'F', # Folder
+               'E', # EnumMember
+               'd', # Contant
+               's', # Struct
+               'E', # Event
+               'o', # Operator
+               'T'  # TypeParameter
+       ]
+  if kind > 25
+    return ''
+  endif
+  return kindMap[kind]
+enddef
+
+# process the 'textDocument/completion' reply from the LSP server
+# Result: CompletionItem[] | CompletionList | null
+def CompletionReply(lspserver: dict<any>, cItems: any)
+  if cItems->empty()
+    return
+  endif
+
+  var items: list<dict<any>>
+  if cItems->type() == v:t_list
+    items = cItems
+  else
+    items = cItems.items
+  endif
+
+  var completeItems: list<dict<any>> = []
+  for item in items
+    var d: dict<any> = {}
+    if item->has_key('textEdit') && item.textEdit->has_key('newText')
+      d.word = item.textEdit.newText
+    elseif item->has_key('insertText')
+      d.word = item.insertText
+    else
+      d.word = item.label
+    endif
+    d.abbr = item.label
+    d.dup = 1
+    if item->has_key('kind')
+      # namespace CompletionItemKind
+      # map LSP kind to complete-item-kind
+      d.kind = LspCompleteItemKindChar(item.kind)
+    endif
+    if lspserver.completionLazyDoc
+      d.info = 'Lazy doc'
+    else
+      if item->has_key('detail')
+        # Solve a issue where if a server send a detail field
+        # with a "\n", on the menu will be everything joined with
+        # a "^@" separating it. (example: clangd)
+        d.menu = item.detail->split("\n")[0]
+      endif
+      if item->has_key('documentation')
+        if item.documentation->type() == v:t_string && item.documentation != ''
+          d.info = item.documentation
+        elseif item.documentation->type() == v:t_dict
+            && item.documentation.value->type() == v:t_string
+          d.info = item.documentation.value
+        endif
+      endif
+    endif
+    d.user_data = item
+    completeItems->add(d)
+  endfor
+
+  if opt.lspOptions.autoComplete && !lspserver.omniCompletePending
+    if completeItems->empty()
+      # no matches
+      return
+    endif
+
+    var m = mode()
+    if m != 'i' && m != 'R' && m != 'Rv'
+      # If not in insert or replace mode, then don't start the completion
+      return
+    endif
+
+    if completeItems->len() == 1
+       && getline('.')->matchstr(completeItems[0].word .. '\>') != ''
+      # only one complete match. No need to show the completion popup
+      return
+    endif
+
+    var start_col: number = 0
+
+    # FIXME: The following doesn't work with typescript as one of the
+    # completion item has a start column that is before the special character.
+    # For example, when completing the methods for "str.", the dot is removed.
+    #
+    # # Find the start column for the completion.  If any of the entries
+    # # returned by the LSP server has a starting position, then use that.
+    # for item in items
+    #   if item->has_key('textEdit')
+    #     start_col = item.textEdit.range.start.character + 1
+    #     break
+    #   endif
+    # endfor
+
+    # LSP server didn't return a starting position for completion, search
+    # backwards from the current cursor position for a non-keyword character.
+    if start_col == 0
+      var line: string = getline('.')
+      var start = col('.') - 1
+      while start > 0 && line[start - 1] =~ '\k'
+       start -= 1
+      endwhile
+      start_col = start + 1
+    endif
+
+    complete(start_col, completeItems)
+  else
+    lspserver.completeItems = completeItems
+    lspserver.omniCompletePending = false
+  endif
+enddef
+
 # Get a list of completion items.
 # Request: "textDocument/completion"
 # Param: CompletionParams
 def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: string): void
   # Check whether LSP server supports completion
-  if !lspserver.caps->has_key('completionProvider')
+  if !lspserver.caps->get('completionProvider', false)
     util.ErrMsg("Error: LSP server does not support completion")
     return
   endif
@@ -501,18 +664,89 @@ def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: st
     return
   endif
 
-  var req = lspserver.createRequest('textDocument/completion')
-
   # interface CompletionParams
   #   interface TextDocumentPositionParams
-  req.params = GetLspTextDocPosition()
+  var params = GetLspTextDocPosition()
   #   interface CompletionContext
-  req.params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
+  params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
 
-  lspserver.sendMessage(req)
-  if get(g:, 'LSPTest')
-    # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
+  lspserver.rpc_a('textDocument/completion', params, CompletionReply)
+enddef
+
+# process the 'completionItem/resolve' reply from the LSP server
+# Result: CompletionItem
+def CompletionResolveReply(lspserver: dict<any>, cItem: dict<any>)
+  if cItem->empty()
+    return
+  endif
+
+  # check if completion item is still selected
+  var cInfo = complete_info()
+  if cInfo->empty()
+      || !cInfo.pum_visible
+      || cInfo.selected == -1
+      || cInfo.items[cInfo.selected].user_data.label != cItem.label
+    return
+  endif
+
+  var infoText: list<string>
+  var infoKind: string
+
+  if cItem->has_key('detail')
+    # Solve a issue where if a server send the detail field with "\n",
+    # on the completion popup, everything will be joined with "^@"
+    # (example: typescript-language-server)
+    infoText->extend(cItem.detail->split("\n"))
+  endif
+
+  if cItem->has_key('documentation')
+    if !infoText->empty()
+      infoText->extend(['- - -'])
+    endif
+    if cItem.documentation->type() == v:t_dict
+      # MarkupContent
+      if cItem.documentation.kind == 'plaintext'
+        infoText->extend(cItem.documentation.value->split("\n"))
+        infoKind = 'text'
+      elseif cItem.documentation.kind == 'markdown'
+        infoText->extend(cItem.documentation.value->split("\n"))
+        infoKind = 'markdown'
+      else
+        util.ErrMsg($'Error: Unsupported documentation type ({cItem.documentation.kind})')
+        return
+      endif
+    elseif cItem.documentation->type() == v:t_string
+      infoText->extend(cItem.documentation->split("\n"))
+    else
+      util.ErrMsg($'Error: Unsupported documentation ({cItem.documentation->string()})')
+      return
+    endif
+  endif
+
+  if infoText->empty()
+    return
+  endif
+
+  # check if completion item is changed in meantime
+  cInfo = complete_info()
+  if cInfo->empty()
+      || !cInfo.pum_visible
+      || cInfo.selected == -1
+      || cInfo.items[cInfo.selected].user_data.label != cItem.label
+    return
+  endif
+
+  var id = popup_findinfo()
+  if id > 0
+    var bufnr = id->winbufnr()
+    infoKind->setbufvar(bufnr, '&ft')
+    if infoKind == 'markdown'
+      3->setwinvar(id, '&conceallevel')
+    else
+      0->setwinvar(id, '&conceallevel')
+    endif
+    id->popup_settext(infoText)
+    id->popup_show()
   endif
 enddef
 
@@ -521,23 +755,16 @@ enddef
 # Param: CompletionItem
 def ResolveCompletion(lspserver: dict<any>, item: dict<any>): void
   # Check whether LSP server supports completion item resolve
-  if !lspserver.caps->has_key('completionProvider')
-      || !lspserver.caps.completionProvider->has_key('resolveProvider')
-      || !lspserver.caps.completionProvider.resolveProvider
+  if !lspserver.caps->get('completionProvider', false)
+      || !lspserver.caps.completionProvider->get('resolveProvider', false)
     util.ErrMsg("Error: LSP server does not support completion item resolve")
     return
   endif
 
-  var req = lspserver.createRequest('completionItem/resolve')
-
   # interface CompletionItem
-  req.params = item
+  var params = item
 
-  lspserver.sendMessage(req)
-  if get(g:, 'LSPTest')
-    # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
-  endif
+  lspserver.rpc_a('completionItem/resolve', params, CompletionResolveReply)
 enddef
 
 # Jump to or peek a symbol location.
@@ -593,8 +820,7 @@ enddef
 # Param: DefinitionParams
 def GotoDefinition(lspserver: dict<any>, peek: bool)
   # Check whether LSP server supports jumping to a definition
-  if !lspserver.caps->has_key('definitionProvider')
-                               || !lspserver.caps.definitionProvider
+  if !lspserver.caps->get('definitionProvider', false)
     util.ErrMsg("Error: Jumping to a symbol definition is not supported")
     return
   endif
@@ -608,8 +834,7 @@ enddef
 # Param: DeclarationParams
 def GotoDeclaration(lspserver: dict<any>, peek: bool): void
   # Check whether LSP server supports jumping to a declaration
-  if !lspserver.caps->has_key('declarationProvider')
-                       || !lspserver.caps.declarationProvider
+  if !lspserver.caps->get('declarationProvider', false)
     util.ErrMsg("Error: Jumping to a symbol declaration is not supported")
     return
   endif
@@ -623,8 +848,7 @@ enddef
 # Param: TypeDefinitionParams
 def GotoTypeDef(lspserver: dict<any>, peek: bool): void
   # Check whether LSP server supports jumping to a type definition
-  if !lspserver.caps->has_key('typeDefinitionProvider')
-                       || !lspserver.caps.typeDefinitionProvider
+  if !lspserver.caps->get('typeDefinitionProvider', false)
     util.ErrMsg("Error: Jumping to a symbol type definition is not supported")
     return
   endif
@@ -638,8 +862,7 @@ enddef
 # Param: ImplementationParams
 def GotoImplementation(lspserver: dict<any>, peek: bool): void
   # Check whether LSP server supports jumping to a implementation
-  if !lspserver.caps->has_key('implementationProvider')
-                       || !lspserver.caps.implementationProvider
+  if !lspserver.caps->get('implementationProvider', false)
     util.ErrMsg("Error: Jumping to a symbol implementation is not supported")
     return
   endif
@@ -677,7 +900,7 @@ enddef
 # Param: SignatureHelpParams
 def ShowSignature(lspserver: dict<any>): void
   # Check whether LSP server supports signature help
-  if !lspserver.caps->has_key('signatureHelpProvider')
+  if !lspserver.caps->get('signatureHelpProvider', false)
     util.ErrMsg("Error: LSP server does not support signature help")
     return
   endif
@@ -710,8 +933,7 @@ enddef
 # Param: HoverParams
 def ShowHoverInfo(lspserver: dict<any>): void
   # Check whether LSP server supports getting hover information
-  if !lspserver.caps->has_key('hoverProvider')
-                       || !lspserver.caps.hoverProvider
+  if !lspserver.caps->get('hoverProvider', false)
     return
   endif
 
@@ -725,8 +947,7 @@ enddef
 # Param: ReferenceParams
 def ShowReferences(lspserver: dict<any>, peek: bool): void
   # Check whether LSP server supports getting reference information
-  if !lspserver.caps->has_key('referencesProvider')
-                       || !lspserver.caps.referencesProvider
+  if !lspserver.caps->get('referencesProvider', false)
     util.ErrMsg("Error: LSP server does not support showing references")
     return
   endif
@@ -751,46 +972,65 @@ def ShowReferences(lspserver: dict<any>, peek: bool): void
   symbol.ShowReferences(lspserver, reply.result, peek)
 enddef
 
+# process the 'textDocument/documentHighlight' reply from the LSP server
+# Result: DocumentHighlight[] | null
+def DocHighlightReply(bnr: number, lspserver: dict<any>, docHighlightReply: any): void
+  if docHighlightReply->empty()
+    return
+  endif
+
+  for docHL in docHighlightReply
+    var kind: number = docHL->get('kind', 1)
+    var propName: string
+    if kind == 2
+      # Read-access
+      propName = 'LspReadRef'
+    elseif kind == 3
+      # Write-access
+      propName = 'LspWriteRef'
+    else
+      # textual reference
+      propName = 'LspTextRef'
+    endif
+    prop_add(docHL.range.start.line + 1,
+               util.GetLineByteFromPos(bnr, docHL.range.start) + 1,
+               {end_lnum: docHL.range.end.line + 1,
+                 end_col: util.GetLineByteFromPos(bnr, docHL.range.end) + 1,
+                 bufnr: bnr,
+                 type: propName})
+  endfor
+enddef
+
 # Request: "textDocument/documentHighlight"
 # Param: DocumentHighlightParams
 def DocHighlight(lspserver: dict<any>): void
   # Check whether LSP server supports getting highlight information
-  if !lspserver.caps->has_key('documentHighlightProvider')
-                       || !lspserver.caps.documentHighlightProvider
+  if !lspserver.caps->get('documentHighlightProvider', false)
     util.ErrMsg("Error: LSP server does not support document highlight")
     return
   endif
 
-  var req = lspserver.createRequest('textDocument/documentHighlight')
   # interface DocumentHighlightParams
   #   interface TextDocumentPositionParams
-  req.params->extend(GetLspTextDocPosition())
-  lspserver.sendMessage(req)
-  if get(g:, 'LSPTest')
-    # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
-  endif
+  var params = GetLspTextDocPosition()
+  lspserver.rpc_a('textDocument/documentHighlight', params,
+                       function('DocHighlightReply', [bufnr()]))
 enddef
 
 # Request: "textDocument/documentSymbol"
 # Param: DocumentSymbolParams
 def GetDocSymbols(lspserver: dict<any>, fname: string): void
   # Check whether LSP server supports getting document symbol information
-  if !lspserver.caps->has_key('documentSymbolProvider')
-                       || !lspserver.caps.documentSymbolProvider
+  if !lspserver.caps->get('documentSymbolProvider', false)
     util.ErrMsg("Error: LSP server does not support getting list of symbols")
     return
   endif
 
-  var req = lspserver.createRequest('textDocument/documentSymbol')
   # interface DocumentSymbolParams
   # interface TextDocumentIdentifier
-  req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}})
-  lspserver.sendMessage(req)
-  if get(g:, 'LSPTest')
-    # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
-  endif
+  var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+  lspserver.rpc_a('textDocument/documentSymbol', params,
+                       function(symbol.DocSymbolReply, [fname]))
 enddef
 
 # Request: "textDocument/formatting"
@@ -801,8 +1041,7 @@ enddef
 def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
                                start_lnum: number, end_lnum: number)
   # Check whether LSP server supports formatting documents
-  if !lspserver.caps->has_key('documentFormattingProvider')
-                       || !lspserver.caps.documentFormattingProvider
+  if !lspserver.caps->get('documentFormattingProvider', false)
     util.ErrMsg("Error: LSP server does not support formatting documents")
     return
   endif
@@ -884,8 +1123,7 @@ enddef
 # Request: "callHierarchy/incomingCalls"
 def IncomingCalls(lspserver: dict<any>, fname: string)
   # Check whether LSP server supports call hierarchy
-  if !lspserver.caps->has_key('callHierarchyProvider')
-                       || !lspserver.caps.callHierarchyProvider
+  if !lspserver.caps->get('callHierarchyProvider', false)
     util.ErrMsg("Error: LSP server does not support call hierarchy")
     return
   endif
@@ -912,8 +1150,7 @@ enddef
 # Request: "callHierarchy/outgoingCalls"
 def OutgoingCalls(lspserver: dict<any>, fname: string)
   # Check whether LSP server supports call hierarchy
-  if !lspserver.caps->has_key('callHierarchyProvider')
-                       || !lspserver.caps.callHierarchyProvider
+  if !lspserver.caps->get('callHierarchyProvider', false)
     util.ErrMsg("Error: LSP server does not support call hierarchy")
     return
   endif
@@ -941,8 +1178,7 @@ enddef
 # Param: RenameParams
 def RenameSymbol(lspserver: dict<any>, newName: string)
   # Check whether LSP server supports rename operation
-  if !lspserver.caps->has_key('renameProvider')
-                       || !lspserver.caps.renameProvider
+  if !lspserver.caps->get('renameProvider', false)
     util.ErrMsg("Error: LSP server does not support rename operation")
     return
   endif
@@ -969,34 +1205,37 @@ enddef
 # Param: CodeActionParams
 def CodeAction(lspserver: dict<any>, fname_arg: string)
   # Check whether LSP server supports code action operation
-  if !lspserver.caps->has_key('codeActionProvider')
-                       || !lspserver.caps.codeActionProvider
+  if !lspserver.caps->get('codeActionProvider', false)
     util.ErrMsg("Error: LSP server does not support code action operation")
     return
   endif
 
-  var req = lspserver.createRequest('textDocument/codeAction')
-
   # interface CodeActionParams
+  var params: dict<any> = {}
   var fname: string = fnamemodify(fname_arg, ':p')
   var bnr: number = fname_arg->bufnr()
   var r: dict<dict<number>> = {
                  start: {line: line('.') - 1, character: charcol('.') - 1},
                  end: {line: line('.') - 1, character: charcol('.') - 1}}
-  req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
+  params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
   var d: list<dict<any>> = []
   var lnum = line('.')
   var diagInfo: dict<any> = diag.GetDiagByLine(lspserver, bnr, lnum)
   if !diagInfo->empty()
     d->add(diagInfo)
   endif
-  req.params->extend({context: {diagnostics: d}})
+  params->extend({context: {diagnostics: d}})
 
-  lspserver.sendMessage(req)
-  if get(g:, 'LSPTest')
-    # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
+  var reply = lspserver.rpc('textDocument/codeAction', params)
+
+  # Result: (Command | CodeAction)[] | null
+  if reply->empty() || reply.result->empty()
+    # no action can be performed
+    util.WarnMsg('No code action is available')
+    return
   endif
+
+  codeaction.ApplyCodeAction(lspserver, reply.result)
 enddef
 
 # List project-wide symbols matching query string
@@ -1004,8 +1243,7 @@ enddef
 # Param: WorkspaceSymbolParams
 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
+  if !lspserver.caps->get('workspaceSymbolProvider', false)
     util.ErrMsg("Error: LSP server does not support listing workspace symbols")
     return
   endif
@@ -1080,8 +1318,7 @@ enddef
 # Param: SelectionRangeParams
 def SelectionRange(lspserver: dict<any>, fname: string)
   # Check whether LSP server supports selection ranges
-  if !lspserver.caps->has_key('selectionRangeProvider')
-                       || !lspserver.caps.selectionRangeProvider
+  if !lspserver.caps->get('selectionRangeProvider', false)
     util.ErrMsg("Error: LSP server does not support selection ranges")
     return
   endif
@@ -1107,8 +1344,7 @@ enddef
 # Expand the previous selection or start a new one
 def SelectionExpand(lspserver: dict<any>)
   # Check whether LSP server supports selection ranges
-  if !lspserver.caps->has_key('selectionRangeProvider')
-                       || !lspserver.caps.selectionRangeProvider
+  if !lspserver.caps->get('selectionRangeProvider', false)
     util.ErrMsg("Error: LSP server does not support selection ranges")
     return
   endif
@@ -1119,8 +1355,7 @@ enddef
 # Shrink the previous selection or start a new one
 def SelectionShrink(lspserver: dict<any>)
   # Check whether LSP server supports selection ranges
-  if !lspserver.caps->has_key('selectionRangeProvider')
-                       || !lspserver.caps.selectionRangeProvider
+  if !lspserver.caps->get('selectionRangeProvider', false)
     util.ErrMsg("Error: LSP server does not support selection ranges")
     return
   endif
@@ -1133,34 +1368,50 @@ enddef
 # Param: FoldingRangeParams
 def FoldRange(lspserver: dict<any>, fname: string)
   # Check whether LSP server supports fold ranges
-  if !lspserver.caps->has_key('foldingRangeProvider')
-                       || !lspserver.caps.foldingRangeProvider
+  if !lspserver.caps->get('foldingRangeProvider', false)
     util.ErrMsg("Error: LSP server does not support folding")
     return
   endif
 
-  var req = lspserver.createRequest('textDocument/foldingRange')
   # interface FoldingRangeParams
   # interface TextDocumentIdentifier
-  req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}})
-  lspserver.sendMessage(req)
-  if get(g:, 'LSPTest')
-    # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
+  var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+  var reply = lspserver.rpc('textDocument/foldingRange', params)
+  if reply->empty() || reply.result->empty()
+    return
+  endif
+
+  # result: FoldingRange[]
+  var end_lnum: number
+  var last_lnum: number = line('$')
+  for foldRange in reply.result
+    end_lnum = foldRange.endLine + 1
+    if end_lnum < foldRange.startLine + 2
+      end_lnum = foldRange.startLine + 2
+    endif
+    exe $':{foldRange.startLine + 2}, {end_lnum}fold'
+    # Open all the folds, otherwise the subsequently created folds are not
+    # correct.
+    :silent! foldopen!
+  endfor
+
+  if &foldcolumn == 0
+    :setlocal foldcolumn=2
   endif
 enddef
 
+# process the 'workspace/executeCommand' reply from the LSP server
+# Result: any | null
+def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
+  # Nothing to do for the reply
+enddef
+
 # Request the LSP server to execute a command
 # Request: workspace/executeCommand
 # Params: ExecuteCommandParams
 def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
-  var req = lspserver.createRequest('workspace/executeCommand')
-  req.params->extend(cmd)
-  lspserver.sendMessage(req)
-  if get(g:, 'LSPTest')
-    # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
-  endif
+  var params = cmd
+  lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
 enddef
 
 # Display the LSP server capabilities (received during the initialization
@@ -1179,8 +1430,7 @@ enddef
 # symbol definition or the symbol is not defined.
 def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
   # Check whether LSP server supports getting the location of a definition
-  if !lspserver.caps->has_key('definitionProvider')
-                               || !lspserver.caps.definitionProvider
+  if !lspserver.caps->get('definitionProvider', false)
     return null
   endif
 
index c0ded0ae476f7e0bdd7c5ad9a64060747f03830b..b5e4886a91970f0e6a1c68fd4b3629e3b9edc269 100644 (file)
@@ -8,6 +8,7 @@ vim9script
 
 import './options.vim' as opt
 import './util.vim'
+import './outline.vim'
 
 # Handle keys pressed when the workspace symbol popup menu is displayed
 def FilterSymbols(lspserver: dict<any>, popupID: number, key: string): bool
@@ -351,4 +352,92 @@ export def TagFunc(lspserver: dict<any>,
   return retval
 enddef
 
+# process SymbolInformation[]
+def ProcessSymbolInfoTable(symbolInfoTable: list<dict<any>>,
+                               symbolTypeTable: dict<list<dict<any>>>,
+                               symbolLineTable: list<dict<any>>)
+  var fname: string
+  var symbolType: string
+  var name: string
+  var r: dict<dict<number>>
+  var symInfo: dict<any>
+
+  for syminfo in symbolInfoTable
+    fname = util.LspUriToFile(syminfo.location.uri)
+    symbolType = SymbolKindToName(syminfo.kind)
+    name = syminfo.name
+    if syminfo->has_key('containerName')
+      if syminfo.containerName != ''
+       name ..= $' [{syminfo.containerName}]'
+      endif
+    endif
+    r = syminfo.location.range
+
+    if !symbolTypeTable->has_key(symbolType)
+      symbolTypeTable[symbolType] = []
+    endif
+    symInfo = {name: name, range: r}
+    symbolTypeTable[symbolType]->add(symInfo)
+    symbolLineTable->add(symInfo)
+  endfor
+enddef
+
+# process DocumentSymbol[]
+def ProcessDocSymbolTable(docSymbolTable: list<dict<any>>,
+                               symbolTypeTable: dict<list<dict<any>>>,
+                               symbolLineTable: list<dict<any>>)
+  var symbolType: string
+  var name: string
+  var r: dict<dict<number>>
+  var symInfo: dict<any>
+  var symbolDetail: string
+  var childSymbols: dict<list<dict<any>>>
+
+  for syminfo in docSymbolTable
+    name = syminfo.name
+    symbolType = SymbolKindToName(syminfo.kind)
+    r = syminfo.range
+    if syminfo->has_key('detail')
+      symbolDetail = syminfo.detail
+    endif
+    if !symbolTypeTable->has_key(symbolType)
+      symbolTypeTable[symbolType] = []
+    endif
+    childSymbols = {}
+    if syminfo->has_key('children')
+      ProcessDocSymbolTable(syminfo.children, childSymbols, symbolLineTable)
+    endif
+    symInfo = {name: name, range: r, detail: symbolDetail,
+                                               children: childSymbols}
+    symbolTypeTable[symbolType]->add(symInfo)
+    symbolLineTable->add(symInfo)
+  endfor
+enddef
+
+# process the 'textDocument/documentSymbol' reply from the LSP server
+# Open a symbols window and display the symbols as a tree
+# Result: DocumentSymbol[] | SymbolInformation[] | null
+export def DocSymbolReply(fname: string, lspserver: dict<any>, docsymbol: any)
+  var symbolTypeTable: dict<list<dict<any>>> = {}
+  var symbolLineTable: list<dict<any>> = []
+
+  if docsymbol->empty()
+    # No symbols defined for this file. Clear the outline window.
+    outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
+    return
+  endif
+
+  if docsymbol[0]->has_key('location')
+    # SymbolInformation[]
+    ProcessSymbolInfoTable(docsymbol, symbolTypeTable, symbolLineTable)
+  else
+    # DocumentSymbol[]
+    ProcessDocSymbolTable(docsymbol, symbolTypeTable, symbolLineTable)
+  endif
+
+  # sort the symbols by line number
+  symbolLineTable->sort((a, b) => a.range.start.line - b.range.start.line)
+  outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
+enddef
+
 # vim: tabstop=8 shiftwidth=2 softtabstop=2
index c9a15a7c29661c1db71e8b79319d6a713dcfa01b..d1d54b3ead1b8c7d09013e651fe9067205c94162 100644 (file)
@@ -18,10 +18,13 @@ def g:LspAddServer(serverList: list<dict<any>>)
   lsp.AddServer(serverList)
 enddef
 
+# Returns true if the language server for the current buffer is initialized
+# and ready to accept requests.
 def g:LspServerReady(): bool
   return lsp.ServerReady()
 enddef
 
+# Returns true if the language server for 'ftype' file type is running
 def g:LspServerRunning(ftype: string): bool
   return lsp.ServerRunning(ftype)
 enddef
index f59c6713a689ddc833783558bb424a176087640a..aae2a6e7abbc590ee452ed2689d55dfcefaaa7ca 100644 (file)
@@ -173,8 +173,8 @@ def Test_LspFormat()
   assert_equal('', execute('LspFormat'))
 
   # file without an LSP server
-  edit a.b
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  edit a.raku
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspFormat')->split("\n"))
 
   :%bw!
@@ -224,8 +224,8 @@ def Test_LspShowReferences()
   assert_equal('', execute('LspShowReferences'))
 
   # file without an LSP server
-  edit a.b
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  edit a.raku
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspShowReferences')->split("\n"))
 
   :%bw!
@@ -342,8 +342,8 @@ def Test_LspCodeAction()
   assert_equal('', execute('LspCodeAction'))
 
   # file without an LSP server
-  edit a.b
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  edit a.raku
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspCodeAction')->split("\n"))
 
   :%bw!
@@ -393,8 +393,8 @@ def Test_LspRename()
   assert_equal('', execute('LspRename'))
 
   # file without an LSP server
-  edit a.b
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  edit a.raku
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspRename')->split("\n"))
 
   :%bw!
@@ -493,8 +493,8 @@ def Test_LspSelection()
   assert_equal('', execute('LspSelectionExpand'))
 
   # file without an LSP server
-  edit a.b
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  edit a.raku
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspSelectionExpand')->split("\n"))
 
   :%bw!
@@ -573,12 +573,12 @@ def Test_LspGotoDefinition()
   assert_equal('', execute('LspGotoImpl'))
 
   # file without an LSP server
-  edit a.b
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  edit a.raku
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspGotoDefinition')->split("\n"))
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspGotoDeclaration')->split("\n"))
-  assert_equal(['Error: LSP server for "a.b" is not found'],
+  assert_equal(['Error: Language server not found for "raku" file type'],
               execute('LspGotoImpl')->split("\n"))
 
   :%bw!