refactor(search): unify dropdown filtering on the shared ranked matcher
Branch, project, agent, model, provider, stash, SSH-host, skill-catalog and archive filters each had their own toLowerCase().includes (or no ordering at all); the git branch and gitmoji pickers also let cmdk re-filter and reorder on top of the manual filter, silently dropping rows. All of them now go through rankByQuery/matchesRankQuery: results are relevance-ordered, multi-word queries match in any order, matching ignores punctuation, and cmdk filtering is disabled where the ranked list is already final. rankBranchesForQuery keeps relevance order instead of re-sorting matches alphabetically; the model picker now also matches model ids.
This commit is contained in:
@@ -41,54 +41,6 @@ export function matchesFuzzyQuery(
|
||||
return fuse.search(query).length > 0;
|
||||
}
|
||||
|
||||
function getFuzzyMatchMask<T>(
|
||||
items: T[],
|
||||
query: string,
|
||||
getText: (item: T) => string,
|
||||
options?: FuzzySearchOptions
|
||||
): boolean[] {
|
||||
if (!query) {
|
||||
return items.map(() => true);
|
||||
}
|
||||
|
||||
const mergedOptions = { ...DEFAULT_FUZZY_OPTIONS, ...options };
|
||||
const queryLower = query.toLowerCase();
|
||||
const matches = new Array(items.length).fill(false);
|
||||
const fuzzyCandidateTexts: string[] = [];
|
||||
const fuzzyCandidateIndices: number[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const target = getText(items[i]);
|
||||
if (!target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mergedOptions.preferSubstring && target.toLowerCase().includes(queryLower)) {
|
||||
matches[i] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
fuzzyCandidateTexts.push(target);
|
||||
fuzzyCandidateIndices.push(i);
|
||||
}
|
||||
|
||||
if (fuzzyCandidateTexts.length === 0) {
|
||||
return matches;
|
||||
}
|
||||
|
||||
const fuse = new Fuse(fuzzyCandidateTexts, {
|
||||
threshold: mergedOptions.threshold,
|
||||
distance: mergedOptions.distance,
|
||||
ignoreLocation: mergedOptions.ignoreLocation,
|
||||
});
|
||||
|
||||
for (const result of fuse.search(query)) {
|
||||
matches[fuzzyCandidateIndices[result.refIndex]] = true;
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Score-sorted fuzzy ranking. Strict (low threshold), prioritizes substring
|
||||
* matches (especially prefix matches), and returns the top N.
|
||||
@@ -271,23 +223,3 @@ export function matchesRankQuery(
|
||||
return tokens.every((token) => scoreRankToken(token, rankFields) !== RANK_TOKEN_MISS);
|
||||
}
|
||||
|
||||
export function partitionByFuzzyQuery<T>(
|
||||
items: T[],
|
||||
query: string,
|
||||
getText: (item: T) => string,
|
||||
options?: FuzzySearchOptions
|
||||
): { matching: T[]; other: T[] } {
|
||||
const matches = getFuzzyMatchMask(items, query, getText, options);
|
||||
const matching: T[] = [];
|
||||
const other: T[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (matches[i]) {
|
||||
matching.push(items[i]);
|
||||
continue;
|
||||
}
|
||||
other.push(items[i]);
|
||||
}
|
||||
|
||||
return { matching, other };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user