"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.WorkspaceFeature = exports.TextDocumentLanguageFeature = exports.TextDocumentEventFeature = exports.DynamicDocumentFeature = exports.DynamicFeature = exports.StaticFeature = exports.ensure = exports.LSPCancellationError = void 0;
const vscode_1 = require("vscode");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const Is = require("./utils/is");
const UUID = require("./utils/uuid");
class LSPCancellationError extends vscode_1.CancellationError {
    constructor(data) {
        super();
        this.data = data;
    }
}
exports.LSPCancellationError = LSPCancellationError;
function ensure(target, key) {
    if (target[key] === undefined) {
        target[key] = {};
    }
    return target[key];
}
exports.ensure = ensure;
var StaticFeature;
(function (StaticFeature) {
    function is(value) {
        const candidate = value;
        return candidate !== undefined && candidate !== null &&
            Is.func(candidate.fillClientCapabilities) && Is.func(candidate.initialize) && Is.func(candidate.getState) && Is.func(candidate.dispose) &&
            (candidate.fillInitializeParams === undefined || Is.func(candidate.fillInitializeParams));
    }
    StaticFeature.is = is;
})(StaticFeature = exports.StaticFeature || (exports.StaticFeature = {}));
var DynamicFeature;
(function (DynamicFeature) {
    function is(value) {
        const candidate = value;
        return candidate !== undefined && candidate !== null &&
            Is.func(candidate.fillClientCapabilities) && Is.func(candidate.initialize) && Is.func(candidate.getState) && Is.func(candidate.dispose) &&
            (candidate.fillInitializeParams === undefined || Is.func(candidate.fillInitializeParams)) && Is.func(candidate.register) &&
            Is.func(candidate.unregister) && candidate.registrationType !== undefined;
    }
    DynamicFeature.is = is;
})(DynamicFeature = exports.DynamicFeature || (exports.DynamicFeature = {}));
/**
 * An abstract dynamic feature implementation that operates on documents (e.g. text
 * documents or notebooks).
 */
class DynamicDocumentFeature {
    constructor(client) {
        this._client = client;
    }
    /**
     * Returns the state the feature is in.
     */
    getState() {
        const selectors = this.getDocumentSelectors();
        let count = 0;
        for (const selector of selectors) {
            count++;
            for (const document of vscode_1.workspace.textDocuments) {
                if (vscode_1.languages.match(selector, document) > 0) {
                    return { kind: 'document', id: this.registrationType.method, registrations: true, matches: true };
                }
            }
        }
        const registrations = count > 0;
        return { kind: 'document', id: this.registrationType.method, registrations, matches: false };
    }
}
exports.DynamicDocumentFeature = DynamicDocumentFeature;
/**
 * An abstract base class to implement features that react to events
 * emitted from text documents.
 */
