From 2249b171f4d689a2b4cb33119a293635846152c1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 20 Jun 2022 23:43:01 -0700 Subject: [PATCH] Reduce number of times MD docs are re-tokenized (#152674) This change reduces the number of times we retokenize a markdown file by doing the following: - Use `MdTableOfContentsProvider` in more places - Introduce a `IMarkdownParser` interface that lets us drop in a caching version of the tokenizer --- .../src/commands/openDocumentLink.ts | 6 +-- .../src/commands/refreshPreview.ts | 4 +- .../src/commands/reloadPlugins.ts | 4 +- .../src/commands/renderDocument.ts | 4 +- .../src/extension.ts | 45 ++++++++++--------- .../src/languageFeatures/diagnostics.ts | 10 ++--- .../languageFeatures/documentLinkProvider.ts | 14 +++--- .../src/languageFeatures/foldingProvider.ts | 14 +++--- .../src/languageFeatures/pathCompletions.ts | 10 ++--- .../src/languageFeatures/references.ts | 10 ++--- .../src/languageFeatures/smartSelect.ts | 12 +++-- .../src/languageFeatures/workspaceCache.ts | 9 ++++ .../src/markdownEngine.ts | 41 ++++++++++++++--- .../src/preview/preview.ts | 28 ++++++------ .../src/preview/previewContentProvider.ts | 4 +- .../src/preview/previewManager.ts | 10 ++--- .../src/tableOfContents.ts | 34 +++++++------- .../src/test/diagnostic.test.ts | 2 +- .../src/test/engine.ts | 6 +-- .../src/test/smartSelect.test.ts | 6 +-- .../src/test/tableOfContentsProvider.test.ts | 26 ++++++----- .../src/test/tableOfContentsWatcher.ts | 11 +++-- .../src/util/openDocumentLink.ts | 23 +++++----- 23 files changed, 190 insertions(+), 143 deletions(-) diff --git a/extensions/markdown-language-features/src/commands/openDocumentLink.ts b/extensions/markdown-language-features/src/commands/openDocumentLink.ts index 060e280c7f6..8db059337d9 100644 --- a/extensions/markdown-language-features/src/commands/openDocumentLink.ts +++ b/extensions/markdown-language-features/src/commands/openDocumentLink.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; +import { MdTableOfContentsProvider } from '../tableOfContents'; import { openDocumentLink } from '../util/openDocumentLink'; type UriComponents = { @@ -48,13 +48,13 @@ export class OpenDocumentLinkCommand implements Command { } public constructor( - private readonly engine: MarkdownEngine + private readonly tocProvider: MdTableOfContentsProvider, ) { } public async execute(args: OpenDocumentLinkArgs) { const fromResource = vscode.Uri.parse('').with(args.fromResource); const targetResource = reviveUri(args.parts).with({ fragment: args.fragment }); - return openDocumentLink(this.engine, targetResource, fromResource); + return openDocumentLink(this.tocProvider, targetResource, fromResource); } } diff --git a/extensions/markdown-language-features/src/commands/refreshPreview.ts b/extensions/markdown-language-features/src/commands/refreshPreview.ts index ec8888b5c59..c75d79347ac 100644 --- a/extensions/markdown-language-features/src/commands/refreshPreview.ts +++ b/extensions/markdown-language-features/src/commands/refreshPreview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownPreviewManager } from '../preview/previewManager'; export class RefreshPreviewCommand implements Command { @@ -12,7 +12,7 @@ export class RefreshPreviewCommand implements Command { public constructor( private readonly webviewManager: MarkdownPreviewManager, - private readonly engine: MarkdownEngine + private readonly engine: MarkdownItEngine ) { } public execute() { diff --git a/extensions/markdown-language-features/src/commands/reloadPlugins.ts b/extensions/markdown-language-features/src/commands/reloadPlugins.ts index ffa69b6977c..640ea9832b9 100644 --- a/extensions/markdown-language-features/src/commands/reloadPlugins.ts +++ b/extensions/markdown-language-features/src/commands/reloadPlugins.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownPreviewManager } from '../preview/previewManager'; export class ReloadPlugins implements Command { @@ -12,7 +12,7 @@ export class ReloadPlugins implements Command { public constructor( private readonly webviewManager: MarkdownPreviewManager, - private readonly engine: MarkdownEngine, + private readonly engine: MarkdownItEngine, ) { } public execute(): void { diff --git a/extensions/markdown-language-features/src/commands/renderDocument.ts b/extensions/markdown-language-features/src/commands/renderDocument.ts index 098f2da771a..f91adcbf5e5 100644 --- a/extensions/markdown-language-features/src/commands/renderDocument.ts +++ b/extensions/markdown-language-features/src/commands/renderDocument.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { SkinnyTextDocument } from '../workspaceContents'; export class RenderDocument implements Command { public readonly id = 'markdown.api.render'; public constructor( - private readonly engine: MarkdownEngine + private readonly engine: MarkdownItEngine ) { } public async execute(document: SkinnyTextDocument | string): Promise { diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index a97caf760a5..ef8ebcf7c35 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -20,7 +20,7 @@ import { registerRenameSupport } from './languageFeatures/rename'; import { registerSmartSelectSupport } from './languageFeatures/smartSelect'; import { registerWorkspaceSymbolSupport } from './languageFeatures/workspaceSymbolProvider'; import { Logger } from './logger'; -import { MarkdownEngine } from './markdownEngine'; +import { MarkdownItEngine, IMdParser, MdParsingProvider } from './markdownEngine'; import { getMarkdownExtensionContributions } from './markdownExtensions'; import { MarkdownContentProvider } from './preview/previewContentProvider'; import { MarkdownPreviewManager } from './preview/previewManager'; @@ -28,7 +28,7 @@ import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, Pr import { githubSlugifier } from './slugify'; import { MdTableOfContentsProvider } from './tableOfContents'; import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter'; -import { VsCodeMdWorkspaceContents } from './workspaceContents'; +import { MdWorkspaceContents, VsCodeMdWorkspaceContents } from './workspaceContents'; export function activate(context: vscode.ExtensionContext) { @@ -39,16 +39,21 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(contributions); const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState); - const engine = new MarkdownEngine(contributions, githubSlugifier); const logger = new Logger(); const commandManager = new CommandManager(); + const engine = new MarkdownItEngine(contributions, githubSlugifier); + const workspaceContents = new VsCodeMdWorkspaceContents(); + const parser = new MdParsingProvider(engine, workspaceContents); + const tocProvider = new MdTableOfContentsProvider(parser, workspaceContents); + context.subscriptions.push(workspaceContents, parser, tocProvider); + const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, contributions, logger); - const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, engine); + const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, tocProvider); context.subscriptions.push(previewManager); - context.subscriptions.push(registerMarkdownLanguageFeatures(commandManager, engine)); - context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine)); + context.subscriptions.push(registerMarkdownLanguageFeatures(parser, workspaceContents, commandManager, tocProvider)); + context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine, tocProvider)); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { logger.updateConfiguration(); @@ -57,37 +62,34 @@ export function activate(context: vscode.ExtensionContext) { } function registerMarkdownLanguageFeatures( + parser: IMdParser, + workspaceContents: MdWorkspaceContents, commandManager: CommandManager, - engine: MarkdownEngine + tocProvider: MdTableOfContentsProvider, ): vscode.Disposable { const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; - const workspaceContents = new VsCodeMdWorkspaceContents(); - - const linkProvider = new MdLinkProvider(engine, workspaceContents); - const tocProvider = new MdTableOfContentsProvider(engine, workspaceContents); - const referencesProvider = new MdReferencesProvider(engine, workspaceContents, tocProvider); + const linkProvider = new MdLinkProvider(parser, workspaceContents); + const referencesProvider = new MdReferencesProvider(parser, workspaceContents, tocProvider); const symbolProvider = new MdDocumentSymbolProvider(tocProvider); return vscode.Disposable.from( - workspaceContents, linkProvider, referencesProvider, - tocProvider, // Language features registerDefinitionSupport(selector, referencesProvider), - registerDiagnosticSupport(selector, engine, workspaceContents, linkProvider, commandManager, referencesProvider, tocProvider), + registerDiagnosticSupport(selector, workspaceContents, linkProvider, commandManager, referencesProvider, tocProvider), registerDocumentLinkSupport(selector, linkProvider), registerDocumentSymbolSupport(selector, tocProvider), registerDropIntoEditorSupport(selector), registerFindFileReferenceSupport(commandManager, referencesProvider), - registerFoldingSupport(selector, engine, tocProvider), + registerFoldingSupport(selector, parser, tocProvider), registerPasteSupport(selector), - registerPathCompletionSupport(selector, engine, linkProvider), + registerPathCompletionSupport(selector, parser, linkProvider), registerReferencesSupport(selector, referencesProvider), - registerRenameSupport(selector, workspaceContents, referencesProvider, engine.slugifier), - registerSmartSelectSupport(selector, engine, tocProvider), + registerRenameSupport(selector, workspaceContents, referencesProvider, parser.slugifier), + registerSmartSelectSupport(selector, parser, tocProvider), registerWorkspaceSymbolSupport(workspaceContents, symbolProvider), ); } @@ -97,7 +99,8 @@ function registerMarkdownCommands( previewManager: MarkdownPreviewManager, telemetryReporter: TelemetryReporter, cspArbiter: ContentSecurityPolicyArbiter, - engine: MarkdownEngine + engine: MarkdownItEngine, + tocProvider: MdTableOfContentsProvider, ): vscode.Disposable { const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager); @@ -108,7 +111,7 @@ function registerMarkdownCommands( commandManager.register(new commands.RefreshPreviewCommand(previewManager, engine)); commandManager.register(new commands.MoveCursorToPositionCommand()); commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager)); - commandManager.register(new commands.OpenDocumentLinkCommand(engine)); + commandManager.register(new commands.OpenDocumentLinkCommand(tocProvider)); commandManager.register(new commands.ToggleLockCommand(previewManager)); commandManager.register(new commands.RenderDocument(engine)); commandManager.register(new commands.ReloadPlugins(previewManager, engine)); diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index fe621104b4c..c57fa013b82 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -7,7 +7,6 @@ import * as picomatch from 'picomatch'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { CommandManager } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { MdTableOfContentsWatcher } from '../test/tableOfContentsWatcher'; import { Delayer } from '../util/async'; @@ -305,12 +304,12 @@ export class DiagnosticManager extends Disposable { public readonly ready: Promise; constructor( - engine: MarkdownEngine, private readonly workspaceContents: MdWorkspaceContents, private readonly computer: DiagnosticComputer, private readonly configuration: DiagnosticConfiguration, private readonly reporter: DiagnosticReporter, private readonly referencesProvider: MdReferencesProvider, + tocProvider: MdTableOfContentsProvider, delay = 300, ) { super(); @@ -346,7 +345,7 @@ export class DiagnosticManager extends Disposable { } })); - this.tableOfContentsWatcher = this._register(new MdTableOfContentsWatcher(engine, workspaceContents)); + this.tableOfContentsWatcher = this._register(new MdTableOfContentsWatcher(workspaceContents, tocProvider)); 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(); @@ -638,7 +637,6 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { export function registerDiagnosticSupport( selector: vscode.DocumentSelector, - engine: MarkdownEngine, workspaceContents: MdWorkspaceContents, linkProvider: MdLinkProvider, commandManager: CommandManager, @@ -647,12 +645,12 @@ export function registerDiagnosticSupport( ): vscode.Disposable { const configuration = new VSCodeDiagnosticConfiguration(); const manager = new DiagnosticManager( - engine, workspaceContents, new DiagnosticComputer(workspaceContents, linkProvider, tocProvider), configuration, new DiagnosticCollectionReporter(), - referenceProvider); + referenceProvider, + tocProvider); return vscode.Disposable.from( configuration, manager, diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts index 5d33055264e..7b4aacc1ec0 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import * as uri from 'vscode-uri'; import { OpenDocumentLinkCommand } from '../commands/openDocumentLink'; -import { MarkdownEngine } from '../markdownEngine'; +import { IMdParser } from '../markdownEngine'; import { coalesce } from '../util/arrays'; import { noopToken } from '../util/cancellation'; import { Disposable } from '../util/dispose'; @@ -233,8 +233,8 @@ const definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^> const inlineCodePattern = /(?:^|[^`])(`+)(?:.+?|.*?(?:(?:\r?\n).+?)*?)(?:\r?\n)?\1(?:$|[^`])/gm; class NoLinkRanges { - public static async compute(document: SkinnyTextDocument, engine: MarkdownEngine): Promise { - const tokens = await engine.parse(document); + public static async compute(tokenizer: IMdParser, document: SkinnyTextDocument): Promise { + const tokens = await tokenizer.tokenize(document); const multiline = tokens.filter(t => (t.type === 'code_block' || t.type === 'fence' || t.type === 'html_block') && !!t.map).map(t => t.map) as [number, number][]; const text = document.getText(); @@ -270,11 +270,11 @@ class NoLinkRanges { export class MdLinkComputer { constructor( - private readonly engine: MarkdownEngine + private readonly tokenizer: IMdParser, ) { } public async getAllLinks(document: SkinnyTextDocument, token: vscode.CancellationToken): Promise { - const noLinkRanges = await NoLinkRanges.compute(document, this.engine); + const noLinkRanges = await NoLinkRanges.compute(this.tokenizer, document); if (token.isCancellationRequested) { return []; } @@ -436,11 +436,11 @@ export class MdLinkProvider extends Disposable { private readonly linkComputer: MdLinkComputer; constructor( - engine: MarkdownEngine, + tokenizer: IMdParser, workspaceContents: MdWorkspaceContents, ) { super(); - this.linkComputer = new MdLinkComputer(engine); + this.linkComputer = new MdLinkComputer(tokenizer); this._linkCache = this._register(new MdDocumentInfoCache(workspaceContents, doc => this.linkComputer.getAllLinks(doc, noopToken))); } diff --git a/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts b/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts index a3b0fb44f1d..7306e048b9a 100644 --- a/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import Token = require('markdown-it/lib/token'); +import type Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; +import { IMdParser } from '../markdownEngine'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { SkinnyTextDocument } from '../workspaceContents'; @@ -18,7 +18,7 @@ interface MarkdownItTokenWithMap extends Token { export class MdFoldingProvider implements vscode.FoldingRangeProvider { constructor( - private readonly engine: MarkdownEngine, + private readonly parser: IMdParser, private readonly tocProvide: MdTableOfContentsProvider, ) { } @@ -36,7 +36,7 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider { } private async getRegions(document: SkinnyTextDocument): Promise { - const tokens = await this.engine.parse(document); + const tokens = await this.parser.tokenize(document); const regionMarkers = tokens.filter(isRegionMarker) .map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) })); @@ -67,7 +67,7 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider { } private async getBlockFoldingRanges(document: SkinnyTextDocument): Promise { - const tokens = await this.engine.parse(document); + const tokens = await this.parser.tokenize(document); const multiLineListItems = tokens.filter(isFoldableToken); return multiLineListItems.map(listItem => { const start = listItem.map[0]; @@ -115,8 +115,8 @@ const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => { export function registerFoldingSupport( selector: vscode.DocumentSelector, - engine: MarkdownEngine, + parser: IMdParser, tocProvider: MdTableOfContentsProvider, ): vscode.Disposable { - return vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine, tocProvider)); + return vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(parser, tocProvider)); } diff --git a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts index b9a59ca4cec..32e4932c338 100644 --- a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts +++ b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts @@ -5,7 +5,7 @@ import { dirname, resolve } from 'path'; import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; +import { IMdParser } from '../markdownEngine'; import { TableOfContents } from '../tableOfContents'; import { resolveUriToMarkdownFile } from '../util/openDocumentLink'; import { SkinnyTextDocument } from '../workspaceContents'; @@ -82,7 +82,7 @@ function tryDecodeUriComponent(str: string): string { export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProvider { constructor( - private readonly engine: MarkdownEngine, + private readonly parser: IMdParser, private readonly linkProvider: MdLinkProvider, ) { } @@ -249,7 +249,7 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv } private async *provideHeaderSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext, insertionRange: vscode.Range): AsyncIterable { - const toc = await TableOfContents.createForDocumentOrNotebook(this.engine, document); + const toc = await TableOfContents.createForDocumentOrNotebook(this.parser, document); for (const entry of toc.entries) { const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length })); yield { @@ -349,8 +349,8 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv export function registerPathCompletionSupport( selector: vscode.DocumentSelector, - engine: MarkdownEngine, + parser: IMdParser, linkProvider: MdLinkProvider, ): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider(selector, new MdVsCodePathCompletionProvider(engine, linkProvider), '.', '/', '#'); + return vscode.languages.registerCompletionItemProvider(selector, new MdVsCodePathCompletionProvider(parser, linkProvider), '.', '/', '#'); } diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts index f532a36bb2f..52d57b56c00 100644 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ b/extensions/markdown-language-features/src/languageFeatures/references.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import * as uri from 'vscode-uri'; -import { MarkdownEngine } from '../markdownEngine'; +import { IMdParser } from '../markdownEngine'; import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents'; import { noopToken } from '../util/cancellation'; import { Disposable } from '../util/dispose'; @@ -68,13 +68,13 @@ export class MdReferencesProvider extends Disposable { private readonly _linkComputer: MdLinkComputer; public constructor( - private readonly engine: MarkdownEngine, + private readonly parser: IMdParser, private readonly workspaceContents: MdWorkspaceContents, private readonly tocProvider: MdTableOfContentsProvider, ) { super(); - this._linkComputer = new MdLinkComputer(engine); + this._linkComputer = new MdLinkComputer(parser); this._linkCache = this._register(new MdWorkspaceInfoCache(workspaceContents, doc => this._linkComputer.getAllLinks(doc, noopToken))); } @@ -114,7 +114,7 @@ export class MdReferencesProvider extends Disposable { for (const link of links) { if (link.href.kind === 'internal' && this.looksLikeLinkToDoc(link.href, document.uri) - && this.engine.slugifier.fromHeading(link.href.fragment).value === header.slug.value + && this.parser.slugifier.fromHeading(link.href.fragment).value === header.slug.value ) { references.push({ kind: 'link', @@ -204,7 +204,7 @@ export class MdReferencesProvider extends Disposable { continue; } - if (this.engine.slugifier.fromHeading(link.href.fragment).equals(this.engine.slugifier.fromHeading(sourceLink.href.fragment))) { + if (this.parser.slugifier.fromHeading(link.href.fragment).equals(this.parser.slugifier.fromHeading(sourceLink.href.fragment))) { const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); references.push({ kind: 'link', diff --git a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts b/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts index fd494a0f8fe..915d60abf47 100644 --- a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts +++ b/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; +import { IMdParser } from '../markdownEngine'; import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents'; import { SkinnyTextDocument } from '../workspaceContents'; @@ -15,7 +15,7 @@ interface MarkdownItTokenWithMap extends Token { export class MdSmartSelect implements vscode.SelectionRangeProvider { constructor( - private readonly engine: MarkdownEngine, + private readonly parser: IMdParser, private readonly tocProvider: MdTableOfContentsProvider, ) { } @@ -37,9 +37,7 @@ export class MdSmartSelect implements vscode.SelectionRangeProvider { } private async getBlockSelectionRange(document: SkinnyTextDocument, position: vscode.Position, headerRange?: vscode.SelectionRange): Promise { - - const tokens = await this.engine.parse(document); - + const tokens = await this.parser.tokenize(document); const blockTokens = getBlockTokensForPosition(tokens, position, headerRange); if (blockTokens.length === 0) { @@ -253,8 +251,8 @@ function getFirstChildHeader(document: SkinnyTextDocument, header?: TocEntry, to export function registerSmartSelectSupport( selector: vscode.DocumentSelector, - engine: MarkdownEngine, + parser: IMdParser, tocProvider: MdTableOfContentsProvider, ): vscode.Disposable { - return vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine, tocProvider)); + return vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(parser, tocProvider)); } diff --git a/extensions/markdown-language-features/src/languageFeatures/workspaceCache.ts b/extensions/markdown-language-features/src/languageFeatures/workspaceCache.ts index 8d7e3c81fa2..fff941e29ea 100644 --- a/extensions/markdown-language-features/src/languageFeatures/workspaceCache.ts +++ b/extensions/markdown-language-features/src/languageFeatures/workspaceCache.ts @@ -65,6 +65,15 @@ export class MdDocumentInfoCache extends Disposable { return doc && this.onDidChangeDocument(doc, true)?.value; } + public async getForDocument(document: SkinnyTextDocument): Promise { + const existing = this._cache.get(document.uri); + if (existing) { + return existing; + } + + return this.onDidChangeDocument(document, true)!.value; + } + public async entries(): Promise> { return this._cache.entries(); } diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 400132d781f..a26a5d5cec5 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -3,15 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import MarkdownIt = require('markdown-it'); -import Token = require('markdown-it/lib/token'); +import type MarkdownIt = require('markdown-it'); +import type Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; +import { MdDocumentInfoCache } from './languageFeatures/workspaceCache'; import { MarkdownContributionProvider } from './markdownExtensions'; import { Slugifier } from './slugify'; +import { Disposable } from './util/dispose'; import { stringHash } from './util/hash'; import { WebviewResourceProvider } from './util/resources'; import { isOfScheme, Schemes } from './util/schemes'; -import { SkinnyTextDocument } from './workspaceContents'; +import { MdWorkspaceContents, SkinnyTextDocument } from './workspaceContents'; const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g; @@ -91,7 +93,12 @@ interface RenderEnv { resourceProvider: WebviewResourceProvider | undefined; } -export class MarkdownEngine { +export interface IMdParser { + readonly slugifier: Slugifier; + tokenize(document: SkinnyTextDocument): Promise; +} + +export class MarkdownItEngine implements IMdParser { private md?: Promise; @@ -213,7 +220,7 @@ export class MarkdownEngine { }; } - public async parse(document: SkinnyTextDocument): Promise { + public async tokenize(document: SkinnyTextDocument): Promise { const config = this.getConfig(document.uri); const engine = await this.getEngine(config); return this.tokenizeDocument(document, config, engine); @@ -427,3 +434,27 @@ function normalizeHighlightLang(lang: string | undefined) { return lang; } } + +export class MdParsingProvider extends Disposable implements IMdParser { + + private readonly _cache: MdDocumentInfoCache; + + public readonly slugifier: Slugifier; + + constructor( + engine: MarkdownItEngine, + workspaceContents: MdWorkspaceContents, + ) { + super(); + + this.slugifier = engine.slugifier; + + this._cache = this._register(new MdDocumentInfoCache(workspaceContents, doc => { + return engine.tokenize(doc); + })); + } + + public tokenize(document: SkinnyTextDocument): Promise { + return this._cache.getForDocument(document); + } +} diff --git a/extensions/markdown-language-features/src/preview/preview.ts b/extensions/markdown-language-features/src/preview/preview.ts index dd3c310a194..1b4a16539ea 100644 --- a/extensions/markdown-language-features/src/preview/preview.ts +++ b/extensions/markdown-language-features/src/preview/preview.ts @@ -7,8 +7,8 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import * as uri from 'vscode-uri'; import { Logger } from '../logger'; -import { MarkdownEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; +import { MdTableOfContentsProvider } from '../tableOfContents'; import { Disposable } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; import { openDocumentLink, resolveDocumentLink, resolveUriToMarkdownFile } from '../util/openDocumentLink'; @@ -116,11 +116,11 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { resource: vscode.Uri, startingScroll: StartingScrollLocation | undefined, private readonly delegate: MarkdownPreviewDelegate, - private readonly engine: MarkdownEngine, private readonly _contentProvider: MarkdownContentProvider, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, private readonly _logger: Logger, private readonly _contributionProvider: MarkdownContributionProvider, + private readonly _tocProvider: MdTableOfContentsProvider, ) { super(); @@ -456,7 +456,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } } - return openDocumentLink(this.engine, targetResource, this.resource); + return openDocumentLink(this._tocProvider, targetResource, this.resource); } //#region WebviewResourceProvider @@ -504,10 +504,10 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown topmostLineMonitor: TopmostLineMonitor, logger: Logger, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, scrollLine?: number, ): StaticMarkdownPreview { - return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, engine, scrollLine); + return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, tocProvider, scrollLine); } private readonly preview: MarkdownPreview; @@ -520,7 +520,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown topmostLineMonitor: TopmostLineMonitor, logger: Logger, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, scrollLine?: number, ) { super(); @@ -532,7 +532,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown fragment }), StaticMarkdownPreview.customEditorViewType, this._webviewPanel.viewColumn); } - }, engine, contentProvider, _previewConfigurations, logger, contributionProvider)); + }, contentProvider, _previewConfigurations, logger, contributionProvider, tocProvider)); this._register(this._webviewPanel.onDidDispose(() => { this.dispose(); @@ -616,12 +616,12 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow logger: Logger, topmostLineMonitor: TopmostLineMonitor, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, ): DynamicMarkdownPreview { webview.iconPath = contentProvider.iconPath; return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine); + contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, tocProvider); } public static create( @@ -632,7 +632,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow logger: Logger, topmostLineMonitor: TopmostLineMonitor, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, ): DynamicMarkdownPreview { const webview = vscode.window.createWebviewPanel( DynamicMarkdownPreview.viewType, @@ -642,7 +642,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow webview.iconPath = contentProvider.iconPath; return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine); + contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, tocProvider); } private constructor( @@ -653,7 +653,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow private readonly _logger: Logger, private readonly _topmostLineMonitor: TopmostLineMonitor, private readonly _contributionProvider: MarkdownContributionProvider, - private readonly _engine: MarkdownEngine, + private readonly _tocProvider: MdTableOfContentsProvider, ) { super(); @@ -805,10 +805,10 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined); } }, - this._engine, this._contentProvider, this._previewConfigurations, this._logger, - this._contributionProvider); + this._contributionProvider, + this._tocProvider); } } diff --git a/extensions/markdown-language-features/src/preview/previewContentProvider.ts b/extensions/markdown-language-features/src/preview/previewContentProvider.ts index 8437017dc4f..30964bd0610 100644 --- a/extensions/markdown-language-features/src/preview/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/preview/previewContentProvider.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import * as uri from 'vscode-uri'; import { Logger } from '../logger'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; import { WebviewResourceProvider } from '../util/resources'; import { MarkdownPreviewConfiguration, MarkdownPreviewConfigurationManager } from './previewConfig'; @@ -47,7 +47,7 @@ export interface MarkdownContentProviderOutput { export class MarkdownContentProvider { constructor( - private readonly engine: MarkdownEngine, + private readonly engine: MarkdownItEngine, private readonly context: vscode.ExtensionContext, private readonly cspArbiter: ContentSecurityPolicyArbiter, private readonly contributionProvider: MarkdownContributionProvider, diff --git a/extensions/markdown-language-features/src/preview/previewManager.ts b/extensions/markdown-language-features/src/preview/previewManager.ts index a0f837c2425..d5d33b73f87 100644 --- a/extensions/markdown-language-features/src/preview/previewManager.ts +++ b/extensions/markdown-language-features/src/preview/previewManager.ts @@ -5,8 +5,8 @@ import * as vscode from 'vscode'; import { Logger } from '../logger'; -import { MarkdownEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; +import { MdTableOfContentsProvider } from '../tableOfContents'; import { Disposable, disposeAll } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; import { DynamicMarkdownPreview, ManagedMarkdownPreview, StaticMarkdownPreview } from './preview'; @@ -71,7 +71,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview private readonly _contentProvider: MarkdownContentProvider, private readonly _logger: Logger, private readonly _contributions: MarkdownContributionProvider, - private readonly _engine: MarkdownEngine, + private readonly _tocProvider: MdTableOfContentsProvider, ) { super(); @@ -166,7 +166,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._logger, this._topmostLineMonitor, this._contributions, - this._engine); + this._tocProvider); this.registerDynamicPreview(preview); } @@ -184,7 +184,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._topmostLineMonitor, this._logger, this._contributions, - this._engine, + this._tocProvider, lineNumber ); this.registerStaticPreview(preview); @@ -209,7 +209,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._logger, this._topmostLineMonitor, this._contributions, - this._engine); + this._tocProvider); this.setPreviewActiveContext(true); this._activePreview = preview; diff --git a/extensions/markdown-language-features/src/tableOfContents.ts b/extensions/markdown-language-features/src/tableOfContents.ts index 0f28f9fa635..c261de9ea53 100644 --- a/extensions/markdown-language-features/src/tableOfContents.ts +++ b/extensions/markdown-language-features/src/tableOfContents.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { MdDocumentInfoCache } from './languageFeatures/workspaceCache'; -import { MarkdownEngine } from './markdownEngine'; +import { IMdParser } from './markdownEngine'; import { githubSlugifier, Slug, Slugifier } from './slugify'; import { Disposable } from './util/dispose'; import { isMarkdownFile } from './util/file'; @@ -63,12 +63,12 @@ export interface TocEntry { export class TableOfContents { - public static async create(engine: MarkdownEngine, document: SkinnyTextDocument,): Promise { - const entries = await this.buildToc(engine, document); - return new TableOfContents(entries, engine.slugifier); + public static async create(parser: IMdParser, document: SkinnyTextDocument,): Promise { + const entries = await this.buildToc(parser, document); + return new TableOfContents(entries, parser.slugifier); } - public static async createForDocumentOrNotebook(engine: MarkdownEngine, document: SkinnyTextDocument): Promise { + public static async createForDocumentOrNotebook(parser: IMdParser, document: SkinnyTextDocument): Promise { if (document.uri.scheme === 'vscode-notebook-cell') { const notebook = vscode.workspace.notebookDocuments .find(notebook => notebook.getCells().some(cell => cell.document === document)); @@ -78,20 +78,20 @@ export class TableOfContents { for (const cell of notebook.getCells()) { if (cell.kind === vscode.NotebookCellKind.Markup && isMarkdownFile(cell.document)) { - entries.push(...(await this.buildToc(engine, cell.document))); + entries.push(...(await this.buildToc(parser, cell.document))); } } - return new TableOfContents(entries, engine.slugifier); + return new TableOfContents(entries, parser.slugifier); } } - return this.create(engine, document); + return this.create(parser, document); } - private static async buildToc(engine: MarkdownEngine, document: SkinnyTextDocument): Promise { + private static async buildToc(parser: IMdParser, document: SkinnyTextDocument): Promise { const toc: TocEntry[] = []; - const tokens = await engine.parse(document); + const tokens = await parser.tokenize(document); const existingSlugEntries = new Map(); @@ -103,11 +103,11 @@ export class TableOfContents { const lineNumber = heading.map[0]; const line = document.lineAt(lineNumber); - let slug = engine.slugifier.fromHeading(line.text); + let slug = parser.slugifier.fromHeading(line.text); const existingSlugEntry = existingSlugEntries.get(slug.value); if (existingSlugEntry) { ++existingSlugEntry.count; - slug = engine.slugifier.fromHeading(slug.value + '-' + existingSlugEntry.count); + slug = parser.slugifier.fromHeading(slug.value + '-' + existingSlugEntry.count); } else { existingSlugEntries.set(slug.value, { count: 0 }); } @@ -181,16 +181,20 @@ export class MdTableOfContentsProvider extends Disposable { private readonly _cache: MdDocumentInfoCache; constructor( - engine: MarkdownEngine, + parser: IMdParser, workspaceContents: MdWorkspaceContents, ) { super(); this._cache = this._register(new MdDocumentInfoCache(workspaceContents, doc => { - return TableOfContents.create(engine, doc); + return TableOfContents.create(parser, doc); })); } public async get(resource: vscode.Uri): Promise { - return (await this._cache.get(resource)) ?? TableOfContents.empty; + return await this._cache.get(resource) ?? TableOfContents.empty; + } + + public getForDocument(doc: SkinnyTextDocument): Promise { + return this._cache.getForDocument(doc); } } diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index 01b7fa30b6e..b8b4f0acdcf 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -440,12 +440,12 @@ suite('Markdown: Diagnostics manager', () => { const tocProvider = new MdTableOfContentsProvider(engine, workspace); const referencesProvider = new MdReferencesProvider(engine, workspace, tocProvider); const manager = new DiagnosticManager( - engine, workspace, new DiagnosticComputer(workspace, linkProvider, tocProvider), configuration, reporter, referencesProvider, + tocProvider, 0); _disposables.push(manager, referencesProvider); return manager; diff --git a/extensions/markdown-language-features/src/test/engine.ts b/extensions/markdown-language-features/src/test/engine.ts index e75be4abac5..2ad00fae957 100644 --- a/extensions/markdown-language-features/src/test/engine.ts +++ b/extensions/markdown-language-features/src/test/engine.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions'; import { githubSlugifier } from '../slugify'; import { Disposable } from '../util/dispose'; @@ -15,6 +15,6 @@ const emptyContributions = new class extends Disposable implements MarkdownContr readonly onContributionsChanged = this._register(new vscode.EventEmitter()).event; }; -export function createNewMarkdownEngine(): MarkdownEngine { - return new MarkdownEngine(emptyContributions, githubSlugifier); +export function createNewMarkdownEngine(): MarkdownItEngine { + return new MarkdownItEngine(emptyContributions, githubSlugifier); } diff --git a/extensions/markdown-language-features/src/test/smartSelect.test.ts b/extensions/markdown-language-features/src/test/smartSelect.test.ts index 9298d5972e4..12c50abdc72 100644 --- a/extensions/markdown-language-features/src/test/smartSelect.test.ts +++ b/extensions/markdown-language-features/src/test/smartSelect.test.ts @@ -6,11 +6,11 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { MdSmartSelect } from '../languageFeatures/smartSelect'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { CURSOR, getCursorPositions, joinLines } from './util'; import { MdTableOfContentsProvider } from '../tableOfContents'; +import { InMemoryDocument } from '../util/inMemoryDocument'; +import { createNewMarkdownEngine } from './engine'; import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; +import { CURSOR, getCursorPositions, joinLines } from './util'; const testFileName = vscode.Uri.file('test.md'); diff --git a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts index 6efb482ad8f..93990128fb0 100644 --- a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts +++ b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts @@ -7,16 +7,22 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; import { TableOfContents } from '../tableOfContents'; -import { createNewMarkdownEngine } from './engine'; import { InMemoryDocument } from '../util/inMemoryDocument'; +import { SkinnyTextDocument } from '../workspaceContents'; +import { createNewMarkdownEngine } from './engine'; const testFileName = vscode.Uri.file('test.md'); +function createToc(doc: SkinnyTextDocument): Promise { + const engine = createNewMarkdownEngine(); + return TableOfContents.create(engine, doc); +} + suite('markdown.TableOfContentsProvider', () => { test('Lookup should not return anything for empty document', async () => { const doc = new InMemoryDocument(testFileName, ''); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); assert.strictEqual(provider.lookup(''), undefined); assert.strictEqual(provider.lookup('foo'), undefined); @@ -24,7 +30,7 @@ suite('markdown.TableOfContentsProvider', () => { test('Lookup should not return anything for document with no headers', async () => { const doc = new InMemoryDocument(testFileName, 'a *b*\nc'); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); assert.strictEqual(provider.lookup(''), undefined); assert.strictEqual(provider.lookup('foo'), undefined); @@ -34,7 +40,7 @@ suite('markdown.TableOfContentsProvider', () => { test('Lookup should return basic #header', async () => { const doc = new InMemoryDocument(testFileName, `# a\nx\n# c`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); { const entry = provider.lookup('a'); @@ -53,7 +59,7 @@ suite('markdown.TableOfContentsProvider', () => { test('Lookups should be case in-sensitive', async () => { const doc = new InMemoryDocument(testFileName, `# fOo\n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); assert.strictEqual((provider.lookup('fOo'))!.line, 0); assert.strictEqual((provider.lookup('foo'))!.line, 0); @@ -62,7 +68,7 @@ suite('markdown.TableOfContentsProvider', () => { test('Lookups should ignore leading and trailing white-space, and collapse internal whitespace', async () => { const doc = new InMemoryDocument(testFileName, `# f o o \n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); assert.strictEqual((provider.lookup('f o o'))!.line, 0); assert.strictEqual((provider.lookup(' f o o'))!.line, 0); @@ -77,14 +83,14 @@ suite('markdown.TableOfContentsProvider', () => { test('should handle special characters #44779', async () => { const doc = new InMemoryDocument(testFileName, `# Indentação\n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); assert.strictEqual((provider.lookup('indentação'))!.line, 0); }); test('should handle special characters 2, #48482', async () => { const doc = new InMemoryDocument(testFileName, `# Инструкция - Делай Раз, Делай Два\n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); assert.strictEqual((provider.lookup('инструкция---делай-раз-делай-два'))!.line, 0); }); @@ -97,7 +103,7 @@ suite('markdown.TableOfContentsProvider', () => { ### Заголовок Header 3 ## Заголовок`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); assert.strictEqual((provider.lookup('header-2'))!.line, 0); assert.strictEqual((provider.lookup('header-3'))!.line, 1); @@ -109,7 +115,7 @@ suite('markdown.TableOfContentsProvider', () => { test('Lookup should support suffixes for repeated headers', async () => { const doc = new InMemoryDocument(testFileName, `# a\n# a\n## a`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); + const provider = await createToc(doc); { const entry = provider.lookup('a'); diff --git a/extensions/markdown-language-features/src/test/tableOfContentsWatcher.ts b/extensions/markdown-language-features/src/test/tableOfContentsWatcher.ts index cef3c33816f..f5bc4be866f 100644 --- a/extensions/markdown-language-features/src/test/tableOfContentsWatcher.ts +++ b/extensions/markdown-language-features/src/test/tableOfContentsWatcher.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents } from '../tableOfContents'; -import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; +import { MdTableOfContentsProvider, TableOfContents } from '../tableOfContents'; import { equals } from '../util/arrays'; import { Disposable } from '../util/dispose'; import { ResourceMap } from '../util/resourceMap'; +import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; /** * Check if the items in a table of contents have changed. @@ -32,8 +31,8 @@ export class MdTableOfContentsWatcher extends Disposable { public readonly onTocChanged = this._onTocChanged.event; public constructor( - private readonly engine: MarkdownEngine, private readonly workspaceContents: MdWorkspaceContents, + private readonly tocProvider: MdTableOfContentsProvider, ) { super(); @@ -43,13 +42,13 @@ export class MdTableOfContentsWatcher extends Disposable { } private async onDidCreateDocument(document: SkinnyTextDocument) { - const toc = await TableOfContents.create(this.engine, document); + const toc = await this.tocProvider.getForDocument(document); this._files.set(document.uri, { toc }); } private async onDidChangeDocument(document: SkinnyTextDocument) { const existing = this._files.get(document.uri); - const newToc = await TableOfContents.create(this.engine, document); + const newToc = await this.tocProvider.getForDocument(document); if (!existing || hasTableOfContentsChanged(existing.toc, newToc)) { this._onTocChanged.fire({ uri: document.uri }); diff --git a/extensions/markdown-language-features/src/util/openDocumentLink.ts b/extensions/markdown-language-features/src/util/openDocumentLink.ts index 376fe548ec4..4f733778026 100644 --- a/extensions/markdown-language-features/src/util/openDocumentLink.ts +++ b/extensions/markdown-language-features/src/util/openDocumentLink.ts @@ -6,8 +6,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as uri from 'vscode-uri'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents } from '../tableOfContents'; +import { MdTableOfContentsProvider } from '../tableOfContents'; import { isMarkdownFile } from './file'; export interface OpenDocumentLinkArgs { @@ -37,10 +36,10 @@ export function resolveDocumentLink(href: string, markdownFile: vscode.Uri): vsc return vscode.Uri.joinPath(dirnameUri, hrefPath).with({ fragment }); } -export async function openDocumentLink(engine: MarkdownEngine, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise { +export async function openDocumentLink(tocProvider: MdTableOfContentsProvider, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise { const column = getViewColumn(fromResource); - if (await tryNavigateToFragmentInActiveEditor(engine, targetResource)) { + if (await tryNavigateToFragmentInActiveEditor(tocProvider, targetResource)) { return; } @@ -58,7 +57,7 @@ export async function openDocumentLink(engine: MarkdownEngine, targetResource: v try { const stat = await vscode.workspace.fs.stat(dotMdResource); if (stat.type === vscode.FileType.File) { - await tryOpenMdFile(engine, dotMdResource, column); + await tryOpenMdFile(tocProvider, dotMdResource, column); return; } } catch { @@ -69,19 +68,19 @@ export async function openDocumentLink(engine: MarkdownEngine, targetResource: v return vscode.commands.executeCommand('revealInExplorer', targetResource); } - await tryOpenMdFile(engine, targetResource, column); + await tryOpenMdFile(tocProvider, targetResource, column); } -async function tryOpenMdFile(engine: MarkdownEngine, resource: vscode.Uri, column: vscode.ViewColumn): Promise { +async function tryOpenMdFile(tocProvider: MdTableOfContentsProvider, resource: vscode.Uri, column: vscode.ViewColumn): Promise { await vscode.commands.executeCommand('vscode.open', resource.with({ fragment: '' }), column); - return tryNavigateToFragmentInActiveEditor(engine, resource); + return tryNavigateToFragmentInActiveEditor(tocProvider, resource); } -async function tryNavigateToFragmentInActiveEditor(engine: MarkdownEngine, resource: vscode.Uri): Promise { +async function tryNavigateToFragmentInActiveEditor(tocProvider: MdTableOfContentsProvider, resource: vscode.Uri): Promise { const activeEditor = vscode.window.activeTextEditor; if (activeEditor?.document.uri.fsPath === resource.fsPath) { if (isMarkdownFile(activeEditor.document)) { - if (await tryRevealLineUsingTocFragment(engine, activeEditor, resource.fragment)) { + if (await tryRevealLineUsingTocFragment(tocProvider, activeEditor, resource.fragment)) { return true; } } @@ -103,8 +102,8 @@ function getViewColumn(resource: vscode.Uri): vscode.ViewColumn { } } -async function tryRevealLineUsingTocFragment(engine: MarkdownEngine, editor: vscode.TextEditor, fragment: string): Promise { - const toc = await TableOfContents.create(engine, editor.document); +async function tryRevealLineUsingTocFragment(tocProvider: MdTableOfContentsProvider, editor: vscode.TextEditor, fragment: string): Promise { + const toc = await tocProvider.getForDocument(editor.document); const entry = toc.lookup(fragment); if (entry) { const lineStart = new vscode.Range(entry.line, 0, entry.line, 0);