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