mirror of
https://github.com/openai/codex.git
synced 2026-05-04 03:16:31 +00:00
Add an in-viewport find experience for the TUI2 transcript (the scrollable region above the composer), without introducing a persistent search bar or scroll indicator. UX: - Ctrl-F opens a 1-row "/ " prompt above the composer and updates highlights live as you type - Ctrl-G jumps to the next match without closing the prompt, and keeps working after the prompt closes while the query is still active - Esc closes the prompt but keeps the active query/highlights; Esc again clears the search - Enter closes the prompt and jumps to the selected match Implementation: - Add a dedicated transcript_find module to own query/edit state, smart-case matching over flattened transcript lines, stable jump anchoring (cell + line-in-cell), and per-line highlight rendering - Keep app.rs integration additive via small delegation calls from the key handler and render loop - Plumb find visibility to the footer so shortcuts show Ctrl-G next match only while the find prompt is visible Docs/tests: - Add tui2/docs/transcript_find.md documenting current behavior vs the ideal/perfect end state and explicitly calling out deferred work - Stabilize VT100-based rendering tests by forcing color output and emitting crossterm fg/bg colors directly in insert-history output
249 lines
9.4 KiB
Markdown
249 lines
9.4 KiB
Markdown
# Transcript Find (Inline Viewport)
|
||
|
||
This document describes the design for “find in transcript” in the **TUI2 inline viewport** (the
|
||
main transcript region above the composer), not the full-screen transcript overlay.
|
||
|
||
The goal is to provide fast, low-friction navigation through the in-memory transcript while keeping
|
||
the UI predictable and the implementation easy to review/maintain.
|
||
|
||
---
|
||
|
||
## Goals
|
||
|
||
- **Search the inline viewport content**, derived from the same flattened transcript lines used for
|
||
scrolling/selection, so search results track what the user sees.
|
||
- **Ephemeral UI**: no always-on search bar and no scroll bar in this iteration.
|
||
- **Fast navigation**:
|
||
- highlight all matches
|
||
- jump to the next match repeatedly without reopening the prompt
|
||
- **Stable anchoring**: jumping should land on stable content anchors (cell + line), not raw screen
|
||
rows.
|
||
- **Reviewable architecture**: keep `app.rs` changes small by placing feature logic in a dedicated
|
||
module and calling it from the render loop and key handler.
|
||
|
||
---
|
||
|
||
## Current Implementation (What We Have Today)
|
||
|
||
This section documents the current state so it’s easy to compare against the “ideal/perfect” end
|
||
state discussed in review.
|
||
|
||
For implementation details, see the rustdoc comments and unit tests in `tui2/src/transcript_find.rs`.
|
||
|
||
### UI
|
||
|
||
- When active, a single prompt row is rendered **above the composer**:
|
||
- `"/ query current/total"`
|
||
- Matches are highlighted in the transcript:
|
||
- all matches: underlined
|
||
- current match: reversed + bold + underlined
|
||
- The prompt is **not persistent**: it only appears while editing.
|
||
|
||
### Keys
|
||
|
||
- `Ctrl-F`: open the find prompt and start editing the query.
|
||
- While editing:
|
||
- type to edit the query (highlights update as you type)
|
||
- `Backspace`: delete one character
|
||
- `Ctrl-U`: clear the query
|
||
- `Enter`: close the prompt and jump to a match (if any)
|
||
- `Esc`: close the prompt without clearing the query (highlights remain)
|
||
- `Ctrl-G`: jump to next match.
|
||
- Works while editing (prompt stays open).
|
||
- Works even after the prompt is closed, as long as the query is still active.
|
||
- `Esc` (when not editing and a query is active): clears the search/highlights.
|
||
|
||
### Footer hints
|
||
|
||
- When the find prompt is visible, the footer shows `Ctrl-G next match`:
|
||
- in the shortcut summary line
|
||
- and in the `?` shortcut overlay
|
||
|
||
### Implementation layout
|
||
|
||
- Core logic lives in `tui2/src/transcript_find.rs`:
|
||
- key handling
|
||
- match computation/caching
|
||
- jump selection
|
||
- per-line rendering helper (`render_line`) and prompt rendering helper (`render_prompt_line`)
|
||
- `tui2/src/app.rs` is kept mostly additive by delegating:
|
||
- early key handling delegation in `App::handle_key_event`
|
||
- per-frame recompute/jump hook after transcript flattening
|
||
- per-row render hook for match highlighting
|
||
- prompt + cursor positioning while editing
|
||
- Footer hint integration is wired via `set_transcript_ui_state(..., find_visible)` through:
|
||
- `tui2/src/chatwidget.rs`
|
||
- `tui2/src/bottom_pane/mod.rs`
|
||
- `tui2/src/bottom_pane/chat_composer.rs`
|
||
- `tui2/src/bottom_pane/footer.rs`
|
||
|
||
---
|
||
|
||
## UX and Keybindings
|
||
|
||
### Entering search
|
||
|
||
- `Ctrl-F` opens the find prompt on the line immediately above the composer.
|
||
- While the prompt is open, typed characters update the query and immediately update highlights.
|
||
|
||
### Navigating results
|
||
|
||
- `Ctrl-G` jumps to the next match.
|
||
- Works while the prompt is open.
|
||
- Also works after the prompt is closed as long as a non-empty query is still active (so users can
|
||
“keep stepping” through matches).
|
||
|
||
### Exiting / clearing
|
||
|
||
- `Esc` closes the prompt without clearing the active query (and therefore keeps highlights).
|
||
- `Esc` again (when not editing and a query is active) clears the search/highlights.
|
||
|
||
### Footer hints
|
||
|
||
When the find prompt is visible, we surface the relevant navigation key (`Ctrl-G`) in:
|
||
|
||
- the shortcut summary line (the default footer mode)
|
||
- the “?” shortcut overlay
|
||
|
||
This keeps the prompt itself visually minimal.
|
||
|
||
---
|
||
|
||
## Data Model: Search Over Flattened Lines
|
||
|
||
Search operates over the same representation as scrolling and selection:
|
||
|
||
1. Cells are flattened into a list of `Line<'static>` plus parallel `TranscriptLineMeta` entries
|
||
(see `tui2/src/tui/scrolling.rs` and `tui2/docs/tui_viewport_and_history.md`).
|
||
2. The find module searches **plain text** extracted from each flattened line (by concatenating its
|
||
spans’ contents).
|
||
3. Each match stores:
|
||
- `line_index` (index into flattened lines)
|
||
- `range` (byte range within the flattened line’s plain text)
|
||
- `anchor` derived from `TranscriptLineMeta::CellLine { cell_index, line_in_cell }`
|
||
|
||
The anchor is used to update `TranscriptScroll` when jumping so the viewport lands on stable content
|
||
even if the transcript grows.
|
||
|
||
---
|
||
|
||
## Matching Semantics
|
||
|
||
### Smart-case
|
||
|
||
The search is “smart-case”:
|
||
|
||
- If the query contains any ASCII uppercase, the match is case-sensitive.
|
||
- Otherwise, both haystack and needle are matched in ASCII-lowercased form.
|
||
|
||
This avoids expensive Unicode case folding and keeps behavior predictable in terminals.
|
||
|
||
---
|
||
|
||
## Rendering
|
||
|
||
### Highlights
|
||
|
||
- All matches are highlighted (currently: underlined).
|
||
- The “current match” is emphasized more strongly (currently: reversed + bold + underlined).
|
||
|
||
Highlighting is applied at render time for each visible line by splitting spans into segments and
|
||
patching styles for the match ranges.
|
||
|
||
### Prompt line
|
||
|
||
While editing, the line directly above the composer shows:
|
||
|
||
`/ query current/total`
|
||
|
||
It is rendered inside the transcript viewport area (not as a persistent UI element), and the cursor
|
||
is moved into this line while editing.
|
||
|
||
---
|
||
|
||
## Performance / Caching
|
||
|
||
Recomputing matches happens only when needed. The search module caches based on:
|
||
|
||
- transcript width (wrapping changes can change the flattened line list)
|
||
- number of flattened lines (transcript growth)
|
||
|
||
This keeps the work proportional to actual content changes rather than every frame.
|
||
|
||
---
|
||
|
||
## Code Layout (Additive, Review-Friendly)
|
||
|
||
The implementation is structured so `app.rs` only delegates:
|
||
|
||
- `tui2/src/transcript_find.rs` owns:
|
||
- query/edit state
|
||
- match computation and caching
|
||
- key handling for find-related shortcuts
|
||
- rendering helpers for highlighted lines and the prompt line
|
||
- producing a scroll anchor when a jump is requested
|
||
|
||
`app.rs` integration points are intentionally small:
|
||
|
||
- **Key handling**: early delegation to `TranscriptFind::handle_key_event`.
|
||
- **Render**:
|
||
- call `TranscriptFind::on_render` after building flattened lines to apply pending jumps
|
||
- call `TranscriptFind::render_line` per visible row
|
||
- render `render_prompt_line` when active and set cursor with `cursor_position`
|
||
- **Footer**:
|
||
- `set_transcript_ui_state(..., find_visible)` so the footer can show find-related hints only when
|
||
the prompt is visible.
|
||
|
||
---
|
||
|
||
## Comparison to the “Ideal” End State
|
||
|
||
### Ideal UX (what “perfect” looks like)
|
||
|
||
- **Ephemeral, minimal UI**: no always-on search bar, and no scroll bar for this feature.
|
||
- **Fast entry**: `Ctrl-F` opens a single prompt row above the composer.
|
||
- **Live feedback**: highlights update as you type, and the prompt shows `current/total`.
|
||
- **Repeat navigation without closing**: `Ctrl-G` jumps to the next match while the prompt stays
|
||
open, and continues to work after the prompt closes as long as the query is active.
|
||
- **Predictable exit semantics**:
|
||
- `Enter`: accept query, close prompt, and jump (if any matches)
|
||
- `Esc`: close the prompt but keep the query/highlights
|
||
- `Esc` again (with an active query): clear the query/highlights
|
||
- **Stable jumping**: navigation targets stable transcript anchors (cell + line-in-cell), so jumping
|
||
behaves well as the transcript grows.
|
||
- **Discoverability without clutter**: when the prompt is visible, the footer/shortcuts surface the
|
||
navigation key (`Ctrl-G`) so the prompt itself stays tight.
|
||
- **Future marker integration**: if/when a scroll indicator is introduced, match markers integrate
|
||
with it (faint ticks for match lines, stronger marker for the current match).
|
||
|
||
### Already aligned with the ideal
|
||
|
||
- Ephemeral prompt (no always-on bar).
|
||
- Live highlighting while typing.
|
||
- `Ctrl-G` repeat navigation without reopening the prompt (including while editing).
|
||
- Stable jump anchoring via `(cell_index, line_in_cell)` metadata.
|
||
- Footer hints (`Ctrl-G next match`) shown only while the prompt is visible.
|
||
- Minimal, review-friendly integration points in `app.rs` via `tui2/src/transcript_find.rs`.
|
||
|
||
### Not implemented yet (intentional deferrals)
|
||
|
||
- Prev match (e.g. `Ctrl-Shift-G`).
|
||
- “Contextual landing” when jumping (e.g. padding/centering so the match isn’t pinned to the top).
|
||
- Match markers integrated with a future scroll indicator.
|
||
|
||
### Known limitations / trade-offs in the current version
|
||
|
||
- Matching is ASCII smart-case (no full Unicode case folding).
|
||
- Match ranges are byte ranges in the flattened plain text. This is fine for styling spans by byte
|
||
slicing, but any future “column-precise” behaviors should be careful with multi-byte characters.
|
||
|
||
---
|
||
|
||
## Future Work (Not Implemented Here)
|
||
|
||
- **Prev match**: add `Ctrl-Shift-G` for previous match if desired.
|
||
- **Marker integration**: if/when a scroll indicator is added, include match markers derived from
|
||
match line indices (faint ticks) and a stronger marker for the current match.
|
||
- **Contextual jump placement**: center the current match (or provide padding above) rather than
|
||
placing it at the exact top row when jumping.
|