Change the default staging and unstaging state display (#26299)

This adds a setting for the "border" hunk display mode, as discussed,
and makes it the default.

Here's how it looks in light mode:

<img width="1512" alt="Screenshot 2025-03-07 at 11 39 25 AM"
src="https://github.com/user-attachments/assets/a934faa3-ec69-47e1-ad46-535e48b98e9f"
/>

And dark mode: 

<img width="1511" alt="Screenshot 2025-03-07 at 11 39 56 AM"
src="https://github.com/user-attachments/assets/43c9afd1-22bb-4bd8-96ce-82702a6cbc80"
/>


Release Notes:

- Git Beta: Adjusted the default hunk styling for staged and unstaged
changes

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Nate <nate@zed.dev>
This commit is contained in:
Mikayla Maki 2025-03-07 11:56:24 -08:00 committed by GitHub
parent 05d3ee8555
commit ec5e7a2653
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 149 additions and 38 deletions

View file

@ -845,7 +845,7 @@
// "hunk_style": "transparent" // "hunk_style": "transparent"
// 2. Show unstaged hunks with a pattern background: // 2. Show unstaged hunks with a pattern background:
// "hunk_style": "pattern" // "hunk_style": "pattern"
"hunk_style": "transparent" "hunk_style": "staged_border"
}, },
// Configuration for how direnv configuration should be loaded. May take 2 values: // Configuration for how direnv configuration should be loaded. May take 2 values:
// 1. Load direnv configuration using `direnv export json` directly. // 1. Load direnv configuration using `direnv export json` directly.

View file

@ -14882,14 +14882,14 @@ impl Editor {
&self, &self,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> BTreeMap<DisplayRow, Background> { ) -> BTreeMap<DisplayRow, LineHighlight> {
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, LineHighlight>::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);
@ -18426,3 +18426,27 @@ impl Render for MissingEditPredictionKeybindingTooltip {
}) })
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LineHighlight {
pub background: Background,
pub border: Option<gpui::Hsla>,
}
impl From<Hsla> for LineHighlight {
fn from(hsla: Hsla) -> Self {
Self {
background: hsla.into(),
border: None,
}
}
}
impl From<Background> for LineHighlight {
fn from(background: Background) -> Self {
Self {
background,
border: None,
}
}
}

View file

