diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 564b893489773..ee7a6ef191a6e 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -204,7 +204,8 @@ impl App { type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; -type KeystrokeObserver = Box; +pub(crate) type KeystrokeObserver = + Box bool + 'static>; type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; type ReleaseListener = Box; type NewViewListener = Box; @@ -1050,7 +1051,7 @@ impl AppContext { /// and that this API will not be invoked if the event's propagation is stopped. pub fn observe_keystrokes( &mut self, - f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static, + mut f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static, ) -> Subscription { fn inner( keystroke_observers: &mut SubscriberSet<(), KeystrokeObserver>, @@ -1060,7 +1061,14 @@ impl AppContext { activate(); subscription } - inner(&mut self.keystroke_observers, Box::new(f)) + + inner( + &mut self.keystroke_observers, + Box::new(move |event, cx| { + f(event, cx); + true + }), + ) } /// Register key bindings. diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index dce136bddddb5..57066b0ce64c5 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4,16 +4,17 @@ use crate::{ Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GPUSpecs, Global, GlobalElementId, GlyphId, Hsla, InputHandler, - IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId, - LineLayoutIndex, Model, ModelContext, Modifiers, ModifiersChangedEvent, MonochromeSprite, - MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, - PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, - PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, - Replay, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, - SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, - TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView, - WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, - WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS, + IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, + KeystrokeObserver, LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers, + ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, + Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, + PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, + RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge, ScaledPixels, Scene, + Shadow, SharedString, Size, StrikethroughStyle, Style, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, TransformationMatrix, Underline, + UnderlineStyle, View, VisualContext, WeakView, WindowAppearance, WindowBackgroundAppearance, + WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, + SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::{FxHashMap, FxHashSet}; @@ -1043,8 +1044,7 @@ impl<'a> WindowContext<'a> { action: action.as_ref().map(|action| action.boxed_clone()), }, self, - ); - true + ) }); } @@ -4250,6 +4250,36 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when a keystroke is received by the application + /// in any window. Note that this fires after all other action and event mechanisms have resolved + /// and that this API will not be invoked if the event's propagation is stopped. + pub fn observe_keystrokes( + &mut self, + mut f: impl FnMut(&mut V, &KeystrokeEvent, &mut ViewContext) + 'static, + ) -> Subscription { + fn inner( + keystroke_observers: &mut SubscriberSet<(), KeystrokeObserver>, + handler: KeystrokeObserver, + ) -> Subscription { + let (subscription, activate) = keystroke_observers.insert((), handler); + activate(); + subscription + } + + let view = self.view.downgrade(); + inner( + &mut self.keystroke_observers, + Box::new(move |event, cx| { + if let Some(view) = view.upgrade() { + view.update(cx, |view, cx| f(view, event, cx)); + true + } else { + false + } + }), + ) + } + /// Register a callback to be invoked when the window's pending input changes. pub fn observe_pending_input( &mut self, diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 6e03374c22595..fc5097d845128 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -24,7 +24,7 @@ use editor::{ }; use gpui::{ actions, impl_actions, Action, AppContext, Entity, EventEmitter, KeyContext, KeystrokeEvent, - Render, View, ViewContext, WeakView, + Render, Subscription, View, ViewContext, WeakView, }; use insert::NormalBefore; use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId}; @@ -166,6 +166,8 @@ pub(crate) struct Vim { pub search: SearchState, editor: WeakView, + + _subscriptions: Vec, } // Hack: Vim intercepts events dispatched to a window and updates the view in response. @@ -189,36 +191,32 @@ impl Vim { pub fn new(cx: &mut ViewContext) -> View { let editor = cx.view().clone(); - cx.new_view(|cx: &mut ViewContext| { - cx.subscribe(&editor, |vim, _, event, cx| { - vim.handle_editor_event(event, cx) - }) - .detach(); - - let listener = cx.listener(Vim::observe_keystrokes); - cx.observe_keystrokes(listener).detach(); - - Vim { - mode: Mode::Normal, - last_mode: Mode::Normal, - pre_count: None, - post_count: None, - operator_stack: Vec::new(), - replacements: Vec::new(), - - marks: HashMap::default(), - stored_visual_mode: None, - change_list: Vec::new(), - change_list_position: None, - current_tx: None, - current_anchor: None, - undo_modes: HashMap::default(), - - selected_register: None, - search: SearchState::default(), - - editor: editor.downgrade(), - } + cx.new_view(|cx| Vim { + mode: Mode::Normal, + last_mode: Mode::Normal, + pre_count: None, + post_count: None, + operator_stack: Vec::new(), + replacements: Vec::new(), + + marks: HashMap::default(), + stored_visual_mode: None, + change_list: Vec::new(), + change_list_position: None, + current_tx: None, + current_anchor: None, + undo_modes: HashMap::default(), + + selected_register: None, + search: SearchState::default(), + + editor: editor.downgrade(), + _subscriptions: vec![ + cx.observe_keystrokes(Self::observe_keystrokes), + cx.subscribe(&editor, |this, _, event, cx| { + this.handle_editor_event(event, cx) + }), + ], }) }