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