Skip to content

feat(search): VS Code-style content search view#227

Open
architawr wants to merge 13 commits into
0-AI-UG:mainfrom
architawr:feat/explorer-search-view
Open

feat(search): VS Code-style content search view#227
architawr wants to merge 13 commits into
0-AI-UG:mainfrom
architawr:feat/explorer-search-view

Conversation

@architawr
Copy link
Copy Markdown
Contributor

@architawr architawr commented May 31, 2026

What & why

Adds a dedicated Search view (activity bar, Cmd+Shift+F) for project-wide content search, modelled on VS Code and backed by ripgrep (@vscode/ripgrep, streamed --json). Cate's previous content search was weak (one match per file, no highlighting, no jump-to-line, no scope filters); this brings it close to VS Code's Search view, plus drag-and-drop of results onto the canvas / dock / terminal / agent.

Screenshots

Results — grouped, highlighted, git-tinted Match details — reveals include/exclude + gear
Git-status colors on result file names Light theme (one-light)

Drag a result onto the canvas / dock / terminal / agent — a single unified drop indicator shows the target:

Search view

  • Match modes: Match Case (Aa), Whole Word (ab), Regex (.*) — literal by default.
  • Scope filters: files-to-include / files-to-exclude glob fields, revealed behind a VS Code-style "Toggle Search Details" button; a gear toggles "use exclude settings & ignore files".
  • Results: grouped by file (collapsible), multiple matches per file, substring highlighting, "N results in M files" count, keyboard nav (↑↓/←→/Enter), open-at-match-line, and per-match / per-file dismiss.
  • Git-aware: result file names are tinted by git status (modified / added / untracked / …) using the same palette as the Explorer, and refresh live on file changes.
  • Readability: left-aligned muted :line numbers; lines show full leading context and truncate on the right (no left "…" clipping); a hover tooltip previews the full line with the match highlighted.
  • Clear button in the header resets the query, results, and any in-flight run.
  • Engine: streams results in batches, cancels superseded searches (one per window), caps total matches, 15s watchdog (regex-DoS guard), and tears down when its window closes.

Drag & drop

Drag a file result or a specific match line from the Search view (and the Explorer) onto the canvas / a dock tab / a terminal / the agent — opens an editor (at the match line for line drags), pastes the path into a terminal, or attaches it to the agent chat. A single unified, theme-aware file-drop indicator shows the active target (replacing the previous per-target overlays that could conflict or linger), clamped to the visible canvas between the sidebars.

Explorer integration

The Explorer's inline search becomes a lightweight filter-by-name ("filter on type") that also hides now-empty folders while filtering; full content search lives in the dedicated Search view. fsSearch is retained for the command palette.

Performance

  • The results list is virtualized (fixed-height windowing) so large result sets (thousands of matches) stay responsive.
  • An identical search is not re-run when the view is reopened after switching sidebar tabs — results persist in the store.
  • The Explorer and the Search view share a single reference-counted fs-watch subscription per root, so neither tears down the other's watcher.

Theming / UX polish

  • Verified across all 16 built-in themes (light + dark): every text/icon reads correctly; active toggles use the per-theme accent (--focus-blue).
  • Portal-based tooltips on the header/option buttons (native title is flaky in Electron).
  • The results scrollbar is inset so it no longer fights the sidebar resize handle.

Packaging

Bundles @vscode/ripgrep (+ platform binary) with asarUnpack; resolves rgPath via the app.asarapp.asar.unpacked swap; restores the binary's exec bit. All bundled deps are permissive (ripgrep MIT/Unlicense).

Tests

  • Unit (vitest): ripgrep arg-builder, --json parser + byte→char offsets, searchStore reducer, git-status decoration, line-display helpers. Full suite 569 passed (3 skipped).
  • e2e (Playwright): Search view (17 cases — query, match modes, include/exclude globs, ignore-files toggle, dismissal, keyboard nav, open-at-match, clear button) and Search drag-and-drop (3 cases). Verified live with screenshots across light/dark themes.
  • npm run typecheck, the full vitest suite, npm run build, and the Search e2e all pass.

Notes / follow-ups

  • Underwent adversarial multi-agent review; confirmed findings fixed (resource cleanup, races, path validation, regex timeout, render purity, dismiss-count accuracy).
  • Live git-status recolor of results piggybacks on the shared fs watcher, which is depth: 1 (same reach the Explorer always had) — deeper/external edits refresh on window focus or the next search. Extending watch depth is a separate, main-process change affecting the Explorer too.
  • Cmd+P fuzzy file search is intentionally deferred to a follow-up PR.

Artur Karapetyan added 2 commits May 31, 2026 21:47
Add a dedicated Search view in the activity bar (Cmd+Shift+F) for
project-wide content search, backed by ripgrep (@vscode/ripgrep, streamed
--json).

- Match modes: Match Case, Whole Word, Regex (literal by default).
- Scope filters: files-to-include / files-to-exclude glob patterns.
- Results grouped by file (collapsible), with multiple matches per file,
  substring highlighting, "N results in M files" count, optional context
  lines, keyboard navigation, open-at-match-line, and per-match / per-file
  dismiss.
