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 <mail4score@gmail.com>
This commit is contained in:
Giorgio PoglianiandKirill Bulatov authored and GitHub committed 2026-09-11 15:19:53 +00:00
1 parent 1e6cc1f182
commit fde3e42be8
7 files changed
+123 -2

No files matched your search

+6 -2
View File
@@ -616,6 +616,10 @@ fn build_code_lens_renderer(line: CodeLensLine, editor: WeakEntity<Editor>) -> 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<Editor>) -> 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<Editor>) -> 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;
+4
View File
@@ -842,6 +842,10 @@ pub struct ThemeColorsContent {
#[serde(rename = "editor.foreground")]
pub editor_foreground: Option<ThemeColor>,
/// Text color used for CodeLens items in the editor.
#[serde(rename = "editor.code_lens.foreground")]
pub editor_code_lens_foreground: Option<ThemeColor>,
#[serde(rename = "editor.background")]
pub editor_background: Option<ThemeColor>,
+2
View File
@@ -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(),
+1
View File
@@ -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,
+8
View File
@@ -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<Hsla>,
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,
+4
View File
@@ -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()
+98
View File
@@ -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
);
}
}