Trying to clean up md link structure by moving defintion up a level

This commit is contained in:
Matt Bierner 2022-03-31 14:59:14 -07:00
parent 0e65adbda8
commit 57dffde3d4
No known key found for this signature in database
GPG key ID: 099C331567E11888
4 changed files with 80 additions and 57 deletions

View file

@ -30,13 +30,7 @@ export interface ReferenceLinkTarget {
readonly ref: string;
}
export interface DefinitionLinkTarget {
readonly kind: 'definition';
readonly ref: string;
readonly target: ExternalLinkTarget | InternalLinkTarget;
}
export type LinkTarget = ExternalLinkTarget | InternalLinkTarget | ReferenceLinkTarget | DefinitionLinkTarget;
export type LinkTarget = ExternalLinkTarget | InternalLinkTarget | ReferenceLinkTarget;
function parseLink(
@ -93,7 +87,9 @@ function getWorkspaceFolder(document: SkinnyTextDocument) {
|| vscode.workspace.workspaceFolders?.[0]?.uri;
}
export interface LinkData {
interface MdInlineLink {
readonly kind: 'link';
readonly target: LinkTarget;
readonly sourceText: string;
@ -101,12 +97,25 @@ export interface LinkData {
readonly sourceRange: vscode.Range;
}
export interface MdLinkDefinition {
readonly kind: 'definition';
readonly sourceText: string;
readonly sourceResource: vscode.Uri;
readonly sourceRange: vscode.Range;
readonly ref: string;
readonly target: ExternalLinkTarget | InternalLinkTarget;
}
export type MdLink = MdInlineLink | MdLinkDefinition;
function extractDocumentLink(
document: SkinnyTextDocument,
pre: number,
link: string,
matchIndex: number | undefined
): LinkData | undefined {
): MdLink | undefined {
const offset = (matchIndex || 0) + pre;
const linkStart = document.positionAt(offset);
const linkEnd = document.positionAt(offset + link.length);
@ -116,6 +125,7 @@ function extractDocumentLink(
return undefined;
}
return {
kind: 'link',
target: linkTarget,
sourceText: link,
sourceResource: document.uri,
@ -179,7 +189,7 @@ async function findCode(document: SkinnyTextDocument, engine: MarkdownEngine): P
return { multiline, inline };
}
function isLinkInsideCode(code: CodeInDocument, link: LinkData) {
function isLinkInsideCode(code: CodeInDocument, link: MdLink) {
return code.multiline.some(interval => link.sourceRange.start.line >= interval[0] && link.sourceRange.start.line < interval[1]) ||
code.inline.some(position => position.intersection(link.sourceRange));
}
@ -204,7 +214,17 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
.map(data => this.toValidDocumentLink(data, definitionSet)));
}
private toValidDocumentLink(link: LinkData, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined {
private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined {
if (link.kind === 'definition') {
return this.toValidDocumentLink({
kind: 'link',
sourceText: link.sourceText,
sourceRange: link.sourceRange,
sourceResource: link.sourceResource,
target: link.target
}, definitionSet);
}
switch (link.target.kind) {
case 'external': {
return new vscode.DocumentLink(link.sourceRange, link.target.uri);
@ -225,28 +245,21 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
return undefined;
}
}
case 'definition':
return this.toValidDocumentLink({
sourceText: link.sourceText,
sourceRange: link.sourceRange,
sourceResource: link.sourceResource,
target: link.target.target
}, definitionSet);
}
}
public async getAllLinks(document: SkinnyTextDocument): Promise<LinkData[]> {
public async getAllLinks(document: SkinnyTextDocument): Promise<MdLink[]> {
return Array.from([
...(await this.getInlineLinks(document)),
...this.getReferenceLinks(document),
...this.getDefinitionLinks(document),
...this.getLinkDefinitions(document),
]);
}
private async getInlineLinks(document: SkinnyTextDocument): Promise<LinkData[]> {
private async getInlineLinks(document: SkinnyTextDocument): Promise<MdLink[]> {
const text = document.getText();
const results: LinkData[] = [];
const results: MdLink[] = [];
const codeInDocument = await findCode(document, this.engine);
for (const match of text.matchAll(linkPattern)) {
const matchImageData = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index);
@ -261,7 +274,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
return results;
}
private *getReferenceLinks(document: SkinnyTextDocument): Iterable<LinkData> {
private *getReferenceLinks(document: SkinnyTextDocument): Iterable<MdLink> {
const text = document.getText();
for (const match of text.matchAll(referenceLinkPattern)) {
let linkStart: vscode.Position;
@ -282,6 +295,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
yield {
kind: 'link',
sourceText: reference,
sourceRange: new vscode.Range(linkStart, linkEnd),
sourceResource: document.uri,
@ -293,7 +307,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
}
public *getDefinitionLinks(document: SkinnyTextDocument): Iterable<LinkData> {
public *getLinkDefinitions(document: SkinnyTextDocument): Iterable<MdLinkDefinition> {
const text = document.getText();
for (const match of text.matchAll(definitionPattern)) {
const pre = match[1];
@ -308,14 +322,13 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
const target = parseLink(document, text);
if (target) {
yield {
kind: 'definition',
sourceText: link,
sourceResource: document.uri,
sourceRange: new vscode.Range(linkStart, linkEnd),
target: {
kind: 'definition',
ref: reference,
target
}
ref: reference,
target,
};
}
} else {
@ -324,14 +337,12 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
const target = parseLink(document, link);
if (target) {
yield {
kind: 'definition',
sourceText: link,
sourceResource: document.uri,
sourceRange: new vscode.Range(linkStart, linkEnd),
target: {
kind: 'definition',
ref: reference,
target,
}
ref: reference,
target,
};
}
}
@ -340,17 +351,17 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
export class LinkDefinitionSet {
private readonly _map = new Map<string, LinkData>();
private readonly _map = new Map<string, MdLink>();
constructor(links: Iterable<LinkData>) {
constructor(links: Iterable<MdLink>) {
for (const link of links) {
if (link.target.kind === 'definition') {
this._map.set(link.target.ref, link);
if (link.kind === 'definition') {
this._map.set(link.ref, link);
}
}
}
public lookup(ref: string): LinkData | undefined {
public lookup(ref: string): MdLink | undefined {
return this._map.get(ref);
}
}

View file

@ -9,7 +9,7 @@ import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { resolveUriToMarkdownFile } from '../util/openDocumentLink';
import { SkinnyTextDocument } from '../workspaceContents';
import { DefinitionLinkTarget, MdLinkProvider } from './documentLinkProvider';
import { MdLinkProvider } from './documentLinkProvider';
enum CompletionContextKind {
/** `[...](|)` */
@ -236,11 +236,11 @@ 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 = this.linkProvider.getDefinitionLinks(document);
const definitions = this.linkProvider.getLinkDefinitions(document);
for (const def of definitions) {
yield {
kind: vscode.CompletionItemKind.Reference,
label: (def.target as DefinitionLinkTarget).ref,
label: def.ref,
range: {
inserting: insertionRange,
replacing: replacementRange,

View file

@ -9,7 +9,7 @@ import { Slugifier } from '../slugify';
import { TableOfContents, TocEntry } from '../tableOfContents';
import { Disposable } from '../util/dispose';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { DefinitionLinkTarget, InternalLinkTarget, LinkData, LinkTarget, MdLinkProvider } from './documentLinkProvider';
import { InternalLinkTarget, MdLink, LinkTarget, MdLinkProvider, MdLinkDefinition } from './documentLinkProvider';
import { MdWorkspaceCache } from './workspaceCache';
@ -59,7 +59,7 @@ interface MdHeaderReference {
export type MdReference = MdLinkReference | MdHeaderReference;
function getFragmentLocation(link: LinkData): vscode.Location | undefined {
function getFragmentLocation(link: MdLink): vscode.Location | undefined {
const index = link.sourceText.indexOf('#');
if (index < 0) {
return undefined;
@ -71,7 +71,7 @@ function getFragmentLocation(link: LinkData): vscode.Location | undefined {
export class MdReferencesProvider extends Disposable implements vscode.ReferenceProvider {
private readonly _linkCache: MdWorkspaceCache<LinkData[]>;
private readonly _linkCache: MdWorkspaceCache<MdLink[]>;
public constructor(
private readonly linkProvider: MdLinkProvider,
@ -129,7 +129,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
location: new vscode.Location(link.sourceResource, link.sourceRange),
fragmentLocation: getFragmentLocation(link),
});
} else if (link.target.kind === 'definition' && isLinkToHeader(link.target.target, header, document.uri, this.slugifier)) {
} else if (link.kind === 'definition' && isLinkToHeader(link.target, header, document.uri, this.slugifier)) {
references.push({
kind: 'link',
isTriggerLocation: false,
@ -149,9 +149,9 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
return sourceLink ? this.getReferencesToLink(sourceLink) : [];
}
private async getReferencesToLink(sourceLink: LinkData): Promise<MdReference[]> {
if (sourceLink.target.kind === 'definition') {
return this.getReferencesToLink(this.getInnerLink(sourceLink, sourceLink.target));
private async getReferencesToLink(sourceLink: MdLink): Promise<MdReference[]> {
if (sourceLink.kind === 'definition') {
return this.getReferencesToLink(this.getInnerLink(sourceLink));
}
const allLinksInWorkspace = (await this._linkCache.getAll()).flat();
@ -194,8 +194,8 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
}
for (let link of allLinksInWorkspace) {
if (link.target.kind === 'definition') {
link = this.getInnerLink(link, link.target);
if (link.kind === 'definition') {
link = this.getInnerLink(link);
}
if (link.target.kind !== 'internal') {
@ -239,28 +239,40 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
return references;
}
private getInnerLink(sourceLink: LinkData, target: DefinitionLinkTarget): LinkData {
private getInnerLink(sourceLink: MdLinkDefinition): MdLink {
return {
kind: 'link',
sourceText: sourceLink.sourceText, // This is not correct
sourceResource: sourceLink.sourceResource,
sourceRange: sourceLink.sourceRange,
target: target.target,
target: sourceLink.target,
};
}
private * getReferencesToReferenceLink(allLinks: Iterable<LinkData>, sourceLink: LinkData): Iterable<MdReference> {
private * getReferencesToReferenceLink(allLinks: Iterable<MdLink>, sourceLink: MdLink): Iterable<MdReference> {
if (sourceLink.target.kind !== 'reference') {
return;
}
for (const link of allLinks) {
if (link.target.kind === 'reference' || link.target.kind === 'definition') {
if (link.kind === 'definition') {
if (link.ref === sourceLink.target.ref && link.sourceResource.fsPath === sourceLink.sourceResource.fsPath) {
const isTriggerLocation = sourceLink.sourceResource.fsPath === link.sourceResource.fsPath && sourceLink.sourceRange.isEqual(link.sourceRange);
yield {
kind: 'link',
isTriggerLocation,
isDefinition: true,
location: new vscode.Location(sourceLink.sourceResource, link.sourceRange),
fragmentLocation: getFragmentLocation(link),
};
}
} else if (link.target.kind === 'reference') {
if (link.target.ref === sourceLink.target.ref && link.sourceResource.fsPath === sourceLink.sourceResource.fsPath) {
const isTriggerLocation = sourceLink.sourceResource.fsPath === link.sourceResource.fsPath && sourceLink.sourceRange.isEqual(link.sourceRange);
yield {
kind: 'link',
isTriggerLocation,
isDefinition: link.target.kind === 'definition',
isDefinition: false,
location: new vscode.Location(sourceLink.sourceResource, link.sourceRange),
fragmentLocation: getFragmentLocation(link),
};

View file

@ -59,7 +59,7 @@ function assertEditsEqual(actualEdit: vscode.WorkspaceEdit, ...expectedEdits: {
}
}
suite.only('markdown: rename', () => {
suite('markdown: rename', () => {
setup(async () => {
// the tests make the assumption that link providers are already registered