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

Release Notes:

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

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
Cole Miller 2025-02-19 13:33:21 -05:00 committed by GitHub
parent c9bd44f983
commit 8e17b34eff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 265 additions and 175 deletions

View file

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

View file

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

View file

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

View file

@ -2618,7 +2618,7 @@ async fn test_git_diff_base_change(
diff.hunks_in_row_range(0..4, buffer, cx), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &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), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &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, 1..2,
"TWO\n", "TWO\n",
"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), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &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), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &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, 1..2,
"TWO_HUNDRED\n", "TWO_HUNDRED\n",
"two\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), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &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), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&staged_text, &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), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&new_staged_text, &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), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&new_staged_text, &new_staged_text,
&[(2..3, "", "three\n", DiffHunkStatus::added())], &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
); );
}); });
} }

View file

@ -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 // 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. // 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 { let related_to_selection = if allow_adjacent {
hunk.row_range.overlaps(&query_rows) hunk.row_range.overlaps(&query_rows)
|| hunk.row_range.start == query_rows.end || hunk.row_range.start == query_rows.end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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