- Streaming engine cancels superseded searches, caps total matches, and has
  a wall-clock watchdog so a pathological regex can't hang.
- Searches are torn down when their window is destroyed.
- The Explorer inline search becomes a lightweight filter-by-name; full
  content search now lives in the Search view. fsSearch is retained for the
  command palette.

Packaging: bundle and unpack @vscode/ripgrep; resolve rgPath with the
app.asar -> app.asar.unpacked swap; restore the binary's exec bit.
- e2e/search.spec.ts drives the real app: open the Search view, type a
  query, assert streamed/highlighted results, "No results", and open-at-match.
- Extend the E2E harness with setWorkspaceRoot / openSidebarView / editorPaths.
- Suppress the post-update feedback modal under CATE_E2E — a fresh test
  profile looks like a first install, and the modal intercepts pointer
  events, flaking tests.
@Anton-Horn
Copy link
Copy Markdown
Contributor

We decided to just have unified search under command + k. Where file/command and terminal search is combined The changes are still very valid. Could you integrate them there? Then I will merge this. And if you change the UI of it please attach a screenshot.

@architawr architawr marked this pull request as draft May 31, 2026 15:48
@architawr
Copy link
Copy Markdown
Contributor Author

architawr commented May 31, 2026

We decided to just have unified search under command + k. Where file/command and terminal search is combined The changes are still very valid. Could you integrate them there? Then I will merge this. And if you change the UI of it please attach a screenshot.

@Anton-Horn , I suggest keeping the separate tab, especially since it's already implemented. In many situations, it's much more convenient than a separate modal window. I'll make the command bar extension a separate PR after merging the current one, so I can reuse the logic.

@Anton-Horn
Copy link
Copy Markdown
Contributor

The search tab can stay, function wise it's good (might add panel content search later on). Command + k stays as a shallow search for commands, panel titles and file names (vscode: command + p). This pr can add the search tab (as is) and remove the search icon above the settings icon in the bottom right). Please add this here too. Then I'll merge this in and the follow up pr reworks the command + k.

Artur Karapetyan added 11 commits June 1, 2026 00:16
…gle + drag-and-drop

UI/UX (closer to VS Code's Search view):
- File-type icons, full filenames with truncated paths, soft theme-aware match
  highlight, muted ":N" line numbers, VS Code-style leading "…" so a match
  always stays visible, clearer row hover + selected ring.
- Full-line preview on hover via a portal tooltip (with highlights), no layout
  shift; theme-aware colors that also read correctly on light themes.
- Removed the Context-lines control; added a "Use Exclude Settings and Ignore
  Files" gear toggle (respectIgnore → ripgrep --no-ignore --hidden when off).

Drag & drop (like VS Code): Search file/line rows are draggable.
- Drop on the canvas opens a floating editor (a line drag reveals that line).
- Drop on a dock zone opens an editor tab (with a drop highlight); the canvas
  stops propagation so it never double-handles.
- Drop on a terminal pastes path:line; drop anywhere on the agent panel inserts
  an @path:line mention.

fix(shortcuts): the global canvas arrow-key navigation listened in the capture
phase and stopped propagation, stealing arrows from focusable lists. Lists now
opt out via data-keynav (the Search results tree), so its keyboard navigation
(↑/↓/←/→, Enter) works.
…yboard, open-at-line, and DnD

- Unit: searchDisplay (leading-"…" + whitespace trimming), terminalDrop
  (path:line + shell-escaping), agentDrop (@path:line mentions).
- e2e (search.spec): match-case / whole-word / regex toggles, invalid-regex
  error, files-to-include / files-to-exclude globs, ignore-files toggle,
  dismiss match / file, keyboard ArrowDown+Enter open, and click-opens-at-line.
- e2e (search-dnd.spec): DataTransfer-based drag of a result onto the canvas
  (file + at-line) and the dock zone (opens a tab) — the real source→target
  chain.
- Harness: getSearchSnapshot() + lastEditorReveal() (+ getLastReveal in
  editorReveal); data-testid/data-selected/data-keynav hooks on result rows.
…view

# Conflicts:
#	src/preload/index.ts
#	src/shared/electron-api.d.ts
…ive toggle

- Explorer name-filter now hides directories with no matching descendant
  (VS Code filter-on-type), via a child→parent visibility cascade in
  FileTreeNode (display:none keeps subtrees mounted so they keep reporting).
- Search-panel toolbar buttons get a reliable portal Tooltip (native `title`
  is flaky in Electron); buttons carry aria-label for a11y + tests.
- Active match-mode toggle was text-blue-300 — invisible on light themes
  (the gear icon vanished). Now bg-blue-600 / text-white, legible on all 16
  themes. Verified light + dark via screenshots.
… agent)

