rush_sync_server/input/
input.rs

1// =====================================================
2// FILE: src/input/input.rs - KORRIGIERTE CONSTRUCTOR
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,
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}
40
41impl InputState {
42    // ✅ KORRIGIERTER CONSTRUCTOR - nimmt nur Config
43    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(), // ✅ GEÄNDERT: Von theme.prompt_text
50            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        // ✅ 1. PRÜFE ZUERST auf History-Actions
228        if let Some(history_action) = HistoryKeyboardHandler::get_history_action(&key) {
229            return self.handle_history_action(history_action);
230        }
231
232        // ✅ 2. ESC wird NICHT hier behandelt - nur in ScreenManager!
233        if key.code == KeyCode::Esc {
234            return None;
235        }
236
237        // ✅ 3. NORMALE Keyboard-Actions (ohne ESC!)
238        let action = self.keyboard_manager.get_action(&key);
239
240        // ✅ 4. CONFIRMATION HANDLING erweitert
241        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        // ✅ 5. NORMALE Eingabeverarbeitung
250        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        // ✅ KORRIGIERT: Nutze config.theme.prompt_color
424        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}