mode_indicator.rs

  1use gpui::{Context, Element, Entity, FontWeight, Render, Subscription, WeakEntity, Window, div};
  2use ui::text_for_keystrokes;
  3use workspace::{StatusItemView, item::ItemHandle, ui::prelude::*};
  4
  5use crate::{Vim, VimEvent, VimGlobals};
  6
  7/// The ModeIndicator displays the current mode in the status bar.
  8pub struct ModeIndicator {
  9    vim: Option<WeakEntity<Vim>>,
 10    pending_keys: Option<String>,
 11    vim_subscription: Option<Subscription>,
 12}
 13
 14impl ModeIndicator {
 15    /// Construct a new mode indicator in this window.
 16    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
 17        cx.observe_pending_input(window, |this: &mut Self, window, cx| {
 18            this.update_pending_keys(window, cx);
 19            cx.notify();
 20        })
 21        .detach();
 22
 23        let handle = cx.entity();
 24        let window_handle = window.window_handle();
 25        cx.observe_new::<Vim>(move |_, window, cx| {
 26            let Some(window) = window else {
 27                return;
 28            };
 29            if window.window_handle() != window_handle {
 30                return;
 31            }
 32            let vim = cx.entity();
 33            handle.update(cx, |_, cx| {
 34                cx.subscribe(&vim, |mode_indicator, vim, event, cx| match event {
 35                    VimEvent::Focused => {
 36                        mode_indicator.vim_subscription =
 37                            Some(cx.observe(&vim, |_, _, cx| cx.notify()));
 38                        mode_indicator.vim = Some(vim.downgrade());
 39                    }
 40                })
 41                .detach()
 42            })
 43        })
 44        .detach();
 45
 46        Self {
 47            vim: None,
 48            pending_keys: None,
 49            vim_subscription: None,
 50        }
 51    }
 52
 53    fn update_pending_keys(&mut self, window: &mut Window, cx: &App) {
 54        self.pending_keys = window
 55            .pending_input_keystrokes()
 56            .map(|keystrokes| text_for_keystrokes(keystrokes, cx));
 57    }
 58
 59    fn vim(&self) -> Option<Entity<Vim>> {
 60        self.vim.as_ref().and_then(|vim| vim.upgrade())
 61    }
 62
 63    fn current_operators_description(&self, vim: Entity<Vim>, cx: &mut Context<Self>) -> String {
 64        let recording = Vim::globals(cx)
 65            .recording_register
 66            .map(|reg| format!("recording @{reg} "))
 67            .into_iter();
 68
 69        let vim = vim.read(cx);
 70        recording
 71            .chain(
 72                cx.global::<VimGlobals>()
 73                    .pre_count
 74                    .map(|count| format!("{}", count)),
 75            )
 76            .chain(vim.selected_register.map(|reg| format!("\"{reg}")))
 77            .chain(vim.operator_stack.iter().map(|item| item.status()))
 78            .chain(
 79                cx.global::<VimGlobals>()
 80                    .post_count
 81                    .map(|count| format!("{}", count)),
 82            )
 83            .collect::<Vec<_>>()
 84            .join("")
 85    }
 86}
 87
 88impl Render for ModeIndicator {
 89    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 90        let vim = self.vim();
 91        let Some(vim) = vim else {
 92            return div().hidden().into_any_element();
 93        };
 94
 95        let vim_readable = vim.read(cx);
 96        let status_label = vim_readable.status_label.clone();
 97        let temp_mode = vim_readable.temp_mode;
 98        let mode = vim_readable.mode;
 99
100        let theme = cx.theme();
101        let colors = theme.colors();
102        let system_transparent = gpui::hsla(0.0, 0.0, 0.0, 0.0);
103        let vim_mode_text = colors.vim_mode_text;
104        let bg_color = match mode {
105            crate::state::Mode::Normal => colors.vim_normal_background,
106            crate::state::Mode::Insert => colors.vim_insert_background,
107            crate::state::Mode::Replace => colors.vim_replace_background,
108            crate::state::Mode::Visual => colors.vim_visual_background,
109            crate::state::Mode::VisualLine => colors.vim_visual_line_background,
110            crate::state::Mode::VisualBlock => colors.vim_visual_block_background,
111            crate::state::Mode::HelixNormal => colors.vim_helix_normal_background,
112            crate::state::Mode::HelixSelect => colors.vim_helix_select_background,
113        };
114
115        let (label, mode): (SharedString, Option<SharedString>) = if let Some(label) = status_label
116        {
117            (label, None)
118        } else {
119            let mode_str = if temp_mode {
120                format!("(insert) {}", mode)
121            } else {
122                mode.to_string()
123            };
124
125            let current_operators_description = self.current_operators_description(vim.clone(), cx);
126            let pending = self
127                .pending_keys
128                .as_ref()
129                .unwrap_or(&current_operators_description);
130            let mode = if bg_color != system_transparent {
131                mode_str.into()
132            } else {
133                format!("-- {} --", mode_str).into()
134            };
135            (pending.into(), Some(mode))
136        };
137        h_flex()
138            .gap_1()
139            .when(!label.is_empty(), |el| {
140                el.child(
141                    Label::new(label)
142                        .line_height_style(LineHeightStyle::UiLabel)
143                        .weight(FontWeight::MEDIUM),
144                )
145            })
146            .when_some(mode, |el, mode| {
147                el.child(
148                    v_flex()
149                        .when(bg_color != system_transparent, |el| el.px_2())
150                        // match with other icons at the bottom that use default buttons
151                        .h(ButtonSize::Default.rems())
152                        .justify_center()
153                        .rounded_sm()
154                        .bg(bg_color)
155                        .child(
156                            Label::new(mode)
157                                .size(LabelSize::Small)
158                                .line_height_style(LineHeightStyle::UiLabel)
159                                .weight(FontWeight::MEDIUM)
160                                .when(
161                                    bg_color != system_transparent
162                                        && vim_mode_text != system_transparent,
163                                    |el| el.color(Color::Custom(vim_mode_text)),
164                                ),
165                        ),
166                )
167            })
168            .into_any()
169    }
170}
171
172impl StatusItemView for ModeIndicator {
173    fn set_active_pane_item(
174        &mut self,
175        _active_pane_item: Option<&dyn ItemHandle>,
176        _window: &mut Window,
177        _cx: &mut Context<Self>,
178    ) {
179    }
180}