Refactor GoToLine to use cx.observe_new_views()

Conrad Irwin created

Change summary

crates/editor2/src/editor.rs          |  3 +
crates/go_to_line2/src/go_to_line.rs  | 55 +++++++++++++++++++++------
crates/gpui2/src/app.rs               | 25 +++++++++++-
crates/gpui2/src/app/async_context.rs |  2 
crates/gpui2/src/gpui2.rs             |  2 
crates/gpui2/src/interactive.rs       |  4 ++
crates/gpui2/src/window.rs            | 15 ++++++-
crates/workspace2/src/dock.rs         |  8 ++++
crates/workspace2/src/modal_layer.rs  | 57 ++++++++++------------------
crates/workspace2/src/toolbar.rs      | 11 +++++
crates/workspace2/src/workspace2.rs   | 21 ++++++----
11 files changed, 137 insertions(+), 66 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -57,6 +57,7 @@ use language::{
     Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
     OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
 };
+use lazy_static::lazy_static;
 use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
 use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};
 use movement::TextLayoutDetails;
@@ -66,7 +67,7 @@ pub use multi_buffer::{
     ToPoint,
 };
 use ordered_float::OrderedFloat;
-use parking_lot::RwLock;
+use parking_lot::{Mutex, RwLock};
 use project::{FormatTrigger, Location, Project};
 use rand::prelude::*;
 use rpc::proto::*;

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -1,25 +1,55 @@
 use editor::Editor;
 use gpui::{
     actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
-    StatelessInteractive, Styled, View, ViewContext, VisualContext,
+    StatefulInteractivity, StatelessInteractive, Styled, View, ViewContext, VisualContext,
 };
 use text::Point;
 use theme::ActiveTheme;
 use ui::{h_stack, modal, v_stack, Label, LabelColor};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::{ModalRegistry, Modal, ModalEvent};
