1use gpui::{div, Element, Render, Subscription, View, ViewContext, WeakView};
2use itertools::Itertools;
3use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
4
5use crate::{Vim, VimEvent};
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(vim.pre_count.map(|count| format!("{}", count)))
72 .chain(vim.selected_register.map(|reg| format!("\"{reg}")))
73 .chain(
74 vim.operator_stack
75 .iter()
76 .map(|item| item.status().to_string()),
77 )
78 .chain(vim.post_count.map(|count| format!("{}", count)))
79 .collect::<Vec<_>>()
80 .join("")
81 }
82}
83
84impl Render for ModeIndicator {
85 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
86 let vim = self.vim();
87 let Some(vim) = vim else {
88 return div().into_any();
89 };
90
91 let vim_readable = vim.read(cx);
92 let mode = if vim_readable.temp_mode {
93 format!("(insert) {}", vim_readable.mode)
94 } else {
95 vim_readable.mode.to_string()
96 };
97
98 let current_operators_description = self.current_operators_description(vim.clone(), cx);
99 let pending = self
100 .pending_keys
101 .as_ref()
102 .unwrap_or(¤t_operators_description);
103 Label::new(format!("{} -- {} --", pending, mode))
104 .size(LabelSize::Small)
105 .line_height_style(LineHeightStyle::UiLabel)
106 .into_any_element()
107 }
108}
109
110impl StatusItemView for ModeIndicator {
111 fn set_active_pane_item(
112 &mut self,
113 _active_pane_item: Option<&dyn ItemHandle>,
114 _cx: &mut ViewContext<Self>,
115 ) {
116 }
117}