5 # The functions to send request messages to the language server are in this
8 # Refer to https://microsoft.github.io/language-server-protocol/specification
9 # for the Language Server Protocol (LSP) specificaiton.
11 import './options.vim' as opt
12 import './handlers.vim'
14 import './capabilities.vim'
17 import './selection.vim'
19 import './textedit.vim'
20 import './completion.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'
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()}')
33 lspserver.processMessages()
36 # LSP server error output handler
37 def Error_cb(lspserver: dict<any>, chan: channel, emsg: string): void
38 lspserver.errorLog(emsg)
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 = {}
51 def StartServer(lspserver: dict<any>, bnr: number): number
53 util.WarnMsg($'LSP server "{lspserver.name}" is already running')
57 var cmd = [lspserver.path]
58 cmd->extend(lspserver.args)
60 var opts = {in_mode: 'lsp',
64 out_cb: function(Output_cb, [lspserver]),
65 err_cb: function(Error_cb, [lspserver]),
66 exit_cb: function(Exit_cb, [lspserver])}
71 lspserver.requests = {}
72 lspserver.omniCompletePending = false
73 lspserver.completionLazyDoc = false
74 lspserver.completionTriggerChars = []
75 lspserver.signaturePopup = -1
77 var job = cmd->job_start(opts)
78 if job->job_status() == 'fail'
79 util.ErrMsg($'Failed to start LSP server {lspserver.path}')
83 # wait a little for the LSP server to start
87 lspserver.running = true
89 lspserver.initServer(bnr)
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()
101 var caps: dict<any> = initResult.capabilities
102 lspserver.caps = caps
104 for [key, val] in initResult->items()
105 if key == 'capabilities'
109 lspserver.caps[$'~additionalInitResult_{key}'] = val
112 capabilities.ProcessServerCaps(lspserver, caps)
114 if caps->has_key('completionProvider')
115 if opt.lspOptions.autoComplete
116 lspserver.completionTriggerChars =
117 caps.completionProvider->get('triggerCharacters', [])
119 lspserver.completionLazyDoc =
120 caps.completionProvider->get('resolveProvider', false)
123 # send a "initialized" notification to server
124 lspserver.sendInitializedNotif()
125 # send any workspace configuration (optional)
126 if !lspserver.workspaceConfig->empty()
127 lspserver.sendWorkspaceConfig()
129 lspserver.ready = true
130 if exists($'#User#LspServerReady{lspserver.name}')
131 exe $'doautocmd <nomodeline> User LspServerReady{lspserver.name}'
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}'
138 # set the server debug trace level
139 if lspserver.traceLevel != 'off'
140 lspserver.setTrace(lspserver.traceLevel)
143 # if the outline window is opened, then request the symbols for the current
145 if bufwinid('LSP-Outline') != -1
146 lspserver.getDocSymbols(@%, true)
149 # Update the inlay hints (if enabled)
150 if opt.lspOptions.showInlayHints && (lspserver.isInlayHintProvider
151 || lspserver.isClangdInlayHintsProvider)
152 inlayhints.LspInlayHintsUpdateNow()
156 # Request: "initialize"
157 # Param: InitializeParams
158 def InitServer(lspserver: dict<any>, bnr: number)
159 # interface 'InitializeParams'
160 var initparams: dict<any> = {}
161 initparams.processId = getpid()
162 initparams.clientInfo = {
164 version: v:versionlong->string(),
167 # Compute the rootpath (based on the directory of the buffer)
169 var rootSearchFiles = lspserver.rootSearchFiles
170 var bufDir = bnr->bufname()->fnamemodify(':p:h')
171 if !rootSearchFiles->empty()
172 rootPath = util.FindNearestRootDir(bufDir, rootSearchFiles)
177 # bufDir is within cwd
178 var bufDirPrefix = bufDir[0 : cwd->strcharlen() - 1]
180 ? bufDirPrefix ==? cwd
181 : bufDirPrefix == cwd
188 lspserver.workspaceFolders = [rootPath]
190 var rootUri = util.LspFileToUri(rootPath)
191 initparams.rootPath = rootPath
192 initparams.rootUri = rootUri
193 initparams.workspaceFolders = [{
194 name: rootPath->fnamemodify(':t'),
198 initparams.trace = 'off'
199 initparams.capabilities = capabilities.GetClientCaps()
200 if !lspserver.initializationOptions->empty()
201 initparams.initializationOptions = lspserver.initializationOptions
203 initparams.initializationOptions = {}
206 lspserver.rpcInitializeRequest = initparams
208 lspserver.rpc_a('initialize', initparams, ServerInitReply)
211 # Send a "initialized" notification to the language server
212 def SendInitializedNotif(lspserver: dict<any>)
213 # Notification: 'initialized'
214 # Params: InitializedParams
215 lspserver.sendNotification('initialized')
220 def ShutdownServer(lspserver: dict<any>): void
221 lspserver.rpc('shutdown', {})
224 # Send a 'exit' notification to the language server
225 def ExitServer(lspserver: dict<any>): void
226 # Notification: 'exit'
228 lspserver.sendNotification('exit')
232 def StopServer(lspserver: dict<any>): number
233 if !lspserver.running
234 util.WarnMsg($'LSP server {lspserver.name} is not running')
238 # Send the shutdown request to the server
239 lspserver.shutdownServer()
241 # Notify the server to exit
242 lspserver.exitServer()
244 # Wait for the server to process the exit notification and exit for a
245 # maximum of 2 seconds.
246 var maxCount: number = 1000
247 while lspserver.job->job_status() == 'run' && maxCount > 0
252 if lspserver.job->job_status() == 'run'
253 lspserver.job->job_stop()
255 lspserver.running = false
256 lspserver.ready = false
257 lspserver.requests = {}
261 # Set the language server trace level using the '$/setTrace' notification.
262 # Supported values for "traceVal" are "off", "messages" and "verbose".
263 def SetTrace(lspserver: dict<any>, traceVal: string)
264 # Notification: '$/setTrace'
265 # Params: SetTraceParams
266 var params = {value: traceVal}
267 lspserver.sendNotification('$/setTrace', params)
270 # Log a debug message to the LSP server debug file
271 def TraceLog(lspserver: dict<any>, msg: string)
273 util.TraceLog(lspserver.logfile, false, msg)
277 # Log an error message to the LSP server error file
278 def ErrorLog(lspserver: dict<any>, errmsg: string)
280 util.TraceLog(lspserver.errfile, true, errmsg)
284 # Return the next id for a LSP server request message
285 def NextReqID(lspserver: dict<any>): number
286 var id = lspserver.nextID
287 lspserver.nextID = id + 1
291 # create a LSP server request message
292 def CreateRequest(lspserver: dict<any>, method: string): dict<any>
295 req.id = lspserver.nextReqID()
299 # Save the request, so that the corresponding response can be processed
300 lspserver.requests->extend({[req.id->string()]: req})
305 # create a LSP server response message
306 def CreateResponse(lspserver: dict<any>, req_id: number): dict<any>
314 # create a LSP server notification message
315 def CreateNotification(lspserver: dict<any>, notif: string): dict<any>
324 # send a response message to the server
325 def SendResponse(lspserver: dict<any>, request: dict<any>, result: any, error: dict<any>)
326 if (request.id->type() == v:t_string
327 && (request.id->trim() =~ '[^[:digit:]]\+'
328 || request.id->trim()->empty()))
329 || (request.id->type() != v:t_string && request.id->type() != v:t_number)
330 util.ErrMsg('request.id of response to LSP server is not a correct number')
333 var resp: dict<any> = lspserver.createResponse(
334 request.id->type() == v:t_string ? request.id->str2nr() : request.id)
336 resp->extend({result: result})
338 resp->extend({error: error})
340 lspserver.sendMessage(resp)
343 # Send a request message to LSP server
344 def SendMessage(lspserver: dict<any>, content: dict<any>): void
345 var job = lspserver.job
346 if job->job_status() != 'run'
347 # LSP server has exited
350 job->ch_sendexpr(content)
351 if content->has_key('id')
352 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {content->string()}')
356 # Send a notification message to the language server
357 def SendNotification(lspserver: dict<any>, method: string, params: any = {})
358 var notif: dict<any> = CreateNotification(lspserver, method)
359 notif.params->extend(params)
360 lspserver.sendMessage(notif)
363 # Translate an LSP error code into a readable string
364 def LspGetErrorMessage(errcode: number): string
366 -32001: 'UnknownErrorCode',
367 -32002: 'ServerNotInitialized',
368 -32600: 'InvalidRequest',
369 -32601: 'MethodNotFound',
370 -32602: 'InvalidParams',
371 -32603: 'InternalError',
372 -32700: 'ParseError',
373 -32800: 'RequestCancelled',
374 -32801: 'ContentModified',
375 -32802: 'ServerCancelled',
376 -32803: 'RequestFailed'
379 return errmap->get(errcode, errcode->string())
382 # Process a LSP server response error and display an error message.
383 def ProcessLspServerError(method: string, responseError: dict<any>)
385 var emsg: string = responseError.message
386 emsg ..= $', error = {LspGetErrorMessage(responseError.code)}'
387 if responseError->has_key('data')
388 emsg ..= $', data = {responseError.data->string()}'
390 util.ErrMsg($'request {method} failed ({emsg})')
393 # Send a sync RPC request message to the LSP server and return the received
394 # reply. In case of an error, an empty Dict is returned.
395 def Rpc(lspserver: dict<any>, method: string, params: any, handleError: bool = true): dict<any>
399 req.params->extend(params)
401 var job = lspserver.job
402 if job->job_status() != 'run'
403 # LSP server has exited
407 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
409 # Do the synchronous RPC call
410 var reply = job->ch_evalexpr(req)
412 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
414 if reply->has_key('result')
419 if reply->has_key('error') && handleError
421 ProcessLspServerError(method, reply.error)
427 # LSP server asynchronous RPC callback
428 def AsyncRpcCb(lspserver: dict<any>, method: string, RpcCb: func, chan: channel, reply: dict<any>)
429 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
435 if reply->has_key('error')
437 ProcessLspServerError(method, reply.error)
441 if !reply->has_key('result')
442 util.ErrMsg($'request {method} failed (no result)')
446 RpcCb(lspserver, reply.result)
449 # Send a async RPC request message to the LSP server with a callback function.
450 # Returns the LSP message id. This id can be used to cancel the RPC request
451 # (if needed). Returns -1 on error.
452 def AsyncRpc(lspserver: dict<any>, method: string, params: any, Cbfunc: func): number
456 req.params->extend(params)
458 var job = lspserver.job
459 if job->job_status() != 'run'
460 # LSP server has exited
464 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
466 # Do the asynchronous RPC call
467 var Fn = function('AsyncRpcCb', [lspserver, method, Cbfunc])
470 if get(g:, 'LSPTest')
471 # When running LSP tests, make this a synchronous RPC call
472 reply = Rpc(lspserver, method, params)
473 Fn(test_null_channel(), reply)
475 # Otherwise, make an asynchronous RPC call
476 reply = job->ch_sendexpr(req, {callback: Fn})
485 # Wait for a response message from the LSP server for the request "req"
486 # Waits for a maximum of 5 seconds
487 def WaitForResponse(lspserver: dict<any>, req: dict<any>)
488 var maxCount: number = 2500
489 var key: string = req.id->string()
491 while lspserver.requests->has_key(key) && maxCount > 0
497 # Retrieve the Workspace configuration asked by the server.
498 # Request: workspace/configuration
499 def WorkspaceConfigGet(lspserver: dict<any>, configItem: dict<any>): dict<any>
500 if lspserver.workspaceConfig->empty()
503 if !configItem->has_key('section') || configItem.section->empty()
504 return lspserver.workspaceConfig
506 var config: dict<any> = lspserver.workspaceConfig
507 for part in configItem.section->split('\.')
508 if !config->has_key(part)
511 config = config[part]
516 # Send a "workspace/didChangeConfiguration" notification to the language
518 def SendWorkspaceConfig(lspserver: dict<any>)
519 # Params: DidChangeConfigurationParams
520 var params = {settings: lspserver.workspaceConfig}
521 lspserver.sendNotification('workspace/didChangeConfiguration', params)
524 # Send a file/document opened notification to the language server.
525 def TextdocDidOpen(lspserver: dict<any>, bnr: number, ftype: string): void
526 # Notification: 'textDocument/didOpen'
527 # Params: DidOpenTextDocumentParams
529 tdi.uri = util.LspBufnrToUri(bnr)
530 tdi.languageId = ftype
532 tdi.text = bnr->getbufline(1, '$')->join("\n") .. "\n"
533 var params = {textDocument: tdi}
534 lspserver.sendNotification('textDocument/didOpen', params)
537 # Send a file/document closed notification to the language server.
538 def TextdocDidClose(lspserver: dict<any>, bnr: number): void
539 # Notification: 'textDocument/didClose'
540 # Params: DidCloseTextDocumentParams
542 tdid.uri = util.LspBufnrToUri(bnr)
543 var params = {textDocument: tdid}
544 lspserver.sendNotification('textDocument/didClose', params)
547 # Send a file/document change notification to the language server.
548 # Params: DidChangeTextDocumentParams
549 def TextdocDidChange(lspserver: dict<any>, bnr: number, start: number,
550 end: number, added: number,
551 changes: list<dict<number>>): void
552 # Notification: 'textDocument/didChange'
553 # Params: DidChangeTextDocumentParams
554 var vtdid: dict<any> = {}
555 vtdid.uri = util.LspBufnrToUri(bnr)
556 # Use Vim 'changedtick' as the LSP document version number
557 vtdid.version = bnr->getbufvar('changedtick')
559 var changeset: list<dict<any>>
561 ##### FIXME: Sending specific buffer changes to the LSP server doesn't
562 ##### work properly as the computed line range numbers is not correct.
563 ##### For now, send the entire buffer content to LSP server.
565 # for change in changes
567 # var start_lnum: number
568 # var end_lnum: number
569 # var start_col: number
570 # var end_col: number
571 # if change.added == 0
573 # start_lnum = change.lnum - 1
574 # end_lnum = change.end - 1
575 # lines = getbufline(bnr, change.lnum, change.end - 1)->join("\n") .. "\n"
578 # elseif change.added > 0
580 # start_lnum = change.lnum - 1
581 # end_lnum = change.lnum - 1
584 # lines = getbufline(bnr, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n"
587 # start_lnum = change.lnum - 1
588 # end_lnum = change.lnum + (-change.added) - 1
593 # var range: dict<dict<number>> = {'start': {'line': start_lnum, 'character': start_col}, 'end': {'line': end_lnum, 'character': end_col}}
594 # changeset->add({'range': range, 'text': lines})
597 changeset->add({text: bnr->getbufline(1, '$')->join("\n") .. "\n"})
598 var params = {textDocument: vtdid, contentChanges: changeset}
599 lspserver.sendNotification('textDocument/didChange', params)
602 # Return the current cursor position as a LSP position.
603 # find_ident will search for a identifier in front of the cursor, just like
604 # CTRL-] and c_CTRL-R_CTRL-W does.
606 # LSP line and column numbers start from zero, whereas Vim line and column
607 # numbers start from one. The LSP column number is the character index in the
608 # line and not the byte index in the line.
609 def GetPosition(lspserver: dict<any>, find_ident: bool): dict<number>
610 var lnum: number = line('.') - 1
611 var col: number = charcol('.') - 1
612 var line = getline('.')
615 # 1. skip to start of identifier
616 while line[col] != '' && line[col] !~ '\k'
620 # 2. back up to start of identifier
621 while col > 0 && line[col - 1] =~ '\k'
626 # Compute character index counting composing characters as separate
628 var pos = {line: lnum, character: util.GetCharIdxWithCompChar(line, col)}
629 lspserver.encodePosition(bufnr(), pos)
634 # Return the current file name and current cursor position as a LSP
635 # TextDocumentPositionParams structure
636 def GetTextDocPosition(lspserver: dict<any>, find_ident: bool): dict<dict<any>>
637 # interface TextDocumentIdentifier
639 return {textDocument: {uri: util.LspFileToUri(@%)},
640 position: lspserver.getPosition(find_ident)}
643 # Get a list of completion items.
644 # Request: "textDocument/completion"
645 # Param: CompletionParams
646 def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: string): void
647 # Check whether LSP server supports completion
648 if !lspserver.isCompletionProvider
649 util.ErrMsg('LSP server does not support completion')
658 # interface CompletionParams
659 # interface TextDocumentPositionParams
660 var params = lspserver.getTextDocPosition(false)
661 # interface CompletionContext
662 params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
664 lspserver.rpc_a('textDocument/completion', params,
665 completion.CompletionReply)
668 # Get lazy properties for a completion item.
669 # Request: "completionItem/resolve"
670 # Param: CompletionItem
671 def ResolveCompletion(lspserver: dict<any>, item: dict<any>): void
672 # Check whether LSP server supports completion item resolve
673 if !lspserver.isCompletionResolveProvider
674 util.ErrMsg('LSP server does not support completion item resolve')
678 # interface CompletionItem
679 lspserver.rpc_a('completionItem/resolve', item,
680 completion.CompletionResolveReply)
683 # Jump to or peek a symbol location.
685 # Send 'msg' to a LSP server and process the reply. 'msg' is one of the
687 # textDocument/definition
688 # textDocument/declaration
689 # textDocument/typeDefinition
690 # textDocument/implementation
692 # Process the LSP server reply and jump to the symbol location. Before
693 # jumping to the symbol location, save the current cursor position in the tag
696 # If 'peekSymbol' is true, then display the symbol location in the preview
697 # window but don't jump to the symbol location.
699 # Result: Location | Location[] | LocationLink[] | null
700 def GotoSymbolLoc(lspserver: dict<any>, msg: string, peekSymbol: bool,
701 cmdmods: string, count: number)
702 var reply = lspserver.rpc(msg, lspserver.getTextDocPosition(true), false)
703 if reply->empty() || reply.result->empty()
705 if msg == 'textDocument/declaration'
706 emsg = 'symbol declaration is not found'
707 elseif msg == 'textDocument/typeDefinition'
708 emsg = 'symbol type definition is not found'
709 elseif msg == 'textDocument/implementation'
710 emsg = 'symbol implementation is not found'
712 emsg = 'symbol definition is not found'
719 var result = reply.result
720 var location: dict<any>
721 if result->type() == v:t_list
723 # When there are multiple symbol locations, and a specific one isn't
724 # requested with 'count', display the locations in a location list.
726 var title: string = ''
727 if msg == 'textDocument/declaration'
728 title = 'Declarations'
729 elseif msg == 'textDocument/typeDefinition'
730 title = 'Type Definitions'
731 elseif msg == 'textDocument/implementation'
732 title = 'Implementations'
734 title = 'Definitions'
737 if lspserver.needOffsetEncoding
738 # Decode the position encoding in all the symbol locations
739 result->map((_, loc) => {
740 lspserver.decodeLocation(loc)
745 symbol.ShowLocations(lspserver, result, peekSymbol, title)
750 # Select the location requested in 'count'
752 if idx >= result->len()
753 idx = result->len() - 1
755 location = result[idx]
759 lspserver.decodeLocation(location)
761 symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods)
764 # Request: "textDocument/definition"
765 # Param: DefinitionParams
766 def GotoDefinition(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
767 # Check whether LSP server supports jumping to a definition
768 if !lspserver.isDefinitionProvider
769 util.ErrMsg('Jumping to a symbol definition is not supported')
773 # interface DefinitionParams
774 # interface TextDocumentPositionParams
775 GotoSymbolLoc(lspserver, 'textDocument/definition', peek, cmdmods, count)
778 # Request: "textDocument/declaration"
779 # Param: DeclarationParams
780 def GotoDeclaration(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
781 # Check whether LSP server supports jumping to a declaration
782 if !lspserver.isDeclarationProvider
783 util.ErrMsg('Jumping to a symbol declaration is not supported')
787 # interface DeclarationParams
788 # interface TextDocumentPositionParams
789 GotoSymbolLoc(lspserver, 'textDocument/declaration', peek, cmdmods, count)
792 # Request: "textDocument/typeDefinition"
793 # Param: TypeDefinitionParams
794 def GotoTypeDef(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
795 # Check whether LSP server supports jumping to a type definition
796 if !lspserver.isTypeDefinitionProvider
797 util.ErrMsg('Jumping to a symbol type definition is not supported')
801 # interface TypeDefinitionParams
802 # interface TextDocumentPositionParams
803 GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek, cmdmods, count)
806 # Request: "textDocument/implementation"
807 # Param: ImplementationParams
808 def GotoImplementation(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
809 # Check whether LSP server supports jumping to a implementation
810 if !lspserver.isImplementationProvider
811 util.ErrMsg('Jumping to a symbol implementation is not supported')
815 # interface ImplementationParams
816 # interface TextDocumentPositionParams
817 GotoSymbolLoc(lspserver, 'textDocument/implementation', peek, cmdmods, count)
820 # Request: "textDocument/switchSourceHeader"
821 # Param: TextDocumentIdentifier
822 # Clangd specific extension
823 def SwitchSourceHeader(lspserver: dict<any>)
825 param.uri = util.LspFileToUri(@%)
826 var reply = lspserver.rpc('textDocument/switchSourceHeader', param)
827 if reply->empty() || reply.result->empty()
828 util.WarnMsg('No alternate file found')
832 # process the 'textDocument/switchSourceHeader' reply from the LSP server
834 var fname = util.LspUriToFile(reply.result)
835 if (&modified && !&hidden) || &buftype != ''
836 # if the current buffer has unsaved changes and 'hidden' is not set,
837 # or if the current buffer is a special buffer, then ask to save changes
838 exe $'confirm edit {fname}'
844 # get symbol signature help.
845 # Request: "textDocument/signatureHelp"
846 # Param: SignatureHelpParams
847 def ShowSignature(lspserver: dict<any>): void
848 # Check whether LSP server supports signature help
849 if !lspserver.isSignatureHelpProvider
850 util.ErrMsg('LSP server does not support signature help')
854 # interface SignatureHelpParams
855 # interface TextDocumentPositionParams
856 var params = lspserver.getTextDocPosition(false)
857 lspserver.rpc_a('textDocument/signatureHelp', params,
858 signature.SignatureHelp)
861 # Send a file/document saved notification to the language server
862 def DidSaveFile(lspserver: dict<any>, bnr: number): void
863 # Check whether the LSP server supports the didSave notification
864 if !lspserver.supportsDidSave
865 # LSP server doesn't support text document synchronization
869 # Notification: 'textDocument/didSave'
870 # Params: DidSaveTextDocumentParams
871 var params = {textDocument: {uri: util.LspBufnrToUri(bnr)}}
872 # FIXME: Need to set "params.text" when
873 # 'lspserver.caps.textDocumentSync.save.includeText' is set to true.
874 lspserver.sendNotification('textDocument/didSave', params)
877 # get the hover information
878 # Request: "textDocument/hover"
880 def ShowHoverInfo(lspserver: dict<any>, cmdmods: string): void
881 # Check whether LSP server supports getting hover information.
882 # caps->hoverProvider can be a "boolean" or "HoverOptions"
883 if !lspserver.isHoverProvider
887 # interface HoverParams
888 # interface TextDocumentPositionParams
889 var params = lspserver.getTextDocPosition(false)
890 lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
891 hover.HoverReply(lspserver, reply, cmdmods)
895 # Request: "textDocument/references"
896 # Param: ReferenceParams
897 def ShowReferences(lspserver: dict<any>, peek: bool): void
898 # Check whether LSP server supports getting reference information
899 if !lspserver.isReferencesProvider
900 util.ErrMsg('LSP server does not support showing references')
904 # interface ReferenceParams
905 # interface TextDocumentPositionParams
907 param = lspserver.getTextDocPosition(true)
908 param.context = {includeDeclaration: true}
909 var reply = lspserver.rpc('textDocument/references', param)
911 # Result: Location[] | null
912 if reply->empty() || reply.result->empty()
913 util.WarnMsg('No references found')
917 if lspserver.needOffsetEncoding
918 # Decode the position encoding in all the reference locations
919 reply.result->map((_, loc) => {
920 lspserver.decodeLocation(loc)
925 symbol.ShowLocations(lspserver, reply.result, peek, 'Symbol References')
928 # process the 'textDocument/documentHighlight' reply from the LSP server
929 # Result: DocumentHighlight[] | null
930 def DocHighlightReply(lspserver: dict<any>, docHighlightReply: any,
931 bnr: number, cmdmods: string): void
932 if docHighlightReply->empty()
933 if cmdmods !~ 'silent'
934 util.WarnMsg($'No highlight for the current position')
939 for docHL in docHighlightReply
940 lspserver.decodeRange(bnr, docHL.range)
941 var kind: number = docHL->get('kind', 1)
945 propName = 'LspReadRef'
948 propName = 'LspWriteRef'
951 propName = 'LspTextRef'
954 prop_add(docHL.range.start.line + 1,
955 util.GetLineByteFromPos(bnr, docHL.range.start) + 1,
956 {end_lnum: docHL.range.end.line + 1,
957 end_col: util.GetLineByteFromPos(bnr, docHL.range.end) + 1,
960 catch /E966\|E964/ # Invalid lnum | Invalid col
961 # Highlight replies arrive asynchronously and the document might have
962 # been modified in the mean time. As the reply is stale, ignore invalid
963 # line number and column number errors.
968 # Request: "textDocument/documentHighlight"
969 # Param: DocumentHighlightParams
970 def DocHighlight(lspserver: dict<any>, cmdmods: string): void
971 # Check whether LSP server supports getting highlight information
972 if !lspserver.isDocumentHighlightProvider
973 util.ErrMsg('LSP server does not support document highlight')
977 # interface DocumentHighlightParams
978 # interface TextDocumentPositionParams
979 var params = lspserver.getTextDocPosition(false)
980 lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
981 DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
985 # Request: "textDocument/documentSymbol"
986 # Param: DocumentSymbolParams
987 def GetDocSymbols(lspserver: dict<any>, fname: string, showOutline: bool): void
988 # Check whether LSP server supports getting document symbol information
989 if !lspserver.isDocumentSymbolProvider
990 util.ErrMsg('LSP server does not support getting list of symbols')
994 # interface DocumentSymbolParams
995 # interface TextDocumentIdentifier
996 var params = {textDocument: {uri: util.LspFileToUri(fname)}}
997 lspserver.rpc_a('textDocument/documentSymbol', params, (_, reply) => {
999 symbol.DocSymbolOutline(lspserver, reply, fname)
1001 symbol.DocSymbolPopup(lspserver, reply, fname)
1006 # Request: "textDocument/formatting"
1007 # Param: DocumentFormattingParams
1009 # Request: "textDocument/rangeFormatting"
1010 # Param: DocumentRangeFormattingParams
1011 def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
1012 start_lnum: number, end_lnum: number)
1013 # Check whether LSP server supports formatting documents
1014 if !lspserver.isDocumentFormattingProvider
1015 util.ErrMsg('LSP server does not support formatting documents')
1021 cmd = 'textDocument/rangeFormatting'
1023 cmd = 'textDocument/formatting'
1026 # interface DocumentFormattingParams
1027 # interface TextDocumentIdentifier
1028 # interface FormattingOptions
1030 param.textDocument = {uri: util.LspFileToUri(fname)}
1031 var fmtopts: dict<any> = {
1032 tabSize: shiftwidth(),
1033 insertSpaces: &expandtab ? true : false,
1035 param.options = fmtopts
1038 var r: dict<dict<number>> = {
1039 start: {line: start_lnum - 1, character: 0},
1040 end: {line: end_lnum - 1, character: charcol([end_lnum, '$']) - 1}}
1044 var reply = lspserver.rpc(cmd, param)
1046 # result: TextEdit[] | null
1048 if reply->empty() || reply.result->empty()
1053 var bnr: number = fname->bufnr()
1055 # file is already removed
1059 if lspserver.needOffsetEncoding
1060 # Decode the position encoding in all the reference locations
1061 reply.result->map((_, textEdit) => {
1062 lspserver.decodeRange(bnr, textEdit.range)
1067 # interface TextEdit
1068 # Apply each of the text edit operations
1069 var save_cursor: list<number> = getcurpos()
1070 textedit.ApplyTextEdits(bnr, reply.result)
1071 save_cursor->setpos('.')
1074 # Request: "textDocument/prepareCallHierarchy"
1075 def PrepareCallHierarchy(lspserver: dict<any>): dict<any>
1076 # interface CallHierarchyPrepareParams
1077 # interface TextDocumentPositionParams
1078 var param: dict<any>
1079 param = lspserver.getTextDocPosition(false)
1080 var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
1081 if reply->empty() || reply.result->empty()
1085 # Result: CallHierarchyItem[] | null
1086 var choice: number = 1
1087 if reply.result->len() > 1
1088 var items: list<string> = ['Select a Call Hierarchy Item:']
1089 for i in reply.result->len()->range()
1090 items->add(printf("%d. %s", i + 1, reply.result[i].name))
1092 choice = items->inputlist()
1093 if choice < 1 || choice > items->len()
1098 return reply.result[choice - 1]
1101 # Request: "callHierarchy/incomingCalls"
1102 def IncomingCalls(lspserver: dict<any>, fname: string)
1103 # Check whether LSP server supports call hierarchy
1104 if !lspserver.isCallHierarchyProvider
1105 util.ErrMsg('LSP server does not support call hierarchy')
1109 callhier.IncomingCalls(lspserver)
1112 def GetIncomingCalls(lspserver: dict<any>, item: dict<any>): any
1113 # Request: "callHierarchy/incomingCalls"
1114 # Param: CallHierarchyIncomingCallsParams
1117 var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
1122 if lspserver.needOffsetEncoding
1123 # Decode the position encoding in all the incoming call locations
1124 var bnr = util.LspUriToBufnr(item.uri)
1125 reply.result->map((_, hierItem) => {
1126 lspserver.decodeRange(bnr, hierItem.from.range)
1134 # Request: "callHierarchy/outgoingCalls"
1135 def OutgoingCalls(lspserver: dict<any>, fname: string)
1136 # Check whether LSP server supports call hierarchy
1137 if !lspserver.isCallHierarchyProvider
1138 util.ErrMsg('LSP server does not support call hierarchy')
1142 callhier.OutgoingCalls(lspserver)
1145 def GetOutgoingCalls(lspserver: dict<any>, item: dict<any>): any
1146 # Request: "callHierarchy/outgoingCalls"
1147 # Param: CallHierarchyOutgoingCallsParams
1150 var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
1155 if lspserver.needOffsetEncoding
1156 # Decode the position encoding in all the outgoing call locations
1157 var bnr = util.LspUriToBufnr(item.uri)
1158 reply.result->map((_, hierItem) => {
1159 lspserver.decodeRange(bnr, hierItem.to.range)
1167 # Request: "textDocument/inlayHint"
1169 def InlayHintsShow(lspserver: dict<any>)
1170 # Check whether LSP server supports type hierarchy
1171 if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
1172 util.ErrMsg('LSP server does not support inlay hint')
1176 var lastlnum = line('$')
1178 textDocument: {uri: util.LspFileToUri(@%)},
1181 start: {line: 0, character: 0},
1182 end: {line: lastlnum - 1, character: charcol([lastlnum, '$']) - 1}
1186 lspserver.encodeRange(bufnr(), param.range)
1189 if lspserver.isClangdInlayHintsProvider
1190 # clangd-style inlay hints
1191 msg = 'clangd/inlayHints'
1193 msg = 'textDocument/inlayHint'
1195 var reply = lspserver.rpc_a(msg, param, inlayhints.InlayHintsReply)
1198 def DecodeTypeHierarchy(lspserver: dict<any>, isSuper: bool, typeHier: dict<any>)
1199 if !lspserver.needOffsetEncoding
1202 var bnr = util.LspUriToBufnr(typeHier.uri)
1203 lspserver.decodeRange(bnr, typeHier.range)
1204 lspserver.decodeRange(bnr, typeHier.selectionRange)
1205 var subType: list<dict<any>>
1207 subType = typeHier->get('parents', [])
1209 subType = typeHier->get('children', [])
1211 if !subType->empty()
1212 # Decode the position encoding in all the type hierarchy items
1213 subType->map((_, typeHierItem) => {
1214 DecodeTypeHierarchy(lspserver, isSuper, typeHierItem)
1220 # Request: "textDocument/typehierarchy"
1221 # Support the clangd version of type hierarchy retrieval method.
1222 # The method described in the LSP 3.17.0 standard is not supported as clangd
1223 # doesn't support that method.
1224 def TypeHiearchy(lspserver: dict<any>, direction: number)
1225 # Check whether LSP server supports type hierarchy
1226 if !lspserver.isTypeHierarchyProvider
1227 util.ErrMsg('LSP server does not support type hierarchy')
1231 # interface TypeHierarchy
1232 # interface TextDocumentPositionParams
1233 var param: dict<any>
1234 param = lspserver.getTextDocPosition(false)
1235 # 0: children, 1: parent, 2: both
1236 param.direction = direction
1238 var reply = lspserver.rpc('textDocument/typeHierarchy', param)
1239 if reply->empty() || reply.result->empty()
1240 util.WarnMsg('No type hierarchy available')
1244 var isSuper = (direction == 1)
1246 DecodeTypeHierarchy(lspserver, isSuper, reply.result)
1248 typehier.ShowTypeHierarchy(lspserver, isSuper, reply.result)
1251 # Decode the ranges in "WorkspaceEdit"
1252 def DecodeWorkspaceEdit(lspserver: dict<any>, workspaceEdit: dict<any>)
1253 if !lspserver.needOffsetEncoding
1256 if workspaceEdit->has_key('changes')
1257 for [uri, changes] in workspaceEdit.changes->items()
1258 var bnr: number = util.LspUriToBufnr(uri)
1262 # Decode the position encoding in all the text edit locations
1263 changes->map((_, textEdit) => {
1264 lspserver.decodeRange(bnr, textEdit.range)
1270 if workspaceEdit->has_key('documentChanges')
1271 for change in workspaceEdit.documentChanges
1272 if !change->has_key('kind')
1273 var bnr: number = util.LspUriToBufnr(change.textDocument.uri)
1277 # Decode the position encoding in all the text edit locations
1278 change.edits->map((_, textEdit) => {
1279 lspserver.decodeRange(bnr, textEdit.range)
1287 # Request: "textDocument/rename"
1288 # Param: RenameParams
1289 def RenameSymbol(lspserver: dict<any>, newName: string)
1290 # Check whether LSP server supports rename operation
1291 if !lspserver.isRenameProvider
1292 util.ErrMsg('LSP server does not support rename operation')
1296 # interface RenameParams
1297 # interface TextDocumentPositionParams
1298 var param: dict<any> = {}
1299 param = lspserver.getTextDocPosition(true)
1300 param.newName = newName
1302 var reply = lspserver.rpc('textDocument/rename', param)
1304 # Result: WorkspaceEdit | null
1305 if reply->empty() || reply.result->empty()
1310 # result: WorkspaceEdit
1311 DecodeWorkspaceEdit(lspserver, reply.result)
1312 textedit.ApplyWorkspaceEdit(reply.result)
1315 # Decode the range in "CodeAction"
1316 def DecodeCodeAction(lspserver: dict<any>, actionList: list<dict<any>>)
1317 if !lspserver.needOffsetEncoding
1320 actionList->map((_, act) => {
1321 if !act->has_key('disabled') && act->has_key('edit')
1322 DecodeWorkspaceEdit(lspserver, act.edit)
1328 # Request: "textDocument/codeAction"
1329 # Param: CodeActionParams
1330 def CodeAction(lspserver: dict<any>, fname_arg: string, line1: number,
1331 line2: number, query: string)
1332 # Check whether LSP server supports code action operation
1333 if !lspserver.isCodeActionProvider
1334 util.ErrMsg('LSP server does not support code action operation')
1338 # interface CodeActionParams
1339 var params: dict<any> = {}
1340 var fname: string = fname_arg->fnamemodify(':p')
1341 var bnr: number = fname_arg->bufnr()
1342 var r: dict<dict<number>> = {
1345 character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
1349 character: util.GetCharIdxWithCompChar(getline(line2), charcol([line2, '$']) - 1)
1352 lspserver.encodeRange(bnr, r)
1353 params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
1354 var d: list<dict<any>> = []
1355 for lnum in range(line1, line2)
1356 var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(bnr, lnum, lspserver)->deepcopy()
1357 if lspserver.needOffsetEncoding
1358 diagsInfo->map((_, di) => {
1359 lspserver.encodeRange(bnr, di.range)
1363 d->extend(diagsInfo)
1365 params->extend({context: {diagnostics: d, triggerKind: 1}})
1367 var reply = lspserver.rpc('textDocument/codeAction', params)
1369 # Result: (Command | CodeAction)[] | null
1370 if reply->empty() || reply.result->empty()
1371 # no action can be performed
1372 util.WarnMsg('No code action is available')
1376 DecodeCodeAction(lspserver, reply.result)
1378 codeaction.ApplyCodeAction(lspserver, reply.result, query)
1381 # Request: "textDocument/codeLens"
1382 # Param: CodeLensParams
1383 def CodeLens(lspserver: dict<any>, fname: string)
1384 # Check whether LSP server supports code lens operation
1385 if !lspserver.isCodeLensProvider
1386 util.ErrMsg('LSP server does not support code lens operation')
1390 var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1391 var reply = lspserver.rpc('textDocument/codeLens', params)
1392 if reply->empty() || reply.result->empty()
1393 util.WarnMsg($'No code lens actions found for the current file')
1397 var bnr = fname->bufnr()
1399 # Decode the position encoding in all the code lens items
1400 if lspserver.needOffsetEncoding
1401 reply.result->map((_, codeLensItem) => {
1402 lspserver.decodeRange(bnr, codeLensItem.range)
1407 codelens.ProcessCodeLens(lspserver, bnr, reply.result)
1410 # Request: "codeLens/resolve"
1412 def ResolveCodeLens(lspserver: dict<any>, bnr: number,
1413 codeLens: dict<any>): dict<any>
1414 if !lspserver.isCodeLensResolveProvider
1418 if lspserver.needOffsetEncoding
1419 lspserver.encodeRange(bnr, codeLens.range)
1422 var reply = lspserver.rpc('codeLens/resolve', codeLens)
1427 var codeLensItem: dict<any> = reply.result
1429 # Decode the position encoding in the code lens item
1430 if lspserver.needOffsetEncoding
1431 lspserver.decodeRange(bnr, codeLensItem.range)
1437 # List project-wide symbols matching query string
1438 # Request: "workspace/symbol"
1439 # Param: WorkspaceSymbolParams
1440 def WorkspaceQuerySymbols(lspserver: dict<any>, query: string, firstCall: bool, cmdmods: string = '')
1441 # Check whether the LSP server supports listing workspace symbols
1442 if !lspserver.isWorkspaceSymbolProvider
1443 util.ErrMsg('LSP server does not support listing workspace symbols')
1447 # Param: WorkspaceSymbolParams
1450 var reply = lspserver.rpc('workspace/symbol', param)
1451 if reply->empty() || reply.result->empty()
1452 util.WarnMsg($'Symbol "{query}" is not found')
1456 var symInfo: list<dict<any>> = reply.result
1458 if lspserver.needOffsetEncoding
1459 # Decode the position encoding in all the symbol locations
1460 symInfo->map((_, sym) => {
1461 if sym->has_key('location')
1462 lspserver.decodeLocation(sym.location)
1468 if firstCall && symInfo->len() == 1
1469 # If there is only one symbol, then jump to the symbol location
1470 var symLoc: dict<any> = symInfo[0]->get('location', {})
1472 symbol.GotoSymbol(lspserver, symLoc, false, cmdmods)
1475 symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods)
1479 # Add a workspace folder to the language server.
1480 def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1481 if !lspserver.caps->has_key('workspace')
1482 || !lspserver.caps.workspace->has_key('workspaceFolders')
1483 || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1484 || !lspserver.caps.workspace.workspaceFolders.supported
1485 util.ErrMsg('LSP server does not support workspace folders')
1489 if lspserver.workspaceFolders->index(dirName) != -1
1490 util.ErrMsg($'{dirName} is already part of this workspace')
1494 # Notification: 'workspace/didChangeWorkspaceFolders'
1495 # Params: DidChangeWorkspaceFoldersParams
1496 var params = {event: {added: [dirName], removed: []}}
1497 lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1499 lspserver.workspaceFolders->add(dirName)
1502 # Remove a workspace folder from the language server.
1503 def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1504 if !lspserver.caps->has_key('workspace')
1505 || !lspserver.caps.workspace->has_key('workspaceFolders')
1506 || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1507 || !lspserver.caps.workspace.workspaceFolders.supported
1508 util.ErrMsg('LSP server does not support workspace folders')
1512 var idx: number = lspserver.workspaceFolders->index(dirName)
1514 util.ErrMsg($'{dirName} is not currently part of this workspace')
1518 # Notification: "workspace/didChangeWorkspaceFolders"
1519 # Param: DidChangeWorkspaceFoldersParams
1520 var params = {event: {added: [], removed: [dirName]}}
1521 lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1523 lspserver.workspaceFolders->remove(idx)
1526 def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
1527 lspserver.decodeRange(bnr, selRange.range)
1528 if selRange->has_key('parent')
1529 DecodeSelectionRange(lspserver, bnr, selRange.parent)
1533 # select the text around the current cursor location
1534 # Request: "textDocument/selectionRange"
1535 # Param: SelectionRangeParams
1536 def SelectionRange(lspserver: dict<any>, fname: string)
1537 # Check whether LSP server supports selection ranges
1538 if !lspserver.isSelectionRangeProvider
1539 util.ErrMsg('LSP server does not support selection ranges')
1543 # clear the previous selection reply
1544 lspserver.selection = {}
1546 # interface SelectionRangeParams
1547 # interface TextDocumentIdentifier
1549 param.textDocument = {}
1550 param.textDocument.uri = util.LspFileToUri(fname)
1551 param.positions = [lspserver.getPosition(false)]
1552 var reply = lspserver.rpc('textDocument/selectionRange', param)
1554 if reply->empty() || reply.result->empty()
1558 # Decode the position encoding in all the selection range items
1559 if lspserver.needOffsetEncoding
1560 var bnr = fname->bufnr()
1561 reply.result->map((_, selItem) => {
1562 DecodeSelectionRange(lspserver, bnr, selItem)
1567 selection.SelectionStart(lspserver, reply.result)
1570 # Expand the previous selection or start a new one
1571 def SelectionExpand(lspserver: dict<any>)
1572 # Check whether LSP server supports selection ranges
1573 if !lspserver.isSelectionRangeProvider
1574 util.ErrMsg('LSP server does not support selection ranges')
1578 selection.SelectionModify(lspserver, true)
1581 # Shrink the previous selection or start a new one
1582 def SelectionShrink(lspserver: dict<any>)
1583 # Check whether LSP server supports selection ranges
1584 if !lspserver.isSelectionRangeProvider
1585 util.ErrMsg('LSP server does not support selection ranges')
1589 selection.SelectionModify(lspserver, false)
1592 # fold the entire document
1593 # Request: "textDocument/foldingRange"
1594 # Param: FoldingRangeParams
1595 def FoldRange(lspserver: dict<any>, fname: string)
1596 # Check whether LSP server supports fold ranges
1597 if !lspserver.isFoldingRangeProvider
1598 util.ErrMsg('LSP server does not support folding')
1602 # interface FoldingRangeParams
1603 # interface TextDocumentIdentifier
1604 var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1605 var reply = lspserver.rpc('textDocument/foldingRange', params)
1606 if reply->empty() || reply.result->empty()
1610 # result: FoldingRange[]
1611 var end_lnum: number
1612 var last_lnum: number = line('$')
1613 for foldRange in reply.result
1614 end_lnum = foldRange.endLine + 1
1615 if end_lnum < foldRange.startLine + 2
1616 end_lnum = foldRange.startLine + 2
1618 exe $':{foldRange.startLine + 2}, {end_lnum}fold'
1619 # Open all the folds, otherwise the subsequently created folds are not
1625 :setlocal foldcolumn=2
1629 # process the 'workspace/executeCommand' reply from the LSP server
1630 # Result: any | null
1631 def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
1632 # Nothing to do for the reply
1635 # Request the LSP server to execute a command
1636 # Request: workspace/executeCommand
1637 # Params: ExecuteCommandParams
1638 def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
1639 # Need to check for lspserver.caps.executeCommandProvider?
1641 lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
1644 # Display the LSP server capabilities (received during the initialization
1646 def GetCapabilities(lspserver: dict<any>): list<string>
1648 var heading = $"'{lspserver.path}' Language Server Capabilities"
1649 var underlines = repeat('=', heading->len())
1650 l->extend([heading, underlines])
1651 for k in lspserver.caps->keys()->sort()
1652 l->add($'{k}: {lspserver.caps[k]->string()}')
1657 # Display the LSP server initialize request and result
1658 def GetInitializeRequest(lspserver: dict<any>): list<string>
1660 var heading = $"'{lspserver.path}' Language Server Initialize Request"
1661 var underlines = repeat('=', heading->len())
1662 l->extend([heading, underlines])
1663 if lspserver->has_key('rpcInitializeRequest')
1664 for k in lspserver.rpcInitializeRequest->keys()->sort()
1665 l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
1671 # Store a log or trace message received from the language server.
1672 def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
1673 # A single message may contain multiple lines separate by newline
1674 var msgs = newMsg->split("\n")
1675 lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
1676 lspserver.messages->extend(msgs[1 : ])
1677 # Keep only the last 500 messages to reduce the memory usage
1678 if lspserver.messages->len() >= 600
1679 lspserver.messages = lspserver.messages[-500 : ]
1683 # Display the log messages received from the LSP server (window/logMessage)
1684 def GetMessages(lspserver: dict<any>): list<string>
1685 if lspserver.messages->empty()
1686 return [$'No messages received from "{lspserver.name}" server']
1690 var heading = $"'{lspserver.path}' Language Server Messages"
1691 var underlines = repeat('=', heading->len())
1692 l->extend([heading, underlines])
1693 l->extend(lspserver.messages)
1697 # Send a 'textDocument/definition' request to the LSP server to get the
1698 # location where the symbol under the cursor is defined and return a list of
1699 # Dicts in a format accepted by the 'tagfunc' option.
1700 # Returns null if the LSP server doesn't support getting the location of a
1701 # symbol definition or the symbol is not defined.
1702 def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
1703 # Check whether LSP server supports getting the location of a definition
1704 if !lspserver.isDefinitionProvider
1708 # interface DefinitionParams
1709 # interface TextDocumentPositionParams
1710 var reply = lspserver.rpc('textDocument/definition',
1711 lspserver.getTextDocPosition(false))
1712 if reply->empty() || reply.result->empty()
1716 var taglocations: list<dict<any>>
1717 if reply.result->type() == v:t_list
1718 taglocations = reply.result
1720 taglocations = [reply.result]
1723 if lspserver.needOffsetEncoding
1724 # Decode the position encoding in all the reference locations
1725 taglocations->map((_, loc) => {
1726 lspserver.decodeLocation(loc)
1731 return symbol.TagFunc(lspserver, taglocations, pat)
1734 # Returns unique ID used for identifying the various servers
1735 var UniqueServerIdCounter = 0
1736 def GetUniqueServerId(): number
1737 UniqueServerIdCounter = UniqueServerIdCounter + 1
1738 return UniqueServerIdCounter
1741 export def NewLspServer(serverParams: dict<any>): dict<any>
1742 var lspserver: dict<any> = {
1743 id: GetUniqueServerId(),
1744 name: serverParams.name,
1745 path: serverParams.path,
1746 args: serverParams.args->deepcopy(),
1754 callHierarchyType: '',
1755 completionTriggerChars: [],
1756 customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
1757 customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
1758 debug: serverParams.debug,
1759 features: serverParams.features->deepcopy(),
1760 forceOffsetEncoding: serverParams.forceOffsetEncoding,
1761 initializationOptions: serverParams.initializationOptions->deepcopy(),
1763 omniCompletePending: false,
1764 peekSymbolFilePopup: -1,
1765 peekSymbolPopup: -1,
1766 processDiagHandler: serverParams.processDiagHandler,
1767 rootSearchFiles: serverParams.rootSearch->deepcopy(),
1768 runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
1769 runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
1772 syncInit: serverParams.syncInit,
1773 traceLevel: serverParams.traceLevel,
1774 typeHierFilePopup: -1,
1776 workspaceConfig: serverParams.workspaceConfig->deepcopy(),
1777 workspaceSymbolPopup: -1,
1778 workspaceSymbolQuery: ''
1780 lspserver.logfile = $'lsp-{lspserver.name}.log'
1781 lspserver.errfile = $'lsp-{lspserver.name}.err'
1783 # Add the LSP server functions
1785 startServer: function(StartServer, [lspserver]),
1786 initServer: function(InitServer, [lspserver]),
1787 stopServer: function(StopServer, [lspserver]),
1788 shutdownServer: function(ShutdownServer, [lspserver]),
1789 exitServer: function(ExitServer, [lspserver]),
1790 setTrace: function(SetTrace, [lspserver]),
1791 traceLog: function(TraceLog, [lspserver]),
1792 errorLog: function(ErrorLog, [lspserver]),
1793 nextReqID: function(NextReqID, [lspserver]),
1794 createRequest: function(CreateRequest, [lspserver]),
1795 createResponse: function(CreateResponse, [lspserver]),
1796 sendResponse: function(SendResponse, [lspserver]),
1797 sendMessage: function(SendMessage, [lspserver]),
1798 sendNotification: function(SendNotification, [lspserver]),
1799 rpc: function(Rpc, [lspserver]),
1800 rpc_a: function(AsyncRpc, [lspserver]),
1801 waitForResponse: function(WaitForResponse, [lspserver]),
1802 processReply: function(handlers.ProcessReply, [lspserver]),
1803 processNotif: function(handlers.ProcessNotif, [lspserver]),
1804 processRequest: function(handlers.ProcessRequest, [lspserver]),
1805 processMessages: function(handlers.ProcessMessages, [lspserver]),
1806 encodePosition: function(offset.EncodePosition, [lspserver]),
1807 decodePosition: function(offset.DecodePosition, [lspserver]),
1808 encodeRange: function(offset.EncodeRange, [lspserver]),
1809 decodeRange: function(offset.DecodeRange, [lspserver]),
1810 encodeLocation: function(offset.EncodeLocation, [lspserver]),
1811 decodeLocation: function(offset.DecodeLocation, [lspserver]),
1812 getPosition: function(GetPosition, [lspserver]),
1813 getTextDocPosition: function(GetTextDocPosition, [lspserver]),
1814 textdocDidOpen: function(TextdocDidOpen, [lspserver]),
1815 textdocDidClose: function(TextdocDidClose, [lspserver]),
1816 textdocDidChange: function(TextdocDidChange, [lspserver]),
1817 sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
1818 sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
1819 getCompletion: function(GetCompletion, [lspserver]),
1820 resolveCompletion: function(ResolveCompletion, [lspserver]),
1821 gotoDefinition: function(GotoDefinition, [lspserver]),
1822 gotoDeclaration: function(GotoDeclaration, [lspserver]),
1823 gotoTypeDef: function(GotoTypeDef, [lspserver]),
1824 gotoImplementation: function(GotoImplementation, [lspserver]),
1825 tagFunc: function(TagFunc, [lspserver]),
1826 switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
1827 showSignature: function(ShowSignature, [lspserver]),
1828 didSaveFile: function(DidSaveFile, [lspserver]),
1829 hover: function(ShowHoverInfo, [lspserver]),
1830 showReferences: function(ShowReferences, [lspserver]),
1831 docHighlight: function(DocHighlight, [lspserver]),
1832 getDocSymbols: function(GetDocSymbols, [lspserver]),
1833 textDocFormat: function(TextDocFormat, [lspserver]),
1834 prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
1835 incomingCalls: function(IncomingCalls, [lspserver]),
1836 getIncomingCalls: function(GetIncomingCalls, [lspserver]),
1837 outgoingCalls: function(OutgoingCalls, [lspserver]),
1838 getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
1839 inlayHintsShow: function(InlayHintsShow, [lspserver]),
1840 typeHierarchy: function(TypeHiearchy, [lspserver]),
1841 renameSymbol: function(RenameSymbol, [lspserver]),
1842 codeAction: function(CodeAction, [lspserver]),
1843 codeLens: function(CodeLens, [lspserver]),
1844 resolveCodeLens: function(ResolveCodeLens, [lspserver]),
1845 workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
1846 addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
1847 removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
1848 selectionRange: function(SelectionRange, [lspserver]),
1849 selectionExpand: function(SelectionExpand, [lspserver]),
1850 selectionShrink: function(SelectionShrink, [lspserver]),
1851 foldRange: function(FoldRange, [lspserver]),
1852 executeCommand: function(ExecuteCommand, [lspserver]),
1853 workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
1854 getCapabilities: function(GetCapabilities, [lspserver]),
1855 getInitializeRequest: function(GetInitializeRequest, [lspserver]),
1856 addMessage: function(AddMessage, [lspserver]),
1857 getMessages: function(GetMessages, [lspserver])
1863 # vim: tabstop=8 shiftwidth=2 softtabstop=2