mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 08: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;
|
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
|
// Get file/directory suggestions for a given path prefix
|
||||||
private getFileSuggestions(prefix: string): AutocompleteItem[] {
|
private getFileSuggestions(prefix: string): AutocompleteItem[] {
|
||||||
try {
|
try {
|
||||||
|
|
@ -610,13 +646,16 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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
|
// Score entries
|
||||||
const scoredEntries = entries
|
const scoredEntries = entries
|
||||||
.map((entry) => ({
|
.map((entry) => ({
|
||||||
...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);
|
.filter((entry) => entry.score > 0);
|
||||||
|
|
||||||
|
|
@ -629,8 +668,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
for (const { path: entryPath, isDirectory } of topEntries) {
|
for (const { path: entryPath, isDirectory } of topEntries) {
|
||||||
// fd already includes trailing / for directories
|
// fd already includes trailing / for directories
|
||||||
const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;
|
const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;
|
||||||
|
const displayPath = scopedQuery
|
||||||
|
? this.scopedPathForDisplay(scopedQuery.displayBase, pathWithoutSlash)
|
||||||
|
: pathWithoutSlash;
|
||||||
const entryName = basename(pathWithoutSlash);
|
const entryName = basename(pathWithoutSlash);
|
||||||
const value = buildCompletionValue(entryPath, {
|
const completionPath = isDirectory ? `${displayPath}/` : displayPath;
|
||||||
|
const value = buildCompletionValue(completionPath, {
|
||||||
isDirectory,
|
isDirectory,
|
||||||
isAtPrefix: true,
|
isAtPrefix: true,
|
||||||
isQuotedPrefix: options.isQuotedPrefix,
|
isQuotedPrefix: options.isQuotedPrefix,
|
||||||
|
|
@ -639,7 +682,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
suggestions.push({
|
suggestions.push({
|
||||||
value,
|
value,
|
||||||
label: entryName + (isDirectory ? "/" : ""),
|
label: entryName + (isDirectory ? "/" : ""),
|
||||||
description: pathWithoutSlash,
|
description: displayPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,14 +107,20 @@ describe("CombinedAutocompleteProvider", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fd @ file suggestions", { skip: !isFdInstalled }, () => {
|
describe("fd @ file suggestions", { skip: !isFdInstalled }, () => {
|
||||||
|
let rootDir = "";
|
||||||
let baseDir = "";
|
let baseDir = "";
|
||||||
|
let outsideDir = "";
|
||||||
|
|
||||||
beforeEach(() => {
|
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(() => {
|
afterEach(() => {
|
||||||
rmSync(baseDir, { recursive: true, force: true });
|
rmSync(rootDir, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("returns all files and folders for empty @ query", () => {
|
test("returns all files and folders for empty @ query", () => {
|
||||||
|
|
@ -231,6 +237,25 @@ describe("CombinedAutocompleteProvider", () => {
|
||||||
assert.ok(!values?.includes("@src/utils/helpers.ts"));
|
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", () => {
|
test("quotes paths with spaces for @ suggestions", () => {
|
||||||
setupFolder(baseDir, {
|
setupFolder(baseDir, {
|
||||||
dirs: ["my folder"],
|
dirs: ["my folder"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue