Switch from associated type to generic on EventEmitter trait (#3281)

Mikayla Maki created

This removes a lot of ad-hoc event translation code in the workspace and
replaces it with a GPUI feature and trait bounds.

TODO:
- [x] Proof out idea
- [x] Convert the workspace
- [x] Convert the rest of the app

Release Notes:

- N/A

Change summary

Cargo.lock                                |   1 
crates/call2/src/call2.rs                 |   5 
crates/call2/src/room.rs                  |   4 
crates/channel2/src/channel_buffer.rs     |   4 
crates/channel2/src/channel_chat.rs       |   4 
crates/channel2/src/channel_store.rs      |   4 
crates/client2/src/user.rs                |   4 
crates/copilot2/src/copilot2.rs           |   4 
crates/editor2/src/editor.rs              |  47 ++++++---
crates/editor2/src/items.rs               |  84 +++++------------
crates/go_to_line2/src/go_to_line.rs      |  16 --
crates/gpui2/src/app.rs                   |  54 +++++++---
crates/gpui2/src/app/model_context.rs     |  19 ++-
crates/gpui2/src/app/test_context.rs      |  13 +-
crates/gpui2/src/gpui2.rs                 |   6 
crates/gpui2/src/subscription.rs          |   2 
crates/gpui2/src/window.rs                |  85 ++++++++++--------
crates/language2/src/buffer.rs            |   4 
crates/multi_buffer2/src/multi_buffer2.rs |   4 
crates/picker2/Cargo.toml                 |   2 
crates/project2/src/project2.rs           |   4 
crates/project2/src/worktree.rs           |   4 
crates/terminal2/src/terminal2.rs         |   4 
crates/workspace2/src/dock.rs             | 113 +++++++++---------------
crates/workspace2/src/item.rs             | 106 +++++++++++-----------
crates/workspace2/src/modal_layer.rs      |  18 +--
crates/workspace2/src/notifications.rs    |  38 +++----
crates/workspace2/src/pane.rs             |  76 ++++++++--------
crates/workspace2/src/searchable.rs       |  23 +---
crates/workspace2/src/toolbar.rs          |  86 ++----------------
crates/workspace2/src/workspace2.rs       |  77 ++++++++--------
31 files changed, 394 insertions(+), 521 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6109,7 +6109,6 @@ dependencies = [
  "settings2",
  "theme2",
  "util",
- "workspace2",
 ]
 
 [[package]]

crates/call2/src/call2.rs 🔗

@@ -17,6 +17,7 @@ use gpui::{
 };
 use postage::watch;
 use project::Project;
+use room::Event;
 use settings::Settings;
 use std::sync::Arc;
 
@@ -85,9 +86,7 @@ pub struct ActiveCall {
     _subscriptions: Vec<client::Subscription>,
 }
 
-impl EventEmitter for ActiveCall {
-    type Event = room::Event;
-}
+impl EventEmitter<Event> for ActiveCall {}
 
 impl ActiveCall {
     fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {

crates/call2/src/room.rs 🔗

@@ -79,9 +79,7 @@ pub struct Room {
     maintain_connection: Option<Task<Option<()>>>,
 }
 
-impl EventEmitter for Room {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Room {}
 
 impl Room {
     pub fn channel_id(&self) -> Option<u64> {

crates/channel2/src/channel_buffer.rs 🔗

@@ -38,9 +38,7 @@ pub enum ChannelBufferEvent {
     ChannelChanged,
 }
 
-impl EventEmitter for ChannelBuffer {
-    type Event = ChannelBufferEvent;
-}
+impl EventEmitter<ChannelBufferEvent> for ChannelBuffer {}
 
 impl ChannelBuffer {
     pub(crate) async fn new(

crates/channel2/src/channel_chat.rs 🔗

@@ -76,9 +76,7 @@ pub enum ChannelChatEvent {
     },
 }
 
-impl EventEmitter for ChannelChat {
-    type Event = ChannelChatEvent;
-}
+impl EventEmitter<ChannelChatEvent> for ChannelChat {}
 pub fn init(client: &Arc<Client>) {
     client.add_model_message_handler(ChannelChat::handle_message_sent);
     client.add_model_message_handler(ChannelChat::handle_message_removed);

crates/channel2/src/channel_store.rs 🔗

@@ -114,9 +114,7 @@ pub enum ChannelEvent {
     ChannelRenamed(ChannelId),
 }
 
-impl EventEmitter for ChannelStore {
-    type Event = ChannelEvent;
-}
+impl EventEmitter<ChannelEvent> for ChannelStore {}
 
 enum OpenedModelHandle<E> {
     Open(WeakModel<E>),

crates/client2/src/user.rs 🔗

@@ -103,9 +103,7 @@ pub enum ContactEventKind {
     Cancelled,
 }
 
-impl EventEmitter for UserStore {
-    type Event = Event;
-}
+impl EventEmitter<Event> for UserStore {}
 
 enum UpdateContacts {
     Update(proto::UpdateContacts),

crates/copilot2/src/copilot2.rs 🔗

@@ -284,9 +284,7 @@ pub enum Event {
     CopilotLanguageServerStarted,
 }
 
-impl EventEmitter for Copilot {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Copilot {}
 
 impl Copilot {
     pub fn global(cx: &AppContext) -> Option<Model<Self>> {

crates/editor2/src/editor.rs 🔗

@@ -96,7 +96,9 @@ use theme::{
     ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
 };
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
-use workspace::{ItemNavHistory, SplitDirection, ViewId, Workspace};
+use workspace::{
+    item::ItemEvent, searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace,
+};
 
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
 const MAX_LINE_LEN: usize = 1024;
@@ -1904,7 +1906,8 @@ impl Editor {
             if let Some(project) = project.as_ref() {
                 if buffer.read(cx).is_singleton() {
                     project_subscriptions.push(cx.observe(project, |_, _, cx| {
-                        cx.emit(Event::TitleChanged);
+                        cx.emit(ItemEvent::UpdateTab);
+                        cx.emit(ItemEvent::UpdateBreadcrumbs);
                     }));
                 }
                 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
@@ -2363,6 +2366,15 @@ impl Editor {
 
         self.blink_manager.update(cx, BlinkManager::pause_blinking);
         cx.emit(Event::SelectionsChanged { local });
+
+        if self.selections.disjoint_anchors().len() == 1 {
+            cx.emit(SearchEvent::ActiveMatchChanged)
+        }
+
+        if local {
+            cx.emit(ItemEvent::UpdateBreadcrumbs);
+        }
+
         cx.notify();
     }
 
@@ -8762,6 +8774,9 @@ impl Editor {
                     self.update_visible_copilot_suggestion(cx);
                 }
                 cx.emit(Event::BufferEdited);
+                cx.emit(ItemEvent::Edit);
+                cx.emit(ItemEvent::UpdateBreadcrumbs);
+                cx.emit(SearchEvent::MatchesInvalidated);
 
                 if *sigleton_buffer_edited {
                     if let Some(project) = &self.project {
@@ -8808,13 +8823,20 @@ impl Editor {
                 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
                 cx.emit(Event::ExcerptsRemoved { ids: ids.clone() })
             }
-            multi_buffer::Event::Reparsed => cx.emit(Event::Reparsed),
-            multi_buffer::Event::DirtyChanged => cx.emit(Event::DirtyChanged),
-            multi_buffer::Event::Saved => cx.emit(Event::Saved),
-            multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged),
-            multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged),
+            multi_buffer::Event::Reparsed => {
+                cx.emit(ItemEvent::UpdateBreadcrumbs);
+            }
+            multi_buffer::Event::DirtyChanged => {
+                cx.emit(ItemEvent::UpdateTab);
+            }
+            multi_buffer::Event::Saved
+            | multi_buffer::Event::FileHandleChanged
+            | multi_buffer::Event::Reloaded => {
+                cx.emit(ItemEvent::UpdateTab);
+                cx.emit(ItemEvent::UpdateBreadcrumbs);
+            }
             multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged),
-            multi_buffer::Event::Closed => cx.emit(Event::Closed),
+            multi_buffer::Event::Closed => cx.emit(ItemEvent::CloseItem),
             multi_buffer::Event::DiagnosticsUpdated => {
                 self.refresh_active_diagnostics(cx);
             }
@@ -9380,12 +9402,8 @@ pub enum Event {
     },
     BufferEdited,
     Edited,
-    Reparsed,
     Focused,
     Blurred,
-    DirtyChanged,
-    Saved,
-    TitleChanged,
     DiffBaseChanged,
     SelectionsChanged {
         local: bool,
@@ -9394,7 +9412,6 @@ pub enum Event {
         local: bool,
         autoscroll: bool,
     },
-    Closed,
 }
 
 pub struct EditorFocused(pub View<Editor>);
@@ -9409,9 +9426,7 @@ pub struct EditorReleased(pub WeakView<Editor>);
 //     }
 // }
 //
-impl EventEmitter for Editor {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Editor {}
 
 impl Render for Editor {
     type Element = EditorElement;

crates/editor2/src/items.rs 🔗

@@ -7,9 +7,9 @@ use anyhow::{anyhow, Context, Result};
 use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
-    div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, FocusHandle, Model,
-    ParentElement, Pixels, SharedString, Styled, Subscription, Task, View, ViewContext,
-    VisualContext, WeakView,
+    div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
+    FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
+    ViewContext, VisualContext, WeakView,
 };
 use language::{
     proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
@@ -29,7 +29,7 @@ use std::{
 use text::Selection;
 use theme::{ActiveTheme, Theme};
 use util::{paths::PathExt, ResultExt, TryFutureExt};
-use workspace::item::{BreadcrumbText, FollowableItemHandle};
+use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
 use workspace::{
     item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
     searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -38,7 +38,26 @@ use workspace::{
 
 pub const MAX_TAB_TITLE_LEN: usize = 24;
 
+impl FollowableEvents for Event {
+    fn to_follow_event(&self) -> Option<workspace::item::FollowEvent> {
+        match self {
+            Event::Edited => Some(FollowEvent::Unfollow),
+            Event::SelectionsChanged { local } | Event::ScrollPositionChanged { local, .. } => {
+                if *local {
+                    Some(FollowEvent::Unfollow)
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        }
+    }
+}
+
+impl EventEmitter<ItemEvent> for Editor {}
+
 impl FollowableItem for Editor {
+    type FollowableEvent = Event;
     fn remote_id(&self) -> Option<ViewId> {
         self.remote_id
     }
@@ -217,7 +236,7 @@ impl FollowableItem for Editor {
 
     fn add_event_to_update_proto(
         &self,
-        event: &Self::Event,
+        event: &Self::FollowableEvent,
         update: &mut Option<proto::update_view::Variant>,
         cx: &AppContext,
     ) -> bool {
@@ -292,15 +311,6 @@ impl FollowableItem for Editor {
         })
     }
 
-    fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool {
-        match event {
-            Event::Edited => true,
-            Event::SelectionsChanged { local } => *local,
-            Event::ScrollPositionChanged { local, .. } => *local,
-            _ => false,
-        }
-    }
-
     fn is_project_item(&self, _cx: &AppContext) -> bool {
         true
     }
@@ -739,32 +749,6 @@ impl Item for Editor {
         })
     }
 
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        let mut result = SmallVec::new();
-        match event {
-            Event::Closed => result.push(ItemEvent::CloseItem),
-            Event::Saved | Event::TitleChanged => {
-                result.push(ItemEvent::UpdateTab);
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            Event::Reparsed => {
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            Event::SelectionsChanged { local } if *local => {
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            Event::DirtyChanged => {
-                result.push(ItemEvent::UpdateTab);
-            }
-            Event::BufferEdited => {
-                result.push(ItemEvent::Edit);
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            _ => {}
-        }
-        result
-    }
-
     fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
         Some(Box::new(handle.clone()))
     }
@@ -913,28 +897,12 @@ impl ProjectItem for Editor {
     }
 }
 
+impl EventEmitter<SearchEvent> for Editor {}
+
 pub(crate) enum BufferSearchHighlights {}
 impl SearchableItem for Editor {
     type Match = Range<Anchor>;
 
-    fn to_search_event(
-        &mut self,
-        event: &Self::Event,
-        _: &mut ViewContext<Self>,
-    ) -> Option<SearchEvent> {
-        match event {
-            Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
-            Event::SelectionsChanged { .. } => {
-                if self.selections.disjoint_anchors().len() == 1 {
-                    Some(SearchEvent::ActiveMatchChanged)
-                } else {
-                    None
-                }
-            }
-            _ => None,
-        }
-    }
-
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
         todo!()
         // self.clear_background_highlights::<BufferSearchHighlights>(cx);

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -8,7 +8,7 @@ use text::{Bias, Point};
 use theme::ActiveTheme;
 use ui::{h_stack, modal, v_stack, Label, LabelColor};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::{Modal, ModalEvent, Workspace};
+use workspace::{ModalEvent, Workspace};
 
 actions!(Toggle);
 
@@ -41,9 +41,9 @@ pub enum Event {
     Dismissed,
 }
 
-impl EventEmitter for GoToLine {
-    type Event = Event;
-}
+impl EventEmitter<Event> for GoToLine {}
+
+impl EventEmitter<ModalEvent> for GoToLine {}
 
 impl GoToLine {
     pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
@@ -149,14 +149,6 @@ impl GoToLine {
     }
 }
 
-impl Modal for GoToLine {
-    fn to_modal_event(&self, e: &Self::Event) -> Option<ModalEvent> {
-        match e {
-            Event::Dismissed => Some(ModalEvent::Dismissed),
-        }
-    }
-}
-
 impl Render for GoToLine {
     type Element = Div<Self, StatefulInteractivity<Self>>;
 

crates/gpui2/src/app.rs 🔗

@@ -203,7 +203,8 @@ pub struct AppContext {
     pub(crate) pending_notifications: HashSet<EntityId>,
     pub(crate) pending_global_notifications: HashSet<TypeId>,
     pub(crate) observers: SubscriberSet<EntityId, Handler>,
-    pub(crate) event_listeners: SubscriberSet<EntityId, Listener>,
+    // TypeId is the type of the event that the listener callback expects
+    pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
     pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
     pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
     pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
@@ -354,14 +355,15 @@ impl AppContext {
         )
     }
 
-    pub fn subscribe<T, E>(
+    pub fn subscribe<T, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(E, &T::Event, &mut AppContext) + 'static,
+        mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
     ) -> Subscription
     where
-        T: 'static + EventEmitter,
+        T: 'static + EventEmitter<Evt>,
         E: Entity<T>,
+        Evt: 'static,
     {
         self.subscribe_internal(entity, move |entity, event, cx| {
             on_event(entity, event, cx);
@@ -369,27 +371,32 @@ impl AppContext {
         })
     }
 
-    pub(crate) fn subscribe_internal<T, E>(
+    pub(crate) fn subscribe_internal<T, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(E, &T::Event, &mut AppContext) -> bool + 'static,
+        mut on_event: impl FnMut(E, &Evt, &mut AppContext) -> bool + 'static,
     ) -> Subscription
     where
-        T: 'static + EventEmitter,
+        T: 'static + EventEmitter<Evt>,
         E: Entity<T>,
+        Evt: 'static,
     {
         let entity_id = entity.entity_id();
         let entity = entity.downgrade();
+
         self.event_listeners.insert(
             entity_id,
-            Box::new(move |event, cx| {
-                let event: &T::Event = event.downcast_ref().expect("invalid event type");
-                if let Some(handle) = E::upgrade_from(&entity) {
-                    on_event(handle, event, cx)
-                } else {
-                    false
-                }
-            }),
+            (
+                TypeId::of::<Evt>(),
+                Box::new(move |event, cx| {
+                    let event: &Evt = event.downcast_ref().expect("invalid event type");
+                    if let Some(handle) = E::upgrade_from(&entity) {
+                        on_event(handle, event, cx)
+                    } else {
+                        false
+                    }
+                }),
+            ),
         )
     }
 
@@ -512,7 +519,11 @@ impl AppContext {
                     Effect::Notify { emitter } => {
                         self.apply_notify_effect(emitter);
                     }
-                    Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event),
+                    Effect::Emit {
+                        emitter,
+                        event_type,
+                        event,
+                    } => self.apply_emit_effect(emitter, event_type, event),
                     Effect::FocusChanged {
                         window_handle,
                         focused,
@@ -608,10 +619,16 @@ impl AppContext {
             .retain(&emitter, |handler| handler(self));
     }
 
-    fn apply_emit_effect(&mut self, emitter: EntityId, event: Box<dyn Any>) {
+    fn apply_emit_effect(&mut self, emitter: EntityId, event_type: TypeId, event: Box<dyn Any>) {
         self.event_listeners
             .clone()
-            .retain(&emitter, |handler| handler(event.as_ref(), self));
+            .retain(&emitter, |(stored_type, handler)| {
+                if *stored_type == event_type {
+                    handler(event.as_ref(), self)
+                } else {
+                    true
+                }
+            });
     }
 
     fn apply_focus_changed_effect(
@@ -999,6 +1016,7 @@ pub(crate) enum Effect {
     },
     Emit {
         emitter: EntityId,
+        event_type: TypeId,
         event: Box<dyn Any>,
     },
     FocusChanged {

crates/gpui2/src/app/model_context.rs 🔗

@@ -59,15 +59,16 @@ impl<'a, T: 'static> ModelContext<'a, T> {
         })
     }
 
-    pub fn subscribe<T2, E>(
+    pub fn subscribe<T2, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + 'static,
+        mut on_event: impl FnMut(&mut T, E, &Evt, &mut ModelContext<'_, T>) + 'static,
     ) -> Subscription
     where
         T: 'static,
-        T2: 'static + EventEmitter,
+        T2: 'static + EventEmitter<Evt>,
         E: Entity<T2>,
+        Evt: 'static,
     {
         let this = self.weak_model();
         self.app.subscribe_internal(entity, move |e, event, cx| {
@@ -189,13 +190,15 @@ impl<'a, T: 'static> ModelContext<'a, T> {
     }
 }
 
-impl<'a, T> ModelContext<'a, T>
-where
-    T: EventEmitter,
-{
-    pub fn emit(&mut self, event: T::Event) {
+impl<'a, T> ModelContext<'a, T> {
+    pub fn emit<Evt>(&mut self, event: Evt)
+    where
+        T: EventEmitter<Evt>,
+        Evt: 'static,
+    {
         self.app.pending_effects.push_back(Effect::Emit {
             emitter: self.model_state.entity_id,
+            event_type: TypeId::of::<Evt>(),
             event: Box::new(event),
         });
     }

crates/gpui2/src/app/test_context.rs 🔗

@@ -197,12 +197,12 @@ impl TestAppContext {
         rx
     }
 
-    pub fn events<T: 'static + EventEmitter>(
+    pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
         &mut self,
         entity: &Model<T>,
-    ) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
+    ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
     where
-        T::Event: 'static + Clone,
+        Evt: 'static + Clone,
     {
         let (tx, rx) = futures::channel::mpsc::unbounded();
         entity
@@ -240,10 +240,11 @@ impl TestAppContext {
     }
 }
 
-impl<T: Send + EventEmitter> Model<T> {
-    pub fn next_event(&self, cx: &mut TestAppContext) -> T::Event
+impl<T: Send> Model<T> {
+    pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
     where
-        T::Event: Send + Clone,
+        Evt: Send + Clone + 'static,
+        T: EventEmitter<Evt>,
     {
         let (tx, mut rx) = futures::channel::mpsc::unbounded();
         let _subscription = self.update(cx, |_, cx| {

crates/gpui2/src/gpui2.rs 🔗

@@ -138,6 +138,8 @@ pub trait Entity<T>: Sealed {
         Self: Sized;
 }
 
+pub trait EventEmitter<E: Any>: 'static {}
+
 pub enum GlobalKey {
     Numeric(usize),
     View(EntityId),
@@ -171,10 +173,6 @@ where
     }
 }
 
-pub trait EventEmitter: 'static {
-    type Event: Any;
-}
-
 pub trait Flatten<T> {
     fn flatten(self) -> Result<T>;
 }

crates/gpui2/src/subscription.rs 🔗

@@ -75,6 +75,8 @@ where
             .flatten()
     }
 
+    /// Call the given callback for each subscriber to the given emitter.
+    /// If the callback returns false, the subscriber is removed.
     pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
     where
         F: FnMut(&mut Callback) -> bool,

crates/gpui2/src/window.rs 🔗

@@ -438,33 +438,37 @@ impl<'a> WindowContext<'a> {
         });
     }
 
-    pub fn subscribe<Emitter, E>(
+    pub fn subscribe<Emitter, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + 'static,
+        mut on_event: impl FnMut(E, &Evt, &mut WindowContext<'_>) + 'static,
     ) -> Subscription
     where
-        Emitter: EventEmitter,
+        Emitter: EventEmitter<Evt>,
         E: Entity<Emitter>,
+        Evt: 'static,
     {
         let entity_id = entity.entity_id();
         let entity = entity.downgrade();
         let window_handle = self.window.handle;
         self.app.event_listeners.insert(
             entity_id,
-            Box::new(move |event, cx| {
-                window_handle
-                    .update(cx, |_, cx| {
-                        if let Some(handle) = E::upgrade_from(&entity) {
-                            let event = event.downcast_ref().expect("invalid event type");
-                            on_event(handle, event, cx);
-                            true
-                        } else {
-                            false
-                        }
-                    })
-                    .unwrap_or(false)
-            }),
+            (
+                TypeId::of::<Evt>(),
+                Box::new(move |event, cx| {
+                    window_handle
+                        .update(cx, |_, cx| {
+                            if let Some(handle) = E::upgrade_from(&entity) {
+                                let event = event.downcast_ref().expect("invalid event type");
+                                on_event(handle, event, cx);
+                                true
+                            } else {
+                                false
+                            }
+                        })
+                        .unwrap_or(false)
+                }),
+            ),
         )
     }
 
@@ -1817,14 +1821,15 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         )
     }
 
-    pub fn subscribe<V2, E>(
+    pub fn subscribe<V2, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, V>) + 'static,
+        mut on_event: impl FnMut(&mut V, E, &Evt, &mut ViewContext<'_, V>) + 'static,
     ) -> Subscription
     where
-        V2: EventEmitter,
+        V2: EventEmitter<Evt>,
         E: Entity<V2>,
+        Evt: 'static,
     {
         let view = self.view().downgrade();
         let entity_id = entity.entity_id();
@@ -1832,19 +1837,22 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         let window_handle = self.window.handle;
         self.app.event_listeners.insert(
             entity_id,
-            Box::new(move |event, cx| {
-                window_handle
-                    .update(cx, |_, cx| {
-                        if let Some(handle) = E::upgrade_from(&handle) {
-                            let event = event.downcast_ref().expect("invalid event type");
-                            view.update(cx, |this, cx| on_event(this, handle, event, cx))
-                                .is_ok()
-                        } else {
-                            false
-                        }
-                    })
-                    .unwrap_or(false)
-            }),
+            (
+                TypeId::of::<Evt>(),
+                Box::new(move |event, cx| {
+                    window_handle
+                        .update(cx, |_, cx| {
+                            if let Some(handle) = E::upgrade_from(&handle) {
+                                let event = event.downcast_ref().expect("invalid event type");
+                                view.update(cx, |this, cx| on_event(this, handle, event, cx))
+                                    .is_ok()
+                            } else {
+                                false
+                            }
+                        })
+                        .unwrap_or(false)
+                }),
+            ),
         )
     }
 
@@ -2176,15 +2184,16 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     }
 }
 
-impl<V> ViewContext<'_, V>
-where
-    V: EventEmitter,
-    V::Event: 'static,
-{
-    pub fn emit(&mut self, event: V::Event) {
+impl<V> ViewContext<'_, V> {
+    pub fn emit<Evt>(&mut self, event: Evt)
+    where
+        Evt: 'static,
+        V: EventEmitter<Evt>,
+    {
         let emitter = self.view.model.entity_id;
         self.app.push_effect(Effect::Emit {
             emitter,
+            event_type: TypeId::of::<Evt>(),
             event: Box::new(event),
         });
     }

crates/language2/src/buffer.rs 🔗

@@ -1815,9 +1815,7 @@ impl Buffer {
     }
 }
 
-impl EventEmitter for Buffer {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Buffer {}
 
 impl Deref for Buffer {
     type Target = TextBuffer;

crates/multi_buffer2/src/multi_buffer2.rs 🔗

@@ -1872,9 +1872,7 @@ impl MultiBuffer {
     }
 }
 
-impl EventEmitter for MultiBuffer {
-    type Event = Event;
-}
+impl EventEmitter<Event> for MultiBuffer {}
 
 impl MultiBufferSnapshot {
     pub fn text(&self) -> String {

crates/picker2/Cargo.toml 🔗

@@ -15,7 +15,6 @@ menu = { package = "menu2", path = "../menu2" }
 settings = { package = "settings2", path = "../settings2" }
 util = { path = "../util" }
 theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2" }
 
 parking_lot.workspace = true
 
@@ -23,6 +22,5 @@ parking_lot.workspace = true
 editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 serde_json.workspace = true
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
 ctor.workspace = true
 env_logger.workspace = true

crates/project2/src/project2.rs 🔗

@@ -9062,9 +9062,7 @@ impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
     }
 }
 
-impl EventEmitter for Project {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Project {}
 
 impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
     fn from((worktree_id, path): (WorktreeId, P)) -> Self {

crates/project2/src/worktree.rs 🔗

@@ -281,9 +281,7 @@ pub enum Event {
     UpdatedGitRepositories(UpdatedGitRepositoriesSet),
 }
 
-impl EventEmitter for Worktree {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Worktree {}
 
 impl Worktree {
     pub async fn local(

crates/terminal2/src/terminal2.rs 🔗

@@ -1351,9 +1351,7 @@ impl Drop for Terminal {
     }
 }
 
-impl EventEmitter for Terminal {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Terminal {}
 
 /// Based on alacritty/src/display/hint.rs > regex_match_at
 /// Retrieve the match, if the specified point is inside the content matching the regex.

crates/workspace2/src/dock.rs 🔗

@@ -7,7 +7,16 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 
-pub trait Panel: Render + EventEmitter {
+pub enum PanelEvent {
+    ChangePosition,
+    ZoomIn,
+    ZoomOut,
+    Activate,
+    Close,
+    Focus,
+}
+
+pub trait Panel: Render + EventEmitter<PanelEvent> {
     fn persistent_name(&self) -> &'static str;
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition) -> bool;
@@ -19,26 +28,12 @@ pub trait Panel: Render + EventEmitter {
     fn icon_label(&self, _: &WindowContext) -> Option<String> {
         None
     }
-    fn should_change_position_on_event(_: &Self::Event) -> bool;
-    fn should_zoom_in_on_event(_: &Self::Event) -> bool {
-        false
-    }
-    fn should_zoom_out_on_event(_: &Self::Event) -> bool {
-        false
-    }
     fn is_zoomed(&self, _cx: &WindowContext) -> bool {
         false
     }
     fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
     fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
-    fn should_activate_on_event(_: &Self::Event) -> bool {
-        false
-    }
-    fn should_close_on_event(_: &Self::Event) -> bool {
-        false
-    }
     fn has_focus(&self, cx: &WindowContext) -> bool;
-    fn is_focus_event(_: &Self::Event) -> bool;
 }
 
 pub trait PanelHandle: Send + Sync {
@@ -268,21 +263,37 @@ impl Dock {
         let subscriptions = [
             cx.observe(&panel, |_, _, cx| cx.notify()),
             cx.subscribe(&panel, |this, panel, event, cx| {
-                if T::should_activate_on_event(event) {
-                    if let Some(ix) = this
-                        .panel_entries
-                        .iter()
-                        .position(|entry| entry.panel.id() == panel.id())
-                    {
-                        this.set_open(true, cx);
-                        this.activate_panel(ix, cx);
+                match event {
+                    PanelEvent::ChangePosition => {
+                        //todo!()
+                        // see: Workspace::add_panel_with_extra_event_handler
+                    }
+                    PanelEvent::ZoomIn => {
+                        //todo!()
+                        // see: Workspace::add_panel_with_extra_event_handler
+                    }
+                    PanelEvent::ZoomOut => {
                         // todo!()
-                        // cx.focus(&panel);
+                        // // see: Workspace::add_panel_with_extra_event_handler
                     }
-                } else if T::should_close_on_event(event)
-                    && this.visible_panel().map_or(false, |p| p.id() == panel.id())
-                {
-                    this.set_open(false, cx);
+                    PanelEvent::Activate => {
+                        if let Some(ix) = this
+                            .panel_entries
+                            .iter()
+                            .position(|entry| entry.panel.id() == panel.id())
+                        {
+                            this.set_open(true, cx);
+                            this.activate_panel(ix, cx);
+                            //` todo!()
+                            // cx.focus(&panel);
+                        }
+                    }
+                    PanelEvent::Close => {
+                        if this.visible_panel().map_or(false, |p| p.id() == panel.id()) {
+                            this.set_open(false, cx);
+                        }
+                    }
+                    PanelEvent::Focus => todo!(),
                 }
             }),
         ];
@@ -460,10 +471,6 @@ impl PanelButtons {
     }
 }
 
-impl EventEmitter for PanelButtons {
-    type Event = ();
-}
-
 // impl Render for PanelButtons {
 //     type Element = ();
 
@@ -633,7 +640,7 @@ impl StatusItemView for PanelButtons {
         _active_pane_item: Option<&dyn crate::ItemHandle>,
         _cx: &mut ViewContext<Self>,
     ) {
-        // todo!(This is empty in the old `workspace::dock`)
+        // Nothing to do, panel buttons don't depend on the active center item
     }
 }
 
@@ -642,16 +649,6 @@ pub mod test {
     use super::*;
     use gpui::{div, Div, ViewContext, WindowContext};
 
-    #[derive(Debug)]
-    pub enum TestPanelEvent {
-        PositionChanged,
-        Activated,
-        Closed,
-        ZoomIn,
-        ZoomOut,
-        Focus,
-    }
-
     pub struct TestPanel {
         pub position: DockPosition,
         pub zoomed: bool,
@@ -660,9 +657,7 @@ pub mod test {
         pub size: f32,
     }
 
-    impl EventEmitter for TestPanel {
-        type Event = TestPanelEvent;
-    }
+    impl EventEmitter<PanelEvent> for TestPanel {}
 
     impl TestPanel {
         pub fn new(position: DockPosition) -> Self {
@@ -699,7 +694,7 @@ pub mod test {
 
         fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
             self.position = position;
-            cx.emit(TestPanelEvent::PositionChanged);
+            cx.emit(PanelEvent::ChangePosition);
         }
 
         fn size(&self, _: &WindowContext) -> f32 {
@@ -718,18 +713,6 @@ pub mod test {
             ("Test Panel".into(), None)
         }
 
-        fn should_change_position_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::PositionChanged)
-        }
-
-        fn should_zoom_in_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::ZoomIn)
-        }
-
-        fn should_zoom_out_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::ZoomOut)
-        }
-
         fn is_zoomed(&self, _: &WindowContext) -> bool {
             self.zoomed
         }
@@ -742,20 +725,8 @@ pub mod test {
             self.active = active;
         }
 
-        fn should_activate_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::Activated)
-        }
-
-        fn should_close_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::Closed)
-        }
-
         fn has_focus(&self, _cx: &WindowContext) -> bool {
             self.has_focus
         }
-
-        fn is_focus_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::Focus)
-        }
     }
 }

crates/workspace2/src/item.rs 🔗

@@ -91,7 +91,7 @@ pub struct BreadcrumbText {
     pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
 }
 
-pub trait Item: Render + EventEmitter {
+pub trait Item: Render + EventEmitter<ItemEvent> {
     fn focus_handle(&self) -> FocusHandle;
     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
@@ -106,12 +106,13 @@ pub trait Item: Render + EventEmitter {
     }
     fn tab_content<V: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<V>;
 
+    /// (model id, Item)
     fn for_each_project_item(
         &self,
         _: &AppContext,
         _: &mut dyn FnMut(EntityId, &dyn project2::Item),
     ) {
-    } // (model id, Item)
+    }
     fn is_singleton(&self, _cx: &AppContext) -> bool {
         false
     }
@@ -153,15 +154,6 @@ pub trait Item: Render + EventEmitter {
     ) -> Task<Result<()>> {
         unimplemented!("reload() must be implemented if can_save() returns true")
     }
-    fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        SmallVec::new()
-    }
-    fn should_close_item_on_event(_: &Self::Event) -> bool {
-        false
-    }
-    fn should_update_tab_on_event(_: &Self::Event) -> bool {
-        false
-    }
 
     fn act_as_type<'a>(
         &'a self,
@@ -218,7 +210,7 @@ pub trait ItemHandle: 'static + Send {
     fn subscribe_to_item_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
+        handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
     ) -> gpui::Subscription;
     fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
     fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
@@ -300,12 +292,10 @@ impl<T: Item> ItemHandle for View<T> {
     fn subscribe_to_item_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
+        handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
     ) -> gpui::Subscription {
         cx.subscribe(self, move |_, event, cx| {
-            for item_event in T::to_item_events(event) {
-                handler(item_event, cx)
-            }
+            handler(event, cx);
         })
     }
 
@@ -433,7 +423,10 @@ impl<T: Item> ItemHandle for View<T> {
                         let is_project_item = item.is_project_item(cx);
                         let leader_id = workspace.leader_for_pane(&pane);
 
-                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
+                        let follow_event = item.to_follow_event(event);
+                        if leader_id.is_some()
+                            && matches!(follow_event, Some(FollowEvent::Unfollow))
+                        {
                             workspace.unfollow(&pane, cx);
                         }
 
@@ -467,36 +460,34 @@ impl<T: Item> ItemHandle for View<T> {
                         }
                     }
 
-                    for item_event in T::to_item_events(event).into_iter() {
-                        match item_event {
-                            ItemEvent::CloseItem => {
-                                pane.update(cx, |pane, cx| {
-                                    pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
-                                })
-                                .detach_and_log_err(cx);
-                                return;
-                            }
+                    match event {
+                        ItemEvent::CloseItem => {
+                            pane.update(cx, |pane, cx| {
+                                pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
+                            })
+                            .detach_and_log_err(cx);
+                            return;
+                        }
 
-                            ItemEvent::UpdateTab => {
-                                pane.update(cx, |_, cx| {
-                                    cx.emit(pane::Event::ChangeItemTitle);
-                                    cx.notify();
-                                });
-                            }
+                        ItemEvent::UpdateTab => {
+                            pane.update(cx, |_, cx| {
+                                cx.emit(pane::Event::ChangeItemTitle);
+                                cx.notify();
+                            });
+                        }
 
-                            ItemEvent::Edit => {
-                                let autosave = WorkspaceSettings::get_global(cx).autosave;
-                                if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
-                                    let delay = Duration::from_millis(milliseconds);
-                                    let item = item.clone();
-                                    pending_autosave.fire_new(delay, cx, move |workspace, cx| {
-                                        Pane::autosave_item(&item, workspace.project().clone(), cx)
-                                    });
-                                }
+                        ItemEvent::Edit => {
+                            let autosave = WorkspaceSettings::get_global(cx).autosave;
+                            if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
+                                let delay = Duration::from_millis(milliseconds);
+                                let item = item.clone();
+                                pending_autosave.fire_new(delay, cx, move |workspace, cx| {
+                                    Pane::autosave_item(&item, workspace.project().clone(), cx)
+                                });
                             }
-
-                            _ => {}
                         }
+
+                        _ => {}
                     }
                 }));
 
@@ -660,7 +651,16 @@ pub trait ProjectItem: Item {
         Self: Sized;
 }
 
+pub enum FollowEvent {
+    Unfollow,
+}
+
+pub trait FollowableEvents {
+    fn to_follow_event(&self) -> Option<FollowEvent>;
+}
+
 pub trait FollowableItem: Item {
+    type FollowableEvent: FollowableEvents;
     fn remote_id(&self) -> Option<ViewId>;
     fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
     fn from_state_proto(
@@ -672,7 +672,7 @@ pub trait FollowableItem: Item {
     ) -> Option<Task<Result<View<Self>>>>;
     fn add_event_to_update_proto(
         &self,
-        event: &Self::Event,
+        event: &Self::FollowableEvent,
         update: &mut Option<proto::update_view::Variant>,
         cx: &AppContext,
     ) -> bool;
@@ -685,7 +685,6 @@ pub trait FollowableItem: Item {
     fn is_project_item(&self, cx: &AppContext) -> bool;
 
     fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
-    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 }
 
 pub trait FollowableItemHandle: ItemHandle {
@@ -698,13 +697,13 @@ pub trait FollowableItemHandle: ItemHandle {
         update: &mut Option<proto::update_view::Variant>,
         cx: &AppContext,
     ) -> bool;
+    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
     fn apply_update_proto(
         &self,
         project: &Model<Project>,
         message: proto::update_view::Variant,
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
-    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
     fn is_project_item(&self, cx: &AppContext) -> bool;
 }
 
@@ -739,6 +738,13 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         }
     }
 
+    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
+        event
+            .downcast_ref()
+            .map(T::FollowableEvent::to_follow_event)
+            .flatten()
+    }
+
     fn apply_update_proto(
         &self,
         project: &Model<Project>,
@@ -748,14 +754,6 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
     }
 
-    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
-        if let Some(event) = event.downcast_ref() {
-            T::should_unfollow_on_event(event, cx)
-        } else {
-            false
-        }
-    }
-
     fn is_project_item(&self, cx: &AppContext) -> bool {
         self.read(cx).is_project_item(cx)
     }

crates/workspace2/src/modal_layer.rs 🔗

@@ -16,10 +16,6 @@ pub enum ModalEvent {
     Dismissed,
 }
 
-pub trait Modal: EventEmitter + Render {
-    fn to_modal_event(&self, _: &Self::Event) -> Option<ModalEvent>;
-}
-
 impl ModalLayer {
     pub fn new() -> Self {
         Self {
@@ -31,7 +27,7 @@ impl ModalLayer {
 
     pub fn register_modal<A: 'static, V, B>(&mut self, action: A, build_view: B)
     where
-        V: Modal,
+        V: EventEmitter<ModalEvent> + Render,
         B: Fn(&mut Workspace, &mut ViewContext<Workspace>) -> Option<View<V>> + 'static,
     {
         let build_view = Arc::new(build_view);
@@ -51,12 +47,12 @@ impl ModalLayer {
         ));
     }
 
-    pub fn show_modal<V: Modal>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Workspace>) {
-        self.subscription = Some(cx.subscribe(&new_modal, |this, modal, e, cx| {
-            match modal.read(cx).to_modal_event(e) {
-                Some(ModalEvent::Dismissed) => this.modal_layer().hide_modal(cx),
-                None => {}
-            }
+    pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Workspace>)
+    where
+        V: EventEmitter<ModalEvent> + Render,
+    {
+        self.subscription = Some(cx.subscribe(&new_modal, |this, modal, e, cx| match e {
+            ModalEvent::Dismissed => this.modal_layer().hide_modal(cx),
         }));
         self.open_modal = Some(new_modal.into());
         cx.notify();

crates/workspace2/src/notifications.rs 🔗

@@ -9,10 +9,12 @@ pub fn init(cx: &mut AppContext) {
     // simple_message_notification::init(cx);
 }
 
-pub trait Notification: EventEmitter + Render {
-    fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool;
+pub enum NotificationEvent {
+    Dismiss,
 }
 
+pub trait Notification: EventEmitter<NotificationEvent> + Render {}
+
 pub trait NotificationHandle: Send {
     fn id(&self) -> EntityId;
     fn to_any(&self) -> AnyView;
@@ -101,11 +103,14 @@ impl Workspace {
             })
         {
             let notification = build_notification(cx);
-            cx.subscribe(&notification, move |this, handle, event, cx| {
-                if handle.read(cx).should_dismiss_notification_on_event(event) {
-                    this.dismiss_notification_internal(type_id, id, cx);
-                }
-            })
+            cx.subscribe(
+                &notification,
+                move |this, handle, event: &NotificationEvent, cx| match event {
+                    NotificationEvent::Dismiss => {
+                        this.dismiss_notification_internal(type_id, id, cx);
+                    }
+                },
+            )
             .detach();
             self.notifications
                 .push((type_id, id, Box::new(notification)));
@@ -159,7 +164,7 @@ impl Workspace {
 }
 
 pub mod simple_message_notification {
-    use super::Notification;
+    use super::{Notification, NotificationEvent};
     use gpui::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext};
     use serde::Deserialize;
     use std::{borrow::Cow, sync::Arc};
@@ -200,13 +205,7 @@ pub mod simple_message_notification {
         click_message: Option<Cow<'static, str>>,
     }
 
-    pub enum MessageNotificationEvent {
-        Dismiss,
-    }
-
-    impl EventEmitter for MessageNotification {
-        type Event = MessageNotificationEvent;
-    }
+    impl EventEmitter<NotificationMessage> for MessageNotification {}
 
     impl MessageNotification {
         pub fn new<S>(message: S) -> MessageNotification
@@ -359,13 +358,8 @@ pub mod simple_message_notification {
     //         }
     //     }
 
-    impl Notification for MessageNotification {
-        fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool {
-            match event {
-                MessageNotificationEvent::Dismiss => true,
-            }
-        }
-    }
+    impl EventEmitter<NotificationEvent> for MessageNotification {}
+    impl Notification for MessageNotification {}
 }
 
 pub trait NotifyResultExt {

crates/workspace2/src/pane.rs 🔗

@@ -9,8 +9,9 @@ use crate::{
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
-    AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, FocusHandle, Model,
-    PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+    actions, register_action, AppContext, AsyncWindowContext, Component, Div, EntityId,
+    EventEmitter, FocusHandle, Model, PromptLevel, Render, Task, View, ViewContext, VisualContext,
+    WeakView, WindowContext,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
@@ -48,8 +49,10 @@ pub enum SaveIntent {
     Skip,
 }
 
-// #[derive(Clone, Deserialize, PartialEq)]
-// pub struct ActivateItem(pub usize);
+//todo!("Do we need the default bound on actions? Decide soon")
+// #[register_action]
+#[derive(Clone, Deserialize, PartialEq, Debug)]
+pub struct ActivateItem(pub usize);
 
 // #[derive(Clone, PartialEq)]
 // pub struct CloseItemById {
@@ -69,40 +72,37 @@ pub enum SaveIntent {
 //     pub pane: WeakView<Pane>,
 // }
 
-// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
-// #[serde(rename_all = "camelCase")]
-// pub struct CloseActiveItem {
-//     pub save_intent: Option<SaveIntent>,
-// }
+#[register_action]
+#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct CloseActiveItem {
+    pub save_intent: Option<SaveIntent>,
+}
 
-// #[derive(Clone, PartialEq, Debug, Deserialize)]
-// #[serde(rename_all = "camelCase")]
-// pub struct CloseAllItems {
-//     pub save_intent: Option<SaveIntent>,
-// }
+#[register_action]
+#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct CloseAllItems {
+    pub save_intent: Option<SaveIntent>,
+}
 
-// todo!()
-// actions!(
-//     pane,
-//     [
-//         ActivatePrevItem,
-//         ActivateNextItem,
-//         ActivateLastItem,
-//         CloseInactiveItems,
-//         CloseCleanItems,
-//         CloseItemsToTheLeft,
-//         CloseItemsToTheRight,
-//         GoBack,
-//         GoForward,
-//         ReopenClosedItem,
-//         SplitLeft,
-//         SplitUp,
-//         SplitRight,
-//         SplitDown,
-//     ]
-// );
-
-// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
+// todo!(These used to be under pane::{Action}. Are they now workspace::pane::{Action}?)
+actions!(
+    ActivatePrevItem,
+    ActivateNextItem,
+    ActivateLastItem,
+    CloseInactiveItems,
+    CloseCleanItems,
+    CloseItemsToTheLeft,
+    CloseItemsToTheRight,
+    GoBack,
+    GoForward,
+    ReopenClosedItem,
+    SplitLeft,
+    SplitUp,
+    SplitRight,
+    SplitDown,
+);
 
 const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
@@ -310,9 +310,7 @@ pub struct NavigationEntry {
 //     .into_any_named("nav button")
 // }
 
-impl EventEmitter for Pane {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Pane {}
 
 impl Pane {
     pub fn new(

crates/workspace2/src/searchable.rs 🔗

@@ -1,6 +1,8 @@
 use std::{any::Any, sync::Arc};
 
-use gpui::{AnyView, AppContext, Subscription, Task, View, ViewContext, WindowContext};
+use gpui::{
+    AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WindowContext,
+};
 use project2::search::SearchQuery;
 
 use crate::{
@@ -29,7 +31,7 @@ pub struct SearchOptions {
     pub replacement: bool,
 }
 
-pub trait SearchableItem: Item {
+pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
     type Match: Any + Sync + Send + Clone;
 
     fn supported_options() -> SearchOptions {
@@ -40,11 +42,7 @@ pub trait SearchableItem: Item {
             replacement: true,
         }
     }
-    fn to_search_event(
-        &mut self,
-        event: &Self::Event,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<SearchEvent>;
+
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
     fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
     fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
@@ -95,7 +93,7 @@ pub trait SearchableItemHandle: ItemHandle {
     fn subscribe_to_search_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(SearchEvent, &mut WindowContext) + Send>,
+        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
     ) -> Subscription;
     fn clear_matches(&self, cx: &mut WindowContext);
     fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
@@ -146,14 +144,9 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
     fn subscribe_to_search_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(SearchEvent, &mut WindowContext) + Send>,
+        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
     ) -> Subscription {
-        cx.subscribe(self, move |handle, event, cx| {
-            let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx));
-            if let Some(search_event) = search_event {
-                handler(search_event, cx)
-            }
-        })
+        cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
     }
 
     fn clear_matches(&self, cx: &mut WindowContext) {

crates/workspace2/src/toolbar.rs 🔗

@@ -1,25 +1,19 @@
 use crate::ItemHandle;
 use gpui::{
-    AnyView, AppContext, Div, Entity, EntityId, EventEmitter, Render, View, ViewContext,
-    WindowContext,
+    AnyView, Div, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext,
 };
 
-pub trait ToolbarItemView: Render + EventEmitter {
+pub enum ToolbarItemEvent {
+    ChangeLocation(ToolbarItemLocation),
+}
+
+pub trait ToolbarItemView: Render + EventEmitter<ToolbarItemEvent> {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
         cx: &mut ViewContext<Self>,
     ) -> ToolbarItemLocation;
 
-    fn location_for_event(
-        &self,
-        _event: &Self::Event,
-        current_location: ToolbarItemLocation,
-        _cx: &AppContext,
-    ) -> ToolbarItemLocation {
-        current_location
-    }
-
     fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
 
     /// Number of times toolbar's height will be repeated to get the effective height.
@@ -141,61 +135,6 @@ impl Render for Toolbar {
 //     }
 // }
 
-// <<<<<<< HEAD
-// =======
-// #[allow(clippy::too_many_arguments)]
-// fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
-//     svg_path: &'static str,
-//     style: theme::Interactive<theme::IconButton>,
-//     nav_button_height: f32,
-//     tooltip_style: TooltipStyle,
-//     enabled: bool,
-//     spacing: f32,
-//     on_click: F,
-//     tooltip_action: A,
-//     action_name: &'static str,
-//     cx: &mut ViewContext<Toolbar>,
-// ) -> AnyElement<Toolbar> {
-//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
-//         let style = if enabled {
-//             style.style_for(state)
-//         } else {
-//             style.disabled_style()
-//         };
-//         Svg::new(svg_path)
-//             .with_color(style.color)
-//             .constrained()
-//             .with_width(style.icon_width)
-//             .aligned()
-//             .contained()
-//             .with_style(style.container)
-//             .constrained()
-//             .with_width(style.button_width)
-//             .with_height(nav_button_height)
-//             .aligned()
-//             .top()
-//     })
-//     .with_cursor_style(if enabled {
-//         CursorStyle::PointingHand
-//     } else {
-//         CursorStyle::default()
-//     })
-//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
-//         on_click(toolbar, cx)
-//     })
-//     .with_tooltip::<A>(
-//         0,
-//         action_name,
-//         Some(Box::new(tooltip_action)),
-//         tooltip_style,
-//         cx,
-//     )
-//     .contained()
-//     .with_margin_right(spacing)
-//     .into_any_named("nav button")
-// }
-
-// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e
 impl Toolbar {
     pub fn new() -> Self {
         Self {
@@ -220,12 +159,13 @@ impl Toolbar {
             if let Some((_, current_location)) =
                 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
             {
-                let new_location = item
-                    .read(cx)
-                    .location_for_event(event, *current_location, cx);
-                if new_location != *current_location {
-                    *current_location = new_location;
-                    cx.notify();
+                match event {
+                    ToolbarItemEvent::ChangeLocation(new_location) => {
+                        if new_location != current_location {
+                            *current_location = *new_location;
+                            cx.notify();
+                        }
+                    }
                 }
             }
         })

crates/workspace2/src/workspace2.rs 🔗

@@ -36,7 +36,7 @@ use futures::{
     Future, FutureExt, StreamExt,
 };
 use gpui::{
-    div, point, rems, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
+    actions, div, point, rems, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
     AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, FocusHandle,
     GlobalPixels, Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive,
     Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds,
@@ -87,35 +87,32 @@ lazy_static! {
 // #[derive(Clone, PartialEq)]
 // pub struct RemoveWorktreeFromProject(pub WorktreeId);
 
-// actions!(
-//     workspace,
-//     [
-//         Open,
-//         NewFile,
-//         NewWindow,
-//         CloseWindow,
-//         CloseInactiveTabsAndPanes,
-//         AddFolderToProject,
-//         Unfollow,
-//         SaveAs,
-//         ReloadActiveItem,
-//         ActivatePreviousPane,
-//         ActivateNextPane,
-//         FollowNextCollaborator,
-//         NewTerminal,
-//         NewCenterTerminal,
-//         ToggleTerminalFocus,
-//         NewSearch,
-//         Feedback,
-//         Restart,
-//         Welcome,
-//         ToggleZoom,
-//         ToggleLeftDock,
-//         ToggleRightDock,
-//         ToggleBottomDock,
-//         CloseAllDocks,
-//     ]
-// );
+actions!(
+    Open,
+    NewFile,
+    NewWindow,
+    CloseWindow,
+    CloseInactiveTabsAndPanes,
+    AddFolderToProject,
+    Unfollow,
+    SaveAs,
+    ReloadActiveItem,
+    ActivatePreviousPane,
+    ActivateNextPane,
+    FollowNextCollaborator,
+    NewTerminal,
+    NewCenterTerminal,
+    ToggleTerminalFocus,
+    NewSearch,
+    Feedback,
+    Restart,
+    Welcome,
+    ToggleZoom,
+    ToggleLeftDock,
+    ToggleRightDock,
+    ToggleBottomDock,
+    CloseAllDocks,
+);
 
 // #[derive(Clone, PartialEq)]
 // pub struct OpenPaths {
@@ -967,6 +964,9 @@ impl Workspace {
     //             let mut prev_position = panel.position(cx);
     //             move |this, panel, event, cx| {
     //                 if T::should_change_position_on_event(event) {
+    //                     THIS HAS BEEN MOVED TO NORMAL EVENT EMISSION
+    //                     See: Dock::add_panel
+    //
     //                     let new_position = panel.read(cx).position(cx);
     //                     let mut was_visible = false;
     //                     dock.update(cx, |dock, cx| {
@@ -997,6 +997,9 @@ impl Workspace {
     //                         }
     //                     });
     //                 } else if T::should_zoom_in_on_event(event) {
+    //                     THIS HAS BEEN MOVED TO NORMAL EVENT EMISSION
+    //                     See: Dock::add_panel
+    //
     //                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
     //                     if !panel.has_focus(cx) {
     //                         cx.focus(&panel);
@@ -1004,6 +1007,9 @@ impl Workspace {
     //                     this.zoomed = Some(panel.downgrade().into_any());
     //                     this.zoomed_position = Some(panel.read(cx).position(cx));
     //                 } else if T::should_zoom_out_on_event(event) {
+    //                     THIS HAS BEEN MOVED TO NORMAL EVENT EMISSION
+    //                     See: Dock::add_panel
+    //
     //                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
     //                     if this.zoomed_position == Some(prev_position) {
     //                         this.zoomed = None;
@@ -1011,6 +1017,9 @@ impl Workspace {
     //                     }
     //                     cx.notify();
     //                 } else if T::is_focus_event(event) {
+    //                     THIS HAS BEEN MOVED TO NORMAL EVENT EMISSION
+    //                     See: Dock::add_panel
+    //
     //                     let position = panel.read(cx).position(cx);
     //                     this.dismiss_zoomed_items_to_reveal(Some(position), cx);
     //                     if panel.is_zoomed(cx) {
@@ -3694,9 +3703,7 @@ fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncA
         .log_err();
 }
 
-impl EventEmitter for Workspace {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Workspace {}
 
 impl Render for Workspace {
     type Element = Div<Self>;
@@ -4138,10 +4145,6 @@ impl WorkspaceStore {
     }
 }
 
-impl EventEmitter for WorkspaceStore {
-    type Event = ();
-}
-
 impl ViewId {
     pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
         Ok(Self {