Replaces three independent, conflicting overlays with one source-of-truth
target: a window-level tracker hit-tests the cursor to the nearest
[data-filedrop] element and <FileDropOverlay/> renders a single dashed-blue
indicator at its bounds (matching the internal drag indicator), with a clear
caption. This fixes: the canvas indicator covering the whole window and
persisting after drop, the canvas/agent indicators showing at once, and the
agent indicator vanishing when hovering its input. Drop handling stays in the
components. Adds e2e harness helpers (getSearchSnapshot/lastEditorReveal/
setTheme/themeIds).
… ignored-file search, accent gear

- Drop indicator: the tracker now listens in the capture phase, so it still
  fires over the terminal (whose dragover stopPropagation previously left a
  stale canvas indicator showing alongside the terminal's own). The terminal
  is marked data-filedrop="terminal" and its private overlay removed → exactly
  one indicator over a terminal.
- The indicator rect is clamped to the open sidebars (--cate-left/right-
  sidebar-width) so the canvas/dock highlight no longer slides under them.
- "Use ignore files" off was slow: ripgrep --no-ignore --hidden floods
  node_modules/.git and the un-virtualized list rendered thousands of rows.
  Now .git is always excluded and the match cap is 2000 (engine + handler),
  keeping the UI responsive.
- Active match-mode toggle uses the theme accent (--focus-blue via new
  bg-accent utility) instead of a hardcoded blue — adapts per theme; verified
  the gear stays visible on light themes.
Search-view refinements (reference: VS Code):

- Virtualize the results list — fixed-height windowing over the flat
  rows[] (ResizeObserver + scrollTop + spacer/translateY, keyboard
  scroll-into-view). Large queries (1600+ matches) stay responsive.
- Tint result file rows by git status via the shared lookupNodeDecoration,
  so they match the Explorer palette (new useGitTree hook + loadGitTree).
- Give file-header rows a subtle bg-black/10 overlay so they read as
  distinct from code rows on any theme.
- Move files-to-include/exclude behind a VS Code-style "..." (DotsThree)
  toggle to the right of the query field.
- Show input placeholders only while focused; add stable aria-labels.
- Fix Tooltip positioning: measure the wrapped child instead of the
  zero-box display:contents wrapper (was pinned to 0,0).
- Clear the row hover preview on drag start / mousedown / drag end and on
  per-row mouse-leave, so it no longer lingers during a drag or when the
  cursor moves over the tooltip itself.

e2e: locate inputs by aria-label (placeholders are now focus-gated).
Address review feedback on the Search view:

- Add a "Clear search" (eraser) button to the right of the Search header
  that resets the query, results, and any in-flight run.
- Keep result git-status colors fresh: route useGitTree through a new
  shared, refcounted renderer-side fs-watch manager so it reloads on file
  changes even while the Explorer (which also watches the root) is
  unmounted on another sidebar. FileExplorer now uses the same manager,
  so neither view tears down the other's watcher. Also reloads on focus.
- Persist a search across remount: skip re-running an identical query
  (same options + root) when the view is reopened after switching sidebar
  tabs — results already live in the store, so no costly re-render.
- Stop the results scrollbar from fighting the sidebar resize handle:
  inset the scroll box 6px (mr-1.5) so the scrollbar sits inboard of the
  handle (padding-right wouldn't move a WebKit scrollbar).
- Drop the left "…" truncation of match lines; show the whitespace-
  trimmed line and truncate on the right (VS Code style) so leading
  context stays readable. Removes now-unused trimForDisplay/MAX_PREFIX.
- Revert the file-row dark background overlay (back to transparent).

e2e: add a clear-button test.
@architawr architawr marked this pull request as ready for review May 31, 2026 19:34
@architawr
Copy link
Copy Markdown
Contributor Author

@PaulHorn — ready for review 🙏

📝 Heads up: the PR description has been updated to match the current state of the branch (it had drifted from the original):

  • Dropped the stale "context lines" mention — that feature was removed.
  • Empty-folder hiding in the Explorer filter is now implemented (the old description listed it as deferred).
  • Documented the work added since: drag-and-drop to canvas / dock / terminal / agent, git-status colors on results, list virtualization, the clear button, the details toggle, and theme-safety.
  • Refreshed the test count (569 passing).

🖼️ Also added UI screenshots to the description (results, match details, git colors, light theme, and the drag-drop indicator).

✅ CI green on macOS / Ubuntu / Windows; npm run typecheck, the full vitest suite, and the Search e2e all pass.

@PaulHorn
Copy link
Copy Markdown
Contributor

Thanks @architawr! One thing before I merge: the standalone search icon above the Settings gear (bottom of the left sidebar) is still there — that's the one Anton asked to drop in this PR.

It's the MagnifyingGlass button just above the Gear button in src/renderer/sidebar/Sidebar.tsx (the side === 'left' bottom group). It only opens the ⌘K command palette, which still works from the keyboard, so removing the button doesn't lose any functionality — it just clears the redundant icon now that the dedicated Search view exists.

Could you drop that button here? Then this is good to merge. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants