mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-19 16:18:58 +01:00
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:
@@ -6,7 +6,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { tryGetUriListSnippet } from './dropIntoEditor';
|
||||
|
||||
export function registerPasteProvider(selector: vscode.DocumentSelector) {
|
||||
export function registerPasteSupport(selector: vscode.DocumentSelector) {
|
||||
return vscode.languages.registerDocumentPasteEditProvider(selector, new class implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
|
||||
@@ -3,21 +3,25 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { SkinnyTextDocument } from '../workspaceContents';
|
||||
import { MdReferencesComputer } from './references';
|
||||
import { MdReferencesProvider } from './references';
|
||||
|
||||
export class MdDefinitionProvider extends Disposable implements vscode.DefinitionProvider {
|
||||
export class MdDefinitionProvider implements vscode.DefinitionProvider {
|
||||
|
||||
constructor(
|
||||
private readonly referencesComputer: MdReferencesComputer
|
||||
) {
|
||||
super();
|
||||
}
|
||||
private readonly referencesProvider: MdReferencesProvider
|
||||
) { }
|
||||
|
||||
async provideDefinition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Definition | undefined> {
|
||||
const allRefs = await this.referencesComputer.getReferencesAtPosition(document, position, token);
|
||||
const allRefs = await this.referencesProvider.getReferencesAtPosition(document, position, token);
|
||||
|
||||
return allRefs.find(ref => ref.kind === 'link' && ref.isDefinition)?.location;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerDefinitionSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
referencesProvider: MdReferencesProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerDefinitionProvider(selector, new MdDefinitionProvider(referencesProvider));
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import { Limiter } from '../util/limiter';
|
||||
import { ResourceMap } from '../util/resourceMap';
|
||||
import { MdTableOfContentsWatcher } from '../test/tableOfContentsWatcher';
|
||||
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
|
||||
import { InternalHref, LinkDefinitionSet, MdLink, MdLinkComputer, MdLinkSource } from './documentLinkProvider';
|
||||
import { MdReferencesComputer, tryFindMdDocumentForLink } from './references';
|
||||
import { InternalHref, MdLink, MdLinkSource, MdLinkProvider, LinkDefinitionSet } from './documentLinkProvider';
|
||||
import { MdReferencesProvider, tryFindMdDocumentForLink } from './references';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -305,7 +305,7 @@ export class DiagnosticManager extends Disposable {
|
||||
private readonly computer: DiagnosticComputer,
|
||||
private readonly configuration: DiagnosticConfiguration,
|
||||
private readonly reporter: DiagnosticReporter,
|
||||
private readonly referencesComputer: MdReferencesComputer,
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
delay = 300,
|
||||
) {
|
||||
super();
|
||||
@@ -344,7 +344,7 @@ export class DiagnosticManager extends Disposable {
|
||||
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<void>();
|
||||
for (const ref of await this.referencesComputer.getAllReferencesToFile(e.uri, noopToken)) {
|
||||
for (const ref of await this.referencesProvider.getAllReferencesToFile(e.uri, noopToken)) {
|
||||
const file = ref.location.uri;
|
||||
if (!triggered.has(file)) {
|
||||
this.triggerDiagnostics(file);
|
||||
@@ -450,11 +450,11 @@ export class DiagnosticComputer {
|
||||
constructor(
|
||||
private readonly engine: MarkdownEngine,
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
private readonly linkComputer: MdLinkComputer,
|
||||
private readonly linkProvider: MdLinkProvider,
|
||||
) { }
|
||||
|
||||
public async getDiagnostics(doc: SkinnyTextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: MdLink[] }> {
|
||||
const links = await this.linkComputer.getAllLinks(doc, token);
|
||||
public async getDiagnostics(doc: SkinnyTextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: readonly MdLink[] }> {
|
||||
const { links, definitions } = await this.linkProvider.getLinks(doc);
|
||||
if (token.isCancellationRequested || !options.enabled) {
|
||||
return { links, diagnostics: [] };
|
||||
}
|
||||
@@ -463,7 +463,7 @@ export class DiagnosticComputer {
|
||||
links,
|
||||
diagnostics: (await Promise.all([
|
||||
this.validateFileLinks(options, links, token),
|
||||
Array.from(this.validateReferenceLinks(options, links)),
|
||||
Array.from(this.validateReferenceLinks(options, links, definitions)),
|
||||
this.validateFragmentLinks(doc, options, links, token),
|
||||
])).flat()
|
||||
};
|
||||
@@ -501,15 +501,14 @@ export class DiagnosticComputer {
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
private *validateReferenceLinks(options: DiagnosticOptions, links: readonly MdLink[]): Iterable<vscode.Diagnostic> {
|
||||
private *validateReferenceLinks(options: DiagnosticOptions, links: readonly MdLink[], definitions: LinkDefinitionSet): Iterable<vscode.Diagnostic> {
|
||||
const severity = toSeverity(options.validateReferences);
|
||||
if (typeof severity === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const definitionSet = new LinkDefinitionSet(links);
|
||||
for (const link of links) {
|
||||
if (link.href.kind === 'reference' && !definitionSet.lookup(link.href.ref)) {
|
||||
if (link.href.kind === 'reference' && !definitions.lookup(link.href.ref)) {
|
||||
yield new vscode.Diagnostic(
|
||||
link.source.hrefRange,
|
||||
localize('invalidReferenceLink', 'No link definition found: \'{0}\'', link.href.ref),
|
||||
@@ -620,19 +619,19 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
export function register(
|
||||
export function registerDiagnosticSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
engine: MarkdownEngine,
|
||||
workspaceContents: MdWorkspaceContents,
|
||||
linkComputer: MdLinkComputer,
|
||||
linkProvider: MdLinkProvider,
|
||||
commandManager: CommandManager,
|
||||
referenceComputer: MdReferencesComputer,
|
||||
referenceComputer: MdReferencesProvider,
|
||||
): vscode.Disposable {
|
||||
const configuration = new VSCodeDiagnosticConfiguration();
|
||||
const manager = new DiagnosticManager(
|
||||
engine,
|
||||
workspaceContents,
|
||||
new DiagnosticComputer(engine, workspaceContents, linkComputer),
|
||||
new DiagnosticComputer(engine, workspaceContents, linkProvider),
|
||||
configuration,
|
||||
new DiagnosticCollectionReporter(),
|
||||
referenceComputer);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -74,3 +74,10 @@ export class MdDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
|
||||
return '#'.repeat(entry.level) + ' ' + entry.text;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerDocumentSymbolSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
engine: MarkdownEngine,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerDocumentSymbolProvider(selector, new MdDocumentSymbolProvider(engine));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const imageFileExtensions = new Set<string>([
|
||||
'.webp',
|
||||
]);
|
||||
|
||||
export function registerDropIntoEditor(selector: vscode.DocumentSelector) {
|
||||
export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) {
|
||||
return vscode.languages.registerDocumentOnDropEditProvider(selector, new class implements vscode.DocumentOnDropEditProvider {
|
||||
async provideDocumentOnDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, CommandManager } from '../commandManager';
|
||||
import { MdReferencesComputer } from './references';
|
||||
import { MdReferencesProvider } from './references';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -16,7 +16,7 @@ export class FindFileReferencesCommand implements Command {
|
||||
public readonly id = 'markdown.findAllFileReferences';
|
||||
|
||||
constructor(
|
||||
private readonly referencesComputer: MdReferencesComputer,
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
) { }
|
||||
|
||||
public async execute(resource?: vscode.Uri) {
|
||||
@@ -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.referencesComputer.getAllReferencesToFile(resource!, token);
|
||||
const references = await this.referencesProvider.getAllReferencesToFile(resource!, token);
|
||||
const locations = references.map(ref => ref.location);
|
||||
|
||||
const config = vscode.workspace.getConfiguration('references');
|
||||
@@ -49,6 +49,9 @@ export class FindFileReferencesCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
export function registerFindFileReferences(commandManager: CommandManager, referencesProvider: MdReferencesComputer): vscode.Disposable {
|
||||
export function registerFindFileReferenceSupport(
|
||||
commandManager: CommandManager,
|
||||
referencesProvider: MdReferencesProvider
|
||||
): vscode.Disposable {
|
||||
return commandManager.register(new FindFileReferencesCommand(referencesProvider));
|
||||
}
|
||||
|
||||
@@ -111,3 +111,10 @@ const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export function registerFoldingSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
engine: MarkdownEngine,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MarkdownEngine } from '../markdownEngine';
|
||||
import { TableOfContents } from '../tableOfContents';
|
||||
import { resolveUriToMarkdownFile } from '../util/openDocumentLink';
|
||||
import { SkinnyTextDocument } from '../workspaceContents';
|
||||
import { MdLinkComputer } from './documentLinkProvider';
|
||||
import { MdLinkProvider } from './documentLinkProvider';
|
||||
|
||||
enum CompletionContextKind {
|
||||
/** `[...](|)` */
|
||||
@@ -76,19 +76,14 @@ function tryDecodeUriComponent(str: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
|
||||
public static register(
|
||||
selector: vscode.DocumentSelector,
|
||||
engine: MarkdownEngine,
|
||||
linkComputer: MdLinkComputer,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerCompletionItemProvider(selector, new MdPathCompletionProvider(engine, linkComputer), '.', '/', '#');
|
||||
}
|
||||
/**
|
||||
* Adds path completions in markdown files by implementing {@link vscode.CompletionItemProvider}.
|
||||
*/
|
||||
export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
|
||||
constructor(
|
||||
private readonly engine: MarkdownEngine,
|
||||
private readonly linkComputer: MdLinkComputer,
|
||||
private readonly linkProvider: MdLinkProvider,
|
||||
) { }
|
||||
|
||||
public async provideCompletionItems(document: SkinnyTextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise<vscode.CompletionItem[]> {
|
||||
@@ -240,8 +235,8 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
const insertionRange = new vscode.Range(context.linkTextStartPosition, position);
|
||||
const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length }));
|
||||
|
||||
const definitions = await this.linkComputer.getLinkDefinitions(document);
|
||||
for (const def of definitions) {
|
||||
const { definitions } = await this.linkProvider.getLinks(document);
|
||||
for (const [_, def] of definitions) {
|
||||
yield {
|
||||
kind: vscode.CompletionItemKind.Reference,
|
||||
label: def.ref.text,
|
||||
@@ -351,3 +346,11 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
return document.uri;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerPathCompletionSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
engine: MarkdownEngine,
|
||||
linkProvider: MdLinkProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerCompletionItemProvider(selector, new MdVsCodePathCompletionProvider(engine, linkProvider), '.', '/', '#');
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as uri from 'vscode-uri';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { Slugifier } from '../slugify';
|
||||
import { TableOfContents, TocEntry } from '../tableOfContents';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
|
||||
import { InternalHref, MdLink, MdLinkComputer } from './documentLinkProvider';
|
||||
import { MdWorkspaceCache } from './workspaceCache';
|
||||
import { MdWorkspaceInfoCache } from './workspaceCache';
|
||||
|
||||
|
||||
/**
|
||||
@@ -59,19 +58,22 @@ export interface MdHeaderReference {
|
||||
|
||||
export type MdReference = MdLinkReference | MdHeaderReference;
|
||||
|
||||
export class MdReferencesComputer extends Disposable {
|
||||
/**
|
||||
* Stateful object that computes references for markdown files.
|
||||
*/
|
||||
export class MdReferencesProvider extends Disposable {
|
||||
|
||||
private readonly _linkCache: MdWorkspaceCache<readonly MdLink[]>;
|
||||
private readonly _linkCache: MdWorkspaceInfoCache<readonly MdLink[]>;
|
||||
private readonly _linkComputer: MdLinkComputer;
|
||||
|
||||
public constructor(
|
||||
private readonly linkComputer: MdLinkComputer,
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
private readonly engine: MarkdownEngine,
|
||||
private readonly slugifier: Slugifier,
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._linkCache = this._register(new MdWorkspaceCache(workspaceContents, doc => linkComputer.getAllLinks(doc, noopToken)));
|
||||
this._linkComputer = new MdLinkComputer(engine);
|
||||
this._linkCache = this._register(new MdWorkspaceInfoCache(workspaceContents, doc => this._linkComputer.getAllLinks(doc, noopToken)));
|
||||
}
|
||||
|
||||
public async getReferencesAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
@@ -110,7 +112,7 @@ export class MdReferencesComputer extends Disposable {
|
||||
for (const link of links) {
|
||||
if (link.href.kind === 'internal'
|
||||
&& this.looksLikeLinkToDoc(link.href, document.uri)
|
||||
&& this.slugifier.fromHeading(link.href.fragment).value === header.slug.value
|
||||
&& this.engine.slugifier.fromHeading(link.href.fragment).value === header.slug.value
|
||||
) {
|
||||
references.push({
|
||||
kind: 'link',
|
||||
@@ -126,7 +128,7 @@ export class MdReferencesComputer extends Disposable {
|
||||
}
|
||||
|
||||
private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
const docLinks = await this.linkComputer.getAllLinks(document, token);
|
||||
const docLinks = await this._linkComputer.getAllLinks(document, token);
|
||||
|
||||
for (const link of docLinks) {
|
||||
if (link.kind === 'definition') {
|
||||
@@ -200,7 +202,7 @@ export class MdReferencesComputer extends Disposable {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.slugifier.fromHeading(link.href.fragment).equals(this.slugifier.fromHeading(sourceLink.href.fragment))) {
|
||||
if (this.engine.slugifier.fromHeading(link.href.fragment).equals(this.engine.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',
|
||||
@@ -284,27 +286,27 @@ export class MdReferencesComputer extends Disposable {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Implements {@link vscode.ReferenceProvider} for markdown documents.
|
||||
*/
|
||||
export class MdVsCodeReferencesProvider implements vscode.ReferenceProvider {
|
||||
|
||||
public constructor(
|
||||
private readonly referencesComputer: MdReferencesComputer
|
||||
private readonly referencesProvider: MdReferencesProvider
|
||||
) { }
|
||||
|
||||
async provideReferences(document: SkinnyTextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[]> {
|
||||
const allRefs = await this.referencesComputer.getReferencesAtPosition(document, position, token);
|
||||
const allRefs = await this.referencesProvider.getReferencesAtPosition(document, position, token);
|
||||
return allRefs
|
||||
.filter(ref => context.includeDeclaration || !ref.isDefinition)
|
||||
.map(ref => ref.location);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerReferencesProvider(
|
||||
export function registerReferencesSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
computer: MdReferencesComputer,
|
||||
referencesProvider: MdReferencesProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerReferenceProvider(selector, new MdVsCodeReferencesProvider(computer));
|
||||
return vscode.languages.registerReferenceProvider(selector, new MdVsCodeReferencesProvider(referencesProvider));
|
||||
}
|
||||
|
||||
export async function tryFindMdDocumentForLink(href: InternalHref, workspaceContents: MdWorkspaceContents): Promise<SkinnyTextDocument | undefined> {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Disposable } from '../util/dispose';
|
||||
import { resolveDocumentLink } from '../util/openDocumentLink';
|
||||
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
|
||||
import { InternalHref } from './documentLinkProvider';
|
||||
import { MdHeaderReference, MdLinkReference, MdReference, MdReferencesComputer, tryFindMdDocumentForLink } from './references';
|
||||
import { MdHeaderReference, MdLinkReference, MdReference, MdReferencesProvider, tryFindMdDocumentForLink } from './references';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -45,7 +45,7 @@ function tryDecodeUri(str: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export class MdRenameProvider extends Disposable implements vscode.RenameProvider {
|
||||
export class MdVsCodeRenameProvider extends Disposable implements vscode.RenameProvider {
|
||||
|
||||
private cachedRefs?: {
|
||||
readonly resource: vscode.Uri;
|
||||
@@ -58,8 +58,8 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
||||
private readonly renameNotSupportedText = localize('invalidRenameLocation', "Rename not supported at location");
|
||||
|
||||
public constructor(
|
||||
private readonly referencesComputer: MdReferencesComputer,
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
private readonly slugifier: Slugifier,
|
||||
) {
|
||||
super();
|
||||
@@ -253,7 +253,7 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
||||
return this.cachedRefs;
|
||||
}
|
||||
|
||||
const references = await this.referencesComputer.getReferencesAtPosition(document, position, token);
|
||||
const references = await this.referencesProvider.getReferencesAtPosition(document, position, token);
|
||||
const triggerRef = references.find(ref => ref.isTriggerLocation);
|
||||
if (!triggerRef) {
|
||||
return undefined;
|
||||
@@ -270,3 +270,12 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function registerRenameSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
workspaceContents: MdWorkspaceContents,
|
||||
referencesProvider: MdReferencesProvider,
|
||||
slugifier: Slugifier,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerRenameProvider(selector, new MdVsCodeRenameProvider(workspaceContents, referencesProvider, slugifier));
|
||||
}
|
||||
|
||||
@@ -249,3 +249,10 @@ function getFirstChildHeader(document: SkinnyTextDocument, header?: TocEntry, to
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function registerSmartSelectSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
engine: MarkdownEngine,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine));
|
||||
}
|
||||
|
||||
@@ -9,12 +9,89 @@ import { Lazy, lazy } from '../util/lazy';
|
||||
import { ResourceMap } from '../util/resourceMap';
|
||||
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
|
||||
|
||||
/**
|
||||
* Cache of information for markdown files in the workspace.
|
||||
*/
|
||||
export class MdWorkspaceCache<T> extends Disposable {
|
||||
class LazyResourceMap<T> {
|
||||
private readonly _map = new ResourceMap<Lazy<Promise<T>>>();
|
||||
|
||||
private readonly _cache = new ResourceMap<Lazy<Promise<T>>>();
|
||||
public has(resource: vscode.Uri): boolean {
|
||||
return this._map.has(resource);
|
||||
}
|
||||
|
||||
public get(resource: vscode.Uri): Promise<T> | undefined {
|
||||
return this._map.get(resource)?.value;
|
||||
}
|
||||
|
||||
public set(resource: vscode.Uri, value: Lazy<Promise<T>>) {
|
||||
this._map.set(resource, value);
|
||||
}
|
||||
|
||||
public delete(resource: vscode.Uri) {
|
||||
this._map.delete(resource);
|
||||
}
|
||||
|
||||
public entries(): Promise<Array<[vscode.Uri, T]>> {
|
||||
return Promise.all(Array.from(this._map.entries(), async ([key, entry]) => {
|
||||
return [key, await entry.value];
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache of information per-document in the workspace.
|
||||
*
|
||||
* The values are computed lazily and invalidated when the document changes.
|
||||
*/
|
||||
export class MdDocumentInfoCache<T> extends Disposable {
|
||||
|
||||
private readonly _cache = new LazyResourceMap<T>();
|
||||
|
||||
public constructor(
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
private readonly getValue: (document: SkinnyTextDocument) => Promise<T>,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this.workspaceContents.onDidChangeMarkdownDocument(doc => this.onDidChangeDocument(doc)));
|
||||
this._register(this.workspaceContents.onDidCreateMarkdownDocument(doc => this.onDidChangeDocument(doc)));
|
||||
this._register(this.workspaceContents.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this));
|
||||
}
|
||||
|
||||
public async get(resource: vscode.Uri): Promise<T | undefined> {
|
||||
const existing = this._cache.get(resource);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const doc = await this.workspaceContents.getMarkdownDocument(resource);
|
||||
return doc && this.onDidChangeDocument(doc, true)?.value;
|
||||
}
|
||||
|
||||
public async entries(): Promise<Array<[vscode.Uri, T]>> {
|
||||
return this._cache.entries();
|
||||
}
|
||||
|
||||
private onDidChangeDocument(document: SkinnyTextDocument, forceAdd = false): Lazy<Promise<T>> | undefined {
|
||||
if (forceAdd || this._cache.has(document.uri)) {
|
||||
const value = lazy(() => this.getValue(document));
|
||||
this._cache.set(document.uri, value);
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private onDidDeleteDocument(resource: vscode.Uri) {
|
||||
this._cache.delete(resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache of information across all markdown files in the workspace.
|
||||
*
|
||||
* Unlike {@link MdDocumentInfoCache}, the entries here are computed eagerly for every file in the workspace.
|
||||
* However the computation of the values is still lazy.
|
||||
*/
|
||||
export class MdWorkspaceInfoCache<T> extends Disposable {
|
||||
|
||||
private readonly _cache = new LazyResourceMap<T>();
|
||||
private _init?: Promise<void>;
|
||||
|
||||
public constructor(
|
||||
@@ -26,14 +103,12 @@ export class MdWorkspaceCache<T> extends Disposable {
|
||||
|
||||
public async entries(): Promise<Array<[vscode.Uri, T]>> {
|
||||
await this.ensureInit();
|
||||
return Promise.all(Array.from(this._cache.entries(), async ([key, entry]) => {
|
||||
return [key, await entry.value];
|
||||
}));
|
||||
return this._cache.entries();
|
||||
}
|
||||
|
||||
public async values(): Promise<Array<T>> {
|
||||
await this.ensureInit();
|
||||
return Promise.all(Array.from(this._cache.values(), x => x.value));
|
||||
return Array.from(await this._cache.entries(), x => x[1]);
|
||||
}
|
||||
|
||||
private async ensureInit(): Promise<void> {
|
||||
|
||||
@@ -7,11 +7,11 @@ import * as vscode from 'vscode';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
import { MdDocumentSymbolProvider } from './documentSymbolProvider';
|
||||
import { MdWorkspaceCache } from './workspaceCache';
|
||||
import { MdWorkspaceInfoCache } from './workspaceCache';
|
||||
|
||||
export class MdWorkspaceSymbolProvider extends Disposable implements vscode.WorkspaceSymbolProvider {
|
||||
|
||||
private readonly _cache: MdWorkspaceCache<vscode.SymbolInformation[]>;
|
||||
private readonly _cache: MdWorkspaceInfoCache<vscode.SymbolInformation[]>;
|
||||
|
||||
public constructor(
|
||||
symbolProvider: MdDocumentSymbolProvider,
|
||||
@@ -19,7 +19,7 @@ export class MdWorkspaceSymbolProvider extends Disposable implements vscode.Work
|
||||
) {
|
||||
super();
|
||||
|
||||
this._cache = this._register(new MdWorkspaceCache(workspaceContents, doc => symbolProvider.provideDocumentSymbolInformation(doc)));
|
||||
this._cache = this._register(new MdWorkspaceInfoCache(workspaceContents, doc => symbolProvider.provideDocumentSymbolInformation(doc)));
|
||||
}
|
||||
|
||||
public async provideWorkspaceSymbols(query: string): Promise<vscode.SymbolInformation[]> {
|
||||
@@ -27,3 +27,10 @@ export class MdWorkspaceSymbolProvider extends Disposable implements vscode.Work
|
||||
return allSymbols.filter(symbolInformation => symbolInformation.name.toLowerCase().indexOf(query.toLowerCase()) !== -1);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerWorkspaceSymbolSupport(
|
||||
workspaceContents: MdWorkspaceContents,
|
||||
symbolProvider: MdDocumentSymbolProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider, workspaceContents));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user