diff --git a/assets/settings/default.json b/assets/settings/default.json index 8e489dd0b5e857f1c85c531ee49de86ea460eb13..f3f3b9abcfcea7c27ebd812e41c622a169e01996 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -837,7 +837,15 @@ // // The minimum column number to show the inline blame information at // "min_column": 0 - } + }, + // How git hunks are displayed visually in the editor. + // This setting can take two values: + // + // 1. Show unstaged hunks with a transparent background (default): + // "hunk_style": "transparent" + // 2. Show unstaged hunks with a pattern background: + // "hunk_style": "pattern" + "hunk_style": "transparent" }, // Configuration for how direnv configuration should be loaded. May take 2 values: // 1. Load direnv configuration using `direnv export json` directly. @@ -851,15 +859,7 @@ // Any addition to this list will be merged with the default list. // Globs are matched relative to the worktree root, // except when starting with a slash (/) or equivalent in Windows. - "disabled_globs": [ - "**/.env*", - "**/*.pem", - "**/*.key", - "**/*.cert", - "**/*.crt", - "**/.dev.vars", - "**/secrets.yml" - ], + "disabled_globs": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/.dev.vars", "**/secrets.yml"], // When to show edit predictions previews in buffer. // This setting takes two possible values: // 1. Display predictions inline when there are no language server completions available. diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4cf2c7a75d5de7b64062e7482de218f6f307d397..b5f4ebaaf1b998c18210c2a887027a29e589cf43 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -32,14 +32,15 @@ use collections::{BTreeMap, HashMap, HashSet}; use file_icons::FileIcons; use git::{blame::BlameEntry, status::FileStatus, Oid}; use gpui::{ - 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, + anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash, + point, px, quad, relative, size, solid_background, 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::{ @@ -54,7 +55,7 @@ use multi_buffer::{ Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow, RowInfo, }; -use project::project_settings::{self, GitGutterSetting, ProjectSettings}; +use project::project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings}; use settings::Settings; use smallvec::{smallvec, SmallVec}; use std::{ @@ -4346,7 +4347,7 @@ impl EditorElement { } } - fn paint_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { + fn paint_gutter_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { let is_light = cx.theme().appearance().is_light(); if layout.display_hunks.is_empty() { @@ -4416,10 +4417,19 @@ impl EditorElement { background_color = background_color.opacity(if is_light { 0.2 } else { 0.32 }); } + + // Flatten the background color with the editor color to prevent + // elements below transparent hunks from showing through + let flattened_background_color = cx + .theme() + .colors() + .editor_background + .blend(background_color); + window.paint_quad(quad( hunk_bounds, corner_radii, - background_color, + flattened_background_color, Edges::default(), transparent_black(), )); @@ -4547,7 +4557,7 @@ impl EditorElement { ) }); if show_git_gutter { - Self::paint_diff_hunks(layout, window, cx) + Self::paint_gutter_diff_hunks(layout, window, cx) } let highlight_width = 0.275 * layout.position_map.line_height; @@ -6711,15 +6721,16 @@ impl Element for EditorElement { .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx)); let is_light = cx.theme().appearance().is_light(); + let use_pattern = ProjectSettings::get_global(cx) + .git + .hunk_style + .map_or(false, |style| matches!(style, GitHunkStyleSetting::Pattern)); for (ix, row_info) in row_infos.iter().enumerate() { let Some(diff_status) = row_info.diff_status else { continue; }; - let staged_opacity = if is_light { 0.14 } else { 0.10 }; - let unstaged_opacity = 0.04; - let background_color = match diff_status.kind { DiffHunkStatusKind::Added => cx.theme().colors().version_control_added, DiffHunkStatusKind::Deleted => { @@ -6730,15 +6741,34 @@ impl Element for EditorElement { continue; } }; - let background_color = if diff_status.has_secondary_hunk() { - background_color.opacity(unstaged_opacity) + + let unstaged = diff_status.has_secondary_hunk(); + let hunk_opacity = if is_light { 0.16 } else { 0.12 }; + + let staged_background = + solid_background(background_color.opacity(hunk_opacity)); + let unstaged_background = if use_pattern { + pattern_slash( + background_color.opacity(hunk_opacity), + window.rem_size().0 * 1.125, // ~18 by default + ) + } else { + solid_background(background_color.opacity(if is_light { + 0.08 + } else { + 0.04 + })) + }; + + let background = if unstaged { + unstaged_background } else { - background_color.opacity(staged_opacity) + staged_background }; highlighted_rows .entry(start_row + DisplayRow(ix as u32)) - .or_insert(background_color.into()); + .or_insert(background); } let highlighted_ranges = self.editor.read(cx).background_highlights_in_range( diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index ec9bfa451b92dc2f03e80a204609969899c12f89..25e92b0dbcfc01394c4e245ef8f187121340eaf9 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -670,6 +670,14 @@ pub fn pattern_slash(color: Hsla, thickness: f32) -> Background { } } +/// Creates a solid background color. +pub fn solid_background(color: impl Into) -> Background { + Background { + solid: color.into(), + ..Default::default() + } +} + /// Creates a LinearGradient background color. /// /// The gradient line's angle of direction. A value of `0.` is equivalent to to top; increasing values rotate clockwise from there. diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index f884ef4780c3a5eccd80ef1cedba1b0cf2ca1f45..c5425084a968d2ce8a15febec47dd5afaee6727c 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -168,6 +168,10 @@ pub struct GitSettings { /// /// Default: on pub inline_blame: Option, + /// How hunks are displayed visually in the editor. + /// + /// Default: transparent + pub hunk_style: Option, } impl GitSettings { @@ -200,6 +204,16 @@ impl GitSettings { } } +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitHunkStyleSetting { + /// Show unstaged hunks with a transparent background + #[default] + Transparent, + /// Show unstaged hunks with a pattern background + Pattern, +} + #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum GitGutterSetting {