]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/lspserver.vim
03d89bd8414953d862576c75b501adfa8752a645
[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)
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   symbol.WorkspaceSymbolPopup(lspserver, query, reply.result)
1453 enddef
1454
1455 # Add a workspace folder to the language server.
1456 def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1457   if !lspserver.caps->has_key('workspace')
1458           || !lspserver.caps.workspace->has_key('workspaceFolders')
1459           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1460           || !lspserver.caps.workspace.workspaceFolders.supported
1461       util.ErrMsg('LSP server does not support workspace folders')
1462     return
1463   endif
1464
1465   if lspserver.workspaceFolders->index(dirName) != -1
1466     util.ErrMsg($'{dirName} is already part of this workspace')
1467     return
1468   endif
1469
1470   # Notification: 'workspace/didChangeWorkspaceFolders'
1471   # Params: DidChangeWorkspaceFoldersParams
1472   var params = {event: {added: [dirName], removed: []}}
1473   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1474
1475   lspserver.workspaceFolders->add(dirName)
1476 enddef
1477
1478 # Remove a workspace folder from the language server.
1479 def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1480   if !lspserver.caps->has_key('workspace')
1481           || !lspserver.caps.workspace->has_key('workspaceFolders')
1482           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1483           || !lspserver.caps.workspace.workspaceFolders.supported
1484       util.ErrMsg('LSP server does not support workspace folders')
1485     return
1486   endif
1487
1488   var idx: number = lspserver.workspaceFolders->index(dirName)
1489   if idx == -1
1490     util.ErrMsg($'{dirName} is not currently part of this workspace')
1491     return
1492   endif
1493
1494   # Notification: "workspace/didChangeWorkspaceFolders"
1495   # Param: DidChangeWorkspaceFoldersParams
1496   var params = {event: {added: [], removed: [dirName]}}
1497   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1498
1499   lspserver.workspaceFolders->remove(idx)
1500 enddef
1501
1502 def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
1503   lspserver.decodeRange(bnr, selRange.range)
1504   if selRange->has_key('parent')
1505     DecodeSelectionRange(lspserver, bnr, selRange.parent)
1506   endif
1507 enddef
1508
1509 # select the text around the current cursor location
1510 # Request: "textDocument/selectionRange"
1511 # Param: SelectionRangeParams
1512 def SelectionRange(lspserver: dict<any>, fname: string)
1513   # Check whether LSP server supports selection ranges
1514   if !lspserver.isSelectionRangeProvider
1515     util.ErrMsg('LSP server does not support selection ranges')
1516     return
1517   endif
1518
1519   # clear the previous selection reply
1520   lspserver.selection = {}
1521
1522   # interface SelectionRangeParams
1523   # interface TextDocumentIdentifier
1524   var param = {}
1525   param.textDocument = {}
1526   param.textDocument.uri = util.LspFileToUri(fname)
1527   param.positions = [lspserver.getPosition(false)]
1528   var reply = lspserver.rpc('textDocument/selectionRange', param)
1529
1530   if reply->empty() || reply.result->empty()
1531     return
1532   endif
1533
1534   # Decode the position encoding in all the selection range items
1535   if lspserver.needOffsetEncoding
1536     var bnr = fname->bufnr()
1537     reply.result->map((_, selItem) => {
1538         DecodeSelectionRange(lspserver, bnr, selItem)
1539         return selItem
1540       })
1541   endif
1542
1543   selection.SelectionStart(lspserver, reply.result)
1544 enddef
1545
1546 # Expand the previous selection or start a new one
1547 def SelectionExpand(lspserver: dict<any>)
1548   # Check whether LSP server supports selection ranges
1549   if !lspserver.isSelectionRangeProvider
1550     util.ErrMsg('LSP server does not support selection ranges')
1551     return
1552   endif
1553
1554   selection.SelectionModify(lspserver, true)
1555 enddef
1556
1557 # Shrink the previous selection or start a new one
1558 def SelectionShrink(lspserver: dict<any>)
1559   # Check whether LSP server supports selection ranges
1560   if !lspserver.isSelectionRangeProvider
1561     util.ErrMsg('LSP server does not support selection ranges')
1562     return
1563   endif
1564
1565   selection.SelectionModify(lspserver, false)
1566 enddef
1567
1568 # fold the entire document
1569 # Request: "textDocument/foldingRange"
1570 # Param: FoldingRangeParams
1571 def FoldRange(lspserver: dict<any>, fname: string)
1572   # Check whether LSP server supports fold ranges
1573   if !lspserver.isFoldingRangeProvider
1574     util.ErrMsg('LSP server does not support folding')
1575     return
1576   endif
1577
1578   # interface FoldingRangeParams
1579   # interface TextDocumentIdentifier
1580   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1581   var reply = lspserver.rpc('textDocument/foldingRange', params)
1582   if reply->empty() || reply.result->empty()
1583     return
1584   endif
1585
1586   # result: FoldingRange[]
1587   var end_lnum: number
1588   var last_lnum: number = line('$')
1589   for foldRange in reply.result
1590     end_lnum = foldRange.endLine + 1
1591     if end_lnum < foldRange.startLine + 2
1592       end_lnum = foldRange.startLine + 2
1593     endif
1594     exe $':{foldRange.startLine + 2}, {end_lnum}fold'
1595     # Open all the folds, otherwise the subsequently created folds are not
1596     # correct.
1597     :silent! foldopen!
1598   endfor
1599
1600   if &foldcolumn == 0
1601     :setlocal foldcolumn=2
1602   endif
1603 enddef
1604
1605 # process the 'workspace/executeCommand' reply from the LSP server
1606 # Result: any | null
1607 def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
1608   # Nothing to do for the reply
1609 enddef
1610
1611 # Request the LSP server to execute a command
1612 # Request: workspace/executeCommand
1613 # Params: ExecuteCommandParams
1614 def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
1615   # Need to check for lspserver.caps.executeCommandProvider?
1616   var params = cmd
1617   lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
1618 enddef
1619
1620 # Display the LSP server capabilities (received during the initialization
1621 # stage).
1622 def GetCapabilities(lspserver: dict<any>): list<string>
1623   var l = []
1624   var heading = $"'{lspserver.path}' Language Server Capabilities"
1625   var underlines = repeat('=', heading->len())
1626   l->extend([heading, underlines])
1627   for k in lspserver.caps->keys()->sort()
1628     l->add($'{k}: {lspserver.caps[k]->string()}')
1629   endfor
1630   return l
1631 enddef
1632
1633 # Display the LSP server initialize request and result
1634 def GetInitializeRequest(lspserver: dict<any>): list<string>
1635   var l = []
1636   var heading = $"'{lspserver.path}' Language Server Initialize Request"
1637   var underlines = repeat('=', heading->len())
1638   l->extend([heading, underlines])
1639   if lspserver->has_key('rpcInitializeRequest')
1640     for k in lspserver.rpcInitializeRequest->keys()->sort()
1641       l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
1642     endfor
1643   endif
1644   return l
1645 enddef
1646
1647 # Store a log or trace message received from the language server.
1648 def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
1649   # A single message may contain multiple lines separate by newline
1650   var msgs = newMsg->split("\n")
1651   lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
1652   lspserver.messages->extend(msgs[1 : ])
1653   # Keep only the last 500 messages to reduce the memory usage
1654   if lspserver.messages->len() >= 600
1655     lspserver.messages = lspserver.messages[-500 : ]
1656   endif
1657 enddef
1658
1659 # Display the log messages received from the LSP server (window/logMessage)
1660 def GetMessages(lspserver: dict<any>): list<string>
1661   if lspserver.messages->empty()
1662     return [$'No messages received from "{lspserver.name}" server']
1663   endif
1664
1665   var l = []
1666   var heading = $"'{lspserver.path}' Language Server Messages"
1667   var underlines = repeat('=', heading->len())
1668   l->extend([heading, underlines])
1669   l->extend(lspserver.messages)
1670   return l
1671 enddef
1672
1673 # Send a 'textDocument/definition' request to the LSP server to get the
1674 # location where the symbol under the cursor is defined and return a list of
1675 # Dicts in a format accepted by the 'tagfunc' option.
1676 # Returns null if the LSP server doesn't support getting the location of a
1677 # symbol definition or the symbol is not defined.
1678 def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
1679   # Check whether LSP server supports getting the location of a definition
1680   if !lspserver.isDefinitionProvider
1681     return null
1682   endif
1683
1684   # interface DefinitionParams
1685   #   interface TextDocumentPositionParams
1686   var reply = lspserver.rpc('textDocument/definition',
1687                             lspserver.getTextDocPosition(false))
1688   if reply->empty() || reply.result->empty()
1689     return null
1690   endif
1691
1692   var taglocations: list<dict<any>>
1693   if reply.result->type() == v:t_list
1694     taglocations = reply.result
1695   else
1696     taglocations = [reply.result]
1697   endif
1698
1699   if lspserver.needOffsetEncoding
1700     # Decode the position encoding in all the reference locations
1701     taglocations->map((_, loc) => {
1702       lspserver.decodeLocation(loc)
1703       return loc
1704     })
1705   endif
1706
1707   return symbol.TagFunc(lspserver, taglocations, pat)
1708 enddef
1709
1710 # Returns unique ID used for identifying the various servers
1711 var UniqueServerIdCounter = 0
1712 def GetUniqueServerId(): number
1713   UniqueServerIdCounter = UniqueServerIdCounter + 1
1714   return UniqueServerIdCounter
1715 enddef
1716
1717 export def NewLspServer(serverParams: dict<any>): dict<any>
1718   var lspserver: dict<any> = {
1719     id: GetUniqueServerId(),
1720     name: serverParams.name,
1721     path: serverParams.path,
1722     args: serverParams.args->deepcopy(),
1723     syncInit: serverParams.syncInit,
1724     initializationOptions: serverParams.initializationOptions->deepcopy(),
1725     customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
1726     customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
1727     processDiagHandler: serverParams.processDiagHandler,
1728     features: serverParams.features->deepcopy(),
1729     running: false,
1730     ready: false,
1731     job: v:none,
1732     data: '',
1733     nextID: 1,
1734     caps: {},
1735     requests: {},
1736     rootSearchFiles: serverParams.rootSearch->deepcopy(),
1737     runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
1738     runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
1739     omniCompletePending: false,
1740     completionTriggerChars: [],
1741     signaturePopup: -1,
1742     typeHierPopup: -1,
1743     typeHierFilePopup: -1,
1744     workspaceSymbolPopup: -1,
1745     workspaceSymbolQuery: '',
1746     peekSymbolPopup: -1,
1747     peekSymbolFilePopup: -1,
1748     callHierarchyType: '',
1749     selection: {},
1750     workspaceConfig: serverParams.workspaceConfig->deepcopy(),
1751     messages: [],
1752     debug: serverParams.debug,
1753     traceLevel: serverParams.traceLevel
1754   }
1755   lspserver.logfile = $'lsp-{lspserver.name}.log'
1756   lspserver.errfile = $'lsp-{lspserver.name}.err'
1757
1758   # Add the LSP server functions
1759   lspserver->extend({
1760     startServer: function(StartServer, [lspserver]),
1761     initServer: function(InitServer, [lspserver]),
1762     stopServer: function(StopServer, [lspserver]),
1763     shutdownServer: function(ShutdownServer, [lspserver]),
1764     exitServer: function(ExitServer, [lspserver]),
1765     setTrace: function(SetTrace, [lspserver]),
1766     traceLog: function(TraceLog, [lspserver]),
1767     errorLog: function(ErrorLog, [lspserver]),
1768     nextReqID: function(NextReqID, [lspserver]),
1769     createRequest: function(CreateRequest, [lspserver]),
1770     createResponse: function(CreateResponse, [lspserver]),
1771     sendResponse: function(SendResponse, [lspserver]),
1772     sendMessage: function(SendMessage, [lspserver]),
1773     sendNotification: function(SendNotification, [lspserver]),
1774     rpc: function(Rpc, [lspserver]),
1775     rpc_a: function(AsyncRpc, [lspserver]),
1776     waitForResponse: function(WaitForResponse, [lspserver]),
1777     processReply: function(handlers.ProcessReply, [lspserver]),
1778     processNotif: function(handlers.ProcessNotif, [lspserver]),
1779     processRequest: function(handlers.ProcessRequest, [lspserver]),
1780     processMessages: function(handlers.ProcessMessages, [lspserver]),
1781     encodePosition: function(offset.EncodePosition, [lspserver]),
1782     decodePosition: function(offset.DecodePosition, [lspserver]),
1783     encodeRange: function(offset.EncodeRange, [lspserver]),
1784     decodeRange: function(offset.DecodeRange, [lspserver]),
1785     encodeLocation: function(offset.EncodeLocation, [lspserver]),
1786     decodeLocation: function(offset.DecodeLocation, [lspserver]),
1787     getPosition: function(GetPosition, [lspserver]),
1788     getTextDocPosition: function(GetTextDocPosition, [lspserver]),
1789     textdocDidOpen: function(TextdocDidOpen, [lspserver]),
1790     textdocDidClose: function(TextdocDidClose, [lspserver]),
1791     textdocDidChange: function(TextdocDidChange, [lspserver]),
1792     sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
1793     sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
1794     getCompletion: function(GetCompletion, [lspserver]),
1795     resolveCompletion: function(ResolveCompletion, [lspserver]),
1796     gotoDefinition: function(GotoDefinition, [lspserver]),
1797     gotoDeclaration: function(GotoDeclaration, [lspserver]),
1798     gotoTypeDef: function(GotoTypeDef, [lspserver]),
1799     gotoImplementation: function(GotoImplementation, [lspserver]),
1800     tagFunc: function(TagFunc, [lspserver]),
1801     switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
1802     showSignature: function(ShowSignature, [lspserver]),
1803     didSaveFile: function(DidSaveFile, [lspserver]),
1804     hover: function(ShowHoverInfo, [lspserver]),
1805     showReferences: function(ShowReferences, [lspserver]),
1806     docHighlight: function(DocHighlight, [lspserver]),
1807     getDocSymbols: function(GetDocSymbols, [lspserver]),
1808     textDocFormat: function(TextDocFormat, [lspserver]),
1809     prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
1810     incomingCalls: function(IncomingCalls, [lspserver]),
1811     getIncomingCalls: function(GetIncomingCalls, [lspserver]),
1812     outgoingCalls: function(OutgoingCalls, [lspserver]),
1813     getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
1814     inlayHintsShow: function(InlayHintsShow, [lspserver]),
1815     typeHierarchy: function(TypeHiearchy, [lspserver]),
1816     renameSymbol: function(RenameSymbol, [lspserver]),
1817     codeAction: function(CodeAction, [lspserver]),
1818     codeLens: function(CodeLens, [lspserver]),
1819     resolveCodeLens: function(ResolveCodeLens, [lspserver]),
1820     workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
1821     addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
1822     removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
1823     selectionRange: function(SelectionRange, [lspserver]),
1824     selectionExpand: function(SelectionExpand, [lspserver]),
1825     selectionShrink: function(SelectionShrink, [lspserver]),
1826     foldRange: function(FoldRange, [lspserver]),
1827     executeCommand: function(ExecuteCommand, [lspserver]),
1828     workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
1829     getCapabilities: function(GetCapabilities, [lspserver]),
1830     getInitializeRequest: function(GetInitializeRequest, [lspserver]),
1831     addMessage: function(AddMessage, [lspserver]),
1832     getMessages: function(GetMessages, [lspserver])
1833   })
1834
1835   return lspserver
1836 enddef
1837
1838 # vim: tabstop=8 shiftwidth=2 softtabstop=2