UNPKG

claude-flow

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

1,091 lines (1,090 loc) 75.7 kB
"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;