steer_tui/tui/handlers/
simple.rs

1use crate::error::Result;
2use crate::tui::InputMode;
3use crate::tui::Tui;
4use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
5use std::time::Duration;
6use steer_core::app::AppCommand;
7use tui_textarea::Input;
8
9impl Tui {
10    pub async fn handle_simple_mode(&mut self, key: KeyEvent) -> Result<bool> {
11        // Check for special modes first
12        match self.input_mode {
13            InputMode::BashCommand => return self.handle_bash_mode(key).await,
14            InputMode::AwaitingApproval => return self.handle_approval_mode(key).await,
15            InputMode::EditMessageSelection => return self.handle_edit_selection_mode(key).await,
16            InputMode::FuzzyFinder => return self.handle_fuzzy_finder_mode(key).await,
17            InputMode::ConfirmExit => return self.handle_confirm_exit_mode(key).await,
18            InputMode::Setup => return self.handle_setup_mode(key).await,
19            InputMode::Simple | InputMode::VimInsert | InputMode::VimNormal => {}
20        }
21
22        match key.code {
23            KeyCode::Esc => {
24                // Check for double-tap first
25                if self
26                    .double_tap_tracker
27                    .is_double_tap(KeyCode::Esc, Duration::from_millis(300))
28                {
29                    // Double ESC
30                    let content = self.input_panel_state.content();
31                    if content.is_empty() {
32                        // Empty content - edit previous message
33                        self.enter_edit_selection_mode();
34                    } else {
35                        // Has content - clear it
36                        self.input_panel_state.clear();
37                    }
38                    // Clear to prevent triple-tap
39                    self.double_tap_tracker.clear_key(&KeyCode::Esc);
40                } else {
41                    // Single ESC - cancel operation if processing, otherwise just record for double-tap
42                    self.double_tap_tracker.record_key(KeyCode::Esc);
43                    if self.is_processing {
44                        self.client
45                            .send_command(AppCommand::CancelProcessing)
46                            .await?;
47                    }
48                    // Don't trigger confirm exit - that's only for Ctrl+C
49                }
50            }
51
52            // Multi-line support - handle before regular Enter
53            KeyCode::Enter
54                if key.modifiers.contains(KeyModifiers::SHIFT)
55                    || key.modifiers.contains(KeyModifiers::ALT)
56                    || key.modifiers.contains(KeyModifiers::CONTROL) =>
57            {
58                self.input_panel_state
59                    .handle_input(Input::from(KeyEvent::new(
60                        KeyCode::Char('\n'),
61                        KeyModifiers::empty(),
62                    )));
63            }
64
65            KeyCode::Enter => {
66                let content = self.input_panel_state.content().trim().to_string();
67                if !content.is_empty() {
68                    if content.starts_with('!') && content.len() > 1 {
69                        // Execute as bash command
70                        let command = content[1..].trim().to_string();
71                        self.client
72                            .send_command(AppCommand::ExecuteBashCommand { command })
73                            .await?;
74                    } else if content.starts_with('/') {
75                        // Handle as slash command
76                        self.handle_slash_command(content).await?;
77                    } else {
78                        // Send as normal message
79                        self.send_message(content).await?;
80                    }
81                    self.input_panel_state.clear();
82                }
83            }
84
85            KeyCode::Char('!') => {
86                let content = self.input_panel_state.content();
87                if content.is_empty() {
88                    // First character - enter bash command mode without inserting '!'
89                    self.input_panel_state
90                        .textarea
91                        .set_placeholder_text("Enter bash command...");
92                    self.switch_mode(InputMode::BashCommand);
93                } else {
94                    // Normal ! character
95                    self.input_panel_state.handle_input(Input::from(key));
96                }
97            }
98
99            KeyCode::Char('/') => {
100                let content = self.input_panel_state.content();
101                if content.is_empty() {
102                    // First character - activate command fuzzy finder
103                    self.input_panel_state.handle_input(Input::from(key));
104                    self.input_panel_state.activate_command_fuzzy();
105                    self.switch_mode(InputMode::FuzzyFinder);
106
107                    // Immediately show all commands
108                    let results: Vec<_> = self
109                        .command_registry
110                        .all_commands()
111                        .into_iter()
112                        .map(|cmd| {
113                            crate::tui::widgets::fuzzy_finder::PickerItem::new(
114                                cmd.name.to_string(),
115                                format!("/{} ", cmd.name),
116                            )
117                        })
118                        .collect();
119                    self.input_panel_state.fuzzy_finder.update_results(results);
120                } else {
121                    // Normal / character
122                    self.input_panel_state.handle_input(Input::from(key));
123                }
124            }
125
126            KeyCode::Char('@') => {
127                // Always activate file fuzzy finder
128                self.input_panel_state.handle_input(Input::from(key));
129                self.input_panel_state.activate_fuzzy();
130                self.switch_mode(InputMode::FuzzyFinder);
131
132                // Immediately show all files (limited to 20)
133                let file_results = self
134                    .input_panel_state
135                    .file_cache()
136                    .fuzzy_search("", Some(20))
137                    .await;
138                let picker_items: Vec<_> = file_results
139                    .into_iter()
140                    .map(|path| {
141                        crate::tui::widgets::fuzzy_finder::PickerItem::new(
142                            path.clone(),
143                            format!("@{path} "),
144                        )
145                    })
146                    .collect();
147                self.input_panel_state
148                    .fuzzy_finder
149                    .update_results(picker_items);
150            }
151
152            KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
153                if self.is_processing {
154                    self.client
155                        .send_command(AppCommand::CancelProcessing)
156                        .await?;
157                } else {
158                    self.switch_mode(InputMode::ConfirmExit);
159                }
160            }
161
162            // Toggle view mode with Ctrl+R
163            KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
164                self.chat_viewport.state_mut().toggle_view_mode();
165            }
166            _ => {
167                // Try common text manipulation first
168                if self.handle_text_manipulation(key)? {
169                    return Ok(false);
170                }
171
172                // Normal text input
173                self.input_panel_state.handle_input(Input::from(key));
174
175                // Reset placeholder if needed
176                if self.input_panel_state.content().is_empty() {
177                    self.input_panel_state
178                        .textarea
179                        .set_placeholder_text("Type your message here...");
180                }
181            }
182        }
183
184        Ok(false)
185    }
186}