UNPKG

vscode-languageclient

Version:
896 lines (895 loc) 42.8 kB
"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 vscode_1 = require("vscode"); const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const uuid_1 = require("./utils/uuid"); const globPattern_1 = require("./utils/globPattern"); 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["onFocus"] = "onFocus"; })(DiagnosticPullMode || (exports.DiagnosticPullMode = DiagnosticPullMode = {})); var RequestStateKind; (function (RequestStateKind) { RequestStateKind["active"] = "open"; RequestStateKind["reschedule"] = "reschedule"; RequestStateKind["outDated"] = "drop"; })(RequestStateKind || (RequestStateKind = {})); 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 { documentPullStates; workspacePullStates; 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); } tracksSameVersion(kind, document) { const key = document.uri.toString(); const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates; const state = states.get(key); return state !== undefined && state.pulledVersion === document.version; } 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 { isDisposed; client; visibleDocuments; options; onDidChangeDiagnosticsEmitter; provider; diagnostics; openRequests; documentStates; workspaceErrorCounter; workspaceCancellation; workspaceTimeout; constructor(client, visibleDocuments, options) { this.client = client; this.visibleDocuments = visibleDocuments; this.options = options; this.isDisposed = false; this.onDidChangeDiagnosticsEmitter = new vscode_1.EventEmitter(); this.provider = this.createProvider(); this.diagnostics = this.createDiagnosticCollection(); this.openRequests = new Map(); this.documentStates = new DocumentPullStateTracker(); this.workspaceErrorCounter = 0; } createDiagnosticCollection() { if (this.client.clientOptions.diagnosticCollectionProvider === undefined) { return vscode_1.languages.createDiagnosticCollection(this.options.identifier); } else { return this.client.clientOptions.diagnosticCollectionProvider.create(this.options.identifier, features_1.DiagnosticCollectionSource.pull); } } knows(kind, document) { const uri = document instanceof vscode_1.Uri ? document : document.uri; return this.documentStates.tracks(kind, document) || this.openRequests.has(uri.toString()); } knowsSameVersion(kind, document) { const requestState = this.openRequests.get(document.uri.toString()); if (requestState === undefined) { return this.documentStates.tracksSameVersion(kind, document); } // A reschedule request is independent of a version so it will trigger // on the latest version no matter what. if (requestState.state === RequestStateKind.reschedule) { return true; } if (requestState.state === RequestStateKind.outDated) { return false; } return requestState.version === document.version; } 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.visibleDocuments.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) { if (this.isDisposed) { return; } const uri = document instanceof vscode_1.Uri ? document : document.uri; const key = uri.toString(); const request = this.openRequests.get(key); if (this.options.workspaceDiagnostics && uri.scheme !== 'untitled') { // If we run workspace diagnostic pull a last time for the diagnostics // and then 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); }); } // The previous resultId from the workspace pull state can map to diagnostics we no longer have // (e.g. they came from a workspace report but were overwritten by a later document pull request). // Clear the workspace pull state for this document as well to ensure we get fresh diagnostics. this.forget(PullState.workspace, 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 (this.isDisposed) { return { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] }; } if (token.isCancellationRequested) { throw new vscode_1.CancellationError(); } if (result === undefined || result === null) { 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: [] }, true, true); }); }; 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, resultReporter) => { 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 if (this.client.clientOptions.diagnosticCollectionProvider !== undefined) { this.client.clientOptions.diagnosticCollectionProvider.dispose(this.diagnostics, features_1.DiagnosticCollectionSource.pull); } else { this.diagnostics.dispose(); } } } class BackgroundScheduler { client; diagnosticRequestor; lastDocumentToPull; documents; timeoutHandle; // The problem is that there could be outstanding diagnostic requests // when we shutdown which when we receive the result will trigger a // reschedule. So we remember if the background scheduler got disposed // and ignore those re-schedules isDisposed; constructor(client, diagnosticRequestor) { this.client = client; 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); // Make sure we run up to that document. We could // consider inserting it after the current last // document for performance reasons but it might not catch // all interfile dependencies. this.lastDocumentToPull = document; } remove(document) { const key = DocumentOrUri.asKey(document); this.documents.delete(key); // No more documents. Stop background activity. if (this.documents.size === 0) { this.stop(); return; } else if (key === this.lastDocumentToPullKey()) { // The remove document was the one we would run up to. So // take the one before it. const before = this.documents.before(key); if (before === undefined) { this.stop(); } else { this.lastDocumentToPull = before; } } } trigger() { this.lastDocumentToPull = this.documents.last; this.runLoop(); } runLoop() { if (this.isDisposed === true) { return; } // We have an empty background list. Make sure we stop // background activity. if (this.documents.size === 0) { this.stop(); return; } // We have no last document anymore so stop the loop if (this.lastDocumentToPull === undefined) { return; } // We have a timeout in the loop. So we should not schedule // another run. if (this.timeoutHandle !== undefined) { return; } this.timeoutHandle = (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(() => { const document = this.documents.first; if (document === undefined) { return; } const key = DocumentOrUri.asKey(document); this.diagnosticRequestor.pullAsync(document).catch((error) => { this.client.error(`Document pull failed for text document ${key}`, error, false); }).finally(() => { this.timeoutHandle = undefined; this.documents.set(key, document, vscode_languageserver_protocol_1.Touch.Last); if (key !== this.lastDocumentToPullKey()) { this.runLoop(); } }); }, 500); } dispose() { this.isDisposed = true; this.stop(); this.documents.clear(); this.lastDocumentToPull = undefined; } stop() { this.timeoutHandle?.dispose(); this.timeoutHandle = undefined; this.lastDocumentToPull = undefined; } lastDocumentToPullKey() { return this.lastDocumentToPull !== undefined ? DocumentOrUri.asKey(this.lastDocumentToPull) : undefined; } } class DiagnosticFeatureProviderImpl { disposable; diagnosticRequestor; activeTextDocument; backgroundScheduler; constructor(client, visibleDocuments, options) { const diagnosticPullOptions = Object.assign({ onChange: false, onSave: false, onFocus: false }, client.clientOptions.diagnosticPullOptions); const documentSelector = client.protocol2CodeConverter.asDocumentSelector(options.documentSelector); const disposables = []; const matchFilter = (filter, resource) => { // 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 && !(0, globPattern_1.matchGlobPattern)(filter.pattern, resource)) { return false; } return true; }; 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; } if (matchFilter(filter, resource)) { return true; } } return false; }; const matches = (document) => { return document instanceof vscode_1.Uri ? matchResource(document) : vscode_1.languages.match(documentSelector, document) > 0 && visibleDocuments.isVisible(document); }; const matchesCell = (cell) => { // Cells match if the language is allowed and if the containing notebook is visible. return vscode_1.languages.match(documentSelector, cell.document) > 0 && visibleDocuments.isVisible(cell.notebook.uri); }; const isActiveDocument = (document) => { return document instanceof vscode_1.Uri ? this.activeTextDocument?.uri.toString() === document.toString() : this.activeTextDocument === document; }; this.diagnosticRequestor = new DiagnosticRequestor(client, visibleDocuments, options); this.backgroundScheduler = new BackgroundScheduler(client, this.diagnosticRequestor); const addToBackgroundIfNeeded = (document) => { if (!matches(document) || !options.interFileDependencies || isActiveDocument(document) || diagnosticPullOptions.onChange === false) { return; } this.backgroundScheduler.add(document); }; const considerDocument = (textDocument, mode) => { return (diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, mode)) && this.diagnosticRequestor.knows(PullState.document, textDocument); }; this.activeTextDocument = vscode_1.window.activeTextEditor?.document; disposables.push(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); if (diagnosticPullOptions.onFocus === true && matches(this.activeTextDocument) && considerDocument(this.activeTextDocument, DiagnosticPullMode.onFocus)) { this.diagnosticRequestor.pull(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.knowsSameVersion(PullState.document, textDocument)) { return; } if (matches(textDocument)) { this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); }); } })); const notebookFeature = client.getFeature(vscode_languageserver_protocol_1.NotebookDocumentSyncRegistrationType.method); disposables.push(notebookFeature.onOpenNotificationSent((event) => { // Send a pull for all opened cells in the notebook. for (const cell of event.getCells()) { if (matchesCell(cell)) { this.diagnosticRequestor.pull(cell.document, () => { addToBackgroundIfNeeded(cell.document); }); } } })); disposables.push(visibleDocuments.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 and 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()); } } // Do the same for already open notebook cells. for (const notebookDocument of vscode_1.workspace.notebookDocuments) { for (const cell of notebookDocument.getCells()) { if (matchesCell(cell)) { this.diagnosticRequestor.pull(cell.document, () => { addToBackgroundIfNeeded(cell.document); }); pulledTextDocuments.add(cell.document.uri.toString()); } } } // Pull all visible documents if not already pulled as text document if (diagnosticPullOptions.onTabs === true) { for (const resource of visibleDocuments.getResources()) { 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 (considerDocument(textDocument, DiagnosticPullMode.onType)) { this.diagnosticRequestor.pull(textDocument, () => { this.backgroundScheduler.trigger(); }); } })); disposables.push(notebookFeature.onChangeNotificationSent(async (event) => { // Send a pull for all changed cells in the notebook. const textEvents = event.cells?.textContent || []; const changedCells = textEvents.map((c) => event.notebook.getCells().find(cell => cell.document.uri.toString() === c.document.uri.toString())); for (const cell of changedCells) { if (cell && matchesCell(cell)) { this.diagnosticRequestor.pull(cell.document, () => { this.backgroundScheduler.trigger(); }); } } // Clear out any closed cells. const closedCells = event.cells?.structure?.didClose || []; for (const cell of closedCells) { this.diagnosticRequestor.forgetDocument(cell.document); } // Send a pull for any new opened cells. const openedCells = event.cells?.structure?.didOpen || []; for (const cell of openedCells) { if (matchesCell(cell)) { this.diagnosticRequestor.pull(cell.document, () => { 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 (considerDocument(textDocument, DiagnosticPullMode.onSave)) { this.diagnosticRequestor.pull(event.textDocument); } })); disposables.push(notebookFeature.onSaveNotificationSent((event) => { for (const cell of event.getCells()) { if (matchesCell(cell)) { this.diagnosticRequestor.pull(cell.document); } } })); } // When the document closes clear things up. We do that right before the // close notification is sent to the server so that it is not taken // from the pull queue in case it is still there. const closeFeature = client.getFeature(vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.method); disposables.push(closeFeature.onAboutToSendNotification((event) => { this.cleanUpDocument(event.textDocument); })); disposables.push(notebookFeature.onCloseNotificationSent((event) => { for (const cell of event.getCells()) { this.cleanUpDocument(cell.document); } })); // Same when a visible document closes. disposables.push(visibleDocuments.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; } forget(document) { this.cleanUpDocument(document); } cleanUpDocument(document) { this.backgroundScheduler.remove(document); if (this.diagnosticRequestor.knows(PullState.document, document)) { this.diagnosticRequestor.forgetDocument(document); } } } class DiagnosticFeature extends features_1.TextDocumentLanguageFeature { constructor(client) { super(client, vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type); } fillClientCapabilities(capabilities) { const capability = ensure(ensure(capabilities, 'textDocument'), 'diagnostic'); capability.relatedInformation = true; capability.tagSupport = { valueSet: [vscode_languageserver_protocol_1.DiagnosticTag.Unnecessary, vscode_languageserver_protocol_1.DiagnosticTag.Deprecated] }; capability.codeDescriptionSupport = true; capability.dataSupport = true; 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; // VS Code has no support for markup content in diagnostic messages. capability.markupMessageSupport = 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(); } }); const [id, options] = this.getRegistration(documentSelector, capabilities.diagnosticProvider); if (!id || !options) { return; } this.register({ id: id, registerOptions: options }); } clear() { super.clear(); } refresh() { for (const provider of this.getAllProviders()) { provider.onDidChangeDiagnosticsEmitter.fire(); } } registerLanguageProvider(options) { const provider = new DiagnosticFeatureProviderImpl(this._client, this._client.visibleDocuments, options); return [provider.disposable, provider]; } } exports.DiagnosticFeature = DiagnosticFeature;