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