Split markdown linkComputer from linkProvider (#152124)

Instead of passing around a full `vscode.DocumentLinkProvider`, we should pass just the more minimal interface that consumers need
This commit is contained in:
Matt Bierner 2022-06-14 15:34:05 -07:00 committed by GitHub
parent 43d8a326fa
commit 00273730e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 105 additions and 90 deletions

View file

@ -9,7 +9,7 @@ import * as commands from './commands/index';
import { registerPasteProvider } from './languageFeatures/copyPaste';
import { MdDefinitionProvider } from './languageFeatures/definitionProvider';
import { register as registerDiagnostics } from './languageFeatures/diagnostics';
import { MdLinkProvider } from './languageFeatures/documentLinkProvider';
import { MdLinkComputer, registerDocumentLinkProvider } from './languageFeatures/documentLinkProvider';
import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbolProvider';
import { registerDropIntoEditor } from './languageFeatures/dropIntoEditor';
import { registerFindFileReferences } from './languageFeatures/fileReferences';
@ -63,21 +63,22 @@ function registerMarkdownLanguageFeatures(
): vscode.Disposable {
const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' };
const linkProvider = new MdLinkProvider(engine);
const linkComputer = new MdLinkComputer(engine);
const workspaceContents = new VsCodeMdWorkspaceContents();
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const referencesProvider = new MdReferencesProvider(linkComputer, workspaceContents, engine, githubSlugifier);
return vscode.Disposable.from(
workspaceContents,
vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider),
vscode.languages.registerDocumentLinkProvider(selector, linkProvider),
vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine)),
vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine)),
vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider, workspaceContents)),
vscode.languages.registerReferenceProvider(selector, referencesProvider),
vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier)),
vscode.languages.registerDefinitionProvider(selector, new MdDefinitionProvider(referencesProvider)),
MdPathCompletionProvider.register(selector, engine, linkProvider),
registerDiagnostics(selector, engine, workspaceContents, linkProvider, commandManager),
MdPathCompletionProvider.register(selector, engine, linkComputer),
registerDocumentLinkProvider(selector, linkComputer),
registerDiagnostics(selector, engine, workspaceContents, linkComputer, commandManager),
registerDropIntoEditor(selector),
registerPasteProvider(selector),
registerFindFileReferences(commandManager, referencesProvider),

View file

