1use std::ops::Range;
2
3use git::diff::{DiffHunk, DiffHunkStatus};
4use language::Point;
5
6use crate::{
7 display_map::{DisplaySnapshot, ToDisplayPoint},
8 AnchorRangeExt,
9};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum DisplayDiffHunk {
13 Folded {
14 display_row: u32,
15 },
16
17 Unfolded {
18 display_row_range: Range<u32>,
19 status: DiffHunkStatus,
20 },
21}
22
23impl DisplayDiffHunk {
24 pub fn start_display_row(&self) -> u32 {
25 match self {
26 &DisplayDiffHunk::Folded { display_row } => display_row,
27 DisplayDiffHunk::Unfolded {
28 display_row_range, ..
29 } => display_row_range.start,
30 }
31 }
32
33 pub fn contains_display_row(&self, display_row: u32) -> bool {
34 let range = match self {
35 &DisplayDiffHunk::Folded { display_row } => display_row..=display_row,
36
37 DisplayDiffHunk::Unfolded {
38 display_row_range, ..
39 } => display_row_range.start..=display_row_range.end,
40 };
41
42 range.contains(&display_row)
43 }
44}
45
46pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) -> DisplayDiffHunk {
47 let hunk_start_point = Point::new(hunk.buffer_range.start, 0);
48 let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0);
49 let hunk_end_point_sub = Point::new(
50 hunk.buffer_range
51 .end
52 .saturating_sub(1)
53 .max(hunk.buffer_range.start),
54 0,
55 );
56
57 let is_removal = hunk.status() == DiffHunkStatus::Removed;
58
59 let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(2), 0);
60 let folds_end = Point::new(hunk.buffer_range.end + 2, 0);
61 let folds_range = folds_start..folds_end;
62
63 let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
64 let fold_point_range = fold.range.to_point(&snapshot.buffer_snapshot);
65 let fold_point_range = fold_point_range.start..=fold_point_range.end;
66
67 let folded_start = fold_point_range.contains(&hunk_start_point);
68 let folded_end = fold_point_range.contains(&hunk_end_point_sub);
69 let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub);
70
71 (folded_start && folded_end) || (is_removal && folded_start_sub)
72 });
73
74 if let Some(fold) = containing_fold {
75 let row = fold.range.start.to_display_point(snapshot).row();
76 DisplayDiffHunk::Folded { display_row: row }
77 } else {
78 let start = hunk_start_point.to_display_point(snapshot).row();
79
80 let hunk_end_row = hunk.buffer_range.end.max(hunk.buffer_range.start);
81 let hunk_end_point = Point::new(hunk_end_row, 0);
82 let end = hunk_end_point.to_display_point(snapshot).row();
83
84 DisplayDiffHunk::Unfolded {
85 display_row_range: start..end,
86 status: hunk.status(),
87 }
88 }
89}
90
91// #[cfg(any(test, feature = "test_support"))]
92// mod tests {
93// // use crate::editor_tests::init_test;
94// use crate::Point;
95// use gpui::TestAppContext;
96// use multi_buffer::{ExcerptRange, MultiBuffer};
97// use project::{FakeFs, Project};
98// use unindent::Unindent;
99// #[gpui::test]
100// async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
101// use git::diff::DiffHunkStatus;
102// init_test(cx, |_| {});
103
104// let fs = FakeFs::new(cx.background());
105// let project = Project::test(fs, [], cx).await;
106
107// // buffer has two modified hunks with two rows each
108// let buffer_1 = project
109// .update(cx, |project, cx| {
110// project.create_buffer(
111// "
112// 1.zero
113// 1.ONE
114// 1.TWO
115// 1.three
116// 1.FOUR
117// 1.FIVE
118// 1.six
119// "
120// .unindent()
121// .as_str(),
122// None,
123// cx,
124// )
125// })
126// .unwrap();
127// buffer_1.update(cx, |buffer, cx| {
128// buffer.set_diff_base(
129// Some(
130// "
131// 1.zero
132// 1.one
133// 1.two
134// 1.three
135// 1.four
136// 1.five
137// 1.six
138// "
139// .unindent(),
140// ),
141// cx,
142// );
143// });
144
145// // buffer has a deletion hunk and an insertion hunk
146// let buffer_2 = project
147// .update(cx, |project, cx| {
148// project.create_buffer(
149// "
150// 2.zero
151// 2.one
152// 2.two
153// 2.three
154// 2.four
155// 2.five
156// 2.six
157// "
158// .unindent()
159// .as_str(),
160// None,
161// cx,
162// )
163// })
164// .unwrap();
165// buffer_2.update(cx, |buffer, cx| {
166// buffer.set_diff_base(
167// Some(
168// "
169// 2.zero
170// 2.one
171// 2.one-and-a-half
172// 2.two
173// 2.three
174// 2.four
175// 2.six
176// "
177// .unindent(),
178// ),
179// cx,
180// );
181// });
182
183// cx.foreground().run_until_parked();
184
185// let multibuffer = cx.add_model(|cx| {
186// let mut multibuffer = MultiBuffer::new(0);
187// multibuffer.push_excerpts(
188// buffer_1.clone(),
189// [
190// // excerpt ends in the middle of a modified hunk
191// ExcerptRange {
192// context: Point::new(0, 0)..Point::new(1, 5),
193// primary: Default::default(),
194// },
195// // excerpt begins in the middle of a modified hunk
196// ExcerptRange {
197// context: Point::new(5, 0)..Point::new(6, 5),
198// primary: Default::default(),
199// },
200// ],
201// cx,
202// );
203// multibuffer.push_excerpts(
204// buffer_2.clone(),
205// [
206// // excerpt ends at a deletion
207// ExcerptRange {
208// context: Point::new(0, 0)..Point::new(1, 5),
209// primary: Default::default(),
210// },
211// // excerpt starts at a deletion
212// ExcerptRange {
213// context: Point::new(2, 0)..Point::new(2, 5),
214// primary: Default::default(),
215// },
216// // excerpt fully contains a deletion hunk
217// ExcerptRange {
218// context: Point::new(1, 0)..Point::new(2, 5),
219// primary: Default::default(),
220// },
221// // excerpt fully contains an insertion hunk
222// ExcerptRange {
223// context: Point::new(4, 0)..Point::new(6, 5),
224// primary: Default::default(),
225// },
226// ],
227// cx,
228// );
229// multibuffer
230// });
231
232// let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
233
234// assert_eq!(
235// snapshot.text(),
236// "
237// 1.zero
238// 1.ONE
239// 1.FIVE
240// 1.six
241// 2.zero
242// 2.one
243// 2.two
244// 2.one
245// 2.two
246// 2.four
247// 2.five
248// 2.six"
249// .unindent()
250// );
251
252// let expected = [
253// (DiffHunkStatus::Modified, 1..2),
254// (DiffHunkStatus::Modified, 2..3),
255// //TODO: Define better when and where removed hunks show up at range extremities
256// (DiffHunkStatus::Removed, 6..6),
257// (DiffHunkStatus::Removed, 8..8),
258// (DiffHunkStatus::Added, 10..11),
259// ];
260
261// assert_eq!(
262// snapshot
263// .git_diff_hunks_in_range(0..12)
264// .map(|hunk| (hunk.status(), hunk.buffer_range))
265// .collect::<Vec<_>>(),
266// &expected,
267// );
268
269// assert_eq!(
270// snapshot
271// .git_diff_hunks_in_range_rev(0..12)
272// .map(|hunk| (hunk.status(), hunk.buffer_range))
273// .collect::<Vec<_>>(),
274// expected
275// .iter()
276// .rev()
277// .cloned()
278// .collect::<Vec<_>>()
279// .as_slice(),
280// );
281// }
282// }