From 478be38cfca8abad63dde92e0487367433439fb1 Mon Sep 17 00:00:00 2001 From: bahdotsh Date: Mon, 21 Apr 2025 11:28:38 +0530 Subject: [PATCH] "fix: scroll logs" --- src/ui.rs | 216 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 134 insertions(+), 82 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 619300b..4262a29 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -492,8 +492,10 @@ impl App { // Scroll logs down fn scroll_logs_down(&mut self) { - if !self.logs.is_empty() { - self.log_scroll = (self.log_scroll + 1).min(self.logs.len() - 1); + // Get total log count including system logs + let total_logs = self.logs.len() + crate::logging::get_logs().len(); + if total_logs > 0 { + self.log_scroll = (self.log_scroll + 1).min(total_logs - 1); } } @@ -954,6 +956,10 @@ pub fn run_tui( } } } + KeyCode::Char('?') => { + // Toggle help overlay + app.show_help = !app.show_help; + } _ => {} } } @@ -1706,19 +1712,35 @@ fn render_logs_tab(f: &mut Frame>, app: &App, area: }) .collect(); + // Create a block with a title showing scrolling info + let log_count = all_logs.len(); + let scroll_info = if log_count > 0 { + format!(" Execution Logs ({}/{}) ", app.log_scroll + 1, log_count) + } else { + " Execution Logs ".to_string() + }; + + // Create a stateful list that supports scrolling + let mut log_list_state = ListState::default(); + if !all_logs.is_empty() { + log_list_state.select(Some(app.log_scroll)); + } + let logs_list = List::new(log_items) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .title(Span::styled( - " Execution Logs ", + scroll_info, Style::default().fg(Color::Yellow), - )), + )) + .title_alignment(Alignment::Center), ) - .start_corner(ratatui::layout::Corner::BottomLeft); // Show most recent logs at the bottom - - f.render_widget(logs_list, area); + .highlight_style(Style::default().bg(Color::DarkGray)) + .start_corner(ratatui::layout::Corner::TopLeft); // Change to TopLeft to show in chronological order + + f.render_stateful_widget(logs_list, area, &mut log_list_state); } // Render the help tab @@ -1756,7 +1778,7 @@ fn render_help_tab(f: &mut Frame>, area: Rect) { .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ), - Span::raw(" - Navigate lists"), + Span::raw(" - Navigate lists or scroll logs"), ]), Line::from(vec![ Span::styled( @@ -1830,6 +1852,15 @@ fn render_help_tab(f: &mut Frame>, area: Rect) { ), Span::raw(" - Back / Exit detailed view"), ]), + Line::from(vec![ + Span::styled( + "?", + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" - Toggle help overlay"), + ]), Line::from(vec![ Span::styled( "q", @@ -1865,6 +1896,32 @@ fn render_help_tab(f: &mut Frame>, area: Rect) { ), Span::raw(" - Emulates GitHub Actions environment locally (no Docker required)"), ]), + Line::from(""), + Line::from(Span::styled( + "Logs Tab", + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + )), + Line::from(""), + Line::from(vec![ + Span::styled( + "Up/Down or j/k", + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" - Scroll through logs"), + ]), + Line::from(vec![ + Span::styled( + "Selected line", + Style::default() + .fg(Color::White) + .bg(Color::DarkGray), + ), + Span::raw(" - Current scroll position"), + ]), ]; let help_widget = Paragraph::new(help_text) @@ -1906,87 +1963,82 @@ fn render_help_overlay(f: &mut Frame>) { // Render the status bar fn render_status_bar(f: &mut Frame>, app: &App, area: Rect) { - let runtime_mode = match app.runtime_type { - RuntimeType::Docker => "Docker", - RuntimeType::Emulation => "Emulation", - }; + let mut status_items = vec![]; - let runtime_style = match app.runtime_type { - RuntimeType::Docker => Style::default() - .fg(Color::Blue) - .add_modifier(Modifier::BOLD), - RuntimeType::Emulation => Style::default() - .fg(Color::Magenta) - .add_modifier(Modifier::BOLD), - }; - - let mode_text = if app.validation_mode { - "VALIDATION" - } else { - "EXECUTION" - }; - - let mode_style = if app.validation_mode { + // Add mode info + status_items.push(Span::styled( + format!(" {} ", app.runtime_type_name()), Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD) - } else { + .bg(match app.runtime_type { + RuntimeType::Docker => Color::Blue, + RuntimeType::Emulation => Color::Magenta, + }) + .fg(Color::White), + )); + + // Add validation/execution mode + status_items.push(Span::raw(" ")); + status_items.push(Span::styled( + format!(" {} ", if app.validation_mode { "Validation" } else { "Execution" }), Style::default() - .fg(Color::Green) - .add_modifier(Modifier::BOLD) + .bg(if app.validation_mode { + Color::Yellow + } else { + Color::Green + }) + .fg(Color::Black), + )); + + // Add context-specific help based on current tab + status_items.push(Span::raw(" ")); + let help_text = match app.selected_tab { + 0 => "[Space] Toggle selection [Enter] Run selected [r] Run all selected", + 1 => { + if app.detailed_view { + "[Esc] Back to jobs [↑/↓] Navigate steps" + } else { + "[Enter] View details [↑/↓] Navigate jobs" + } + } + 2 => { + // For logs tab, show scrolling instructions + let log_count = app.logs.len() + crate::logging::get_logs().len(); + if log_count > 0 { + // Convert to a static string for consistent return type + let scroll_text = format!("[↑/↓] Scroll logs ({}/{})", app.log_scroll + 1, log_count); + Box::leak(scroll_text.into_boxed_str()) + } else { + "[No logs to display]" + } + } + 3 => "[?] Toggle help overlay", + _ => "", }; + status_items.push(Span::styled( + format!(" {} ", help_text), + Style::default().fg(Color::White), + )); - // Left side of status bar - let left_text = Line::from(vec![ - Span::raw("Runtime: "), - Span::styled(runtime_mode, runtime_style), - Span::raw(" | "), - Span::raw("Mode: "), - Span::styled(mode_text, mode_style), - Span::raw(" | "), - Span::styled( - format!("{} workflow(s) loaded", app.workflows.len()), - Style::default().fg(Color::White), - ), - ]); + // Show keybindings for common actions + status_items.push(Span::raw(" ")); + status_items.push(Span::styled( + " [Tab] Switch tabs ", + Style::default().fg(Color::White), + )); + status_items.push(Span::styled( + " [?] Help ", + Style::default().fg(Color::White), + )); + status_items.push(Span::styled( + " [q] Quit ", + Style::default().fg(Color::White), + )); - // Right side of status bar - let right_text = Line::from(vec![ - Span::styled( - "e", - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), - ), - Span::raw(": Toggle Emulation | "), - Span::styled( - "v", - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), - ), - Span::raw(": Toggle Validation | "), - Span::styled( - "q", - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), - ), - Span::raw(": Quit"), - ]); + let status_bar = Paragraph::new(Line::from(status_items)) + .style(Style::default().bg(Color::DarkGray)) + .alignment(Alignment::Left); - // Create a layout with two parts for left and right aligned text - let status_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) - .split(area); - - let left_status = Paragraph::new(left_text).alignment(Alignment::Left); - - let right_status = Paragraph::new(right_text).alignment(Alignment::Right); - - f.render_widget(left_status, status_chunks[0]); - f.render_widget(right_status, status_chunks[1]); + f.render_widget(status_bar, area); } // Validate a workflow or directory containing workflows