feat(core): improve @file autocomplete to prioritize filenames (#21064)

This commit is contained in:
Sehoon Shon
2026-03-04 12:24:34 -05:00
committed by GitHub
parent 66721379f8
commit bc89b05f01
3 changed files with 82 additions and 1 deletions
@@ -13,6 +13,44 @@ import { AsyncFzf, type FzfResultItem } from 'fzf';
import { unescapePath } from '../paths.js';
import type { FileDiscoveryService } from '../../services/fileDiscoveryService.js';
// Tiebreaker: Prefers shorter paths.
const byLengthAsc = (a: { item: string }, b: { item: string }) =>
a.item.length - b.item.length;
// Tiebreaker: Prefers matches at the start of the filename (basename prefix).
const byBasenamePrefix = (
a: { item: string; positions: Set<number> },
b: { item: string; positions: Set<number> },
) => {
const getBasenameStart = (p: string) => {
const trimmed = p.endsWith('/') ? p.slice(0, -1) : p;
return Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\')) + 1;
};
const aDiff = Math.min(...a.positions) - getBasenameStart(a.item);
const bDiff = Math.min(...b.positions) - getBasenameStart(b.item);
const aIsFilenameMatch = aDiff >= 0;
const bIsFilenameMatch = bDiff >= 0;
if (aIsFilenameMatch && !bIsFilenameMatch) return -1;
if (!aIsFilenameMatch && bIsFilenameMatch) return 1;
if (aIsFilenameMatch && bIsFilenameMatch) return aDiff - bDiff;
return 0; // Both are directory matches, let subsequent tiebreakers decide.
};
// Tiebreaker: Prefers matches closer to the end of the path.
const byMatchPosFromEnd = (
a: { item: string; positions: Set<number> },
b: { item: string; positions: Set<number> },
) => {
const maxPosA = Math.max(-1, ...a.positions);
const maxPosB = Math.max(-1, ...b.positions);
const distA = a.item.length - maxPosA;
const distB = b.item.length - maxPosB;
return distA - distB;
};
export interface FileSearchOptions {
projectRoot: string;
ignoreDirs: string[];
@@ -192,6 +230,8 @@ class RecursiveFileSearch implements FileSearch {
// files, because the v2 algorithm is just too slow in those cases.
this.fzf = new AsyncFzf(this.allFiles, {
fuzzy: this.allFiles.length > 20000 ? 'v1' : 'v2',
forward: false,
tiebreakers: [byBasenamePrefix, byMatchPosFromEnd, byLengthAsc],
});
}
}