1use crate::commands::history::HistoryKeyboardHandler;
6use crate::commands::lang::LanguageService;
7use crate::commands::theme::ThemeSystem;
8use crate::core::prelude::*;
9use crate::input::{
10 input::InputState,
11 keyboard::{KeyAction, KeyboardManager},
12};
13use crate::input::{AppEvent, EventHandler};
14use crate::output::{display::MessageDisplay, logging::AppLogger};
15use crate::ui::{
16 color::AppColor, terminal::TerminalManager, viewport::ScrollDirection, widget::Widget,
17};
18
19use crossterm::event::KeyEvent;
20use ratatui::{backend::CrosstermBackend, Terminal};
21use std::io::{self, Stdout};
22
23pub type TerminalBackend = Terminal<CrosstermBackend<Stdout>>;
24
25use crossterm::execute;
26
27pub struct ScreenManager {
28 terminal: TerminalBackend,
29 message_display: MessageDisplay,
30 input_state: Box<dyn Widget>,
31 config: Config,
32 terminal_mgr: TerminalManager,
33 events: EventHandler,
34 keyboard_manager: KeyboardManager,
35 waiting_for_restart_confirmation: bool,
36}
37
38impl ScreenManager {
39 pub async fn new(config: &Config) -> Result<Self> {
40 let mut terminal_mgr = TerminalManager::new().await?;
41 terminal_mgr.setup().await?;
42
43 let backend = CrosstermBackend::new(io::stdout());
44 let terminal = Terminal::new(backend)?;
45 let size = terminal.size()?;
46
47 let message_display = MessageDisplay::new(config, size.width, size.height);
48
49 log::info!(
51 "🖥️ Screen initialized: {}x{} terminal",
52 size.width,
53 size.height
54 );
55
56 let owned_config = config.clone();
57
58 Ok(Self {
59 terminal,
60 terminal_mgr,
61 message_display,
62 input_state: Box::new(InputState::new(config)),
63 config: owned_config,
64 events: EventHandler::new(config.poll_rate),
65 keyboard_manager: KeyboardManager::new(),
66 waiting_for_restart_confirmation: false,
67 })
68 }
69
70 pub async fn run(&mut self) -> Result<()> {
71 let result = loop {
72 if let Some(event) = self.events.next().await {
73 match event {
74 AppEvent::Input(key) => {
75 if self.handle_input_event(key).await? {
76 self.events.shutdown().await;
77 break Ok(());
78 }
79 }
80 AppEvent::Resize(width, height) => {
81 self.handle_resize_event(width, height).await?;
82 }
83 AppEvent::Tick => {
84 self.handle_tick_event().await?;
85 }
86 }
87 }
88
89 self.process_pending_logs().await;
90 self.render().await?;
91 };
92
93 self.terminal_mgr.cleanup().await?;
94 result
95 }
96
97 async fn handle_input_event(&mut self, key: KeyEvent) -> Result<bool> {
98 if HistoryKeyboardHandler::get_history_action(&key).is_some() {
100 if let Some(new_input) = self.input_state.handle_input(key) {
101 if let Some(processed) = LanguageService::process_save_message(&new_input).await {
102 self.message_display.add_message(processed);
103 return Ok(false);
104 }
105
106 if let Some(processed) = self.process_live_theme_update(&new_input).await {
107 self.message_display.add_message(processed);
108 return Ok(false);
109 }
110
111 self.message_display.add_message(new_input.clone());
112
113 if new_input.starts_with("__CLEAR__") {
114 self.message_display.clear_messages();
115 } else if new_input.starts_with("__EXIT__") {
116 return Ok(true);
117 }
118 }
119 return Ok(false);
120 }
121
122 match self.keyboard_manager.get_action(&key) {
124 KeyAction::ScrollUp => {
125 self.message_display.handle_scroll(ScrollDirection::Up, 1);
126 return Ok(false);
127 }
128 KeyAction::ScrollDown => {
129 self.message_display.handle_scroll(ScrollDirection::Down, 1);
130 return Ok(false);
131 }
132 KeyAction::PageUp => {
133 self.message_display
134 .handle_scroll(ScrollDirection::PageUp, 0);
135 return Ok(false);
136 }
137 KeyAction::PageDown => {
138 self.message_display
139 .handle_scroll(ScrollDirection::PageDown, 0);
140 return Ok(false);
141 }
142 KeyAction::Submit => {
143 if let Some(new_input) = self.input_state.handle_input(key) {
144 let input_command = new_input.trim().to_lowercase();
145 let is_performance_command = input_command == "perf"
146 || input_command == "performance"
147 || input_command == "stats";
148
149 if let Some(processed) = LanguageService::process_save_message(&new_input).await
150 {
151 self.message_display.add_message(processed);
152 return Ok(false);
153 }
154
155 if let Some(processed) = self.process_live_theme_update(&new_input).await {
156 self.message_display.add_message(processed);
157 return Ok(false);
158 }
159
160 self.message_display.add_message(new_input.clone());
161
162 if is_performance_command {
163 log::debug!(
164 "{}",
165 t!("screen.performance_command_detected", &input_command)
166 );
167
168 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
169 self.message_display.viewport_mut().force_auto_scroll();
170
171 log::debug!(
172 "{}",
173 t!("screen.performance_command_viewport_reset_applied")
174 );
175 }
176
177 if new_input.starts_with("__CLEAR__") {
178 self.message_display.clear_messages();
179 } else if new_input.starts_with("__EXIT__") {
180 return Ok(true);
181 } else if new_input.starts_with("__RESTART_WITH_MSG__") {
182 let feedback_msg = new_input
183 .replace("__RESTART_WITH_MSG__", "")
184 .trim()
185 .to_string();
186
187 if !feedback_msg.is_empty() {
188 self.message_display.add_message(feedback_msg);
189 tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
190 }
191
192 if let Err(e) = self.perform_restart().await {
193 self.message_display
194 .add_message(format!("Restart failed: {}", e));
195 }
196 } else if new_input.starts_with("__RESTART_FORCE__")
197 || new_input == "__RESTART__"
198 {
199 if let Err(e) = self.perform_restart().await {
200 self.message_display
201 .add_message(format!("Restart failed: {}", e));
202 }
203 }
204 }
205 }
206 KeyAction::Quit => return Ok(true),
207 _ => {
208 if let Some(new_input) = self.input_state.handle_input(key) {
209 if let Some(processed) = LanguageService::process_save_message(&new_input).await
210 {
211 self.message_display.add_message(processed);
212 return Ok(false);
213 }
214
215 if let Some(processed) = self.process_live_theme_update(&new_input).await {
216 self.message_display.add_message(processed);
217 return Ok(false);
218 }
219
220 self.message_display.add_message(new_input);
221 }
222 }
223 }
224 Ok(false)
225 }
226
227 async fn process_live_theme_update(&mut self, message: &str) -> Option<String> {
229 if !message.starts_with("__LIVE_THEME_UPDATE__") {
230 return None;
231 }
232
233 let parts: Vec<&str> = message.split("__MESSAGE__").collect();
234 if parts.len() != 2 {
235 log::error!("{}", t!("screen.theme.invalid_format"));
236 return None;
237 }
238
239 let theme_part = parts[0].replace("__LIVE_THEME_UPDATE__", "");
240 let display_message = parts[1];
241
242 log::info!(
243 "🎨 LIVE THEME UPDATE STARTING: '{}' → '{}'",
244 self.config.current_theme_name,
245 theme_part
246 );
247
248 let theme_system = match ThemeSystem::load() {
249 Ok(system) => system,
250 Err(e) => {
251 log::error!("{} {}", t!("screen.theme.load_failed"), e);
252 return Some(tc!("screen.theme.failed", &e.to_string()));
253 }
254 };
255
256 if let Some(theme_def) = theme_system.get_theme(&theme_part) {
257 log::info!(
259 "📋 THEME DEFINITION LOADED:\n \
260 input_cursor_prefix: '{}'\n \
261 input_cursor_color: '{}'\n \
262 input_cursor: '{}'\n \
263 output_cursor: '{}'\n \
264 output_cursor_color: '{}'",
265 theme_def.input_cursor_prefix,
266 theme_def.input_cursor_color,
267 theme_def.input_cursor,
268 theme_def.output_cursor,
269 theme_def.output_cursor_color
270 );
271
272 match self.create_theme_from_definition(theme_def) {
273 Ok(new_theme) => {
274 let backup = self.input_state.get_backup_data().unwrap_or_default();
275
276 log::info!(
278 "🔄 THEME CONVERSION COMPLETE:\n \
279 OLD Config: input_cursor='{}', input_cursor_color='{}'\n \
280 NEW Config: input_cursor='{}', input_cursor_color='{}'",
281 self.config.theme.input_cursor,
282 self.config.theme.input_cursor_color.to_name(),
283 new_theme.input_cursor,
284 new_theme.input_cursor_color.to_name()
285 );
286
287 self.message_display.clear_messages();
289
290 self.config.theme = new_theme;
292 self.config.current_theme_name = theme_part.clone();
293
294 self.message_display.update_config(&self.config);
296
297 log::info!("🔄 RECREATING InputState with central cursor API...");
298 self.input_state = Box::new(InputState::new(&self.config));
299
300 if let Some(_input_widget) = self.input_state.as_input_state() {
302 log::info!(
303 "✅ INPUT-CURSOR CREATED:\n \
304 Expected: cursor='{}' (color: {})\n \
305 Theme config: prefix='{}' (color: {})",
306 self.config.theme.input_cursor,
307 self.config.theme.input_cursor_color.to_name(),
308 self.config.theme.input_cursor_prefix,
309 self.config.theme.input_cursor_color.to_name()
310 );
311 }
312
313 self.input_state.restore_backup_data(backup.clone());
314
315 log::info!(
317 "✅ LIVE THEME APPLIED SUCCESSFULLY:\n \
318 theme='{}'\n \
319 prefix='{}'\n \
320 input_cursor='{}'\n \
321 input_cursor_color='{}'\n \
322 output_cursor='{}'\n \
323 output_cursor_color='{}'\n \
324 history={} entries",
325 theme_part.to_uppercase(),
326 self.config.theme.input_cursor_prefix,
327 self.config.theme.input_cursor,
328 self.config.theme.input_cursor_color.to_name(),
329 self.config.theme.output_cursor,
330 self.config.theme.output_cursor_color.to_name(),
331 backup.history.len()
332 );
333
334 Some(display_message.to_string())
335 }
336 Err(e) => {
337 log::error!("{} {}", t!("screen.theme.load_failed"), e);
338 Some(tc!("screen.theme.failed", &e.to_string()))
339 }
340 }
341 } else {
342 log::error!("{} {}", t!("screen.theme.not_found"), theme_part);
343 Some(tc!("screen.theme.not_found_feedback", theme_part.as_str()))
344 }
345 }
346
347 fn create_theme_from_definition(
349 &self,
350 theme_def: &crate::commands::theme::ThemeDefinition,
351 ) -> Result<crate::core::config::Theme> {
352 use crate::ui::color::AppColor;
353
354 log::debug!(
355 "🔧 create_theme_from_definition STARTING:\n \
356 input_cursor_prefix: '{}'\n \
357 input_cursor_color: '{}'\n \
358 input_cursor: '{}'\n \
359 output_cursor: '{}'\n \
360 output_cursor_color: '{}'",
361 theme_def.input_cursor_prefix,
362 theme_def.input_cursor_color,
363 theme_def.input_cursor,
364 theme_def.output_cursor,
365 theme_def.output_cursor_color
366 );
367
368 let input_cursor_color = AppColor::from_string(&theme_def.input_cursor_color)?;
369 let output_cursor_color = AppColor::from_string(&theme_def.output_cursor_color)?;
370
371 log::debug!(
372 "🎨 COLOR CONVERSION COMPLETE:\n \
373 input_cursor_color: '{}' → '{}'\n \
374 output_cursor_color: '{}' → '{}'",
375 theme_def.input_cursor_color,
376 input_cursor_color.to_name(),
377 theme_def.output_cursor_color,
378 output_cursor_color.to_name()
379 );
380
381 Ok(crate::core::config::Theme {
382 input_text: AppColor::from_string(&theme_def.input_text)?,
383 input_bg: AppColor::from_string(&theme_def.input_bg)?,
384 output_text: AppColor::from_string(&theme_def.output_text)?,
385 output_bg: AppColor::from_string(&theme_def.output_bg)?,
386
387 input_cursor_prefix: theme_def.input_cursor_prefix.clone(),
389 input_cursor_color,
390 input_cursor: theme_def.input_cursor.clone(),
391 output_cursor: theme_def.output_cursor.clone(),
392 output_cursor_color,
393 })
394 }
395
396 async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
397 log::info!(
398 "{}",
399 t!(
400 "screen.resize_event",
401 &self
402 .message_display
403 .viewport()
404 .terminal_size()
405 .0
406 .to_string(),
407 &self
408 .message_display
409 .viewport()
410 .terminal_size()
411 .1
412 .to_string(),
413 &width.to_string(),
414 &height.to_string()
415 )
416 );
417
418 let changed = self.message_display.handle_resize(width, height);
419
420 if changed {
421 log::info!(
422 "{}",
423 t!(
424 "screen.resize_completed",
425 &self.message_display.viewport().debug_info()
426 )
427 );
428 }
429
430 Ok(())
431 }
432
433 async fn handle_tick_event(&mut self) -> Result<()> {
434 self.message_display.update_typewriter();
436
437 if let Some(input_state) = self.input_state.as_input_state() {
439 input_state.update_cursor_blink();
440 }
441 Ok(())
442 }
443
444 async fn process_pending_logs(&mut self) {
445 match AppLogger::get_messages() {
446 Ok(messages) => {
447 for log_msg in messages {
448 self.message_display.add_message(log_msg.formatted());
449 }
450 }
451 Err(e) => {
452 self.message_display.add_message(
453 AppColor::from_any("error")
454 .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
455 );
456 }
457 }
458 }
459
460 async fn render(&mut self) -> Result<()> {
462 let (input_widget, cursor_pos) = self.input_state.render_with_cursor();
464
465 self.terminal.draw(|frame| {
466 let size = frame.size();
467
468 if size.width < 10 || size.height < 5 {
469 log::error!(
470 "{}",
471 t!(
472 "screen.render.too_small_log",
473 &size.width.to_string(),
474 &size.height.to_string()
475 )
476 );
477
478 let emergency_area = ratatui::layout::Rect {
479 x: 0,
480 y: 0,
481 width: size.width.max(1),
482 height: size.height.max(1),
483 };
484
485 let emergency_widget =
486 ratatui::widgets::Paragraph::new(t!("screen.render.too_small.text"))
487 .block(ratatui::widgets::Block::default());
488
489 frame.render_widget(emergency_widget, emergency_area);
490 return;
491 }
492
493 let viewport = self.message_display.viewport();
494
495 if !viewport.is_usable() {
496 log::error!("{}", t!("screen.render.viewport_not_usable_log"));
497
498 let error_area = ratatui::layout::Rect {
499 x: 0,
500 y: 0,
501 width: size.width,
502 height: size.height,
503 };
504
505 let error_widget =
506 ratatui::widgets::Paragraph::new(t!("screen.render.viewport_error.text"))
507 .block(ratatui::widgets::Block::default());
508
509 frame.render_widget(error_widget, error_area);
510 return;
511 }
512
513 let output_area = viewport.output_area();
514 let input_area = viewport.input_area();
515
516 if !output_area.is_valid() || !input_area.is_valid() {
517 log::error!(
518 "{}",
519 t!(
520 "screen.render.invalid_layout_log",
521 &output_area.width.to_string(),
522 &output_area.height.to_string(),
523 &output_area.x.to_string(),
524 &output_area.y.to_string(),
525 &input_area.width.to_string(),
526 &input_area.height.to_string(),
527 &input_area.x.to_string(),
528 &input_area.y.to_string()
529 )
530 );
531 return;
532 }
533
534 if output_area.x + output_area.width > size.width
535 || output_area.y + output_area.height > size.height
536 || input_area.x + input_area.width > size.width
537 || input_area.y + input_area.height > size.height
538 {
539 log::error!(
540 "{}",
541 t!(
542 "screen.render.exceed_bounds_log",
543 &size.width.to_string(),
544 &size.height.to_string()
545 )
546 );
547 return;
548 }
549
550 let (visible_messages, config, output_layout, cursor_state) =
552 self.message_display.create_output_widget_for_rendering();
553
554 let message_refs: Vec<(String, usize, bool, bool, bool)> = visible_messages;
555
556 let output_widget = crate::output::display::create_output_widget(
557 &message_refs,
558 output_layout,
559 &config,
560 cursor_state,
561 );
562
563 frame.render_widget(output_widget, output_area.as_rect());
565 frame.render_widget(input_widget, input_area.as_rect());
566
567 if let Some((rel_x, rel_y)) = cursor_pos {
569 let padding_left = 3u16;
571 let padding_top = 1u16;
572
573 let abs_x = input_area.x + padding_left + rel_x;
574 let abs_y = input_area.y + padding_top + rel_y;
575
576 log::debug!(
577 "🎯 TERMINAL CURSOR: abs_pos=({}, {}), input_area=({}, {}), rel_pos=({}, {})",
578 abs_x,
579 abs_y,
580 input_area.x,
581 input_area.y,
582 rel_x,
583 rel_y
584 );
585
586 frame.set_cursor(abs_x, abs_y);
588 }
589 })?;
590
591 if cursor_pos.is_some() {
593 let cursor_commands = self.get_terminal_cursor_commands();
595 for command in cursor_commands {
596 execute!(std::io::stdout(), crossterm::style::Print(command))?;
597 }
598 } else {
599 execute!(
601 std::io::stdout(),
602 crossterm::style::Print("\x1B[?25l") )?;
604 }
605
606 Ok(())
607 }
608
609 fn get_terminal_cursor_commands(&self) -> Vec<String> {
611 let mut commands = Vec::new();
612
613 let form_command = match self.config.theme.input_cursor.to_uppercase().as_str() {
615 "PIPE" => "\x1B[6 q", "UNDERSCORE" => "\x1B[4 q", "BLOCK" => "\x1B[2 q", _ => "\x1B[6 q", };
620 commands.push(form_command.to_string());
621
622 let cursor_color = &self.config.theme.input_cursor_color;
624 let color_commands = self.get_all_cursor_color_sequences(cursor_color);
625 commands.extend(color_commands);
626
627 commands.push("\x1B[?25h".to_string()); log::debug!(
632 "🎨 CURSOR: form='{}', color='{}' → {} commands sent",
633 self.config.theme.input_cursor,
634 cursor_color.to_name(),
635 commands.len()
636 );
637
638 commands
639 }
640
641 fn get_all_cursor_color_sequences(&self, color: &AppColor) -> Vec<String> {
643 let mut sequences = Vec::new();
644 let (r, g, b) = self.get_rgb_values(color);
645
646 sequences.push(format!("\x1B]Pl{:02x}{:02x}{:02x}\x1B\\", r, g, b));
648
649 sequences.push(format!("\x1B]12;#{:02x}{:02x}{:02x}\x07", r, g, b));
651
652 sequences.push(format!(
654 "\x1B]12;rgb:{:02x}{:02x}/{:02x}{:02x}/{:02x}{:02x}\x07",
655 r, r, g, g, b, b
656 ));
657
658 sequences.push(format!(
660 "\x1B]50;CursorShape=1;CursorColor=#{:02x}{:02x}{:02x}\x07",
661 r, g, b
662 ));
663
664 sequences.push(format!("\x1B]12;#{:02x}{:02x}{:02x}\x1B\\", r, g, b));
666
667 sequences.push(format!(
669 "\x1BPtmux;\x1B\x1B]12;#{:02x}{:02x}{:02x}\x07\x1B\\",
670 r, g, b
671 ));
672
673 log::debug!(
675 "🌈 Color sequences for '{}' (RGB: {}, {}, {}) - {} variants",
676 color.to_name(),
677 r,
678 g,
679 b,
680 sequences.len()
681 );
682
683 sequences
684 }
685
686 fn get_rgb_values(&self, color: &AppColor) -> (u8, u8, u8) {
688 let rgb = match color.to_name() {
689 "black" => (0, 0, 0),
690 "red" => (255, 0, 0),
691 "green" => (0, 255, 0),
692 "yellow" => (255, 255, 0), "blue" => (0, 0, 255),
694 "magenta" => (255, 0, 255),
695 "cyan" => (0, 255, 255),
696 "white" => (255, 255, 255),
697 "gray" => (128, 128, 128),
698 "darkgray" => (64, 64, 64),
699 "lightred" => (255, 128, 128),
700 "lightgreen" => (128, 255, 128),
701 "lightyellow" => (255, 255, 128),
702 "lightblue" => (128, 128, 255),
703 "lightmagenta" => (255, 128, 255),
704 "lightcyan" => (128, 255, 255),
705 _ => (255, 255, 255), };
707
708 log::trace!(
709 "🎨 COLOR MAPPING: '{}' → RGB({}, {}, {})",
710 color.to_name(),
711 rgb.0,
712 rgb.1,
713 rgb.2
714 );
715 rgb
716 }
717
718 async fn perform_restart(&mut self) -> Result<()> {
719 log::info!("{}", t!("screen.restart.start"));
720
721 execute!(
723 std::io::stdout(),
724 crossterm::style::Print("\x1B[0 q"), crossterm::style::Print("\x1B[?25h") )?;
727
728 self.terminal_mgr.cleanup().await?;
729 self.terminal_mgr = TerminalManager::new().await?;
730 self.terminal_mgr.setup().await?;
731
732 let backend = CrosstermBackend::new(io::stdout());
733 self.terminal = Terminal::new(backend)?;
734 let size = self.terminal.size()?;
735
736 self.message_display = MessageDisplay::new(&self.config, size.width, size.height);
737 self.input_state = Box::new(InputState::new(&self.config));
738 self.waiting_for_restart_confirmation = false;
739
740 self.message_display
741 .add_message(tc!("system.commands.restart.success"));
742
743 log::info!("{}", t!("screen.restart.done"));
744 Ok(())
745 }
746}
747
748impl ScreenManager {
750 pub fn validate_i18n_keys() -> Vec<String> {
751 let required_keys = [
752 "screen.performance_command_detected",
753 "screen.performance_command_viewport_reset_applied",
754 "screen.theme.invalid_format",
755 "screen.theme.processing",
756 "screen.theme.load_failed",
757 "screen.theme.failed",
758 "screen.theme.applied",
759 "screen.theme.not_found",
760 "screen.theme.not_found_feedback",
761 "screen.render.too_small_log",
762 "screen.render.too_small.text",
763 "screen.render.viewport_not_usable_log",
764 "screen.render.viewport_error.text",
765 "screen.render.invalid_layout_log",
766 "screen.render.exceed_bounds_log",
767 "screen.restart.start",
768 "screen.restart.done",
769 "system.commands.restart.success",
770 ];
771
772 let mut missing = Vec::new();
773 for key in required_keys {
774 if !crate::i18n::has_translation(key) {
775 missing.push(key.to_string());
776 }
777 }
778 missing
779 }
780
781 pub fn get_i18n_debug_info() -> HashMap<String, usize> {
782 let mut info = HashMap::new();
783 let stats = crate::i18n::get_translation_stats();
784
785 info.insert("screen_manager_keys".to_string(), 18);
786 info.extend(stats);
787
788 info
789 }
790}