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
32 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {msg->string()}')
35 lspserver.processMessages()
38 # LSP server error output handler
39 def Error_cb(lspserver: dict<any>, chan: channel, emsg: string): void
40 lspserver.errorLog(emsg)
43 # LSP server exit callback
44 def Exit_cb(lspserver: dict<any>, job: job, status: number): void
45 util.WarnMsg($'{strftime("%m/%d/%y %T")}: LSP server ({lspserver.name}) exited with status {status}')
46 lspserver.running = false
47 lspserver.ready = false
48 lspserver.requests = {}
53 def StartServer(lspserver: dict<any>, bnr: number): number
55 util.WarnMsg($'LSP server "{lspserver.name}" is already running')
59 var cmd = [lspserver.path]
60 cmd->extend(lspserver.args)
62 var opts = {in_mode: 'lsp',
66 out_cb: function(Output_cb, [lspserver]),
67 err_cb: function(Error_cb, [lspserver]),
68 exit_cb: function(Exit_cb, [lspserver])}
73 lspserver.requests = {}
74 lspserver.omniCompletePending = false
75 lspserver.completionLazyDoc = false
76 lspserver.completionTriggerChars = []
77 lspserver.signaturePopup = -1
79 var job = cmd->job_start(opts)
80 if job->job_status() == 'fail'
81 util.ErrMsg($'Failed to start LSP server {lspserver.path}')
85 # wait a little for the LSP server to start
89 lspserver.running = true
91 lspserver.initServer(bnr)
96 # process the "initialize" method reply from the LSP server
97 # Result: InitializeResult
98 def ServerInitReply(lspserver: dict<any>, initResult: dict<any>): void
99 if initResult->empty()
103 var caps: dict<any> = initResult.capabilities
104 lspserver.caps = caps
106 for [key, val] in initResult->items()
107 if key == 'capabilities'
111 lspserver.caps[$'~additionalInitResult_{key}'] = val
114 capabilities.ProcessServerCaps(lspserver, caps)
116 if caps->has_key('completionProvider')
117 if opt.lspOptions.autoComplete
118 lspserver.completionTriggerChars =
119 caps.completionProvider->get('triggerCharacters', [])
121 lspserver.completionLazyDoc =
122 caps.completionProvider->get('resolveProvider', false)
125 # send a "initialized" notification to server
126 lspserver.sendInitializedNotif()
127 # send any workspace configuration (optional)
128 if !lspserver.workspaceConfig->empty()
129 lspserver.sendWorkspaceConfig()
131 lspserver.ready = true
132 if exists($'#User#LspServerReady{lspserver.name}')
133 exe $'doautocmd <nomodeline> User LspServerReady{lspserver.name}'
135 # Used internally, and shouldn't be used by users
136 if exists($'#User#LspServerReady_{lspserver.id}')
137 exe $'doautocmd <nomodeline> User LspServerReady_{lspserver.id}'
140 # set the server debug trace level
141 if lspserver.traceLevel != 'off'
142 lspserver.setTrace(lspserver.traceLevel)
145 # if the outline window is opened, then request the symbols for the current
147 if bufwinid('LSP-Outline') != -1
148 lspserver.getDocSymbols(@%, true)
151 # Update the inlay hints (if enabled)
152 if opt.lspOptions.showInlayHints && (lspserver.isInlayHintProvider
153 || lspserver.isClangdInlayHintsProvider)
154 inlayhints.LspInlayHintsUpdateNow(bufnr())
158 # Request: "initialize"
159 # Param: InitializeParams
160 def InitServer(lspserver: dict<any>, bnr: number)
161 # interface 'InitializeParams'
162 var initparams: dict<any> = {}
163 initparams.processId = getpid()
164 initparams.clientInfo = {
166 version: v:versionlong->string(),
169 # Compute the rootpath (based on the directory of the buffer)
171 var rootSearchFiles = lspserver.rootSearchFiles
172 var bufDir = bnr->bufname()->fnamemodify(':p:h')
173 if !rootSearchFiles->empty()
174 rootPath = util.FindNearestRootDir(bufDir, rootSearchFiles)
179 # bufDir is within cwd
180 var bufDirPrefix = bufDir[0 : cwd->strcharlen() - 1]
182 ? bufDirPrefix ==? cwd
183 : bufDirPrefix == cwd
190 lspserver.workspaceFolders = [rootPath]
192 var rootUri = util.LspFileToUri(rootPath)
193 initparams.rootPath = rootPath
194 initparams.rootUri = rootUri
195 initparams.workspaceFolders = [{
196 name: rootPath->fnamemodify(':t'),
200 initparams.trace = 'off'
201 initparams.capabilities = capabilities.GetClientCaps()
202 if !lspserver.initializationOptions->empty()
203 initparams.initializationOptions = lspserver.initializationOptions
205 initparams.initializationOptions = {}
208 lspserver.rpcInitializeRequest = initparams
210 lspserver.rpc_a('initialize', initparams, ServerInitReply)
213 # Send a "initialized" notification to the language server
214 def SendInitializedNotif(lspserver: dict<any>)
215 # Notification: 'initialized'
216 # Params: InitializedParams
217 lspserver.sendNotification('initialized')
222 def ShutdownServer(lspserver: dict<any>): void
223 lspserver.rpc('shutdown', {})
226 # Send a 'exit' notification to the language server
227 def ExitServer(lspserver: dict<any>): void
228 # Notification: 'exit'
230 lspserver.sendNotification('exit')
234 def StopServer(lspserver: dict<any>): number
235 if !lspserver.running
236 util.WarnMsg($'LSP server {lspserver.name} is not running')
240 # Send the shutdown request to the server
241 lspserver.shutdownServer()
243 # Notify the server to exit
244 lspserver.exitServer()
246 # Wait for the server to process the exit notification and exit for a
247 # maximum of 2 seconds.
248 var maxCount: number = 1000
249 while lspserver.job->job_status() == 'run' && maxCount > 0
254 if lspserver.job->job_status() == 'run'
255 lspserver.job->job_stop()
257 lspserver.running = false
258 lspserver.ready = false
259 lspserver.requests = {}
263 # Set the language server trace level using the '$/setTrace' notification.
264 # Supported values for "traceVal" are "off", "messages" and "verbose".
265 def SetTrace(lspserver: dict<any>, traceVal: string)
266 # Notification: '$/setTrace'
267 # Params: SetTraceParams
268 var params = {value: traceVal}
269 lspserver.sendNotification('$/setTrace', params)
272 # Log a debug message to the LSP server debug file
273 def TraceLog(lspserver: dict<any>, msg: string)
275 util.TraceLog(lspserver.logfile, false, msg)
279 # Log an error message to the LSP server error file
280 def ErrorLog(lspserver: dict<any>, errmsg: string)
282 util.TraceLog(lspserver.errfile, true, errmsg)
286 # Return the next id for a LSP server request message
287 def NextReqID(lspserver: dict<any>): number
288 var id = lspserver.nextID
289 lspserver.nextID = id + 1
293 # create a LSP server request message
294 def CreateRequest(lspserver: dict<any>, method: string): dict<any>
297 id: lspserver.nextReqID(),
302 # Save the request, so that the corresponding response can be processed
303 lspserver.requests->extend({[req.id->string()]: req})
308 # create a LSP server response message
309 def CreateResponse(lspserver: dict<any>, req_id: number): dict<any>
317 # create a LSP server notification message
318 def CreateNotification(lspserver: dict<any>, notif: string): dict<any>
328 # send a response message to the server
329 def SendResponse(lspserver: dict<any>, request: dict<any>, result: any, error: dict<any>)
330 if (request.id->type() == v:t_string
331 && (request.id->trim() =~ '[^[:digit:]]\+'
332 || request.id->trim()->empty()))
333 || (request.id->type() != v:t_string && request.id->type() != v:t_number)
334 util.ErrMsg('request.id of response to LSP server is not a correct number')
337 var resp: dict<any> = lspserver.createResponse(
338 request.id->type() == v:t_string ? request.id->str2nr() : request.id)
340 resp->extend({result: result})
342 resp->extend({error: error})
344 lspserver.sendMessage(resp)
347 # Send a request message to LSP server
348 def SendMessage(lspserver: dict<any>, content: dict<any>): void
349 var job = lspserver.job
350 if job->job_status() != 'run'
351 # LSP server has exited
354 job->ch_sendexpr(content)
355 if content->has_key('id')
357 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {content->string()}')
362 # Send a notification message to the language server
363 def SendNotification(lspserver: dict<any>, method: string, params: any = {})
364 var notif: dict<any> = CreateNotification(lspserver, method)
365 notif.params->extend(params)
366 lspserver.sendMessage(notif)
369 # Translate an LSP error code into a readable string
370 def LspGetErrorMessage(errcode: number): string
372 -32001: 'UnknownErrorCode',
373 -32002: 'ServerNotInitialized',
374 -32600: 'InvalidRequest',
375 -32601: 'MethodNotFound',
376 -32602: 'InvalidParams',
377 -32603: 'InternalError',
378 -32700: 'ParseError',
379 -32800: 'RequestCancelled',
380 -32801: 'ContentModified',
381 -32802: 'ServerCancelled',
382 -32803: 'RequestFailed'
385 return errmap->get(errcode, errcode->string())
388 # Process a LSP server response error and display an error message.
389 def ProcessLspServerError(method: string, responseError: dict<any>)
391 var emsg: string = responseError.message
392 emsg ..= $', error = {LspGetErrorMessage(responseError.code)}'
393 if responseError->has_key('data')
394 emsg ..= $', data = {responseError.data->string()}'
396 util.ErrMsg($'request {method} failed ({emsg})')
399 # Send a sync RPC request message to the LSP server and return the received
400 # reply. In case of an error, an empty Dict is returned.
401 def Rpc(lspserver: dict<any>, method: string, params: any, handleError: bool = true): dict<any>
407 var job = lspserver.job
408 if job->job_status() != 'run'
409 # LSP server has exited
414 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
417 # Do the synchronous RPC call
418 var reply = job->ch_evalexpr(req)
421 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
424 if reply->has_key('result')
429 if reply->has_key('error') && handleError
431 ProcessLspServerError(method, reply.error)
437 # LSP server asynchronous RPC callback
438 def AsyncRpcCb(lspserver: dict<any>, method: string, RpcCb: func, chan: channel, reply: dict<any>)
440 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
447 if reply->has_key('error')
449 ProcessLspServerError(method, reply.error)
453 if !reply->has_key('result')
454 util.ErrMsg($'request {method} failed (no result)')
458 RpcCb(lspserver, reply.result)
461 # Send a async RPC request message to the LSP server with a callback function.
462 # Returns the LSP message id. This id can be used to cancel the RPC request
463 # (if needed). Returns -1 on error.
464 def AsyncRpc(lspserver: dict<any>, method: string, params: any, Cbfunc: func): number
470 var job = lspserver.job
471 if job->job_status() != 'run'
472 # LSP server has exited
477 lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
480 # Do the asynchronous RPC call
481 var Fn = function('AsyncRpcCb', [lspserver, method, Cbfunc])
484 if get(g:, 'LSPTest')
485 # When running LSP tests, make this a synchronous RPC call
486 reply = Rpc(lspserver, method, params)
487 Fn(test_null_channel(), reply)
489 # Otherwise, make an asynchronous RPC call
490 reply = job->ch_sendexpr(req, {callback: Fn})
499 # Wait for a response message from the LSP server for the request "req"
500 # Waits for a maximum of 5 seconds
501 def WaitForResponse(lspserver: dict<any>, req: dict<any>)
502 var maxCount: number = 2500
503 var key: string = req.id->string()
505 while lspserver.requests->has_key(key) && maxCount > 0
511 # Returns true when the "lspserver" has "feature" enabled.
512 # By default, all the features of a lsp server are enabled.
513 def FeatureEnabled(lspserver: dict<any>, feature: string): bool
514 return lspserver.features->get(feature, true)
517 # Retrieve the Workspace configuration asked by the server.
518 # Request: workspace/configuration
519 def WorkspaceConfigGet(lspserver: dict<any>, configItem: dict<any>): dict<any>
520 if lspserver.workspaceConfig->empty()
523 if !configItem->has_key('section') || configItem.section->empty()
524 return lspserver.workspaceConfig
526 var config: dict<any> = lspserver.workspaceConfig
527 for part in configItem.section->split('\.')
528 if !config->has_key(part)
531 config = config[part]
536 # Send a "workspace/didChangeConfiguration" notification to the language
538 def SendWorkspaceConfig(lspserver: dict<any>)
539 # Params: DidChangeConfigurationParams
540 var params = {settings: lspserver.workspaceConfig}
541 lspserver.sendNotification('workspace/didChangeConfiguration', params)
544 # Send a file/document opened notification to the language server.
545 def TextdocDidOpen(lspserver: dict<any>, bnr: number, ftype: string): void
546 # Notification: 'textDocument/didOpen'
547 # Params: DidOpenTextDocumentParams
550 uri: util.LspBufnrToUri(bnr),
552 # Use Vim 'changedtick' as the LSP document version number
553 version: bnr->getbufvar('changedtick'),
554 text: bnr->getbufline(1, '$')->join("\n") .. "\n"
557 lspserver.sendNotification('textDocument/didOpen', params)
560 # Send a file/document closed notification to the language server.
561 def TextdocDidClose(lspserver: dict<any>, bnr: number): void
562 # Notification: 'textDocument/didClose'
563 # Params: DidCloseTextDocumentParams
566 uri: util.LspBufnrToUri(bnr)
569 lspserver.sendNotification('textDocument/didClose', params)
572 # Send a file/document change notification to the language server.
573 # Params: DidChangeTextDocumentParams
574 def TextdocDidChange(lspserver: dict<any>, bnr: number, start: number,
575 end: number, added: number,
576 changes: list<dict<number>>): void
577 # Notification: 'textDocument/didChange'
578 # Params: DidChangeTextDocumentParams
580 # var changeset: list<dict<any>>
582 ##### FIXME: Sending specific buffer changes to the LSP server doesn't
583 ##### work properly as the computed line range numbers is not correct.
584 ##### For now, send the entire buffer content to LSP server.
586 # for change in changes
588 # var start_lnum: number
589 # var end_lnum: number
590 # var start_col: number
591 # var end_col: number
592 # if change.added == 0
594 # start_lnum = change.lnum - 1
595 # end_lnum = change.end - 1
596 # lines = getbufline(bnr, change.lnum, change.end - 1)->join("\n") .. "\n"
599 # elseif change.added > 0
601 # start_lnum = change.lnum - 1
602 # end_lnum = change.lnum - 1
605 # lines = getbufline(bnr, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n"
608 # start_lnum = change.lnum - 1
609 # end_lnum = change.lnum + (-change.added) - 1
614 # var range: dict<dict<number>> = {'start': {'line': start_lnum, 'character': start_col}, 'end': {'line': end_lnum, 'character': end_col}}
615 # changeset->add({'range': range, 'text': lines})
620 uri: util.LspBufnrToUri(bnr),
621 # Use Vim 'changedtick' as the LSP document version number
622 version: bnr->getbufvar('changedtick')
625 {text: bnr->getbufline(1, '$')->join("\n") .. "\n"}
628 lspserver.sendNotification('textDocument/didChange', params)
631 # Return the current cursor position as a LSP position.
632 # find_ident will search for a identifier in front of the cursor, just like
633 # CTRL-] and c_CTRL-R_CTRL-W does.
635 # LSP line and column numbers start from zero, whereas Vim line and column
636 # numbers start from one. The LSP column number is the character index in the
637 # line and not the byte index in the line.
638 def GetPosition(lspserver: dict<any>, find_ident: bool): dict<number>
639 var lnum: number = line('.') - 1
640 var col: number = charcol('.') - 1
641 var line = getline('.')
644 # 1. skip to start of identifier
645 while line[col] != '' && line[col] !~ '\k'
649 # 2. back up to start of identifier
650 while col > 0 && line[col - 1] =~ '\k'
655 # Compute character index counting composing characters as separate
657 var pos = {line: lnum, character: util.GetCharIdxWithCompChar(line, col)}
658 lspserver.encodePosition(bufnr(), pos)
663 # Return the current file name and current cursor position as a LSP
664 # TextDocumentPositionParams structure
665 def GetTextDocPosition(lspserver: dict<any>, find_ident: bool): dict<dict<any>>
666 # interface TextDocumentIdentifier
668 return {textDocument: {uri: util.LspFileToUri(@%)},
669 position: lspserver.getPosition(find_ident)}
672 # Get a list of completion items.
673 # Request: "textDocument/completion"
674 # Param: CompletionParams
675 def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: string): void
676 # Check whether LSP server supports completion
677 if !lspserver.isCompletionProvider
678 util.ErrMsg('LSP server does not support completion')
687 # interface CompletionParams
688 # interface TextDocumentPositionParams
689 var params = lspserver.getTextDocPosition(false)
690 # interface CompletionContext
691 params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
693 lspserver.rpc_a('textDocument/completion', params,
694 completion.CompletionReply)
697 # Get lazy properties for a completion item.
698 # Request: "completionItem/resolve"
699 # Param: CompletionItem
700 def ResolveCompletion(lspserver: dict<any>, item: dict<any>, sync: bool = false): dict<any>
701 # Check whether LSP server supports completion item resolve
702 if !lspserver.isCompletionResolveProvider
706 # interface CompletionItem
708 var reply = lspserver.rpc('completionItem/resolve', item)
709 if !reply->empty() && !reply.result->empty()
713 lspserver.rpc_a('completionItem/resolve', item,
714 completion.CompletionResolveReply)
719 # Jump to or peek a symbol location.
721 # Send 'msg' to a LSP server and process the reply. 'msg' is one of the
723 # textDocument/definition
724 # textDocument/declaration
725 # textDocument/typeDefinition
726 # textDocument/implementation
728 # Process the LSP server reply and jump to the symbol location. Before
729 # jumping to the symbol location, save the current cursor position in the tag
732 # If 'peekSymbol' is true, then display the symbol location in the preview
733 # window but don't jump to the symbol location.
735 # Result: Location | Location[] | LocationLink[] | null
736 def GotoSymbolLoc(lspserver: dict<any>, msg: string, peekSymbol: bool,
737 cmdmods: string, count: number)
738 var reply = lspserver.rpc(msg, lspserver.getTextDocPosition(true), false)
739 if reply->empty() || reply.result->empty()
741 if msg == 'textDocument/declaration'
742 emsg = 'symbol declaration is not found'
743 elseif msg == 'textDocument/typeDefinition'
744 emsg = 'symbol type definition is not found'
745 elseif msg == 'textDocument/implementation'
746 emsg = 'symbol implementation is not found'
748 emsg = 'symbol definition is not found'
755 var result = reply.result
756 var location: dict<any>
757 if result->type() == v:t_list
759 # When there are multiple symbol locations, and a specific one isn't
760 # requested with 'count', display the locations in a location list.
762 var title: string = ''
763 if msg == 'textDocument/declaration'
764 title = 'Declarations'
765 elseif msg == 'textDocument/typeDefinition'
766 title = 'Type Definitions'
767 elseif msg == 'textDocument/implementation'
768 title = 'Implementations'
770 title = 'Definitions'
773 if lspserver.needOffsetEncoding
774 # Decode the position encoding in all the symbol locations
775 result->map((_, loc) => {
776 lspserver.decodeLocation(loc)
781 symbol.ShowLocations(lspserver, result, peekSymbol, title)
786 # Select the location requested in 'count'
788 if idx >= result->len()
789 idx = result->len() - 1
791 location = result[idx]
795 lspserver.decodeLocation(location)
797 symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods)
800 # Request: "textDocument/definition"
801 # Param: DefinitionParams
802 def GotoDefinition(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
803 # Check whether LSP server supports jumping to a definition
804 if !lspserver.isDefinitionProvider
805 util.ErrMsg('Jumping to a symbol definition is not supported')
809 # interface DefinitionParams
810 # interface TextDocumentPositionParams
811 GotoSymbolLoc(lspserver, 'textDocument/definition', peek, cmdmods, count)
814 # Request: "textDocument/declaration"
815 # Param: DeclarationParams
816 def GotoDeclaration(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
817 # Check whether LSP server supports jumping to a declaration
818 if !lspserver.isDeclarationProvider
819 util.ErrMsg('Jumping to a symbol declaration is not supported')
823 # interface DeclarationParams
824 # interface TextDocumentPositionParams
825 GotoSymbolLoc(lspserver, 'textDocument/declaration', peek, cmdmods, count)
828 # Request: "textDocument/typeDefinition"
829 # Param: TypeDefinitionParams
830 def GotoTypeDef(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
831 # Check whether LSP server supports jumping to a type definition
832 if !lspserver.isTypeDefinitionProvider
833 util.ErrMsg('Jumping to a symbol type definition is not supported')
837 # interface TypeDefinitionParams
838 # interface TextDocumentPositionParams
839 GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek, cmdmods, count)
842 # Request: "textDocument/implementation"
843 # Param: ImplementationParams
844 def GotoImplementation(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
845 # Check whether LSP server supports jumping to a implementation
846 if !lspserver.isImplementationProvider
847 util.ErrMsg('Jumping to a symbol implementation is not supported')
851 # interface ImplementationParams
852 # interface TextDocumentPositionParams
853 GotoSymbolLoc(lspserver, 'textDocument/implementation', peek, cmdmods, count)
856 # Request: "textDocument/switchSourceHeader"
857 # Param: TextDocumentIdentifier
858 # Clangd specific extension
859 def SwitchSourceHeader(lspserver: dict<any>)
861 uri: util.LspFileToUri(@%)
863 var reply = lspserver.rpc('textDocument/switchSourceHeader', param)
864 if reply->empty() || reply.result->empty()
865 util.WarnMsg('Source/Header file is not found')
869 # process the 'textDocument/switchSourceHeader' reply from the LSP server
871 var fname = util.LspUriToFile(reply.result)
872 # TODO: Add support for cmd modifiers
873 if (&modified && !&hidden) || &buftype != ''
874 # if the current buffer has unsaved changes and 'hidden' is not set,
875 # or if the current buffer is a special buffer, then ask to save changes
876 exe $'confirm edit {fname}'
882 # get symbol signature help.
883 # Request: "textDocument/signatureHelp"
884 # Param: SignatureHelpParams
885 def ShowSignature(lspserver: dict<any>): void
886 # Check whether LSP server supports signature help
887 if !lspserver.isSignatureHelpProvider
888 util.ErrMsg('LSP server does not support signature help')
892 # interface SignatureHelpParams
893 # interface TextDocumentPositionParams
894 var params = lspserver.getTextDocPosition(false)
895 lspserver.rpc_a('textDocument/signatureHelp', params,
896 signature.SignatureHelp)
899 # Send a file/document saved notification to the language server
900 def DidSaveFile(lspserver: dict<any>, bnr: number): void
901 # Check whether the LSP server supports the didSave notification
902 if !lspserver.supportsDidSave
903 # LSP server doesn't support text document synchronization
907 # Notification: 'textDocument/didSave'
908 # Params: DidSaveTextDocumentParams
909 var params: dict<any> = {textDocument: {uri: util.LspBufnrToUri(bnr)}}
911 if lspserver.caps.textDocumentSync->type() == v:t_dict
912 && lspserver.caps.textDocumentSync->has_key('save')
913 if lspserver.caps.textDocumentSync.save->type() == v:t_dict
914 && lspserver.caps.textDocumentSync.save->has_key('includeText')
915 && lspserver.caps.textDocumentSync.save.includeText
916 params.text = bnr->getbufline(1, '$')->join("\n") .. "\n"
920 lspserver.sendNotification('textDocument/didSave', params)
923 # get the hover information
924 # Request: "textDocument/hover"
926 def ShowHoverInfo(lspserver: dict<any>, cmdmods: string): void
927 # Check whether LSP server supports getting hover information.
928 # caps->hoverProvider can be a "boolean" or "HoverOptions"
929 if !lspserver.isHoverProvider
933 # interface HoverParams
934 # interface TextDocumentPositionParams
935 var params = lspserver.getTextDocPosition(false)
936 lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
937 hover.HoverReply(lspserver, reply, cmdmods)
941 # Request: "textDocument/references"
942 # Param: ReferenceParams
943 def ShowReferences(lspserver: dict<any>, peek: bool): void
944 # Check whether LSP server supports getting reference information
945 if !lspserver.isReferencesProvider
946 util.ErrMsg('LSP server does not support showing references')
950 # interface ReferenceParams
951 # interface TextDocumentPositionParams
953 param = lspserver.getTextDocPosition(true)
954 param.context = {includeDeclaration: true}
955 var reply = lspserver.rpc('textDocument/references', param)
957 # Result: Location[] | null
958 if reply->empty() || reply.result->empty()
959 util.WarnMsg('No references found')
963 if lspserver.needOffsetEncoding
964 # Decode the position encoding in all the reference locations
965 reply.result->map((_, loc) => {
966 lspserver.decodeLocation(loc)
971 symbol.ShowLocations(lspserver, reply.result, peek, 'Symbol References')
974 # process the 'textDocument/documentHighlight' reply from the LSP server
975 # Result: DocumentHighlight[] | null
976 def DocHighlightReply(lspserver: dict<any>, docHighlightReply: any,
977 bnr: number, cmdmods: string): void
978 if docHighlightReply->empty()
979 if cmdmods !~ 'silent'
980 util.WarnMsg($'No highlight for the current position')
985 for docHL in docHighlightReply
986 lspserver.decodeRange(bnr, docHL.range)
987 var kind: number = docHL->get('kind', 1)
991 propName = 'LspReadRef'
994 propName = 'LspWriteRef'
997 propName = 'LspTextRef'
1000 var docHL_range = docHL.range
1001 var docHL_start = docHL_range.start
1002 var docHL_end = docHL_range.end
1003 prop_add(docHL_start.line + 1,
1004 util.GetLineByteFromPos(bnr, docHL_start) + 1,
1005 {end_lnum: docHL_end.line + 1,
1006 end_col: util.GetLineByteFromPos(bnr, docHL_end) + 1,
1009 catch /E966\|E964/ # Invalid lnum | Invalid col
1010 # Highlight replies arrive asynchronously and the document might have
1011 # been modified in the mean time. As the reply is stale, ignore invalid
1012 # line number and column number errors.
1017 # Request: "textDocument/documentHighlight"
1018 # Param: DocumentHighlightParams
1019 def DocHighlight(lspserver: dict<any>, bnr: number, cmdmods: string): void
1020 # Check whether LSP server supports getting highlight information
1021 if !lspserver.isDocumentHighlightProvider
1022 util.ErrMsg('LSP server does not support document highlight')
1026 # Send the pending buffer changes to the language server
1027 bnr->listener_flush()
1029 # interface DocumentHighlightParams
1030 # interface TextDocumentPositionParams
1031 var params = lspserver.getTextDocPosition(false)
1032 lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
1033 DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
1037 # Request: "textDocument/documentSymbol"
1038 # Param: DocumentSymbolParams
1039 def GetDocSymbols(lspserver: dict<any>, fname: string, showOutline: bool): void
1040 # Check whether LSP server supports getting document symbol information
1041 if !lspserver.isDocumentSymbolProvider
1042 util.ErrMsg('LSP server does not support getting list of symbols')
1046 # interface DocumentSymbolParams
1047 # interface TextDocumentIdentifier
1048 var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1049 lspserver.rpc_a('textDocument/documentSymbol', params, (_, reply) => {
1051 symbol.DocSymbolOutline(lspserver, reply, fname)
1053 symbol.DocSymbolPopup(lspserver, reply, fname)
1058 # Request: "textDocument/formatting"
1059 # Param: DocumentFormattingParams
1061 # Request: "textDocument/rangeFormatting"
1062 # Param: DocumentRangeFormattingParams
1063 def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
1064 start_lnum: number, end_lnum: number)
1065 # Check whether LSP server supports formatting documents
1066 if !lspserver.isDocumentFormattingProvider
1067 util.ErrMsg('LSP server does not support formatting documents')
1073 cmd = 'textDocument/rangeFormatting'
1075 cmd = 'textDocument/formatting'
1078 # interface DocumentFormattingParams
1079 # interface TextDocumentIdentifier
1080 # interface FormattingOptions
1081 var fmtopts: dict<any> = {
1082 tabSize: shiftwidth(),
1083 insertSpaces: &expandtab ? true : false,
1087 uri: util.LspFileToUri(fname)
1093 var r: dict<dict<number>> = {
1094 start: {line: start_lnum - 1, character: 0},
1095 end: {line: end_lnum - 1, character: charcol([end_lnum, '$']) - 1}}
1099 var reply = lspserver.rpc(cmd, param)
1101 # result: TextEdit[] | null
1103 if reply->empty() || reply.result->empty()
1108 var bnr: number = fname->bufnr()
1110 # file is already removed
1114 if lspserver.needOffsetEncoding
1115 # Decode the position encoding in all the reference locations
1116 reply.result->map((_, textEdit) => {
1117 lspserver.decodeRange(bnr, textEdit.range)
1122 # interface TextEdit
1123 # Apply each of the text edit operations
1124 var save_cursor: list<number> = getcurpos()
1125 textedit.ApplyTextEdits(bnr, reply.result)
1126 save_cursor->setpos('.')
1129 # Request: "textDocument/prepareCallHierarchy"
1130 def PrepareCallHierarchy(lspserver: dict<any>): dict<any>
1131 # interface CallHierarchyPrepareParams
1132 # interface TextDocumentPositionParams
1133 var param: dict<any>
1134 param = lspserver.getTextDocPosition(false)
1135 var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
1136 if reply->empty() || reply.result->empty()
1140 # Result: CallHierarchyItem[] | null
1141 var choice: number = 1
1142 if reply.result->len() > 1
1143 var items: list<string> = ['Select a Call Hierarchy Item:']
1144 for i in reply.result->len()->range()
1145 items->add(printf("%d. %s", i + 1, reply.result[i].name))
1147 choice = items->inputlist()
1148 if choice < 1 || choice > items->len()
1153 return reply.result[choice - 1]
1156 # Request: "callHierarchy/incomingCalls"
1157 def IncomingCalls(lspserver: dict<any>, fname: string)
1158 # Check whether LSP server supports call hierarchy
1159 if !lspserver.isCallHierarchyProvider
1160 util.ErrMsg('LSP server does not support call hierarchy')
1164 callhier.IncomingCalls(lspserver)
1167 def GetIncomingCalls(lspserver: dict<any>, item_arg: dict<any>): any
1168 # Request: "callHierarchy/incomingCalls"
1169 # Param: CallHierarchyIncomingCallsParams
1173 var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
1178 if lspserver.needOffsetEncoding
1179 # Decode the position encoding in all the incoming call locations
1180 var bnr = util.LspUriToBufnr(item_arg.uri)
1181 reply.result->map((_, hierItem) => {
1182 lspserver.decodeRange(bnr, hierItem.from.range)
1190 # Request: "callHierarchy/outgoingCalls"
1191 def OutgoingCalls(lspserver: dict<any>, fname: string)
1192 # Check whether LSP server supports call hierarchy
1193 if !lspserver.isCallHierarchyProvider
1194 util.ErrMsg('LSP server does not support call hierarchy')
1198 callhier.OutgoingCalls(lspserver)
1201 def GetOutgoingCalls(lspserver: dict<any>, item_arg: dict<any>): any
1202 # Request: "callHierarchy/outgoingCalls"
1203 # Param: CallHierarchyOutgoingCallsParams
1207 var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
1212 if lspserver.needOffsetEncoding
1213 # Decode the position encoding in all the outgoing call locations
1214 var bnr = util.LspUriToBufnr(item_arg.uri)
1215 reply.result->map((_, hierItem) => {
1216 lspserver.decodeRange(bnr, hierItem.to.range)
1224 # Request: "textDocument/inlayHint"
1226 def InlayHintsShow(lspserver: dict<any>, bnr: number)
1227 # Check whether LSP server supports type hierarchy
1228 if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
1229 util.ErrMsg('LSP server does not support inlay hint')
1233 # Send the pending buffer changes to the language server
1234 bnr->listener_flush()
1236 var binfo = bnr->getbufinfo()
1240 var lastlnum = binfo[0].linecount
1241 var lastline = bnr->getbufline('$')
1243 if !lastline->empty() && !lastline[0]->empty()
1244 lastcol = lastline[0]->strchars()
1247 textDocument: {uri: util.LspBufnrToUri(bnr)},
1250 start: {line: 0, character: 0},
1251 end: {line: lastlnum - 1, character: lastcol - 1}
1255 lspserver.encodeRange(bnr, param.range)
1258 if lspserver.isClangdInlayHintsProvider
1259 # clangd-style inlay hints
1260 msg = 'clangd/inlayHints'
1262 msg = 'textDocument/inlayHint'
1264 var reply = lspserver.rpc_a(msg, param, (_, reply) => {
1265 inlayhints.InlayHintsReply(lspserver, bnr, reply)
1269 def DecodeTypeHierarchy(lspserver: dict<any>, isSuper: bool, typeHier: dict<any>)
1270 if !lspserver.needOffsetEncoding
1273 var bnr = util.LspUriToBufnr(typeHier.uri)
1274 lspserver.decodeRange(bnr, typeHier.range)
1275 lspserver.decodeRange(bnr, typeHier.selectionRange)
1276 var subType: list<dict<any>>
1278 subType = typeHier->get('parents', [])
1280 subType = typeHier->get('children', [])
1282 if !subType->empty()
1283 # Decode the position encoding in all the type hierarchy items
1284 subType->map((_, typeHierItem) => {
1285 DecodeTypeHierarchy(lspserver, isSuper, typeHierItem)
1291 # Request: "textDocument/typehierarchy"
1292 # Support the clangd version of type hierarchy retrieval method.
1293 # The method described in the LSP 3.17.0 standard is not supported as clangd
1294 # doesn't support that method.
1295 def TypeHierarchy(lspserver: dict<any>, direction: number)
1296 # Check whether LSP server supports type hierarchy
1297 if !lspserver.isTypeHierarchyProvider
1298 util.ErrMsg('LSP server does not support type hierarchy')
1302 # interface TypeHierarchy
1303 # interface TextDocumentPositionParams
1304 var param: dict<any>
1305 param = lspserver.getTextDocPosition(false)
1306 # 0: children, 1: parent, 2: both
1307 param.direction = direction
1309 var reply = lspserver.rpc('textDocument/typeHierarchy', param)
1310 if reply->empty() || reply.result->empty()
1311 util.WarnMsg('No type hierarchy available')
1315 var isSuper = (direction == 1)
1317 DecodeTypeHierarchy(lspserver, isSuper, reply.result)
1319 typehier.ShowTypeHierarchy(lspserver, isSuper, reply.result)
1322 # Decode the ranges in "WorkspaceEdit"
1323 def DecodeWorkspaceEdit(lspserver: dict<any>, workspaceEdit: dict<any>)
1324 if !lspserver.needOffsetEncoding
1327 if workspaceEdit->has_key('changes')
1328 for [uri, changes] in workspaceEdit.changes->items()
1329 var bnr: number = util.LspUriToBufnr(uri)
1333 # Decode the position encoding in all the text edit locations
1334 changes->map((_, textEdit) => {
1335 lspserver.decodeRange(bnr, textEdit.range)
1341 if workspaceEdit->has_key('documentChanges')
1342 for change in workspaceEdit.documentChanges
1343 if !change->has_key('kind')
1344 var bnr: number = util.LspUriToBufnr(change.textDocument.uri)
1348 # Decode the position encoding in all the text edit locations
1349 change.edits->map((_, textEdit) => {
1350 lspserver.decodeRange(bnr, textEdit.range)
1358 # Request: "textDocument/rename"
1359 # Param: RenameParams
1360 def RenameSymbol(lspserver: dict<any>, newName: string)
1361 # Check whether LSP server supports rename operation
1362 if !lspserver.isRenameProvider
1363 util.ErrMsg('LSP server does not support rename operation')
1367 # interface RenameParams
1368 # interface TextDocumentPositionParams
1369 var param: dict<any> = {}
1370 param = lspserver.getTextDocPosition(true)
1371 param.newName = newName
1373 var reply = lspserver.rpc('textDocument/rename', param)
1375 # Result: WorkspaceEdit | null
1376 if reply->empty() || reply.result->empty()
1381 # result: WorkspaceEdit
1382 DecodeWorkspaceEdit(lspserver, reply.result)
1383 textedit.ApplyWorkspaceEdit(reply.result)
1386 # Decode the range in "CodeAction"
1387 def DecodeCodeAction(lspserver: dict<any>, actionList: list<dict<any>>)
1388 if !lspserver.needOffsetEncoding
1391 actionList->map((_, act) => {
1392 if !act->has_key('disabled') && act->has_key('edit')
1393 DecodeWorkspaceEdit(lspserver, act.edit)
1399 # Request: "textDocument/codeAction"
1400 # Param: CodeActionParams
1401 def CodeAction(lspserver: dict<any>, fname_arg: string, line1: number,
1402 line2: number, query: string)
1403 # Check whether LSP server supports code action operation
1404 if !lspserver.isCodeActionProvider
1405 util.ErrMsg('LSP server does not support code action operation')
1409 # interface CodeActionParams
1410 var params: dict<any> = {}
1411 var fname: string = fname_arg->fnamemodify(':p')
1412 var bnr: number = fname_arg->bufnr()
1413 var r: dict<dict<number>> = {
1416 character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
1420 character: util.GetCharIdxWithCompChar(getline(line2), charcol([line2, '$']) - 1)
1423 lspserver.encodeRange(bnr, r)
1424 params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
1425 var d: list<dict<any>> = []
1426 for lnum in range(line1, line2)
1427 var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(bnr, lnum, lspserver)->deepcopy()
1428 if lspserver.needOffsetEncoding
1429 diagsInfo->map((_, di) => {
1430 lspserver.encodeRange(bnr, di.range)
1434 d->extend(diagsInfo)
1436 params->extend({context: {diagnostics: d, triggerKind: 1}})
1438 var reply = lspserver.rpc('textDocument/codeAction', params)
1440 # Result: (Command | CodeAction)[] | null
1441 if reply->empty() || reply.result->empty()
1442 # no action can be performed
1443 util.WarnMsg('No code action is available')
1447 DecodeCodeAction(lspserver, reply.result)
1449 codeaction.ApplyCodeAction(lspserver, reply.result, query)
1452 # Request: "textDocument/codeLens"
1453 # Param: CodeLensParams
1454 def CodeLens(lspserver: dict<any>, fname: string)
1455 # Check whether LSP server supports code lens operation
1456 if !lspserver.isCodeLensProvider
1457 util.ErrMsg('LSP server does not support code lens operation')
1461 var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1462 var reply = lspserver.rpc('textDocument/codeLens', params)
1463 if reply->empty() || reply.result->empty()
1464 util.WarnMsg($'No code lens actions found for the current file')
1468 var bnr = fname->bufnr()
1470 # Decode the position encoding in all the code lens items
1471 if lspserver.needOffsetEncoding
1472 reply.result->map((_, codeLensItem) => {
1473 lspserver.decodeRange(bnr, codeLensItem.range)
1478 codelens.ProcessCodeLens(lspserver, bnr, reply.result)
1481 # Request: "codeLens/resolve"
1483 def ResolveCodeLens(lspserver: dict<any>, bnr: number,
1484 codeLens: dict<any>): dict<any>
1485 if !lspserver.isCodeLensResolveProvider
1489 if lspserver.needOffsetEncoding
1490 lspserver.encodeRange(bnr, codeLens.range)
1493 var reply = lspserver.rpc('codeLens/resolve', codeLens)
1498 var codeLensItem: dict<any> = reply.result
1500 # Decode the position encoding in the code lens item
1501 if lspserver.needOffsetEncoding
1502 lspserver.decodeRange(bnr, codeLensItem.range)
1508 # List project-wide symbols matching query string
1509 # Request: "workspace/symbol"
1510 # Param: WorkspaceSymbolParams
1511 def WorkspaceQuerySymbols(lspserver: dict<any>, query: string, firstCall: bool, cmdmods: string = '')
1512 # Check whether the LSP server supports listing workspace symbols
1513 if !lspserver.isWorkspaceSymbolProvider
1514 util.ErrMsg('LSP server does not support listing workspace symbols')
1518 # Param: WorkspaceSymbolParams
1522 var reply = lspserver.rpc('workspace/symbol', param)
1523 if reply->empty() || reply.result->empty()
1524 util.WarnMsg($'Symbol "{query}" is not found')
1528 var symInfo: list<dict<any>> = reply.result
1530 if lspserver.needOffsetEncoding
1531 # Decode the position encoding in all the symbol locations
1532 symInfo->map((_, sym) => {
1533 if sym->has_key('location')
1534 lspserver.decodeLocation(sym.location)
1540 if firstCall && symInfo->len() == 1
1541 # If there is only one symbol, then jump to the symbol location
1542 var symLoc: dict<any> = symInfo[0]->get('location', {})
1544 symbol.GotoSymbol(lspserver, symLoc, false, cmdmods)
1547 symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods)
1551 # Add a workspace folder to the language server.
1552 def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1553 if !lspserver.caps->has_key('workspace')
1554 || !lspserver.caps.workspace->has_key('workspaceFolders')
1555 || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1556 || !lspserver.caps.workspace.workspaceFolders.supported
1557 util.ErrMsg('LSP server does not support workspace folders')
1561 if lspserver.workspaceFolders->index(dirName) != -1
1562 util.ErrMsg($'{dirName} is already part of this workspace')
1566 # Notification: 'workspace/didChangeWorkspaceFolders'
1567 # Params: DidChangeWorkspaceFoldersParams
1568 var params = {event: {added: [dirName], removed: []}}
1569 lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1571 lspserver.workspaceFolders->add(dirName)
1574 # Remove a workspace folder from the language server.
1575 def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
1576 if !lspserver.caps->has_key('workspace')
1577 || !lspserver.caps.workspace->has_key('workspaceFolders')
1578 || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
1579 || !lspserver.caps.workspace.workspaceFolders.supported
1580 util.ErrMsg('LSP server does not support workspace folders')
1584 var idx: number = lspserver.workspaceFolders->index(dirName)
1586 util.ErrMsg($'{dirName} is not currently part of this workspace')
1590 # Notification: "workspace/didChangeWorkspaceFolders"
1591 # Param: DidChangeWorkspaceFoldersParams
1592 var params = {event: {added: [], removed: [dirName]}}
1593 lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
1595 lspserver.workspaceFolders->remove(idx)
1598 def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
1599 lspserver.decodeRange(bnr, selRange.range)
1600 if selRange->has_key('parent')
1601 DecodeSelectionRange(lspserver, bnr, selRange.parent)
1605 # select the text around the current cursor location
1606 # Request: "textDocument/selectionRange"
1607 # Param: SelectionRangeParams
1608 def SelectionRange(lspserver: dict<any>, fname: string)
1609 # Check whether LSP server supports selection ranges
1610 if !lspserver.isSelectionRangeProvider
1611 util.ErrMsg('LSP server does not support selection ranges')
1615 # clear the previous selection reply
1616 lspserver.selection = {}
1618 # interface SelectionRangeParams
1619 # interface TextDocumentIdentifier
1622 uri: util.LspFileToUri(fname)
1624 positions: [lspserver.getPosition(false)]
1626 var reply = lspserver.rpc('textDocument/selectionRange', param)
1628 if reply->empty() || reply.result->empty()
1632 # Decode the position encoding in all the selection range items
1633 if lspserver.needOffsetEncoding
1634 var bnr = fname->bufnr()
1635 reply.result->map((_, selItem) => {
1636 DecodeSelectionRange(lspserver, bnr, selItem)
1641 selection.SelectionStart(lspserver, reply.result)
1644 # Expand the previous selection or start a new one
1645 def SelectionExpand(lspserver: dict<any>)
1646 # Check whether LSP server supports selection ranges
1647 if !lspserver.isSelectionRangeProvider
1648 util.ErrMsg('LSP server does not support selection ranges')
1652 selection.SelectionModify(lspserver, true)
1655 # Shrink the previous selection or start a new one
1656 def SelectionShrink(lspserver: dict<any>)
1657 # Check whether LSP server supports selection ranges
1658 if !lspserver.isSelectionRangeProvider
1659 util.ErrMsg('LSP server does not support selection ranges')
1663 selection.SelectionModify(lspserver, false)
1666 # fold the entire document
1667 # Request: "textDocument/foldingRange"
1668 # Param: FoldingRangeParams
1669 def FoldRange(lspserver: dict<any>, fname: string)
1670 # Check whether LSP server supports fold ranges
1671 if !lspserver.isFoldingRangeProvider
1672 util.ErrMsg('LSP server does not support folding')
1676 # interface FoldingRangeParams
1677 # interface TextDocumentIdentifier
1678 var params = {textDocument: {uri: util.LspFileToUri(fname)}}
1679 var reply = lspserver.rpc('textDocument/foldingRange', params)
1680 if reply->empty() || reply.result->empty()
1684 # result: FoldingRange[]
1685 var end_lnum: number
1686 var last_lnum: number = line('$')
1687 for foldRange in reply.result
1688 end_lnum = foldRange.endLine + 1
1689 if end_lnum < foldRange.startLine + 2
1690 end_lnum = foldRange.startLine + 2
1692 exe $':{foldRange.startLine + 2}, {end_lnum}fold'
1693 # Open all the folds, otherwise the subsequently created folds are not
1699 :setlocal foldcolumn=2
1703 # process the 'workspace/executeCommand' reply from the LSP server
1704 # Result: any | null
1705 def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
1706 # Nothing to do for the reply
1709 # Request the LSP server to execute a command
1710 # Request: workspace/executeCommand
1711 # Params: ExecuteCommandParams
1712 def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
1713 # Need to check for lspserver.caps.executeCommandProvider?
1715 lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
1718 # Display the LSP server capabilities (received during the initialization
1720 def GetCapabilities(lspserver: dict<any>): list<string>
1722 var heading = $"'{lspserver.path}' Language Server Capabilities"
1723 var underlines = repeat('=', heading->len())
1724 l->extend([heading, underlines])
1725 for k in lspserver.caps->keys()->sort()
1726 l->add($'{k}: {lspserver.caps[k]->string()}')
1731 # Display the LSP server initialize request and result
1732 def GetInitializeRequest(lspserver: dict<any>): list<string>
1734 var heading = $"'{lspserver.path}' Language Server Initialize Request"
1735 var underlines = repeat('=', heading->len())
1736 l->extend([heading, underlines])
1737 if lspserver->has_key('rpcInitializeRequest')
1738 for k in lspserver.rpcInitializeRequest->keys()->sort()
1739 l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
1745 # Store a log or trace message received from the language server.
1746 def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
1747 # A single message may contain multiple lines separate by newline
1748 var msgs = newMsg->split("\n")
1749 lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
1750 lspserver.messages->extend(msgs[1 : ])
1751 # Keep only the last 500 messages to reduce the memory usage
1752 if lspserver.messages->len() >= 600
1753 lspserver.messages = lspserver.messages[-500 : ]
1757 # Display the log messages received from the LSP server (window/logMessage)
1758 def GetMessages(lspserver: dict<any>): list<string>
1759 if lspserver.messages->empty()
1760 return [$'No messages received from "{lspserver.name}" server']
1764 var heading = $"'{lspserver.path}' Language Server Messages"
1765 var underlines = repeat('=', heading->len())
1766 l->extend([heading, underlines])
1767 l->extend(lspserver.messages)
1771 # Send a 'textDocument/definition' request to the LSP server to get the
1772 # location where the symbol under the cursor is defined and return a list of
1773 # Dicts in a format accepted by the 'tagfunc' option.
1774 # Returns null if the LSP server doesn't support getting the location of a
1775 # symbol definition or the symbol is not defined.
1776 def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
1777 # Check whether LSP server supports getting the location of a definition
1778 if !lspserver.isDefinitionProvider
1782 # interface DefinitionParams
1783 # interface TextDocumentPositionParams
1784 var reply = lspserver.rpc('textDocument/definition',
1785 lspserver.getTextDocPosition(false))
1786 if reply->empty() || reply.result->empty()
1790 var taglocations: list<dict<any>>
1791 if reply.result->type() == v:t_list
1792 taglocations = reply.result
1794 taglocations = [reply.result]
1797 if lspserver.needOffsetEncoding
1798 # Decode the position encoding in all the reference locations
1799 taglocations->map((_, loc) => {
1800 lspserver.decodeLocation(loc)
1805 return symbol.TagFunc(lspserver, taglocations, pat)
1808 # Returns unique ID used for identifying the various servers
1809 var UniqueServerIdCounter = 0
1810 def GetUniqueServerId(): number
1811 UniqueServerIdCounter = UniqueServerIdCounter + 1
1812 return UniqueServerIdCounter
1815 export def NewLspServer(serverParams: dict<any>): dict<any>
1816 var lspserver: dict<any> = {
1817 id: GetUniqueServerId(),
1818 name: serverParams.name,
1819 path: serverParams.path,
1820 args: serverParams.args->deepcopy(),
1828 callHierarchyType: '',
1829 completionTriggerChars: [],
1830 customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
1831 customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
1832 debug: serverParams.debug,
1833 features: serverParams.features->deepcopy(),
1834 forceOffsetEncoding: serverParams.forceOffsetEncoding,
1835 initializationOptions: serverParams.initializationOptions->deepcopy(),
1837 needOffsetEncoding: false,
1838 omniCompletePending: false,
1839 peekSymbolFilePopup: -1,
1840 peekSymbolPopup: -1,
1841 processDiagHandler: serverParams.processDiagHandler,
1842 rootSearchFiles: serverParams.rootSearch->deepcopy(),
1843 runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
1844 runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
1847 syncInit: serverParams.syncInit,
1848 traceLevel: serverParams.traceLevel,
1849 typeHierFilePopup: -1,
1851 workspaceConfig: serverParams.workspaceConfig->deepcopy(),
1852 workspaceSymbolPopup: -1,
1853 workspaceSymbolQuery: ''
1855 lspserver.logfile = $'lsp-{lspserver.name}.log'
1856 lspserver.errfile = $'lsp-{lspserver.name}.err'
1858 # Add the LSP server functions
1860 startServer: function(StartServer, [lspserver]),
1861 initServer: function(InitServer, [lspserver]),
1862 stopServer: function(StopServer, [lspserver]),
1863 shutdownServer: function(ShutdownServer, [lspserver]),
1864 exitServer: function(ExitServer, [lspserver]),
1865 setTrace: function(SetTrace, [lspserver]),
1866 traceLog: function(TraceLog, [lspserver]),
1867 errorLog: function(ErrorLog, [lspserver]),
1868 nextReqID: function(NextReqID, [lspserver]),
1869 createRequest: function(CreateRequest, [lspserver]),
1870 createResponse: function(CreateResponse, [lspserver]),
1871 sendResponse: function(SendResponse, [lspserver]),
1872 sendMessage: function(SendMessage, [lspserver]),
1873 sendNotification: function(SendNotification, [lspserver]),
1874 rpc: function(Rpc, [lspserver]),
1875 rpc_a: function(AsyncRpc, [lspserver]),
1876 waitForResponse: function(WaitForResponse, [lspserver]),
1877 processReply: function(handlers.ProcessReply, [lspserver]),
1878 processNotif: function(handlers.ProcessNotif, [lspserver]),
1879 processRequest: function(handlers.ProcessRequest, [lspserver]),
1880 processMessages: function(handlers.ProcessMessages, [lspserver]),
1881 encodePosition: function(offset.EncodePosition, [lspserver]),
1882 decodePosition: function(offset.DecodePosition, [lspserver]),
1883 encodeRange: function(offset.EncodeRange, [lspserver]),
1884 decodeRange: function(offset.DecodeRange, [lspserver]),
1885 encodeLocation: function(offset.EncodeLocation, [lspserver]),
1886 decodeLocation: function(offset.DecodeLocation, [lspserver]),
1887 getPosition: function(GetPosition, [lspserver]),
1888 getTextDocPosition: function(GetTextDocPosition, [lspserver]),
1889 featureEnabled: function(FeatureEnabled, [lspserver]),
1890 textdocDidOpen: function(TextdocDidOpen, [lspserver]),
1891 textdocDidClose: function(TextdocDidClose, [lspserver]),
1892 textdocDidChange: function(TextdocDidChange, [lspserver]),
1893 sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
1894 sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
1895 getCompletion: function(GetCompletion, [lspserver]),
1896 resolveCompletion: function(ResolveCompletion, [lspserver]),
1897 gotoDefinition: function(GotoDefinition, [lspserver]),
1898 gotoDeclaration: function(GotoDeclaration, [lspserver]),
1899 gotoTypeDef: function(GotoTypeDef, [lspserver]),
1900 gotoImplementation: function(GotoImplementation, [lspserver]),
1901 tagFunc: function(TagFunc, [lspserver]),
1902 switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
1903 showSignature: function(ShowSignature, [lspserver]),
1904 didSaveFile: function(DidSaveFile, [lspserver]),
1905 hover: function(ShowHoverInfo, [lspserver]),
1906 showReferences: function(ShowReferences, [lspserver]),
1907 docHighlight: function(DocHighlight, [lspserver]),
1908 getDocSymbols: function(GetDocSymbols, [lspserver]),
1909 textDocFormat: function(TextDocFormat, [lspserver]),
1910 prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
1911 incomingCalls: function(IncomingCalls, [lspserver]),
1912 getIncomingCalls: function(GetIncomingCalls, [lspserver]),
1913 outgoingCalls: function(OutgoingCalls, [lspserver]),
1914 getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
1915 inlayHintsShow: function(InlayHintsShow, [lspserver]),
1916 typeHierarchy: function(TypeHierarchy, [lspserver]),
1917 renameSymbol: function(RenameSymbol, [lspserver]),
1918 codeAction: function(CodeAction, [lspserver]),
1919 codeLens: function(CodeLens, [lspserver]),
1920 resolveCodeLens: function(ResolveCodeLens, [lspserver]),
1921 workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
1922 addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
1923 removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
1924 selectionRange: function(SelectionRange, [lspserver]),
1925 selectionExpand: function(SelectionExpand, [lspserver]),
1926 selectionShrink: function(SelectionShrink, [lspserver]),
1927 foldRange: function(FoldRange, [lspserver]),
1928 executeCommand: function(ExecuteCommand, [lspserver]),
1929 workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
1930 getCapabilities: function(GetCapabilities, [lspserver]),
1931 getInitializeRequest: function(GetInitializeRequest, [lspserver]),
1932 addMessage: function(AddMessage, [lspserver]),
1933 getMessages: function(GetMessages, [lspserver])
1939 # vim: tabstop=8 shiftwidth=2 softtabstop=2