use crate::components::Component;
use crate::app::{AppState, Action};
use crate::errors::ComponentError;
use crate::input::InputEvent;
use crate::ui::style;
use crate::ui::style::DiffKind;
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::widgets::Paragraph;
use ratatui::text::{Line, Span};
pub struct DiffPane;
impl DiffPane {
pub fn new() -> Self {
Self
}
}
impl Component for DiffPane {
fn handle_event(
&mut self,
_event: InputEvent,
_state: &AppState,
) -> Result<Option<Action>, ComponentError> {
Ok(None)
}
fn render(&mut self, frame: &mut Frame, area: Rect, state: &AppState) {
let theme = &state.theme;
const MAX_RENDER_LINES: usize = 1000;
let total_lines = state.last_diff.lines().count();
let capped_total = if state.load_full_diff { total_lines } else { total_lines.min(MAX_RENDER_LINES) };
let start = state.detail_scroll.min(capped_total.saturating_sub(1));
let visible_height = area.height.saturating_sub(1) as usize; let end = (start + visible_height).min(capped_total);
let visible_lines: Vec<Line> = state
.last_diff
.lines()
.skip(start)
.take(end.saturating_sub(start))
.enumerate()
.map(|(idx, l)| {
let mut style = if l.starts_with('+') {
style::diff_style(theme, DiffKind::Add)
} else if l.starts_with('-') {
style::diff_style(theme, DiffKind::Remove)
} else if l.starts_with("@@") {
style::diff_style(theme, DiffKind::Hunk)
} else {
style::diff_style(theme, DiffKind::Context)
};
let absolute = start + idx;
if state.line_select_mode && state.selected_lines.contains(&absolute) {
style = style::selection(theme);
}
Line::from(Span::styled(format!("{:>5} {}", absolute + 1, l), style))
})
.collect();
let mut base_footer = if !state.load_full_diff && total_lines > MAX_RENDER_LINES {
format!(
"visible {}-{} of {} (truncated to {}) — press L to load full",
start,
end.saturating_sub(1),
total_lines,
MAX_RENDER_LINES
)
} else {
format!(
"visible {}-{} of {} (full)",
start,
end.saturating_sub(1),
total_lines
)
};
if state.side_by_side {
if state.has_delta && state.use_delta {
base_footer.push_str(" • side-by-side (delta required; inline fallback)");
} else {
base_footer.push_str(" • side-by-side requested (delta not available)");
}
}
let footer_text = if state.line_select_mode {
format!(
"{} — line select mode (selected: {})",
base_footer,
state.selected_lines.len()
)
} else {
base_footer
};
let title = if state.diff_focus { "Diff (focused)" } else { "Diff" };
let block = style::pane_block(theme, title, state.diff_focus);
let chunks = ratatui::layout::Layout::default()
.direction(ratatui::layout::Direction::Vertical)
.constraints([
ratatui::layout::Constraint::Min(1),
ratatui::layout::Constraint::Length(1),
])
.split(area);
let paragraph = Paragraph::new(visible_lines)
.style(style::body_style(theme))
.block(block);
frame.render_widget(paragraph, chunks[0]);
let footer = Paragraph::new(footer_text).style(style::text(theme, style::Emphasis::Muted));
frame.render_widget(footer, chunks[1]);
}
fn name(&self) -> &'static str {
"DiffPane"
}
}