Properly calculate expanded git diff hunk highlight ranges (#11632)

Kirill Bulatov created

Closes https://github.com/zed-industries/zed/issues/11576

Release Notes:

- Fixed expanded diff hunks highlighting an extra row as added
([11576](https://github.com/zed-industries/zed/issues/11576))

Change summary

crates/collab/src/tests/editor_tests.rs |  18 +--
crates/editor/src/editor.rs             |  55 ++++++++----
crates/editor/src/editor_tests.rs       | 115 +++++++++++---------------
crates/editor/src/hunk_diff.rs          |  49 +++++++++--
crates/editor/src/test.rs               |  39 ++++++---
crates/go_to_line/src/go_to_line.rs     |   2 
crates/outline/src/outline.rs           |   4 
7 files changed, 161 insertions(+), 121 deletions(-)

Detailed changes

crates/collab/src/tests/editor_tests.rs 🔗

@@ -2110,10 +2110,7 @@ struct Row10;"#};
         let snapshot = editor.snapshot(cx);
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
-        assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new(),
-        );
+        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
         assert_eq!(
             all_hunks,
             vec![
@@ -2135,8 +2132,8 @@ struct Row10;"#};
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![1..3, 8..9],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![1..=2, 8..=8],
         );
         assert_eq!(
             all_hunks,
@@ -2170,10 +2167,7 @@ struct Row10;"#};
         let snapshot = editor.snapshot(cx);
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
-        assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new(),
-        );
+        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
         assert_eq!(
             all_hunks,
             vec![(
@@ -2189,8 +2183,8 @@ struct Row10;"#};
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new(),
+            expanded_hunks_background_highlights(editor, cx),
+            vec![5..=5]
         );
         assert_eq!(
             all_hunks,

crates/editor/src/editor.rs 🔗

@@ -449,7 +449,7 @@ pub struct Editor {
     show_wrap_guides: Option<bool>,
     placeholder_text: Option<Arc<str>>,
     highlight_order: usize,
-    highlighted_rows: HashMap<TypeId, Vec<(usize, Range<Anchor>, Hsla)>>,
+    highlighted_rows: HashMap<TypeId, Vec<(usize, RangeInclusive<Anchor>, Option<Hsla>)>>,
     background_highlights: TreeMap<TypeId, BackgroundHighlight>,
     scrollbar_marker_state: ScrollbarMarkerState,
     nav_history: Option<ItemNavHistory>,
@@ -9705,7 +9705,7 @@ impl Editor {
     /// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
     pub fn highlight_rows<T: 'static>(
         &mut self,
-        rows: Range<Anchor>,
+        rows: RangeInclusive<Anchor>,
         color: Option<Hsla>,
         cx: &mut ViewContext<Self>,
     ) {
@@ -9716,9 +9716,13 @@ impl Editor {
                 let existing_highlight_index =
                     row_highlights.binary_search_by(|(_, highlight_range, _)| {
                         highlight_range
-                            .start
-                            .cmp(&rows.start, &multi_buffer_snapshot)
-                            .then(highlight_range.end.cmp(&rows.end, &multi_buffer_snapshot))
+                            .start()
+                            .cmp(&rows.start(), &multi_buffer_snapshot)
+                            .then(
+                                highlight_range
+                                    .end()
+                                    .cmp(&rows.end(), &multi_buffer_snapshot),
+                            )
                     });
                 match color {
                     Some(color) => {
@@ -9728,20 +9732,22 @@ impl Editor {
                         };
                         row_highlights.insert(
                             insert_index,
-                            (post_inc(&mut self.highlight_order), rows, color),
+                            (post_inc(&mut self.highlight_order), rows, Some(color)),
                         );
                     }
-                    None => {
-                        if let Ok(i) = existing_highlight_index {
+                    None => match existing_highlight_index {
+                        Ok(i) => {
                             row_highlights.remove(i);
                         }
-                    }
+                        Err(i) => {
+                            row_highlights
+                                .insert(i, (post_inc(&mut self.highlight_order), rows, None));
+                        }
+                    },
                 }
             }
             hash_map::Entry::Vacant(v) => {
-                if let Some(color) = color {
-                    v.insert(vec![(post_inc(&mut self.highlight_order), rows, color)]);
-                }
+                v.insert(vec![(post_inc(&mut self.highlight_order), rows, color)]);
             }
         }
     }
@@ -9754,12 +9760,12 @@ impl Editor {
     /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
     pub fn highlighted_rows<T: 'static>(
         &self,
-    ) -> Option<impl Iterator<Item = (&Range<Anchor>, &Hsla)>> {
+    ) -> Option<impl Iterator<Item = (&RangeInclusive<Anchor>, Option<&Hsla>)>> {
         Some(
             self.highlighted_rows
                 .get(&TypeId::of::<T>())?
                 .iter()
-                .map(|(_, range, color)| (range, color)),
+                .map(|(_, range, color)| (range, color.as_ref())),
         )
     }
 
@@ -9780,14 +9786,21 @@ impl Editor {
             .fold(
                 BTreeMap::<u32, Hsla>::new(),
                 |mut unique_rows, (highlight_order, anchor_range, hsla)| {
-                    let start_row = anchor_range.start.to_display_point(&snapshot).row();
-                    let end_row = anchor_range.end.to_display_point(&snapshot).row();
+                    let start_row = anchor_range.start().to_display_point(&snapshot).row();
+                    let end_row = anchor_range.end().to_display_point(&snapshot).row();
                     for row in start_row..=end_row {
                         let used_index =
                             used_highlight_orders.entry(row).or_insert(*highlight_order);
                         if highlight_order >= used_index {
                             *used_index = *highlight_order;
-                            unique_rows.insert(row, *hsla);
+                            match hsla {
+                                Some(hsla) => {
+                                    unique_rows.insert(row, *hsla);
+                                }
+                                None => {
+                                    unique_rows.remove(&row);
+                                }
+                            }
                         }
                     }
                     unique_rows
@@ -11587,6 +11600,12 @@ trait RangeToAnchorExt {
 
 impl<T: ToOffset> RangeToAnchorExt for Range<T> {
     fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
-        snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
+        let start_offset = self.start.to_offset(snapshot);
+        let end_offset = self.end.to_offset(snapshot);
+        if start_offset == end_offset {
+            snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
+        } else {
+            snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
+        }
     }
 }

crates/editor/src/editor_tests.rs 🔗

@@ -9543,8 +9543,8 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![1..2, 7..8, 9..10],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![1..=1, 7..=7, 9..=9],
             "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
         );
         assert_eq!(
@@ -9571,7 +9571,7 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
+            expanded_hunks_background_highlights(editor, cx),
             Vec::new(),
             "After cancelling in editor, no git highlights should be left"
         );
@@ -9684,8 +9684,8 @@ async fn test_toggled_diff_base_change(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![9..11, 13..15],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![9..=10, 13..=14],
             "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
         );
         assert_eq!(
@@ -9713,7 +9713,7 @@ async fn test_toggled_diff_base_change(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
+            expanded_hunks_background_highlights(editor, cx),
             Vec::new(),
             "After diff base is changed, old git highlights should be removed"
         );
@@ -9858,8 +9858,8 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![9..11, 13..15, 19..20]
+            expanded_hunks_background_highlights(editor, cx),
+            vec![9..=10, 13..=14, 19..=19]
         );
         assert_eq!(
             all_hunks,
@@ -9923,8 +9923,8 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![5..6],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![0..=0, 5..=5],
             "Only one hunk is left not folded, its highlight should be visible"
         );
         assert_eq!(
@@ -10004,8 +10004,8 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![9..11, 13..15, 19..20],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![9..=10, 13..=14, 19..=19],
             "After unfolding, all hunk diffs should be visible again"
         );
         assert_eq!(
@@ -10172,10 +10172,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
         let snapshot = editor.snapshot(cx);
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
-        assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new(),
-        );
+        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
         assert_eq!(all_hunks, expected_all_hunks);
         assert_eq!(all_expanded_hunks, Vec::new());
     });
@@ -10190,8 +10187,8 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![18..19, 33..34],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![18..=18, 33..=33],
         );
         assert_eq!(all_hunks, expected_all_hunks_shifted);
         assert_eq!(all_hunks, all_expanded_hunks);
@@ -10205,10 +10202,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
         let snapshot = editor.snapshot(cx);
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
-        assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new(),
-        );
+        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
         assert_eq!(all_hunks, expected_all_hunks);
         assert_eq!(all_expanded_hunks, Vec::new());
     });
@@ -10222,8 +10216,8 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![18..19, 33..34],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![18..=18, 33..=33],
         );
         assert_eq!(all_hunks, expected_all_hunks_shifted);
         assert_eq!(all_hunks, all_expanded_hunks);
@@ -10237,10 +10231,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
         let snapshot = editor.snapshot(cx);
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
-        assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new(),
-        );
+        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
         assert_eq!(all_hunks, expected_all_hunks);
         assert_eq!(all_expanded_hunks, Vec::new());
     });
