]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/lspserver.vim
3686b4446d5f4881cdbab3ae3163520250e7014b
[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 = {textDocument: {uri: util.LspBufnrToUri(bnr)}}
910   # FIXME: Need to set "params.text" when
911   # 'lspserver.caps.textDocumentSync.save.includeText' is set to true.
912   lspserver.sendNotification('textDocument/didSave', params)
913 enddef
914
915 # get the hover information
916 # Request: "textDocument/hover"
917 # Param: HoverParams
918 def ShowHoverInfo(lspserver: dict<any>, cmdmods: string): void
919   # Check whether LSP server supports getting hover information.
920   # caps->hoverProvider can be a "boolean" or "HoverOptions"
921   if !lspserver.isHoverProvider
922     return
923   endif
924
925   # interface HoverParams
926   #   interface TextDocumentPositionParams
927   var params = lspserver.getTextDocPosition(false)
928   lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
929     hover.HoverReply(lspserver, reply, cmdmods)
930   })
931 enddef
932
933 # Request: "textDocument/references"
934 # Param: ReferenceParams
935 def ShowReferences(lspserver: dict<any>, peek: bool): void
936   # Check whether LSP server supports getting reference information
937   if !lspserver.isReferencesProvider
938     util.ErrMsg('LSP server does not support showing references')
939     return
940   endif
941
942   # interface ReferenceParams
943   #   interface TextDocumentPositionParams
944   var param: dict<any>
945   param = lspserver.getTextDocPosition(true)
946   param.context = {includeDeclaration: true}
947   var reply = lspserver.rpc('textDocument/references', param)
948
949   # Result: Location[] | null
950   if reply->empty() || reply.result->empty()
951     util.WarnMsg('No references found')
952     return
953   endif
954
955   if lspserver.needOffsetEncoding
956     # Decode the position encoding in all the reference locations
957     reply.result->map((_, loc) => {
958       lspserver.decodeLocation(loc)
959       return loc
960     })
961   endif
962
963   symbol.ShowLocations(lspserver, reply.result, peek, 'Symbol References')
964 enddef
965
966 # process the 'textDocument/documentHighlight' reply from the LSP server
967 # Result: DocumentHighlight[] | null
968 def DocHighlightReply(lspserver: dict<any>, docHighlightReply: any,
969                       bnr: number, cmdmods: string): void
970   if docHighlightReply->empty()
971     if cmdmods !~ 'silent'
972       util.WarnMsg($'No highlight for the current position')
973     endif
974     return
975   endif
976
977   for docHL in docHighlightReply
978     lspserver.decodeRange(bnr, docHL.range)
979     var kind: number = docHL->get('kind', 1)
980     var propName: string
981     if kind == 2
982       # Read-access
983       propName = 'LspReadRef'
984     elseif kind == 3
985       # Write-access
986       propName = 'LspWriteRef'
987     else
988       # textual reference
989       propName = 'LspTextRef'
990     endif
991     try
992       var docHL_range = docHL.range
993       var docHL_start = docHL_range.start
994       var docHL_end = docHL_range.end
995       prop_add(docHL_start.line + 1,
996                   util.GetLineByteFromPos(bnr, docHL_start) + 1,
997                   {end_lnum: docHL_end.line + 1,
998                     end_col: util.GetLineByteFromPos(bnr, docHL_end) + 1,
999                     bufnr: bnr,
1000                     type: propName})
1001     catch /E966\|E964/ # Invalid lnum | Invalid col
1002       # Highlight replies arrive asynchronously and the document might have
1003       # been modified in the mean time.  As the reply is stale, ignore invalid
1004       # line number and column number errors.
1005     endtry
1006   endfor
1007 enddef
1008
1009 # Request: "textDocument/documentHighlight"
1010 # Param: DocumentHighlightParams
1011 def DocHighlight(lspserver: dict<any>, bnr: number, cmdmods: string): void
1012   # Check whether LSP server supports getting highlight information
1013   if !lspserver.isDocumentHighlightProvider
1014     util.ErrMsg('LSP server does not support document highlight')
1015     return
1016   endif
1017
1018   # Send the pending buffer changes to the language server
1019   bnr->listener_flush()
1020
1021   # interface DocumentHighlightParams
1022   #   interface TextDocumentPositionParams
1023   var params = lspserver.getTextDocPosition(false)
1024   lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
1025     DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
1026   })
1027 enddef
1028
1029 # Request: "textDocument/documentSymbol"
1030 # Param: DocumentSymbolParams
1031 def GetDocSymbols(lspserver: dict<any>, fname: string, showOutline: bool): void
1032   # Check whether LSP server supports getting document symbol information
1033   if !lspserver.isDocumentSymbolProvider
1034     util.ErrMsg('LSP server does not support getting list of symbols')
1035     return
1036   endif
1037
1038   # interface DocumentSymbolParams
1039   # interface TextDocumentIdentifier
1040   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1041   lspserver.rpc_a('textDocument/documentSymbol', params, (_, reply) => {
1042     if showOutline
1043       symbol.DocSymbolOutline(lspserver, reply, fname)
1044     else
1045       symbol.DocSymbolPopup(lspserver, reply, fname)
1046     endif
1047   })
1048 enddef
1049
1050 # Request: "textDocument/formatting"
1051 # Param: DocumentFormattingParams
1052 # or
1053 # Request: "textDocument/rangeFormatting"
1054 # Param: DocumentRangeFormattingParams
1055 def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
1056                                 start_lnum: number, end_lnum: number)
1057   # Check whether LSP server supports formatting documents
1058   if !lspserver.isDocumentFormattingProvider
1059     util.ErrMsg('LSP server does not support formatting documents')
1060     return
1061   endif
1062
1063   var cmd: string
1064   if rangeFormat
1065     cmd = 'textDocument/rangeFormatting'
1066   else
1067     cmd = 'textDocument/formatting'
1068   endif
1069
1070   # interface DocumentFormattingParams
1071   #   interface TextDocumentIdentifier
1072   #   interface FormattingOptions
1073   var fmtopts: dict<any> = {
1074     tabSize: shiftwidth(),
1075     insertSpaces: &expandtab ? true : false,
1076   }
1077   var param = {
1078     textDocument: {
1079       uri: util.LspFileToUri(fname)
1080     },
1081     options: fmtopts
1082   }
1083
1084   if rangeFormat
1085     var r: dict<dict<number>> = {
1086         start: {line: start_lnum - 1, character: 0},
1087         end: {line: end_lnum - 1, character: charcol([end_lnum, '$']) - 1}}
1088     param.range = r
1089   endif
1090
1091   var reply = lspserver.rpc(cmd, param)
1092
1093   # result: TextEdit[] | null
1094
1095   if reply->empty() || reply.result->empty()
1096     # nothing to format
1097     return
1098   endif
1099
1100   var bnr: number = fname->bufnr()
1101   if bnr == -1
1102     # file is already removed
1103     return
1104   endif
1105
1106   if lspserver.needOffsetEncoding
1107     # Decode the position encoding in all the reference locations
1108     reply.result->map((_, textEdit) => {
1109       lspserver.decodeRange(bnr, textEdit.range)
1110       return textEdit
1111     })
1112   endif
1113
1114   # interface TextEdit
1115   # Apply each of the text edit operations
1116   var save_cursor: list<number> = getcurpos()
1117   textedit.ApplyTextEdits(bnr, reply.result)
1118   save_cursor->setpos('.')
1119 enddef
1120
1121 # Request: "textDocument/prepareCallHierarchy"
1122 def PrepareCallHierarchy(lspserver: dict<any>): dict<any>
1123   # interface CallHierarchyPrepareParams
1124   #   interface TextDocumentPositionParams
1125   var param: dict<any>
1126   param = lspserver.getTextDocPosition(false)
1127   var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
1128   if reply->empty() || reply.result->empty()
1129     return {}
1130   endif
1131
1132   # Result: CallHierarchyItem[] | null
1133   var choice: number = 1
1134   if reply.result->len() > 1
1135     var items: list<string> = ['Select a Call Hierarchy Item:']
1136     for i in reply.result->len()->range()
1137       items->add(printf("%d. %s", i + 1, reply.result[i].name))
1138     endfor
1139     choice = items->inputlist()
1140     if choice < 1 || choice > items->len()
1141       return {}
1142     endif
1143   endif
1144
1145   return reply.result[choice - 1]
1146 enddef
1147
1148 # Request: "callHierarchy/incomingCalls"
1149 def IncomingCalls(lspserver: dict<any>, fname: string)
1150   # Check whether LSP server supports call hierarchy
1151   if !lspserver.isCallHierarchyProvider
1152     util.ErrMsg('LSP server does not support call hierarchy')
1153     return
1154   endif
1155
1156   callhier.IncomingCalls(lspserver)
1157 enddef
1158
1159 def GetIncomingCalls(lspserver: dict<any>, item_arg: dict<any>): any
1160   # Request: "callHierarchy/incomingCalls"
1161   # Param: CallHierarchyIncomingCallsParams
1162   var param = {
1163     item: item_arg
1164   }
1165   var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
1166   if reply->empty()
1167     return null
1168   endif
1169
1170   if lspserver.needOffsetEncoding
1171     # Decode the position encoding in all the incoming call locations
1172     var bnr = util.LspUriToBufnr(item_arg.uri)
1173     reply.result->map((_, hierItem) => {
1174       lspserver.decodeRange(bnr, hierItem.from.range)
1175       return hierItem
1176     })
1177   endif
1178
1179   return reply.result
1180 enddef
1181
1182 # Request: "callHierarchy/outgoingCalls"
1183 def OutgoingCalls(lspserver: dict<any>, fname: string)
1184   # Check whether LSP server supports call hierarchy
1185   if !lspserver.isCallHierarchyProvider
1186     util.ErrMsg('LSP server does not support call hierarchy')
1187     return
1188   endif
1189
1190   callhier.OutgoingCalls(lspserver)
1191 enddef
1192
1193 def GetOutgoingCalls(lspserver: dict<any>, item_arg: dict<any>): any
1194   # Request: "callHierarchy/outgoingCalls"
1195   # Param: CallHierarchyOutgoingCallsParams
1196   var param = {
1197     item: item_arg
1198   }
1199   var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
1200   if reply->empty()
1201     return null
1202   endif
1203
1204   if lspserver.needOffsetEncoding
1205     # Decode the position encoding in all the outgoing call locations
1206     var bnr = util.LspUriToBufnr(item_arg.uri)
1207     reply.result->map((_, hierItem) => {
1208       lspserver.decodeRange(bnr, hierItem.to.range)
1209       return hierItem
1210     })
1211   endif
1212
1213   return reply.result
1214 enddef
1215
1216 # Request: "textDocument/inlayHint"
1217 # Inlay hints.
1218 def InlayHintsShow(lspserver: dict<any>, bnr: number)
1219   # Check whether LSP server supports type hierarchy
1220   if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
1221     util.ErrMsg('LSP server does not support inlay hint')
1222     return
1223   endif
1224
1225   # Send the pending buffer changes to the language server
1226   bnr->listener_flush()
1227
1228   var binfo = bnr->getbufinfo()
1229   if binfo->empty()
1230     return
1231   endif
1232   var lastlnum = binfo[0].linecount
1233   var lastline = bnr->getbufline('$')
1234   var lastcol = 1
1235   if !lastline->empty() && !lastline[0]->empty()
1236     lastcol = lastline[0]->strchars()
1237   endif
1238   var param = {
1239       textDocument: {uri: util.LspBufnrToUri(bnr)},
1240       range:
1241       {
1242         start: {line: 0, character: 0},
1243         end: {line: lastlnum - 1, character: lastcol - 1}
1244       }
1245   }
1246
1247   lspserver.encodeRange(bnr, param.range)
1248
1249   var msg: string
1250   if lspserver.isClangdInlayHintsProvider
1251     # clangd-style inlay hints
1252     msg = 'clangd/inlayHints'
1253   else
1254     msg = 'textDocument/inlayHint'
1255   endif
1256   var reply = lspserver.rpc_a(msg, param, (_, reply) => {
1257     inlayhints.InlayHintsReply(lspserver, bnr, reply)
1258   })
1259 enddef
1260
1261 def DecodeTypeHierarchy(lspserver: dict<any>, isSuper: bool, typeHier: dict<any>)
1262   if !lspserver.needOffsetEncoding
1263     return
1264   endif
1265   var bnr = util.LspUriToBufnr(typeHier.uri)
1266   lspserver.decodeRange(bnr, typeHier.range)
1267   lspserver.decodeRange(bnr, typeHier.selectionRange)
1268   var subType: list<dict<any>>
1269   if isSuper
1270     subType = typeHier->get('parents', [])
1271   else
1272     subType = typeHier->get('children', [])
1273   endif
1274   if !subType->empty()
1275     # Decode the position encoding in all the type hierarchy items
1276     subType->map((_, typeHierItem) => {
1277         DecodeTypeHierarchy(lspserver, isSuper, typeHierItem)
1278         return typeHierItem
1279       })
1280   endif
1281 enddef
1282
1283 # Request: "textDocument/typehierarchy"
1284 # Support the clangd version of type hierarchy retrieval method.
1285 # The method described in the LSP 3.17.0 standard is not supported as clangd
1286 # doesn't support that method.
1287 def TypeHierarchy(lspserver: dict<any>, direction: number)
1288   # Check whether LSP server supports type hierarchy
1289   if !lspserver.isTypeHierarchyProvider
1290     util.ErrMsg('LSP server does not support type hierarchy')
1291     return
1292   endif
1293
1294   # interface TypeHierarchy
1295   #   interface TextDocumentPositionParams
1296   var param: dict<any>
1297   param = lspserver.getTextDocPosition(false)
1298   # 0: children, 1: parent, 2: both
1299   param.direction = direction
1300   param.resolve = 5
1301   var reply = lspserver.rpc('textDocument/typeHierarchy', param)
1302   if reply->empty() || reply.result->empty()
1303     util.WarnMsg('No type hierarchy available')
1304     return
1305   endif
1306
1307   var isSuper = (direction == 1)
1308
1309   DecodeTypeHierarchy(lspserver, isSuper, reply.result)
1310
1311   typehier.ShowTypeHierarchy(lspserver, isSuper, reply.result)
1312 enddef
1313
1314 # Decode the ranges in "WorkspaceEdit"
1315 def DecodeWorkspaceEdit(lspserver: dict<any>, workspaceEdit: dict<any>)
1316   if !lspserver.needOffsetEncoding
1317     return
1318   endif
1319   if workspaceEdit->has_key('changes')
1320     for [uri, changes] in workspaceEdit.changes->items()
1321       var bnr: number = util.LspUriToBufnr(uri)
1322       if bnr <= 0
1323         continue
1324       endif
1325       # Decode the position encoding in all the text edit locations
1326       changes->map((_, textEdit) => {
1327         lspserver.decodeRange(bnr, textEdit.range)
1328         return textEdit
1329       })
1330     endfor
1331   endif
1332
1333   if workspaceEdit->has_key('documentChanges')
1334     for change in workspaceEdit.documentChanges
1335       if !change->has_key('kind')
1336         var bnr: number = util.LspUriToBufnr(change.textDocument.uri)
1337         if bnr <= 0
1338           continue
1339         endif
1340         # Decode the position encoding in all the text edit locations
1341         change.edits->map((_, textEdit) => {
1342           lspserver.decodeRange(bnr, textEdit.range)
1343           return textEdit
1344         })
1345       endif
1346     endfor
1347   endif
1348 enddef
1349
1350 # Request: "textDocument/rename"
1351 # Param: RenameParams
1352 def RenameSymbol(lspserver: dict<any>, newName: string)
1353   # Check whether LSP server supports rename operation
1354   if !lspserver.isRenameProvider
1355     util.ErrMsg('LSP server does not support rename operation')
1356     return
1357   endif
1358
1359   # interface RenameParams
1360   #   interface TextDocumentPositionParams
1361   var param: dict<any> = {}
1362   param = lspserver.getTextDocPosition(true)
1363   param.newName = newName
1364
1365   var reply = lspserver.rpc('textDocument/rename', param)
1366
1367   # Result: WorkspaceEdit | null
1368   if reply->empty() || reply.result->empty()
1369     # nothing to rename
1370     return
1371   endif
1372
1373   # result: WorkspaceEdit
1374   DecodeWorkspaceEdit(lspserver, reply.result)
1375   textedit.ApplyWorkspaceEdit(reply.result)
1376 enddef
1377
1378 # Decode the range in "CodeAction"
1379 def DecodeCodeAction(lspserver: dict<any>, actionList: list<dict<any>>)
1380   if !lspserver.needOffsetEncoding
1381     return
1382   endif
1383   actionList->map((_, act) => {
1384       if !act->has_key('disabled') && act->has_key('edit')
1385         DecodeWorkspaceEdit(lspserver, act.edit)
1386       endif
1387       return act
1388     })
1389 enddef
1390
1391 # Request: "textDocument/codeAction"
1392 # Param: CodeActionParams
1393 def CodeAction(lspserver: dict<any>, fname_arg: string, line1: number,
1394                 line2: number, query: string)
1395   # Check whether LSP server supports code action operation
1396   if !lspserver.isCodeActionProvider
1397     util.ErrMsg('LSP server does not support code action operation')
1398     return
1399   endif
1400
1401   # interface CodeActionParams
1402   var params: dict<any> = {}
1403   var fname: string = fname_arg->fnamemodify(':p')
1404   var bnr: number = fname_arg->bufnr()
1405   var r: dict<dict<number>> = {
1406     start: {
1407       line: line1 - 1,
1408       character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
1409     },
1410     end: {
1411       line: line2 - 1,
1412       character: util.GetCharIdxWithCompChar(getline(line2), charcol([line2, '$']) - 1)
1413     }
1414   }
1415   lspserver.encodeRange(bnr, r)
1416   params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
1417   var d: list<dict<any>> = []
1418   for lnum in range(line1, line2)
1419     var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(bnr, lnum, lspserver)->deepcopy()
1420     if lspserver.needOffsetEncoding
1421       diagsInfo->map((_, di) => {
1422           lspserver.encodeRange(bnr, di.range)
1423           return di
1424         })
1425     endif
1426     d->extend(diagsInfo)
1427   endfor
1428   params->extend({context: {diagnostics: d, triggerKind: 1}})
1429
1430   var reply = lspserver.rpc('textDocument/codeAction', params)
1431
1432   # Result: (Command | CodeAction)[] | null
1433   if reply->empty() || reply.result->empty()
1434     # no action can be performed
1435     util.WarnMsg('No code action is available')
1436     return
1437   endif
1438
1439   DecodeCodeAction(lspserver, reply.result)
1440
1441   codeaction.ApplyCodeAction(lspserver, reply.result, query)
1442 enddef
1443
1444 # Request: "textDocument/codeLens"
1445 # Param: CodeLensParams
1446 def CodeLens(lspserver: dict<any>, fname: string)
1447   # Check whether LSP server supports code lens operation
1448   if !lspserver.isCodeLensProvider
1449     util.ErrMsg('LSP server does not support code lens operation')
1450     return
1451   endif
1452
1453   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1454   var reply = lspserver.rpc('textDocument/codeLens', params)
1455   if reply->empty() || reply.result->empty()
1456     util.WarnMsg($'No code lens actions found for the current file')
1457     return
1458   endif
1459
1460   var bnr = fname->bufnr()
1461
1462   # Decode the position encoding in all the code lens items
1463   if lspserver.needOffsetEncoding
1464     reply.result->map((_, codeLensItem) => {
1465       lspserver.decodeRange(bnr, codeLensItem.range)
1466       return codeLensItem
1467     })
1468   endif
1469
1470   codelens.ProcessCodeLens(lspserver, bnr, reply.result)
1471 enddef
1472
1473 # Request: "codeLens/resolve"
1474 # Param: CodeLens
1475 def ResolveCodeLens(lspserver: dict<any>, bnr: number,
1476                     codeLens: dict<any>): dict<any>
1477   if !lspserver.isCodeLensResolveProvider
1478     return {}
1479   endif
1480
1481   if lspserver.needOffsetEncoding
1482     lspserver.encodeRange(bnr, codeLens.range)
1483   endif
1484
1485   var reply = lspserver.rpc('codeLens/resolve', codeLens)
1486   if reply->empty()
1487     return {}
1488   endif
1489
1490   var codeLensItem: dict<any> = reply.result
1491
1492   # Decode the position encoding in the code lens item
1493   if lspserver.needOffsetEncoding
1494     lspserver.decodeRange(bnr, codeLensItem.range)
1495   endif
1496
1497   return codeLensItem
1498 enddef
1499
1500 # List project-wide symbols matching query string
1501 # Request: "workspace/symbol"
1502 # Param: WorkspaceSymbolParams
1503 def WorkspaceQuerySymbols(lspserver: dict<any>, query: string, firstCall: bool, cmdmods: string = '')
1504   # Check whether the LSP server supports listing workspace symbols
1505   if !lspserver.isWorkspaceSymbolProvider
1506     util.ErrMsg('LSP server does not support listing workspace symbols')
1507     return
1508   endif
1509
1510   # Param: WorkspaceSymbolParams
1511   var param = {
1512     query: query
1513   }
1514   var reply = lspserver.rpc('workspace/symbol', param)
1515   if reply->empty() || reply.result->empty()
1516     util.WarnMsg($'Symbol "{query}" is not found')
1517     return
1518   endif
1519
1520   var symInfo: list<dict<any>> = reply.result
1521
1522   if lspserver.needOffsetEncoding
1523     # Decode the position encoding in all the symbol locations
1524     symInfo->map((_, sym) => {
1525       if sym->has_key('location')
1526         lspserver.decodeLocation(sym.location)
1527       endif
1528       return sym
1529     })
1530   endif
1531
1532   if firstCall && symInfo->len() == 1
1533     # If there is only one symbol, then jump to the symbol location
1534     var symLoc: dict<any> = symInfo[0]->get('location', {})
1535     if !symLoc->empty()
1536       symbol.GotoSymbol(lspserver, symLoc, false, cmdmods)
1537     endif
1538   else
1539     symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods)
1540   endif
1541 enddef
1542
1543 # Add a workspace folder to the language server.
1544 def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1545   if !lspserver.caps->has_key('workspace')
1546           || !lspserver.caps.workspace->has_key('workspaceFolders')
1547           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1548           || !lspserver.caps.workspace.workspaceFolders.supported
1549       util.ErrMsg('LSP server does not support workspace folders')
1550     return
1551   endif
1552
1553   if lspserver.workspaceFolders->index(dirName) != -1
1554     util.ErrMsg($'{dirName} is already part of this workspace')
1555     return
1556   endif
1557
1558   # Notification: 'workspace/didChangeWorkspaceFolders'
1559   # Params: DidChangeWorkspaceFoldersParams
1560   var params = {event: {added: [dirName], removed: []}}
1561   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1562
1563   lspserver.workspaceFolders->add(dirName)
1564 enddef
1565
1566 # Remove a workspace folder from the language server.
1567 def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1568   if !lspserver.caps->has_key('workspace')
1569           || !lspserver.caps.workspace->has_key('workspaceFolders')
1570           || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1571           || !lspserver.caps.workspace.workspaceFolders.supported
1572       util.ErrMsg('LSP server does not support workspace folders')
1573     return
1574   endif
1575
1576   var idx: number = lspserver.workspaceFolders->index(dirName)
1577   if idx == -1
1578     util.ErrMsg($'{dirName} is not currently part of this workspace')
1579     return
1580   endif
1581
1582   # Notification: "workspace/didChangeWorkspaceFolders"
1583   # Param: DidChangeWorkspaceFoldersParams
1584   var params = {event: {added: [], removed: [dirName]}}
1585   lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1586
1587   lspserver.workspaceFolders->remove(idx)
1588 enddef
1589
1590 def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
1591   lspserver.decodeRange(bnr, selRange.range)
1592   if selRange->has_key('parent')
1593     DecodeSelectionRange(lspserver, bnr, selRange.parent)
1594   endif
1595 enddef
1596
1597 # select the text around the current cursor location
1598 # Request: "textDocument/selectionRange"
1599 # Param: SelectionRangeParams
1600 def SelectionRange(lspserver: dict<any>, fname: string)
1601   # Check whether LSP server supports selection ranges
1602   if !lspserver.isSelectionRangeProvider
1603     util.ErrMsg('LSP server does not support selection ranges')
1604     return
1605   endif
1606
1607   # clear the previous selection reply
1608   lspserver.selection = {}
1609
1610   # interface SelectionRangeParams
1611   # interface TextDocumentIdentifier
1612   var param = {
1613     textDocument: {
1614       uri: util.LspFileToUri(fname)
1615     },
1616     positions: [lspserver.getPosition(false)]
1617   }
1618   var reply = lspserver.rpc('textDocument/selectionRange', param)
1619
1620   if reply->empty() || reply.result->empty()
1621     return
1622   endif
1623
1624   # Decode the position encoding in all the selection range items
1625   if lspserver.needOffsetEncoding
1626     var bnr = fname->bufnr()
1627     reply.result->map((_, selItem) => {
1628         DecodeSelectionRange(lspserver, bnr, selItem)
1629         return selItem
1630       })
1631   endif
1632
1633   selection.SelectionStart(lspserver, reply.result)
1634 enddef
1635
1636 # Expand the previous selection or start a new one
1637 def SelectionExpand(lspserver: dict<any>)
1638   # Check whether LSP server supports selection ranges
1639   if !lspserver.isSelectionRangeProvider
1640     util.ErrMsg('LSP server does not support selection ranges')
1641     return
1642   endif
1643
1644   selection.SelectionModify(lspserver, true)
1645 enddef
1646
1647 # Shrink the previous selection or start a new one
1648 def SelectionShrink(lspserver: dict<any>)
1649   # Check whether LSP server supports selection ranges
1650   if !lspserver.isSelectionRangeProvider
1651     util.ErrMsg('LSP server does not support selection ranges')
1652     return
1653   endif
1654
1655   selection.SelectionModify(lspserver, false)
1656 enddef
1657
1658 # fold the entire document
1659 # Request: "textDocument/foldingRange"
1660 # Param: FoldingRangeParams
1661 def FoldRange(lspserver: dict<any>, fname: string)
1662   # Check whether LSP server supports fold ranges
1663   if !lspserver.isFoldingRangeProvider
1664     util.ErrMsg('LSP server does not support folding')
1665     return
1666   endif
1667
1668   # interface FoldingRangeParams
1669   # interface TextDocumentIdentifier
1670   var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1671   var reply = lspserver.rpc('textDocument/foldingRange', params)
1672   if reply->empty() || reply.result->empty()
1673     return
1674   endif
1675
1676   # result: FoldingRange[]
1677   var end_lnum: number
1678   var last_lnum: number = line('$')
1679   for foldRange in reply.result
1680     end_lnum = foldRange.endLine + 1
1681     if end_lnum < foldRange.startLine + 2
1682       end_lnum = foldRange.startLine + 2
1683     endif
1684     exe $':{foldRange.startLine + 2}, {end_lnum}fold'
1685     # Open all the folds, otherwise the subsequently created folds are not
1686     # correct.
1687     :silent! foldopen!
1688   endfor
1689
1690   if &foldcolumn == 0
1691     :setlocal foldcolumn=2
1692   endif
1693 enddef
1694
1695 # process the 'workspace/executeCommand' reply from the LSP server
1696 # Result: any | null
1697 def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
1698   # Nothing to do for the reply
1699 enddef
1700
1701 # Request the LSP server to execute a command
1702 # Request: workspace/executeCommand
1703 # Params: ExecuteCommandParams
1704 def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
1705   # Need to check for lspserver.caps.executeCommandProvider?
1706   var params = cmd
1707   lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
1708 enddef
1709
1710 # Display the LSP server capabilities (received during the initialization
1711 # stage).
1712 def GetCapabilities(lspserver: dict<any>): list<string>
1713   var l = []
1714   var heading = $"'{lspserver.path}' Language Server Capabilities"
1715   var underlines = repeat('=', heading->len())
1716   l->extend([heading, underlines])
1717   for k in lspserver.caps->keys()->sort()
1718     l->add($'{k}: {lspserver.caps[k]->string()}')
1719   endfor
1720   return l
1721 enddef
1722
1723 # Display the LSP server initialize request and result
1724 def GetInitializeRequest(lspserver: dict<any>): list<string>
1725   var l = []
1726   var heading = $"'{lspserver.path}' Language Server Initialize Request"
1727   var underlines = repeat('=', heading->len())
1728   l->extend([heading, underlines])
1729   if lspserver->has_key('rpcInitializeRequest')
1730     for k in lspserver.rpcInitializeRequest->keys()->sort()
1731       l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
1732     endfor
1733   endif
1734   return l
1735 enddef
1736
1737 # Store a log or trace message received from the language server.
1738 def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
1739   # A single message may contain multiple lines separate by newline
1740   var msgs = newMsg->split("\n")
1741   lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
1742   lspserver.messages->extend(msgs[1 : ])
1743   # Keep only the last 500 messages to reduce the memory usage
1744   if lspserver.messages->len() >= 600
1745     lspserver.messages = lspserver.messages[-500 : ]
1746   endif
1747 enddef
1748
1749 # Display the log messages received from the LSP server (window/logMessage)
1750 def GetMessages(lspserver: dict<any>): list<string>
1751   if lspserver.messages->empty()
1752     return [$'No messages received from "{lspserver.name}" server']
1753   endif
1754
1755   var l = []
1756   var heading = $"'{lspserver.path}' Language Server Messages"
1757   var underlines = repeat('=', heading->len())
1758   l->extend([heading, underlines])
1759   l->extend(lspserver.messages)
1760   return l
1761 enddef
1762
1763 # Send a 'textDocument/definition' request to the LSP server to get the
1764 # location where the symbol under the cursor is defined and return a list of
1765 # Dicts in a format accepted by the 'tagfunc' option.
1766 # Returns null if the LSP server doesn't support getting the location of a
1767 # symbol definition or the symbol is not defined.
1768 def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
1769   # Check whether LSP server supports getting the location of a definition
1770   if !lspserver.isDefinitionProvider
1771     return null
1772   endif
1773
1774   # interface DefinitionParams
1775   #   interface TextDocumentPositionParams
1776   var reply = lspserver.rpc('textDocument/definition',
1777                             lspserver.getTextDocPosition(false))
1778   if reply->empty() || reply.result->empty()
1779     return null
1780   endif
1781
1782   var taglocations: list<dict<any>>
1783   if reply.result->type() == v:t_list
1784     taglocations = reply.result
1785   else
1786     taglocations = [reply.result]
1787   endif
1788
1789   if lspserver.needOffsetEncoding
1790     # Decode the position encoding in all the reference locations
1791     taglocations->map((_, loc) => {
1792       lspserver.decodeLocation(loc)
1793       return loc
1794     })
1795   endif
1796
1797   return symbol.TagFunc(lspserver, taglocations, pat)
1798 enddef
1799
1800 # Returns unique ID used for identifying the various servers
1801 var UniqueServerIdCounter = 0
1802 def GetUniqueServerId(): number
1803   UniqueServerIdCounter = UniqueServerIdCounter + 1
1804   return UniqueServerIdCounter
1805 enddef
1806
1807 export def NewLspServer(serverParams: dict<any>): dict<any>
1808   var lspserver: dict<any> = {
1809     id: GetUniqueServerId(),
1810     name: serverParams.name,
1811     path: serverParams.path,
1812     args: serverParams.args->deepcopy(),
1813     running: false,
1814     ready: false,
1815     job: v:none,
1816     data: '',
1817     nextID: 1,
1818     caps: {},
1819     requests: {},
1820     callHierarchyType: '',
1821     completionTriggerChars: [],
1822     customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
1823     customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
1824     debug: serverParams.debug,
1825     features: serverParams.features->deepcopy(),
1826     forceOffsetEncoding: serverParams.forceOffsetEncoding,
1827     initializationOptions: serverParams.initializationOptions->deepcopy(),
1828     messages: [],
1829     omniCompletePending: false,
1830     peekSymbolFilePopup: -1,
1831     peekSymbolPopup: -1,
1832     processDiagHandler: serverParams.processDiagHandler,
1833     rootSearchFiles: serverParams.rootSearch->deepcopy(),
1834     runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
1835     runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
1836     selection: {},
1837     signaturePopup: -1,
1838     syncInit: serverParams.syncInit,
1839     traceLevel: serverParams.traceLevel,
1840     typeHierFilePopup: -1,
1841     typeHierPopup: -1,
1842     workspaceConfig: serverParams.workspaceConfig->deepcopy(),
1843     workspaceSymbolPopup: -1,
1844     workspaceSymbolQuery: ''
1845   }
1846   lspserver.logfile = $'lsp-{lspserver.name}.log'
1847   lspserver.errfile = $'lsp-{lspserver.name}.err'
1848
1849   # Add the LSP server functions
1850   lspserver->extend({
1851     startServer: function(StartServer, [lspserver]),
1852     initServer: function(InitServer, [lspserver]),
1853     stopServer: function(StopServer, [lspserver]),
1854     shutdownServer: function(ShutdownServer, [lspserver]),
1855     exitServer: function(ExitServer, [lspserver]),
1856     setTrace: function(SetTrace, [lspserver]),
1857     traceLog: function(TraceLog, [lspserver]),
1858     errorLog: function(ErrorLog, [lspserver]),
1859     nextReqID: function(NextReqID, [lspserver]),
1860     createRequest: function(CreateRequest, [lspserver]),
1861     createResponse: function(CreateResponse, [lspserver]),
1862     sendResponse: function(SendResponse, [lspserver]),
1863     sendMessage: function(SendMessage, [lspserver]),
1864     sendNotification: function(SendNotification, [lspserver]),
1865     rpc: function(Rpc, [lspserver]),
1866     rpc_a: function(AsyncRpc, [lspserver]),
1867     waitForResponse: function(WaitForResponse, [lspserver]),
1868     processReply: function(handlers.ProcessReply, [lspserver]),
1869     processNotif: function(handlers.ProcessNotif, [lspserver]),
1870     processRequest: function(handlers.ProcessRequest, [lspserver]),
1871     processMessages: function(handlers.ProcessMessages, [lspserver]),
1872     encodePosition: function(offset.EncodePosition, [lspserver]),
1873     decodePosition: function(offset.DecodePosition, [lspserver]),
1874     encodeRange: function(offset.EncodeRange, [lspserver]),
1875     decodeRange: function(offset.DecodeRange, [lspserver]),
1876     encodeLocation: function(offset.EncodeLocation, [lspserver]),
1877     decodeLocation: function(offset.DecodeLocation, [lspserver]),
1878     getPosition: function(GetPosition, [lspserver]),
1879     getTextDocPosition: function(GetTextDocPosition, [lspserver]),
1880     featureEnabled: function(FeatureEnabled, [lspserver]),
1881     textdocDidOpen: function(TextdocDidOpen, [lspserver]),
1882     textdocDidClose: function(TextdocDidClose, [lspserver]),
1883     textdocDidChange: function(TextdocDidChange, [lspserver]),
1884     sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
1885     sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
1886     getCompletion: function(GetCompletion, [lspserver]),
1887     resolveCompletion: function(ResolveCompletion, [lspserver]),
1888     gotoDefinition: function(GotoDefinition, [lspserver]),
1889     gotoDeclaration: function(GotoDeclaration, [lspserver]),
1890     gotoTypeDef: function(GotoTypeDef, [lspserver]),
1891     gotoImplementation: function(GotoImplementation, [lspserver]),
1892     tagFunc: function(TagFunc, [lspserver]),
1893     switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
1894     showSignature: function(ShowSignature, [lspserver]),
1895     didSaveFile: function(DidSaveFile, [lspserver]),
1896     hover: function(ShowHoverInfo, [lspserver]),
1897     showReferences: function(ShowReferences, [lspserver]),
1898     docHighlight: function(DocHighlight, [lspserver]),
1899     getDocSymbols: function(GetDocSymbols, [lspserver]),
1900     textDocFormat: function(TextDocFormat, [lspserver]),
1901     prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
1902     incomingCalls: function(IncomingCalls, [lspserver]),
1903     getIncomingCalls: function(GetIncomingCalls, [lspserver]),
1904     outgoingCalls: function(OutgoingCalls, [lspserver]),
1905     getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
1906     inlayHintsShow: function(InlayHintsShow, [lspserver]),
1907     typeHierarchy: function(TypeHierarchy, [lspserver]),
1908     renameSymbol: function(RenameSymbol, [lspserver]),
1909     codeAction: function(CodeAction, [lspserver]),
1910     codeLens: function(CodeLens, [lspserver]),
1911     resolveCodeLens: function(ResolveCodeLens, [lspserver]),
1912     workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
1913     addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
1914     removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
1915     selectionRange: function(SelectionRange, [lspserver]),
1916     selectionExpand: function(SelectionExpand, [lspserver]),
1917     selectionShrink: function(SelectionShrink, [lspserver]),
1918     foldRange: function(FoldRange, [lspserver]),
1919     executeCommand: function(ExecuteCommand, [lspserver]),
1920     workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
1921     getCapabilities: function(GetCapabilities, [lspserver]),
1922     getInitializeRequest: function(GetInitializeRequest, [lspserver]),
1923     addMessage: function(AddMessage, [lspserver]),
1924     getMessages: function(GetMessages, [lspserver])
1925   })
1926
1927   return lspserver
1928 enddef
1929
1930 # vim: tabstop=8 shiftwidth=2 softtabstop=2