From 6abea062ba1a7951e9a1e77b8a0b03037f292a9d Mon Sep 17 00:00:00 2001 From: filipriec Date: Fri, 13 Jun 2025 15:26:45 +0200 Subject: [PATCH] ui debug in status line --- client/src/components/common/status_line.rs | 64 +++++++++++++++------ client/src/state/app/state.rs | 14 ++++- client/src/ui/handlers/render.rs | 14 ++++- client/src/ui/handlers/ui.rs | 23 ++++++-- client/src/utils/debug_logger.rs | 22 +++---- 5 files changed, 100 insertions(+), 37 deletions(-) diff --git a/client/src/components/common/status_line.rs b/client/src/components/common/status_line.rs index 39545ab..0d193b0 100644 --- a/client/src/components/common/status_line.rs +++ b/client/src/components/common/status_line.rs @@ -1,11 +1,11 @@ -// src/components/common/status_line.rs +// client/src/components/common/status_line.rs use crate::config::colors::themes::Theme; use crate::state::app::state::AppState; use ratatui::{ layout::Rect, style::Style, - text::{Line, Span}, - widgets::Paragraph, + text::{Line, Span, Text}, + widgets::{Paragraph, Wrap}, // Make sure Wrap is imported Frame, }; use std::path::Path; @@ -22,22 +22,37 @@ pub fn render_status_line( ) { #[cfg(feature = "ui-debug")] { - // If ui-debug is enabled, we only show the debug info. - let debug_text = &app_state.debug_info; - let debug_span = Span::styled( - debug_text.as_str(), - Style::default().fg(theme.accent).bg(theme.bg), - ); - let paragraph = Paragraph::new(Line::from(vec![debug_span])); - f.render_widget(paragraph, area); - return; // Early return to skip the normal status line rendering. + if let Some(debug_state) = &app_state.debug_state { + let paragraph = if debug_state.is_error { + // --- THIS IS THE CRITICAL LOGIC FOR ERRORS --- + // 1. Create a `Text` object, which can contain multiple lines. + let error_text = Text::from(debug_state.displayed_message.clone()); + + // 2. Create a Paragraph from the Text and TELL IT TO WRAP. + Paragraph::new(error_text) + .wrap(Wrap { trim: true }) // This line makes the text break into new rows. + .style(Style::default().bg(theme.highlight).fg(theme.bg)) + } else { + // --- This is for normal, single-line info messages --- + Paragraph::new(debug_state.displayed_message.as_str()) + .style(Style::default().fg(theme.accent).bg(theme.bg)) + }; + f.render_widget(paragraph, area); + } else { + // Fallback for when debug state is None + let paragraph = Paragraph::new("").style(Style::default().bg(theme.bg)); + f.render_widget(paragraph, area); + } + return; // Stop here and don't render the normal status line. } + // --- The normal status line rendering logic (unchanged) --- let program_info = format!("multieko2 v{}", env!("CARGO_PKG_VERSION")); let mode_text = if is_edit_mode { "[EDIT]" } else { "[READ-ONLY]" }; - let home_dir = - dirs::home_dir().map(|p| p.to_string_lossy().into_owned()).unwrap_or_default(); + let home_dir = dirs::home_dir() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default(); let display_dir = if current_dir.starts_with(&home_dir) { current_dir.replacen(&home_dir, "~", 1) } else { @@ -73,7 +88,9 @@ pub fn render_status_line( }), ); - let dir_display_text_str = if UnicodeWidthStr::width(display_dir.as_str()) <= remaining_width_for_dir { + let dir_display_text_str = if UnicodeWidthStr::width(display_dir.as_str()) + <= remaining_width_for_dir + { display_dir } else { let dir_name = Path::new(current_dir) @@ -83,7 +100,10 @@ pub fn render_status_line( if UnicodeWidthStr::width(dir_name) <= remaining_width_for_dir { dir_name.to_string() } else { - dir_name.chars().take(remaining_width_for_dir).collect::() + dir_name + .chars() + .take(remaining_width_for_dir) + .collect::() } }; @@ -99,12 +119,18 @@ pub fn render_status_line( let mut line_spans = vec![ Span::styled(mode_text, Style::default().fg(theme.accent)), Span::styled(separator, Style::default().fg(theme.border)), - Span::styled(dir_display_text_str.as_str(), Style::default().fg(theme.fg)), + Span::styled( + dir_display_text_str.as_str(), + Style::default().fg(theme.fg), + ), Span::styled(separator, Style::default().fg(theme.border)), - Span::styled(program_info.as_str(), Style::default().fg(theme.secondary)), + Span::styled( + program_info.as_str(), + Style::default().fg(theme.secondary), + ), ]; -if show_fps { + if show_fps { line_spans .push(Span::styled(separator, Style::default().fg(theme.border))); line_spans.push(Span::styled( diff --git a/client/src/state/app/state.rs b/client/src/state/app/state.rs index f3cb8a8..9258672 100644 --- a/client/src/state/app/state.rs +++ b/client/src/state/app/state.rs @@ -6,6 +6,8 @@ use crate::modes::handlers::mode_manager::AppMode; use crate::ui::handlers::context::DialogPurpose; use crate::state::app::search::SearchState; // ADDED use anyhow::Result; +#[cfg(feature = "ui-debug")] +use std::time::Instant; // --- YOUR EXISTING DIALOGSTATE IS UNTOUCHED --- pub struct DialogState { @@ -33,6 +35,14 @@ pub struct UiState { pub dialog: DialogState, } +#[cfg(feature = "ui-debug")] +#[derive(Debug, Clone)] +pub struct DebugState { + pub displayed_message: String, + pub is_error: bool, + pub display_start_time: Instant, +} + pub struct AppState { // Core editor state pub current_dir: String, @@ -52,7 +62,7 @@ pub struct AppState { pub ui: UiState, #[cfg(feature = "ui-debug")] - pub debug_info: String, + pub debug_state: Option, } impl AppState { @@ -73,7 +83,7 @@ impl AppState { ui: UiState::default(), #[cfg(feature = "ui-debug")] - debug_info: String::new(), + debug_state: None, }) } diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 52abba3..d28a7f2 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -55,10 +55,22 @@ pub fn render_ui( ) { render_background(f, f.area(), theme); + // --- START DYNAMIC LAYOUT LOGIC --- + let mut status_line_height = 1; + #[cfg(feature = "ui-debug")] + { + if let Some(debug_state) = &app_state.debug_state { + if debug_state.is_error { + status_line_height = 4; + } + } + } + // --- END DYNAMIC LAYOUT LOGIC --- + const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15; - let mut bottom_area_constraints: Vec = vec![Constraint::Length(1)]; + let mut bottom_area_constraints: Vec = vec![Constraint::Length(status_line_height)]; let command_palette_area_height = if navigation_state.active { 1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT } else if event_handler_command_mode_active { diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index 91c1fff..e4ff45b 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -24,17 +24,19 @@ use crate::ui::handlers::render::render_ui; use crate::tui::functions::common::login::LoginResult; use crate::tui::functions::common::register::RegisterResult; use crate::ui::handlers::context::DialogPurpose; -#[cfg(feature = "ui-debug")] -use crate::utils::debug_logger::get_latest_debug_message; use crate::tui::functions::common::login; use crate::tui::functions::common::register; use crate::utils::columns::filter_user_columns; -use std::time::Instant; use anyhow::{anyhow, Context, Result}; use crossterm::cursor::SetCursorStyle; use crossterm::event as crossterm_event; use tracing::{error, info, warn}; use tokio::sync::mpsc; +use std::time::{Duration, Instant}; +#[cfg(feature = "ui-debug")] +use crate::state::app::state::DebugState; +#[cfg(feature = "ui-debug")] +use crate::utils::debug_logger::pop_next_debug_message; pub async fn run_ui() -> Result<()> { let config = Config::load().context("Failed to load configuration")?; @@ -520,7 +522,20 @@ pub async fn run_ui() -> Result<()> { #[cfg(feature = "ui-debug")] { - app_state.debug_info = get_latest_debug_message(); + let can_display_next = match &app_state.debug_state { + Some(current) => current.display_start_time.elapsed() >= Duration::from_secs(2), + None => true, + }; + + if can_display_next { + if let Some((new_message, is_error)) = pop_next_debug_message() { + app_state.debug_state = Some(DebugState { + displayed_message: new_message, + is_error, + display_start_time: Instant::now(), + }); + } + } } if event_processed || needs_redraw || position_changed { diff --git a/client/src/utils/debug_logger.rs b/client/src/utils/debug_logger.rs index b307379..6e733af 100644 --- a/client/src/utils/debug_logger.rs +++ b/client/src/utils/debug_logger.rs @@ -1,11 +1,12 @@ // client/src/utils/debug_logger.rs use lazy_static::lazy_static; +use std::collections::VecDeque; // <-- FIX: Import VecDeque use std::io; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex}; // <-- FIX: Import Mutex lazy_static! { - static ref UI_DEBUG_BUFFER: Arc> = - Arc::new(Mutex::new(String::from("Logger initialized..."))); + static ref UI_DEBUG_BUFFER: Arc>> = + Arc::new(Mutex::new(VecDeque::from([(String::from("Logger initialized..."), false)]))); } #[derive(Clone)] @@ -23,15 +24,14 @@ impl UiDebugWriter { } } -// Implement the io::Write trait for our writer. -// tracing_subscriber can use any type that implements `MakeWriter`. -// A simple way to do this is to have our writer implement `io::Write`. impl io::Write for UiDebugWriter { fn write(&mut self, buf: &[u8]) -> io::Result { let mut buffer = UI_DEBUG_BUFFER.lock().unwrap(); let message = String::from_utf8_lossy(buf); - // We just want the latest message, trimmed of whitespace. - *buffer = message.trim().to_string(); + let trimmed_message = message.trim().to_string(); + let is_error = trimmed_message.starts_with("ERROR"); + // Add the new message to the back of the queue + buffer.push_back((trimmed_message, is_error)); Ok(buf.len()) } @@ -40,7 +40,7 @@ impl io::Write for UiDebugWriter { } } -// A public function to safely get the latest message from anywhere in the app. -pub fn get_latest_debug_message() -> String { - UI_DEBUG_BUFFER.lock().unwrap().clone() +// A public function to pop the next message from the front of the queue. +pub fn pop_next_debug_message() -> Option<(String, bool)> { + UI_DEBUG_BUFFER.lock().unwrap().pop_front() }