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