Add action to go to next/previous git diff in editor

Julia and Kay Simmons created

Co-Authored-By: Kay Simmons <kay@zed.dev>

Change summary

assets/keymaps/default.json                |   4 
crates/collab/src/integration_tests.rs     |  18 +-
crates/editor/src/editor.rs                |  67 ++++++++++++
crates/editor/src/element.rs               | 134 ++++++++---------------
crates/editor/src/git.rs                   |  74 +++++++++++++
crates/editor/src/multi_buffer.rs          |   5 
crates/editor/src/selections_collection.rs |   1 
crates/git/src/diff.rs                     |  22 ++-
crates/language/src/buffer.rs              |   4 
9 files changed, 219 insertions(+), 110 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -402,7 +402,9 @@
     {
         "context": "Editor",
         "bindings": {
-            "alt-enter": "editor::OpenExcerpts"
+            "alt-enter": "editor::OpenExcerpts",
+            "cmd-f8": "editor::GoToHunk",
+            "cmd-shift-f8": "editor::GoToPrevHunk"
         }
     },
     {

crates/collab/src/integration_tests.rs 🔗

@@ -1483,7 +1483,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),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(1..2, "", "two\n")],
@@ -1503,7 +1503,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),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(1..2, "", "two\n")],
@@ -1527,7 +1527,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),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(2..3, "", "three\n")],
@@ -1538,7 +1538,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),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(2..3, "", "three\n")],
@@ -1581,7 +1581,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),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(1..2, "", "two\n")],
@@ -1601,7 +1601,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),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(1..2, "", "two\n")],
@@ -1629,12 +1629,12 @@ async fn test_git_diff_base_change(
             "{:?}",
             buffer
                 .snapshot()
-                .git_diff_hunks_in_range(0..4)
+                .git_diff_hunks_in_range(0..4, false)
                 .collect::<Vec<_>>()
         );
 
         git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_range(0..4),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(2..3, "", "three\n")],
@@ -1645,7 +1645,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),
+            buffer.snapshot().git_diff_hunks_in_range(0..4, false),
             &buffer,
             &diff_base,
             &[(2..3, "", "three\n")],

crates/editor/src/editor.rs 🔗

@@ -1,6 +1,7 @@
 mod blink_manager;
 pub mod display_map;
 mod element;
+mod git;
 mod highlight_matching_bracket;
 mod hover_popover;
 pub mod items;
