Detailed changes
@@ -1817,7 +1817,7 @@ async fn test_git_diff_base_change(
buffer_local_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -1837,7 +1837,7 @@ async fn test_git_diff_base_change(
buffer_remote_a.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -1861,7 +1861,7 @@ async fn test_git_diff_base_change(
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -1872,7 +1872,7 @@ async fn test_git_diff_base_change(
buffer_remote_a.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -1915,7 +1915,7 @@ async fn test_git_diff_base_change(
buffer_local_b.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -1935,7 +1935,7 @@ async fn test_git_diff_base_change(
buffer_remote_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -1963,12 +1963,12 @@ async fn test_git_diff_base_change(
"{:?}",
buffer
.snapshot()
- .git_diff_hunks_in_range(0..4, false)
+ .git_diff_hunks_in_row_range(0..4, false)
.collect::<Vec<_>>()
);
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -1979,7 +1979,7 @@ async fn test_git_diff_base_change(
buffer_remote_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
git::diff::assert_hunks(
- buffer.snapshot().git_diff_hunks_in_range(0..4, false),
+ buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -575,6 +575,15 @@ impl Item for ProjectDiagnosticsEditor {
unreachable!()
}
+ fn git_diff_recalc(
+ &mut self,
+ project: ModelHandle<Project>,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ self.editor
+ .update(cx, |editor, cx| editor.git_diff_recalc(project, cx))
+ }
+
fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
Editor::to_item_events(event)
}
@@ -2710,11 +2710,73 @@ impl MultiBufferSnapshot {
row_range: Range<u32>,
reversed: bool,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
- self.as_singleton()
- .into_iter()
- .flat_map(move |(_, _, buffer)| {
- buffer.git_diff_hunks_in_range(row_range.clone(), reversed)
- })
+ let mut cursor = self.excerpts.cursor::<Point>();
+
+ if reversed {
+ cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
+ if cursor.item().is_none() {
+ cursor.prev(&());
+ }
+ } else {
+ cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &());
+ }
+
+ std::iter::from_fn(move || {
+ let excerpt = cursor.item()?;
+ let multibuffer_start = *cursor.start();
+ let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
+ if multibuffer_start.row >= row_range.end {
+ return None;
+ }
+
+ let mut buffer_start = excerpt.range.context.start;
+ let mut buffer_end = excerpt.range.context.end;
+ let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
+ let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
+
+ if row_range.start > multibuffer_start.row {
+ let buffer_start_point =
+ excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
+ buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
+ }
+
+ if row_range.end < multibuffer_end.row {
+ let buffer_end_point =
+ excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
+ buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
+ }
+
+ let buffer_hunks = excerpt
+ .buffer
+ .git_diff_hunks_intersecting_range(buffer_start..buffer_end, reversed)
+ .filter_map(move |hunk| {
+ let start = multibuffer_start.row
+ + hunk
+ .buffer_range
+ .start
+ .saturating_sub(excerpt_start_point.row);
+ let end = multibuffer_start.row
+ + hunk
+ .buffer_range
+ .end
+ .min(excerpt_end_point.row + 1)
+ .saturating_sub(excerpt_start_point.row);
+
+ Some(DiffHunk {
+ buffer_range: start..end,
+ diff_base_byte_range: hunk.diff_base_byte_range.clone(),
+ })
+ });
+
+ if reversed {
+ cursor.prev(&());
+ } else {
+ cursor.next(&());
+ }
+
+ Some(buffer_hunks)
+ })
+ .flatten()
}
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
@@ -3546,11 +3608,12 @@ impl ToPointUtf16 for PointUtf16 {
#[cfg(test)]
mod tests {
use super::*;
- use gpui::MutableAppContext;
+ use gpui::{MutableAppContext, TestAppContext};
use language::{Buffer, Rope};
use rand::prelude::*;
use settings::Settings;
use std::{env, rc::Rc};
+ use unindent::Unindent;
use util::test::sample_text;
@@ -4168,6 +4231,178 @@ mod tests {
);
}
+ #[gpui::test]
+ async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
+ use git::diff::DiffHunkStatus;
+
+ // buffer has two modified hunks with two rows each
+ let buffer_1 = cx.add_model(|cx| {
+ let mut buffer = Buffer::new(
+ 0,
+ "
+ 1.zero
+ 1.ONE
+ 1.TWO
+ 1.three
+ 1.FOUR
+ 1.FIVE
+ 1.six
+ "
+ .unindent(),
+ cx,
+ );
+ buffer.set_diff_base(
+ Some(
+ "
+ 1.zero
+ 1.one
+ 1.two
+ 1.three
+ 1.four
+ 1.five
+ 1.six
+ "
+ .unindent(),
+ ),
+ cx,
+ );
+ buffer
+ });
+
+ // buffer has a deletion hunk and an insertion hunk
+ let buffer_2 = cx.add_model(|cx| {
+ let mut buffer = Buffer::new(
+ 0,
+ "
+ 2.zero
+ 2.one
+ 2.two
+ 2.three
+ 2.four
+ 2.five
+ 2.six
+ "
+ .unindent(),
+ 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,
+ );
+ buffer
+ });
+
+ 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, false)
+ .map(|hunk| (hunk.status(), hunk.buffer_range))
+ .collect::<Vec<_>>(),
+ &expected,
+ );
+
+ assert_eq!(
+ snapshot
+ .git_diff_hunks_in_range(0..12, true)
+ .map(|hunk| (hunk.status(), hunk.buffer_range))
+ .collect::<Vec<_>>(),
+ expected
+ .iter()
+ .rev()
+ .cloned()
+ .collect::<Vec<_>>()
+ .as_slice(),
+ );
+ }
+
#[gpui::test(iterations = 100)]
fn test_random_multibuffer(cx: &mut MutableAppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS")
@@ -254,7 +254,7 @@ impl<'a> EditorTestContext<'a> {
Actual selections:
{}
- "},
+ "},
self.assertion_context(),
expected_marked_text,
actual_marked_text,
@@ -71,18 +71,26 @@ impl BufferDiff {
}
}
- pub fn hunks_in_range<'a>(
+ pub fn hunks_in_row_range<'a>(
&'a self,
- query_row_range: Range<u32>,
+ range: Range<u32>,
buffer: &'a BufferSnapshot,
reversed: bool,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
- let start = buffer.anchor_before(Point::new(query_row_range.start, 0));
- let end = buffer.anchor_after(Point::new(query_row_range.end, 0));
+ let start = buffer.anchor_before(Point::new(range.start, 0));
+ let end = buffer.anchor_after(Point::new(range.end, 0));
+ self.hunks_intersecting_range(start..end, buffer, reversed)
+ }
+ pub fn hunks_intersecting_range<'a>(
+ &'a self,
+ range: Range<Anchor>,
+ buffer: &'a BufferSnapshot,
+ reversed: bool,
+ ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
- let before_start = summary.buffer_range.end.cmp(&start, buffer).is_lt();
- let after_end = summary.buffer_range.start.cmp(&end, buffer).is_gt();
+ let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
+ let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
!before_start && !after_end
});
@@ -141,7 +149,9 @@ impl BufferDiff {
#[cfg(test)]
fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
- self.hunks_in_range(0..u32::MAX, text, false)
+ let start = text.anchor_before(Point::new(0, 0));
+ let end = text.anchor_after(Point::new(u32::MAX, u32::MAX));
+ self.hunks_intersecting_range(start..end, text, false)
}
fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
@@ -355,7 +365,7 @@ mod tests {
assert_eq!(diff.hunks(&buffer).count(), 8);
assert_hunks(
- diff.hunks_in_range(7..12, &buffer, false),
+ diff.hunks_in_row_range(7..12, &buffer, false),
&buffer,
&diff_base,
&[
@@ -2310,13 +2310,21 @@ impl BufferSnapshot {
})
}
- pub fn git_diff_hunks_in_range<'a>(
+ pub fn git_diff_hunks_in_row_range<'a>(
&'a self,
- query_row_range: Range<u32>,
+ range: Range<u32>,
+ reversed: bool,
+ ) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
+ self.git_diff.hunks_in_row_range(range, self, reversed)
+ }
+
+ pub fn git_diff_hunks_intersecting_range<'a>(
+ &'a self,
+ range: Range<Anchor>,
reversed: bool,
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
self.git_diff
- .hunks_in_range(query_row_range, self, reversed)
+ .hunks_intersecting_range(range, self, reversed)
}
pub fn diagnostics_in_range<'a, T, O>(
@@ -334,6 +334,15 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.navigate(data, cx))
}
+ fn git_diff_recalc(
+ &mut self,
+ project: ModelHandle<Project>,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.results_editor
+ .update(cx, |editor, cx| editor.git_diff_recalc(project, cx))
+ }
+
fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
match event {
ViewEvent::UpdateTab => vec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab],
@@ -597,6 +597,10 @@ where
self.cursor.item()
}
+ pub fn item_summary(&self) -> Option<&'a T::Summary> {
+ self.cursor.item_summary()
+ }
+
pub fn next(&mut self, cx: &<T::Summary as Summary>::Context) {
self.cursor.next_internal(&mut self.filter_node, cx);
}