rush_sync_server/input/
input.rs

1// =====================================================
2// FILE: src/input/input.rs - COMPLETE FIXED mit t! Makro Support
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 std::process::Command;
17use unicode_segmentation::UnicodeSegmentation;
18
19pub struct InputState {
20    content: String,
21    cursor: UiCursor,
22    prompt: String,
23    history_manager: HistoryManager,
24    config: Config,
25    command_handler: CommandHandler,
26    keyboard_manager: KeyboardManager,
27    waiting_for_exit_confirmation: bool,
28    waiting_for_restart_confirmation: bool,
29}
30
31#[derive(Debug, Clone, Default)]
32pub struct InputStateBackup {
33    pub content: String,
34    pub history: Vec<String>,
35    pub cursor_pos: usize,
36}
37
38impl InputState {
39    pub fn new(config: &Config) -> Self {
40        let history_config = HistoryConfig::from_main_config(config);
41
42        Self {
43            content: String::with_capacity(100),
44            cursor: UiCursor::from_config(config, CursorKind::Input),
45            prompt: config.theme.input_cursor_prefix.clone(),
46            history_manager: HistoryManager::new(history_config.max_entries),
47            config: config.clone(),
48            command_handler: CommandHandler::new(),
49            keyboard_manager: KeyboardManager::new(),
50            waiting_for_exit_confirmation: false,
51            waiting_for_restart_confirmation: false,
52        }
53    }
54
55    pub fn update_from_config(&mut self, config: &Config) {
56        self.cursor.update_from_config(config);
57        self.prompt = config.theme.input_cursor_prefix.clone();
58        self.config = config.clone();
59
60        log::debug!(
61            "βœ… InputState cursor updated via central API: {}",
62            self.cursor.debug_info()
63        );
64    }
65
66    pub fn validate_input(&self, input: &str) -> crate::core::error::Result<()> {
67        if input.trim().is_empty() {
68            return Err(AppError::Validation(t!("system.input.empty")));
69        }
70        let grapheme_count = input.graphemes(true).count();
71        let max_length = 1024;
72
73        if grapheme_count > max_length {
74            return Err(AppError::Validation(t!(
75                "system.input.too_long",
76                &max_length.to_string()
77            )));
78        }
79        Ok(())
80    }
81
82    pub fn reset_for_language_change(&mut self) {
83        self.waiting_for_exit_confirmation = false;
84        self.waiting_for_restart_confirmation = false;
85        self.content.clear();
86        self.history_manager.reset_position();
87        self.cursor.move_to_start();
88        log::debug!("InputState reset for language change");
89    }
90
91    fn handle_exit_confirmation(&mut self, action: KeyAction) -> Option<String> {
92        match action {
93            KeyAction::Submit => {
94                self.waiting_for_exit_confirmation = false;
95                let confirm_short = t!("system.input.confirm.short");
96                let cancel_short = t!("system.input.cancel.short");
97                match self.content.trim().to_lowercase().as_str() {
98                    input if input == confirm_short.to_lowercase() => {
99                        self.content.clear();
100                        Some("__EXIT__".to_string())
101                    }
102                    input if input == cancel_short.to_lowercase() => {
103                        self.clear_input();
104                        Some(t!("system.input.cancelled"))
105                    }
106                    _ => {
107                        self.clear_input();
108                        Some(t!("system.input.cancelled"))
109                    }
110                }
111            }
112            KeyAction::InsertChar(c) => {
113                let confirm_short = t!("system.input.confirm.short");
114                let cancel_short = t!("system.input.cancel.short");
115                if c.to_lowercase().to_string() == confirm_short.to_lowercase()
116                    || c.to_lowercase().to_string() == cancel_short.to_lowercase()
117                {
118                    self.content.clear();
119                    self.content.push(c);
120                    self.cursor.update_text_length(&self.content);
121                    self.cursor.move_to_end();
122                }
123                None
124            }
125            KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
126                self.clear_input();
127                None
128            }
129            _ => None,
130        }
131    }
132
133    fn handle_restart_confirmation(&mut self, action: KeyAction) -> Option<String> {
134        match action {
135            KeyAction::Submit => {
136                self.waiting_for_restart_confirmation = false;
137                let confirm_short = t!("system.input.confirm.short");
138                let cancel_short = t!("system.input.cancel.short");
139                match self.content.trim().to_lowercase().as_str() {
140                    input if input == confirm_short.to_lowercase() => {
141                        self.content.clear();
142                        Some("__RESTART__".to_string())
143                    }
144                    input if input == cancel_short.to_lowercase() => {
145                        self.clear_input();
146                        Some(t!("system.input.cancelled"))
147                    }
148                    _ => {
149                        self.clear_input();
150                        Some(t!("system.input.cancelled"))
151                    }
152                }
153            }
154            KeyAction::InsertChar(c) => {
155                let confirm_short = t!("system.input.confirm.short");
156                let cancel_short = t!("system.input.cancel.short");
157                if c.to_lowercase().to_string() == confirm_short.to_lowercase()
158                    || c.to_lowercase().to_string() == cancel_short.to_lowercase()
159                {
160                    self.content.clear();
161                    self.content.push(c);
162                    self.cursor.update_text_length(&self.content);
163                    self.cursor.move_to_end();
164                }
165                None
166            }
167            KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
168                self.clear_input();
169                None
170            }
171            _ => None,
172        }
173    }
174
175    fn clear_input(&mut self) {
176        self.content.clear();
177        self.history_manager.reset_position();
178        self.cursor.move_to_start();
179    }
180
181    fn handle_history_action(&mut self, action: HistoryAction) -> Option<String> {
182        match action {
183            HistoryAction::NavigatePrevious => {
184                if let Some(entry) = self.history_manager.navigate_previous() {
185                    self.content = entry;
186                    self.cursor.update_text_length(&self.content);
187                    self.cursor.move_to_end();
188                }
189            }
190            HistoryAction::NavigateNext => {
191                if let Some(entry) = self.history_manager.navigate_next() {
192                    self.content = entry;
193                    self.cursor.update_text_length(&self.content);
194                    self.cursor.move_to_end();
195                }
196            }
197        }
198        None
199    }
200
201    fn handle_history_event(&mut self, event: HistoryEvent) -> String {
202        match event {
203            HistoryEvent::Clear => {
204                self.history_manager.clear();
205                HistoryEventHandler::create_clear_response()
206            }
207            HistoryEvent::Add(entry) => {
208                self.history_manager.add_entry(entry);
209                String::new()
210            }
211            _ => String::new(),
212        }
213    }
214
215    pub fn execute(&self) -> crate::core::error::Result<String> {
216        Ok(format!(
217            "__CONFIRM_EXIT__{}",
218            t!("system.input.confirm_exit")
219        ))
220    }
221
222    // βœ… ECHTES CLIPBOARD LESEN (Mac/Linux/Windows)
223    fn read_clipboard(&self) -> Option<String> {
224        #[cfg(target_os = "macos")]
225        {
226            // Mac: pbpaste verwenden
227            match Command::new("pbpaste").output() {
228                Ok(output) => {
229                    let clipboard_text = String::from_utf8_lossy(&output.stdout).to_string();
230                    if !clipboard_text.trim().is_empty() {
231                        log::info!("πŸ“‹ Mac clipboard read: {} chars", clipboard_text.len());
232                        Some(clipboard_text.trim_end_matches('\n').to_string())
233                    } else {
234                        log::debug!("πŸ“‹ Mac clipboard empty");
235                        None
236                    }
237                }
238                Err(e) => {
239                    log::error!("πŸ“‹ Failed to read Mac clipboard: {}", e);
240                    None
241                }
242            }
243        }
244
245        #[cfg(target_os = "linux")]
246        {
247            // Linux: xclip oder xsel verwenden
248            match Command::new("xclip")
249                .args(["-selection", "clipboard", "-o"])
250                .output()
251            {
252                Ok(output) => {
253                    let clipboard_text = String::from_utf8_lossy(&output.stdout).to_string();
254                    if !clipboard_text.trim().is_empty() {
255                        log::info!("πŸ“‹ Linux clipboard read: {} chars", clipboard_text.len());
256                        Some(clipboard_text.trim_end_matches('\n').to_string())
257                    } else {
258                        None
259                    }
260                }
261                Err(_) => {
262                    // Fallback zu xsel
263                    match Command::new("xsel").args(["-b", "-o"]).output() {
264                        Ok(output) => {
265                            let clipboard_text =
266                                String::from_utf8_lossy(&output.stdout).to_string();
267                            Some(clipboard_text.trim_end_matches('\n').to_string())
268                        }
269                        Err(e) => {
270                            log::error!("πŸ“‹ Failed to read Linux clipboard: {}", e);
271                            None
272                        }
273                    }
274                }
275            }
276        }
277
278        #[cfg(target_os = "windows")]
279        {
280            // Windows: PowerShell verwenden
281            match Command::new("powershell")
282                .args(["-Command", "Get-Clipboard"])
283                .output()
284            {
285                Ok(output) => {
286                    let clipboard_text = String::from_utf8_lossy(&output.stdout).to_string();
287                    if !clipboard_text.trim().is_empty() {
288                        log::info!("πŸ“‹ Windows clipboard read: {} chars", clipboard_text.len());
289                        Some(clipboard_text.trim_end_matches('\n').to_string())
290                    } else {
291                        None
292                    }
293                }
294                Err(e) => {
295                    log::error!("πŸ“‹ Failed to read Windows clipboard: {}", e);
296                    None
297                }
298            }
299        }
300
301        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
302        {
303            log::warn!("πŸ“‹ Clipboard not supported on this platform");
304            None
305        }
306    }
307
308    // βœ… ECHTES CLIPBOARD SCHREIBEN
309    fn write_clipboard(&self, text: &str) -> bool {
310        #[cfg(target_os = "macos")]
311        {
312            match Command::new("pbcopy")
313                .stdin(std::process::Stdio::piped())
314                .spawn()
315                .and_then(|mut child| {
316                    use std::io::Write;
317                    if let Some(stdin) = child.stdin.as_mut() {
318                        stdin.write_all(text.as_bytes())?;
319                    }
320                    child.wait().map(|_| ())
321                }) {
322                Ok(_) => {
323                    log::info!("πŸ“‹ Mac clipboard written: {} chars", text.len());
324                    true
325                }
326                Err(e) => {
327                    log::error!("πŸ“‹ Failed to write Mac clipboard: {}", e);
328                    false
329                }
330            }
331        }
332
333        #[cfg(target_os = "linux")]
334        {
335            match Command::new("xclip")
336                .args(["-selection", "clipboard"])
337                .stdin(std::process::Stdio::piped())
338                .spawn()
339                .and_then(|mut child| {
340                    use std::io::Write;
341                    if let Some(stdin) = child.stdin.as_mut() {
342                        stdin.write_all(text.as_bytes())?;
343                    }
344                    child.wait().map(|_| ())
345                }) {
346                Ok(_) => {
347                    log::info!("πŸ“‹ Linux clipboard written: {} chars", text.len());
348                    true
349                }
350                Err(e) => {
351                    log::error!("πŸ“‹ Failed to write Linux clipboard: {}", e);
352                    false
353                }
354            }
355        }
356
357        #[cfg(target_os = "windows")]
358        {
359            match Command::new("powershell")
360                .args(["-Command", &format!("'{}' | Set-Clipboard", text)])
361                .output()
362            {
363                Ok(_) => {
364                    log::info!("πŸ“‹ Windows clipboard written: {} chars", text.len());
365                    true
366                }
367                Err(e) => {
368                    log::error!("πŸ“‹ Failed to write Windows clipboard: {}", e);
369                    false
370                }
371            }
372        }
373
374        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
375        {
376            log::warn!("πŸ“‹ Clipboard write not supported on this platform");
377            false
378        }
379    }
380
381    pub fn handle_key_event(&mut self, key: KeyEvent) -> Option<String> {
382        if let Some(history_action) = HistoryKeyboardHandler::get_history_action(&key) {
383            return self.handle_history_action(history_action);
384        }
385
386        if key.code == KeyCode::Esc {
387            return None;
388        }
389
390        let action = self.keyboard_manager.get_action(&key);
391
392        if self.waiting_for_exit_confirmation {
393            return self.handle_exit_confirmation(action);
394        }
395        if self.waiting_for_restart_confirmation {
396            return self.handle_restart_confirmation(action);
397        }
398
399        // KOMPLETTER match action Block in handle_key_event:
400
401        match action {
402            KeyAction::Submit => {
403                // βœ… KORRIGIERTE DEBUG COMMANDS - alle im gleichen match Block
404                match self.content.trim() {
405                    "cursor-debug" => {
406                        let debug_info = format!(
407                            "🎯 CURSOR COLOR DEBUG:\n\
408                    πŸ“Š Theme: {}\n\
409                    🎨 Expected input_cursor_color: {}\n\
410                    🎨 Actual cursor color: {}\n\
411                    🎨 Actual fg color: {}\n\
412                    πŸ“ Cursor details:\n\
413                    {}",
414                            self.config.current_theme_name,
415                            self.config.theme.input_cursor_color.to_name(),
416                            self.cursor.color.to_name(),
417                            self.cursor.fg.to_name(),
418                            self.cursor.debug_info()
419                        );
420                        self.content.clear();
421                        self.cursor.reset_for_empty_text();
422                        Some(debug_info)
423                    }
424
425                    "theme-config-debug" => {
426                        let debug_info = format!(
427                            "πŸ” COMPLETE THEME CONFIG DEBUG:\n\
428                    πŸ“ Current Theme: {}\n\
429                    🎨 input_cursor_color: {} ⬅️ CONFIG VALUE\n\
430                    🎨 input_cursor: {}\n\
431                    🎨 input_cursor_prefix: '{}'\n\
432                    🎨 output_cursor_color: {}\n\
433                    🎨 output_cursor: {}\n\
434                    \n🎯 ACTUAL CURSOR STATE:\n\
435                    🎨 cursor.color: {} ⬅️ ACTUAL VALUE\n\
436                    🎯 cursor.ctype: {:?}\n\
437                    πŸ‘οΈ cursor.visible: {}",
438                            self.config.current_theme_name,
439                            self.config.theme.input_cursor_color.to_name(),
440                            self.config.theme.input_cursor,
441                            self.config.theme.input_cursor_prefix,
442                            self.config.theme.output_cursor_color.to_name(),
443                            self.config.theme.output_cursor,
444                            self.cursor.color.to_name(),
445                            self.cursor.ctype,
446                            self.cursor.is_visible()
447                        );
448                        self.content.clear();
449                        self.cursor.reset_for_empty_text();
450                        Some(debug_info)
451                    }
452
453                    "color-test" => {
454                        let test_colors = vec![
455                            "Red",
456                            "Green",
457                            "Blue",
458                            "Yellow",
459                            "Magenta",
460                            "Cyan",
461                            "LightRed",
462                            "LightGreen",
463                            "LightBlue",
464                            "LightYellow",
465                            "LightMagenta",
466                            "LightCyan",
467                            "White",
468                            "Black",
469                        ];
470
471                        let mut results = String::from("🎨 COLOR CONVERSION TEST:\n");
472                        for color_name in test_colors {
473                            match crate::ui::color::AppColor::from_string(color_name) {
474                                Ok(color) => {
475                                    results.push_str(&format!(
476                                        "βœ… '{}' β†’ '{}'\n",
477                                        color_name,
478                                        color.to_name()
479                                    ));
480                                }
481                                Err(e) => {
482                                    results
483                                        .push_str(&format!("❌ '{}' β†’ ERROR: {}\n", color_name, e));
484                                }
485                            }
486                        }
487
488                        self.content.clear();
489                        self.cursor.reset_for_empty_text();
490                        Some(results)
491                    }
492
493                    "full-debug" => {
494                        let (_, cursor_pos) = self.render_with_cursor();
495                        let debug_info = format!(
496                            "πŸ” FULL CURSOR DEBUG:\n\
497                    🎨 Config Theme: '{}'\n\
498                    πŸ“ input_cursor: '{}'\n\
499                    🎯 Parsed Type: {:?}\n\
500                    πŸ”€ Symbol: '{}'\n\
501                    πŸ‘οΈ Is Visible: {}\n\
502                    πŸ“ Position: {}\n\
503                    πŸ–₯️ Terminal Pos: {:?}\n\
504                    πŸ”§ Match Block: {}\n\
505                    ⚑ Should Use Terminal: {}",
506                            self.config.current_theme_name,
507                            self.config.theme.input_cursor,
508                            self.cursor.ctype,
509                            self.cursor.get_symbol(),
510                            self.cursor.is_visible(),
511                            self.cursor.get_position(),
512                            cursor_pos,
513                            matches!(self.cursor.ctype, CursorType::Block),
514                            !matches!(self.cursor.ctype, CursorType::Block)
515                        );
516                        self.content.clear();
517                        self.cursor.reset_for_empty_text();
518                        Some(debug_info)
519                    }
520
521                    "term-test" => {
522                        let info = format!(
523                            "πŸ–₯️ TERMINAL INFO:\n\
524                    πŸ“Ί Terminal: {:?}\n\
525                    🎯 Cursor Support: Testing...\n\
526                    πŸ’‘ Try: ESC[?25h (show cursor)\n\
527                    πŸ’‘ Or: Different terminal app",
528                            std::env::var("TERM").unwrap_or_else(|_| "unknown".to_string())
529                        );
530                        self.content.clear();
531                        self.cursor.reset_for_empty_text();
532                        Some(info)
533                    }
534
535                    // βœ… ALLE ANDEREN COMMANDS (nicht-debug)
536                    _ => {
537                        if self.content.is_empty() {
538                            return None;
539                        }
540                        if self.validate_input(&self.content).is_ok() {
541                            let content = std::mem::take(&mut self.content);
542                            self.cursor.reset_for_empty_text();
543                            self.history_manager.add_entry(content.clone());
544                            let result = self.command_handler.handle_input(&content);
545
546                            if let Some(event) =
547                                HistoryEventHandler::handle_command_result(&result.message)
548                            {
549                                return Some(self.handle_history_event(event));
550                            }
551                            if result.message.starts_with("__CONFIRM_EXIT__") {
552                                self.waiting_for_exit_confirmation = true;
553                                return Some(result.message.replace("__CONFIRM_EXIT__", ""));
554                            }
555                            if result.message.starts_with("__CONFIRM_RESTART__") {
556                                self.waiting_for_restart_confirmation = true;
557                                return Some(result.message.replace("__CONFIRM_RESTART__", ""));
558                            }
559                            if result.message.starts_with("__RESTART_FORCE__")
560                                || result.message.starts_with("__RESTART__")
561                            {
562                                let feedback_text =
563                                    if result.message.starts_with("__RESTART_FORCE__") {
564                                        result
565                                            .message
566                                            .replace("__RESTART_FORCE__", "")
567                                            .trim()
568                                            .to_string()
569                                    } else {
570                                        result.message.replace("__RESTART__", "").trim().to_string()
571                                    };
572                                if !feedback_text.is_empty() {
573                                    return Some(format!("__RESTART_WITH_MSG__{}", feedback_text));
574                                } else {
575                                    return Some("__RESTART__".to_string());
576                                }
577                            }
578                            if result.should_exit {
579                                return Some(format!("__EXIT__{}", result.message));
580                            }
581                            return Some(result.message);
582                        }
583                        None
584                    }
585                }
586            }
587
588            KeyAction::InsertChar(c) => {
589                if self.content.graphemes(true).count() < self.config.input_max_length {
590                    let byte_pos = self.cursor.get_byte_position(&self.content);
591                    self.content.insert(byte_pos, c);
592                    self.cursor.update_text_length(&self.content);
593                    self.cursor.move_right();
594                }
595                None
596            }
597
598            // βœ… EINMALIGER ClearLine Handler
599            KeyAction::ClearLine => {
600                if !self.content.is_empty() {
601                    log::info!("🧹 Clearing input line ({} chars)", self.content.len());
602                    self.content.clear();
603                    self.cursor.reset_for_empty_text();
604                    self.history_manager.reset_position();
605                    Some("Input cleared".to_string())
606                } else {
607                    log::debug!("🧹 ClearLine called but input already empty");
608                    None
609                }
610            }
611
612            KeyAction::MoveLeft => {
613                self.cursor.move_left();
614                None
615            }
616            KeyAction::MoveRight => {
617                self.cursor.move_right();
618                None
619            }
620            KeyAction::MoveToStart => {
621                self.cursor.move_to_start();
622                None
623            }
624            KeyAction::MoveToEnd => {
625                self.cursor.move_to_end();
626                None
627            }
628
629            KeyAction::Backspace => {
630                if self.content.is_empty() || self.cursor.get_position() == 0 {
631                    return None;
632                }
633                let current_byte_pos = self.cursor.get_byte_position(&self.content);
634                let prev_byte_pos = self.cursor.get_prev_byte_position(&self.content);
635                if prev_byte_pos >= current_byte_pos || current_byte_pos > self.content.len() {
636                    self.cursor.update_text_length(&self.content);
637                    return None;
638                }
639                self.cursor.move_left();
640                self.content
641                    .replace_range(prev_byte_pos..current_byte_pos, "");
642                self.cursor.update_text_length(&self.content);
643                if self.content.is_empty() {
644                    self.cursor.reset_for_empty_text();
645                }
646                None
647            }
648
649            KeyAction::Delete => {
650                let text_length = self.content.graphemes(true).count();
651                if self.cursor.get_position() >= text_length || text_length == 0 {
652                    return None;
653                }
654                let current_byte_pos = self.cursor.get_byte_position(&self.content);
655                let next_byte_pos = self.cursor.get_next_byte_position(&self.content);
656                if current_byte_pos >= next_byte_pos || next_byte_pos > self.content.len() {
657                    self.cursor.update_text_length(&self.content);
658                    return None;
659                }
660                self.content
661                    .replace_range(current_byte_pos..next_byte_pos, "");
662                self.cursor.update_text_length(&self.content);
663                if self.content.is_empty() {
664                    self.cursor.reset_for_empty_text();
665                }
666                None
667            }
668
669            // βœ… COPY/PASTE IMPLEMENTATION
670            KeyAction::CopySelection => {
671                if !self.content.is_empty() {
672                    if self.write_clipboard(&self.content) {
673                        log::info!("πŸ“‹ Copied to clipboard: '{}'", self.content);
674                        Some(format!("πŸ“‹ Copied: {}", self.content))
675                    } else {
676                        log::error!("πŸ“‹ Failed to copy to clipboard");
677                        Some("❌ Copy failed".to_string())
678                    }
679                } else {
680                    log::debug!("πŸ“‹ Copy called but nothing to copy");
681                    Some("❌ Nothing to copy".to_string())
682                }
683            }
684
685            // βœ… ECHTE PASTE-IMPLEMENTATION
686            KeyAction::PasteBuffer => {
687                log::debug!("πŸ“‹ Paste requested");
688
689                if let Some(clipboard_text) = self.read_clipboard() {
690                    // Clipboard-Text validieren und einfΓΌgen
691                    let sanitized = clipboard_text
692                        .replace('\n', " ") // Newlines zu Spaces
693                        .replace('\r', "") // Carriage returns entfernen
694                        .chars()
695                        .filter(|c| !c.is_control() || *c == ' ') // Nur printable chars + spaces
696                        .collect::<String>();
697
698                    if !sanitized.is_empty() {
699                        // Check gegen max_length
700                        let available_space = self
701                            .config
702                            .input_max_length
703                            .saturating_sub(self.content.graphemes(true).count());
704
705                        let paste_text = if sanitized.graphemes(true).count() > available_space {
706                            // KΓΌrzen wenn zu lang
707                            sanitized
708                                .graphemes(true)
709                                .take(available_space)
710                                .collect::<String>()
711                        } else {
712                            sanitized
713                        };
714
715                        if !paste_text.is_empty() {
716                            // An Cursor-Position einfΓΌgen
717                            let byte_pos = self.cursor.get_byte_position(&self.content);
718                            self.content.insert_str(byte_pos, &paste_text);
719
720                            // Cursor entsprechend bewegen
721                            let chars_added = paste_text.graphemes(true).count();
722                            self.cursor.update_text_length(&self.content);
723                            for _ in 0..chars_added {
724                                self.cursor.move_right();
725                            }
726
727                            log::info!("πŸ“‹ Pasted {} chars at position {}", chars_added, byte_pos);
728                            Some(format!("πŸ“‹ Pasted: {} chars", chars_added))
729                        } else {
730                            Some("❌ Nothing to paste (text too long)".to_string())
731                        }
732                    } else {
733                        Some("❌ Clipboard contains no valid text".to_string())
734                    }
735                } else {
736                    Some("❌ Clipboard empty or inaccessible".to_string())
737                }
738            }
739
740            // Alle anderen Actions als NoAction behandeln
741            KeyAction::ScrollUp
742            | KeyAction::ScrollDown
743            | KeyAction::PageUp
744            | KeyAction::PageDown
745            | KeyAction::Cancel
746            | KeyAction::Quit
747            | KeyAction::NoAction => None,
748        }
749    }
750
751    pub fn export_state(&self) -> InputStateBackup {
752        InputStateBackup {
753            content: self.content.clone(),
754            history: self.history_manager.get_all_entries(),
755            cursor_pos: self.cursor.get_current_position(),
756        }
757    }
758
759    pub fn import_state(&mut self, backup: InputStateBackup) {
760        self.content = backup.content;
761        self.history_manager.import_entries(backup.history);
762        self.cursor.update_text_length(&self.content);
763        log::debug!(
764            "βœ… InputState imported: {} chars, {} history entries",
765            self.content.len(),
766            self.history_manager.entry_count()
767        );
768    }
769
770    pub fn get_content(&self) -> &str {
771        &self.content
772    }
773
774    pub fn get_history_count(&self) -> usize {
775        self.history_manager.entry_count()
776    }
777
778    // =====================================================
779    // βœ… 2-LAYER RENDERING: Text separat, Cursor als Terminal-Cursor
780    // =====================================================
781}
782
783// βœ… SINGLE Widget Implementation - NO DUPLICATES!
784impl Widget for InputState {
785    fn render(&self) -> Paragraph {
786        self.render_with_cursor().0
787    }
788
789    /// βœ… 2-LAYER CURSOR: Text + Terminal-Cursor getrennt!
790    fn render_with_cursor(&self) -> (Paragraph, Option<(u16, u16)>) {
791        use unicode_width::UnicodeWidthStr;
792
793        let graphemes: Vec<&str> = self.content.graphemes(true).collect();
794        let cursor_pos = self.cursor.get_position();
795
796        let prompt_display = self.config.theme.input_cursor_prefix.clone();
797        let prompt_width = prompt_display.width(); // βœ… visuelle Zellenbreite!
798
799        let available_width = self
800            .config
801            .input_max_length
802            .saturating_sub(prompt_width + 4);
803
804        let viewport_start = if cursor_pos > available_width {
805            cursor_pos - available_width + 10
806        } else {
807            0
808        };
809
810        // βœ… LAYER 1: KOMPLETTER TEXT (inklusive Zeichen an Cursor-Position!)
811        let mut spans = Vec::new();
812        spans.push(Span::styled(
813            prompt_display,
814            Style::default().fg(self.config.theme.input_cursor_color.into()),
815        ));
816
817        // βœ… WICHTIG: GANZEN sichtbaren Text rendern (auch an Cursor-Position)
818        let end_pos = (viewport_start + available_width).min(graphemes.len());
819        let visible = graphemes
820            .get(viewport_start..end_pos)
821            .unwrap_or(&[])
822            .join("");
823        spans.push(Span::styled(
824            visible,
825            Style::default().fg(self.config.theme.input_text.into()),
826        ));
827
828        let paragraph = Paragraph::new(Line::from(spans)).block(
829            Block::default()
830                .padding(Padding::new(3, 1, 1, 1))
831                .borders(Borders::NONE)
832                .style(Style::default().bg(self.config.theme.input_bg.into())),
833        );
834
835        // βœ… LAYER 2: Cursor-Koordinate berechnen (OVERLAY ΓΌber existierendem Text!)
836        let cursor_coord = if self.cursor.is_visible() {
837            // βœ… CRITICAL FIX: Cursor ÜBER das Zeichen an cursor_pos legen!
838            let visible_chars_before_cursor = if cursor_pos > viewport_start {
839                // Nur Zeichen VOR dem Cursor zΓ€hlen (nicht bis cursor_pos!)
840                let chars_before = graphemes.get(viewport_start..cursor_pos).unwrap_or(&[]);
841                chars_before
842                    .iter()
843                    .map(|g| UnicodeWidthStr::width(*g))
844                    .sum::<usize>()
845            } else {
846                0
847            };
848
849            // βœ… WICHTIG: rel_x zeigt GENAU auf das Zeichen, wo der Cursor stehen soll
850            let rel_x = (prompt_width + visible_chars_before_cursor) as u16;
851            let rel_y = 0u16;
852
853            log::debug!(
854                "🎯 CURSOR OVERLAY: cursor_pos={}, viewport_start={}, chars_before={}, rel_x={}, prompt_width={}",
855                cursor_pos, viewport_start, visible_chars_before_cursor, rel_x, prompt_width
856            );
857
858            Some((rel_x, rel_y))
859        } else {
860            None // Cursor unsichtbar (Blinken)
861        };
862
863        (paragraph, cursor_coord)
864    }
865
866    fn handle_input(&mut self, key: KeyEvent) -> Option<String> {
867        self.handle_key_event(key)
868    }
869
870    fn as_input_state(&mut self) -> Option<&mut dyn InputWidget> {
871        Some(self)
872    }
873
874    fn get_backup_data(&self) -> Option<InputStateBackup> {
875        Some(self.export_state())
876    }
877
878    fn restore_backup_data(&mut self, backup: InputStateBackup) {
879        self.import_state(backup);
880    }
881}
882
883impl InputWidget for InputState {
884    fn update_cursor_blink(&mut self) {
885        self.cursor.update_blink();
886    }
887}