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