mode_indicator.rs

  1use gpui::{div, Element, Render, Subscription, ViewContext};
  2use itertools::Itertools;
  3use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
  4
  5use crate::{state::Mode, Vim};
  6
  7/// The ModeIndicator displays the current mode in the status bar.
  8pub struct ModeIndicator {
  9    pub(crate) mode: Option<Mode>,
 10    pub(crate) operators: String,
 11    pending_keys: Option<String>,
 12    _subscriptions: Vec<Subscription>,
 13}
 14
 15impl ModeIndicator {
 16    /// Construct a new mode indicator in this window.
 17    pub fn new(cx: &mut ViewContext<Self>) -> Self {
 18        let _subscriptions = vec![
 19            cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
 20            cx.observe_pending_input(|this, cx| {
 21                this.update_pending_keys(cx);
 22                cx.notify();
 23            }),
 24        ];
 25
 26        let mut this = Self {
 27            mode: None,
 28            operators: "".to_string(),
 29            pending_keys: None,
 30            _subscriptions,
 31        };
 32        this.update_mode(cx);
 33        this
 34    }
 35
 36    fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
 37        if let Some(vim) = self.vim(cx) {
 38            self.mode = Some(vim.state().mode);
 39            self.operators = self.current_operators_description(&vim);
 40        } else {
 41            self.mode = None;
 42        }
 43    }
 44
 45    fn update_pending_keys(&mut self, cx: &mut ViewContext<Self>) {
 46        if self.vim(cx).is_some() {
 47            self.pending_keys = cx.pending_input_keystrokes().map(|keystrokes| {
 48                keystrokes
 49                    .iter()
 50                    .map(|keystroke| format!("{}", keystroke))
 51                    .join(" ")
 52            });
 53        } else {
 54            self.pending_keys = None;
 55        }
 56    }
 57
 58    fn vim<'a>(&self, cx: &'a mut ViewContext<Self>) -> Option<&'a Vim> {
 59        // In some tests Vim isn't enabled, so we use try_global.
 60        cx.try_global::<Vim>().filter(|vim| vim.enabled)
 61    }
 62
 63    fn current_operators_description(&self, vim: &Vim) -> String {
 64        vim.workspace_state
 65            .recording_register
 66            .map(|reg| format!("recording @{reg} "))
 67            .into_iter()
 68            .chain(vim.state().pre_count.map(|count| format!("{}", count)))
 69            .chain(vim.state().selected_register.map(|reg| format!("\"{reg}")))
 70            .chain(
 71                vim.state()
 72                    .operator_stack
 73                    .iter()
 74                    .map(|item| item.id().to_string()),
 75            )
 76            .chain(vim.state().post_count.map(|count| format!("{}", count)))
 77            .collect::<Vec<_>>()
 78            .join("")
 79    }
 80}
 81
 82impl Render for ModeIndicator {
 83    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
 84        let Some(mode) = self.mode.as_ref() else {
 85            return div().into_any();
 86        };
 87
 88        let pending = self.pending_keys.as_ref().unwrap_or(&self.operators);
 89
 90        Label::new(format!("{} -- {} --", pending, mode))
 91            .size(LabelSize::Small)
 92            .line_height_style(LineHeightStyle::UiLabel)
 93            .into_any_element()
 94    }
 95}
 96
 97impl StatusItemView for ModeIndicator {
 98    fn set_active_pane_item(
 99        &mut self,
100        _active_pane_item: Option<&dyn ItemHandle>,
101        _cx: &mut ViewContext<Self>,
102    ) {
103        // nothing to do.
104    }
105}