+use workspace::{Modal, ModalEvent, Workspace};
 
 actions!(Toggle);
 
 pub fn init(cx: &mut AppContext) {
-    cx.global_mut::<ModalRegistry>()
-        .register_modal(Toggle, |workspace, cx| {
-            let editor = workspace
-                .active_item(cx)
-                .and_then(|active_item| active_item.downcast::<Editor>())?;
-
-            Some(cx.build_view(|cx| GoToLine::new(editor, cx)))
-        });
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
+            workspace
+                .modal_layer()
+                .register_modal(Toggle, |workspace, cx| {
+                    let editor = workspace
+                        .active_item(cx)
+                        .and_then(|active_item| active_item.downcast::<Editor>())?;
+
+                    Some(cx.build_view(|cx| GoToLine::new(editor, cx)))
+                });
+            dbg!("hey!");
+        },
+    )
+    .detach();
+
+    // // cx.window_global()
+    // // cx.window_global::<Workspace>
+    // // cx.window_global::<ActiveEditor>()
+    // Workspace::on_init(|workspace, cx| {
+    //     workspace.on_open_item()
+    // });
+
+    // Editor::on_init(|editor, cx|{
+
+    // })
+
+    // Editor::register_action(|_editor, _: &Toggle, cx| {
+    //     dbg!("HEY!");
+    //     // let editor = cx.view();
+    //     // cx.update_window(cx.window().handle(), |cx, view| {
+    //     //     let workspace = view.downcast::<Workspace>();
+    //     // })
+    //     // workspace.show_modal(cx.build_view(|cx| GoToLine::new(editor, cx)))
+    // })
+    // cx.global_mut::<ModalRegistry>()
+    //     .register_modal(Toggle, |workspace, cx| {
+    //     });
 }
 
 pub struct GoToLine {
@@ -128,13 +158,14 @@ impl Modal for GoToLine {
 }
 
 impl Render for GoToLine {
-    type Element = Div<Self>;
+    type Element = Div<Self, StatefulInteractivity<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         modal(cx)
-            .w_96()
+            .id("go to line")
             .on_action(Self::cancel)
             .on_action(Self::confirm)
+            .w_96()
             .child(
                 v_stack()
                     .px_1()

crates/gpui2/src/app.rs 🔗

@@ -18,8 +18,8 @@ use crate::{
     AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
     Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap,
     LayoutId, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
-    Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
-    WindowContext, WindowHandle, WindowId,
+    Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext,
+    Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -167,6 +167,7 @@ type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
 type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> 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>;
 
 // struct FrameConsumer {
 //     next_frame_callbacks: Vec<FrameCallback>,
@@ -193,6 +194,7 @@ pub struct AppContext {
     pub(crate) text_style_stack: Vec<TextStyleRefinement>,
     pub(crate) globals_by_type: HashMap<TypeId, AnyBox>,
     pub(crate) entities: EntityMap,
+    pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     pub(crate) keymap: Arc<Mutex<Keymap>>,
     pub(crate) global_action_listeners:
@@ -251,6 +253,7 @@ impl AppContext {
                 text_style_stack: Vec::new(),
                 globals_by_type: HashMap::default(),
                 entities,
+                new_view_observers: SubscriberSet::new(),
                 windows: SlotMap::with_key(),
                 keymap: Arc::new(Mutex::new(Keymap::default())),
                 global_action_listeners: HashMap::default(),
@@ -599,6 +602,7 @@ impl AppContext {
 
     fn apply_notify_effect(&mut self, emitter: EntityId) {
         self.pending_notifications.remove(&emitter);
+
         self.observers
             .clone()
             .retain(&emitter, |handler| handler(self));
@@ -828,6 +832,23 @@ impl AppContext {
         self.globals_by_type.insert(global_type, lease.global);
     }
 
+    pub fn observe_new_views<V: 'static>(
+        &mut self,
+        on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
+    ) -> Subscription {
+        self.new_view_observers.insert(
+            TypeId::of::<V>(),
+            Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
+                any_view
+                    .downcast::<V>()
+                    .unwrap()
+                    .update(cx, |view_state, cx| {
+                        on_new(view_state, cx);
+                    })
+            }),
+        )
+    }
+
     pub fn observe_release<E, T>(
         &mut self,
         handle: &E,

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

@@ -258,7 +258,7 @@ impl VisualContext for AsyncWindowContext {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static,
+        V: 'static + Render,
     {
         self.window
             .update(self, |_, cx| cx.build_view(build_view_state))

crates/gpui2/src/gpui2.rs 🔗

@@ -112,7 +112,7 @@ pub trait VisualContext: Context {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static;
+        V: 'static + Render;
 
     fn update_view<V: 'static, R>(
         &mut self,

crates/gpui2/src/interactive.rs 🔗

@@ -414,10 +414,14 @@ pub trait ElementInteractivity<V: 'static>: 'static {
                     Box::new(move |_, key_down, context, phase, cx| {
                         if phase == DispatchPhase::Bubble {
                             let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
+                            dbg!(key_down);
                             if let KeyMatch::Some(action) =
                                 cx.match_keystroke(&global_id, &key_down.keystroke, context)
                             {
+                                dbg!(&action);
                                 return Some(action);
+                            } else {
+                                dbg!("none");
                             }
                         }
 

crates/gpui2/src/window.rs 🔗

@@ -1411,7 +1411,7 @@ impl VisualContext for WindowContext<'_> {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static,
+        V: 'static + Render,
     {
         let slot = self.app.entities.reserve();
         let view = View {
@@ -1419,7 +1419,16 @@ impl VisualContext for WindowContext<'_> {
         };
         let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view);
         let entity = build_view_state(&mut cx);
-        self.entities.insert(slot, entity);
+        cx.entities.insert(slot, entity);
+
+        cx.new_view_observers
+            .clone()
+            .retain(&TypeId::of::<V>(), |observer| {
+                let any_view = AnyView::from(view.clone());
+                (observer)(any_view, self);
+                true
+            });
+
         view
     }
 
@@ -2102,7 +2111,7 @@ impl<V> Context for ViewContext<'_, V> {
 }
 
 impl<V: 'static> VisualContext for ViewContext<'_, V> {
-    fn build_view<W: 'static>(
+    fn build_view<W: Render + 'static>(
         &mut self,
         build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W,
     ) -> Self::Result<View<W>> {

crates/workspace2/src/dock.rs 🔗

@@ -407,6 +407,14 @@ impl Dock {
     //     }
 }
 
+impl Render for Dock {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        todo!()
+    }
+}
+
 // todo!()
 // impl View for Dock {
 //     fn ui_name() -> &'static str {

crates/workspace2/src/modal_layer.rs 🔗

@@ -1,25 +1,15 @@
 use crate::Workspace;
 use gpui::{
-    div, px, AnyView, AppContext, Component, Div, EventEmitter, ParentElement, Render,
-    StatelessInteractive, Styled, Subscription, View, ViewContext, WeakView,
+    div, px, AnyView, Component, Div, EventEmitter, ParentElement, Render, StatelessInteractive,
+    Styled, Subscription, View, ViewContext,
 };
 use std::{any::TypeId, sync::Arc};
 use ui::v_stack;
 
-pub struct ModalRegistry {
-    registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
-}
-
 pub struct ModalLayer {
-    workspace: WeakView<Workspace>,
     open_modal: Option<AnyView>,
     subscription: Option<Subscription>,
-}
-
-pub fn init_modal_registry(cx: &mut AppContext) {
-    cx.set_global(ModalRegistry {
-        registered_modals: Vec::new(),
-    });
+    registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
 }
 
 pub enum ModalEvent {
@@ -30,7 +20,15 @@ pub trait Modal: EventEmitter + Render {
     fn to_modal_event(&self, _: &Self::Event) -> Option<ModalEvent>;
 }
 
-impl ModalRegistry {
+impl ModalLayer {
+    pub fn new() -> Self {
+        Self {
+            open_modal: None,
+            subscription: None,
+            registered_modals: Vec::new(),
+        }
+    }
+
     pub fn register_modal<A: 'static, V, B>(&mut self, action: A, build_view: B)
     where
         V: Modal,
@@ -44,32 +42,19 @@ impl ModalRegistry {
                 let build_view = build_view.clone();
 
                 div.on_action(move |workspace, event: &A, cx| {
-                    let Some(new_modal) =
-                        (build_view)(workspace, cx) else {
-                            return
-                        };
-                    workspace.modal_layer.update(cx, |modal_layer, cx| {
-                        modal_layer.show_modal(new_modal, cx);
-                    })
+                    let Some(new_modal) = (build_view)(workspace, cx) else {
+                        return;
+                    };
+                    workspace.modal_layer().show_modal(new_modal, cx);
                 })
             }),
         ));
     }
-}
-
-impl ModalLayer {
-    pub fn new(workspace: WeakView<Workspace>) -> Self {
-        Self {
-            workspace,
-            open_modal: None,
-            subscription: None,
-        }
-    }
 
-    pub fn show_modal<V: Modal>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>) {
+    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.hide_modal(cx),
+                Some(ModalEvent::Dismissed) => this.modal_layer().hide_modal(cx),
                 None => {}
             }
         }));
@@ -77,16 +62,16 @@ impl ModalLayer {
         cx.notify();
     }
 
-    pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) {
+    pub fn hide_modal(&mut self, cx: &mut ViewContext<Workspace>) {
         self.open_modal.take();
         self.subscription.take();
         cx.notify();
     }
 
-    pub fn render(&self, cx: &ViewContext<Workspace>) -> Div<Workspace> {
+    pub fn wrapper_element(&self, cx: &ViewContext<Workspace>) -> Div<Workspace> {
         let mut parent = div().relative().size_full();
 
-        for (_, action) in cx.global::<ModalRegistry>().registered_modals.iter() {
+        for (_, action) in self.registered_modals.iter() {
             parent = (action)(parent);
         }
 

crates/workspace2/src/toolbar.rs 🔗

@@ -1,6 +1,7 @@
 use crate::ItemHandle;
 use gpui::{
-    AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext,
+    AnyView, AppContext, Div, Entity, EntityId, EventEmitter, Render, View, ViewContext,
+    WindowContext,
 };
 
 pub trait ToolbarItemView: Render + EventEmitter {
@@ -56,6 +57,14 @@ pub struct Toolbar {
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
+impl Render for Toolbar {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        todo!()
+    }
+}
+
 // todo!()
 // impl View for Toolbar {
 //     fn ui_name() -> &'static str {

crates/workspace2/src/workspace2.rs 🔗

@@ -37,10 +37,10 @@ use futures::{
 };
 use gpui::{
     div, point, 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,
-    WindowContext, WindowHandle, WindowOptions,
+    AsyncWindowContext, Bounds, Component, Context, Div, Entity, EntityId, EventEmitter,
+    FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render, Size,
+    StatefulInteractive, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+    WindowBounds, WindowContext, WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -225,7 +225,6 @@ pub fn init_settings(cx: &mut AppContext) {
 
 pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     init_settings(cx);
-    init_modal_registry(cx);
     pane::init(cx);
     notifications::init(cx);
 
@@ -545,7 +544,7 @@ pub struct Workspace {
     last_active_center_pane: Option<WeakView<Pane>>,
     last_active_view_id: Option<proto::ViewId>,
     status_bar: View<StatusBar>,
-    modal_layer: View<ModalLayer>,
+    modal_layer: ModalLayer,
     //     titlebar_item: Option<AnyViewHandle>,
     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
     project: Model<Project>,
@@ -697,7 +696,7 @@ impl Workspace {
         });
 
         let workspace_handle = cx.view().downgrade();
-        let modal_layer = cx.build_view(|cx| ModalLayer::new(workspace_handle));
+        let modal_layer = ModalLayer::new();
 
         // todo!()
         // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
@@ -781,6 +780,10 @@ impl Workspace {
         }
     }
 
+    pub fn modal_layer(&mut self) -> &mut ModalLayer {
+        &mut self.modal_layer
+    }
+
     fn new_local(
         abs_paths: Vec<PathBuf>,
         app_state: Arc<AppState>,
@@ -3707,9 +3710,9 @@ impl Render for Workspace {
             .bg(cx.theme().colors().background)
             .child(self.render_titlebar(cx))
             .child(
+                // todo! should this be a component a view?
                 self.modal_layer
-                    .read(cx)
-                    .render(cx)
+                    .wrapper_element(cx)
                     .relative()
                     .flex_1()
                     .w_full()