Refactor markdown language features (#152402)

(sorry for the size of this PR)

This change cleans up the markdown language features by making the following changes:

- Use `registerXSupport` public functions to register these
- Expose the slugifier the `MarkdownEngine` uses. You never want to use a different one if you already have a markdown engine
- Sort of clean up names. I'd introduced a bunch of confusing names while iterating in this space. What I'm working towards:

    - `Computer` — Stateless thing that computer data
    - `Provider` — Potentially stateful thing that provides data (which may be cached)
    - `VsCodeProvider` — The actual implementation of the various vscode language features (which should only be used by VS Code and in tests, not shared with other features)
- Introduce `MdLinkProvider` to avoid recomputing links for a given document. Also use this to hide more internals of link computation
This commit is contained in:
Matt Bierner
2022-06-17 01:25:52 -07:00
committed by GitHub
parent 54f5758f81
commit 623f55f437
24 changed files with 333 additions and 181 deletions

View File

@@ -9,8 +9,11 @@ import * as uri from 'vscode-uri';
import { OpenDocumentLinkCommand } from '../commands/openDocumentLink';
import { MarkdownEngine } from '../markdownEngine';
import { coalesce } from '../util/arrays';
import { noopToken } from '../util/cancellation';
import { Disposable } from '../util/dispose';
import { getUriForLinkWithKnownExternalScheme, isOfScheme, Schemes } from '../util/schemes';
import { SkinnyTextDocument } from '../workspaceContents';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { MdDocumentInfoCache } from './workspaceCache';
const localize = nls.loadMessageBundle();
@@ -242,6 +245,9 @@ class NoLinkRanges {
}
}
/**
* Stateless object that extracts link information from markdown files.
*/
export class MdLinkComputer {
constructor(
@@ -257,7 +263,7 @@ export class MdLinkComputer {
return Array.from([
...this.getInlineLinks(document, noLinkRanges),
...this.getReferenceLinks(document, noLinkRanges),
...this.getLinkDefinitions2(document, noLinkRanges),
...this.getLinkDefinitions(document, noLinkRanges),
...this.getAutoLinks(document, noLinkRanges),
]);
}
@@ -369,12 +375,7 @@ export class MdLinkComputer {
}
}
public async getLinkDefinitions(document: SkinnyTextDocument): Promise<Iterable<MdLinkDefinition>> {
const noLinkRanges = await NoLinkRanges.compute(document, this.engine);
return this.getLinkDefinitions2(document, noLinkRanges);
}
private *getLinkDefinitions2(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable<MdLinkDefinition> {
private *getLinkDefinitions(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable<MdLinkDefinition> {
const text = document.getText();
for (const match of text.matchAll(definitionPattern)) {
const pre = match[1];
@@ -419,7 +420,37 @@ export class MdLinkComputer {
}
}
export class LinkDefinitionSet {
/**
* Stateful object which provides links for markdown files the workspace.
*/
export class MdLinkProvider extends Disposable {
private readonly _linkCache: MdDocumentInfoCache<readonly MdLink[]>;
private readonly linkComputer: MdLinkComputer;
constructor(
engine: MarkdownEngine,
workspaceContents: MdWorkspaceContents,
) {
super();
this.linkComputer = new MdLinkComputer(engine);
this._linkCache = this._register(new MdDocumentInfoCache(workspaceContents, doc => this.linkComputer.getAllLinks(doc, noopToken)));
}
public async getLinks(document: SkinnyTextDocument): Promise<{
readonly links: readonly MdLink[];
readonly definitions: LinkDefinitionSet;
}> {
const links = (await this._linkCache.get(document.uri)) ?? [];
return {
links,
definitions: new LinkDefinitionSet(links),
};
}
}
export class LinkDefinitionSet implements Iterable<[string, MdLinkDefinition]> {
private readonly _map = new Map<string, MdLinkDefinition>();
constructor(links: Iterable<MdLink>) {
@@ -430,29 +461,31 @@ export class LinkDefinitionSet {
}
}
public [Symbol.iterator](): Iterator<[string, MdLinkDefinition]> {
return this._map.entries();
}
public lookup(ref: string): MdLinkDefinition | undefined {
return this._map.get(ref);
}
}
export class MdLinkProvider implements vscode.DocumentLinkProvider {
export class MdVsCodeLinkProvider implements vscode.DocumentLinkProvider {
constructor(
private readonly _linkComputer: MdLinkComputer,
private readonly _linkProvider: MdLinkProvider,
) { }
public async provideDocumentLinks(
document: SkinnyTextDocument,
token: vscode.CancellationToken
): Promise<vscode.DocumentLink[]> {
const allLinks = (await this._linkComputer.getAllLinks(document, token)) ?? [];
const { links, definitions } = await this._linkProvider.getLinks(document);
if (token.isCancellationRequested) {
return [];
}
const definitionSet = new LinkDefinitionSet(allLinks);
return coalesce(allLinks
.map(data => this.toValidDocumentLink(data, definitionSet)));
return coalesce(links.map(data => this.toValidDocumentLink(data, definitions)));
}
private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined {
@@ -482,9 +515,9 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
}
export function registerDocumentLinkProvider(
export function registerDocumentLinkSupport(
selector: vscode.DocumentSelector,
linkComputer: MdLinkComputer,
linkProvider: MdLinkProvider,
): vscode.Disposable {
return vscode.languages.registerDocumentLinkProvider(selector, new MdLinkProvider(linkComputer));
return vscode.languages.registerDocumentLinkProvider(selector, new MdVsCodeLinkProvider(linkProvider));
}