@ -3,20 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as picomatch from 'picomatch';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as picomatch from 'picomatch';
import { CommandManager } from '../commandManager';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { Delayer } from '../util/async';
import { Disposable } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
import { Limiter } from '../util/limiter';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinkProvider';
import { tryFindMdDocumentForLink } from './references';
import { CommandManager } from '../commandManager';
import { ResourceMap } from '../util/resourceMap';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalHref, LinkDefinitionSet, MdLink, MdLinkComputer, MdLinkSource } from './documentLinkProvider';
import { tryFindMdDocumentForLink } from './references';
const localize = nls.loadMessageBundle();
@ -382,11 +382,11 @@ export class DiagnosticComputer {
constructor(
private readonly engine: MarkdownEngine,
private readonly workspaceContents: MdWorkspaceContents,
private readonly linkProvider: MdLinkProvider,
private readonly linkComputer: MdLinkComputer,
) { }
public async getDiagnostics(doc: SkinnyTextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: MdLink[] }> {
const links = await this.linkProvider.getAllLinks(doc, token);
const links = await this.linkComputer.getAllLinks(doc, token);
if (token.isCancellationRequested) {
return { links, diagnostics: [] };
}
@ -559,11 +559,11 @@ export function register(
selector: vscode.DocumentSelector,
engine: MarkdownEngine,
workspaceContents: MdWorkspaceContents,
linkProvider: MdLinkProvider,
linkComputer: MdLinkComputer,
commandManager: CommandManager,
): vscode.Disposable {
const configuration = new VSCodeDiagnosticConfiguration();
const manager = new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkProvider), configuration);
const manager = new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkComputer), configuration);
return vscode.Disposable.from(
configuration,
manager,

View file

@ -242,53 +242,12 @@ class NoLinkRanges {
}
}
export class MdLinkProvider implements vscode.DocumentLinkProvider {
export class MdLinkComputer {
constructor(
private readonly engine: MarkdownEngine
) { }
public async provideDocumentLinks(
document: SkinnyTextDocument,
token: vscode.CancellationToken
): Promise<vscode.DocumentLink[]> {
const allLinks = await this.getAllLinks(document, token);
if (token.isCancellationRequested) {
return [];
}
const definitionSet = new LinkDefinitionSet(allLinks);
return coalesce(allLinks
.map(data => this.toValidDocumentLink(data, definitionSet)));
}
private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined {
switch (link.href.kind) {
case 'external': {
return new vscode.DocumentLink(link.source.hrefRange, link.href.uri);
}
case 'internal': {
const uri = OpenDocumentLinkCommand.createCommandUri(link.source.resource, link.href.path, link.href.fragment);
const documentLink = new vscode.DocumentLink(link.source.hrefRange, uri);
documentLink.tooltip = localize('documentLink.tooltip', 'Follow link');
return documentLink;
}
case 'reference': {
const def = definitionSet.lookup(link.href.ref);
if (def) {
const documentLink = new vscode.DocumentLink(
link.source.hrefRange,
vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.source.hrefRange.start.line, def.source.hrefRange.start.character]))}`));
documentLink.tooltip = localize('documentLink.referenceTooltip', 'Go to link definition');
return documentLink;
} else {
return undefined;
}
}
}
}
public async getAllLinks(document: SkinnyTextDocument, token: vscode.CancellationToken): Promise<MdLink[]> {
const noLinkRanges = await NoLinkRanges.compute(document, this.engine);
if (token.isCancellationRequested) {
@ -475,3 +434,57 @@ export class LinkDefinitionSet {
return this._map.get(ref);
}
}
export class MdLinkProvider implements vscode.DocumentLinkProvider {
constructor(
private readonly _linkComputer: MdLinkComputer,
) { }
public async provideDocumentLinks(
document: SkinnyTextDocument,
token: vscode.CancellationToken
): Promise<vscode.DocumentLink[]> {
const allLinks = (await this._linkComputer.getAllLinks(document, token)) ?? [];
if (token.isCancellationRequested) {
return [];
}
const definitionSet = new LinkDefinitionSet(allLinks);
return coalesce(allLinks
.map(data => this.toValidDocumentLink(data, definitionSet)));
}
private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined {
switch (link.href.kind) {
case 'external': {
return new vscode.DocumentLink(link.source.hrefRange, link.href.uri);
}
case 'internal': {
const uri = OpenDocumentLinkCommand.createCommandUri(link.source.resource, link.href.path, link.href.fragment);
const documentLink = new vscode.DocumentLink(link.source.hrefRange, uri);
documentLink.tooltip = localize('documentLink.tooltip', 'Follow link');
return documentLink;
}
case 'reference': {
const def = definitionSet.lookup(link.href.ref);
if (def) {
const documentLink = new vscode.DocumentLink(
link.source.hrefRange,
vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.source.hrefRange.start.line, def.source.hrefRange.start.character]))}`));
documentLink.tooltip = localize('documentLink.referenceTooltip', 'Go to link definition');
return documentLink;
} else {
return undefined;
}
}
}
}
}
export function registerDocumentLinkProvider(
selector: vscode.DocumentSelector,
linkComputer: MdLinkComputer,
): vscode.Disposable {
return vscode.languages.registerDocumentLinkProvider(selector, new MdLinkProvider(linkComputer));
}

View file

