rush_sync_server/input/
input.rs

1// =====================================================
2// FILE: src/input/input.rs - COMPLETE FIXED (Single Widget Impl)
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::{CursorKind, CursorType, UiCursor};
13use crate::ui::widget::{InputWidget, Widget};
14use ratatui::prelude::*;
15use ratatui::widgets::{Block, Borders, Padding, Paragraph};
16use unicode_segmentation::UnicodeSegmentation;
17
18pub struct InputState {
19    content: String,
20    cursor: UiCursor,
21    prompt: String,
22    history_manager: HistoryManager,
23    config: Config,
24    command_handler: CommandHandler,
25    keyboard_manager: KeyboardManager,
26    waiting_for_exit_confirmation: bool,
27    waiting_for_restart_confirmation: bool,
28}
29
30#[derive(Debug, Clone, Default)]
31pub struct InputStateBackup {
32    pub content: String,
33    pub history: Vec<String>,
34    pub cursor_pos: usize,
35}
36
37impl InputState {
38    pub fn new(config: &Config) -> Self {
39        let history_config = HistoryConfig::from_main_config(config);
40
41        Self {
42            content: String::with_capacity(100),
43            cursor: UiCursor::from_config(config, CursorKind::Input),
44            prompt: config.theme.input_cursor_prefix.clone(),
45            history_manager: HistoryManager::new(history_config.max_entries),
46            config: config.clone(),
47            command_handler: CommandHandler::new(),
48            keyboard_manager: KeyboardManager::new(),
49            waiting_for_exit_confirmation: false,
50            waiting_for_restart_confirmation: false,
51        }
52    }
53
54    pub fn update_from_config(&mut self, config: &Config) {
55        self.cursor.update_from_config(config);
56        self.prompt = config.theme.input_cursor_prefix.clone();
57        self.config = config.clone();
58
59        log::debug!(
60            "βœ… InputState cursor updated via central API: {}",
61            self.cursor.debug_info()
62        );
63    }
64
65    pub fn validate_input(&self, input: &str) -> crate::core::error::Result<()> {
66        if input.trim().is_empty() {
67            return Err(AppError::Validation(get_translation(
68                "system.input.empty",
69                &[],
70            )));
71        }
72        let grapheme_count = input.graphemes(true).count();
73        let max_length = 1024;
74
75        if grapheme_count > max_length {
76            return Err(AppError::Validation(get_translation(
77                "system.input.too_long",
78                &[&max_length.to_string()],
79            )));
80        }
81        Ok(())
82    }
83
84    pub fn reset_for_language_change(&mut self) {
85        self.waiting_for_exit_confirmation = false;
86        self.waiting_for_restart_confirmation = false;
87        self.content.clear();
88        self.history_manager.reset_position();
89        self.cursor.move_to_start();
90        log::debug!("InputState reset for language change");
91    }
92
93    fn handle_exit_confirmation(&mut self, action: KeyAction) -> Option<String> {
94        match action {
95            KeyAction::Submit => {
96                self.waiting_for_exit_confirmation = false;
97                let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
98                let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
99                match self.content.trim().to_lowercase().as_str() {
100                    input if input == confirm_short.to_lowercase() => {
101                        self.content.clear();
102                        Some("__EXIT__".to_string())
103                    }
104                    input if input == cancel_short.to_lowercase() => {
105                        self.clear_input();
106                        Some(crate::i18n::get_translation("system.input.cancelled", &[]))
107                    }
108                    _ => {
109                        self.clear_input();
110                        Some(crate::i18n::get_translation("system.input.cancelled", &[]))
111                    }
112                }
113            }
114            KeyAction::InsertChar(c) => {
115                let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
116                let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
117                if c.to_lowercase().to_string() == confirm_short.to_lowercase()
118                    || c.to_lowercase().to_string() == cancel_short.to_lowercase()
119                {
120                    self.content.clear();
121                    self.content.push(c);
122                    self.cursor.update_text_length(&self.content);
123                    self.cursor.move_to_end();
124                }
125                None
126            }
127            KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
128                self.clear_input();
129                None
130            }
131            _ => None,
132        }
133    }
134
135    fn handle_restart_confirmation(&mut self, action: KeyAction) -> Option<String> {
136        match action {
137            KeyAction::Submit => {
138                self.waiting_for_restart_confirmation = false;
139                let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
140                let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
141                match self.content.trim().to_lowercase().as_str() {
142                    input if input == confirm_short.to_lowercase() => {
143                        self.content.clear();
144                        Some("__RESTART__".to_string())
145                    }
146                    input if input == cancel_short.to_lowercase() => {
147                        self.clear_input();
148                        Some(crate::i18n::get_translation("system.input.cancelled", &[]))
149                    }
150                    _ => {
151                        self.clear_input();
152                        Some(crate::i18n::get_translation("system.input.cancelled", &[]))
153                    }
154                }
155            }
156            KeyAction::InsertChar(c) => {
157                let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
158                let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
159                if c.to_lowercase().to_string() == confirm_short.to_lowercase()
160                    || c.to_lowercase().to_string() == cancel_short.to_lowercase()
161                {
162                    self.content.clear();
163                    self.content.push(c);
164                    self.cursor.update_text_length(&self.content);
165                    self.cursor.move_to_end();
166                }
167                None
168            }
169            KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
170                self.clear_input();
171                None
172            }
173            _ => None,
174        }
175    }
176
177    fn clear_input(&mut self) {
178        self.content.clear();
179        self.history_manager.reset_position();
180        self.cursor.move_to_start();
181    }
182
183    fn handle_history_action(&mut self, action: HistoryAction) -> Option<String> {
184        match action {
185            HistoryAction::NavigatePrevious => {
186                if let Some(entry) = self.history_manager.navigate_previous() {
187                    self.content = entry;
188                    self.cursor.update_text_length(&self.content);
189                    self.cursor.move_to_end();
190                }
191            }
192            HistoryAction::NavigateNext => {
193                if let Some(entry) = self.history_manager.navigate_next() {
194                    self.content = entry;
195                    self.cursor.update_text_length(&self.content);
196                    self.cursor.move_to_end();
197                }
198            }
199        }
200        None
201    }
202
203    fn handle_history_event(&mut self, event: HistoryEvent) -> String {
204        match event {
205            HistoryEvent::Clear => {
206                self.history_manager.clear();
207                HistoryEventHandler::create_clear_response()
208            }
209            HistoryEvent::Add(entry) => {
210                self.history_manager.add_entry(entry);
211                String::new()
212            }
213            _ => String::new(),
214        }
215    }
216
217    pub fn execute(&self) -> crate::core::error::Result<String> {
218        Ok(format!(
219            "__CONFIRM_EXIT__{}",
220            get_translation("system.input.confirm_exit", &[])
221        ))
222    }
223
224    pub fn handle_key_event(&mut self, key: KeyEvent) -> Option<String> {
225        if let Some(history_action) = HistoryKeyboardHandler::get_history_action(&key) {
226            return self.handle_history_action(history_action);
227        }
228
229        if key.code == KeyCode::Esc {
230            return None;
231        }
232
233        let action = self.keyboard_manager.get_action(&key);
234
235        if self.waiting_for_exit_confirmation {
236            return self.handle_exit_confirmation(action);
237        }
238        if self.waiting_for_restart_confirmation {
239            return self.handle_restart_confirmation(action);
240        }
241
242        match action {
243            KeyAction::Submit => {
244                if self.content.trim() == "full-debug" {
245                    let (_, cursor_pos) = self.render_with_cursor();
246                    let debug_info = format!(
247                        "πŸ” FULL CURSOR DEBUG:\n\
248                        🎨 Config Theme: '{}'\n\
249                        πŸ“ input_cursor: '{}'\n\
250                        🎯 Parsed Type: {:?}\n\
251                        πŸ”€ Symbol: '{}'\n\
252                        πŸ‘οΈ Is Visible: {}\n\
253                        πŸ“ Position: {}\n\
254                        πŸ–₯️ Terminal Pos: {:?}\n\
255                        πŸ”§ Match Block: {}\n\
256                        ⚑ Should Use Terminal: {}",
257                        self.config.current_theme_name,
258                        self.config.theme.input_cursor,
259                        self.cursor.ctype,
260                        self.cursor.get_symbol(),
261                        self.cursor.is_visible(),
262                        self.cursor.get_position(),
263                        cursor_pos,
264                        matches!(self.cursor.ctype, CursorType::Block),
265                        !matches!(self.cursor.ctype, CursorType::Block)
266                    );
267                    self.content.clear();
268                    self.cursor.reset_for_empty_text();
269                    return Some(debug_info);
270                }
271
272                if self.content.trim() == "term-test" {
273                    let info = format!(
274                        "πŸ–₯️ TERMINAL INFO:\n\
275                        πŸ“Ί Terminal: {:?}\n\
276                        🎯 Cursor Support: Testing...\n\
277                        πŸ’‘ Try: ESC[?25h (show cursor)\n\
278                        πŸ’‘ Or: Different terminal app",
279                        std::env::var("TERM").unwrap_or_else(|_| "unknown".to_string())
280                    );
281                    self.content.clear();
282                    self.cursor.reset_for_empty_text();
283                    return Some(info);
284                }
285
286                if self.content.is_empty() {
287                    return None;
288                }
289                if self.validate_input(&self.content).is_ok() {
290                    let content = std::mem::take(&mut self.content);
291                    self.cursor.reset_for_empty_text();
292                    self.history_manager.add_entry(content.clone());
293                    let result = self.command_handler.handle_input(&content);
294
295                    if let Some(event) = HistoryEventHandler::handle_command_result(&result.message)
296                    {
297                        return Some(self.handle_history_event(event));
298                    }
299                    if result.message.starts_with("__CONFIRM_EXIT__") {
300                        self.waiting_for_exit_confirmation = true;
301                        return Some(result.message.replace("__CONFIRM_EXIT__", ""));
302                    }
303                    if result.message.starts_with("__CONFIRM_RESTART__") {
304                        self.waiting_for_restart_confirmation = true;
305                        return Some(result.message.replace("__CONFIRM_RESTART__", ""));
306                    }
307                    if result.message.starts_with("__RESTART_FORCE__")
308                        || result.message.starts_with("__RESTART__")
309                    {
310                        let feedback_text = if result.message.starts_with("__RESTART_FORCE__") {
311                            result
312                                .message
313                                .replace("__RESTART_FORCE__", "")
314                                .trim()
315                                .to_string()
316                        } else {
317                            result.message.replace("__RESTART__", "").trim().to_string()
318                        };
319                        if !feedback_text.is_empty() {
320                            return Some(format!("__RESTART_WITH_MSG__{}", feedback_text));
321                        } else {
322                            return Some("__RESTART__".to_string());
323                        }
324                    }
325                    if result.should_exit {
326                        return Some(format!("__EXIT__{}", result.message));
327                    }
328                    return Some(result.message);
329                }
330                None
331            }
332            KeyAction::InsertChar(c) => {
333                if self.content.graphemes(true).count() < self.config.input_max_length {
334                    let byte_pos = self.cursor.get_byte_position(&self.content);
335                    self.content.insert(byte_pos, c);
336                    self.cursor.update_text_length(&self.content);
337                    self.cursor.move_right();
338                }
339                None
340            }
341            KeyAction::MoveLeft => {
342                self.cursor.move_left();
343                None
344            }
345            KeyAction::MoveRight => {
346                self.cursor.move_right();
347                None
348            }
349            KeyAction::MoveToStart => {
350                self.cursor.move_to_start();
351                None
352            }
353            KeyAction::MoveToEnd => {
354                self.cursor.move_to_end();
355                None
356            }
357            KeyAction::Backspace => {
358                if self.content.is_empty() || self.cursor.get_position() == 0 {
359                    return None;
360                }
361                let current_byte_pos = self.cursor.get_byte_position(&self.content);
362                let prev_byte_pos = self.cursor.get_prev_byte_position(&self.content);
363                if prev_byte_pos >= current_byte_pos || current_byte_pos > self.content.len() {
364                    self.cursor.update_text_length(&self.content);
365                    return None;
366                }
367                self.cursor.move_left();
368                self.content
369                    .replace_range(prev_byte_pos..current_byte_pos, "");
370                self.cursor.update_text_length(&self.content);
371                if self.content.is_empty() {
372                    self.cursor.reset_for_empty_text();
373                }
374                None
375            }
376            KeyAction::Delete => {
377                let text_length = self.content.graphemes(true).count();
378                if self.cursor.get_position() >= text_length || text_length == 0 {
379                    return None;
380                }
381                let current_byte_pos = self.cursor.get_byte_position(&self.content);
382                let next_byte_pos = self.cursor.get_next_byte_position(&self.content);
383                if current_byte_pos >= next_byte_pos || next_byte_pos > self.content.len() {
384                    self.cursor.update_text_length(&self.content);
385                    return None;
386                }
387                self.content
388                    .replace_range(current_byte_pos..next_byte_pos, "");
389                self.cursor.update_text_length(&self.content);
390                if self.content.is_empty() {
391                    self.cursor.reset_for_empty_text();
392                }
393                None
394            }
395            KeyAction::ClearLine
396            | KeyAction::ScrollUp
397            | KeyAction::ScrollDown
398            | KeyAction::PageUp
399            | KeyAction::PageDown
400            | KeyAction::Cancel
401            | KeyAction::Quit
402            | KeyAction::CopySelection
403            | KeyAction::PasteBuffer
404            | KeyAction::NoAction => None,
405        }
406    }
407
408    pub fn export_state(&self) -> InputStateBackup {
409        InputStateBackup {
410            content: self.content.clone(),
411            history: self.history_manager.get_all_entries(),
412            cursor_pos: self.cursor.get_current_position(),
413        }
414    }
415
416    pub fn import_state(&mut self, backup: InputStateBackup) {
417        self.content = backup.content;
418        self.history_manager.import_entries(backup.history);
419        self.cursor.update_text_length(&self.content);
420        log::debug!(
421            "βœ… InputState imported: {} chars, {} history entries",
422            self.content.len(),
423            self.history_manager.entry_count()
424        );
425    }
426
427    pub fn get_content(&self) -> &str {
428        &self.content
429    }
430
431    pub fn get_history_count(&self) -> usize {
432        self.history_manager.entry_count()
433    }
434
435    // =====================================================
436    // βœ… RENDERING METHODS (IN InputState impl, NICHT Widget trait!)
437    // =====================================================
438
439    /// βœ… FIXED: BLOCK-CURSOR mit korrekter Farbe
440    fn render_block_cursor(
441        &self,
442        spans: &mut Vec<Span<'static>>,
443        graphemes: &[&str],
444        cursor_pos: usize,
445        viewport_start: usize,
446        available_width: usize,
447    ) {
448        // Text vor Cursor
449        if cursor_pos > viewport_start {
450            let visible_text = graphemes[viewport_start..cursor_pos].join("");
451            if !visible_text.is_empty() {
452                spans.push(Span::styled(
453                    visible_text,
454                    Style::default().fg(self.config.theme.input_text.into()),
455                ));
456            }
457        }
458
459        // βœ… ZEICHEN AM CURSOR: Invertiert wenn sichtbar
460        let char_at_cursor = graphemes.get(cursor_pos).copied().unwrap_or(" ");
461        if self.cursor.is_visible() {
462            // Invertierung: Farben tauschen
463            spans.push(Span::styled(
464                char_at_cursor.to_string(),
465                Style::default()
466                    .fg(self.config.theme.input_bg.into()) // Hintergrund wird Vordergrund
467                    .bg(self.config.theme.input_cursor_color.into()), // βœ… FIXED: Richtige Cursor-Farbe
468            ));
469        } else {
470            // Normal: Kein Cursor sichtbar
471            spans.push(Span::styled(
472                char_at_cursor.to_string(),
473                Style::default().fg(self.config.theme.input_text.into()),
474            ));
475        }
476
477        // Text nach Cursor
478        let end_pos = (viewport_start + available_width).min(graphemes.len());
479        if cursor_pos + 1 < end_pos {
480            let remaining_text = graphemes[cursor_pos + 1..end_pos].join("");
481            if !remaining_text.is_empty() {
482                spans.push(Span::styled(
483                    remaining_text,
484                    Style::default().fg(self.config.theme.input_text.into()),
485                ));
486            }
487        }
488    }
489
490    /// βœ… FIXED: TERMINAL-CURSOR - Text komplett normal
491    fn render_normal_text(
492        &self,
493        spans: &mut Vec<Span<'static>>,
494        graphemes: &[&str],
495        viewport_start: usize,
496        available_width: usize,
497    ) {
498        // βœ… WICHTIG: Kompletter Text OHNE Cursor-Symbol
499        // Der Cursor wird spΓ€ter ΓΌber Terminal-Cursor dargestellt
500        let end_pos = (viewport_start + available_width).min(graphemes.len());
501        if viewport_start < end_pos {
502            let visible_text = graphemes[viewport_start..end_pos].join("");
503            if !visible_text.is_empty() {
504                spans.push(Span::styled(
505                    visible_text,
506                    Style::default().fg(self.config.theme.input_text.into()),
507                ));
508            }
509        }
510    }
511}
512
513// βœ… SINGLE Widget Implementation - NO DUPLICATES!
514impl Widget for InputState {
515    fn render(&self) -> Paragraph {
516        self.render_with_cursor().0
517    }
518
519    /// βœ… FIXED: Mit korrektem Layer-System
520    fn render_with_cursor(&self) -> (Paragraph, Option<(u16, u16)>) {
521        let graphemes: Vec<&str> = self.content.graphemes(true).collect();
522        let cursor_pos = self.cursor.get_position();
523        let mut spans = Vec::with_capacity(8);
524
525        // Prompt-Text aus Theme
526        let prompt_display = self.config.theme.input_cursor_prefix.clone();
527        let prompt_width = prompt_display.graphemes(true).count();
528        spans.push(Span::styled(
529            prompt_display,
530            Style::default().fg(self.config.theme.input_cursor_color.into()),
531        ));
532
533        let available_width = self
534            .config
535            .input_max_length
536            .saturating_sub(prompt_width + 4);
537
538        let viewport_start = if cursor_pos > available_width {
539            cursor_pos - available_width + 10
540        } else {
541            0
542        };
543
544        // βœ… FIXED: Cursor-Layer-System - Text bleibt IMMER gleich!
545        match self.cursor.ctype {
546            CursorType::Block => {
547                // BLOCK: Invertiere das Zeichen unter dem Cursor (Original-System)
548                self.render_block_cursor(
549                    &mut spans,
550                    &graphemes,
551                    cursor_pos,
552                    viewport_start,
553                    available_width,
554                );
555                // Kein Terminal-Cursor
556                let paragraph = Paragraph::new(Line::from(spans)).block(
557                    Block::default()
558                        .padding(Padding::new(3, 1, 1, 1))
559                        .borders(Borders::NONE)
560                        .style(Style::default().bg(self.config.theme.input_bg.into())),
561                );
562                (paragraph, None)
563            }
564            _ => {
565                // βœ… PIPE/UNDERSCORE/DEFAULT: Text KOMPLETT normal rendern
566                self.render_normal_text(&mut spans, &graphemes, viewport_start, available_width);
567
568                let paragraph = Paragraph::new(Line::from(spans)).block(
569                    Block::default()
570                        .padding(Padding::new(3, 1, 1, 1))
571                        .borders(Borders::NONE)
572                        .style(Style::default().bg(self.config.theme.input_bg.into())),
573                );
574
575                // βœ… CURSOR AUF SEPARATEM LAYER (Terminal-Cursor)
576                let terminal_cursor_pos = if self.cursor.is_visible() {
577                    let visible_chars_before_cursor = if cursor_pos > viewport_start {
578                        cursor_pos - viewport_start
579                    } else {
580                        0
581                    };
582                    let cursor_x = 3 + prompt_width + visible_chars_before_cursor;
583                    Some((cursor_x as u16, 1))
584                } else {
585                    None // Blinken: Cursor verschwindet
586                };
587
588                (paragraph, terminal_cursor_pos)
589            }
590        }
591    }
592
593    fn handle_input(&mut self, key: KeyEvent) -> Option<String> {
594        self.handle_key_event(key)
595    }
596
597    fn as_input_state(&mut self) -> Option<&mut dyn InputWidget> {
598        Some(self)
599    }
600
601    fn get_backup_data(&self) -> Option<InputStateBackup> {
602        Some(self.export_state())
603    }
604
605    fn restore_backup_data(&mut self, backup: InputStateBackup) {
606        self.import_state(backup);
607    }
608}
609
610impl InputWidget for InputState {
611    fn update_cursor_blink(&mut self) {
612        self.cursor.update_blink();
613    }
614}