diff_tool/services/
git.rs1use std::{
2 path::Path,
3 process::{Command, Stdio},
4};
5
6#[derive(Default, Debug)]
7pub struct Diff {
8 old_diff: Vec<DiffLine>,
9 current_diff: Vec<DiffLine>,
10}
11
12impl Diff {
13 pub fn longest_diff_len(&self) -> usize {
14 let old_diff = self.old_diff.len();
15 let current_diff = self.current_diff.len();
16
17 std::cmp::max(old_diff, current_diff) - 1
18 }
19
20 pub fn old_diff(&self) -> &[DiffLine] {
21 &self.old_diff
22 }
23
24 pub fn current_diff(&self) -> &[DiffLine] {
25 &self.current_diff
26 }
27
28 pub fn largest_line_number_char_len(&self) -> u16 {
29 let (old_diff, current_diff) = self.largest_line_number();
30
31 let largest_line_number = std::cmp::max(old_diff, current_diff);
32
33 let length = std::cmp::min(largest_line_number.to_string().len(), u16::MAX.into());
34 length.try_into().unwrap_or(4)
35 }
36
37 fn largest_line_number(&self) -> (usize, usize) {
39 let old_diff = self
40 .old_diff
41 .iter()
42 .map(|x| x.line_number().unwrap_or(0))
43 .max()
44 .unwrap_or(0);
45
46 let current_diff = self
47 .old_diff
48 .iter()
49 .map(|x| x.line_number().unwrap_or(0))
50 .max()
51 .unwrap_or(0);
52
53 (old_diff, current_diff)
54 }
55
56 pub fn parse_diff(diff_string: &str) -> Self {
57 let lines = diff_string.split("\n");
58
59 let mut diff = Self::default();
60
61 let mut start = false;
62 let mut additions = 0;
63 let mut removals = 0;
64 let mut diff_one_line = 1;
65 let mut diff_two_line = 1;
66
67 for line in lines {
68 if line.starts_with("@@") && line.ends_with("@@") {
69 start = true;
70 continue;
71 }
72
73 if !start {
74 continue;
75 }
76
77 let (prefix, content) = remove_first_char(line);
78
79 match prefix {
80 '+' => {
81 diff.current_diff.push(DiffLine::new(
82 content,
83 DiffKind::Addition,
84 Some(diff_two_line),
85 ));
86 diff_two_line += 1;
87 if removals > 0 {
88 removals -= 1
89 } else {
90 additions += 1
91 }
92 }
93
94 '-' => {
95 diff.old_diff.push(DiffLine::new(
96 content,
97 DiffKind::Removal,
98 Some(diff_one_line),
99 ));
100 diff_one_line += 1;
101 removals += 1
102 }
103 _ => {
104 for _ in 0..removals {
105 diff.current_diff
106 .push(DiffLine::new("", DiffKind::Blank, None))
107 }
108
109 removals = 0;
110
111 for _ in 0..additions {
112 diff.old_diff.push(DiffLine::new("", DiffKind::Blank, None))
113 }
114
115 additions = 0;
116
117 diff.old_diff.push(DiffLine::new(
118 content,
119 DiffKind::Neutral,
120 Some(diff_one_line),
121 ));
122 diff_one_line += 1;
123 diff.current_diff.push(DiffLine::new(
124 content,
125 DiffKind::Neutral,
126 Some(diff_two_line),
127 ));
128 diff_two_line += 1
129 }
130 }
131 }
132
133 for _ in 0..removals {
134 diff.current_diff
135 .push(DiffLine::new("", DiffKind::Blank, None))
136 }
137
138 for _ in 0..additions {
139 diff.old_diff.push(DiffLine::new("", DiffKind::Blank, None))
140 }
141
142 diff
143 }
144}
145
146#[derive(Debug, Clone, Default)]
147pub struct DiffLine {
148 content: String,
149 kind: DiffKind,
150 line_number: Option<usize>,
151}
152
153impl DiffLine {
154 fn new(content: &str, kind: DiffKind, line_number: Option<usize>) -> Self {
155 let content = content.to_string();
156 Self {
157 content,
158 kind,
159 line_number,
160 }
161 }
162
163 pub fn content(&self) -> &str {
164 &self.content
165 }
166
167 pub fn kind(&self) -> &DiffKind {
168 &self.kind
169 }
170
171 pub fn line_number(&self) -> &Option<usize> {
172 &self.line_number
173 }
174}
175
176#[derive(Debug, Clone, Copy, Default)]
177pub enum DiffKind {
178 Addition,
179 Removal,
180 Neutral,
181 #[default]
182 Blank,
183}
184
185impl DiffKind {
186 pub fn value(&self) -> &str {
187 match self {
188 DiffKind::Addition => "+",
189 DiffKind::Removal => "-",
190 DiffKind::Neutral => " ",
191 DiffKind::Blank => " ",
192 }
193 }
194}
195
196pub fn get_raw_diff(path: &Path, dir_flag: bool) -> String {
198 let args = if !dir_flag {
199 vec!["diff", "-U1000", path.to_str().unwrap()]
200 } else {
201 vec![
202 "-C",
203 path.parent().unwrap().to_str().unwrap(),
204 "diff",
205 "-U1000",
206 path.file_name().unwrap().to_str().unwrap(),
207 ]
208 };
209
210 let output = Command::new("git")
212 .args(args)
213 .stdout(Stdio::piped())
214 .output()
215 .expect("Failed to execute git diff");
216
217 String::from_utf8(output.stdout).expect("UTF8 data to convert to string")
219}
220
221fn remove_first_char(string: &str) -> (char, &str) {
222 let mut chars = string.chars();
223 (chars.next().unwrap_or(' '), chars.as_str())
224}