diff --git a/extensions/markdown-language-features/src/languageFeatures/documentSymbolProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentSymbolProvider.ts index 94db3edaae6..cdb1b8a79b8 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentSymbolProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentSymbolProvider.ts @@ -58,7 +58,7 @@ export class MdDocumentSymbolProvider implements vscode.DocumentSymbolProvider { this.getSymbolName(entry), vscode.SymbolKind.String, '', - entry.location); + entry.sectionLocation); } private toDocumentSymbol(entry: TocEntry) { @@ -66,8 +66,8 @@ export class MdDocumentSymbolProvider implements vscode.DocumentSymbolProvider { this.getSymbolName(entry), '', vscode.SymbolKind.String, - entry.location.range, - entry.location.range); + entry.sectionLocation.range, + entry.sectionLocation.range); } private getSymbolName(entry: TocEntry): string { diff --git a/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts b/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts index 8198472521d..243a2ee12fd 100644 --- a/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts @@ -57,7 +57,7 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider { private async getHeaderFoldingRanges(document: SkinnyTextDocument) { const toc = await TableOfContents.create(this.engine, document); return toc.entries.map(entry => { - let endLine = entry.location.range.end.line; + let endLine = entry.sectionLocation.range.end.line; if (document.lineAt(endLine).isEmptyOrWhitespace && endLine >= entry.line + 1) { endLine = endLine - 1; } diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts index 0a75f1e3126..2db4fae1c36 100644 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ b/extensions/markdown-language-features/src/languageFeatures/references.ts @@ -21,6 +21,7 @@ function isLinkToHeader(target: LinkTarget, header: TocEntry, headerDocument: vs export interface MdReference { + readonly isTriggerLocation: boolean; readonly isDefinition: boolean; readonly location: vscode.Location; } @@ -41,25 +42,27 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference } async provideReferences(document: SkinnyTextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise { - const toc = await TableOfContents.create(this.engine, document); - if (token.isCancellationRequested) { - return undefined; - } - - const header = toc.entries.find(entry => entry.line === position.line); - - let allRefs: MdReference[]; - if (header) { - allRefs = await this.getReferencesToHeader(document, header); - } else { - allRefs = await this.getReferencesToLinkAtPosition(document, position); - } + const allRefs = await this.getAllReferences(document, position, token); return allRefs .filter(ref => context.includeDeclaration || !ref.isDefinition) .map(ref => ref.location); } + public async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + const toc = await TableOfContents.create(this.engine, document); + if (token.isCancellationRequested) { + return []; + } + + const header = toc.entries.find(entry => entry.line === position.line); + if (header) { + return this.getReferencesToHeader(document, header); + } else { + return this.getReferencesToLinkAtPosition(document, position); + } + } + private async getReferencesToHeader(document: SkinnyTextDocument, header: TocEntry): Promise { const links = (await this._linkCache.getAll()).flat(); @@ -67,6 +70,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference const line = document.lineAt(header.line); references.push({ + isTriggerLocation: true, isDefinition: true, location: new vscode.Location(document.uri, new vscode.Range(header.line, 0, header.line, line.text.length)), }); @@ -74,11 +78,13 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference for (const link of links) { if (isLinkToHeader(link.target, header, document.uri, this.slugifier)) { references.push({ + isTriggerLocation: false, isDefinition: false, location: new vscode.Location(link.sourceResource, link.sourceRange) }); } else if (link.target.kind === 'definition' && isLinkToHeader(link.target.target, header, document.uri, this.slugifier)) { references.push({ + isTriggerLocation: false, isDefinition: false, location: new vscode.Location(link.sourceResource, link.sourceRange) }); @@ -124,7 +130,11 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference const toc = await TableOfContents.create(this.engine, targetDoc); const entry = toc.lookup(sourceLink.target.fragment); if (entry) { - references.push({ isDefinition: true, location: entry.location }); + references.push({ + isTriggerLocation: false, + isDefinition: true, + location: entry.headerLocation, + }); } } @@ -140,15 +150,25 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference continue; } + const isTriggerLocation = sourceLink.sourceResource.fsPath === link.sourceResource.fsPath && sourceLink.sourceRange.isEqual(link.sourceRange); + if (sourceLink.target.fragment) { if (this.slugifier.fromHeading(link.target.fragment).equals(this.slugifier.fromHeading(sourceLink.target.fragment))) { - references.push({ isDefinition: false, location: new vscode.Location(link.sourceResource, link.sourceRange) }); + references.push({ + isTriggerLocation, + isDefinition: false, + location: new vscode.Location(link.sourceResource, link.sourceRange), + }); } } else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments // But exclude cases where the file is referencing itself if (link.sourceResource.fsPath !== targetDoc.uri.fsPath) { - references.push({ isDefinition: false, location: new vscode.Location(link.sourceResource, link.sourceRange) }); + references.push({ + isTriggerLocation, + isDefinition: false, + location: new vscode.Location(link.sourceResource, link.sourceRange), + }); } } } @@ -156,14 +176,17 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference return references; } - private *getReferencesToReferenceLink(allLinks: Iterable, sourceLink: LinkData): Iterable { + private * getReferencesToReferenceLink(allLinks: Iterable, sourceLink: LinkData): Iterable { if (sourceLink.target.kind !== 'reference') { return; } + for (const link of allLinks) { if (link.target.kind === 'reference' || link.target.kind === 'definition') { 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 { + isTriggerLocation, isDefinition: false, location: new vscode.Location(sourceLink.sourceResource, link.sourceRange) }; diff --git a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts b/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts index 235347cbf3f..74b1d8e1b98 100644 --- a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts +++ b/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts @@ -70,7 +70,7 @@ export class MdSmartSelect implements vscode.SelectionRangeProvider { } function getHeadersForPosition(toc: readonly TocEntry[], position: vscode.Position): { headers: TocEntry[]; headerOnThisLine: boolean } { - const enclosingHeaders = toc.filter(header => header.location.range.start.line <= position.line && header.location.range.end.line >= position.line); + const enclosingHeaders = toc.filter(header => header.sectionLocation.range.start.line <= position.line && header.sectionLocation.range.end.line >= position.line); const sortedHeaders = enclosingHeaders.sort((header1, header2) => (header1.line - position.line) - (header2.line - position.line)); const onThisLine = toc.find(header => header.line === position.line) !== undefined; return { @@ -80,7 +80,7 @@ function getHeadersForPosition(toc: readonly TocEntry[], position: vscode.Positi } function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean, onHeaderLine: boolean, parent?: vscode.SelectionRange, startOfChildRange?: vscode.Position): vscode.SelectionRange | undefined { - const range = header.location.range; + const range = header.sectionLocation.range; const contentRange = new vscode.Range(range.start.translate(1), range.end); if (onHeaderLine && isClosestHeaderToPosition && startOfChildRange) { // selection was made on this header line, so select header and its content until the start of its first child @@ -240,9 +240,9 @@ function isBlockElement(token: Token): boolean { function getFirstChildHeader(document: SkinnyTextDocument, header?: TocEntry, toc?: readonly TocEntry[]): vscode.Position | undefined { let childRange: vscode.Position | undefined; if (header && toc) { - let children = toc.filter(t => header.location.range.contains(t.location.range) && t.location.range.start.line > header.location.range.start.line).sort((t1, t2) => t1.line - t2.line); + let children = toc.filter(t => header.sectionLocation.range.contains(t.sectionLocation.range) && t.sectionLocation.range.start.line > header.sectionLocation.range.start.line).sort((t1, t2) => t1.line - t2.line); if (children.length > 0) { - childRange = children[0].location.range.start; + childRange = children[0].sectionLocation.range.start; const lineText = document.lineAt(childRange.line - 1).text; return childRange ? childRange.translate(-1, lineText.length) : undefined; } diff --git a/extensions/markdown-language-features/src/tableOfContents.ts b/extensions/markdown-language-features/src/tableOfContents.ts index 7dbdb830b5e..318641315b6 100644 --- a/extensions/markdown-language-features/src/tableOfContents.ts +++ b/extensions/markdown-language-features/src/tableOfContents.ts @@ -14,7 +14,16 @@ export interface TocEntry { readonly text: string; readonly level: number; readonly line: number; - readonly location: vscode.Location; + + /** + * The entire range of the header section + */ + readonly sectionLocation: vscode.Location; + + /** + * The range of the header itself + */ + readonly headerLocation: vscode.Location; } export class TableOfContents { @@ -68,13 +77,16 @@ export class TableOfContents { existingSlugEntries.set(slug.value, { count: 0 }); } + const headerLocation = new vscode.Location(document.uri, + new vscode.Range(lineNumber, 0, lineNumber, line.text.length)); + toc.push({ slug, text: TableOfContents.getHeaderText(line.text), level: TableOfContents.getHeaderLevel(heading.markup), line: lineNumber, - location: new vscode.Location(document.uri, - new vscode.Range(lineNumber, 0, lineNumber, line.text.length)) + sectionLocation: headerLocation, // Populated in next steps + headerLocation, }); } @@ -90,9 +102,9 @@ export class TableOfContents { const endLine = end ?? document.lineCount - 1; return { ...entry, - location: new vscode.Location(document.uri, + sectionLocation: new vscode.Location(document.uri, new vscode.Range( - entry.location.range.start, + entry.sectionLocation.range.start, new vscode.Position(endLine, document.lineAt(endLine).text.length))) }; }); diff --git a/extensions/markdown-language-features/src/test/references.test.ts b/extensions/markdown-language-features/src/test/references.test.ts index c8d04c2c29a..b05ae175791 100644 --- a/extensions/markdown-language-features/src/test/references.test.ts +++ b/extensions/markdown-language-features/src/test/references.test.ts @@ -30,7 +30,8 @@ function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expect const actual = actualRefs[i]; const expected = expectedRefs[i]; assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`); - assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected line`); + assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`); + assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`); } }