diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index fc0ea7989e4..44f79742a48 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -324,12 +324,18 @@ export class DiagnosticManager extends Disposable { this._register(workspaceContents.onDidCreateMarkdownDocument(doc => { this.triggerDiagnostics(doc.uri); + // Links in other files may have become valid + this.triggerForReferencingFiles(doc.uri); })); this._register(workspaceContents.onDidChangeMarkdownDocument(doc => { this.triggerDiagnostics(doc.uri); })); + this._register(workspaceContents.onDidDeleteMarkdownDocument(uri => { + this.triggerForReferencingFiles(uri); + })); + this._register(vscode.workspace.onDidCloseTextDocument(({ uri }) => { this.pendingDiagnostics.delete(uri); this.inFlightDiagnostics.cancel(uri); @@ -337,7 +343,6 @@ export class DiagnosticManager extends Disposable { this.reporter.delete(uri); })); - this._register(this.linkWatcher.onDidChangeLinkedToFile(changedDocuments => { for (const resource of changedDocuments) { const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resource.toString()); @@ -347,22 +352,28 @@ export class DiagnosticManager extends Disposable { } })); - this.tableOfContentsWatcher = this._register(new MdTableOfContentsWatcher(workspaceContents, tocProvider, delay)); - this._register(this.tableOfContentsWatcher.onTocChanged(async e => { - // When the toc of a document changes, revalidate every file that linked to it too - const triggered = new ResourceMap(); - for (const ref of await this.referencesProvider.getAllReferencesToFile(e.uri, noopToken)) { - const file = ref.location.uri; - if (!triggered.has(file)) { - this.triggerDiagnostics(file); - triggered.set(file); - } - } + this.tableOfContentsWatcher = this._register(new MdTableOfContentsWatcher(workspaceContents, tocProvider, delay / 2)); + this._register(this.tableOfContentsWatcher.onTocChanged(e => { + return this.triggerForReferencingFiles(e.uri); })); this.ready = this.rebuild(); } + private triggerForReferencingFiles(uri: vscode.Uri): Promise { + return this.reporter.addWorkItem( + (async () => { + const triggered = new ResourceMap>(); + for (const ref of await this.referencesProvider.getAllReferencesToFile(uri, noopToken)) { + const file = ref.location.uri; + if (!triggered.has(file)) { + triggered.set(file, this.triggerDiagnostics(file)); + } + } + await Promise.all(triggered.values()); + })()); + } + public override dispose() { super.dispose(); this.pendingDiagnostics.clear(); diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index c438f4a7333..dfc6051be3e 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -40,7 +40,7 @@ async function getComputedDiagnostics(doc: InMemoryDocument, workspace: MdWorksp } function assertDiagnosticsEqual(actual: readonly vscode.Diagnostic[], expectedRanges: readonly vscode.Range[]) { - assert.strictEqual(actual.length, expectedRanges.length); + assert.strictEqual(actual.length, expectedRanges.length, "Diagnostic count equal"); for (let i = 0; i < actual.length; ++i) { assertRangeEqual(actual[i].range, expectedRanges[i], `Range ${i} to be equal`); @@ -449,7 +449,7 @@ suite('Markdown: Diagnostics manager', () => { tocProvider, nulLogger, 0); - _disposables.push(manager, referencesProvider); + _disposables.push(linkProvider, tocProvider, referencesProvider, manager); return manager; } @@ -554,4 +554,40 @@ suite('Markdown: Diagnostics manager', () => { new vscode.Range(2, 7, 2, 17), ]); }); + + test('Should revalidate linked files when file is deleted/created', async () => { + const doc1Uri = workspacePath('doc1.md'); + const doc1 = new InMemoryDocument(doc1Uri, joinLines( + `[text](/doc2.md)`, + `[text](/doc2.md#header)`, + )); + const doc2Uri = workspacePath('doc2.md'); + const doc2 = new InMemoryDocument(doc2Uri, joinLines( + `# Header` + )); + + const workspace = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); + const reporter = new MemoryDiagnosticReporter(); + + const manager = createDiagnosticsManager(workspace, new MemoryDiagnosticConfiguration({}), reporter); + await manager.ready; + + // Check initial state + await reporter.waitPendingWork(); + assertDiagnosticsEqual(reporter.get(doc1Uri), []); + + // Edit header + workspace.deleteDocument(doc2Uri); + + await reporter.waitPendingWork(); + assertDiagnosticsEqual(reporter.get(doc1Uri), [ + new vscode.Range(0, 7, 0, 15), + new vscode.Range(1, 7, 1, 22), + ]); + + // Revert to original file + workspace.createDocument(doc2); + await reporter.waitPendingWork(); + assertDiagnosticsEqual(reporter.get(doc1Uri), []); + }); }); diff --git a/extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts b/extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts index 2fa5dc52bb9..c4a3c767def 100644 --- a/extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts +++ b/extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts @@ -5,11 +5,11 @@ import * as vscode from 'vscode'; import { MdTableOfContentsProvider, TableOfContents } from '../tableOfContents'; +import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; import { equals } from './arrays'; +import { Delayer } from './async'; import { Disposable } from './dispose'; import { ResourceMap } from './resourceMap'; -import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; -import { Delayer } from './async'; /** * Check if the items in a table of contents have changed.