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