diff_tool/view/
body.rs

1use ratatui::{
2    layout::{Constraint, Layout, Rect},
3    style::{Color, Modifier, Style},
4    text::{Line, Span},
5    widgets::{Block, BorderType, Row, Table},
6    Frame,
7};
8
9use crate::{
10    app::App,
11    services::git::{DiffKind, DiffLine},
12};
13
14pub(super) fn render_body(model: &mut App, f: &mut Frame, area: Rect) {
15    // Body Layout (Left Diff & Right Diff)
16    let [left_side, right_side] =
17        Layout::horizontal(Constraint::from_percentages([50, 50])).areas(area);
18
19    let line_number_char_len = model.diff().unwrap().largest_line_number_char_len();
20
21    // Old/Left Diff
22    let old_diff = model.diff().unwrap().old_diff();
23    let old_diff_table = build_diff_table(old_diff, false, line_number_char_len);
24    let mut old_diff_state = model.diff_state().old_diff().borrow_mut();
25
26    // Current/Right Diff
27    let current_diff = model.diff().unwrap().current_diff();
28    let current_diff_table = build_diff_table(current_diff, true, line_number_char_len);
29    let mut current_diff_state = model.diff_state().current_diff().borrow_mut();
30
31    f.render_stateful_widget(old_diff_table, left_side, &mut old_diff_state);
32    f.render_stateful_widget(current_diff_table, right_side, &mut current_diff_state)
33}
34
35/// Draws a diff table
36fn build_diff_table(diff: &[DiffLine], is_current_diff: bool, line_number_char_len: u16) -> Table {
37    let diff_title = if is_current_diff { "New" } else { "Original" };
38
39    let rows = diff.iter().map(parse_diff_line);
40
41    // Dynamic column width
42    let widths = [
43        // Line Number col depends on the largest line number
44        Constraint::Length(line_number_char_len),
45        Constraint::Percentage(2),
46        Constraint::Percentage(97),
47    ];
48
49    Table::new(rows, widths)
50        .block(
51            Block::bordered()
52                .title(Span::styled(
53                    diff_title,
54                    Style::default().fg(Color::LightCyan),
55                ))
56                .style(Style::default().fg(Color::White))
57                .border_type(BorderType::Plain),
58        )
59        .highlight_style(if is_current_diff {
60            Style::default()
61                .fg(Color::Magenta)
62                .add_modifier(Modifier::BOLD)
63        } else {
64            Style::default()
65        })
66        .highlight_symbol(">>")
67}
68
69fn parse_diff_line(line: &DiffLine) -> Row {
70    // TODO: The styling should be a property of the model
71    let line_number_style = Style::default().fg(Color::Gray);
72
73    let (prefix_style, content_style) = match line.kind() {
74        DiffKind::Addition => (
75            Style::default()
76                .fg(Color::Green)
77                .add_modifier(Modifier::BOLD),
78            Style::default()
79                .bg(Color::Rgb(131, 242, 140))
80                .fg(Color::Black),
81        ),
82        DiffKind::Removal => (
83            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
84            Style::default().bg(Color::LightRed).fg(Color::Black),
85        ),
86        DiffKind::Neutral => (Style::default(), Style::default()),
87        DiffKind::Blank => (Style::default(), Style::default().bg(Color::DarkGray)),
88    };
89
90    let line_number = match line.line_number() {
91        Some(x) => x.to_string(),
92        None => " ".to_string(),
93    };
94
95    let prefix = line.kind().value();
96    let content = line.content();
97
98    Row::new([
99        Line::styled(line_number, line_number_style).right_aligned(),
100        Line::styled(prefix, prefix_style).centered(),
101        Line::styled(content, content_style),
102    ])
103}