ratatui_toolkit/widgets/code_diff/widget/methods/helpers/
build_aligned_lines.rs

1use crate::widgets::code_diff::diff_hunk::DiffHunk;
2use crate::widgets::code_diff::diff_line::DiffLine;
3use crate::widgets::code_diff::enums::DiffLineKind;
4
5/// A pair of lines for side-by-side display.
6///
7/// In side-by-side mode, we need to align lines so that:
8/// - Context lines appear on both sides at the same row
9/// - Removed lines appear on the left, with empty space on the right
10/// - Added lines appear on the right, with empty space on the left
11#[derive(Debug, Clone)]
12pub struct AlignedLinePair {
13    /// The line to show on the left (old version).
14    pub left: Option<DiffLine>,
15
16    /// The line to show on the right (new version).
17    pub right: Option<DiffLine>,
18}
19
20/// Builds aligned line pairs from a hunk for side-by-side display.
21///
22/// This function takes the lines from a hunk and pairs them up so that:
23/// - Context lines have the same content on both sides
24/// - Removed lines are on the left with None on the right
25/// - Added lines are on the right with None on the left
26/// - Consecutive removes/adds are paired up when possible
27///
28/// # Arguments
29///
30/// * `hunk` - The diff hunk to process
31///
32/// # Returns
33///
34/// A vector of aligned line pairs for rendering
35pub fn build_aligned_lines(hunk: &DiffHunk) -> Vec<AlignedLinePair> {
36    let mut pairs: Vec<AlignedLinePair> = Vec::new();
37    let mut pending_removes: Vec<DiffLine> = Vec::new();
38    let mut pending_adds: Vec<DiffLine> = Vec::new();
39
40    for line in &hunk.lines {
41        match line.kind {
42            DiffLineKind::Context | DiffLineKind::HunkHeader => {
43                // Flush pending removes/adds before context
44                flush_pending(&mut pairs, &mut pending_removes, &mut pending_adds);
45
46                // Context lines appear on both sides
47                pairs.push(AlignedLinePair {
48                    left: Some(line.clone()),
49                    right: Some(line.clone()),
50                });
51            }
52            DiffLineKind::Removed => {
53                // If we have pending adds, we should flush first
54                // (removes should come before adds in the grouping)
55                if !pending_adds.is_empty() {
56                    flush_pending(&mut pairs, &mut pending_removes, &mut pending_adds);
57                }
58                pending_removes.push(line.clone());
59            }
60            DiffLineKind::Added => {
61                pending_adds.push(line.clone());
62            }
63        }
64    }
65
66    // Flush any remaining pending lines
67    flush_pending(&mut pairs, &mut pending_removes, &mut pending_adds);
68
69    pairs
70}
71
72/// Flushes pending removed and added lines into aligned pairs.
73///
74/// This pairs up removes with adds where possible, showing them
75/// side by side. Any extras are shown with None on the opposite side.
76fn flush_pending(
77    pairs: &mut Vec<AlignedLinePair>,
78    pending_removes: &mut Vec<DiffLine>,
79    pending_adds: &mut Vec<DiffLine>,
80) {
81    let remove_count = pending_removes.len();
82    let add_count = pending_adds.len();
83    let max_count = remove_count.max(add_count);
84
85    for i in 0..max_count {
86        let left = pending_removes.get(i).cloned();
87        let right = pending_adds.get(i).cloned();
88        pairs.push(AlignedLinePair { left, right });
89    }
90
91    pending_removes.clear();
92    pending_adds.clear();
93}