@@ -42,6 +43,7 @@ use gpui::{
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
 pub use items::MAX_TAB_TITLE_LEN;
+use itertools::Itertools;
 pub use language::{char_kind, CharKind};
 use language::{
     AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
@@ -79,6 +81,8 @@ use theme::{DiagnosticStyle, Theme};
 use util::{post_inc, ResultExt, TryFutureExt};
 use workspace::{ItemNavHistory, Workspace};
 
+use crate::git::diff_hunk_to_display;
+
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
 const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
 const MAX_LINE_LEN: usize = 1024;
@@ -162,6 +166,8 @@ actions!(
         NewlineBelow,
         GoToDiagnostic,
         GoToPrevDiagnostic,
+        GoToHunk,
+        GoToPrevHunk,
         Indent,
         Outdent,
         DeleteLine,
@@ -338,6 +344,8 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(Editor::redo_selection);
     cx.add_action(Editor::go_to_diagnostic);
     cx.add_action(Editor::go_to_prev_diagnostic);
+    cx.add_action(Editor::go_to_hunk);
+    cx.add_action(Editor::go_to_prev_hunk);
     cx.add_action(Editor::go_to_definition);
     cx.add_action(Editor::go_to_type_definition);
     cx.add_action(Editor::fold);
@@ -5232,6 +5240,65 @@ impl Editor {
         }
     }
 
+    fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
+        self.go_to_hunk_impl(Direction::Next, cx)
+    }
+
+    fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
+        self.go_to_hunk_impl(Direction::Prev, cx)
+    }
+
+    pub fn go_to_hunk_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+        let snapshot = self
+            .display_map
+            .update(cx, |display_map, cx| display_map.snapshot(cx));
+        let selection = self.selections.newest::<Point>(cx);
+
+        fn seek_in_direction(
+            this: &mut Editor,
+            snapshot: &DisplaySnapshot,
+            initial_point: Point,
+            direction: Direction,
+            cx: &mut ViewContext<Editor>,
+        ) -> bool {
+            let hunks = if direction == Direction::Next {
+                snapshot
+                    .buffer_snapshot
+                    .git_diff_hunks_in_range(initial_point.row..u32::MAX, false)
+            } else {
+                snapshot
+                    .buffer_snapshot
+                    .git_diff_hunks_in_range(0..initial_point.row, true)
+            };
+
+            let display_point = initial_point.to_display_point(snapshot);
+            let mut hunks = hunks
+                .map(|hunk| diff_hunk_to_display(hunk, &snapshot))
+                .skip_while(|hunk| hunk.display_row_range().contains(&display_point.row()))
+                .dedup();
+
+            if let Some(hunk) = hunks.next() {
+                this.change_selections(Some(Autoscroll::Center), cx, |s| {
+                    let row = hunk.display_row_range().start;
+                    let point = DisplayPoint::new(row, 0);
+                    s.select_display_ranges([point..point]);
+                });
+
+                true
+            } else {
+                false
+            }
+        }
+
+        if !seek_in_direction(self, &snapshot, selection.head(), direction, cx) {
+            let wrapped_point = match direction {
+                Direction::Next => Point::zero(),
+                Direction::Prev => snapshot.buffer_snapshot.max_point(),
+            };
+            seek_in_direction(self, &snapshot, wrapped_point, direction, cx);
+        }
+    }
+
     pub fn go_to_definition(
         workspace: &mut Workspace,
         _: &GoToDefinition,

crates/editor/src/element.rs 🔗

@@ -5,6 +5,7 @@ use super::{
 };
 use crate::{
     display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
+    git::{diff_hunk_to_display, DisplayDiffHunk},
     hover_popover::{
         HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
     },
@@ -12,7 +13,7 @@ use crate::{
         GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
     },
     mouse_context_menu::DeployMouseContextMenu,
-    AnchorRangeExt, EditorStyle,
+    EditorStyle,
 };
 use clock::ReplicaId;
 use collections::{BTreeMap, HashMap};
@@ -33,8 +34,9 @@ use gpui::{
     Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MutableAppContext,
     PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WeakViewHandle,
 };
+use itertools::Itertools;
 use json::json;
-use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Point, Selection};
+use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection};
 use project::ProjectPath;
 use settings::{GitGutter, Settings};
 use smallvec::SmallVec;
@@ -46,13 +48,6 @@ use std::{
     sync::Arc,
 };
 