class TextDocumentEventFeature extends DynamicDocumentFeature {
    constructor(client, event, type, middleware, createParams, textDocument, selectorFilter) {
        super(client);
        this._event = event;
        this._type = type;
        this._middleware = middleware;
        this._createParams = createParams;
        this._textDocument = textDocument;
        this._selectorFilter = selectorFilter;
        this._selectors = new Map();
        this._onNotificationSent = new vscode_1.EventEmitter();
    }
    static textDocumentFilter(selectors, textDocument) {
        for (const selector of selectors) {
            if (vscode_1.languages.match(selector, textDocument) > 0) {
                return true;
            }
        }
        return false;
    }
    getStateInfo() {
        return [this._selectors.values(), false];
    }
    getDocumentSelectors() {
        return this._selectors.values();
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        if (!this._listener) {
            this._listener = this._event((data) => {
                this.callback(data).catch((error) => {
                    this._client.error(`Sending document notification ${this._type.method} failed.`, error);
                });
            });
        }
        this._selectors.set(data.id, this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector));
    }
    async callback(data) {
        const doSend = async (data) => {
            const params = this._createParams(data);
            await this._client.sendNotification(this._type, params);
            this.notificationSent(this.getTextDocument(data), this._type, params);
        };
        if (this.matches(data)) {
            const middleware = this._middleware();
            return middleware ? middleware(data, (data) => doSend(data)) : doSend(data);
        }
    }
    matches(data) {
        if (this._client.hasDedicatedTextSynchronizationFeature(this._textDocument(data))) {
            return false;
        }
        return !this._selectorFilter || this._selectorFilter(this._selectors.values(), data);
    }
    get onNotificationSent() {
        return this._onNotificationSent.event;
    }
    notificationSent(textDocument, type, params) {
        this._onNotificationSent.fire({ textDocument, type, params });
    }
    unregister(id) {
        this._selectors.delete(id);
        if (this._selectors.size === 0 && this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    dispose() {
        this._selectors.clear();
        this._onNotificationSent.dispose();
        if (this._listener) {
            this._listener.dispose();
            this._listener = undefined;
        }
    }
    getProvider(document) {
        for (const selector of this._selectors.values()) {
            if (vscode_1.languages.match(selector, document) > 0) {
                return {
                    send: (data) => {
                        return this.callback(data);
                    }
                };
            }
        }
        return undefined;
    }
}
exports.TextDocumentEventFeature = TextDocumentEventFeature;
/**
 * A abstract feature implementation that registers language providers
 * for text documents using a given document selector.
 */
class TextDocumentLanguageFeature extends DynamicDocumentFeature {
    constructor(client, registrationType) {
        super(client);
        this._registrationType = registrationType;
        this._registrations = new Map();
    }
    *getDocumentSelectors() {
        for (const registration of this._registrations.values()) {
            const selector = registration.data.registerOptions.documentSelector;
            if (selector === null) {
                continue;
            }
            yield this._client.protocol2CodeConverter.asDocumentSelector(selector);
        }
    }
    get registrationType() {
        return this._registrationType;
    }
    register(data) {
        if (!data.registerOptions.documentSelector) {
            return;
        }
        let registration = this.registerLanguageProvider(data.registerOptions, data.id);
        this._registrations.set(data.id, { disposable: registration[0], data, provider: registration[1] });
    }
    unregister(id) {
        let registration = this._registrations.get(id);
        if (registration !== undefined) {
            registration.disposable.dispose();
        }
    }
    dispose() {
        this._registrations.forEach((value) => {
            value.disposable.dispose();
        });
        this._registrations.clear();
    }
    getRegistration(documentSelector, capability) {
        if (!capability) {
            return [undefined, undefined];
        }
        else if (vscode_languageserver_protocol_1.TextDocumentRegistrationOptions.is(capability)) {
            const id = vscode_languageserver_protocol_1.StaticRegistrationOptions.hasId(capability) ? capability.id : UUID.generateUuid();
            const selector = capability.documentSelector ?? documentSelector;
            if (selector) {
                return [id, Object.assign({}, capability, { documentSelector: selector })];
            }
        }
        else if (Is.boolean(capability) && capability === true || vscode_languageserver_protocol_1.WorkDoneProgressOptions.is(capability)) {
            if (!documentSelector) {
                return [undefined, undefined];
            }
            const options = (Is.boolean(capability) && capability === true ? { documentSelector } : Object.assign({}, capability, { documentSelector }));
            return [UUID.generateUuid(), options];
        }
        return [undefined, undefined];
    }
    getRegistrationOptions(documentSelector, capability) {
        if (!documentSelector || !capability) {
            return undefined;
        }
        return (Is.boolean(capability) && capability === true ? { documentSelector } : Object.assign({}, capability, { documentSelector }));
    }
    getProvider(textDocument) {
        for (const registration of this._registrations.values()) {
            let selector = registration.data.registerOptions.documentSelector;
            if (selector !== null && vscode_1.languages.match(this._client.protocol2CodeConverter.asDocumentSelector(selector), textDocument) > 0) {
                return registration.provider;
            }
        }
        return undefined;
    }
    getAllProviders() {
        const result = [];
        for (const item of this._registrations.values()) {
            result.push(item.provider);
        }
        return result;
    }
}
exports.TextDocumentLanguageFeature = TextDocumentLanguageFeature;
class WorkspaceFeature {
    constructor(client, registrationType) {
        this._client = client;
        this._registrationType = registrationType;
        this._registrations = new Map();
    }
    getState() {
        const registrations = this._registrations.size > 0;
        return { kind: 'workspace', id: this._registrationType.method, registrations };
    }
    get registrationType() {
        return this._registrationType;
    }
    register(data) {
        const registration = this.registerLanguageProvider(data.registerOptions);
        this._registrations.set(data.id, { disposable: registration[0], provider: registration[1] });
    }
    unregister(id) {
        let registration = this._registrations.get(id);
        if (registration !== undefined) {
            registration.disposable.dispose();
        }
    }
    dispose() {
        this._registrations.forEach((registration) => {
            registration.disposable.dispose();
        });
        this._registrations.clear();
    }
    getProviders() {
        const result = [];
        for (const registration of this._registrations.values()) {
            result.push(registration.provider);
        }
        return result;
    }
}
exports.WorkspaceFeature = WorkspaceFeature;
