added support for finding folders as well

This commit is contained in:
Tino Ehrich 2025-11-26 12:16:38 +01:00 committed by Mario Zechner
parent b8e5f8db6d
commit 15d5120b6a
2 changed files with 45 additions and 32 deletions

View file

@ -462,12 +462,14 @@ The interactive input editor includes several productivity features:
### File Reference (`@`)
Type **`@`** to fuzzy-search for files in your project:
- `@editor` → finds files with "editor" in the name
Type **`@`** to fuzzy-search for files and folders in your project:
- `@editor` → finds files/folders with "editor" in the name
- `@readme` → finds README files anywhere in the project
- `@src` → finds folders like `src/`, `resources/`, etc.
- Directories are prioritized and shown with trailing `/`
- Autocomplete triggers immediately when you type `@`
- Use **Up/Down arrows** to navigate, **Tab**/**Enter** to select
- Only shows attachable files (text, code, images)
- Only shows attachable files (text, code, images) and directories
Uses `fdfind`/`fd` for fast searching if available, falls back to `find` on all Unix systems.

View file

@ -494,25 +494,28 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
}
}
// Score a file against the query (higher = better match)
private scoreFile(filePath: string, query: string): number {
// Score an entry against the query (higher = better match)
// isDirectory adds bonus to prioritize folders
private scoreEntry(filePath: string, query: string, isDirectory: boolean): number {
const fileName = basename(filePath);
const lowerFileName = fileName.toLowerCase();
const lowerQuery = query.toLowerCase();
let score = 0;
// Exact filename match (highest)
if (lowerFileName === lowerQuery) return 100;
if (lowerFileName === lowerQuery) score = 100;
// Filename starts with query
if (lowerFileName.startsWith(lowerQuery)) return 80;
else if (lowerFileName.startsWith(lowerQuery)) score = 80;
// Substring match in filename
if (lowerFileName.includes(lowerQuery)) return 50;
else if (lowerFileName.includes(lowerQuery)) score = 50;
// Substring match in full path
if (filePath.toLowerCase().includes(lowerQuery)) return 30;
else if (filePath.toLowerCase().includes(lowerQuery)) score = 30;
return 0;
// Directories get a bonus to appear first
if (isDirectory && score > 0) score += 10;
return score;
}
// Fuzzy file search using fdfind, fd, or find (fallback)
@ -522,7 +525,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
const fdCommand = this.getFdCommand();
if (fdCommand) {
const args = ["-t", "f", "--max-results", "100"];
const args = ["--max-results", "100"];
if (query) {
args.push(query);
@ -541,8 +544,6 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
const cmd = [
"find",
".",
"-type",
"f",
"-iname",
`'${pattern}'`,
"!",
@ -578,42 +579,52 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
});
}
const files = result
const entries = result
.trim()
.split("\n")
.filter((f) => f.length > 0)
.map((f) => (f.startsWith("./") ? f.slice(2) : f));
// Score and filter files
const scoredFiles: { path: string; score: number }[] = [];
// Score and filter entries (files and directories)
const scoredEntries: { path: string; score: number; isDirectory: boolean }[] = [];
for (const filePath of files) {
const fullPath = join(this.basePath, filePath);
for (const entryPath of entries) {
const fullPath = join(this.basePath, entryPath);
if (!isAttachableFile(fullPath)) {
let isDirectory: boolean;
try {
isDirectory = statSync(fullPath).isDirectory();
} catch {
continue; // Skip if we can't stat
}
// For files, check if attachable
if (!isDirectory && !isAttachableFile(fullPath)) {
continue;
}
const score = query ? this.scoreFile(filePath, query) : 1;
const score = query ? this.scoreEntry(entryPath, query, isDirectory) : 1;
if (score > 0) {
scoredFiles.push({ path: filePath, score });
scoredEntries.push({ path: entryPath, score, isDirectory });
}
}
// Sort by score (descending) and take top 20
scoredFiles.sort((a, b) => b.score - a.score);
const topFiles = scoredFiles.slice(0, 20);
scoredEntries.sort((a, b) => b.score - a.score);
const topEntries = scoredEntries.slice(0, 20);
// Build suggestions
const suggestions: AutocompleteItem[] = [];
for (const { path: filePath } of topFiles) {
const fileName = basename(filePath);
const dirPath = dirname(filePath);
for (const { path: entryPath, isDirectory } of topEntries) {
const entryName = basename(entryPath);
// Normalize path - remove trailing slash if present, we'll add it back for dirs
const normalizedPath = entryPath.endsWith("/") ? entryPath.slice(0, -1) : entryPath;
const valuePath = isDirectory ? normalizedPath + "/" : normalizedPath;
suggestions.push({
value: "@" + filePath,
label: fileName,
description: dirPath === "." ? "" : dirPath,
value: "@" + valuePath,
label: entryName + (isDirectory ? "/" : ""),
description: normalizedPath,
});
}