editor: Update git hunk indicators to show staging status when hunk is expanded (#24818)

- Update git hunk indicators to show staging status when hunk is
expanded
- Updates uses of status colors to the new version control theme colors
- Adds new version control theme colors to included themes

Before:

![CleanShot 2025-02-13 at 14 42
48@2x](https://github.com/user-attachments/assets/ccca147e-0de2-4e69-9cd4-01b010bf06d0)

After:

![CleanShot 2025-02-13 at 14 42
04@2x](https://github.com/user-attachments/assets/1ab49174-bde5-43b2-83c5-d217533df49a)

(Colors here are from before theme colors were added)


Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: cole-miller <m@cole-miller.net>
This commit is contained in:
Nate Butler 2025-02-13 15:12:23 -05:00 committed by GitHub
parent a6a8d79d86
commit 8c202b3b09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 167 additions and 65 deletions

View file

@ -105,6 +105,16 @@
"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.added_background": "#b7bb2614",
"version_control.deleted": "#fb4a35ff",
"version_control.deleted_background": "#fb4a3514",
"version_control.modified": "#f9bd2fff",
"version_control.modified_background": "#f9bd2f14",
"version_control.renamed": "#83a598ff",
"version_control.conflict": "#f9bd2fff",
"version_control.conflict_background": "#f9bd2f14",
"version_control.ignored": "#998b78ff",
"conflict": "#f9bd2fff", "conflict": "#f9bd2fff",
"conflict.background": "#572e10ff", "conflict.background": "#572e10ff",
"conflict.border": "#754916ff", "conflict.border": "#754916ff",
@ -490,6 +500,16 @@
"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.added_background": "#b7bb2614",
"version_control.deleted": "#fb4a35ff",
"version_control.deleted_background": "#fb4a3514",
"version_control.modified": "#f9bd2fff",
"version_control.modified_background": "#f9bd2f14",
"version_control.renamed": "#83a598ff",
"version_control.conflict": "#f9bd2fff",
"version_control.conflict_background": "#f9bd2f14",
"version_control.ignored": "#998b78ff",
"conflict": "#f9bd2fff", "conflict": "#f9bd2fff",
"conflict.background": "#572e10ff", "conflict.background": "#572e10ff",
"conflict.border": "#754916ff", "conflict.border": "#754916ff",
@ -875,6 +895,16 @@
"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.added_background": "#b7bb2614",
"version_control.deleted": "#fb4a35ff",
"version_control.deleted_background": "#fb4a3514",
"version_control.modified": "#f9bd2fff",
"version_control.modified_background": "#f9bd2f14",
"version_control.renamed": "#83a598ff",
"version_control.conflict": "#f9bd2fff",
"version_control.conflict_background": "#f9bd2f14",
"version_control.ignored": "#998b78ff",
"conflict": "#f9bd2fff", "conflict": "#f9bd2fff",
"conflict.background": "#572e10ff", "conflict.background": "#572e10ff",
"conflict.border": "#754916ff", "conflict.border": "#754916ff",
@ -1260,6 +1290,16 @@
"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": "#79740eff",
"version_control.added_background": "#79740e14",
"version_control.deleted": "#9d0006ff",
"version_control.deleted_background": "#9d000614",
"version_control.modified": "#b57614ff",
"version_control.modified_background": "#b5761414",
"version_control.renamed": "#076678ff",
"version_control.conflict": "#b57614ff",
"version_control.conflict_background": "#b5761414",
"version_control.ignored": "#928374ff",
"conflict": "#b57615ff", "conflict": "#b57615ff",
"conflict.background": "#f5e2d0ff", "conflict.background": "#f5e2d0ff",
"conflict.border": "#ebccabff", "conflict.border": "#ebccabff",
@ -1645,6 +1685,16 @@
"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": "#79740eff",
"version_control.added_background": "#79740e14",
"version_control.deleted": "#9d0006ff",
"version_control.deleted_background": "#9d000614",
"version_control.modified": "#b57614ff",
"version_control.modified_background": "#b5761414",
"version_control.renamed": "#076678ff",
"version_control.conflict": "#b57614ff",
"version_control.conflict_background": "#b5761414",
"version_control.ignored": "#928374ff",
"conflict": "#b57615ff", "conflict": "#b57615ff",
"conflict.background": "#f5e2d0ff", "conflict.background": "#f5e2d0ff",
"conflict.border": "#ebccabff", "conflict.border": "#ebccabff",
@ -2030,6 +2080,16 @@
"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": "#79740eff",
"version_control.added_background": "#79740e14",
"version_control.deleted": "#9d0006ff",
"version_control.deleted_background": "#9d000614",
"version_control.modified": "#b57614ff",
"version_control.modified_background": "#b5761414",
"version_control.renamed": "#076678ff",
"version_control.conflict": "#b57614ff",
"version_control.conflict_background": "#b5761414",
"version_control.ignored": "#928374ff",
"conflict": "#b57615ff", "conflict": "#b57615ff",
"conflict.background": "#f5e2d0ff", "conflict.background": "#f5e2d0ff",
"conflict.border": "#ebccabff", "conflict.border": "#ebccabff",

View file

@ -96,6 +96,16 @@
"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": "#a1c181ff",
"version_control.added_background": "#a1c18114",
"version_control.deleted": "#d07277ff",
"version_control.deleted_background": "#d0727714",
"version_control.modified": "#dec184ff",
"version_control.modified_background": "#dec18414",
"version_control.renamed": "#74ade8ff",
"version_control.conflict": "#dec184ff",
"version_control.conflict_background": "#dec18414",
"version_control.ignored": "#878a98ff",
"conflict": "#dec184ff", "conflict": "#dec184ff",
"conflict.background": "#dec1841a", "conflict.background": "#dec1841a",
"conflict.border": "#5d4c2fff", "conflict.border": "#5d4c2fff",

View file

@ -80,13 +80,13 @@ use code_context_menus::{
use git::blame::GitBlame; use git::blame::GitBlame;
use gpui::{ use gpui::{
div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation, div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Background, Bounds, AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Bounds, ClipboardEntry,
ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, Entity, EntityInputHandler, ClipboardItem, Context, DispatchPhase, ElementId, Entity, EntityInputHandler, EventEmitter,
EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton, MouseDownEvent, InteractiveText, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement,
PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, Task, TextStyle,
Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
WeakEntity, WeakFocusHandle, Window, WeakFocusHandle, Window,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
@ -13645,14 +13645,14 @@ impl Editor {
&self, &self,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> BTreeMap<DisplayRow, Background> { ) -> BTreeMap<DisplayRow, Hsla> {
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let mut used_highlight_orders = HashMap::default(); let mut used_highlight_orders = HashMap::default();
self.highlighted_rows self.highlighted_rows
.iter() .iter()
.flat_map(|(_, highlighted_rows)| highlighted_rows.iter()) .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
.fold( .fold(
BTreeMap::<DisplayRow, Background>::new(), BTreeMap::<DisplayRow, Hsla>::new(),
|mut unique_rows, highlight| { |mut unique_rows, highlight| {
let start = highlight.range.start.to_display_point(&snapshot); let start = highlight.range.start.to_display_point(&snapshot);
let end = highlight.range.end.to_display_point(&snapshot); let end = highlight.range.end.to_display_point(&snapshot);
@ -13669,7 +13669,7 @@ impl Editor {
used_highlight_orders.entry(row).or_insert(highlight.index); used_highlight_orders.entry(row).or_insert(highlight.index);
if highlight.index >= *used_index { if highlight.index >= *used_index {
*used_index = highlight.index; *used_index = highlight.index;
unique_rows.insert(DisplayRow(row), highlight.color.into()); unique_rows.insert(DisplayRow(row), highlight.color);
} }
} }
unique_rows unique_rows

View file

@ -31,7 +31,7 @@ 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, pattern_slash,
point, px, quad, relative, size, svg, transparent_black, Action, AnyElement, App, point, px, quad, relative, size, solid_color, svg, transparent_black, Action, AnyElement, App,
AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners,
CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable, FontId, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable, FontId,
GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
@ -89,6 +89,7 @@ enum DisplayDiffHunk {
display_row_range: Range<DisplayRow>, display_row_range: Range<DisplayRow>,
multi_buffer_range: Range<Anchor>, multi_buffer_range: Range<Anchor>,
status: DiffHunkStatus, status: DiffHunkStatus,
contains_expanded: bool,
}, },
} }
@ -1567,6 +1568,11 @@ impl EditorElement {
if hunk_display_end.column() > 0 { if hunk_display_end.column() > 0 {
end_row.0 += 1; end_row.0 += 1;
} }
let start_row = hunk_display_start.row();
let contains_expanded = snapshot
.row_infos(start_row)
.take(end_row.0 as usize - start_row.0 as usize)
.any(|row_info| row_info.diff_status.is_some());
DisplayDiffHunk::Unfolded { DisplayDiffHunk::Unfolded {
status: hunk.status(), status: hunk.status(),
diff_base_byte_range: hunk.diff_base_byte_range, diff_base_byte_range: hunk.diff_base_byte_range,
@ -1576,6 +1582,7 @@ impl EditorElement {
hunk.buffer_id, hunk.buffer_id,
hunk.buffer_range, hunk.buffer_range,
), ),
contains_expanded,
} }
}; };
@ -4341,7 +4348,7 @@ impl EditorElement {
window.paint_quad(fill(Bounds { origin, size }, color)); window.paint_quad(fill(Bounds { origin, size }, color));
}; };
let mut current_paint: Option<(gpui::Background, Range<DisplayRow>)> = None; let mut current_paint: Option<(Hsla, Range<DisplayRow>)> = None;
for (&new_row, &new_background) in &layout.highlighted_rows { for (&new_row, &new_background) in &layout.highlighted_rows {
match &mut current_paint { match &mut current_paint {
Some((current_background, current_range)) => { Some((current_background, current_range)) => {
@ -4539,11 +4546,17 @@ impl EditorElement {
} }
} }
fn paint_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { fn paint_diff_hunk_gutter_indicators(
layout: &mut EditorLayout,
window: &mut Window,
cx: &mut App,
) {
if layout.display_hunks.is_empty() { if layout.display_hunks.is_empty() {
return; return;
} }
let corners = Corners::all(px(0.));
let line_height = layout.position_map.line_height; let line_height = layout.position_map.line_height;
window.paint_layer(layout.gutter_hitbox.bounds, |window| { window.paint_layer(layout.gutter_hitbox.bounds, |window| {
for (hunk, hitbox) in &layout.display_hunks { for (hunk, hitbox) in &layout.display_hunks {
@ -4557,36 +4570,41 @@ impl EditorElement {
); );
Some(( Some((
hunk_bounds, hunk_bounds,
cx.theme().status().modified, cx.theme().colors().version_control_modified.opacity(0.7),
Corners::all(px(0.)), corners,
&DiffHunkSecondaryStatus::None, &DiffHunkSecondaryStatus::None,
false,
)) ))
} }
DisplayDiffHunk::Unfolded { DisplayDiffHunk::Unfolded {
status, status,
display_row_range, display_row_range,
contains_expanded,
.. ..
} => hitbox.as_ref().map(|hunk_hitbox| match status { } => hitbox.as_ref().map(|hunk_hitbox| match status {
DiffHunkStatus::Added(secondary_status) => ( DiffHunkStatus::Added(secondary_status) => (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().status().created, cx.theme().colors().version_control_added.opacity(0.7),
Corners::all(px(0.)), corners,
secondary_status, secondary_status,
*contains_expanded,
), ),
DiffHunkStatus::Modified(secondary_status) => ( DiffHunkStatus::Modified(secondary_status) => (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().status().modified, cx.theme().colors().version_control_modified.opacity(0.7),
Corners::all(px(0.)), corners,
secondary_status, secondary_status,
*contains_expanded,
), ),
DiffHunkStatus::Removed(secondary_status) DiffHunkStatus::Removed(secondary_status)
if !display_row_range.is_empty() => if !display_row_range.is_empty() =>
{ {
( (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().status().deleted, cx.theme().colors().version_control_deleted.opacity(0.7),
Corners::all(px(0.)), corners,
secondary_status, secondary_status,
*contains_expanded,
) )
} }
DiffHunkStatus::Removed(secondary_status) => ( DiffHunkStatus::Removed(secondary_status) => (
@ -4597,23 +4615,34 @@ 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.opacity(0.7),
Corners::all(1. * line_height), Corners::all(1. * line_height),
secondary_status, secondary_status,
*contains_expanded,
), ),
}), }),
}; };
if let Some((hunk_bounds, mut background_color, corner_radii, secondary_status)) = if let Some((
hunk_to_paint hunk_bounds,
background_color,
corner_radii,
secondary_status,
contains_expanded,
)) = hunk_to_paint
{ {
if *secondary_status != DiffHunkSecondaryStatus::None { let background = if *secondary_status != DiffHunkSecondaryStatus::None
background_color.a *= 0.6; && contains_expanded
} {
pattern_slash(background_color, line_height.0 / 2.5)
} else {
solid_color(background_color)
};
window.paint_quad(quad( window.paint_quad(quad(
hunk_bounds, hunk_bounds,
corner_radii, corner_radii,
background_color, background,
Edges::default(), Edges::default(),
transparent_black(), transparent_black(),
)); ));
@ -4733,7 +4762,7 @@ impl EditorElement {
) )
}); });
if show_git_gutter { if show_git_gutter {
Self::paint_diff_hunks(layout, window, cx) Self::paint_diff_hunk_gutter_indicators(layout, window, cx)
} }
let highlight_width = 0.275 * layout.position_map.line_height; let highlight_width = 0.275 * layout.position_map.line_height;
@ -5292,9 +5321,15 @@ impl EditorElement {
end_display_row.0 -= 1; end_display_row.0 -= 1;
} }
let color = match &hunk.status() { let color = match &hunk.status() {
DiffHunkStatus::Added(_) => theme.status().created, DiffHunkStatus::Added(_) => {
DiffHunkStatus::Modified(_) => theme.status().modified, theme.colors().version_control_added
DiffHunkStatus::Removed(_) => theme.status().deleted, }
DiffHunkStatus::Modified(_) => {
theme.colors().version_control_modified
}
DiffHunkStatus::Removed(_) => {
theme.colors().version_control_deleted
}
}; };
ColoredRange { ColoredRange {
start: start_display_row, start: start_display_row,
@ -6875,39 +6910,17 @@ 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 background = match row_info.diff_status {
Some(DiffHunkStatus::Added(secondary_status)) => { Some(DiffHunkStatus::Added(_)) => {
let color = style.status.created_background; cx.theme().colors().version_control_added_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)) => { Some(DiffHunkStatus::Removed(_)) => {
let color = style.status.deleted_background; cx.theme().colors().version_control_deleted_background
match secondary_status {
DiffHunkSecondaryStatus::HasSecondaryHunk
| DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
if distinguish_unstaged_hunks =>
{
pattern_slash(color, line_height.0 / 4.0)
}
_ => color.into(),
}
} }
_ => continue, _ => continue,
}; };
@ -7755,7 +7768,7 @@ pub struct EditorLayout {
indent_guides: Option<Vec<IndentGuideLayout>>, indent_guides: Option<Vec<IndentGuideLayout>>,
visible_display_row_range: Range<DisplayRow>, visible_display_row_range: Range<DisplayRow>,
active_rows: BTreeMap<DisplayRow, bool>, active_rows: BTreeMap<DisplayRow, bool>,
highlighted_rows: BTreeMap<DisplayRow, gpui::Background>, highlighted_rows: BTreeMap<DisplayRow, Hsla>,
line_elements: SmallVec<[AnyElement; 1]>, line_elements: SmallVec<[AnyElement; 1]>,
line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>, line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>, display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,

View file

@ -607,6 +607,25 @@ impl Default for Background {
} }
} }
impl Background {
/// Gets the color of the background if there is one.
pub fn color(&self) -> Option<Hsla> {
match self.tag {
BackgroundTag::Solid => Some(self.solid),
BackgroundTag::LinearGradient => None,
BackgroundTag::PatternSlash => Some(self.solid),
}
}
}
/// Creates a background with a solid color
pub fn solid_color(color: impl Into<Hsla>) -> Background {
Background {
solid: color.into(),
..Default::default()
}
}
/// Creates a hash pattern background /// Creates a hash pattern background
pub fn pattern_slash(color: Hsla, thickness: f32) -> Background { pub fn pattern_slash(color: Hsla, thickness: f32) -> Background {
Background { Background {

View file

@ -136,11 +136,11 @@ impl ThemeColors {
terminal_ansi_dim_white: neutral().light().step_11(), terminal_ansi_dim_white: neutral().light().step_11(),
link_text_hover: orange().light().step_10(), link_text_hover: orange().light().step_10(),
version_control_added: ADDED_COLOR, version_control_added: ADDED_COLOR,
version_control_added_background: ADDED_COLOR.opacity(0.1), version_control_added_background: ADDED_COLOR.opacity(0.08),
version_control_deleted: REMOVED_COLOR, version_control_deleted: REMOVED_COLOR,
version_control_deleted_background: REMOVED_COLOR.opacity(0.1), version_control_deleted_background: REMOVED_COLOR.opacity(0.08),
version_control_modified: MODIFIED_COLOR, version_control_modified: MODIFIED_COLOR,
version_control_modified_background: MODIFIED_COLOR.opacity(0.1), version_control_modified_background: MODIFIED_COLOR.opacity(0.08),
version_control_renamed: MODIFIED_COLOR, version_control_renamed: MODIFIED_COLOR,
version_control_conflict: orange().light().step_12(), version_control_conflict: orange().light().step_12(),
version_control_conflict_background: orange().light().step_12().opacity(0.1), version_control_conflict_background: orange().light().step_12().opacity(0.1),