Cap content-search previews so huge single-line files don't freeze the UI#56
Conversation
…ze the UI Searching a workspace containing a big single-line minified file (a 935KB highlight.js in the dotfiles repo) shipped the full line as the preview text for every match: up to 200 matches times 935KB serialized over IPC and rendered into the DOM, freezing the whole webview for minutes. Reopening the search pane re-ran the same query and froze it again. Previews are now capped at 500 chars around the match with an ellipsis on each cut end, in both workspace search (Rust) and single-file search (TS). matchStart/matchEnd stay UTF-16 offsets into the full line, so editor match selection is unchanged. A line's match offsets are also computed in one scan of the line instead of one scan per match. Claude-Session: https://claude.ai/code/session_018TAt3cnJRcK25UqH2YKwNz
📝 WalkthroughWalkthroughSearch results now use bounded previews for long lines. Workspace search batches UTF-16 offset mapping and preserves match positions across Unicode content, while current-file search applies equivalent preview-window behavior with boundary-safe ellipses. ChangesSearch preview updates
Estimated code review effort: 3 (Moderate) | ~30 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces preview capping for search matches on extremely long or minified lines in both the Rust backend and TypeScript frontend to prevent performance issues and webview freezes. It also optimizes UTF-16 offset mapping by scanning lines once instead of per-match. The review feedback suggests simplifying the Rust character boundary logic using idiomatic iterators and adjusting the TypeScript string slicing boundaries to prevent splitting surrogate pairs (e.g., emojis).
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR prevents UI freezes when searching files that contain extremely large single-line (minified) content by capping the per-match preview text to a small window around the match, while keeping matchStart/matchEnd as full-line UTF-16 offsets so EditorPane selection behavior remains unchanged.
Changes:
- Cap current-file search match previews to a 500-char window around the match (
src/currentFileSearch.ts). - Cap workspace search previews similarly and compute UTF-16 offsets for all matches on a line in a single scan (
src-tauri/src/workspace.rs). - Add/extend tests to validate preview caps and offset integrity (
src/currentFileSearch.test.ts,src-tauri/src/workspace.rstests).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/currentFileSearch.ts | Adds preview windowing for current-file search results to avoid huge IPC/DOM payloads. |
| src/currentFileSearch.test.ts | Adds tests for preview capping and ellipsis behavior in current-file search. |
| src-tauri/src/workspace.rs | Caps workspace-search preview text and optimizes UTF-16 offset computation across matches. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/currentFileSearch.ts`:
- Around line 13-27: Update previewWindow to adjust windowStart and windowEnd to
Unicode code-point boundaries before slicing, avoiding surrogate-pair splits
while preserving the existing maximum preview length and ellipsis behavior. Add
a regression test covering a mixed ASCII/emoji line where the 160-character
window boundary falls inside an emoji.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b9133186-6de9-440b-b687-9a8bd1942a95
📒 Files selected for processing (3)
src-tauri/src/workspace.rssrc/currentFileSearch.test.tssrc/currentFileSearch.ts
- Avoid splitting surrogate pairs at preview window edges in currentFileSearch.ts (Gemini, Copilot, CodeRabbit) — boundaries nudge off a low surrogate so no lone surrogate renders in the preview; new test covers both edges. - Truncate match ranges to the remaining result capacity before building the UTF-16 offset table in workspace.rs (Copilot) — a one-letter query on a huge minified line no longer allocates offsets for matches past the page. - Rewrite build_search_preview boundary walks with char_indices iterators (Gemini) and apply cargo fmt (fixes the red Build & Test check). - Correct a misleading test comment about match counts (Copilot). Claude-Session: https://claude.ai/code/session_018TAt3cnJRcK25UqH2YKwNz
Copilot follow-up: line_text was an owned copy of the whole (possibly hundreds-of-KB) line even though only the preview window ships, and the char count scanned every line fully. The line is borrowed now, and the short-line check is a byte-length test plus a probe one char past the cap. Claude-Session: https://claude.ai/code/session_018TAt3cnJRcK25UqH2YKwNz
| let mut ranges = case_insensitive_match_byte_ranges(line, &normalized_query); | ||
| if ranges.is_empty() { | ||
| continue; | ||
| } | ||
| // Each range becomes exactly one SearchMatch and the loop below stops at | ||
| // collection_limit, so ranges past the remaining capacity would only feed | ||
| // offset bookkeeping for matches that never ship. Drop them up front — | ||
| // on a huge minified line a one-letter query can produce hundreds of | ||
| // thousands of ranges for a 200-result page. | ||
| ranges.truncate(collection_limit - matches.len()); | ||
|
|
Summary
highlight.jsin the dotfiles repo, just under the 1MB search cap) froze the whole app for minutes: every match carried the full line as its preview, so up to 200 matches × 935KB got serialized over IPC and rendered into the DOM. The tree view shares that webview, so everything locked up, and reopening the search pane re-ran the query and froze it again.src-tauri/src/workspace.rs, which also covers the browser HTTP endpoint) and single-file find (src/currentFileSearch.ts).matchStart/matchEndare untouched: still UTF-16 offsets into the full line, so EditorPane match selection works exactly as before. A line's offsets are also computed in one scan instead of one scan per match.Test plan
cargo test(235 passed) andcargo clippy --all-targets(clean), including 3 new tests: preview cap on huge lines, full-line UTF-16 offsets preserved (astral emoji), multi-byte chars at window edgesnpm test(406 passed) andtsc --noEmitclean, including 4 new currentFileSearch tests (cap, offset integrity, short-line passthrough, start/end-of-line ellipsis)Summary by CodeRabbit