Skip to content

Commit 489b41f

Browse files
authored
feat: use fuze for file grep when using @ (#507)
### TL;DR Improved file suggestion search with better matching and increased result limits. closes #505 ### What changed? - Increased file display limit from 5 to 25 files - Added a separate file fetch limit of 100 to retrieve more potential matches - Implemented a dedicated fuzzy search for files using Fuse.js with appropriate weighting - Added sorting logic to prioritize files that start with the query string - Renamed `FUSE_OPTIONS` to `COMMAND_FUSE_OPTIONS` for clarity - Created a new `FileItem` interface to better structure file data ### How to test? 1. Open the message editor 2. Type a file reference (e.g., `@file:`) 3. Enter search terms and verify: - Up to 25 results are displayed (instead of 5) - Results are properly sorted with exact matches first - Fuzzy matching works correctly for partial file names ### Why make this change? The previous implementation limited file suggestions to only 5 results and lacked sophisticated search capabilities. This change enhances the user experience by providing more relevant file suggestions through improved search algorithms and displaying a more useful number of results.
1 parent f38fcf3 commit 489b41f

File tree

1 file changed

+50
-7
lines changed

1 file changed

+50
-7
lines changed

apps/array/src/renderer/features/message-editor/suggestions/getSuggestions.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import Fuse, { type IFuseOptions } from "fuse.js";
66
import { useDraftStore } from "../stores/draftStore";
77
import type { CommandSuggestionItem, FileSuggestionItem } from "../types";
88

9-
const FILE_LIMIT = 5;
9+
const FILE_DISPLAY_LIMIT = 25;
10+
const FILE_FETCH_LIMIT = 100;
1011
const COMMAND_LIMIT = 5;
1112

12-
const FUSE_OPTIONS: IFuseOptions<AvailableCommand> = {
13+
const COMMAND_FUSE_OPTIONS: IFuseOptions<AvailableCommand> = {
1314
keys: [
1415
{ name: "name", weight: 0.7 },
1516
{ name: "description", weight: 0.3 },
@@ -18,6 +19,20 @@ const FUSE_OPTIONS: IFuseOptions<AvailableCommand> = {
1819
includeScore: true,
1920
};
2021

22+
interface FileItem {
23+
path: string;
24+
name: string;
25+
}
26+
27+
const FILE_FUSE_OPTIONS: IFuseOptions<FileItem> = {
28+
keys: [
29+
{ name: "name", weight: 0.7 },
30+
{ name: "path", weight: 0.3 },
31+
],
32+
threshold: 0.4,
33+
includeScore: true,
34+
};
35+
2136
function searchCommands(
2237
commands: AvailableCommand[],
2338
query: string,
@@ -26,7 +41,7 @@ function searchCommands(
2641
return commands.slice(0, COMMAND_LIMIT);
2742
}
2843

29-
const fuse = new Fuse(commands, FUSE_OPTIONS);
44+
const fuse = new Fuse(commands, COMMAND_FUSE_OPTIONS);
3045
const results = fuse.search(query, { limit: COMMAND_LIMIT * 2 });
3146

3247
const lowerQuery = query.toLowerCase();
@@ -42,6 +57,27 @@ function searchCommands(
4257
return results.slice(0, COMMAND_LIMIT).map((result) => result.item);
4358
}
4459

60+
function searchFiles(files: FileItem[], query: string): FileItem[] {
61+
if (!query.trim()) {
62+
return files.slice(0, FILE_DISPLAY_LIMIT);
63+
}
64+
65+
const fuse = new Fuse(files, FILE_FUSE_OPTIONS);
66+
const results = fuse.search(query, { limit: FILE_DISPLAY_LIMIT * 2 });
67+
68+
const lowerQuery = query.toLowerCase();
69+
results.sort((a, b) => {
70+
const aStartsWithQuery = a.item.name.toLowerCase().startsWith(lowerQuery);
71+
const bStartsWithQuery = b.item.name.toLowerCase().startsWith(lowerQuery);
72+
73+
if (aStartsWithQuery && !bStartsWithQuery) return -1;
74+
if (!aStartsWithQuery && bStartsWithQuery) return 1;
75+
return (a.score ?? 0) - (b.score ?? 0);
76+
});
77+
78+
return results.slice(0, FILE_DISPLAY_LIMIT).map((result) => result.item);
79+
}
80+
4581
export async function getFileSuggestions(
4682
sessionId: string,
4783
query: string,
@@ -55,19 +91,26 @@ export async function getFileSuggestions(
5591
const results = await trpcVanilla.fs.listRepoFiles.query({
5692
repoPath,
5793
query,
58-
limit: FILE_LIMIT,
94+
limit: FILE_FETCH_LIMIT,
5995
});
6096

61-
return results
97+
const files: FileItem[] = results
6298
.filter(
6399
(file: MentionItem): file is MentionItem & { path: string } =>
64100
!!file.path,
65101
)
66102
.map((file) => ({
67-
id: file.path,
68-
label: file.path,
69103
path: file.path,
104+
name: file.path.split("/").pop() ?? file.path,
70105
}));
106+
107+
const matched = searchFiles(files, query);
108+
109+
return matched.map((file) => ({
110+
id: file.path,
111+
label: file.path,
112+
path: file.path,
113+
}));
71114
}
72115

73116
export function getCommandSuggestions(

0 commit comments

Comments
 (0)