Fix references to header to return just the span of the header itself and not its body

This commit is contained in:
Matt Bierner 2022-03-31 11:44:05 -07:00
parent 464e50f207
commit e32a13be77
No known key found for this signature in database
GPG key ID: 099C331567E11888
6 changed files with 67 additions and 31 deletions

View file

@ -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 {

View file

@ -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;
}

View file

@ -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<vscode.Location[] | undefined> {
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<MdReference[]> {
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<MdReference[]> {
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<LinkData>, sourceLink: LinkData): Iterable<MdReference> {
private * getReferencesToReferenceLink(allLinks: Iterable<LinkData>, sourceLink: LinkData): Iterable<MdReference> {
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)
};

View file

@ -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;
}

View file

@ -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)))
};
});

View file

@ -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`);
}
}