use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph, Wrap};
use ratatui::Frame;
use gitkraft_core::DiffLine;
use crate::app::{ActivePane, App};
pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
let theme = app.theme();
let is_active = app.active_pane == ActivePane::DiffView;
let border_color = if is_active {
theme.border_active
} else {
theme.border_inactive
};
if app.tab().commit_files.len() > 1 {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Length(30), Constraint::Min(20)])
.split(area);
render_file_list(app, frame, chunks[0], border_color);
render_diff_content(app, frame, chunks[1], border_color);
} else {
render_diff_content(app, frame, area, border_color);
}
}
fn render_file_list(
app: &mut App,
frame: &mut Frame,
area: Rect,
border_color: ratatui::style::Color,
) {
let theme = app.theme();
let tab = app.tab();
let commit_diff_file_index = tab.commit_diff_file_index;
let block = Block::default()
.title(format!(" Files ({}) ", tab.commit_files.len()))
.borders(Borders::ALL)
.border_style(Style::default().fg(border_color));
let items: Vec<ListItem> = tab
.commit_files
.iter()
.enumerate()
.map(|(i, diff)| {
let is_selected = i == commit_diff_file_index;
let file_name = diff.file_name();
let status_char = format!("{}", diff.status);
let status_color = match diff.status.color_category() {
gitkraft_core::StatusColorCategory::Added => theme.success,
gitkraft_core::StatusColorCategory::Modified => theme.warning,
gitkraft_core::StatusColorCategory::Deleted => theme.error,
gitkraft_core::StatusColorCategory::Renamed => theme.accent,
};
let name_color = if is_selected {
theme.text_primary
} else {
theme.text_secondary
};
let line = Line::from(vec![
Span::styled(
format!("{} ", status_char),
Style::default()
.fg(status_color)
.add_modifier(Modifier::BOLD),
),
Span::styled(file_name.to_string(), Style::default().fg(name_color)),
]);
ListItem::new(line)
})
.collect();
let mut list_state = ratatui::widgets::ListState::default();
list_state.select(Some(commit_diff_file_index));
let list = List::new(items)
.block(block)
.highlight_style(
Style::default()
.bg(theme.sel_bg)
.add_modifier(Modifier::REVERSED),
)
.highlight_symbol("▸ ");
frame.render_stateful_widget(list, area, &mut list_state);
}
fn render_diff_content(
app: &mut App,
frame: &mut Frame,
area: Rect,
border_color: ratatui::style::Color,
) {
let theme = app.theme();
let tab = app.tab_mut();
let title = match &tab.selected_diff {
Some(diff) => {
let name = diff.display_path();
if name.is_empty() {
" Diff ".to_string()
} else {
format!(" Diff: {} ", name)
}
}
None => " Diff ".to_string(),
};
let block = Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(Style::default().fg(border_color));
match &tab.selected_diff {
None => {
let placeholder = Paragraph::new(Line::from(vec![Span::styled(
"Select a commit or file to view diff",
Style::default().fg(theme.text_muted),
)]))
.block(block)
.alignment(ratatui::layout::Alignment::Center);
frame.render_widget(placeholder, area);
}
Some(diff) => {
let mut lines: Vec<Line> = Vec::new();
for hunk in &diff.hunks {
for line in &hunk.lines {
let styled_line = match line {
DiffLine::Addition(s) => Line::from(Span::styled(
format!("+{}", s),
Style::default().fg(theme.diff_add),
)),
DiffLine::Deletion(s) => Line::from(Span::styled(
format!("-{}", s),
Style::default().fg(theme.diff_del),
)),
DiffLine::Context(s) => Line::from(Span::styled(
format!(" {}", s),
Style::default().fg(theme.diff_context),
)),
DiffLine::HunkHeader(s) => Line::from(Span::styled(
s.clone(),
Style::default()
.fg(theme.diff_hunk)
.add_modifier(Modifier::BOLD),
)),
};
lines.push(styled_line);
}
}
let content_height = lines.len() as u16;
let visible_height = area.height.saturating_sub(2); if content_height > visible_height {
if tab.diff_scroll > content_height.saturating_sub(visible_height) {
tab.diff_scroll = content_height.saturating_sub(visible_height);
}
} else {
tab.diff_scroll = 0;
}
let paragraph = Paragraph::new(lines)
.block(block)
.wrap(Wrap { trim: false })
.scroll((tab.diff_scroll, 0));
frame.render_widget(paragraph, area);
}
}
}