@@ -10329,8 +10320,8 @@ async fn test_edits_around_toggled_additions(
             vec![("".to_string(), DiffHunkStatus::Added, 4..7)]
         );
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![4..7]
+            expanded_hunks_background_highlights(editor, cx),
+            vec![4..=6]
         );
         assert_eq!(all_hunks, all_expanded_hunks);
     });
@@ -10365,8 +10356,8 @@ async fn test_edits_around_toggled_additions(
             vec![("".to_string(), DiffHunkStatus::Added, 4..8)]
         );
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![4..8],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![4..=6],
             "Edited hunk should have one more line added"
         );
         assert_eq!(
@@ -10406,8 +10397,8 @@ async fn test_edits_around_toggled_additions(
             vec![("".to_string(), DiffHunkStatus::Added, 4..9)]
         );
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![4..9],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![4..=6],
             "Edited hunk should have one more line added"
         );
         assert_eq!(all_hunks, all_expanded_hunks);
@@ -10446,8 +10437,8 @@ async fn test_edits_around_toggled_additions(
             vec![("".to_string(), DiffHunkStatus::Added, 4..8)]
         );
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![4..8],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![4..=6],
             "Deleting a line should shrint the hunk"
         );
         assert_eq!(
@@ -10490,8 +10481,8 @@ async fn test_edits_around_toggled_additions(
             vec![("".to_string(), DiffHunkStatus::Added, 5..6)]
         );
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![5..6]
+            expanded_hunks_background_highlights(editor, cx),
+            vec![5..=5]
         );
         assert_eq!(all_hunks, all_expanded_hunks);
     });
