"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.DiagnosticFeature = exports.DiagnosticPullMode = exports.vsdiag = void 0;
const minimatch = require("minimatch");
const vscode_1 = require("vscode");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const uuid_1 = require("./utils/uuid");
const features_1 = require("./features");
function ensure(target, key) {
    if (target[key] === void 0) {
        target[key] = {};
    }
    return target[key];
}
var vsdiag;
(function (vsdiag) {
    let DocumentDiagnosticReportKind;
    (function (DocumentDiagnosticReportKind) {
        DocumentDiagnosticReportKind["full"] = "full";
        DocumentDiagnosticReportKind["unChanged"] = "unChanged";
    })(DocumentDiagnosticReportKind = vsdiag.DocumentDiagnosticReportKind || (vsdiag.DocumentDiagnosticReportKind = {}));
})(vsdiag = exports.vsdiag || (exports.vsdiag = {}));
var DiagnosticPullMode;
(function (DiagnosticPullMode) {
    DiagnosticPullMode["onType"] = "onType";
    DiagnosticPullMode["onSave"] = "onSave";
})(DiagnosticPullMode = exports.DiagnosticPullMode || (exports.DiagnosticPullMode = {}));
var RequestStateKind;
(function (RequestStateKind) {
    RequestStateKind["active"] = "open";
    RequestStateKind["reschedule"] = "reschedule";
    RequestStateKind["outDated"] = "drop";
})(RequestStateKind || (RequestStateKind = {}));
/**
 * Manages the open tabs. We don't directly use the tab API since for
 * diagnostics we need to de-dupe tabs that show the same resources since
 * we pull on the model not the UI.
 */
