mirror of
https://github.com/Kong/insomnia.git
synced 2026-07-30 09:16:44 -04:00
* perf: rewrite command palette search to eliminate UI freezes and stale results
Problems fixed:
- clientLoader blocked navigation on every palette open, freezing the UI during
full sequential DB scan
- no AbortController: stale searches could overwrite newer results mid-type
- no debounce: every keystroke triggered a full DB traversal immediately
- React Aria's default contains filter re-filtered server-side fuzzy results,
silently dropping valid matches and making fuzzy work redundant
- no warm baseline: palette started blank until the entire load completed
Changes:
- move fuzzyMatch/fuzzyMatchAll → insomnia-data/common-src/search.ts (shared,
testable in node)
- move search logic → insomnia-data/node-src/services/helpers/command-search.ts
- per-request AbortController cancellation via abort(requestId)
- recursion depth guard (max 20) on request group traversal
- safeParent() replaces non-null assertions, safe against orphaned documents
- add CommandSearchResult type in insomnia-data/src/command-search-types.ts
- add useCommandSearch hook: 250ms debounce + abort on filter change + warm
baseline on mount
- add defaultFilter={() => true} to ComboBox, delegating all filtering to the
service
- delete routes/commands.tsx
* fix
* fix
73 lines
1.5 KiB
TypeScript
73 lines
1.5 KiB
TypeScript
import fuzzysort from 'fuzzysort';
|
|
|
|
export interface FuzzyMatchOptions {
|
|
splitSpace?: boolean;
|
|
loose?: boolean;
|
|
}
|
|
|
|
export function fuzzyMatch(
|
|
searchString: string,
|
|
text: string,
|
|
options: FuzzyMatchOptions = {},
|
|
): null | {
|
|
score: number;
|
|
indexes: number[];
|
|
} {
|
|
return fuzzyMatchAll(searchString, [text], options);
|
|
}
|
|
|
|
export function fuzzyMatchAll(searchString: string, allText: string[], options: FuzzyMatchOptions = {}) {
|
|
if (!searchString || !searchString.trim()) {
|
|
return null;
|
|
}
|
|
|
|
const words = searchString.split(' ').filter(w => w.trim());
|
|
const terms = options.splitSpace ? [...words, searchString] : [searchString];
|
|
let maxScore: number | null = null;
|
|
let indexes: number[] = [];
|
|
let termsMatched = 0;
|
|
|
|
for (const term of terms) {
|
|
let matchedTerm = false;
|
|
|
|
for (const text of allText.filter(t => t && t.trim())) {
|
|
const result = fuzzysort.single(term, text);
|
|
|
|
if (!result) {
|
|
continue;
|
|
}
|
|
|
|
// Don't match garbage
|
|
if (result.score < -8000) {
|
|
continue;
|
|
}
|
|
|
|
if (maxScore === null || result.score > maxScore) {
|
|
maxScore = result.score;
|
|
}
|
|
|
|
indexes = [...indexes, ...result.indexes];
|
|
matchedTerm = true;
|
|
}
|
|
|
|
if (matchedTerm) {
|
|
termsMatched++;
|
|
}
|
|
}
|
|
|
|
// Make sure we match all provided terms except the last (full) one
|
|
if (!options.loose && termsMatched < terms.length - 1) {
|
|
return null;
|
|
}
|
|
|
|
if (maxScore === null) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
score: maxScore,
|
|
indexes,
|
|
target: allText.join(' '),
|
|
};
|
|
}
|