diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index d645057259..5384367a29 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -539,6 +539,7 @@ pub struct Window { pub(crate) focus: Option, focus_enabled: bool, pending_input: Option, + pending_input_observers: SubscriberSet<(), AnyObserver>, prompt: Option, } @@ -810,6 +811,7 @@ impl Window { focus: None, focus_enabled: true, pending_input: None, + pending_input_observers: SubscriberSet::new(), prompt: None, }) } @@ -3128,16 +3130,20 @@ impl<'a> WindowContext<'a> { let Some(currently_pending) = cx.window.pending_input.take() else { return; }; - cx.replay_pending_input(currently_pending) + cx.pending_input_changed(); + cx.replay_pending_input(currently_pending); }) .log_err(); })); self.window.pending_input = Some(currently_pending); + self.pending_input_changed(); self.propagate_event = false; + return; } else if let Some(currently_pending) = self.window.pending_input.take() { + self.pending_input_changed(); if bindings .iter() .all(|binding| !currently_pending.used_by_binding(binding)) @@ -3173,6 +3179,13 @@ impl<'a> WindowContext<'a> { self.dispatch_keystroke_observers(event, None); } + fn pending_input_changed(&mut self) { + self.window + .pending_input_observers + .clone() + .retain(&(), |callback| callback(self)); + } + fn dispatch_key_down_up_event( &mut self, event: &dyn Any, @@ -3230,6 +3243,14 @@ impl<'a> WindowContext<'a> { .has_pending_keystrokes() } + /// Returns the currently pending input keystrokes that might result in a multi-stroke key binding. + pub fn pending_input_keystrokes(&self) -> Option<&[Keystroke]> { + self.window + .pending_input + .as_ref() + .map(|pending_input| pending_input.keystrokes.as_slice()) + } + fn replay_pending_input(&mut self, currently_pending: PendingInput) { let node_id = self .window @@ -4037,6 +4058,20 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the window's pending input changes. + pub fn observe_pending_input( + &mut self, + mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let (subscription, activate) = self.window.pending_input_observers.insert( + (), + Box::new(move |cx| view.update(cx, |view, cx| callback(view, cx)).is_ok()), + ); + activate(); + subscription + } + /// Register a listener to be called when the given focus handle receives focus. /// Returns a subscription and persists until the subscription is dropped. pub fn on_focus( diff --git a/crates/vim/src/mode_indicator.rs b/crates/vim/src/mode_indicator.rs index 784ac03f33..32b38b2bea 100644 --- a/crates/vim/src/mode_indicator.rs +++ b/crates/vim/src/mode_indicator.rs @@ -1,4 +1,5 @@ use gpui::{div, Element, Render, Subscription, ViewContext}; +use itertools::Itertools; use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView}; use crate::{state::Mode, Vim}; @@ -7,29 +8,33 @@ use crate::{state::Mode, Vim}; pub struct ModeIndicator { pub(crate) mode: Option, pub(crate) operators: String, - _subscription: Subscription, + pending_keys: Option, + _subscriptions: Vec, } impl ModeIndicator { /// Construct a new mode indicator in this window. pub fn new(cx: &mut ViewContext) -> Self { - let _subscription = cx.observe_global::(|this, cx| this.update_mode(cx)); + let _subscriptions = vec![ + cx.observe_global::(|this, cx| this.update_mode(cx)), + cx.observe_pending_input(|this, cx| { + this.update_pending_keys(cx); + cx.notify(); + }), + ]; + let mut this = Self { mode: None, operators: "".to_string(), - _subscription, + pending_keys: None, + _subscriptions, }; this.update_mode(cx); this } fn update_mode(&mut self, cx: &mut ViewContext) { - // Vim doesn't exist in some tests - let Some(vim) = cx.try_global::() else { - return; - }; - - if vim.enabled { + if let Some(vim) = self.vim(cx) { self.mode = Some(vim.state().mode); self.operators = self.current_operators_description(&vim); } else { @@ -37,6 +42,24 @@ impl ModeIndicator { } } + fn update_pending_keys(&mut self, cx: &mut ViewContext) { + if self.vim(cx).is_some() { + self.pending_keys = cx.pending_input_keystrokes().map(|keystrokes| { + keystrokes + .iter() + .map(|keystroke| format!("{}", keystroke)) + .join(" ") + }); + } else { + self.pending_keys = None; + } + } + + fn vim<'a>(&self, cx: &'a mut ViewContext) -> Option<&'a Vim> { + // In some tests Vim isn't enabled, so we use try_global. + cx.try_global::().filter(|vim| vim.enabled) + } + fn current_operators_description(&self, vim: &Vim) -> String { vim.state() .pre_count @@ -61,7 +84,9 @@ impl Render for ModeIndicator { return div().into_any(); }; - Label::new(format!("{} -- {} --", self.operators, mode)) + let pending = self.pending_keys.as_ref().unwrap_or(&self.operators); + + Label::new(format!("{} -- {} --", pending, mode)) .size(LabelSize::Small) .line_height_style(LineHeightStyle::UiLabel) .into_any_element()