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!(
50 "{}",
51 t!(
52 "screen.initialized",
53 &size.width.to_string(),
54 &size.height.to_string(),
55 &message_display.viewport().debug_info()
56 )
57 );
58
59 let owned_config = config.clone();
60
61 Ok(Self {
62 terminal,
63 terminal_mgr,
64 message_display,
65 input_state: Box::new(InputState::new(config)),
66 config: owned_config,
67 events: EventHandler::new(config.poll_rate),
68 keyboard_manager: KeyboardManager::new(),
69 waiting_for_restart_confirmation: false,
70 })
71 }
72
73 pub async fn run(&mut self) -> Result<()> {
74 let result = loop {
75 if let Some(event) = self.events.next().await {
76 match event {
77 AppEvent::Input(key) => {
78 if self.handle_input_event(key).await? {
79 self.events.shutdown().await;
80 break Ok(());
81 }
82 }
83 AppEvent::Resize(width, height) => {
84 self.handle_resize_event(width, height).await?;
85 }
86 AppEvent::Tick => {
87 self.handle_tick_event().await?;
88 }
89 }
90 }
91
92 self.process_pending_logs().await;
93 self.render().await?;
94 };
95
96 self.terminal_mgr.cleanup().await?;
97 result
98 }
99
100 async fn handle_input_event(&mut self, key: KeyEvent) -> Result<bool> {
101 if HistoryKeyboardHandler::get_history_action(&key).is_some() {
103 if let Some(new_input) = self.input_state.handle_input(key) {
104 if let Some(processed) = LanguageService::process_save_message(&new_input).await {
105 self.message_display.add_message(processed);
106 return Ok(false);
107 }
108
109 if let Some(processed) = self.process_live_theme_update(&new_input).await {
110 self.message_display.add_message(processed);
111 return Ok(false);
112 }
113
114 self.message_display.add_message(new_input.clone());
115
116 if new_input.starts_with("__CLEAR__") {
117 self.message_display.clear_messages();
118 } else if new_input.starts_with("__EXIT__") {
119 return Ok(true);
120 }
121 }
122 return Ok(false);
123 }
124
125 match self.keyboard_manager.get_action(&key) {
127 KeyAction::ScrollUp => {
128 self.message_display.handle_scroll(ScrollDirection::Up, 1);
129 return Ok(false);
130 }
131 KeyAction::ScrollDown => {
132 self.message_display.handle_scroll(ScrollDirection::Down, 1);
133 return Ok(false);
134 }
135 KeyAction::PageUp => {
136 self.message_display
137 .handle_scroll(ScrollDirection::PageUp, 0);
138 return Ok(false);
139 }
140 KeyAction::PageDown => {
141 self.message_display
142 .handle_scroll(ScrollDirection::PageDown, 0);
143 return Ok(false);
144 }
145 KeyAction::Submit => {
146 if let Some(new_input) = self.input_state.handle_input(key) {
147 let input_command = new_input.trim().to_lowercase();
148 let is_performance_command = input_command == "perf"
149 || input_command == "performance"
150 || input_command == "stats";
151
152 if let Some(processed) = LanguageService::process_save_message(&new_input).await
153 {
154 self.message_display.add_message(processed);
155 return Ok(false);
156 }
157
158 if let Some(processed) = self.process_live_theme_update(&new_input).await {
159 self.message_display.add_message(processed);
160 return Ok(false);
161 }
162
163 self.message_display.add_message(new_input.clone());
164
165 if is_performance_command {
166 log::debug!(
167 "{}",
168 t!("screen.performance_command_detected", &input_command)
169 );
170
171 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
172 self.message_display.viewport_mut().force_auto_scroll();
173
174 log::debug!(
175 "{}",
176 t!("screen.performance_command_viewport_reset_applied")
177 );
178 }
179
180 if new_input.starts_with("__CLEAR__") {
181 self.message_display.clear_messages();
182 } else if new_input.starts_with("__EXIT__") {
183 return Ok(true);
184 } else if new_input.starts_with("__RESTART_WITH_MSG__") {
185 let feedback_msg = new_input
186 .replace("__RESTART_WITH_MSG__", "")
187 .trim()
188 .to_string();
189
190 if !feedback_msg.is_empty() {
191 self.message_display.add_message(feedback_msg);
192 tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
193 }
194
195 if let Err(e) = self.perform_restart().await {
196 self.message_display
197 .add_message(format!("Restart failed: {}", e));
198 }
199 } else if new_input.starts_with("__RESTART_FORCE__")
200 || new_input == "__RESTART__"
201 {
202 if let Err(e) = self.perform_restart().await {
203 self.message_display
204 .add_message(format!("Restart failed: {}", e));
205 }
206 }
207 }
208 }
209 KeyAction::Quit => return Ok(true),
210 _ => {
211 if let Some(new_input) = self.input_state.handle_input(key) {
212 if let Some(processed) = LanguageService::process_save_message(&new_input).await
213 {
214 self.message_display.add_message(processed);
215 return Ok(false);
216 }
217
218 if let Some(processed) = self.process_live_theme_update(&new_input).await {
219 self.message_display.add_message(processed);
220 return Ok(false);
221 }
222
223 self.message_display.add_message(new_input);
224 }
225 }
226 }
227 Ok(false)
228 }
229
230 async fn process_live_theme_update(&mut self, message: &str) -> Option<String> {
232 if !message.starts_with("__LIVE_THEME_UPDATE__") {
233 return None;
234 }
235
236 let parts: Vec<&str> = message.split("__MESSAGE__").collect();
237 if parts.len() != 2 {
238 log::error!("{}", t!("screen.theme.invalid_format"));
239 return None;
240 }
241
242 let theme_part = parts[0].replace("__LIVE_THEME_UPDATE__", "");
243 let display_message = parts[1];
244
245 log::info!(
246 "π¨ LIVE THEME UPDATE STARTING: '{}' β '{}'",
247 self.config.current_theme_name,
248 theme_part
249 );
250
251 let theme_system = match ThemeSystem::load() {
252 Ok(system) => system,
253 Err(e) => {
254 log::error!("{} {}", t!("screen.theme.load_failed"), e);
255 return Some(tc!("screen.theme.failed", &e.to_string()));
256 }
257 };
258
259 if let Some(theme_def) = theme_system.get_theme(&theme_part) {
260 log::info!(
262 "π THEME DEFINITION LOADED:\n \
263 input_cursor_prefix: '{}'\n \
264 input_cursor_color: '{}'\n \
265 input_cursor: '{}'\n \
266 output_cursor: '{}'\n \
267 output_cursor_color: '{}'",
268 theme_def.input_cursor_prefix,
269 theme_def.input_cursor_color,
270 theme_def.input_cursor,
271 theme_def.output_cursor,
272 theme_def.output_cursor_color
273 );
274
275 match self.create_theme_from_definition(theme_def) {
276 Ok(new_theme) => {
277 let backup = self.input_state.get_backup_data().unwrap_or_default();
278
279 log::info!(
281 "π THEME CONVERSION COMPLETE:\n \
282 OLD Config: input_cursor='{}', input_cursor_color='{}'\n \
283 NEW Config: input_cursor='{}', input_cursor_color='{}'",
284 self.config.theme.input_cursor,
285 self.config.theme.input_cursor_color.to_name(),
286 new_theme.input_cursor,
287 new_theme.input_cursor_color.to_name()
288 );
289
290 self.message_display.clear_messages();
292
293 self.config.theme = new_theme;
295 self.config.current_theme_name = theme_part.clone();
296
297 self.message_display.update_config(&self.config);
299
300 log::info!("π RECREATING InputState with central cursor API...");
301 self.input_state = Box::new(InputState::new(&self.config));
302
303 if let Some(_input_widget) = self.input_state.as_input_state() {
305 log::info!(
306 "β
INPUT-CURSOR CREATED:\n \
307 Expected: cursor='{}' (color: {})\n \
308 Theme config: prefix='{}' (color: {})",
309 self.config.theme.input_cursor,
310 self.config.theme.input_cursor_color.to_name(),
311 self.config.theme.input_cursor_prefix,
312 self.config.theme.input_cursor_color.to_name()
313 );
314 }
315
316 self.input_state.restore_backup_data(backup.clone());
317
318 log::info!(
320 "β
LIVE THEME APPLIED SUCCESSFULLY:\n \
321 theme='{}'\n \
322 prefix='{}'\n \
323 input_cursor='{}'\n \
324 input_cursor_color='{}'\n \
325 output_cursor='{}'\n \
326 output_cursor_color='{}'\n \
327 history={} entries",
328 theme_part.to_uppercase(),
329 self.config.theme.input_cursor_prefix,
330 self.config.theme.input_cursor,
331 self.config.theme.input_cursor_color.to_name(),
332 self.config.theme.output_cursor,
333 self.config.theme.output_cursor_color.to_name(),
334 backup.history.len()
335 );
336
337 Some(display_message.to_string())
338 }
339 Err(e) => {
340 log::error!("{} {}", t!("screen.theme.load_failed"), e);
341 Some(tc!("screen.theme.failed", &e.to_string()))
342 }
343 }
344 } else {
345 log::error!("{} {}", t!("screen.theme.not_found"), theme_part);
346 Some(tc!("screen.theme.not_found_feedback", theme_part.as_str()))
347 }
348 }
349
350 fn create_theme_from_definition(
352 &self,
353 theme_def: &crate::commands::theme::ThemeDefinition,
354 ) -> Result<crate::core::config::Theme> {
355 use crate::ui::color::AppColor;
356
357 log::debug!(
358 "π§ create_theme_from_definition STARTING:\n \
359 input_cursor_prefix: '{}'\n \
360 input_cursor_color: '{}'\n \
361 input_cursor: '{}'\n \
362 output_cursor: '{}'\n \
363 output_cursor_color: '{}'",
364 theme_def.input_cursor_prefix,
365 theme_def.input_cursor_color,
366 theme_def.input_cursor,
367 theme_def.output_cursor,
368 theme_def.output_cursor_color
369 );
370
371 let input_cursor_color = AppColor::from_string(&theme_def.input_cursor_color)?;
372 let output_cursor_color = AppColor::from_string(&theme_def.output_cursor_color)?;
373
374 log::debug!(
375 "π¨ COLOR CONVERSION COMPLETE:\n \
376 input_cursor_color: '{}' β '{}'\n \
377 output_cursor_color: '{}' β '{}'",
378 theme_def.input_cursor_color,
379 input_cursor_color.to_name(),
380 theme_def.output_cursor_color,
381 output_cursor_color.to_name()
382 );
383
384 Ok(crate::core::config::Theme {
385 input_text: AppColor::from_string(&theme_def.input_text)?,
386 input_bg: AppColor::from_string(&theme_def.input_bg)?,
387 output_text: AppColor::from_string(&theme_def.output_text)?,
388 output_bg: AppColor::from_string(&theme_def.output_bg)?,
389
390 input_cursor_prefix: theme_def.input_cursor_prefix.clone(),
392 input_cursor_color,
393 input_cursor: theme_def.input_cursor.clone(),
394 output_cursor: theme_def.output_cursor.clone(),
395 output_cursor_color,
396 })
397 }
398
399 async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
400 log::info!(
401 "{}",
402 t!(
403 "screen.resize_event",
404 &self
405 .message_display
406 .viewport()
407 .terminal_size()
408 .0
409 .to_string(),
410 &self
411 .message_display
412 .viewport()
413 .terminal_size()
414 .1
415 .to_string(),
416 &width.to_string(),
417 &height.to_string()
418 )
419 );
420
421 let changed = self.message_display.handle_resize(width, height);
422
423 if changed {
424 log::info!(
425 "{}",
426 t!(
427 "screen.resize_completed",
428 &self.message_display.viewport().debug_info()
429 )
430 );
431 }
432
433 Ok(())
434 }
435
436 async fn handle_tick_event(&mut self) -> Result<()> {
437 self.message_display.update_typewriter();
439
440 if let Some(input_state) = self.input_state.as_input_state() {
442 input_state.update_cursor_blink();
443 }
444 Ok(())
445 }
446
447 async fn process_pending_logs(&mut self) {
448 match AppLogger::get_messages() {
449 Ok(messages) => {
450 for log_msg in messages {
451 self.message_display.add_message(log_msg.formatted());
452 }
453 }
454 Err(e) => {
455 self.message_display.add_message(
456 AppColor::from_any("error")
457 .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
458 );
459 }
460 }
461 }
462
463 async fn render(&mut self) -> Result<()> {
465 let (input_widget, cursor_pos) = self.input_state.render_with_cursor();
467
468 self.terminal.draw(|frame| {
469 let size = frame.size();
470
471 if size.width < 10 || size.height < 5 {
472 log::error!(
473 "{}",
474 t!(
475 "screen.render.too_small_log",
476 &size.width.to_string(),
477 &size.height.to_string()
478 )
479 );
480
481 let emergency_area = ratatui::layout::Rect {
482 x: 0,
483 y: 0,
484 width: size.width.max(1),
485 height: size.height.max(1),
486 };
487
488 let emergency_widget =
489 ratatui::widgets::Paragraph::new(t!("screen.render.too_small.text"))
490 .block(ratatui::widgets::Block::default());
491
492 frame.render_widget(emergency_widget, emergency_area);
493 return;
494 }
495
496 let viewport = self.message_display.viewport();
497
498 if !viewport.is_usable() {
499 log::error!("{}", t!("screen.render.viewport_not_usable_log"));
500
501 let error_area = ratatui::layout::Rect {
502 x: 0,
503 y: 0,
504 width: size.width,
505 height: size.height,
506 };
507
508 let error_widget =
509 ratatui::widgets::Paragraph::new(t!("screen.render.viewport_error.text"))
510 .block(ratatui::widgets::Block::default());
511
512 frame.render_widget(error_widget, error_area);
513 return;
514 }
515
516 let output_area = viewport.output_area();
517 let input_area = viewport.input_area();
518
519 if !output_area.is_valid() || !input_area.is_valid() {
520 log::error!(
521 "{}",
522 t!(
523 "screen.render.invalid_layout_log",
524 &output_area.width.to_string(),
525 &output_area.height.to_string(),
526 &output_area.x.to_string(),
527 &output_area.y.to_string(),
528 &input_area.width.to_string(),
529 &input_area.height.to_string(),
530 &input_area.x.to_string(),
531 &input_area.y.to_string()
532 )
533 );
534 return;
535 }
536
537 if output_area.x + output_area.width > size.width
538 || output_area.y + output_area.height > size.height
539 || input_area.x + input_area.width > size.width
540 || input_area.y + input_area.height > size.height
541 {
542 log::error!(
543 "{}",
544 t!(
545 "screen.render.exceed_bounds_log",
546 &size.width.to_string(),
547 &size.height.to_string()
548 )
549 );
550 return;
551 }
552
553 let (visible_messages, config, output_layout, cursor_state) =
555 self.message_display.create_output_widget_for_rendering();
556
557 let message_refs: Vec<(String, usize, bool, bool, bool)> = visible_messages;
558
559 let output_widget = crate::output::display::create_output_widget(
560 &message_refs,
561 output_layout,
562 &config,
563 cursor_state,
564 );
565
566 frame.render_widget(output_widget, output_area.as_rect());
567 frame.render_widget(input_widget, input_area.as_rect());
568
569 if let Some((cursor_x, cursor_y)) = cursor_pos {
571 let absolute_x = input_area.x + cursor_x;
572 let absolute_y = input_area.y + cursor_y;
573 frame.set_cursor(absolute_x, absolute_y);
574 }
575 })?;
576
577 if cursor_pos.is_some() {
579 let cursor_commands = self.get_terminal_cursor_commands();
581 for command in cursor_commands {
582 execute!(std::io::stdout(), crossterm::style::Print(command))?;
583 }
584 } else {
585 execute!(
587 std::io::stdout(),
588 crossterm::style::Print("\x1B[?25l") )?;
590 }
591
592 Ok(())
593 }
594
595 fn get_terminal_cursor_commands(&self) -> Vec<&'static str> {
597 match self.config.theme.input_cursor.to_uppercase().as_str() {
598 "PIPE" => vec![
599 "\x1B[6 q", "\x1B[?25h", ],
602 "UNDERSCORE" => vec![
603 "\x1B[4 q", "\x1B[?25h", ],
606 "BLOCK" => vec![
607 "\x1B[2 q", "\x1B[?25h", ],
610 _ => vec![
611 "\x1B[6 q", "\x1B[?25h", ],
614 }
615 }
616
617 async fn perform_restart(&mut self) -> Result<()> {
618 log::info!("{}", t!("screen.restart.start"));
619
620 self.terminal_mgr.cleanup().await?;
621 self.terminal_mgr = TerminalManager::new().await?;
622 self.terminal_mgr.setup().await?;
623
624 let backend = CrosstermBackend::new(io::stdout());
625 self.terminal = Terminal::new(backend)?;
626 let size = self.terminal.size()?;
627
628 self.message_display = MessageDisplay::new(&self.config, size.width, size.height);
629 self.input_state = Box::new(InputState::new(&self.config));
630 self.waiting_for_restart_confirmation = false;
631
632 self.message_display
633 .add_message(tc!("system.commands.restart.success"));
634
635 log::info!("{}", t!("screen.restart.done"));
636 Ok(())
637 }
638}
639
640impl ScreenManager {
642 pub fn validate_i18n_keys() -> Vec<String> {
643 let required_keys = [
644 "screen.performance_command_detected",
645 "screen.performance_command_viewport_reset_applied",
646 "screen.theme.invalid_format",
647 "screen.theme.processing",
648 "screen.theme.load_failed",
649 "screen.theme.failed",
650 "screen.theme.applied",
651 "screen.theme.not_found",
652 "screen.theme.not_found_feedback",
653 "screen.render.too_small_log",
654 "screen.render.too_small.text",
655 "screen.render.viewport_not_usable_log",
656 "screen.render.viewport_error.text",
657 "screen.render.invalid_layout_log",
658 "screen.render.exceed_bounds_log",
659 "screen.restart.start",
660 "screen.restart.done",
661 "system.commands.restart.success",
662 ];
663
664 let mut missing = Vec::new();
665 for key in required_keys {
666 if !crate::i18n::has_translation(key) {
667 missing.push(key.to_string());
668 }
669 }
670 missing
671 }
672
673 pub fn get_i18n_debug_info() -> HashMap<String, usize> {
674 let mut info = HashMap::new();
675 let stats = crate::i18n::get_translation_stats();
676
677 info.insert("screen_manager_keys".to_string(), 18);
678 info.extend(stats);
679
680 info
681 }
682}