Skip to content

Commit

Permalink
Fix keystroke observer leak in vim crate (#17913)
Browse files Browse the repository at this point in the history
Release Notes:

- Fixed a performance problem that happened when using vim mode after
opening and closing many editors

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
  • Loading branch information
3 people committed Sep 16, 2024
1 parent cfe5dd8 commit ecc1ba1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 46 deletions.
14 changes: 11 additions & 3 deletions crates/gpui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ impl App {

type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
pub(crate) type KeystrokeObserver =
Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) -> bool + 'static>;
type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
Expand Down Expand Up @@ -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>,
Expand All @@ -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.
Expand Down
54 changes: 42 additions & 12 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -1043,8 +1044,7 @@ impl<'a> WindowContext<'a> {
action: action.as_ref().map(|action| action.boxed_clone()),
},
self,
);
true
)
});
}

Expand Down Expand Up @@ -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<V>) + '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,
Expand Down
60 changes: 29 additions & 31 deletions crates/vim/src/vim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -166,6 +166,8 @@ pub(crate) struct Vim {
pub search: SearchState,

editor: WeakView<Editor>,

_subscriptions: Vec<Subscription>,
}

// Hack: Vim intercepts events dispatched to a window and updates the view in response.
Expand All @@ -189,36 +191,32 @@ impl Vim {
pub fn new(cx: &mut ViewContext<Editor>) -> View<Self> {
let editor = cx.view().clone();

cx.new_view(|cx: &mut ViewContext<Vim>| {
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)
}),
],
})
}

Expand Down

0 comments on commit ecc1ba1

Please sign in to comment.