"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 = vsdiag = {})); var DiagnosticPullMode; (function (DiagnosticPullMode) { DiagnosticPullMode["onType"] = "onType"; DiagnosticPullMode["onSave"] = "onSave"; })(DiagnosticPullMode || (exports.DiagnosticPullMode = 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; } else if (input instanceof vscode_1.TabInputCustom) { uri = input.uri; } 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); }); } })); disposables.push(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; } } // In VS Code the event timing is as follows: // 1. tab events are fired. // 2. open document events are fired and internal data structures like // workspace.textDocuments and Window.activeTextEditor are updated. // // This means: for newly created tab/editors we don't find the underlying // document yet. So we do nothing an rely on the underlying open document event // to be fired. if (textDocument !== undefined && matches(textDocument)) { this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); }); } } })); // 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 }); } clear() { if (this.tabs !== undefined) { this.tabs.dispose(); this.tabs = undefined; } super.clear(); } 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;