Skip to content

Commit

Permalink
chore: Fix subscriptions leak. (#597)
Browse files Browse the repository at this point in the history
  • Loading branch information
huacnlee authored Feb 4, 2025
1 parent 3ffbbb0 commit dffb419
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 81 deletions.
10 changes: 6 additions & 4 deletions crates/story/src/title_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::rc::Rc;
use gpui::{
div, px, AnyElement, App, AppContext, ClickEvent, Context, Corner, Entity, FocusHandle, Hsla,
InteractiveElement as _, IntoElement, MouseButton, ParentElement as _, Render, SharedString,
Styled as _, Window,
Styled as _, Subscription, Window,
};
use ui::{
badge::Badge,
Expand All @@ -24,6 +24,7 @@ pub struct AppTitleBar {
font_size_selector: Entity<FontSizeSelector>,
theme_color_picker: Entity<ColorPicker>,
child: Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>,
_subscriptions: Vec<Subscription>,
}

impl AppTitleBar {
Expand All @@ -43,16 +44,16 @@ impl AppTitleBar {
picker.set_value(cx.theme().primary, window, cx);
picker
});
cx.subscribe_in(

let _subscriptions = vec![cx.subscribe_in(
&theme_color_picker,
window,
|this, _, ev: &ColorPickerEvent, window, cx| match ev {
ColorPickerEvent::Change(color) => {
this.set_theme_color(*color, window, cx);
}
},
)
.detach();
)];

Self {
title: title.into(),
Expand All @@ -61,6 +62,7 @@ impl AppTitleBar {
font_size_selector,
theme_color_picker,
child: Rc::new(|_, _| div().into_any_element()),
_subscriptions,
}
}

Expand Down
9 changes: 5 additions & 4 deletions crates/ui/src/color_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use gpui::{
anchored, canvas, deferred, div, prelude::FluentBuilder as _, px, relative, App, AppContext,
Bounds, Context, Corner, ElementId, Entity, EventEmitter, FocusHandle, Focusable, Hsla,
InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
Render, SharedString, StatefulInteractiveElement as _, Styled, Window,
Render, SharedString, StatefulInteractiveElement as _, Styled, Subscription, Window,
};

use crate::{
Expand Down Expand Up @@ -66,13 +66,14 @@ pub struct ColorPicker {

open: bool,
bounds: Bounds<Pixels>,
_subscriptions: Vec<Subscription>,
}

impl ColorPicker {
pub fn new(id: impl Into<ElementId>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let color_input = cx.new(|cx| TextInput::new(window, cx).xsmall());

cx.subscribe_in(
let _subscriptions = vec![cx.subscribe_in(
&color_input,
window,
|this, _, ev: &InputEvent, window, cx| match ev {
Expand All @@ -91,8 +92,7 @@ impl ColorPicker {
}
_ => {}
},
)
.detach();
)];

Self {
id: id.into(),
Expand All @@ -118,6 +118,7 @@ impl ColorPicker {
color_input,
open: false,
bounds: Bounds::default(),
_subscriptions,
}
}

Expand Down
12 changes: 8 additions & 4 deletions crates/ui/src/dropdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use gpui::{
actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App,
AppContext, Bounds, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter,
FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement,
Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Task, WeakEntity, Window,
Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task,
WeakEntity, Window,
};
use rust_i18n::t;

Expand Down Expand Up @@ -252,6 +253,7 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
/// Store the bounds of the input
bounds: Bounds<Pixels>,
disabled: bool,
_subscriptions: Vec<Subscription>,
}

pub struct SearchableVec<T> {
Expand Down Expand Up @@ -352,9 +354,10 @@ where
list
});

cx.on_blur(&list.focus_handle(cx), window, Self::on_blur)
.detach();
cx.on_blur(&focus_handle, window, Self::on_blur).detach();
let _subscriptions = vec![
cx.on_blur(&list.focus_handle(cx), window, Self::on_blur),
cx.on_blur(&focus_handle, window, Self::on_blur),
];

let mut this = Self {
id: id.into(),
Expand All @@ -372,6 +375,7 @@ where
menu_width: Length::Auto,
bounds: Bounds::default(),
disabled: false,
_subscriptions,
};
this.set_selected_index(selected_index, window, cx);
this
Expand Down
48 changes: 24 additions & 24 deletions crates/ui/src/input/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use gpui::{
Context, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle,
ScrollWheelEvent, SharedString, Styled as _, UTF16Selection, Window, WrappedLine,
ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine,
};

// TODO:
Expand Down Expand Up @@ -230,6 +230,7 @@ pub struct TextInput {
pub(crate) scroll_size: gpui::Size<Pixels>,
/// To remember the horizontal column (x-coordinate) of the cursor position.
preferred_x_offset: Option<Pixels>,
_subscriptions: Vec<Subscription>,
}

impl EventEmitter<InputEvent> for TextInput {}
Expand All @@ -239,7 +240,26 @@ impl TextInput {
let focus_handle = cx.focus_handle();
let blink_cursor = cx.new(|_| BlinkCursor::new());
let history = History::new().group_interval(std::time::Duration::from_secs(1));
let input = Self {

let _subscriptions = vec![
// Observe the blink cursor to repaint the view when it changes.
cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
// Blink the cursor when the window is active, pause when it's not.
cx.observe_window_activation(window, |input, window, cx| {
if window.is_window_active() {
let focus_handle = input.focus_handle.clone();
if focus_handle.is_focused(window) {
input.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
});
}
}
}),
cx.on_focus(&focus_handle, window, Self::on_focus),
cx.on_blur(&focus_handle, window, Self::on_blur),
];

Self {
focus_handle: focus_handle.clone(),
text: "".into(),
multi_line: false,
Expand Down Expand Up @@ -272,28 +292,8 @@ impl TextInput {
scrollbar_state: Rc::new(Cell::new(ScrollbarState::default())),
scroll_size: gpui::size(px(0.), px(0.)),
preferred_x_offset: None,
};

// Observe the blink cursor to repaint the view when it changes.
cx.observe(&input.blink_cursor, |_, _, cx| cx.notify())
.detach();
// Blink the cursor when the window is active, pause when it's not.
cx.observe_window_activation(window, |input, window, cx| {
if window.is_window_active() {
let focus_handle = input.focus_handle.clone();
if focus_handle.is_focused(window) {
input.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
});
}
}
})
.detach();

cx.on_focus(&focus_handle, window, Self::on_focus).detach();
cx.on_blur(&focus_handle, window, Self::on_blur).detach();

input
_subscriptions,
}
}

