Color staged and unstaged hunks differently by opacity (#25108)

Cole Miller and Nate Butler created

Release Notes:

- Make staged diff hunks appear as more opaque than unstaged hunks

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>

Change summary

assets/themes/gruvbox/gruvbox.json            |  18 ++
assets/themes/one/one.json                    |   6 
crates/buffer_diff/src/buffer_diff.rs         | 113 +++++++++++----
crates/collab/src/tests/integration_tests.rs  |  20 +-
crates/editor/src/editor.rs                   |   2 
crates/editor/src/editor_tests.rs             |  57 +++++--
crates/editor/src/element.rs                  | 149 ++++++++++----------
crates/editor/src/test/editor_test_context.rs |  10 
crates/multi_buffer/src/multi_buffer.rs       |  17 +
crates/multi_buffer/src/multi_buffer_tests.rs |  30 ++--
crates/project/src/project_tests.rs           |  14 
11 files changed, 263 insertions(+), 173 deletions(-)

Detailed changes

assets/themes/gruvbox/gruvbox.json ๐Ÿ”—

@@ -105,6 +105,9 @@
         "terminal.ansi.bright_white": "#fbf1c7ff",
         "terminal.ansi.dim_white": "#b0a189ff",
         "link_text.hover": "#83a598ff",
+        "version_control_added": "#b7bb26ff",
+        "version_control_modified": "#f9bd2fff",
+        "version_control_deleted": "#fb4a35ff",
         "conflict": "#f9bd2fff",
         "conflict.background": "#572e10ff",
         "conflict.border": "#754916ff",
@@ -490,6 +493,9 @@
         "terminal.ansi.bright_white": "#fbf1c7ff",
         "terminal.ansi.dim_white": "#b0a189ff",
         "link_text.hover": "#83a598ff",
+        "version_control_added": "#b7bb26ff",
+        "version_control_modified": "#f9bd2fff",
+        "version_control_deleted": "#fb4a35ff",
         "conflict": "#f9bd2fff",
         "conflict.background": "#572e10ff",
         "conflict.border": "#754916ff",
@@ -875,6 +881,9 @@
         "terminal.ansi.bright_white": "#fbf1c7ff",
         "terminal.ansi.dim_white": "#b0a189ff",
         "link_text.hover": "#83a598ff",
+        "version_control_added": "#b7bb26ff",
+        "version_control_modified": "#f9bd2fff",
+        "version_control_deleted": "#fb4a35ff",
         "conflict": "#f9bd2fff",
         "conflict.background": "#572e10ff",
         "conflict.border": "#754916ff",
@@ -1260,6 +1269,9 @@
         "terminal.ansi.bright_white": "#282828ff",
         "terminal.ansi.dim_white": "#73675eff",
         "link_text.hover": "#0b6678ff",
+        "version_control_added": "#797410ff",
+        "version_control_modified": "#b57615ff",
+        "version_control_deleted": "#9d0308ff",
         "conflict": "#b57615ff",
         "conflict.background": "#f5e2d0ff",
         "conflict.border": "#ebccabff",
@@ -1645,6 +1657,9 @@
         "terminal.ansi.bright_white": "#282828ff",
         "terminal.ansi.dim_white": "#73675eff",
         "link_text.hover": "#0b6678ff",
+        "version_control_added": "#797410ff",
+        "version_control_modified": "#b57615ff",
+        "version_control_deleted": "#9d0308ff",
         "conflict": "#b57615ff",
         "conflict.background": "#f5e2d0ff",
         "conflict.border": "#ebccabff",
@@ -2030,6 +2045,9 @@
         "terminal.ansi.bright_white": "#282828ff",
         "terminal.ansi.dim_white": "#73675eff",
         "link_text.hover": "#0b6678ff",
+        "version_control_added": "#797410ff",
+        "version_control_modified": "#b57615ff",
+        "version_control_deleted": "#9d0308ff",
         "conflict": "#b57615ff",
         "conflict.background": "#f5e2d0ff",
         "conflict.border": "#ebccabff",

assets/themes/one/one.json ๐Ÿ”—

@@ -96,6 +96,9 @@
         "terminal.ansi.bright_white": "#dce0e5ff",
         "terminal.ansi.dim_white": "#575d65ff",
         "link_text.hover": "#74ade8ff",
+        "version_control_added": "#a7c088ff",
+        "version_control_modified": "#dec184ff",
+        "version_control_deleted": "#d07277ff",
         "conflict": "#dec184ff",
         "conflict.background": "#dec1841a",
         "conflict.border": "#5d4c2fff",
@@ -472,6 +475,9 @@
         "terminal.ansi.bright_white": "#242529ff",
         "terminal.ansi.dim_white": "#97979aff",
         "link_text.hover": "#5c78e2ff",
+        "version_control_added": "#669f59ff",
+        "version_control_modified": "#a48819ff",
+        "version_control_deleted": "#d36151ff",
         "conflict": "#a48819ff",
         "conflict.background": "#faf2e6ff",
         "conflict.border": "#f4e7d1ff",

crates/buffer_diff/src/buffer_diff.rs ๐Ÿ”—

@@ -29,10 +29,16 @@ struct BufferDiffInner {
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum DiffHunkStatus {
-    Added(DiffHunkSecondaryStatus),
-    Modified(DiffHunkSecondaryStatus),
-    Removed(DiffHunkSecondaryStatus),
+pub struct DiffHunkStatus {
+    pub kind: DiffHunkStatusKind,
+    pub secondary: DiffHunkSecondaryStatus,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum DiffHunkStatusKind {
+    Added,
+    Modified,
+    Deleted,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -42,6 +48,16 @@ pub enum DiffHunkSecondaryStatus {
     None,
 }
 
+impl DiffHunkSecondaryStatus {
+    pub fn is_secondary(&self) -> bool {
+        match self {
+            DiffHunkSecondaryStatus::HasSecondaryHunk => true,
+            DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => true,
+            DiffHunkSecondaryStatus::None => false,
+        }
+    }
+}
+
 /// A diff hunk resolved to rows in the buffer.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct DiffHunk {
@@ -927,34 +943,76 @@ impl BufferDiff {
 
 impl DiffHunk {
     pub fn status(&self) -> DiffHunkStatus {
-        if self.buffer_range.start == self.buffer_range.end {
-            DiffHunkStatus::Removed(self.secondary_status)
+        let kind = if self.buffer_range.start == self.buffer_range.end {
+            DiffHunkStatusKind::Deleted
         } else if self.diff_base_byte_range.is_empty() {
-            DiffHunkStatus::Added(self.secondary_status)
+            DiffHunkStatusKind::Added
         } else {
-            DiffHunkStatus::Modified(self.secondary_status)
+            DiffHunkStatusKind::Modified
+        };
+        DiffHunkStatus {
+            kind,
+            secondary: self.secondary_status,
         }
     }
 }
 
 impl DiffHunkStatus {
-    pub fn is_removed(&self) -> bool {
-        matches!(self, DiffHunkStatus::Removed(_))
+    pub fn is_deleted(&self) -> bool {
+        self.kind == DiffHunkStatusKind::Deleted
+    }
+
+    pub fn is_added(&self) -> bool {
+        self.kind == DiffHunkStatusKind::Added
+    }
+
+    pub fn is_modified(&self) -> bool {
+        self.kind == DiffHunkStatusKind::Modified
+    }
+
+    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
+        Self {
+            kind: DiffHunkStatusKind::Added,
+            secondary,
+        }
+    }
+
+    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
+        Self {
+            kind: DiffHunkStatusKind::Modified,
+            secondary,
+        }
+    }
+
+    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
+        Self {
+            kind: DiffHunkStatusKind::Deleted,
+            secondary,
+        }
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn removed() -> Self {
-        DiffHunkStatus::Removed(DiffHunkSecondaryStatus::None)
+    pub fn deleted_none() -> Self {
+        Self {
+            kind: DiffHunkStatusKind::Deleted,
+            secondary: DiffHunkSecondaryStatus::None,
+        }
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn added() -> Self {
-        DiffHunkStatus::Added(DiffHunkSecondaryStatus::None)
+    pub fn added_none() -> Self {
+        Self {
+            kind: DiffHunkStatusKind::Added,
+            secondary: DiffHunkSecondaryStatus::None,
+        }
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn modified() -> Self {
-        DiffHunkStatus::Modified(DiffHunkSecondaryStatus::None)
+    pub fn modified_none() -> Self {
+        Self {
+            kind: DiffHunkStatusKind::Modified,
+            secondary: DiffHunkSecondaryStatus::None,
+        }
     }
 }
 
@@ -1031,7 +1089,7 @@ mod tests {
             diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
             &buffer,
             &diff_base,
-            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified())],
+            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
         );
 
         buffer.edit([(0..0, "point five\n")]);
@@ -1041,8 +1099,8 @@ mod tests {
             &buffer,
             &diff_base,
             &[
-                (0..1, "", "point five\n", DiffHunkStatus::added()),
-                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified()),
+                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
+                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
             ],
         );
 
@@ -1105,23 +1163,18 @@ mod tests {
         let uncommitted_diff = BufferDiff::build_sync(buffer.clone(), head_text.clone(), cx);
 
         let expected_hunks = vec![
-            (
-                2..3,
-                "two\n",
-                "TWO\n",
-                DiffHunkStatus::Modified(DiffHunkSecondaryStatus::None),
-            ),
+            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
             (
                 4..6,
                 "four\nfive\n",
                 "FOUR\nFIVE\n",
-                DiffHunkStatus::Modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
+                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
             ),
             (
                 7..8,
                 "seven\n",
                 "SEVEN\n",
-                DiffHunkStatus::Modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
+                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
             ),
         ];
 
@@ -1197,9 +1250,9 @@ mod tests {
             &buffer,
             &diff_base,
             &[
-                (6..7, "", "HELLO\n", DiffHunkStatus::added()),
-                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified()),
-                (12..13, "", "WORLD\n", DiffHunkStatus::added()),
+                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
+                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
+                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
             ],
         );
     }

crates/collab/src/tests/integration_tests.rs ๐Ÿ”—

@@ -2618,7 +2618,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &diff.base_text_string().unwrap(),
-            &[(1..2, "", "two\n", DiffHunkStatus::added())],
+            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
         );
     });
 
@@ -2646,7 +2646,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &diff.base_text_string().unwrap(),
-            &[(1..2, "", "two\n", DiffHunkStatus::added())],
+            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
         );
     });
 
@@ -2672,7 +2672,7 @@ async fn test_git_diff_base_change(
                 1..2,
                 "TWO\n",
                 "two\n",
-                DiffHunkStatus::Modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
+                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
             )],
         );
     });
