diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts index d65a9b901e4..3b4ca16a074 100644 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ b/extensions/markdown-language-features/src/languageFeatures/references.ts @@ -9,17 +9,10 @@ import { Slugifier } from '../slugify'; import { TableOfContents, TocEntry } from '../tableOfContents'; import { Disposable } from '../util/dispose'; import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; -import { InternalHref, LinkHref, MdLink, MdLinkProvider } from './documentLinkProvider'; +import { InternalHref, MdLink, MdLinkProvider } from './documentLinkProvider'; import { MdWorkspaceCache } from './workspaceCache'; -function isLinkToHeader(target: LinkHref, header: TocEntry, headerDocument: vscode.Uri, slugifier: Slugifier): target is InternalHref { - return target.kind === 'internal' - && target.path.fsPath === headerDocument.fsPath - && slugifier.fromHeading(target.fragment).value === header.slug.value; -} - - /** * A link in a markdown file. */ @@ -121,15 +114,10 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference }); for (const link of links) { - if (isLinkToHeader(link.href, header, document.uri, this.slugifier)) { - references.push({ - kind: 'link', - isTriggerLocation: false, - isDefinition: false, - location: new vscode.Location(link.sourceResource, link.sourceRange), - fragmentLocation: getFragmentLocation(link), - }); - } else if (link.kind === 'definition' && isLinkToHeader(link.href, header, document.uri, this.slugifier)) { + if (link.href.kind === 'internal' + && this.looksLikeLinkToDoc(link.href, document) + && this.slugifier.fromHeading(link.href.fragment).value === header.slug.value + ) { references.push({ kind: 'link', isTriggerLocation: false, @@ -194,10 +182,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference continue; } - const matchesFilePart = link.href.path.fsPath === targetDoc.uri.fsPath - || uri.Utils.extname(link.href.path) === '' && link.href.path.with({ path: link.href.path.path + '.md' }).fsPath === targetDoc.uri.fsPath; - - if (!matchesFilePart) { + if (!this.looksLikeLinkToDoc(link.href, targetDoc)) { continue; } @@ -231,6 +216,11 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference return references; } + private looksLikeLinkToDoc(href: InternalHref, targetDoc: SkinnyTextDocument) { + return href.path.fsPath === targetDoc.uri.fsPath + || uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.uri.fsPath; + } + private * getReferencesToReferenceLink(allLinks: Iterable, sourceLink: MdLink): Iterable { if (sourceLink.href.kind !== 'reference') { return; diff --git a/extensions/markdown-language-features/src/test/references.test.ts b/extensions/markdown-language-features/src/test/references.test.ts index 0a563e07129..4c100aac9bd 100644 --- a/extensions/markdown-language-features/src/test/references.test.ts +++ b/extensions/markdown-language-features/src/test/references.test.ts @@ -215,6 +215,37 @@ suite('markdown: find all references', () => { ); }); + test('Should find references without requiring file extensions', async () => { + const docUri = workspacePath('doc.md'); + const other1Uri = workspacePath('other.md'); + + const doc = new InMemoryDocument(docUri, joinLines( + `# a B c`, + ``, + `[link 1](#a-b-c)`, + )); + const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryWorkspaceMarkdownDocuments([ + doc, + new InMemoryDocument(other1Uri, joinLines( + `[not link](#a-b-c)`, + `[not link](/doc.md#a-b-z)`, + `[with ext](/doc.md#a-b-c)`, + `[without ext](/doc#a-b-c)`, + `[rel with ext](./doc.md#a-b-c)`, + `[rel without ext](./doc#a-b-c)`, + )), + ])); + + assertReferencesEqual(refs!, + { uri: docUri, line: 0 }, // Header definition + { uri: docUri, line: 2 }, + { uri: other1Uri, line: 2 }, // Other with ext + { uri: other1Uri, line: 3 }, // Other without ext + { uri: other1Uri, line: 4 }, // Other relative link with ext + { uri: other1Uri, line: 5 }, // Other relative link without ext + ); + }); + test('Should find references from link across files when triggered on link without file extension', async () => { const docUri = workspacePath('doc.md'); const other1Uri = workspacePath('sub', 'other.md'); diff --git a/extensions/markdown-language-features/src/test/rename.test.ts b/extensions/markdown-language-features/src/test/rename.test.ts index b73e3908478..69cd3e8e668 100644 --- a/extensions/markdown-language-features/src/test/rename.test.ts +++ b/extensions/markdown-language-features/src/test/rename.test.ts @@ -149,4 +149,62 @@ suite('markdown: rename', () => { ] }); }); + + test('Rename on header should pick up links across files', async () => { + const uri = workspacePath('doc.md'); + const otherUri = workspacePath('other.md'); + const doc = new InMemoryDocument(uri, joinLines( + `### A b C`, // rename here + `[text](#a-b-c)`, + )); + + const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([ + doc, + new InMemoryDocument(otherUri, joinLines( + `[text](#a-b-c)`, // Should not find this + `[text](./doc.md#a-b-c)`, // But should find this + `[text](./doc#a-b-c)`, // And this + )) + ])); + assertEditsEqual(edit!, { + uri: uri, edits: [ + new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), + new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), + ] + }, { + uri: otherUri, edits: [ + new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'), + new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), + ] + }); + }); + + test('Rename on link should pick up links across files', async () => { + const uri = workspacePath('doc.md'); + const otherUri = workspacePath('other.md'); + const doc = new InMemoryDocument(uri, joinLines( + `### A b C`, + `[text](#a-b-c)`, // rename here + )); + + const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryWorkspaceMarkdownDocuments([ + doc, + new InMemoryDocument(otherUri, joinLines( + `[text](#a-b-c)`, // Should not find this + `[text](./doc.md#a-b-c)`, // But should find this + `[text](./doc#a-b-c)`, // And this + )) + ])); + assertEditsEqual(edit!, { + uri: uri, edits: [ + new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), + new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), + ] + }, { + uri: otherUri, edits: [ + new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'), + new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), + ] + }); + }); });