]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/lspserver.vim
1dc4a3637d87375606c181aeac67341a88396a0b
[vim-lsp.git] / autoload / lsp / lspserver.vim
1 vim9script
2
3 # LSP server functions
4 #
5 # The functions to send request messages to the language server are in this
6 # file.
7 #
8 # Refer to https://microsoft.github.io/language-server-protocol/specification
9 # for the Language Server Protocol (LSP) specificaiton.
10
11 import './options.vim' as opt
12 import './handlers.vim'
13 import './util.vim'
14 import './capabilities.vim'
15 import './offset.vim'
16 import './diag.vim'
17 import './selection.vim'
18 import './symbol.vim'
19 import './textedit.vim'
20 import './completion.vim'
21 import './hover.vim'
22 import './signature.vim'
23 import './codeaction.vim'
24 import './codelens.vim'
25 import './callhierarchy.vim' as callhier
26 import './typehierarchy.vim' as typehier
27 import './inlayhints.vim'
28
29 # LSP server standard output handler
30 def Output_cb(lspserver: dict<any>, chan: channel, msg: any): void
31   if lspserver.debug
32     lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {msg->string()}')
33   endif
34   lspserver.data = msg
35   lspserver.processMessages()
36 enddef
37
38 # LSP server error output handler
39 def Error_cb(lspserver: dict<any>, chan: channel, emsg: string): void
40   lspserver.errorLog(emsg)
41 enddef
42
43 # LSP server exit callback
44 def Exit_cb(lspserver: dict<any>, job: job, status: number): void
45   util.WarnMsg($'{strftime("%m/%d/%y %T")}: LSP server ({lspserver.name}) exited with status {status}')
46   lspserver.running = false
47   lspserver.ready = false
48   lspserver.requests = {}
49 enddef
50
51 # Start a LSP server
52 #
53 def StartServer(lspserver: dict<any>, bnr: number): number
54   if lspserver.running
55     util.WarnMsg($'LSP server "{lspserver.name}" is already running')
56     return 0
57   endif
58
59   var cmd = [lspserver.path]
60   cmd->extend(lspserver.args)
61
62   var opts = {in_mode: 'lsp',
63                 out_mode: 'lsp',
64                 err_mode: 'raw',
65                 noblock: 1,
66                 out_cb: function(Output_cb, [lspserver]),
67                 err_cb: function(Error_cb, [lspserver]),
68                 exit_cb: function(Exit_cb, [lspserver])}
69
70   lspserver.data = ''
71   lspserver.caps = {}
72   lspserver.nextID = 1
73   lspserver.requests = {}
74   lspserver.omniCompletePending = false
75   lspserver.completionLazyDoc = false
76   lspserver.completionTriggerChars = []
77   lspserver.signaturePopup = -1
78
79   var job = cmd->job_start(opts)
80   if job->job_status() == 'fail'
81     util.ErrMsg($'Failed to start LSP server {lspserver.path}')
82     return 1
83   endif
84
85   # wait a little for the LSP server to start
86   sleep 10m
87
88   lspserver.job = job
89   lspserver.running = true
90
91   lspserver.initServer(bnr)
92
93   return 0
94 enddef
95
96 # process the "initialize" method reply from the LSP server
97 # Result: InitializeResult
98 def ServerInitReply(lspserver: dict<any>, initResult: dict<any>): void
99   if initResult->empty()
100     return
101   endif
102
103   var caps: dict<any> = initResult.capabilities
104   lspserver.caps = caps
105
106   for [key, val] in initResult->items()
107     if key == 'capabilities'
108       continue
109     endif
110
111     lspserver.caps[$'~additionalInitResult_{key}'] = val
112   endfor
113
114   capabilities.ProcessServerCaps(lspserver, caps)
115
116   if caps->has_key('completionProvider')
117     if opt.lspOptions.autoComplete
118       lspserver.completionTriggerChars =
119                         caps.completionProvider->get('triggerCharacters', [])
120     endif
121     lspserver.completionLazyDoc =
122                         caps.completionProvider->get('resolveProvider', false)
123   endif
124
125   # send a "initialized" notification to server
126   lspserver.sendInitializedNotif()
127   # send any workspace configuration (optional)
128   if !lspserver.workspaceConfig->empty()
129     lspserver.sendWorkspaceConfig()
130   endif
131   lspserver.ready = true
132   if exists($'#User#LspServerReady{lspserver.name}')
133     exe $'doautocmd <nomodeline> User LspServerReady{lspserver.name}'
134   endif
135   # Used internally, and shouldn't be used by users
136   if exists($'#User#LspServerReady_{lspserver.id}')
137     exe $'doautocmd <nomodeline> User LspServerReady_{lspserver.id}'
138   endif
139
140   # set the server debug trace level
141   if lspserver.traceLevel != 'off'
142     lspserver.setTrace(lspserver.traceLevel)
143   endif
144
145   # if the outline window is opened, then request the symbols for the current
146   # buffer
147   if bufwinid('LSP-Outline') != -1
148     lspserver.getDocSymbols(@%, true)
149   endif
150
151   # Update the inlay hints (if enabled)
152   if opt.lspOptions.showInlayHints && (lspserver.isInlayHintProvider
153                                     || lspserver.isClangdInlayHintsProvider)
154     inlayhints.LspInlayHintsUpdateNow()
155   endif
156 enddef
157
158 # Request: "initialize"
159 # Param: InitializeParams
160 def InitServer(lspserver: dict<any>, bnr: number)
161   # interface 'InitializeParams'
162   var initparams: dict<any> = {}
163   initparams.processId = getpid()
164   initparams.clientInfo = {
165         name: 'Vim',
166         version: v:versionlong->string(),
167       }
168
169   # Compute the rootpath (based on the directory of the buffer)
170   var rootPath = ''
171   var rootSearchFiles = lspserver.rootSearchFiles
172   var bufDir = bnr->bufname()->fnamemodify(':p:h')
173   if !rootSearchFiles->empty()
174     rootPath = util.FindNearestRootDir(bufDir, rootSearchFiles)
175   endif
176   if rootPath->empty()
177     var cwd = getcwd()
178
179     # bufDir is within cwd
180     var bufDirPrefix = bufDir[0 : cwd->strcharlen() - 1]
181     if &fileignorecase
182         ? bufDirPrefix ==? cwd
183         : bufDirPrefix == cwd
184       rootPath = cwd
185     else
186       rootPath = bufDir
187     endif
188   endif
189
190   lspserver.workspaceFolders = [rootPath]
191
192   var rootUri = util.LspFileToUri(rootPath)
193   initparams.rootPath = rootPath
194   initparams.rootUri = rootUri
195   initparams.workspaceFolders = [{
196         name: rootPath->fnamemodify(':t'),
197         uri: rootUri
198      }]
199
200   initparams.trace = 'off'
201   initparams.capabilities = capabilities.GetClientCaps()
202   if !lspserver.initializationOptions->empty()
203     initparams.initializationOptions = lspserver.initializationOptions
204   else
205     initparams.initializationOptions = {}
206   endif
207
208   lspserver.rpcInitializeRequest = initparams
209
210   lspserver.rpc_a('initialize', initparams, ServerInitReply)
211 enddef
212
213 # Send a "initialized" notification to the language server
214 def SendInitializedNotif(lspserver: dict<any>)
215   # Notification: 'initialized'
216   # Params: InitializedParams
217   lspserver.sendNotification('initialized')
218 enddef
219
220 # Request: shutdown
221 # Param: void
222 def ShutdownServer(lspserver: dict<any>): void
223   lspserver.rpc('shutdown', {})
224 enddef
225
226 # Send a 'exit' notification to the language server
227 def ExitServer(lspserver: dict<any>): void
228   # Notification: 'exit'
229   # Params: void
230   lspserver.sendNotification('exit')
231 enddef
232
233 # Stop a LSP server
234 def StopServer(lspserver: dict<any>): number
235   if !lspserver.running
236     util.WarnMsg($'LSP server {lspserver.name} is not running')
237     return 0
238   endif
239
240   # Send the shutdown request to the server
241   lspserver.shutdownServer()
242
243   # Notify the server to exit
244   lspserver.exitServer()
245
246   # Wait for the server to process the exit notification and exit for a
247   # maximum of 2 seconds.
248   var maxCount: number = 1000
249   while lspserver.job->job_status() == 'run' && maxCount > 0
250     sleep 2m
251     maxCount -= 1
252   endwhile
253
254   if lspserver.job->job_status() == 'run'
255     lspserver.job->job_stop()
256   endif
257   lspserver.running = false
258   lspserver.ready = false
259   lspserver.requests = {}
260   return 0
261 enddef
262
263 # Set the language server trace level using the '$/setTrace' notification.
264 # Supported values for "traceVal" are "off", "messages" and "verbose".
265 def SetTrace(lspserver: dict<any>, traceVal: string)
266   # Notification: '$/setTrace'
267   # Params: SetTraceParams
268   var params = {value: traceVal}
269   lspserver.sendNotification('$/setTrace', params)
270 enddef
271
272 # Log a debug message to the LSP server debug file
273 def TraceLog(lspserver: dict<any>, msg: string)
274   if lspserver.debug
275     util.TraceLog(lspserver.logfile, false, msg)
276   endif
277 enddef
278
279 # Log an error message to the LSP server error file
280 def ErrorLog(lspserver: dict<any>, errmsg: string)
281   if lspserver.debug
282     util.TraceLog(lspserver.errfile, true, errmsg)
283   endif
284 enddef
285
286 # Return the next id for a LSP server request message
287 def NextReqID(lspserver: dict<any>): number
288   var id = lspserver.nextID
289   lspserver.nextID = id + 1
290   return id
291 enddef
292
293 # create a LSP server request message
294 def CreateRequest(lspserver: dict<any>, method: string): dict<any>
295   var req = {}
296   req.jsonrpc = '2.0'
297   req.id = lspserver.nextReqID()
298   req.method = method
299   req.params = {}
300
301   # Save the request, so that the corresponding response can be processed
302   lspserver.requests->extend({[req.id->string()]: req})
303
304   return req
305 enddef
306
307 # create a LSP server response message
308 def CreateResponse(lspserver: dict<any>, req_id: number): dict<any>
309   var resp = {}
310   resp.jsonrpc = '2.0'
311   resp.id = req_id
312
313   return resp
314 enddef
315
316 # create a LSP server notification message
317 def CreateNotification(lspserver: dict<any>, notif: string): dict<any>
318   var req = {}
319   req.jsonrpc = '2.0'
320   req.method = notif
321   req.params = {}
322
323   return req
324 enddef
325
326 # send a response message to the server
327 def SendResponse(lspserver: dict<any>, request: dict<any>, result: any, error: dict<any>)
328   if (request.id->type() == v:t_string
329         && (request.id->trim() =~ '[^[:digit:]]\+'
330             || request.id->trim()->empty()))
331     || (request.id->type() != v:t_string && request.id->type() != v:t_number)
332     util.ErrMsg('request.id of response to LSP server is not a correct number')
333     return
334   endif
335   var resp: dict<any> = lspserver.createResponse(
336             request.id->type() == v:t_string ? request.id->str2nr() : request.id)
337   if error->empty()
338     resp->extend({result: result})
339   else
340     resp->extend({error: error})
341   endif
342   lspserver.sendMessage(resp)
343 enddef
344
345 # Send a request message to LSP server
346 def SendMessage(lspserver: dict<any>, content: dict<any>): void
347   var job = lspserver.job
348   if job->job_status() != 'run'
349     # LSP server has exited
350     return
351   endif
352   job->ch_sendexpr(content)
353   if content->has_key('id')
354     if lspserver.debug
355       lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {content->string()}')
356     endif
357   endif
358 enddef
359
360 # Send a notification message to the language server
361 def SendNotification(lspserver: dict<any>, method: string, params: any = {})
362   var notif: dict<any> = CreateNotification(lspserver, method)
363   notif.params->extend(params)
364   lspserver.sendMessage(notif)
365 enddef
366
367 # Translate an LSP error code into a readable string
368 def LspGetErrorMessage(errcode: number): string
369   var errmap = {
370     -32001: 'UnknownErrorCode',
371     -32002: 'ServerNotInitialized',
372     -32600: 'InvalidRequest',
373     -32601: 'MethodNotFound',
374     -32602: 'InvalidParams',
375     -32603: 'InternalError',
376     -32700: 'ParseError',
377     -32800: 'RequestCancelled',
378     -32801: 'ContentModified',
379     -32802: 'ServerCancelled',
380     -32803: 'RequestFailed'
381   }
382
383   return errmap->get(errcode, errcode->string())
384 enddef
385
386 # Process a LSP server response error and display an error message.
387 def ProcessLspServerError(method: string, responseError: dict<any>)
388   # request failed
389   var emsg: string = responseError.message
390   emsg ..= $', error = {LspGetErrorMessage(responseError.code)}'
391   if responseError->has_key('data')
392     emsg ..= $', data = {responseError.data->string()}'
393   endif
394   util.ErrMsg($'request {method} failed ({emsg})')
395 enddef
396
397 # Send a sync RPC request message to the LSP server and return the received
398 # reply.  In case of an error, an empty Dict is returned.
399 def Rpc(lspserver: dict<any>, method: string, params: any, handleError: bool = true): dict<any>
400   var req = {
401     method: method,
402     params: params
403   }
404
405   var job = lspserver.job
406   if job->job_status() != 'run'
407     # LSP server has exited
408     return {}
409   endif
410
411   if lspserver.debug
412     lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
413   endif
414
415   # Do the synchronous RPC call
416   var reply = job->ch_evalexpr(req)
417
418   if lspserver.debug
419     lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
420   endif
421
422   if reply->has_key('result')
423     # successful reply
424     return reply
425   endif
426
427   if reply->has_key('error') && handleError
428     # request failed
429     ProcessLspServerError(method, reply.error)
430   endif
431
432   return {}
433 enddef
434
435 # LSP server asynchronous RPC callback
436 def AsyncRpcCb(lspserver: dict<any>, method: string, RpcCb: func, chan: channel, reply: dict<any>)
437   if lspserver.debug
438     lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
439   endif
440
441   if reply->empty()
442     return
443   endif
444
445   if reply->has_key('error')
446     # request failed
447     ProcessLspServerError(method, reply.error)
448     return
449   endif
450
451   if !reply->has_key('result')
452     util.ErrMsg($'request {method} failed (no result)')
453     return
454   endif
455
456   RpcCb(lspserver, reply.result)
457 enddef
458
459 # Send a async RPC request message to the LSP server with a callback function.
460 # Returns the LSP message id.  This id can be used to cancel the RPC request
461 # (if needed).  Returns -1 on error.
462 def AsyncRpc(lspserver: dict<any>, method: string, params: any, Cbfunc: func): number
463   var req = {
464     method: method,
465     params: params
466   }
467
468   var job = lspserver.job
469   if job->job_status() != 'run'
470     # LSP server has exited
471     return -1
472   endif
473
474   if lspserver.debug
475     lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
476   endif
477
478   # Do the asynchronous RPC call
479   var Fn = function('AsyncRpcCb', [lspserver, method, Cbfunc])
480
481   var reply: dict<any>
482   if get(g:, 'LSPTest')
483     # When running LSP tests, make this a synchronous RPC call
484     reply = Rpc(lspserver, method, params)
485     Fn(test_null_channel(), reply)
486   else
487     # Otherwise, make an asynchronous RPC call
488     reply = job->ch_sendexpr(req, {callback: Fn})
489   endif
490   if reply->empty()
491     return -1
492   endif
493
494   return reply.id
495 enddef
496
497 # Wait for a response message from the LSP server for the request "req"
498 # Waits for a maximum of 5 seconds
499 def WaitForResponse(lspserver: dict<any>, req: dict<any>)
500   var maxCount: number = 2500
501   var key: string = req.id->string()
502
503   while lspserver.requests->has_key(key) && maxCount > 0
504     sleep 2m
505     maxCount -= 1
506   endwhile
507 enddef
508
509 # Retrieve the Workspace configuration asked by the server.
510 # Request: workspace/configuration
511 def WorkspaceConfigGet(lspserver: dict<any>, configItem: dict<any>): dict<any>
512   if lspserver.workspaceConfig->empty()
513     return {}
514   endif
515   if !configItem->has_key('section') || configItem.section->empty()
516     return lspserver.workspaceConfig
517   endif
518   var config: dict<any> = lspserver.workspaceConfig
519   for part in configItem.section->split('\.')
520     if !config->has_key(part)
521       return {}
522     endif
523     config = config[part]
524   endfor
525   return config
526 enddef
527
528 # Send a "workspace/didChangeConfiguration" notification to the language
529 # server.
530 def SendWorkspaceConfig(lspserver: dict<any>)
531   # Params: DidChangeConfigurationParams
532   var params = {settings: lspserver.workspaceConfig}
533   lspserver.sendNotification('workspace/didChangeConfiguration', params)
534 enddef
535
536 # Send a file/document opened notification to the language server.
537 def TextdocDidOpen(lspserver: dict<any>, bnr: number, ftype: string): void
538   # Notification: 'textDocument/didOpen'
539   # Params: DidOpenTextDocumentParams
540   var tdi = {}
541   tdi.uri = util.LspBufnrToUri(bnr)
542   tdi.languageId = ftype
543   tdi.version = 1
544   tdi.text = bnr->getbufline(1, '$')->join("\n") .. "\n"
545   var params = {textDocument: tdi}
546   lspserver.sendNotification('textDocument/didOpen', params)
547 enddef
548
549 # Send a file/document closed notification to the language server.
550 def TextdocDidClose(lspserver: dict<any>, bnr: number): void
551   # Notification: 'textDocument/didClose'
552   # Params: DidCloseTextDocumentParams
553   var tdid = {}
554   tdid.uri = util.LspBufnrToUri(bnr)
555   var params = {textDocument: tdid}
556   lspserver.sendNotification('textDocument/didClose', params)
557 enddef
558
559 # Send a file/document change notification to the language server.
560 # Params: DidChangeTextDocumentParams
561 def TextdocDidChange(lspserver: dict<any>, bnr: number, start: number,
562                         end: number, added: number,
563                         changes: list<dict<number>>): void
564   # Notification: 'textDocument/didChange'
565   # Params: DidChangeTextDocumentParams
566   var vtdid: dict<any> = {}
567   vtdid.uri = util.LspBufnrToUri(bnr)
568   # Use Vim 'changedtick' as the LSP document version number
569   vtdid.version = bnr->getbufvar('changedtick')
570
571   var changeset: list<dict<any>>
572
573   ##### FIXME: Sending specific buffer changes to the LSP server doesn't
574   ##### work properly as the computed line range numbers is not correct.
575   ##### For now, send the entire buffer content to LSP server.
576   # #     Range
577   # for change in changes
578   #   var lines: string
579   #   var start_lnum: number
580   #   var end_lnum: number
581   #   var start_col: number
582   #   var end_col: number
583   #   if change.added == 0
584   #     # lines changed
585   #     start_lnum =  change.lnum - 1
586   #     end_lnum = change.end - 1
587   #     lines = getbufline(bnr, change.lnum, change.end - 1)->join("\n") .. "\n"
588   #     start_col = 0
589   #     end_col = 0
590   #   elseif change.added > 0
591   #     # lines added
592   #     start_lnum = change.lnum - 1
593   #     end_lnum = change.lnum - 1
594   #     start_col = 0
595   #     end_col = 0
596   #     lines = getbufline(bnr, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n"
597   #   else
598   #     # lines removed
599   #     start_lnum = change.lnum - 1
600   #     end_lnum = change.lnum + (-change.added) - 1
601   #     start_col = 0
602   #     end_col = 0
603   #     lines = ''
604   #   endif
605   #   var range: dict<dict<number>> = {'start': {'line': start_lnum, 'character': start_col}, 'end': {'line': end_lnum, 'character': end_col}}
606   #   changeset->add({'range': range, 'text': lines})
607   # endfor
608
609   changeset->add({text: bnr->getbufline(1, '$')->join("\n") .. "\n"})
610   var params = {textDocument: vtdid, contentChanges: changeset}
611   lspserver.sendNotification('textDocument/didChange', params)
612 enddef
613
614 # Return the current cursor position as a LSP position.
615 # find_ident will search for a identifier in front of the cursor, just like
616 # CTRL-] and c_CTRL-R_CTRL-W does.
617 #
618 # LSP line and column numbers start from zero, whereas Vim line and column
619 # numbers start from one. The LSP column number is the character index in the
620 # line and not the byte index in the line.
621 def GetPosition(lspserver: dict<any>, find_ident: bool): dict<number>
622   var lnum: number = line('.') - 1
623   var col: number = charcol('.') - 1
624   var line = getline('.')
625
626   if find_ident
627     # 1. skip to start of identifier
628     while line[col] != '' && line[col] !~ '\k'
629       col = col + 1
630     endwhile
631
632     # 2. back up to start of identifier
633     while col > 0 && line[col - 1] =~ '\k'
634       col = col - 1
635     endwhile
636   endif
637
638   # Compute character index counting composing characters as separate
639   # characters
640   var pos = {line: lnum, character: util.GetCharIdxWithCompChar(line, col)}
641   lspserver.encodePosition(bufnr(), pos)
642
643   return pos
644 enddef
645
646 # Return the current file name and current cursor position as a LSP
647 # TextDocumentPositionParams structure
648 def GetTextDocPosition(lspserver: dict<any>, find_ident: bool): dict<dict<any>>
649   # interface TextDocumentIdentifier
650   # interface Position
651   return {textDocument: {uri: util.LspFileToUri(@%)},
652           position: lspserver.getPosition(find_ident)}
653 enddef
654
655 # Get a list of completion items.
656 # Request: "textDocument/completion"
657 # Param: CompletionParams
658 def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: string): void
659   # Check whether LSP server supports completion
660   if !lspserver.isCompletionProvider
661     util.ErrMsg('LSP server does not support completion')
662     return
663   endif
664
665   var fname = @%
666   if fname->empty()
667     return
668   endif
669
670   # interface CompletionParams
671   #   interface TextDocumentPositionParams
672   var params = lspserver.getTextDocPosition(false)
673   #   interface CompletionContext
674   params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
675
676   lspserver.rpc_a('textDocument/completion', params,
677                         completion.CompletionReply)
678 enddef
679
680 # Get lazy properties for a completion item.
681 # Request: "completionItem/resolve"
682 # Param: CompletionItem
683 def ResolveCompletion(lspserver: dict<any>, item: dict<any>): void
684   # Check whether LSP server supports completion item resolve
685   if !lspserver.isCompletionResolveProvider
686     util.ErrMsg('LSP server does not support completion item resolve')
687     return
688   endif
689
690   # interface CompletionItem
691   lspserver.rpc_a('completionItem/resolve', item,
692                         completion.CompletionResolveReply)
693 enddef
694
695 # Jump to or peek a symbol location.
696 #
697 # Send 'msg' to a LSP server and process the reply.  'msg' is one of the
698 # following:
699 #   textDocument/definition
700 #   textDocument/declaration
701 #   textDocument/typeDefinition
702 #   textDocument/implementation
703 #
704 # Process the LSP server reply and jump to the symbol location.  Before
705 # jumping to the symbol location, save the current cursor position in the tag
706 # stack.
707 #
708 # If 'peekSymbol' is true, then display the symbol location in the preview
709 # window but don't jump to the symbol location.
710 #
711 # Result: Location | Location[] | LocationLink[] | null
712 def GotoSymbolLoc(lspserver: dict<any>, msg: string, peekSymbol: bool,
713                   cmdmods: string, count: number)
714   var reply = lspserver.rpc(msg, lspserver.getTextDocPosition(true), false)
715   if reply->empty() || reply.result->empty()
716     var emsg: string
717     if msg == 'textDocument/declaration'
718       emsg = 'symbol declaration is not found'
719     elseif msg == 'textDocument/typeDefinition'
720       emsg = 'symbol type definition is not found'
721     elseif msg == 'textDocument/implementation'
722       emsg = 'symbol implementation is not found'
723     else
724       emsg = 'symbol definition is not found'
725     endif
726
727     util.WarnMsg(emsg)
728     return
729   endif
730
731   var result = reply.result
732   var location: dict<any>
733   if result->type() == v:t_list
734     if count == 0
735       # When there are multiple symbol locations, and a specific one isn't
736       # requested with 'count', display the locations in a location list.
737       if result->len() > 1
738         var title: string = ''
739         if msg == 'textDocument/declaration'
740           title = 'Declarations'
741         elseif msg == 'textDocument/typeDefinition'
742           title = 'Type Definitions'
743         elseif msg == 'textDocument/implementation'
744           title = 'Implementations'
745         else
746           title = 'Definitions'
747         endif
748
749         if lspserver.needOffsetEncoding
750           # Decode the position encoding in all the symbol locations
751           result->map((_, loc) => {
752               lspserver.decodeLocation(loc)
753               return loc
754             })
755         endif
756
757         symbol.ShowLocations(lspserver, result, peekSymbol, title)
758         return
759       endif
760     endif
761
762     # Select the location requested in 'count'
763     var idx = count - 1
764     if idx >= result->len()
765       idx = result->len() - 1
766     endif
767     location = result[idx]
768   else
769     location = result
770   endif
771   lspserver.decodeLocation(location)
772
773   symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods)
774 enddef
775
776 # Request: "textDocument/definition"
777 # Param: DefinitionParams
778 def GotoDefinition(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
779   # Check whether LSP server supports jumping to a definition
780   if !lspserver.isDefinitionProvider
781     util.ErrMsg('Jumping to a symbol definition is not supported')
782     return
783   endif
784
785   # interface DefinitionParams
786   #   interface TextDocumentPositionParams
787   GotoSymbolLoc(lspserver, 'textDocument/definition', peek, cmdmods, count)
788 enddef
789
790 # Request: "textDocument/declaration"
791 # Param: DeclarationParams
792 def GotoDeclaration(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
793   # Check whether LSP server supports jumping to a declaration
794   if !lspserver.isDeclarationProvider
795     util.ErrMsg('Jumping to a symbol declaration is not supported')
796     return
797   endif
798
799   # interface DeclarationParams
800   #   interface TextDocumentPositionParams
801   GotoSymbolLoc(lspserver, 'textDocument/declaration', peek, cmdmods, count)
802 enddef
803
804 # Request: "textDocument/typeDefinition"
805 # Param: TypeDefinitionParams
806 def GotoTypeDef(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
807   # Check whether LSP server supports jumping to a type definition
808   if !lspserver.isTypeDefinitionProvider
809     util.ErrMsg('Jumping to a symbol type definition is not supported')
810     return
811   endif
812
813   # interface TypeDefinitionParams
814   #   interface TextDocumentPositionParams
815   GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek, cmdmods, count)
816 enddef
817
818 # Request: "textDocument/implementation"
819 # Param: ImplementationParams
820 def GotoImplementation(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
821   # Check whether LSP server supports jumping to a implementation
822   if !lspserver.isImplementationProvider
823     util.ErrMsg('Jumping to a symbol implementation is not supported')
824     return
825   endif
826
827   # interface ImplementationParams
828   #   interface TextDocumentPositionParams
829   GotoSymbolLoc(lspserver, 'textDocument/implementation', peek, cmdmods, count)
830 enddef
831
832 # Request: "textDocument/switchSourceHeader"
833 # Param: TextDocumentIdentifier
834 # Clangd specific extension
835 def SwitchSourceHeader(lspserver: dict<any>)
836   var param = {}
837   param.uri = util.LspFileToUri(@%)
838   var reply = lspserver.rpc('textDocument/switchSourceHeader', param)
839   if reply->empty() || reply.result->empty()
840     util.WarnMsg('No alternate file found')
841     return
842   endif
843
844   # process the 'textDocument/switchSourceHeader' reply from the LSP server
845   # Result: URI | null
846   var fname = util.LspUriToFile(reply.result)
847   if (&modified && !&hidden) || &buftype != ''
848     # if the current buffer has unsaved changes and 'hidden' is not set,
849     # or if the current buffer is a special buffer, then ask to save changes
850     exe $'confirm edit {fname}'
851   else
852     exe $'edit {fname}'
853   endif
854 enddef
855
856 # get symbol signature help.
857 # Request: "textDocument/signatureHelp"
858 # Param: SignatureHelpParams
859 def ShowSignature(lspserver: dict<any>): void
860   # Check whether LSP server supports signature help
861   if !lspserver.isSignatureHelpProvider
862     util.ErrMsg('LSP server does not support signature help')
863     return
864   endif
865
866   # interface SignatureHelpParams
867   #   interface TextDocumentPositionParams
868   var params = lspserver.getTextDocPosition(false)
869   lspserver.rpc_a('textDocument/signatureHelp', params,
870                         signature.SignatureHelp)
871 enddef
872
873 # Send a file/document saved notification to the language server
874 def DidSaveFile(lspserver: dict<any>, bnr: number): void
875   # Check whether the LSP server supports the didSave notification
876   if !lspserver.supportsDidSave
877     # LSP server doesn't support text document synchronization
878     return
879   endif
880
881   # Notification: 'textDocument/didSave'
882   # Params: DidSaveTextDocumentParams
883   var params = {textDocument: {uri: util.LspBufnrToUri(bnr)}}
884   # FIXME: Need to set "params.text" when
885   # 'lspserver.caps.textDocumentSync.save.includeText' is set to true.
886   lspserver.sendNotification('textDocument/didSave', params)
887 enddef
888
889 # get the hover information
890 # Request: "textDocument/hover"
891 # Param: HoverParams
892 def ShowHoverInfo(lspserver: dict<any>, cmdmods: string): void
893   # Check whether LSP server supports getting hover information.
894   # caps->hoverProvider can be a "boolean" or "HoverOptions"
895   if !lspserver.isHoverProvider
896     return
897   endif
898
899   # interface HoverParams
900   #   interface TextDocumentPositionParams
901   var params = lspserver.getTextDocPosition(false)
902   lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
903     hover.HoverReply(lspserver, reply, cmdmods)
904   })
905 enddef
906
907 # Request: "textDocument/references"
908 # Param: ReferenceParams
909 def ShowReferences(lspserver: dict<any>, peek: bool): void
910   # Check whether LSP server supports getting reference information
911   if !lspserver.isReferencesProvider
912     util.ErrMsg('LSP server does not support showing references')
913     return
914   endif
915
916   # interface ReferenceParams
917   #   interface TextDocumentPositionParams
918   var param: dict<any>
919   param = lspserver.getTextDocPosition(true)
920   param.context = {includeDeclaration: true}
921   var reply = lspserver.rpc('textDocument/references', param)
922
923   # Result: Location[] | null
924   if reply->empty() || reply.result->empty()
925     util.WarnMsg('No references found')
926     return
927   endif
928
929   if lspserver.needOffsetEncoding
930     # Decode the position encoding in all the reference locations
931     reply.result->map((_, loc) => {
932       lspserver.decodeLocation(loc)
933       return loc
934     })
935   endif
936
937   symbol.ShowLocations(lspserver, reply.result, peek, 'Symbol References')
938 enddef
939
940 # process the 'textDocument/documentHighlight' reply from the LSP server
941 # Result: DocumentHighlight[] | null
942 def DocHighlightReply(lspserver: dict<any>, docHighlightReply: any,
943                       bnr: number, cmdmods: string): void
944   if docHighlightReply->empty()
945     if cmdmods !~ 'silent'
946       util.WarnMsg($'No highlight for the current position')
947     endif
948     return
949   endif
950
951   for docHL in docHighlightReply
952     lspserver.decodeRange(bnr, docHL.range)
953     var kind: number = docHL->get('kind', 1)
954     var propName: string
955     if kind == 2
956       # Read-access
957       propName = 'LspReadRef'
958     elseif kind == 3
959       # Write-access
960       propName = 'LspWriteRef'
961     else
962       # textual reference
963       propName = 'LspTextRef'
964     endif
965     try
966       prop_add(docHL.range.start.line + 1,
967                   util.GetLineByteFromPos(bnr, docHL.range.start) + 1,
968                   {end_lnum: docHL.range.end.line + 1,
969                     end_col: util.GetLineByteFromPos(bnr, docHL.range.end) + 1,
970                     bufnr: bnr,
971                     type: propName})
972     catch /E966\|E964/ # Invalid lnum | Invalid col
973       # Highlight replies arrive asynchronously and the document might have
974       # been modified in the mean time.  As the reply is stale, ignore invalid
975       # line number and column number errors.
976     endtry
977   endfor
978 enddef
979
980 # Request: "textDocument/documentHighlight"
981 # Param: DocumentHighlightParams
982 def DocHighlight(lspserver: dict<any>, cmdmods: string): void
983   # Check whether LSP server supports getting highlight information
984   if !lspserver.isDocumentHighlightProvider
985     util.ErrMsg('LSP server does not support document highlight')
986     return
987   endif
988
989   # interface DocumentHighlightParams
990   #   interface TextDocumentPositionParams
991   var params = lspserver.getTextDocPosition(false)
992   lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
993     DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
994   })
995 enddef
996
997 # Request: "textDocument/documentSymbol"
998 # Param: DocumentSymbolParams
999 def GetDocSymbols(lspserver: dict<any>, fname: string, showOutline: bool): void
1000   # Check whether LSP server supports getting document symbol information
1001   if !lspserver.isDocumentSymbolProvider
1002     util.ErrMsg('LSP server does not support getting list of symbols')
1003     return
1004   endif
1005
1006   # interface DocumentSymbolParams
1007   # interface TextDocumentIdentifier
1008   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1009   lspserver.rpc_a('textDocument/documentSymbol', params, (_, reply) => {
1010     if showOutline
1011       symbol.DocSymbolOutline(lspserver, reply, fname)
1012     else
1013       symbol.DocSymbolPopup(lspserver, reply, fname)
1014     endif
1015   })
1016 enddef
1017
1018 # Request: "textDocument/formatting"
1019 # Param: DocumentFormattingParams
1020 # or
1021 # Request: "textDocument/rangeFormatting"
1022 # Param: DocumentRangeFormattingParams
1023 def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
1024                                 start_lnum: number, end_lnum: number)
1025   # Check whether LSP server supports formatting documents
1026   if !lspserver.isDocumentFormattingProvider
1027     util.ErrMsg('LSP server does not support formatting documents')
1028     return
1029   endif
1030
1031   var cmd: string
1032   if rangeFormat
1033     cmd = 'textDocument/rangeFormatting'
1034   else
1035     cmd = 'textDocument/formatting'
1036   endif
1037
1038   # interface DocumentFormattingParams
1039   #   interface TextDocumentIdentifier
1040   #   interface FormattingOptions
1041   var param = {}
1042   param.textDocument = {uri: util.LspFileToUri(fname)}
1043   var fmtopts: dict<any> = {
1044     tabSize: shiftwidth(),
1045     insertSpaces: &expandtab ? true : false,
1046   }
1047   param.options = fmtopts
1048
1049   if rangeFormat
1050     var r: dict<dict<number>> = {
1051         start: {line: start_lnum - 1, character: 0},
1052         end: {line: end_lnum - 1, character: charcol([end_lnum, '$']) - 1}}
1053     param.range = r
1054   endif
1055
1056   var reply = lspserver.rpc(cmd, param)
1057
1058   # result: TextEdit[] | null
1059
1060   if reply->empty() || reply.result->empty()
1061     # nothing to format
1062     return
1063   endif
1064
1065   var bnr: number = fname->bufnr()
1066   if bnr == -1
1067     # file is already removed
1068     return
1069   endif
1070
1071   if lspserver.needOffsetEncoding
1072     # Decode the position encoding in all the reference locations
1073     reply.result->map((_, textEdit) => {
1074       lspserver.decodeRange(bnr, textEdit.range)
1075       return textEdit
1076     })
1077   endif
1078
1079   # interface TextEdit
1080   # Apply each of the text edit operations
1081   var save_cursor: list<number> = getcurpos()
1082   textedit.ApplyTextEdits(bnr, reply.result)
1083   save_cursor->setpos('.')
1084 enddef
1085
1086 # Request: "textDocument/prepareCallHierarchy"
1087 def PrepareCallHierarchy(lspserver: dict<any>): dict<any>
1088   # interface CallHierarchyPrepareParams
1089   #   interface TextDocumentPositionParams
1090   var param: dict<any>
1091   param = lspserver.getTextDocPosition(false)
1092   var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
1093   if reply->empty() || reply.result->empty()
1094     return {}
1095   endif
1096
1097   # Result: CallHierarchyItem[] | null
1098   var choice: number = 1
1099   if reply.result->len() > 1
1100     var items: list<string> = ['Select a Call Hierarchy Item:']
1101     for i in reply.result->len()->range()
1102       items->add(printf("%d. %s", i + 1, reply.result[i].name))
1103     endfor
1104     choice = items->inputlist()
1105     if choice < 1 || choice > items->len()
1106       return {}
1107     endif
1108   endif
1109
1110   return reply.result[choice - 1]
1111 enddef
1112
1113 # Request: "callHierarchy/incomingCalls"
1114 def IncomingCalls(lspserver: dict<any>, fname: string)
1115   # Check whether LSP server supports call hierarchy
1116   if !lspserver.isCallHierarchyProvider
1117     util.ErrMsg('LSP server does not support call hierarchy')
1118     return
1119   endif
1120
1121   callhier.IncomingCalls(lspserver)
1122 enddef
1123
1124 def GetIncomingCalls(lspserver: dict<any>, item: dict<any>): any
1125   # Request: "callHierarchy/incomingCalls"
1126   # Param: CallHierarchyIncomingCallsParams
1127   var param = {}
1128   param.item = item
1129   var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
1130   if reply->empty()
1131     return null
1132   endif
1133
1134   if lspserver.needOffsetEncoding
1135     # Decode the position encoding in all the incoming call locations
1136     var bnr = util.LspUriToBufnr(item.uri)
1137     reply.result->map((_, hierItem) => {
1138       lspserver.decodeRange(bnr, hierItem.from.range)
1139       return hierItem
1140     })
1141   endif
1142
1143   return reply.result
1144 enddef
1145
1146 # Request: "callHierarchy/outgoingCalls"
1147 def OutgoingCalls(lspserver: dict<any>, fname: string)
1148   # Check whether LSP server supports call hierarchy
1149   if !lspserver.isCallHierarchyProvider
1150     util.ErrMsg('LSP server does not support call hierarchy')
1151     return
1152   endif
1153
1154   callhier.OutgoingCalls(lspserver)
1155 enddef
1156
1157 def GetOutgoingCalls(lspserver: dict<any>, item: dict<any>): any
1158   # Request: "callHierarchy/outgoingCalls"
1159   # Param: CallHierarchyOutgoingCallsParams
1160   var param = {}
1161   param.item = item
1162   var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
1163   if reply->empty()
1164     return null
1165   endif
1166
1167   if lspserver.needOffsetEncoding
1168     # Decode the position encoding in all the outgoing call locations
1169     var bnr = util.LspUriToBufnr(item.uri)
1170     reply.result->map((_, hierItem) => {
1171       lspserver.decodeRange(bnr, hierItem.to.range)
1172       return hierItem
1173     })
1174   endif
1175
1176   return reply.result
1177 enddef
1178
1179 # Request: "textDocument/inlayHint"
1180 # Inlay hints.
1181 def InlayHintsShow(lspserver: dict<any>)
1182   # Check whether LSP server supports type hierarchy
1183   if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
1184     util.ErrMsg('LSP server does not support inlay hint')
1185     return
1186   endif
1187
1188   var lastlnum = line('$')
1189   var param = {
1190       textDocument: {uri: util.LspFileToUri(@%)},
1191       range:
1192       {
1193         start: {line: 0, character: 0},
1194         end: {line: lastlnum - 1, character: charcol([lastlnum, '$']) - 1}
1195       }
1196   }
1197
1198   lspserver.encodeRange(bufnr(), param.range)
1199
1200   var msg: string
1201   if lspserver.isClangdInlayHintsProvider
1202     # clangd-style inlay hints
1203     msg = 'clangd/inlayHints'
1204   else
1205     msg = 'textDocument/inlayHint'
1206   endif
1207   var reply = lspserver.rpc_a(msg, param, inlayhints.InlayHintsReply)
1208 enddef
1209
1210 def DecodeTypeHierarchy(lspserver: dict<any>, isSuper: bool, typeHier: dict<any>)
1211   if !lspserver.needOffsetEncoding
1212     return
1213   endif
1214   var bnr = util.LspUriToBufnr(typeHier.uri)
1215   lspserver.decodeRange(bnr, typeHier.range)
1216   lspserver.decodeRange(bnr, typeHier.selectionRange)
1217   var subType: list<dict<any>>
1218   if isSuper
1219     subType = typeHier->get('parents', [])
1220   else
1221     subType = typeHier->get('children', [])
1222   endif
1223   if !subType->empty()
1224     # Decode the position encoding in all the type hierarchy items
1225     subType->map((_, typeHierItem) => {
1226         DecodeTypeHierarchy(lspserver, isSuper, typeHierItem)
1227         return typeHierItem
1228       })
1229   endif
1230 enddef
1231
1232 # Request: "textDocument/typehierarchy"
1233 # Support the clangd version of type hierarchy retrieval method.
1234 # The method described in the LSP 3.17.0 standard is not supported as clangd
1235 # doesn't support that method.
1236 def TypeHierarchy(lspserver: dict<any>, direction: number)
1237   # Check whether LSP server supports type hierarchy
1238   if !lspserver.isTypeHierarchyProvider
1239     util.ErrMsg('LSP server does not support type hierarchy')
1240     return
1241   endif
1242
1243   # interface TypeHierarchy
1244   #   interface TextDocumentPositionParams
1245   var param: dict<any>
1246   param = lspserver.getTextDocPosition(false)
1247   # 0: children, 1: parent, 2: both
1248   param.direction = direction
1249   param.resolve = 5
1250   var reply = lspserver.rpc('textDocument/typeHierarchy', param)
1251   if reply->empty() || reply.result->empty()
1252     util.WarnMsg('No type hierarchy available')
1253     return
1254   endif
1255
1256   var isSuper = (direction == 1)
1257
1258   DecodeTypeHierarchy(lspserver, isSuper, reply.result)
1259
1260   typehier.ShowTypeHierarchy(lspserver, isSuper, reply.result)
1261 enddef
1262
1263 # Decode the ranges in "WorkspaceEdit"
1264 def DecodeWorkspaceEdit(lspserver: dict<any>, workspaceEdit: dict<any>)
1265   if !lspserver.needOffsetEncoding
1266     return
1267   endif
1268   if workspaceEdit->has_key('changes')
1269     for [uri, changes] in workspaceEdit.changes->items()
1270       var bnr: number = util.LspUriToBufnr(uri)
1271       if bnr <= 0
1272         continue
1273       endif
1274       # Decode the position encoding in all the text edit locations
1275       changes->map((_, textEdit) => {
1276         lspserver.decodeRange(bnr, textEdit.range)
1277         return textEdit
1278       })
1279     endfor
1280   endif
1281
1282   if workspaceEdit->has_key('documentChanges')
1283     for change in workspaceEdit.documentChanges
1284       if !change->has_key('kind')
1285         var bnr: number = util.LspUriToBufnr(change.textDocument.uri)
1286         if bnr <= 0
1287           continue
1288         endif
1289         # Decode the position encoding in all the text edit locations
1290         change.edits->map((_, textEdit) => {
1291           lspserver.decodeRange(bnr, textEdit.range)
1292           return textEdit
1293         })
1294       endif
1295     endfor
1296   endif
1297 enddef
1298
1299 # Request: "textDocument/rename"
1300 # Param: RenameParams
1301 def RenameSymbol(lspserver: dict<any>, newName: string)
1302   # Check whether LSP server supports rename operation
1303   if !lspserver.isRenameProvider
1304     util.ErrMsg('LSP server does not support rename operation')
1305     return
1306   endif
1307
1308   # interface RenameParams
1309   #   interface TextDocumentPositionParams
1310   var param: dict<any> = {}
1311   param = lspserver.getTextDocPosition(true)
1312   param.newName = newName
1313
1314   var reply = lspserver.rpc('textDocument/rename', param)
1315
1316   # Result: WorkspaceEdit | null
1317   if reply->empty() || reply.result->empty()
1318     # nothing to rename
1319     return
1320   endif
1321
1322   # result: WorkspaceEdit
1323   DecodeWorkspaceEdit(lspserver, reply.result)
1324   textedit.ApplyWorkspaceEdit(reply.result)
1325 enddef
1326
1327 # Decode the range in "CodeAction"
1328 def DecodeCodeAction(lspserver: dict<any>, actionList: list<dict<any>>)
1329   if !lspserver.needOffsetEncoding
1330     return
1331   endif
1332   actionList->map((_, act) => {
1333       if !act->has_key('disabled') && act->has_key('edit')
1334         DecodeWorkspaceEdit(lspserver, act.edit)
1335       endif
1336       return act
1337     })
1338 enddef
1339
1340 # Request: "textDocument/codeAction"
1341 # Param: CodeActionParams
1342 def CodeAction(lspserver: dict<any>, fname_arg: string, line1: number,
1343                 line2: number, query: string)
1344   # Check whether LSP server supports code action operation
1345   if !lspserver.isCodeActionProvider
1346     util.ErrMsg('LSP server does not support code action operation')
1347     return
1348   endif
1349
1350   # interface CodeActionParams
1351   var params: dict<any> = {}
1352   var fname: string = fname_arg->fnamemodify(':p')
1353   var bnr: number = fname_arg->bufnr()
1354   var r: dict<dict<number>> = {
1355     start: {
1356       line: line1 - 1,
1357       character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
1358     },
1359     end: {
1360       line: line2 - 1,
1361       character: util.GetCharIdxWithCompChar(getline(line2), charcol([line2, '$']) - 1)
1362     }
1363   }
1364   lspserver.encodeRange(bnr, r)
1365   params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
1366   var d: list<dict<any>> = []
1367   for lnum in range(line1, line2)
1368     var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(bnr, lnum, lspserver)->deepcopy()
1369     if lspserver.needOffsetEncoding
1370       diagsInfo->map((_, di) => {
1371           lspserver.encodeRange(bnr, di.range)
1372           return di
1373         })
1374     endif
1375     d->extend(diagsInfo)
1376   endfor
1377   params->extend({context: {diagnostics: d, triggerKind: 1}})
1378
1379   var reply = lspserver.rpc('textDocument/codeAction', params)
1380
1381   # Result: (Command | CodeAction)[] | null
1382   if reply->empty() || reply.result->empty()
1383     # no action can be performed
1384     util.WarnMsg('No code action is available')
1385     return
1386   endif
1387
1388   DecodeCodeAction(lspserver, reply.result)
1389
1390   codeaction.ApplyCodeAction(lspserver, reply.result, query)
1391 enddef
1392
1393 # Request: "textDocument/codeLens"
1394 # Param: CodeLensParams
1395 def CodeLens(lspserver: dict<any>, fname: string)
1396   # Check whether LSP server supports code lens operation
1397   if !lspserver.isCodeLensProvider
1398     util.ErrMsg('LSP server does not support code lens operation')
1399     return
1400   endif
1401
1402   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1403   var reply = lspserver.rpc('textDocument/codeLens', params)
1404   if reply->empty() || reply.result->empty()
1405     util.WarnMsg($'No code lens actions found for the current file')
1406     return
1407   endif
1408
1409   var bnr = fname->bufnr()
1410
1411   # Decode the position encoding in all the code lens items
1412   if lspserver.needOffsetEncoding
1413     reply.result->map((_, codeLensItem) => {
1414       lspserver.decodeRange(bnr, codeLensItem.range)
1415       return codeLensItem
1416     })
1417   endif
1418
1419   codelens.ProcessCodeLens(lspserver, bnr, reply.result)
1420 enddef
1421
1422 # Request: "codeLens/resolve"
1423 # Param: CodeLens
1424 def ResolveCodeLens(lspserver: dict<any>, bnr: number,
1425                     codeLens: dict<any>): dict<any>
1426   if !lspserver.isCodeLensResolveProvider
1427     return {}
1428   endif
1429
1430   if lspserver.needOffsetEncoding
1431     lspserver.encodeRange(bnr, codeLens.range)
1432   endif
1433
1434   var reply = lspserver.rpc('codeLens/resolve', codeLens)
1435   if reply->empty()
1436     return {}
1437   endif
1438
1439   var codeLensItem: dict<any> = reply.result
1440
1441   # Decode the position encoding in the code lens item
1442   if lspserver.needOffsetEncoding
1443     lspserver.decodeRange(bnr, codeLensItem.range)
1444   endif
1445
1446   return codeLensItem
1447 enddef
1448
1449 # List project-wide symbols matching query string
1450 # Request: "workspace/symbol"
1451 # Param: WorkspaceSymbolParams
1452 def WorkspaceQuerySymbols(lspserver: dict<any>, query: string, firstCall: bool, cmdmods: string = '')
1453   # Check whether the LSP server supports listing workspace symbols
1454   if !lspserver.isWorkspaceSymbolProvider
1455     util.ErrMsg('LSP server does not support listing workspace symbols')
1456     return
1457   endif
1458
1459   # Param: WorkspaceSymbolParams
1460   var param = {}
1461   param.query = query
1462   var reply = lspserver.rpc('workspace/symbol', param)
1463   if reply->empty() || reply.result->empty()
1464     util.WarnMsg($'Symbol "{query}" is not found')
1465     return
1466   endif
1467
1468   var symInfo: list<dict<any>> = reply.result
1469
1470   if lspserver.needOffsetEncoding
1471     # Decode the position encoding in all the symbol locations
1472     symInfo->map((_, sym) => {
1473       if sym->has_key('location')
1474         lspserver.decodeLocation(sym.location)
1475       endif
1476       return sym
1477     })
1478   endif
1479
1480   if firstCall && symInfo->len() == 1
1481     # If there is only one symbol, then jump to the symbol location
1482     var symLoc: dict<any> = symInfo[0]->get('location', {})
1483     if !symLoc->empty()
1484       symbol.GotoSymbol(lspserver, symLoc, false, cmdmods)
1485     endif
1486   else
1487     symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods)
1488   endif
1489 enddef
1490
1491 # Add a workspace folder to the language server.
1492 def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1493   if !lspserver.caps->has_key('workspace')
1494           || !lspserver.caps.workspace->has_key('workspaceFolders')
1495           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1496           || !lspserver.caps.workspace.workspaceFolders.supported
1497       util.ErrMsg('LSP server does not support workspace folders')
1498     return
1499   endif
1500
1501   if lspserver.workspaceFolders->index(dirName) != -1
1502     util.ErrMsg($'{dirName} is already part of this workspace')
1503     return
1504   endif
1505
1506   # Notification: 'workspace/didChangeWorkspaceFolders'
1507   # Params: DidChangeWorkspaceFoldersParams
1508   var params = {event: {added: [dirName], removed: []}}
1509   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1510
1511   lspserver.workspaceFolders->add(dirName)
1512 enddef
1513
1514 # Remove a workspace folder from the language server.
1515 def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1516   if !lspserver.caps->has_key('workspace')
1517           || !lspserver.caps.workspace->has_key('workspaceFolders')
1518           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1519           || !lspserver.caps.workspace.workspaceFolders.supported
1520       util.ErrMsg('LSP server does not support workspace folders')
1521     return
1522   endif
1523
1524   var idx: number = lspserver.workspaceFolders->index(dirName)
1525   if idx == -1
1526     util.ErrMsg($'{dirName} is not currently part of this workspace')
1527     return
1528   endif
1529
1530   # Notification: "workspace/didChangeWorkspaceFolders"
1531   # Param: DidChangeWorkspaceFoldersParams
1532   var params = {event: {added: [], removed: [dirName]}}
1533   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1534
1535   lspserver.workspaceFolders->remove(idx)
1536 enddef
1537
1538 def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
1539   lspserver.decodeRange(bnr, selRange.range)
1540   if selRange->has_key('parent')
1541     DecodeSelectionRange(lspserver, bnr, selRange.parent)
1542   endif
1543 enddef
1544
1545 # select the text around the current cursor location
1546 # Request: "textDocument/selectionRange"
1547 # Param: SelectionRangeParams
1548 def SelectionRange(lspserver: dict<any>, fname: string)
1549   # Check whether LSP server supports selection ranges
1550   if !lspserver.isSelectionRangeProvider
1551     util.ErrMsg('LSP server does not support selection ranges')
1552     return
1553   endif
1554
1555   # clear the previous selection reply
1556   lspserver.selection = {}
1557
1558   # interface SelectionRangeParams
1559   # interface TextDocumentIdentifier
1560   var param = {}
1561   param.textDocument = {}
1562   param.textDocument.uri = util.LspFileToUri(fname)
1563   param.positions = [lspserver.getPosition(false)]
1564   var reply = lspserver.rpc('textDocument/selectionRange', param)
1565
1566   if reply->empty() || reply.result->empty()
1567     return
1568   endif
1569
1570   # Decode the position encoding in all the selection range items
1571   if lspserver.needOffsetEncoding
1572     var bnr = fname->bufnr()
1573     reply.result->map((_, selItem) => {
1574         DecodeSelectionRange(lspserver, bnr, selItem)
1575         return selItem
1576       })
1577   endif
1578
1579   selection.SelectionStart(lspserver, reply.result)
1580 enddef
1581
1582 # Expand the previous selection or start a new one
1583 def SelectionExpand(lspserver: dict<any>)
1584   # Check whether LSP server supports selection ranges
1585   if !lspserver.isSelectionRangeProvider
1586     util.ErrMsg('LSP server does not support selection ranges')
1587     return
1588   endif
1589
1590   selection.SelectionModify(lspserver, true)
1591 enddef
1592
1593 # Shrink the previous selection or start a new one
1594 def SelectionShrink(lspserver: dict<any>)
1595   # Check whether LSP server supports selection ranges
1596   if !lspserver.isSelectionRangeProvider
1597     util.ErrMsg('LSP server does not support selection ranges')
1598     return
1599   endif
1600
1601   selection.SelectionModify(lspserver, false)
1602 enddef
1603
1604 # fold the entire document
1605 # Request: "textDocument/foldingRange"
1606 # Param: FoldingRangeParams
1607 def FoldRange(lspserver: dict<any>, fname: string)
1608   # Check whether LSP server supports fold ranges
1609   if !lspserver.isFoldingRangeProvider
1610     util.ErrMsg('LSP server does not support folding')
1611     return
1612   endif
1613
1614   # interface FoldingRangeParams
1615   # interface TextDocumentIdentifier
1616   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1617   var reply = lspserver.rpc('textDocument/foldingRange', params)
1618   if reply->empty() || reply.result->empty()
1619     return
1620   endif
1621
1622   # result: FoldingRange[]
1623   var end_lnum: number
1624   var last_lnum: number = line('$')
1625   for foldRange in reply.result
1626     end_lnum = foldRange.endLine + 1
1627     if end_lnum < foldRange.startLine + 2
1628       end_lnum = foldRange.startLine + 2
1629     endif
1630     exe $':{foldRange.startLine + 2}, {end_lnum}fold'
1631     # Open all the folds, otherwise the subsequently created folds are not
1632     # correct.
1633     :silent! foldopen!
1634   endfor
1635
1636   if &foldcolumn == 0
1637     :setlocal foldcolumn=2
1638   endif
1639 enddef
1640
1641 # process the 'workspace/executeCommand' reply from the LSP server
1642 # Result: any | null
1643 def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
1644   # Nothing to do for the reply
1645 enddef
1646
1647 # Request the LSP server to execute a command
1648 # Request: workspace/executeCommand
1649 # Params: ExecuteCommandParams
1650 def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
1651   # Need to check for lspserver.caps.executeCommandProvider?
1652   var params = cmd
1653   lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
1654 enddef
1655
1656 # Display the LSP server capabilities (received during the initialization
1657 # stage).
1658 def GetCapabilities(lspserver: dict<any>): list<string>
1659   var l = []
1660   var heading = $"'{lspserver.path}' Language Server Capabilities"
1661   var underlines = repeat('=', heading->len())
1662   l->extend([heading, underlines])
1663   for k in lspserver.caps->keys()->sort()
1664     l->add($'{k}: {lspserver.caps[k]->string()}')
1665   endfor
1666   return l
1667 enddef
1668
1669 # Display the LSP server initialize request and result
1670 def GetInitializeRequest(lspserver: dict<any>): list<string>
1671   var l = []
1672   var heading = $"'{lspserver.path}' Language Server Initialize Request"
1673   var underlines = repeat('=', heading->len())
1674   l->extend([heading, underlines])
1675   if lspserver->has_key('rpcInitializeRequest')
1676     for k in lspserver.rpcInitializeRequest->keys()->sort()
1677       l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
1678     endfor
1679   endif
1680   return l
1681 enddef
1682
1683 # Store a log or trace message received from the language server.
1684 def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
1685   # A single message may contain multiple lines separate by newline
1686   var msgs = newMsg->split("\n")
1687   lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
1688   lspserver.messages->extend(msgs[1 : ])
1689   # Keep only the last 500 messages to reduce the memory usage
1690   if lspserver.messages->len() >= 600
1691     lspserver.messages = lspserver.messages[-500 : ]
1692   endif
1693 enddef
1694
1695 # Display the log messages received from the LSP server (window/logMessage)
1696 def GetMessages(lspserver: dict<any>): list<string>
1697   if lspserver.messages->empty()
1698     return [$'No messages received from "{lspserver.name}" server']
1699   endif
1700
1701   var l = []
1702   var heading = $"'{lspserver.path}' Language Server Messages"
1703   var underlines = repeat('=', heading->len())
1704   l->extend([heading, underlines])
1705   l->extend(lspserver.messages)
1706   return l
1707 enddef
1708
1709 # Send a 'textDocument/definition' request to the LSP server to get the
1710 # location where the symbol under the cursor is defined and return a list of
1711 # Dicts in a format accepted by the 'tagfunc' option.
1712 # Returns null if the LSP server doesn't support getting the location of a
1713 # symbol definition or the symbol is not defined.
1714 def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
1715   # Check whether LSP server supports getting the location of a definition
1716   if !lspserver.isDefinitionProvider
1717     return null
1718   endif
1719
1720   # interface DefinitionParams
1721   #   interface TextDocumentPositionParams
1722   var reply = lspserver.rpc('textDocument/definition',
1723                             lspserver.getTextDocPosition(false))
1724   if reply->empty() || reply.result->empty()
1725     return null
1726   endif
1727
1728   var taglocations: list<dict<any>>
1729   if reply.result->type() == v:t_list
1730     taglocations = reply.result
1731   else
1732     taglocations = [reply.result]
1733   endif
1734
1735   if lspserver.needOffsetEncoding
1736     # Decode the position encoding in all the reference locations
1737     taglocations->map((_, loc) => {
1738       lspserver.decodeLocation(loc)
1739       return loc
1740     })
1741   endif
1742
1743   return symbol.TagFunc(lspserver, taglocations, pat)
1744 enddef
1745
1746 # Returns unique ID used for identifying the various servers
1747 var UniqueServerIdCounter = 0
1748 def GetUniqueServerId(): number
1749   UniqueServerIdCounter = UniqueServerIdCounter + 1
1750   return UniqueServerIdCounter
1751 enddef
1752
1753 export def NewLspServer(serverParams: dict<any>): dict<any>
1754   var lspserver: dict<any> = {
1755     id: GetUniqueServerId(),
1756     name: serverParams.name,
1757     path: serverParams.path,
1758     args: serverParams.args->deepcopy(),
1759     running: false,
1760     ready: false,
1761     job: v:none,
1762     data: '',
1763     nextID: 1,
1764     caps: {},
1765     requests: {},
1766     callHierarchyType: '',
1767     completionTriggerChars: [],
1768     customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
1769     customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
1770     debug: serverParams.debug,
1771     features: serverParams.features->deepcopy(),
1772     forceOffsetEncoding: serverParams.forceOffsetEncoding,
1773     initializationOptions: serverParams.initializationOptions->deepcopy(),
1774     messages: [],
1775     omniCompletePending: false,
1776     peekSymbolFilePopup: -1,
1777     peekSymbolPopup: -1,
1778     processDiagHandler: serverParams.processDiagHandler,
1779     rootSearchFiles: serverParams.rootSearch->deepcopy(),
1780     runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
1781     runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
1782     selection: {},
1783     signaturePopup: -1,
1784     syncInit: serverParams.syncInit,
1785     traceLevel: serverParams.traceLevel,
1786     typeHierFilePopup: -1,
1787     typeHierPopup: -1,
1788     workspaceConfig: serverParams.workspaceConfig->deepcopy(),
1789     workspaceSymbolPopup: -1,
1790     workspaceSymbolQuery: ''
1791   }
1792   lspserver.logfile = $'lsp-{lspserver.name}.log'
1793   lspserver.errfile = $'lsp-{lspserver.name}.err'
1794
1795   # Add the LSP server functions
1796   lspserver->extend({
1797     startServer: function(StartServer, [lspserver]),
1798     initServer: function(InitServer, [lspserver]),
1799     stopServer: function(StopServer, [lspserver]),
1800     shutdownServer: function(ShutdownServer, [lspserver]),
1801     exitServer: function(ExitServer, [lspserver]),
1802     setTrace: function(SetTrace, [lspserver]),
1803     traceLog: function(TraceLog, [lspserver]),
1804     errorLog: function(ErrorLog, [lspserver]),
1805     nextReqID: function(NextReqID, [lspserver]),
1806     createRequest: function(CreateRequest, [lspserver]),
1807     createResponse: function(CreateResponse, [lspserver]),
1808     sendResponse: function(SendResponse, [lspserver]),
1809     sendMessage: function(SendMessage, [lspserver]),
1810     sendNotification: function(SendNotification, [lspserver]),
1811     rpc: function(Rpc, [lspserver]),
1812     rpc_a: function(AsyncRpc, [lspserver]),
1813     waitForResponse: function(WaitForResponse, [lspserver]),
1814     processReply: function(handlers.ProcessReply, [lspserver]),
1815     processNotif: function(handlers.ProcessNotif, [lspserver]),
1816     processRequest: function(handlers.ProcessRequest, [lspserver]),
1817     processMessages: function(handlers.ProcessMessages, [lspserver]),
1818     encodePosition: function(offset.EncodePosition, [lspserver]),
1819     decodePosition: function(offset.DecodePosition, [lspserver]),
1820     encodeRange: function(offset.EncodeRange, [lspserver]),
1821     decodeRange: function(offset.DecodeRange, [lspserver]),
1822     encodeLocation: function(offset.EncodeLocation, [lspserver]),
1823     decodeLocation: function(offset.DecodeLocation, [lspserver]),
1824     getPosition: function(GetPosition, [lspserver]),
1825     getTextDocPosition: function(GetTextDocPosition, [lspserver]),
1826     textdocDidOpen: function(TextdocDidOpen, [lspserver]),
1827     textdocDidClose: function(TextdocDidClose, [lspserver]),
1828     textdocDidChange: function(TextdocDidChange, [lspserver]),
1829     sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
1830     sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
1831     getCompletion: function(GetCompletion, [lspserver]),
1832     resolveCompletion: function(ResolveCompletion, [lspserver]),
1833     gotoDefinition: function(GotoDefinition, [lspserver]),
1834     gotoDeclaration: function(GotoDeclaration, [lspserver]),
1835     gotoTypeDef: function(GotoTypeDef, [lspserver]),
1836     gotoImplementation: function(GotoImplementation, [lspserver]),
1837     tagFunc: function(TagFunc, [lspserver]),
1838     switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
1839     showSignature: function(ShowSignature, [lspserver]),
1840     didSaveFile: function(DidSaveFile, [lspserver]),
1841     hover: function(ShowHoverInfo, [lspserver]),
1842     showReferences: function(ShowReferences, [lspserver]),
1843     docHighlight: function(DocHighlight, [lspserver]),
1844     getDocSymbols: function(GetDocSymbols, [lspserver]),
1845     textDocFormat: function(TextDocFormat, [lspserver]),
1846     prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
1847     incomingCalls: function(IncomingCalls, [lspserver]),
1848     getIncomingCalls: function(GetIncomingCalls, [lspserver]),
1849     outgoingCalls: function(OutgoingCalls, [lspserver]),
1850     getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
1851     inlayHintsShow: function(InlayHintsShow, [lspserver]),
1852     typeHierarchy: function(TypeHierarchy, [lspserver]),
1853     renameSymbol: function(RenameSymbol, [lspserver]),
1854     codeAction: function(CodeAction, [lspserver]),
1855     codeLens: function(CodeLens, [lspserver]),
1856     resolveCodeLens: function(ResolveCodeLens, [lspserver]),
1857     workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
1858     addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
1859     removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
1860     selectionRange: function(SelectionRange, [lspserver]),
1861     selectionExpand: function(SelectionExpand, [lspserver]),
1862     selectionShrink: function(SelectionShrink, [lspserver]),
1863     foldRange: function(FoldRange, [lspserver]),
1864     executeCommand: function(ExecuteCommand, [lspserver]),
1865     workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
1866     getCapabilities: function(GetCapabilities, [lspserver]),
1867     getInitializeRequest: function(GetInitializeRequest, [lspserver]),
1868     addMessage: function(AddMessage, [lspserver]),
1869     getMessages: function(GetMessages, [lspserver])
1870   })
1871
1872   return lspserver
1873 enddef
1874
1875 # vim: tabstop=8 shiftwidth=2 softtabstop=2