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