This PR makes editor diff hunk colors configurable from themes, instead of hardcoded values. It introduces 6 new optional theme keys: - `editor.diff_hunk.added.background` - `editor.diff_hunk.added.hollow_background` - `editor.diff_hunk.added.hollow_border` - `editor.diff_hunk.deleted.background` - `editor.diff_hunk.deleted.hollow_background` - `editor.diff_hunk.deleted.hollow_border` When a theme omits these keys, each color falls back to the existing version-control color with the previous hardcoded opacity values: - Light defaults: - background_opacity = 0.16 - hollow_background_opacity = 0.08 - hollow_border_opacity = 0.48 - Dark defaults: - background_opacity = 0.12 - hollow_background_opacity = 0.06 - hollow_border_opacity = 0.36 There is an existing feature request https://github.com/zed-industries/zed/discussions/51667 ## Screenshots I used `Modus Themes` (Modus Vivendi) since these themes provide highly accessible themes. Original version: <img width="3248" height="2120" alt="CleanShot 2026-03-17 at 20 26 41@2x" src="https://github.com/user-attachments/assets/730be802-835d-436e-a1fc-4c60dcb5fce8" /> This version: <img width="3248" height="2120" alt="CleanShot 2026-03-17 at 20 23 09@2x" src="https://github.com/user-attachments/assets/785c86d3-147e-437f-9624-9576bc201551" /> Before you mark this PR as ready for review, make sure that you have: - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - Added theme keys for configuring editor diff hunk colors. --------- Co-authored-by: MrSubidubi <finn@zed.dev>
1107 lines
40 KiB
Rust
1107 lines
40 KiB
Rust
#![allow(missing_docs)]
|
|
|
|
use gpui::{HighlightStyle, Hsla};
|
|
use palette::FromColor;
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use settings::IntoGpui;
|
|
pub use settings::{
|
|
FontStyleContent, HighlightStyleContent, StatusColorsContent, ThemeColorsContent,
|
|
ThemeStyleContent,
|
|
};
|
|
pub use settings::{FontWeightContent, WindowBackgroundContent};
|
|
|
|
use theme::{StatusColorsRefinement, ThemeColorsRefinement};
|
|
|
|
const LIGHT_DIFF_HUNK_FILLED_OPACITY: f32 = 0.16;
|
|
const LIGHT_DIFF_HUNK_HOLLOW_BACKGROUND_OPACITY: f32 = 0.08;
|
|
const LIGHT_DIFF_HUNK_HOLLOW_BORDER_OPACITY: f32 = 0.48;
|
|
const DARK_DIFF_HUNK_FILLED_OPACITY: f32 = 0.12;
|
|
const DARK_DIFF_HUNK_HOLLOW_BACKGROUND_OPACITY: f32 = 0.06;
|
|
const DARK_DIFF_HUNK_HOLLOW_BORDER_OPACITY: f32 = 0.36;
|
|
|
|
/// The content of a serialized theme family.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
pub struct ThemeFamilyContent {
|
|
pub name: String,
|
|
pub author: String,
|
|
pub themes: Vec<ThemeContent>,
|
|
}
|
|
|
|
/// The content of a serialized theme.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
pub struct ThemeContent {
|
|
pub name: String,
|
|
pub appearance: theme::AppearanceContent,
|
|
pub style: settings::ThemeStyleContent,
|
|
}
|
|
|
|
/// Returns the syntax style overrides in the [`ThemeContent`].
|
|
pub fn syntax_overrides(this: &settings::ThemeStyleContent) -> Vec<(String, HighlightStyle)> {
|
|
this.syntax
|
|
.iter()
|
|
.map(|(key, style)| {
|
|
(
|
|
key.clone(),
|
|
HighlightStyle {
|
|
color: style
|
|
.color
|
|
.as_ref()
|
|
.and_then(|color| theme::try_parse_color(color).ok()),
|
|
background_color: style
|
|
.background_color
|
|
.as_ref()
|
|
.and_then(|color| theme::try_parse_color(color).ok()),
|
|
font_style: style.font_style.map(|s| s.into_gpui()),
|
|
font_weight: style.font_weight.map(|w| w.into_gpui()),
|
|
..Default::default()
|
|
},
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn status_colors_refinement(colors: &settings::StatusColorsContent) -> StatusColorsRefinement {
|
|
StatusColorsRefinement {
|
|
conflict: colors
|
|
.conflict
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
conflict_background: colors
|
|
.conflict_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
conflict_border: colors
|
|
.conflict_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
created: colors
|
|
.created
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
created_background: colors
|
|
.created_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
created_border: colors
|
|
.created_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
deleted: colors
|
|
.deleted
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
deleted_background: colors
|
|
.deleted_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
deleted_border: colors
|
|
.deleted_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
error: colors
|
|
.error
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
error_background: colors
|
|
.error_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
error_border: colors
|
|
.error_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
hidden: colors
|
|
.hidden
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
hidden_background: colors
|
|
.hidden_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
hidden_border: colors
|
|
.hidden_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
hint: colors
|
|
.hint
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
hint_background: colors
|
|
.hint_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
hint_border: colors
|
|
.hint_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ignored: colors
|
|
.ignored
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ignored_background: colors
|
|
.ignored_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ignored_border: colors
|
|
.ignored_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
info: colors
|
|
.info
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
info_background: colors
|
|
.info_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
info_border: colors
|
|
.info_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
modified: colors
|
|
.modified
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
modified_background: colors
|
|
.modified_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
modified_border: colors
|
|
.modified_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
predictive: colors
|
|
.predictive
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
predictive_background: colors
|
|
.predictive_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
predictive_border: colors
|
|
.predictive_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
renamed: colors
|
|
.renamed
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
renamed_background: colors
|
|
.renamed_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
renamed_border: colors
|
|
.renamed_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
success: colors
|
|
.success
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
success_background: colors
|
|
.success_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
success_border: colors
|
|
.success_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
unreachable: colors
|
|
.unreachable
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
unreachable_background: colors
|
|
.unreachable_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
unreachable_border: colors
|
|
.unreachable_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
warning: colors
|
|
.warning
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
warning_background: colors
|
|
.warning_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
warning_border: colors
|
|
.warning_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
}
|
|
}
|
|
|
|
pub fn theme_colors_refinement(
|
|
this: &settings::ThemeColorsContent,
|
|
status_colors: &StatusColorsRefinement,
|
|
is_light: bool,
|
|
) -> ThemeColorsRefinement {
|
|
let border = this
|
|
.border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok());
|
|
let editor_document_highlight_read_background = this
|
|
.editor_document_highlight_read_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok());
|
|
let scrollbar_thumb_background = this
|
|
.scrollbar_thumb_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or_else(|| {
|
|
this.deprecated_scrollbar_thumb_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
});
|
|
let scrollbar_thumb_hover_background = this
|
|
.scrollbar_thumb_hover_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok());
|
|
let scrollbar_thumb_active_background = this
|
|
.scrollbar_thumb_active_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(scrollbar_thumb_background);
|
|
let scrollbar_thumb_border = this
|
|
.scrollbar_thumb_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok());
|
|
let element_hover = this
|
|
.element_hover
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok());
|
|
let panel_background = this
|
|
.panel_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok());
|
|
let search_match_background = this
|
|
.search_match_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok());
|
|
let search_active_match_background = this
|
|
.search_active_match_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(search_match_background);
|
|
let version_control_added = this
|
|
.version_control_added
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(status_colors.created);
|
|
let version_control_deleted = this
|
|
.version_control_deleted
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(status_colors.deleted);
|
|
let (hunk_fill, hunk_hollow_bg, hunk_hollow_border) = if is_light {
|
|
(
|
|
LIGHT_DIFF_HUNK_FILLED_OPACITY,
|
|
LIGHT_DIFF_HUNK_HOLLOW_BACKGROUND_OPACITY,
|
|
LIGHT_DIFF_HUNK_HOLLOW_BORDER_OPACITY,
|
|
)
|
|
} else {
|
|
(
|
|
DARK_DIFF_HUNK_FILLED_OPACITY,
|
|
DARK_DIFF_HUNK_HOLLOW_BACKGROUND_OPACITY,
|
|
DARK_DIFF_HUNK_HOLLOW_BORDER_OPACITY,
|
|
)
|
|
};
|
|
ThemeColorsRefinement {
|
|
border,
|
|
border_variant: this
|
|
.border_variant
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
border_focused: this
|
|
.border_focused
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
border_selected: this
|
|
.border_selected
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
border_transparent: this
|
|
.border_transparent
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
border_disabled: this
|
|
.border_disabled
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
elevated_surface_background: this
|
|
.elevated_surface_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
surface_background: this
|
|
.surface_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
background: this
|
|
.background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
element_background: this
|
|
.element_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
element_hover,
|
|
element_active: this
|
|
.element_active
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
element_selected: this
|
|
.element_selected
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
element_disabled: this
|
|
.element_disabled
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
element_selection_background: this
|
|
.element_selection_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
drop_target_background: this
|
|
.drop_target_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
drop_target_border: this
|
|
.drop_target_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ghost_element_background: this
|
|
.ghost_element_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ghost_element_hover: this
|
|
.ghost_element_hover
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ghost_element_active: this
|
|
.ghost_element_active
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ghost_element_selected: this
|
|
.ghost_element_selected
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
ghost_element_disabled: this
|
|
.ghost_element_disabled
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
text: this
|
|
.text
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
text_muted: this
|
|
.text_muted
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
text_placeholder: this
|
|
.text_placeholder
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
text_disabled: this
|
|
.text_disabled
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
text_accent: this
|
|
.text_accent
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
icon: this
|
|
.icon
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
icon_muted: this
|
|
.icon_muted
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
icon_disabled: this
|
|
.icon_disabled
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
icon_placeholder: this
|
|
.icon_placeholder
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
icon_accent: this
|
|
.icon_accent
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
debugger_accent: this
|
|
.debugger_accent
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
status_bar_background: this
|
|
.status_bar_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
title_bar_background: this
|
|
.title_bar_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
title_bar_inactive_background: this
|
|
.title_bar_inactive_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
toolbar_background: this
|
|
.toolbar_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
tab_bar_background: this
|
|
.tab_bar_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
tab_inactive_background: this
|
|
.tab_inactive_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
tab_active_background: this
|
|
.tab_active_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
search_match_background,
|
|
search_active_match_background,
|
|
panel_background,
|
|
panel_focused_border: this
|
|
.panel_focused_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
panel_indent_guide: this
|
|
.panel_indent_guide
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
panel_indent_guide_hover: this
|
|
.panel_indent_guide_hover
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
panel_indent_guide_active: this
|
|
.panel_indent_guide_active
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
panel_overlay_background: this
|
|
.panel_overlay_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(panel_background.map(ensure_opaque)),
|
|
panel_overlay_hover: this
|
|
.panel_overlay_hover
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(panel_background
|
|
.zip(element_hover)
|
|
.map(|(panel_bg, hover_bg)| panel_bg.blend(hover_bg))
|
|
.map(ensure_opaque)),
|
|
pane_focused_border: this
|
|
.pane_focused_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
pane_group_border: this
|
|
.pane_group_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(border),
|
|
scrollbar_thumb_background,
|
|
scrollbar_thumb_hover_background,
|
|
scrollbar_thumb_active_background,
|
|
scrollbar_thumb_border,
|
|
scrollbar_track_background: this
|
|
.scrollbar_track_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
scrollbar_track_border: this
|
|
.scrollbar_track_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
minimap_thumb_background: this
|
|
.minimap_thumb_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(scrollbar_thumb_background.map(ensure_non_opaque)),
|
|
minimap_thumb_hover_background: this
|
|
.minimap_thumb_hover_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(scrollbar_thumb_hover_background.map(ensure_non_opaque)),
|
|
minimap_thumb_active_background: this
|
|
.minimap_thumb_active_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(scrollbar_thumb_active_background.map(ensure_non_opaque)),
|
|
minimap_thumb_border: this
|
|
.minimap_thumb_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(scrollbar_thumb_border),
|
|
editor_foreground: this
|
|
.editor_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_background: this
|
|
.editor_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_gutter_background: this
|
|
.editor_gutter_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_subheader_background: this
|
|
.editor_subheader_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_active_line_background: this
|
|
.editor_active_line_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_highlighted_line_background: this
|
|
.editor_highlighted_line_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_debugger_active_line_background: this
|
|
.editor_debugger_active_line_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_line_number: this
|
|
.editor_line_number
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_hover_line_number: this
|
|
.editor_hover_line_number
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_active_line_number: this
|
|
.editor_active_line_number
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_invisible: this
|
|
.editor_invisible
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_wrap_guide: this
|
|
.editor_wrap_guide
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_active_wrap_guide: this
|
|
.editor_active_wrap_guide
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_indent_guide: this
|
|
.editor_indent_guide
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_indent_guide_active: this
|
|
.editor_indent_guide_active
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_document_highlight_read_background,
|
|
editor_document_highlight_write_background: this
|
|
.editor_document_highlight_write_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
editor_document_highlight_bracket_background: this
|
|
.editor_document_highlight_bracket_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(editor_document_highlight_read_background),
|
|
editor_diff_hunk_added_background: this
|
|
.editor_diff_hunk_added_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or_else(|| version_control_added.map(|c| c.opacity(hunk_fill))),
|
|
editor_diff_hunk_added_hollow_background: this
|
|
.editor_diff_hunk_added_hollow_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or_else(|| version_control_added.map(|c| c.opacity(hunk_hollow_bg))),
|
|
editor_diff_hunk_added_hollow_border: this
|
|
.editor_diff_hunk_added_hollow_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or_else(|| version_control_added.map(|c| c.opacity(hunk_hollow_border))),
|
|
editor_diff_hunk_deleted_background: this
|
|
.editor_diff_hunk_deleted_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or_else(|| version_control_deleted.map(|c| c.opacity(hunk_fill))),
|
|
editor_diff_hunk_deleted_hollow_background: this
|
|
.editor_diff_hunk_deleted_hollow_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or_else(|| version_control_deleted.map(|c| c.opacity(hunk_hollow_bg))),
|
|
editor_diff_hunk_deleted_hollow_border: this
|
|
.editor_diff_hunk_deleted_hollow_border
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or_else(|| version_control_deleted.map(|c| c.opacity(hunk_hollow_border))),
|
|
terminal_background: this
|
|
.terminal_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_background: this
|
|
.terminal_ansi_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_foreground: this
|
|
.terminal_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_bright_foreground: this
|
|
.terminal_bright_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_dim_foreground: this
|
|
.terminal_dim_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_black: this
|
|
.terminal_ansi_black
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_black: this
|
|
.terminal_ansi_bright_black
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_black: this
|
|
.terminal_ansi_dim_black
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_red: this
|
|
.terminal_ansi_red
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_red: this
|
|
.terminal_ansi_bright_red
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_red: this
|
|
.terminal_ansi_dim_red
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_green: this
|
|
.terminal_ansi_green
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_green: this
|
|
.terminal_ansi_bright_green
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_green: this
|
|
.terminal_ansi_dim_green
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_yellow: this
|
|
.terminal_ansi_yellow
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_yellow: this
|
|
.terminal_ansi_bright_yellow
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_yellow: this
|
|
.terminal_ansi_dim_yellow
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_blue: this
|
|
.terminal_ansi_blue
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_blue: this
|
|
.terminal_ansi_bright_blue
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_blue: this
|
|
.terminal_ansi_dim_blue
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_magenta: this
|
|
.terminal_ansi_magenta
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_magenta: this
|
|
.terminal_ansi_bright_magenta
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_magenta: this
|
|
.terminal_ansi_dim_magenta
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_cyan: this
|
|
.terminal_ansi_cyan
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_cyan: this
|
|
.terminal_ansi_bright_cyan
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_cyan: this
|
|
.terminal_ansi_dim_cyan
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_white: this
|
|
.terminal_ansi_white
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_bright_white: this
|
|
.terminal_ansi_bright_white
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
terminal_ansi_dim_white: this
|
|
.terminal_ansi_dim_white
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
link_text_hover: this
|
|
.link_text_hover
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
version_control_added,
|
|
version_control_deleted,
|
|
version_control_modified: this
|
|
.version_control_modified
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(status_colors.modified),
|
|
version_control_renamed: this
|
|
.version_control_renamed
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(status_colors.modified),
|
|
version_control_conflict: this
|
|
.version_control_conflict
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(status_colors.ignored),
|
|
version_control_ignored: this
|
|
.version_control_ignored
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(status_colors.ignored),
|
|
version_control_word_added: this
|
|
.version_control_word_added
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
version_control_word_deleted: this
|
|
.version_control_word_deleted
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
#[allow(deprecated)]
|
|
version_control_conflict_marker_ours: this
|
|
.version_control_conflict_marker_ours
|
|
.as_ref()
|
|
.or(this.version_control_conflict_ours_background.as_ref())
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
#[allow(deprecated)]
|
|
version_control_conflict_marker_theirs: this
|
|
.version_control_conflict_marker_theirs
|
|
.as_ref()
|
|
.or(this.version_control_conflict_theirs_background.as_ref())
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_normal_background: this
|
|
.vim_normal_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_insert_background: this
|
|
.vim_insert_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_replace_background: this
|
|
.vim_replace_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_visual_background: this
|
|
.vim_visual_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_visual_line_background: this
|
|
.vim_visual_line_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_visual_block_background: this
|
|
.vim_visual_block_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_yank_background: this
|
|
.vim_yank_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(editor_document_highlight_read_background),
|
|
vim_helix_jump_label_foreground: this
|
|
.vim_helix_jump_label_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok())
|
|
.or(status_colors.error),
|
|
vim_helix_normal_background: this
|
|
.vim_helix_normal_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_helix_select_background: this
|
|
.vim_helix_select_background
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_normal_foreground: this
|
|
.vim_normal_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_insert_foreground: this
|
|
.vim_insert_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_replace_foreground: this
|
|
.vim_replace_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_visual_foreground: this
|
|
.vim_visual_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_visual_line_foreground: this
|
|
.vim_visual_line_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_visual_block_foreground: this
|
|
.vim_visual_block_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_helix_normal_foreground: this
|
|
.vim_helix_normal_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
vim_helix_select_foreground: this
|
|
.vim_helix_select_foreground
|
|
.as_ref()
|
|
.and_then(|color| try_parse_color(color).ok()),
|
|
}
|
|
}
|
|
|
|
fn ensure_non_opaque(color: Hsla) -> Hsla {
|
|
const MAXIMUM_OPACITY: f32 = 0.7;
|
|
if color.a <= MAXIMUM_OPACITY {
|
|
color
|
|
} else {
|
|
Hsla {
|
|
a: MAXIMUM_OPACITY,
|
|
..color
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ensure_opaque(color: Hsla) -> Hsla {
|
|
Hsla { a: 1.0, ..color }
|
|
}
|
|
|
|
fn try_parse_color(color: &str) -> anyhow::Result<Hsla> {
|
|
let rgba = gpui::Rgba::try_from(color)?;
|
|
let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
|
|
let hsla = palette::Hsla::from_color(rgba);
|
|
|
|
let hsla = gpui::hsla(
|
|
hsla.hue.into_positive_degrees() / 360.,
|
|
hsla.saturation,
|
|
hsla.lightness,
|
|
hsla.alpha,
|
|
);
|
|
|
|
Ok(hsla)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use theme::StatusColorsRefinement;
|
|
|
|
use super::{
|
|
StatusColorsContent, ThemeColorsContent, status_colors_refinement, theme_colors_refinement,
|
|
try_parse_color,
|
|
};
|
|
|
|
#[test]
|
|
fn explicit_diff_hunk_colors_take_precedence_over_fallbacks() {
|
|
let mut colors = ThemeColorsContent::default();
|
|
colors.editor_diff_hunk_added_background = Some("#112233".to_string());
|
|
colors.editor_diff_hunk_added_hollow_background = Some("#223344".to_string());
|
|
colors.editor_diff_hunk_added_hollow_border = Some("#334455".to_string());
|
|
colors.editor_diff_hunk_deleted_background = Some("#445566".to_string());
|
|
colors.editor_diff_hunk_deleted_hollow_background = Some("#556677".to_string());
|
|
colors.editor_diff_hunk_deleted_hollow_border = Some("#667788".to_string());
|
|
colors.version_control_added = Some("#00ff00".to_string());
|
|
colors.version_control_deleted = Some("#ff0000".to_string());
|
|
|
|
let refinement = theme_colors_refinement(
|
|
&colors,
|
|
&status_colors_refinement(&StatusColorsContent::default()),
|
|
true,
|
|
);
|
|
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_added_background,
|
|
Some(parse_color("#112233"))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_added_hollow_background,
|
|
Some(parse_color("#223344"))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_added_hollow_border,
|
|
Some(parse_color("#334455"))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_deleted_background,
|
|
Some(parse_color("#445566"))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_deleted_hollow_background,
|
|
Some(parse_color("#556677"))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_deleted_hollow_border,
|
|
Some(parse_color("#667788"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn diff_hunk_colors_fallback_to_version_control_colors() {
|
|
let mut colors = ThemeColorsContent::default();
|
|
colors.version_control_added = Some("#00ff00".to_string());
|
|
colors.version_control_deleted = Some("#ff0000".to_string());
|
|
|
|
let refinement = theme_colors_refinement(
|
|
&colors,
|
|
&status_colors_refinement(&StatusColorsContent::default()),
|
|
true,
|
|
);
|
|
|
|
let added = parse_color("#00ff00");
|
|
let deleted = parse_color("#ff0000");
|
|
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_added_background,
|
|
Some(added.opacity(0.16))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_added_hollow_background,
|
|
Some(added.opacity(0.08))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_added_hollow_border,
|
|
Some(added.opacity(0.48))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_deleted_background,
|
|
Some(deleted.opacity(0.16))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_deleted_hollow_background,
|
|
Some(deleted.opacity(0.08))
|
|
);
|
|
assert_eq!(
|
|
refinement.editor_diff_hunk_deleted_hollow_border,
|
|
Some(deleted.opacity(0.48))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn diff_hunk_opacity_fallbacks_use_correct_values_for_light_and_dark_themes() {
|
|
let mut colors = ThemeColorsContent::default();
|
|
colors.version_control_added = Some("#00ff00".to_string());
|
|
|
|
let light_refinement = theme_colors_refinement(
|
|
&colors,
|
|
&status_colors_refinement(&StatusColorsContent::default()),
|
|
true,
|
|
);
|
|
let dark_refinement = theme_colors_refinement(
|
|
&colors,
|
|
&status_colors_refinement(&StatusColorsContent::default()),
|
|
false,
|
|
);
|
|
|
|
let added = parse_color("#00ff00");
|
|
|
|
assert_eq!(
|
|
light_refinement.editor_diff_hunk_added_background,
|
|
Some(added.opacity(0.16))
|
|
);
|
|
assert_eq!(
|
|
light_refinement.editor_diff_hunk_added_hollow_background,
|
|
Some(added.opacity(0.08))
|
|
);
|
|
assert_eq!(
|
|
light_refinement.editor_diff_hunk_added_hollow_border,
|
|
Some(added.opacity(0.48))
|
|
);
|
|
|
|
assert_eq!(
|
|
dark_refinement.editor_diff_hunk_added_background,
|
|
Some(added.opacity(0.12))
|
|
);
|
|
assert_eq!(
|
|
dark_refinement.editor_diff_hunk_added_hollow_background,
|
|
Some(added.opacity(0.06))
|
|
);
|
|
assert_eq!(
|
|
dark_refinement.editor_diff_hunk_added_hollow_border,
|
|
Some(added.opacity(0.36))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn diff_hunk_fallbacks_are_absent_when_status_and_version_control_colors_are_missing() {
|
|
let refinement = theme_colors_refinement(
|
|
&ThemeColorsContent::default(),
|
|
&status_colors_refinement(&StatusColorsContent::default()),
|
|
true,
|
|
);
|
|
|
|
assert_eq!(refinement.editor_diff_hunk_added_background, None);
|
|
assert_eq!(refinement.editor_diff_hunk_added_hollow_background, None);
|
|
assert_eq!(refinement.editor_diff_hunk_added_hollow_border, None);
|
|
assert_eq!(refinement.editor_diff_hunk_deleted_background, None);
|
|
assert_eq!(refinement.editor_diff_hunk_deleted_hollow_background, None);
|
|
assert_eq!(refinement.editor_diff_hunk_deleted_hollow_border, None);
|
|
}
|
|
|
|
fn parse_color(color: &str) -> gpui::Hsla {
|
|
match try_parse_color(color) {
|
|
Ok(color) => color,
|
|
Err(error) => panic!("failed to parse color {color}: {error}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn helix_jump_label_color_uses_theme_color_or_status_error() {
|
|
let status_error = try_parse_color("#e63333").expect("valid color");
|
|
let override_color = try_parse_color("#00ff00").expect("valid color");
|
|
let status_colors = StatusColorsRefinement {
|
|
error: Some(status_error),
|
|
..Default::default()
|
|
};
|
|
|
|
let fallback_refinement = theme_colors_refinement(
|
|
&ThemeColorsContent::default(),
|
|
&status_colors,
|
|
Default::default(),
|
|
);
|
|
|
|
assert_eq!(
|
|
fallback_refinement.vim_helix_jump_label_foreground,
|
|
Some(status_error)
|
|
);
|
|
|
|
let override_refinement = theme_colors_refinement(
|
|
&ThemeColorsContent {
|
|
vim_helix_jump_label_foreground: Some("#00ff00".to_string()),
|
|
..Default::default()
|
|
},
|
|
&status_colors,
|
|
Default::default(),
|
|
);
|
|
|
|
assert_eq!(
|
|
override_refinement.vim_helix_jump_label_foreground,
|
|
Some(override_color)
|
|
);
|
|
}
|
|
}
|