feat(palette): unify quick open into command palette with multi-source search
Merge file picker into command palette. Single Cmd+P entry searches files, sessions, settings pages and commands; groups re-order by best fuzzy score per source. Sessions show branch labels; git status is lazily fetched for all session directories on open. Drop QuickOpenDialog and Cmd+K shortcut.
This commit is contained in:
@@ -107,6 +107,67 @@ export function filterByFuzzyQuery<T>(
|
||||
return matching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Score-sorted fuzzy ranking. Strict (low threshold), prioritizes substring
|
||||
* matches (especially prefix matches), and returns the top N.
|
||||
*
|
||||
* Use this for command-palette-style result ranking where order matters more
|
||||
* than recall.
|
||||
*/
|
||||
export function scoreByFuzzyQuery<T>(
|
||||
items: T[],
|
||||
query: string,
|
||||
getText: (item: T) => string,
|
||||
options?: { limit?: number; threshold?: number; noFuzzy?: boolean },
|
||||
): { item: T; score: number }[] {
|
||||
if (!query) return items.map((item) => ({ item, score: 0 }));
|
||||
const limit = options?.limit ?? items.length;
|
||||
const threshold = options?.threshold ?? 0.3;
|
||||
const noFuzzy = options?.noFuzzy ?? false;
|
||||
const queryLower = query.toLowerCase();
|
||||
|
||||
const scored: { item: T; score: number }[] = [];
|
||||
const fuzzyCandidates: { item: T; text: string }[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
const text = getText(item);
|
||||
if (!text) continue;
|
||||
const lower = text.toLowerCase();
|
||||
const idx = lower.indexOf(queryLower);
|
||||
if (idx === 0) {
|
||||
scored.push({ item, score: -1 });
|
||||
continue;
|
||||
}
|
||||
if (idx > 0) {
|
||||
scored.push({ item, score: idx / 1000 });
|
||||
continue;
|
||||
}
|
||||
if (!noFuzzy) fuzzyCandidates.push({ item, text });
|
||||
}
|
||||
|
||||
if (fuzzyCandidates.length > 0) {
|
||||
const fuse = new Fuse(
|
||||
fuzzyCandidates.map((c) => c.text),
|
||||
{ threshold, ignoreLocation: true, distance: 100, includeScore: true, minMatchCharLength: 2 },
|
||||
);
|
||||
for (const result of fuse.search(query)) {
|
||||
scored.push({ item: fuzzyCandidates[result.refIndex].item, score: result.score ?? 1 });
|
||||
}
|
||||
}
|
||||
|
||||
scored.sort((a, b) => a.score - b.score);
|
||||
return scored.slice(0, limit);
|
||||
}
|
||||
|
||||
export function rankByFuzzyQuery<T>(
|
||||
items: T[],
|
||||
query: string,
|
||||
getText: (item: T) => string,
|
||||
options?: { limit?: number; threshold?: number; noFuzzy?: boolean },
|
||||
): T[] {
|
||||
return scoreByFuzzyQuery(items, query, getText, options).map((x) => x.item);
|
||||
}
|
||||
|
||||
export function partitionByFuzzyQuery<T>(
|
||||
items: T[],
|
||||
query: string,
|
||||
|
||||
Reference in New Issue
Block a user