@@ -2699,7 +2699,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &diff.base_text_string().unwrap(),
-            &[(2..3, "", "three\n", DiffHunkStatus::added())],
+            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
         );
     });
 
@@ -2713,7 +2713,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &diff.base_text_string().unwrap(),
-            &[(2..3, "", "three\n", DiffHunkStatus::added())],
+            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
         );
     });
 
@@ -2731,7 +2731,7 @@ async fn test_git_diff_base_change(
                 1..2,
                 "TWO_HUNDRED\n",
                 "two\n",
-                DiffHunkStatus::Modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
+                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
             )],
         );
     });
@@ -2778,7 +2778,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &diff.base_text_string().unwrap(),
-            &[(1..2, "", "two\n", DiffHunkStatus::added())],
+            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
         );
     });
 
@@ -2805,7 +2805,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &staged_text,
-            &[(1..2, "", "two\n", DiffHunkStatus::added())],
+            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
         );
     });
 
@@ -2827,7 +2827,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &new_staged_text,
-            &[(2..3, "", "three\n", DiffHunkStatus::added())],
+            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
         );
     });
 
@@ -2841,7 +2841,7 @@ async fn test_git_diff_base_change(
             diff.hunks_in_row_range(0..4, buffer, cx),
             buffer,
             &new_staged_text,
-            &[(2..3, "", "three\n", DiffHunkStatus::added())],
+            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
         );
     });
 }

