git.rs

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