"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");
/**
 * Controls when the output channel is revealed.
 */
var RevealOutputChannelOn;
(function (RevealOutputChannelOn) {
    RevealOutputChannelOn[RevealOutputChannelOn["Info"] = 1] = "Info";
    RevealOutputChannelOn[RevealOutputChannelOn["Warn"] = 2] = "Warn";
    RevealOutputChannelOn[RevealOutputChannelOn["Error"] = 3] = "Error";
    RevealOutputChannelOn[RevealOutputChannelOn["Never"] = 4] = "Never";
})(RevealOutputChannelOn = exports.RevealOutputChannelOn || (exports.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 || (exports.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 || (exports.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 || (exports.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 || (exports.SuspendMode = {}));
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 || (exports.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 = clientOptions.markdown.isTrusted === true;
            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);
        }
        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);
        }
        return 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();
    }
    info(message, data, showNotification = true) {
        this.outputChannel.appendLine(`[Info  - ${(new Date().toLocaleTimeString())}] ${message}`);
        if (data !== null && data !== undefined) {
            this.outputChannel.appendLine(this.data2String(data));
        }
        if (showNotification && this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Info) {
            this.showNotificationMessage(vscode_languageserver_protocol_1.MessageType.Info, message);
        }
    }
    warn(message, data, showNotification = true) {
        this.outputChannel.appendLine(`[Warn  - ${(new Date().toLocaleTimeString())}] ${message}`);
        if (data !== null && data !== undefined) {
            this.outputChannel.appendLine(this.data2String(data));
        }
        if (showNotification && this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Warn) {
            this.showNotificationMessage(vscode_languageserver_protocol_1.MessageType.Warning, message);
        }
    }
    error(message, data, showNotification = true) {
        this.outputChannel.appendLine(`[Error - ${(new Date().toLocaleTimeString())}] ${message}`);
        if (data !== null && data !== undefined) {
            this.outputChannel.appendLine(this.data2String(data));
        }
        if (showNotification === 'force' || (showNotification && this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Error)) {
            this.showNotificationMessage(vscode_languageserver_protocol_1.MessageType.Error, 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;
                    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();
        }
        // Dispose features in reverse order;
        for (const feature of Array.from(this._features.entries()).map(entry => entry[1]).reverse()) {
            feature.dispose();
        }
        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. Shutting down server.`, undefined, handlerResult.handled === true ? false : 'force');
            this.stop().catch((error) => {
                this.error(`Stopping server failed`, error, false);
            });
        }
    }
    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) {
        // 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) {
        for (let 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 = [];
        return result;
    }
    ProposedFeatures.createAll = createAll;
})(ProposedFeatures = exports.ProposedFeatures || (exports.ProposedFeatures = {}));