@ -9,7 +9,7 @@ import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { resolveUriToMarkdownFile } from '../util/openDocumentLink';
import { SkinnyTextDocument } from '../workspaceContents';
import { MdLinkProvider } from './documentLinkProvider';
import { MdLinkComputer } from './documentLinkProvider';
enum CompletionContextKind {
/** `[...](|)` */
@ -81,14 +81,14 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
public static register(
selector: vscode.DocumentSelector,
engine: MarkdownEngine,
linkProvider: MdLinkProvider,
linkComputer: MdLinkComputer,
): vscode.Disposable {
return vscode.languages.registerCompletionItemProvider(selector, new MdPathCompletionProvider(engine, linkProvider), '.', '/', '#');
return vscode.languages.registerCompletionItemProvider(selector, new MdPathCompletionProvider(engine, linkComputer), '.', '/', '#');
}
constructor(
private readonly engine: MarkdownEngine,
private readonly linkProvider: MdLinkProvider,
private readonly linkComputer: MdLinkComputer,
) { }
public async provideCompletionItems(document: SkinnyTextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise<vscode.CompletionItem[]> {
@ -240,7 +240,7 @@ 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.linkProvider.getLinkDefinitions(document);
const definitions = await this.linkComputer.getLinkDefinitions(document);
for (const def of definitions) {
yield {
kind: vscode.CompletionItemKind.Reference,

View file

@ -10,7 +10,7 @@ import { TableOfContents, TocEntry } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { Disposable } from '../util/dispose';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalHref, MdLink, MdLinkProvider } from './documentLinkProvider';
import { InternalHref, MdLink, MdLinkComputer } from './documentLinkProvider';
import { MdWorkspaceCache } from './workspaceCache';
@ -64,14 +64,14 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
private readonly _linkCache: MdWorkspaceCache<readonly MdLink[]>;
public constructor(
private readonly linkProvider: MdLinkProvider,
private readonly linkComputer: MdLinkComputer,
private readonly workspaceContents: MdWorkspaceContents,
private readonly engine: MarkdownEngine,
private readonly slugifier: Slugifier,
) {
super();
this._linkCache = this._register(new MdWorkspaceCache(workspaceContents, doc => linkProvider.getAllLinks(doc, noopToken)));
this._linkCache = this._register(new MdWorkspaceCache(workspaceContents, doc => linkComputer.getAllLinks(doc, noopToken)));
}
async provideReferences(document: SkinnyTextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {
@ -129,7 +129,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
}
private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
const docLinks = await this.linkProvider.getAllLinks(document, token);
const docLinks = await this.linkComputer.getAllLinks(document, token);
for (const link of docLinks) {
if (link.kind === 'definition') {

View file

@ -7,7 +7,7 @@ import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdDefinitionProvider } from '../languageFeatures/definitionProvider';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdLinkComputer } from '../languageFeatures/documentLinkProvider';
import { MdReferencesProvider } from '../languageFeatures/references';
import { githubSlugifier } from '../slugify';
import { noopToken } from '../util/cancellation';
@ -20,8 +20,8 @@ import { joinLines, workspacePath } from './util';
function getDefinition(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const linkComputer = new MdLinkComputer(engine);
const referencesProvider = new MdReferencesProvider(linkComputer, workspaceContents, engine, githubSlugifier);
const provider = new MdDefinitionProvider(referencesProvider);
return provider.provideDefinition(doc, pos, noopToken);
}

View file

@ -7,7 +7,7 @@ import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { DiagnosticComputer, DiagnosticConfiguration, DiagnosticLevel, DiagnosticManager, DiagnosticOptions } from '../languageFeatures/diagnostics';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdLinkComputer } from '../languageFeatures/documentLinkProvider';
import { noopToken } from '../util/cancellation';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { MdWorkspaceContents } from '../workspaceContents';
@ -18,8 +18,8 @@ import { assertRangeEqual, joinLines, workspacePath } from './util';
async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: MdWorkspaceContents): Promise<vscode.Diagnostic[]> {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const computer = new DiagnosticComputer(engine, workspaceContents, linkProvider);
const linkComputer = new MdLinkComputer(engine);
const computer = new DiagnosticComputer(engine, workspaceContents, linkComputer);
return (
await computer.getDiagnostics(doc, {
enabled: true,
@ -34,8 +34,8 @@ async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents:
function createDiagnosticsManager(workspaceContents: MdWorkspaceContents, configuration = new MemoryDiagnosticConfiguration({})) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
return new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkProvider), configuration);
const linkComputer = new MdLinkComputer(engine);
return new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkComputer), configuration);
}
function assertDiagnosticsEqual(actual: readonly vscode.Diagnostic[], expectedRanges: readonly vscode.Range[]) {

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdLinkComputer, MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { noopToken } from '../util/cancellation';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
@ -17,7 +17,8 @@ const testFile = vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri,
function getLinksForFile(fileContents: string) {
const doc = new InMemoryDocument(testFile, fileContents);
const provider = new MdLinkProvider(createNewMarkdownEngine());
const linkComputer = new MdLinkComputer(createNewMarkdownEngine());
const provider = new MdLinkProvider(linkComputer);
return provider.provideDocumentLinks(doc, noopToken);
}

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdLinkComputer } from '../languageFeatures/documentLinkProvider';
import { MdReference, MdReferencesProvider } from '../languageFeatures/references';
import { githubSlugifier } from '../slugify';
import { noopToken } from '../util/cancellation';
@ -19,8 +19,8 @@ import { joinLines, workspacePath } from './util';
function getFileReferences(resource: vscode.Uri, workspaceContents: MdWorkspaceContents) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const linkComputer = new MdLinkComputer(engine);
const provider = new MdReferencesProvider(linkComputer, workspaceContents, engine, githubSlugifier);
return provider.getAllReferencesToFile(resource, noopToken);
}

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdLinkComputer } from '../languageFeatures/documentLinkProvider';
import { MdPathCompletionProvider } from '../languageFeatures/pathCompletions';
import { noopToken } from '../util/cancellation';
import { InMemoryDocument } from '../util/inMemoryDocument';
@ -17,8 +17,8 @@ import { CURSOR, getCursorPositions, joinLines, workspacePath } from './util';
function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string) {
const doc = new InMemoryDocument(resource, fileContents);
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const provider = new MdPathCompletionProvider(engine, linkProvider);
const linkComputer = new MdLinkComputer(engine);
const provider = new MdPathCompletionProvider(engine, linkComputer);
const cursorPositions = getCursorPositions(fileContents, doc);
return provider.provideCompletionItems(doc, cursorPositions[0], noopToken, {
triggerCharacter: undefined,

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdLinkComputer } from '../languageFeatures/documentLinkProvider';
import { MdReferencesProvider } from '../languageFeatures/references';
import { githubSlugifier } from '../slugify';
import { noopToken } from '../util/cancellation';
@ -19,8 +19,8 @@ import { joinLines, workspacePath } from './util';
function getReferences(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const linkComputer = new MdLinkComputer(engine);
const provider = new MdReferencesProvider(linkComputer, workspaceContents, engine, githubSlugifier);
return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
}

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdLinkComputer } from '../languageFeatures/documentLinkProvider';
import { MdReferencesProvider } from '../languageFeatures/references';
import { MdRenameProvider, MdWorkspaceEdit } from '../languageFeatures/rename';
import { githubSlugifier } from '../slugify';
@ -23,8 +23,8 @@ import { assertRangeEqual, joinLines, workspacePath } from './util';
*/
function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents): Promise<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const linkComputer = new MdLinkComputer(engine);
const referencesProvider = new MdReferencesProvider(linkComputer, workspaceContents, engine, githubSlugifier);
const renameProvider = new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier);
return renameProvider.prepareRename(doc, pos, noopToken);
}
@ -34,8 +34,8 @@ function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspaceCon
*/
function getRenameEdits(doc: InMemoryDocument, pos: vscode.Position, newName: string, workspaceContents: MdWorkspaceContents): Promise<MdWorkspaceEdit | undefined> {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const linkComputer = new MdLinkComputer(engine);
const referencesProvider = new MdReferencesProvider(linkComputer, workspaceContents, engine, githubSlugifier);
const renameProvider = new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier);
return renameProvider.provideRenameEditsImpl(doc, pos, newName, noopToken);
}