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