use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, List, ListItem};
use ratatui::Frame;
use crate::app::{ActivePane, App};
fn graph_color(color_index: usize, graph_colors: &[Color; 8]) -> Color {
graph_colors[color_index % graph_colors.len()]
}
fn build_graph_spans(
row: &gitkraft_core::GraphRow,
graph_colors: &[Color; 8],
) -> Vec<Span<'static>> {
let width = row.width;
if width == 0 {
return vec![Span::styled(
"● ",
Style::default().fg(graph_color(row.node_color, graph_colors)),
)];
}
let mut column_passthrough: Vec<Option<usize>> = vec![None; width]; let mut has_left_cross = false; let mut has_right_cross = false; let mut left_cross_color: usize = 0;
let mut right_cross_color: usize = 0;
let mut cross_left_col: usize = row.node_column;
let mut cross_right_col: usize = row.node_column;
for edge in &row.edges {
if edge.from_column == edge.to_column {
column_passthrough[edge.to_column] = Some(edge.color_index);
} else {
let target = edge.to_column;
if target < row.node_column {
has_left_cross = true;
left_cross_color = edge.color_index;
if target < cross_left_col {
cross_left_col = target;
}
} else if target > row.node_column {
has_right_cross = true;
right_cross_color = edge.color_index;
if target > cross_right_col {
cross_right_col = target;
}
}
}
}
let mut spans: Vec<Span<'static>> = Vec::with_capacity(width + 1);
for col in 0..width {
if col == row.node_column {
spans.push(Span::styled(
"● ".to_string(),
Style::default()
.fg(graph_color(row.node_color, graph_colors))
.add_modifier(Modifier::BOLD),
));
} else if let Some(ci) = column_passthrough.get(col).copied().flatten() {
let in_left_range = has_left_cross && col >= cross_left_col && col < row.node_column;
let in_right_range = has_right_cross && col > row.node_column && col <= cross_right_col;
if in_left_range || in_right_range {
let cross_ci = if in_left_range {
left_cross_color
} else {
right_cross_color
};
spans.push(Span::styled(
"├─".to_string(),
Style::default().fg(graph_color(cross_ci, graph_colors)),
));
} else {
spans.push(Span::styled(
"│ ".to_string(),
Style::default().fg(graph_color(ci, graph_colors)),
));
}
} else {
let in_left_range = has_left_cross && col >= cross_left_col && col < row.node_column;
let in_right_range = has_right_cross && col > row.node_column && col <= cross_right_col;
if in_left_range {
if col == cross_left_col {
spans.push(Span::styled(
"╭─".to_string(),
Style::default().fg(graph_color(left_cross_color, graph_colors)),
));
} else {
spans.push(Span::styled(
"──".to_string(),
Style::default().fg(graph_color(left_cross_color, graph_colors)),
));
}
} else if in_right_range {
if col == cross_right_col {
spans.push(Span::styled(
"─╮".to_string(),
Style::default().fg(graph_color(right_cross_color, graph_colors)),
));
} else {
spans.push(Span::styled(
"──".to_string(),
Style::default().fg(graph_color(right_cross_color, graph_colors)),
));
}
} else {
spans.push(Span::styled(
" ".to_string(),
Style::default().fg(Color::DarkGray),
));
}
}
}
spans.push(Span::styled(" ", Style::default()));
spans
}
pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
let theme = app.theme();
let is_active = app.active_pane == ActivePane::CommitLog;
let border_color = if is_active {
theme.border_active
} else {
theme.border_inactive
};
let block = Block::default()
.title(" Commit Log ")
.borders(Borders::ALL)
.border_style(Style::default().fg(border_color));
if app.tab().commits.is_empty() {
let items: Vec<ListItem> = vec![ListItem::new(Line::from(vec![Span::styled(
" No commits yet",
Style::default().fg(theme.text_muted),
)]))];
let list = List::new(items).block(block);
frame.render_widget(list, area);
return;
}
let items: Vec<ListItem> = app
.tab()
.commits
.iter()
.enumerate()
.map(|(idx, commit)| {
let summary = gitkraft_core::truncate_str(&commit.summary, 50);
let relative = commit.relative_time();
let mut spans = if let Some(row) = app.tab().graph_rows.get(idx) {
build_graph_spans(row, &theme.graph_colors)
} else {
vec![Span::raw(" ")]
};
spans.push(Span::styled(
format!("{} ", commit.short_oid),
Style::default().fg(theme.warning),
));
spans.push(Span::styled(
summary,
Style::default().fg(theme.text_primary),
));
spans.push(Span::styled(
format!(" ({}", commit.author_name),
Style::default().fg(theme.accent),
));
spans.push(Span::styled(
format!(", {})", relative),
Style::default().fg(theme.text_muted),
));
ListItem::new(Line::from(spans))
})
.collect();
let list = List::new(items)
.block(block)
.highlight_style(
Style::default()
.bg(theme.sel_bg)
.fg(theme.text_primary)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol("▶ ");
frame.render_stateful_widget(list, area, &mut app.tab_mut().commit_list_state);
}