use std::ops::Range; use git::diff::{DiffHunk, DiffHunkStatus}; use language::Point; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, AnchorRangeExt, }; #[derive(Debug, Clone, PartialEq, Eq)] pub enum DisplayDiffHunk { Folded { display_row: u32, }, Unfolded { display_row_range: Range, status: DiffHunkStatus, }, } impl DisplayDiffHunk { pub fn start_display_row(&self) -> u32 { match self { &DisplayDiffHunk::Folded { display_row } => display_row, DisplayDiffHunk::Unfolded { display_row_range, .. } => display_row_range.start, } } pub fn contains_display_row(&self, display_row: u32) -> bool { let range = match self { &DisplayDiffHunk::Folded { display_row } => display_row..=display_row, DisplayDiffHunk::Unfolded { display_row_range, .. } => display_row_range.start..=display_row_range.end, }; range.contains(&display_row) } } pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> DisplayDiffHunk { let hunk_start_point = Point::new(hunk.buffer_range.start, 0); let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0); let hunk_end_point_sub = Point::new( hunk.buffer_range .end .saturating_sub(1) .max(hunk.buffer_range.start), 0, ); let is_removal = hunk.status() == DiffHunkStatus::Removed; let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(2), 0); let folds_end = Point::new(hunk.buffer_range.end + 2, 0); let folds_range = folds_start..folds_end; let containing_fold = snapshot.folds_in_range(folds_range).find(|fold_range| { let fold_point_range = fold_range.to_point(&snapshot.buffer_snapshot); let fold_point_range = fold_point_range.start..=fold_point_range.end; let folded_start = fold_point_range.contains(&hunk_start_point); let folded_end = fold_point_range.contains(&hunk_end_point_sub); let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub); (folded_start && folded_end) || (is_removal && folded_start_sub) }); if let Some(fold) = containing_fold { let row = fold.start.to_display_point(snapshot).row(); DisplayDiffHunk::Folded { display_row: row } } else { let start = hunk_start_point.to_display_point(snapshot).row(); let hunk_end_row = hunk.buffer_range.end.max(hunk.buffer_range.start); let hunk_end_point = Point::new(hunk_end_row, 0); let end = hunk_end_point.to_display_point(snapshot).row(); DisplayDiffHunk::Unfolded { display_row_range: start..end, status: hunk.status(), } } } // #[cfg(any(test, feature = "test_support"))] // mod tests { // // use crate::editor_tests::init_test; // use crate::Point; // use gpui::TestAppContext; // use multi_buffer::{ExcerptRange, MultiBuffer}; // use project::{FakeFs, Project}; // use unindent::Unindent; // #[gpui::test] // async fn test_diff_hunks_in_range(cx: &mut TestAppContext) { // use git::diff::DiffHunkStatus; // init_test(cx, |_| {}); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, [], cx).await; // // buffer has two modified hunks with two rows each // let buffer_1 = project // .update(cx, |project, cx| { // project.create_buffer( // " // 1.zero // 1.ONE // 1.TWO // 1.three // 1.FOUR // 1.FIVE // 1.six // " // .unindent() // .as_str(), // None, // cx, // ) // }) // .unwrap(); // buffer_1.update(cx, |buffer, cx| { // buffer.set_diff_base( // Some( // " // 1.zero // 1.one // 1.two // 1.three // 1.four // 1.five // 1.six // " // .unindent(), // ), // cx, // ); // }); // // buffer has a deletion hunk and an insertion hunk // let buffer_2 = project // .update(cx, |project, cx| { // project.create_buffer( // " // 2.zero // 2.one // 2.two // 2.three // 2.four // 2.five // 2.six // " // .unindent() // .as_str(), // None, // cx, // ) // }) // .unwrap(); // buffer_2.update(cx, |buffer, cx| { // buffer.set_diff_base( // Some( // " // 2.zero // 2.one // 2.one-and-a-half // 2.two // 2.three // 2.four // 2.six // " // .unindent(), // ), // cx, // ); // }); // cx.foreground().run_until_parked(); // let multibuffer = cx.add_model(|cx| { // let mut multibuffer = MultiBuffer::new(0); // multibuffer.push_excerpts( // buffer_1.clone(), // [ // // excerpt ends in the middle of a modified hunk // ExcerptRange { // context: Point::new(0, 0)..Point::new(1, 5), // primary: Default::default(), // }, // // excerpt begins in the middle of a modified hunk // ExcerptRange { // context: Point::new(5, 0)..Point::new(6, 5), // primary: Default::default(), // }, // ], // cx, // ); // multibuffer.push_excerpts( // buffer_2.clone(), // [ // // excerpt ends at a deletion // ExcerptRange { // context: Point::new(0, 0)..Point::new(1, 5), // primary: Default::default(), // }, // // excerpt starts at a deletion // ExcerptRange { // context: Point::new(2, 0)..Point::new(2, 5), // primary: Default::default(), // }, // // excerpt fully contains a deletion hunk // ExcerptRange { // context: Point::new(1, 0)..Point::new(2, 5), // primary: Default::default(), // }, // // excerpt fully contains an insertion hunk // ExcerptRange { // context: Point::new(4, 0)..Point::new(6, 5), // primary: Default::default(), // }, // ], // cx, // ); // multibuffer // }); // let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx)); // assert_eq!( // snapshot.text(), // " // 1.zero // 1.ONE // 1.FIVE // 1.six // 2.zero // 2.one // 2.two // 2.one // 2.two // 2.four // 2.five // 2.six" // .unindent() // ); // let expected = [ // (DiffHunkStatus::Modified, 1..2), // (DiffHunkStatus::Modified, 2..3), // //TODO: Define better when and where removed hunks show up at range extremities // (DiffHunkStatus::Removed, 6..6), // (DiffHunkStatus::Removed, 8..8), // (DiffHunkStatus::Added, 10..11), // ]; // assert_eq!( // snapshot // .git_diff_hunks_in_range(0..12) // .map(|hunk| (hunk.status(), hunk.buffer_range)) // .collect::>(), // &expected, // ); // assert_eq!( // snapshot // .git_diff_hunks_in_range_rev(0..12) // .map(|hunk| (hunk.status(), hunk.buffer_range)) // .collect::>(), // expected // .iter() // .rev() // .cloned() // .collect::>() // .as_slice(), // ); // } // }