WIP

Antonio Scandurra created

Change summary

crates/gpui2/src/app.rs                |   9 
crates/gpui2/src/app/async_context.rs  |  33 
crates/gpui2/src/app/entity_map.rs     |   2 
crates/gpui2/src/geometry.rs           |  12 
crates/gpui2/src/platform.rs           |  23 
crates/gpui2/src/window.rs             |  19 
crates/workspace2/src/dock.rs          |  55 
crates/workspace2/src/notifications.rs | 386 ++++++-----
crates/workspace2/src/pane.rs          | 174 ++--
crates/workspace2/src/workspace2.rs    | 907 +++++++++++++--------------
10 files changed, 837 insertions(+), 783 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -833,6 +833,10 @@ where
         self.platform().path_for_auxiliary_executable(name)
     }
 
+    pub fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
+        self.platform().displays()
+    }
+
     pub fn display_for_uuid(&self, uuid: Uuid) -> Option<Rc<dyn PlatformDisplay>> {
         self.platform()
             .displays()
@@ -889,13 +893,14 @@ impl MainThread<AppContext> {
     pub fn open_window<V: Render>(
         &mut self,
         options: crate::WindowOptions,
-        build_root_view: impl FnOnce(&mut WindowContext) -> View<V> + Send + 'static,
+        build_root_view: impl FnOnce(&mut MainThread<WindowContext>) -> View<V> + Send + 'static,
     ) -> WindowHandle<V> {
         self.update(|cx| {
             let id = cx.windows.insert(None);
             let handle = WindowHandle::new(id);
             let mut window = Window::new(handle.into(), options, cx);
-            let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
+            let mut window_context = MainThread(WindowContext::new(cx, &mut window));
+            let root_view = build_root_view(&mut window_context);
             window.root_view.replace(root_view.into());
             cx.windows.get_mut(id).unwrap().replace(window);
             handle

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

@@ -5,7 +5,7 @@ use crate::{
 use anyhow::Context as _;
 use derive_more::{Deref, DerefMut};
 use parking_lot::Mutex;
-use std::{future::Future, sync::Weak};
+use std::{future::Future, mem, sync::Weak};
 
 #[derive(Clone)]
 pub struct AsyncAppContext {
@@ -44,7 +44,9 @@ impl Context for AsyncAppContext {
     where
         F: FnOnce(&mut Self::WindowContext<'_>) -> T,
     {
-        todo!()
+        let app = self.app.upgrade().context("app was released")?;
+        let mut lock = app.lock(); // Need this to compile
+        lock.update_window(window, f)
     }
 }
 
@@ -100,14 +102,14 @@ impl AsyncAppContext {
 
     pub fn spawn_on_main<Fut, R>(
         &self,
-        f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
+        f: impl FnOnce(MainThread<AsyncAppContext>) -> Fut + Send + 'static,
     ) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,
         R: Send + 'static,
     {
         let this = self.clone();
-        self.executor.spawn_on_main(|| f(this))
+        self.executor.spawn_on_main(|| f(MainThread(this)))
     }
 
     pub fn run_on_main<R>(
@@ -153,6 +155,29 @@ impl AsyncAppContext {
     }
 }
 
+impl MainThread<AsyncAppContext> {
+    pub fn update<R>(&self, f: impl FnOnce(&mut MainThread<AppContext>) -> R) -> Result<R> {
+        let app = self.app.upgrade().context("app was released")?;
+        let cx = &mut *app.lock();
+        let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
+        Ok(f(cx))
+    }
+
+    /// Opens a new window with the given option and the root view returned by the given function.
+    /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
+    /// functionality.
+    pub fn open_window<V: Render>(
+        &mut self,
+        options: crate::WindowOptions,
+        build_root_view: impl FnOnce(&mut MainThread<WindowContext>) -> View<V> + Send + 'static,
+    ) -> Result<WindowHandle<V>> {
+        let app = self.app.upgrade().context("app was released")?;
+        let cx = &mut *app.lock();
+        let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
+        Ok(cx.open_window(options, build_root_view))
+    }
+}
+
 #[derive(Clone, Deref, DerefMut)]
 pub struct AsyncWindowContext {
     #[deref]

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

@@ -1,4 +1,4 @@
-use crate::{private::Sealed, AnyBox, AppContext, AsyncAppContext, Context, Entity, ModelContext};
+use crate::{private::Sealed, AnyBox, AppContext, Context, Entity};
 use anyhow::{anyhow, Result};
 use derive_more::{Deref, DerefMut};
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};

crates/gpui2/src/geometry.rs 🔗

@@ -931,6 +931,18 @@ impl From<f64> for GlobalPixels {
     }
 }
 
+impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
+
+impl sqlez::bindable::Bind for GlobalPixels {
+    fn bind(
+        &self,
+        statement: &sqlez::statement::Statement,
+        start_index: i32,
+    ) -> anyhow::Result<i32> {
+        self.0.bind(statement, start_index)
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
 pub struct Rems(f32);
 

crates/gpui2/src/platform.rs 🔗

@@ -397,18 +397,17 @@ impl Bind for WindowBounds {
             }
         };
 
-        // statement.bind(
-        //     &region.map(|region| {
-        //         (
-        //             region.origin.x,
-        //             region.origin.y,
-        //             region.size.width,
-        //             region.size.height,
-        //         )
-        //     }),
-        //     next_index,
-        // )
-        todo!()
+        statement.bind(
+            &region.map(|region| {
+                (
+                    region.origin.x,
+                    region.origin.y,
+                    region.size.width,
+                    region.size.height,
+                )
+            }),
+            next_index,
+        )
     }
 }
 

crates/gpui2/src/window.rs 🔗

@@ -21,6 +21,7 @@ use std::{
     borrow::{Borrow, BorrowMut, Cow},
     fmt::Debug,
     future::Future,
+    hash::{Hash, Hasher},
     marker::PhantomData,
     mem,
     sync::{
@@ -2014,7 +2015,7 @@ impl WindowId {
     }
 }
 
-#[derive(PartialEq, Eq, Deref, DerefMut)]
+#[derive(Deref, DerefMut)]
 pub struct WindowHandle<V> {
     #[deref]
     #[deref_mut]
@@ -2062,13 +2063,27 @@ impl<V> Clone for WindowHandle<V> {
     }
 }
 
+impl<V> PartialEq for WindowHandle<V> {
+    fn eq(&self, other: &Self) -> bool {
+        self.any_handle == other.any_handle
+    }
+}
+
+impl<V> Eq for WindowHandle<V> {}
+
+impl<V> Hash for WindowHandle<V> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.any_handle.hash(state);
+    }
+}
+
 impl<V: 'static> Into<AnyWindowHandle> for WindowHandle<V> {
     fn into(self) -> AnyWindowHandle {
         self.any_handle
     }
 }
 
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
 pub struct AnyWindowHandle {
     pub(crate) id: WindowId,
     state_type: TypeId,

crates/workspace2/src/dock.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{status_bar::StatusItemView, Axis, Workspace};
 use gpui2::{
-    Action, AnyView, Div, EventEmitter, Render, Subscription, View, ViewContext, WeakView,
-    WindowContext,
+    Action, AnyView, Div, Entity, EntityId, EventEmitter, Render, Subscription, View, ViewContext,
+    WeakView, WindowContext,
 };
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
@@ -40,8 +40,8 @@ pub trait Panel: Render + EventEmitter {
     fn is_focus_event(_: &Self::Event) -> bool;
 }
 
-pub trait PanelHandle {
-    fn id(&self) -> usize;
+pub trait PanelHandle: Send + Sync {
+    fn id(&self) -> EntityId;
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
     fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
@@ -61,8 +61,8 @@ impl<T> PanelHandle for View<T>
 where
     T: Panel,
 {
-    fn id(&self) -> usize {
-        self.id()
+    fn id(&self) -> EntityId {
+        self.entity_id()
     }
 
     fn position(&self, cx: &WindowContext) -> DockPosition {
@@ -178,18 +178,18 @@ pub struct PanelButtons {
 }
 
 impl Dock {
-    //     pub fn new(position: DockPosition) -> Self {
-    //         Self {
-    //             position,
-    //             panel_entries: Default::default(),
-    //             active_panel_index: 0,
-    //             is_open: false,
-    //         }
-    //     }
+    pub fn new(position: DockPosition) -> Self {
+        Self {
+            position,
+            panel_entries: Default::default(),
+            active_panel_index: 0,
+            is_open: false,
+        }
+    }
 
-    //     pub fn position(&self) -> DockPosition {
-    //         self.position
-    //     }
+    pub fn position(&self) -> DockPosition {
+        self.position
+    }
 
     pub fn is_open(&self) -> bool {
         self.is_open
@@ -432,17 +432,16 @@ impl Dock {
 //     }
 // }
 
-// todo!()
-// impl PanelButtons {
-//     pub fn new(
-//         dock: View<Dock>,
-//         workspace: WeakViewHandle<Workspace>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Self {
-//         cx.observe(&dock, |_, _, cx| cx.notify()).detach();
-//         Self { dock, workspace }
-//     }
-// }
+impl PanelButtons {
+    pub fn new(
+        dock: View<Dock>,
+        workspace: WeakView<Workspace>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        cx.observe(&dock, |_, _, cx| cx.notify()).detach();
+        Self { dock, workspace }
+    }
+}
 
 impl EventEmitter for PanelButtons {
     type Event = ();

crates/workspace2/src/notifications.rs 🔗

@@ -1,35 +1,36 @@
 use crate::{Toast, Workspace};
 use collections::HashMap;
-use gpui2::{AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle};
+use gpui2::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext};
 use std::{any::TypeId, ops::DerefMut};
 
 pub fn init(cx: &mut AppContext) {
     cx.set_global(NotificationTracker::new());
-    simple_message_notification::init(cx);
+    // todo!()
+    // simple_message_notification::init(cx);
 }
 
-pub trait Notification: View {
-    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
+pub trait Notification: EventEmitter + Render {
+    fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool;
 }
 
-pub trait NotificationHandle {
-    fn id(&self) -> usize;
-    fn as_any(&self) -> &AnyViewHandle;
+pub trait NotificationHandle: Send {
+    fn id(&self) -> EntityId;
+    fn to_any(&self) -> AnyView;
 }
 
-impl<T: Notification> NotificationHandle for ViewHandle<T> {
-    fn id(&self) -> usize {
-        self.id()
+impl<T: Notification> NotificationHandle for View<T> {
+    fn id(&self) -> EntityId {
+        self.entity_id()
     }
 
-    fn as_any(&self) -> &AnyViewHandle {
-        self
+    fn to_any(&self) -> AnyView {
+        self.clone().into()
     }
 }
 
-impl From<&dyn NotificationHandle> for AnyViewHandle {
+impl From<&dyn NotificationHandle> for AnyView {
     fn from(val: &dyn NotificationHandle) -> Self {
-        val.as_any().clone()
+        val.to_any()
     }
 }
 
@@ -75,14 +76,12 @@ impl Workspace {
         &mut self,
         id: usize,
         cx: &mut ViewContext<Self>,
-        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
+        build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
     ) {
         if !self.has_shown_notification_once::<V>(id, cx) {
-            cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
-                let entry = tracker.entry(TypeId::of::<V>()).or_default();
-                entry.push(id);
-            });
-
+            let tracker = cx.global_mut::<NotificationTracker>();
+            let entry = tracker.entry(TypeId::of::<V>()).or_default();
+            entry.push(id);
             self.show_notification::<V>(id, cx, build_notification)
         }
     }
@@ -91,7 +90,7 @@ impl Workspace {
         &mut self,
         id: usize,
         cx: &mut ViewContext<Self>,
-        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
+        build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
     ) {
         let type_id = TypeId::of::<V>();
         if self
@@ -121,22 +120,24 @@ impl Workspace {
     }
 
     pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
-        self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
-        self.show_notification(toast.id, cx, |cx| {
-            cx.add_view(|_cx| match toast.on_click.as_ref() {
-                Some((click_msg, on_click)) => {
-                    let on_click = on_click.clone();
-                    simple_message_notification::MessageNotification::new(toast.msg.clone())
-                        .with_click_message(click_msg.clone())
-                        .on_click(move |cx| on_click(cx))
-                }
-                None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
-            })
-        })
+        todo!()
+        // self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
+        // self.show_notification(toast.id, cx, |cx| {
+        //     cx.add_view(|_cx| match toast.on_click.as_ref() {
+        //         Some((click_msg, on_click)) => {
+        //             let on_click = on_click.clone();
+        //             simple_message_notification::MessageNotification::new(toast.msg.clone())
+        //                 .with_click_message(click_msg.clone())
+        //                 .on_click(move |cx| on_click(cx))
+        //         }
+        //         None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
+        //     })
+        // })
     }
 
     pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
-        self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
+        todo!()
+        // self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
     }
 
     fn dismiss_notification_internal(
@@ -159,20 +160,12 @@ impl Workspace {
 
 pub mod simple_message_notification {
     use super::Notification;
-    use crate::Workspace;
-    use gpui2::{
-        actions,
-        elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
-        fonts::TextStyle,
-        impl_actions,
-        platform::{CursorStyle, MouseButton},
-        AnyElement, AppContext, Element, Entity, View, ViewContext,
-    };
-    use menu::Cancel;
+    use gpui2::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext};
     use serde::Deserialize;
     use std::{borrow::Cow, sync::Arc};
 
-    actions!(message_notifications, [CancelMessageNotification]);
+    // todo!()
+    // actions!(message_notifications, [CancelMessageNotification]);
 
     #[derive(Clone, Default, Deserialize, PartialEq)]
     pub struct OsOpen(pub Cow<'static, str>);
@@ -183,16 +176,18 @@ pub mod simple_message_notification {
         }
     }
 
-    impl_actions!(message_notifications, [OsOpen]);
-
-    pub fn init(cx: &mut AppContext) {
-        cx.add_action(MessageNotification::dismiss);
-        cx.add_action(
-            |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
-                cx.platform().open_url(open_action.0.as_ref());
-            },
-        )
-    }
+    // todo!()
+    //     impl_actions!(message_notifications, [OsOpen]);
+    //
+    // todo!()
+    //     pub fn init(cx: &mut AppContext) {
+    //         cx.add_action(MessageNotification::dismiss);
+    //         cx.add_action(
+    //             |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
+    //                 cx.platform().open_url(open_action.0.as_ref());
+    //             },
+    //         )
+    //     }
 
     enum NotificationMessage {
         Text(Cow<'static, str>),
@@ -201,7 +196,7 @@ pub mod simple_message_notification {
 
     pub struct MessageNotification {
         message: NotificationMessage,
-        on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
+        on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>) + Send + Sync>>,
         click_message: Option<Cow<'static, str>>,
     }
 
@@ -209,7 +204,7 @@ pub mod simple_message_notification {
         Dismiss,
     }
 
-    impl Entity for MessageNotification {
+    impl EventEmitter for MessageNotification {
         type Event = MessageNotificationEvent;
     }
 
@@ -225,138 +220,147 @@ pub mod simple_message_notification {
             }
         }
 
-        pub fn new_element(
-            message: fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>,
-        ) -> MessageNotification {
-            Self {
-                message: NotificationMessage::Element(message),
-                on_click: None,
-                click_message: None,
-            }
-        }
-
-        pub fn with_click_message<S>(mut self, message: S) -> Self
-        where
-            S: Into<Cow<'static, str>>,
-        {
-            self.click_message = Some(message.into());
-            self
-        }
-
-        pub fn on_click<F>(mut self, on_click: F) -> Self
-        where
-            F: 'static + Fn(&mut ViewContext<Self>),
-        {
-            self.on_click = Some(Arc::new(on_click));
-            self
-        }
-
-        pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
-            cx.emit(MessageNotificationEvent::Dismiss);
-        }
+        // todo!()
+        //         pub fn new_element(
+        //             message: fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>,
+        //         ) -> MessageNotification {
+        //             Self {
+        //                 message: NotificationMessage::Element(message),
+        //                 on_click: None,
+        //                 click_message: None,
+        //             }
+        //         }
+
+        //         pub fn with_click_message<S>(mut self, message: S) -> Self
+        //         where
+        //             S: Into<Cow<'static, str>>,
+        //         {
+        //             self.click_message = Some(message.into());
+        //             self
+        //         }
+
+        //         pub fn on_click<F>(mut self, on_click: F) -> Self
+        //         where
+        //             F: 'static + Fn(&mut ViewContext<Self>),
+        //         {
+        //             self.on_click = Some(Arc::new(on_click));
+        //             self
+        //         }
+
+        //         pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
+        //             cx.emit(MessageNotificationEvent::Dismiss);
+        //         }
     }
 
-    impl View for MessageNotification {
-        fn ui_name() -> &'static str {
-            "MessageNotification"
-        }
-
-        fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> gpui::AnyElement<Self> {
-            let theme = theme2::current(cx).clone();
-            let theme = &theme.simple_message_notification;
+    impl Render for MessageNotification {
+        type Element = Div<Self>;
 
-            enum MessageNotificationTag {}
-
-            let click_message = self.click_message.clone();
-            let message = match &self.message {
-                NotificationMessage::Text(text) => {
-                    Text::new(text.to_owned(), theme.message.text.clone()).into_any()
-                }
-                NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
-            };
-            let on_click = self.on_click.clone();
-            let has_click_action = on_click.is_some();
-
-            Flex::column()
-                .with_child(
-                    Flex::row()
-                        .with_child(
-                            message
-                                .contained()
-                                .with_style(theme.message.container)
-                                .aligned()
-                                .top()
-                                .left()
-                                .flex(1., true),
-                        )
-                        .with_child(
-                            MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
-                                let style = theme.dismiss_button.style_for(state);
-                                Svg::new("icons/x.svg")
-                                    .with_color(style.color)
-                                    .constrained()
-                                    .with_width(style.icon_width)
-                                    .aligned()
-                                    .contained()
-                                    .with_style(style.container)
-                                    .constrained()
-                                    .with_width(style.button_width)
-                                    .with_height(style.button_width)
-                            })
-                            .with_padding(Padding::uniform(5.))
-                            .on_click(MouseButton::Left, move |_, this, cx| {
-                                this.dismiss(&Default::default(), cx);
-                            })
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .aligned()
-                            .constrained()
-                            .with_height(cx.font_cache().line_height(theme.message.text.font_size))
-                            .aligned()
-                            .top()
-                            .flex_float(),
-                        ),
-                )
-                .with_children({
-                    click_message
-                        .map(|click_message| {
-                            MouseEventHandler::new::<MessageNotificationTag, _>(
-                                0,
-                                cx,
-                                |state, _| {
-                                    let style = theme.action_message.style_for(state);
-
-                                    Flex::row()
-                                        .with_child(
-                                            Text::new(click_message, style.text.clone())
-                                                .contained()
-                                                .with_style(style.container),
-                                        )
-                                        .contained()
-                                },
-                            )
-                            .on_click(MouseButton::Left, move |_, this, cx| {
-                                if let Some(on_click) = on_click.as_ref() {
-                                    on_click(cx);
-                                    this.dismiss(&Default::default(), cx);
-                                }
-                            })
-                            // Since we're not using a proper overlay, we have to capture these extra events
-                            .on_down(MouseButton::Left, |_, _, _| {})
-                            .on_up(MouseButton::Left, |_, _, _| {})
-                            .with_cursor_style(if has_click_action {
-                                CursorStyle::PointingHand
-                            } else {
-                                CursorStyle::Arrow
-                            })
-                        })
-                        .into_iter()
-                })
-                .into_any()
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            todo!()
         }
     }
+    // todo!()
+    //     impl View for MessageNotification {
+    //         fn ui_name() -> &'static str {
+    //             "MessageNotification"
+    //         }
+
+    //         fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> gpui::AnyElement<Self> {
+    //             let theme = theme2::current(cx).clone();
+    //             let theme = &theme.simple_message_notification;
+
+    //             enum MessageNotificationTag {}
+
+    //             let click_message = self.click_message.clone();
+    //             let message = match &self.message {
+    //                 NotificationMessage::Text(text) => {
+    //                     Text::new(text.to_owned(), theme.message.text.clone()).into_any()
+    //                 }
+    //                 NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
+    //             };
+    //             let on_click = self.on_click.clone();
+    //             let has_click_action = on_click.is_some();
+
+    //             Flex::column()
+    //                 .with_child(
+    //                     Flex::row()
+    //                         .with_child(
+    //                             message
+    //                                 .contained()
+    //                                 .with_style(theme.message.container)
+    //                                 .aligned()
+    //                                 .top()
+    //                                 .left()
+    //                                 .flex(1., true),
+    //                         )
+    //                         .with_child(
+    //                             MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
+    //                                 let style = theme.dismiss_button.style_for(state);
+    //                                 Svg::new("icons/x.svg")
+    //                                     .with_color(style.color)
+    //                                     .constrained()
+    //                                     .with_width(style.icon_width)
+    //                                     .aligned()
+    //                                     .contained()
+    //                                     .with_style(style.container)
+    //                                     .constrained()
+    //                                     .with_width(style.button_width)
+    //                                     .with_height(style.button_width)
+    //                             })
+    //                             .with_padding(Padding::uniform(5.))
+    //                             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                                 this.dismiss(&Default::default(), cx);
+    //                             })
+    //                             .with_cursor_style(CursorStyle::PointingHand)
+    //                             .aligned()
+    //                             .constrained()
+    //                             .with_height(cx.font_cache().line_height(theme.message.text.font_size))
+    //                             .aligned()
+    //                             .top()
+    //                             .flex_float(),
+    //                         ),
+    //                 )
+    //                 .with_children({
+    //                     click_message
+    //                         .map(|click_message| {
+    //                             MouseEventHandler::new::<MessageNotificationTag, _>(
+    //                                 0,
+    //                                 cx,
+    //                                 |state, _| {
+    //                                     let style = theme.action_message.style_for(state);
+
+    //                                     Flex::row()
+    //                                         .with_child(
+    //                                             Text::new(click_message, style.text.clone())
+    //                                                 .contained()
+    //                                                 .with_style(style.container),
+    //                                         )
+    //                                         .contained()
+    //                                 },
+    //                             )
+    //                             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                                 if let Some(on_click) = on_click.as_ref() {
+    //                                     on_click(cx);
+    //                                     this.dismiss(&Default::default(), cx);
+    //                                 }
+    //                             })
+    //                             // Since we're not using a proper overlay, we have to capture these extra events
+    //                             .on_down(MouseButton::Left, |_, _, _| {})
+    //                             .on_up(MouseButton::Left, |_, _, _| {})
+    //                             .with_cursor_style(if has_click_action {
+    //                                 CursorStyle::PointingHand
+    //                             } else {
+    //                                 CursorStyle::Arrow
+    //                             })
+    //                         })
+    //                         .into_iter()
+    //                 })
+    //                 .into_any()
+    //         }
+    //     }
 
     impl Notification for MessageNotification {
-        fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
+        fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool {
             match event {
                 MessageNotificationEvent::Dismiss => true,
             }
@@ -384,15 +388,15 @@ where
         match self {
             Ok(value) => Some(value),
             Err(err) => {
-                workspace.show_notification(0, cx, |cx| {
-                    cx.add_view(|_cx| {
-                        simple_message_notification::MessageNotification::new(format!(
-                            "Error: {:?}",
-                            err,
-                        ))
-                    })
-                });
-
+                log::error!("TODO {err:?}");
+                // todo!()
+                // workspace.show_notification(0, cx, |cx| {
+                //     cx.add_view(|_cx| {
+                //         simple_message_notification::MessageNotification::new(format!(
+                //             "Error: {err:?}",
+                //         ))
+                //     })
+                // });
                 None
             }
         }

crates/workspace2/src/pane.rs 🔗

@@ -168,7 +168,7 @@ impl fmt::Debug for Event {
 pub struct Pane {
     items: Vec<Box<dyn ItemHandle>>,
     activation_history: Vec<usize>,
-    //     zoomed: bool,
+    zoomed: bool,
     active_item_index: usize,
     //     last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
     autoscroll: bool,
@@ -220,7 +220,7 @@ impl Default for NavigationMode {
 
 pub struct NavigationEntry {
     pub item: Arc<dyn WeakItemHandle>,
-    pub data: Option<Box<dyn Any>>,
+    pub data: Option<Box<dyn Any + Send>>,
     pub timestamp: usize,
 }
 
@@ -327,7 +327,7 @@ impl Pane {
         Self {
             items: Vec::new(),
             activation_history: Vec::new(),
-            // zoomed: false,
+            zoomed: false,
             active_item_index: 0,
             // last_focused_view_by_item: Default::default(),
             autoscroll: false,
@@ -648,9 +648,9 @@ impl Pane {
     //         })
     //     }
 
-    //     pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
-    //         self.items.iter().position(|i| i.id() == item.id())
-    //     }
+    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
+        self.items.iter().position(|i| i.id() == item.id())
+    }
 
     //     pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
     //         // Potentially warn the user of the new keybinding
@@ -994,77 +994,73 @@ impl Pane {
     //         })
     //     }
 
-    //     pub fn remove_item(
-    //         &mut self,
-    //         item_index: usize,
-    //         activate_pane: bool,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         self.activation_history
-    //             .retain(|&history_entry| history_entry != self.items[item_index].id());
-
-    //         if item_index == self.active_item_index {
-    //             let index_to_activate = self
-    //                 .activation_history
-    //                 .pop()
-    //                 .and_then(|last_activated_item| {
-    //                     self.items.iter().enumerate().find_map(|(index, item)| {
-    //                         (item.id() == last_activated_item).then_some(index)
-    //                     })
-    //                 })
-    //                 // We didn't have a valid activation history entry, so fallback
-    //                 // to activating the item to the left
-    //                 .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
+    pub fn remove_item(
+        &mut self,
+        item_index: usize,
+        activate_pane: bool,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.activation_history
+            .retain(|&history_entry| history_entry != self.items[item_index].id());
+
+        if item_index == self.active_item_index {
+            let index_to_activate = self
+                .activation_history
+                .pop()
+                .and_then(|last_activated_item| {
+                    self.items.iter().enumerate().find_map(|(index, item)| {
+                        (item.id() == last_activated_item).then_some(index)
+                    })
+                })
+                // We didn't have a valid activation history entry, so fallback
+                // to activating the item to the left
+                .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
 
-    //             let should_activate = activate_pane || self.has_focus;
-    //             self.activate_item(index_to_activate, should_activate, should_activate, cx);
-    //         }
+            let should_activate = activate_pane || self.has_focus;
+            self.activate_item(index_to_activate, should_activate, should_activate, cx);
+        }
 
-    //         let item = self.items.remove(item_index);
+        let item = self.items.remove(item_index);
 
-    //         cx.emit(Event::RemoveItem { item_id: item.id() });
-    //         if self.items.is_empty() {
-    //             item.deactivated(cx);
-    //             self.update_toolbar(cx);
-    //             cx.emit(Event::Remove);
-    //         }
+        cx.emit(Event::RemoveItem { item_id: item.id() });
+        if self.items.is_empty() {
+            item.deactivated(cx);
+            self.update_toolbar(cx);
+            cx.emit(Event::Remove);
+        }
 
-    //         if item_index < self.active_item_index {
-    //             self.active_item_index -= 1;
-    //         }
+        if item_index < self.active_item_index {
+            self.active_item_index -= 1;
+        }
 
-    //         self.nav_history.set_mode(NavigationMode::ClosingItem);
-    //         item.deactivated(cx);
-    //         self.nav_history.set_mode(NavigationMode::Normal);
-
-    //         if let Some(path) = item.project_path(cx) {
-    //             let abs_path = self
-    //                 .nav_history
-    //                 .0
-    //                 .borrow()
-    //                 .paths_by_item
-    //                 .get(&item.id())
-    //                 .and_then(|(_, abs_path)| abs_path.clone());
-
-    //             self.nav_history
-    //                 .0
-    //                 .borrow_mut()
-    //                 .paths_by_item
-    //                 .insert(item.id(), (path, abs_path));
-    //         } else {
-    //             self.nav_history
-    //                 .0
-    //                 .borrow_mut()
-    //                 .paths_by_item
-    //                 .remove(&item.id());
-    //         }
+        self.nav_history.set_mode(NavigationMode::ClosingItem);
+        item.deactivated(cx);
+        self.nav_history.set_mode(NavigationMode::Normal);
+
+        if let Some(path) = item.project_path(cx) {
+            let abs_path = self
+                .nav_history
+                .0
+                .lock()
+                .paths_by_item
+                .get(&item.id())
+                .and_then(|(_, abs_path)| abs_path.clone());
+
+            self.nav_history
+                .0
+                .lock()
+                .paths_by_item
+                .insert(item.id(), (path, abs_path));
+        } else {
+            self.nav_history.0.lock().paths_by_item.remove(&item.id());
+        }
 
-    //         if self.items.is_empty() && self.zoomed {
-    //             cx.emit(Event::ZoomOut);
-    //         }
+        if self.items.is_empty() && self.zoomed {
+            cx.emit(Event::ZoomOut);
+        }
 
-    //         cx.notify();
-    //     }
+        cx.notify();
+    }
 
     //     pub async fn save_item(
     //         project: Model<Project>,
@@ -1314,28 +1310,28 @@ impl Pane {
     //         });
     //     }
 
-    //     pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
-    //         &self.toolbar
-    //     }
+    pub fn toolbar(&self) -> &View<Toolbar> {
+        &self.toolbar
+    }
 
-    //     pub fn handle_deleted_project_item(
-    //         &mut self,
-    //         entry_id: ProjectEntryId,
-    //         cx: &mut ViewContext<Pane>,
-    //     ) -> Option<()> {
-    //         let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
-    //             if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
-    //                 Some((i, item.id()))
-    //             } else {
-    //                 None
-    //             }
-    //         })?;
+    pub fn handle_deleted_project_item(
+        &mut self,
+        entry_id: ProjectEntryId,
+        cx: &mut ViewContext<Pane>,
+    ) -> Option<()> {
+        let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
+            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
+                Some((i, item.id()))
+            } else {
+                None
+            }
+        })?;
 
-    //         self.remove_item(item_index_to_delete, false, cx);
-    //         self.nav_history.remove_item(item_id);
+        self.remove_item(item_index_to_delete, false, cx);
+        self.nav_history.remove_item(item_id);
 
-    //         Some(())
-    //     }
+        Some(())
+    }
 
     fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
         let active_item = self

crates/workspace2/src/workspace2.rs 🔗

@@ -1,6 +1,6 @@
 pub mod dock;
 pub mod item;
-// pub mod notifications;
+pub mod notifications;
 pub mod pane;
 pub mod pane_group;
 mod persistence;
@@ -10,6 +10,10 @@ mod status_bar;
 mod toolbar;
 mod workspace_settings;
 
+use crate::persistence::model::{
+    DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
+    SerializedWorkspace,
+};
 use anyhow::{anyhow, Result};
 use call2::ActiveCall;
 use client2::{
@@ -17,19 +21,22 @@ use client2::{
     Client, TypedEnvelope, UserStore,
 };
 use collections::{HashMap, HashSet};
-use dock::Dock;
+use dock::{Dock, DockPosition, PanelButtons};
 use futures::{
     channel::{mpsc, oneshot},
-    FutureExt,
+    FutureExt, Stream, StreamExt,
 };
 use gpui2::{
-    div, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Div, Entity,
-    EventEmitter, MainThread, Model, ModelContext, Render, Subscription, Task, View, ViewContext,
-    VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
+    div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds,
+    Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size,
+    Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
+    WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
 use language2::LanguageRegistry;
+use lazy_static::lazy_static;
 use node_runtime::NodeRuntime;
+use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
 pub use pane::*;
 pub use pane_group::*;
 use persistence::{
@@ -37,8 +44,12 @@ use persistence::{
     DB,
 };
 use project2::{Project, ProjectEntryId, ProjectPath, Worktree};
+use serde::Deserialize;
+use status_bar::StatusBar;
 use std::{
     any::TypeId,
+    borrow::Cow,
+    env,
     path::{Path, PathBuf},
     sync::{atomic::AtomicUsize, Arc},
     time::Duration,
@@ -47,21 +58,16 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
 use util::ResultExt;
 use uuid::Uuid;
 
-use crate::persistence::model::{
-    DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
-    SerializedWorkspace,
-};
-
-// lazy_static! {
-//     static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
-//         .ok()
-//         .as_deref()
-//         .and_then(parse_pixel_position_env_var);
-//     static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
-//         .ok()
-//         .as_deref()
-//         .and_then(parse_pixel_position_env_var);
-// }
+lazy_static! {
+    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
+        .ok()
+        .as_deref()
+        .and_then(parse_pixel_size_env_var);
+    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
+        .ok()
+        .as_deref()
+        .and_then(parse_pixel_position_env_var);
+}
 
 // pub trait Modal: View {
 //     fn has_focus(&self) -> bool;
@@ -151,13 +157,13 @@ use crate::persistence::model::{
 //     pub save_intent: Option<SaveIntent>,
 // }
 
-// #[derive(Deserialize)]
-// pub struct Toast {
-//     id: usize,
-//     msg: Cow<'static, str>,
-//     #[serde(skip)]
-//     on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
-// }
+#[derive(Deserialize)]
+pub struct Toast {
+    id: usize,
+    msg: Cow<'static, str>,
+    #[serde(skip)]
+    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
+}
 
 // impl Toast {
 //     pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
@@ -336,7 +342,7 @@ pub type WorkspaceId = i64;
 //                     err.notify_err(workspace, cx);
 //                 } else {
 //                     workspace.show_notification(1, cx, |cx| {
-//                         cx.add_view(|_| {
+//                         cx.build_view(|_| {
 //                             MessageNotification::new("Successfully installed the `zed` binary")
 //                         })
 //                     });
@@ -418,7 +424,7 @@ pub struct AppState {
     pub workspace_store: Model<WorkspaceStore>,
     pub fs: Arc<dyn fs2::Fs>,
     pub build_window_options:
-        fn(Option<WindowBounds>, Option<Uuid>, MainThread<AppContext>) -> WindowOptions,
+        fn(Option<WindowBounds>, Option<Uuid>, &mut MainThread<AppContext>) -> WindowOptions,
     pub initialize_workspace: fn(
         WeakView<Workspace>,
         bool,
@@ -539,24 +545,24 @@ pub struct Workspace {
     right_dock: View<Dock>,
     panes: Vec<View<Pane>>,
     panes_by_item: HashMap<usize, WeakView<Pane>>,
-    //     active_pane: View<Pane>,
+    active_pane: View<Pane>,
     last_active_center_pane: Option<WeakView<Pane>>,
     //     last_active_view_id: Option<proto::ViewId>,
     //     status_bar: View<StatusBar>,
     //     titlebar_item: Option<AnyViewHandle>,
-    //     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
+    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
     project: Model<Project>,
     follower_states: HashMap<View<Pane>, FollowerState>,
     last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
-    //     window_edited: bool,
+    window_edited: bool,
     active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
     database_id: WorkspaceId,
     app_state: Arc<AppState>,
-    //     subscriptions: Vec<Subscription>,
-    //     _apply_leader_updates: Task<Result<()>>,
-    //     _observe_current_user: Task<Result<()>>,
-    //     _schedule_serialize: Option<Task<()>>,
+    subscriptions: Vec<Subscription>,
+    _apply_leader_updates: Task<Result<()>>,
+    _observe_current_user: Task<Result<()>>,
+    _schedule_serialize: Option<Task<()>>,
     pane_history_timestamp: Arc<AtomicUsize>,
 }
 
@@ -581,200 +587,205 @@ struct FollowerState {
 enum WorkspaceBounds {}
 
 impl Workspace {
-    //     pub fn new(
-    //         workspace_id: WorkspaceId,
-    //         project: ModelHandle<Project>,
-    //         app_state: Arc<AppState>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Self {
-    //         cx.observe(&project, |_, _, cx| cx.notify()).detach();
-    //         cx.subscribe(&project, move |this, _, event, cx| {
-    //             match event {
-    //                 project::Event::RemoteIdChanged(_) => {
-    //                     this.update_window_title(cx);
-    //                 }
+    pub fn new(
+        workspace_id: WorkspaceId,
+        project: Model<Project>,
+        app_state: Arc<AppState>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        cx.observe(&project, |_, _, cx| cx.notify()).detach();
+        cx.subscribe(&project, move |this, _, event, cx| {
+            match event {
+                project2::Event::RemoteIdChanged(_) => {
+                    this.update_window_title(cx);
+                }
 
-    //                 project::Event::CollaboratorLeft(peer_id) => {
-    //                     this.collaborator_left(*peer_id, cx);
-    //                 }
+                project2::Event::CollaboratorLeft(peer_id) => {
+                    this.collaborator_left(*peer_id, cx);
+                }
 
-    //                 project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
-    //                     this.update_window_title(cx);
-    //                     this.serialize_workspace(cx);
-    //                 }
+                project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
+                    this.update_window_title(cx);
+                    this.serialize_workspace(cx);
+                }
 
-    //                 project::Event::DisconnectedFromHost => {
-    //                     this.update_window_edited(cx);
-    //                     cx.blur();
-    //                 }
+                project2::Event::DisconnectedFromHost => {
+                    this.update_window_edited(cx);
+                    cx.blur();
+                }
 
-    //                 project::Event::Closed => {
-    //                     cx.remove_window();
-    //                 }
+                project2::Event::Closed => {
+                    // todo!()
+                    // cx.remove_window();
+                }
 
-    //                 project::Event::DeletedEntry(entry_id) => {
-    //                     for pane in this.panes.iter() {
-    //                         pane.update(cx, |pane, cx| {
-    //                             pane.handle_deleted_project_item(*entry_id, cx)
-    //                         });
-    //                     }
-    //                 }
+                project2::Event::DeletedEntry(entry_id) => {
+                    for pane in this.panes.iter() {
+                        pane.update(cx, |pane, cx| {
+                            pane.handle_deleted_project_item(*entry_id, cx)
+                        });
+                    }
+                }
 
-    //                 project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
-    //                     cx.add_view(|_| MessageNotification::new(message.clone()))
-    //                 }),
+                project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+                    cx.build_view(|_| MessageNotification::new(message.clone()))
+                }),
 
-    //                 _ => {}
-    //             }
-    //             cx.notify()
-    //         })
-    //         .detach();
+                _ => {}
+            }
+            cx.notify()
+        })
+        .detach();
 
-    //         let weak_handle = cx.weak_handle();
-    //         let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
+        let weak_handle = cx.view().downgrade();
+        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 
-    //         let center_pane = cx.add_view(|cx| {
-    //             Pane::new(
-    //                 weak_handle.clone(),
-    //                 project.clone(),
-    //                 pane_history_timestamp.clone(),
-    //                 cx,
-    //             )
-    //         });
-    //         cx.subscribe(&center_pane, Self::handle_pane_event).detach();
-    //         cx.focus(&center_pane);
-    //         cx.emit(Event::PaneAdded(center_pane.clone()));
+        let center_pane = cx.build_view(|cx| {
+            Pane::new(
+                weak_handle.clone(),
+                project.clone(),
+                pane_history_timestamp.clone(),
+                cx,
+            )
+        });
+        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
+        // todo!()
+        // cx.focus(&center_pane);
+        cx.emit(Event::PaneAdded(center_pane.clone()));
 
-    //         app_state.workspace_store.update(cx, |store, _| {
-    //             store.workspaces.insert(weak_handle.clone());
-    //         });
+        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
+        app_state.workspace_store.update(cx, |store, _| {
+            store.workspaces.insert(window_handle);
+        });
 
-    //         let mut current_user = app_state.user_store.read(cx).watch_current_user();
-    //         let mut connection_status = app_state.client.status();
-    //         let _observe_current_user = cx.spawn(|this, mut cx| async move {
-    //             current_user.recv().await;
-    //             connection_status.recv().await;
-    //             let mut stream =
-    //                 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
+        let mut current_user = app_state.user_store.read(cx).watch_current_user();
+        let mut connection_status = app_state.client.status();
+        let _observe_current_user = cx.spawn(|this, mut cx| async move {
+            current_user.next().await;
+            connection_status.next().await;
+            let mut stream =
+                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 
-    //             while stream.recv().await.is_some() {
-    //                 this.update(&mut cx, |_, cx| cx.notify())?;
-    //             }
-    //             anyhow::Ok(())
-    //         });
+            while stream.recv().await.is_some() {
+                this.update(&mut cx, |_, cx| cx.notify())?;
+            }
+            anyhow::Ok(())
+        });
 
-    //         // All leader updates are enqueued and then processed in a single task, so
-    //         // that each asynchronous operation can be run in order.
-    //         let (leader_updates_tx, mut leader_updates_rx) =
-    //             mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
-    //         let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
-    //             while let Some((leader_id, update)) = leader_updates_rx.next().await {
-    //                 Self::process_leader_update(&this, leader_id, update, &mut cx)
-    //                     .await
-    //                     .log_err();
-    //             }
+        // All leader updates are enqueued and then processed in a single task, so
+        // that each asynchronous operation can be run in order.
+        let (leader_updates_tx, mut leader_updates_rx) =
+            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
+        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
+            while let Some((leader_id, update)) = leader_updates_rx.next().await {
+                Self::process_leader_update(&this, leader_id, update, &mut cx)
+                    .await
+                    .log_err();
+            }
 
-    //             Ok(())
-    //         });
+            Ok(())
+        });
 
-    //         cx.emit_global(WorkspaceCreated(weak_handle.clone()));
-
-    //         let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
-    //         let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
-    //         let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
-    //         let left_dock_buttons =
-    //             cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
-    //         let bottom_dock_buttons =
-    //             cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
-    //         let right_dock_buttons =
-    //             cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
-    //         let status_bar = cx.add_view(|cx| {
-    //             let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
-    //             status_bar.add_left_item(left_dock_buttons, cx);
-    //             status_bar.add_right_item(right_dock_buttons, cx);
-    //             status_bar.add_right_item(bottom_dock_buttons, cx);
-    //             status_bar
-    //         });
+        // todo!("replace with a different mechanism")
+        // cx.emit_global(WorkspaceCreated(weak_handle.clone()));
+
+        let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
+        let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
+        let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right));
+        let left_dock_buttons =
+            cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
+        let bottom_dock_buttons =
+            cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
+        let right_dock_buttons =
+            cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
+        let status_bar = cx.build_view(|cx| {
+            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
+            status_bar.add_left_item(left_dock_buttons, cx);
+            status_bar.add_right_item(right_dock_buttons, cx);
+            status_bar.add_right_item(bottom_dock_buttons, cx);
+            status_bar
+        });
 
-    //         cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
-    //             drag_and_drop.register_container(weak_handle.clone());
-    //         });
+        // todo!()
+        // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
+        //     drag_and_drop.register_container(weak_handle.clone());
+        // });
 
-    //         let mut active_call = None;
-    //         if cx.has_global::<ModelHandle<ActiveCall>>() {
-    //             let call = cx.global::<ModelHandle<ActiveCall>>().clone();
-    //             let mut subscriptions = Vec::new();
-    //             subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
-    //             active_call = Some((call, subscriptions));
-    //         }
+        let mut active_call = None;
+        if cx.has_global::<Model<ActiveCall>>() {
+            let call = cx.global::<Model<ActiveCall>>().clone();
+            let mut subscriptions = Vec::new();
+            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
+            active_call = Some((call, subscriptions));
+        }
 
-    //         let subscriptions = vec![
-    //             cx.observe_fullscreen(|_, _, cx| cx.notify()),
-    //             cx.observe_window_activation(Self::on_window_activation_changed),
-    //             cx.observe_window_bounds(move |_, mut bounds, display, cx| {
-    //                 // Transform fixed bounds to be stored in terms of the containing display
-    //                 if let WindowBounds::Fixed(mut window_bounds) = bounds {
-    //                     if let Some(screen) = cx.platform().screen_by_id(display) {
-    //                         let screen_bounds = screen.bounds();
-    //                         window_bounds
-    //                             .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
-    //                         window_bounds
-    //                             .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
-    //                         bounds = WindowBounds::Fixed(window_bounds);
-    //                     }
-    //                 }
+        let subscriptions = vec![
+            cx.observe_fullscreen(|_, _, cx| cx.notify()),
+            cx.observe_window_activation(Self::on_window_activation_changed),
+            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
+                // Transform fixed bounds to be stored in terms of the containing display
+                if let WindowBounds::Fixed(mut window_bounds) = bounds {
+                    if let Some(screen) = cx.platform().screen_by_id(display) {
+                        let screen_bounds = screen.bounds();
+                        window_bounds
+                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
+                        window_bounds
+                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
+                        bounds = WindowBounds::Fixed(window_bounds);
+                    }
+                }
 
-    //                 cx.background()
-    //                     .spawn(DB.set_window_bounds(workspace_id, bounds, display))
-    //                     .detach_and_log_err(cx);
-    //             }),
-    //             cx.observe(&left_dock, |this, _, cx| {
-    //                 this.serialize_workspace(cx);
-    //                 cx.notify();
-    //             }),
-    //             cx.observe(&bottom_dock, |this, _, cx| {
-    //                 this.serialize_workspace(cx);
-    //                 cx.notify();
-    //             }),
-    //             cx.observe(&right_dock, |this, _, cx| {
-    //                 this.serialize_workspace(cx);
-    //                 cx.notify();
-    //             }),
-    //         ];
-
-    //         cx.defer(|this, cx| this.update_window_title(cx));
-    //         Workspace {
-    //             weak_self: weak_handle.clone(),
-    //             modal: None,
-    //             zoomed: None,
-    //             zoomed_position: None,
-    //             center: PaneGroup::new(center_pane.clone()),
-    //             panes: vec![center_pane.clone()],
-    //             panes_by_item: Default::default(),
-    //             active_pane: center_pane.clone(),
-    //             last_active_center_pane: Some(center_pane.downgrade()),
-    //             last_active_view_id: None,
-    //             status_bar,
-    //             titlebar_item: None,
-    //             notifications: Default::default(),
-    //             left_dock,
-    //             bottom_dock,
-    //             right_dock,
-    //             project: project.clone(),
-    //             follower_states: Default::default(),
-    //             last_leaders_by_pane: Default::default(),
-    //             window_edited: false,
-    //             active_call,
-    //             database_id: workspace_id,
-    //             app_state,
-    //             _observe_current_user,
-    //             _apply_leader_updates,
-    //             _schedule_serialize: None,
-    //             leader_updates_tx,
-    //             subscriptions,
-    //             pane_history_timestamp,
-    //         }
-    //     }
+                cx.background()
+                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
+                    .detach_and_log_err(cx);
+            }),
+            cx.observe(&left_dock, |this, _, cx| {
+                this.serialize_workspace(cx);
+                cx.notify();
+            }),
+            cx.observe(&bottom_dock, |this, _, cx| {
+                this.serialize_workspace(cx);
+                cx.notify();
+            }),
+            cx.observe(&right_dock, |this, _, cx| {
+                this.serialize_workspace(cx);
+                cx.notify();
+            }),
+        ];
+
+        cx.defer(|this, cx| this.update_window_title(cx));
+        Workspace {
+            weak_self: weak_handle.clone(),
+            // modal: None,
+            // zoomed: None,
+            // zoomed_position: None,
+            center: PaneGroup::new(center_pane.clone()),
+            panes: vec![center_pane.clone()],
+            panes_by_item: Default::default(),
+            active_pane: center_pane.clone(),
+            last_active_center_pane: Some(center_pane.downgrade()),
+            // last_active_view_id: None,
+            // status_bar,
+            // titlebar_item: None,
+            notifications: Default::default(),
+            left_dock,
+            bottom_dock,
+            right_dock,
+            project: project.clone(),
+            follower_states: Default::default(),
+            last_leaders_by_pane: Default::default(),
+            window_edited: false,
+            active_call,
+            database_id: workspace_id,
+            app_state,
+            _observe_current_user,
+            _apply_leader_updates,
+            _schedule_serialize: None,
+            leader_updates_tx,
+            subscriptions,
+            pane_history_timestamp,
+        }
+    }
 
     fn new_local(
         abs_paths: Vec<PathBuf>,
@@ -832,54 +843,39 @@ impl Workspace {
                 });
                 window
             } else {
-                {
-                    let window_bounds_override = window_bounds_env_override(&cx);
-                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
-                        (Some(bounds), None)
-                    } else {
-                        serialized_workspace
-                            .as_ref()
-                            .and_then(|serialized_workspace| {
-                                let display = serialized_workspace.display?;
-                                let mut bounds = serialized_workspace.bounds?;
-
-                                // Stored bounds are relative to the containing display.
-                                // So convert back to global coordinates if that screen still exists
-                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                                    if let Some(screen) = cx.platform().screen_by_id(display) {
-                                        let screen_bounds = screen.bounds();
-                                        window_bounds.origin.x += screen_bounds.origin.x;
-                                        window_bounds.origin.y += screen_bounds.origin.y;
-                                        bounds = WindowBounds::Fixed(window_bounds);
-                                    } else {
-                                        // Screen no longer exists. Return none here.
-                                        return None;
-                                    }
-                                }
-
-                                Some((bounds, display))
-                            })
-                            .unzip()
-                    };
-
-                    // Use the serialized workspace to construct the new window
-                    cx.open_window(
-                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
-                        |cx| {
-                            Workspace::new(
-                                workspace_id,
-                                project_handle.clone(),
-                                app_state.clone(),
-                                cx,
-                            )
-                        },
-                    )
-                }
-            };
+                let window_bounds_override = window_bounds_env_override(&cx);
+                let (bounds, display) = if let Some(bounds) = window_bounds_override {
+                    (Some(bounds), None)
+                } else {
+                    serialized_workspace
+                        .as_ref()
+                        .and_then(|serialized_workspace| {
+                            let display = serialized_workspace.display?;
+                            let mut bounds = serialized_workspace.bounds?;
+
+                            // Stored bounds are relative to the containing display.
+                            // So convert back to global coordinates if that screen still exists
+                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
+                                let screen =
+                                    cx.update(|cx| cx.display_for_uuid(display)).ok()??;
+                                let screen_bounds = screen.bounds();
+                                window_bounds.origin.x += screen_bounds.origin.x;
+                                window_bounds.origin.y += screen_bounds.origin.y;
+                                bounds = WindowBounds::Fixed(window_bounds);
+                            }
 
-            // We haven't yielded the main thread since obtaining the window handle,
-            // so the window exists.
-            let workspace = window.root(&cx).unwrap();
+                            Some((bounds, display))
+                        })
+                        .unzip()
+                };
+
+                // Use the serialized workspace to construct the new window
+                let options =
+                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
+                cx.open_window(options, |cx| {
+                    Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+                })?
+            };
 
             (app_state.initialize_workspace)(
                 workspace.downgrade(),
@@ -1594,7 +1590,7 @@ impl Workspace {
     //     pub fn toggle_modal<V, F>(
     //         &mut self,
     //         cx: &mut ViewContext<Self>,
-    //         add_view: F,
+    //         build_view: F,
     //     ) -> Option<View<V>>
     //     where
     //         V: 'static + Modal,
@@ -1610,7 +1606,7 @@ impl Workspace {
     //             cx.focus_self();
     //             Some(already_open_modal)
     //         } else {
-    //             let modal = add_view(self, cx);
+    //             let modal = build_view(self, cx);
     //             cx.subscribe(&modal, |this, _, event, cx| {
     //                 if V::dismiss_on_event(event) {
     //                     this.dismiss_modal(cx);
@@ -1647,12 +1643,12 @@ impl Workspace {
     //         }
     //     }
 
-    //     pub fn items<'a>(
-    //         &'a self,
-    //         cx: &'a AppContext,
-    //     ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
-    //         self.panes.iter().flat_map(|pane| pane.read(cx).items())
-    //     }
+    pub fn items<'a>(
+        &'a self,
+        cx: &'a AppContext,
+    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
+        self.panes.iter().flat_map(|pane| pane.read(cx).items())
+    }
 
     //     pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
     //         self.items_of_type(cx).max_by_key(|item| item.id())
@@ -1667,9 +1663,9 @@ impl Workspace {
     //             .flat_map(|pane| pane.read(cx).items_of_type())
     //     }
 
-    //     pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-    //         self.active_pane().read(cx).active_item()
-    //     }
+    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+        self.active_pane().read(cx).active_item()
+    }
 
     //     fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
     //         self.active_item(cx).and_then(|item| item.project_path(cx))
@@ -2133,7 +2129,7 @@ impl Workspace {
     //             return item;
     //         }
 
-    //         let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
+    //         let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
     //         self.add_item(Box::new(item.clone()), cx);
     //         item
     //     }
@@ -2157,7 +2153,7 @@ impl Workspace {
     //             return item;
     //         }
 
-    //         let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
+    //         let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
     //         self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
     //         item
     //     }
@@ -2282,64 +2278,65 @@ impl Workspace {
     //         cx.notify();
     //     }
 
-    //     fn handle_pane_event(
-    //         &mut self,
-    //         pane: View<Pane>,
-    //         event: &pane::Event,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         match event {
-    //             pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
-    //             pane::Event::Split(direction) => {
-    //                 self.split_and_clone(pane, *direction, cx);
-    //             }
-    //             pane::Event::Remove => self.remove_pane(pane, cx),
-    //             pane::Event::ActivateItem { local } => {
-    //                 if *local {
-    //                     self.unfollow(&pane, cx);
-    //                 }
-    //                 if &pane == self.active_pane() {
-    //                     self.active_item_path_changed(cx);
-    //                 }
-    //             }
-    //             pane::Event::ChangeItemTitle => {
-    //                 if pane == self.active_pane {
-    //                     self.active_item_path_changed(cx);
-    //                 }
-    //                 self.update_window_edited(cx);
-    //             }
-    //             pane::Event::RemoveItem { item_id } => {
-    //                 self.update_window_edited(cx);
-    //                 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
-    //                     if entry.get().id() == pane.id() {
-    //                         entry.remove();
-    //                     }
-    //                 }
-    //             }
-    //             pane::Event::Focus => {
-    //                 self.handle_pane_focused(pane.clone(), cx);
-    //             }
-    //             pane::Event::ZoomIn => {
-    //                 if pane == self.active_pane {
-    //                     pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
-    //                     if pane.read(cx).has_focus() {
-    //                         self.zoomed = Some(pane.downgrade().into_any());
-    //                         self.zoomed_position = None;
-    //                     }
-    //                     cx.notify();
-    //                 }
-    //             }
-    //             pane::Event::ZoomOut => {
-    //                 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-    //                 if self.zoomed_position.is_none() {
-    //                     self.zoomed = None;
-    //                 }
-    //                 cx.notify();
-    //             }
-    //         }
+    fn handle_pane_event(
+        &mut self,
+        pane: View<Pane>,
+        event: &pane::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        todo!()
+        // match event {
+        //     pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
+        //     pane::Event::Split(direction) => {
+        //         self.split_and_clone(pane, *direction, cx);
+        //     }
+        //     pane::Event::Remove => self.remove_pane(pane, cx),
+        //     pane::Event::ActivateItem { local } => {
+        //         if *local {
+        //             self.unfollow(&pane, cx);
+        //         }
+        //         if &pane == self.active_pane() {
+        //             self.active_item_path_changed(cx);
+        //         }
+        //     }
+        //     pane::Event::ChangeItemTitle => {
+        //         if pane == self.active_pane {
+        //             self.active_item_path_changed(cx);
+        //         }
+        //         self.update_window_edited(cx);
+        //     }
+        //     pane::Event::RemoveItem { item_id } => {
+        //         self.update_window_edited(cx);
+        //         if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
+        //             if entry.get().id() == pane.id() {
+        //                 entry.remove();
+        //             }
+        //         }
+        //     }
+        //     pane::Event::Focus => {
+        //         self.handle_pane_focused(pane.clone(), cx);
+        //     }
+        //     pane::Event::ZoomIn => {
+        //         if pane == self.active_pane {
+        //             pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
+        //             if pane.read(cx).has_focus() {
+        //                 self.zoomed = Some(pane.downgrade().into_any());
+        //                 self.zoomed_position = None;
+        //             }
+        //             cx.notify();
+        //         }
+        //     }
+        //     pane::Event::ZoomOut => {
+        //         pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+        //         if self.zoomed_position.is_none() {
+        //             self.zoomed = None;
+        //         }
+        //         cx.notify();
+        //     }
+        // }
 
-    //         self.serialize_workspace(cx);
-    //     }
+        // self.serialize_workspace(cx);
+    }
 
     //     pub fn split_pane(
     //         &mut self,
@@ -2468,27 +2465,27 @@ impl Workspace {
     //         }
     //     }
 
-    //     pub fn panes(&self) -> &[View<Pane>] {
-    //         &self.panes
-    //     }
+    pub fn panes(&self) -> &[View<Pane>] {
+        &self.panes
+    }
 
-    //     pub fn active_pane(&self) -> &View<Pane> {
-    //         &self.active_pane
-    //     }
+    pub fn active_pane(&self) -> &View<Pane> {
+        &self.active_pane
+    }
 
-    //     fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
-    //         self.follower_states.retain(|_, state| {
-    //             if state.leader_id == peer_id {
-    //                 for item in state.items_by_leader_view_id.values() {
-    //                     item.set_leader_peer_id(None, cx);
-    //                 }
-    //                 false
-    //             } else {
-    //                 true
-    //             }
-    //         });
-    //         cx.notify();
-    //     }
+    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+        self.follower_states.retain(|_, state| {
+            if state.leader_id == peer_id {
+                for item in state.items_by_leader_view_id.values() {
+                    item.set_leader_peer_id(None, cx);
+                }
+                false
+            } else {
+                true
+            }
+        });
+        cx.notify();
+    }
 
     //     fn start_following(
     //         &mut self,
@@ -2703,60 +2700,62 @@ impl Workspace {
     //         self.update_window_title(cx);
     //     }
 
-    //     fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
-    //         let project = self.project().read(cx);
-    //         let mut title = String::new();
-
-    //         if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
-    //             let filename = path
-    //                 .path
-    //                 .file_name()
-    //                 .map(|s| s.to_string_lossy())
-    //                 .or_else(|| {
-    //                     Some(Cow::Borrowed(
-    //                         project
-    //                             .worktree_for_id(path.worktree_id, cx)?
-    //                             .read(cx)
-    //                             .root_name(),
-    //                     ))
-    //                 });
+    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
+        let project = self.project().read(cx);
+        let mut title = String::new();
+
+        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
+            let filename = path
+                .path
+                .file_name()
+                .map(|s| s.to_string_lossy())
+                .or_else(|| {
+                    Some(Cow::Borrowed(
+                        project
+                            .worktree_for_id(path.worktree_id, cx)?
+                            .read(cx)
+                            .root_name(),
+                    ))
+                });
 
-    //             if let Some(filename) = filename {
-    //                 title.push_str(filename.as_ref());
-    //                 title.push_str(" — ");
-    //             }
-    //         }
+            if let Some(filename) = filename {
+                title.push_str(filename.as_ref());
+                title.push_str(" — ");
+            }
+        }
 
-    //         for (i, name) in project.worktree_root_names(cx).enumerate() {
-    //             if i > 0 {
-    //                 title.push_str(", ");
-    //             }
-    //             title.push_str(name);
-    //         }
+        for (i, name) in project.worktree_root_names(cx).enumerate() {
+            if i > 0 {
+                title.push_str(", ");
+            }
+            title.push_str(name);
+        }
 
-    //         if title.is_empty() {
-    //             title = "empty project".to_string();
-    //         }
+        if title.is_empty() {
+            title = "empty project".to_string();
+        }
 
-    //         if project.is_remote() {
-    //             title.push_str(" ↙");
-    //         } else if project.is_shared() {
-    //             title.push_str(" ↗");
-    //         }
+        if project.is_remote() {
+            title.push_str(" ↙");
+        } else if project.is_shared() {
+            title.push_str(" ↗");
+        }
 
-    //         cx.set_window_title(&title);
-    //     }
+        todo!()
+        // cx.set_window_title(&title);
+    }
 
-    //     fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
-    //         let is_edited = !self.project.read(cx).is_read_only()
-    //             && self
-    //                 .items(cx)
-    //                 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
-    //         if is_edited != self.window_edited {
-    //             self.window_edited = is_edited;
-    //             cx.set_window_edited(self.window_edited)
-    //         }
-    //     }
+    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
+        let is_edited = !self.project.read(cx).is_read_only()
+            && self
+                .items(cx)
+                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
+        if is_edited != self.window_edited {
+            self.window_edited = is_edited;
+            todo!()
+            // cx.set_window_edited(self.window_edited)
+        }
+    }
 
     //     fn render_disconnected_overlay(
     //         &self,
@@ -2875,64 +2874,64 @@ impl Workspace {
             .ok();
     }
 
-    //     async fn process_leader_update(
-    //         this: &WeakView<Self>,
-    //         leader_id: PeerId,
-    //         update: proto::UpdateFollowers,
-    //         cx: &mut AsyncAppContext,
-    //     ) -> Result<()> {
-    //         match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
-    //             proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
-    //                 this.update(cx, |this, _| {
-    //                     for (_, state) in &mut this.follower_states {
-    //                         if state.leader_id == leader_id {
-    //                             state.active_view_id =
-    //                                 if let Some(active_view_id) = update_active_view.id.clone() {
-    //                                     Some(ViewId::from_proto(active_view_id)?)
-    //                                 } else {
-    //                                     None
-    //                                 };
-    //                         }
-    //                     }
-    //                     anyhow::Ok(())
-    //                 })??;
-    //             }
-    //             proto::update_followers::Variant::UpdateView(update_view) => {
-    //                 let variant = update_view
-    //                     .variant
-    //                     .ok_or_else(|| anyhow!("missing update view variant"))?;
-    //                 let id = update_view
-    //                     .id
-    //                     .ok_or_else(|| anyhow!("missing update view id"))?;
-    //                 let mut tasks = Vec::new();
-    //                 this.update(cx, |this, cx| {
-    //                     let project = this.project.clone();
-    //                     for (_, state) in &mut this.follower_states {
-    //                         if state.leader_id == leader_id {
-    //                             let view_id = ViewId::from_proto(id.clone())?;
-    //                             if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
-    //                                 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
-    //                             }
-    //                         }
-    //                     }
-    //                     anyhow::Ok(())
-    //                 })??;
-    //                 try_join_all(tasks).await.log_err();
-    //             }
-    //             proto::update_followers::Variant::CreateView(view) => {
-    //                 let panes = this.read_with(cx, |this, _| {
-    //                     this.follower_states
-    //                         .iter()
-    //                         .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
-    //                         .cloned()
-    //                         .collect()
-    //                 })?;
-    //                 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
-    //             }
-    //         }
-    //         this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
-    //         Ok(())
-    //     }
+    async fn process_leader_update(
+        this: &WeakView<Self>,
+        leader_id: PeerId,
+        update: proto::UpdateFollowers,
+        cx: &mut AsyncAppContext,
+    ) -> Result<()> {
+        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
+            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
+                this.update(cx, |this, _| {
+                    for (_, state) in &mut this.follower_states {
+                        if state.leader_id == leader_id {
+                            state.active_view_id =
+                                if let Some(active_view_id) = update_active_view.id.clone() {
+                                    Some(ViewId::from_proto(active_view_id)?)
+                                } else {
+                                    None
+                                };
+                        }
+                    }
+                    anyhow::Ok(())
+                })??;
+            }
+            proto::update_followers::Variant::UpdateView(update_view) => {
+                let variant = update_view
+                    .variant
+                    .ok_or_else(|| anyhow!("missing update view variant"))?;
+                let id = update_view
+                    .id
+                    .ok_or_else(|| anyhow!("missing update view id"))?;
+                let mut tasks = Vec::new();
+                this.update(cx, |this, cx| {
+                    let project = this.project.clone();
+                    for (_, state) in &mut this.follower_states {
+                        if state.leader_id == leader_id {
+                            let view_id = ViewId::from_proto(id.clone())?;
+                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
+                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
+                            }
+                        }
+                    }
+                    anyhow::Ok(())
+                })??;
+                try_join_all(tasks).await.log_err();
+            }
+            proto::update_followers::Variant::CreateView(view) => {
+                let panes = this.read_with(cx, |this, _| {
+                    this.follower_states
+                        .iter()
+                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
+                        .cloned()
+                        .collect()
+                })?;
+                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
+            }
+        }
+        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
+        Ok(())
+    }
 
     //     async fn add_views_from_leader(
     //         this: WeakView<Self>,