1use ropey::Rope;
15
16use crate::types::HunkIndex;
17
18pub fn apply_two_way(a: &Rope, b: &Rope, hunks: &HunkIndex) -> Rope {
26 let mut result = a.clone();
27 for hunk in hunks.hunks.iter().rev() {
28 let a_range = hunk.ranges[0];
29 let b_range = hunk.ranges[1];
30
31 let a_char_start = line_to_char_clamped(&result, a_range.start);
32 let a_char_end = line_to_char_clamped(&result, a_range.end);
33
34 let b_text = if b_range.is_empty() {
35 String::new()
36 } else {
37 let b_char_start = line_to_char_clamped(b, b_range.start);
38 let b_char_end = line_to_char_clamped(b, b_range.end);
39 b.slice(b_char_start..b_char_end).to_string()
40 };
41
42 if a_char_start < a_char_end {
43 result.remove(a_char_start..a_char_end);
44 }
45 if !b_text.is_empty() {
46 result.insert(a_char_start, &b_text);
47 }
48 }
49 result
50}
51
52fn line_to_char_clamped(rope: &Rope, line: u32) -> usize {
58 let max = rope.len_lines();
59 let clamped = (line as usize).min(max);
60 rope.line_to_char(clamped)
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::DiffAlgorithm;
67 use crate::compute::two_way;
68
69 fn round_trip(a: &Rope, b: &Rope, algorithm: DiffAlgorithm) -> Rope {
72 let hunks = two_way(a, b, algorithm);
73 apply_two_way(a, b, &hunks)
74 }
75
76 fn rt_eq(a: &str, b: &str) {
77 for alg in [
78 DiffAlgorithm::Histogram,
79 DiffAlgorithm::Myers,
80 DiffAlgorithm::MyersMinimal,
81 ] {
82 let a_rope = Rope::from(a);
83 let b_rope = Rope::from(b);
84 let result = round_trip(&a_rope, &b_rope, alg);
85 assert_eq!(
86 result.to_string(),
87 b,
88 "round trip failed for algorithm {alg:?}\n a = {a:?}\n b = {b:?}"
89 );
90 }
91 }
92
93 #[test]
94 fn round_trip_identical() {
95 rt_eq("alpha\nbeta\ngamma\n", "alpha\nbeta\ngamma\n");
96 }
97
98 #[test]
99 fn round_trip_empty_both() {
100 rt_eq("", "");
101 }
102
103 #[test]
104 fn round_trip_empty_to_content() {
105 rt_eq("", "alpha\nbeta\n");
106 }
107
108 #[test]
109 fn round_trip_content_to_empty() {
110 rt_eq("alpha\nbeta\n", "");
111 }
112
113 #[test]
114 fn round_trip_single_change() {
115 rt_eq("alpha\nbeta\ngamma\n", "alpha\nBETA\ngamma\n");
116 }
117
118 #[test]
119 fn round_trip_insert_middle() {
120 rt_eq("alpha\ngamma\n", "alpha\nbeta\ngamma\n");
121 }
122
123 #[test]
124 fn round_trip_delete_middle() {
125 rt_eq("alpha\nbeta\ngamma\n", "alpha\ngamma\n");
126 }
127
128 #[test]
129 fn round_trip_multiple_hunks() {
130 rt_eq(
131 "alpha\nbeta\ngamma\ndelta\nepsilon\nzeta\n",
132 "alpha\nBETA\ngamma\nDELTA\nepsilon\nZETA\n",
133 );
134 }
135
136 #[test]
137 fn round_trip_block_replacement() {
138 rt_eq(
139 "alpha\nbeta\ngamma\ndelta\n",
140 "alpha\nfoo\nbar\nbaz\nquux\ndelta\n",
141 );
142 }
143
144 #[test]
145 fn round_trip_no_trailing_newline() {
146 rt_eq("alpha\nbeta", "alpha\nBETA");
147 }
148
149 #[test]
150 fn round_trip_mixed_trailing_newline() {
151 rt_eq("alpha\nbeta", "alpha\nbeta\n");
152 }
153}