mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
Refactor shared TUI input routing (#42199)
## What changed - Centralize app-level keymap actions in a shared dispatcher, including raw-output and external-editor chords. - Move read-only transcript input handling into its own module while preserving prompt backtracking and older-history loading. - Add regression coverage for transcript draft isolation, backtrack selection, global chords, and cached history when a thread is unavailable. GitOrigin-RevId: a1465942d1ea6c57c2f54e5e313025aceb9f8510
This commit is contained in:
committed by
copyberry
parent
798833fe97
commit
0b509e930e
@@ -308,42 +308,11 @@ impl App {
|
||||
}
|
||||
|
||||
let app_keymap_shortcuts_available = self.app_keymap_shortcuts_available();
|
||||
|
||||
let side_toggle_bindings = &self.keymap.app.toggle_side_conversation;
|
||||
if app_keymap_shortcuts_available
|
||||
&& (side_toggle_bindings.is_pressed(key_event)
|
||||
|| side_toggle_bindings.contains(&crate::key_hint::ctrl(KeyCode::Char('/')))
|
||||
&& crate::key_hint::ctrl(KeyCode::Char('7')).is_press(key_event))
|
||||
&& self
|
||||
.handle_shared_app_keymap_action(tui, app_server, key_event)
|
||||
.await
|
||||
{
|
||||
if let Err(err) = self.toggle_side_conversation(tui, app_server).await {
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to switch side conversation: {err}"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if app_keymap_shortcuts_available && self.keymap.app.toggle_vim_mode.is_pressed(key_event) {
|
||||
self.chat_widget.toggle_vim_mode_and_notify();
|
||||
return;
|
||||
}
|
||||
|
||||
if app_keymap_shortcuts_available
|
||||
&& self.keymap.app.toggle_fast_mode.is_pressed(key_event)
|
||||
&& self.chat_widget.can_toggle_fast_mode_from_keybinding()
|
||||
{
|
||||
self.chat_widget.toggle_fast_mode_from_ui();
|
||||
return;
|
||||
}
|
||||
|
||||
if app_keymap_shortcuts_available && self.keymap.app.toggle_raw_output.is_pressed(key_event)
|
||||
{
|
||||
let enabled = !self.chat_widget.raw_output_mode();
|
||||
self.apply_raw_output_mode(tui, enabled, /*notify*/ false);
|
||||
return;
|
||||
}
|
||||
|
||||
if app_keymap_shortcuts_available && self.keymap.app.open_agents.is_pressed(key_event) {
|
||||
self.open_agents_overview(app_server);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -356,27 +325,7 @@ impl App {
|
||||
return;
|
||||
}
|
||||
|
||||
if app_keymap_shortcuts_available
|
||||
&& self.keymap.app.open_external_editor.is_pressed(key_event)
|
||||
{
|
||||
// Only launch the external editor if there is no overlay and the bottom pane is not in use.
|
||||
// Note that it can be launched while a task is running to enable editing while the previous turn is ongoing.
|
||||
if self.overlay.is_none()
|
||||
&& self.chat_widget.can_launch_external_editor()
|
||||
&& self.chat_widget.external_editor_state() == ExternalEditorState::Closed
|
||||
{
|
||||
self.request_external_editor_launch(tui);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.chat_widget.has_active_view()
|
||||
&& self
|
||||
.current_displayed_thread_id()
|
||||
.is_some_and(|id| self.thread_unavailable(id))
|
||||
&& !(key_event.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& matches!(key_event.code, KeyCode::Char('c' | 'd')))
|
||||
{
|
||||
if self.should_handle_unavailable_thread_key(key_event) {
|
||||
self.chat_widget.handle_disconnected_key(key_event);
|
||||
return;
|
||||
}
|
||||
@@ -399,22 +348,6 @@ impl App {
|
||||
}
|
||||
|
||||
match key_event {
|
||||
_ if app_keymap_shortcuts_available
|
||||
&& self.keymap.app.clear_terminal.is_pressed(key_event) =>
|
||||
{
|
||||
if !self.chat_widget.can_run_ctrl_l_clear_now() {
|
||||
return;
|
||||
}
|
||||
if let Err(err) = self.clear_terminal_ui(tui, /*redraw_header*/ false) {
|
||||
tracing::warn!(error = %err, "failed to clear terminal UI");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to clear terminal UI: {err}"));
|
||||
} else {
|
||||
self.reset_app_ui_state_after_clear();
|
||||
self.queue_clear_ui_header(tui);
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
}
|
||||
// Enter confirms backtrack when primed + count > 0. Otherwise pass to widget.
|
||||
KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
@@ -447,6 +380,89 @@ impl App {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_shared_app_keymap_action(
|
||||
&mut self,
|
||||
tui: &mut tui::Tui,
|
||||
app_server: &mut AppServerSession,
|
||||
key_event: KeyEvent,
|
||||
) -> bool {
|
||||
let side_toggle_bindings = &self.keymap.app.toggle_side_conversation;
|
||||
if side_toggle_bindings.is_pressed(key_event)
|
||||
|| side_toggle_bindings.contains(&crate::key_hint::ctrl(KeyCode::Char('/')))
|
||||
&& crate::key_hint::ctrl(KeyCode::Char('7')).is_press(key_event)
|
||||
{
|
||||
if let Err(err) = self.toggle_side_conversation(tui, app_server).await {
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to switch side conversation: {err}"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.keymap.app.toggle_vim_mode.is_pressed(key_event) {
|
||||
self.chat_widget.toggle_vim_mode_and_notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.keymap.app.toggle_fast_mode.is_pressed(key_event)
|
||||
&& self.chat_widget.can_toggle_fast_mode_from_keybinding()
|
||||
{
|
||||
self.chat_widget.toggle_fast_mode_from_ui();
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.keymap.app.toggle_raw_output.is_pressed(key_event) {
|
||||
let enabled = !self.chat_widget.raw_output_mode();
|
||||
self.apply_raw_output_mode(tui, enabled, /*notify*/ false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.keymap.app.open_agents.is_pressed(key_event) {
|
||||
self.open_agents_overview(app_server);
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.keymap.app.open_external_editor.is_pressed(key_event) {
|
||||
if self.overlay.is_none()
|
||||
&& self.chat_widget.can_launch_external_editor()
|
||||
&& self.chat_widget.external_editor_state() == ExternalEditorState::Closed
|
||||
{
|
||||
self.request_external_editor_launch(tui);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.keymap.app.clear_terminal.is_pressed(key_event) {
|
||||
// Leave cached history intact and let the unavailable-thread input path handle this key.
|
||||
if self.should_handle_unavailable_thread_key(key_event) {
|
||||
return false;
|
||||
}
|
||||
if !self.chat_widget.can_run_ctrl_l_clear_now() {
|
||||
return true;
|
||||
}
|
||||
if let Err(err) = self.clear_terminal_ui(tui, /*redraw_header*/ false) {
|
||||
tracing::warn!(error = %err, "failed to clear terminal UI");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to clear terminal UI: {err}"));
|
||||
} else {
|
||||
self.reset_app_ui_state_after_clear();
|
||||
self.queue_clear_ui_header(tui);
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn should_handle_unavailable_thread_key(&self, key_event: KeyEvent) -> bool {
|
||||
!self.chat_widget.has_active_view()
|
||||
&& self
|
||||
.current_displayed_thread_id()
|
||||
.is_some_and(|id| self.thread_unavailable(id))
|
||||
&& !(key_event.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& matches!(key_event.code, KeyCode::Char('c' | 'd')))
|
||||
}
|
||||
|
||||
pub(super) fn should_handle_backtrack_esc(&self, key_event: KeyEvent) -> bool {
|
||||
!self.chat_widget.side_conversation_active()
|
||||
&& self.chat_widget.is_normal_backtrack_mode()
|
||||
|
||||
@@ -38,6 +38,8 @@ mod startup;
|
||||
mod stream_animation_tests;
|
||||
#[path = "tests/thread_usage.rs"]
|
||||
mod thread_usage;
|
||||
#[path = "tests/transcript_composer.rs"]
|
||||
mod transcript_composer;
|
||||
#[path = "tests/turn_submission.rs"]
|
||||
mod turn_submission;
|
||||
|
||||
|
||||
@@ -115,6 +115,30 @@ async fn completed_global_chord_reuses_the_existing_action_handler() -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn completed_global_chords_toggle_output_and_request_external_editor() -> Result<()> {
|
||||
let (mut app, mut tui, mut app_server) = chord_app().await?;
|
||||
let config = toml::from_str(
|
||||
"[global]\ntoggle_raw_output = [\"ctrl-x r\"]\nopen_external_editor = [\"ctrl-x e\"]",
|
||||
)?;
|
||||
app.keymap = RuntimeKeymap::from_config(&config).expect("valid global chords");
|
||||
app.chat_widget.apply_keymap_update(config, &app.keymap);
|
||||
|
||||
for key in [ctrl('x'), KeyCode::Char('r').into()] {
|
||||
press(&mut app, &mut tui, &mut app_server, key).await?;
|
||||
}
|
||||
assert!(app.chat_widget.raw_output_mode());
|
||||
|
||||
for key in [ctrl('x'), KeyCode::Char('e').into()] {
|
||||
press(&mut app, &mut tui, &mut app_server, key).await?;
|
||||
}
|
||||
assert_eq!(
|
||||
app.chat_widget.external_editor_state(),
|
||||
super::ExternalEditorState::Requested
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn wrong_second_stroke_passes_through_but_escape_is_consumed() -> Result<()> {
|
||||
let (mut app, mut tui, mut app_server) = chord_app().await?;
|
||||
|
||||
@@ -390,7 +390,19 @@ async fn reconnect_daemon_command_center_after_socket_replacement_without_a_conv
|
||||
let history = drain_history(&mut app, &mut tui, &mut session, &mut events).await?;
|
||||
assert!(history.contains("Cached previous conversation"));
|
||||
|
||||
let content = &history[history.find("Cached previous conversation").unwrap()..];
|
||||
app.handle_tui_event(
|
||||
&mut tui,
|
||||
&mut session,
|
||||
TuiEvent::Key(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL)),
|
||||
)
|
||||
.await?;
|
||||
let preserved_history =
|
||||
drain_history(&mut app, &mut tui, &mut session, &mut events).await?;
|
||||
assert_eq!(preserved_history, history);
|
||||
|
||||
let content = &preserved_history[preserved_history
|
||||
.find("Cached previous conversation")
|
||||
.unwrap()..];
|
||||
assert_snapshot!(
|
||||
"reconnected_unavailable_conversation",
|
||||
format!(
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: tui/src/app/tests/transcript_composer.rs
|
||||
expression: text
|
||||
---
|
||||
/ T R A N S C R I P T / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
|
||||
|
||||
› first
|
||||
|
||||
|
||||
|
||||
› second
|
||||
|
||||
───────────────────────────────────────────────────────────────────────────────────────────── 100% ─
|
||||
↑/↓ to scroll pgup/pgdn to page home/end to jump
|
||||
q to quit esc to edit prev
|
||||
109
codex-rs/tui/src/app/tests/transcript_composer.rs
Normal file
109
codex-rs/tui/src/app/tests/transcript_composer.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
//! Regression coverage for transcript viewer input and prompt selection.
|
||||
//!
|
||||
//! The default-off feature must leave the existing viewer and its draft intact.
|
||||
|
||||
use super::*;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
async fn press_key(
|
||||
app: &mut App,
|
||||
tui: &mut crate::tui::Tui,
|
||||
app_server: &mut AppServerSession,
|
||||
code: KeyCode,
|
||||
) -> Result<()> {
|
||||
app.handle_tui_event(
|
||||
tui,
|
||||
app_server,
|
||||
TuiEvent::Key(KeyEvent::new(code, KeyModifiers::NONE)),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn transcript_flag_off_preserves_viewer_and_backtracking() -> Result<()> {
|
||||
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
|
||||
let keymap_config = toml::from_str("[composer]\nsubmit = [\"ctrl-x enter\"]")?;
|
||||
app.keymap =
|
||||
crate::keymap::RuntimeKeymap::from_config(&keymap_config).expect("valid composer chord");
|
||||
app.chat_widget
|
||||
.apply_keymap_update(keymap_config, &app.keymap);
|
||||
let mut app_server = start_config_write_test_app_server(&app).await?;
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
let session = test_thread_session(ThreadId::new(), app.config.cwd.to_path_buf());
|
||||
app.chat_widget.handle_thread_session(session);
|
||||
app.transcript_cells = ["first", "second"]
|
||||
.map(|message| {
|
||||
Arc::new(UserHistoryCell {
|
||||
message: message.into(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
}) as Arc<dyn HistoryCell>
|
||||
})
|
||||
.to_vec();
|
||||
app.chat_widget
|
||||
.apply_external_edit("preserved draft".into());
|
||||
app.open_transcript_overlay(&mut tui);
|
||||
for event in [
|
||||
TuiEvent::Paste("not composer input".into()),
|
||||
TuiEvent::Key(KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE)),
|
||||
] {
|
||||
app.handle_tui_event(&mut tui, &mut app_server, event)
|
||||
.await?;
|
||||
}
|
||||
assert_eq!(
|
||||
app.chat_widget.composer_text_with_pending(),
|
||||
"preserved draft"
|
||||
);
|
||||
let chord_prefix = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL);
|
||||
app.handle_tui_event(&mut tui, &mut app_server, TuiEvent::Key(chord_prefix))
|
||||
.await?;
|
||||
assert!(!app.key_chord_matcher.is_pending());
|
||||
assert!(!app.backtrack.overlay_preview_active);
|
||||
let area = Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 100, /*height*/ 12,
|
||||
);
|
||||
let mut buffer = ratatui::buffer::Buffer::empty(area);
|
||||
let Some(Overlay::Transcript(overlay)) = &mut app.overlay else {
|
||||
panic!("viewer closed")
|
||||
};
|
||||
overlay.render(area, &mut buffer);
|
||||
let text = buffer
|
||||
.content()
|
||||
.chunks(usize::from(area.width))
|
||||
.map(|row| {
|
||||
row.iter()
|
||||
.map(ratatui::buffer::Cell::symbol)
|
||||
.collect::<String>()
|
||||
.trim_end()
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
insta::assert_snapshot!("transcript_flag_off_viewer", text);
|
||||
for (key, selected) in [
|
||||
(KeyCode::Esc, 1),
|
||||
(KeyCode::Esc, 0),
|
||||
(KeyCode::Right, 1),
|
||||
(KeyCode::Right, 1),
|
||||
] {
|
||||
press_key(&mut app, &mut tui, &mut app_server, key).await?;
|
||||
assert_eq!(app.backtrack.nth_user_message, selected);
|
||||
}
|
||||
press_key(&mut app, &mut tui, &mut app_server, KeyCode::Enter).await?;
|
||||
assert!(app.overlay.is_none());
|
||||
assert!(
|
||||
std::iter::from_fn(|| app_event_rx.try_recv().ok()).any(|event| matches!(
|
||||
event,
|
||||
AppEvent::ForkSessionForPromptEdit {
|
||||
nth_user_message: 1,
|
||||
..
|
||||
}
|
||||
))
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -21,6 +21,8 @@
|
||||
//! `TranscriptOverlay::sync_live_tail`. This preserves the invariant that the overlay reflects
|
||||
//! both committed history and in-flight activity without changing flush or coalescing behavior.
|
||||
|
||||
mod legacy_input;
|
||||
|
||||
use std::any::TypeId;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -93,79 +95,7 @@ impl App {
|
||||
app_server: &mut AppServerSession,
|
||||
event: TuiEvent,
|
||||
) -> Result<bool> {
|
||||
if let TuiEvent::Key(key_event) = &event
|
||||
&& let Some(Overlay::Transcript(overlay)) = self.overlay.as_ref()
|
||||
&& (overlay.should_load_older(*key_event)
|
||||
|| (self.backtrack.overlay_preview_active
|
||||
&& self.backtrack.nth_user_message == 0
|
||||
&& matches!(key_event.code, KeyCode::Esc | KeyCode::Left)
|
||||
&& matches!(key_event.kind, KeyEventKind::Press | KeyEventKind::Repeat)))
|
||||
&& let Some(thread_id) = self.chat_widget.thread_id()
|
||||
&& app_server.has_older_history(thread_id)
|
||||
&& self.request_older_history_page(app_server, thread_id)
|
||||
{
|
||||
if let Some(Overlay::Transcript(overlay)) = self.overlay.as_mut() {
|
||||
overlay.set_history_state(if overlay.should_load_from_start(*key_event) {
|
||||
TranscriptHistoryState::LoadingBeginning
|
||||
} else {
|
||||
TranscriptHistoryState::LoadingOlder
|
||||
});
|
||||
}
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
if self.backtrack.overlay_preview_active {
|
||||
match event {
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Esc,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
}) => {
|
||||
self.overlay_step_backtrack(tui, event)?;
|
||||
Ok(true)
|
||||
}
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Left,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
}) => {
|
||||
self.overlay_step_backtrack(tui, event)?;
|
||||
Ok(true)
|
||||
}
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Right,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
}) => {
|
||||
self.overlay_step_backtrack_forward(tui, event)?;
|
||||
Ok(true)
|
||||
}
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
}) => {
|
||||
self.overlay_confirm_backtrack(tui);
|
||||
Ok(true)
|
||||
}
|
||||
_ => {
|
||||
self.overlay_forward_event(tui, event)?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
} else if let TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Esc,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
// First Esc in transcript overlay: begin backtrack preview at latest user message.
|
||||
self.begin_overlay_backtrack_preview(tui);
|
||||
Ok(true)
|
||||
} else {
|
||||
// Not in backtrack mode: forward events to the overlay widget.
|
||||
self.overlay_forward_event(tui, event)?;
|
||||
Ok(true)
|
||||
}
|
||||
self.handle_legacy_transcript_event(tui, app_server, event)
|
||||
}
|
||||
|
||||
/// Handle global Esc presses for backtracking when no overlay is present.
|
||||
|
||||
74
codex-rs/tui/src/app_backtrack/legacy_input.rs
Normal file
74
codex-rs/tui/src/app_backtrack/legacy_input.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
//! Input routing for the read-only transcript viewer.
|
||||
//!
|
||||
//! Preview selection is shared with the interactive transcript path so both
|
||||
//! modes retain the same prompt navigation and confirmation behavior.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl App {
|
||||
pub(super) fn handle_legacy_transcript_event(
|
||||
&mut self,
|
||||
tui: &mut tui::Tui,
|
||||
app_server: &mut AppServerSession,
|
||||
event: TuiEvent,
|
||||
) -> Result<bool> {
|
||||
if let TuiEvent::Key(key_event) = &event
|
||||
&& let Some(Overlay::Transcript(overlay)) = self.overlay.as_ref()
|
||||
&& (overlay.should_load_older(*key_event)
|
||||
|| (self.backtrack.overlay_preview_active
|
||||
&& self.backtrack.nth_user_message == 0
|
||||
&& matches!(key_event.code, KeyCode::Esc | KeyCode::Left)
|
||||
&& matches!(key_event.kind, KeyEventKind::Press | KeyEventKind::Repeat)))
|
||||
&& let Some(thread_id) = self.chat_widget.thread_id()
|
||||
&& app_server.has_older_history(thread_id)
|
||||
&& self.request_older_history_page(app_server, thread_id)
|
||||
{
|
||||
if let Some(Overlay::Transcript(overlay)) = self.overlay.as_mut() {
|
||||
overlay.set_history_state(if overlay.should_load_from_start(*key_event) {
|
||||
TranscriptHistoryState::LoadingBeginning
|
||||
} else {
|
||||
TranscriptHistoryState::LoadingOlder
|
||||
});
|
||||
}
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
if self.backtrack.overlay_preview_active {
|
||||
return self.handle_backtrack_preview_event(tui, event);
|
||||
}
|
||||
match event {
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Esc,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
}) => self.begin_overlay_backtrack_preview(tui),
|
||||
event => self.overlay_forward_event(tui, event)?,
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(super) fn handle_backtrack_preview_event(
|
||||
&mut self,
|
||||
tui: &mut tui::Tui,
|
||||
event: TuiEvent,
|
||||
) -> Result<bool> {
|
||||
match event {
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Esc | KeyCode::Left,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
}) => self.overlay_step_backtrack(tui, event)?,
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Right,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
}) => self.overlay_step_backtrack_forward(tui, event)?,
|
||||
TuiEvent::Key(KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
}) => self.overlay_confirm_backtrack(tui),
|
||||
event => self.overlay_forward_event(tui, event)?,
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user