use crate::executor::NodeStream;
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph, Wrap},
};
pub fn render(
f: &mut Frame,
stream_a: &NodeStream,
stream_b: &NodeStream,
node_a_idx: usize,
node_b_idx: usize,
scroll_pos: usize,
) {
render_in_area(
f,
f.area(),
stream_a,
stream_b,
node_a_idx,
node_b_idx,
scroll_pos,
);
}
pub fn render_in_area(
f: &mut Frame,
area: Rect,
stream_a: &NodeStream,
stream_b: &NodeStream,
node_a_idx: usize,
node_b_idx: usize,
scroll_pos: usize,
) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), Constraint::Min(0), Constraint::Length(3), ])
.split(area);
render_header(f, chunks[0]);
let content_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(chunks[1]);
render_node_output(f, content_chunks[0], stream_a, node_a_idx, scroll_pos);
render_node_output(f, content_chunks[1], stream_b, node_b_idx, scroll_pos);
render_footer(f, chunks[2]);
}
fn render_header(f: &mut Frame, area: Rect) {
let header = Paragraph::new(Line::from(Span::styled(
" Diff View - Comparing nodes side-by-side ",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)))
.block(Block::default().borders(Borders::ALL));
f.render_widget(header, area);
}
fn render_node_output(
f: &mut Frame,
area: Rect,
stream: &NodeStream,
node_idx: usize,
scroll_pos: usize,
) {
let node = &stream.node;
let title = format!(" [{}] {} ", node_idx + 1, node.host);
let stdout = String::from_utf8_lossy(stream.stdout());
let lines: Vec<Line> = if stdout.is_empty() {
vec![Line::from(Span::styled(
"(no output)",
Style::default().fg(Color::Gray),
))]
} else {
stdout
.lines()
.map(|line| Line::from(line.to_string()))
.collect()
};
let viewport_height = area.height.saturating_sub(2) as usize;
let scroll = scroll_pos.min(lines.len().saturating_sub(viewport_height));
let block = Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::White));
let paragraph = Paragraph::new(lines)
.block(block)
.scroll((scroll as u16, 0))
.wrap(Wrap { trim: false });
f.render_widget(paragraph, area);
}
fn render_footer(f: &mut Frame, area: Rect) {
let help_text = Line::from(vec![
Span::styled(" [↑/↓] ", Style::default().fg(Color::Yellow)),
Span::raw("Sync scroll "),
Span::styled(" [Esc] ", Style::default().fg(Color::Yellow)),
Span::raw("Summary "),
Span::styled(" [q] ", Style::default().fg(Color::Yellow)),
Span::raw("Quit "),
]);
let footer = Paragraph::new(help_text).block(Block::default().borders(Borders::ALL));
f.render_widget(footer, area);
}