mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
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:
@@ -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!(
|
||||
(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
+6
-6
@@ -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
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+9
-7
@@ -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,
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user