1use std::path::PathBuf;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum HunkKind {
8 Add,
9 Change,
10 Delete,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum LineOrigin {
17 Context,
18 Addition,
19 Deletion,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct DiffLine {
26 pub origin: LineOrigin,
27 pub old_lineno: Option<usize>,
28 pub new_lineno: Option<usize>,
29 pub text: Vec<u8>,
32 pub has_newline: bool,
35}
36
37impl DiffLine {
38 pub fn text_str(&self) -> std::borrow::Cow<'_, str> {
40 String::from_utf8_lossy(&self.text)
41 }
42 pub fn bytes_with_terminator(&self) -> Vec<u8> {
44 let mut b = self.text.clone();
45 if self.has_newline {
46 b.push(b'\n');
47 }
48 b
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct Hunk {
55 pub kind: HunkKind,
56 pub new_start: usize,
59 pub new_count: usize,
60 pub old_start: usize,
61 pub old_count: usize,
62 pub lines: Vec<DiffLine>,
63}
64
65#[derive(Debug, Clone)]
67pub struct FileDiff {
68 pub path: PathBuf,
69 pub hunks: Vec<Hunk>,
70 pub added: usize,
71 pub deleted: usize,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum Sign {
78 AddOrChange,
80 DeleteAfter,
83}
84
85impl Hunk {
86 pub fn signs(&self) -> Vec<(usize, Sign)> {
88 let mut out = Vec::new();
89 let mut nl = self.new_start;
90 for line in &self.lines {
91 match line.origin {
92 LineOrigin::Addition => {
93 out.push((nl, Sign::AddOrChange));
94 nl += 1;
95 }
96 LineOrigin::Deletion => out.push((nl, Sign::DeleteAfter)),
97 LineOrigin::Context => nl += 1,
98 }
99 }
100 out
101 }
102
103 pub fn changed_region(&self) -> (usize, usize, usize, usize) {
108 let mut nl = self.new_start;
109 let mut ol = self.old_start;
110 let mut new_lines = Vec::new();
111 let mut old_lines = Vec::new();
112 for line in &self.lines {
113 match line.origin {
114 LineOrigin::Addition => {
115 new_lines.push(nl);
116 nl += 1;
117 }
118 LineOrigin::Deletion => {
119 old_lines.push(ol);
120 ol += 1;
121 }
122 LineOrigin::Context => {
123 nl += 1;
124 ol += 1;
125 }
126 }
127 }
128 let new_first = new_lines.first().copied().unwrap_or(nl);
129 let old_first = old_lines.first().copied().unwrap_or(ol);
130 (new_first, new_lines.len(), old_first, old_lines.len())
131 }
132
133 pub fn covers(&self, line_1based: usize, total_lines: usize) -> bool {
136 self.signs().iter().any(|&(l, kind)| match kind {
137 Sign::AddOrChange => l == line_1based,
138 Sign::DeleteAfter => l.min(total_lines) == line_1based,
139 })
140 }
141
142 pub fn header(&self) -> String {
144 format!(
145 "@@ -{},{} +{},{} @@",
146 self.old_start, self.old_count, self.new_start, self.new_count
147 )
148 }
149
150 pub fn build(
154 old_start: usize,
155 old_count: usize,
156 new_start: usize,
157 new_count: usize,
158 lines: Vec<DiffLine>,
159 ) -> Self {
160 let has_add = lines.iter().any(|l| l.origin == LineOrigin::Addition);
161 let has_del = lines.iter().any(|l| l.origin == LineOrigin::Deletion);
162 let kind = match (has_add, has_del) {
163 (true, false) => HunkKind::Add,
164 (false, true) => HunkKind::Delete,
165 _ => HunkKind::Change,
166 };
167 Hunk {
168 kind,
169 new_start,
170 new_count,
171 old_start,
172 old_count,
173 lines,
174 }
175 }
176}