crates/editor/src/editor.rs ๐Ÿ”—

@@ -15956,7 +15956,7 @@ impl EditorSnapshot {
             ) {
                 // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
                 // when the caret is just above or just below the deleted hunk.
-                let allow_adjacent = hunk.status().is_removed();
+                let allow_adjacent = hunk.status().is_deleted();
                 let related_to_selection = if allow_adjacent {
                     hunk.row_range.overlaps(&query_rows)
                         || hunk.row_range.start == query_rows.end

crates/editor/src/editor_tests.rs ๐Ÿ”—

@@ -12258,7 +12258,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
                    struct Row9.2;
                    struct Row9.3;
                    struct Row10;"#},
-        vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
+        vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
         indoc! {r#"struct Row;
                    struct Row1;
                    struct Row1.1;
@@ -12296,7 +12296,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
                    struct Row8;
                    struct Row9;
                    struct Row10;"#},
-        vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
+        vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
         indoc! {r#"struct Row;
                    struct Row1;
                    struct Row2;
@@ -12343,11 +12343,11 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
                    ยซห‡// something on bottomยป
                    struct Row10;"#},
         vec![
-            DiffHunkStatus::added(),
-            DiffHunkStatus::added(),
-            DiffHunkStatus::added(),
-            DiffHunkStatus::added(),
-            DiffHunkStatus::added(),
+            DiffHunkStatus::added_none(),
+            DiffHunkStatus::added_none(),
+            DiffHunkStatus::added_none(),
+            DiffHunkStatus::added_none(),
+            DiffHunkStatus::added_none(),
         ],
         indoc! {r#"struct Row;
                    ห‡struct Row1;
@@ -12395,7 +12395,10 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
                    struct Row99;
                    struct Row9;
                    struct Row10;"#},
-        vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
+        vec![
+            DiffHunkStatus::modified_none(),
+            DiffHunkStatus::modified_none(),
+        ],
         indoc! {r#"struct Row;
                    struct Row1;
                    struct Row33;
@@ -12422,7 +12425,10 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
                    struct Row99;
                    struct Row9;
                    struct Row10;"#},
-        vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
+        vec![
+            DiffHunkStatus::modified_none(),
+            DiffHunkStatus::modified_none(),
+        ],
         indoc! {r#"struct Row;
                    struct Row1;
                    struct Row33;
@@ -12451,12 +12457,12 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
                    struct Row9;
                    struct Row1011;ห‡"#},
         vec![
-            DiffHunkStatus::modified(),
-            DiffHunkStatus::modified(),
-            DiffHunkStatus::modified(),
-            DiffHunkStatus::modified(),
-            DiffHunkStatus::modified(),
-            DiffHunkStatus::modified(),
+            DiffHunkStatus::modified_none(),
+            DiffHunkStatus::modified_none(),
+            DiffHunkStatus::modified_none(),
+            DiffHunkStatus::modified_none(),
+            DiffHunkStatus::modified_none(),
+            DiffHunkStatus::modified_none(),
         ],
         indoc! {r#"struct Row;
                    ห‡struct Row1;
@@ -12534,7 +12540,10 @@ struct Row10;"#};
                    ห‡
                    struct Row8;
                    struct Row10;"#},
-        vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
+        vec![
+            DiffHunkStatus::deleted_none(),
+            DiffHunkStatus::deleted_none(),
+        ],
         indoc! {r#"struct Row;
                    struct Row2;
 
@@ -12557,7 +12566,10 @@ struct Row10;"#};
                    ห‡ยป
                    struct Row8;
                    struct Row10;"#},
-        vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
+        vec![
+            DiffHunkStatus::deleted_none(),
+            DiffHunkStatus::deleted_none(),
+        ],
         indoc! {r#"struct Row;
                    struct Row2;
 
@@ -12582,7 +12594,10 @@ struct Row10;"#};
 
                    struct Row8;ห‡
                    struct Row10;"#},
-        vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
+        vec![
+            DiffHunkStatus::deleted_none(),
+            DiffHunkStatus::deleted_none(),
+        ],
         indoc! {r#"struct Row;
                    struct Row1;
                    ห‡struct Row2;
@@ -12607,9 +12622,9 @@ struct Row10;"#};
                    struct Row8;ห‡ยป
                    struct Row10;"#},
         vec![
-            DiffHunkStatus::removed(),
-            DiffHunkStatus::removed(),
-            DiffHunkStatus::removed(),
+            DiffHunkStatus::deleted_none(),
+            DiffHunkStatus::deleted_none(),
+            DiffHunkStatus::deleted_none(),
         ],
         indoc! {r#"struct Row;
                    struct Row1;

crates/editor/src/element.rs ๐Ÿ”—

@@ -25,20 +25,20 @@ use crate::{
     CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
     MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
 };
-use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus};
+use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
 use client::ParticipantIndex;
 use collections::{BTreeMap, HashMap, HashSet};
 use file_icons::FileIcons;
 use git::{blame::BlameEntry, Oid};
 use gpui::{
-    anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash,
-    point, px, quad, relative, size, svg, transparent_black, Action, AnyElement, App,
-    AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners,
-    CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _,
-    FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
-    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
-    ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
-    StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, Window,
+    anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
+    relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
+    ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase,
+    Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox,
+    Hsla, InteractiveElement, IntoElement, Keystroke, Length, ModifiersChangedEvent, MouseButton,
+    MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
+    ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
+    Subscription, TextRun, TextStyleRefinement, Window,
 };
 use itertools::Itertools;
 use language::{
@@ -74,7 +74,7 @@ use ui::{
     POPOVER_Y_PADDING,
 };
 use unicode_segmentation::UnicodeSegmentation;
-use util::{RangeExt, ResultExt};
+use util::{debug_panic, RangeExt, ResultExt};
 use workspace::{item::Item, notifications::NotifyTaskExt};
 
 const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
@@ -2124,7 +2124,10 @@ impl EditorElement {
                     .get(&display_row)
                     .unwrap_or(&non_relative_number);
                 write!(&mut line_number, "{number}").unwrap();
-                if matches!(row_info.diff_status, Some(DiffHunkStatus::Removed(_))) {
+                if row_info
+                    .diff_status
+                    .is_some_and(|status| status.is_deleted())
+                {
                     return None;
                 }
 
@@ -4174,10 +4177,10 @@ impl EditorElement {
                 if row_infos[row_ix].diff_status.is_none() {
                     continue;
                 }
-                if matches!(
-                    row_infos[row_ix].diff_status,
-                    Some(DiffHunkStatus::Added(_))
-                ) && !matches!(*status, DiffHunkStatus::Added(_))
+                if row_infos[row_ix]
+                    .diff_status
+                    .is_some_and(|status| status.is_added())
+                    && !status.is_added()
                 {
                     continue;
                 }
@@ -4571,39 +4574,35 @@ impl EditorElement {
                         );
                         Some((
                             hunk_bounds,
-                            cx.theme().status().modified,
+                            cx.theme().colors().version_control_modified,
                             Corners::all(px(0.)),
-                            &DiffHunkSecondaryStatus::None,
+                            DiffHunkSecondaryStatus::None,
                         ))
                     }
                     DisplayDiffHunk::Unfolded {
                         status,
                         display_row_range,
                         ..
-                    } => hitbox.as_ref().map(|hunk_hitbox| match status {
-                        DiffHunkStatus::Added(secondary_status) => (
+                    } => hitbox.as_ref().map(|hunk_hitbox| match status.kind {
+                        DiffHunkStatusKind::Added => (
                             hunk_hitbox.bounds,
-                            cx.theme().status().created,
+                            cx.theme().colors().version_control_added,
                             Corners::all(px(0.)),
-                            secondary_status,
+                            status.secondary,
                         ),
-                        DiffHunkStatus::Modified(secondary_status) => (
+                        DiffHunkStatusKind::Modified => (
                             hunk_hitbox.bounds,
-                            cx.theme().status().modified,
+                            cx.theme().colors().version_control_modified,
                             Corners::all(px(0.)),
-                            secondary_status,
+                            status.secondary,
                         ),
-                        DiffHunkStatus::Removed(secondary_status)
-                            if !display_row_range.is_empty() =>
-                        {
-                            (
-                                hunk_hitbox.bounds,
-                                cx.theme().status().deleted,
-                                Corners::all(px(0.)),
-                                secondary_status,
-                            )
-                        }
-                        DiffHunkStatus::Removed(secondary_status) => (
+                        DiffHunkStatusKind::Deleted if !display_row_range.is_empty() => (
+                            hunk_hitbox.bounds,
+                            cx.theme().colors().version_control_deleted,
+                            Corners::all(px(0.)),
+                            status.secondary,
+                        ),
+                        DiffHunkStatusKind::Deleted => (
                             Bounds::new(
                                 point(
                                     hunk_hitbox.origin.x - hunk_hitbox.size.width,
@@ -4611,19 +4610,21 @@ impl EditorElement {
                                 ),
                                 size(hunk_hitbox.size.width * px(2.), hunk_hitbox.size.height),
                             ),
-                            cx.theme().status().deleted,
+                            cx.theme().colors().version_control_deleted,
                             Corners::all(1. * line_height),
-                            secondary_status,
+                            status.secondary,
                         ),
                     }),
                 };
 
-                if let Some((hunk_bounds, mut background_color, corner_radii, secondary_status)) =
+                if let Some((hunk_bounds, background_color, corner_radii, secondary_status)) =
                     hunk_to_paint
                 {
-                    if *secondary_status != DiffHunkSecondaryStatus::None {
-                        background_color.a *= 0.6;
-                    }
+                    let background_color = if secondary_status != DiffHunkSecondaryStatus::None {
+                        background_color.opacity(0.3)
+                    } else {
+                        background_color.opacity(1.0)
+                    };
                     window.paint_quad(quad(
                         hunk_bounds,
                         corner_radii,
@@ -4659,7 +4660,7 @@ impl EditorElement {
                 status,
                 ..
             } => {
-                if status.is_removed() && display_row_range.is_empty() {
+                if status.is_deleted() && display_row_range.is_empty() {
                     let row = display_row_range.start;
 
                     let offset = line_height / 2.;
@@ -5312,10 +5313,10 @@ impl EditorElement {
                                         if end_display_row != start_display_row {
                                             end_display_row.0 -= 1;
                                         }
-                                        let color = match &hunk.status() {
-                                            DiffHunkStatus::Added(_) => theme.status().created,
-                                            DiffHunkStatus::Modified(_) => theme.status().modified,
-                                            DiffHunkStatus::Removed(_) => theme.status().deleted,
+                                        let color = match &hunk.status().kind {
+                                            DiffHunkStatusKind::Added => theme.status().created,
+                                            DiffHunkStatusKind::Modified => theme.status().modified,
+                                            DiffHunkStatusKind::Deleted => theme.status().deleted,
                                         };
                                         ColoredRange {
                                             start: start_display_row,
@@ -6896,46 +6897,38 @@ impl Element for EditorElement {
                         )
                     };
 
-                    let (mut highlighted_rows, distinguish_unstaged_hunks) =
-                        self.editor.update(cx, |editor, cx| {
-                            (
-                                editor.highlighted_display_rows(window, cx),
-                                editor.distinguish_unstaged_diff_hunks,
-                            )
-                        });
+                    let mut highlighted_rows = self
+                        .editor
+                        .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx));
 
                     for (ix, row_info) in row_infos.iter().enumerate() {
-                        let background = match row_info.diff_status {
-                            Some(DiffHunkStatus::Added(secondary_status)) => {
-                                let color = style.status.created_background;
-                                match secondary_status {
-                                    DiffHunkSecondaryStatus::HasSecondaryHunk
-                                    | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
-                                        if distinguish_unstaged_hunks =>
-                                    {
-                                        pattern_slash(color, line_height.0 / 4.0)
-                                    }
-                                    _ => color.into(),
-                                }
+                        let Some(diff_status) = row_info.diff_status else {
+                            continue;
+                        };
+
+                        let staged_opacity = 0.10;
+                        let unstaged_opacity = 0.04;
+
+                        let background_color = match diff_status.kind {
+                            DiffHunkStatusKind::Added => cx.theme().colors().version_control_added,
+                            DiffHunkStatusKind::Deleted => {
+                                cx.theme().colors().version_control_deleted
                             }
-                            Some(DiffHunkStatus::Removed(secondary_status)) => {
-                                let color = style.status.deleted_background;
-                                match secondary_status {
-                                    DiffHunkSecondaryStatus::HasSecondaryHunk
-                                    | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
-                                        if distinguish_unstaged_hunks =>
-                                    {
-                                        pattern_slash(color, line_height.0 / 4.0)
-                                    }
-                                    _ => color.into(),
-                                }
+                            DiffHunkStatusKind::Modified => {
+                                debug_panic!("modified diff status for row info");
+                                continue;
                             }
-                            _ => continue,
                         };
+                        let background_color =
+                            if diff_status.secondary == DiffHunkSecondaryStatus::None {
+                                background_color.opacity(staged_opacity)
+                            } else {
+                                background_color.opacity(unstaged_opacity)
+                            };
 
                         highlighted_rows
                             .entry(start_row + DisplayRow(ix as u32))
-                            .or_insert(background);
+                            .or_insert(background_color.into());
                     }
 
                     let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(

crates/editor/src/test/editor_test_context.rs ๐Ÿ”—

@@ -2,7 +2,7 @@ use crate::{
     display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
     RowExt,
 };
-use buffer_diff::DiffHunkStatus;
+use buffer_diff::DiffHunkStatusKind;
 use collections::BTreeMap;
 use futures::Future;
 
@@ -470,10 +470,10 @@ pub fn assert_state_with_diff(
         .split('\n')
         .zip(line_infos)
         .map(|(line, info)| {
-            let mut marker = match info.diff_status {
-                Some(DiffHunkStatus::Added(_)) => "+ ",
-                Some(DiffHunkStatus::Removed(_)) => "- ",
-                Some(DiffHunkStatus::Modified(_)) => unreachable!(),
+            let mut marker = match info.diff_status.map(|status| status.kind) {
+                Some(DiffHunkStatusKind::Added) => "+ ",
+                Some(DiffHunkStatusKind::Deleted) => "- ",
+                Some(DiffHunkStatusKind::Modified) => unreachable!(),
                 None => {
                     if has_diff {
                         "  "

crates/multi_buffer/src/multi_buffer.rs ๐Ÿ”—

@@ -9,6 +9,7 @@ pub use position::{TypedOffset, TypedPoint, TypedRow};
 use anyhow::{anyhow, Result};
 use buffer_diff::{
     BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunkSecondaryStatus, DiffHunkStatus,
+    DiffHunkStatusKind,
 };
 use clock::ReplicaId;
 use collections::{BTreeMap, Bound, HashMap, HashSet};
@@ -135,12 +136,16 @@ pub struct MultiBufferDiffHunk {
 
 impl MultiBufferDiffHunk {
     pub fn status(&self) -> DiffHunkStatus {
-        if self.buffer_range.start == self.buffer_range.end {
-            DiffHunkStatus::Removed(self.secondary_status)
+        let kind = if self.buffer_range.start == self.buffer_range.end {
+            DiffHunkStatusKind::Deleted
         } else if self.diff_base_byte_range.is_empty() {
-            DiffHunkStatus::Added(self.secondary_status)
+            DiffHunkStatusKind::Added
         } else {
-            DiffHunkStatus::Modified(self.secondary_status)
+            DiffHunkStatusKind::Modified
+        };
+        DiffHunkStatus {
+            kind,
+            secondary: self.secondary_status,
         }
     }
 }
@@ -6210,7 +6215,7 @@ where
                     excerpt,
                     has_trailing_newline: *has_trailing_newline,
                     is_main_buffer: false,
-                    diff_hunk_status: Some(DiffHunkStatus::Removed(
+                    diff_hunk_status: Some(DiffHunkStatus::deleted(
                         hunk_info.hunk_secondary_status,
                     )),
                     buffer_range: buffer_start..buffer_end,
@@ -6256,7 +6261,7 @@ where
                     has_trailing_newline,
                     is_main_buffer: true,
                     diff_hunk_status: inserted_hunk_info
-                        .map(|info| DiffHunkStatus::Added(info.hunk_secondary_status)),
+                        .map(|info| DiffHunkStatus::added(info.hunk_secondary_status)),
                     buffer_range: buffer_start..buffer_end,
                     range: start..end,
                 })

crates/multi_buffer/src/multi_buffer_tests.rs ๐Ÿ”—

@@ -1,5 +1,5 @@
 use super::*;
-use buffer_diff::DiffHunkStatus;
+use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
 use gpui::{App, TestAppContext};
 use indoc::indoc;
 use language::{Buffer, Rope};
@@ -1325,13 +1325,13 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
             .map(|info| (info.buffer_row, info.diff_status))
             .collect::<Vec<_>>(),
         vec![
-            (Some(0), Some(DiffHunkStatus::added())),
+            (Some(0), Some(DiffHunkStatus::added_none())),
             (Some(1), None),
-            (Some(1), Some(DiffHunkStatus::removed())),
-            (Some(2), Some(DiffHunkStatus::added())),
+            (Some(1), Some(DiffHunkStatus::deleted_none())),
+            (Some(2), Some(DiffHunkStatus::added_none())),
             (Some(3), None),
-            (Some(3), Some(DiffHunkStatus::removed())),
-            (Some(4), Some(DiffHunkStatus::removed())),
+            (Some(3), Some(DiffHunkStatus::deleted_none())),
+            (Some(4), Some(DiffHunkStatus::deleted_none())),
             (Some(4), None),
             (Some(5), None)
         ]
@@ -2279,7 +2279,7 @@ impl ReferenceMultibuffer {
                             buffer_start: Some(
                                 base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
                             ),
-                            status: Some(DiffHunkStatus::Removed(hunk.secondary_status)),
+                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
                         });
                     }
 
@@ -2294,7 +2294,7 @@ impl ReferenceMultibuffer {
                         buffer_id: Some(buffer.remote_id()),
                         range: len..text.len(),
                         buffer_start: Some(buffer.offset_to_point(offset)),
-                        status: Some(DiffHunkStatus::Added(hunk.secondary_status)),
+                        status: Some(DiffHunkStatus::added(hunk.secondary_status)),
                     });
                     offset = hunk_range.end;
                 }
@@ -2664,13 +2664,13 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
             snapshot.widest_line_number(),
             expected_row_infos
                 .into_iter()
-                .filter_map(
-                    |info| if matches!(info.diff_status, Some(DiffHunkStatus::Removed(_))) {
+                .filter_map(|info| {
+                    if info.diff_status.is_some_and(|status| status.is_deleted()) {
                         None
                     } else {
                         info.buffer_row
                     }
-                )
+                })
                 .max()
                 .unwrap()
                 + 1
@@ -3021,10 +3021,10 @@ fn format_diff(
         .enumerate()
         .zip(row_infos)
         .map(|((ix, line), info)| {
-            let marker = match info.diff_status {
-                Some(DiffHunkStatus::Added(_)) => "+ ",
-                Some(DiffHunkStatus::Removed(_)) => "- ",
-                Some(DiffHunkStatus::Modified(_)) => unreachable!(),
+            let marker = match info.diff_status.map(|status| status.kind) {
+                Some(DiffHunkStatusKind::Added) => "+ ",
+                Some(DiffHunkStatusKind::Deleted) => "- ",
+                Some(DiffHunkStatusKind::Modified) => unreachable!(),
                 None => {
                     if has_diff && !line.is_empty() {
                         "  "

crates/project/src/project_tests.rs ๐Ÿ”—

@@ -5693,12 +5693,12 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) {
             &snapshot,
             &unstaged_diff.base_text_string().unwrap(),
             &[
-                (0..1, "", "// print goodbye\n", DiffHunkStatus::added()),
+                (0..1, "", "// print goodbye\n", DiffHunkStatus::added_none()),
                 (
                     2..3,
                     "    println!(\"hello world\");\n",
                     "    println!(\"goodbye world\");\n",
-                    DiffHunkStatus::modified(),
+                    DiffHunkStatus::modified_none(),
                 ),
             ],
         );
@@ -5727,7 +5727,7 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) {
                 2..3,
                 "",
                 "    println!(\"goodbye world\");\n",
-                DiffHunkStatus::added(),
+                DiffHunkStatus::added_none(),
             )],
         );
     });
@@ -5815,13 +5815,13 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
                     0..1,
                     "",
                     "// print goodbye\n",
-                    DiffHunkStatus::Added(DiffHunkSecondaryStatus::HasSecondaryHunk),
+                    DiffHunkStatus::added(DiffHunkSecondaryStatus::HasSecondaryHunk),
                 ),
                 (
                     2..3,
                     "    println!(\"hello world\");\n",
                     "    println!(\"goodbye world\");\n",
-                    DiffHunkStatus::modified(),
+                    DiffHunkStatus::modified_none(),
                 ),
             ],
         );
@@ -5850,7 +5850,7 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
                 2..3,
                 "",
                 "    println!(\"goodbye world\");\n",
-                DiffHunkStatus::added(),
+                DiffHunkStatus::added_none(),
             )],
         );
     });
@@ -5916,7 +5916,7 @@ async fn test_single_file_diffs(cx: &mut gpui::TestAppContext) {
                 1..2,
                 "    println!(\"hello from HEAD\");\n",
                 "    println!(\"hello from the working copy\");\n",
-                DiffHunkStatus::modified(),
+                DiffHunkStatus::modified_none(),
             )],
         );
     });