"use strict"; /* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ProposedFeatures = exports.BaseLanguageClient = exports.MessageTransports = exports.SuspendMode = exports.State = exports.CloseAction = exports.ErrorAction = exports.RevealOutputChannelOn = void 0; const vscode_1 = require("vscode"); const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const c2p = require("./codeConverter"); const p2c = require("./protocolConverter"); const Is = require("./utils/is"); const async_1 = require("./utils/async"); const UUID = require("./utils/uuid"); const progressPart_1 = require("./progressPart"); const features_1 = require("./features"); const diagnostic_1 = require("./diagnostic"); const notebook_1 = require("./notebook"); const configuration_1 = require("./configuration"); const textSynchronization_1 = require("./textSynchronization"); const completion_1 = require("./completion"); const hover_1 = require("./hover"); const definition_1 = require("./definition"); const signatureHelp_1 = require("./signatureHelp"); const documentHighlight_1 = require("./documentHighlight"); const documentSymbol_1 = require("./documentSymbol"); const workspaceSymbol_1 = require("./workspaceSymbol"); const reference_1 = require("./reference"); const codeAction_1 = require("./codeAction"); const codeLens_1 = require("./codeLens"); const formatting_1 = require("./formatting"); const rename_1 = require("./rename"); const documentLink_1 = require("./documentLink"); const executeCommand_1 = require("./executeCommand"); const fileSystemWatcher_1 = require("./fileSystemWatcher"); const colorProvider_1 = require("./colorProvider"); const implementation_1 = require("./implementation"); const typeDefinition_1 = require("./typeDefinition"); const workspaceFolder_1 = require("./workspaceFolder"); const foldingRange_1 = require("./foldingRange"); const declaration_1 = require("./declaration"); const selectionRange_1 = require("./selectionRange"); const progress_1 = require("./progress"); const callHierarchy_1 = require("./callHierarchy"); const semanticTokens_1 = require("./semanticTokens"); const fileOperations_1 = require("./fileOperations"); const linkedEditingRange_1 = require("./linkedEditingRange"); const typeHierarchy_1 = require("./typeHierarchy"); const inlineValue_1 = require("./inlineValue"); const inlayHint_1 = require("./inlayHint"); const inlineCompletion_1 = require("./inlineCompletion"); /** * Controls when the output channel is revealed. */ var RevealOutputChannelOn; (function (RevealOutputChannelOn) { RevealOutputChannelOn[RevealOutputChannelOn["Debug"] = 0] = "Debug"; RevealOutputChannelOn[RevealOutputChannelOn["Info"] = 1] = "Info"; RevealOutputChannelOn[RevealOutputChannelOn["Warn"] = 2] = "Warn"; RevealOutputChannelOn[RevealOutputChannelOn["Error"] = 3] = "Error"; RevealOutputChannelOn[RevealOutputChannelOn["Never"] = 4] = "Never"; })(RevealOutputChannelOn || (exports.RevealOutputChannelOn = RevealOutputChannelOn = {})); /** * An action to be performed when the connection is producing errors. */ var ErrorAction; (function (ErrorAction) { /** * Continue running the server. */ ErrorAction[ErrorAction["Continue"] = 1] = "Continue"; /** * Shutdown the server. */ ErrorAction[ErrorAction["Shutdown"] = 2] = "Shutdown"; })(ErrorAction || (exports.ErrorAction = ErrorAction = {})); /** * An action to be performed when the connection to a server got closed. */ var CloseAction; (function (CloseAction) { /** * Don't restart the server. The connection stays closed. */ CloseAction[CloseAction["DoNotRestart"] = 1] = "DoNotRestart"; /** * Restart the server. */ CloseAction[CloseAction["Restart"] = 2] = "Restart"; })(CloseAction || (exports.CloseAction = CloseAction = {})); /** * Signals in which state the language client is in. */ var State; (function (State) { /** * The client is stopped or got never started. */ State[State["Stopped"] = 1] = "Stopped"; /** * The client is starting but not ready yet. */ State[State["Starting"] = 3] = "Starting"; /** * The client is running and ready. */ State[State["Running"] = 2] = "Running"; })(State || (exports.State = State = {})); var SuspendMode; (function (SuspendMode) { /** * Don't allow suspend mode. */ SuspendMode["off"] = "off"; /** * Support suspend mode even if not all * registered providers have a corresponding * activation listener. */ SuspendMode["on"] = "on"; })(SuspendMode || (exports.SuspendMode = SuspendMode = {})); var ResolvedClientOptions; (function (ResolvedClientOptions) { function sanitizeIsTrusted(isTrusted) { if (isTrusted === undefined || isTrusted === null) { return false; } if ((typeof isTrusted === 'boolean') || (typeof isTrusted === 'object' && isTrusted !== null && Is.stringArray(isTrusted.enabledCommands))) { return isTrusted; } return false; } ResolvedClientOptions.sanitizeIsTrusted = sanitizeIsTrusted; })(ResolvedClientOptions || (ResolvedClientOptions = {})); class DefaultErrorHandler { constructor(client, maxRestartCount) { this.client = client; this.maxRestartCount = maxRestartCount; this.restarts = []; } error(_error, _message, count) { if (count && count <= 3) { return { action: ErrorAction.Continue }; } return { action: ErrorAction.Shutdown }; } closed() { this.restarts.push(Date.now()); if (this.restarts.length <= this.maxRestartCount) { return { action: CloseAction.Restart }; } else { let diff = this.restarts[this.restarts.length - 1] - this.restarts[0]; if (diff <= 3 * 60 * 1000) { return { action: CloseAction.DoNotRestart, message: `The ${this.client.name} server crashed ${this.maxRestartCount + 1} times in the last 3 minutes. The server will not be restarted. See the output for more information.` }; } else { this.restarts.shift(); return { action: CloseAction.Restart }; } } } } var ClientState; (function (ClientState) { ClientState["Initial"] = "initial"; ClientState["Starting"] = "starting"; ClientState["StartFailed"] = "startFailed"; ClientState["Running"] = "running"; ClientState["Stopping"] = "stopping"; ClientState["Stopped"] = "stopped"; })(ClientState || (ClientState = {})); var MessageTransports; (function (MessageTransports) { function is(value) { let candidate = value; return candidate && vscode_languageserver_protocol_1.MessageReader.is(value.reader) && vscode_languageserver_protocol_1.MessageWriter.is(value.writer); } MessageTransports.is = is; })(MessageTransports || (exports.MessageTransports = MessageTransports = {})); class BaseLanguageClient { constructor(id, name, clientOptions) { this._traceFormat = vscode_languageserver_protocol_1.TraceFormat.Text; this._diagnosticQueue = new Map(); this._diagnosticQueueState = { state: 'idle' }; this._features = []; this._dynamicFeatures = new Map(); this.workspaceEditLock = new async_1.Semaphore(1); this._id = id; this._name = name; clientOptions = clientOptions || {}; const markdown = { isTrusted: false, supportHtml: false }; if (clientOptions.markdown !== undefined) { markdown.isTrusted = ResolvedClientOptions.sanitizeIsTrusted(clientOptions.markdown.isTrusted); markdown.supportHtml = clientOptions.markdown.supportHtml === true; } // const defaultInterval = (clientOptions as TestOptions).$testMode ? 50 : 60000; this._clientOptions = { documentSelector: clientOptions.documentSelector ?? [], synchronize: clientOptions.synchronize ?? {}, diagnosticCollectionName: clientOptions.diagnosticCollectionName, outputChannelName: clientOptions.outputChannelName ?? this._name, revealOutputChannelOn: clientOptions.revealOutputChannelOn ?? RevealOutputChannelOn.Error, stdioEncoding: clientOptions.stdioEncoding ?? 'utf8', initializationOptions: clientOptions.initializationOptions, initializationFailedHandler: clientOptions.initializationFailedHandler, progressOnInitialization: !!clientOptions.progressOnInitialization, errorHandler: clientOptions.errorHandler ?? this.createDefaultErrorHandler(clientOptions.connectionOptions?.maxRestartCount), middleware: clientOptions.middleware ?? {}, uriConverters: clientOptions.uriConverters, workspaceFolder: clientOptions.workspaceFolder, connectionOptions: clientOptions.connectionOptions, markdown, // suspend: { // mode: clientOptions.suspend?.mode ?? SuspendMode.off, // callback: clientOptions.suspend?.callback ?? (() => Promise.resolve(true)), // interval: clientOptions.suspend?.interval ? Math.max(clientOptions.suspend.interval, defaultInterval) : defaultInterval // }, diagnosticPullOptions: clientOptions.diagnosticPullOptions ?? { onChange: true, onSave: false }, notebookDocumentOptions: clientOptions.notebookDocumentOptions ?? {} }; this._clientOptions.synchronize = this._clientOptions.synchronize || {}; this._state = ClientState.Initial; this._ignoredRegistrations = new Set(); this._listeners = []; this._notificationHandlers = new Map(); this._pendingNotificationHandlers = new Map(); this._notificationDisposables = new Map(); this._requestHandlers = new Map(); this._pendingRequestHandlers = new Map(); this._requestDisposables = new Map(); this._progressHandlers = new Map(); this._pendingProgressHandlers = new Map(); this._progressDisposables = new Map(); this._connection = undefined; // this._idleStart = undefined; this._initializeResult = undefined; if (clientOptions.outputChannel) { this._outputChannel = clientOptions.outputChannel; this._disposeOutputChannel = false; } else { this._outputChannel = undefined; this._disposeOutputChannel = true; } this._traceOutputChannel = clientOptions.traceOutputChannel; this._diagnostics = undefined; this._pendingOpenNotifications = new Set(); this._pendingChangeSemaphore = new async_1.Semaphore(1); this._pendingChangeDelayer = new async_1.Delayer(250); this._fileEvents = []; this._fileEventDelayer = new async_1.Delayer(250); this._onStop = undefined; this._telemetryEmitter = new vscode_languageserver_protocol_1.Emitter(); this._stateChangeEmitter = new vscode_languageserver_protocol_1.Emitter(); this._trace = vscode_languageserver_protocol_1.Trace.Off; this._tracer = { log: (messageOrDataObject, data) => { if (Is.string(messageOrDataObject)) { this.logTrace(messageOrDataObject, data); } else { this.logObjectTrace(messageOrDataObject); } }, }; this._c2p = c2p.createConverter(clientOptions.uriConverters ? clientOptions.uriConverters.code2Protocol : undefined); this._p2c = p2c.createConverter(clientOptions.uriConverters ? clientOptions.uriConverters.protocol2Code : undefined, this._clientOptions.markdown.isTrusted, this._clientOptions.markdown.supportHtml); this._syncedDocuments = new Map(); this.registerBuiltinFeatures(); } get name() { return this._name; } get middleware() { return this._clientOptions.middleware ?? Object.create(null); } get clientOptions() { return this._clientOptions; } get protocol2CodeConverter() { return this._p2c; } get code2ProtocolConverter() { return this._c2p; } get onTelemetry() { return this._telemetryEmitter.event; } get onDidChangeState() { return this._stateChangeEmitter.event; } get outputChannel() { if (!this._outputChannel) { this._outputChannel = vscode_1.window.createOutputChannel(this._clientOptions.outputChannelName ? this._clientOptions.outputChannelName : this._name); } return this._outputChannel; } get traceOutputChannel() { if (this._traceOutputChannel) { return this._traceOutputChannel; } return this.outputChannel; } get diagnostics() { return this._diagnostics; } get state() { return this.getPublicState(); } get $state() { return this._state; } set $state(value) { let oldState = this.getPublicState(); this._state = value; let newState = this.getPublicState(); if (newState !== oldState) { this._stateChangeEmitter.fire({ oldState, newState }); } } getPublicState() { switch (this.$state) { case ClientState.Starting: return State.Starting; case ClientState.Running: return State.Running; default: return State.Stopped; } } get initializeResult() { return this._initializeResult; } async sendRequest(type, ...params) { if (this.$state === ClientState.StartFailed || this.$state === ClientState.Stopping || this.$state === ClientState.Stopped) { return Promise.reject(new vscode_languageserver_protocol_1.ResponseError(vscode_languageserver_protocol_1.ErrorCodes.ConnectionInactive, `Client is not running`)); } // Ensure we have a connection before we force the document sync. const connection = await this.$start(); // If any document is synced in full mode make sure we flush any pending // full document syncs. if (this._didChangeTextDocumentFeature.syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full) { await this.sendPendingFullTextDocumentChanges(connection); } const _sendRequest = this._clientOptions.middleware?.sendRequest; if (_sendRequest !== undefined) { let param = undefined; let token = undefined; // Separate cancellation tokens from other parameters for a better client interface if (params.length === 1) { // CancellationToken is an interface, so we need to check if the first param complies to it if (vscode_languageserver_protocol_1.CancellationToken.is(params[0])) { token = params[0]; } else { param = params[0]; } } else if (params.length === 2) { param = params[0]; token = params[1]; } // Return the general middleware invocation defining `next` as a utility function that reorganizes parameters to // pass them to the original sendRequest function. return _sendRequest(type, param, token, (type, param, token) => { const params = []; // Add the parameters if there are any if (param !== undefined) { params.push(param); } // Add the cancellation token if there is one if (token !== undefined) { params.push(token); } return connection.sendRequest(type, ...params); }); } else { return connection.sendRequest(type, ...params); } } onRequest(type, handler) { const method = typeof type === 'string' ? type : type.method; this._requestHandlers.set(method, handler); const connection = this.activeConnection(); let disposable; if (connection !== undefined) { this._requestDisposables.set(method, connection.onRequest(type, handler)); disposable = { dispose: () => { const disposable = this._requestDisposables.get(method); if (disposable !== undefined) { disposable.dispose(); this._requestDisposables.delete(method); } } }; } else { this._pendingRequestHandlers.set(method, handler); disposable = { dispose: () => { this._pendingRequestHandlers.delete(method); const disposable = this._requestDisposables.get(method); if (disposable !== undefined) { disposable.dispose(); this._requestDisposables.delete(method); } } }; } return { dispose: () => { this._requestHandlers.delete(method); disposable.dispose(); } }; } async sendNotification(type, params) { if (this.$state === ClientState.StartFailed || this.$state === ClientState.Stopping || this.$state === ClientState.Stopped) { return Promise.reject(new vscode_languageserver_protocol_1.ResponseError(vscode_languageserver_protocol_1.ErrorCodes.ConnectionInactive, `Client is not running`)); } const needsPendingFullTextDocumentSync = this._didChangeTextDocumentFeature.syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full; let openNotification; if (needsPendingFullTextDocumentSync && typeof type !== 'string' && type.method === vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.method) { openNotification = params?.textDocument.uri; this._pendingOpenNotifications.add(openNotification); } // Ensure we have a connection before we force the document sync. const connection = await this.$start(); // If any document is synced in full mode make sure we flush any pending // full document syncs. if (needsPendingFullTextDocumentSync) { await this.sendPendingFullTextDocumentChanges(connection); } // We need to remove the pending open notification before we actually // send the notification over the connection. Otherwise there could be // a request coming in that although the open notification got already put // onto the wire will ignore pending document changes. // // Since the code path of connection.sendNotification is actually sync // until the message is handed of to the writer and the writer as a semaphore // lock with a capacity of 1 no additional async scheduling can happen until // the message is actually handed of. if (openNotification !== undefined) { this._pendingOpenNotifications.delete(openNotification); } const _sendNotification = this._clientOptions.middleware?.sendNotification; return _sendNotification ? _sendNotification(type, connection.sendNotification.bind(connection), params) : connection.sendNotification(type, params); } onNotification(type, handler) { const method = typeof type === 'string' ? type : type.method; this._notificationHandlers.set(method, handler); const connection = this.activeConnection(); let disposable; if (connection !== undefined) { this._notificationDisposables.set(method, connection.onNotification(type, handler)); disposable = { dispose: () => { const disposable = this._notificationDisposables.get(method); if (disposable !== undefined) { disposable.dispose(); this._notificationDisposables.delete(method); } } }; } else { this._pendingNotificationHandlers.set(method, handler); disposable = { dispose: () => { this._pendingNotificationHandlers.delete(method); const disposable = this._notificationDisposables.get(method); if (disposable !== undefined) { disposable.dispose(); this._notificationDisposables.delete(method); } } }; } return { dispose: () => { this._notificationHandlers.delete(method); disposable.dispose(); } }; } async sendProgress(type, token, value) { if (this.$state === ClientState.StartFailed || this.$state === ClientState.Stopping || this.$state === ClientState.Stopped) { return Promise.reject(new vscode_languageserver_protocol_1.ResponseError(vscode_languageserver_protocol_1.ErrorCodes.ConnectionInactive, `Client is not running`)); } try { // Ensure we have a connection before we force the document sync. const connection = await this.$start(); return connection.sendProgress(type, token, value); } catch (error) { this.error(`Sending progress for token ${token} failed.`, error); throw error; } } onProgress(type, token, handler) { this._progressHandlers.set(token, { type, handler }); const connection = this.activeConnection(); let disposable; const handleWorkDoneProgress = this._clientOptions.middleware?.handleWorkDoneProgress; const realHandler = vscode_languageserver_protocol_1.WorkDoneProgress.is(type) && handleWorkDoneProgress !== undefined ? (params) => { handleWorkDoneProgress(token, params, () => handler(params)); } : handler; if (connection !== undefined) { this._progressDisposables.set(token, connection.onProgress(type, token, realHandler)); disposable = { dispose: () => { const disposable = this._progressDisposables.get(token); if (disposable !== undefined) { disposable.dispose(); this._progressDisposables.delete(token); } } }; } else { this._pendingProgressHandlers.set(token, { type, handler }); disposable = { dispose: () => { this._pendingProgressHandlers.delete(token); const disposable = this._progressDisposables.get(token); if (disposable !== undefined) { disposable.dispose(); this._progressDisposables.delete(token); } } }; } return { dispose: () => { this._progressHandlers.delete(token); disposable.dispose(); } }; } createDefaultErrorHandler(maxRestartCount) { if (maxRestartCount !== undefined && maxRestartCount < 0) { throw new Error(`Invalid maxRestartCount: ${maxRestartCount}`); } return new DefaultErrorHandler(this, maxRestartCount ?? 4); } async setTrace(value) { this._trace = value; const connection = this.activeConnection(); if (connection !== undefined) { await connection.trace(this._trace, this._tracer, { sendNotification: false, traceFormat: this._traceFormat }); } } data2String(data) { if (data instanceof vscode_languageserver_protocol_1.ResponseError) { const responseError = data; return ` Message: ${responseError.message}\n Code: ${responseError.code} ${responseError.data ? '\n' + responseError.data.toString() : ''}`; } if (data instanceof Error) { if (Is.string(data.stack)) { return data.stack; } return data.message; } if (Is.string(data)) { return data; } return data.toString(); } debug(message, data, showNotification = true) { this.logOutputMessage(vscode_languageserver_protocol_1.MessageType.Debug, RevealOutputChannelOn.Debug, 'Debug', message, data, showNotification); } info(message, data, showNotification = true) { this.logOutputMessage(vscode_languageserver_protocol_1.MessageType.Info, RevealOutputChannelOn.Info, 'Info', message, data, showNotification); } warn(message, data, showNotification = true) { this.logOutputMessage(vscode_languageserver_protocol_1.MessageType.Warning, RevealOutputChannelOn.Warn, 'Warn', message, data, showNotification); } error(message, data, showNotification = true) { this.logOutputMessage(vscode_languageserver_protocol_1.MessageType.Error, RevealOutputChannelOn.Error, 'Error', message, data, showNotification); } logOutputMessage(type, reveal, name, message, data, showNotification) { this.outputChannel.appendLine(`[${name.padEnd(5)} - ${(new Date().toLocaleTimeString())}] ${message}`); if (data !== null && data !== undefined) { this.outputChannel.appendLine(this.data2String(data)); } if (showNotification === 'force' || (showNotification && this._clientOptions.revealOutputChannelOn <= reveal)) { this.showNotificationMessage(type, message); } } showNotificationMessage(type, message) { message = message ?? 'A request has failed. See the output for more information.'; const messageFunc = type === vscode_languageserver_protocol_1.MessageType.Error ? vscode_1.window.showErrorMessage : type === vscode_languageserver_protocol_1.MessageType.Warning ? vscode_1.window.showWarningMessage : vscode_1.window.showInformationMessage; void messageFunc(message, 'Go to output').then((selection) => { if (selection !== undefined) { this.outputChannel.show(true); } }); } logTrace(message, data) { this.traceOutputChannel.appendLine(`[Trace - ${(new Date().toLocaleTimeString())}] ${message}`); if (data) { this.traceOutputChannel.appendLine(this.data2String(data)); } } logObjectTrace(data) { if (data.isLSPMessage && data.type) { this.traceOutputChannel.append(`[LSP - ${(new Date().toLocaleTimeString())}] `); } else { this.traceOutputChannel.append(`[Trace - ${(new Date().toLocaleTimeString())}] `); } if (data) { this.traceOutputChannel.appendLine(`${JSON.stringify(data)}`); } } needsStart() { return this.$state === ClientState.Initial || this.$state === ClientState.Stopping || this.$state === ClientState.Stopped; } needsStop() { return this.$state === ClientState.Starting || this.$state === ClientState.Running; } activeConnection() { return this.$state === ClientState.Running && this._connection !== undefined ? this._connection : undefined; } isRunning() { return this.$state === ClientState.Running; } async start() { if (this._disposed === 'disposing' || this._disposed === 'disposed') { throw new Error(`Client got disposed and can't be restarted.`); } if (this.$state === ClientState.Stopping) { throw new Error(`Client is currently stopping. Can only restart a full stopped client`); } // We are already running or are in the process of getting up // to speed. if (this._onStart !== undefined) { return this._onStart; } const [promise, resolve, reject] = this.createOnStartPromise(); this._onStart = promise; // If we restart then the diagnostics collection is reused. if (this._diagnostics === undefined) { this._diagnostics = this._clientOptions.diagnosticCollectionName ? vscode_1.languages.createDiagnosticCollection(this._clientOptions.diagnosticCollectionName) : vscode_1.languages.createDiagnosticCollection(); } // When we start make all buffer handlers pending so that they // get added. for (const [method, handler] of this._notificationHandlers) { if (!this._pendingNotificationHandlers.has(method)) { this._pendingNotificationHandlers.set(method, handler); } } for (const [method, handler] of this._requestHandlers) { if (!this._pendingRequestHandlers.has(method)) { this._pendingRequestHandlers.set(method, handler); } } for (const [token, data] of this._progressHandlers) { if (!this._pendingProgressHandlers.has(token)) { this._pendingProgressHandlers.set(token, data); } } this.$state = ClientState.Starting; try { const connection = await this.createConnection(); connection.onNotification(vscode_languageserver_protocol_1.LogMessageNotification.type, (message) => { switch (message.type) { case vscode_languageserver_protocol_1.MessageType.Error: this.error(message.message, undefined, false); break; case vscode_languageserver_protocol_1.MessageType.Warning: this.warn(message.message, undefined, false); break; case vscode_languageserver_protocol_1.MessageType.Info: this.info(message.message, undefined, false); break; case vscode_languageserver_protocol_1.MessageType.Debug: this.debug(message.message, undefined, false); break; default: this.outputChannel.appendLine(message.message); } }); connection.onNotification(vscode_languageserver_protocol_1.ShowMessageNotification.type, (message) => { switch (message.type) { case vscode_languageserver_protocol_1.MessageType.Error: void vscode_1.window.showErrorMessage(message.message); break; case vscode_languageserver_protocol_1.MessageType.Warning: void vscode_1.window.showWarningMessage(message.message); break; case vscode_languageserver_protocol_1.MessageType.Info: void vscode_1.window.showInformationMessage(message.message); break; default: void vscode_1.window.showInformationMessage(message.message); } }); connection.onRequest(vscode_languageserver_protocol_1.ShowMessageRequest.type, (params) => { let messageFunc; switch (params.type) { case vscode_languageserver_protocol_1.MessageType.Error: messageFunc = vscode_1.window.showErrorMessage; break; case vscode_languageserver_protocol_1.MessageType.Warning: messageFunc = vscode_1.window.showWarningMessage; break; case vscode_languageserver_protocol_1.MessageType.Info: messageFunc = vscode_1.window.showInformationMessage; break; default: messageFunc = vscode_1.window.showInformationMessage; } let actions = params.actions || []; return messageFunc(params.message, ...actions); }); connection.onNotification(vscode_languageserver_protocol_1.TelemetryEventNotification.type, (data) => { this._telemetryEmitter.fire(data); }); connection.onRequest(vscode_languageserver_protocol_1.ShowDocumentRequest.type, async (params) => { const showDocument = async (params) => { const uri = this.protocol2CodeConverter.asUri(params.uri); try { if (params.external === true) { const success = await vscode_1.env.openExternal(uri); return { success }; } else { const options = {}; if (params.selection !== undefined) { options.selection = this.protocol2CodeConverter.asRange(params.selection); } if (params.takeFocus === undefined || params.takeFocus === false) { options.preserveFocus = true; } else if (params.takeFocus === true) { options.preserveFocus = false; } await vscode_1.window.showTextDocument(uri, options); return { success: true }; } } catch (error) { return { success: false }; } }; const middleware = this._clientOptions.middleware.window?.showDocument; if (middleware !== undefined) { return middleware(params, showDocument); } else { return showDocument(params); } }); connection.listen(); await this.initialize(connection); resolve(); } catch (error) { this.$state = ClientState.StartFailed; this.error(`${this._name} client: couldn't create connection to server.`, error, 'force'); reject(error); } return this._onStart; } createOnStartPromise() { let resolve; let reject; const promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); return [promise, resolve, reject]; } async initialize(connection) { this.refreshTrace(connection, false); const initOption = this._clientOptions.initializationOptions; // If the client is locked to a workspace folder use it. In this case the workspace folder // feature is not registered and we need to initialize the value here. const [rootPath, workspaceFolders] = this._clientOptions.workspaceFolder !== undefined ? [this._clientOptions.workspaceFolder.uri.fsPath, [{ uri: this._c2p.asUri(this._clientOptions.workspaceFolder.uri), name: this._clientOptions.workspaceFolder.name }]] : [this._clientGetRootPath(), null]; const initParams = { processId: null, clientInfo: { name: vscode_1.env.appName, version: vscode_1.version }, locale: this.getLocale(), rootPath: rootPath ? rootPath : null, rootUri: rootPath ? this._c2p.asUri(vscode_1.Uri.file(rootPath)) : null, capabilities: this.computeClientCapabilities(), initializationOptions: Is.func(initOption) ? initOption() : initOption, trace: vscode_languageserver_protocol_1.Trace.toString(this._trace), workspaceFolders: workspaceFolders }; this.fillInitializeParams(initParams); if (this._clientOptions.progressOnInitialization) { const token = UUID.generateUuid(); const part = new progressPart_1.ProgressPart(connection, token); initParams.workDoneToken = token; try { const result = await this.doInitialize(connection, initParams); part.done(); return result; } catch (error) { part.cancel(); throw error; } } else { return this.doInitialize(connection, initParams); } } async doInitialize(connection, initParams) { try { const result = await connection.initialize(initParams); if (result.capabilities.positionEncoding !== undefined && result.capabilities.positionEncoding !== vscode_languageserver_protocol_1.PositionEncodingKind.UTF16) { throw new Error(`Unsupported position encoding (${result.capabilities.positionEncoding}) received from server ${this.name}`); } this._initializeResult = result; this.$state = ClientState.Running; let textDocumentSyncOptions = undefined; if (Is.number(result.capabilities.textDocumentSync)) { if (result.capabilities.textDocumentSync === vscode_languageserver_protocol_1.TextDocumentSyncKind.None) { textDocumentSyncOptions = { openClose: false, change: vscode_languageserver_protocol_1.TextDocumentSyncKind.None, save: undefined }; } else { textDocumentSyncOptions = { openClose: true, change: result.capabilities.textDocumentSync, save: { includeText: false } }; } } else if (result.capabilities.textDocumentSync !== undefined && result.capabilities.textDocumentSync !== null) { textDocumentSyncOptions = result.capabilities.textDocumentSync; } this._capabilities = Object.assign({}, result.capabilities, { resolvedTextDocumentSync: textDocumentSyncOptions }); connection.onNotification(vscode_languageserver_protocol_1.PublishDiagnosticsNotification.type, params => this.handleDiagnostics(params)); connection.onRequest(vscode_languageserver_protocol_1.RegistrationRequest.type, params => this.handleRegistrationRequest(params)); // See https://github.com/Microsoft/vscode-languageserver-node/issues/199 connection.onRequest('client/registerFeature', params => this.handleRegistrationRequest(params)); connection.onRequest(vscode_languageserver_protocol_1.UnregistrationRequest.type, params => this.handleUnregistrationRequest(params)); // See https://github.com/Microsoft/vscode-languageserver-node/issues/199 connection.onRequest('client/unregisterFeature', params => this.handleUnregistrationRequest(params)); connection.onRequest(vscode_languageserver_protocol_1.ApplyWorkspaceEditRequest.type, params => this.handleApplyWorkspaceEdit(params)); // Add pending notification, request and progress handlers. for (const [method, handler] of this._pendingNotificationHandlers) { this._notificationDisposables.set(method, connection.onNotification(method, handler)); } this._pendingNotificationHandlers.clear(); for (const [method, handler] of this._pendingRequestHandlers) { this._requestDisposables.set(method, connection.onRequest(method, handler)); } this._pendingRequestHandlers.clear(); for (const [token, data] of this._pendingProgressHandlers) { this._progressDisposables.set(token, connection.onProgress(data.type, token, data.handler)); } this._pendingProgressHandlers.clear(); // if (this._clientOptions.suspend.mode !== SuspendMode.off) { // this._idleInterval = RAL().timer.setInterval(() => this.checkSuspend(), this._clientOptions.suspend.interval); // } await connection.sendNotification(vscode_languageserver_protocol_1.InitializedNotification.type, {}); this.hookFileEvents(connection); this.hookConfigurationChanged(connection); this.initializeFeatures(connection); return result; } catch (error) { if (this._clientOptions.initializationFailedHandler) { if (this._clientOptions.initializationFailedHandler(error)) { void this.initialize(connection); } else { void this.stop(); } } else if (error instanceof vscode_languageserver_protocol_1.ResponseError && error.data && error.data.retry) { void vscode_1.window.showErrorMessage(error.message, { title: 'Retry', id: 'retry' }).then(item => { if (item && item.id === 'retry') { void this.initialize(connection); } else { void this.stop(); } }); } else { if (error && error.message) { void vscode_1.window.showErrorMessage(error.message); } this.error('Server initialization failed.', error); void this.stop(); } throw error; } } _clientGetRootPath() { let folders = vscode_1.workspace.workspaceFolders; if (!folders || folders.length === 0) { return undefined; } let folder = folders[0]; if (folder.uri.scheme === 'file') { return folder.uri.fsPath; } return undefined; } stop(timeout = 2000) { // Wait 2 seconds on stop return this.shutdown('stop', timeout); } dispose(timeout = 2000) { try { this._disposed = 'disposing'; return this.stop(timeout); } finally { this._disposed = 'disposed'; } } async shutdown(mode, timeout) { // If the client is stopped or in its initial state return. if (this.$state === ClientState.Stopped || this.$state === ClientState.Initial) { return; } // If we are stopping the client and have a stop promise return it. if (this.$state === ClientState.Stopping) { if (this._onStop !== undefined) { return this._onStop; } else { throw new Error(`Client is stopping but no stop promise available.`); } } const connection = this.activeConnection(); // We can't stop a client that is not running (e.g. has no connection). Especially not // on that us starting since it can't be correctly synchronized. if (connection === undefined || this.$state !== ClientState.Running) { throw new Error(`Client is not running and can't be stopped. It's current state is: ${this.$state}`); } this._initializeResult = undefined; this.$state = ClientState.Stopping; this.cleanUp(mode); const tp = new Promise(c => { (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(c, timeout); }); const shutdown = (async (connection) => { await connection.shutdown(); await connection.exit(); return connection; })(connection); return this._onStop = Promise.race([tp, shutdown]).then((connection) => { // The connection won the race with the timeout. if (connection !== undefined) { connection.end(); connection.dispose(); } else { this.error(`Stopping server timed out`, undefined, false); throw new Error(`Stopping the server timed out`); } }, (error) => { this.error(`Stopping server failed`, error, false); throw error; }).finally(() => { this.$state = ClientState.Stopped; mode === 'stop' && this.cleanUpChannel(); this._onStart = undefined; this._onStop = undefined; this._connection = undefined; this._ignoredRegistrations.clear(); }); } cleanUp(mode) { // purge outstanding file events. this._fileEvents = []; this._fileEventDelayer.cancel(); const disposables = this._listeners.splice(0, this._listeners.length); for (const disposable of disposables) { disposable.dispose(); } if (this._syncedDocuments) { this._syncedDocuments.clear(); } // Clear features in reverse order; for (const feature of Array.from(this._features.entries()).map(entry => entry[1]).reverse()) { feature.clear(); } if (mode === 'stop' && this._diagnostics !== undefined) { this._diagnostics.dispose(); this._diagnostics = undefined; } if (this._idleInterval !== undefined) { this._idleInterval.dispose(); this._idleInterval = undefined; } // this._idleStart = undefined; } cleanUpChannel() { if (this._outputChannel !== undefined && this._disposeOutputChannel) { this._outputChannel.dispose(); this._outputChannel = undefined; } } notifyFileEvent(event) { const client = this; async function didChangeWatchedFile(event) { client._fileEvents.push(event); return client._fileEventDelayer.trigger(async () => { await client.sendNotification(vscode_languageserver_protocol_1.DidChangeWatchedFilesNotification.type, { changes: client._fileEvents }); client._fileEvents = []; }); } const workSpaceMiddleware = this.clientOptions.middleware?.workspace; (workSpaceMiddleware?.didChangeWatchedFile ? workSpaceMiddleware.didChangeWatchedFile(event, didChangeWatchedFile) : didChangeWatchedFile(event)).catch((error) => { client.error(`Notify file events failed.`, error); }); } async sendPendingFullTextDocumentChanges(connection) { return this._pendingChangeSemaphore.lock(async () => { try { const changes = this._didChangeTextDocumentFeature.getPendingDocumentChanges(this._pendingOpenNotifications); if (changes.length === 0) { return; } for (const document of changes) { const params = this.code2ProtocolConverter.asChangeTextDocumentParams(document); // We await the send and not the delivery since it is more or less the same for // notifications. await connection.sendNotification(vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type, params); this._didChangeTextDocumentFeature.notificationSent(document, vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type, params); } } catch (error) { this.error(`Sending pending changes failed`, error, false); throw error; } }); } triggerPendingChangeDelivery() { this._pendingChangeDelayer.trigger(async () => { const connection = this.activeConnection(); if (connection === undefined) { this.triggerPendingChangeDelivery(); return; } await this.sendPendingFullTextDocumentChanges(connection); }).catch((error) => this.error(`Delivering pending changes failed`, error, false)); } handleDiagnostics(params) { if (!this._diagnostics) { return; } const key = params.uri; if (this._diagnosticQueueState.state === 'busy' && this._diagnosticQueueState.document === key) { // Cancel the active run; this._diagnosticQueueState.tokenSource.cancel(); } this._diagnosticQueue.set(params.uri, params.diagnostics); this.triggerDiagnosticQueue(); } triggerDiagnosticQueue() { (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => { this.workDiagnosticQueue(); }); } workDiagnosticQueue() { if (this._diagnosticQueueState.state === 'busy') { return; } const next = this._diagnosticQueue.entries().next(); if (next.done === true) { // Nothing in the queue return; } const [document, diagnostics] = next.value; this._diagnosticQueue.delete(document); const tokenSource = new vscode_1.CancellationTokenSource(); this._diagnosticQueueState = { state: 'busy', document: document, tokenSource }; this._p2c.asDiagnostics(diagnostics, tokenSource.token).then((converted) => { if (!tokenSource.token.isCancellationRequested) { const uri = this._p2c.asUri(document); const middleware = this.clientOptions.middleware; if (middleware.handleDiagnostics) { middleware.handleDiagnostics(uri, converted, (uri, diagnostics) => this.setDiagnostics(uri, diagnostics)); } else { this.setDiagnostics(uri, converted); } } }).finally(() => { this._diagnosticQueueState = { state: 'idle' }; this.triggerDiagnosticQueue(); }); } setDiagnostics(uri, diagnostics) { if (!this._diagnostics) { return; } this._diagnostics.set(uri, diagnostics); } getLocale() { return vscode_1.env.language; } async $start() { if (this.$state === ClientState.StartFailed) { throw new Error(`Previous start failed. Can't restart server.`); } await this.start(); const connection = this.activeConnection(); if (connection === undefined) { throw new Error(`Starting server failed`); } return connection; } async createConnection() { let errorHandler = (error, message, count) => { this.handleConnectionError(error, message, count).catch((error) => this.error(`Handling connection error failed`, error)); }; let closeHandler = () => { this.handleConnectionClosed().catch((error) => this.error(`Handling connection close failed`, error)); }; const transports = await this.createMessageTransports(this._clientOptions.stdioEncoding || 'utf8'); this._connection = createConnection(transports.reader, transports.writer, errorHandler, closeHandler, this._clientOptions.connectionOptions); return this._connection; } async handleConnectionClosed() { // Check whether this is a normal shutdown in progress or the client stopped normally. if (this.$state === ClientState.Stopped) { return; } try { if (this._connection !== undefined) { this._connection.dispose(); } } catch (error) { // Disposing a connection could fail if error cases. } let handlerResult = { action: CloseAction.DoNotRestart }; if (this.$state !== ClientState.Stopping) { try { handlerResult = await this._clientOptions.errorHandler.closed(); } catch (error) { // Ignore errors coming from the error handler. } } this._connection = undefined; if (handlerResult.action === CloseAction.DoNotRestart) { this.error(handlerResult.message ?? 'Connection to server got closed. Server will not be restarted.', undefined, handlerResult.handled === true ? false : 'force'); this.cleanUp('stop'); if (this.$state === ClientState.Starting) { this.$state = ClientState.StartFailed; } else { this.$state = ClientState.Stopped; } this._onStop = Promise.resolve(); this._onStart = undefined; } else if (handlerResult.action === CloseAction.Restart) { this.info(handlerResult.message ?? 'Connection to server got closed. Server will restart.', !handlerResult.handled); this.cleanUp('restart'); this.$state = ClientState.Initial; this._onStop = Promise.resolve(); this._onStart = undefined; this.start().catch((error) => this.error(`Restarting server failed`, error, 'force')); } } async handleConnectionError(error, message, count) { const handlerResult = await this._clientOptions.errorHandler.error(error, message, count); if (handlerResult.action === ErrorAction.Shutdown) { this.error(handlerResult.message ?? `Client ${this._name}: connection to server is erroring.\n${error.message}\nShutting down server.`, undefined, handlerResult.handled === true ? false : 'force'); this.stop().catch((error) => { this.error(`Stopping server failed`, error, false); }); } else { this.error(handlerResult.message ?? `Client ${this._name}: connection to server is erroring.\n${error.message}`, undefined, handlerResult.handled === true ? false : 'force'); } } hookConfigurationChanged(connection) { this._listeners.push(vscode_1.workspace.onDidChangeConfiguration(() => { this.refreshTrace(connection, true); })); } refreshTrace(connection, sendNotification = false) { const config = vscode_1.workspace.getConfiguration(this._id); let trace = vscode_languageserver_protocol_1.Trace.Off; let traceFormat = vscode_languageserver_protocol_1.TraceFormat.Text; if (config) { const traceConfig = config.get('trace.server', 'off'); if (typeof traceConfig === 'string') { trace = vscode_languageserver_protocol_1.Trace.fromString(traceConfig); } else { trace = vscode_languageserver_protocol_1.Trace.fromString(config.get('trace.server.verbosity', 'off')); traceFormat = vscode_languageserver_protocol_1.TraceFormat.fromString(config.get('trace.server.format', 'text')); } } this._trace = trace; this._traceFormat = traceFormat; connection.trace(this._trace, this._tracer, { sendNotification, traceFormat: this._traceFormat }).catch((error) => { this.error(`Updating trace failed with error`, error, false); }); } hookFileEvents(_connection) { let fileEvents = this._clientOptions.synchronize.fileEvents; if (!fileEvents) { return; } let watchers; if (Is.array(fileEvents)) { watchers = fileEvents; } else { watchers = [fileEvents]; } if (!watchers) { return; } this._dynamicFeatures.get(vscode_languageserver_protocol_1.DidChangeWatchedFilesNotification.type.method).registerRaw(UUID.generateUuid(), watchers); } registerFeatures(features) { for (let feature of features) { this.registerFeature(feature); } } registerFeature(feature) { this._features.push(feature); if (features_1.DynamicFeature.is(feature)) { const registrationType = feature.registrationType; this._dynamicFeatures.set(registrationType.method, feature); } } getFeature(request) { return this._dynamicFeatures.get(request); } hasDedicatedTextSynchronizationFeature(textDocument) { const feature = this.getFeature(vscode_languageserver_protocol_1.NotebookDocumentSyncRegistrationType.method); if (feature === undefined || !(feature instanceof notebook_1.NotebookDocumentSyncFeature)) { return false; } return feature.handles(textDocument); } registerBuiltinFeatures() { const pendingFullTextDocumentChanges = new Map(); this.registerFeature(new configuration_1.ConfigurationFeature(this)); this.registerFeature(new textSynchronization_1.DidOpenTextDocumentFeature(this, this._syncedDocuments)); this._didChangeTextDocumentFeature = new textSynchronization_1.DidChangeTextDocumentFeature(this, pendingFullTextDocumentChanges); this._didChangeTextDocumentFeature.onPendingChangeAdded(() => { this.triggerPendingChangeDelivery(); }); this.registerFeature(this._didChangeTextDocumentFeature); this.registerFeature(new textSynchronization_1.WillSaveFeature(this)); this.registerFeature(new textSynchronization_1.WillSaveWaitUntilFeature(this)); this.registerFeature(new textSynchronization_1.DidSaveTextDocumentFeature(this)); this.registerFeature(new textSynchronization_1.DidCloseTextDocumentFeature(this, this._syncedDocuments, pendingFullTextDocumentChanges)); this.registerFeature(new fileSystemWatcher_1.FileSystemWatcherFeature(this, (event) => this.notifyFileEvent(event))); this.registerFeature(new completion_1.CompletionItemFeature(this)); this.registerFeature(new hover_1.HoverFeature(this)); this.registerFeature(new signatureHelp_1.SignatureHelpFeature(this)); this.registerFeature(new definition_1.DefinitionFeature(this)); this.registerFeature(new reference_1.ReferencesFeature(this)); this.registerFeature(new documentHighlight_1.DocumentHighlightFeature(this)); this.registerFeature(new documentSymbol_1.DocumentSymbolFeature(this)); this.registerFeature(new workspaceSymbol_1.WorkspaceSymbolFeature(this)); this.registerFeature(new codeAction_1.CodeActionFeature(this)); this.registerFeature(new codeLens_1.CodeLensFeature(this)); this.registerFeature(new formatting_1.DocumentFormattingFeature(this)); this.registerFeature(new formatting_1.DocumentRangeFormattingFeature(this)); this.registerFeature(new formatting_1.DocumentOnTypeFormattingFeature(this)); this.registerFeature(new rename_1.RenameFeature(this)); this.registerFeature(new documentLink_1.DocumentLinkFeature(this)); this.registerFeature(new executeCommand_1.ExecuteCommandFeature(this)); this.registerFeature(new configuration_1.SyncConfigurationFeature(this)); this.registerFeature(new typeDefinition_1.TypeDefinitionFeature(this)); this.registerFeature(new implementation_1.ImplementationFeature(this)); this.registerFeature(new colorProvider_1.ColorProviderFeature(this)); // We only register the workspace folder feature if the client is not locked // to a specific workspace folder. if (this.clientOptions.workspaceFolder === undefined) { this.registerFeature(new workspaceFolder_1.WorkspaceFoldersFeature(this)); } this.registerFeature(new foldingRange_1.FoldingRangeFeature(this)); this.registerFeature(new declaration_1.DeclarationFeature(this)); this.registerFeature(new selectionRange_1.SelectionRangeFeature(this)); this.registerFeature(new progress_1.ProgressFeature(this)); this.registerFeature(new callHierarchy_1.CallHierarchyFeature(this)); this.registerFeature(new semanticTokens_1.SemanticTokensFeature(this)); this.registerFeature(new linkedEditingRange_1.LinkedEditingFeature(this)); this.registerFeature(new fileOperations_1.DidCreateFilesFeature(this)); this.registerFeature(new fileOperations_1.DidRenameFilesFeature(this)); this.registerFeature(new fileOperations_1.DidDeleteFilesFeature(this)); this.registerFeature(new fileOperations_1.WillCreateFilesFeature(this)); this.registerFeature(new fileOperations_1.WillRenameFilesFeature(this)); this.registerFeature(new fileOperations_1.WillDeleteFilesFeature(this)); this.registerFeature(new typeHierarchy_1.TypeHierarchyFeature(this)); this.registerFeature(new inlineValue_1.InlineValueFeature(this)); this.registerFeature(new inlayHint_1.InlayHintsFeature(this)); this.registerFeature(new diagnostic_1.DiagnosticFeature(this)); this.registerFeature(new notebook_1.NotebookDocumentSyncFeature(this)); } registerProposedFeatures() { this.registerFeatures(ProposedFeatures.createAll(this)); } fillInitializeParams(params) { for (let feature of this._features) { if (Is.func(feature.fillInitializeParams)) { feature.fillInitializeParams(params); } } } computeClientCapabilities() { const result = {}; (0, features_1.ensure)(result, 'workspace').applyEdit = true; const workspaceEdit = (0, features_1.ensure)((0, features_1.ensure)(result, 'workspace'), 'workspaceEdit'); workspaceEdit.documentChanges = true; workspaceEdit.resourceOperations = [vscode_languageserver_protocol_1.ResourceOperationKind.Create, vscode_languageserver_protocol_1.ResourceOperationKind.Rename, vscode_languageserver_protocol_1.ResourceOperationKind.Delete]; workspaceEdit.failureHandling = vscode_languageserver_protocol_1.FailureHandlingKind.TextOnlyTransactional; workspaceEdit.normalizesLineEndings = true; workspaceEdit.changeAnnotationSupport = { groupsOnLabel: true }; const diagnostics = (0, features_1.ensure)((0, features_1.ensure)(result, 'textDocument'), 'publishDiagnostics'); diagnostics.relatedInformation = true; diagnostics.versionSupport = false; diagnostics.tagSupport = { valueSet: [vscode_languageserver_protocol_1.DiagnosticTag.Unnecessary, vscode_languageserver_protocol_1.DiagnosticTag.Deprecated] }; diagnostics.codeDescriptionSupport = true; diagnostics.dataSupport = true; const windowCapabilities = (0, features_1.ensure)(result, 'window'); const showMessage = (0, features_1.ensure)(windowCapabilities, 'showMessage'); showMessage.messageActionItem = { additionalPropertiesSupport: true }; const showDocument = (0, features_1.ensure)(windowCapabilities, 'showDocument'); showDocument.support = true; const generalCapabilities = (0, features_1.ensure)(result, 'general'); generalCapabilities.staleRequestSupport = { cancel: true, retryOnContentModified: Array.from(BaseLanguageClient.RequestsToCancelOnContentModified) }; generalCapabilities.regularExpressions = { engine: 'ECMAScript', version: 'ES2020' }; generalCapabilities.markdown = { parser: 'marked', version: '1.1.0', }; generalCapabilities.positionEncodings = ['utf-16']; if (this._clientOptions.markdown.supportHtml) { generalCapabilities.markdown.allowedTags = ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span']; } for (let feature of this._features) { feature.fillClientCapabilities(result); } return result; } initializeFeatures(_connection) { const documentSelector = this._clientOptions.documentSelector; for (const feature of this._features) { if (Is.func(feature.preInitialize)) { feature.preInitialize(this._capabilities, documentSelector); } } for (const feature of this._features) { feature.initialize(this._capabilities, documentSelector); } } async handleRegistrationRequest(params) { const middleware = this.clientOptions.middleware?.handleRegisterCapability; if (middleware) { return middleware(params, nextParams => this.doRegisterCapability(nextParams)); } else { return this.doRegisterCapability(params); } } async doRegisterCapability(params) { // We will not receive a registration call before a client is running // from a server. However if we stop or shutdown we might which might // try to restart the server. So ignore registrations if we are not running if (!this.isRunning()) { for (const registration of params.registrations) { this._ignoredRegistrations.add(registration.id); } return; } for (const registration of params.registrations) { const feature = this._dynamicFeatures.get(registration.method); if (feature === undefined) { return Promise.reject(new Error(`No feature implementation for ${registration.method} found. Registration failed.`)); } const options = registration.registerOptions ?? {}; options.documentSelector = options.documentSelector ?? this._clientOptions.documentSelector; const data = { id: registration.id, registerOptions: options }; try { feature.register(data); } catch (err) { return Promise.reject(err); } } } async handleUnregistrationRequest(params) { const middleware = this.clientOptions.middleware?.handleUnregisterCapability; if (middleware) { return middleware(params, nextParams => this.doUnregisterCapability(nextParams)); } else { return this.doUnregisterCapability(params); } } async doUnregisterCapability(params) { for (const unregistration of params.unregisterations) { if (this._ignoredRegistrations.has(unregistration.id)) { continue; } const feature = this._dynamicFeatures.get(unregistration.method); if (!feature) { return Promise.reject(new Error(`No feature implementation for ${unregistration.method} found. Unregistration failed.`)); } feature.unregister(unregistration.id); } } async handleApplyWorkspaceEdit(params) { const workspaceEdit = params.edit; // Make sure we convert workspace edits one after the other. Otherwise // we might execute a workspace edit received first after we received another // one since the conversion might race. const converted = await this.workspaceEditLock.lock(() => { return this._p2c.asWorkspaceEdit(workspaceEdit); }); // This is some sort of workaround since the version check should be done by VS Code in the Workspace.applyEdit. // However doing it here adds some safety since the server can lag more behind then an extension. const openTextDocuments = new Map(); vscode_1.workspace.textDocuments.forEach((document) => openTextDocuments.set(document.uri.toString(), document)); let versionMismatch = false; if (workspaceEdit.documentChanges) { for (const change of workspaceEdit.documentChanges) { if (vscode_languageserver_protocol_1.TextDocumentEdit.is(change) && change.textDocument.version && change.textDocument.version >= 0) { const changeUri = this._p2c.asUri(change.textDocument.uri).toString(); const textDocument = openTextDocuments.get(changeUri); if (textDocument && textDocument.version !== change.textDocument.version) { versionMismatch = true; break; } } } } if (versionMismatch) { return Promise.resolve({ applied: false }); } return Is.asPromise(vscode_1.workspace.applyEdit(converted).then((value) => { return { applied: value }; })); } handleFailedRequest(type, token, error, defaultValue, showNotification = true) { // If we get a request cancel or a content modified don't log anything. if (error instanceof vscode_languageserver_protocol_1.ResponseError) { // The connection got disposed while we were waiting for a response. // Simply return the default value. Is the best we can do. if (error.code === vscode_languageserver_protocol_1.ErrorCodes.PendingResponseRejected || error.code === vscode_languageserver_protocol_1.ErrorCodes.ConnectionInactive) { return defaultValue; } if (error.code === vscode_languageserver_protocol_1.LSPErrorCodes.RequestCancelled || error.code === vscode_languageserver_protocol_1.LSPErrorCodes.ServerCancelled) { if (token !== undefined && token.isCancellationRequested) { return defaultValue; } else { if (error.data !== undefined) { throw new features_1.LSPCancellationError(error.data); } else { throw new vscode_1.CancellationError(); } } } else if (error.code === vscode_languageserver_protocol_1.LSPErrorCodes.ContentModified) { if (BaseLanguageClient.RequestsToCancelOnContentModified.has(type.method) || BaseLanguageClient.CancellableResolveCalls.has(type.method)) { throw new vscode_1.CancellationError(); } else { return defaultValue; } } } this.error(`Request ${type.method} failed.`, error, showNotification); throw error; } } exports.BaseLanguageClient = BaseLanguageClient; BaseLanguageClient.RequestsToCancelOnContentModified = new Set([ vscode_languageserver_protocol_1.SemanticTokensRequest.method, vscode_languageserver_protocol_1.SemanticTokensRangeRequest.method, vscode_languageserver_protocol_1.SemanticTokensDeltaRequest.method ]); BaseLanguageClient.CancellableResolveCalls = new Set([ vscode_languageserver_protocol_1.CompletionResolveRequest.method, vscode_languageserver_protocol_1.CodeLensResolveRequest.method, vscode_languageserver_protocol_1.CodeActionResolveRequest.method, vscode_languageserver_protocol_1.InlayHintResolveRequest.method, vscode_languageserver_protocol_1.DocumentLinkResolveRequest.method, vscode_languageserver_protocol_1.WorkspaceSymbolResolveRequest.method ]); class ConsoleLogger { error(message) { (0, vscode_languageserver_protocol_1.RAL)().console.error(message); } warn(message) { (0, vscode_languageserver_protocol_1.RAL)().console.warn(message); } info(message) { (0, vscode_languageserver_protocol_1.RAL)().console.info(message); } log(message) { (0, vscode_languageserver_protocol_1.RAL)().console.log(message); } } function createConnection(input, output, errorHandler, closeHandler, options) { const logger = new ConsoleLogger(); const connection = (0, vscode_languageserver_protocol_1.createProtocolConnection)(input, output, logger, options); connection.onError((data) => { errorHandler(data[0], data[1], data[2]); }); connection.onClose(closeHandler); const result = { listen: () => connection.listen(), sendRequest: connection.sendRequest, onRequest: connection.onRequest, hasPendingResponse: connection.hasPendingResponse, sendNotification: connection.sendNotification, onNotification: connection.onNotification, onProgress: connection.onProgress, sendProgress: connection.sendProgress, trace: (value, tracer, sendNotificationOrTraceOptions) => { const defaultTraceOptions = { sendNotification: false, traceFormat: vscode_languageserver_protocol_1.TraceFormat.Text }; if (sendNotificationOrTraceOptions === undefined) { return connection.trace(value, tracer, defaultTraceOptions); } else if (Is.boolean(sendNotificationOrTraceOptions)) { return connection.trace(value, tracer, sendNotificationOrTraceOptions); } else { return connection.trace(value, tracer, sendNotificationOrTraceOptions); } }, initialize: (params) => { // This needs to return and MUST not be await to avoid any async // scheduling. Otherwise messages might overtake each other. return connection.sendRequest(vscode_languageserver_protocol_1.InitializeRequest.type, params); }, shutdown: () => { // This needs to return and MUST not be await to avoid any async // scheduling. Otherwise messages might overtake each other. return connection.sendRequest(vscode_languageserver_protocol_1.ShutdownRequest.type, undefined); }, exit: () => { // This needs to return and MUST not be await to avoid any async // scheduling. Otherwise messages might overtake each other. return connection.sendNotification(vscode_languageserver_protocol_1.ExitNotification.type); }, end: () => connection.end(), dispose: () => connection.dispose() }; return result; } // Exporting proposed protocol. var ProposedFeatures; (function (ProposedFeatures) { function createAll(_client) { let result = [ new inlineCompletion_1.InlineCompletionItemFeature(_client) ]; return result; } ProposedFeatures.createAll = createAll; })(ProposedFeatures || (exports.ProposedFeatures = ProposedFeatures = {}));