/// Use the text input field as a multi-line Textarea.
Expand Down
47 changes: 24 additions & 23 deletions crates/ui/src/input/otp_input.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use gpui::{
div, prelude::FluentBuilder, px, AnyElement, AppContext as _, Context, Entity, EventEmitter,
FocusHandle, Focusable, InteractiveElement, IntoElement, KeyDownEvent, MouseButton,
MouseDownEvent, ParentElement as _, Render, SharedString, Styled as _, Window,
MouseDownEvent, ParentElement as _, Render, SharedString, Styled as _, Subscription, Window,
};

use crate::{h_flex, v_flex, ActiveTheme, Icon, IconName, Sizable, Size};
Expand Down Expand Up @@ -29,41 +29,42 @@ pub struct OtpInput {
value: SharedString,
blink_cursor: Entity<BlinkCursor>,
size: Size,
_subscriptions: Vec<Subscription>,
}

impl OtpInput {
pub fn new(length: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
let blink_cursor = cx.new(|_| BlinkCursor::new());
let input = Self {

let _subscriptions = vec![
// Observe the blink cursor to repaint the view when it changes.
cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
// Blink the cursor when the window is active, pause when it's not.
cx.observe_window_activation(window, |this, window, cx| {
if window.is_window_active() {
let focus_handle = this.focus_handle.clone();
if focus_handle.is_focused(window) {
this.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
});
}
}
}),
cx.on_focus(&focus_handle, window, Self::on_focus),
cx.on_blur(&focus_handle, window, Self::on_blur),
];

Self {
focus_handle: focus_handle.clone(),
length,
number_of_groups: 2,
value: SharedString::default(),
masked: false,
blink_cursor: blink_cursor.clone(),
size: Size::Medium,
};

// Observe the blink cursor to repaint the view when it changes.
cx.observe(&blink_cursor, |_, _, cx| cx.notify()).detach();
// Blink the cursor when the window is active, pause when it's not.
cx.observe_window_activation(window, |this, window, cx| {
if window.is_window_active() {
let focus_handle = this.focus_handle.clone();
if focus_handle.is_focused(window) {
this.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
});
}
}
})
.detach();

