git.rs

  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}