@: directories complete, not just files
walk({includeDirs}) yields src/ and src/ui/ with trailing slash, only
on the @ path. matchPaths ranks dirs before files for the same prefix
so @src/ surfaces the directory itself. Insertion keeps the slash.
This commit is contained in:
+1
-1
@@ -403,7 +403,7 @@ const hooks: AppHooks = {
|
||||
instructionFiles: () => instructions.map((i) => i.path),
|
||||
listPaths: async () => {
|
||||
const found: string[] = [];
|
||||
for await (const rel of walk({ limit: 5000 })) found.push(rel);
|
||||
for await (const rel of walk({ limit: 5000, includeDirs: true })) found.push(rel);
|
||||
return found;
|
||||
},
|
||||
fileChangeSeq: () => session.fileChangeSeq,
|
||||
|
||||
+25
-7
@@ -43,25 +43,43 @@ const MAX_MATCHES = 8;
|
||||
* Ties break on path length: the shallower file is more often the one meant.
|
||||
*/
|
||||
export function matchPaths(paths: readonly string[], query: string, limit = MAX_MATCHES): string[] {
|
||||
if (!query) return [...paths].sort((a, b) => a.length - b.length || a.localeCompare(b)).slice(0, limit);
|
||||
if (!query) {
|
||||
const dirs = paths.filter((p) => p.endsWith('/'));
|
||||
const files = paths.filter((p) => !p.endsWith('/'));
|
||||
const byLength = (a: string, b: string) => a.length - b.length || a.localeCompare(b);
|
||||
// Empty query: prefer directories (the user just typed `@src/` and wants to go deeper)
|
||||
// when available, otherwise fall back to the shallowest files.
|
||||
if (dirs.length > 0) return [...dirs.sort(byLength), ...files.sort(byLength)].slice(0, limit);
|
||||
return [...files.sort(byLength)].slice(0, limit);
|
||||
}
|
||||
|
||||
const needle = query.toLowerCase();
|
||||
const prefix: string[] = [];
|
||||
const substring: string[] = [];
|
||||
const prefixDirs: string[] = [];
|
||||
const prefixFiles: string[] = [];
|
||||
const substringDirs: string[] = [];
|
||||
const substringFiles: string[] = [];
|
||||
|
||||
for (const path of paths) {
|
||||
const lower = path.toLowerCase();
|
||||
if (lower.startsWith(needle)) prefix.push(path);
|
||||
else if (lower.includes(needle)) substring.push(path);
|
||||
const isDir = path.endsWith('/');
|
||||
const hit = lower.startsWith(needle) ? 'prefix' : lower.includes(needle) ? 'substring' : undefined;
|
||||
if (!hit) continue;
|
||||
if (hit === 'prefix') (isDir ? prefixDirs : prefixFiles).push(path);
|
||||
else (isDir ? substringDirs : substringFiles).push(path);
|
||||
}
|
||||
|
||||
const byLength = (a: string, b: string) => a.length - b.length || a.localeCompare(b);
|
||||
return [...prefix.sort(byLength), ...substring.sort(byLength)].slice(0, limit);
|
||||
return [
|
||||
...prefixDirs.sort(byLength),
|
||||
...prefixFiles.sort(byLength),
|
||||
...substringDirs.sort(byLength),
|
||||
...substringFiles.sort(byLength),
|
||||
].slice(0, limit);
|
||||
}
|
||||
|
||||
export type Completion = { value: string; cursor: number };
|
||||
|
||||
/** Replaces the token with a plain relative path and a trailing space. */
|
||||
/** Replaces the token with a plain relative path and a trailing space (dirs keep their trailing `/`). */
|
||||
export function completePath(value: string, token: PathToken, path: string): Completion {
|
||||
const next = `${value.slice(0, token.start)}${path} ${value.slice(token.end)}`;
|
||||
return { value: next, cursor: token.start + path.length + 1 };
|
||||
|
||||
@@ -88,6 +88,8 @@ export type WalkOptions = {
|
||||
root?: string;
|
||||
/** Include files git would ignore. */
|
||||
noIgnore?: boolean;
|
||||
/** Also yield directories (with trailing `/`), so `@` can complete to them. */
|
||||
includeDirs?: boolean;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
@@ -130,6 +132,10 @@ export async function* walk(options: WalkOptions = {}): AsyncGenerator<string> {
|
||||
if (!options.noIgnore && ignored(rel, entry.isDirectory, rules)) continue;
|
||||
|
||||
if (entry.isDirectory) {
|
||||
if (options.includeDirs) {
|
||||
yield `${rel}/`;
|
||||
if (++yielded >= limit) return;
|
||||
}
|
||||
// A symlinked directory is not descended into: it can point anywhere,
|
||||
// including back into the tree.
|
||||
if (entry.isLink) continue;
|
||||
|
||||
Reference in New Issue
Block a user