-#[derive(Debug)]
-struct DiffHunkLayout {
-    visual_range: Range<u32>,
-    status: DiffHunkStatus,
-    is_folded: bool,
-}
-
 struct SelectionLayout {
     head: DisplayPoint,
     cursor_shape: CursorShape,
@@ -576,17 +571,11 @@ impl EditorElement {
         let scroll_position = layout.position_map.snapshot.scroll_position();
         let scroll_top = scroll_position.y() * line_height;
 
-        for hunk in &layout.hunk_layouts {
-            let color = match (hunk.status, hunk.is_folded) {
-                (DiffHunkStatus::Added, false) => diff_style.inserted,
-                (DiffHunkStatus::Modified, false) => diff_style.modified,
-
+        for hunk in &layout.display_hunks {
+            let (display_row_range, status) = match hunk {
                 //TODO: This rendering is entirely a horrible hack
-                (DiffHunkStatus::Removed, false) => {
-                    let row = hunk.visual_range.start;
-
-                    let offset = line_height / 2.;
-                    let start_y = row as f32 * line_height - offset - scroll_top;
+                &DisplayDiffHunk::Folded { display_row: row } => {
+                    let start_y = row as f32 * line_height - scroll_top;
                     let end_y = start_y + line_height;
 
                     let width = diff_style.removed_width_em * line_height;
@@ -596,7 +585,7 @@ impl EditorElement {
 
                     cx.scene.push_quad(Quad {
                         bounds: highlight_bounds,
-                        background: Some(diff_style.deleted),
+                        background: Some(diff_style.modified),
                         border: Border::new(0., Color::transparent_black()),
                         corner_radius: 1. * line_height,
                     });
@@ -604,9 +593,22 @@ impl EditorElement {
                     continue;
                 }
 
-                (_, true) => {
-                    let row = hunk.visual_range.start;
-                    let start_y = row as f32 * line_height - scroll_top;
+                DisplayDiffHunk::Unfolded {
+                    display_row_range,
+                    status,
+                } => (display_row_range, status),
+            };
+
+            let color = match status {
+                DiffHunkStatus::Added => diff_style.inserted,
+                DiffHunkStatus::Modified => diff_style.modified,
+
+                //TODO: This rendering is entirely a horrible hack
+                DiffHunkStatus::Removed => {
+                    let row = display_row_range.start;
+
+                    let offset = line_height / 2.;
+                    let start_y = row as f32 * line_height - offset - scroll_top;
                     let end_y = start_y + line_height;
 
                     let width = diff_style.removed_width_em * line_height;
@@ -616,7 +618,7 @@ impl EditorElement {
 
                     cx.scene.push_quad(Quad {
                         bounds: highlight_bounds,
-                        background: Some(diff_style.modified),
+                        background: Some(diff_style.deleted),
                         border: Border::new(0., Color::transparent_black()),
                         corner_radius: 1. * line_height,
                     });
@@ -625,8 +627,8 @@ impl EditorElement {
                 }
             };
 
-            let start_row = hunk.visual_range.start;
-            let end_row = hunk.visual_range.end;
+            let start_row = display_row_range.start;
+            let end_row = display_row_range.end;
 
             let start_y = start_row as f32 * line_height - scroll_top;
             let end_y = end_row as f32 * line_height - scroll_top;
@@ -1107,69 +1109,23 @@ impl EditorElement {
     //If a fold contains any hunks then that fold line is marked as modified
     fn layout_git_gutters(
         &self,
-        rows: Range<u32>,
+        display_rows: Range<u32>,
         snapshot: &EditorSnapshot,
-    ) -> Vec<DiffHunkLayout> {
+    ) -> Vec<DisplayDiffHunk> {
         let buffer_snapshot = &snapshot.buffer_snapshot;
-        let visual_start = DisplayPoint::new(rows.start, 0).to_point(snapshot).row;
-        let visual_end = DisplayPoint::new(rows.end, 0).to_point(snapshot).row;
-        let hunks = buffer_snapshot.git_diff_hunks_in_range(visual_start..visual_end);
-
-        let mut layouts = Vec::<DiffHunkLayout>::new();
-
-        for hunk in hunks {
-            let hunk_start_point = Point::new(hunk.buffer_range.start, 0);
-            let hunk_end_point = Point::new(hunk.buffer_range.end, 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(1), 0);
-            let folds_end = Point::new(hunk.buffer_range.end + 1, 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(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)
-            });
-
-            let visual_range = if let Some(fold) = containing_fold {
-                let row = fold.start.to_display_point(snapshot).row();
-                row..row
-            } else {
-                let start = hunk_start_point.to_display_point(snapshot).row();
-                let end = hunk_end_point.to_display_point(snapshot).row();
-                start..end
-            };
-
-            let has_existing_layout = match layouts.last() {
-                Some(e) => visual_range == e.visual_range && e.status == hunk.status(),
-                None => false,
-            };
-
-            if !has_existing_layout {
-                layouts.push(DiffHunkLayout {
-                    visual_range,
-                    status: hunk.status(),
-                    is_folded: containing_fold.is_some(),
-                });
-            }
-        }
 
-        layouts
+        let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
+            .to_point(snapshot)
+            .row;
+        let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
+            .to_point(snapshot)
+            .row;
+
+        buffer_snapshot
+            .git_diff_hunks_in_range(buffer_start_row..buffer_end_row, false)
+            .map(|hunk| diff_hunk_to_display(hunk, snapshot))
+            .dedup()
+            .collect()
     }
 
     fn layout_line_numbers(
@@ -1721,7 +1677,7 @@ impl Element for EditorElement {
         let line_number_layouts =
             self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
 
-        let hunk_layouts = self.layout_git_gutters(start_row..end_row, &snapshot);
+        let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
 
         let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines);
 
@@ -1880,7 +1836,7 @@ impl Element for EditorElement {
                 highlighted_rows,
                 highlighted_ranges,
                 line_number_layouts,
-                hunk_layouts,
+                display_hunks,
                 blocks,
                 selections,
                 context_menu,
@@ -2002,7 +1958,7 @@ pub struct LayoutState {
     active_rows: BTreeMap<u32, bool>,
     highlighted_rows: Option<Range<u32>>,
     line_number_layouts: Vec<Option<text_layout::Line>>,
-    hunk_layouts: Vec<DiffHunkLayout>,
+    display_hunks: Vec<DisplayDiffHunk>,
     blocks: Vec<BlockLayout>,
     highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
     selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,

crates/editor/src/git.rs 🔗

@@ -0,0 +1,74 @@
+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<u32>,
+        status: DiffHunkStatus,
+    },
+}
+
+impl DisplayDiffHunk {
+    pub fn display_row_range(&self) -> Range<u32> {
+        match self {
+            &DisplayDiffHunk::Folded { display_row } => display_row..display_row + 1,
+            DisplayDiffHunk::Unfolded {
+                display_row_range, ..
+            } => display_row_range.clone(),
+        }
+    }
+}
+
+pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) -> DisplayDiffHunk {
+    let hunk_start_point = Point::new(hunk.buffer_range.start, 0);
+    let hunk_end_point = Point::new(hunk.buffer_range.end, 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(1), 0);
+    let folds_end = Point::new(hunk.buffer_range.end + 1, 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 end = hunk_end_point.to_display_point(snapshot).row();
+        DisplayDiffHunk::Unfolded {
+            display_row_range: start..end,
+            status: hunk.status(),
+        }
+    }
+}

crates/editor/src/multi_buffer.rs 🔗

@@ -2592,10 +2592,13 @@ impl MultiBufferSnapshot {
     pub fn git_diff_hunks_in_range<'a>(
         &'a self,
         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()))
+            .flat_map(move |(_, _, buffer)| {
+                buffer.git_diff_hunks_in_range(row_range.clone(), reversed)
+            })
     }
 
     pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {

crates/editor/src/selections_collection.rs 🔗

@@ -594,7 +594,6 @@ impl<'a> MutableSelectionsCollection<'a> {
         self.select_anchors(selections)
     }
 
-    #[cfg(any(test, feature = "test-support"))]
     pub fn select_display_ranges<T>(&mut self, ranges: T)
     where
         T: IntoIterator<Item = Range<DisplayPoint>>,

crates/git/src/diff.rs 🔗

@@ -15,12 +15,12 @@ pub enum DiffHunkStatus {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct DiffHunk<T> {
     pub buffer_range: Range<T>,
-    pub head_byte_range: Range<usize>,
+    pub diff_base_byte_range: Range<usize>,
 }
 
 impl DiffHunk<u32> {
     pub fn status(&self) -> DiffHunkStatus {
-        if self.head_byte_range.is_empty() {
+        if self.diff_base_byte_range.is_empty() {
             DiffHunkStatus::Added
         } else if self.buffer_range.is_empty() {
             DiffHunkStatus::Removed
@@ -75,6 +75,7 @@ impl BufferDiff {
         &'a self,
         query_row_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));
@@ -86,7 +87,12 @@ impl BufferDiff {
         });
 
         std::iter::from_fn(move || {
-            cursor.next(buffer);
+            if reversed {
+                cursor.prev(buffer);
+            } else {
+                cursor.next(buffer);
+            }
+
             let hunk = cursor.item()?;
 
             let range = hunk.buffer_range.to_point(buffer);
@@ -98,7 +104,7 @@ impl BufferDiff {
 
             Some(DiffHunk {
                 buffer_range: range.start.row..end_row,
-                head_byte_range: hunk.head_byte_range.clone(),
+                diff_base_byte_range: hunk.diff_base_byte_range.clone(),
             })
         })
     }
@@ -135,7 +141,7 @@ 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)
+        self.hunks_in_range(0..u32::MAX, text, false)
     }
 
     fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
@@ -222,7 +228,7 @@ impl BufferDiff {
         let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
         DiffHunk {
             buffer_range,
-            head_byte_range,
+            diff_base_byte_range: head_byte_range,
         }
     }
 }
@@ -242,7 +248,7 @@ pub fn assert_hunks<Iter>(
         .map(|hunk| {
             (
                 hunk.buffer_range.clone(),
-                &diff_base[hunk.head_byte_range],
+                &diff_base[hunk.diff_base_byte_range],
                 buffer
                     .text_for_range(
                         Point::new(hunk.buffer_range.start, 0)
@@ -349,7 +355,7 @@ mod tests {
         assert_eq!(diff.hunks(&buffer).count(), 8);
 
         assert_hunks(
-            diff.hunks_in_range(7..12, &buffer),
+            diff.hunks_in_range(7..12, &buffer, false),
             &buffer,
             &diff_base,
             &[

crates/language/src/buffer.rs 🔗

@@ -2286,8 +2286,10 @@ impl BufferSnapshot {
     pub fn git_diff_hunks_in_range<'a>(
         &'a self,
         query_row_range: Range<u32>,
+        reversed: bool,
     ) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
-        self.git_diff.hunks_in_range(query_row_range, self)
+        self.git_diff
+            .hunks_in_range(query_row_range, self, reversed)
     }
 
     pub fn diagnostics_in_range<'a, T, O>(