/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
/// <reference path="./thenable.ts" />
'use strict';
function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
exports.RequestType = vscode_jsonrpc_1.RequestType;
exports.RequestType0 = vscode_jsonrpc_1.RequestType0;
exports.NotificationType = vscode_jsonrpc_1.NotificationType;
exports.NotificationType0 = vscode_jsonrpc_1.NotificationType0;
exports.ResponseError = vscode_jsonrpc_1.ResponseError;
exports.ErrorCodes = vscode_jsonrpc_1.ErrorCodes;
exports.StreamMessageReader = vscode_jsonrpc_1.StreamMessageReader;
exports.IPCMessageReader = vscode_jsonrpc_1.IPCMessageReader;
exports.StreamMessageWriter = vscode_jsonrpc_1.StreamMessageWriter;
exports.IPCMessageWriter = vscode_jsonrpc_1.IPCMessageWriter;
exports.createServerPipeTransport = vscode_jsonrpc_1.createServerPipeTransport;
exports.Disposable = vscode_jsonrpc_1.Disposable;
exports.Event = vscode_jsonrpc_1.Event;
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const protocol_1 = require("./protocol");
const Is = require("./utils/is");
const UUID = require("./utils/uuid");
__export(require("vscode-languageserver-types"));
__export(require("./protocol"));
const fm = require("./files");
const net = require("net");
const stream = require("stream");
var Files;
(function (Files) {
    Files.uriToFilePath = fm.uriToFilePath;
    Files.resolveGlobalNodePath = fm.resolveGlobalNodePath;
    Files.resolve = fm.resolve;
    Files.resolveModule = fm.resolveModule;
    Files.resolveModule2 = fm.resolveModule2;
    Files.resolveModulePath = fm.resolveModulePath;
})(Files = exports.Files || (exports.Files = {}));
/**
 * A manager for simple text documents
 */
class TextDocuments {
    /**
     * Create a new text document manager.
     */
    constructor() {
        this._documents = Object.create(null);
        this._onDidChangeContent = new vscode_jsonrpc_1.Emitter();
        this._onDidOpen = new vscode_jsonrpc_1.Emitter();
        this._onDidClose = new vscode_jsonrpc_1.Emitter();
        this._onDidSave = new vscode_jsonrpc_1.Emitter();
        this._onWillSave = new vscode_jsonrpc_1.Emitter();
    }
    /**
     * Returns the [TextDocumentSyncKind](#TextDocumentSyncKind) used by
     * this text document manager.
     */
    get syncKind() {
        return protocol_1.TextDocumentSyncKind.Full;
    }
    /**
     * An event that fires when a text document managed by this manager
     * has been opened or the content changes.
     */
    get onDidChangeContent() {
        return this._onDidChangeContent.event;
    }
    /**
     * An event that fires when a text document managed by this manager
     * has been opened.
     */
    get onDidOpen() {
        return this._onDidOpen.event;
    }
    /**
     * An event that fires when a text document managed by this manager
     * will be saved.
     */
    get onWillSave() {
        return this._onWillSave.event;
    }
    /**
     * Sets a handler that will be called if a participant wants to provide
     * edits during a text document save.
     */
    onWillSaveWaitUntil(handler) {
        this._willSaveWaitUntil = handler;
    }
    /**
     * An event that fires when a text document managed by this manager
     * has been saved.
     */
    get onDidSave() {
        return this._onDidSave.event;
    }
    /**
     * An event that fires when a text document managed by this manager
     * has been closed.
     */
    get onDidClose() {
        return this._onDidClose.event;
    }
    /**
     * Returns the document for the given URI. Returns undefined if
     * the document is not mananged by this instance.
     *
     * @param uri The text document's URI to retrieve.
     * @return the text document or `undefined`.
     */
    get(uri) {
        return this._documents[uri];
    }
    /**
     * Returns all text documents managed by this instance.
     *
     * @return all text documents.
     */
    all() {
        return Object.keys(this._documents).map(key => this._documents[key]);
    }
    /**
     * Returns the URIs of all text documents managed by this instance.
     *
     * @return the URI's of all text documents.
     */
    keys() {
        return Object.keys(this._documents);
    }
    /**
     * Listens for `low level` notification on the given connection to
     * update the text documents managed by this instance.
     *
     * @param connection The connection to listen on.
     */
    listen(connection) {
        function isUpdateableDocument(value) {
            return Is.func(value.update);
        }
        connection.__textDocumentSync = protocol_1.TextDocumentSyncKind.Full;
        connection.onDidOpenTextDocument((event) => {
            let td = event.textDocument;
            let document = vscode_languageserver_types_1.TextDocument.create(td.uri, td.languageId, td.version, td.text);
            this._documents[td.uri] = document;
            let toFire = Object.freeze({ document });
            this._onDidOpen.fire(toFire);
            this._onDidChangeContent.fire(toFire);
        });
        connection.onDidChangeTextDocument((event) => {
            let td = event.textDocument;
            let changes = event.contentChanges;
            let last = changes.length > 0 ? changes[changes.length - 1] : undefined;
            if (last) {
                let document = this._documents[td.uri];
                if (document && isUpdateableDocument(document)) {
                    document.update(last, td.version);
                    this._onDidChangeContent.fire(Object.freeze({ document }));
                }
            }
        });
        connection.onDidCloseTextDocument((event) => {
            let document = this._documents[event.textDocument.uri];
            if (document) {
                delete this._documents[event.textDocument.uri];
                this._onDidClose.fire(Object.freeze({ document }));
            }
        });
        connection.onWillSaveTextDocument((event) => {
            let document = this._documents[event.textDocument.uri];
            if (document) {
                this._onWillSave.fire(Object.freeze({ document, reason: event.reason }));
            }
        });
        connection.onWillSaveTextDocumentWaitUntil((event, token) => {
            let document = this._documents[event.textDocument.uri];
            if (document && this._willSaveWaitUntil) {
                return this._willSaveWaitUntil(Object.freeze({ document, reason: event.reason }), token);
            }
            else {
                return [];
            }
        });
        connection.onDidSaveTextDocument((event) => {
            let document = this._documents[event.textDocument.uri];
            if (document) {
                this._onDidSave.fire(Object.freeze({ document }));
            }
        });
    }
}
exports.TextDocuments = TextDocuments;
// ------------------------- implementation of the language server protocol ---------------------------------------------
/**
 * Helps tracking error message. Equal occurences of the same
 * message are only stored once. This class is for example
 * usefull if text documents are validated in a loop and equal
 * error message should be folded into one.
 */
class ErrorMessageTracker {
    constructor() {
        this._messages = Object.create(null);
    }
    /**
     * Add a message to the tracker.
     *
     * @param message The message to add.
     */
    add(message) {
        let count = this._messages[message];
        if (!count) {
            count = 0;
        }
        count++;
        this._messages[message] = count;
    }
    /**
     * Send all tracked messages to the conenction's window.
     *
     * @param connection The connection establised between client and server.
     */
    sendErrors(connection) {
        Object.keys(this._messages).forEach(message => {
            connection.window.showErrorMessage(message);
        });
    }
}
exports.ErrorMessageTracker = ErrorMessageTracker;
var BulkRegistration;
(function (BulkRegistration) {
    /**
     * Creates a new bulk registration.
     * @return an empty bulk registration.
     */
    function create() {
        return new BulkRegistrationImpl();
    }
    BulkRegistration.create = create;
})(BulkRegistration = exports.BulkRegistration || (exports.BulkRegistration = {}));
class BulkRegistrationImpl {
    constructor() {
        this._registrations = [];
        this._registered = new Set();
    }
    add(type, registerOptions) {
        const method = Is.string(type) ? type : type.method;
        if (this._registered.has(method)) {
            throw new Error(`${method} is already added to this registration`);
        }
        const id = UUID.generateUuid();
        this._registrations.push({
            id: id,
            method: method,
            registerOptions: registerOptions || {}
        });
        this._registered.add(method);
    }
    asRegistrationParams() {
        return {
            registrations: this._registrations
        };
    }
}
var BulkUnregistration;
(function (BulkUnregistration) {
    function create() {
        return new BulkUnregistrationImpl(undefined, undefined, []);
    }
    BulkUnregistration.create = create;
})(BulkUnregistration = exports.BulkUnregistration || (exports.BulkUnregistration = {}));
class BulkUnregistrationImpl {
    constructor(_connection, _console, unregistrations) {
        this._connection = _connection;
        this._console = _console;
        this._unregistrations = new Map();
        unregistrations.forEach(unregistration => {
            this._unregistrations.set(unregistration.method, unregistration);
        });
    }
    get isAttached() {
        return !!this._connection && !!this._console;
    }
    attach(connection, _console) {
        this._connection = connection;
        this._console = _console;
    }
    add(unregistration) {
        this._unregistrations.set(unregistration.method, unregistration);
    }
    dispose() {
        let unregistrations = [];
        for (let unregistration of this._unregistrations.values()) {
            unregistrations.push(unregistration);
        }
        let params = {
            unregisterations: unregistrations
        };
        this._connection.sendRequest(protocol_1.UnregistrationRequest.type, params).then(undefined, (_error) => {
            this._console.info(`Bulk unregistration failed.`);
        });
    }
    disposeSingle(arg) {
        const method = Is.string(arg) ? arg : arg.method;
        const unregistration = this._unregistrations.get(method);
        if (!unregistration) {
            return false;
        }
        let params = {
            unregisterations: [unregistration]
        };
        this._connection.sendRequest(protocol_1.UnregistrationRequest.type, params).then(() => {
            this._unregistrations.delete(method);
        }, (_error) => {
            this._console.info(`Unregistering request handler for ${unregistration.id} failed.`);
        });
        return true;
    }
}
class ConnectionLogger {
    constructor() {
    }
    attach(connection) {
        this._connection = connection;
    }
    error(message) {
        this.send(protocol_1.MessageType.Error, message);
    }
    warn(message) {
        this.send(protocol_1.MessageType.Warning, message);
    }
    info(message) {
        this.send(protocol_1.MessageType.Info, message);
    }
    log(message) {
        this.send(protocol_1.MessageType.Log, message);
    }
    send(type, message) {
        if (this._connection) {
            this._connection.sendNotification(protocol_1.LogMessageNotification.type, { type, message });
        }
    }
}
class RemoteWindowImpl {
    constructor(_connection) {
        this._connection = _connection;
    }
    showErrorMessage(message, ...actions) {
        let params = { type: protocol_1.MessageType.Error, message, actions };
        return this._connection.sendRequest(protocol_1.ShowMessageRequest.type, params);
    }
    showWarningMessage(message, ...actions) {
        let params = { type: protocol_1.MessageType.Warning, message, actions };
        return this._connection.sendRequest(protocol_1.ShowMessageRequest.type, params);
    }
    showInformationMessage(message, ...actions) {
        let params = { type: protocol_1.MessageType.Info, message, actions };
        return this._connection.sendRequest(protocol_1.ShowMessageRequest.type, params);
    }
}
class RemoteClientImpl {
    constructor(_connection, _console) {
        this._connection = _connection;
        this._console = _console;
    }
    register(typeOrRegistrations, registerOptionsOrType, registerOptions) {
        if (typeOrRegistrations instanceof BulkRegistrationImpl) {
            return this.registerMany(typeOrRegistrations);
        }
        else if (typeOrRegistrations instanceof BulkUnregistrationImpl) {
            return this.registerSingle1(typeOrRegistrations, registerOptionsOrType, registerOptions);
        }
        else {
            return this.registerSingle2(typeOrRegistrations, registerOptionsOrType);
        }
    }
    registerSingle1(unregistration, type, registerOptions) {
        const method = Is.string(type) ? type : type.method;
        const id = UUID.generateUuid();
        let params = {
            registrations: [{ id, method, registerOptions: registerOptions || {} }]
        };
        if (!unregistration.isAttached) {
            unregistration.attach(this._connection, this._console);
        }
        return this._connection.sendRequest(protocol_1.RegistrationRequest.type, params).then((_result) => {
            unregistration.add({ id: id, method: method });
            return unregistration;
        }, (_error) => {
            this._console.info(`Registering request handler for ${method} failed.`);
            return Promise.reject(_error);
        });
    }
    registerSingle2(type, registerOptions) {
        const method = Is.string(type) ? type : type.method;
        const id = UUID.generateUuid();
        let params = {
            registrations: [{ id, method, registerOptions: registerOptions || {} }]
        };
        return this._connection.sendRequest(protocol_1.RegistrationRequest.type, params).then((_result) => {
            return vscode_jsonrpc_1.Disposable.create(() => {
                this.unregisterSingle(id, method);
            });
        }, (_error) => {
            this._console.info(`Registering request handler for ${method} failed.`);
            return Promise.reject(_error);
        });
    }
    unregisterSingle(id, method) {
        let params = {
            unregisterations: [{ id, method }]
        };
        return this._connection.sendRequest(protocol_1.UnregistrationRequest.type, params).then(undefined, (_error) => {
            this._console.info(`Unregistering request handler for ${id} failed.`);
        });
    }
    registerMany(registrations) {
        let params = registrations.asRegistrationParams();
        return this._connection.sendRequest(protocol_1.RegistrationRequest.type, params).then(() => {
            return new BulkUnregistrationImpl(this._connection, this._console, params.registrations.map(registration => { return { id: registration.id, method: registration.method }; }));
        }, (_error) => {
            this._console.info(`Bulk registeration failed.`);
            return Promise.reject(_error);
        });
    }
}
class RemoteWorkspaceImpl {
    constructor(_connection) {
        this._connection = _connection;
    }
    applyEdit(edit) {
        let params = {
            edit
        };
        return this._connection.sendRequest(protocol_1.ApplyWorkspaceEditRequest.type, params);
    }
}
class TracerImpl {
    constructor(_connection) {
        this._connection = _connection;
        this._trace = vscode_jsonrpc_1.Trace.Off;
    }
    set trace(value) {
        this._trace = value;
    }
    log(message, verbose) {
        if (this._trace === vscode_jsonrpc_1.Trace.Off) {
            return;
        }
        this._connection.sendNotification(vscode_jsonrpc_1.LogTraceNotification.type, {
            message: message,
            verbose: this._trace === vscode_jsonrpc_1.Trace.Verbose ? verbose : undefined
        });
    }
}
class TelemetryImpl {
    constructor(_connection) {
        this._connection = _connection;
    }
    logEvent(data) {
        this._connection.sendNotification(protocol_1.TelemetryEventNotification.type, data);
    }
}
function createConnection(input, output) {
    if (!input && !output && process.argv.length > 2) {
        let port = void 0;
        let pipeName = void 0;
        let argv = process.argv.slice(2);
        for (let i = 0; i < argv.length; i++) {
            let arg = argv[i];
            if (arg === '--node-ipc') {
                input = new vscode_jsonrpc_1.IPCMessageReader(process);
                output = new vscode_jsonrpc_1.IPCMessageWriter(process);
                break;
            }
            else if (arg === '--stdio') {
                input = process.stdin;
                output = process.stdout;
                break;
            }
            else if (arg === '--socket') {
                port = parseInt(argv[i + 1]);
                break;
            }
            else if (arg === '--pipe') {
                pipeName = argv[i + 1];
                break;
            }
            else {
                var args = arg.split('=');
                if (args[0] === '--socket') {
                    port = parseInt(args[1]);
                    break;
                }
                else if (args[0] === '--pipe') {
                    pipeName = args[1];
                    break;
                }
            }
        }
        if (port) {
            output = new stream.PassThrough();
            input = new stream.PassThrough();
            let server = net.createServer(socket => {
                server.close();
                socket.pipe(output);
                input.pipe(socket);
            }).listen(port);
        }
        else if (pipeName) {
            let protocol = vscode_jsonrpc_1.createServerPipeTransport(pipeName);
            input = protocol[0];
            output = protocol[1];
        }
    }
    var commandLineMessage = "Use arguments of createConnection or set command line parameters: '--node-ipc', '--stdio' or '--socket={number}'";
    if (!input) {
        throw new Error("Connection input stream is not set. " + commandLineMessage);
    }
    if (!output) {
        throw new Error("Connection output stream is not set. " + commandLineMessage);
    }
    let shutdownReceived;
    // Backwards compatibility
    if (Is.func(input.read) && Is.func(input.on)) {
        let inputStream = input;
        inputStream.on('end', () => {
            process.exit(shutdownReceived ? 0 : 1);
        });
        inputStream.on('close', () => {
            process.exit(shutdownReceived ? 0 : 1);
        });
    }
    const logger = new ConnectionLogger();
    const connection = vscode_jsonrpc_1.createMessageConnection(input, output, logger);
    logger.attach(connection);
    const remoteWindow = new RemoteWindowImpl(connection);
    const telemetry = new TelemetryImpl(connection);
    const tracer = new TracerImpl(connection);
    const client = new RemoteClientImpl(connection, logger);
    const workspace = new RemoteWorkspaceImpl(connection);
    function asThenable(value) {
        if (Is.thenable(value)) {
            return value;
        }
        else {
            return Promise.resolve(value);
        }
    }
    let shutdownHandler = undefined;
    let initializeHandler = undefined;
    let exitHandler = undefined;
    let protocolConnection = {
        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),
        onInitialize: (handler) => initializeHandler = handler,
        onInitialized: (handler) => connection.onNotification(protocol_1.InitializedNotification.type, handler),
        onShutdown: (handler) => shutdownHandler = handler,
        onExit: (handler) => exitHandler = handler,
        get console() { return logger; },
        get window() { return remoteWindow; },
        get telemetry() { return telemetry; },
        get tracer() { return tracer; },
        get client() { return client; },
        get workspace() { return workspace; },
        onDidChangeConfiguration: (handler) => connection.onNotification(protocol_1.DidChangeConfigurationNotification.type, handler),
        onDidChangeWatchedFiles: (handler) => connection.onNotification(protocol_1.DidChangeWatchedFilesNotification.type, handler),
        __textDocumentSync: undefined,
        onDidOpenTextDocument: (handler) => connection.onNotification(protocol_1.DidOpenTextDocumentNotification.type, handler),
        onDidChangeTextDocument: (handler) => connection.onNotification(protocol_1.DidChangeTextDocumentNotification.type, handler),
        onDidCloseTextDocument: (handler) => connection.onNotification(protocol_1.DidCloseTextDocumentNotification.type, handler),
        onWillSaveTextDocument: (handler) => connection.onNotification(protocol_1.WillSaveTextDocumentNotification.type, handler),
        onWillSaveTextDocumentWaitUntil: (handler) => connection.onRequest(protocol_1.WillSaveTextDocumentWaitUntilRequest.type, handler),
        onDidSaveTextDocument: (handler) => connection.onNotification(protocol_1.DidSaveTextDocumentNotification.type, handler),
        sendDiagnostics: (params) => connection.sendNotification(protocol_1.PublishDiagnosticsNotification.type, params),
        onHover: (handler) => connection.onRequest(protocol_1.HoverRequest.type, handler),
        onCompletion: (handler) => connection.onRequest(protocol_1.CompletionRequest.type, handler),
        onCompletionResolve: (handler) => connection.onRequest(protocol_1.CompletionResolveRequest.type, handler),
        onSignatureHelp: (handler) => connection.onRequest(protocol_1.SignatureHelpRequest.type, handler),
        onDefinition: (handler) => connection.onRequest(protocol_1.DefinitionRequest.type, handler),
        onReferences: (handler) => connection.onRequest(protocol_1.ReferencesRequest.type, handler),
        onDocumentHighlight: (handler) => connection.onRequest(protocol_1.DocumentHighlightRequest.type, handler),
        onDocumentSymbol: (handler) => connection.onRequest(protocol_1.DocumentSymbolRequest.type, handler),
        onWorkspaceSymbol: (handler) => connection.onRequest(protocol_1.WorkspaceSymbolRequest.type, handler),
        onCodeAction: (handler) => connection.onRequest(protocol_1.CodeActionRequest.type, handler),
        onCodeLens: (handler) => connection.onRequest(protocol_1.CodeLensRequest.type, handler),
        onCodeLensResolve: (handler) => connection.onRequest(protocol_1.CodeLensResolveRequest.type, handler),
        onDocumentFormatting: (handler) => connection.onRequest(protocol_1.DocumentFormattingRequest.type, handler),
        onDocumentRangeFormatting: (handler) => connection.onRequest(protocol_1.DocumentRangeFormattingRequest.type, handler),
        onDocumentOnTypeFormatting: (handler) => connection.onRequest(protocol_1.DocumentOnTypeFormattingRequest.type, handler),
        onRenameRequest: (handler) => connection.onRequest(protocol_1.RenameRequest.type, handler),
        onDocumentLinks: (handler) => connection.onRequest(protocol_1.DocumentLinkRequest.type, handler),
        onDocumentLinkResolve: (handler) => connection.onRequest(protocol_1.DocumentLinkResolveRequest.type, handler),
        onExecuteCommand: (handler) => connection.onRequest(protocol_1.ExecuteCommandRequest.type, handler),
        dispose: () => connection.dispose()
    };
    connection.onRequest(protocol_1.InitializeRequest.type, (params) => {
        if (Is.number(params.processId)) {
            // We received a parent process id. Set up a timer to periodically check
            // if the parent is still alive.
            setInterval(() => {
                try {
                    process.kill(params.processId, 0);
                }
                catch (ex) {
                    // Parent process doesn't exist anymore. Exit the server.
                    process.exit(shutdownReceived ? 0 : 1);
                }
            }, 3000);
        }
        if (Is.string(params.trace)) {
            tracer.trace = vscode_jsonrpc_1.Trace.fromString(params.trace);
        }
        if (initializeHandler) {
            let result = initializeHandler(params, new vscode_jsonrpc_1.CancellationTokenSource().token);
            return asThenable(result).then((value) => {
                if (value instanceof vscode_jsonrpc_1.ResponseError) {
                    return value;
                }
                let result = value;
                if (!result) {
                    result = { capabilities: {} };
                }
                let capabilities = result.capabilities;
                if (!capabilities) {
                    capabilities = {};
                    result.capabilities = {};
                }
                if (!Is.number(capabilities.textDocumentSync)) {
                    capabilities.textDocumentSync = Is.number(protocolConnection.__textDocumentSync) ? protocolConnection.__textDocumentSync : protocol_1.TextDocumentSyncKind.None;
                }
                return result;
            });
        }
        else {
            let result = { capabilities: { textDocumentSync: protocol_1.TextDocumentSyncKind.None } };
            return result;
        }
    });
    connection.onRequest(protocol_1.ShutdownRequest.type, () => {
        shutdownReceived = true;
        if (shutdownHandler) {
            return shutdownHandler(new vscode_jsonrpc_1.CancellationTokenSource().token);
        }
        else {
            return undefined;
        }
    });
    connection.onNotification(protocol_1.ExitNotification.type, () => {
        try {
            if (exitHandler) {
                exitHandler();
            }
        }
        finally {
            if (shutdownReceived) {
                process.exit(0);
            }
            else {
                process.exit(1);
            }
        }
    });
    connection.onNotification(vscode_jsonrpc_1.SetTraceNotification.type, (params) => {
        tracer.trace = vscode_jsonrpc_1.Trace.fromString(params.value);
    });
    return protocolConnection;
}
exports.createConnection = createConnection;