class Tabs {
    constructor() {
        this.open = new Set();
        this._onOpen = new vscode_1.EventEmitter();
        this._onClose = new vscode_1.EventEmitter();
        Tabs.fillTabResources(this.open);
        const openTabsHandler = (event) => {
            if (event.closed.length === 0 && event.opened.length === 0) {
                return;
            }
            const oldTabs = this.open;
            const currentTabs = new Set();
            Tabs.fillTabResources(currentTabs);
            const closed = new Set();
            const opened = new Set(currentTabs);
            for (const tab of oldTabs.values()) {
                if (currentTabs.has(tab)) {
                    opened.delete(tab);
                }
                else {
                    closed.add(tab);
                }
            }
            this.open = currentTabs;
            if (closed.size > 0) {
                const toFire = new Set();
                for (const item of closed) {
                    toFire.add(vscode_1.Uri.parse(item));
                }
                this._onClose.fire(toFire);
            }
            if (opened.size > 0) {
                const toFire = new Set();
                for (const item of opened) {
                    toFire.add(vscode_1.Uri.parse(item));
                }
                this._onOpen.fire(toFire);
            }
        };
        if (vscode_1.window.tabGroups.onDidChangeTabs !== undefined) {
            this.disposable = vscode_1.window.tabGroups.onDidChangeTabs(openTabsHandler);
        }
        else {
            this.disposable = { dispose: () => { } };
        }
    }
    get onClose() {
        return this._onClose.event;
    }
    get onOpen() {
        return this._onOpen.event;
    }
    dispose() {
        this.disposable.dispose();
    }
    isActive(document) {
        return document instanceof vscode_1.Uri
            ? vscode_1.window.activeTextEditor?.document.uri === document
            : vscode_1.window.activeTextEditor?.document === document;
    }
    isVisible(document) {
        const uri = document instanceof vscode_1.Uri ? document : document.uri;
        return this.open.has(uri.toString());
    }
    getTabResources() {
        const result = new Set();
        Tabs.fillTabResources(new Set(), result);
        return result;
    }
    static fillTabResources(strings, uris) {
        const seen = strings ?? new Set();
        for (const group of vscode_1.window.tabGroups.all) {
            for (const tab of group.tabs) {
                const input = tab.input;
                let uri;
                if (input instanceof vscode_1.TabInputText) {
                    uri = input.uri;
                }
                else if (input instanceof vscode_1.TabInputTextDiff) {
                    uri = input.modified;
                }
                if (uri !== undefined && !seen.has(uri.toString())) {
                    seen.add(uri.toString());
                    uris !== undefined && uris.add(uri);
                }
            }
        }
    }
}
var PullState;
(function (PullState) {
    PullState[PullState["document"] = 1] = "document";
    PullState[PullState["workspace"] = 2] = "workspace";
})(PullState || (PullState = {}));
var DocumentOrUri;
(function (DocumentOrUri) {
    function asKey(document) {
        return document instanceof vscode_1.Uri ? document.toString() : document.uri.toString();
    }
    DocumentOrUri.asKey = asKey;
})(DocumentOrUri || (DocumentOrUri = {}));
class DocumentPullStateTracker {
    constructor() {
        this.documentPullStates = new Map();
        this.workspacePullStates = new Map();
    }
    track(kind, document, arg1) {
        const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
        const [key, uri, version] = document instanceof vscode_1.Uri
            ? [document.toString(), document, arg1]
            : [document.uri.toString(), document.uri, document.version];
        let state = states.get(key);
        if (state === undefined) {
            state = { document: uri, pulledVersion: version, resultId: undefined };
            states.set(key, state);
        }
        return state;
    }
    update(kind, document, arg1, arg2) {
        const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
        const [key, uri, version, resultId] = document instanceof vscode_1.Uri
            ? [document.toString(), document, arg1, arg2]
            : [document.uri.toString(), document.uri, document.version, arg1];
        let state = states.get(key);
        if (state === undefined) {
            state = { document: uri, pulledVersion: version, resultId };
            states.set(key, state);
        }
        else {
            state.pulledVersion = version;
            state.resultId = resultId;
        }
    }
    unTrack(kind, document) {
        const key = DocumentOrUri.asKey(document);
        const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
        states.delete(key);
    }
    tracks(kind, document) {
        const key = DocumentOrUri.asKey(document);
        const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
        return states.has(key);
    }
    getResultId(kind, document) {
        const key = DocumentOrUri.asKey(document);
        const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
        return states.get(key)?.resultId;
    }
    getAllResultIds() {
        const result = [];
        for (let [uri, value] of this.workspacePullStates) {
            if (this.documentPullStates.has(uri)) {
                value = this.documentPullStates.get(uri);
            }
            if (value.resultId !== undefined) {
                result.push({ uri, value: value.resultId });
            }
        }
        return result;
    }
}
class DiagnosticRequestor {
    constructor(client, tabs, options) {
        this.client = client;
        this.tabs = tabs;
        this.options = options;
        this.isDisposed = false;
        this.onDidChangeDiagnosticsEmitter = new vscode_1.EventEmitter();
        this.provider = this.createProvider();
        this.diagnostics = vscode_1.languages.createDiagnosticCollection(options.identifier);
        this.openRequests = new Map();
        this.documentStates = new DocumentPullStateTracker();
        this.workspaceErrorCounter = 0;
    }
    knows(kind, document) {
        const uri = document instanceof vscode_1.Uri ? document : document.uri;
        return this.documentStates.tracks(kind, document) || this.openRequests.has(uri.toString());
    }
    forget(kind, document) {
        this.documentStates.unTrack(kind, document);
    }
    pull(document, cb) {
        if (this.isDisposed) {
            return;
        }
        const uri = document instanceof vscode_1.Uri ? document : document.uri;
        this.pullAsync(document).then(() => {
            if (cb) {
                cb();
            }
        }, (error) => {
            this.client.error(`Document pull failed for text document ${uri.toString()}`, error, false);
        });
    }
    async pullAsync(document, version) {
        if (this.isDisposed) {
            return;
        }
        const isUri = document instanceof vscode_1.Uri;
        const uri = isUri ? document : document.uri;
        const key = uri.toString();
        version = isUri ? version : document.version;
        const currentRequestState = this.openRequests.get(key);
        const documentState = isUri
            ? this.documentStates.track(PullState.document, document, version)
            : this.documentStates.track(PullState.document, document);
        if (currentRequestState === undefined) {
            const tokenSource = new vscode_1.CancellationTokenSource();
            this.openRequests.set(key, { state: RequestStateKind.active, document: document, version: version, tokenSource });
            let report;
            let afterState;
            try {
                report = await this.provider.provideDiagnostics(document, documentState.resultId, tokenSource.token) ?? { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] };
            }
            catch (error) {
                if (error instanceof features_1.LSPCancellationError && vscode_languageserver_protocol_1.DiagnosticServerCancellationData.is(error.data) && error.data.retriggerRequest === false) {
                    afterState = { state: RequestStateKind.outDated, document };
                }
                if (afterState === undefined && error instanceof vscode_1.CancellationError) {
                    afterState = { state: RequestStateKind.reschedule, document };
                }
                else {
                    throw error;
                }
            }
            afterState = afterState ?? this.openRequests.get(key);
            if (afterState === undefined) {
                // This shouldn't happen. Log it
                this.client.error(`Lost request state in diagnostic pull model. Clearing diagnostics for ${key}`);
                this.diagnostics.delete(uri);
                return;
            }
            this.openRequests.delete(key);
            if (!this.tabs.isVisible(document)) {
                this.documentStates.unTrack(PullState.document, document);
                return;
            }
            if (afterState.state === RequestStateKind.outDated) {
                return;
            }
            // report is only undefined if the request has thrown.
            if (report !== undefined) {
                if (report.kind === vsdiag.DocumentDiagnosticReportKind.full) {
                    this.diagnostics.set(uri, report.items);
                }
                documentState.pulledVersion = version;
                documentState.resultId = report.resultId;
            }
            if (afterState.state === RequestStateKind.reschedule) {
                this.pull(document);
            }
        }
        else {
            if (currentRequestState.state === RequestStateKind.active) {
                // Cancel the current request and reschedule a new one when the old one returned.
                currentRequestState.tokenSource.cancel();
                this.openRequests.set(key, { state: RequestStateKind.reschedule, document: currentRequestState.document });
            }
            else if (currentRequestState.state === RequestStateKind.outDated) {
                this.openRequests.set(key, { state: RequestStateKind.reschedule, document: currentRequestState.document });
            }
        }
    }
    forgetDocument(document) {
        const uri = document instanceof vscode_1.Uri ? document : document.uri;
        const key = uri.toString();
        const request = this.openRequests.get(key);
        if (this.options.workspaceDiagnostics) {
            // If we run workspace diagnostic pull a last time for the diagnostics
            // and the rely on getting them from the workspace result.
            if (request !== undefined) {
                this.openRequests.set(key, { state: RequestStateKind.reschedule, document: document });
            }
            else {
                this.pull(document, () => {
                    this.forget(PullState.document, document);
                });
            }
        }
        else {
            // We have normal pull or inter file dependencies. In this case we
            // clear the diagnostics (to have the same start as after startup).
            // We also cancel outstanding requests.
            if (request !== undefined) {
                if (request.state === RequestStateKind.active) {
                    request.tokenSource.cancel();
                }
                this.openRequests.set(key, { state: RequestStateKind.outDated, document: document });
            }
            this.diagnostics.delete(uri);
            this.forget(PullState.document, document);
        }
    }
    pullWorkspace() {
        if (this.isDisposed) {
            return;
        }
        this.pullWorkspaceAsync().then(() => {
            this.workspaceTimeout = (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(() => {
                this.pullWorkspace();
            }, 2000);
        }, (error) => {
            if (!(error instanceof features_1.LSPCancellationError) && !vscode_languageserver_protocol_1.DiagnosticServerCancellationData.is(error.data)) {
                this.client.error(`Workspace diagnostic pull failed.`, error, false);
                this.workspaceErrorCounter++;
            }
            if (this.workspaceErrorCounter <= 5) {
                this.workspaceTimeout = (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(() => {
                    this.pullWorkspace();
                }, 2000);
            }
        });
    }
    async pullWorkspaceAsync() {
        if (!this.provider.provideWorkspaceDiagnostics || this.isDisposed) {
            return;
        }
        if (this.workspaceCancellation !== undefined) {
            this.workspaceCancellation.cancel();
            this.workspaceCancellation = undefined;
        }
        this.workspaceCancellation = new vscode_1.CancellationTokenSource();
        const previousResultIds = this.documentStates.getAllResultIds().map((item) => {
            return {
                uri: this.client.protocol2CodeConverter.asUri(item.uri),
                value: item.value
            };
        });
        await this.provider.provideWorkspaceDiagnostics(previousResultIds, this.workspaceCancellation.token, (chunk) => {
            if (!chunk || this.isDisposed) {
                return;
            }
            for (const item of chunk.items) {
                if (item.kind === vsdiag.DocumentDiagnosticReportKind.full) {
                    // Favour document pull result over workspace results. So skip if it is tracked
                    // as a document result.
                    if (!this.documentStates.tracks(PullState.document, item.uri)) {
                        this.diagnostics.set(item.uri, item.items);
                    }
                }
                this.documentStates.update(PullState.workspace, item.uri, item.version ?? undefined, item.resultId);
            }
        });
    }
    createProvider() {
        const result = {
            onDidChangeDiagnostics: this.onDidChangeDiagnosticsEmitter.event,
            provideDiagnostics: (document, previousResultId, token) => {
                const provideDiagnostics = (document, previousResultId, token) => {
                    const params = {
                        identifier: this.options.identifier,
                        textDocument: { uri: this.client.code2ProtocolConverter.asUri(document instanceof vscode_1.Uri ? document : document.uri) },
                        previousResultId: previousResultId
                    };
                    if (this.isDisposed === true || !this.client.isRunning()) {
                        return { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] };
                    }
                    return this.client.sendRequest(vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type, params, token).then(async (result) => {
                        if (result === undefined || result === null || this.isDisposed || token.isCancellationRequested) {
                            return { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] };
                        }
                        if (result.kind === vscode_languageserver_protocol_1.DocumentDiagnosticReportKind.Full) {
                            return { kind: vsdiag.DocumentDiagnosticReportKind.full, resultId: result.resultId, items: await this.client.protocol2CodeConverter.asDiagnostics(result.items, token) };
                        }
                        else {
                            return { kind: vsdiag.DocumentDiagnosticReportKind.unChanged, resultId: result.resultId };
                        }
                    }, (error) => {
                        return this.client.handleFailedRequest(vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type, token, error, { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] });
                    });
                };
                const middleware = this.client.middleware;
                return middleware.provideDiagnostics
                    ? middleware.provideDiagnostics(document, previousResultId, token, provideDiagnostics)
                    : provideDiagnostics(document, previousResultId, token);
            }
        };
        if (this.options.workspaceDiagnostics) {
            result.provideWorkspaceDiagnostics = (resultIds, token, resultReporter) => {
                const convertReport = async (report) => {
                    if (report.kind === vscode_languageserver_protocol_1.DocumentDiagnosticReportKind.Full) {
                        return {
                            kind: vsdiag.DocumentDiagnosticReportKind.full,
                            uri: this.client.protocol2CodeConverter.asUri(report.uri),
                            resultId: report.resultId,
                            version: report.version,
                            items: await this.client.protocol2CodeConverter.asDiagnostics(report.items, token)
                        };
                    }
                    else {
                        return {
                            kind: vsdiag.DocumentDiagnosticReportKind.unChanged,
                            uri: this.client.protocol2CodeConverter.asUri(report.uri),
                            resultId: report.resultId,
                            version: report.version
                        };
                    }
                };
                const convertPreviousResultIds = (resultIds) => {
                    const converted = [];
                    for (const item of resultIds) {
                        converted.push({ uri: this.client.code2ProtocolConverter.asUri(item.uri), value: item.value });
                    }
                    return converted;
                };
                const provideDiagnostics = (resultIds, token) => {
                    const partialResultToken = (0, uuid_1.generateUuid)();
                    const disposable = this.client.onProgress(vscode_languageserver_protocol_1.WorkspaceDiagnosticRequest.partialResult, partialResultToken, async (partialResult) => {
                        if (partialResult === undefined || partialResult === null) {
                            resultReporter(null);
                            return;
                        }
                        const converted = {
                            items: []
                        };
                        for (const item of partialResult.items) {
                            try {
                                converted.items.push(await convertReport(item));
                            }
                            catch (error) {
                                this.client.error(`Converting workspace diagnostics failed.`, error);
                            }
                        }
                        resultReporter(converted);
                    });
                    const params = {
                        identifier: this.options.identifier,
                        previousResultIds: convertPreviousResultIds(resultIds),
                        partialResultToken: partialResultToken
                    };
                    if (this.isDisposed === true || !this.client.isRunning()) {
                        return { items: [] };
                    }
                    return this.client.sendRequest(vscode_languageserver_protocol_1.WorkspaceDiagnosticRequest.type, params, token).then(async (result) => {
                        if (token.isCancellationRequested) {
                            return { items: [] };
                        }
                        const converted = {
                            items: []
                        };
                        for (const item of result.items) {
                            converted.items.push(await convertReport(item));
                        }
                        disposable.dispose();
                        resultReporter(converted);
                        return { items: [] };
                    }, (error) => {
                        disposable.dispose();
                        return this.client.handleFailedRequest(vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type, token, error, { items: [] });
                    });
                };
                const middleware = this.client.middleware;
                return middleware.provideWorkspaceDiagnostics
                    ? middleware.provideWorkspaceDiagnostics(resultIds, token, resultReporter, provideDiagnostics)
                    : provideDiagnostics(resultIds, token, resultReporter);
            };
        }
        return result;
    }
    dispose() {
        this.isDisposed = true;
        // Cancel and clear workspace pull if present.
        this.workspaceCancellation?.cancel();
        this.workspaceTimeout?.dispose();
        // Cancel all request and mark open requests as outdated.
        for (const [key, request] of this.openRequests) {
            if (request.state === RequestStateKind.active) {
                request.tokenSource.cancel();
            }
            this.openRequests.set(key, { state: RequestStateKind.outDated, document: request.document });
        }
        // cleanup old diagnostics
        this.diagnostics.dispose();
    }
}
class BackgroundScheduler {
    constructor(diagnosticRequestor) {
        this.diagnosticRequestor = diagnosticRequestor;
        this.documents = new vscode_languageserver_protocol_1.LinkedMap();
        this.isDisposed = false;
    }
    add(document) {
        if (this.isDisposed === true) {
            return;
        }
        const key = DocumentOrUri.asKey(document);
        if (this.documents.has(key)) {
            return;
        }
        this.documents.set(key, document, vscode_languageserver_protocol_1.Touch.Last);
        this.trigger();
    }
    remove(document) {
        const key = DocumentOrUri.asKey(document);
        this.documents.delete(key);
        // No more documents. Stop background activity.
        if (this.documents.size === 0) {
            this.stop();
        }
        else if (key === this.endDocumentKey()) {
            // Make sure we have a correct last document. It could have
            this.endDocument = this.documents.last;
        }
    }
    trigger() {
        if (this.isDisposed === true) {
            return;
        }
        // We have a round running. So simply make sure we run up to the
        // last document
        if (this.intervalHandle !== undefined) {
            this.endDocument = this.documents.last;
            return;
        }
        this.endDocument = this.documents.last;
        this.intervalHandle = (0, vscode_languageserver_protocol_1.RAL)().timer.setInterval(() => {
            const document = this.documents.first;
            if (document !== undefined) {
                const key = DocumentOrUri.asKey(document);
                this.diagnosticRequestor.pull(document);
                this.documents.set(key, document, vscode_languageserver_protocol_1.Touch.Last);
                if (key === this.endDocumentKey()) {
                    this.stop();
                }
            }
        }, 200);
    }
    dispose() {
        this.isDisposed = true;
        this.stop();
        this.documents.clear();
    }
    stop() {
        this.intervalHandle?.dispose();
        this.intervalHandle = undefined;
        this.endDocument = undefined;
    }
    endDocumentKey() {
        return this.endDocument !== undefined ? DocumentOrUri.asKey(this.endDocument) : undefined;
    }
}
class DiagnosticFeatureProviderImpl {
    constructor(client, tabs, options) {
        const diagnosticPullOptions = client.clientOptions.diagnosticPullOptions ?? { onChange: true, onSave: false };
        const documentSelector = client.protocol2CodeConverter.asDocumentSelector(options.documentSelector);
        const disposables = [];
        const matchResource = (resource) => {
            const selector = options.documentSelector;
            if (diagnosticPullOptions.match !== undefined) {
                return diagnosticPullOptions.match(selector, resource);
            }
            for (const filter of selector) {
                if (!vscode_languageserver_protocol_1.TextDocumentFilter.is(filter)) {
                    continue;
                }
                // The filter is a language id. We can't determine if it matches
                // so we return false.
                if (typeof filter === 'string') {
                    return false;
                }
                if (filter.language !== undefined && filter.language !== '*') {
                    return false;
                }
                if (filter.scheme !== undefined && filter.scheme !== '*' && filter.scheme !== resource.scheme) {
                    return false;
                }
                if (filter.pattern !== undefined) {
                    const matcher = new minimatch.Minimatch(filter.pattern, { noext: true });
                    if (!matcher.makeRe()) {
                        return false;
                    }
                    if (!matcher.match(resource.fsPath)) {
                        return false;
                    }
                }
            }
            return true;
        };
        const matches = (document) => {
            return document instanceof vscode_1.Uri
                ? matchResource(document)
                : vscode_1.languages.match(documentSelector, document) > 0 && tabs.isVisible(document);
        };
        const isActiveDocument = (document) => {
            return document instanceof vscode_1.Uri
                ? this.activeTextDocument?.uri.toString() === document.toString()
                : this.activeTextDocument === document;
        };
        this.diagnosticRequestor = new DiagnosticRequestor(client, tabs, options);
        this.backgroundScheduler = new BackgroundScheduler(this.diagnosticRequestor);
        const addToBackgroundIfNeeded = (document) => {
            if (!matches(document) || !options.interFileDependencies || isActiveDocument(document)) {
                return;
            }
            this.backgroundScheduler.add(document);
        };
        this.activeTextDocument = vscode_1.window.activeTextEditor?.document;
        vscode_1.window.onDidChangeActiveTextEditor((editor) => {
            const oldActive = this.activeTextDocument;
            this.activeTextDocument = editor?.document;
            if (oldActive !== undefined) {
                addToBackgroundIfNeeded(oldActive);
            }
            if (this.activeTextDocument !== undefined) {
                this.backgroundScheduler.remove(this.activeTextDocument);
            }
        });
        // For pull model diagnostics we pull for documents visible in the UI.
        // From an eventing point of view we still rely on open document events
        // and filter the documents that are not visible in the UI instead of
        // listening to Tab events. Major reason is event timing since we need
        // to ensure that the pull is send after the document open has reached
        // the server.
        // We always pull on open.
        const openFeature = client.getFeature(vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.method);
        disposables.push(openFeature.onNotificationSent((event) => {
            const textDocument = event.textDocument;
            // We already know about this document. This can happen via a tab open.
            if (this.diagnosticRequestor.knows(PullState.document, textDocument)) {
                return;
            }
            if (matches(textDocument)) {
                this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
            }
        }));
        tabs.onOpen((opened) => {
            for (const resource of opened) {
                // We already know about this document. This can happen via a document open.
                if (this.diagnosticRequestor.knows(PullState.document, resource)) {
                    continue;
                }
                const uriStr = resource.toString();
                let textDocument;
                for (const item of vscode_1.workspace.textDocuments) {
                    if (uriStr === item.uri.toString()) {
                        textDocument = item;
                        break;
                    }
                }
                // We have an open document for the resource behind the tab.
                if (textDocument !== undefined && matches(textDocument)) {
                    this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
                }
                else if (matches(resource)) {
                    // Check if the resource matches
                    this.diagnosticRequestor.pull(resource, () => { addToBackgroundIfNeeded(resource); });
                }
            }
        });
        // Pull all diagnostics for documents that are already open
        const pulledTextDocuments = new Set();
        for (const textDocument of vscode_1.workspace.textDocuments) {
            if (matches(textDocument)) {
                this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
                pulledTextDocuments.add(textDocument.uri.toString());
            }
        }
        // Pull all tabs if not already pulled as text document
        if (diagnosticPullOptions.onTabs === true) {
            for (const resource of tabs.getTabResources()) {
                if (!pulledTextDocuments.has(resource.toString()) && matches(resource)) {
                    this.diagnosticRequestor.pull(resource, () => { addToBackgroundIfNeeded(resource); });
                }
            }
        }
        // We don't need to pull on tab open since we will receive a document open as well later on
        // and that event allows us to use a document for a match check which will have a set
        // language id.
        if (diagnosticPullOptions.onChange === true) {
            const changeFeature = client.getFeature(vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.method);
            disposables.push(changeFeature.onNotificationSent(async (event) => {
                const textDocument = event.textDocument;
                if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onType)) && this.diagnosticRequestor.knows(PullState.document, textDocument)) {
                    this.diagnosticRequestor.pull(textDocument, () => { this.backgroundScheduler.trigger(); });
                }
            }));
        }
        if (diagnosticPullOptions.onSave === true) {
            const saveFeature = client.getFeature(vscode_languageserver_protocol_1.DidSaveTextDocumentNotification.method);
            disposables.push(saveFeature.onNotificationSent((event) => {
                const textDocument = event.textDocument;
                if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onSave)) && this.diagnosticRequestor.knows(PullState.document, textDocument)) {
                    this.diagnosticRequestor.pull(event.textDocument, () => { this.backgroundScheduler.trigger(); });
                }
            }));
        }
        // When the document closes clear things up
        const closeFeature = client.getFeature(vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.method);
        disposables.push(closeFeature.onNotificationSent((event) => {
            this.cleanUpDocument(event.textDocument);
        }));
        // Same when a tabs closes.
        tabs.onClose((closed) => {
            for (const document of closed) {
                this.cleanUpDocument(document);
            }
        });
        // We received a did change from the server.
        this.diagnosticRequestor.onDidChangeDiagnosticsEmitter.event(() => {
            for (const textDocument of vscode_1.workspace.textDocuments) {
                if (matches(textDocument)) {
                    this.diagnosticRequestor.pull(textDocument);
                }
            }
        });
        // da348dc5-c30a-4515-9d98-31ff3be38d14 is the test UUID to test the middle ware. So don't auto trigger pulls.
        if (options.workspaceDiagnostics === true && options.identifier !== 'da348dc5-c30a-4515-9d98-31ff3be38d14') {
            this.diagnosticRequestor.pullWorkspace();
        }
        this.disposable = vscode_1.Disposable.from(...disposables, this.backgroundScheduler, this.diagnosticRequestor);
    }
    get onDidChangeDiagnosticsEmitter() {
        return this.diagnosticRequestor.onDidChangeDiagnosticsEmitter;
    }
    get diagnostics() {
        return this.diagnosticRequestor.provider;
    }
    cleanUpDocument(document) {
        if (this.diagnosticRequestor.knows(PullState.document, document)) {
            this.diagnosticRequestor.forgetDocument(document);
            this.backgroundScheduler.remove(document);
        }
    }
}
class DiagnosticFeature extends features_1.TextDocumentLanguageFeature {
    constructor(client) {
        super(client, vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type);
    }
    fillClientCapabilities(capabilities) {
        let capability = ensure(ensure(capabilities, 'textDocument'), 'diagnostic');
        capability.dynamicRegistration = true;
        // We first need to decide how a UI will look with related documents.
        // An easy implementation would be to only show related diagnostics for
        // the active editor.
        capability.relatedDocumentSupport = false;
        ensure(ensure(capabilities, 'workspace'), 'diagnostics').refreshSupport = true;
    }
    initialize(capabilities, documentSelector) {
        const client = this._client;
        client.onRequest(vscode_languageserver_protocol_1.DiagnosticRefreshRequest.type, async () => {
            for (const provider of this.getAllProviders()) {
                provider.onDidChangeDiagnosticsEmitter.fire();
            }
        });
        let [id, options] = this.getRegistration(documentSelector, capabilities.diagnosticProvider);
        if (!id || !options) {
            return;
        }
        this.register({ id: id, registerOptions: options });
    }
    dispose() {
        if (this.tabs !== undefined) {
            this.tabs.dispose();
            this.tabs = undefined;
        }
        super.dispose();
    }
    registerLanguageProvider(options) {
        if (this.tabs === undefined) {
            this.tabs = new Tabs();
        }
        const provider = new DiagnosticFeatureProviderImpl(this._client, this.tabs, options);
        return [provider.disposable, provider];
    }
}
exports.DiagnosticFeature = DiagnosticFeature;
