1use gpui::{
2 elements::{Empty, Label},
3 AnyElement, Element, Entity, Subscription, View, ViewContext,
4};
5use settings::SettingsStore;
6use workspace::{item::ItemHandle, StatusItemView};
7
8use crate::{state::Mode, Vim, VimEvent, VimModeSetting};
9
10pub struct ModeIndicator {
11 pub mode: Option<Mode>,
12 _subscription: Subscription,
13}
14
15impl ModeIndicator {
16 pub fn new(cx: &mut ViewContext<Self>) -> Self {
17 let handle = cx.handle().downgrade();
18
19 let _subscription = cx.subscribe_global::<VimEvent, _>(move |&event, cx| {
20 if let Some(mode_indicator) = handle.upgrade(cx) {
21 match event {
22 VimEvent::ModeChanged { mode } => {
23 mode_indicator.window().update(cx, |cx| {
24 mode_indicator.update(cx, move |mode_indicator, cx| {
25 mode_indicator.set_mode(mode, cx);
26 })
27 });
28 }
29 }
30 }
31 });
32
33 cx.observe_global::<SettingsStore, _>(move |mode_indicator, cx| {
34 if settings::get::<VimModeSetting>(cx).0 {
35 mode_indicator.mode = cx
36 .has_global::<Vim>()
37 .then(|| cx.global::<Vim>().state.mode);
38 } else {
39 mode_indicator.mode.take();
40 }
41 })
42 .detach();
43
44 // Vim doesn't exist in some tests
45 let mode = cx
46 .has_global::<Vim>()
47 .then(|| {
48 let vim = cx.global::<Vim>();
49 vim.enabled.then(|| vim.state.mode)
50 })
51 .flatten();
52
53 Self {
54 mode,
55 _subscription,
56 }
57 }
58
59 pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
60 if self.mode != Some(mode) {
61 self.mode = Some(mode);
62 cx.notify();
63 }
64 }
65}
66
67impl Entity for ModeIndicator {
68 type Event = ();
69}
70
71impl View for ModeIndicator {
72 fn ui_name() -> &'static str {
73 "ModeIndicatorView"
74 }
75
76 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
77 let Some(mode) = self.mode.as_ref() else {
78 return Empty::new().into_any();
79 };
80
81 let theme = &theme::current(cx).workspace.status_bar;
82
83 // we always choose text to be 12 monospace characters
84 // so that as the mode indicator changes, the rest of the
85 // UI stays still.
86 let text = match mode {
87 Mode::Normal => "-- NORMAL --",
88 Mode::Insert => "-- INSERT --",
89 Mode::Visual { line: false } => "-- VISUAL --",
90 Mode::Visual { line: true } => "VISUAL LINE",
91 };
92 Label::new(text, theme.vim_mode_indicator.text.clone())
93 .contained()
94 .with_style(theme.vim_mode_indicator.container)
95 .into_any()
96 }
97}
98
99impl StatusItemView for ModeIndicator {
100 fn set_active_pane_item(
101 &mut self,
102 _active_pane_item: Option<&dyn ItemHandle>,
103 _cx: &mut ViewContext<Self>,
104 ) {
105 // nothing to do.
106 }
107}