vscode-languageclient
Version:
VSCode Language client implementation
1,136 lines • 89.8 kB
JavaScript
"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