1use gpui::{Context, Element, Entity, FontWeight, 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();
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();
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 fn update_pending_keys(&mut self, window: &mut Window, cx: &App) {
54 self.pending_keys = window
55 .pending_input_keystrokes()
56 .map(|keystrokes| text_for_keystrokes(keystrokes, cx));
57 }
58
59 fn vim(&self) -> Option<Entity<Vim>> {
60 self.vim.as_ref().and_then(|vim| vim.upgrade())
61 }
62
63 fn current_operators_description(&self, vim: Entity<Vim>, cx: &mut Context<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(vim.operator_stack.iter().map(|item| item.status()))
78 .chain(
79 cx.global::<VimGlobals>()
80 .post_count
81 .map(|count| format!("{}", count)),
82 )
83 .collect::<Vec<_>>()
84 .join("")
85 }
86}
87
88impl Render for ModeIndicator {
89 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
90 let vim = self.vim();
91 let Some(vim) = vim else {
92 return div().hidden().into_any_element();
93 };
94
95 let vim_readable = vim.read(cx);
96 let status_label = vim_readable.status_label.clone();
97 let temp_mode = vim_readable.temp_mode;
98 let mode = vim_readable.mode;
99
100 let theme = cx.theme();
101 let colors = theme.colors();
102 let system_transparent = gpui::hsla(0.0, 0.0, 0.0, 0.0);
103 let vim_mode_text = colors.vim_mode_text;
104 let bg_color = match mode {
105 crate::state::Mode::Normal => colors.vim_normal_background,
106 crate::state::Mode::Insert => colors.vim_insert_background,
107 crate::state::Mode::Replace => colors.vim_replace_background,
108 crate::state::Mode::Visual => colors.vim_visual_background,
109 crate::state::Mode::VisualLine => colors.vim_visual_line_background,
110 crate::state::Mode::VisualBlock => colors.vim_visual_block_background,
111 crate::state::Mode::HelixNormal => colors.vim_helix_normal_background,
112 crate::state::Mode::HelixSelect => colors.vim_helix_select_background,
113 };
114
115 let (label, mode): (SharedString, Option<SharedString>) = if let Some(label) = status_label
116 {
117 (label, None)
118 } else {
119 let mode_str = if temp_mode {
120 format!("(insert) {}", mode)
121 } else {
122 mode.to_string()
123 };
124
125 let current_operators_description = self.current_operators_description(vim.clone(), cx);
126 let pending = self
127 .pending_keys
128 .as_ref()
129 .unwrap_or(¤t_operators_description);
130 let mode = if bg_color != system_transparent {
131 mode_str.into()
132 } else {
133 format!("-- {} --", mode_str).into()
134 };
135 (pending.into(), Some(mode))
136 };
137 h_flex()
138 .gap_1()
139 .when(!label.is_empty(), |el| {
140 el.child(
141 Label::new(label)
142 .line_height_style(LineHeightStyle::UiLabel)
143 .weight(FontWeight::MEDIUM),
144 )
145 })
146 .when_some(mode, |el, mode| {
147 el.child(
148 v_flex()
149 .when(bg_color != system_transparent, |el| el.px_2())
150 // match with other icons at the bottom that use default buttons
151 .h(ButtonSize::Default.rems())
152 .justify_center()
153 .rounded_sm()
154 .bg(bg_color)
155 .child(
156 Label::new(mode)
157 .size(LabelSize::Small)
158 .line_height_style(LineHeightStyle::UiLabel)
159 .weight(FontWeight::MEDIUM)
160 .when(
161 bg_color != system_transparent
162 && vim_mode_text != system_transparent,
163 |el| el.color(Color::Custom(vim_mode_text)),
164 ),
165 ),
166 )
167 })
168 .into_any()
169 }
170}
171
172impl StatusItemView for ModeIndicator {
173 fn set_active_pane_item(
174 &mut self,
175 _active_pane_item: Option<&dyn ItemHandle>,
176 _window: &mut Window,
177 _cx: &mut Context<Self>,
178 ) {
179 }
180}