mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-19 08:08:39 +01:00
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:
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user