1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
use super::state::{AppMode, AppState, ExitAction};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use tui_textarea::{Input, Key};
/// Thin wrapper around AppState for compatibility
/// All state lives in AppState, this just provides the handle_key interface
pub struct App {
pub state: AppState,
}
impl App {
pub fn new() -> Self {
Self {
state: AppState::new(),
}
}
/// Handle a key event. Returns true if render is needed.
pub fn handle_key(&mut self, key: KeyEvent) -> bool {
match self.state.mode {
AppMode::Input => self.handle_input_key(key),
AppMode::Generating => self.handle_generating_key(key),
AppMode::Streaming => self.handle_streaming_key(key),
AppMode::Review => self.handle_review_key(key),
AppMode::Error => self.handle_error_key(key),
}
}
fn handle_input_key(&mut self, key: KeyEvent) -> bool {
// Handle special keys ourselves
match key.code {
KeyCode::Esc => {
self.state.exit(ExitAction::Cancel);
return true;
}
KeyCode::Enter => {
if self.state.input_is_empty() {
self.state.exit(ExitAction::Cancel);
} else {
self.state.start_generating();
}
return true;
}
_ => {}
}
// Delegate all other keys to textarea
// Manually convert crossterm KeyEvent to tui-textarea Input
// (needed due to crossterm version mismatch)
let tui_key = match key.code {
KeyCode::Char(c) => Key::Char(c),
KeyCode::Backspace => Key::Backspace,
KeyCode::Delete => Key::Delete,
KeyCode::Left => Key::Left,
KeyCode::Right => Key::Right,
KeyCode::Up => Key::Up,
KeyCode::Down => Key::Down,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
KeyCode::Tab => Key::Tab,
_ => Key::Null,
};
if tui_key != Key::Null {
let input = Input {
key: tui_key,
ctrl: key.modifiers.contains(KeyModifiers::CONTROL),
alt: key.modifiers.contains(KeyModifiers::ALT),
shift: key.modifiers.contains(KeyModifiers::SHIFT),
};
self.state.textarea.input(input);
}
true
}
fn handle_generating_key(&mut self, key: KeyEvent) -> bool {
match key.code {
KeyCode::Esc => {
self.state.cancel_generation();
true
}
_ => false, // Discard other keys during generation
}
}
fn handle_streaming_key(&mut self, key: KeyEvent) -> bool {
match key.code {
KeyCode::Esc => {
self.state.cancel_streaming();
true
}
_ => false, // Ignore other keys during streaming
}
}
fn handle_review_key(&mut self, key: KeyEvent) -> bool {
match key.code {
KeyCode::Esc => {
self.state.confirmation_pending = false; // Clear confirmation state
self.state.exit(ExitAction::Cancel);
true
}
KeyCode::Enter => {
let cmd = self.state.current_command().map(|c| c.to_string());
if let Some(cmd) = cmd {
if self.state.is_current_command_dangerous() && !self.state.confirmation_pending
{
// First Enter on dangerous command: enter confirmation mode
self.state.confirmation_pending = true;
} else {
// Second Enter (confirmation), or non-dangerous command: execute
self.state.confirmation_pending = false;
self.state.exit(ExitAction::Execute(cmd));
}
}
true
}
KeyCode::Tab => {
let cmd = self.state.current_command().map(|c| c.to_string());
if let Some(cmd) = cmd {
self.state.confirmation_pending = false; // Clear on Tab too
self.state.exit(ExitAction::Insert(cmd));
}
true
}
KeyCode::Char('f') => {
// Changed from 'e' to 'f' for follow-up mode
self.state.confirmation_pending = false; // Clear on follow-up
self.state.start_edit_mode();
true
}
_ => false,
}
}
fn handle_error_key(&mut self, key: KeyEvent) -> bool {
match key.code {
KeyCode::Esc => {
self.state.exit(ExitAction::Cancel);
true
}
KeyCode::Enter | KeyCode::Char('r') => {
self.state.retry();
true
}
_ => false,
}
}
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
|