use ratatui::{
Frame,
layout::Rect,
style::Modifier,
text::{Line, Span},
widgets::Block,
};
use crate::tui::hunk_selector::{FileEntry, HunkEntry, HunkOrigin};
use crate::tui::theme::TuiTheme;
use crate::tui::widgets::common::hunk_line_style;
use crate::tui::widgets::diff_pane::DiffPane;
pub(crate) enum HunkEvent {
Handled,
WrapToPrevFile,
WrapToNextFile,
}
pub(crate) struct HunkView {
index: usize,
pane: DiffPane,
}
impl HunkView {
pub fn new() -> Self {
HunkView {
index: 0,
pane: DiffPane::new(),
}
}
#[cfg(test)]
pub fn index(&self) -> usize {
self.index
}
pub fn scroll(&self) -> u16 {
self.pane.scroll()
}
pub fn scroll_by(&mut self, delta: i32) {
self.pane.scroll_by(delta);
}
pub fn reset(&mut self) {
self.index = 0;
self.pane.reset();
}
pub fn move_up(&mut self, hunks: &[HunkEntry]) -> HunkEvent {
if self.index > 0 {
self.index -= 1;
self.scroll_to_hunk(hunks);
HunkEvent::Handled
} else {
HunkEvent::WrapToPrevFile
}
}
pub fn move_down(&mut self, hunks: &[HunkEntry]) -> HunkEvent {
if self.index + 1 < hunks.len() {
self.index += 1;
self.scroll_to_hunk(hunks);
HunkEvent::Handled
} else {
HunkEvent::WrapToNextFile
}
}
pub fn focus_last(&mut self, hunks: &[HunkEntry]) {
self.index = hunks.len().saturating_sub(1);
self.scroll_to_hunk(hunks);
}
pub fn toggle_focused(&mut self, hunks: &mut [HunkEntry]) {
if let Some(h) = hunks.get_mut(self.index) {
h.selected = !h.selected;
}
}
pub fn click(&mut self, hunks: &mut [HunkEntry], clicked_line: usize) {
let total = hunks.len();
let mut line: usize = 0;
for (i, entry) in hunks.iter_mut().enumerate() {
let hunk_lines = 1 + entry.hunk.text.lines().count();
let separator = if i + 1 < total { 1 } else { 0 };
if clicked_line < line + hunk_lines {
self.index = i;
if clicked_line == line {
entry.selected = !entry.selected;
}
return;
}
line += hunk_lines + separator;
}
}
fn scroll_to_hunk(&mut self, hunks: &[HunkEntry]) {
let row: usize = hunks[..self.index]
.iter()
.map(|entry| 2 + entry.hunk.text.lines().count())
.sum();
self.pane.set_scroll(u16::try_from(row).unwrap_or(u16::MAX));
}
pub fn build_lines(
&self,
file: &FileEntry,
theme: &TuiTheme,
focused: bool,
) -> Vec<Line<'static>> {
let total = file.hunks.len();
let mut lines: Vec<Line<'static>> = Vec::new();
for (i, entry) in file.hunks.iter().enumerate() {
let marker = if entry.selected { "\u{2713}" } else { " " };
let origin_label = match entry.origin {
HunkOrigin::Staged => " (staged)",
HunkOrigin::Unstaged => "",
HunkOrigin::Commit => "",
};
let header_text = format!("[{}] Hunk {}/{}{}", marker, i + 1, total, origin_label);
let header_style = if focused && i == self.index {
theme.hunk_header.add_modifier(Modifier::REVERSED)
} else {
theme.hunk_header
};
lines.push(Line::from(Span::styled(header_text, header_style)));
for raw_line in entry.hunk.text.lines() {
let style = hunk_line_style(raw_line, theme);
lines.push(Line::from(Span::styled(raw_line.to_string(), style)));
}
if i + 1 < total {
lines.push(Line::from(""));
}
}
lines.push(Line::from(""));
lines.push(Line::from(""));
lines
}
pub fn render(
&mut self,
frame: &mut Frame,
area: Rect,
lines: Vec<Line<'_>>,
block: Block<'_>,
) {
self.pane.render(frame, area, lines, block);
}
}