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> {
231 if !message.starts_with("__LIVE_THEME_UPDATE__") {
232 return None;
233 }
234
235 let parts: Vec<&str> = message.split("__MESSAGE__").collect();
236 if parts.len() != 2 {
237 log::error!("{}", t!("screen.theme.invalid_format"));
238 return None;
239 }
240
241 let theme_part = parts[0].replace("__LIVE_THEME_UPDATE__", "");
242 let display_message = parts[1];
243
244 log::info!(
245 "π¨ LIVE THEME UPDATE STARTING: '{}' β '{}'",
246 self.config.current_theme_name,
247 theme_part
248 );
249
250 let theme_system = match ThemeSystem::load() {
251 Ok(system) => system,
252 Err(e) => {
253 log::error!("{} {}", t!("screen.theme.load_failed"), e);
254 return Some(tc!("screen.theme.failed", &e.to_string()));
255 }
256 };
257
258 if let Some(theme_def) = theme_system.get_theme(&theme_part) {
259 log::info!(
261 "π THEME DETAILS: prefix: '{}' β '{}' | input_cursor: '{}' β '{}'",
262 self.config.theme.input_cursor_prefix,
263 theme_def.input_cursor_prefix,
264 self.config.theme.input_cursor,
265 theme_def.input_cursor
266 );
267 match self.create_theme_from_definition(theme_def) {
268 Ok(new_theme) => {
269 let backup = self.input_state.get_backup_data().unwrap_or_default();
270
271 log::info!(
273 "π THEME TRANSITION: old='{}'/prefix='{}'/input_cursor='{}'/output_cursor='{}' β new='{}'/prefix='{}'/input_cursor='{}'/output_cursor='{}'",
274 self.config.current_theme_name,
275 self.config.theme.input_cursor_prefix,
276 self.config.theme.input_cursor,
277 self.config.theme.output_cursor,
278 theme_part,
279 theme_def.input_cursor_prefix,
280 theme_def.input_cursor,
281 theme_def.output_cursor
282 );
283
284 self.message_display.clear_messages();
286
287 self.config.theme = new_theme;
289 self.config.current_theme_name = theme_part.clone();
290
291 log::info!("π FORCING MessageDisplay config update...");
293 self.message_display.update_config(&self.config);
294
295 log::info!("π RECREATING InputState with central cursor API...");
296 self.input_state = Box::new(InputState::new(&self.config));
297 self.input_state.restore_backup_data(backup.clone());
298
299 log::info!(
301 "β
LIVE THEME APPLIED: theme='{}' | prefix='{}' | input_cursor='{}' | output_cursor='{}' | output_cursor_color='{}' | history={}",
302 theme_part.to_uppercase(),
303 self.config.theme.input_cursor_prefix,
304 self.config.theme.input_cursor,
305 self.config.theme.output_cursor,
306 self.config.theme.output_cursor_color.to_name(),
307 backup.history.len()
308 );
309
310 Some(display_message.to_string())
311 }
312 Err(e) => {
313 log::error!("{} {}", t!("screen.theme.load_failed"), e);
314 Some(tc!("screen.theme.failed", &e.to_string()))
315 }
316 }
317 } else {
318 log::error!("{} {}", t!("screen.theme.not_found"), theme_part);
319 Some(tc!("screen.theme.not_found_feedback", theme_part.as_str()))
320 }
321 }
322
323 fn create_theme_from_definition(
324 &self,
325 theme_def: &crate::commands::theme::ThemeDefinition,
326 ) -> Result<crate::core::config::Theme> {
327 use crate::ui::color::AppColor;
328
329 Ok(crate::core::config::Theme {
330 input_text: AppColor::from_string(&theme_def.input_text)?,
331 input_bg: AppColor::from_string(&theme_def.input_bg)?,
332 cursor: AppColor::from_string(&theme_def.cursor)?,
333 output_text: AppColor::from_string(&theme_def.output_text)?,
334 output_bg: AppColor::from_string(&theme_def.output_bg)?,
335
336 input_cursor_prefix: theme_def.input_cursor_prefix.clone(),
338 input_cursor_color: AppColor::from_string(&theme_def.input_cursor_color)?,
339 input_cursor: theme_def.input_cursor.clone(),
340 output_cursor: theme_def.output_cursor.clone(),
341 output_cursor_color: AppColor::from_string(&theme_def.output_cursor_color)
342 .unwrap_or_default(),
343 })
344 }
345
346 async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
347 log::info!(
348 "{}",
349 t!(
350 "screen.resize_event",
351 &self
352 .message_display
353 .viewport()
354 .terminal_size()
355 .0
356 .to_string(),
357 &self
358 .message_display
359 .viewport()
360 .terminal_size()
361 .1
362 .to_string(),
363 &width.to_string(),
364 &height.to_string()
365 )
366 );
367
368 let changed = self.message_display.handle_resize(width, height);
369
370 if changed {
371 log::info!(
372 "{}",
373 t!(
374 "screen.resize_completed",
375 &self.message_display.viewport().debug_info()
376 )
377 );
378 }
379
380 Ok(())
381 }
382
383 async fn handle_tick_event(&mut self) -> Result<()> {
384 self.message_display.update_typewriter();
386
387 if let Some(input_state) = self.input_state.as_input_state() {
389 input_state.update_cursor_blink();
390 }
391 Ok(())
392 }
393
394 async fn process_pending_logs(&mut self) {
395 match AppLogger::get_messages() {
396 Ok(messages) => {
397 for log_msg in messages {
398 self.message_display.add_message(log_msg.formatted());
399 }
400 }
401 Err(e) => {
402 self.message_display.add_message(
403 AppColor::from_any("error")
404 .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
405 );
406 }
407 }
408 }
409
410 async fn render(&mut self) -> Result<()> {
412 let (input_widget, cursor_pos) = self.input_state.render_with_cursor();
414
415 self.terminal.draw(|frame| {
416 let size = frame.size();
417
418 if size.width < 10 || size.height < 5 {
419 log::error!(
420 "{}",
421 t!(
422 "screen.render.too_small_log",
423 &size.width.to_string(),
424 &size.height.to_string()
425 )
426 );
427
428 let emergency_area = ratatui::layout::Rect {
429 x: 0,
430 y: 0,
431 width: size.width.max(1),
432 height: size.height.max(1),
433 };
434
435 let emergency_widget =
436 ratatui::widgets::Paragraph::new(t!("screen.render.too_small.text"))
437 .block(ratatui::widgets::Block::default());
438
439 frame.render_widget(emergency_widget, emergency_area);
440 return;
441 }
442
443 let viewport = self.message_display.viewport();
444
445 if !viewport.is_usable() {
446 log::error!("{}", t!("screen.render.viewport_not_usable_log"));
447
448 let error_area = ratatui::layout::Rect {
449 x: 0,
450 y: 0,
451 width: size.width,
452 height: size.height,
453 };
454
455 let error_widget =
456 ratatui::widgets::Paragraph::new(t!("screen.render.viewport_error.text"))
457 .block(ratatui::widgets::Block::default());
458
459 frame.render_widget(error_widget, error_area);
460 return;
461 }
462
463 let output_area = viewport.output_area();
464 let input_area = viewport.input_area();
465
466 if !output_area.is_valid() || !input_area.is_valid() {
467 log::error!(
468 "{}",
469 t!(
470 "screen.render.invalid_layout_log",
471 &output_area.width.to_string(),
472 &output_area.height.to_string(),
473 &output_area.x.to_string(),
474 &output_area.y.to_string(),
475 &input_area.width.to_string(),
476 &input_area.height.to_string(),
477 &input_area.x.to_string(),
478 &input_area.y.to_string()
479 )
480 );
481 return;
482 }
483
484 if output_area.x + output_area.width > size.width
485 || output_area.y + output_area.height > size.height
486 || input_area.x + input_area.width > size.width
487 || input_area.y + input_area.height > size.height
488 {
489 log::error!(
490 "{}",
491 t!(
492 "screen.render.exceed_bounds_log",
493 &size.width.to_string(),
494 &size.height.to_string()
495 )
496 );
497 return;
498 }
499
500 let (visible_messages, config, output_layout, cursor_state) =
502 self.message_display.create_output_widget_for_rendering();
503
504 let message_refs: Vec<(String, usize, bool, bool, bool)> = visible_messages;
505
506 let output_widget = crate::output::display::create_output_widget(
507 &message_refs,
508 output_layout,
509 &config,
510 cursor_state,
511 );
512
513 frame.render_widget(output_widget, output_area.as_rect());
514 frame.render_widget(input_widget, input_area.as_rect());
515
516 if let Some((cursor_x, cursor_y)) = cursor_pos {
518 let absolute_x = input_area.x + cursor_x;
519 let absolute_y = input_area.y + cursor_y;
520 frame.set_cursor(absolute_x, absolute_y);
521 }
522 })?;
523
524 if cursor_pos.is_some() {
526 let cursor_commands = self.get_terminal_cursor_commands();
528 for command in cursor_commands {
529 execute!(std::io::stdout(), crossterm::style::Print(command))?;
530 }
531 } else {
532 execute!(
534 std::io::stdout(),
535 crossterm::style::Print("\x1B[?25l") )?;
537 }
538
539 Ok(())
540 }
541
542 fn get_terminal_cursor_commands(&self) -> Vec<&'static str> {
544 match self.config.theme.input_cursor.to_uppercase().as_str() {
545 "PIPE" | "DEFAULT" => vec![
546 "\x1B[6 q", "\x1B[?25h", ],
549 "UNDERSCORE" => vec![
550 "\x1B[4 q", "\x1B[?25h", ],
553 "BLOCK" => vec![
554 "\x1B[2 q", "\x1B[?25h", ],
557 _ => vec![
558 "\x1B[6 q", "\x1B[?25h", ],
561 }
562 }
563
564 async fn perform_restart(&mut self) -> Result<()> {
565 log::info!("{}", t!("screen.restart.start"));
566
567 self.terminal_mgr.cleanup().await?;
568 self.terminal_mgr = TerminalManager::new().await?;
569 self.terminal_mgr.setup().await?;
570
571 let backend = CrosstermBackend::new(io::stdout());
572 self.terminal = Terminal::new(backend)?;
573 let size = self.terminal.size()?;
574
575 self.message_display = MessageDisplay::new(&self.config, size.width, size.height);
576 self.input_state = Box::new(InputState::new(&self.config));
577 self.waiting_for_restart_confirmation = false;
578
579 self.message_display
580 .add_message(tc!("system.commands.restart.success"));
581
582 log::info!("{}", t!("screen.restart.done"));
583 Ok(())
584 }
585}
586
587impl ScreenManager {
589 pub fn validate_i18n_keys() -> Vec<String> {
590 let required_keys = [
591 "screen.performance_command_detected",
592 "screen.performance_command_viewport_reset_applied",
593 "screen.theme.invalid_format",
594 "screen.theme.processing",
595 "screen.theme.load_failed",
596 "screen.theme.failed",
597 "screen.theme.applied",
598 "screen.theme.not_found",
599 "screen.theme.not_found_feedback",
600 "screen.render.too_small_log",
601 "screen.render.too_small.text",
602 "screen.render.viewport_not_usable_log",
603 "screen.render.viewport_error.text",
604 "screen.render.invalid_layout_log",
605 "screen.render.exceed_bounds_log",
606 "screen.restart.start",
607 "screen.restart.done",
608 "system.commands.restart.success",
609 ];
610
611 let mut missing = Vec::new();
612 for key in required_keys {
613 if !crate::i18n::has_translation(key) {
614 missing.push(key.to_string());
615 }
616 }
617 missing
618 }
619
620 pub fn get_i18n_debug_info() -> HashMap<String, usize> {
621 let mut info = HashMap::new();
622 let stats = crate::i18n::get_translation_stats();
623
624 info.insert("screen_manager_keys".to_string(), 18);
625 info.extend(stats);
626
627 info
628 }
629}