rush_sync_server/input/
input.rs

1// =====================================================
2// FILE: input/input.rs - FINAL VERSION mit RESTART SUPPORT
3// =====================================================
4
5use 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, // nun eigener Clone
27    command_handler: CommandHandler,
28    keyboard_manager: KeyboardManager,
29    waiting_for_exit_confirmation: bool,
30    waiting_for_restart_confirmation: bool, // ← NEU HINZUGEFÜGT
31}
32
33/// ✅ Backup structure für InputState
34#[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(), // Clone statt Referenz
52            command_handler: CommandHandler::new(),
53            keyboard_manager: KeyboardManager::new(),
54            waiting_for_exit_confirmation: false,
55            waiting_for_restart_confirmation: false, // ← NEU HINZUGEFÜGT
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; // ← NEU HINZUGEFÜGT
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    // ✅ NEU: Restart Confirmation Handler
135    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()) // ✅ RESTART auslösen
147                    }
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        // ✅ 1. PRÜFE ZUERST auf History-Actions
229        if let Some(history_action) = HistoryKeyboardHandler::get_history_action(&key) {
230            return self.handle_history_action(history_action);
231        }
232
233        // ✅ 2. ESC wird NICHT hier behandelt - nur in ScreenManager!
234        if key.code == KeyCode::Esc {
235            return None;
236        }
237
238        // ✅ 3. NORMALE Keyboard-Actions (ohne ESC!)
239        let action = self.keyboard_manager.get_action(&key);
240
241        // ✅ 4. CONFIRMATION HANDLING erweitert
242        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        // ✅ 5. NORMALE Eingabeverarbeitung
251        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                    // ✅ KORRIGIERT: Restart Confirmation handling (auch mit zusätzlichem Text)
273                    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                    // ✅ KRITISCH KORRIGIERT: Restart handling - akzeptiert auch __RESTART__ mit zusätzlichem Text
279                    if result.message.starts_with("__RESTART_FORCE__")
280                        || result.message.starts_with("__RESTART__")
281                    // ✅ DIES WAR DAS PROBLEM!
282                    {
283                        // ✅ EXTRAHIERE den Text nach __RESTART__ für User-Feedback
284                        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                        // ✅ ZEIGE FEEDBACK falls vorhanden
295                        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            // ✅ REST bleibt unverändert...
310            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    /// ✅ Import state from backup
405    pub fn import_state(&mut self, backup: InputStateBackup) {
406        // Restore content
407        self.content = backup.content;
408
409        // Restore history
410        self.history_manager.import_entries(backup.history);
411
412        // Restore cursor
413        self.cursor.update_text_length(&self.content);
414        // Note: cursor position will be recalculated by update_text_length
415
416        // Restore prompt if changed
417        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    /// ✅ Get current input content (public getter)
427    pub fn get_content(&self) -> &str {
428        &self.content
429    }
430
431    /// ✅ Get history count (public getter)
432    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    /// ✅ NEU: Backup implementation
512    fn get_backup_data(&self) -> Option<InputStateBackup> {
513        Some(self.export_state())
514    }
515
516    /// ✅ NEU: Restore implementation
517    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}