mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
parent
0d1530cf2f
commit
c39d09a4c0
|
@ -94,7 +94,7 @@ export interface MdInlineLink {
|
|||
|
||||
readonly sourceText: string;
|
||||
readonly sourceResource: vscode.Uri;
|
||||
readonly sourceRange: vscode.Range;
|
||||
readonly sourceHrefRange: vscode.Range;
|
||||
}
|
||||
|
||||
export interface MdLinkDefinition {
|
||||
|
@ -102,7 +102,9 @@ export interface MdLinkDefinition {
|
|||
|
||||
readonly sourceText: string;
|
||||
readonly sourceResource: vscode.Uri;
|
||||
readonly sourceRange: vscode.Range;
|
||||
readonly sourceHrefRange: vscode.Range;
|
||||
|
||||
readonly refRange: vscode.Range;
|
||||
|
||||
readonly ref: string;
|
||||
readonly href: ExternalHref | InternalHref;
|
||||
|
@ -129,7 +131,7 @@ function extractDocumentLink(
|
|||
href: linkTarget,
|
||||
sourceText: link,
|
||||
sourceResource: document.uri,
|
||||
sourceRange: new vscode.Range(linkStart, linkEnd)
|
||||
sourceHrefRange: new vscode.Range(linkStart, linkEnd)
|
||||
};
|
||||
} catch {
|
||||
return undefined;
|
||||
|
@ -190,8 +192,8 @@ async function findCode(document: SkinnyTextDocument, engine: MarkdownEngine): P
|
|||
}
|
||||
|
||||
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));
|
||||
return code.multiline.some(interval => link.sourceHrefRange.start.line >= interval[0] && link.sourceHrefRange.start.line < interval[1]) ||
|
||||
code.inline.some(position => position.intersection(link.sourceHrefRange));
|
||||
}
|
||||
|
||||
export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
||||
|
@ -217,11 +219,11 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
|||
private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined {
|
||||
switch (link.href.kind) {
|
||||
case 'external': {
|
||||
return new vscode.DocumentLink(link.sourceRange, link.href.uri);
|
||||
return new vscode.DocumentLink(link.sourceHrefRange, link.href.uri);
|
||||
}
|
||||
case 'internal': {
|
||||
const uri = OpenDocumentLinkCommand.createCommandUri(link.sourceResource, link.href.path, link.href.fragment);
|
||||
const documentLink = new vscode.DocumentLink(link.sourceRange, uri);
|
||||
const documentLink = new vscode.DocumentLink(link.sourceHrefRange, uri);
|
||||
documentLink.tooltip = localize('documentLink.tooltip', 'Follow link');
|
||||
return documentLink;
|
||||
}
|
||||
|
@ -229,8 +231,8 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
|||
const def = definitionSet.lookup(link.href.ref);
|
||||
if (def) {
|
||||
return new vscode.DocumentLink(
|
||||
link.sourceRange,
|
||||
vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.sourceRange.start.line, def.sourceRange.start.character]))}`));
|
||||
link.sourceHrefRange,
|
||||
vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.sourceHrefRange.start.line, def.sourceHrefRange.start.character]))}`));
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -287,7 +289,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
|||
yield {
|
||||
kind: 'link',
|
||||
sourceText: reference,
|
||||
sourceRange: new vscode.Range(linkStart, linkEnd),
|
||||
sourceHrefRange: new vscode.Range(linkStart, linkEnd),
|
||||
sourceResource: document.uri,
|
||||
href: {
|
||||
kind: 'reference',
|
||||
|
@ -305,6 +307,9 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
|||
const link = match[3].trim();
|
||||
const offset = (match.index || 0) + pre.length;
|
||||
|
||||
const refStart = document.positionAt((match.index ?? 0) + 1);
|
||||
const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length }));
|
||||
|
||||
if (angleBracketLinkRe.test(link)) {
|
||||
const linkStart = document.positionAt(offset + 1);
|
||||
const linkEnd = document.positionAt(offset + link.length - 1);
|
||||
|
@ -315,7 +320,8 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
|||
kind: 'definition',
|
||||
sourceText: link,
|
||||
sourceResource: document.uri,
|
||||
sourceRange: new vscode.Range(linkStart, linkEnd),
|
||||
sourceHrefRange: new vscode.Range(linkStart, linkEnd),
|
||||
refRange,
|
||||
ref: reference,
|
||||
href: target,
|
||||
};
|
||||
|
@ -329,7 +335,8 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
|||
kind: 'definition',
|
||||
sourceText: link,
|
||||
sourceResource: document.uri,
|
||||
sourceRange: new vscode.Range(linkStart, linkEnd),
|
||||
sourceHrefRange: new vscode.Range(linkStart, linkEnd),
|
||||
refRange,
|
||||
ref: reference,
|
||||
href: target,
|
||||
};
|
||||
|
|
|
@ -22,6 +22,8 @@ interface MdLinkReference {
|
|||
readonly isDefinition: boolean;
|
||||
readonly location: vscode.Location;
|
||||
|
||||
readonly link: MdLink;
|
||||
|
||||
readonly fragmentLocation: vscode.Location | undefined;
|
||||
}
|
||||
|
||||
|
@ -57,8 +59,8 @@ function getFragmentLocation(link: MdLink): vscode.Location | undefined {
|
|||
if (index < 0) {
|
||||
return undefined;
|
||||
}
|
||||
return new vscode.Location(link.sourceResource, link.sourceRange.with({
|
||||
start: link.sourceRange.start.translate({ characterDelta: index + 1 }),
|
||||
return new vscode.Location(link.sourceResource, link.sourceHrefRange.with({
|
||||
start: link.sourceHrefRange.start.translate({ characterDelta: index + 1 }),
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -122,7 +124,8 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
kind: 'link',
|
||||
isTriggerLocation: false,
|
||||
isDefinition: false,
|
||||
location: new vscode.Location(link.sourceResource, link.sourceRange),
|
||||
link,
|
||||
location: new vscode.Location(link.sourceResource, link.sourceHrefRange),
|
||||
fragmentLocation: getFragmentLocation(link),
|
||||
});
|
||||
}
|
||||
|
@ -133,15 +136,30 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
|
||||
private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position): Promise<MdReference[]> {
|
||||
const docLinks = await this.linkProvider.getAllLinks(document);
|
||||
const sourceLink = docLinks.find(link => link.sourceRange.contains(position));
|
||||
return sourceLink ? this.getReferencesToLink(sourceLink) : [];
|
||||
|
||||
for (const link of docLinks) {
|
||||
if (link.kind === 'definition') {
|
||||
// We could be in either the ref name or the definition
|
||||
if (link.refRange.contains(position)) {
|
||||
return Array.from(this.getReferencesToLinkReference(docLinks, link.ref, { resource: document.uri, range: link.refRange }));
|
||||
} else if (link.sourceHrefRange.contains(position)) {
|
||||
return this.getReferencesToLink(link);
|
||||
}
|
||||
} else {
|
||||
if (link.sourceHrefRange.contains(position)) {
|
||||
return this.getReferencesToLink(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private async getReferencesToLink(sourceLink: MdLink): Promise<MdReference[]> {
|
||||
const allLinksInWorkspace = (await this._linkCache.getAll()).flat();
|
||||
|
||||
if (sourceLink.href.kind === 'reference') {
|
||||
return Array.from(this.getReferencesToReferenceLink(allLinksInWorkspace, sourceLink));
|
||||
return Array.from(this.getReferencesToLinkReference(allLinksInWorkspace, sourceLink.href.ref, { resource: sourceLink.sourceResource, range: sourceLink.sourceHrefRange }));
|
||||
}
|
||||
|
||||
if (sourceLink.href.kind !== 'internal') {
|
||||
|
@ -186,7 +204,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
continue;
|
||||
}
|
||||
|
||||
const isTriggerLocation = sourceLink.sourceResource.fsPath === link.sourceResource.fsPath && sourceLink.sourceRange.isEqual(link.sourceRange);
|
||||
const isTriggerLocation = sourceLink.sourceResource.fsPath === link.sourceResource.fsPath && sourceLink.sourceHrefRange.isEqual(link.sourceHrefRange);
|
||||
|
||||
if (sourceLink.href.fragment) {
|
||||
if (this.slugifier.fromHeading(link.href.fragment).equals(this.slugifier.fromHeading(sourceLink.href.fragment))) {
|
||||
|
@ -194,7 +212,8 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: false,
|
||||
location: new vscode.Location(link.sourceResource, link.sourceRange),
|
||||
link,
|
||||
location: new vscode.Location(link.sourceResource, link.sourceHrefRange),
|
||||
fragmentLocation: getFragmentLocation(link),
|
||||
});
|
||||
}
|
||||
|
@ -206,7 +225,8 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: false,
|
||||
location: new vscode.Location(link.sourceResource, link.sourceRange),
|
||||
link,
|
||||
location: new vscode.Location(link.sourceResource, link.sourceHrefRange),
|
||||
fragmentLocation: getFragmentLocation(link),
|
||||
});
|
||||
}
|
||||
|
@ -221,11 +241,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
|| uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.uri.fsPath;
|
||||
}
|
||||
|
||||
private * getReferencesToReferenceLink(allLinks: Iterable<MdLink>, sourceLink: MdLink): Iterable<MdReference> {
|
||||
if (sourceLink.href.kind !== 'reference') {
|
||||
return;
|
||||
}
|
||||
|
||||
private *getReferencesToLinkReference(allLinks: Iterable<MdLink>, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable<MdReference> {
|
||||
for (const link of allLinks) {
|
||||
let ref: string;
|
||||
if (link.kind === 'definition') {
|
||||
|
@ -236,13 +252,15 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
continue;
|
||||
}
|
||||
|
||||
if (ref === sourceLink.href.ref && link.sourceResource.fsPath === sourceLink.sourceResource.fsPath) {
|
||||
const isTriggerLocation = sourceLink.sourceResource.fsPath === link.sourceResource.fsPath && sourceLink.sourceRange.isEqual(link.sourceRange);
|
||||
if (ref === refToFind && link.sourceResource.fsPath === from.resource.fsPath) {
|
||||
const isTriggerLocation = from.resource.fsPath === link.sourceResource.fsPath && (
|
||||
(link.href.kind === 'reference' && from.range.isEqual(link.sourceHrefRange)) || (link.kind === 'definition' && from.range.isEqual(link.refRange)));
|
||||
yield {
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: link.kind === 'definition',
|
||||
location: new vscode.Location(sourceLink.sourceResource, link.sourceRange),
|
||||
link,
|
||||
location: new vscode.Location(from.resource, link.sourceHrefRange),
|
||||
fragmentLocation: getFragmentLocation(link),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,10 +43,21 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (triggerRef.kind === 'header') {
|
||||
return triggerRef.headerTextLocation.range;
|
||||
} else {
|
||||
return triggerRef.fragmentLocation?.range ?? triggerRef.location.range;
|
||||
switch (triggerRef.kind) {
|
||||
case 'header':
|
||||
return triggerRef.headerTextLocation.range;
|
||||
|
||||
case 'link':
|
||||
if (triggerRef.link.kind === 'definition') {
|
||||
// We may have been triggered on the ref or the definition itself
|
||||
if (triggerRef.link.refRange.contains(position)) {
|
||||
return triggerRef.link.refRange;
|
||||
} else {
|
||||
return triggerRef.link.sourceHrefRange;
|
||||
}
|
||||
} else {
|
||||
return triggerRef.fragmentLocation?.range ?? triggerRef.location.range;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,15 +67,35 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const triggerRef = references.find(ref => ref.isTriggerLocation);
|
||||
if (!triggerRef) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isRefRename = triggerRef.kind === 'link' && (
|
||||
(triggerRef.link.kind === 'definition' && triggerRef.link.refRange.contains(position)) || triggerRef.link.href.kind === 'reference'
|
||||
);
|
||||
const slug = this.slugifier.fromHeading(newName).value;
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
|
||||
const slug = this.slugifier.fromHeading(newName);
|
||||
|
||||
for (const ref of references) {
|
||||
if (ref.kind === 'header') {
|
||||
edit.replace(ref.location.uri, ref.headerTextLocation.range, newName);
|
||||
} else {
|
||||
edit.replace(ref.location.uri, ref.fragmentLocation?.range ?? ref.location.range, slug.value);
|
||||
switch (ref.kind) {
|
||||
case 'header':
|
||||
edit.replace(ref.location.uri, ref.headerTextLocation.range, newName);
|
||||
break;
|
||||
|
||||
case 'link':
|
||||
if (ref.link.kind === 'definition') {
|
||||
// We may be renaming either the reference or the definition itself
|
||||
if (isRefRename) {
|
||||
edit.replace(ref.link.sourceResource, ref.link.refRange, newName);
|
||||
} else {
|
||||
edit.replace(ref.link.sourceResource, ref.fragmentLocation?.range ?? ref.link.sourceHrefRange, ref.fragmentLocation ? slug : newName);
|
||||
}
|
||||
} else {
|
||||
edit.replace(ref.location.uri, ref.fragmentLocation?.range ?? ref.location.range, ref.link.href.kind === 'reference' ? newName : slug);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -319,10 +319,10 @@ suite('markdown: find all references', () => {
|
|||
});
|
||||
|
||||
suite('Reference links', () => {
|
||||
test('Should find reference links within file', async () => {
|
||||
test('Should find reference links within file from link', async () => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`,
|
||||
`[link 1][abc]`, // trigger here
|
||||
``,
|
||||
`[abc]: https://example.com`,
|
||||
));
|
||||
|
@ -334,6 +334,21 @@ suite('markdown: find all references', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('Should find reference links within file from definition', async () => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`,
|
||||
``,
|
||||
`[abc]: https://example.com`, // trigger here
|
||||
));
|
||||
|
||||
const refs = await getReferences(doc, new vscode.Position(2, 3), new InMemoryWorkspaceMarkdownDocuments([doc]));
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
});
|
||||
|
||||
test('Should not find reference links across files', async () => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
|
|
|
@ -207,4 +207,42 @@ suite('markdown: rename', () => {
|
|||
]
|
||||
});
|
||||
});
|
||||
|
||||
test('Rename on ref should rename refs and def', async () => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text][ref]`, // rename here
|
||||
`[other][ref]`,
|
||||
``,
|
||||
`[ref]: https://example.com`,
|
||||
));
|
||||
|
||||
const edit = await getRenameEdits(doc, new vscode.Position(0, 8), "new ref", new InMemoryWorkspaceMarkdownDocuments([doc]));
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
test('Rename on def should rename refs and def', async () => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text][ref]`,
|
||||
`[other][ref]`,
|
||||
``,
|
||||
`[ref]: https://example.com`, // rename here
|
||||
));
|
||||
|
||||
const edit = await getRenameEdits(doc, new vscode.Position(3, 3), "new ref", new InMemoryWorkspaceMarkdownDocuments([doc]));
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue