feat(quick-open): support smart separator matching#8757
feat(quick-open): support smart separator matching#8757ferdiangunawan wants to merge 12 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughQuick-open indexing now stores word-start metadata alongside lowercased paths. Query normalization and fuzzy ranking support whitespace, path and identifier separators, word boundaries, Unicode lowercasing, and nullable non-match scores. Tab-entry classification accepts precomputed indexes, and the tab bar memoizes and supplies them. Tests cover the new ranking cases, index metadata, oversized queries, and index reuse. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 2f13a07f-e31d-4bff-b5e3-a8707df31796
📒 Files selected for processing (5)
src/renderer/src/components/quick-open-search.test.tssrc/renderer/src/components/quick-open-search.tssrc/renderer/src/components/tab-bar/TabBarCreateEntry.tsxsrc/renderer/src/components/tab-bar/tab-create-entry-classifier.test.tssrc/renderer/src/components/tab-bar/tab-create-entry-classifier.ts
| const wanted = query[qi] | ||
| let matched = false | ||
| for (let ti = lastMatchIdx + 1; ti < path.length; ti++) { | ||
| if (!searchCharactersMatch(path[ti], wanted)) { | ||
| continue | ||
| } | ||
| if (requireWordStart && wordStarts[ti] !== 1) { | ||
| continue | ||
| } | ||
|
|
||
| for (let ti = 0; ti < file.lowerPath.length && qi < query.length; ti++) { | ||
| if (file.lowerPath[ti] === query[qi]) { | ||
| // Preserve the existing Quick Open score semantics while avoiding | ||
| // repeated lowercase work for each candidate on every keystroke. | ||
| const gap = lastMatchIdx === -1 ? 0 : ti - lastMatchIdx - 1 | ||
| score += gap | ||
| if ( | ||
| ti > 0 && | ||
| (file.lowerPath[ti - 1] === '/' || | ||
| file.lowerPath[ti - 1] === '.' || | ||
| file.lowerPath[ti - 1] === '-') | ||
| ) { | ||
| // Why: equivalent separators should match, but the literally typed form | ||
| // should rank first when both filename variants exist. | ||
| if (path[ti] !== wanted) { | ||
| score++ | ||
| } | ||
| // Why: skip path index 0 so a leading `s` in `src/...` stays score 0 | ||
| // (previous scorer only bonused matches after `/` `.` `-`). | ||
| if (ti > 0 && wordStarts[ti] === 1) { | ||
| score -= 5 | ||
| } | ||
| lastMatchIdx = ti | ||
| qi++ | ||
| requireWordStart = false | ||
| matched = true | ||
| break | ||
| } | ||
| } | ||
|
|
||
| if (qi < query.length) { | ||
| return -1 | ||
| if (!matched) { | ||
| return null |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make typed _/- separators match camelCase boundaries.
These separators currently require a literal _ or -, so product-detail and product_detail cannot match ProductDetail.tsx. Add a zero-width word-start fallback while preserving the literal-separator ranking bonus, plus regression tests for both queries.
Also applies to: 253-262
Make human word queries work across common filename conventions without regressing large-repository search responsiveness.
Typed `-`/`_` required a literal separator in the path, so
`product-detail` could not find ProductDetail.tsx while the spaced
form could. Separators now bridge zero-width case transitions with a
deterministic rank: literal < swapped separator < case transition.
- extend the filename boost to camelCase names for `-`/`_` queries so
they rank like spaced queries instead of trailing by ~100 points
- tolerate a trailing separator mid-typing ("product-") instead of
blanking the result list between keystrokes
…h separator queries
…nsions, and spaces Round-1 review fixes for smart separator matching: - Re-anchor at segment boundaries (bounded to segments with a first-char match) so separator queries reach camelCase names in inner directories (user-profile -> user/UserProfile/index.tsx), not just basenames. - Stop skipping a value separator that matches the query char, so extension-bearing queries (product-detail.dart) keep the camelCase filename boost instead of losing it to junk. - Treat a typed '-'/'_' as equivalent to a literal space in a filename, making the space/underscore/hyphen equivalence symmetric. - Add regression tests for all three plus the bare-separator guard.
…g boost Round-2 review fixes: - The segment-anchor loop tested the first-char match after consuming the '/' boundary, so '/'- or '\'-leading separator queries skipped all re-anchoring and dropped nested matches (user/UserProfile/index.tsx). Check the first-char match before the boundary; differential-fuzzed equivalent to anchoring at every slash (0 divergences / 6k cases incl. slash-leading and dot-separated basenames). - The filename boost ignored '.'/'.' for identifier-separated queries, so 'product-detail' wrongly boosted a dot-separated 'product.detail.dart'. Only spaced queries may bridge '/'/'.'; '-'/'_' keep them distinct. - Add regression tests for both.
Round-3 review fix: a query space consumes the entire path separator run,
so a literal separator typed right after ("Meeting - 2026", "a _ b", also
space+/ and space+.) had nothing left to match and the file was dropped
entirely — a regression vs the pre-PR literal-substring matcher that also
broke exact-filename paste and blanked results mid-keystroke. Absorb the
following query separators/spaces the consumed run already covers, staying
strict for '/'/'.'. Differential-fuzzed equivalent to naive all-anchor
(0 divergences / 7k cases). Add regression tests.
Round-4 review fix: after a query space consumes a '/' or '.' run,
lastMatchIdx lands on the separator, so the camelCase-bridge detector
scanned from the char after it and misread that separator-induced word
start as a zero-width case transition — letting a typed '-'/'_' cross a
'/'/'.' boundary the spec keeps distinct (e.g. 'user _profile' fuzzy-
matched and boosted user.profile.ts above the real user_my_profile.ts).
Require the transition's prior char to be a non-separator so only genuine
camelCase/acronym boundaries (ProductDetail, APIClient) bridge. Also cover
the spaced extension-boost guard ('foo bar.ts') that lacked a test.
Round-5 review fix: the round-4 guard made the bridge WITNESS strict, but the consumption loop still landed on the first word start, which is the separator-induced one when a space consumed a '/'/'.' run and a deeper same-letter camelCase decoy authorized the bridge (e.g. 'user -profile' matched src/user/profilePage.ts across '/', while the snake twin returned nothing). A zero-width transition can't sit on a just-consumed '/'/'.', so skip the bridge when lastMatchIdx is on one. Add decoy regression cases.
…guard The identifier-transition guard's residual behavior (a typed '-'/'_' not bridging a separator-induced word start, e.g. 'a--b' must not match 'a-b') had no coverage once the round-5 gate subsumed its '/'/'.' role. Pin it so a future simplification can't silently drop the guard.
…parator
Round-7 review fix: the round-5 gate only skipped the camelCase bridge after a
consumed '/' or '.', so a doubled typed separator ('a--b') could still bridge
onto a single-separator name when a deeper same-letter camelCase tail
('x/a-bcBx.ts') authorized it and consumption landed on the '-'-induced word
start. A zero-width transition can't sit right after ANY matched separator, so
widen the gate to !isPathSeparator(path[lastMatchIdx]). Extend the doubled-
separator test with the decoy variant.
6fefccb to
0e8de17
Compare
Summary
Quick Open now treats spaces, underscores, and hyphens as equivalent word boundaries when searching for files.
product detail,product_detail, andproduct-detailcan suggest the same matching file.Screenshots
product detail scrmatchingproduct_detail_screen.dart:product-detailmatching underscore-named files:Testing
pnpm lintpnpm typecheckpnpm testpnpm buildFocused Quick Open and tab-entry suites pass: 3 files, 45 tests. The full test command was attempted, but this sandbox rejects the local Unix sockets used by unrelated daemon, runtime, relay, and provider tests; the installed Node v25.2.1 also differs from the repository's required Node 24.
The desktop and web build stages completed successfully. The final macOS native stage could not write Swift's Clang module cache outside the workspace (
~/.cache/clang) and exited withOperation not permitted.AI Review Report
Security Audit
Notes