1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use anyhow::Result;
use std::fmt::Write;
/// Represents a line in a diff
#[derive(Debug)]
pub enum DiffLine {
Added(String),
Removed(String),
Context(String),
}
/// Utilities for generating and formatting diffs between text
pub struct DiffTools;
impl DiffTools {
/// Generate a diff between two strings
pub fn generate_diff(old_text: &str, new_text: &str) -> Vec<DiffLine> {
let old_lines: Vec<&str> = old_text.lines().collect();
let new_lines: Vec<&str> = new_text.lines().collect();
// Simple line-by-line diff implementation
let mut diff = Vec::new();
let mut i = 0;
let mut j = 0;
while i < old_lines.len() || j < new_lines.len() {
if i < old_lines.len() && j < new_lines.len() && old_lines[i] == new_lines[j] {
// Line is the same
diff.push(DiffLine::Context(old_lines[i].to_string()));
i += 1;
j += 1;
} else {
// Find next matching line
let mut found_match = false;
// Look ahead in new_lines
for look_ahead in 0..3 {
if i < old_lines.len()
&& j + look_ahead < new_lines.len()
&& old_lines[i] == new_lines[j + look_ahead]
{
// Found a matching line in new_lines, add the added lines before it
for k in 0..look_ahead {
diff.push(DiffLine::Added(new_lines[j + k].to_string()));
}
j += look_ahead;
found_match = true;
break;
}
}
// Look ahead in old_lines if no match found
if !found_match {
for look_ahead in 0..3 {
if i + look_ahead < old_lines.len()
&& j < new_lines.len()
&& old_lines[i + look_ahead] == new_lines[j]
{
// Found a matching line in old_lines, add the removed lines before it
for k in 0..look_ahead {
diff.push(DiffLine::Removed(old_lines[i + k].to_string()));
}
i += look_ahead;
found_match = true;
break;
}
}
}
// If no match found, add one line as difference
if !found_match {
if i < old_lines.len() {
diff.push(DiffLine::Removed(old_lines[i].to_string()));
i += 1;
}
if j < new_lines.len() {
diff.push(DiffLine::Added(new_lines[j].to_string()));
j += 1;
}
}
}
}
diff
}
/// Format diff as a string with line numbers and colors
pub fn format_diff(diff: &[DiffLine], file_path: &str) -> Result<String> {
let mut output = String::new();
let mut line_number = 0;
let mut adds = 0;
let mut removes = 0;
// Count additions and removals first
for line in diff {
match line {
DiffLine::Added(_) => adds += 1,
DiffLine::Removed(_) => removes += 1,
_ => {}
}
}
// Add header
writeln!(
output,
" ⎿ Updated {} with {} addition{} and {} removal{}",
file_path,
adds,
if adds == 1 { "" } else { "s" },
removes,
if removes == 1 { "" } else { "s" }
)?;
// Only show the diff if there are changes
if adds > 0 || removes > 0 {
// Add the diff content with line numbers and colored indicators
for line in diff {
match line {
DiffLine::Context(text) => {
line_number += 1;
writeln!(output, " {:3} {}", line_number, text)?;
}
DiffLine::Added(text) => {
line_number += 1;
// Use ANSI colors to show additions in light green
writeln!(output, " \x1b[92m{:3}+ {}\x1b[0m", line_number, text)?;
}
DiffLine::Removed(text) => {
// For removed lines, use a darker red color
// Don't increment line number for removed lines
writeln!(output, " \x1b[91m{:3}- {}\x1b[0m", line_number, text)?;
}
}
}
}
Ok(output)
}
}