1use std::ops::Range;
2use std::sync::Arc;
3
4use sum_tree::Bias;
5use text::{Anchor, Point};
6
7pub use git2 as libgit;
8use libgit::Patch as GitPatch;
9
10#[derive(Debug, Clone, Copy)]
11pub enum DiffHunkStatus {
12 Added,
13 Modified,
14 Removed,
15}
16
17#[derive(Debug)]
18pub struct DiffHunk<T> {
19 pub buffer_range: Range<T>,
20 pub head_range: Range<usize>,
21}
22
23impl DiffHunk<u32> {
24 pub fn status(&self) -> DiffHunkStatus {
25 if self.head_range.is_empty() {
26 DiffHunkStatus::Added
27 } else if self.buffer_range.is_empty() {
28 DiffHunkStatus::Removed
29 } else {
30 DiffHunkStatus::Modified
31 }
32 }
33}
34
35pub struct BufferDiff {
36 hunks: Arc<[DiffHunk<Anchor>]>,
37}
38
39impl BufferDiff {
40 pub fn new() -> BufferDiff {
41 BufferDiff {
42 hunks: Arc::new([]),
43 }
44 }
45
46 pub fn hunks(&self) -> Arc<[DiffHunk<Anchor>]> {
47 self.hunks.clone()
48 }
49
50 pub fn update(&mut self, head: &str, buffer: &text::BufferSnapshot) {
51 let current = buffer.as_rope().to_string().into_bytes();
52 let patch = match GitPatch::from_buffers(head.as_bytes(), None, ¤t, None, None) {
53 Ok(patch) => patch,
54 Err(_) => {
55 //Reset hunks in case of failure to avoid showing a stale (potentially erroneous) diff
56 self.hunks = Arc::new([]);
57 return;
58 }
59 };
60
61 let mut hunks = Vec::new();
62 for index in 0..patch.num_hunks() {
63 let (hunk, _) = match patch.hunk(index) {
64 Ok(it) => it,
65 Err(_) => break,
66 };
67
68 let new_start = hunk.new_start();
69 let new_end = new_start + hunk.new_lines();
70 let start_anchor = buffer.anchor_at(Point::new(new_start, 0), Bias::Left);
71 let end_anchor = buffer.anchor_at(Point::new(new_end, 0), Bias::Left);
72 let buffer_range = start_anchor..end_anchor;
73
74 let old_start = hunk.old_start() as usize;
75 let old_end = old_start + hunk.old_lines() as usize;
76 let head_range = old_start..old_end;
77
78 hunks.push(DiffHunk {
79 buffer_range,
80 head_range,
81 });
82 }
83
84 self.hunks = hunks.into();
85 }
86}
87
88#[derive(Debug, Clone, Copy)]
89pub enum GitDiffEdit {
90 Added(u32),
91 Modified(u32),
92 Removed(u32),
93}
94
95impl GitDiffEdit {
96 pub fn line(self) -> u32 {
97 use GitDiffEdit::*;
98
99 match self {
100 Added(line) | Modified(line) | Removed(line) => line,
101 }
102 }
103}
104
105// struct DiffTracker {
106// track_line_num: u32,
107// edits: Vec<GitDiffEdit>,
108// }
109
110// impl DiffTracker {
111// fn new() -> DiffTracker {
112// DiffTracker {
113// track_line_num: 0,
114// edits: Vec::new(),
115// }
116// }
117
118// fn attempt_finalize_file(&mut self, base_path: &Path) -> Result<()> {
119// let relative = if let Some(relative) = self.last_file_path.clone() {
120// relative
121// } else {
122// return Ok(());
123// };
124
125// let mut path = base_path.to_path_buf();
126// path.push(relative);
127// path = canonicalize(path).map_err(Error::Io)?;
128
129// self.diffs.push(GitFileDiff {
130// path,
131// edits: take(&mut self.edits),
132// });
133
134// Ok(())
135// }
136
137// fn handle_diff_line(
138// &mut self,
139// delta: DiffDelta,
140// line: DiffLine,
141// base_path: &Path,
142// ) -> Result<()> {
143// let path = match (delta.old_file().path(), delta.new_file().path()) {
144// (Some(old), _) => old,
145// (_, Some(new)) => new,
146// (_, _) => return Err(Error::DeltaMissingPath),
147// };
148
149// if self.last_file_path.as_deref() != Some(path) {
150// self.attempt_finalize_file(base_path)?;
151// self.last_file_path = Some(path.to_path_buf());
152// self.track_line_num = 0;
153// }
154
155// match line.origin_value() {
156// DiffLineType::Context => {
157// self.track_line_num = line.new_lineno().ok_or(Error::ContextMissingLineNum)?;
158// }
159
160// DiffLineType::Deletion => {
161// self.track_line_num += 1;
162// self.edits.push(GitDiffEdit::Removed(self.track_line_num));
163// }
164
165// DiffLineType::Addition => {
166// let addition_line_num = line.new_lineno().ok_or(Error::AdditionMissingLineNum)?;
167// self.track_line_num = addition_line_num;
168
169// let mut replaced = false;
170// for rewind_index in (0..self.edits.len()).rev() {
171// let edit = &mut self.edits[rewind_index];
172
173// if let GitDiffEdit::Removed(removed_line_num) = *edit {
174// match removed_line_num.cmp(&addition_line_num) {
175// Ordering::Equal => {
176// *edit = GitDiffEdit::Modified(addition_line_num);
177// replaced = true;
178// break;
179// }
180
181// Ordering::Greater => continue,
182// Ordering::Less => break,
183// }
184// }
185// }
186
187// if !replaced {
188// self.edits.push(GitDiffEdit::Added(addition_line_num));
189// }
190// }
191
192// _ => {}
193// }
194
195// Ok(())
196// }
197// }