mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 17:00:59 +00:00
fix(tui): scope @ fuzzy search to path prefixes\n\ncloses #1423
This commit is contained in:
parent
ed0cfcbda2
commit
31f765ff1b
2 changed files with 74 additions and 6 deletions
|
|
@ -447,6 +447,42 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|||
return path;
|
||||
}
|
||||
|
||||
private resolveScopedFuzzyQuery(rawQuery: string): { baseDir: string; query: string; displayBase: string } | null {
|
||||
const slashIndex = rawQuery.lastIndexOf("/");
|
||||
if (slashIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayBase = rawQuery.slice(0, slashIndex + 1);
|
||||
const query = rawQuery.slice(slashIndex + 1);
|
||||
|
||||
let baseDir: string;
|
||||
if (displayBase.startsWith("~/")) {
|
||||
baseDir = this.expandHomePath(displayBase);
|
||||
} else if (displayBase.startsWith("/")) {
|
||||
baseDir = displayBase;
|
||||
} else {
|
||||
baseDir = join(this.basePath, displayBase);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!statSync(baseDir).isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { baseDir, query, displayBase };
|
||||
}
|
||||
|
||||
private scopedPathForDisplay(displayBase: string, relativePath: string): string {
|
||||
if (displayBase === "/") {
|
||||
return `/${relativePath}`;
|
||||
}
|
||||
return `${displayBase}${relativePath}`;
|
||||
}
|
||||
|
||||
// Get file/directory suggestions for a given path prefix
|
||||
private getFileSuggestions(prefix: string): AutocompleteItem[] {
|
||||
try {
|
||||
|
|
@ -610,13 +646,16 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|||
}
|
||||
|
||||
try {
|
||||
const entries = walkDirectoryWithFd(this.basePath, this.fdPath, query, 100);
|
||||
const scopedQuery = this.resolveScopedFuzzyQuery(query);
|
||||
const fdBaseDir = scopedQuery?.baseDir ?? this.basePath;
|
||||
const fdQuery = scopedQuery?.query ?? query;
|
||||
const entries = walkDirectoryWithFd(fdBaseDir, this.fdPath, fdQuery, 100);
|
||||
|
||||
// Score entries
|
||||
const scoredEntries = entries
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
score: query ? this.scoreEntry(entry.path, query, entry.isDirectory) : 1,
|
||||
score: fdQuery ? this.scoreEntry(entry.path, fdQuery, entry.isDirectory) : 1,
|
||||
}))
|
||||
.filter((entry) => entry.score > 0);
|
||||
|
||||
|
|
@ -629,8 +668,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|||
for (const { path: entryPath, isDirectory } of topEntries) {
|
||||
// fd already includes trailing / for directories
|
||||
const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;
|
||||
const displayPath = scopedQuery
|
||||
? this.scopedPathForDisplay(scopedQuery.displayBase, pathWithoutSlash)
|
||||
: pathWithoutSlash;
|
||||
const entryName = basename(pathWithoutSlash);
|
||||
const value = buildCompletionValue(entryPath, {
|
||||
const completionPath = isDirectory ? `${displayPath}/` : displayPath;
|
||||
const value = buildCompletionValue(completionPath, {
|
||||
isDirectory,
|
||||
isAtPrefix: true,
|
||||
isQuotedPrefix: options.isQuotedPrefix,
|
||||
|
|
@ -639,7 +682,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|||
suggestions.push({
|
||||
value,
|
||||
label: entryName + (isDirectory ? "/" : ""),
|
||||
description: pathWithoutSlash,
|
||||
description: displayPath,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,14 +107,20 @@ describe("CombinedAutocompleteProvider", () => {
|
|||
});
|
||||
|
||||
describe("fd @ file suggestions", { skip: !isFdInstalled }, () => {
|
||||
let rootDir = "";
|
||||
let baseDir = "";
|
||||
let outsideDir = "";
|
||||
|
||||
beforeEach(() => {
|
||||
baseDir = mkdtempSync(join(tmpdir(), "pi-autocomplete-"));
|
||||
rootDir = mkdtempSync(join(tmpdir(), "pi-autocomplete-root-"));
|
||||
baseDir = join(rootDir, "cwd");
|
||||
outsideDir = join(rootDir, "outside");
|
||||
mkdirSync(baseDir, { recursive: true });
|
||||
mkdirSync(outsideDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(baseDir, { recursive: true, force: true });
|
||||
rmSync(rootDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("returns all files and folders for empty @ query", () => {
|
||||
|
|
@ -231,6 +237,25 @@ describe("CombinedAutocompleteProvider", () => {
|
|||
assert.ok(!values?.includes("@src/utils/helpers.ts"));
|
||||
});
|
||||
|
||||
test("scopes fuzzy search to relative directories and searches recursively", () => {
|
||||
setupFolder(outsideDir, {
|
||||
files: {
|
||||
"nested/alpha.ts": "export {};",
|
||||
"nested/deeper/also-alpha.ts": "export {};",
|
||||
"nested/deeper/zzz.ts": "export {};",
|
||||
},
|
||||
});
|
||||
|
||||
const provider = new CombinedAutocompleteProvider([], baseDir, requireFdPath());
|
||||
const line = "@../outside/a";
|
||||
const result = provider.getSuggestions([line], 0, line.length);
|
||||
|
||||
const values = result?.items.map((item) => item.value);
|
||||
assert.ok(values?.includes("@../outside/nested/alpha.ts"));
|
||||
assert.ok(values?.includes("@../outside/nested/deeper/also-alpha.ts"));
|
||||
assert.ok(!values?.includes("@../outside/nested/deeper/zzz.ts"));
|
||||
});
|
||||
|
||||
test("quotes paths with spaces for @ suggestions", () => {
|
||||
setupFolder(baseDir, {
|
||||
dirs: ["my folder"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue