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` <img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 08" src="https://github.com/user-attachments/assets/a00d9ae4-b6db-46a0-84e2-98d2691a11ad" /> <img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 16" src="https://github.com/user-attachments/assets/f27fddab-524d-43c4-9307-46b6a656cd35" /> <img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 23" src="https://github.com/user-attachments/assets/7e477fff-7a40-4c01-95a7-fbd40fff6caa" /> No theme applied <img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 31" src="https://github.com/user-attachments/assets/8b7b2c75-007b-4074-a552-181c53f31213" /> <img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 36" src="https://github.com/user-attachments/assets/7a708d81-2033-4d72-a844-57607a0434ea" /> <img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 40" src="https://github.com/user-attachments/assets/526f9d10-4d0f-4bc5-af89-31fcca538ce4" /> https://github.com/user-attachments/assets/d0d71d4d-504f-4d18-bbd9-83d3a4b2adb7 Release Notes: - Vim make mode indicator themeable --------- Co-authored-by: willyHetland <willy.hetland@zeekit.no> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
181 lines
6.4 KiB
Rust
181 lines
6.4 KiB
Rust
use gpui::{Context, Element, Entity, FontWeight, Render, Subscription, WeakEntity, Window, div};
|
|
use ui::text_for_keystrokes;
|
|
use workspace::{StatusItemView, item::ItemHandle, ui::prelude::*};
|
|
|
|
use crate::{Vim, VimEvent, VimGlobals};
|
|
|
|
/// The ModeIndicator displays the current mode in the status bar.
|
|
pub struct ModeIndicator {
|
|
vim: Option<WeakEntity<Vim>>,
|
|
pending_keys: Option<String>,
|
|
vim_subscription: Option<Subscription>,
|
|
}
|
|
|
|
impl ModeIndicator {
|
|
/// Construct a new mode indicator in this window.
|
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
|
cx.observe_pending_input(window, |this: &mut Self, window, cx| {
|
|
this.update_pending_keys(window, cx);
|
|
cx.notify();
|
|
})
|
|
.detach();
|
|
|
|
let handle = cx.entity();
|
|
let window_handle = window.window_handle();
|
|
cx.observe_new::<Vim>(move |_, window, cx| {
|
|
let Some(window) = window else {
|
|
return;
|
|
};
|
|
if window.window_handle() != window_handle {
|
|
return;
|
|
}
|
|
let vim = cx.entity();
|
|
handle.update(cx, |_, cx| {
|
|
cx.subscribe(&vim, |mode_indicator, vim, event, cx| match event {
|
|
VimEvent::Focused => {
|
|
mode_indicator.vim_subscription =
|
|
Some(cx.observe(&vim, |_, _, cx| cx.notify()));
|
|
mode_indicator.vim = Some(vim.downgrade());
|
|
}
|
|
})
|
|
.detach()
|
|
})
|
|
})
|
|
.detach();
|
|
|
|
Self {
|
|
vim: None,
|
|
pending_keys: None,
|
|
vim_subscription: None,
|
|
}
|
|
}
|
|
|
|
fn update_pending_keys(&mut self, window: &mut Window, cx: &App) {
|
|
self.pending_keys = window
|
|
.pending_input_keystrokes()
|
|
.map(|keystrokes| text_for_keystrokes(keystrokes, cx));
|
|
}
|
|
|
|
fn vim(&self) -> Option<Entity<Vim>> {
|
|
self.vim.as_ref().and_then(|vim| vim.upgrade())
|
|
}
|
|
|
|
fn current_operators_description(&self, vim: Entity<Vim>, cx: &mut Context<Self>) -> String {
|
|
let recording = Vim::globals(cx)
|
|
.recording_register
|
|
.map(|reg| format!("recording @{reg} "))
|
|
.into_iter();
|
|
|
|
let vim = vim.read(cx);
|
|
recording
|
|
.chain(
|
|
cx.global::<VimGlobals>()
|
|
.pre_count
|
|
.map(|count| format!("{}", count)),
|
|
)
|
|
.chain(vim.selected_register.map(|reg| format!("\"{reg}")))
|
|
.chain(vim.operator_stack.iter().map(|item| item.status()))
|
|
.chain(
|
|
cx.global::<VimGlobals>()
|
|
.post_count
|
|
.map(|count| format!("{}", count)),
|
|
)
|
|
.collect::<Vec<_>>()
|
|
.join("")
|
|
}
|
|
}
|
|
|
|
impl Render for ModeIndicator {
|
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let vim = self.vim();
|
|
let Some(vim) = vim else {
|
|
return div().hidden().into_any_element();
|
|
};
|
|
|
|
let vim_readable = vim.read(cx);
|
|
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<SharedString>) = if let Some(label) = status_label
|
|
{
|
|
(label, None)
|
|
} else {
|
|
let mode_str = if temp_mode {
|
|
format!("(insert) {}", mode)
|
|
} else {
|
|
mode.to_string()
|
|
};
|
|
|
|
let current_operators_description = self.current_operators_description(vim.clone(), cx);
|
|
let pending = self
|
|
.pending_keys
|
|
.as_ref()
|
|
.unwrap_or(¤t_operators_description);
|
|
let mode = if bg_color != system_transparent {
|
|
mode_str.into()
|
|
} else {
|
|
format!("-- {} --", mode_str).into()
|
|
};
|
|
(pending.into(), Some(mode))
|
|
};
|
|
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()
|
|
}
|
|
}
|
|
|
|
impl StatusItemView for ModeIndicator {
|
|
fn set_active_pane_item(
|
|
&mut self,
|
|
_active_pane_item: Option<&dyn ItemHandle>,
|
|
_window: &mut Window,
|
|
_cx: &mut Context<Self>,
|
|
) {
|
|
}
|
|
}
|