/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
'use strict';
function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
const cp = require("child_process");
const vscode_1 = require("vscode");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
exports.ErrorCodes = vscode_jsonrpc_1.ErrorCodes;
exports.ResponseError = vscode_jsonrpc_1.ResponseError;
exports.RequestType = vscode_jsonrpc_1.RequestType;
exports.RequestType0 = vscode_jsonrpc_1.RequestType0;
exports.NotificationType = vscode_jsonrpc_1.NotificationType;
exports.NotificationType0 = vscode_jsonrpc_1.NotificationType0;
const protocol_1 = require("./protocol");
exports.InitializeError = protocol_1.InitializeError;
const c2p = require("./codeConverter");
const p2c = require("./protocolConverter");
const is = require("./utils/is");
const electron = require("./utils/electron");
const processes_1 = require("./utils/processes");
const async_1 = require("./utils/async");
const UUID = require("./utils/uuid");
__export(require("vscode-languageserver-types"));
__export(require("./protocol"));
class ConsoleLogger {
    error(message) {
        console.error(message);
    }
    warn(message) {
        console.warn(message);
    }
    info(message) {
        console.info(message);
    }
    log(message) {
        console.log(message);
    }
}
function createConnection(input, output, errorHandler, closeHandler) {
    let logger = new ConsoleLogger();
    let connection = vscode_jsonrpc_1.createMessageConnection(input, output, logger);
    connection.onError((data) => { errorHandler(data[0], data[1], data[2]); });
    connection.onClose(closeHandler);
    let result = {
        listen: () => connection.listen(),
        sendRequest: (type, ...params) => connection.sendRequest(is.string(type) ? type : type.method, ...params),
        onRequest: (type, handler) => connection.onRequest(is.string(type) ? type : type.method, handler),
        sendNotification: (type, params) => connection.sendNotification(is.string(type) ? type : type.method, params),
        onNotification: (type, handler) => connection.onNotification(is.string(type) ? type : type.method, handler),
        trace: (value, tracer, sendNotification = false) => connection.trace(value, tracer, sendNotification),
        initialize: (params) => connection.sendRequest(protocol_1.InitializeRequest.type, params),
        shutdown: () => connection.sendRequest(protocol_1.ShutdownRequest.type, undefined),
        exit: () => connection.sendNotification(protocol_1.ExitNotification.type),
        onLogMessage: (handler) => connection.onNotification(protocol_1.LogMessageNotification.type, handler),
        onShowMessage: (handler) => connection.onNotification(protocol_1.ShowMessageNotification.type, handler),
        onTelemetry: (handler) => connection.onNotification(protocol_1.TelemetryEventNotification.type, handler),
        didChangeConfiguration: (params) => connection.sendNotification(protocol_1.DidChangeConfigurationNotification.type, params),
        didChangeWatchedFiles: (params) => connection.sendNotification(protocol_1.DidChangeWatchedFilesNotification.type, params),
        didOpenTextDocument: (params) => connection.sendNotification(protocol_1.DidOpenTextDocumentNotification.type, params),
        didChangeTextDocument: (params) => connection.sendNotification(protocol_1.DidChangeTextDocumentNotification.type, params),
        didCloseTextDocument: (params) => connection.sendNotification(protocol_1.DidCloseTextDocumentNotification.type, params),
        didSaveTextDocument: (params) => connection.sendNotification(protocol_1.DidSaveTextDocumentNotification.type, params),
        onDiagnostics: (handler) => connection.onNotification(protocol_1.PublishDiagnosticsNotification.type, handler),
        dispose: () => connection.dispose()
    };
    return result;
}
var TransportKind;
(function (TransportKind) {
    TransportKind[TransportKind["stdio"] = 0] = "stdio";
    TransportKind[TransportKind["ipc"] = 1] = "ipc";
    TransportKind[TransportKind["pipe"] = 2] = "pipe";
})(TransportKind = exports.TransportKind || (exports.TransportKind = {}));
/**
 * 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 = {}));
class DefaultErrorHandler {
    constructor(name) {
        this.name = name;
        this.restarts = [];
    }
    error(_error, _message, count) {
        if (count && count <= 3) {
            return ErrorAction.Continue;
        }
        return ErrorAction.Shutdown;
    }
    closed() {
        this.restarts.push(Date.now());
        if (this.restarts.length < 5) {
            return CloseAction.Restart;
        }
        else {
            let diff = this.restarts[this.restarts.length - 1] - this.restarts[0];
            if (diff <= 3 * 60 * 1000) {
                vscode_1.window.showErrorMessage(`The ${this.name} server crashed 5 times in the last 3 minutes. The server will not be restarted.`);
                return CloseAction.DoNotRestart;
            }
            else {
                this.restarts.shift();
                return CloseAction.Restart;
            }
        }
    }
}
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 = {}));
var State;
(function (State) {
    State[State["Stopped"] = 1] = "Stopped";
    State[State["Running"] = 2] = "Running";
})(State = exports.State || (exports.State = {}));
var ClientState;
(function (ClientState) {
    ClientState[ClientState["Initial"] = 0] = "Initial";
    ClientState[ClientState["Starting"] = 1] = "Starting";
    ClientState[ClientState["StartFailed"] = 2] = "StartFailed";
    ClientState[ClientState["Running"] = 3] = "Running";
    ClientState[ClientState["Stopping"] = 4] = "Stopping";
    ClientState[ClientState["Stopped"] = 5] = "Stopped";
})(ClientState || (ClientState = {}));
class DocumentNotifiactions {
    constructor(_client, _event, _type, _createParams, _selectorFilter) {
        this._client = _client;
        this._event = _event;
        this._type = _type;
        this._createParams = _createParams;
        this._selectorFilter = _selectorFilter;
        this._selectors = new Map();
    }
    static textDocumentFilter(selectors, textDocument) {
        for (const selector of selectors) {
            if (vscode_1.languages.match(selector, textDocument)) {
                return true;
            }
        }
        return false;
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        if (!this._listener) {
            this._listener = this._event(this.callback, this);
        }
        this._selectors.set(data.id, data.registerOptions.documentSelector);
    }
    callback(data) {
        if (!this._selectorFilter || this._selectorFilter(this._selectors.values(), data)) {
            this._client.sendNotification(this._type, this._createParams(data));
            this.notificationSent(data);
        }
    }
    notificationSent(_data) {
    }
    unregister(id) {
        this._selectors.delete(id);
        if (this._selectors.size === 0 && this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    dispose() {
        if (this._listener) {
            this._listener.dispose();
        }
    }
}
class DidOpenTextDocumentFeature extends DocumentNotifiactions {
    constructor(client, _syncedDocuments) {
        super(client, vscode_1.workspace.onDidOpenTextDocument, protocol_1.DidOpenTextDocumentNotification.type, (textDocument) => client.code2ProtocolConverter.asOpenTextDocumentParams(textDocument), DocumentNotifiactions.textDocumentFilter);
        this._syncedDocuments = _syncedDocuments;
    }
    register(data) {
        super.register(data);
        if (!data.registerOptions.documentSelector) {
            return;
        }
        let documentSelector = data.registerOptions.documentSelector;
        vscode_1.workspace.textDocuments.forEach((textDocument) => {
            let uri = textDocument.uri.toString();
            if (this._syncedDocuments.has(uri)) {
                return;
            }
            if (vscode_1.languages.match(documentSelector, textDocument)) {
                this._client.sendNotification(this._type, this._createParams(textDocument));
                this._syncedDocuments.set(uri, textDocument);
            }
        });
    }
    notificationSent(textDocument) {
        super.notificationSent(textDocument);
        this._syncedDocuments.set(textDocument.uri.toString(), textDocument);
    }
}
class DidCloseTextDocumentFeature extends DocumentNotifiactions {
    constructor(client, _syncedDocuments) {
        super(client, vscode_1.workspace.onDidCloseTextDocument, protocol_1.DidCloseTextDocumentNotification.type, (textDocument) => client.code2ProtocolConverter.asCloseTextDocumentParams(textDocument), DocumentNotifiactions.textDocumentFilter);
        this._syncedDocuments = _syncedDocuments;
    }
    notificationSent(textDocument) {
        super.notificationSent(textDocument);
        this._syncedDocuments.delete(textDocument.uri.toString());
    }
    unregister(id) {
        let selector = this._selectors.get(id);
        super.unregister(id);
        let selectors = this._selectors.values();
        this._syncedDocuments.forEach((textDocument) => {
            if (vscode_1.languages.match(selector, textDocument) && !this._selectorFilter(selectors, textDocument)) {
                this._client.sendNotification(this._type, this._createParams(textDocument));
                this._syncedDocuments.delete(textDocument.uri.toString());
            }
        });
    }
}
class DidChangeTextDocumentFeature {
    constructor(_client) {
        this._client = _client;
        this._changeData = new Map();
        this._forcingDelivery = false;
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        if (!this._listener) {
            this._listener = vscode_1.workspace.onDidChangeTextDocument(this.callback, this);
        }
        this._changeData.set(data.id, {
            documentSelector: data.registerOptions.documentSelector,
            syncKind: data.registerOptions.syncKind
        });
    }
    callback(event) {
        for (const changeData of this._changeData.values()) {
            if (vscode_1.languages.match(changeData.documentSelector, event.document)) {
                if (changeData.syncKind === protocol_1.TextDocumentSyncKind.Incremental) {
                    this._client.sendNotification(protocol_1.DidChangeTextDocumentNotification.type, this._client.code2ProtocolConverter.asChangeTextDocumentParams(event));
                    break;
                }
                else if (changeData.syncKind === protocol_1.TextDocumentSyncKind.Full) {
                    if (this._changeDelayer) {
                        if (this._changeDelayer.uri !== event.document.uri.toString()) {
                            // Use this force delivery to track boolean state. Otherwise we might call two times.
                            this.forceDelivery();
                            this._changeDelayer.uri = event.document.uri.toString();
                        }
                        this._changeDelayer.delayer.trigger(() => {
                            this._client.sendNotification(protocol_1.DidChangeTextDocumentNotification.type, this._client.code2ProtocolConverter.asChangeTextDocumentParams(event.document));
                        });
                    }
                    else {
                        this._changeDelayer = {
                            uri: event.document.uri.toString(),
                            delayer: new async_1.Delayer(200)
                        };
                        this._changeDelayer.delayer.trigger(() => {
                            this._client.sendNotification(protocol_1.DidChangeTextDocumentNotification.type, this._client.code2ProtocolConverter.asChangeTextDocumentParams(event.document));
                        }, -1);
                    }
                    break;
                }
            }
        }
    }
    unregister(id) {
        this._changeData.delete(id);
        if (this._changeData.size === 0 && this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    dispose() {
        if (this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    forceDelivery() {
        if (this._forcingDelivery || !this._changeDelayer) {
            return;
        }
        try {
            this._forcingDelivery = true;
            this._changeDelayer.delayer.forceDelivery();
        }
        finally {
            this._forcingDelivery = false;
        }
    }
}
class WillSaveWaitUntilFeature {
    constructor(_client) {
        this._client = _client;
        this._selectors = new Map();
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        if (!this._listener) {
            this._listener = vscode_1.workspace.onWillSaveTextDocument(this.callback, this);
        }
        this._selectors.set(data.id, data.registerOptions.documentSelector);
    }
    callback(event) {
        if (DocumentNotifiactions.textDocumentFilter(this._selectors.values(), event.document)) {
            event.waitUntil(this._client.sendRequest(protocol_1.WillSaveTextDocumentWaitUntilRequest.type, this._client.code2ProtocolConverter.asWillSaveTextDocumentParams(event)).then((edits) => {
                return this._client.protocol2CodeConverter.asTextEdits(edits);
            }));
        }
    }
    unregister(id) {
        this._selectors.delete(id);
        if (this._selectors.size === 0 && this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    dispose() {
        if (this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
}
class DidSaveTextDocumentFeature extends DocumentNotifiactions {
    constructor(client) {
        super(client, vscode_1.workspace.onDidSaveTextDocument, protocol_1.DidSaveTextDocumentNotification.type, (textDocument) => client.code2ProtocolConverter.asSaveTextDocumentParams(textDocument, this._includeText), DocumentNotifiactions.textDocumentFilter);
    }
    register(data) {
        this._includeText = !!data.registerOptions.includeText;
        super.register(data);
    }
}
class LanguageFeature {
    constructor(_createProvider) {
        this._createProvider = _createProvider;
        this._providers = new Map();
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        let provider = this._createProvider(data.registerOptions);
        if (provider) {
            this._providers.set(data.id, provider);
        }
    }
    unregister(id) {
        let provider = this._providers.get(id);
        if (provider) {
            provider.dispose();
        }
    }
    dispose() {
        this._providers.forEach((value) => {
            value.dispose();
        });
    }
}
class ExecuteCommandFeature {
    constructor(_client, _logger) {
        this._client = _client;
        this._logger = _logger;
        this._commands = new Map();
    }
    register(data) {
        if (data.registerOptions.commands) {
            let disposeables = [];
            for (const command of data.registerOptions.commands) {
                disposeables.push(vscode_1.commands.registerCommand(command, (...args) => {
                    let params = {
                        command,
                        arguments: args
                    };
                    this._client.sendRequest(protocol_1.ExecuteCommandRequest.type, params).then(undefined, (error) => { this._logger(protocol_1.ExecuteCommandRequest.type, error); });
                }));
            }
            this._commands.set(data.id, disposeables);
        }
    }
    unregister(id) {
        let disposeables = this._commands.get(id);
        if (disposeables) {
            disposeables.forEach(disposable => disposable.dispose());
        }
    }
    dispose() {
        this._commands.forEach((value) => {
            value.forEach(disposable => disposable.dispose());
        });
    }
}
const clientCapabilities = {
    workspace: {
        applyEdit: true,
        workspaceEdit: {
            documentChanges: true
        },
        didChangeConfiguration: {
            dynamicRegistration: false
        },
        didChangeWatchedFiles: {
            dynamicRegistration: false
        },
        symbol: {
            dynamicRegistration: true
        },
        executeCommand: {
            dynamicRegistration: true
        }
    },
    textDocument: {
        synchronization: {
            dynamicRegistration: true,
            willSave: true,
            willSaveWaitUntil: true,
            didSave: true
        },
        completion: {
            dynamicRegistration: true,
            completionItem: {
                snippetSupport: true
            }
        },
        hover: {
            dynamicRegistration: true
        },
        signatureHelp: {
            dynamicRegistration: true
        },
        references: {
            dynamicRegistration: true
        },
        documentHighlight: {
            dynamicRegistration: true
        },
        documentSymbol: {
            dynamicRegistration: true
        },
        formatting: {
            dynamicRegistration: true
        },
        rangeFormatting: {
            dynamicRegistration: true
        },
        onTypeFormatting: {
            dynamicRegistration: true
        },
        definition: {
            dynamicRegistration: true
        },
        codeAction: {
            dynamicRegistration: true
        },
        codeLens: {
            dynamicRegistration: true
        },
        documentLink: {
            dynamicRegistration: true
        },
        rename: {
            dynamicRegistration: true
        }
    }
};
class LanguageClient {
    constructor(arg1, arg2, arg3, arg4, arg5) {
        this._registeredHandlers = new Map();
        let clientOptions;
        let forceDebug;
        if (is.string(arg2)) {
            this._id = arg1;
            this._name = arg2;
            this._serverOptions = arg3;
            clientOptions = arg4;
            forceDebug = !!arg5;
        }
        else {
            this._id = arg1.toLowerCase();
            this._name = arg1;
            this._serverOptions = arg2;
            clientOptions = arg3;
            forceDebug = arg4;
        }
        if (forceDebug === void 0) {
            forceDebug = false;
        }
        clientOptions = clientOptions || {};
        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,
            errorHandler: clientOptions.errorHandler || new DefaultErrorHandler(this._name),
            uriConverters: clientOptions.uriConverters
        };
        this._clientOptions.synchronize = this._clientOptions.synchronize || {};
        this._forceDebug = forceDebug;
        this.state = ClientState.Initial;
        this._connectionPromise = undefined;
        this._resolvedConnection = undefined;
        this._childProcess = undefined;
        this._outputChannel = undefined;
        this._listeners = undefined;
        this._providers = undefined;
        this._diagnostics = undefined;
        this._fileEvents = [];
        this._fileEventDelayer = new async_1.Delayer(250);
        this._onReady = new Promise((resolve, reject) => {
            this._onReadyCallbacks = { resolve, reject };
        });
        this._telemetryEmitter = new vscode_jsonrpc_1.Emitter();
        this._stateChangeEmitter = new vscode_jsonrpc_1.Emitter();
        this._tracer = {
            log: (message, data) => {
                this.logTrace(message, data);
            }
        };
        this._c2p = c2p.createConverter(clientOptions.uriConverters ? clientOptions.uriConverters.code2Protocol : undefined);
        this._p2c = p2c.createConverter(clientOptions.uriConverters ? clientOptions.uriConverters.protocol2Code : undefined);
    }
    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() {
        if (this.state === ClientState.Running) {
            return State.Running;
        }
        else {
            return State.Stopped;
        }
    }
    sendRequest(type, ...params) {
        if (!this.isConnectionActive()) {
            throw new Error('Language client is not ready yet');
        }
        this.forceDocumentSync();
        try {
            return this._resolvedConnection.sendRequest(type, ...params);
        }
        catch (error) {
            this.error(`Sending request ${is.string(type) ? type : type.method} failed.`, error);
            throw error;
        }
    }
    onRequest(type, handler) {
        if (!this.isConnectionActive()) {
            throw new Error('Language client is not ready yet');
        }
        try {
            this._resolvedConnection.onRequest(type, handler);
        }
        catch (error) {
            this.error(`Registering request handler ${is.string(type) ? type : type.method} failed.`, error);
            throw error;
        }
    }
    sendNotification(type, params) {
        if (!this.isConnectionActive()) {
            throw new Error('Language client is not ready yet');
        }
        this.forceDocumentSync();
        try {
            this._resolvedConnection.sendNotification(type, params);
        }
        catch (error) {
            this.error(`Sending notification ${is.string(type) ? type : type.method} failed.`, error);
            throw error;
        }
    }
    onNotification(type, handler) {
        if (!this.isConnectionActive()) {
            throw new Error('Language client is not ready yet');
        }
        try {
            this._resolvedConnection.onNotification(type, handler);
        }
        catch (error) {
            this.error(`Registering notification handler ${is.string(type) ? type : type.method} failed.`, error);
            throw error;
        }
    }
    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 diagnostics() {
        return this._diagnostics;
    }
    createDefaultErrorHandler() {
        return new DefaultErrorHandler(this._name);
    }
    set trace(value) {
        this._trace = value;
        this.onReady().then(() => {
            this.resolveConnection().then((connection) => {
                connection.trace(value, this._tracer);
            });
        }, () => {
        });
    }
    data2String(data) {
        if (data instanceof vscode_jsonrpc_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) {
        this.outputChannel.appendLine(`[Info  - ${(new Date().toLocaleTimeString())}] ${message}`);
        if (data) {
            this.outputChannel.appendLine(this.data2String(data));
        }
        if (this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Info) {
            this.outputChannel.show(true);
        }
    }
    warn(message, data) {
        this.outputChannel.appendLine(`[Warn  - ${(new Date().toLocaleTimeString())}] ${message}`);
        if (data) {
            this.outputChannel.appendLine(this.data2String(data));
        }
        if (this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Warn) {
            this.outputChannel.show(true);
        }
    }
    error(message, data) {
        this.outputChannel.appendLine(`[Error - ${(new Date().toLocaleTimeString())}] ${message}`);
        if (data) {
            this.outputChannel.appendLine(this.data2String(data));
        }
        if (this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Error) {
            this.outputChannel.show(true);
        }
    }
    logTrace(message, data) {
        this.outputChannel.appendLine(`[Trace - ${(new Date().toLocaleTimeString())}] ${message}`);
        if (data) {
            this.outputChannel.appendLine(this.data2String(data));
        }
        this.outputChannel.show(true);
    }
    needsStart() {
        return this.state === ClientState.Initial || this.state === ClientState.Stopping || this.state === ClientState.Stopped;
    }
    needsStop() {
        return this.state === ClientState.Starting || this.state === ClientState.Running;
    }
    onReady() {
        return this._onReady;
    }
    isConnectionActive() {
        return this.state === ClientState.Running && !!this._resolvedConnection;
    }
    start() {
        this._listeners = [];
        this._providers = [];
        // If we restart then the diagnostics collection is reused.
        if (!this._diagnostics) {
            this._diagnostics = this._clientOptions.diagnosticCollectionName
                ? vscode_1.languages.createDiagnosticCollection(this._clientOptions.diagnosticCollectionName)
                : vscode_1.languages.createDiagnosticCollection();
        }
        this.state = ClientState.Starting;
        this.resolveConnection().then((connection) => {
            connection.onLogMessage((message) => {
                switch (message.type) {
                    case protocol_1.MessageType.Error:
                        this.error(message.message);
                        break;
                    case protocol_1.MessageType.Warning:
                        this.warn(message.message);
                        break;
                    case protocol_1.MessageType.Info:
                        this.info(message.message);
                        break;
                    default:
                        this.outputChannel.appendLine(message.message);
                }
            });
            connection.onShowMessage((message) => {
                switch (message.type) {
                    case protocol_1.MessageType.Error:
                        vscode_1.window.showErrorMessage(message.message);
                        break;
                    case protocol_1.MessageType.Warning:
                        vscode_1.window.showWarningMessage(message.message);
                        break;
                    case protocol_1.MessageType.Info:
                        vscode_1.window.showInformationMessage(message.message);
                        break;
                    default:
                        vscode_1.window.showInformationMessage(message.message);
                }
            });
            connection.onRequest(protocol_1.ShowMessageRequest.type, (params) => {
                let messageFunc;
                switch (params.type) {
                    case protocol_1.MessageType.Error:
                        messageFunc = vscode_1.window.showErrorMessage;
                        break;
                    case protocol_1.MessageType.Warning:
                        messageFunc = vscode_1.window.showWarningMessage;
                        break;
                    case 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.onTelemetry((data) => {
                this._telemetryEmitter.fire(data);
            });
            this.initRegistrationHandlers(connection);
            connection.listen();
            // Error is handled in the intialize call.
            this.initialize(connection).then(undefined, () => { });
        }, (error) => {
            this.state = ClientState.StartFailed;
            this._onReadyCallbacks.reject(error);
            this.error('Starting client failed', error);
            vscode_1.window.showErrorMessage(`Couldn't start client ${this._name}`);
        });
        return new vscode_1.Disposable(() => {
            if (this.needsStop()) {
                this.stop();
            }
        });
    }
    resolveConnection() {
        if (!this._connectionPromise) {
            this._connectionPromise = this.createConnection();
        }
        return this._connectionPromise;
    }
    initialize(connection) {
        this.refreshTrace(connection, false);
        let initOption = this._clientOptions.initializationOptions;
        let initParams = {
            processId: process.pid,
            rootPath: vscode_1.workspace.rootPath ? vscode_1.workspace.rootPath : null,
            rootUri: vscode_1.workspace.rootPath ? vscode_1.Uri.file(vscode_1.workspace.rootPath).toString() : null,
            capabilities: clientCapabilities,
            initializationOptions: is.func(initOption) ? initOption() : initOption,
            trace: vscode_jsonrpc_1.Trace.toString(this._trace)
        };
        return connection.initialize(initParams).then((result) => {
            this._resolvedConnection = connection;
            this.state = ClientState.Running;
            this._capabilites = result.capabilities;
            connection.onDiagnostics(params => this.handleDiagnostics(params));
            connection.onRequest(protocol_1.RegistrationRequest.type, params => this.handleRegistrationRequest(params));
            connection.onRequest(protocol_1.UnregistrationRequest.type, params => this.handleUnregistrationRequest(params));
            connection.onRequest(protocol_1.ApplyWorkspaceEditRequest.type, params => this.handleApplyWorkspaceEdit(params));
            connection.sendNotification(protocol_1.InitializedNotification.type, {});
            this.hookFileEvents(connection);
            this.hookConfigurationChanged(connection);
            if (this._clientOptions.documentSelector) {
                let selectorOptions = { documentSelector: this._clientOptions.documentSelector };
                let textDocumentSyncOptions = undefined;
                if (is.number(this._capabilites.textDocumentSync) && this._capabilites.textDocumentSync !== protocol_1.TextDocumentSyncKind.None) {
                    textDocumentSyncOptions = {
                        openClose: true,
                        change: this._capabilites.textDocumentSync,
                        save: {
                            includeText: false
                        }
                    };
                }
                else if (this._capabilites.textDocumentSync !== void 0 && this._capabilites.textDocumentSync === null) {
                    textDocumentSyncOptions = this._capabilites.textDocumentSync;
                }
                if (textDocumentSyncOptions) {
                    let registeredHandlers = this._registeredHandlers;
                    if (textDocumentSyncOptions.openClose) {
                        registeredHandlers.get(protocol_1.DidOpenTextDocumentNotification.type.method).register({ id: UUID.generateUuid(), registerOptions: selectorOptions });
                        registeredHandlers.get(protocol_1.DidCloseTextDocumentNotification.type.method).register({ id: UUID.generateUuid(), registerOptions: selectorOptions });
                    }
                    if (textDocumentSyncOptions.change !== protocol_1.TextDocumentSyncKind.None) {
                        registeredHandlers.get(protocol_1.DidChangeTextDocumentNotification.type.method).register({
                            id: UUID.generateUuid(),
                            registerOptions: Object.assign({}, selectorOptions, { syncKind: textDocumentSyncOptions.change })
                        });
                    }
                    if (textDocumentSyncOptions.willSave) {
                        registeredHandlers.get(protocol_1.WillSaveTextDocumentNotification.type.method).register({ id: UUID.generateUuid(), registerOptions: selectorOptions });
                    }
                    if (textDocumentSyncOptions.willSaveWaitUntil) {
                        registeredHandlers.get(protocol_1.WillSaveTextDocumentWaitUntilRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: selectorOptions });
                    }
                    if (textDocumentSyncOptions.save) {
                        registeredHandlers.get(protocol_1.DidSaveTextDocumentNotification.type.method).register({
                            id: UUID.generateUuid(),
                            registerOptions: Object.assign({}, selectorOptions, { includeText: !!textDocumentSyncOptions.save.includeText })
                        });
                    }
                }
            }
            this.hookCapabilities(connection);
            this._onReadyCallbacks.resolve();
            return result;
        }, (error) => {
            if (this._clientOptions.initializationFailedHandler) {
                if (this._clientOptions.initializationFailedHandler(error)) {
                    this.initialize(connection);
                }
                else {
                    this.stop();
                    this._onReadyCallbacks.reject(error);
                }
            }
            else if (error instanceof vscode_jsonrpc_1.ResponseError && error.data && error.data.retry) {
                vscode_1.window.showErrorMessage(error.message, { title: 'Retry', id: "retry" }).then(item => {
                    if (item && item.id === 'retry') {
                        this.initialize(connection);
                    }
                    else {
                        this.stop();
                        this._onReadyCallbacks.reject(error);
                    }
                });
            }
            else {
                if (error && error.message) {
                    vscode_1.window.showErrorMessage(error.message);
                }
                this.error('Server initialization failed.', error);
                this.stop();
                this._onReadyCallbacks.reject(error);
            }
        });
    }
    stop() {
        if (!this._connectionPromise) {
            this.state = ClientState.Stopped;
            return Promise.resolve();
        }
        this.state = ClientState.Stopping;
        this.cleanUp();
        // unkook listeners
        return this.resolveConnection().then(connection => {
            return connection.shutdown().then(() => {
                connection.exit();
                connection.dispose();
                this.state = ClientState.Stopped;
                this._connectionPromise = undefined;
                this._resolvedConnection = undefined;
                let toCheck = this._childProcess;
                this._childProcess = undefined;
                // Remove all markers
                this.checkProcessDied(toCheck);
            });
        });
    }
    cleanUp(diagnostics = true) {
        if (this._listeners) {
            this._listeners.forEach(listener => listener.dispose());
            this._listeners = undefined;
        }
        if (this._providers) {
            this._providers.forEach(provider => provider.dispose());
            this._providers = undefined;
        }
        if (diagnostics && this._diagnostics) {
            this._diagnostics.dispose();
            this._diagnostics = undefined;
        }
    }
    notifyFileEvent(event) {
        this._fileEvents.push(event);
        this._fileEventDelayer.trigger(() => {
            this.onReady().then(() => {
                this.resolveConnection().then(connection => {
                    if (this.isConnectionActive()) {
                        connection.didChangeWatchedFiles({ changes: this._fileEvents });
                    }
                    this._fileEvents = [];
                });
            }, (error) => {
                this.error(`Notify file events failed.`, error);
            });
        });
    }
    forceDocumentSync() {
        this._registeredHandlers.get(protocol_1.DidChangeTextDocumentNotification.type.method).forceDelivery();
    }
    handleDiagnostics(params) {
        if (!this._diagnostics) {
            return;
        }
        let uri = this._p2c.asUri(params.uri);
        let diagnostics = this._p2c.asDiagnostics(params.diagnostics);
        this._diagnostics.set(uri, diagnostics);
    }
    createConnection() {
        function getEnvironment(env) {
            if (!env) {
                return process.env;
            }
            let result = Object.create(null);
            Object.keys(process.env).forEach(key => result[key] = process.env[key]);
            Object.keys(env).forEach(key => result[key] = env[key]);
        }
        function startedInDebugMode() {
            let args = process.execArgv;
            if (args) {
                return args.some((arg) => /^--debug=?/.test(arg) || /^--debug-brk=?/.test(arg));
            }
            ;
            return false;
        }
        let encoding = this._clientOptions.stdioEncoding;
        let errorHandler = (error, message, count) => {
            this.handleConnectionError(error, message, count);
        };
        let closeHandler = () => {
            this.handleConnectionClosed();
        };
        let server = this._serverOptions;
        // We got a function.
        if (is.func(server)) {
            return server().then((result) => {
                let info = result;
                if (info.writer && info.reader) {
                    return createConnection(info.reader, info.writer, errorHandler, closeHandler);
                }
                else {
                    let cp = result;
                    return createConnection(cp.stdout, cp.stdin, errorHandler, closeHandler);
                }
            });
        }
        let json;
        let runDebug = server;
        if (runDebug.run || runDebug.debug) {
            // We are under debugging. So use debug as well.
            if (typeof v8debug === 'object' || this._forceDebug || startedInDebugMode()) {
                json = runDebug.debug;
            }
            else {
                json = runDebug.run;
            }
        }
        else {
            json = server;
        }
        if (json.module) {
            let node = json;
            let transport = node.transport || TransportKind.stdio;
            if (node.runtime) {
                let args = [];
                let options = node.options || Object.create(null);
                if (options.execArgv) {
                    options.execArgv.forEach(element => args.push(element));
                }
                args.push(node.module);
                if (node.args) {
                    node.args.forEach(element => args.push(element));
                }
                let execOptions = Object.create(null);
                execOptions.cwd = options.cwd || vscode_1.workspace.rootPath;
                execOptions.env = getEnvironment(options.env);
                let pipeName = undefined;
                if (transport === TransportKind.ipc) {
                    // exec options not correctly typed in lib
                    execOptions.stdio = [null, null, null, 'ipc'];
                    args.push('--node-ipc');
                }
                else if (transport === TransportKind.stdio) {
                    args.push('--stdio');
                }
                else if (transport === TransportKind.pipe) {
                    pipeName = vscode_jsonrpc_1.generateRandomPipeName();
                    args.push(`--pipe=${pipeName}`);
                }
                if (transport === TransportKind.ipc || transport === TransportKind.stdio) {
                    let process = cp.spawn(node.runtime, args, execOptions);
                    if (!process || !process.pid) {
                        return Promise.reject(`Launching server using runtime ${node.runtime} failed.`);
                    }
                    this._childProcess = process;
                    process.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                    if (transport === TransportKind.ipc) {
                        process.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                        return Promise.resolve(createConnection(new vscode_jsonrpc_1.IPCMessageReader(process), new vscode_jsonrpc_1.IPCMessageWriter(process), errorHandler, closeHandler));
                    }
                    else {
                        return Promise.resolve(createConnection(process.stdout, process.stdin, errorHandler, closeHandler));
                    }
                }
                else if (transport == TransportKind.pipe) {
                    return vscode_jsonrpc_1.createClientPipeTransport(pipeName).then((transport) => {
                        let process = cp.spawn(node.runtime, args, execOptions);
                        if (!process || !process.pid) {
                            return Promise.reject(`Launching server using runtime ${node.runtime} failed.`);
                        }
                        this._childProcess = process;
                        process.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                        process.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                        return transport.onConnected().then((protocol) => {
                            return createConnection(protocol[0], protocol[1], errorHandler, closeHandler);
                        });
                    });
                }
            }
            else {
                let pipeName = undefined;
                return new Promise((resolve, reject) => {
                    let args = node.args && node.args.slice() || [];
                    if (transport === TransportKind.ipc) {
                        args.push('--node-ipc');
                    }
                    else if (transport === TransportKind.stdio) {
                        args.push('--stdio');
                    }
                    else if (transport === TransportKind.pipe) {
                        pipeName = vscode_jsonrpc_1.generateRandomPipeName();
                        args.push(`--pipe=${pipeName}`);
                    }
                    let options = node.options || Object.create(null);
                    options.execArgv = options.execArgv || [];
                    options.cwd = options.cwd || vscode_1.workspace.rootPath;
                    if (transport === TransportKind.ipc || transport === TransportKind.stdio) {
                        electron.fork(node.module, args || [], options, (error, cp) => {
                            if (error || !cp) {
                                reject(error);
                            }
                            else {
                                this._childProcess = cp;
                                cp.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                                if (transport === TransportKind.ipc) {
                                    cp.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                                    resolve(createConnection(new vscode_jsonrpc_1.IPCMessageReader(this._childProcess), new vscode_jsonrpc_1.IPCMessageWriter(this._childProcess), errorHandler, closeHandler));
                                }
                                else {
                                    resolve(createConnection(cp.stdout, cp.stdin, errorHandler, closeHandler));
                                }
                            }
                        });
                    }
                    else if (transport === TransportKind.pipe) {
                        vscode_jsonrpc_1.createClientPipeTransport(pipeName).then((transport) => {
                            electron.fork(node.module, args || [], options, (error, cp) => {
                                if (error || !cp) {
                                    reject(error);
                                }
                                else {
                                    this._childProcess = cp;
                                    cp.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                                    cp.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
                                    transport.onConnected().then((protocol) => {
                                        resolve(createConnection(protocol[0], protocol[1], errorHandler, closeHandler));
                                    });
                                }
                            });
                        });
                    }
                });
            }
        }
        else if (json.command) {
            let command = json;
            let options = command.options || {};
            options.cwd = options.cwd || vscode_1.workspace.rootPath;
            let process = cp.spawn(command.command, command.args, command.options);
            if (!process || !process.pid) {
                return Promise.reject(`Launching server using command ${command.command} failed.`);
            }
            process.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding)));
            this._childProcess = process;
            return Promise.resolve(createConnection(process.stdout, process.stdin, errorHandler, closeHandler));
        }
        return Promise.reject(new Error(`Unsupported server configuartion ` + JSON.stringify(server, null, 4)));
    }
    handleConnectionClosed() {
        // Check whether this is a normal shutdown in progress or the client stopped normally.
        if (this.state === ClientState.Stopping || this.state === ClientState.Stopped) {
            return;
        }
        this._connectionPromise = undefined;
        this._resolvedConnection = undefined;
        this._childProcess = undefined;
        let action = this._clientOptions.errorHandler.closed();
        if (action === CloseAction.DoNotRestart) {
            this.error('Connection to server got closed. Server will not be restarted.');
            this.state = ClientState.Stopped;
            this.cleanUp();
        }
        else if (action === CloseAction.Restart) {
            this.info('Connection to server got closed. Server will restart.');
            this.cleanUp(false);
            this.state = ClientState.Initial;
            this.start();
        }
    }
    handleConnectionError(error, message, count) {
        let action = this._clientOptions.errorHandler.error(error, message, count);
        if (action === ErrorAction.Shutdown) {
            this.error('Connection to server is erroring. Shutting down server.');
            this.stop();
        }
    }
    checkProcessDied(childProcess) {
        if (!childProcess) {
            return;
        }
        setTimeout(() => {
            // Test if the process is still alive. Throws an exception if not
            try {
                process.kill(childProcess.pid, 0);
                processes_1.terminate(childProcess);
            }
            catch (error) {
            }
        }, 2000);
    }
    hookConfigurationChanged(connection) {
        if (!this._clientOptions.synchronize.configurationSection) {
            return;
        }
        vscode_1.workspace.onDidChangeConfiguration(() => this.onDidChangeConfiguration(connection), this, this._listeners);
        this.onDidChangeConfiguration(connection);
    }
    refreshTrace(connection, sendNotification = false) {
        let config = vscode_1.workspace.getConfiguration(this._id);
        let trace = vscode_jsonrpc_1.Trace.Off;
        if (config) {
            trace = vscode_jsonrpc_1.Trace.fromString(config.get('trace.server', 'off'));
        }
        this._trace = trace;
        connection.trace(this._trace, this._tracer, sendNotification);
    }
    onDidChangeConfiguration(connection) {
        this.refreshTrace(connection, true);
        let keys;
        let configurationSection = this._clientOptions.synchronize.configurationSection;
        if (is.string(configurationSection)) {
            keys = [configurationSection];
        }
        else if (is.stringArray(configurationSection)) {
            keys = configurationSection;
        }
        if (keys) {
            if (this.isConnectionActive()) {
                connection.didChangeConfiguration({ settings: this.extractSettingsInformation(keys) });
            }
        }
    }
    extractSettingsInformation(keys) {
        function ensurePath(config, path) {
            let current = config;
            for (let i = 0; i < path.length - 1; i++) {
                let obj = current[path[i]];
                if (!obj) {
                    obj = Object.create(null);
                    current[path[i]] = obj;
                }
                current = obj;
            }
            return current;
        }
        let result = Object.create(null);
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];
            let index = key.indexOf('.');
            let config = null;
            if (index >= 0) {
                config = vscode_1.workspace.getConfiguration(key.substr(0, index)).get(key.substr(index + 1));
            }
            else {
                config = vscode_1.workspace.getConfiguration(key);
            }
            if (config) {
                let path = keys[i].split('.');
                ensurePath(result, path)[path[path.length - 1]] = config;
            }
        }
        return result;
    }
    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;
        }
        watchers.forEach(watcher => {
            watcher.onDidCreate((resource) => this.notifyFileEvent({
                uri: resource.toString(),
                type: protocol_1.FileChangeType.Created
            }), null, this._listeners);
            watcher.onDidChange((resource) => this.notifyFileEvent({
                uri: resource.toString(),
                type: protocol_1.FileChangeType.Changed
            }), null, this._listeners);
            watcher.onDidDelete((resource) => this.notifyFileEvent({
                uri: resource.toString(),
                type: protocol_1.FileChangeType.Deleted
            }), null, this._listeners);
        });
    }
    initRegistrationHandlers(_connection) {
        const syncedDocuments = new Map();
        const logger = (type, error) => { this.logFailedRequest(type, error); };
        this._registeredHandlers.set(protocol_1.DidOpenTextDocumentNotification.type.method, new DidOpenTextDocumentFeature(this, syncedDocuments));
        this._registeredHandlers.set(protocol_1.DidChangeTextDocumentNotification.type.method, new DidChangeTextDocumentFeature(this));
        this._registeredHandlers.set(protocol_1.WillSaveTextDocumentNotification.type.method, new DocumentNotifiactions(this, vscode_1.workspace.onWillSaveTextDocument, protocol_1.WillSaveTextDocumentNotification.type, (willSaveEvent) => this._c2p.asWillSaveTextDocumentParams(willSaveEvent), (selectors, willSaveEvent) => DocumentNotifiactions.textDocumentFilter(selectors, willSaveEvent.document)));
        this._registeredHandlers.set(protocol_1.WillSaveTextDocumentWaitUntilRequest.type.method, new WillSaveWaitUntilFeature(this));
        this._registeredHandlers.set(protocol_1.DidSaveTextDocumentNotification.type.method, new DidSaveTextDocumentFeature(this));
        this._registeredHandlers.set(protocol_1.DidCloseTextDocumentNotification.type.method, new DidCloseTextDocumentFeature(this, syncedDocuments));
        this._registeredHandlers.set(protocol_1.CompletionRequest.type.method, new LanguageFeature((options) => this.createCompletionProvider(options)));
        this._registeredHandlers.set(protocol_1.HoverRequest.type.method, new LanguageFeature((options) => this.createHoverProvider(options)));
        this._registeredHandlers.set(protocol_1.SignatureHelpRequest.type.method, new LanguageFeature((options) => this.createSignatureHelpProvider(options)));
        this._registeredHandlers.set(protocol_1.DefinitionRequest.type.method, new LanguageFeature((options) => this.createDefinitionProvider(options)));
        this._registeredHandlers.set(protocol_1.ReferencesRequest.type.method, new LanguageFeature((options) => this.createReferencesProvider(options)));
        this._registeredHandlers.set(protocol_1.DocumentHighlightRequest.type.method, new LanguageFeature((options) => this.createDocumentHighlightProvider(options)));
        this._registeredHandlers.set(protocol_1.DocumentSymbolRequest.type.method, new LanguageFeature((options) => this.createDocumentSymbolProvider(options)));
        this._registeredHandlers.set(protocol_1.WorkspaceSymbolRequest.type.method, new LanguageFeature((options) => this.createWorkspaceSymbolProvider(options)));
        this._registeredHandlers.set(protocol_1.CodeActionRequest.type.method, new LanguageFeature((options) => this.createCodeActionsProvider(options)));
        this._registeredHandlers.set(protocol_1.CodeLensRequest.type.method, new LanguageFeature((options) => this.createCodeLensProvider(options)));
        this._registeredHandlers.set(protocol_1.DocumentFormattingRequest.type.method, new LanguageFeature((options) => this.createDocumentFormattingProvider(options)));
        this._registeredHandlers.set(protocol_1.DocumentRangeFormattingRequest.type.method, new LanguageFeature((options) => this.createDocumentRangeFormattingProvider(options)));
        this._registeredHandlers.set(protocol_1.DocumentOnTypeFormattingRequest.type.method, new LanguageFeature((options) => this.createDocumentOnTypeFormattingProvider(options)));
        this._registeredHandlers.set(protocol_1.RenameRequest.type.method, new LanguageFeature((options) => this.createRenameProvider(options)));
        this._registeredHandlers.set(protocol_1.DocumentLinkRequest.type.method, new LanguageFeature((options) => this.createDocumentLinkProvider(options)));
        this._registeredHandlers.set(protocol_1.ExecuteCommandRequest.type.method, new ExecuteCommandFeature(this, logger));
    }
    handleRegistrationRequest(params) {
        return new Promise((resolve, _reject) => {
            params.registrations.forEach((element) => {
                const handler = this._registeredHandlers.get(element.method);
                const options = element.registerOptions || {};
                options.documentSelector = options.documentSelector || this._clientOptions.documentSelector;
                const data = {
                    id: element.id,
                    registerOptions: options
                };
                if (handler) {
                    handler.register(data);
                }
            });
            resolve();
        });
    }
    handleUnregistrationRequest(params) {
        return new Promise((resolve, _reject) => {
            params.unregisterations.forEach((element) => {
                const handler = this._registeredHandlers.get(element.method);
                if (handler) {
                    handler.unregister(element.id);
                }
            });
            resolve();
        });
    }
    handleApplyWorkspaceEdit(params) {
        // 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.
        let workspaceEdit = params.edit;
        let 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 (change.textDocument.version && change.textDocument.version >= 0) {
                    let textDocument = openTextDocuments.get(change.textDocument.uri);
                    if (textDocument && textDocument.version !== change.textDocument.version) {
                        versionMismatch = true;
                        break;
                    }
                }
            }
        }
        if (versionMismatch) {
            return Promise.resolve({ applied: false });
        }
        return vscode_1.workspace.applyEdit(this._p2c.asWorkspaceEdit(params.edit)).then((value) => { return { applied: value }; });
    }
    ;
    hookCapabilities(_connection) {
        let documentSelector = this._clientOptions.documentSelector;
        if (!documentSelector) {
            return;
        }
        let selectorOptions = { documentSelector: documentSelector };
        let registeredHandlers = this._registeredHandlers;
        if (this._capabilites.completionProvider) {
            let options = Object.assign({}, selectorOptions, this._capabilites.completionProvider);
            registeredHandlers.get(protocol_1.CompletionRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: options });
        }
        if (this._capabilites.hoverProvider) {
            registeredHandlers.get(protocol_1.HoverRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.signatureHelpProvider) {
            let options = Object.assign({}, selectorOptions, this._capabilites.signatureHelpProvider);
            registeredHandlers.get(protocol_1.SignatureHelpRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: options });
        }
        if (this._capabilites.definitionProvider) {
            registeredHandlers.get(protocol_1.DefinitionRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.referencesProvider) {
            registeredHandlers.get(protocol_1.ReferencesRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.documentHighlightProvider) {
            registeredHandlers.get(protocol_1.DocumentHighlightRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.documentSymbolProvider) {
            registeredHandlers.get(protocol_1.DocumentSymbolRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.workspaceSymbolProvider) {
            registeredHandlers.get(protocol_1.WorkspaceSymbolRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.codeActionProvider) {
            registeredHandlers.get(protocol_1.CodeActionRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.codeLensProvider) {
            let options = Object.assign({}, selectorOptions, this._capabilites.codeLensProvider);
            registeredHandlers.get(protocol_1.CodeLensRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: options });
        }
        if (this._capabilites.documentFormattingProvider) {
            registeredHandlers.get(protocol_1.DocumentFormattingRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.documentRangeFormattingProvider) {
            registeredHandlers.get(protocol_1.DocumentRangeFormattingRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.documentOnTypeFormattingProvider) {
            let options = Object.assign({}, selectorOptions, this._capabilites.documentOnTypeFormattingProvider);
            registeredHandlers.get(protocol_1.DocumentOnTypeFormattingRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: options });
        }
        if (this._capabilites.renameProvider) {
            registeredHandlers.get(protocol_1.RenameRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, selectorOptions) });
        }
        if (this._capabilites.documentLinkProvider) {
            let options = Object.assign({}, selectorOptions, this._capabilites.documentLinkProvider);
            registeredHandlers.get(protocol_1.DocumentLinkRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: options });
        }
        if (this._capabilites.executeCommandProvider) {
            let options = Object.assign({}, this._capabilites.executeCommandProvider);
            registeredHandlers.get(protocol_1.ExecuteCommandRequest.type.method).register({ id: UUID.generateUuid(), registerOptions: options });
        }
    }
    logFailedRequest(type, error) {
        // If we get a request cancel don't log anything.
        if (error instanceof vscode_jsonrpc_1.ResponseError && error.code === vscode_jsonrpc_1.ErrorCodes.RequestCancelled) {
            return;
        }
        this.error(`Request ${type.method} failed.`, error);
    }
    createCompletionProvider(options) {
        let triggerCharacters = options.triggerCharacters || [];
        return vscode_1.languages.registerCompletionItemProvider(options.documentSelector, {
            provideCompletionItems: (document, position, token) => {
                return this.sendRequest(protocol_1.CompletionRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(this._p2c.asCompletionResult, (error) => {
                    this.logFailedRequest(protocol_1.CompletionRequest.type, error);
                    return Promise.resolve([]);
                });
            },
            resolveCompletionItem: options.resolveProvider
                ? (item, token) => {
                    return this.sendRequest(protocol_1.CompletionResolveRequest.type, this._c2p.asCompletionItem(item), token).then(this._p2c.asCompletionItem, (error) => {
                        this.logFailedRequest(protocol_1.CompletionResolveRequest.type, error);
                        return Promise.resolve(item);
                    });
                }
                : undefined
        }, ...triggerCharacters);
    }
    createHoverProvider(options) {
        return vscode_1.languages.registerHoverProvider(options.documentSelector, {
            provideHover: (document, position, token) => {
                return this.sendRequest(protocol_1.HoverRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(this._p2c.asHover, (error) => {
                    this.logFailedRequest(protocol_1.HoverRequest.type, error);
                    return Promise.resolve(null);
                });
            }
        });
    }
    createSignatureHelpProvider(options) {
        let triggerCharacters = options.triggerCharacters || [];
        return vscode_1.languages.registerSignatureHelpProvider(options.documentSelector, {
            provideSignatureHelp: (document, position, token) => {
                return this.sendRequest(protocol_1.SignatureHelpRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(this._p2c.asSignatureHelp, (error) => {
                    this.logFailedRequest(protocol_1.SignatureHelpRequest.type, error);
                    return Promise.resolve(null);
                });
            }
        }, ...triggerCharacters);
    }
    createDefinitionProvider(options) {
        return vscode_1.languages.registerDefinitionProvider(options.documentSelector, {
            provideDefinition: (document, position, token) => {
                return this.sendRequest(protocol_1.DefinitionRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(this._p2c.asDefinitionResult, (error) => {
                    this.logFailedRequest(protocol_1.DefinitionRequest.type, error);
                    return Promise.resolve(null);
                });
            }
        });
    }
    createReferencesProvider(options) {
        return vscode_1.languages.registerReferenceProvider(options.documentSelector, {
            provideReferences: (document, position, options, token) => {
                return this.sendRequest(protocol_1.ReferencesRequest.type, this._c2p.asReferenceParams(document, position, options), token).then(this._p2c.asReferences, (error) => {
                    this.logFailedRequest(protocol_1.ReferencesRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        });
    }
    createDocumentHighlightProvider(options) {
        return vscode_1.languages.registerDocumentHighlightProvider(options.documentSelector, {
            provideDocumentHighlights: (document, position, token) => {
                return this.sendRequest(protocol_1.DocumentHighlightRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(this._p2c.asDocumentHighlights, (error) => {
                    this.logFailedRequest(protocol_1.DocumentHighlightRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        });
    }
    createDocumentSymbolProvider(options) {
        return vscode_1.languages.registerDocumentSymbolProvider(options.documentSelector, {
            provideDocumentSymbols: (document, token) => {
                return this.sendRequest(protocol_1.DocumentSymbolRequest.type, this._c2p.asDocumentSymbolParams(document), token).then(this._p2c.asSymbolInformations, (error) => {
                    this.logFailedRequest(protocol_1.DocumentSymbolRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        });
    }
    createWorkspaceSymbolProvider(_options) {
        return vscode_1.languages.registerWorkspaceSymbolProvider({
            provideWorkspaceSymbols: (query, token) => {
                return this.sendRequest(protocol_1.WorkspaceSymbolRequest.type, { query }, token).then(this._p2c.asSymbolInformations, (error) => {
                    this.logFailedRequest(protocol_1.WorkspaceSymbolRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        });
    }
    createCodeActionsProvider(options) {
        return vscode_1.languages.registerCodeActionsProvider(options.documentSelector, {
            provideCodeActions: (document, range, context, token) => {
                let params = {
                    textDocument: this._c2p.asTextDocumentIdentifier(document),
                    range: this._c2p.asRange(range),
                    context: this._c2p.asCodeActionContext(context)
                };
                return this.sendRequest(protocol_1.CodeActionRequest.type, params, token).then(this._p2c.asCommands, (error) => {
                    this.logFailedRequest(protocol_1.CodeActionRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        });
    }
    createCodeLensProvider(options) {
        return vscode_1.languages.registerCodeLensProvider(options.documentSelector, {
            provideCodeLenses: (document, token) => {
                return this.sendRequest(protocol_1.CodeLensRequest.type, this._c2p.asCodeLensParams(document), token).then(this._p2c.asCodeLenses, (error) => {
                    this.logFailedRequest(protocol_1.CodeLensRequest.type, error);
                    return Promise.resolve([]);
                });
            },
            resolveCodeLens: (options.resolveProvider)
                ? (codeLens, token) => {
                    return this.sendRequest(protocol_1.CodeLensResolveRequest.type, this._c2p.asCodeLens(codeLens), token).then(this._p2c.asCodeLens, (error) => {
                        this.logFailedRequest(protocol_1.CodeLensResolveRequest.type, error);
                        return codeLens;
                    });
                }
                : undefined
        });
    }
    createDocumentFormattingProvider(options) {
        return vscode_1.languages.registerDocumentFormattingEditProvider(options.documentSelector, {
            provideDocumentFormattingEdits: (document, options, token) => {
                let params = {
                    textDocument: this._c2p.asTextDocumentIdentifier(document),
                    options: this._c2p.asFormattingOptions(options)
                };
                return this.sendRequest(protocol_1.DocumentFormattingRequest.type, params, token).then(this._p2c.asTextEdits, (error) => {
                    this.logFailedRequest(protocol_1.DocumentFormattingRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        });
    }
    createDocumentRangeFormattingProvider(options) {
        return vscode_1.languages.registerDocumentRangeFormattingEditProvider(options.documentSelector, {
            provideDocumentRangeFormattingEdits: (document, range, options, token) => {
                let params = {
                    textDocument: this._c2p.asTextDocumentIdentifier(document),
                    range: this._c2p.asRange(range),
                    options: this._c2p.asFormattingOptions(options)
                };
                return this.sendRequest(protocol_1.DocumentRangeFormattingRequest.type, params, token).then(this._p2c.asTextEdits, (error) => {
                    this.logFailedRequest(protocol_1.DocumentRangeFormattingRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        });
    }
    createDocumentOnTypeFormattingProvider(options) {
        let moreTriggerCharacter = options.moreTriggerCharacter || [];
        return vscode_1.languages.registerOnTypeFormattingEditProvider(options.documentSelector, {
            provideOnTypeFormattingEdits: (document, position, ch, options, token) => {
                let params = {
                    textDocument: this._c2p.asTextDocumentIdentifier(document),
                    position: this._c2p.asPosition(position),
                    ch: ch,
                    options: this._c2p.asFormattingOptions(options)
                };
                return this.sendRequest(protocol_1.DocumentOnTypeFormattingRequest.type, params, token).then(this._p2c.asTextEdits, (error) => {
                    this.logFailedRequest(protocol_1.DocumentOnTypeFormattingRequest.type, error);
                    return Promise.resolve([]);
                });
            }
        }, options.firstTriggerCharacter, ...moreTriggerCharacter);
    }
    createRenameProvider(options) {
        return vscode_1.languages.registerRenameProvider(options.documentSelector, {
            provideRenameEdits: (document, position, newName, token) => {
                let params = {
                    textDocument: this._c2p.asTextDocumentIdentifier(document),
                    position: this._c2p.asPosition(position),
                    newName: newName
                };
                return this.sendRequest(protocol_1.RenameRequest.type, params, token).then(this._p2c.asWorkspaceEdit, (error) => {
                    this.logFailedRequest(protocol_1.RenameRequest.type, error);
                    Promise.resolve(new Error(error.message));
                });
            }
        });
    }
    createDocumentLinkProvider(options) {
        return vscode_1.languages.registerDocumentLinkProvider(options.documentSelector, {
            provideDocumentLinks: (document, token) => {
                return this.sendRequest(protocol_1.DocumentLinkRequest.type, this._c2p.asDocumentLinkParams(document), token).then(this._p2c.asDocumentLinks, (error) => {
                    this.logFailedRequest(protocol_1.DocumentLinkRequest.type, error);
                    Promise.resolve(new Error(error.message));
                });
            },
            resolveDocumentLink: options.resolveProvider
                ? (link, token) => {
                    return this.sendRequest(protocol_1.DocumentLinkResolveRequest.type, this._c2p.asDocumentLink(link), token).then(this._p2c.asDocumentLink, (error) => {
                        this.logFailedRequest(protocol_1.DocumentLinkResolveRequest.type, error);
                        Promise.resolve(new Error(error.message));
                    });
                }
                : undefined
        });
    }
}
exports.LanguageClient = LanguageClient;
class SettingMonitor {
    constructor(_client, _setting) {
        this._client = _client;
        this._setting = _setting;
        this._listeners = [];
    }
    start() {
        vscode_1.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this._listeners);
        this.onDidChangeConfiguration();
        return new vscode_1.Disposable(() => {
            if (this._client.needsStop()) {
                this._client.stop();
            }
        });
    }
    onDidChangeConfiguration() {
        let index = this._setting.indexOf('.');
        let primary = index >= 0 ? this._setting.substr(0, index) : this._setting;
        let rest = index >= 0 ? this._setting.substr(index + 1) : undefined;
        let enabled = rest ? vscode_1.workspace.getConfiguration(primary).get(rest, false) : vscode_1.workspace.getConfiguration(primary);
        if (enabled && this._client.needsStart()) {
            this._client.start();
        }
        else if (!enabled && this._client.needsStop()) {
            this._client.stop();
        }
    }
}
exports.SettingMonitor = SettingMonitor;
