diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/atuin-ai/Cargo.toml | 2 | ||||
| -rw-r--r-- | crates/atuin-ai/src/tools/descriptor.rs | 2 | ||||
| -rw-r--r-- | crates/atuin-ai/src/tui/components/atuin_ai.rs | 68 |
3 files changed, 44 insertions, 28 deletions
diff --git a/crates/atuin-ai/Cargo.toml b/crates/atuin-ai/Cargo.toml index 3bdd45d2..3be127de 100644 --- a/crates/atuin-ai/Cargo.toml +++ b/crates/atuin-ai/Cargo.toml @@ -45,7 +45,7 @@ async-stream = "0.3" uuid = { workspace = true } tui-textarea-2 = "0.10.2" unicode-width = "0.2" -eye_declare = "0.4.2" +eye_declare = "0.4.3" ratatui-core = "0.1" ratatui-widgets = "0.3" thiserror = { workspace = true } diff --git a/crates/atuin-ai/src/tools/descriptor.rs b/crates/atuin-ai/src/tools/descriptor.rs index 3b2b7ebf..fc44ec10 100644 --- a/crates/atuin-ai/src/tools/descriptor.rs +++ b/crates/atuin-ai/src/tools/descriptor.rs @@ -23,7 +23,7 @@ pub(crate) struct ToolDescriptor { pub(crate) const READ: &ToolDescriptor = &ToolDescriptor { canonical_names: &["read_file"], - capability: Some("client_v1_read"), + capability: Some("client_v1_read_file"), display_verb: "read", progressive_verb: "Reading file...", past_verb: "Read file", diff --git a/crates/atuin-ai/src/tui/components/atuin_ai.rs b/crates/atuin-ai/src/tui/components/atuin_ai.rs index c04ac722..848a001a 100644 --- a/crates/atuin-ai/src/tui/components/atuin_ai.rs +++ b/crates/atuin-ai/src/tui/components/atuin_ai.rs @@ -1,8 +1,9 @@ //! Top-level AtuinAi component that translates key events into AiTuiEvents. //! -//! This component wraps the entire view and handles key events that bubble up -//! from child components (or aren't consumed by them). It maps raw key events -//! to semantic `AiTuiEvent` variants based on the current `AppMode`. +//! Global shortcuts (Ctrl+C, Esc) are handled in the capture phase so they +//! fire regardless of which child is focused. Contextual shortcuts (Enter, +//! Tab) are handled in the bubble phase so child components like the +//! permission Select can consume them first. use std::sync::mpsc; @@ -41,6 +42,7 @@ fn atuin_ai( state.tx = tx.cloned(); }); + // Capture phase: global shortcuts that must fire regardless of child focus. hooks.use_event_capture(move |event, props, state| { let Event::Key(KeyEvent { code, @@ -66,28 +68,53 @@ fn atuin_ai( return EventResult::Consumed; } - match props.mode { - AppMode::Input => match code { - KeyCode::Esc => { + // Esc — always handled at the top level + if *code == KeyCode::Esc { + match props.mode { + AppMode::Input => { if props.has_executing_preview { let _ = tx.send(AiTuiEvent::InterruptToolExecution); - return EventResult::Consumed; - } - - if props.pending_confirmation { + } else if props.pending_confirmation { let _ = tx.send(AiTuiEvent::CancelConfirmation); - return EventResult::Consumed; + } else { + let _ = tx.send(AiTuiEvent::Exit); } - + } + AppMode::Generating | AppMode::Streaming => { + let _ = tx.send(AiTuiEvent::CancelGeneration); + } + AppMode::Error => { let _ = tx.send(AiTuiEvent::Exit); - EventResult::Consumed } + } + return EventResult::Consumed; + } + + EventResult::Ignored + }); + + // Bubble phase: contextual shortcuts that children (e.g. Select) may handle first. + hooks.use_event(move |event, props, state| { + let Event::Key(KeyEvent { + code, + kind: KeyEventKind::Press, + .. + }) = event + else { + return EventResult::Ignored; + }; + + let Some(ref tx) = state.read().tx else { + return EventResult::Ignored; + }; + + match props.mode { + AppMode::Input => match code { KeyCode::Tab => { if props.has_command && props.is_input_blank { let _ = tx.send(AiTuiEvent::InsertCommand); return EventResult::Consumed; } - EventResult::Ignored } KeyCode::Enter => { @@ -95,29 +122,18 @@ fn atuin_ai( let _ = tx.send(AiTuiEvent::ExecuteCommand); return EventResult::Consumed; } - EventResult::Ignored } _ => EventResult::Ignored, }, - AppMode::Generating | AppMode::Streaming => match code { - KeyCode::Esc => { - let _ = tx.send(AiTuiEvent::CancelGeneration); - EventResult::Consumed - } - _ => EventResult::Ignored, - }, AppMode::Error => match code { - KeyCode::Esc => { - let _ = tx.send(AiTuiEvent::Exit); - EventResult::Consumed - } KeyCode::Enter | KeyCode::Char('r') => { let _ = tx.send(AiTuiEvent::Retry); EventResult::Consumed } _ => EventResult::Ignored, }, + _ => EventResult::Ignored, } }); |
