Use Enter to accept transcript Find results and Escape to cancel (#50503)

## Why

Accepting a Find result must not let a held `Enter` submit the composer draft, including when terminals report repeated keys as fresh presses.

## What changed

- Make `Enter` accept the displayed result for reading, while `Ctrl+P` and `Ctrl+N` navigate older and newer matches.
- Make `Escape` cancel query editing and restore the pre-search presentation; after acceptance, it returns to the latest transcript content.
- Consume unmodified `Enter` while reading a result, but let modified `Enter` reach configured composer bindings.
- Update footer hints to show acceptance, cancellation, and the active Find state.

## Testing

Extend input-routing coverage for repeated `Enter` presses and modified composer bindings. Update search tests and footer snapshots for acceptance, cancellation, and result navigation.

GitOrigin-RevId: 2a2b7c389240ffa941e3623d7fb7d877455fa9a5
This commit is contained in:
Felipe Coury
2026-10-03 02:52:56 +00:00
committed by copyberry
parent 55922b7984
commit 9ce35d337a
15 changed files with 161 additions and 84 deletions
@@ -101,7 +101,9 @@ async fn browsing_search_and_selection_consume_escape_before_mode_exit() -> Resu
app.transcript_view.begin_search();
app.handle_tui_event(&mut tui, &mut server, TuiEvent::Paste("first".to_string()))
.await?;
app.handle_tui_event(&mut tui, &mut server, TuiEvent::Key(KeyCode::Esc.into()))
app.handle_tui_event(&mut tui, &mut server, TuiEvent::Draw)
.await?;
app.handle_tui_event(&mut tui, &mut server, TuiEvent::Key(KeyCode::Enter.into()))
.await?;
assert_eq!(
(
+47 -6
View File
@@ -787,9 +787,50 @@ async fn owned_search_and_selection_consume_input_before_composer_and_backtrack(
.to_string()
.starts_with("Find: needle")
);
let escape = TuiEvent::Key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
assert!(app.handle_owned_transcript_event(&mut tui, &mut app_server, &escape)?);
let accept = TuiEvent::Key(KeyCode::Enter.into());
let escape = TuiEvent::Key(KeyCode::Esc.into());
assert!(app.handle_owned_transcript_event(&mut tui, &mut app_server, &accept)?);
assert!(!app.transcript_view.is_search_editing());
assert!(app.transcript_view.has_active_interaction());
assert_eq!(
app.chat_widget.composer_text_with_pending(),
"composer draft"
);
assert!(!app.backtrack.overlay_preview_active);
// Ghostty, iTerm2 and some tmux paths report held keys as Press, not Repeat.
for kind in [
crossterm::event::KeyEventKind::Press,
crossterm::event::KeyEventKind::Repeat,
] {
let repeat = KeyEvent {
kind,
..KeyCode::Enter.into()
};
assert!(app.transcript_view.owns_interaction_key(repeat));
assert!(app.handle_owned_transcript_event(
&mut tui,
&mut app_server,
&TuiEvent::Key(repeat)
)?);
}
assert_eq!(
app.chat_widget.composer_text_with_pending(),
"composer draft"
);
// Once Find is accepted, modified Enter can still reach configured composer bindings.
for modifiers in [
KeyModifiers::SHIFT,
KeyModifiers::ALT,
KeyModifiers::CONTROL,
] {
let key = KeyEvent::new(KeyCode::Enter, modifiers);
assert!(!app.transcript_view.owns_interaction_key(key));
assert!(!app.handle_owned_transcript_event(
&mut tui,
&mut app_server,
&TuiEvent::Key(key)
)?);
}
// Start and extend a selection through the app, then inspect its copy action without touching
// the host clipboard. The app's existing clipboard handler is tested with an injected writer.
for key in [
@@ -844,13 +885,13 @@ async fn inline_transcript_search_draws_and_escape_precedes_backtrack() -> Resul
&tui.terminal,
));
assert!(
rendered.contains("enter/⌃p older"),
rendered.contains("enter accept"),
"search advances on the inline overlay draw path"
);
app.handle_backtrack_overlay_event(
&mut tui,
&mut app_server,
TuiEvent::Key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)),
TuiEvent::Key(KeyCode::Enter.into()),
)
.await?;
assert!(
@@ -920,7 +961,7 @@ async fn find_owns_editor_chords_without_changing_the_composer_draft() -> Result
crate::custom_terminal::test_support::last_rendered_buffer(&tui.terminal),
);
assert!(
rendered.contains("enter/⌃p older"),
rendered.contains("enter accept"),
"Find must resume after pasting over selection"
);
}
@@ -1328,7 +1369,7 @@ async fn find_refreshes_live_details_before_searching_the_first_query() -> Resul
let rendered = buffer_text(crate::custom_terminal::test_support::last_rendered_buffer(
&tui.terminal,
));
assert!(rendered.contains("enter/⌃p older"), "{rendered}");
assert!(rendered.contains("enter accept"), "{rendered}");
tui.set_owned_screen(/*owned*/ false)?;
app.open_transcript_overlay(&mut tui);
let Some(Overlay::Transcript(overlay)) = &mut app.overlay else {
@@ -23,4 +23,4 @@ $ 'printf visible
Find: needle
enter/⌃p older · ⌃n newer · full transcript · esc close
enter accept · ⌃p older · ⌃n newer · full transcript · esc cancel
@@ -166,7 +166,7 @@ fn closing_find_at_bottom_does_not_show_back_to_bottom() {
assert!(!view.advance_search(&cells));
let before = paint(&mut view, &cells, /*width*/ 40);
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
let after = paint(&mut view, &cells, /*width*/ 40);
assert!(!view.is_search_editing());
assert!(!view.is_following());
@@ -241,6 +241,8 @@ pub(super) fn navigation_line(navigation: &str) -> Line<'static> {
" clear selection",
" copy+↓",
" previous",
" accept",
" cancel",
" older",
" newer",
" latest",
@@ -269,7 +269,7 @@ fn loading_and_failure_preserve_search_query_and_caret() {
view.history = TranscriptHistoryState::Complete;
assert!(!view.advance_search(&cells));
view.handle_key(KeyCode::Esc.into(), &cells);
insta::assert_snapshot!(view.footer(/*width*/ 32, MotionMode::Reduced).unwrap().text.to_string(), @"No matches · esc latest");
assert!(!view.has_active_interaction());
view.handle_key(
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL),
&cells,
@@ -289,7 +289,7 @@ fn reading_a_find_result_shows_history_loading_and_failure() {
view.begin_search();
view.paste_search("needle");
assert!(!view.advance_search(&cells));
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
let mut hints = Vec::new();
for history in [
@@ -305,7 +305,7 @@ fn reading_a_find_result_shows_history_loading_and_failure() {
insta::assert_snapshot!(hints.join("\n"), @"
↑ Loading earlier messages… · esc latest
Retry history: ⌥</⌃home. esc latest
ctrl+p older · ctrl+n newer · esc latest
Find · ctrl+p older · ctrl+n newer · esc latest
");
}
+2 -1
View File
@@ -152,7 +152,8 @@ impl TranscriptView {
}
self.search.is_active()
&& (code == KeyCode::Esc
|| (code == KeyCode::Enter && self.is_search_editing())
|| (code == KeyCode::Enter
&& (self.is_search_editing() || modifiers == KeyModifiers::NONE))
|| (modifiers == KeyModifiers::NONE
&& matches!(code, KeyCode::PageUp | KeyCode::PageDown))
|| (modifiers == KeyModifiers::CONTROL
@@ -418,7 +418,7 @@ fn folding_a_reasoning_only_page_retains_its_find_and_copy_revision() {
view.search
.status_line(/*width*/ 80, view.history)
.to_string()
.starts_with("enter/⌃p older")
.starts_with("enter accept")
);
}
let before = render(&mut view, &cells, /*width*/ 30);
@@ -444,7 +444,7 @@ fn folding_a_reasoning_only_page_retains_its_find_and_copy_revision() {
view.search
.status_line(/*width*/ 80, view.history)
.to_string()
.starts_with("enter/⌃p older")
.starts_with("enter accept")
);
}
}
+24 -14
View File
@@ -3,7 +3,7 @@
//! Search retains one match and one entry being scanned. Each frame examines a bounded text
//! chunk; reaching the oldest loaded entry asks the app's existing history pager to continue.
//! The pager owns loading and failure status; search only remembers that it needs another page.
//! Closing the query keeps the match readable; cancellation restores the original presentation.
//! Accepting a match keeps it readable; cancellation restores the original presentation.
use crate::bottom_pane::TextArea;
use crate::keymap::RuntimeKeymap;
@@ -152,25 +152,35 @@ impl TranscriptView {
let (code, modifiers) = crate::key_hint::normalize_key_parts(key.code, key.modifiers);
match (code, modifiers) {
(KeyCode::Esc, _) if self.search.is_reading() => self.jump_to_latest(),
(KeyCode::Esc, _) if !self.search.editor.is_empty() => {
self.search.mode = SearchMode::Reading;
self.search.progress = if matches!(self.search.progress, Progress::Exhausted) {
Progress::Exhausted
} else if self.search.current.is_some() {
Progress::Found
} else {
Progress::Idle
};
self.search.scanning_layout = None;
}
(KeyCode::Esc, _) | (KeyCode::Char('c'), KeyModifiers::CONTROL) => {
self.cancel_search();
}
(KeyCode::Enter, _) if self.search.is_reading() => return false,
// Some terminals report held Enter as Press, indistinguishable from a fresh key.
// Keep it in Find so accepting a hit cannot submit the composer draft.
(KeyCode::Enter, _) if self.search.is_reading() => {
return modifiers == KeyModifiers::NONE;
}
(KeyCode::Enter, KeyModifiers::NONE) => {
// Accept the displayed match even if finding an older one is still pending.
// An idle nonempty query can also mean scrolling released a live match.
// Leave initial scans and empty/unmatched queries open until they find a hit.
if !self.search.editor.is_empty()
&& (self.search.current.is_some()
|| matches!(self.search.progress, Progress::Idle))
{
self.search.mode = SearchMode::Reading;
self.search.progress = match self.search.progress {
Progress::Exhausted => Progress::Exhausted,
_ if self.search.current.is_some() => Progress::Found,
_ => Progress::Idle,
};
self.search.scanning_layout = None;
}
}
(KeyCode::Char('n'), KeyModifiers::CONTROL) => {
self.next_search_match(cells, Direction::Newer);
}
(KeyCode::Enter, KeyModifiers::NONE) | (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
(KeyCode::Char('p'), KeyModifiers::CONTROL) => {
self.next_search_match(cells, Direction::Older);
}
(KeyCode::PageUp | KeyCode::PageDown, KeyModifiers::NONE) => return false,
@@ -52,25 +52,26 @@ impl Search {
let next = crate::key_hint::ctrl(crossterm::event::KeyCode::Char('n')).display_label();
let retry_hint = format!("{previous} retry");
let unavailable_hint = format!("History unavailable · {retry_hint}");
let next_hint = format!("enter/{previous} older · {next} newer");
let next_hint = format!("enter accept · {previous} older · {next} newer");
let exhausted_hint = format!("No more matches · {next_hint}");
let (status, compact) = match self.progress {
Progress::Idle | Progress::Found if self.is_reading() => {
("ctrl+p older · ctrl+n newer", "ctrl+p older")
}
Progress::Idle if !self.editor.is_empty() => (next_hint.as_str(), "enter accept"),
Progress::Idle => ("Type to find", "Type to find"),
Progress::Restart | Progress::Scanning(_) => ("Searching…", "Searching…"),
Progress::AwaitingHistory if history == TranscriptHistoryState::Failed => {
(unavailable_hint.as_str(), retry_hint.as_str())
}
Progress::AwaitingHistory => ("Searching earlier history…", "Loading…"),
Progress::Found => (next_hint.as_str(), "enter older"),
Progress::Found => (next_hint.as_str(), "enter accept"),
Progress::Exhausted if self.is_reading() && self.current.is_some() => (
"No more matches · ctrl+p older · ctrl+n newer",
"ctrl+p older",
),
Progress::Exhausted if self.current.is_some() => {
(exhausted_hint.as_str(), "enter older")
(exhausted_hint.as_str(), "enter accept")
}
Progress::Exhausted => ("No matches", "No matches"),
};
@@ -82,8 +83,8 @@ impl Search {
if self.is_reading() {
return crate::footer_hint::first_fitting_line(
[
format!("{status} · esc latest"),
format!("{compact} · esc latest"),
format!("Find · {status} · esc latest"),
format!("Find · {compact} · esc latest"),
"esc latest".to_owned(),
]
.map(|hint| crate::transcript_view::footer::navigation_line(&hint)),
@@ -92,11 +93,11 @@ impl Search {
}
crate::footer_hint::first_fitting_line(
[
format!("{status} · full transcript · esc close{limit}"),
format!("{status} · esc close{limit}"),
format!("{compact} · esc close"),
format!("{status} · full transcript · esc cancel{limit}"),
format!("{status} · esc cancel{limit}"),
format!("{compact} · esc cancel"),
format!("{compact} · esc"),
"esc close".to_owned(),
"esc cancel".to_owned(),
]
.map(|hint| crate::transcript_view::footer::navigation_line(&hint)),
width,
@@ -62,10 +62,10 @@ fn find_keeps_context_and_allows_reading_without_losing_the_query() {
let scrolled = view.position;
assert_ne!(scrolled, matched);
assert_eq!(view.search.editor.text(), "needle");
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
insta::assert_snapshot!(
view.footer(/*width*/ 80, crate::motion::MotionMode::Reduced).unwrap().text.to_string(),
@"ctrl+p older · ctrl+n newer · esc latest"
@"Find · ctrl+p older · ctrl+n newer · esc latest"
);
assert_eq!(
(
@@ -75,13 +75,16 @@ fn find_keeps_context_and_allows_reading_without_losing_the_query() {
),
(scrolled, false, true),
);
assert!(view.handle_key(KeyCode::Enter.into(), &cells).is_none());
assert!(matches!(
view.handle_key(KeyCode::Enter.into(), &cells),
Some(ViewAction::Changed)
));
view.begin_search();
assert_eq!(
(view.position, view.search.editor.text()),
(scrolled, "needle")
);
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
view.handle_key(
KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL),
&cells,
@@ -194,7 +197,7 @@ fn paging_away_from_a_live_match_does_not_restart_find() {
let scrolled = view.position;
assert!(view.held_reading.is_none());
finish_scan(&mut view, &cells);
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
assert_eq!(
(
view.position,
@@ -233,8 +236,10 @@ fn reading_without_a_match_searches_from_the_visible_entry_in_each_direction() {
};
view.begin_search();
view.paste_search("needle");
// Close the query before its initial scan has found a match.
view.handle_key(KeyCode::Esc.into(), &cells);
finish_scan(&mut view, &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
view.jump_to_entry(&cells, /*index*/ 1);
view.restart_search();
view.handle_key(
KeyEvent::new(KeyCode::Char(key), KeyModifiers::CONTROL),
&cells,
@@ -262,7 +267,7 @@ fn resizing_a_read_find_result_preserves_relative_navigation() {
view.begin_search();
view.paste_search("needle");
finish_scan(&mut view, &cells);
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
view.prepare_width(/*width*/ 20);
let mut resized = Buffer::empty(view.area);
view.render(view.area, &mut resized, &cells);
@@ -314,7 +319,7 @@ fn reading_a_find_result_loads_older_context() {
finish_scan(&mut view, &cells);
view.handle_key(KeyCode::PageUp.into(), &cells);
assert!(view.needs_history(&cells));
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
view.handle_key(KeyCode::PageUp.into(), &cells);
assert!(view.needs_history(&cells));
assert_eq!(view.search.editor.text(), "needle");
@@ -618,7 +623,7 @@ fn previous_match_continues_through_regrouped_pages_without_revisiting_newer_hit
),
(EntryKey::cell(&middle), true),
);
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"Searching earlier history… · full transcript · esc close");
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"Searching earlier history… · full transcript · esc cancel");
let oldest = cell("needle oldest");
cells.insert(/*index*/ 0, Arc::clone(&oldest));
@@ -710,7 +715,7 @@ fn enter_preserves_the_initial_scan_and_each_older_page_until_the_first_match()
view.begin_search();
view.paste_search("LONG T0000 USER");
view.handle_search_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE), &cells);
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"Searching… · full transcript · esc close");
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"Searching… · full transcript · esc cancel");
assert!(view.advance_search(&cells));
let Progress::Scanning(before) = view.search.progress else {
panic!("initial scan should still have unread text");
@@ -747,7 +752,7 @@ fn enter_preserves_the_initial_scan_and_each_older_page_until_the_first_match()
));
finish_scan(&mut view, &cells);
assert!(view.search.needs_history(view.history));
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"Searching earlier history… · full transcript · esc close");
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"Searching earlier history… · full transcript · esc cancel");
for page in ["middle page without a hit", "LONG T0000 USER"] {
view.handle_search_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE), &cells);
@@ -841,7 +846,10 @@ fn next_previous_highlight_and_cancel_share_the_transcript_position() {
view.search.current.as_ref().map(|found| found.anchor.index),
Some(1)
);
view.handle_search_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE), &cells);
view.handle_search_key(
KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL),
&cells,
);
finish_scan(&mut view, &cells);
assert_eq!(
view.search.current.as_ref().map(|found| found.anchor.index),
@@ -862,14 +870,18 @@ fn next_previous_highlight_and_cancel_share_the_transcript_position() {
true, true, true, true, true, true, false, false, false, false
]
);
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"enter/⌃p older · ⌃n newer · full transcript · esc close");
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"enter accept · ⌃p older · ⌃n newer · full transcript · esc cancel");
view.handle_search_key(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE), &cells);
assert!(matches!(view.search.progress, Progress::Found));
view.handle_search_key(
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL),
&cells,
view.handle_key(KeyCode::Esc.into(), &cells);
assert_eq!(
(
view.position,
view.search.is_active(),
view.search.editor.text()
),
(original, false, ""),
);
assert_eq!((view.position, view.search.is_active()), (original, false));
}
#[test]
@@ -909,7 +921,7 @@ fn legacy_search_shortcuts_navigate_in_both_directions_without_wrapping() {
(2, true)
],
);
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"No more matches · enter/⌃p older · ⌃n newer · full transcript · esc close");
insta::assert_snapshot!(view.search.status_line(/*width*/ 80, view.history).to_string(), @"No more matches · enter accept · ⌃p older · ⌃n newer · esc cancel");
}
#[test]
@@ -947,7 +959,10 @@ fn find_expands_only_the_match_and_preserves_manual_disclosures() {
finish_scan(&mut view, &cells);
for (key, expected) in [
(None, [false, false, true, true]),
(Some(KeyCode::Enter.into()), [false, true, false, true]),
(
Some(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL)),
[false, true, false, true],
),
(
Some(KeyEvent::new(KeyCode::Char('n'), KeyModifiers::CONTROL)),
[false, false, true, true],
@@ -975,7 +990,7 @@ fn find_expands_only_the_match_and_preserves_manual_disclosures() {
"find_selective_expansion",
crate::transcript_view::tests::text(&buffer)
);
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
// Reading keeps the result and lets the normal activity controls open another block.
view.handle_key(KeyCode::F(4).into(), &cells);
view.handle_key(KeyCode::Home.into(), &cells);
@@ -1066,7 +1081,7 @@ fn find_searches_hidden_live_content_and_collapses_it_when_leaving_the_match() {
view.layout(&cells, /*index*/ 1).unwrap().text(),
"hidden live needle"
);
view.handle_key(KeyCode::Esc.into(), &cells);
view.handle_key(KeyCode::Enter.into(), &cells);
view.prepare_width(/*width*/ 20);
assert_eq!(
view.layout(&cells, /*index*/ 1).unwrap().text(),
@@ -1226,7 +1241,7 @@ fn find_reveals_hidden_command_output_and_restores_compact_presentation() {
assert!(!view.is_detailed());
let mut found = Buffer::empty(view.area);
view.render(view.area, &mut found, &cells);
view.handle_search_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE), &cells);
view.handle_search_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE), &cells);
let mut reading = Buffer::empty(view.area);
view.render(view.area, &mut reading, &cells);
assert_eq!(reading, found);
@@ -1320,7 +1335,7 @@ fn failed_history_waits_for_explicit_retry_and_empty_query_cancels_loading() {
view.search
.status_line(/*width*/ 32, view.history)
.to_string(),
"⌃p retry · esc close",
"⌃p retry · esc cancel",
);
view.handle_search_key(
KeyEvent::new(KeyCode::Char('n'), KeyModifiers::CONTROL),
@@ -1435,7 +1450,10 @@ fn live_match_stays_displayed_after_commit_and_a_fresh_query_searches_current_co
(found.anchor.key, found.anchor.offset..found.end),
(EntryKey::Live, 8..14)
);
view.handle_search_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE), &cells);
view.handle_search_key(
KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL),
&cells,
);
finish_scan(&mut view, &cells);
assert!(view.held_reading.is_none());
assert_eq!(
@@ -1628,7 +1646,7 @@ fn bounded_query_paste_keeps_the_suffix_and_whole_graphemes() {
(view.search.editor.text(), view.search.query_truncated),
(full.as_str(), true)
);
insta::assert_snapshot!(view.search.status_line(/*width*/ 100, view.history).to_string(), @"Searching… · full transcript · esc close · query limited to 4 KiB");
insta::assert_snapshot!(view.search.status_line(/*width*/ 100, view.history).to_string(), @"Searching… · full transcript · esc cancel · query limited to 4 KiB");
view.handle_search_key(KeyEvent::new(KeyCode::Delete, KeyModifiers::NONE), &[]);
assert_eq!(view.search.editor.text(), &full[..full.len() - 1]);
}
@@ -2,9 +2,9 @@
source: tui/src/transcript_view/footer_tests.rs
expression: "hints.join(\"\\n\")"
---
32 columns, LoadingOlder: Loading… · esc close
32 columns, LoadingBeginning: Loading… · esc close
32 columns, Failed: ⌃p retry · esc close
12 columns, LoadingOlder: esc close
12 columns, LoadingBeginning: esc close
12 columns, Failed: esc close
32 columns, LoadingOlder: Loading… · esc cancel
32 columns, LoadingBeginning: Loading… · esc cancel
32 columns, Failed: ⌃p retry · esc cancel
12 columns, LoadingOlder: esc cancel
12 columns, LoadingBeginning: esc cancel
12 columns, Failed: esc cancel
@@ -6,7 +6,7 @@ search=false, 12 columns: esc clear
search=false, 20 columns: enter copy+↓ · esc
search=false, 32 columns: enter copy & follow · esc clear
search=false, 80 columns: ⌃c copy · enter copy & follow · esc clear
search=true, 12 columns: esc close
search=true, 20 columns: enter older · esc
search=true, 32 columns: enter older · esc close
search=true, 80 columns: enter/⌃p older · ⌃n newer · full transcript · esc close
search=true, 12 columns: esc cancel
search=true, 20 columns: enter accept · esc
search=true, 32 columns: enter accept · esc cancel
search=true, 80 columns: enter accept · ⌃p older · ⌃n newer · full transcript · esc cancel
@@ -16,7 +16,7 @@ searching=false · released empty
searching=true · mouse down
Find:
Searching… · full transcript · esc close
Searching… · full transcript · esc cancel
searching=true · one character
Find:
@@ -24,8 +24,8 @@ Find:
searching=true · back to anchor
Find:
Searching… · full transcript · esc close
Searching… · full transcript · esc cancel
searching=true · released empty
Find:
Searching… · full transcript · esc close
Searching… · full transcript · esc cancel
@@ -6,17 +6,19 @@ Buffer {
area: Rect { x: 0, y: 0, width: 80, height: 2 },
content: [
"Find: Activity ",
"enter/⌃p older · ⌃n newer · full transcript · esc close ",
"enter accept · ⌃p older · ⌃n newer · full transcript · esc cancel ",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 6, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
x: 8, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 17, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
x: 19, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 46, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
x: 49, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 55, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 5, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 15, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
x: 17, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 26, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
x: 28, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 55, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
x: 58, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 65, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}