1use gpui::{Context, Element, Entity, 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().clone();
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().clone();
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 // TODO:
54 // what's this?
55 fn update_pending_keys(&mut self, window: &mut Window, cx: &App) {
56 self.pending_keys = window
57 .pending_input_keystrokes()
58 .map(|keystrokes| text_for_keystrokes(keystrokes, cx));
59 }
60
61 fn vim(&self) -> Option<Entity<Vim>> {
62 self.vim.as_ref().and_then(|vim| vim.upgrade())
63 }
64
65 fn current_operators_description(&self, vim: Entity<Vim>, cx: &mut Context<Self>) -> String {
66 let recording = Vim::globals(cx)
67 .recording_register
68 .map(|reg| format!("recording @{reg} "))
69 .into_iter();
70
71 let vim = vim.read(cx);
72 recording
73 .chain(
74 cx.global::<VimGlobals>()
75 .pre_count
76 .map(|count| format!("{}", count)),
77 )
78 .chain(vim.selected_register.map(|reg| format!("\"{reg}")))
79 .chain(
80 vim.operator_stack
81 .iter()
82 .map(|item| item.status().to_string()),
83 )
84 .chain(
85 cx.global::<VimGlobals>()
86 .post_count
87 .map(|count| format!("{}", count)),
88 )
89 .collect::<Vec<_>>()
90 .join("")
91 }
92}
93
94impl Render for ModeIndicator {
95 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
96 let vim = self.vim();
97 let Some(vim) = vim else {
98 return div().into_any();
99 };
100
101 let vim_readable = vim.read(cx);
102 let label = if let Some(label) = vim_readable.status_label.clone() {
103 label
104 } else {
105 let mode = if vim_readable.temp_mode {
106 format!("(insert) {}", vim_readable.mode)
107 } else {
108 vim_readable.mode.to_string()
109 };
110
111 let current_operators_description = self.current_operators_description(vim.clone(), cx);
112 let pending = self
113 .pending_keys
114 .as_ref()
115 .unwrap_or(¤t_operators_description);
116 format!("{} -- {} --", pending, mode).into()
117 };
118
119 Label::new(label)
120 .size(LabelSize::Small)
121 .line_height_style(LineHeightStyle::UiLabel)
122 .into_any_element()
123 }
124}
125
126impl StatusItemView for ModeIndicator {
127 fn set_active_pane_item(
128 &mut self,
129 _active_pane_item: Option<&dyn ItemHandle>,
130 _window: &mut Window,
131 _cx: &mut Context<Self>,
132 ) {
133 }
134}