1use std::ops::Range;
2
3use sum_tree::{Bias, SumTree};
4use text::{Anchor, BufferSnapshot, Point, Rope};
5
6pub use git2 as libgit;
7use libgit::{DiffOptions as GitOptions, Patch as GitPatch};
8
9#[derive(Debug, Clone, Copy)]
10pub enum DiffHunkStatus {
11 Added,
12 Modified,
13 Removed,
14}
15
16#[derive(Debug, Clone)]
17pub struct DiffHunk<T> {
18 pub buffer_range: Range<T>,
19 pub head_range: Range<usize>,
20}
21
22impl DiffHunk<u32> {
23 pub fn status(&self) -> DiffHunkStatus {
24 if self.head_range.is_empty() {
25 DiffHunkStatus::Added
26 } else if self.buffer_range.is_empty() {
27 DiffHunkStatus::Removed
28 } else {
29 DiffHunkStatus::Modified
30 }
31 }
32}
33
34impl sum_tree::Item for DiffHunk<Anchor> {
35 type Summary = DiffHunkSummary;
36
37 fn summary(&self) -> Self::Summary {
38 DiffHunkSummary {
39 head_range: self.head_range.clone(),
40 }
41 }
42}
43
44#[derive(Debug, Default, Clone)]
45pub struct DiffHunkSummary {
46 head_range: Range<usize>,
47}
48
49impl sum_tree::Summary for DiffHunkSummary {
50 type Context = ();
51
52 fn add_summary(&mut self, other: &Self, _: &Self::Context) {
53 self.head_range.start = self.head_range.start.min(other.head_range.start);
54 self.head_range.end = self.head_range.end.max(other.head_range.end);
55 }
56}
57
58#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
59struct HunkHeadEnd(usize);
60
61impl<'a> sum_tree::Dimension<'a, DiffHunkSummary> for HunkHeadEnd {
62 fn add_summary(&mut self, summary: &'a DiffHunkSummary, _: &()) {
63 self.0 = summary.head_range.end;
64 }
65
66 fn from_summary(summary: &'a DiffHunkSummary, _: &()) -> Self {
67 HunkHeadEnd(summary.head_range.end)
68 }
69}
70
71struct HunkIter<'a> {
72 index: usize,
73 patch: GitPatch<'a>,
74}
75
76impl<'a> HunkIter<'a> {
77 fn diff(head: &'a [u8], current: &'a [u8]) -> Option<Self> {
78 let mut options = GitOptions::default();
79 options.context_lines(0);
80 let patch = match GitPatch::from_buffers(head, None, current, None, Some(&mut options)) {
81 Ok(patch) => patch,
82 Err(_) => return None,
83 };
84
85 Some(HunkIter { index: 0, patch })
86 }
87
88 fn next(&mut self, buffer: &BufferSnapshot) -> Option<DiffHunk<Anchor>> {
89 if self.index >= self.patch.num_hunks() {
90 return None;
91 }
92
93 let (hunk, _) = match self.patch.hunk(self.index) {
94 Ok(it) => it,
95 Err(_) => return None,
96 };
97
98 let new_start = hunk.new_start() - 1;
99 let new_end = new_start + hunk.new_lines();
100 let start_anchor = buffer.anchor_at(Point::new(new_start, 0), Bias::Left);
101 let end_anchor = buffer.anchor_at(Point::new(new_end, 0), Bias::Left);
102 let buffer_range = start_anchor..end_anchor;
103
104 //This is probably wrong? When does this trigger? Should buffer range also do this?
105 let head_range = if hunk.old_start() == 0 {
106 0..0
107 } else {
108 let old_start = hunk.old_start() as usize - 1;
109 let old_end = old_start + hunk.old_lines() as usize;
110 old_start..old_end
111 };
112
113 self.index += 1;
114 Some(DiffHunk {
115 buffer_range,
116 head_range,
117 })
118 }
119}
120
121pub struct BufferDiff {
122 last_update_version: clock::Global,
123 hunks: SumTree<DiffHunk<Anchor>>,
124}
125
126impl BufferDiff {
127 pub fn new(head_text: &Option<String>, buffer: &text::BufferSnapshot) -> BufferDiff {
128 let hunks = if let Some(head_text) = head_text {
129 let buffer_string = buffer.as_rope().to_string();
130 let buffer_bytes = buffer_string.as_bytes();
131 let iter = HunkIter::diff(head_text.as_bytes(), buffer_bytes);
132 if let Some(mut iter) = iter {
133 println!("some iter");
134 let mut hunks = SumTree::new();
135 while let Some(hunk) = iter.next(buffer) {
136 println!("hunk");
137 hunks.push(hunk, &());
138 }
139 hunks
140 } else {
141 SumTree::new()
142 }
143 } else {
144 SumTree::new()
145 };
146
147 BufferDiff {
148 last_update_version: buffer.version().clone(),
149 hunks,
150 }
151 }
152
153 pub fn hunks(&self) -> &SumTree<DiffHunk<Anchor>> {
154 &self.hunks
155 }
156
157 pub fn update(&mut self, head: &Rope, buffer: &text::BufferSnapshot) {
158 let expand_by = 20;
159 let combine_distance = 5;
160
161 struct EditRange {
162 head_start: u32,
163 head_end: u32,
164 buffer_start: u32,
165 buffer_end: u32,
166 }
167
168 let mut ranges = Vec::<EditRange>::new();
169
170 for edit in buffer.edits_since::<Point>(&self.last_update_version) {
171 //This bit is extremely wrong, this is not where these row lines should come from
172 let head_start = edit.old.start.row.saturating_sub(expand_by);
173 let head_end = (edit.old.end.row + expand_by).min(head.summary().lines.row + 1);
174
175 let buffer_start = edit.new.start.row.saturating_sub(expand_by);
176 let buffer_end = (edit.new.end.row + expand_by).min(buffer.row_count());
177
178 if let Some(last_range) = ranges.last_mut() {
179 let head_distance = last_range.head_end.abs_diff(head_end);
180 let buffer_distance = last_range.buffer_end.abs_diff(buffer_end);
181
182 if head_distance <= combine_distance || buffer_distance <= combine_distance {
183 last_range.head_start = last_range.head_start.min(head_start);
184 last_range.head_end = last_range.head_end.max(head_end);
185
186 last_range.buffer_start = last_range.buffer_start.min(buffer_start);
187 last_range.buffer_end = last_range.buffer_end.max(buffer_end);
188 } else {
189 ranges.push(EditRange {
190 head_start,
191 head_end,
192 buffer_start,
193 buffer_end,
194 });
195 }
196 } else {
197 ranges.push(EditRange {
198 head_start,
199 head_end,
200 buffer_start,
201 buffer_end,
202 });
203 }
204 }
205
206 self.last_update_version = buffer.version().clone();
207
208 let mut new_hunks = SumTree::new();
209 let mut cursor = self.hunks.cursor::<HunkHeadEnd>();
210
211 for range in ranges {
212 let head_range = range.head_start..range.head_end;
213 let head_slice = head.slice_rows(head_range.clone());
214 let head_str = head_slice.to_string();
215
216 let buffer_range = range.buffer_start..range.buffer_end;
217 let buffer_slice = buffer.as_rope().slice_rows(buffer_range.clone());
218 let buffer_str = buffer_slice.to_string();
219
220 println!("diffing head {:?}, buffer {:?}", head_range, buffer_range);
221
222 let mut iter = match HunkIter::diff(head_str.as_bytes(), buffer_str.as_bytes()) {
223 Some(iter) => iter,
224 None => continue,
225 };
226
227 while let Some(hunk) = iter.next(buffer) {
228 println!("hunk");
229 let prefix = cursor.slice(&HunkHeadEnd(hunk.head_range.end), Bias::Right, &());
230 println!("prefix len: {}", prefix.iter().count());
231 new_hunks.extend(prefix.iter().cloned(), &());
232
233 new_hunks.push(hunk.clone(), &());
234
235 cursor.seek(&HunkHeadEnd(hunk.head_range.end), Bias::Right, &());
236 println!("item: {:?}", cursor.item());
237 if let Some(item) = cursor.item() {
238 if item.head_range.end <= hunk.head_range.end {
239 println!("skipping");
240 cursor.next(&());
241 }
242 }
243 }
244 }
245
246 new_hunks.extend(
247 cursor
248 .suffix(&())
249 .iter()
250 .map(|i| {
251 println!("extending with {i:?}");
252 i
253 })
254 .cloned(),
255 &(),
256 );
257 drop(cursor);
258
259 self.hunks = new_hunks;
260 }
261}
262
263#[derive(Debug, Clone, Copy)]
264pub enum GitDiffEdit {
265 Added(u32),
266 Modified(u32),
267 Removed(u32),
268}
269
270impl GitDiffEdit {
271 pub fn line(self) -> u32 {
272 use GitDiffEdit::*;
273
274 match self {
275 Added(line) | Modified(line) | Removed(line) => line,
276 }
277 }
278}