cx.on_focus(&focus_handle, window, Self::on_focus).detach();
cx.on_blur(&focus_handle, window, Self::on_blur).detach();

input
_subscriptions,
}
}

/// Set number of groups in the OTP Input.
Expand Down
12 changes: 7 additions & 5 deletions crates/ui/src/list/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use gpui::{
ListSizingBehavior, MouseButton, ParentElement, Render, SharedString, Styled, Task,
UniformListScrollHandle, Window,
};
use gpui::{px, App, Context, EventEmitter, ScrollStrategy};
use gpui::{px, App, Context, EventEmitter, ScrollStrategy, Subscription};
use smol::Timer;

use super::loading::Loading;
Expand Down Expand Up @@ -158,6 +158,7 @@ pub struct List<D: ListDelegate> {
right_clicked_index: Option<usize>,
_search_task: Task<()>,
_load_more_task: Task<()>,
_query_input_subscription: Subscription,
}

impl<D> List<D>
Expand All @@ -173,8 +174,8 @@ where
.cleanable()
});

cx.subscribe_in(&query_input, window, Self::on_query_input_event)
.detach();
let _query_input_subscription =
cx.subscribe_in(&query_input, window, Self::on_query_input_event);

Self {
focus_handle: cx.focus_handle(),
Expand All @@ -192,6 +193,7 @@ where
size: Size::default(),
_search_task: Task::ready(()),
_load_more_task: Task::ready(()),
_query_input_subscription,
}
}

Expand Down Expand Up @@ -233,8 +235,8 @@ where
window: &mut Window,
cx: &mut Context<Self>,
) {
cx.subscribe_in(&query_input, window, Self::on_query_input_event)
.detach();
self._query_input_subscription =
cx.subscribe_in(&query_input, window, Self::on_query_input_event);
self.query_input = Some(query_input);
}

Expand Down
26 changes: 19 additions & 7 deletions crates/ui/src/notification.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
use std::{any::TypeId, collections::VecDeque, sync::Arc, time::Duration};
use std::{
any::TypeId,
collections::{HashMap, VecDeque},
sync::Arc,
time::Duration,
};

use gpui::{
div, prelude::FluentBuilder, px, Animation, AnimationExt, App, AppContext, ClickEvent, Context,
DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement,
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Window,
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
Window,
};
use smol::Timer;

Expand All @@ -20,7 +26,7 @@ pub enum NotificationType {
Error,
}

#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
pub(crate) enum NotificationId {
Id(TypeId),
IdAndElementId(TypeId, ElementId),
Expand Down Expand Up @@ -286,13 +292,15 @@ pub struct NotificationList {
/// Notifications that will be auto hidden.
pub(crate) notifications: VecDeque<Entity<Notification>>,
expanded: bool,
_subscriptions: HashMap<NotificationId, Subscription>,
}

impl NotificationList {
pub fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
Self {
notifications: VecDeque::new(),
expanded: false,
_subscriptions: HashMap::new(),
}
}

Expand All @@ -310,10 +318,14 @@ impl NotificationList {
self.notifications.retain(|note| note.read(cx).id != id);

let notification = cx.new(|_| notification);
cx.subscribe(&notification, move |view, _, _: &DismissEvent, cx| {
view.notifications.retain(|note| id != note.read(cx).id);
})
.detach();

self._subscriptions.insert(
id.clone(),
cx.subscribe(&notification, move |view, _, _: &DismissEvent, cx| {
view.notifications.retain(|note| id != note.read(cx).id);
view._subscriptions.remove(&id);
}),
);

self.notifications.push_back(notification.clone());
if autohide {
Expand Down
Loading

0 comments on commit dffb419

Please sign in to comment.