From fde3e42be8eba570e784b655ce779726525d72f0 Mon Sep 17 00:00:00 2001 From: Giorgio Pogliani Date: Fri, 11 Sep 2026 15:19:53 +0000 Subject: [PATCH] editor: Allow customizing CodeLens text color (#64084) CodeLens text currently hardcodes `text.muted`, so changing its color also changes unrelated muted UI text such as the sidebar. This adds a dedicated `editor.code_lens.foreground` theme color. When omitted, it falls back to `text.muted`, preserving the existing appearance of themes while allowing CodeLens text to be customized independently. Release Notes: - Added `editor.code_lens.foreground` for customizing CodeLens text independently. --------- Co-authored-by: Kirill Bulatov --- crates/editor/src/code_lens.rs | 8 ++- crates/settings_content/src/theme.rs | 4 ++ crates/theme/src/default_colors.rs | 2 + crates/theme/src/fallback_themes.rs | 1 + crates/theme/src/styles/colors.rs | 8 +++ crates/theme_settings/src/schema.rs | 4 ++ crates/theme_settings/src/settings.rs | 98 +++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/code_lens.rs b/crates/editor/src/code_lens.rs index d8bd285e15..c8ba5be703 100644 --- a/crates/editor/src/code_lens.rs +++ b/crates/editor/src/code_lens.rs @@ -616,6 +616,10 @@ fn build_code_lens_renderer(line: CodeLensLine, editor: WeakEntity) -> R let text_style = &cx.editor_style.text; let font = text_style.font(); let font_size = text_style.font_size.to_pixels(cx.window.rem_size()) * 0.9; + let colors = cx.app.theme().colors(); + let code_lens_color = colors + .editor_code_lens_foreground + .unwrap_or(colors.text_muted); for (i, (title, action)) in resolved_items.into_iter().enumerate() { if i > 0 { @@ -623,7 +627,7 @@ fn build_code_lens_renderer(line: CodeLensLine, editor: WeakEntity) -> R div() .font(font.clone()) .text_size(font_size) - .text_color(cx.app.theme().colors().text_muted) + .text_color(code_lens_color) .child(CODE_LENS_SEPARATOR) .into_any_element(), ); @@ -634,7 +638,7 @@ fn build_code_lens_renderer(line: CodeLensLine, editor: WeakEntity) -> R .id(ElementId::from(i)) .font(font.clone()) .text_size(font_size) - .text_color(cx.app.theme().colors().text_muted) + .text_color(code_lens_color) .child(title.clone()) .when_some(action, |code_lens_div, action| { let position = line.position; diff --git a/crates/settings_content/src/theme.rs b/crates/settings_content/src/theme.rs index 649153e600..e225545a84 100644 --- a/crates/settings_content/src/theme.rs +++ b/crates/settings_content/src/theme.rs @@ -842,6 +842,10 @@ pub struct ThemeColorsContent { #[serde(rename = "editor.foreground")] pub editor_foreground: Option, + /// Text color used for CodeLens items in the editor. + #[serde(rename = "editor.code_lens.foreground")] + pub editor_code_lens_foreground: Option, + #[serde(rename = "editor.background")] pub editor_background: Option, diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index b52e855f2b..7a6cba6697 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -112,6 +112,7 @@ impl ThemeColors { minimap_thumb_active_background: neutral().light_alpha().step_5().alpha(0.7), minimap_thumb_border: gpui::transparent_black(), editor_foreground: neutral().light().step_12(), + editor_code_lens_foreground: None, editor_background: neutral().light().step_1(), editor_gutter_background: neutral().light().step_1(), editor_subheader_background: neutral().light().step_2(), @@ -265,6 +266,7 @@ impl ThemeColors { minimap_thumb_active_background: neutral().dark_alpha().step_5().alpha(0.7), minimap_thumb_border: gpui::transparent_black(), editor_foreground: neutral().dark().step_12(), + editor_code_lens_foreground: None, editor_background: neutral().dark().step_1(), editor_gutter_background: neutral().dark().step_1(), editor_subheader_background: neutral().dark().step_3(), diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index 2716eae0b2..c22a3e13ef 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -247,6 +247,7 @@ pub(crate) fn zed_default_dark() -> Theme { minimap_thumb_active_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 0.7), minimap_thumb_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.), editor_foreground: hsla(218. / 360., 14. / 100., 71. / 100., 1.), + editor_code_lens_foreground: None, link_text_hover: blue, version_control_added: ADDED_COLOR, version_control_deleted: REMOVED_COLOR, diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index f9ebd441aa..8364994d39 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -204,6 +204,10 @@ pub struct ThemeColors { // Editor // === pub editor_foreground: Hsla, + /// Text color used for CodeLens items in the editor. + /// + /// Falls back to `text_muted` when not explicitly set. + pub editor_code_lens_foreground: Option, pub editor_background: Hsla, pub editor_gutter_background: Hsla, pub editor_subheader_background: Hsla, @@ -404,6 +408,7 @@ pub enum ThemeColorField { MinimapThumbActiveBackground, MinimapThumbBorder, EditorForeground, + EditorCodeLensForeground, EditorBackground, EditorGutterBackground, EditorSubheaderBackground, @@ -522,6 +527,9 @@ impl ThemeColors { ThemeColorField::MinimapThumbActiveBackground => self.minimap_thumb_active_background, ThemeColorField::MinimapThumbBorder => self.minimap_thumb_border, ThemeColorField::EditorForeground => self.editor_foreground, + ThemeColorField::EditorCodeLensForeground => { + self.editor_code_lens_foreground.unwrap_or(self.text_muted) + } ThemeColorField::EditorBackground => self.editor_background, ThemeColorField::EditorGutterBackground => self.editor_gutter_background, ThemeColorField::EditorSubheaderBackground => self.editor_subheader_background, diff --git a/crates/theme_settings/src/schema.rs b/crates/theme_settings/src/schema.rs index 904c216715..d8c753f81c 100644 --- a/crates/theme_settings/src/schema.rs +++ b/crates/theme_settings/src/schema.rs @@ -541,6 +541,10 @@ pub fn theme_colors_refinement( .editor_foreground .as_ref() .and_then(|color| try_parse_color(color).ok()), + editor_code_lens_foreground: this + .editor_code_lens_foreground + .as_ref() + .and_then(|color| try_parse_color(color).ok()), editor_background: this .editor_background .as_ref() diff --git a/crates/theme_settings/src/settings.rs b/crates/theme_settings/src/settings.rs index cd632bb018..e72179bdc5 100644 --- a/crates/theme_settings/src/settings.rs +++ b/crates/theme_settings/src/settings.rs @@ -765,3 +765,101 @@ impl settings::Settings for ThemeSettings { } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn theme_with_colors(colors: ::settings::ThemeColorsContent) -> Theme { + crate::refine_theme(&crate::ThemeContent { + name: "Test".into(), + appearance: ::theme::AppearanceContent::Dark, + style: ::settings::ThemeStyleContent { + colors, + ..Default::default() + }, + }) + } + + fn style_with_colors(colors: ::settings::ThemeColorsContent) -> ::settings::ThemeStyleContent { + ::settings::ThemeStyleContent { + colors, + ..Default::default() + } + } + + #[test] + fn code_lens_foreground_from_theme_survives_text_muted_override() { + let magenta = ::theme::try_parse_color("#ff00ff").unwrap(); + let green = ::theme::try_parse_color("#00ff00").unwrap(); + let mut test_theme = theme_with_colors(::settings::ThemeColorsContent { + editor_code_lens_foreground: Some("#ff00ff".into()), + ..Default::default() + }); + + ThemeSettings::modify_theme( + &mut test_theme, + &style_with_colors(::settings::ThemeColorsContent { + text_muted: Some("#00ff00".into()), + ..Default::default() + }), + ); + + assert_eq!( + test_theme.styles.colors.editor_code_lens_foreground, + Some(magenta) + ); + assert_eq!(test_theme.styles.colors.text_muted, green); + } + + #[test] + fn code_lens_foreground_from_earlier_override_survives_later_muted_override() { + let magenta = ::theme::try_parse_color("#ff00ff").unwrap(); + let green = ::theme::try_parse_color("#00ff00").unwrap(); + let mut test_theme = theme_with_colors(Default::default()); + + ThemeSettings::modify_theme( + &mut test_theme, + &style_with_colors(::settings::ThemeColorsContent { + editor_code_lens_foreground: Some("#ff00ff".into()), + ..Default::default() + }), + ); + ThemeSettings::modify_theme( + &mut test_theme, + &style_with_colors(::settings::ThemeColorsContent { + text_muted: Some("#00ff00".into()), + ..Default::default() + }), + ); + + assert_eq!( + test_theme.styles.colors.editor_code_lens_foreground, + Some(magenta) + ); + assert_eq!(test_theme.styles.colors.text_muted, green); + } + + #[test] + fn code_lens_foreground_follows_effective_text_muted_without_explicit_color() { + let green = ::theme::try_parse_color("#00ff00").unwrap(); + let mut test_theme = theme_with_colors(Default::default()); + + ThemeSettings::modify_theme( + &mut test_theme, + &style_with_colors(::settings::ThemeColorsContent { + text_muted: Some("#00ff00".into()), + ..Default::default() + }), + ); + + assert_eq!(test_theme.styles.colors.editor_code_lens_foreground, None); + assert_eq!( + test_theme + .styles + .colors + .color(::theme::ThemeColorField::EditorCodeLensForeground), + green + ); + } +}