Skip to content

agent_ui: Add in-thread search bar#57231

Open
dandv wants to merge 8 commits into
zed-industries:mainfrom
dandv:agent-ui-thread-search
Open

agent_ui: Add in-thread search bar#57231
dandv wants to merge 8 commits into
zed-industries:mainfrom
dandv:agent-ui-thread-search

Conversation

@dandv

@dandv dandv commented May 20, 2026

Copy link
Copy Markdown
Contributor

This is a narrower alternative to #54816, scoped to search only the currently-loaded thread, excluding tool output or thinking blocks (happy to follow up on those, see next). It uses a custom bar confined to agent_ui rather than BufferSearchBar + SearchableItem, avoiding the cross-crate plumbing that #54816 reached. Open as draft pending direction from @benbrandt on what scope/approach would be acceptable for in-thread search.

image

What this PR does

Adds a search bar to the agent panel, triggered by Ctrl+F (Cmd+F on macOS), that lets users grep the currently-loaded thread without leaving the agent panel; not cross-thread or cross-agent.

  • Searches visible content only: user messages, assistant message chunks, and tool-call labels. Thought blocks and rendered tool-call content (collapsed by default) are intentionally skipped so that:
    1. The visible match count matches what the user sees.
    2. The search experience is consistent between tool output blocks in the current Zed session, ans tool output blocks from past sessions, which are not rendered - see issue agent_ui: tool output not rendered after restarting Zed #57230
  • Highlights matches inline via Markdown::set_search_highlights for markdown-rendered content and Editor::highlight_background(HighlightKey::BufferSearchHighlights, …) for past user messages (rendered through MessageEditor's inner Editor, not through markdown).
  • Next/prev navigation, case/whole-word/regex toggles (same UI as BufferSearchBar).
  • Returns focus to the message editor on dismiss so the user can keep typing immediately.
Commits (authored by Claude 4.7 Opus, max)

Seven logical commits

  1. agent_ui: Add in-thread search bar — initial implementation: bar UI, keymap bindings under AcpThreadSearchBar context, markdown highlight plumbing, focus-restore on dismiss.
  2. agent_ui: Add unit tests for in-thread search — coverage of the matcher across entry kinds and the dismiss-clears-highlights path.
  3. agent_ui: Limit search to visible tool-call text — UX fixes from manual testing: track_focus so AcpThreadSearchBar context lands in the editor's dispatch chain; red border on zero-match query; skip tool-call content (only search labels).
  4. agent_ui: Fix Esc dispatch, smart toggle, error message, skip Thought blocks — round 2 of UX fixes: contribute AcpThreadSearchBar context from ThreadView when bar is visible, smart Ctrl/Cmd+F outside-the-bar focuses instead of closing, regex error message row, skip AssistantMessageChunk::Thought.
  5. agent_ui: Polish thread search bar — Esc routing, user-message highlights, action forwardingsearch::* action forwarders on ThreadView; cx.defer around the activate callback (fixes a double-borrow panic); editor::actions::Cancel interception so Esc dismisses our bar instead of escaping to the workspace's BufferSearchBar; user-message highlights via the inner Editor; muted zero-match counter; three new gpui regression tests.
  6. *agent_ui: Fix Shift+Enter shadowing in thread search bar** — capture-phase intercept of editor::Newlineon the bar'sbar_rowelement so a base keymap bindingshift-enterat theEditorcontext (e.g. JetBrains →editor::NewlineBelow) can't shadow the bar's agent::SelectPreviousThreadMatch. Adds a regression test that loads default-linux.json+linux/jetbrains.jsonand assertsshift-enter` navigates instead of inserting a newline.
  7. agent_ui: Debounce thread search, refresh on thread changes, highlight user messages** — last round before maintainer review: 150 ms debounce on the match rescan; subscribe to AcpThread updates so results/highlights/counter follow a streaming conversation live; navigate the list to the entry owning the active match; .ok() instead of let _ =; assorted cleanups; two new regression tests (test_thread_search_refreshes_on_new_thread_entry, test_thread_search_scrolls_to_later_user_message_match).

8 gpui tests cover the load-bearing logic (cargo test -p agent_ui --lib -- thread_search). All pass.

Manual testing

Verified on Linux against upstream/main 13e7c11768 (full release profile, with LTO and codegen-units=1 built and tested at that commit). Branch since merged with upstream/main a6780a5. Of the 100 intervening upstream commits, several touch files in this diff; one produced a real conflict in crates/agent_ui/src/conversation_view.rs (gpui::{...} import block — both sides added new, non-overlapping symbols; resolved by union).

  • Bar opens / dismisses via Ctrl+F and Esc

  • Next/prev navigation via Enter / Shift+Enter / F3 / Shift+F3 / chevron buttons

  • Case / whole-word / regex toggles via buttons and via Alt+C / Alt+W / Alt+R (Linux)

  • Highlights inside past user messages render correctly on the MessageEditor's inner Editor

  • query text turns red on no-match; bad regex shows error message; zero-match counter stays muted (not red)

  • search::* actions fire from outside the bar (focus in message editor) via the ThreadView-level forwarders

  • Workspace pane BufferSearchBar is unaffected; the two bars hold independent state

  • Feature looks great in light mode and dark mode, and with various themes

  • Navigating to a match in a past user message scrolls that message into view (its top to the viewport top)

  • Results update live as the agent streams new messages while the bar is open (debounced ~150 ms, same for typing)

    Zed.agent.thread.search.subscribe.to.thread.updates.webm
Full manual test plan

Setup (preconditions for every test below unless otherwise noted)

  1. Launch the dev binary: ./target/release-fast/zed --user-data-dir ~/.local/share/zed-dev-feat
  2. Open the agent panel, start a thread, send 2–3 prompts so the thread contains:
    • At least one user prompt with a distinctive substring (e.g. watermelon)
    • At least one assistant message with a distinctive substring
    • At least one tool call with a known label substring
    • At least one collapsed Thinking block
  3. In parallel: have at least one code editor open in a workspace pane (some tests cross-check that path).

Core matching

T-001 — Ctrl+F opens the bar with focus in query

Status: ✅ PASS 2026-05-15 15:30

Repro: With focus in the agent panel's message editor, press Ctrl+F (Linux) / Cmd+F (macOS). The search bar appears at the top of the thread view and the query input is focused.

T-002a — Active match visually distinct from inactive matches

Status: ✅ PASS 2026-05-15 15:30

Repro: Type a query that produces ≥3 matches. The current ("active") match must be visually different from the others — different highlight color, not just position. If they look identical, this may be a theme issue (search_active_match_background vs search_match_background too close on the entry's background); note the theme.

T-002b — User-prompt text gets inline highlight, not just match count

Status: ✅ PASS 2026-05-15 17:15 (fix shipped 2026-05-15 16:15; regression-guarded by test_thread_search_highlights_user_message_editor)

Repro: Send a user prompt containing a distinctive substring. Then search that substring. The user prompt text must show yellow highlight, not just be counted. The bar's matcher reads from message.content.markdown() but the on-screen render goes through a MessageEditor; the current code only highlights via Markdown::set_search_highlights, which misses the editor.

Notes: Fix routes user-message matches through Editor::highlight_background(HighlightKey::BufferSearchHighlights, …) — the same path Editor's own SearchableItem impl uses. collect_markdowns no longer pulls the user message's markdown (avoiding double-counting). The bar now maintains separate highlighted_markdowns and highlighted_editors lists for clean-up. Match-target enum (MatchTarget::Markdown vs MatchTarget::Editor) tags each match so active-vs-inactive re-paint can dispatch correctly. Pending runtime verification on next release-fast build.

T-003 — Enter / Shift+Enter in bar navigates matches

Status: ✅ PASS 2026-05-25 14:00 (verified on release build)

Repro: With focus in the query, type a query with ≥3 matches. Press Enter → counter 1/N → 2/N → 3/N. Press Shift+Enter → reverses (and no \ns are output in the search box). F3 / Shift+F3 same.

Option toggles

T-004 — Alt+C toggles case sensitivity from inside bar

Status: ✅ PASS 2026-05-15 07:50

Repro: With focus in query, press Alt+C. Aa button toggles state; results re-filter.

T-005 — Alt+W toggles whole-word from inside bar

Status: ✅ PASS 2026-05-15 15:30

Repro: Same as T-004, with Alt+W toggling wd button.

T-006 — Alt+R toggles regex from inside bar (Linux)

Status: ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 06:35)

Repro: Press Alt+R. .* button toggles.

Notes: macOS keymap (alt-cmd-x) left unchanged in this session — it matches the wider macOS Zed convention.

T-006a — Tooltips on toggle buttons show their hotkey

Status: ✅ PASS 2026-05-15 15:30

Repro: Hover each toggle button (Aa, wd, .*). Tooltip shows action name AND keybinding (e.g. "Toggle Case Sensitive · Alt+C").

Error / empty states

T-007 — Empty matches: no red border on bar input

Status: ✅ PASS 2026-05-15 15:30

Repro: Type zzzzzzz. Query text turns red. Counter shows 0/0. Bar input border stays neutral, no red box.

T-008 — Bad-regex error message

Status: ✅ PASS 2026-05-15 15:30 (error renders aligned with the input)

Repro: Switch to regex mode, type [. Error message appears below bar, aligned with the input.

T-019 — Zero-match counter not colored red

Status: ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 06:30)

Repro: Type a query with no matches. The counter (0/0) stays muted, not red. MPS-parity.

Dismissal & re-entry

T-009 — Esc doesn't interrupt agent generation

Status: ✅ PASS 2026-05-15 15:30

Repro: Start a long prompt; while the agent is generating, open the search bar and press Esc. Bar closes, generation continues.

T-010 — Close-X button position

Status: ✅ PASS 2026-05-15 15:30

Repro: Look at the bar. Close (X) button is on the right side, after the nav arrows (< > 1/N X). NOT free-floating left of the input.

T-011 — Ctrl+F in bar selects all query text

Status: ✅ PASS 2026-05-15 15:30

Repro: With bar open and query containing text, press Ctrl+F. All query text becomes selected (next keystroke replaces).

T-012 — Ctrl+F outside the bar (bar already open) does not crash

Status: ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 06:30; regression-guarded by test_thread_search_select_next_from_thread_view_update_does_not_panic)

