rush_sync_server/input/
state.rs

1// =====================================================
2// SCHRITT 2: DIESER CODE KOMMT IN src/input/state.rs
3// LÖSCHE DEN KOMPLETTEN ALTEN INHALT UND FÜGE DAS HIER EIN:
4// =====================================================
5
6use crate::commands::handler::CommandHandler;
7use crate::commands::history::{
8    HistoryAction, HistoryConfig, HistoryEvent, HistoryEventHandler, HistoryKeyboardHandler,
9    HistoryManager,
10};
11use crate::core::prelude::*;
12use crate::input::keyboard::{KeyAction, KeyboardManager};
13use crate::ui::cursor::{CursorKind, UiCursor};
14use crate::ui::widget::{AnimatedWidget, CursorWidget, StatefulWidget, Widget};
15use ratatui::prelude::*;
16use ratatui::widgets::{Block, Borders, Padding, Paragraph};
17use unicode_segmentation::UnicodeSegmentation;
18use unicode_width::UnicodeWidthStr;
19
20// ✅ ZENTRALER SYSTEM COMMAND PROCESSOR
21#[derive(Debug)]
22pub struct SystemCommandProcessor {
23    pending_confirmation: Option<PendingConfirmation>,
24}
25
26#[derive(Debug, Clone)]
27struct PendingConfirmation {
28    action: SystemAction,
29}
30
31#[derive(Debug, Clone, PartialEq)]
32enum SystemAction {
33    Exit,
34    Restart,
35    ClearHistory,
36}
37
38impl SystemCommandProcessor {
39    pub fn new() -> Self {
40        Self {
41            pending_confirmation: None,
42        }
43    }
44
45    /// ✅ HAUPTFUNKTION: Verarbeite ALLE System-Commands zentral
46    pub fn process_command(&mut self, input: &str) -> SystemCommandResult {
47        // 1️⃣ Prüfe auf System-Commands
48        if let Some(result) = self.handle_system_commands(input) {
49            return result;
50        }
51
52        // 2️⃣ Prüfe auf Bestätigungs-Requests
53        if let Some(result) = self.handle_confirmation_requests(input) {
54            return result;
55        }
56
57        // 3️⃣ Verarbeite Bestätigungen (wenn pending)
58        if self.pending_confirmation.is_some() {
59            return self.handle_user_confirmation(input);
60        }
61
62        // 4️⃣ Kein System-Command
63        SystemCommandResult::NotSystemCommand
64    }
65
66    /// ✅ DIREKTE System-Commands (sofort ausführen)
67    fn handle_system_commands(&mut self, input: &str) -> Option<SystemCommandResult> {
68        match input.trim() {
69            "__CLEAR__" => Some(SystemCommandResult::ClearScreen),
70            "__EXIT__" => Some(SystemCommandResult::Exit),
71            "__RESTART__" | "__RESTART_FORCE__" => Some(SystemCommandResult::Restart),
72            "__CLEAR_HISTORY__" => Some(SystemCommandResult::ClearHistory),
73            _ => None,
74        }
75    }
76
77    /// ✅ Bestätigungs-Requests (zeige Prompt)
78    fn handle_confirmation_requests(&mut self, input: &str) -> Option<SystemCommandResult> {
79        // Exit-Bestätigung
80        if let Some(prompt) = input.strip_prefix("__CONFIRM:__EXIT__") {
81            self.pending_confirmation = Some(PendingConfirmation {
82                action: SystemAction::Exit,
83            });
84            return Some(SystemCommandResult::ShowPrompt(prompt.to_string()));
85        }
86
87        // Restart-Bestätigung
88        if let Some(prompt) = input.strip_prefix("__CONFIRM:__RESTART__") {
89            self.pending_confirmation = Some(PendingConfirmation {
90                action: SystemAction::Restart,
91            });
92            return Some(SystemCommandResult::ShowPrompt(prompt.to_string()));
93        }
94
95        // History-Clear-Bestätigung
96        if let Some(prompt) = input.strip_prefix("__CONFIRM:__CLEAR_HISTORY__") {
97            self.pending_confirmation = Some(PendingConfirmation {
98                action: SystemAction::ClearHistory,
99            });
100            return Some(SystemCommandResult::ShowPrompt(prompt.to_string()));
101        }
102
103        None
104    }
105
106    /// ✅ Benutzer-Bestätigung verarbeiten (j/n)
107    fn handle_user_confirmation(&mut self, input: &str) -> SystemCommandResult {
108        let confirm_key = t!("system.input.confirm.short").to_lowercase();
109        let user_input = input.trim().to_lowercase();
110
111        let result = if user_input == confirm_key {
112            // ✅ BESTÄTIGT - Führe Aktion aus
113            match &self.pending_confirmation.as_ref().unwrap().action {
114                SystemAction::Exit => SystemCommandResult::Exit,
115                SystemAction::Restart => SystemCommandResult::Restart,
116                SystemAction::ClearHistory => SystemCommandResult::ClearHistory,
117            }
118        } else {
119            // ❌ ABGEBROCHEN
120            SystemCommandResult::Message(get_translation("system.input.cancelled", &[]))
121        };
122
123        // Cleanup
124        self.pending_confirmation = None;
125        result
126    }
127
128    /// ✅ HILFSFUNKTION: Ist ein Bestätigungs-Zeichen erlaubt?
129    pub fn is_valid_confirmation_char(&self, c: char) -> bool {
130        if self.pending_confirmation.is_none() {
131            return false;
132        }
133
134        let confirm_char = t!("system.input.confirm.short").to_lowercase();
135        let cancel_char = t!("system.input.cancel.short").to_lowercase();
136        let char_str = c.to_lowercase().to_string();
137
138        [confirm_char, cancel_char].contains(&char_str)
139    }
140
141    /// ✅ STATUS: Warten wir auf Bestätigung?
142    pub fn is_waiting_for_confirmation(&self) -> bool {
143        self.pending_confirmation.is_some()
144    }
145
146    /// ✅ RESET bei Sprach-Wechsel
147    pub fn reset_for_language_change(&mut self) {
148        self.pending_confirmation = None;
149    }
150}
151
152#[derive(Debug, PartialEq)]
153pub enum SystemCommandResult {
154    NotSystemCommand,
155    ClearScreen,
156    Exit,
157    Restart,
158    ClearHistory,
159    ShowPrompt(String),
160    Message(String),
161}
162
163// =====================================================
164// ERWEITERTE InputState MIT ZENTRALER VERARBEITUNG
165// =====================================================
166
167pub struct InputState {
168    content: String,
169    cursor: UiCursor,
170    prompt: String,
171    history_manager: HistoryManager,
172    config: Config,
173    command_handler: CommandHandler,
174    keyboard_manager: KeyboardManager,
175    system_processor: SystemCommandProcessor, // ✅ NEU: Zentrale Verarbeitung
176}
177
178#[derive(Debug, Clone, Default)]
179pub struct InputStateBackup {
180    pub content: String,
181    pub history: Vec<String>,
182    pub cursor_pos: usize,
183}
184
185impl InputState {
186    pub fn new(config: &Config) -> Self {
187        let history_config = HistoryConfig::from_main_config(config);
188        Self {
189            content: String::with_capacity(100),
190            cursor: UiCursor::from_config(config, CursorKind::Input),
191            prompt: config.theme.input_cursor_prefix.clone(),
192            history_manager: HistoryManager::new(history_config.max_entries),
193            config: config.clone(),
194            command_handler: CommandHandler::new(),
195            keyboard_manager: KeyboardManager::new(),
196            system_processor: SystemCommandProcessor::new(), // ✅ NEU
197        }
198    }
199
200    pub fn update_from_config(&mut self, config: &Config) {
201        self.cursor.update_from_config(config);
202        self.prompt = config.theme.input_cursor_prefix.clone();
203        self.config = config.clone();
204    }
205
206    pub fn reset_for_language_change(&mut self) {
207        self.system_processor.reset_for_language_change(); // ✅ ZENTRAL
208        self.clear_input();
209    }
210
211    // ✅ NEUE ZENTRALE FUNKTION: History-Clear
212    pub fn clear_history(&mut self) {
213        self.history_manager.clear();
214    }
215
216    // =====================================================
217    // HAUPTFUNKTION: INPUT HANDLING
218    // =====================================================
219
220    pub fn handle_key_event(&mut self, key: KeyEvent) -> Option<String> {
221        // History navigation
222        if let Some(action) = HistoryKeyboardHandler::get_history_action(&key) {
223            return self.handle_history(action);
224        }
225
226        if key.code == KeyCode::Esc {
227            return None;
228        }
229
230        let action = self.keyboard_manager.get_action(&key);
231
232        // ✅ BESTÄTIGUNGS-MODUS: Nur bestimmte Zeichen erlauben
233        if self.system_processor.is_waiting_for_confirmation() {
234            return self.handle_confirmation_input(action);
235        }
236
237        // ✅ NORMALER MODUS: Alle Aktionen
238        match action {
239            KeyAction::Submit => self.handle_submit(),
240            KeyAction::PasteBuffer => self.handle_paste(),
241            KeyAction::CopySelection => self.handle_copy(),
242            KeyAction::ClearLine => self.handle_clear_line(),
243            KeyAction::InsertChar(c) => {
244                self.insert_char(c);
245                None
246            }
247            KeyAction::MoveLeft => {
248                self.cursor.move_left();
249                None
250            }
251            KeyAction::MoveRight => {
252                self.cursor.move_right();
253                None
254            }
255            KeyAction::MoveToStart => {
256                self.cursor.move_to_start();
257                None
258            }
259            KeyAction::MoveToEnd => {
260                self.cursor.move_to_end();
261                None
262            }
263            KeyAction::Backspace => {
264                self.handle_backspace();
265                None
266            }
267            KeyAction::Delete => {
268                self.handle_delete();
269                None
270            }
271            _ => None,
272        }
273    }
274
275    /// ✅ BESTÄTIGUNGS-INPUT (nur j/n erlaubt)
276    fn handle_confirmation_input(&mut self, action: KeyAction) -> Option<String> {
277        match action {
278            KeyAction::Submit => {
279                let result = self.system_processor.process_command(&self.content);
280                self.clear_input();
281                self.convert_system_result(result)
282            }
283            KeyAction::InsertChar(c) => {
284                if self.system_processor.is_valid_confirmation_char(c) {
285                    self.content.clear();
286                    self.content.push(c);
287                    self.cursor.update_text_length(&self.content);
288                    self.cursor.move_to_end();
289                }
290                None
291            }
292            KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
293                self.clear_input();
294                None
295            }
296            _ => None,
297        }
298    }
299
300    /// ✅ SUBMIT: Zentrale Verarbeitung
301    fn handle_submit(&mut self) -> Option<String> {
302        if self.content.is_empty() || self.content.trim().is_empty() {
303            return None;
304        }
305
306        if self.content.graphemes(true).count() > 1024 {
307            return Some(get_translation("system.input.too_long", &["1024"]));
308        }
309
310        let input = self.content.trim().to_string();
311
312        // 1️⃣ SYSTEM-COMMAND VERARBEITUNG (zentral!)
313        let system_result = self.system_processor.process_command(&input);
314        if system_result != SystemCommandResult::NotSystemCommand {
315            self.clear_input();
316            return self.convert_system_result(system_result);
317        }
318
319        // 2️⃣ NORMALE COMMAND VERARBEITUNG
320        let content = std::mem::take(&mut self.content);
321        self.cursor.reset_for_empty_text();
322        self.history_manager.add_entry(content.clone());
323
324        let result = self.command_handler.handle_input(&content);
325
326        // 3️⃣ HANDLE SPECIAL RESPONSES
327        if let Some(event) = HistoryEventHandler::handle_command_result(&result.message) {
328            return Some(self.handle_history_event(event));
329        }
330
331        // 4️⃣ PRÜFE AUF SYSTEM-RESPONSES
332        let system_result = self.system_processor.process_command(&result.message);
333        if system_result != SystemCommandResult::NotSystemCommand {
334            return self.convert_system_result(system_result);
335        }
336
337        // 5️⃣ STANDARD RESPONSE
338        if result.should_exit {
339            Some(format!("__EXIT__{}", result.message))
340        } else {
341            Some(result.message)
342        }
343    }
344
345    /// ✅ KONVERTIERE System-Result zu String
346    fn convert_system_result(&mut self, result: SystemCommandResult) -> Option<String> {
347        match result {
348            SystemCommandResult::NotSystemCommand => None,
349            SystemCommandResult::ClearScreen => Some("__CLEAR__".to_string()),
350            SystemCommandResult::Exit => Some("__EXIT__".to_string()),
351            SystemCommandResult::Restart => Some("__RESTART__".to_string()),
352            SystemCommandResult::ClearHistory => {
353                self.clear_history(); // ✅ ZENTRAL
354                Some(get_translation("system.input.history_cleared", &[]))
355            }
356            SystemCommandResult::ShowPrompt(prompt) => Some(prompt),
357            SystemCommandResult::Message(msg) => Some(msg),
358        }
359    }
360
361    // =====================================================
362    // BESTEHENDE FUNKTIONEN (unverändert)
363    // =====================================================
364
365    fn handle_history(&mut self, action: HistoryAction) -> Option<String> {
366        let entry = match action {
367            HistoryAction::NavigatePrevious => self.history_manager.navigate_previous(),
368            HistoryAction::NavigateNext => self.history_manager.navigate_next(),
369        };
370
371        if let Some(entry) = entry {
372            self.content = entry;
373            self.cursor.update_text_length(&self.content);
374            self.cursor.move_to_end();
375        }
376        None
377    }
378
379    fn handle_history_event(&mut self, event: HistoryEvent) -> String {
380        match event {
381            HistoryEvent::Clear => {
382                self.clear_history(); // ✅ ZENTRAL
383                HistoryEventHandler::create_clear_response()
384            }
385            HistoryEvent::Add(entry) => {
386                self.history_manager.add_entry(entry);
387                String::new()
388            }
389            _ => String::new(),
390        }
391    }
392
393    // ✅ CLIPBOARD OPERATIONS (unverändert von deinem Code)
394    fn handle_paste(&mut self) -> Option<String> {
395        let text = self.read_clipboard()?;
396        let clean = text
397            .replace(['\n', '\r', '\t'], " ")
398            .chars()
399            .filter(|c| !c.is_control() || *c == ' ')
400            .collect::<String>();
401
402        if clean.is_empty() {
403            return Some(get_translation("system.input.clipboard.empty", &[]));
404        }
405
406        let current_len = self.content.graphemes(true).count();
407        let available = self.config.input_max_length.saturating_sub(current_len);
408        let paste_text = clean.graphemes(true).take(available).collect::<String>();
409
410        if !paste_text.is_empty() {
411            let byte_pos = self.cursor.get_byte_position(&self.content);
412            self.content.insert_str(byte_pos, &paste_text);
413            let chars_added = paste_text.graphemes(true).count();
414            self.cursor.update_text_length(&self.content);
415
416            for _ in 0..chars_added {
417                self.cursor.move_right();
418            }
419            Some(get_translation(
420                "system.input.clipboard.pasted",
421                &[&chars_added.to_string()],
422            ))
423        } else {
424            Some(get_translation(
425                "system.input.clipboard.nothing_to_paste",
426                &[],
427            ))
428        }
429    }
430
431    fn handle_copy(&self) -> Option<String> {
432        if self.content.is_empty() {
433            return Some(get_translation(
434                "system.input.clipboard.nothing_to_copy",
435                &[],
436            ));
437        }
438
439        if self.write_clipboard(&self.content) {
440            let preview = if self.content.chars().count() > 50 {
441                format!("{}...", self.content.chars().take(50).collect::<String>())
442            } else {
443                self.content.clone()
444            };
445            Some(get_translation(
446                "system.input.clipboard.copied",
447                &[&preview],
448            ))
449        } else {
450            Some(get_translation("system.input.clipboard.copy_failed", &[]))
451        }
452    }
453
454    fn handle_clear_line(&mut self) -> Option<String> {
455        if self.content.is_empty() {
456            return None;
457        }
458
459        let result = if self.write_clipboard(&self.content) {
460            let preview = if self.content.chars().count() > 50 {
461                format!("{}...", self.content.chars().take(50).collect::<String>())
462            } else {
463                self.content.clone()
464            };
465            get_translation("system.input.clipboard.cut", &[&preview])
466        } else {
467            get_translation("system.input.clipboard.cleared", &[])
468        };
469
470        self.clear_input();
471        Some(result)
472    }
473
474    // ✅ CLIPBOARD SYSTEM (unverändert)
475    fn read_clipboard(&self) -> Option<String> {
476        let output = self.get_clipboard_cmd("read")?.output().ok()?;
477        let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
478        if text.is_empty() {
479            None
480        } else {
481            Some(text)
482        }
483    }
484
485    fn write_clipboard(&self, text: &str) -> bool {
486        if text.is_empty() {
487            return false;
488        }
489
490        if let Some(mut cmd) = self.get_clipboard_cmd("write") {
491            if let Ok(mut child) = cmd.stdin(std::process::Stdio::piped()).spawn() {
492                if let Some(stdin) = child.stdin.as_mut() {
493                    use std::io::Write;
494                    let _ = stdin.write_all(text.as_bytes());
495                }
496                return child.wait().is_ok();
497            }
498        }
499        false
500    }
501
502    fn get_clipboard_cmd(&self, op: &str) -> Option<std::process::Command> {
503        #[cfg(target_os = "macos")]
504        {
505            Some(std::process::Command::new(if op == "read" {
506                "pbpaste"
507            } else {
508                "pbcopy"
509            }))
510        }
511
512        #[cfg(target_os = "linux")]
513        {
514            let mut cmd = std::process::Command::new("xclip");
515            if op == "read" {
516                cmd.args(["-selection", "clipboard", "-o"]);
517            } else {
518                cmd.args(["-selection", "clipboard"]);
519            }
520            Some(cmd)
521        }
522
523        #[cfg(target_os = "windows")]
524        {
525            if op == "read" {
526                let mut cmd = std::process::Command::new("powershell");
527                cmd.args(["-Command", "Get-Clipboard"]);
528                Some(cmd)
529            } else {
530                None
531            }
532        }
533
534        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
535        None
536    }
537
538    // ✅ TEXT EDITING (unverändert)
539    fn insert_char(&mut self, c: char) {
540        if self.content.graphemes(true).count() < self.config.input_max_length {
541            let byte_pos = self.cursor.get_byte_position(&self.content);
542            self.content.insert(byte_pos, c);
543            self.cursor.update_text_length(&self.content);
544            self.cursor.move_right();
545        }
546    }
547
548    fn handle_backspace(&mut self) {
549        if self.content.is_empty() || self.cursor.get_position() == 0 {
550            return;
551        }
552
553        let current = self.cursor.get_byte_position(&self.content);
554        let prev = self.cursor.get_prev_byte_position(&self.content);
555
556        if prev < current && current <= self.content.len() {
557            self.cursor.move_left();
558            self.content.replace_range(prev..current, "");
559            self.cursor.update_text_length(&self.content);
560
561            if self.content.is_empty() {
562                self.cursor.reset_for_empty_text();
563            }
564        }
565    }
566
567    fn handle_delete(&mut self) {
568        let text_len = self.content.graphemes(true).count();
569        if self.cursor.get_position() >= text_len || text_len == 0 {
570            return;
571        }
572
573        let current = self.cursor.get_byte_position(&self.content);
574        let next = self.cursor.get_next_byte_position(&self.content);
575
576        if current < next && next <= self.content.len() {
577            self.content.replace_range(current..next, "");
578            self.cursor.update_text_length(&self.content);
579
580            if self.content.is_empty() {
581                self.cursor.reset_for_empty_text();
582            }
583        }
584    }
585
586    fn clear_input(&mut self) {
587        self.content.clear();
588        self.history_manager.reset_position();
589        self.cursor.move_to_start();
590    }
591
592    // ✅ GETTERS
593    pub fn get_content(&self) -> &str {
594        &self.content
595    }
596
597    pub fn get_history_count(&self) -> usize {
598        self.history_manager.entry_count()
599    }
600}
601
602// ✅ WIDGET TRAIT IMPLEMENTATIONS (unverändert)
603impl Widget for InputState {
604    fn render(&self) -> Paragraph {
605        self.render_with_cursor().0
606    }
607
608    fn handle_input(&mut self, key: KeyEvent) -> Option<String> {
609        self.handle_key_event(key)
610    }
611}
612
613impl CursorWidget for InputState {
614    fn render_with_cursor(&self) -> (Paragraph, Option<(u16, u16)>) {
615        let graphemes: Vec<&str> = self.content.graphemes(true).collect();
616        let cursor_pos = self.cursor.get_position();
617        let prompt_width = self.prompt.width();
618        let available_width = self
619            .config
620            .input_max_length
621            .saturating_sub(prompt_width + 4);
622
623        // Viewport calculation
624        let viewport_start = if cursor_pos > available_width {
625            cursor_pos - available_width + 10
626        } else {
627            0
628        };
629
630        // Create spans
631        let mut spans = vec![Span::styled(
632            &self.prompt,
633            Style::default().fg(self.config.theme.input_cursor_color.into()),
634        )];
635
636        let end_pos = (viewport_start + available_width).min(graphemes.len());
637        let visible = graphemes
638            .get(viewport_start..end_pos)
639            .unwrap_or(&[])
640            .join("");
641        spans.push(Span::styled(
642            visible,
643            Style::default().fg(self.config.theme.input_text.into()),
644        ));
645
646        let paragraph = Paragraph::new(Line::from(spans)).block(
647            Block::default()
648                .padding(Padding::new(3, 1, 1, 1))
649                .borders(Borders::NONE)
650                .style(Style::default().bg(self.config.theme.input_bg.into())),
651        );
652
653        // Cursor coordinates
654        let cursor_coord = if self.cursor.is_visible() && cursor_pos >= viewport_start {
655            let chars_before = graphemes.get(viewport_start..cursor_pos).unwrap_or(&[]);
656            let visible_width: usize = chars_before
657                .iter()
658                .map(|g| UnicodeWidthStr::width(*g))
659                .sum();
660            Some(((prompt_width + visible_width) as u16, 0u16))
661        } else {
662            None
663        };
664
665        (paragraph, cursor_coord)
666    }
667}
668
669impl StatefulWidget for InputState {
670    fn export_state(&self) -> InputStateBackup {
671        InputStateBackup {
672            content: self.content.clone(),
673            history: self.history_manager.get_all_entries(),
674            cursor_pos: self.cursor.get_current_position(),
675        }
676    }
677
678    fn import_state(&mut self, state: InputStateBackup) {
679        self.content = state.content;
680        self.history_manager.import_entries(state.history);
681        self.cursor.update_text_length(&self.content);
682    }
683}
684
685impl AnimatedWidget for InputState {
686    fn tick(&mut self) {
687        self.cursor.update_blink();
688    }
689}