返回 DeepSeek-Reasonix
atMatches.ts
根目录 / desktop / frontend / src / lib / atMatches.ts
1 // atMatches filters the @-menu candidates shown in the Composer. It is
2 // extracted from the Composer component so it can be unit-tested without
3 // mounting the full React tree.
4 //
5 // The match logic mirrors the v2 fuzzy @-search behavior: the user's
6 // fragment is matched against each entry's full relative path (which the
7 // backend already returns as a slash-normalized string for search
8 // results, and as a single-segment name for ListDir results). This
9 // allows entries like "src/planind/index.tsx" to surface when the user
10 // types a directory segment such as "planind", not just the basename
11 // "index.tsx".
12 //
13 // Entries from the local ListDir (`entries`) and the fuzzy Search
14 // (`searchEntries`) are merged with stable de-duplication keyed on the
15 // submitted path (`entry.path` when present, otherwise `entry.name`) so a result
16 // that appears in both lists is shown only once. The local list takes
17 // precedence (it represents the user's current directory and is more immediate).
18
19 import type { DirEntry } from "./types";
20
21 function entryKey(entry: DirEntry): string {
22 return entry.path || entry.name;
23 }
24
25 function searchableText(entry: DirEntry): string {
26 return [entry.name, entry.path, entry.displayName, entry.displayPath].filter(Boolean).join(" ").toLowerCase();
27 }
28
29 export function filterAtMatches(
30 entries: readonly DirEntry[],
31 searchEntries: readonly DirEntry[],
32 atFrag: string,
33 ): DirEntry[] {
34 const frag = atFrag.toLowerCase();
35 const local = entries.filter((e) => searchableText(e).includes(frag));
36 const seen = new Set(local.map(entryKey));
37 const searched = searchEntries.filter((e) => {
38 if (seen.has(entryKey(e))) return false;
39 return searchableText(e).includes(frag);
40 });
41 return [...local, ...searched];
42 }
43
43 lines TYPESCRIPT