Repro: Bar visible, focus in the message editor (not the bar). Press Ctrl+F. Bar gets re-focused, query text is selected. Must not crash.

Notes: Original crash also reproducible from Enter / Shift+Enter inside the bar after a search had matches. Fix wraps the on_activate_match callback's view.update in cx.defer.

T-021 — Esc dismisses agent bar (not the unrelated workspace pane's BufferSearchBar)

Status: ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 14:40; regression-guarded by test_thread_search_editor_cancel_dismisses_bar)

Repro: Open a file in a workspace pane editor (e.g. settings.json). Open BufferSearchBar there. Switch focus to the agent panel, open agent search bar, type a query. Press Esc. The agent bar must dismiss; the workspace pane's BufferSearchBar must remain.

Notes: Root cause: Editor::cancel in single-line mode calls cx.propagate(). The propagated editor::actions::Cancel walked past our bar all the way to Workspace, where BufferSearchBar::register had registered a workspace-wide handler that dismissed the active pane's bar. Fix: ThreadView now catches editor::actions::Cancel when the bar is visible.

Action routing while bar is open, focus is in the message editor

T-013 — F3 / Shift+F3 from outside the bar

Status: ✅ PASS 2026-05-15 15:30 (Enter from outside is expected not to work — message editor's enter binding outranks. F3 is the canonical out-of-bar nav key.)

Repro: Bar open with matches, focus in message editor. Press F3 → next match. Shift+F3 → previous.

T-014 — Alt+C / Alt+W / Alt+R from outside the bar

Status: ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 02:30; ThreadView forwarders for search::ToggleCaseSensitive, ToggleWholeWord, ToggleRegex, FocusSearch)