@ -20,10 +20,10 @@ use crate::{
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
GoToPreviousHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, GoToPreviousHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp,
PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight,
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR, Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS,
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::{DiffHunkStatus, DiffHunkStatusKind}; use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
@ -4132,46 +4132,74 @@ impl EditorElement {
} }
} }
let mut paint_highlight = let mut paint_highlight = |highlight_row_start: DisplayRow,
|highlight_row_start: DisplayRow, highlight_row_end: DisplayRow, color| { highlight_row_end: DisplayRow,
let origin = point( highlight: crate::LineHighlight,
layout.hitbox.origin.x, edges| {
layout.hitbox.origin.y let origin = point(
+ (highlight_row_start.as_f32() - scroll_top) layout.hitbox.origin.x,
* layout.position_map.line_height, layout.hitbox.origin.y
); + (highlight_row_start.as_f32() - scroll_top)
let size = size( * layout.position_map.line_height,
layout.hitbox.size.width, );
layout.position_map.line_height let size = size(
* highlight_row_end.next_row().minus(highlight_row_start) as f32, layout.hitbox.size.width,
); layout.position_map.line_height
window.paint_quad(fill(Bounds { origin, size }, color)); * highlight_row_end.next_row().minus(highlight_row_start) as f32,
}; );
let mut quad = fill(Bounds { origin, size }, highlight.background);
if let Some(border_color) = highlight.border {
quad.border_color = border_color;
quad.border_widths = edges
}
window.paint_quad(quad);
};
let mut current_paint: Option<(gpui::Background, Range<DisplayRow>)> = None; let mut current_paint: Option<(LineHighlight, Range<DisplayRow>, Edges<Pixels>)> =
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, mut edges)) => {
let current_background = *current_background; let current_background = *current_background;
let new_range_started = current_background != new_background let new_range_started = current_background != new_background
|| current_range.end.next_row() != new_row; || current_range.end.next_row() != new_row;
if new_range_started { if new_range_started {
if current_range.end.next_row() == new_row {
edges.bottom = px(0.);
};
paint_highlight( paint_highlight(
current_range.start, current_range.start,
current_range.end, current_range.end,
current_background, current_background,
edges,
); );
current_paint = Some((new_background, new_row..new_row)); let edges = Edges {
top: if current_range.end.next_row() != new_row {
px(1.)
} else {
px(0.)
},
bottom: px(1.),
..Default::default()
};
current_paint = Some((new_background, new_row..new_row, edges));
continue; continue;
} else { } else {
current_range.end = current_range.end.next_row(); current_range.end = current_range.end.next_row();
} }
} }
None => current_paint = Some((new_background, new_row..new_row)), None => {
let edges = Edges {
top: px(1.),
bottom: px(1.),
..Default::default()
};
current_paint = Some((new_background, new_row..new_row, edges))
}
}; };
} }
if let Some((color, range)) = current_paint { if let Some((color, range, edges)) = current_paint {
paint_highlight(range.start, range.end, color); paint_highlight(range.start, range.end, color, edges);
} }
let scroll_left = let scroll_left =
@ -4431,6 +4459,9 @@ impl EditorElement {
background_color.opacity(if is_light { 0.2 } else { 0.32 }); background_color.opacity(if is_light { 0.2 } else { 0.32 });
} }
} }
GitHunkStyleSetting::StagedBorder | GitHunkStyleSetting::Border => {
// Don't change the background color
}
} }
// Flatten the background color with the editor color to prevent // Flatten the background color with the editor color to prevent
@ -6775,12 +6806,15 @@ impl Element for EditorElement {
let hunk_opacity = if is_light { 0.16 } else { 0.12 }; let hunk_opacity = if is_light { 0.16 } else { 0.12 };
let slash_width = line_height.0 / 1.5; // ~16 by default let slash_width = line_height.0 / 1.5; // ~16 by default
let staged_background = match hunk_style { let staged_highlight: LineHighlight = match hunk_style {
GitHunkStyleSetting::Transparent | GitHunkStyleSetting::Pattern => { GitHunkStyleSetting::Transparent
solid_background(background_color.opacity(hunk_opacity)) | GitHunkStyleSetting::Pattern
| GitHunkStyleSetting::Border => {
solid_background(background_color.opacity(hunk_opacity)).into()
} }
GitHunkStyleSetting::StagedPattern => { GitHunkStyleSetting::StagedPattern => {
pattern_slash(background_color.opacity(hunk_opacity), slash_width) pattern_slash(background_color.opacity(hunk_opacity), slash_width)
.into()
} }
GitHunkStyleSetting::StagedTransparent => { GitHunkStyleSetting::StagedTransparent => {
solid_background(background_color.opacity(if is_light { solid_background(background_color.opacity(if is_light {
@ -6788,30 +6822,56 @@ impl Element for EditorElement {
} else { } else {
0.04 0.04
})) }))
.into()
} }
GitHunkStyleSetting::StagedBorder => LineHighlight {
background: (background_color.opacity(if is_light {
0.08
} else {
0.06
}))
.into(),
border: Some(if is_light {
background_color.opacity(0.48)
} else {
background_color.opacity(0.36)
}),
},
}; };
let unstaged_background = match hunk_style { let unstaged_highlight = match hunk_style {
GitHunkStyleSetting::Transparent => { GitHunkStyleSetting::Transparent => {
solid_background(background_color.opacity(if is_light { solid_background(background_color.opacity(if is_light {
0.08 0.08
} else { } else {
0.04 0.04
})) }))
.into()
} }
GitHunkStyleSetting::Pattern => { GitHunkStyleSetting::Pattern => {
pattern_slash(background_color.opacity(hunk_opacity), slash_width) pattern_slash(background_color.opacity(hunk_opacity), slash_width)
.into()
} }
GitHunkStyleSetting::Border => LineHighlight {
background: (background_color.opacity(if is_light {
0.08
} else {
0.02
}))
.into(),
border: Some(background_color.opacity(0.5)),
},
GitHunkStyleSetting::StagedPattern GitHunkStyleSetting::StagedPattern
| GitHunkStyleSetting::StagedTransparent => { | GitHunkStyleSetting::StagedTransparent
solid_background(background_color.opacity(hunk_opacity)) | GitHunkStyleSetting::StagedBorder => {
solid_background(background_color.opacity(hunk_opacity)).into()
} }
}; };
let background = if unstaged { let background = if unstaged {
unstaged_background unstaged_highlight
} else { } else {
staged_background staged_highlight
}; };
highlighted_rows highlighted_rows
@ -7660,7 +7720,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, LineHighlight>,
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

@ -634,7 +634,7 @@ impl Display for ColorSpace {
} }
/// A background color, which can be either a solid color or a linear gradient. /// A background color, which can be either a solid color or a linear gradient.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
#[repr(C)] #[repr(C)]
pub struct Background { pub struct Background {
pub(crate) tag: BackgroundTag, pub(crate) tag: BackgroundTag,
@ -646,6 +646,28 @@ pub struct Background {
pad: u32, pad: u32,
} }
impl std::fmt::Debug for Background {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.tag {
BackgroundTag::Solid => write!(f, "Solid({:?})", self.solid),
BackgroundTag::LinearGradient => {
write!(
f,
"LinearGradient({}, {:?}, {:?})",
self.gradient_angle_or_pattern_height, self.colors[0], self.colors[1]
)
}
BackgroundTag::PatternSlash => {
write!(
f,
"PatternSlash({:?}, {})",
self.solid, self.gradient_angle_or_pattern_height
)
}
}
}
}
impl Eq for Background {} impl Eq for Background {}
impl Default for Background { impl Default for Background {
fn default() -> Self { fn default() -> Self {

View file

@ -212,10 +212,15 @@ pub enum GitHunkStyleSetting {
Transparent, Transparent,
/// Show unstaged hunks with a pattern background /// Show unstaged hunks with a pattern background
Pattern, Pattern,
/// Show unstaged hunks with a border background
Border,
/// Show staged hunks with a pattern background /// Show staged hunks with a pattern background
StagedPattern, StagedPattern,
/// Show staged hunks with a pattern background /// Show staged hunks with a pattern background
StagedTransparent, StagedTransparent,
/// Show staged hunks with a pattern background
StagedBorder,
} }
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]