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