Skip to content

Commit d191817

Browse files
committed
refactor(terminal): tighten search per multi-agent review
- Lazy-load the search addon on first use so terminals that are never searched don't carry an idle per-write listener (perf review). - Collapse resultIndex/resultCount into one signal with a shared reset helper, removing the triplicated reset (simplicity review). - Dispose the search addon in cleanup for parity with webglAddon. - Hoist SEARCH_DECORATIONS to module scope; drop the unexplained selection-length guard; clarify the Esc-handling comment.
1 parent 47da4c0 commit d191817

2 files changed

Lines changed: 39 additions & 28 deletions

File tree

src/components/TerminalSearchOverlay.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ export function TerminalSearchOverlay(props: TerminalSearchOverlayProps): JSX.El
4242
else props.onNext();
4343
} else if (e.key === 'Escape') {
4444
e.preventDefault();
45-
// Stop the app-level Esc handler from also reacting (e.g. closing panels).
45+
// Defensive: the app's window-level Esc handler already ignores keys while
46+
// an input is focused, but stop propagation anyway so this Esc only ever
47+
// closes the find bar — never an enclosing panel.
4648
e.stopPropagation();
4749
props.onClose();
4850
}

src/components/TerminalView.tsx

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ function getTerminalBindings() {
107107
return resolvedBindings().filter((b) => b.layer === 'terminal');
108108
}
109109

110+
// Browser-style find: amber highlight for all matches, orange for the active
111+
// one. Like a browser, the highlight palette is fixed rather than theme-derived.
112+
// Overview-ruler colors must be solid; match backgrounds carry alpha so the
113+
// underlying glyphs stay legible on both light and dark terminals.
114+
const SEARCH_DECORATIONS = {
115+
matchBackground: 'rgba(255, 213, 79, 0.4)',
116+
matchOverviewRuler: '#ffd54f',
117+
activeMatchBackground: 'rgba(255, 138, 0, 0.85)',
118+
activeMatchColorOverviewRuler: '#ff8a00',
119+
} as const;
120+
110121
export function TerminalView(props: TerminalViewProps) {
111122
let containerRef!: HTMLDivElement;
112123
let term: Terminal | undefined;
@@ -116,26 +127,30 @@ export function TerminalView(props: TerminalViewProps) {
116127
let searchInputRef: HTMLInputElement | undefined;
117128
const [searchOpen, setSearchOpen] = createSignal(false);
118129
const [searchQuery, setSearchQuery] = createSignal('');
119-
const [searchResultIndex, setSearchResultIndex] = createSignal(-1);
120-
const [searchResultCount, setSearchResultCount] = createSignal(0);
121-
122-
// Browser-style find: amber highlight for all matches, orange for the active
123-
// one. Overview-ruler colors must be solid; match backgrounds carry alpha so
124-
// the underlying glyphs stay legible regardless of theme.
125-
const SEARCH_DECORATIONS = {
126-
matchBackground: 'rgba(255, 213, 79, 0.4)',
127-
matchOverviewRuler: '#ffd54f',
128-
activeMatchBackground: 'rgba(255, 138, 0, 0.85)',
129-
activeMatchColorOverviewRuler: '#ff8a00',
130-
} as const;
130+
// resultIndex is -1 when there's no active match (or when xterm's match
131+
// threshold is exceeded); count is the total. They always change together.
132+
const [searchResult, setSearchResult] = createSignal({ index: -1, count: 0 });
133+
134+
const resetSearchResults = () => setSearchResult({ index: -1, count: 0 });
135+
136+
// The find addon is loaded lazily on first use: most terminals are never
137+
// searched, and an idle addon keeps a per-write listener alive on every pane.
138+
function ensureSearchAddon(): SearchAddon | undefined {
139+
if (searchAddon || !term) return searchAddon;
140+
searchAddon = new SearchAddon();
141+
term.loadAddon(searchAddon);
142+
searchAddon.onDidChangeResults(({ resultIndex, resultCount }) =>
143+
setSearchResult({ index: resultIndex, count: resultCount }),
144+
);
145+
return searchAddon;
146+
}
131147

132148
function runSearch(direction: 'next' | 'prev', incremental = false) {
133149
if (!searchAddon) return;
134150
const q = searchQuery();
135151
if (!q) {
136152
searchAddon.clearDecorations();
137-
setSearchResultIndex(-1);
138-
setSearchResultCount(0);
153+
resetSearchResults();
139154
return;
140155
}
141156
const opts = { incremental, decorations: SEARCH_DECORATIONS };
@@ -157,18 +172,18 @@ export function TerminalView(props: TerminalViewProps) {
157172
searchInputRef?.select();
158173
return;
159174
}
175+
ensureSearchAddon();
160176
// Seed from a single-line selection, like a browser's Find does.
161177
const sel = term.getSelection();
162-
if (sel && !sel.includes('\n') && sel.length <= 200) setSearchQuery(sel);
178+
if (sel && !sel.includes('\n')) setSearchQuery(sel);
163179
setSearchOpen(true);
164180
if (searchQuery()) runSearch('next');
165181
}
166182

167183
function closeSearch() {
168184
setSearchOpen(false);
169185
searchAddon?.clearDecorations();
170-
setSearchResultIndex(-1);
171-
setSearchResultCount(0);
186+
resetSearchResults();
172187
term?.focus();
173188
}
174189

@@ -216,14 +231,6 @@ export function TerminalView(props: TerminalViewProps) {
216231
term.loadAddon(fitAddon);
217232
term.loadAddon(new WebLinksAddon(openTerminalHttpLinkWithModifier));
218233

219-
searchAddon = new SearchAddon();
220-
term.loadAddon(searchAddon);
221-
// Keep the find-bar counter in sync with xterm's match results.
222-
searchAddon.onDidChangeResults(({ resultIndex, resultCount }) => {
223-
setSearchResultIndex(resultIndex);
224-
setSearchResultCount(resultCount);
225-
});
226-
227234
term.open(containerRef);
228235

229236
// Block direct PTY keyboard input only after self-landing has removed the
@@ -830,6 +837,8 @@ export function TerminalView(props: TerminalViewProps) {
830837
onOutput.cleanup?.();
831838
webglAddon?.dispose();
832839
webglAddon = undefined;
840+
searchAddon?.dispose();
841+
searchAddon = undefined;
833842
unregisterTerminal(agentId);
834843
if (ptyPaused && !taskPtyDetached()) {
835844
fireAndForget(IPC.ResumeAgent, { agentId });
@@ -880,8 +889,8 @@ export function TerminalView(props: TerminalViewProps) {
880889
<Show when={searchOpen()}>
881890
<TerminalSearchOverlay
882891
query={searchQuery()}
883-
resultIndex={searchResultIndex()}
884-
resultCount={searchResultCount()}
892+
resultIndex={searchResult().index}
893+
resultCount={searchResult().count}
885894
onInput={onSearchInput}
886895
onNext={() => runSearch('next')}
887896
onPrev={() => runSearch('prev')}

0 commit comments

Comments
 (0)