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