1use crate::commands::handler::CommandHandler;
6use crate::commands::history::{
7 HistoryAction, HistoryConfig, HistoryEvent, HistoryEventHandler, HistoryKeyboardHandler,
8 HistoryManager,
9};
10use crate::core::prelude::*;
11use crate::input::keyboard::{KeyAction, KeyboardManager};
12use crate::ui::cursor::CursorState;
13use crate::ui::widget::{InputWidget, Widget};
14use ratatui::{
15 style::Style,
16 text::{Line, Span},
17 widgets::{Block, Borders, Padding, Paragraph},
18};
19use unicode_segmentation::UnicodeSegmentation;
20
21pub struct InputState {
22 content: String,
23 cursor: CursorState,
24 prompt: String,
25 history_manager: HistoryManager,
26 config: Config, command_handler: CommandHandler,
28 keyboard_manager: KeyboardManager,
29 waiting_for_exit_confirmation: bool,
30 waiting_for_restart_confirmation: bool, }
32
33#[derive(Debug, Clone, Default)]
35pub struct InputStateBackup {
36 pub content: String,
37 pub history: Vec<String>,
38 pub cursor_pos: usize,
39 pub prompt: String,
40}
41
42impl InputState {
43 pub fn new(prompt: &str, config: &Config) -> Self {
44 let history_config = HistoryConfig::from_main_config(config);
45
46 Self {
47 content: String::with_capacity(100),
48 cursor: CursorState::new(),
49 prompt: prompt.to_string(),
50 history_manager: HistoryManager::new(history_config.max_entries),
51 config: config.clone(), command_handler: CommandHandler::new(),
53 keyboard_manager: KeyboardManager::new(),
54 waiting_for_exit_confirmation: false,
55 waiting_for_restart_confirmation: false, }
57 }
58
59 pub fn validate_input(&self, input: &str) -> crate::core::error::Result<()> {
60 if input.trim().is_empty() {
61 return Err(AppError::Validation(get_translation(
62 "system.input.empty",
63 &[],
64 )));
65 }
66
67 let grapheme_count = input.graphemes(true).count();
68 let max_length = 1024;
69
70 if grapheme_count > max_length {
71 return Err(AppError::Validation(get_translation(
72 "system.input.too_long",
73 &[&max_length.to_string()],
74 )));
75 }
76
77 Ok(())
78 }
79
80 pub fn reset_for_language_change(&mut self) {
81 self.waiting_for_exit_confirmation = false;
82 self.waiting_for_restart_confirmation = false; self.content.clear();
84 self.history_manager.reset_position();
85 self.cursor.move_to_start();
86 log::debug!("InputState reset for language change");
87 }
88
89 fn handle_exit_confirmation(&mut self, action: KeyAction) -> Option<String> {
90 match action {
91 KeyAction::Submit => {
92 self.waiting_for_exit_confirmation = false;
93
94 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
95 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
96
97 match self.content.trim().to_lowercase().as_str() {
98 input if input == confirm_short.to_lowercase() => {
99 self.content.clear();
100 Some("__EXIT__".to_string())
101 }
102 input if input == cancel_short.to_lowercase() => {
103 self.clear_input();
104 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
105 }
106 _ => {
107 self.clear_input();
108 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
109 }
110 }
111 }
112 KeyAction::InsertChar(c) => {
113 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
114 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
115
116 if c.to_lowercase().to_string() == confirm_short.to_lowercase()
117 || c.to_lowercase().to_string() == cancel_short.to_lowercase()
118 {
119 self.content.clear();
120 self.content.push(c);
121 self.cursor.update_text_length(&self.content);
122 self.cursor.move_to_end();
123 }
124 None
125 }
126 KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
127 self.clear_input();
128 None
129 }
130 _ => None,
131 }
132 }
133
134 fn handle_restart_confirmation(&mut self, action: KeyAction) -> Option<String> {
136 match action {
137 KeyAction::Submit => {
138 self.waiting_for_restart_confirmation = false;
139
140 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
141 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
142
143 match self.content.trim().to_lowercase().as_str() {
144 input if input == confirm_short.to_lowercase() => {
145 self.content.clear();
146 Some("__RESTART__".to_string()) }
148 input if input == cancel_short.to_lowercase() => {
149 self.clear_input();
150 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
151 }
152 _ => {
153 self.clear_input();
154 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
155 }
156 }
157 }
158 KeyAction::InsertChar(c) => {
159 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
160 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
161
162 if c.to_lowercase().to_string() == confirm_short.to_lowercase()
163 || c.to_lowercase().to_string() == cancel_short.to_lowercase()
164 {
165 self.content.clear();
166 self.content.push(c);
167 self.cursor.update_text_length(&self.content);
168 self.cursor.move_to_end();
169 }
170 None
171 }
172 KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
173 self.clear_input();
174 None
175 }
176 _ => None,
177 }
178 }
179
180 fn clear_input(&mut self) {
181 self.content.clear();
182 self.history_manager.reset_position();
183 self.cursor.move_to_start();
184 }
185
186 fn handle_history_action(&mut self, action: HistoryAction) -> Option<String> {
187 match action {
188 HistoryAction::NavigatePrevious => {
189 if let Some(entry) = self.history_manager.navigate_previous() {
190 self.content = entry;
191 self.cursor.update_text_length(&self.content);
192 self.cursor.move_to_end();
193 }
194 }
195 HistoryAction::NavigateNext => {
196 if let Some(entry) = self.history_manager.navigate_next() {
197 self.content = entry;
198 self.cursor.update_text_length(&self.content);
199 self.cursor.move_to_end();
200 }
201 }
202 }
203 None
204 }
205
206 fn handle_history_event(&mut self, event: HistoryEvent) -> String {
207 match event {
208 HistoryEvent::Clear => {
209 self.history_manager.clear();
210 HistoryEventHandler::create_clear_response()
211 }
212 HistoryEvent::Add(entry) => {
213 self.history_manager.add_entry(entry);
214 String::new()
215 }
216 _ => String::new(),
217 }
218 }
219
220 pub fn execute(&self) -> crate::core::error::Result<String> {
221 Ok(format!(
222 "__CONFIRM_EXIT__{}",
223 get_translation("system.input.confirm_exit", &[])
224 ))
225 }
226
227 pub fn handle_key_event(&mut self, key: KeyEvent) -> Option<String> {
228 if let Some(history_action) = HistoryKeyboardHandler::get_history_action(&key) {
230 return self.handle_history_action(history_action);
231 }
232
233 if key.code == KeyCode::Esc {
235 return None;
236 }
237
238 let action = self.keyboard_manager.get_action(&key);
240
241 if self.waiting_for_exit_confirmation {
243 return self.handle_exit_confirmation(action);
244 }
245
246 if self.waiting_for_restart_confirmation {
247 return self.handle_restart_confirmation(action);
248 }
249
250 match action {
252 KeyAction::Submit => {
253 if self.content.is_empty() {
254 return None;
255 }
256 if self.validate_input(&self.content).is_ok() {
257 let content = std::mem::take(&mut self.content);
258 self.cursor.reset_for_empty_text();
259 self.history_manager.add_entry(content.clone());
260 let result = self.command_handler.handle_input(&content);
261
262 if let Some(event) = HistoryEventHandler::handle_command_result(&result.message)
263 {
264 return Some(self.handle_history_event(event));
265 }
266
267 if result.message.starts_with("__CONFIRM_EXIT__") {
268 self.waiting_for_exit_confirmation = true;
269 return Some(result.message.replace("__CONFIRM_EXIT__", ""));
270 }
271
272 if result.message.starts_with("__CONFIRM_RESTART__") {
274 self.waiting_for_restart_confirmation = true;
275 return Some(result.message.replace("__CONFIRM_RESTART__", ""));
276 }
277
278 if result.message.starts_with("__RESTART_FORCE__")
280 || result.message.starts_with("__RESTART__")
281 {
283 let feedback_text = if result.message.starts_with("__RESTART_FORCE__") {
285 result
286 .message
287 .replace("__RESTART_FORCE__", "")
288 .trim()
289 .to_string()
290 } else {
291 result.message.replace("__RESTART__", "").trim().to_string()
292 };
293
294 if !feedback_text.is_empty() {
296 return Some(format!("__RESTART_WITH_MSG__{}", feedback_text));
297 } else {
298 return Some("__RESTART__".to_string());
299 }
300 }
301
302 if result.should_exit {
303 return Some(format!("__EXIT__{}", result.message));
304 }
305 return Some(result.message);
306 }
307 None
308 }
309 KeyAction::InsertChar(c) => {
311 if self.content.graphemes(true).count() < self.config.input_max_length {
312 let byte_pos = self.cursor.get_byte_position(&self.content);
313 self.content.insert(byte_pos, c);
314 self.cursor.update_text_length(&self.content);
315 self.cursor.move_right();
316 }
317 None
318 }
319 KeyAction::MoveLeft => {
320 self.cursor.move_left();
321 None
322 }
323 KeyAction::MoveRight => {
324 self.cursor.move_right();
325 None
326 }
327 KeyAction::MoveToStart => {
328 self.cursor.move_to_start();
329 None
330 }
331 KeyAction::MoveToEnd => {
332 self.cursor.move_to_end();
333 None
334 }
335 KeyAction::Backspace => {
336 if self.content.is_empty() || self.cursor.get_position() == 0 {
337 return None;
338 }
339
340 let current_byte_pos = self.cursor.get_byte_position(&self.content);
341 let prev_byte_pos = self.cursor.get_prev_byte_position(&self.content);
342
343 if prev_byte_pos >= current_byte_pos || current_byte_pos > self.content.len() {
344 self.cursor.update_text_length(&self.content);
345 return None;
346 }
347
348 self.cursor.move_left();
349 self.content
350 .replace_range(prev_byte_pos..current_byte_pos, "");
351 self.cursor.update_text_length(&self.content);
352
353 if self.content.is_empty() {
354 self.cursor.reset_for_empty_text();
355 }
356 None
357 }
358 KeyAction::Delete => {
359 let text_length = self.content.graphemes(true).count();
360 if self.cursor.get_position() >= text_length || text_length == 0 {
361 return None;
362 }
363
364 let current_byte_pos = self.cursor.get_byte_position(&self.content);
365 let next_byte_pos = self.cursor.get_next_byte_position(&self.content);
366
367 if current_byte_pos >= next_byte_pos || next_byte_pos > self.content.len() {
368 self.cursor.update_text_length(&self.content);
369 return None;
370 }
371
372 self.content
373 .replace_range(current_byte_pos..next_byte_pos, "");
374 self.cursor.update_text_length(&self.content);
375
376 if self.content.is_empty() {
377 self.cursor.reset_for_empty_text();
378 }
379 None
380 }
381
382 KeyAction::ClearLine
383 | KeyAction::ScrollUp
384 | KeyAction::ScrollDown
385 | KeyAction::PageUp
386 | KeyAction::PageDown
387 | KeyAction::Cancel
388 | KeyAction::Quit
389 | KeyAction::CopySelection
390 | KeyAction::PasteBuffer
391 | KeyAction::NoAction => None,
392 }
393 }
394
395 pub fn export_state(&self) -> InputStateBackup {
396 InputStateBackup {
397 content: self.content.clone(),
398 history: self.history_manager.get_all_entries(),
399 cursor_pos: self.cursor.get_current_position(),
400 prompt: self.prompt.clone(),
401 }
402 }
403
404 pub fn import_state(&mut self, backup: InputStateBackup) {
406 self.content = backup.content;
408
409 self.history_manager.import_entries(backup.history);
411
412 self.cursor.update_text_length(&self.content);
414 self.prompt = backup.prompt;
418
419 log::debug!(
420 "✅ InputState imported: {} chars, {} history entries",
421 self.content.len(),
422 self.history_manager.entry_count()
423 );
424 }
425
426 pub fn get_content(&self) -> &str {
428 &self.content
429 }
430
431 pub fn get_history_count(&self) -> usize {
433 self.history_manager.entry_count()
434 }
435}
436
437impl Widget for InputState {
438 fn render(&self) -> Paragraph {
439 let graphemes: Vec<&str> = self.content.graphemes(true).collect();
440 let cursor_pos = self.cursor.get_position();
441 let mut spans = Vec::with_capacity(4);
442
443 spans.push(Span::styled(
444 &self.prompt,
445 Style::default().fg(self.config.prompt.color.into()),
446 ));
447
448 let prompt_width = self.prompt.graphemes(true).count();
449 let available_width = self
450 .config
451 .input_max_length
452 .saturating_sub(prompt_width + 4);
453
454 let viewport_start = if cursor_pos > available_width {
455 cursor_pos - available_width + 10
456 } else {
457 0
458 };
459
460 if cursor_pos > 0 {
461 let visible_text = if viewport_start < cursor_pos {
462 graphemes[viewport_start..cursor_pos].join("")
463 } else {
464 String::new()
465 };
466
467 spans.push(Span::styled(
468 visible_text,
469 Style::default().fg(self.config.theme.input_text.into()),
470 ));
471 }
472
473 let cursor_char = graphemes.get(cursor_pos).map_or(" ", |&c| c);
474 let cursor_style = if self.cursor.is_visible() {
475 Style::default()
476 .fg(self.config.theme.input_text.into())
477 .bg(self.config.theme.cursor.into())
478 } else {
479 Style::default().fg(self.config.theme.input_text.into())
480 };
481 spans.push(Span::styled(cursor_char, cursor_style));
482
483 if cursor_pos < graphemes.len() {
484 let remaining_width = available_width.saturating_sub(cursor_pos - viewport_start);
485 let end_pos = (cursor_pos + 1 + remaining_width).min(graphemes.len());
486
487 if cursor_pos + 1 < end_pos {
488 spans.push(Span::styled(
489 graphemes[cursor_pos + 1..end_pos].join(""),
490 Style::default().fg(self.config.theme.input_text.into()),
491 ));
492 }
493 }
494
495 Paragraph::new(Line::from(spans)).block(
496 Block::default()
497 .padding(Padding::new(3, 1, 1, 1))
498 .borders(Borders::NONE)
499 .style(Style::default().bg(self.config.theme.input_bg.into())),
500 )
501 }
502
503 fn handle_input(&mut self, key: KeyEvent) -> Option<String> {
504 self.handle_key_event(key)
505 }
506
507 fn as_input_state(&mut self) -> Option<&mut dyn InputWidget> {
508 Some(self)
509 }
510
511 fn get_backup_data(&self) -> Option<InputStateBackup> {
513 Some(self.export_state())
514 }
515
516 fn restore_backup_data(&mut self, backup: InputStateBackup) {
518 self.import_state(backup);
519 }
520}
521
522impl InputWidget for InputState {
523 fn update_cursor_blink(&mut self) {
524 self.cursor.update_blink();
525 }
526}