@@ -10533,7 +10524,7 @@ async fn test_edits_around_toggled_additions(
             ]
         );
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
+            expanded_hunks_background_highlights(editor, cx),
             Vec::new(),
             "Should close all stale expanded addition hunks"
         );
@@ -10632,10 +10623,7 @@ async fn test_edits_around_toggled_deletions(
         let snapshot = editor.snapshot(cx);
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
-        assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new()
-        );
+        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
         assert_eq!(
             all_hunks,
             vec![(
@@ -10672,7 +10660,7 @@ async fn test_edits_around_toggled_deletions(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
+            expanded_hunks_background_highlights(editor, cx),
             Vec::new(),
             "Deleted hunks do not highlight current editor's background"
         );
@@ -10710,10 +10698,7 @@ async fn test_edits_around_toggled_deletions(
         let snapshot = editor.snapshot(cx);
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
-        assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            Vec::new()
-        );
+        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
         assert_eq!(
             all_hunks,
             vec![(
@@ -10757,8 +10742,8 @@ async fn test_edits_around_toggled_deletions(
             )]
         );
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![7..8],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![7..=7],
             "Modified expanded hunks should display additions and highlight their background"
         );
         assert_eq!(all_hunks, all_expanded_hunks);
@@ -10851,8 +10836,8 @@ async fn test_edits_around_toggled_modifications(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![6..7],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![6..=6],
         );
         assert_eq!(
             all_hunks,
@@ -10894,8 +10879,8 @@ async fn test_edits_around_toggled_modifications(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![6..9],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![6..=6],
             "Modified hunk should grow highlighted lines on more text additions"
         );
         assert_eq!(
@@ -10940,9 +10925,8 @@ async fn test_edits_around_toggled_modifications(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![6..9],
-            "Modified hunk should grow deleted lines on text deletions above"
+            expanded_hunks_background_highlights(editor, cx),
+            vec![6..=8],
         );
         assert_eq!(
             all_hunks,
@@ -10950,7 +10934,8 @@ async fn test_edits_around_toggled_modifications(
                 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
                 DiffHunkStatus::Modified,
                 6..9
-            )]
+            )],
+            "Modified hunk should grow deleted lines on text deletions above"
         );
         assert_eq!(all_hunks, all_expanded_hunks);
     });
