UNPKG

vscode-languageclient

Version:
1,136 lines 89.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ProposedFeatures = exports.LanguageClient = exports.BaseLanguageClient = exports.ShutdownMode = exports.MessageTransports = exports.SuspendMode = exports.State = exports.CloseAction = exports.ErrorAction = exports.RevealOutputChannelOn = void 0; /* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ const vscode_1 = require("vscode"); const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const c2p = __importStar(require("./codeConverter")); const p2c = __importStar(require("./protocolConverter")); const Is = __importStar(require("./utils/is")); const async_1 = require("./utils/async"); const UUID = __importStar(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 typeDefinition_1 = require("./typeDefinition"); const implementation_1 = require("./implementation"); const colorProvider_1 = require("./colorProvider"); 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 foldingRange_1 = require("./foldingRange"); const declaration_1 = require("./declaration"); const selectionRange_1 = require("./selectionRange"); const callHierarchy_1 = require("./callHierarchy"); const semanticTokens_1 = require("./semanticTokens"); const linkedEditingRange_1 = require("./linkedEditingRange"); const typeHierarchy_1 = require("./typeHierarchy"); const inlineValue_1 = require("./inlineValue"); const inlayHint_1 = require("./inlayHint"); const workspaceFolder_1 = require("./workspaceFolder"); const fileOperations_1 = require("./fileOperations"); const inlineCompletion_1 = require("./inlineCompletion"); const textDocumentContent_1 = require("./textDocumentContent"); const fileSystemWatcher_1 = require("./fileSystemWatcher"); const progress_1 = require("./progress"); /** * 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 start has failed. */ State[State["StartFailed"] = 4] = "StartFailed"; /** * 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 { client; maxRestartCount; restarts; 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 { const 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) { const 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 = {})); var ShutdownMode; (function (ShutdownMode) { ShutdownMode["Restart"] = "restart"; ShutdownMode["Stop"] = "stop"; })(ShutdownMode || (exports.ShutdownMode = ShutdownMode = {})); /** * Manages the open tabs. We don't directly use the tab API since for * diagnostics we need to de-dupe tabs that show the same resources since * we pull on the model not the UI. */ class VisibleDocumentsImpl { open; _onOpen; _onClose; disposables; constructor() { this.disposables = []; this.open = new Set(); this._onOpen = new vscode_1.EventEmitter(); this._onClose = new vscode_1.EventEmitter(); VisibleDocumentsImpl.fillVisibleResources(this.open); const updateVisibleDocuments = () => { const oldTabs = this.open; const currentTabs = new Set(); VisibleDocumentsImpl.fillVisibleResources(currentTabs); const closed = new Set(); const opened = new Set(currentTabs); for (const tab of oldTabs.values()) { if (currentTabs.has(tab)) { opened.delete(tab); } else { closed.add(tab); } } this.open = currentTabs; if (closed.size > 0) { const toFire = new Set(); for (const item of closed) { toFire.add(vscode_1.Uri.parse(item)); } this._onClose.fire(toFire); } if (opened.size > 0) { const toFire = new Set(); for (const item of opened) { toFire.add(vscode_1.Uri.parse(item)); } this._onOpen.fire(toFire); } }; this.disposables.push(vscode_1.window.tabGroups.onDidChangeTabs((event) => { if (event.closed.length === 0 && event.opened.length === 0) { return; } updateVisibleDocuments(); })); this.disposables.push(vscode_1.window.onDidChangeVisibleTextEditors((_editors) => { updateVisibleDocuments(); })); } get onClose() { return this._onClose.event; } get onOpen() { return this._onOpen.event; } dispose() { this.disposables.forEach(disposable => disposable.dispose()); } isActive(document) { return document instanceof vscode_1.Uri ? vscode_1.window.activeTextEditor?.document.uri === document : vscode_1.window.activeTextEditor?.document === document; } isVisible(document) { const uri = document instanceof vscode_1.Uri ? document : document.uri; if (uri.scheme === notebook_1.NotebookDocumentSyncFeature.CellScheme) { // Notebook cells aren't in the list of tabs, but the notebook should be. return vscode_1.workspace.notebookDocuments.some(notebook => { if (this.open.has(notebook.uri.toString())) { const cell = notebook.getCells().find(cell => cell.document.uri.toString() === uri.toString()); return cell !== undefined; } return false; }); } return this.open.has(uri.toString()); } getResources() { const result = new Set(); VisibleDocumentsImpl.fillVisibleResources(new Set(), result); return result; } static fillVisibleResources(strings, uris) { const seen = strings ?? new Set(); for (const group of vscode_1.window.tabGroups.all) { for (const tab of group.tabs) { const input = tab.input; let uri; if (input instanceof vscode_1.TabInputText) { uri = input.uri; } else if (input instanceof vscode_1.TabInputTextDiff) { uri = input.modified; } else if (input instanceof vscode_1.TabInputCustom) { uri = input.uri; } else if (input instanceof vscode_1.TabInputNotebook) { uri = input.uri; } if (uri !== undefined && !seen.has(uri.toString())) { seen.add(uri.toString()); uris !== undefined && uris.add(uri); } } } // Peek editors are not part of tabs but should be considered visible for (const editor of vscode_1.window.visibleTextEditors) { const uri = editor.document.uri; if (!seen.has(uri.toString())) { seen.add(uri.toString()); uris !== undefined && uris.add(uri); } } } } class BaseLanguageClient { _id; _name; _clientOptions; _state; _onStart; _onStop; _connection; _idleInterval; _ignoredRegistrations; // private _idleStart: number | undefined; _listeners; _disposed; _notificationHandlers; _notificationDisposables; _pendingNotificationHandlers; _requestHandlers; _requestDisposables; _pendingRequestHandlers; _progressHandlers; _pendingProgressHandlers; _progressDisposables; _initializeResult; _outputChannel; _disposeOutputChannel; _traceOutputChannel; _traceLogLevel; _capabilities; // undefined: allow lazy creation of the diagnostic collection. // null: the diagnostic collection has been disposed and should not be // recreated. _diagnostics; _syncedDocuments; _didChangeTextDocumentFeature; _inFlightOpenNotifications; _pendingChangeSemaphore; _pendingChangeDelayer; _didOpenTextDocumentFeature; _fileEvents; _fileEventDelayer; _telemetryEmitter; _stateChangeEmitter; _trace; _traceFormat = vscode_languageserver_protocol_1.TraceFormat.Text; _tracer; _c2p; _p2c; _visibleDocuments; constructor(id, name, clientOptions) { this._id = id; this._name = name; clientOptions = clientOptions || {}; const markdown = { isTrusted: false, supportHtml: false, supportThemeIcons: false }; if (clientOptions.markdown !== undefined) { markdown.isTrusted = ResolvedClientOptions.sanitizeIsTrusted(clientOptions.markdown.isTrusted); markdown.supportHtml = clientOptions.markdown.supportHtml === true; markdown.supportThemeIcons = clientOptions.markdown.supportThemeIcons === 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 }, diagnosticCollectionProvider: clientOptions.diagnosticCollectionProvider ?? new features_1.DefaultDiagnosticCollectionProvider(), notebookDocumentOptions: clientOptions.notebookDocumentOptions ?? {}, textSynchronization: this.createTextSynchronizationOptions(clientOptions.textSynchronization) }; 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; this._traceLogLevel = this._outputChannel.logLevel; } else { this._outputChannel = undefined; this._disposeOutputChannel = true; this._traceLogLevel = vscode_1.LogLevel.Info; } this._traceOutputChannel = clientOptions.traceOutputChannel; if (this._traceOutputChannel !== undefined) { this._traceLogLevel = this._traceOutputChannel.logLevel; } // Allow lazy creation of the diagnostic collection. this._diagnostics = undefined; this._inFlightOpenNotifications = 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.trace(messageOrDataObject, data); } else { this.traceObject(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._clientOptions.markdown.supportThemeIcons); this._syncedDocuments = new Map(); this.registerBuiltinFeatures(); } createTextSynchronizationOptions(options) { if (!options) { return { delayOpenNotifications: false }; } if (typeof options.delayOpenNotifications === 'boolean') { return { delayOpenNotifications: options.delayOpenNotifications }; } return { delayOpenNotifications: false }; } 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 visibleDocuments() { if (this._visibleDocuments === undefined) { this._visibleDocuments = new VisibleDocumentsImpl(); } return this._visibleDocuments; } 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, { log: true }); if (this._traceOutputChannel === undefined) { this._traceLogLevel = this._outputChannel.logLevel; } } return this._outputChannel; } get traceOutputChannel() { return this._traceOutputChannel ? this._traceOutputChannel : this.outputChannel; } get diagnostics() { if (this._diagnostics === null) { return undefined; } if (this._diagnostics === undefined) { this._diagnostics = this._clientOptions.diagnosticCollectionProvider.create(this._clientOptions.diagnosticCollectionName ?? this._id, features_1.DiagnosticCollectionSource.push); } return this._diagnostics; } get state() { return this.getPublicState(); } get $state() { return this._state; } set $state(value) { const oldState = this.getPublicState(); this._state = value; const 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; case ClientState.StartFailed: return State.StartFailed; 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(); // Send ony depending open notifications await this._didOpenTextDocumentFeature.sendPendingOpenNotifications(); // 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); } 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]; } if (token !== undefined && token.isCancellationRequested) { return Promise.reject(new vscode_languageserver_protocol_1.ResponseError(vscode_languageserver_protocol_1.LSPErrorCodes.RequestCancelled, 'Request got cancelled')); } const _sendRequest = this._clientOptions.middleware?.sendRequest; if (_sendRequest !== undefined) { // 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._inFlightOpenNotifications.add(openNotification); } let documentToClose; if (typeof type !== 'string' && type.method === vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.method) { documentToClose = params.textDocument.uri; } // Ensure we have a connection before we force the document sync. const connection = await this.$start(); // Send any depending open notifications const didDropOpenNotification = await this._didOpenTextDocumentFeature.sendPendingOpenNotifications(documentToClose); if (didDropOpenNotification) { // Don't forward this close notification if we dropped the // corresponding open notification. return; } // 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 off to the writer and the writer has a semaphore // lock with a capacity of 1 no additional async scheduling can happen until // the message is actually handed off. if (openNotification !== undefined) { this._inFlightOpenNotifications.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(); } shouldLogToOutputChannel() { if (this.$state !== ClientState.Stopped) { return true; } return this._outputChannel !== undefined; } error(message, data, showNotification = true) { if (this.shouldLogToOutputChannel()) { this.outputChannel.error(this.getLogMessage(message, data)); } if (showNotification === 'force' || (showNotification && this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Error)) { this.showNotificationMessage(vscode_languageserver_protocol_1.MessageType.Error, message, data); } } warn(message, data, showNotification = true) { if (this.shouldLogToOutputChannel()) { this.outputChannel.warn(this.getLogMessage(message, data)); } if (showNotification && this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Warn) { this.showNotificationMessage(vscode_languageserver_protocol_1.MessageType.Warning, message, data); } } info(message, data, showNotification = true) { if (this.shouldLogToOutputChannel()) { this.outputChannel.info(this.getLogMessage(message, data)); } if (showNotification && this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Info) { this.showNotificationMessage(vscode_languageserver_protocol_1.MessageType.Info, message, data); } } debug(message, data, showNotification = true) { if (this.shouldLogToOutputChannel()) { this.outputChannel.debug(this.getLogMessage(message, data)); } if (showNotification && this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Debug) { this.showNotificationMessage(vscode_languageserver_protocol_1.MessageType.Debug, message, data); } } trace(message, data) { this.traceOutputChannel.trace(this.getLogMessage(message, data)); } traceObject(data) { this.traceOutputChannel.trace(JSON.stringify(data)); } showNotificationMessage(type, message, data) { message = message ?? 'A request has failed. See the output for more information.'; if (data) { message += '\n' + this.data2String(data); } 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); } }); } getLogMessage(message, data) { return data !== null && data !== undefined ? `${message}\n${this.data2String(data)}` : message; } 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; // A previous shutdown disposed the diagnostic collection and set it to // `null` to prevent recreation. On (re)start we re-enable lazy creation. this._diagnostics = undefined; // 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; } const 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, token) => { 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, token, 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