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.state()
 65            .pre_count
 66            .map(|count| format!("{}", count))
 67            .into_iter()
 68            .chain(vim.state().selected_register.map(|reg| format!("\"{reg}")))
 69            .chain(
 70                vim.state()
 71                    .operator_stack
 72                    .iter()
 73                    .map(|item| item.id().to_string()),
 74            )
 75            .chain(vim.state().post_count.map(|count| format!("{}", count)))
 76            .collect::<Vec<_>>()
 77            .join("")
 78    }
 79}
 80
 81impl Render for ModeIndicator {
 82    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
 83        let Some(mode) = self.mode.as_ref() else {
 84            return div().into_any();
 85        };
 86
 87        let pending = self.pending_keys.as_ref().unwrap_or(&self.operators);
 88
 89        Label::new(format!("{} -- {} --", pending, mode))
 90            .size(LabelSize::Small)
 91            .line_height_style(LineHeightStyle::UiLabel)
 92            .into_any_element()
 93    }
 94}
 95
 96impl StatusItemView for ModeIndicator {
 97    fn set_active_pane_item(
 98        &mut self,
 99        _active_pane_item: Option<&dyn ItemHandle>,
100        _cx: &mut ViewContext<Self>,
101    ) {
102        // nothing to do.
103    }
104}