Skip to content

feat: embedded terminal search (Cmd+F)#115

Merged
grimmerk merged 2 commits intomainfrom
feat/terminal-search
Apr 8, 2026
Merged

feat: embedded terminal search (Cmd+F)#115
grimmerk merged 2 commits intomainfrom
feat/terminal-search

Conversation

@grimmerk
Copy link
Copy Markdown
Owner

@grimmerk grimmerk commented Apr 8, 2026

Summary

Add Cmd+F search to the embedded terminal, matching VS Code's terminal behavior. Powered by @xterm/addon-search.

UX

  • Cmd+F opens a search overlay (top-right of the terminal)
  • The overlay has: input field, match counter, previous/next/close buttons
  • Typing searches incrementally as you go
  • Enter jumps to next match, Shift+Enter to previous, Escape closes
  • Empty input shows nothing; query with no matches shows "No results" in red
  • Highlight colors match VS Code's dark theme (#515C6A for matches, #A8AC94 for active match)

Why xterm.js doesn't ship a search UI

xterm.js (the library VS Code's terminal also uses) only provides search APIs through @xterm/addon-searchfindNext(), findPrevious(), onDidChangeResults. The UI is the host app's responsibility. This PR builds a small React overlay that calls into the addon.

Changes

  • package.json — add @xterm/addon-search@^0.16.0
  • src/terminal-tab.tsx:
    • Load SearchAddon
    • Intercept Cmd+F in attachCustomKeyEventHandler (returns false so the keystroke doesn't reach the PTY)
    • Subscribe to onDidChangeResults for live match count
    • Render overlay (searchOpen state) with input + counter + buttons
    • closeSearch() clears decorations and refocuses the terminal
  • CHANGELOG.md + version bump to 1.0.73

Test plan

  • Cmd+F opens the search overlay
  • Typing searches incrementally
  • Enter / Shift+Enter cycle through matches
  • Escape closes the overlay and refocuses the terminal
  • Match counter updates live
  • No results → shows "No results" in red
  • Search overlay doesn't intercept Cmd+F outside the Terminal tab
  • Existing terminal shortcuts (Cmd+K clear, Shift+Enter multiline, Cmd+←/→) still work

🤖 On behalf of @grimmerk — generated with Claude Code


Summary by cubic

Add Cmd+F search to the embedded terminal with a lightweight overlay and keyboard navigation, matching VS Code behavior. Uses @xterm/addon-search for search, decorations, and live result counts.

  • New Features

    • Cmd+F opens a search overlay with input, match counter, and prev/next/close; incremental search as you type.
    • Enter next, Shift+Enter previous, Escape closes; highlights match VS Code dark theme and the keystroke isn’t sent to the PTY.
  • Bug Fixes

    • Show “N+ results” when matches exceed the add-on’s highlight limit.
    • Dispose onDidChangeResults listener on unmount; add ARIA labels to input/buttons and aria-live to the counter.

Written for commit c4392ba. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Embedded terminal search accessible with Cmd+F.
    • Search overlay with input, match counter, and previous/next/close controls.
    • Keyboard navigation: Enter = next, Shift+Enter = previous, Escape = close.

Add search overlay to the embedded terminal, matching VS Code's
behavior. Powered by @xterm/addon-search.

- Cmd+F opens an overlay (input + match count + prev/next/close)
- Enter = next match, Shift+Enter = previous, Escape = close
- Input changes search incrementally
- VS Code dark theme highlight colors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 582afa60-aa51-49b8-8f56-4387223095c2

📥 Commits

Reviewing files that changed from the base of the PR and between 0b7271c and c4392ba.

📒 Files selected for processing (1)
  • src/terminal-tab.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/terminal-tab.tsx

📝 Walkthrough

Walkthrough

Adds an embedded terminal search feature (Cmd+F) using the @xterm/addon-search addon with a React search overlay, keyboard navigation (Enter/Shift+Enter/Escape), match counter, and decorations/cleanup on close.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added ## 1.0.73 entry documenting embedded terminal search and usage notes.
Version & Dependency
package.json
Bumped version to 1.0.73; added runtime dependency @xterm/addon-search ^0.16.0.
Terminal Search Implementation
src/terminal-tab.tsx
Integrated SearchAddon, added search overlay state/UI, Cmd+F interception to open overlay, findNext/findPrevious handling, match-result subscription and cleanup, keyboard handlers (Enter, Shift+Enter, Escape), and decoration management.

Sequence Diagram

sequenceDiagram
    actor User
    participant Terminal as Terminal Component
    participant SearchAddon as `@xterm/addon-search`
    participant UI as Search Overlay UI

    User->>Terminal: Press Cmd+F
    Terminal->>Terminal: Intercept key, open overlay
    Terminal->>UI: Render overlay & focus input
    User->>UI: Type query
    UI->>SearchAddon: findNext(query)
    SearchAddon->>Terminal: Highlight matches / return count
    Terminal->>UI: Update match counter
    User->>UI: Press Enter / Shift+Enter
    UI->>SearchAddon: findNext / findPrevious
    SearchAddon->>Terminal: Update highlight
    User->>UI: Press Escape
    UI->>SearchAddon: Clear decorations
    Terminal->>Terminal: Close overlay, restore state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I tapped Cmd+F and hopped inside,
Searching bytes where scrolls abide,
Matches sparkle, one by one,
Hop, next, previous — coding fun,
A tiny rabbit cheers: "Well found!" 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: embedded terminal search (Cmd+F)' clearly and concisely summarizes the main change: adding a search feature to the embedded terminal with a Cmd+F shortcut.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/terminal-search

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/terminal-tab.tsx (2)

95-97: Consider storing the disposable from onDidChangeResults.

The onDidChangeResults method returns an IDisposable that could be stored and disposed during cleanup. However, since the terminal is a singleton that persists for the app lifetime, and the addon is tied to the terminal's lifecycle, this is a minor concern.

🔧 Optional: Store disposable for completeness
+  const searchResultDisposableRef = useRef<{ dispose: () => void } | null>(null);
   // ...in useEffect after loading searchAddon:
-    searchAddon.onDidChangeResults((result) => {
-      setSearchResult(result);
-    });
+    searchResultDisposableRef.current = searchAddon.onDidChangeResults((result) => {
+      setSearchResult(result);
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/terminal-tab.tsx` around lines 95 - 97, The callback subscription
returned by searchAddon.onDidChangeResults is an IDisposable but currently
unused; capture its return value (e.g., const disposable =
searchAddon.onDidChangeResults(...)) and ensure it is disposed during cleanup
(call disposable.dispose() or push it into the component/terminal disposables
array or the useEffect cleanup that manages terminal lifecycle). Update the code
around searchAddon.onDidChangeResults and setSearchResult so the IDisposable is
stored and disposed when the terminal/component cleans up to avoid leaking the
subscription.

182-275: Search overlay implementation looks good.

The UI correctly implements:

  • Incremental search on input change
  • Keyboard navigation (Escape, Enter, Shift+Enter)
  • Match counter with proper 1-indexing and "No results" state
  • Hover effects on buttons
💡 Optional: Add aria-label for accessibility
           <input
             ref={searchInputRef}
             type="text"
             value={searchQuery}
             placeholder="Find"
+            aria-label="Search in terminal"
             onChange={(e) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/terminal-tab.tsx` around lines 182 - 275, Add accessible labels and
roles: give the search input (bound to searchInputRef, value searchQuery) an
aria-label like "Find in terminal" and ensure the match counter span (uses
searchResult) has aria-live="polite" so screen readers announce changes; add
aria-labels to the Previous/Next/Close buttons (that call findPrevious,
findNext, closeSearch and use searchButtonStyle) such as "Previous match", "Next
match", "Close search" and include title duplication only if needed for visual
tooltips.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/terminal-tab.tsx`:
- Around line 95-97: The callback subscription returned by
searchAddon.onDidChangeResults is an IDisposable but currently unused; capture
its return value (e.g., const disposable = searchAddon.onDidChangeResults(...))
and ensure it is disposed during cleanup (call disposable.dispose() or push it
into the component/terminal disposables array or the useEffect cleanup that
manages terminal lifecycle). Update the code around
searchAddon.onDidChangeResults and setSearchResult so the IDisposable is stored
and disposed when the terminal/component cleans up to avoid leaking the
subscription.
- Around line 182-275: Add accessible labels and roles: give the search input
(bound to searchInputRef, value searchQuery) an aria-label like "Find in
terminal" and ensure the match counter span (uses searchResult) has
aria-live="polite" so screen readers announce changes; add aria-labels to the
Previous/Next/Close buttons (that call findPrevious, findNext, closeSearch and
use searchButtonStyle) such as "Previous match", "Next match", "Close search"
and include title duplication only if needed for visual tooltips.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62d2162b-f54f-4b15-88b6-2ea0f1c5f445

📥 Commits

Reviewing files that changed from the base of the PR and between 654c8e6 and 0b7271c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • CHANGELOG.md
  • package.json
  • src/terminal-tab.tsx

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/terminal-tab.tsx">

<violation number="1" location="src/terminal-tab.tsx:243">
P2: When the number of matches exceeds the search addon's highlight limit (default 1000), `resultIndex` is `-1` per the API docs, causing the counter to display "0 of N". Handle this case explicitly — e.g., show "N results" or "1000+ results" instead.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread src/terminal-tab.tsx Outdated
- cubic P2: handle resultIndex=-1 (when matches exceed the
  search addon's highlight limit of 1000) — show "N+ results"
  instead of "0 of N"
- CodeRabbit: store and dispose the onDidChangeResults
  IDisposable on component unmount
- CodeRabbit: add aria-label to search input + buttons,
  aria-live="polite" to the match counter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@grimmerk
Copy link
Copy Markdown
Owner Author

grimmerk commented Apr 8, 2026

Thanks for the nitpicks @coderabbitai — both addressed in c4392ba:

  1. onDidChangeResults disposable — stored in searchResultDisposableRef and disposed in a useEffect cleanup on component unmount.
  2. A11y labels — added aria-label to the search input ("Find in terminal"), prev/next/close buttons ("Previous match", "Next match", "Close search"), and aria-live="polite" to the match counter span.

🤖 On behalf of @grimmerk — generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Tip

For best results, initiate chat on the files or code changes.

@grimmerk: Thanks for the update! Both fixes look great:

  1. Disposable cleanup — storing the onDidChangeResults subscription in a ref and disposing it on unmount is the right pattern, preventing potential memory leaks if the terminal component is torn down.
  2. Accessibility — the aria-label attributes on the input and buttons, combined with aria-live="polite" on the match counter, will make the search overlay fully navigable for screen reader users.

🐇✨

@grimmerk grimmerk merged commit 8687d33 into main Apr 8, 2026
2 checks passed
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.

1 participant