mode_indicator.rs

  1use gpui::{div, Element, Render, Subscription, View, ViewContext, WeakView};
  2use itertools::Itertools;
  3use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
  4
  5use crate::{Vim, VimEvent, VimGlobals};
  6
  7/// The ModeIndicator displays the current mode in the status bar.
  8pub struct ModeIndicator {
  9    vim: Option<WeakView<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(cx: &mut ViewContext<Self>) -> Self {
 17        cx.observe_pending_input(|this, cx| {
 18            this.update_pending_keys(cx);
 19            cx.notify();
 20        })
 21        .detach();
 22
 23        let handle = cx.view().clone();
 24        let window = cx.window_handle();
 25        cx.observe_new_views::<Vim>(move |_, cx| {
 26            if cx.window_handle() != window {
 27                return;
 28            }
 29            let vim = cx.view().clone();
 30            handle.update(cx, |_, cx| {
 31                cx.subscribe(&vim, |mode_indicator, vim, event, cx| match event {
 32                    VimEvent::Focused => {
 33                        mode_indicator.vim_subscription =
 34                            Some(cx.observe(&vim, |_, _, cx| cx.notify()));
 35                        mode_indicator.vim = Some(vim.downgrade());
 36                    }
 37                })
 38                .detach()
 39            })
 40        })
 41        .detach();
 42
 43        Self {
 44            vim: None,
 45            pending_keys: None,
 46            vim_subscription: None,
 47        }
 48    }
 49
 50    fn update_pending_keys(&mut self, cx: &mut ViewContext<Self>) {
 51        self.pending_keys = cx.pending_input_keystrokes().map(|keystrokes| {
 52            keystrokes
 53                .iter()
 54                .map(|keystroke| format!("{}", keystroke))
 55                .join(" ")
 56        });
 57    }
 58
 59    fn vim(&self) -> Option<View<Vim>> {
 60        self.vim.as_ref().and_then(|vim| vim.upgrade())
 61    }
 62
 63    fn current_operators_description(&self, vim: View<Vim>, cx: &mut ViewContext<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(
 78                vim.operator_stack
 79                    .iter()
 80                    .map(|item| item.status().to_string()),
 81            )
 82            .chain(
 83                cx.global::<VimGlobals>()
 84                    .post_count
 85                    .map(|count| format!("{}", count)),
 86            )
 87            .collect::<Vec<_>>()
 88            .join("")
 89    }
 90}
 91
 92impl Render for ModeIndicator {
 93    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 94        let vim = self.vim();
 95        let Some(vim) = vim else {
 96            return div().into_any();
 97        };
 98
 99        let vim_readable = vim.read(cx);
100        let label = if let Some(label) = vim_readable.status_label.clone() {
101            label
102        } else {
103            let mode = if vim_readable.temp_mode {
104                format!("(insert) {}", vim_readable.mode)
105            } else {
106                vim_readable.mode.to_string()
107            };
108
109            let current_operators_description = self.current_operators_description(vim.clone(), cx);
110            let pending = self
111                .pending_keys
112                .as_ref()
113                .unwrap_or(&current_operators_description);
114            format!("{} -- {} --", pending, mode).into()
115        };
116
117        Label::new(label)
118            .size(LabelSize::Small)
119            .line_height_style(LineHeightStyle::UiLabel)
120            .into_any_element()
121    }
122}
123
124impl StatusItemView for ModeIndicator {
125    fn set_active_pane_item(
126        &mut self,
127        _active_pane_item: Option<&dyn ItemHandle>,
128        _cx: &mut ViewContext<Self>,
129    ) {
130    }
131}