@@ -10984,8 +10969,8 @@ async fn test_edits_around_toggled_modifications(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![6..10],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![6..=9],
             "Modified hunk should grow deleted lines on text modifications above"
         );
         assert_eq!(
@@ -11028,8 +11013,8 @@ async fn test_edits_around_toggled_modifications(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![6..9],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![6..=8],
             "Modified hunk should grow shrink lines on modification lines removal"
         );
         assert_eq!(
@@ -11069,7 +11054,7 @@ async fn test_edits_around_toggled_modifications(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
+            expanded_hunks_background_highlights(editor, cx),
             Vec::new(),
             "Modified hunk should turn into a removed one on all modified lines removal"
         );
@@ -11172,8 +11157,8 @@ async fn test_multiple_expanded_hunks_merge(
         let all_hunks = editor_hunks(editor, &snapshot, cx);
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
-            expanded_hunks_background_highlights(editor, &snapshot),
-            vec![6..7],
+            expanded_hunks_background_highlights(editor, cx),
+            vec![6..=6],
         );
         assert_eq!(
             all_hunks,

crates/editor/src/hunk_diff.rs 🔗

@@ -1,4 +1,7 @@
-use std::{ops::Range, sync::Arc};
+use std::{
+    ops::{Range, RangeInclusive},
+    sync::Arc,
+};
 
 use collections::{hash_map, HashMap, HashSet};
 use git::diff::{DiffHunk, DiffHunkStatus};
@@ -14,8 +17,8 @@ use util::{debug_panic, RangeExt};
 use crate::{
     git::{diff_hunk_to_display, DisplayDiffHunk},
     hunks_for_selections, BlockDisposition, BlockId, BlockProperties, BlockStyle, DiffRowHighlight,
-    Editor, ExpandAllHunkDiffs, RangeToAnchorExt, RevertSelectedHunks, ToDisplayPoint,
-    ToggleHunkDiff,
+    Editor, EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt, RevertSelectedHunks,
+    ToDisplayPoint, ToggleHunkDiff,
 };
 
 #[derive(Debug, Clone)]
@@ -184,7 +187,11 @@ impl Editor {
                     }
 
                     for removed_rows in highlights_to_remove {
-                        editor.highlight_rows::<DiffRowHighlight>(removed_rows, None, cx);
+                        editor.highlight_rows::<DiffRowHighlight>(
+                            to_inclusive_row_range(removed_rows, &snapshot),
+                            None,
+                            cx,
+                        );
                     }
                     editor.remove_blocks(blocks_to_remove, None, cx);
                     for hunk in hunks_to_expand {
@@ -216,9 +223,9 @@ impl Editor {
         let hunk_end = hunk.multi_buffer_range.end;
 
         let buffer = self.buffer().clone();
+        let snapshot = self.snapshot(cx);
         let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            let hunk = buffer_diff_hunk(&snapshot, multi_buffer_row_range.clone())?;
+            let hunk = buffer_diff_hunk(&snapshot.buffer_snapshot, multi_buffer_row_range.clone())?;
             let mut buffer_ranges = buffer.range_to_buffer_ranges(multi_buffer_row_range, cx);
             if buffer_ranges.len() == 1 {
                 let (buffer, _, _) = buffer_ranges.pop()?;
@@ -256,7 +263,7 @@ impl Editor {
             }
             DiffHunkStatus::Added => {
                 self.highlight_rows::<DiffRowHighlight>(
-                    hunk_start..hunk_end,
+                    to_inclusive_row_range(hunk_start..hunk_end, &snapshot),
                     Some(added_hunk_color(cx)),
                     cx,
                 );
@@ -264,13 +271,16 @@ impl Editor {
             }
             DiffHunkStatus::Modified => {
                 self.highlight_rows::<DiffRowHighlight>(
-                    hunk_start..hunk_end,
+                    to_inclusive_row_range(hunk_start..hunk_end, &snapshot),
                     Some(added_hunk_color(cx)),
                     cx,
                 );
                 self.insert_deleted_text_block(diff_base_buffer, deleted_text_lines, &hunk, cx)
             }
         };
+        if hunk_start.cmp(&hunk_end, &multi_buffer_snapshot).is_gt() {
+            eprintln!("@######################!!!!!!!!!!!!!");
+        };
         self.expanded_hunks.hunks.insert(
             block_insert_index,
             ExpandedHunk {
@@ -461,7 +471,11 @@ impl Editor {
                     });
 
                     for removed_rows in highlights_to_remove {
-                        editor.highlight_rows::<DiffRowHighlight>(removed_rows, None, cx);
+                        editor.highlight_rows::<DiffRowHighlight>(
+                            to_inclusive_row_range(removed_rows, &snapshot),
+                            None,
+                            cx,
+                        );
                     }
                     editor.remove_blocks(blocks_to_remove, None, cx);
 
@@ -565,7 +579,7 @@ fn editor_with_deleted_text(
             .buffer_snapshot
             .anchor_after(editor.buffer.read(cx).len(cx));
 
-        editor.highlight_rows::<DiffRowHighlight>(start..end, Some(deleted_color), cx);
+        editor.highlight_rows::<DiffRowHighlight>(start..=end, Some(deleted_color), cx);
         let hunk_related_subscription = cx.on_blur(&editor.focus_handle, |editor, cx| {
             editor.change_selections(None, cx, |s| {
                 s.try_cancel();
@@ -619,3 +633,18 @@ fn buffer_diff_hunk(
     }
     None
 }
+
+fn to_inclusive_row_range(
+    row_range: Range<Anchor>,
+    snapshot: &EditorSnapshot,
+) -> RangeInclusive<Anchor> {
+    let mut display_row_range =
+        row_range.start.to_display_point(snapshot)..row_range.end.to_display_point(snapshot);
+    if display_row_range.end.row() > display_row_range.start.row() {
+        *display_row_range.end.row_mut() -= 1;
+    }
+    let point_range = display_row_range.start.to_point(&snapshot.display_snapshot)
+        ..display_row_range.end.to_point(&snapshot.display_snapshot);
+    let new_range = point_range.to_anchors(&snapshot.buffer_snapshot);
+    new_range.start..=new_range.end
+}

crates/editor/src/test.rs 🔗

@@ -148,18 +148,31 @@ pub fn expanded_hunks(
 
 #[cfg(any(test, feature = "test-support"))]
 pub fn expanded_hunks_background_highlights(
-    editor: &Editor,
-    snapshot: &DisplaySnapshot,
-) -> Vec<core::ops::Range<u32>> {
-    use itertools::Itertools;
+    editor: &mut Editor,
+    cx: &mut gpui::WindowContext,
+) -> Vec<std::ops::RangeInclusive<u32>> {
+    let mut highlights = Vec::new();
+
+    let mut range_start = 0;
+    let mut previous_highlighted_row = None;
+    for (highlighted_row, _) in editor.highlighted_display_rows(collections::HashSet::default(), cx)
+    {
+        match previous_highlighted_row {
+            Some(previous_row) => {
+                if previous_row + 1 != highlighted_row {
+                    highlights.push(range_start..=previous_row);
+                    range_start = highlighted_row;
+                }
+            }
+            None => {
+                range_start = highlighted_row;
+            }
+        }
+        previous_highlighted_row = Some(highlighted_row);
+    }
+    if let Some(previous_row) = previous_highlighted_row {
+        highlights.push(range_start..=previous_row);
+    }
 
-    editor
-        .highlighted_rows::<crate::DiffRowHighlight>()
-        .into_iter()
-        .flatten()
-        .map(|(range, _)| {
-            range.start.to_display_point(snapshot).row()..range.end.to_display_point(snapshot).row()
-        })
-        .unique()
-        .collect()
+    highlights
 }

crates/go_to_line/src/go_to_line.rs 🔗

@@ -120,7 +120,7 @@ impl GoToLine {
                 let anchor = snapshot.buffer_snapshot.anchor_before(point);
                 active_editor.clear_row_highlights::<GoToLineRowHighlights>();
                 active_editor.highlight_rows::<GoToLineRowHighlights>(
-                    anchor..anchor,
+                    anchor..=anchor,
                     Some(cx.theme().colors().editor_highlighted_line_background),
                     cx,
                 );

crates/outline/src/outline.rs 🔗

@@ -142,7 +142,7 @@ impl OutlineViewDelegate {
             self.active_editor.update(cx, |active_editor, cx| {
                 active_editor.clear_row_highlights::<OutlineRowHighlights>();
                 active_editor.highlight_rows::<OutlineRowHighlights>(
-                    outline_item.range.clone(),
+                    outline_item.range.start..=outline_item.range.end,
                     Some(cx.theme().colors().editor_highlighted_line_background),
                     cx,
                 );
@@ -243,7 +243,7 @@ impl PickerDelegate for OutlineViewDelegate {
                 .and_then(|highlights| highlights.into_iter().next().map(|(rows, _)| rows.clone()))
             {
                 active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                    s.select_ranges([rows.start..rows.start])
+                    s.select_ranges([*rows.start()..*rows.start()])
                 });
                 active_editor.clear_row_highlights::<OutlineRowHighlights>();
                 active_editor.focus(cx);