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,
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct InputStateBackup {
36 pub content: String,
37 pub history: Vec<String>,
38 pub cursor_pos: usize,
39}
40
41impl InputState {
42 pub fn new(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: config.theme.prompt_text.clone(), history_manager: HistoryManager::new(history_config.max_entries),
51 config: config.clone(),
52 command_handler: CommandHandler::new(),
53 keyboard_manager: KeyboardManager::new(),
54 waiting_for_exit_confirmation: false,
55 waiting_for_restart_confirmation: false,
56 }
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;
83 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> {
135 match action {
136 KeyAction::Submit => {
137 self.waiting_for_restart_confirmation = false;
138
139 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
140 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
141
142 match self.content.trim().to_lowercase().as_str() {
143 input if input == confirm_short.to_lowercase() => {
144 self.content.clear();
145 Some("__RESTART__".to_string())
146 }
147 input if input == cancel_short.to_lowercase() => {
148 self.clear_input();
149 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
150 }
151 _ => {
152 self.clear_input();
153 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
154 }
155 }
156 }
157 KeyAction::InsertChar(c) => {
158 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
159 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
160
161 if c.to_lowercase().to_string() == confirm_short.to_lowercase()
162 || c.to_lowercase().to_string() == cancel_short.to_lowercase()
163 {
164 self.content.clear();
165 self.content.push(c);
166 self.cursor.update_text_length(&self.content);
167 self.cursor.move_to_end();
168 }
169 None
170 }
171 KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
172 self.clear_input();
173 None
174 }
175 _ => None,
176 }
177 }
178
179 fn clear_input(&mut self) {
180 self.content.clear();
181 self.history_manager.reset_position();
182 self.cursor.move_to_start();
183 }
184
185 fn handle_history_action(&mut self, action: HistoryAction) -> Option<String> {
186 match action {
187 HistoryAction::NavigatePrevious => {
188 if let Some(entry) = self.history_manager.navigate_previous() {
189 self.content = entry;
190 self.cursor.update_text_length(&self.content);
191 self.cursor.move_to_end();
192 }
193 }
194 HistoryAction::NavigateNext => {
195 if let Some(entry) = self.history_manager.navigate_next() {
196 self.content = entry;
197 self.cursor.update_text_length(&self.content);
198 self.cursor.move_to_end();
199 }
200 }
201 }
202 None
203 }
204
205 fn handle_history_event(&mut self, event: HistoryEvent) -> String {
206 match event {
207 HistoryEvent::Clear => {
208 self.history_manager.clear();
209 HistoryEventHandler::create_clear_response()
210 }
211 HistoryEvent::Add(entry) => {
212 self.history_manager.add_entry(entry);
213 String::new()
214 }
215 _ => String::new(),
216 }
217 }
218
219 pub fn execute(&self) -> crate::core::error::Result<String> {
220 Ok(format!(
221 "__CONFIRM_EXIT__{}",
222 get_translation("system.input.confirm_exit", &[])
223 ))
224 }
225
226 pub fn handle_key_event(&mut self, key: KeyEvent) -> Option<String> {
227 if let Some(history_action) = HistoryKeyboardHandler::get_history_action(&key) {
229 return self.handle_history_action(history_action);
230 }
231
232 if key.code == KeyCode::Esc {
234 return None;
235 }
236
237 let action = self.keyboard_manager.get_action(&key);
239
240 if self.waiting_for_exit_confirmation {
242 return self.handle_exit_confirmation(action);
243 }
244
245 if self.waiting_for_restart_confirmation {
246 return self.handle_restart_confirmation(action);
247 }
248
249 match action {
251 KeyAction::Submit => {
252 if self.content.is_empty() {
253 return None;
254 }
255 if self.validate_input(&self.content).is_ok() {
256 let content = std::mem::take(&mut self.content);
257 self.cursor.reset_for_empty_text();
258 self.history_manager.add_entry(content.clone());
259 let result = self.command_handler.handle_input(&content);
260
261 if let Some(event) = HistoryEventHandler::handle_command_result(&result.message)
262 {
263 return Some(self.handle_history_event(event));
264 }
265
266 if result.message.starts_with("__CONFIRM_EXIT__") {
267 self.waiting_for_exit_confirmation = true;
268 return Some(result.message.replace("__CONFIRM_EXIT__", ""));
269 }
270
271 if result.message.starts_with("__CONFIRM_RESTART__") {
272 self.waiting_for_restart_confirmation = true;
273 return Some(result.message.replace("__CONFIRM_RESTART__", ""));
274 }
275
276 if result.message.starts_with("__RESTART_FORCE__")
277 || result.message.starts_with("__RESTART__")
278 {
279 let feedback_text = if result.message.starts_with("__RESTART_FORCE__") {
280 result
281 .message
282 .replace("__RESTART_FORCE__", "")
283 .trim()
284 .to_string()
285 } else {
286 result.message.replace("__RESTART__", "").trim().to_string()
287 };
288
289 if !feedback_text.is_empty() {
290 return Some(format!("__RESTART_WITH_MSG__{}", feedback_text));
291 } else {
292 return Some("__RESTART__".to_string());
293 }
294 }
295
296 if result.should_exit {
297 return Some(format!("__EXIT__{}", result.message));
298 }
299 return Some(result.message);
300 }
301 None
302 }
303 KeyAction::InsertChar(c) => {
304 if self.content.graphemes(true).count() < self.config.input_max_length {
305 let byte_pos = self.cursor.get_byte_position(&self.content);
306 self.content.insert(byte_pos, c);
307 self.cursor.update_text_length(&self.content);
308 self.cursor.move_right();
309 }
310 None
311 }
312 KeyAction::MoveLeft => {
313 self.cursor.move_left();
314 None
315 }
316 KeyAction::MoveRight => {
317 self.cursor.move_right();
318 None
319 }
320 KeyAction::MoveToStart => {
321 self.cursor.move_to_start();
322 None
323 }
324 KeyAction::MoveToEnd => {
325 self.cursor.move_to_end();
326 None
327 }
328 KeyAction::Backspace => {
329 if self.content.is_empty() || self.cursor.get_position() == 0 {
330 return None;
331 }
332
333 let current_byte_pos = self.cursor.get_byte_position(&self.content);
334 let prev_byte_pos = self.cursor.get_prev_byte_position(&self.content);
335
336 if prev_byte_pos >= current_byte_pos || current_byte_pos > self.content.len() {
337 self.cursor.update_text_length(&self.content);
338 return None;
339 }
340
341 self.cursor.move_left();
342 self.content
343 .replace_range(prev_byte_pos..current_byte_pos, "");
344 self.cursor.update_text_length(&self.content);
345
346 if self.content.is_empty() {
347 self.cursor.reset_for_empty_text();
348 }
349 None
350 }
351 KeyAction::Delete => {
352 let text_length = self.content.graphemes(true).count();
353 if self.cursor.get_position() >= text_length || text_length == 0 {
354 return None;
355 }
356
357 let current_byte_pos = self.cursor.get_byte_position(&self.content);
358 let next_byte_pos = self.cursor.get_next_byte_position(&self.content);
359
360 if current_byte_pos >= next_byte_pos || next_byte_pos > self.content.len() {
361 self.cursor.update_text_length(&self.content);
362 return None;
363 }
364
365 self.content
366 .replace_range(current_byte_pos..next_byte_pos, "");
367 self.cursor.update_text_length(&self.content);
368
369 if self.content.is_empty() {
370 self.cursor.reset_for_empty_text();
371 }
372 None
373 }
374
375 KeyAction::ClearLine
376 | KeyAction::ScrollUp
377 | KeyAction::ScrollDown
378 | KeyAction::PageUp
379 | KeyAction::PageDown
380 | KeyAction::Cancel
381 | KeyAction::Quit
382 | KeyAction::CopySelection
383 | KeyAction::PasteBuffer
384 | KeyAction::NoAction => None,
385 }
386 }
387
388 pub fn export_state(&self) -> InputStateBackup {
389 InputStateBackup {
390 content: self.content.clone(),
391 history: self.history_manager.get_all_entries(),
392 cursor_pos: self.cursor.get_current_position(),
393 }
394 }
395
396 pub fn import_state(&mut self, backup: InputStateBackup) {
397 self.content = backup.content;
398 self.history_manager.import_entries(backup.history);
399 self.cursor.update_text_length(&self.content);
400
401 log::debug!(
402 "✅ InputState imported: {} chars, {} history entries",
403 self.content.len(),
404 self.history_manager.entry_count()
405 );
406 }
407
408 pub fn get_content(&self) -> &str {
409 &self.content
410 }
411
412 pub fn get_history_count(&self) -> usize {
413 self.history_manager.entry_count()
414 }
415}
416
417impl Widget for InputState {
418 fn render(&self) -> Paragraph {
419 let graphemes: Vec<&str> = self.content.graphemes(true).collect();
420 let cursor_pos = self.cursor.get_position();
421 let mut spans = Vec::with_capacity(4);
422
423 spans.push(Span::styled(
425 &self.prompt,
426 Style::default().fg(self.config.theme.prompt_color.into()),
427 ));
428
429 let prompt_width = self.prompt.graphemes(true).count();
430 let available_width = self
431 .config
432 .input_max_length
433 .saturating_sub(prompt_width + 4);
434
435 let viewport_start = if cursor_pos > available_width {
436 cursor_pos - available_width + 10
437 } else {
438 0
439 };
440
441 if cursor_pos > 0 {
442 let visible_text = if viewport_start < cursor_pos {
443 graphemes[viewport_start..cursor_pos].join("")
444 } else {
445 String::new()
446 };
447
448 spans.push(Span::styled(
449 visible_text,
450 Style::default().fg(self.config.theme.input_text.into()),
451 ));
452 }
453
454 let cursor_char = graphemes.get(cursor_pos).map_or(" ", |&c| c);
455 let cursor_style = if self.cursor.is_visible() {
456 Style::default()
457 .fg(self.config.theme.input_text.into())
458 .bg(self.config.theme.cursor.into())
459 } else {
460 Style::default().fg(self.config.theme.input_text.into())
461 };
462 spans.push(Span::styled(cursor_char, cursor_style));
463
464 if cursor_pos < graphemes.len() {
465 let remaining_width = available_width.saturating_sub(cursor_pos - viewport_start);
466 let end_pos = (cursor_pos + 1 + remaining_width).min(graphemes.len());
467
468 if cursor_pos + 1 < end_pos {
469 spans.push(Span::styled(
470 graphemes[cursor_pos + 1..end_pos].join(""),
471 Style::default().fg(self.config.theme.input_text.into()),
472 ));
473 }
474 }
475
476 Paragraph::new(Line::from(spans)).block(
477 Block::default()
478 .padding(Padding::new(3, 1, 1, 1))
479 .borders(Borders::NONE)
480 .style(Style::default().bg(self.config.theme.input_bg.into())),
481 )
482 }
483
484 fn handle_input(&mut self, key: KeyEvent) -> Option<String> {
485 self.handle_key_event(key)
486 }
487
488 fn as_input_state(&mut self) -> Option<&mut dyn InputWidget> {
489 Some(self)
490 }
491
492 fn get_backup_data(&self) -> Option<InputStateBackup> {
493 Some(self.export_state())
494 }
495
496 fn restore_backup_data(&mut self, backup: InputStateBackup) {
497 self.import_state(backup);
498 }
499}
500
501impl InputWidget for InputState {
502 fn update_cursor_blink(&mut self) {
503 self.cursor.update_blink();
504 }
505}