Repro: Bar visible, focus in message editor. Alt+C/W/R toggles the bar's options and re-runs search.

Nav buttons

T-022 — < / > chevron buttons in the bar advance match

Status: ✅ PASS 2026-05-15 15:30

Repro: With ≥2 matches, click < → previous. Click > → next. Wraps at ends.

Coexistence with workspace-pane buffer search

T-015 — Ctrl+F in a workspace-pane editor still opens BufferSearchBar (no agent-panel takeover)

Status: ✅ PASS 2026-05-15 15:30

Repro: Focus a code editor in a workspace pane, press Ctrl+F. The pane's BufferSearchBar opens as before; agent panel's bar does NOT open.

T-016 — Independent bars: agent vs workspace pane

Status: ✅ PASS 2026-05-15 15:30

Repro: Have both bars open simultaneously. They hold independent state (different queries, different toggles, independent dismissal).

Tool-call content (auto-expand on match)

T-017 — Auto-expand collapsed tool calls on match

Status: ⏸️ BLOCKED (not implemented; out of scope for current session)

Repro: Have a tool call whose collapsed content contains a unique substring. Search that substring. The tool call auto-expands so the match is visible. After dismissal, the tool call collapses again unless the user manually expanded it before.

T-018 — Auto-expand collapsed Thinking blocks on match

