From 71ea133d72eba0d8ab0216d78ae0ac05fd10c406 Mon Sep 17 00:00:00 2001 From: Willy Hetland <58665964+willeyh-git@users.noreply.github.com> Date: Tue, 21 Oct 2025 06:30:30 +0200 Subject: [PATCH] Theme-able Vim Mode wrapper (#39813) Closes [#14093](https://github.com/zed-industries/zed/issues/14093) Builds on [#32279](https://github.com/zed-industries/zed/pull/32279) by making it theme dependent. Discussion [#37816](https://github.com/zed-industries/zed/discussions/37816) Wraps the mode label indicator in a div and makes the wrapper and label theme-able. Label weight to medium Mode indicator will render like previously if not theme colors have been set. (i.e., they match zed default- and fallbacks) Really helps with visual confirmation of current mode. _Did not investigate further if there is a way to keep the leading and trailing -- if no theme var given._ Can be applied either by a theme itself or using `theme_overrides` in settings.json Theme colors applied via `theme_overrides` Screenshot 2025-10-08 at 23 01 08 Screenshot 2025-10-08 at 23 01 16 Screenshot 2025-10-08 at 23 01 23 No theme applied Screenshot 2025-10-08 at 23 01 31 Screenshot 2025-10-08 at 23 01 36 Screenshot 2025-10-08 at 23 01 40 https://github.com/user-attachments/assets/d0d71d4d-504f-4d18-bbd9-83d3a4b2adb7 Release Notes: - Vim make mode indicator themeable --------- Co-authored-by: willyHetland Co-authored-by: Conrad Irwin --- crates/settings/src/settings_content/theme.rs | 29 +++++++ crates/theme/src/default_colors.rs | 18 +++++ crates/theme/src/fallback_themes.rs | 10 +++ crates/theme/src/schema.rs | 36 +++++++++ crates/theme/src/styles/colors.rs | 19 +++++ crates/vim/src/mode_indicator.rs | 76 ++++++++++++++++--- 6 files changed, 176 insertions(+), 12 deletions(-) diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs index 0228fcbfe832f56c8ad504fd289b273b0a1c0ec7..487ffad34e313f403d97f91af5df205294df61df 100644 --- a/crates/settings/src/settings_content/theme.rs +++ b/crates/settings/src/settings_content/theme.rs @@ -889,6 +889,35 @@ pub struct ThemeColorsContent { /// Deprecated in favor of `version_control_conflict_marker_theirs`. #[deprecated] pub version_control_conflict_theirs_background: Option, + + /// Background color for Vim Normal mode indicator. + #[serde(rename = "vim.normal.background")] + pub vim_normal_background: Option, + /// Background color for Vim Insert mode indicator. + #[serde(rename = "vim.insert.background")] + pub vim_insert_background: Option, + /// Background color for Vim Replace mode indicator. + #[serde(rename = "vim.replace.background")] + pub vim_replace_background: Option, + /// Background color for Vim Visual mode indicator. + #[serde(rename = "vim.visual.background")] + pub vim_visual_background: Option, + /// Background color for Vim Visual Line mode indicator. + #[serde(rename = "vim.visual_line.background")] + pub vim_visual_line_background: Option, + /// Background color for Vim Visual Block mode indicator. + #[serde(rename = "vim.visual_block.background")] + pub vim_visual_block_background: Option, + /// Background color for Vim Helix Normal mode indicator. + #[serde(rename = "vim.helix_normal.background")] + pub vim_helix_normal_background: Option, + /// Background color for Vim Helix Select mode indicator. + #[serde(rename = "vim.helix_select.background")] + pub vim_helix_select_background: Option, + + /// Text color for Vim mode indicator label. + #[serde(rename = "vim.mode.text")] + pub vim_mode_text: Option, } #[skip_serializing_none] diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 051b7acf102597b6f11581afdd45611b9a4b76e3..80ad845e989b244a5bcd5eb529720d10416ea7bc 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -154,6 +154,15 @@ impl ThemeColors { version_control_ignored: gray().light().step_12(), version_control_conflict_marker_ours: green().light().step_10().alpha(0.5), version_control_conflict_marker_theirs: blue().light().step_10().alpha(0.5), + vim_normal_background: system.transparent, + vim_insert_background: system.transparent, + vim_replace_background: system.transparent, + vim_visual_background: system.transparent, + vim_visual_line_background: system.transparent, + vim_visual_block_background: system.transparent, + vim_helix_normal_background: system.transparent, + vim_helix_select_background: system.transparent, + vim_mode_text: system.transparent, } } @@ -280,6 +289,15 @@ impl ThemeColors { version_control_ignored: gray().dark().step_12(), version_control_conflict_marker_ours: green().dark().step_10().alpha(0.5), version_control_conflict_marker_theirs: blue().dark().step_10().alpha(0.5), + vim_normal_background: system.transparent, + vim_insert_background: system.transparent, + vim_replace_background: system.transparent, + vim_visual_background: system.transparent, + vim_visual_line_background: system.transparent, + vim_visual_block_background: system.transparent, + vim_helix_normal_background: system.transparent, + vim_helix_select_background: system.transparent, + vim_mode_text: system.transparent, } } } diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index 4fb8069bc16d1967dfe10b2e6a577b990d942db7..ae120165f23095266cf92fd33a1cd1ccb88fe309 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -233,6 +233,16 @@ pub(crate) fn zed_default_dark() -> Theme { version_control_ignored: crate::gray().light().step_12(), version_control_conflict_marker_ours: crate::green().light().step_12().alpha(0.5), version_control_conflict_marker_theirs: crate::blue().light().step_12().alpha(0.5), + + vim_normal_background: SystemColors::default().transparent, + vim_insert_background: SystemColors::default().transparent, + vim_replace_background: SystemColors::default().transparent, + vim_visual_background: SystemColors::default().transparent, + vim_visual_line_background: SystemColors::default().transparent, + vim_visual_block_background: SystemColors::default().transparent, + vim_helix_normal_background: SystemColors::default().transparent, + vim_helix_select_background: SystemColors::default().transparent, + vim_mode_text: SystemColors::default().transparent, }, status: StatusColors { conflict: yellow, diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index 2d7e1ff9d823eae0d48b375592c6d1f91318f472..c4ed624bf642e0820fd9187224f96e2acfa92018 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -756,6 +756,42 @@ pub fn theme_colors_refinement( .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_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_mode_text: this + .vim_mode_text + .as_ref() + .and_then(|color| try_parse_color(color).ok()), } } diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index 198ad97adb5d964a1d8f62c5bde99d1d5be5adf7..179d02b91684410bb641893e87759bd30cc73b36 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -162,6 +162,25 @@ pub struct ThemeColors { /// The border color of the minimap thumb. pub minimap_thumb_border: Hsla, + /// Background color for Vim Normal mode indicator. + pub vim_normal_background: Hsla, + /// Background color for Vim Insert mode indicator. + pub vim_insert_background: Hsla, + /// Background color for Vim Replace mode indicator. + pub vim_replace_background: Hsla, + /// Background color for Vim Visual mode indicator. + pub vim_visual_background: Hsla, + /// Background color for Vim Visual Line mode indicator. + pub vim_visual_line_background: Hsla, + /// Background color for Vim Visual Block mode indicator. + pub vim_visual_block_background: Hsla, + /// Background color for Vim Helix Normal mode indicator. + pub vim_helix_normal_background: Hsla, + /// Background color for Vim Helix Select mode indicator. + pub vim_helix_select_background: Hsla, + /// Text color for Vim mode indicator label. + pub vim_mode_text: Hsla, + // === // Editor // === diff --git a/crates/vim/src/mode_indicator.rs b/crates/vim/src/mode_indicator.rs index ed182eb74d8e01cc9b8f543cbbc928f6864391e3..42d4915fc509e0f373c8d2c5a2a422b74cc84a8f 100644 --- a/crates/vim/src/mode_indicator.rs +++ b/crates/vim/src/mode_indicator.rs @@ -1,4 +1,4 @@ -use gpui::{Context, Entity, Render, Subscription, WeakEntity, Window, div}; +use gpui::{Context, Element, Entity, FontWeight, Render, Subscription, WeakEntity, Window, div}; use ui::text_for_keystrokes; use workspace::{StatusItemView, item::ItemHandle, ui::prelude::*}; @@ -93,13 +93,33 @@ impl Render for ModeIndicator { }; let vim_readable = vim.read(cx); - let label = if let Some(label) = vim_readable.status_label.clone() { - label + let status_label = vim_readable.status_label.clone(); + let temp_mode = vim_readable.temp_mode; + let mode = vim_readable.mode; + + let theme = cx.theme(); + let colors = theme.colors(); + let system_transparent = gpui::hsla(0.0, 0.0, 0.0, 0.0); + let vim_mode_text = colors.vim_mode_text; + let bg_color = match mode { + crate::state::Mode::Normal => colors.vim_normal_background, + crate::state::Mode::Insert => colors.vim_insert_background, + crate::state::Mode::Replace => colors.vim_replace_background, + crate::state::Mode::Visual => colors.vim_visual_background, + crate::state::Mode::VisualLine => colors.vim_visual_line_background, + crate::state::Mode::VisualBlock => colors.vim_visual_block_background, + crate::state::Mode::HelixNormal => colors.vim_helix_normal_background, + crate::state::Mode::HelixSelect => colors.vim_helix_select_background, + }; + + let (label, mode): (SharedString, Option) = if let Some(label) = status_label + { + (label, None) } else { - let mode = if vim_readable.temp_mode { - format!("(insert) {}", vim_readable.mode) + let mode_str = if temp_mode { + format!("(insert) {}", mode) } else { - vim_readable.mode.to_string() + mode.to_string() }; let current_operators_description = self.current_operators_description(vim.clone(), cx); @@ -107,13 +127,45 @@ impl Render for ModeIndicator { .pending_keys .as_ref() .unwrap_or(¤t_operators_description); - format!("{} -- {} --", pending, mode).into() + let mode = if bg_color != system_transparent { + mode_str.into() + } else { + format!("-- {} --", mode_str).into() + }; + (pending.into(), Some(mode)) }; - - Label::new(label) - .size(LabelSize::Small) - .line_height_style(LineHeightStyle::UiLabel) - .into_any_element() + h_flex() + .gap_1() + .when(!label.is_empty(), |el| { + el.child( + Label::new(label) + .line_height_style(LineHeightStyle::UiLabel) + .weight(FontWeight::MEDIUM), + ) + }) + .when_some(mode, |el, mode| { + el.child( + v_flex() + .when(bg_color != system_transparent, |el| el.px_2()) + // match with other icons at the bottom that use default buttons + .h(ButtonSize::Default.rems()) + .justify_center() + .rounded_sm() + .bg(bg_color) + .child( + Label::new(mode) + .size(LabelSize::Small) + .line_height_style(LineHeightStyle::UiLabel) + .weight(FontWeight::MEDIUM) + .when( + bg_color != system_transparent + && vim_mode_text != system_transparent, + |el| el.color(Color::Custom(vim_mode_text)), + ), + ), + ) + }) + .into_any() } }