ifc-language-server/client/node_modules/vscode-languageclient/lib/common/client.js
Ryan Schultz 8afacf268a Implemented a working Language Server Protocol (LSP) for IFC files with:
- Hover provider showing entity information and type
- Go-to-definition (F12) for entity references
- Basic IFC file validation (ISO-10303-21 header check)
- Entity parsing with regex-based detection
- Proper CommonJS module system (avoiding ES module issues)

This replaces the broken baseline from ifc-developer-tools which had:
- Non-functional ES module configuration
- Circular dependency issues
- Parser crashes
- Non-working PositionVisitor

Built on Microsoft's LSP example template for a clean, maintainable foundation.

Next: Add hierarchical entity dependency tree in hover tooltip."
2025-12-07 10:20:07 -06:00

1600 lines
74 KiB
JavaScript

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