Only compute diagnostics for opened md files (#153395)

* Only compute diagnostics for opened md files

For #152494

* Make tests stable for result ordering
This commit is contained in:
Matt Bierner 2022-06-27 15:55:38 -07:00 committed by GitHub
parent 87e684ef9b
commit e13feea6ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 91 additions and 40 deletions

View file

@ -242,7 +242,9 @@ export abstract class DiagnosticReporter extends Disposable {
public abstract delete(uri: vscode.Uri): void;
public abstract areDiagnosticsEnabled(uri: vscode.Uri): boolean;
public abstract isOpen(uri: vscode.Uri): boolean;
public abstract getOpenDocuments(): ITextDocument[];
public addWorkItem(promise: Promise<any>): Promise<any> {
this.pending.add(promise);
@ -256,6 +258,7 @@ export abstract class DiagnosticReporter extends Disposable {
}
export class DiagnosticCollectionReporter extends DiagnosticReporter {
private readonly collection: vscode.DiagnosticCollection;
constructor() {
@ -269,11 +272,11 @@ export class DiagnosticCollectionReporter extends DiagnosticReporter {
}
public set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void {
this.collection.set(uri, this.areDiagnosticsEnabled(uri) ? diagnostics : []);
this.collection.set(uri, this.isOpen(uri) ? diagnostics : []);
}
public areDiagnosticsEnabled(uri: vscode.Uri): boolean {
const tabs = this.getAllTabResources();
public isOpen(uri: vscode.Uri): boolean {
const tabs = this.getTabResources();
return tabs.has(uri);
}
@ -281,7 +284,12 @@ export class DiagnosticCollectionReporter extends DiagnosticReporter {
this.collection.delete(uri);
}
private getAllTabResources(): ResourceMap<void> {
public getOpenDocuments(): ITextDocument[] {
const tabs = this.getTabResources();
return vscode.workspace.textDocuments.filter(doc => tabs.has(doc.uri));
}
private getTabResources(): ResourceMap<void> {
const openedTabDocs = new ResourceMap<void>();
for (const group of vscode.window.tabGroups.all) {
for (const tab of group.tabs) {
@ -365,7 +373,7 @@ export class DiagnosticManager extends Disposable {
return this.reporter.addWorkItem(
(async () => {
const triggered = new ResourceMap<Promise<void>>();
for (const ref of await this.referencesProvider.getAllReferencesToFile(uri, noopToken)) {
for (const ref of await this.referencesProvider.getReferencesToFileInDocs(uri, this.reporter.getOpenDocuments(), noopToken)) {
const file = ref.location.uri;
if (!triggered.has(file)) {
triggered.set(file, this.triggerDiagnostics(file));
@ -398,7 +406,7 @@ export class DiagnosticManager extends Disposable {
const doc = await this.workspace.getOrLoadMarkdownDocument(resource);
if (doc) {
await this.inFlightDiagnostics.trigger(doc.uri, async (token) => {
if (this.reporter.areDiagnosticsEnabled(doc.uri)) {
if (this.reporter.isOpen(doc.uri)) {
const state = await this.recomputeDiagnosticState(doc, token);
this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFileLinks ? state.links : []);
this.reporter.set(doc.uri, state.diagnostics);
@ -417,12 +425,7 @@ export class DiagnosticManager extends Disposable {
this.inFlightDiagnostics.clear();
return this.reporter.addWorkItem(
(async () => {
// TODO: This pulls in all md files in the workspace. Instead we only care about opened text documents.
// Need a new way to handle that.
const allDocs = await this.workspace.getAllMarkdownDocuments();
await Promise.all(Array.from(allDocs, doc => this.triggerDiagnostics(doc.uri)));
})()
Promise.all(Array.from(this.reporter.getOpenDocuments(), doc => this.triggerDiagnostics(doc.uri)))
);
}

View file

@ -33,7 +33,7 @@ export class FindFileReferencesCommand implements Command {
location: vscode.ProgressLocation.Window,
title: localize('progress.title', "Finding file references")
}, async (_progress, token) => {
const references = await this.referencesProvider.getAllReferencesToFile(resource!, token);
const references = await this.referencesProvider.getReferencesToFileInWorkspace(resource!, token);
const locations = references.map(ref => ref.location);
const config = vscode.workspace.getConfiguration('references');

View file

@ -67,7 +67,6 @@ export type MdReference = MdLinkReference | MdHeaderReference;
export class MdReferencesProvider extends Disposable {
private readonly _linkCache: MdWorkspaceInfoCache<readonly MdLink[]>;
private readonly _linkComputer: MdLinkComputer;
public constructor(
private readonly parser: IMdParser,
@ -77,8 +76,8 @@ export class MdReferencesProvider extends Disposable {
) {
super();
this._linkComputer = new MdLinkComputer(parser);
this._linkCache = this._register(new MdWorkspaceInfoCache(workspace, doc => this._linkComputer.getAllLinks(doc, noopToken)));
const linkComputer = new MdLinkComputer(parser);
this._linkCache = this._register(new MdWorkspaceInfoCache(workspace, doc => linkComputer.getAllLinks(doc, noopToken)));
}
public async getReferencesAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
@ -97,11 +96,26 @@ export class MdReferencesProvider extends Disposable {
}
}
public async getAllReferencesToFile(resource: vscode.Uri, _token: vscode.CancellationToken): Promise<MdReference[]> {
this.logger.verbose('ReferencesProvider', `getAllReferencesToFile: ${resource}`);
public async getReferencesToFileInWorkspace(resource: vscode.Uri, token: vscode.CancellationToken): Promise<MdReference[]> {
this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInWorkspace: ${resource}`);
const allLinksInWorkspace = (await this._linkCache.values()).flat();
return Array.from(this.findAllLinksToFile(resource, allLinksInWorkspace, undefined));
if (token.isCancellationRequested) {
return [];
}
return Array.from(this.findLinksToFile(resource, allLinksInWorkspace, undefined));
}
public async getReferencesToFileInDocs(resource: vscode.Uri, otherDocs: readonly ITextDocument[], token: vscode.CancellationToken): Promise<MdReference[]> {
this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInFiles: ${resource}`);
const links = (await this._linkCache.getForDocs(otherDocs)).flat();
if (token.isCancellationRequested) {
return [];
}
return Array.from(this.findLinksToFile(resource, links, undefined));
}
private async getReferencesToHeader(document: ITextDocument, header: TocEntry): Promise<MdReference[]> {
@ -137,7 +151,7 @@ export class MdReferencesProvider extends Disposable {
}
private async getReferencesToLinkAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
const docLinks = await this._linkComputer.getAllLinks(document, token);
const docLinks = (await this._linkCache.getForDocs([document]))[0];
for (const link of docLinks) {
if (link.kind === 'definition') {
@ -223,7 +237,7 @@ export class MdReferencesProvider extends Disposable {
}
}
} else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments
references.push(...this.findAllLinksToFile(resolvedResource ?? sourceLink.href.path, allLinksInWorkspace, sourceLink));
references.push(...this.findLinksToFile(resolvedResource ?? sourceLink.href.path, allLinksInWorkspace, sourceLink));
}
return references;
@ -238,8 +252,8 @@ export class MdReferencesProvider extends Disposable {
|| uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath;
}
private *findAllLinksToFile(resource: vscode.Uri, allLinksInWorkspace: readonly MdLink[], sourceLink: MdLink | undefined): Iterable<MdReference> {
for (const link of allLinksInWorkspace) {
private *findLinksToFile(resource: vscode.Uri, links: readonly MdLink[], sourceLink: MdLink | undefined): Iterable<MdReference> {
for (const link of links) {
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) {
continue;
}

View file

@ -58,7 +58,7 @@ export class VsCodeOutputLogger extends Disposable implements ILogger {
const now = new Date();
return String(now.getUTCHours()).padStart(2, '0')
+ ':' + String(now.getMinutes()).padStart(2, '0')
+ ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + now.getMilliseconds();
+ ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0');
}
private updateConfiguration(): void {

View file

@ -10,6 +10,7 @@ import { DiagnosticCollectionReporter, DiagnosticComputer, DiagnosticConfigurati
import { MdLinkProvider } from '../languageFeatures/documentLinks';
import { MdReferencesProvider } from '../languageFeatures/references';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { ITextDocument } from '../types/textDocument';
import { noopToken } from '../util/cancellation';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
@ -79,6 +80,12 @@ class MemoryDiagnosticReporter extends DiagnosticReporter {
private readonly diagnostics = new ResourceMap<readonly vscode.Diagnostic[]>();
constructor(
private readonly workspace: InMemoryMdWorkspace,
) {
super();
}
override dispose(): void {
super.clear();
this.clear();
@ -93,7 +100,7 @@ class MemoryDiagnosticReporter extends DiagnosticReporter {
this.diagnostics.set(uri, diagnostics);
}
areDiagnosticsEnabled(_uri: vscode.Uri): boolean {
isOpen(_uri: vscode.Uri): boolean {
return true;
}
@ -104,6 +111,10 @@ class MemoryDiagnosticReporter extends DiagnosticReporter {
get(uri: vscode.Uri): readonly vscode.Diagnostic[] {
return orderDiagnosticsByRange(this.diagnostics.get(uri) ?? []);
}
getOpenDocuments(): ITextDocument[] {
return this.workspace.values();
}
}
suite('markdown: Diagnostic Computer', () => {
@ -454,7 +465,7 @@ suite('Markdown: Diagnostics manager', () => {
))
]));
const reporter = store.add(new MemoryDiagnosticReporter());
const reporter = store.add(new MemoryDiagnosticReporter(workspace));
const config = new MemoryDiagnosticConfiguration({ enabled: true });
const manager = createDiagnosticsManager(store, workspace, config, reporter);
@ -499,7 +510,7 @@ suite('Markdown: Diagnostics manager', () => {
`[text](#no-such-2)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
const reporter = store.add(new MemoryDiagnosticReporter());
const reporter = store.add(new MemoryDiagnosticReporter(workspace));
const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter);
await manager.ready;
@ -554,7 +565,7 @@ suite('Markdown: Diagnostics manager', () => {
`# Header`
));
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
const reporter = store.add(new MemoryDiagnosticReporter());
const reporter = store.add(new MemoryDiagnosticReporter(workspace));
const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter);
await manager.ready;

View file

@ -22,7 +22,7 @@ function getFileReferences(store: DisposableStore, resource: vscode.Uri, workspa
const engine = createNewMarkdownEngine();
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
return computer.getAllReferencesToFile(resource, noopToken);
return computer.getReferencesToFileInWorkspace(resource, noopToken);
}
function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRefs: { uri: vscode.Uri; line: number }[]) {

View file

@ -22,10 +22,14 @@ export class InMemoryMdWorkspace extends Disposable implements IMdWorkspace {
}
}
public async getAllMarkdownDocuments() {
public values() {
return Array.from(this._documents.values());
}
public async getAllMarkdownDocuments() {
return this.values();
}
public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise<ITextDocument | undefined> {
return this._documents.get(resource);
}

View file

@ -18,12 +18,19 @@ import { nulLogger } from './nulLogging';
import { joinLines, withStore, workspacePath } from './util';
function getReferences(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
async function getReferences(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
const engine = createNewMarkdownEngine();
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
const provider = new MdVsCodeReferencesProvider(computer);
return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
const refs = await provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
return refs.sort((a, b) => {
const pathCompare = a.uri.toString().localeCompare(b.uri.toString());
if (pathCompare !== 0) {
return pathCompare;
}
return a.range.start.compareTo(b.range.start);
});
}
function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expectedRefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) {
@ -130,7 +137,7 @@ suite('Markdown: Find all references', () => {
test('Should find references from header across files', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const other2Uri = workspacePath('other2.md');
const other2Uri = workspacePath('zOther2.md');
const doc = new InMemoryDocument(docUri, joinLines(
`# abc`,
@ -216,7 +223,7 @@ suite('Markdown: Find all references', () => {
test('Should find references from link across files', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const other2Uri = workspacePath('other2.md');
const other2Uri = workspacePath('zOther2.md');
const doc = new InMemoryDocument(docUri, joinLines(
`# abc`,
@ -300,9 +307,9 @@ suite('Markdown: Find all references', () => {
const refs = await getReferences(store, doc, new vscode.Position(0, 23), workspace);
assertReferencesEqual(refs!,
{ uri: other1Uri, line: 1 }, // Header definition
{ uri: docUri, line: 0 },
{ uri: docUri, line: 1 },
{ uri: other1Uri, line: 1 }, // Header definition
);
}));
@ -467,7 +474,7 @@ suite('Markdown: Find all references', () => {
{
// Check refs to header fragment
const headerRefs = await getReferences(store, otherDoc, new vscode.Position(0, 16), workspace);
assertReferencesEqual(headerRefs!,
assertReferencesEqual(headerRefs,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
{ uri: other1Uri, line: 0 },
@ -477,7 +484,7 @@ suite('Markdown: Find all references', () => {
{
// Check refs to file itself from link with ext
const fileRefs = await getReferences(store, otherDoc, new vscode.Position(0, 9), workspace);
assertReferencesEqual(fileRefs!,
assertReferencesEqual(fileRefs,
{ uri: other1Uri, line: 0, endCharacter: 14 },
{ uri: other1Uri, line: 1, endCharacter: 19 },
);
@ -485,7 +492,7 @@ suite('Markdown: Find all references', () => {
{
// Check refs to file itself from link without ext
const fileRefs = await getReferences(store, otherDoc, new vscode.Position(1, 17), workspace);
assertReferencesEqual(fileRefs!,
assertReferencesEqual(fileRefs,
{ uri: other1Uri, line: 0 },
{ uri: other1Uri, line: 1 },
);

View file

@ -143,6 +143,16 @@ export class MdWorkspaceInfoCache<T> extends Disposable {
return Array.from(await this._cache.entries(), x => x[1]);
}
public async getForDocs(docs: readonly ITextDocument[]): Promise<T[]> {
for (const doc of docs) {
if (!this._cache.has(doc.uri)) {
this.update(doc);
}
}
return Promise.all(docs.map(doc => this._cache.get(doc.uri) as Promise<T>));
}
private async ensureInit(): Promise<void> {
if (!this._init) {
this._init = this.populateCache();
@ -157,7 +167,9 @@ export class MdWorkspaceInfoCache<T> extends Disposable {
private async populateCache(): Promise<void> {
const markdownDocumentUris = await this.workspace.getAllMarkdownDocuments();
for (const document of markdownDocumentUris) {
this.update(document);
if (!this._cache.has(document.uri)) {
this.update(document);
}
}
}