Status: ⏸️ BLOCKED (not implemented; out of scope for current session)

Repro: Same shape as T-017 for a collapsed Thinking block.

T-019 — Streaming results automatically highlighted / live thread search

Status: ✅ PASS 2026-06-08

Repro: Send prompt "Write a 5-paragraph story about a researcher who hated noise. The last paragraph should be a one-sentence: "So she moved to " (replace that with the actual name)", then search for Antarctica.

Known limitations

  1. (pre-existing, not from this PR): search highlights inside markdown table cells can be visually offset by a few characters in columns starting with the second. The bug lives in crates/markdown's render layer - see issue markdown: search highlights in table cells are mispositioned within a single line, for columns 2..N #57229
  2. (excluded vs. agent_ui: Add thread search to the agent panel #54816 to keep the scope tight): raw tool-call input/output JSON and terminal scrollback are not indexed.
  3. Navigating to a search hit located below the fold of a very long user message highlights the match but only scrolls the message's top into view, so the hit can stay off-screen until you scroll manually. Past user messages render through an AutoHeight Editor inside a virtualized List, which can't reliably drive intra-entry autoscroll the way markdown content does. User messages are usually shorter than a viewport, so this is left unaddressed.
  4. Plain-text search across triple-backtick fences does not match (the fence characters live in the markdown source; regex search with explicit fence handling does).
  5. The literal text "Thinking" rendered as the thought-disclosure header is not searchable (rendered chrome, not markdown source).

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
    • No new unsafe introduced; no network, file-system, or process APIs touched; only added dep is search (promoted from dev-dep to runtime), already used by this crate's tests; MessageEditor::editor() accessor widening is pub(crate) (in-crate only). Assessment: no new attack surface.
  • Unsafe blocks (if any) have justifying comments
  • The content is consistent with the UI/UX checklist — partially; see "What I did NOT verify" above
    • I did not verify macOS and Windows. Keymap entries exist for all three platforms (AcpThreadSearchBar context bindings) but I only verified Linux at runtime.
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable: regexp-searching for "t.e" in a thread almost at the context limit (919k tokens) felt instant, and navigation among the 2100+ results occurred at the keyboard autorepeat rate.
    • Haven't measured against the 8ms / 120fps frame budget.

Partially closes #39338

Release Notes:

  • Added in-thread search to the agent panel (Ctrl/Cmd+F)

@benbrandt

Copy link
Copy Markdown
Member

@dandv I like the feature idea! Will send to the team to give more detailed feedback

@dandv dandv marked this pull request as ready for review May 25, 2026 21:22
@smitbarmase smitbarmase added the area:ai/agent thread Feedback for Zed's Agent Thread label May 29, 2026
@dandv dandv force-pushed the agent-ui-thread-search branch 6 times, most recently from b0250b2 to ae5567d Compare May 30, 2026 21:19
@dandv dandv force-pushed the agent-ui-thread-search branch 4 times, most recently from ccc8fde to 5edee1c Compare June 3, 2026 23:13
@dandv dandv force-pushed the agent-ui-thread-search branch 2 times, most recently from 7e03a02 to cd5bc8e Compare June 6, 2026 03:38
@bennetbo

bennetbo commented Jun 8, 2026

Copy link
Copy Markdown
Member

Thanks for working on this. Looks promising, I'll try to look at this PR this week. Is there anything you'd like to get in before I start making changes @dandv?

@dandv dandv force-pushed the agent-ui-thread-search branch 2 times, most recently from 0e977fa to a4ed268 Compare June 9, 2026 07:45
@dandv

dandv commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@bennetbo I've just added search for streaming results (with debounce), and did a final cleanup round. All yours!

@bennetbo bennetbo self-assigned this Jun 9, 2026
dandv added 5 commits June 9, 2026 10:25
Adds a search bar to the agent panel, triggered by Ctrl+F (Cmd+F on
macOS), that lets users grep the currently-loaded thread without leaving
the agent panel.

Custom bar rather than `BufferSearchBar` + `SearchableItem` because the
agent panel is not a workspace `Item`, so the toolbar/`ItemHandle`
plumbing those assume doesn't apply. The bar reuses:

- `SearchQuery` for query parsing (regex / case / whole-word toggles).
- `search::SearchOption::as_button` for the toggle button visuals — same
  buttons as `BufferSearchBar`.
- `Markdown::set_search_highlights` / `set_active_search_highlight` for
  inline highlight rendering, exactly as `MarkdownPreviewView` uses
  them.

Each visible per-entry markdown block in the active thread (user
messages, assistant chunks, tool-call labels) is searched, and results
are surfaced one match at a time via the next/prev controls. Activating
a match jumps the `ListState` to the entry that owns it and asks the
markdown to auto-scroll to the source index.

Keybindings under the `AcpThreadSearchBar` context:

- Linux and Windows: `alt-r` toggles regex, matching the wider `Pane`
  context convention for `search::ToggleRegex` on those platforms.
- macOS: `alt-cmd-x` toggles regex. `alt-r` on macOS is reserved for
  the `®` character; chord shortcuts use `cmd+alt` instead.
- Ctrl+F (Cmd+F on macOS) inside the bar dispatches
  `search::FocusSearch` (focus query + select all), mirroring how
  `BufferSearchBar` handles re-entry into an already-open bar.

When dismissed, focus returns to the message editor (matching the
pattern used by `cancel_editing` and `send_queued_message_at_index`) so
the user can immediately keep typing.

Out of scope for this initial version:

- Searching `AssistantMessageChunk::Thought` blocks or any
  `ToolCallContent` (terminal command output, file content read by
  tools, diff editors, image previews). The matcher never walks
  `tool_call.content` and skips `Thought` chunks of assistant messages;
  expanding a tool call does NOT make its content searchable. Trade-off:
  the visible match count stays consistent with what the user sees on
  screen, at the cost of users not being able to grep tool output
  through this bar.
- Searching tool-call raw input/output JSON, which is not a `Markdown`
  entity.
- Searching across multiple threads.
Two integration tests in `conversation_view::tests` cover the matcher
and bar lifecycle:

- `test_thread_search_finds_matches_across_entries` drives a thread
  containing case-insensitive matches in user-message text and
  assistant-message chunks. Asserts the total match count, exercises
  next/prev cycling, and verifies the active match index updates
  correctly.

- `test_thread_search_dismiss_clears_highlights` opens the bar with a
  query, dismisses it, and asserts no markdown is left holding a
  search highlight.

Adds two `#[cfg(test)] pub(super)` accessors on `ThreadSearchBar`
(`match_count` and `active_match_index`) so the tests can observe
matcher state without exposing the private `ThreadMatch` struct more
broadly. Also drops a redundant `focus_handle` clone in `nav_button`'s
tooltip closure (the prior site was its final use).
Two UX fixes from manual testing of the in-thread search bar:

* `track_focus` on the bar root so the `AcpThreadSearchBar` key context
  is in the focused editor's dispatch chain. Without this, Esc fell
  through to ancestor handlers (`menu::Cancel` which cancels agent
  generation, or the workspace `BufferSearchBar` dismiss).

* Skip tool-call content; only search labels. Searching inside
  collapsed content produced large match counts with no visible
  highlights, which is user-hostile. Labels are always visible.
… blocks

Round 2 of UX fixes from manual testing:

* Esc actually closes the bar: contribute the `AcpThreadSearchBar` key
  context from `ThreadView` when the bar is visible, and register
  forwarding handlers (`DismissThreadSearch`,
  `SelectNext/PreviousThreadMatch`) on the same outer element. Mirrors
  the context-contribution pattern that lets `BufferSearchBar`'s Esc
  work despite no `track_focus` on its own element.

* Cmd/Ctrl+F outside the bar focuses it instead of closing it.
  `toggle_search` now distinguishes visible-and-focused (close) from
  visible-and-unfocused (focus the bar). The close path is still
  Esc / the X button.

* Show regex error message below the input. `build_query` now returns
  `(Option<query>, Option<error>)`; errors are rendered in a small red
  `Label` row beneath the bar, matching `MarkdownPreview` search.

* Drop the conditional red border on the input. Both error states
  (bad regex and non-empty query with no matches) are now conveyed via
  red query text alone, via a new `in_error_state` flag passed to
  `render_query_input`. No border treatment in any state.

* Skip `AssistantMessageChunk::Thought` in the matcher: same
  hidden-collapsed-content failure mode as tool-call content. Search
  only the visible `Message` chunks. Searching collapsed thinking
  output is not supported by this bar.
…ghts, action forwarding

Several fixes layered on top of the initial custom-bar implementation,
all confined to `agent_ui`:

- Forward `search::Toggle{CaseSensitive,WholeWord,Regex}` and
  `search::FocusSearch` from `ThreadView` so they fire when the bar is
  visible but focus is in the message editor. Without these the keymap
  resolved the binding (because `AcpThreadSearchBar` is contributed at
  the `ThreadView` level when the bar is visible) but the dispatched
  action found no handler on the bubble path.

- Defer the bar's `on_activate_match` callback's `view.update` via
  `cx.defer` to avoid re-entering `ThreadView`'s update from inside the
  bar's `select_next_match`. Reproduced as a double-borrow panic
  ("cannot update X while it is already being updated") on Enter and
  on Ctrl+F while the bar was open.

- Catch `editor::actions::Cancel` at the `ThreadView` level when the
  bar is visible. The keymap binds `escape` to `editor::Cancel` at the
  `Editor` context, which shadows `AcpThreadSearchBar`'s `escape ->
  agent::DismissThreadSearch`. `Editor::cancel` propagates when there's
  nothing to cancel locally; without the new handler, the propagated
  `Cancel` walked up to `Workspace`, where `BufferSearchBar::register`
  has a workspace-wide handler that dismissed the active pane's search
  bar instead of ours.

- Highlight user-message search hits via
  `Editor::highlight_background(HighlightKey::BufferSearchHighlights, …)`
  on the inner `Editor` backing each `MessageEditor`. Past user messages
  are rendered through `MessageEditor`, not through the markdown
  attached to the entry, so highlights painted on the markdown were
  counted but invisible. Introduces a `MatchTarget::{Markdown, Editor}`
  enum on `ThreadMatch` so active-vs-inactive re-paint dispatches to
  the right entity; splits the bar's `highlighted` field into
  `highlighted_markdowns` and `highlighted_editors` for clean clearing.
  `collect_markdowns` no longer pulls the user message's markdown,
  avoiding double-counting.

- Mute the zero-match counter color (was `Color::Error`; mirror MPS /
  `BufferSearchBar`). Red feedback comes from the query text alone, not
  also from the counter.

- Search placeholder: "Search thread…" → "Search this thread…" to make
  the per-thread scope explicit. Addresses the "we'll need to think
  about how to communicate this feature" line from the PR zed-industries#54816
  rejection — the bar searches only the currently-loaded thread,
  never history or cross-agent context.

- Three new gpui tests in `crates/agent_ui/src/conversation_view.rs`:
  - `test_thread_search_select_next_from_thread_view_update_does_not_panic`
    drives `select_next_match` from inside a `ThreadView::update_in`
    closure, reproducing the action-dispatch nesting that motivated
    the `cx.defer` fix.
  - `test_thread_search_editor_cancel_dismisses_bar` dispatches
    `editor::actions::Cancel` while the bar is visible and asserts the
    bar is dismissed before propagation reaches the workspace.
  - `test_thread_search_highlights_user_message_editor` searches a
    query that only matches the user message and asserts the user
    message's inner `Editor` carries `BufferSearchHighlights` after
    the matcher runs, and that `clear_highlights` removes them.

- Promote `MessageEditor::editor()` from `#[cfg(test)] pub(crate)` to
  always-`pub(crate)` so the search bar can paint highlights on the
  inner `Editor` outside tests. Kept the accessor narrow (crate-local)
  and documented the contract.

Release Notes:

- Added in-thread search for the agent panel (Ctrl/Cmd+F). Searches the
  currently-loaded thread only; not cross-thread or cross-agent.
dandv added 3 commits June 9, 2026 10:25
The bar binds `shift-enter` → `agent::SelectPreviousThreadMatch` under
the `AcpThreadSearchBar` context. Base keymaps that bind `shift-enter`
at the bare `Editor` context — notably JetBrains, which maps it to
`editor::NewlineBelow` — shadow the bar's binding because both
predicates match the focus chain at the same depth and gpui's
binding-index tiebreak favors the later-loaded base keymap. Result:
pressing Shift+Enter in the query input inserts a newline (rendered as
a visible `\n` glyph in the single-line editor) instead of navigating
to the previous match.

An `AcpThreadSearchBar > Editor` keymap block (the pattern
`BufferSearchBar && !in_replace > Editor` uses for the workspace buffer
search bar) does NOT win either: it also matches at the same depth and
still loses to the base keymap on the load-order tiebreak. The bar's
binding only wins when no base keymap claims `shift-enter` at the
`Editor` context.

Fix by capturing `editor::Newline*` actions on the bar's `bar_row`
element in the capture phase and routing them to `select_prev_match`,
calling `cx.stop_propagation()` to prevent the editor's bubble-phase
handler from inserting a newline into the single-line query buffer.
This is the same pattern `BufferSearchBar`, `MessageEditor`, and the
inline-assist prompt editor use for the same class of conflict.

Also adds a regression test that loads `default-linux.json` plus
`linux/jetbrains.json`, then asserts
`cx.simulate_keystrokes("shift-enter")` navigates without inserting a
newline into the query buffer.
The `ContextCompaction` variant of `AgentThreadEntry` was added on the
base branch, but the exhaustive `match` in `collect_markdowns` was not
updated during the merge, breaking the build. It carries no searchable
markdown content, so handle it as a no-op like `CompletedPlan`.
user messages

- Debounce the match rescan (150ms) so fast typing and streaming
  message chunks don't trigger a full synchronous scan per event.
- Subscribe to AcpThread updates (NewEntry / EntryUpdated /
  EntriesRemoved) so matches, highlights, and the counter track a
  streaming conversation without re-touching the query. Gated on an
  `is_active` flag and cancelled on hide so a hidden bar never
  re-applies highlights behind closed search UI.
- Navigate the list to the entry that owns the active match (the same
  path `scroll_to_most_recent_user_prompt` uses), and select the match
  range in past user messages with `no_scroll` so the active highlight
  tracks it. Cross-entry scrolling brings the message's top into view.
  Known limitation: a match below the fold of a very long user message
  stays highlighted but may remain off-screen, because an AutoHeight
  editor inside a virtualized List can't reliably drive intra-entry
  autoscroll (markdown matches don't share this, as the markdown element
  re-issues its autoscroll request every paint). Most user messages are
  shorter than a viewport, so this is left unaddressed by design.
- Replace `let _ = view.update(...)` with `.ok()` per repo error-
  handling rules.
- Drop the vestigial source_index callback param; share
  focus_query_and_select_all between focus_and_refresh and focus_search;
  remove the dead `let _ = tool_call.content`; fix the stale `matches`
  doc comment; note the O(matches) per-step editor repaint.
- Add regression tests: one that streams a new entry while search is
  open and asserts the subscription refreshes the match count, and one
  that asserts the list scrolls to the user-message entry owning a
  match.
@dandv dandv force-pushed the agent-ui-thread-search branch from a4ed268 to 3fb0448 Compare June 9, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ai/agent thread Feedback for Zed's Agent Thread cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants