"use strict";
/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DidSaveTextDocumentFeature = exports.WillSaveWaitUntilFeature = exports.WillSaveFeature = exports.DidChangeTextDocumentFeature = exports.DidCloseTextDocumentFeature = exports.DidOpenTextDocumentFeature = void 0;
const vscode_1 = require("vscode");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const features_1 = require("./features");
const UUID = require("./utils/uuid");
class DidOpenTextDocumentFeature extends features_1.TextDocumentEventFeature {
    constructor(client, syncedDocuments) {
        super(client, vscode_1.workspace.onDidOpenTextDocument, vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.type, () => client.middleware.didOpen, (textDocument) => client.code2ProtocolConverter.asOpenTextDocumentParams(textDocument), (data) => data, features_1.TextDocumentEventFeature.textDocumentFilter);
        this._syncedDocuments = syncedDocuments;
    }
    get openDocuments() {
        return this._syncedDocuments.values();
    }
    fillClientCapabilities(capabilities) {
        (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').dynamicRegistration = true;
    }
    initialize(capabilities, documentSelector) {
        const textDocumentSyncOptions = capabilities.resolvedTextDocumentSync;
        if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.openClose) {
            this.register({ id: UUID.generateUuid(), registerOptions: { documentSelector: documentSelector } });
        }
    }
    get registrationType() {
        return vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.type;
    }
    register(data) {
        super.register(data);
        if (!data.registerOptions.documentSelector) {
            return;
        }
        const documentSelector = this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector);
        vscode_1.workspace.textDocuments.forEach((textDocument) => {
            const uri = textDocument.uri.toString();
            if (this._syncedDocuments.has(uri)) {
                return;
            }
            if (vscode_1.languages.match(documentSelector, textDocument) > 0 && !this._client.hasDedicatedTextSynchronizationFeature(textDocument)) {
                const middleware = this._client.middleware;
                const didOpen = (textDocument) => {
                    return this._client.sendNotification(this._type, this._createParams(textDocument));
                };
                (middleware.didOpen ? middleware.didOpen(textDocument, didOpen) : didOpen(textDocument)).catch((error) => {
                    this._client.error(`Sending document notification ${this._type.method} failed`, error);
                });
                this._syncedDocuments.set(uri, textDocument);
            }
        });
    }
    getTextDocument(data) {
        return data;
    }
    notificationSent(textDocument, type, params) {
        this._syncedDocuments.set(textDocument.uri.toString(), textDocument);
        super.notificationSent(textDocument, type, params);
    }
}
exports.DidOpenTextDocumentFeature = DidOpenTextDocumentFeature;
class DidCloseTextDocumentFeature extends features_1.TextDocumentEventFeature {
    constructor(client, syncedDocuments, pendingTextDocumentChanges) {
        super(client, vscode_1.workspace.onDidCloseTextDocument, vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.type, () => client.middleware.didClose, (textDocument) => client.code2ProtocolConverter.asCloseTextDocumentParams(textDocument), (data) => data, features_1.TextDocumentEventFeature.textDocumentFilter);
        this._syncedDocuments = syncedDocuments;
        this._pendingTextDocumentChanges = pendingTextDocumentChanges;
    }
    get registrationType() {
        return vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.type;
    }
    fillClientCapabilities(capabilities) {
        (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').dynamicRegistration = true;
    }
    initialize(capabilities, documentSelector) {
        let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync;
        if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.openClose) {
            this.register({ id: UUID.generateUuid(), registerOptions: { documentSelector: documentSelector } });
        }
    }
    async callback(data) {
        await super.callback(data);
        this._pendingTextDocumentChanges.delete(data.uri.toString());
    }
    getTextDocument(data) {
        return data;
    }
    notificationSent(textDocument, type, params) {
        this._syncedDocuments.delete(textDocument.uri.toString());
        super.notificationSent(textDocument, type, params);
    }
    unregister(id) {
        const selector = this._selectors.get(id);
        // The super call removed the selector from the map
        // of selectors.
        super.unregister(id);
        const selectors = this._selectors.values();
        this._syncedDocuments.forEach((textDocument) => {
            if (vscode_1.languages.match(selector, textDocument) > 0 && !this._selectorFilter(selectors, textDocument) && !this._client.hasDedicatedTextSynchronizationFeature(textDocument)) {
                let middleware = this._client.middleware;
                let didClose = (textDocument) => {
                    return this._client.sendNotification(this._type, this._createParams(textDocument));
                };
                this._syncedDocuments.delete(textDocument.uri.toString());
                (middleware.didClose ? middleware.didClose(textDocument, didClose) : didClose(textDocument)).catch((error) => {
                    this._client.error(`Sending document notification ${this._type.method} failed`, error);
                });
            }
        });
    }
}
exports.DidCloseTextDocumentFeature = DidCloseTextDocumentFeature;
class DidChangeTextDocumentFeature extends features_1.DynamicDocumentFeature {
    constructor(client, pendingTextDocumentChanges) {
        super(client);
        this._changeData = new Map();
        this._onNotificationSent = new vscode_1.EventEmitter();
        this._onPendingChangeAdded = new vscode_1.EventEmitter();
        this._pendingTextDocumentChanges = pendingTextDocumentChanges;
        this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None;
    }
    get onNotificationSent() {
        return this._onNotificationSent.event;
    }
    get onPendingChangeAdded() {
        return this._onPendingChangeAdded.event;
    }
    get syncKind() {
        return this._syncKind;
    }
    get registrationType() {
        return vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type;
    }
    fillClientCapabilities(capabilities) {
        (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').dynamicRegistration = true;
    }
    initialize(capabilities, documentSelector) {
        let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync;
        if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.change !== undefined && textDocumentSyncOptions.change !== vscode_languageserver_protocol_1.TextDocumentSyncKind.None) {
            this.register({
                id: UUID.generateUuid(),
                registerOptions: Object.assign({}, { documentSelector: documentSelector }, { syncKind: textDocumentSyncOptions.change })
            });
        }
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        if (!this._listener) {
            this._listener = vscode_1.workspace.onDidChangeTextDocument(this.callback, this);
        }
        this._changeData.set(data.id, {
            syncKind: data.registerOptions.syncKind,
            documentSelector: this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector),
        });
        this.updateSyncKind(data.registerOptions.syncKind);
    }
    *getDocumentSelectors() {
        for (const data of this._changeData.values()) {
            yield data.documentSelector;
        }
    }
    async callback(event) {
        // Text document changes are send for dirty changes as well. We don't
        // have dirty / un-dirty events in the LSP so we ignore content changes
        // with length zero.
        if (event.contentChanges.length === 0) {
            return;
        }
        // We need to capture the URI and version here since they might change on the text document
        // until we reach did `didChange` call since the middleware support async execution.
        const uri = event.document.uri;
        const version = event.document.version;
        const promises = [];
        for (const changeData of this._changeData.values()) {
            if (vscode_1.languages.match(changeData.documentSelector, event.document) > 0 && !this._client.hasDedicatedTextSynchronizationFeature(event.document)) {
                const middleware = this._client.middleware;
                if (changeData.syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Incremental) {
                    const didChange = async (event) => {
                        const params = this._client.code2ProtocolConverter.asChangeTextDocumentParams(event, uri, version);
                        await this._client.sendNotification(vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type, params);
                        this.notificationSent(event.document, vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type, params);
                    };
                    promises.push(middleware.didChange ? middleware.didChange(event, event => didChange(event)) : didChange(event));
                }
                else if (changeData.syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full) {
                    const didChange = async (event) => {
                        const eventUri = event.document.uri.toString();
                        this._pendingTextDocumentChanges.set(eventUri, event.document);
                        this._onPendingChangeAdded.fire();
                    };
                    promises.push(middleware.didChange ? middleware.didChange(event, event => didChange(event)) : didChange(event));
                }
            }
        }
        return Promise.all(promises).then(undefined, (error) => {
            this._client.error(`Sending document notification ${vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type.method} failed`, error);
            throw error;
        });
    }
    notificationSent(textDocument, type, params) {
        this._onNotificationSent.fire({ textDocument, type, params });
    }
    unregister(id) {
        this._changeData.delete(id);
        if (this._changeData.size === 0) {
            if (this._listener) {
                this._listener.dispose();
                this._listener = undefined;
            }
            this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None;
        }
        else {
            this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None;
            for (const changeData of this._changeData.values()) {
                this.updateSyncKind(changeData.syncKind);
                if (this._syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full) {
                    break;
                }
            }
        }
    }
    dispose() {
        this._pendingTextDocumentChanges.clear();
        this._changeData.clear();
        this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None;
        if (this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    getPendingDocumentChanges(excludes) {
        if (this._pendingTextDocumentChanges.size === 0) {
            return [];
        }
        let result;
        if (excludes.size === 0) {
            result = Array.from(this._pendingTextDocumentChanges.values());
            this._pendingTextDocumentChanges.clear();
        }
        else {
            result = [];
            for (const entry of this._pendingTextDocumentChanges) {
                if (!excludes.has(entry[0])) {
                    result.push(entry[1]);
                    this._pendingTextDocumentChanges.delete(entry[0]);
                }
            }
        }
        return result;
    }
    getProvider(document) {
        for (const changeData of this._changeData.values()) {
            if (vscode_1.languages.match(changeData.documentSelector, document) > 0) {
                return {
                    send: (event) => {
                        return this.callback(event);
                    }
                };
            }
        }
        return undefined;
    }
    updateSyncKind(syncKind) {
        if (this._syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full) {
            return;
        }
        switch (syncKind) {
            case vscode_languageserver_protocol_1.TextDocumentSyncKind.Full:
                this._syncKind = syncKind;
                break;
            case vscode_languageserver_protocol_1.TextDocumentSyncKind.Incremental:
                if (this._syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.None) {
                    this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.Incremental;
                }
                break;
        }
    }
}
exports.DidChangeTextDocumentFeature = DidChangeTextDocumentFeature;
class WillSaveFeature extends features_1.TextDocumentEventFeature {
    constructor(client) {
        super(client, vscode_1.workspace.onWillSaveTextDocument, vscode_languageserver_protocol_1.WillSaveTextDocumentNotification.type, () => client.middleware.willSave, (willSaveEvent) => client.code2ProtocolConverter.asWillSaveTextDocumentParams(willSaveEvent), (event) => event.document, (selectors, willSaveEvent) => features_1.TextDocumentEventFeature.textDocumentFilter(selectors, willSaveEvent.document));
    }
    get registrationType() {
        return vscode_languageserver_protocol_1.WillSaveTextDocumentNotification.type;
    }
    fillClientCapabilities(capabilities) {
        let value = (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization');
        value.willSave = true;
    }
    initialize(capabilities, documentSelector) {
        let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync;
        if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.willSave) {
            this.register({
                id: UUID.generateUuid(),
                registerOptions: { documentSelector: documentSelector }
            });
        }
    }
    getTextDocument(data) {
        return data.document;
    }
}
exports.WillSaveFeature = WillSaveFeature;
class WillSaveWaitUntilFeature extends features_1.DynamicDocumentFeature {
    constructor(client) {
        super(client);
        this._selectors = new Map();
    }
    getDocumentSelectors() {
        return this._selectors.values();
    }
    get registrationType() {
        return vscode_languageserver_protocol_1.WillSaveTextDocumentWaitUntilRequest.type;
    }
    fillClientCapabilities(capabilities) {
        let value = (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization');
        value.willSaveWaitUntil = true;
    }
    initialize(capabilities, documentSelector) {
        let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync;
        if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.willSaveWaitUntil) {
            this.register({
                id: UUID.generateUuid(),
                registerOptions: { documentSelector: documentSelector }
            });
        }
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        if (!this._listener) {
            this._listener = vscode_1.workspace.onWillSaveTextDocument(this.callback, this);
        }
        this._selectors.set(data.id, this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector));
    }
    callback(event) {
        if (features_1.TextDocumentEventFeature.textDocumentFilter(this._selectors.values(), event.document) && !this._client.hasDedicatedTextSynchronizationFeature(event.document)) {
            let middleware = this._client.middleware;
            let willSaveWaitUntil = (event) => {
                return this._client.sendRequest(vscode_languageserver_protocol_1.WillSaveTextDocumentWaitUntilRequest.type, this._client.code2ProtocolConverter.asWillSaveTextDocumentParams(event)).then(async (edits) => {
                    let vEdits = await this._client.protocol2CodeConverter.asTextEdits(edits);
                    return vEdits === undefined ? [] : vEdits;
                });
            };
            event.waitUntil(middleware.willSaveWaitUntil
                ? middleware.willSaveWaitUntil(event, willSaveWaitUntil)
                : willSaveWaitUntil(event));
        }
    }
    unregister(id) {
        this._selectors.delete(id);
        if (this._selectors.size === 0 && this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    dispose() {
        this._selectors.clear();
        if (this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
}
exports.WillSaveWaitUntilFeature = WillSaveWaitUntilFeature;
class DidSaveTextDocumentFeature extends features_1.TextDocumentEventFeature {
    constructor(client) {
        super(client, vscode_1.workspace.onDidSaveTextDocument, vscode_languageserver_protocol_1.DidSaveTextDocumentNotification.type, () => client.middleware.didSave, (textDocument) => client.code2ProtocolConverter.asSaveTextDocumentParams(textDocument, this._includeText), (data) => data, features_1.TextDocumentEventFeature.textDocumentFilter);
        this._includeText = false;
    }
    get registrationType() {
        return vscode_languageserver_protocol_1.DidSaveTextDocumentNotification.type;
    }
    fillClientCapabilities(capabilities) {
        (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').didSave = true;
    }
    initialize(capabilities, documentSelector) {
        const textDocumentSyncOptions = capabilities.resolvedTextDocumentSync;
        if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.save) {
            const saveOptions = typeof textDocumentSyncOptions.save === 'boolean'
                ? { includeText: false }
                : { includeText: !!textDocumentSyncOptions.save.includeText };
            this.register({
                id: UUID.generateUuid(),
                registerOptions: Object.assign({}, { documentSelector: documentSelector }, saveOptions)
            });
        }
    }
    register(data) {
        this._includeText = !!data.registerOptions.includeText;
        super.register(data);
    }
    getTextDocument(data) {
        return data;
    }
}
exports.DidSaveTextDocumentFeature = DidSaveTextDocumentFeature;
