Add table of contents provider abstraction (#152504)

We currently re-compute the same table of contents for markdown files multiple times. This is because multiple language features all need table of contents

With this change, we introduce a new `TableOfContentsProvider` which maintains a cache of the table of contents per file. This provider is then passed into every caller that needs a toc
This commit is contained in:
Matt Bierner
2022-06-17 11:20:02 -07:00
committed by GitHub
parent 5947c2a93c
commit dea813ff7c
16 changed files with 133 additions and 86 deletions

View File

@@ -8,16 +8,16 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { CommandManager } from '../commandManager';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { MdTableOfContentsWatcher } from '../test/tableOfContentsWatcher';
import { Delayer } from '../util/async';
import { noopToken } from '../util/cancellation';
import { Disposable } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
import { Limiter } from '../util/limiter';
import { ResourceMap } from '../util/resourceMap';
import { MdTableOfContentsWatcher } from '../test/tableOfContentsWatcher';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalHref, MdLink, MdLinkSource, MdLinkProvider, LinkDefinitionSet } from './documentLinkProvider';
import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinkProvider';
import { MdReferencesProvider, tryFindMdDocumentForLink } from './references';
const localize = nls.loadMessageBundle();
@@ -448,9 +448,9 @@ class FileLinkMap {
export class DiagnosticComputer {
constructor(
private readonly engine: MarkdownEngine,
private readonly workspaceContents: MdWorkspaceContents,
private readonly linkProvider: MdLinkProvider,
private readonly tocProvider: MdTableOfContentsProvider,
) { }
public async getDiagnostics(doc: SkinnyTextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: readonly MdLink[] }> {
@@ -475,7 +475,7 @@ export class DiagnosticComputer {
return [];
}
const toc = await TableOfContents.create(this.engine, doc);
const toc = await this.tocProvider.get(doc.uri);
if (token.isCancellationRequested) {
return [];
}
@@ -552,7 +552,7 @@ export class DiagnosticComputer {
// Validate each of the links to headers in the file
const fragmentLinks = links.filter(x => x.fragment);
if (fragmentLinks.length) {
const toc = await TableOfContents.create(this.engine, hrefDoc);
const toc = await this.tocProvider.get(hrefDoc.uri);
for (const link of fragmentLinks) {
if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.text)) {
const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment);
@@ -625,16 +625,17 @@ export function registerDiagnosticSupport(
workspaceContents: MdWorkspaceContents,
linkProvider: MdLinkProvider,
commandManager: CommandManager,
referenceComputer: MdReferencesProvider,
referenceProvider: MdReferencesProvider,
tocProvider: MdTableOfContentsProvider,
): vscode.Disposable {
const configuration = new VSCodeDiagnosticConfiguration();
const manager = new DiagnosticManager(
engine,
workspaceContents,
new DiagnosticComputer(engine, workspaceContents, linkProvider),
new DiagnosticComputer(workspaceContents, linkProvider, tocProvider),
configuration,
new DiagnosticCollectionReporter(),
referenceComputer);
referenceProvider);
return vscode.Disposable.from(
configuration,
manager,

View File

@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents, TocEntry } from '../tableOfContents';
import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents';
import { SkinnyTextDocument } from '../workspaceContents';
interface MarkdownSymbol {
@@ -17,16 +16,16 @@ interface MarkdownSymbol {
export class MdDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
constructor(
private readonly engine: MarkdownEngine
private readonly tocProvider: MdTableOfContentsProvider,
) { }
public async provideDocumentSymbolInformation(document: SkinnyTextDocument): Promise<vscode.SymbolInformation[]> {
const toc = await TableOfContents.create(this.engine, document);
const toc = await this.tocProvider.get(document.uri);
return toc.entries.map(entry => this.toSymbolInformation(entry));
}
public async provideDocumentSymbols(document: SkinnyTextDocument): Promise<vscode.DocumentSymbol[]> {
const toc = await TableOfContents.create(this.engine, document);
const toc = await this.tocProvider.get(document.uri);
const root: MarkdownSymbol = {
level: -Infinity,
children: [],
@@ -77,7 +76,7 @@ export class MdDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
export function registerDocumentSymbolSupport(
selector: vscode.DocumentSelector,
engine: MarkdownEngine,
tocProvider: MdTableOfContentsProvider,
): vscode.Disposable {
return vscode.languages.registerDocumentSymbolProvider(selector, new MdDocumentSymbolProvider(engine));
return vscode.languages.registerDocumentSymbolProvider(selector, new MdDocumentSymbolProvider(tocProvider));
}

View File

@@ -6,7 +6,7 @@
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { SkinnyTextDocument } from '../workspaceContents';
const rangeLimit = 5000;
@@ -18,7 +18,8 @@ interface MarkdownItTokenWithMap extends Token {
export class MdFoldingProvider implements vscode.FoldingRangeProvider {
constructor(
private readonly engine: MarkdownEngine
private readonly engine: MarkdownEngine,
private readonly tocProvide: MdTableOfContentsProvider,
) { }
public async provideFoldingRanges(
@@ -54,8 +55,8 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider {
.filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region);
}
private async getHeaderFoldingRanges(document: SkinnyTextDocument) {
const toc = await TableOfContents.create(this.engine, document);
private async getHeaderFoldingRanges(document: SkinnyTextDocument): Promise<vscode.FoldingRange[]> {
const toc = await this.tocProvide.get(document.uri);
return toc.entries.map(entry => {
let endLine = entry.sectionLocation.range.end.line;
if (document.lineAt(endLine).isEmptyOrWhitespace && endLine >= entry.line + 1) {
@@ -115,6 +116,7 @@ const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => {
export function registerFoldingSupport(
selector: vscode.DocumentSelector,
engine: MarkdownEngine,
tocProvider: MdTableOfContentsProvider,
): vscode.Disposable {
return vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine));
return vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine, tocProvider));
}

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import * as uri from 'vscode-uri';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents, TocEntry } from '../tableOfContents';
import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { Disposable } from '../util/dispose';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
@@ -69,6 +69,7 @@ export class MdReferencesProvider extends Disposable {
public constructor(
private readonly engine: MarkdownEngine,
private readonly workspaceContents: MdWorkspaceContents,
private readonly tocProvider: MdTableOfContentsProvider,
) {
super();
@@ -77,7 +78,7 @@ export class MdReferencesProvider extends Disposable {
}
public async getReferencesAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
const toc = await TableOfContents.create(this.engine, document);
const toc = await this.tocProvider.get(document.uri);
if (token.isCancellationRequested) {
return [];
}
@@ -184,7 +185,7 @@ export class MdReferencesProvider extends Disposable {
const references: MdReference[] = [];
if (targetDoc && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) {
const toc = await TableOfContents.create(this.engine, targetDoc);
const toc = await this.tocProvider.get(targetDoc.uri);
const entry = toc.lookup(sourceLink.href.fragment);
if (entry) {
references.push({

View File

@@ -5,7 +5,7 @@
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents, TocEntry } from '../tableOfContents';
import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents';
import { SkinnyTextDocument } from '../workspaceContents';
interface MarkdownItTokenWithMap extends Token {
@@ -15,7 +15,8 @@ interface MarkdownItTokenWithMap extends Token {
export class MdSmartSelect implements vscode.SelectionRangeProvider {
constructor(
private readonly engine: MarkdownEngine
private readonly engine: MarkdownEngine,
private readonly tocProvider: MdTableOfContentsProvider,
) { }
public async provideSelectionRanges(document: SkinnyTextDocument, positions: vscode.Position[], _token: vscode.CancellationToken): Promise<vscode.SelectionRange[] | undefined> {
@@ -54,7 +55,7 @@ export class MdSmartSelect implements vscode.SelectionRangeProvider {
}
private async getHeaderSelectionRange(document: SkinnyTextDocument, position: vscode.Position): Promise<vscode.SelectionRange | undefined> {
const toc = await TableOfContents.create(this.engine, document);
const toc = await this.tocProvider.get(document.uri);
const headerInfo = getHeadersForPosition(toc.entries, position);
@@ -253,6 +254,7 @@ function getFirstChildHeader(document: SkinnyTextDocument, header?: TocEntry, to
export function registerSmartSelectSupport(
selector: vscode.DocumentSelector,
engine: MarkdownEngine,
tocProvider: MdTableOfContentsProvider,
): vscode.Disposable {
return vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine));
return vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine, tocProvider));
}