From 7432e947bc60204bc0f8fcfb13ab06f190369451 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Fri, 27 Jun 2025 10:46:04 -0500 Subject: [PATCH] Add `element_selection_background` highlight to theme (#32388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #32354 The issue is that we render selections over the text in the agent panel, but under the text in editor, so themes that have no alpha for the selection background color (defaults to 0xff) will just occlude the selected region. Making the selection render under the text in markdown would be a significant (and complicated) refactor, as selections can cross element boundaries (i.e. spanning code block and a header after the code block). The solution is to add a new highlight to themes `element_selection_background` that defaults to the local players selection background with an alpha of 0.25 (roughly equal to 0x3D which is the alpha we use for selection backgrounds in default themes) if the alpha of the local players selection is 1.0. The idea here is to give theme authors more control over how the selections look outside of editor, as in the agent panel specifically, the background color is different, so while an alpha of 0.25 looks acceptable, a different color would likely be better. CC: @iamnbutler. Would appreciate your thoughts on this. > Note: Before and after using Everforest theme | Before | After | |-------| -----| | Screenshot 2025-06-09 at 5 23 10 PM | Screenshot 2025-06-09 at 5 25 03 PM | Clearly, the selection in the after doesn't look _that_ great, but it is better than the before, and this PR makes the color of the selection configurable by the theme so that this theme author could make it a lighter color for better contrast. Release Notes: - agent panel: Fixed an issue with some themes where selections inside the agent panel would occlude the selected text completely Co-authored-by: Antonio --- crates/agent_ui/src/active_thread.rs | 4 ++-- .../configure_context_server_modal.rs | 2 +- crates/assistant_tools/src/edit_file_tool.rs | 2 +- crates/assistant_tools/src/terminal_tool.rs | 2 +- crates/editor/src/hover_popover.rs | 4 ++-- crates/markdown/examples/markdown.rs | 6 +----- crates/markdown/examples/markdown_as_child.rs | 6 +----- crates/markdown/src/markdown.rs | 1 - crates/recent_projects/src/ssh_connections.rs | 2 +- crates/theme/src/default_colors.rs | 2 ++ crates/theme/src/fallback_themes.rs | 20 +++++++++++++++++-- crates/theme/src/schema.rs | 8 ++++++++ crates/theme/src/styles/colors.rs | 2 ++ crates/theme/src/theme.rs | 15 ++++++++------ crates/ui_prompt/src/ui_prompt.rs | 5 ++++- 15 files changed, 53 insertions(+), 28 deletions(-) diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs index 4da959d36e..5f9dfc7ab2 100644 --- a/crates/agent_ui/src/active_thread.rs +++ b/crates/agent_ui/src/active_thread.rs @@ -204,7 +204,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle MarkdownStyle { base_text_style: text_style.clone(), syntax: cx.theme().syntax().clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: true, table_overflow_x_scroll: true, heading_level_styles: Some(HeadingLevelStyles { @@ -301,7 +301,7 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle { MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: false, code_block: StyleRefinement { margin: EdgesRefinement::default(), diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index 30fad51cfc..af08eaf935 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -748,7 +748,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle MarkdownStyle { base_text_style: text_style.clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: colors.element_selection_background, link: TextStyleRefinement { background_color: Some(colors.editor_foreground.opacity(0.025)), underline: Some(UnderlineStyle { diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index fde697b00e..fcf8285692 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -1065,7 +1065,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle { MarkdownStyle { base_text_style: text_style.clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() } } diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index 5ec0ce7b8f..2c582a5310 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -691,7 +691,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle { MarkdownStyle { base_text_style: text_style.clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index b174a3ba62..9e6fc356ea 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -648,7 +648,7 @@ pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { cx.theme().players().local().selection }, + selection_background_color: cx.theme().colors().element_selection_background, heading: StyleRefinement::default() .font_weight(FontWeight::BOLD) .text_base() @@ -697,7 +697,7 @@ pub fn diagnostics_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { cx.theme().players().local().selection }, + selection_background_color: cx.theme().colors().element_selection_background, height_is_multiple_of_line_height: true, heading: StyleRefinement::default() .font_weight(FontWeight::BOLD) diff --git a/crates/markdown/examples/markdown.rs b/crates/markdown/examples/markdown.rs index 16387a8000..bf685bd9ac 100644 --- a/crates/markdown/examples/markdown.rs +++ b/crates/markdown/examples/markdown.rs @@ -107,11 +107,7 @@ impl Render for MarkdownExample { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { - let mut selection = cx.theme().players().local().selection; - selection.fade_out(0.7); - selection - }, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() }; diff --git a/crates/markdown/examples/markdown_as_child.rs b/crates/markdown/examples/markdown_as_child.rs index 62a35629b1..862b657c8c 100644 --- a/crates/markdown/examples/markdown_as_child.rs +++ b/crates/markdown/examples/markdown_as_child.rs @@ -91,11 +91,7 @@ impl Render for HelloWorld { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { - let mut selection = cx.theme().players().local().selection; - selection.fade_out(0.7); - selection - }, + selection_background_color: cx.theme().colors().element_selection_background, heading: Default::default(), ..Default::default() }; diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index ac959d13b5..9c057baec9 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -504,7 +504,6 @@ impl MarkdownElement { let selection = self.markdown.read(cx).selection; let selection_start = rendered_text.position_for_source_index(selection.start); let selection_end = rendered_text.position_for_source_index(selection.end); - if let Some(((start_position, start_line_height), (end_position, end_line_height))) = selection_start.zip(selection_end) { diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 070d8dc4e3..5a38e1aadb 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -248,7 +248,7 @@ impl Render for SshPrompt { text_style.refine(&refinement); let markdown_style = MarkdownStyle { base_text_style: text_style, - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() }; diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 33d7b86e3d..3424e0fe04 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -52,6 +52,7 @@ impl ThemeColors { element_active: neutral().light_alpha().step_5(), element_selected: neutral().light_alpha().step_5(), element_disabled: neutral().light_alpha().step_3(), + element_selection_background: blue().light().step_3().alpha(0.25), drop_target_background: blue().light_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().light_alpha().step_3(), @@ -174,6 +175,7 @@ impl ThemeColors { element_active: neutral().dark_alpha().step_5(), element_selected: neutral().dark_alpha().step_5(), element_disabled: neutral().dark_alpha().step_3(), + element_selection_background: blue().dark().step_3().alpha(0.25), drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().dark_alpha().step_4(), diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index afc977d7fd..5e9967d460 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -4,7 +4,8 @@ use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearan use crate::{ AccentColors, Appearance, PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, - SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, default_color_scales, + SystemColors, Theme, ThemeColors, ThemeColorsRefinement, ThemeFamily, ThemeStyles, + default_color_scales, }; /// The default theme family for Zed. @@ -41,6 +42,19 @@ pub(crate) fn apply_status_color_defaults(status: &mut StatusColorsRefinement) { } } +pub(crate) fn apply_theme_color_defaults( + theme_colors: &mut ThemeColorsRefinement, + player_colors: &PlayerColors, +) { + if theme_colors.element_selection_background.is_none() { + let mut selection = player_colors.local().selection; + if selection.a == 1.0 { + selection.a = 0.25; + } + theme_colors.element_selection_background = Some(selection); + } +} + pub(crate) fn zed_default_dark() -> Theme { let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.); let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.); @@ -74,6 +88,7 @@ pub(crate) fn zed_default_dark() -> Theme { a: 1.0, }; + let player = PlayerColors::dark(); Theme { id: "one_dark".to_string(), name: "One Dark".into(), @@ -97,6 +112,7 @@ pub(crate) fn zed_default_dark() -> Theme { element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0), element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), element_disabled: SystemColors::default().transparent, + element_selection_background: player.local().selection.alpha(0.25), drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0), ghost_element_background: SystemColors::default().transparent, ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0), @@ -258,7 +274,7 @@ pub(crate) fn zed_default_dark() -> Theme { warning_background: yellow, warning_border: yellow, }, - player: PlayerColors::dark(), + player, syntax: Arc::new(SyntaxTheme { highlights: vec![ ("attribute".into(), purple.into()), diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index a071ca26c8..01fdafd94d 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -219,6 +219,10 @@ pub struct ThemeColorsContent { #[serde(rename = "element.disabled")] pub element_disabled: Option, + /// Background Color. Used for the background of selections in a UI element. + #[serde(rename = "element.selection_background")] + pub element_selection_background: Option, + /// Background Color. Used for the area that shows where a dragged element will be dropped. #[serde(rename = "drop_target.background")] pub drop_target_background: Option, @@ -726,6 +730,10 @@ impl ThemeColorsContent { .element_disabled .as_ref() .and_then(|color| try_parse_color(color).ok()), + element_selection_background: self + .element_selection_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), drop_target_background: self .drop_target_background .as_ref() diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index fb821385f5..76d18c6d65 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -51,6 +51,8 @@ pub struct ThemeColors { /// /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub element_selected: Hsla, + /// Background Color. Used for the background of selections in a UI element. + pub element_selection_background: Hsla, /// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on. /// /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3b5306c216..bdb52693c0 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -35,6 +35,7 @@ use serde::Deserialize; use uuid::Uuid; pub use crate::default_colors::*; +use crate::fallback_themes::apply_theme_color_defaults; pub use crate::font_family_cache::*; pub use crate::icon_theme::*; pub use crate::icon_theme_schema::*; @@ -165,12 +166,6 @@ impl ThemeFamily { AppearanceContent::Dark => Appearance::Dark, }; - let mut refined_theme_colors = match theme.appearance { - AppearanceContent::Light => ThemeColors::light(), - AppearanceContent::Dark => ThemeColors::dark(), - }; - refined_theme_colors.refine(&theme.style.theme_colors_refinement()); - let mut refined_status_colors = match theme.appearance { AppearanceContent::Light => StatusColors::light(), AppearanceContent::Dark => StatusColors::dark(), @@ -185,6 +180,14 @@ impl ThemeFamily { }; refined_player_colors.merge(&theme.style.players); + let mut refined_theme_colors = match theme.appearance { + AppearanceContent::Light => ThemeColors::light(), + AppearanceContent::Dark => ThemeColors::dark(), + }; + let mut theme_colors_refinement = theme.style.theme_colors_refinement(); + apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors); + refined_theme_colors.refine(&theme_colors_refinement); + let mut refined_accent_colors = match theme.appearance { AppearanceContent::Light => AccentColors::light(), AppearanceContent::Dark => AccentColors::dark(), diff --git a/crates/ui_prompt/src/ui_prompt.rs b/crates/ui_prompt/src/ui_prompt.rs index dc6aee177d..2b6a030f26 100644 --- a/crates/ui_prompt/src/ui_prompt.rs +++ b/crates/ui_prompt/src/ui_prompt.rs @@ -153,7 +153,10 @@ impl Render for ZedPromptRenderer { }); MarkdownStyle { base_text_style, - selection_background_color: { cx.theme().players().local().selection }, + selection_background_color: cx + .theme() + .colors() + .element_selection_background, ..Default::default() } }))