]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/lspserver.vim
Access the save key in textDocumentSync only if it is a Dict
[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 # Returns true when the "lspserver" has "feature" enabled.
512 # By default, all the features of a lsp server are enabled.
513 def FeatureEnabled(lspserver: dict<any>, feature: string): bool
514   return lspserver.features->get(feature, true)
515 enddef
516
517 # Retrieve the Workspace configuration asked by the server.
518 # Request: workspace/configuration
519 def WorkspaceConfigGet(lspserver: dict<any>, configItem: dict<any>): dict<any>
520   if lspserver.workspaceConfig->empty()
521     return {}
522   endif
523   if !configItem->has_key('section') || configItem.section->empty()
524     return lspserver.workspaceConfig
525   endif
526   var config: dict<any> = lspserver.workspaceConfig
527   for part in configItem.section->split('\.')
528     if !config->has_key(part)
529       return {}
530     endif
531     config = config[part]
532   endfor
533   return config
534 enddef
535
536 # Send a "workspace/didChangeConfiguration" notification to the language
537 # server.
538 def SendWorkspaceConfig(lspserver: dict<any>)
539   # Params: DidChangeConfigurationParams
540   var params = {settings: lspserver.workspaceConfig}
541   lspserver.sendNotification('workspace/didChangeConfiguration', params)
542 enddef
543
544 # Send a file/document opened notification to the language server.
545 def TextdocDidOpen(lspserver: dict<any>, bnr: number, ftype: string): void
546   # Notification: 'textDocument/didOpen'
547   # Params: DidOpenTextDocumentParams
548   var params = {
549     textDocument: {
550       uri: util.LspBufnrToUri(bnr),
551       languageId: ftype,
552       # Use Vim 'changedtick' as the LSP document version number
553       version: bnr->getbufvar('changedtick'),
554       text: bnr->getbufline(1, '$')->join("\n") .. "\n"
555     }
556   }
557   lspserver.sendNotification('textDocument/didOpen', params)
558 enddef
559
560 # Send a file/document closed notification to the language server.
561 def TextdocDidClose(lspserver: dict<any>, bnr: number): void
562   # Notification: 'textDocument/didClose'
563   # Params: DidCloseTextDocumentParams
564   var params = {
565     textDocument: {
566       uri: util.LspBufnrToUri(bnr)
567     }
568   }
569   lspserver.sendNotification('textDocument/didClose', params)
570 enddef
571
572 # Send a file/document change notification to the language server.
573 # Params: DidChangeTextDocumentParams
574 def TextdocDidChange(lspserver: dict<any>, bnr: number, start: number,
575                         end: number, added: number,
576                         changes: list<dict<number>>): void
577   # Notification: 'textDocument/didChange'
578   # Params: DidChangeTextDocumentParams
579
580   # var changeset: list<dict<any>>
581
582   ##### FIXME: Sending specific buffer changes to the LSP server doesn't
583   ##### work properly as the computed line range numbers is not correct.
584   ##### For now, send the entire buffer content to LSP server.
585   # #     Range
586   # for change in changes
587   #   var lines: string
588   #   var start_lnum: number
589   #   var end_lnum: number
590   #   var start_col: number
591   #   var end_col: number
592   #   if change.added == 0
593   #     # lines changed
594   #     start_lnum =  change.lnum - 1
595   #     end_lnum = change.end - 1
596   #     lines = getbufline(bnr, change.lnum, change.end - 1)->join("\n") .. "\n"
597   #     start_col = 0
598   #     end_col = 0
599   #   elseif change.added > 0
600   #     # lines added
601   #     start_lnum = change.lnum - 1
602   #     end_lnum = change.lnum - 1
603   #     start_col = 0
604   #     end_col = 0
605   #     lines = getbufline(bnr, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n"
606   #   else
607   #     # lines removed
608   #     start_lnum = change.lnum - 1
609   #     end_lnum = change.lnum + (-change.added) - 1
610   #     start_col = 0
611   #     end_col = 0
612   #     lines = ''
613   #   endif
614   #   var range: dict<dict<number>> = {'start': {'line': start_lnum, 'character': start_col}, 'end': {'line': end_lnum, 'character': end_col}}
615   #   changeset->add({'range': range, 'text': lines})
616   # endfor
617
618   var params = {
619     textDocument: {
620       uri: util.LspBufnrToUri(bnr),
621       # Use Vim 'changedtick' as the LSP document version number
622       version: bnr->getbufvar('changedtick')
623     },
624     contentChanges: [
625       {text: bnr->getbufline(1, '$')->join("\n") .. "\n"}
626     ]
627   }
628   lspserver.sendNotification('textDocument/didChange', params)
629 enddef
630
631 # Return the current cursor position as a LSP position.
632 # find_ident will search for a identifier in front of the cursor, just like
633 # CTRL-] and c_CTRL-R_CTRL-W does.
634 #
635 # LSP line and column numbers start from zero, whereas Vim line and column
636 # numbers start from one. The LSP column number is the character index in the
637 # line and not the byte index in the line.
638 def GetPosition(lspserver: dict<any>, find_ident: bool): dict<number>
639   var lnum: number = line('.') - 1
640   var col: number = charcol('.') - 1
641   var line = getline('.')
642
643   if find_ident
644     # 1. skip to start of identifier
645     while line[col] != '' && line[col] !~ '\k'
646       col = col + 1
647     endwhile
648
649     # 2. back up to start of identifier
650     while col > 0 && line[col - 1] =~ '\k'
651       col = col - 1
652     endwhile
653   endif
654
655   # Compute character index counting composing characters as separate
656   # characters
657   var pos = {line: lnum, character: util.GetCharIdxWithCompChar(line, col)}
658   lspserver.encodePosition(bufnr(), pos)
659
660   return pos
661 enddef
662
663 # Return the current file name and current cursor position as a LSP
664 # TextDocumentPositionParams structure
665 def GetTextDocPosition(lspserver: dict<any>, find_ident: bool): dict<dict<any>>
666   # interface TextDocumentIdentifier
667   # interface Position
668   return {textDocument: {uri: util.LspFileToUri(@%)},
669           position: lspserver.getPosition(find_ident)}
670 enddef
671
672 # Get a list of completion items.
673 # Request: "textDocument/completion"
674 # Param: CompletionParams
675 def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: string): void
676   # Check whether LSP server supports completion
677   if !lspserver.isCompletionProvider
678     util.ErrMsg('LSP server does not support completion')
679     return
680   endif
681
682   var fname = @%
683   if fname->empty()
684     return
685   endif
686
687   # interface CompletionParams
688   #   interface TextDocumentPositionParams
689   var params = lspserver.getTextDocPosition(false)
690   #   interface CompletionContext
691   params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
692
693   lspserver.rpc_a('textDocument/completion', params,
694                         completion.CompletionReply)
695 enddef
696
697 # Get lazy properties for a completion item.
698 # Request: "completionItem/resolve"
699 # Param: CompletionItem
700 def ResolveCompletion(lspserver: dict<any>, item: dict<any>, sync: bool = false): dict<any>
701   # Check whether LSP server supports completion item resolve
702   if !lspserver.isCompletionResolveProvider
703     return {}
704   endif
705
706   # interface CompletionItem
707   if sync
708     var reply = lspserver.rpc('completionItem/resolve', item)
709     if !reply->empty() && !reply.result->empty()
710       return reply.result
711     endif
712   else
713     lspserver.rpc_a('completionItem/resolve', item,
714                           completion.CompletionResolveReply)
715   endif
716   return {}
717 enddef
718
719 # Jump to or peek a symbol location.
720 #
721 # Send 'msg' to a LSP server and process the reply.  'msg' is one of the
722 # following:
723 #   textDocument/definition
724 #   textDocument/declaration
725 #   textDocument/typeDefinition
726 #   textDocument/implementation
727 #
728 # Process the LSP server reply and jump to the symbol location.  Before
729 # jumping to the symbol location, save the current cursor position in the tag
730 # stack.
731 #
732 # If 'peekSymbol' is true, then display the symbol location in the preview
733 # window but don't jump to the symbol location.
734 #
735 # Result: Location | Location[] | LocationLink[] | null
736 def GotoSymbolLoc(lspserver: dict<any>, msg: string, peekSymbol: bool,
737                   cmdmods: string, count: number)
738   var reply = lspserver.rpc(msg, lspserver.getTextDocPosition(true), false)
739   if reply->empty() || reply.result->empty()
740     var emsg: string
741     if msg == 'textDocument/declaration'
742       emsg = 'symbol declaration is not found'
743     elseif msg == 'textDocument/typeDefinition'
744       emsg = 'symbol type definition is not found'
745     elseif msg == 'textDocument/implementation'
746       emsg = 'symbol implementation is not found'
747     else
748       emsg = 'symbol definition is not found'
749     endif
750
751     util.WarnMsg(emsg)
752     return
753   endif
754
755   var result = reply.result
756   var location: dict<any>
757   if result->type() == v:t_list
758     if count == 0
759       # When there are multiple symbol locations, and a specific one isn't
760       # requested with 'count', display the locations in a location list.
761       if result->len() > 1
762         var title: string = ''
763         if msg == 'textDocument/declaration'
764           title = 'Declarations'
765         elseif msg == 'textDocument/typeDefinition'
766           title = 'Type Definitions'
767         elseif msg == 'textDocument/implementation'
768           title = 'Implementations'
769         else
770           title = 'Definitions'
771         endif
772
773         if lspserver.needOffsetEncoding
774           # Decode the position encoding in all the symbol locations
775           result->map((_, loc) => {
776               lspserver.decodeLocation(loc)
777               return loc
778             })
779         endif
780
781         symbol.ShowLocations(lspserver, result, peekSymbol, title)
782         return
783       endif
784     endif
785
786     # Select the location requested in 'count'
787     var idx = count - 1
788     if idx >= result->len()
789       idx = result->len() - 1
790     endif
791     location = result[idx]
792   else
793     location = result
794   endif
795   lspserver.decodeLocation(location)
796
797   symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods)
798 enddef
799
800 # Request: "textDocument/definition"
801 # Param: DefinitionParams
802 def GotoDefinition(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
803   # Check whether LSP server supports jumping to a definition
804   if !lspserver.isDefinitionProvider
805     util.ErrMsg('Jumping to a symbol definition is not supported')
806     return
807   endif
808
809   # interface DefinitionParams
810   #   interface TextDocumentPositionParams
811   GotoSymbolLoc(lspserver, 'textDocument/definition', peek, cmdmods, count)
812 enddef
813
814 # Request: "textDocument/declaration"
815 # Param: DeclarationParams
816 def GotoDeclaration(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
817   # Check whether LSP server supports jumping to a declaration
818   if !lspserver.isDeclarationProvider
819     util.ErrMsg('Jumping to a symbol declaration is not supported')
820     return
821   endif
822
823   # interface DeclarationParams
824   #   interface TextDocumentPositionParams
825   GotoSymbolLoc(lspserver, 'textDocument/declaration', peek, cmdmods, count)
826 enddef
827
828 # Request: "textDocument/typeDefinition"
829 # Param: TypeDefinitionParams
830 def GotoTypeDef(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
831   # Check whether LSP server supports jumping to a type definition
832   if !lspserver.isTypeDefinitionProvider
833     util.ErrMsg('Jumping to a symbol type definition is not supported')
834     return
835   endif
836
837   # interface TypeDefinitionParams
838   #   interface TextDocumentPositionParams
839   GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek, cmdmods, count)
840 enddef
841
842 # Request: "textDocument/implementation"
843 # Param: ImplementationParams
844 def GotoImplementation(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
845   # Check whether LSP server supports jumping to a implementation
846   if !lspserver.isImplementationProvider
847     util.ErrMsg('Jumping to a symbol implementation is not supported')
848     return
849   endif
850
851   # interface ImplementationParams
852   #   interface TextDocumentPositionParams
853   GotoSymbolLoc(lspserver, 'textDocument/implementation', peek, cmdmods, count)
854 enddef
855
856 # Request: "textDocument/switchSourceHeader"
857 # Param: TextDocumentIdentifier
858 # Clangd specific extension
859 def SwitchSourceHeader(lspserver: dict<any>)
860   var param = {
861     uri: util.LspFileToUri(@%)
862   }
863   var reply = lspserver.rpc('textDocument/switchSourceHeader', param)
864   if reply->empty() || reply.result->empty()
865     util.WarnMsg('Source/Header file is not found')
866     return
867   endif
868
869   # process the 'textDocument/switchSourceHeader' reply from the LSP server
870   # Result: URI | null
871   var fname = util.LspUriToFile(reply.result)
872   # TODO: Add support for cmd modifiers
873   if (&modified && !&hidden) || &buftype != ''
874     # if the current buffer has unsaved changes and 'hidden' is not set,
875     # or if the current buffer is a special buffer, then ask to save changes
876     exe $'confirm edit {fname}'
877   else
878     exe $'edit {fname}'
879   endif
880 enddef
881
882 # get symbol signature help.
883 # Request: "textDocument/signatureHelp"
884 # Param: SignatureHelpParams
885 def ShowSignature(lspserver: dict<any>): void
886   # Check whether LSP server supports signature help
887   if !lspserver.isSignatureHelpProvider
888     util.ErrMsg('LSP server does not support signature help')
889     return
890   endif
891
892   # interface SignatureHelpParams
893   #   interface TextDocumentPositionParams
894   var params = lspserver.getTextDocPosition(false)
895   lspserver.rpc_a('textDocument/signatureHelp', params,
896                         signature.SignatureHelp)
897 enddef
898
899 # Send a file/document saved notification to the language server
900 def DidSaveFile(lspserver: dict<any>, bnr: number): void
901   # Check whether the LSP server supports the didSave notification
902   if !lspserver.supportsDidSave
903     # LSP server doesn't support text document synchronization
904     return
905   endif
906
907   # Notification: 'textDocument/didSave'
908   # Params: DidSaveTextDocumentParams
909   var params: dict<any> = {textDocument: {uri: util.LspBufnrToUri(bnr)}}
910
911   if lspserver.caps.textDocumentSync->type() == v:t_dict
912       && lspserver.caps.textDocumentSync->has_key('save')
913     if lspserver.caps.textDocumentSync.save->type() == v:t_dict
914         && lspserver.caps.textDocumentSync.save->has_key('includeText')
915         && lspserver.caps.textDocumentSync.save.includeText
916       params.text = bnr->getbufline(1, '$')->join("\n") .. "\n"
917     endif
918   endif
919
920   lspserver.sendNotification('textDocument/didSave', params)
921 enddef
922
923 # get the hover information
924 # Request: "textDocument/hover"
925 # Param: HoverParams
926 def ShowHoverInfo(lspserver: dict<any>, cmdmods: string): void
927   # Check whether LSP server supports getting hover information.
928   # caps->hoverProvider can be a "boolean" or "HoverOptions"
929   if !lspserver.isHoverProvider
930     return
931   endif
932
933   # interface HoverParams
934   #   interface TextDocumentPositionParams
935   var params = lspserver.getTextDocPosition(false)
936   lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
937     hover.HoverReply(lspserver, reply, cmdmods)
938   })
939 enddef
940
941 # Request: "textDocument/references"
942 # Param: ReferenceParams
943 def ShowReferences(lspserver: dict<any>, peek: bool): void
944   # Check whether LSP server supports getting reference information
945   if !lspserver.isReferencesProvider
946     util.ErrMsg('LSP server does not support showing references')
947     return
948   endif
949
950   # interface ReferenceParams
951   #   interface TextDocumentPositionParams
952   var param: dict<any>
953   param = lspserver.getTextDocPosition(true)
954   param.context = {includeDeclaration: true}
955   var reply = lspserver.rpc('textDocument/references', param)
956
957   # Result: Location[] | null
958   if reply->empty() || reply.result->empty()
959     util.WarnMsg('No references found')
960     return
961   endif
962
963   if lspserver.needOffsetEncoding
964     # Decode the position encoding in all the reference locations
965     reply.result->map((_, loc) => {
966       lspserver.decodeLocation(loc)
967       return loc
968     })
969   endif
970
971   symbol.ShowLocations(lspserver, reply.result, peek, 'Symbol References')
972 enddef
973
974 # process the 'textDocument/documentHighlight' reply from the LSP server
975 # Result: DocumentHighlight[] | null
976 def DocHighlightReply(lspserver: dict<any>, docHighlightReply: any,
977                       bnr: number, cmdmods: string): void
978   if docHighlightReply->empty()
979     if cmdmods !~ 'silent'
980       util.WarnMsg($'No highlight for the current position')
981     endif
982     return
983   endif
984
985   for docHL in docHighlightReply
986     lspserver.decodeRange(bnr, docHL.range)
987     var kind: number = docHL->get('kind', 1)
988     var propName: string
989     if kind == 2
990       # Read-access
991       propName = 'LspReadRef'
992     elseif kind == 3
993       # Write-access
994       propName = 'LspWriteRef'
995     else
996       # textual reference
997       propName = 'LspTextRef'
998     endif
999     try
1000       var docHL_range = docHL.range
1001       var docHL_start = docHL_range.start
1002       var docHL_end = docHL_range.end
1003       prop_add(docHL_start.line + 1,
1004                   util.GetLineByteFromPos(bnr, docHL_start) + 1,
1005                   {end_lnum: docHL_end.line + 1,
1006                     end_col: util.GetLineByteFromPos(bnr, docHL_end) + 1,
1007                     bufnr: bnr,
1008                     type: propName})
1009     catch /E966\|E964/ # Invalid lnum | Invalid col
1010       # Highlight replies arrive asynchronously and the document might have
1011       # been modified in the mean time.  As the reply is stale, ignore invalid
1012       # line number and column number errors.
1013     endtry
1014   endfor
1015 enddef
1016
1017 # Request: "textDocument/documentHighlight"
1018 # Param: DocumentHighlightParams
1019 def DocHighlight(lspserver: dict<any>, bnr: number, cmdmods: string): void
1020   # Check whether LSP server supports getting highlight information
1021   if !lspserver.isDocumentHighlightProvider
1022     util.ErrMsg('LSP server does not support document highlight')
1023     return
1024   endif
1025
1026   # Send the pending buffer changes to the language server
1027   bnr->listener_flush()
1028
1029   # interface DocumentHighlightParams
1030   #   interface TextDocumentPositionParams
1031   var params = lspserver.getTextDocPosition(false)
1032   lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
1033     DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
1034   })
1035 enddef
1036
1037 # Request: "textDocument/documentSymbol"
1038 # Param: DocumentSymbolParams
1039 def GetDocSymbols(lspserver: dict<any>, fname: string, showOutline: bool): void
1040   # Check whether LSP server supports getting document symbol information
1041   if !lspserver.isDocumentSymbolProvider
1042     util.ErrMsg('LSP server does not support getting list of symbols')
1043     return
1044   endif
1045
1046   # interface DocumentSymbolParams
1047   # interface TextDocumentIdentifier
1048   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1049   lspserver.rpc_a('textDocument/documentSymbol', params, (_, reply) => {
1050     if showOutline
1051       symbol.DocSymbolOutline(lspserver, reply, fname)
1052     else
1053       symbol.DocSymbolPopup(lspserver, reply, fname)
1054     endif
1055   })
1056 enddef
1057
1058 # Request: "textDocument/formatting"
1059 # Param: DocumentFormattingParams
1060 # or
1061 # Request: "textDocument/rangeFormatting"
1062 # Param: DocumentRangeFormattingParams
1063 def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
1064                                 start_lnum: number, end_lnum: number)
1065   # Check whether LSP server supports formatting documents
1066   if !lspserver.isDocumentFormattingProvider
1067     util.ErrMsg('LSP server does not support formatting documents')
1068     return
1069   endif
1070
1071   var cmd: string
1072   if rangeFormat
1073     cmd = 'textDocument/rangeFormatting'
1074   else
1075     cmd = 'textDocument/formatting'
1076   endif
1077
1078   # interface DocumentFormattingParams
1079   #   interface TextDocumentIdentifier
1080   #   interface FormattingOptions
1081   var fmtopts: dict<any> = {
1082     tabSize: shiftwidth(),
1083     insertSpaces: &expandtab ? true : false,
1084   }
1085   var param = {
1086     textDocument: {
1087       uri: util.LspFileToUri(fname)
1088     },
1089     options: fmtopts
1090   }
1091
1092   if rangeFormat
1093     var r: dict<dict<number>> = {
1094         start: {line: start_lnum - 1, character: 0},
1095         end: {line: end_lnum - 1, character: charcol([end_lnum, '$']) - 1}}
1096     param.range = r
1097   endif
1098
1099   var reply = lspserver.rpc(cmd, param)
1100
1101   # result: TextEdit[] | null
1102
1103   if reply->empty() || reply.result->empty()
1104     # nothing to format
1105     return
1106   endif
1107
1108   var bnr: number = fname->bufnr()
1109   if bnr == -1
1110     # file is already removed
1111     return
1112   endif
1113
1114   if lspserver.needOffsetEncoding
1115     # Decode the position encoding in all the reference locations
1116     reply.result->map((_, textEdit) => {
1117       lspserver.decodeRange(bnr, textEdit.range)
1118       return textEdit
1119     })
1120   endif
1121
1122   # interface TextEdit
1123   # Apply each of the text edit operations
1124   var save_cursor: list<number> = getcurpos()
1125   textedit.ApplyTextEdits(bnr, reply.result)
1126   save_cursor->setpos('.')
1127 enddef
1128
1129 # Request: "textDocument/prepareCallHierarchy"
1130 def PrepareCallHierarchy(lspserver: dict<any>): dict<any>
1131   # interface CallHierarchyPrepareParams
1132   #   interface TextDocumentPositionParams
1133   var param: dict<any>
1134   param = lspserver.getTextDocPosition(false)
1135   var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
1136   if reply->empty() || reply.result->empty()
1137     return {}
1138   endif
1139
1140   # Result: CallHierarchyItem[] | null
1141   var choice: number = 1
1142   if reply.result->len() > 1
1143     var items: list<string> = ['Select a Call Hierarchy Item:']
1144     for i in reply.result->len()->range()
1145       items->add(printf("%d. %s", i + 1, reply.result[i].name))
1146     endfor
1147     choice = items->inputlist()
1148     if choice < 1 || choice > items->len()
1149       return {}
1150     endif
1151   endif
1152
1153   return reply.result[choice - 1]
1154 enddef
1155
1156 # Request: "callHierarchy/incomingCalls"
1157 def IncomingCalls(lspserver: dict<any>, fname: string)
1158   # Check whether LSP server supports call hierarchy
1159   if !lspserver.isCallHierarchyProvider
1160     util.ErrMsg('LSP server does not support call hierarchy')
1161     return
1162   endif
1163
1164   callhier.IncomingCalls(lspserver)
1165 enddef
1166
1167 def GetIncomingCalls(lspserver: dict<any>, item_arg: dict<any>): any
1168   # Request: "callHierarchy/incomingCalls"
1169   # Param: CallHierarchyIncomingCallsParams
1170   var param = {
1171     item: item_arg
1172   }
1173   var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
1174   if reply->empty()
1175     return null
1176   endif
1177
1178   if lspserver.needOffsetEncoding
1179     # Decode the position encoding in all the incoming call locations
1180     var bnr = util.LspUriToBufnr(item_arg.uri)
1181     reply.result->map((_, hierItem) => {
1182       lspserver.decodeRange(bnr, hierItem.from.range)
1183       return hierItem
1184     })
1185   endif
1186
1187   return reply.result
1188 enddef
1189
1190 # Request: "callHierarchy/outgoingCalls"
1191 def OutgoingCalls(lspserver: dict<any>, fname: string)
1192   # Check whether LSP server supports call hierarchy
1193   if !lspserver.isCallHierarchyProvider
1194     util.ErrMsg('LSP server does not support call hierarchy')
1195     return
1196   endif
1197
1198   callhier.OutgoingCalls(lspserver)
1199 enddef
1200
1201 def GetOutgoingCalls(lspserver: dict<any>, item_arg: dict<any>): any
1202   # Request: "callHierarchy/outgoingCalls"
1203   # Param: CallHierarchyOutgoingCallsParams
1204   var param = {
1205     item: item_arg
1206   }
1207   var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
1208   if reply->empty()
1209     return null
1210   endif
1211
1212   if lspserver.needOffsetEncoding
1213     # Decode the position encoding in all the outgoing call locations
1214     var bnr = util.LspUriToBufnr(item_arg.uri)
1215     reply.result->map((_, hierItem) => {
1216       lspserver.decodeRange(bnr, hierItem.to.range)
1217       return hierItem
1218     })
1219   endif
1220
1221   return reply.result
1222 enddef
1223
1224 # Request: "textDocument/inlayHint"
1225 # Inlay hints.
1226 def InlayHintsShow(lspserver: dict<any>, bnr: number)
1227   # Check whether LSP server supports type hierarchy
1228   if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
1229     util.ErrMsg('LSP server does not support inlay hint')
1230     return
1231   endif
1232
1233   # Send the pending buffer changes to the language server
1234   bnr->listener_flush()
1235
1236   var binfo = bnr->getbufinfo()
1237   if binfo->empty()
1238     return
1239   endif
1240   var lastlnum = binfo[0].linecount
1241   var lastline = bnr->getbufline('$')
1242   var lastcol = 1
1243   if !lastline->empty() && !lastline[0]->empty()
1244     lastcol = lastline[0]->strchars()
1245   endif
1246   var param = {
1247       textDocument: {uri: util.LspBufnrToUri(bnr)},
1248       range:
1249       {
1250         start: {line: 0, character: 0},
1251         end: {line: lastlnum - 1, character: lastcol - 1}
1252       }
1253   }
1254
1255   lspserver.encodeRange(bnr, param.range)
1256
1257   var msg: string
1258   if lspserver.isClangdInlayHintsProvider
1259     # clangd-style inlay hints
1260     msg = 'clangd/inlayHints'
1261   else
1262     msg = 'textDocument/inlayHint'
1263   endif
1264   var reply = lspserver.rpc_a(msg, param, (_, reply) => {
1265     inlayhints.InlayHintsReply(lspserver, bnr, reply)
1266   })
1267 enddef
1268
1269 def DecodeTypeHierarchy(lspserver: dict<any>, isSuper: bool, typeHier: dict<any>)
1270   if !lspserver.needOffsetEncoding
1271     return
1272   endif
1273   var bnr = util.LspUriToBufnr(typeHier.uri)
1274   lspserver.decodeRange(bnr, typeHier.range)
1275   lspserver.decodeRange(bnr, typeHier.selectionRange)
1276   var subType: list<dict<any>>
1277   if isSuper
1278     subType = typeHier->get('parents', [])
1279   else
1280     subType = typeHier->get('children', [])
1281   endif
1282   if !subType->empty()
1283     # Decode the position encoding in all the type hierarchy items
1284     subType->map((_, typeHierItem) => {
1285         DecodeTypeHierarchy(lspserver, isSuper, typeHierItem)
1286         return typeHierItem
1287       })
1288   endif
1289 enddef
1290
1291 # Request: "textDocument/typehierarchy"
1292 # Support the clangd version of type hierarchy retrieval method.
1293 # The method described in the LSP 3.17.0 standard is not supported as clangd
1294 # doesn't support that method.
1295 def TypeHierarchy(lspserver: dict<any>, direction: number)
1296   # Check whether LSP server supports type hierarchy
1297   if !lspserver.isTypeHierarchyProvider
1298     util.ErrMsg('LSP server does not support type hierarchy')
1299     return
1300   endif
1301
1302   # interface TypeHierarchy
1303   #   interface TextDocumentPositionParams
1304   var param: dict<any>
1305   param = lspserver.getTextDocPosition(false)
1306   # 0: children, 1: parent, 2: both
1307   param.direction = direction
1308   param.resolve = 5
1309   var reply = lspserver.rpc('textDocument/typeHierarchy', param)
1310   if reply->empty() || reply.result->empty()
1311     util.WarnMsg('No type hierarchy available')
1312     return
1313   endif
1314
1315   var isSuper = (direction == 1)
1316
1317   DecodeTypeHierarchy(lspserver, isSuper, reply.result)
1318
1319   typehier.ShowTypeHierarchy(lspserver, isSuper, reply.result)
1320 enddef
1321
1322 # Decode the ranges in "WorkspaceEdit"
1323 def DecodeWorkspaceEdit(lspserver: dict<any>, workspaceEdit: dict<any>)
1324   if !lspserver.needOffsetEncoding
1325     return
1326   endif
1327   if workspaceEdit->has_key('changes')
1328     for [uri, changes] in workspaceEdit.changes->items()
1329       var bnr: number = util.LspUriToBufnr(uri)
1330       if bnr <= 0
1331         continue
1332       endif
1333       # Decode the position encoding in all the text edit locations
1334       changes->map((_, textEdit) => {
1335         lspserver.decodeRange(bnr, textEdit.range)
1336         return textEdit
1337       })
1338     endfor
1339   endif
1340
1341   if workspaceEdit->has_key('documentChanges')
1342     for change in workspaceEdit.documentChanges
1343       if !change->has_key('kind')
1344         var bnr: number = util.LspUriToBufnr(change.textDocument.uri)
1345         if bnr <= 0
1346           continue
1347         endif
1348         # Decode the position encoding in all the text edit locations
1349         change.edits->map((_, textEdit) => {
1350           lspserver.decodeRange(bnr, textEdit.range)
1351           return textEdit
1352         })
1353       endif
1354     endfor
1355   endif
1356 enddef
1357
1358 # Request: "textDocument/rename"
1359 # Param: RenameParams
1360 def RenameSymbol(lspserver: dict<any>, newName: string)
1361   # Check whether LSP server supports rename operation
1362   if !lspserver.isRenameProvider
1363     util.ErrMsg('LSP server does not support rename operation')
1364     return
1365   endif
1366
1367   # interface RenameParams
1368   #   interface TextDocumentPositionParams
1369   var param: dict<any> = {}
1370   param = lspserver.getTextDocPosition(true)
1371   param.newName = newName
1372
1373   var reply = lspserver.rpc('textDocument/rename', param)
1374
1375   # Result: WorkspaceEdit | null
1376   if reply->empty() || reply.result->empty()
1377     # nothing to rename
1378     return
1379   endif
1380
1381   # result: WorkspaceEdit
1382   DecodeWorkspaceEdit(lspserver, reply.result)
1383   textedit.ApplyWorkspaceEdit(reply.result)
1384 enddef
1385
1386 # Decode the range in "CodeAction"
1387 def DecodeCodeAction(lspserver: dict<any>, actionList: list<dict<any>>)
1388   if !lspserver.needOffsetEncoding
1389     return
1390   endif
1391   actionList->map((_, act) => {
1392       if !act->has_key('disabled') && act->has_key('edit')
1393         DecodeWorkspaceEdit(lspserver, act.edit)
1394       endif
1395       return act
1396     })
1397 enddef
1398
1399 # Request: "textDocument/codeAction"
1400 # Param: CodeActionParams
1401 def CodeAction(lspserver: dict<any>, fname_arg: string, line1: number,
1402                 line2: number, query: string)
1403   # Check whether LSP server supports code action operation
1404   if !lspserver.isCodeActionProvider
1405     util.ErrMsg('LSP server does not support code action operation')
1406     return
1407   endif
1408
1409   # interface CodeActionParams
1410   var params: dict<any> = {}
1411   var fname: string = fname_arg->fnamemodify(':p')
1412   var bnr: number = fname_arg->bufnr()
1413   var r: dict<dict<number>> = {
1414     start: {
1415       line: line1 - 1,
1416       character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
1417     },
1418     end: {
1419       line: line2 - 1,
1420       character: util.GetCharIdxWithCompChar(getline(line2), charcol([line2, '$']) - 1)
1421     }
1422   }
1423   lspserver.encodeRange(bnr, r)
1424   params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
1425   var d: list<dict<any>> = []
1426   for lnum in range(line1, line2)
1427     var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(bnr, lnum, lspserver)->deepcopy()
1428     if lspserver.needOffsetEncoding
1429       diagsInfo->map((_, di) => {
1430           lspserver.encodeRange(bnr, di.range)
1431           return di
1432         })
1433     endif
1434     d->extend(diagsInfo)
1435   endfor
1436   params->extend({context: {diagnostics: d, triggerKind: 1}})
1437
1438   var reply = lspserver.rpc('textDocument/codeAction', params)
1439
1440   # Result: (Command | CodeAction)[] | null
1441   if reply->empty() || reply.result->empty()
1442     # no action can be performed
1443     util.WarnMsg('No code action is available')
1444     return
1445   endif
1446
1447   DecodeCodeAction(lspserver, reply.result)
1448
1449   codeaction.ApplyCodeAction(lspserver, reply.result, query)
1450 enddef
1451
1452 # Request: "textDocument/codeLens"
1453 # Param: CodeLensParams
1454 def CodeLens(lspserver: dict<any>, fname: string)
1455   # Check whether LSP server supports code lens operation
1456   if !lspserver.isCodeLensProvider
1457     util.ErrMsg('LSP server does not support code lens operation')
1458     return
1459   endif
1460
1461   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1462   var reply = lspserver.rpc('textDocument/codeLens', params)
1463   if reply->empty() || reply.result->empty()
1464     util.WarnMsg($'No code lens actions found for the current file')
1465     return
1466   endif
1467
1468   var bnr = fname->bufnr()
1469
1470   # Decode the position encoding in all the code lens items
1471   if lspserver.needOffsetEncoding
1472     reply.result->map((_, codeLensItem) => {
1473       lspserver.decodeRange(bnr, codeLensItem.range)
1474       return codeLensItem
1475     })
1476   endif
1477
1478   codelens.ProcessCodeLens(lspserver, bnr, reply.result)
1479 enddef
1480
1481 # Request: "codeLens/resolve"
1482 # Param: CodeLens
1483 def ResolveCodeLens(lspserver: dict<any>, bnr: number,
1484                     codeLens: dict<any>): dict<any>
1485   if !lspserver.isCodeLensResolveProvider
1486     return {}
1487   endif
1488
1489   if lspserver.needOffsetEncoding
1490     lspserver.encodeRange(bnr, codeLens.range)
1491   endif
1492
1493   var reply = lspserver.rpc('codeLens/resolve', codeLens)
1494   if reply->empty()
1495     return {}
1496   endif
1497
1498   var codeLensItem: dict<any> = reply.result
1499
1500   # Decode the position encoding in the code lens item
1501   if lspserver.needOffsetEncoding
1502     lspserver.decodeRange(bnr, codeLensItem.range)
1503   endif
1504
1505   return codeLensItem
1506 enddef
1507
1508 # List project-wide symbols matching query string
1509 # Request: "workspace/symbol"
1510 # Param: WorkspaceSymbolParams
1511 def WorkspaceQuerySymbols(lspserver: dict<any>, query: string, firstCall: bool, cmdmods: string = '')
1512   # Check whether the LSP server supports listing workspace symbols
1513   if !lspserver.isWorkspaceSymbolProvider
1514     util.ErrMsg('LSP server does not support listing workspace symbols')
1515     return
1516   endif
1517
1518   # Param: WorkspaceSymbolParams
1519   var param = {
1520     query: query
1521   }
1522   var reply = lspserver.rpc('workspace/symbol', param)
1523   if reply->empty() || reply.result->empty()
1524     util.WarnMsg($'Symbol "{query}" is not found')
1525     return
1526   endif
1527
1528   var symInfo: list<dict<any>> = reply.result
1529
1530   if lspserver.needOffsetEncoding
1531     # Decode the position encoding in all the symbol locations
1532     symInfo->map((_, sym) => {
1533       if sym->has_key('location')
1534         lspserver.decodeLocation(sym.location)
1535       endif
1536       return sym
1537     })
1538   endif
1539
1540   if firstCall && symInfo->len() == 1
1541     # If there is only one symbol, then jump to the symbol location
1542     var symLoc: dict<any> = symInfo[0]->get('location', {})
1543     if !symLoc->empty()
1544       symbol.GotoSymbol(lspserver, symLoc, false, cmdmods)
1545     endif
1546   else
1547     symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods)
1548   endif
1549 enddef
1550
1551 # Add a workspace folder to the language server.
1552 def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1553   if !lspserver.caps->has_key('workspace')
1554           || !lspserver.caps.workspace->has_key('workspaceFolders')
1555           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1556           || !lspserver.caps.workspace.workspaceFolders.supported
1557       util.ErrMsg('LSP server does not support workspace folders')
1558     return
1559   endif
1560
1561   if lspserver.workspaceFolders->index(dirName) != -1
1562     util.ErrMsg($'{dirName} is already part of this workspace')
1563     return
1564   endif
1565
1566   # Notification: 'workspace/didChangeWorkspaceFolders'
1567   # Params: DidChangeWorkspaceFoldersParams
1568   var params = {event: {added: [dirName], removed: []}}
1569   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1570
1571   lspserver.workspaceFolders->add(dirName)
1572 enddef
1573
1574 # Remove a workspace folder from the language server.
1575 def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1576   if !lspserver.caps->has_key('workspace')
1577           || !lspserver.caps.workspace->has_key('workspaceFolders')
1578           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1579           || !lspserver.caps.workspace.workspaceFolders.supported
1580       util.ErrMsg('LSP server does not support workspace folders')
1581     return
1582   endif
1583
1584   var idx: number = lspserver.workspaceFolders->index(dirName)
1585   if idx == -1
1586     util.ErrMsg($'{dirName} is not currently part of this workspace')
1587     return
1588   endif
1589
1590   # Notification: "workspace/didChangeWorkspaceFolders"
1591   # Param: DidChangeWorkspaceFoldersParams
1592   var params = {event: {added: [], removed: [dirName]}}
1593   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1594
1595   lspserver.workspaceFolders->remove(idx)
1596 enddef
1597
1598 def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
1599   lspserver.decodeRange(bnr, selRange.range)
1600   if selRange->has_key('parent')
1601     DecodeSelectionRange(lspserver, bnr, selRange.parent)
1602   endif
1603 enddef
1604
1605 # select the text around the current cursor location
1606 # Request: "textDocument/selectionRange"
1607 # Param: SelectionRangeParams
1608 def SelectionRange(lspserver: dict<any>, fname: string)
1609   # Check whether LSP server supports selection ranges
1610   if !lspserver.isSelectionRangeProvider
1611     util.ErrMsg('LSP server does not support selection ranges')
1612     return
1613   endif
1614
1615   # clear the previous selection reply
1616   lspserver.selection = {}
1617
1618   # interface SelectionRangeParams
1619   # interface TextDocumentIdentifier
1620   var param = {
1621     textDocument: {
1622       uri: util.LspFileToUri(fname)
1623     },
1624     positions: [lspserver.getPosition(false)]
1625   }
1626   var reply = lspserver.rpc('textDocument/selectionRange', param)
1627
1628   if reply->empty() || reply.result->empty()
1629     return
1630   endif
1631
1632   # Decode the position encoding in all the selection range items
1633   if lspserver.needOffsetEncoding
1634     var bnr = fname->bufnr()
1635     reply.result->map((_, selItem) => {
1636         DecodeSelectionRange(lspserver, bnr, selItem)
1637         return selItem
1638       })
1639   endif
1640
1641   selection.SelectionStart(lspserver, reply.result)
1642 enddef
1643
1644 # Expand the previous selection or start a new one
1645 def SelectionExpand(lspserver: dict<any>)
1646   # Check whether LSP server supports selection ranges
1647   if !lspserver.isSelectionRangeProvider
1648     util.ErrMsg('LSP server does not support selection ranges')
1649     return
1650   endif
1651
1652   selection.SelectionModify(lspserver, true)
1653 enddef
1654
1655 # Shrink the previous selection or start a new one
1656 def SelectionShrink(lspserver: dict<any>)
1657   # Check whether LSP server supports selection ranges
1658   if !lspserver.isSelectionRangeProvider
1659     util.ErrMsg('LSP server does not support selection ranges')
1660     return
1661   endif
1662
1663   selection.SelectionModify(lspserver, false)
1664 enddef
1665
1666 # fold the entire document
1667 # Request: "textDocument/foldingRange"
1668 # Param: FoldingRangeParams
1669 def FoldRange(lspserver: dict<any>, fname: string)
1670   # Check whether LSP server supports fold ranges
1671   if !lspserver.isFoldingRangeProvider
1672     util.ErrMsg('LSP server does not support folding')
1673     return
1674   endif
1675
1676   # interface FoldingRangeParams
1677   # interface TextDocumentIdentifier
1678   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1679   var reply = lspserver.rpc('textDocument/foldingRange', params)
1680   if reply->empty() || reply.result->empty()
1681     return
1682   endif
1683
1684   # result: FoldingRange[]
1685   var end_lnum: number
1686   var last_lnum: number = line('$')
1687   for foldRange in reply.result
1688     end_lnum = foldRange.endLine + 1
1689     if end_lnum < foldRange.startLine + 2
1690       end_lnum = foldRange.startLine + 2
1691     endif
1692     exe $':{foldRange.startLine + 2}, {end_lnum}fold'
1693     # Open all the folds, otherwise the subsequently created folds are not
1694     # correct.
1695     :silent! foldopen!
1696   endfor
1697
1698   if &foldcolumn == 0
1699     :setlocal foldcolumn=2
1700   endif
1701 enddef
1702
1703 # process the 'workspace/executeCommand' reply from the LSP server
1704 # Result: any | null
1705 def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
1706   # Nothing to do for the reply
1707 enddef
1708
1709 # Request the LSP server to execute a command
1710 # Request: workspace/executeCommand
1711 # Params: ExecuteCommandParams
1712 def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
1713   # Need to check for lspserver.caps.executeCommandProvider?
1714   var params = cmd
1715   lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
1716 enddef
1717
1718 # Display the LSP server capabilities (received during the initialization
1719 # stage).
1720 def GetCapabilities(lspserver: dict<any>): list<string>
1721   var l = []
1722   var heading = $"'{lspserver.path}' Language Server Capabilities"
1723   var underlines = repeat('=', heading->len())
1724   l->extend([heading, underlines])
1725   for k in lspserver.caps->keys()->sort()
1726     l->add($'{k}: {lspserver.caps[k]->string()}')
1727   endfor
1728   return l
1729 enddef
1730
1731 # Display the LSP server initialize request and result
1732 def GetInitializeRequest(lspserver: dict<any>): list<string>
1733   var l = []
1734   var heading = $"'{lspserver.path}' Language Server Initialize Request"
1735   var underlines = repeat('=', heading->len())
1736   l->extend([heading, underlines])
1737   if lspserver->has_key('rpcInitializeRequest')
1738     for k in lspserver.rpcInitializeRequest->keys()->sort()
1739       l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
1740     endfor
1741   endif
1742   return l
1743 enddef
1744
1745 # Store a log or trace message received from the language server.
1746 def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
1747   # A single message may contain multiple lines separate by newline
1748   var msgs = newMsg->split("\n")
1749   lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
1750   lspserver.messages->extend(msgs[1 : ])
1751   # Keep only the last 500 messages to reduce the memory usage
1752   if lspserver.messages->len() >= 600
1753     lspserver.messages = lspserver.messages[-500 : ]
1754   endif
1755 enddef
1756
1757 # Display the log messages received from the LSP server (window/logMessage)
1758 def GetMessages(lspserver: dict<any>): list<string>
1759   if lspserver.messages->empty()
1760     return [$'No messages received from "{lspserver.name}" server']
1761   endif
1762
1763   var l = []
1764   var heading = $"'{lspserver.path}' Language Server Messages"
1765   var underlines = repeat('=', heading->len())
1766   l->extend([heading, underlines])
1767   l->extend(lspserver.messages)
1768   return l
1769 enddef
1770
1771 # Send a 'textDocument/definition' request to the LSP server to get the
1772 # location where the symbol under the cursor is defined and return a list of
1773 # Dicts in a format accepted by the 'tagfunc' option.
1774 # Returns null if the LSP server doesn't support getting the location of a
1775 # symbol definition or the symbol is not defined.
1776 def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
1777   # Check whether LSP server supports getting the location of a definition
1778   if !lspserver.isDefinitionProvider
1779     return null
1780   endif
1781
1782   # interface DefinitionParams
1783   #   interface TextDocumentPositionParams
1784   var reply = lspserver.rpc('textDocument/definition',
1785                             lspserver.getTextDocPosition(false))
1786   if reply->empty() || reply.result->empty()
1787     return null
1788   endif
1789
1790   var taglocations: list<dict<any>>
1791   if reply.result->type() == v:t_list
1792     taglocations = reply.result
1793   else
1794     taglocations = [reply.result]
1795   endif
1796
1797   if lspserver.needOffsetEncoding
1798     # Decode the position encoding in all the reference locations
1799     taglocations->map((_, loc) => {
1800       lspserver.decodeLocation(loc)
1801       return loc
1802     })
1803   endif
1804
1805   return symbol.TagFunc(lspserver, taglocations, pat)
1806 enddef
1807
1808 # Returns unique ID used for identifying the various servers
1809 var UniqueServerIdCounter = 0
1810 def GetUniqueServerId(): number
1811   UniqueServerIdCounter = UniqueServerIdCounter + 1
1812   return UniqueServerIdCounter
1813 enddef
1814
1815 export def NewLspServer(serverParams: dict<any>): dict<any>
1816   var lspserver: dict<any> = {
1817     id: GetUniqueServerId(),
1818     name: serverParams.name,
1819     path: serverParams.path,
1820     args: serverParams.args->deepcopy(),
1821     running: false,
1822     ready: false,
1823     job: v:none,
1824     data: '',
1825     nextID: 1,
1826     caps: {},
1827     requests: {},
1828     callHierarchyType: '',
1829     completionTriggerChars: [],
1830     customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
1831     customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
1832     debug: serverParams.debug,
1833     features: serverParams.features->deepcopy(),
1834     forceOffsetEncoding: serverParams.forceOffsetEncoding,
1835     initializationOptions: serverParams.initializationOptions->deepcopy(),
1836     messages: [],
1837     needOffsetEncoding: false,
1838     omniCompletePending: false,
1839     peekSymbolFilePopup: -1,
1840     peekSymbolPopup: -1,
1841     processDiagHandler: serverParams.processDiagHandler,
1842     rootSearchFiles: serverParams.rootSearch->deepcopy(),
1843     runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
1844     runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
1845     selection: {},
1846     signaturePopup: -1,
1847     syncInit: serverParams.syncInit,
1848     traceLevel: serverParams.traceLevel,
1849     typeHierFilePopup: -1,
1850     typeHierPopup: -1,
1851     workspaceConfig: serverParams.workspaceConfig->deepcopy(),
1852     workspaceSymbolPopup: -1,
1853     workspaceSymbolQuery: ''
1854   }
1855   lspserver.logfile = $'lsp-{lspserver.name}.log'
1856   lspserver.errfile = $'lsp-{lspserver.name}.err'
1857
1858   # Add the LSP server functions
1859   lspserver->extend({
1860     startServer: function(StartServer, [lspserver]),
1861     initServer: function(InitServer, [lspserver]),
1862     stopServer: function(StopServer, [lspserver]),
1863     shutdownServer: function(ShutdownServer, [lspserver]),
1864     exitServer: function(ExitServer, [lspserver]),
1865     setTrace: function(SetTrace, [lspserver]),
1866     traceLog: function(TraceLog, [lspserver]),
1867     errorLog: function(ErrorLog, [lspserver]),
1868     nextReqID: function(NextReqID, [lspserver]),
1869     createRequest: function(CreateRequest, [lspserver]),
1870     createResponse: function(CreateResponse, [lspserver]),
1871     sendResponse: function(SendResponse, [lspserver]),
1872     sendMessage: function(SendMessage, [lspserver]),
1873     sendNotification: function(SendNotification, [lspserver]),
1874     rpc: function(Rpc, [lspserver]),
1875     rpc_a: function(AsyncRpc, [lspserver]),
1876     waitForResponse: function(WaitForResponse, [lspserver]),
1877     processReply: function(handlers.ProcessReply, [lspserver]),
1878     processNotif: function(handlers.ProcessNotif, [lspserver]),
1879     processRequest: function(handlers.ProcessRequest, [lspserver]),
1880     processMessages: function(handlers.ProcessMessages, [lspserver]),
1881     encodePosition: function(offset.EncodePosition, [lspserver]),
1882     decodePosition: function(offset.DecodePosition, [lspserver]),
1883     encodeRange: function(offset.EncodeRange, [lspserver]),
1884     decodeRange: function(offset.DecodeRange, [lspserver]),
1885     encodeLocation: function(offset.EncodeLocation, [lspserver]),
1886     decodeLocation: function(offset.DecodeLocation, [lspserver]),
1887     getPosition: function(GetPosition, [lspserver]),
1888     getTextDocPosition: function(GetTextDocPosition, [lspserver]),
1889     featureEnabled: function(FeatureEnabled, [lspserver]),
1890     textdocDidOpen: function(TextdocDidOpen, [lspserver]),
1891     textdocDidClose: function(TextdocDidClose, [lspserver]),
1892     textdocDidChange: function(TextdocDidChange, [lspserver]),
1893     sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
1894     sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
1895     getCompletion: function(GetCompletion, [lspserver]),
1896     resolveCompletion: function(ResolveCompletion, [lspserver]),
1897     gotoDefinition: function(GotoDefinition, [lspserver]),
1898     gotoDeclaration: function(GotoDeclaration, [lspserver]),
1899     gotoTypeDef: function(GotoTypeDef, [lspserver]),
1900     gotoImplementation: function(GotoImplementation, [lspserver]),
1901     tagFunc: function(TagFunc, [lspserver]),
1902     switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
1903     showSignature: function(ShowSignature, [lspserver]),
1904     didSaveFile: function(DidSaveFile, [lspserver]),
1905     hover: function(ShowHoverInfo, [lspserver]),
1906     showReferences: function(ShowReferences, [lspserver]),
1907     docHighlight: function(DocHighlight, [lspserver]),
1908     getDocSymbols: function(GetDocSymbols, [lspserver]),
1909     textDocFormat: function(TextDocFormat, [lspserver]),
1910     prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
1911     incomingCalls: function(IncomingCalls, [lspserver]),
1912     getIncomingCalls: function(GetIncomingCalls, [lspserver]),
1913     outgoingCalls: function(OutgoingCalls, [lspserver]),
1914     getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
1915     inlayHintsShow: function(InlayHintsShow, [lspserver]),
1916     typeHierarchy: function(TypeHierarchy, [lspserver]),
1917     renameSymbol: function(RenameSymbol, [lspserver]),
1918     codeAction: function(CodeAction, [lspserver]),
1919     codeLens: function(CodeLens, [lspserver]),
1920     resolveCodeLens: function(ResolveCodeLens, [lspserver]),
1921     workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
1922     addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
1923     removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
1924     selectionRange: function(SelectionRange, [lspserver]),
1925     selectionExpand: function(SelectionExpand, [lspserver]),
1926     selectionShrink: function(SelectionShrink, [lspserver]),
1927     foldRange: function(FoldRange, [lspserver]),
1928     executeCommand: function(ExecuteCommand, [lspserver]),
1929     workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
1930     getCapabilities: function(GetCapabilities, [lspserver]),
1931     getInitializeRequest: function(GetInitializeRequest, [lspserver]),
1932     addMessage: function(AddMessage, [lspserver]),
1933     getMessages: function(GetMessages, [lspserver])
1934   })
1935
1936   return lspserver
1937 enddef
1938
1939 # vim: tabstop=8 shiftwidth=2 softtabstop=2