WIP

Antonio Scandurra created

Change summary

crates/workspace2/src/item.rs               |  680 ++++++------
crates/workspace2/src/pane.rs               |   43 
crates/workspace2/src/pane_group.rs         | 1202 +++++++++++-----------
crates/workspace2/src/persistence.rs        |  849 ++++++++--------
crates/workspace2/src/persistence/model.rs  |   32 
crates/workspace2/src/toolbar.rs            |  186 +-
crates/workspace2/src/workspace2.rs         |  295 ++--
crates/workspace2/src/workspace_settings.rs |    6 
8 files changed, 1,652 insertions(+), 1,641 deletions(-)

Detailed changes

crates/workspace2/src/item.rs 🔗

@@ -8,6 +8,7 @@ use client2::{
     proto::{self, PeerId, ViewId},
     Client,
 };
+use settings2::Settings;
 use theme2::Theme;
 // use client2::{
 //     proto::{self, PeerId},
@@ -16,8 +17,8 @@ use theme2::Theme;
 // use gpui2::geometry::vector::Vector2F;
 // use gpui2::AnyWindowHandle;
 // use gpui2::{
-//     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
-//     ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+//     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Handle, Task, View,
+//     ViewContext, View, WeakViewHandle, WindowContext,
 // };
 // use project2::{Project, ProjectEntryId, ProjectPath};
 // use schemars::JsonSchema;
@@ -97,7 +98,7 @@ pub struct BreadcrumbText {
     pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
 }
 
-pub trait Item: EventEmitter {
+pub trait Item: EventEmitter + Sized {
     //     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
     //     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
     //     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
@@ -138,14 +139,14 @@ pub trait Item: EventEmitter {
     //     }
     //     fn save(
     //         &mut self,
-    //         _project: ModelHandle<Project>,
+    //         _project: Handle<Project>,
     //         _cx: &mut ViewContext<Self>,
     //     ) -> Task<Result<()>> {
     //         unimplemented!("save() must be implemented if can_save() returns true")
     //     }
     //     fn save_as(
     //         &mut self,
-    //         _project: ModelHandle<Project>,
+    //         _project: Handle<Project>,
     //         _abs_path: PathBuf,
     //         _cx: &mut ViewContext<Self>,
     //     ) -> Task<Result<()>> {
@@ -153,7 +154,7 @@ pub trait Item: EventEmitter {
     //     }
     //     fn reload(
     //         &mut self,
-    //         _project: ModelHandle<Project>,
+    //         _project: Handle<Project>,
     //         _cx: &mut ViewContext<Self>,
     //     ) -> Task<Result<()>> {
     //         unimplemented!("reload() must be implemented if can_save() returns true")
@@ -171,7 +172,7 @@ pub trait Item: EventEmitter {
     //     fn act_as_type<'a>(
     //         &'a self,
     //         type_id: TypeId,
-    //         self_handle: &'a ViewHandle<Self>,
+    //         self_handle: &'a View<Self>,
     //         _: &'a AppContext,
     //     ) -> Option<&AnyViewHandle> {
     //         if TypeId::of::<Self>() == type_id {
@@ -181,7 +182,7 @@ pub trait Item: EventEmitter {
     //         }
     //     }
 
-    //     fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    //     fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
     //         None
     //     }
 
@@ -200,12 +201,12 @@ pub trait Item: EventEmitter {
     //     }
 
     //     fn deserialize(
-    //         _project: ModelHandle<Project>,
+    //         _project: Handle<Project>,
     //         _workspace: WeakViewHandle<Workspace>,
     //         _workspace_id: WorkspaceId,
     //         _item_id: ItemId,
     //         _cx: &mut ViewContext<Pane>,
-    //     ) -> Task<Result<ViewHandle<Self>>> {
+    //     ) -> Task<Result<View<Self>>> {
     //         unimplemented!(
     //             "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
     //         )
@@ -218,27 +219,36 @@ pub trait Item: EventEmitter {
     //     }
 }
 
-use core::fmt;
 use std::{
-    any::{Any, TypeId},
+    any::Any,
     borrow::Cow,
+    cell::RefCell,
     ops::Range,
     path::PathBuf,
-    sync::Arc,
+    rc::Rc,
+    sync::{
+        atomic::{AtomicBool, Ordering},
+        Arc,
+    },
+    time::Duration,
 };
 
 use gpui2::{
-    AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels,
-    Point, Task, View, ViewContext, WindowContext,
+    AnyElement, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, Point,
+    Task, View, ViewContext, WindowContext,
 };
 use project2::{Project, ProjectEntryId, ProjectPath};
 use smallvec::SmallVec;
 
 use crate::{
-    pane::Pane, searchable::SearchableItemHandle, ToolbarItemLocation, Workspace, WorkspaceId,
+    pane::{self, Pane},
+    searchable::SearchableItemHandle,
+    workspace_settings::{AutosaveSetting, WorkspaceSettings},
+    DelayedDebouncedEditAction, FollowableItemBuilders, ToolbarItemLocation, Workspace,
+    WorkspaceId,
 };
 
-pub trait ItemHandle: 'static + fmt::Debug + Send {
+pub trait ItemHandle: 'static + Send {
     fn subscribe_to_item_events(
         &self,
         cx: &mut WindowContext,
@@ -305,355 +315,347 @@ pub trait WeakItemHandle {
 
 // todo!()
 // impl dyn ItemHandle {
-//     pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
+//     pub fn downcast<T: View>(&self) -> Option<View<T>> {
 //         self.as_any().clone().downcast()
 //     }
 
-//     pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
+//     pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<View<T>> {
 //         self.act_as_type(TypeId::of::<T>(), cx)
 //             .and_then(|t| t.clone().downcast())
 //     }
 // }
 
-// impl<T: Item> ItemHandle for ViewHandle<T> {
-//     fn subscribe_to_item_events(
-//         &self,
-//         cx: &mut WindowContext,
-//         handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
-//     ) -> gpui2::Subscription {
-//         cx.subscribe(self, move |_, event, cx| {
-//             for item_event in T::to_item_events(event) {
-//                 handler(item_event, cx)
-//             }
-//         })
-//     }
-
-//     fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>> {
-//         self.read(cx).tab_tooltip_text(cx)
-//     }
-
-//     fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
-//         self.read(cx).tab_description(detail, cx)
-//     }
-
-//     fn tab_content(
-//         &self,
-//         detail: Option<usize>,
-//         style: &theme2::Tab,
-//         cx: &AppContext,
-//     ) -> AnyElement<Pane> {
-//         self.read(cx).tab_content(detail, style, cx)
-//     }
-
-//     fn dragged_tab_content(
-//         &self,
-//         detail: Option<usize>,
-//         style: &theme2::Tab,
-//         cx: &AppContext,
-//     ) -> AnyElement<Workspace> {
-//         self.read(cx).tab_content(detail, style, cx)
-//     }
+impl<T: Item> ItemHandle for View<T> {
+    fn subscribe_to_item_events(
+        &self,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
+    ) -> gpui2::Subscription {
+        cx.subscribe(self, move |_, event, cx| {
+            for item_event in T::to_item_events(event) {
+                handler(item_event, cx)
+            }
+        })
+    }
 
-//     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
-//         let this = self.read(cx);
-//         let mut result = None;
-//         if this.is_singleton(cx) {
-//             this.for_each_project_item(cx, &mut |_, item| {
-//                 result = item.project_path(cx);
-//             });
-//         }
-//         result
-//     }
+    fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+        self.read(cx).tab_tooltip_text(cx)
+    }
 
-//     fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
-//         let mut result = SmallVec::new();
-//         self.read(cx).for_each_project_item(cx, &mut |_, item| {
-//             if let Some(id) = item.entry_id(cx) {
-//                 result.push(id);
-//             }
-//         });
-//         result
-//     }
+    fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+        self.read(cx).tab_description(detail, cx)
+    }
 
-//     fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> {
-//         let mut result = SmallVec::new();
-//         self.read(cx).for_each_project_item(cx, &mut |id, _| {
-//             result.push(id);
-//         });
-//         result
-//     }
+    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane> {
+        self.read(cx).tab_content(detail, cx)
+    }
 
-//     fn for_each_project_item(
-//         &self,
-//         cx: &AppContext,
-//         f: &mut dyn FnMut(usize, &dyn project2::Item),
-//     ) {
-//         self.read(cx).for_each_project_item(cx, f)
-//     }
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace> {
+        self.read(cx).tab_content(detail, cx)
+    }
 
-//     fn is_singleton(&self, cx: &AppContext) -> bool {
-//         self.read(cx).is_singleton(cx)
-//     }
+    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
+        let this = self.read(cx);
+        let mut result = None;
+        if this.is_singleton(cx) {
+            this.for_each_project_item(cx, &mut |_, item| {
+                result = item.project_path(cx);
+            });
+        }
+        result
+    }
 
-//     fn boxed_clone(&self) -> Box<dyn ItemHandle> {
-//         Box::new(self.clone())
-//     }
+    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
+        let mut result = SmallVec::new();
+        self.read(cx).for_each_project_item(cx, &mut |_, item| {
+            if let Some(id) = item.entry_id(cx) {
+                result.push(id);
+            }
+        });
+        result
+    }
 
-//     fn clone_on_split(
-//         &self,
-//         workspace_id: WorkspaceId,
-//         cx: &mut WindowContext,
-//     ) -> Option<Box<dyn ItemHandle>> {
-//         self.update(cx, |item, cx| {
-//             cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
-//         })
-//         .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
-//     }
+    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> {
+        let mut result = SmallVec::new();
+        self.read(cx).for_each_project_item(cx, &mut |id, _| {
+            result.push(id);
+        });
+        result
+    }
 
-//     fn added_to_pane(
-//         &self,
-//         workspace: &mut Workspace,
-//         pane: ViewHandle<Pane>,
-//         cx: &mut ViewContext<Workspace>,
-//     ) {
-//         let history = pane.read(cx).nav_history_for_item(self);
-//         self.update(cx, |this, cx| {
-//             this.set_nav_history(history, cx);
-//             this.added_to_workspace(workspace, cx);
-//         });
-
-//         if let Some(followed_item) = self.to_followable_item_handle(cx) {
-//             if let Some(message) = followed_item.to_state_proto(cx) {
-//                 workspace.update_followers(
-//                     followed_item.is_project_item(cx),
-//                     proto::update_followers::Variant::CreateView(proto::View {
-//                         id: followed_item
-//                             .remote_id(&workspace.app_state.client, cx)
-//                             .map(|id| id.to_proto()),
-//                         variant: Some(message),
-//                         leader_id: workspace.leader_for_pane(&pane),
-//                     }),
-//                     cx,
-//                 );
-//             }
-//         }
+    fn for_each_project_item(
+        &self,
+        cx: &AppContext,
+        f: &mut dyn FnMut(usize, &dyn project2::Item),
+    ) {
+        self.read(cx).for_each_project_item(cx, f)
+    }
 
-//         if workspace
-//             .panes_by_item
-//             .insert(self.id(), pane.downgrade())
-//             .is_none()
-//         {
-//             let mut pending_autosave = DelayedDebouncedEditAction::new();
-//             let pending_update = Rc::new(RefCell::new(None));
-//             let pending_update_scheduled = Rc::new(AtomicBool::new(false));
-
-//             let mut event_subscription =
-//                 Some(cx.subscribe(self, move |workspace, item, event, cx| {
-//                     let pane = if let Some(pane) = workspace
-//                         .panes_by_item
-//                         .get(&item.id())
-//                         .and_then(|pane| pane.upgrade(cx))
-//                     {
-//                         pane
-//                     } else {
-//                         log::error!("unexpected item event after pane was dropped");
-//                         return;
-//                     };
-
-//                     if let Some(item) = item.to_followable_item_handle(cx) {
-//                         let is_project_item = item.is_project_item(cx);
-//                         let leader_id = workspace.leader_for_pane(&pane);
-
-//                         if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
-//                             workspace.unfollow(&pane, cx);
-//                         }
-
-//                         if item.add_event_to_update_proto(
-//                             event,
-//                             &mut *pending_update.borrow_mut(),
-//                             cx,
-//                         ) && !pending_update_scheduled.load(Ordering::SeqCst)
-//                         {
-//                             pending_update_scheduled.store(true, Ordering::SeqCst);
-//                             cx.after_window_update({
-//                                 let pending_update = pending_update.clone();
-//                                 let pending_update_scheduled = pending_update_scheduled.clone();
-//                                 move |this, cx| {
-//                                     pending_update_scheduled.store(false, Ordering::SeqCst);
-//                                     this.update_followers(
-//                                         is_project_item,
-//                                         proto::update_followers::Variant::UpdateView(
-//                                             proto::UpdateView {
-//                                                 id: item
-//                                                     .remote_id(&this.app_state.client, cx)
-//                                                     .map(|id| id.to_proto()),
-//                                                 variant: pending_update.borrow_mut().take(),
-//                                                 leader_id,
-//                                             },
-//                                         ),
-//                                         cx,
-//                                     );
-//                                 }
-//                             });
-//                         }
-//                     }
-
-//                     for item_event in T::to_item_events(event).into_iter() {
-//                         match item_event {
-//                             ItemEvent::CloseItem => {
-//                                 pane.update(cx, |pane, cx| {
-//                                     pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
-//                                 })
-//                                 .detach_and_log_err(cx);
-//                                 return;
-//                             }
-
-//                             ItemEvent::UpdateTab => {
-//                                 pane.update(cx, |_, cx| {
-//                                     cx.emit(pane::Event::ChangeItemTitle);
-//                                     cx.notify();
-//                                 });
-//                             }
-
-//                             ItemEvent::Edit => {
-//                                 let autosave = settings2::get::<WorkspaceSettings>(cx).autosave;
-//                                 if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
-//                                     let delay = Duration::from_millis(milliseconds);
-//                                     let item = item.clone();
-//                                     pending_autosave.fire_new(delay, cx, move |workspace, cx| {
-//                                         Pane::autosave_item(&item, workspace.project().clone(), cx)
-//                                     });
-//                                 }
-//                             }
-
-//                             _ => {}
-//                         }
-//                     }
-//                 }));
-
-//             cx.observe_focus(self, move |workspace, item, focused, cx| {
-//                 if !focused
-//                     && settings2::get::<WorkspaceSettings>(cx).autosave
-//                         == AutosaveSetting::OnFocusChange
-//                 {
-//                     Pane::autosave_item(&item, workspace.project.clone(), cx)
-//                         .detach_and_log_err(cx);
-//                 }
-//             })
-//             .detach();
+    fn is_singleton(&self, cx: &AppContext) -> bool {
+        self.read(cx).is_singleton(cx)
+    }
 
-//             let item_id = self.id();
-//             cx.observe_release(self, move |workspace, _, _| {
-//                 workspace.panes_by_item.remove(&item_id);
-//                 event_subscription.take();
-//             })
-//             .detach();
-//         }
+    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
+        Box::new(self.clone())
+    }
 
-//         cx.defer(|workspace, cx| {
-//             workspace.serialize_workspace(cx);
-//         });
-//     }
+    fn clone_on_split(
+        &self,
+        workspace_id: WorkspaceId,
+        cx: &mut WindowContext,
+    ) -> Option<Box<dyn ItemHandle>> {
+        self.update(cx, |item, cx| {
+            cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
+        })
+        .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
+    }
 
-//     fn deactivated(&self, cx: &mut WindowContext) {
-//         self.update(cx, |this, cx| this.deactivated(cx));
-//     }
+    fn added_to_pane(
+        &self,
+        workspace: &mut Workspace,
+        pane: View<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let history = pane.read(cx).nav_history_for_item(self);
+        self.update(cx, |this, cx| {
+            this.set_nav_history(history, cx);
+            this.added_to_workspace(workspace, cx);
+        });
+
+        if let Some(followed_item) = self.to_followable_item_handle(cx) {
+            if let Some(message) = followed_item.to_state_proto(cx) {
+                workspace.update_followers(
+                    followed_item.is_project_item(cx),
+                    proto::update_followers::Variant::CreateView(proto::View {
+                        id: followed_item
+                            .remote_id(&workspace.app_state.client, cx)
+                            .map(|id| id.to_proto()),
+                        variant: Some(message),
+                        leader_id: workspace.leader_for_pane(&pane),
+                    }),
+                    cx,
+                );
+            }
+        }
+
+        if workspace
+            .panes_by_item
+            .insert(self.id(), pane.downgrade())
+            .is_none()
+        {
+            let mut pending_autosave = DelayedDebouncedEditAction::new();
+            let pending_update = Rc::new(RefCell::new(None));
+            let pending_update_scheduled = Rc::new(AtomicBool::new(false));
+
+            let mut event_subscription =
+                Some(cx.subscribe(self, move |workspace, item, event, cx| {
+                    let pane = if let Some(pane) = workspace
+                        .panes_by_item
+                        .get(&item.id())
+                        .and_then(|pane| pane.upgrade(cx))
+                    {
+                        pane
+                    } else {
+                        log::error!("unexpected item event after pane was dropped");
+                        return;
+                    };
+
+                    if let Some(item) = item.to_followable_item_handle(cx) {
+                        let is_project_item = item.is_project_item(cx);
+                        let leader_id = workspace.leader_for_pane(&pane);
+
+                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
+                            workspace.unfollow(&pane, cx);
+                        }
+
+                        if item.add_event_to_update_proto(
+                            event,
+                            &mut *pending_update.borrow_mut(),
+                            cx,
+                        ) && !pending_update_scheduled.load(Ordering::SeqCst)
+                        {
+                            pending_update_scheduled.store(true, Ordering::SeqCst);
+                            cx.after_window_update({
+                                let pending_update = pending_update.clone();
+                                let pending_update_scheduled = pending_update_scheduled.clone();
+                                move |this, cx| {
+                                    pending_update_scheduled.store(false, Ordering::SeqCst);
+                                    this.update_followers(
+                                        is_project_item,
+                                        proto::update_followers::Variant::UpdateView(
+                                            proto::UpdateView {
+                                                id: item
+                                                    .remote_id(&this.app_state.client, cx)
+                                                    .map(|id| id.to_proto()),
+                                                variant: pending_update.borrow_mut().take(),
+                                                leader_id,
+                                            },
+                                        ),
+                                        cx,
+                                    );
+                                }
+                            });
+                        }
+                    }
+
+                    for item_event in T::to_item_events(event).into_iter() {
+                        match item_event {
+                            ItemEvent::CloseItem => {
+                                pane.update(cx, |pane, cx| {
+                                    pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
+                                })
+                                .detach_and_log_err(cx);
+                                return;
+                            }
+
+                            ItemEvent::UpdateTab => {
+                                pane.update(cx, |_, cx| {
+                                    cx.emit(pane::Event::ChangeItemTitle);
+                                    cx.notify();
+                                });
+                            }
+
+                            ItemEvent::Edit => {
+                                let autosave = WorkspaceSettings::get_global(cx).autosave;
+                                if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
+                                    let delay = Duration::from_millis(milliseconds);
+                                    let item = item.clone();
+                                    pending_autosave.fire_new(delay, cx, move |workspace, cx| {
+                                        Pane::autosave_item(&item, workspace.project().clone(), cx)
+                                    });
+                                }
+                            }
+
+                            _ => {}
+                        }
+                    }
+                }));
+
+            cx.observe_focus(self, move |workspace, item, focused, cx| {
+                if !focused
+                    && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
+                {
+                    Pane::autosave_item(&item, workspace.project.clone(), cx)
+                        .detach_and_log_err(cx);
+                }
+            })
+            .detach();
+
+            let item_id = self.id();
+            cx.observe_release(self, move |workspace, _, _| {
+                workspace.panes_by_item.remove(&item_id);
+                event_subscription.take();
+            })
+            .detach();
+        }
+
+        cx.defer(|workspace, cx| {
+            workspace.serialize_workspace(cx);
+        });
+    }
 
-//     fn workspace_deactivated(&self, cx: &mut WindowContext) {
-//         self.update(cx, |this, cx| this.workspace_deactivated(cx));
-//     }
+    fn deactivated(&self, cx: &mut WindowContext) {
+        self.update(cx, |this, cx| this.deactivated(cx));
+    }
 
-//     fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
-//         self.update(cx, |this, cx| this.navigate(data, cx))
-//     }
+    fn workspace_deactivated(&self, cx: &mut WindowContext) {
+        self.update(cx, |this, cx| this.workspace_deactivated(cx));
+    }
 
-//     fn id(&self) -> usize {
-//         self.id()
-//     }
+    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
+        self.update(cx, |this, cx| this.navigate(data, cx))
+    }
 
-//     fn window(&self) -> AnyWindowHandle {
-//         AnyViewHandle::window(self)
-//     }
+    fn id(&self) -> usize {
+        self.id()
+    }
 
-//     fn as_any(&self) -> &AnyViewHandle {
-//         self
-//     }
+    fn window(&self) -> AnyWindowHandle {
+        todo!()
+        // AnyViewHandle::window(self)
+    }
 
-//     fn is_dirty(&self, cx: &AppContext) -> bool {
-//         self.read(cx).is_dirty(cx)
-//     }
+    // todo!()
+    // fn as_any(&self) -> &AnyViewHandle {
+    //     self
+    // }
 
-//     fn has_conflict(&self, cx: &AppContext) -> bool {
-//         self.read(cx).has_conflict(cx)
-//     }
+    fn is_dirty(&self, cx: &AppContext) -> bool {
+        self.read(cx).is_dirty(cx)
+    }
 
-//     fn can_save(&self, cx: &AppContext) -> bool {
-//         self.read(cx).can_save(cx)
-//     }
+    fn has_conflict(&self, cx: &AppContext) -> bool {
+        self.read(cx).has_conflict(cx)
+    }
 
-//     fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
-//         self.update(cx, |item, cx| item.save(project, cx))
-//     }
+    fn can_save(&self, cx: &AppContext) -> bool {
+        self.read(cx).can_save(cx)
+    }
 
-//     fn save_as(
-//         &self,
-//         project: ModelHandle<Project>,
-//         abs_path: PathBuf,
-//         cx: &mut WindowContext,
-//     ) -> Task<anyhow::Result<()>> {
-//         self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
-//     }
+    fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+        self.update(cx, |item, cx| item.save(project, cx))
+    }
 
-//     fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
-//         self.update(cx, |item, cx| item.reload(project, cx))
-//     }
+    fn save_as(
+        &self,
+        project: Handle<Project>,
+        abs_path: PathBuf,
+        cx: &mut WindowContext,
+    ) -> Task<anyhow::Result<()>> {
+        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
+    }
 
-//     fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
-//         self.read(cx).act_as_type(type_id, self, cx)
-//     }
+    fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+        self.update(cx, |item, cx| item.reload(project, cx))
+    }
 
-//     fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
-//         if cx.has_global::<FollowableItemBuilders>() {
-//             let builders = cx.global::<FollowableItemBuilders>();
-//             let item = self.as_any();
-//             Some(builders.get(&item.view_type())?.1(item))
-//         } else {
-//             None
-//         }
-//     }
+    // todo!()
+    // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
+    //     self.read(cx).act_as_type(type_id, self, cx)
+    // }
+
+    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
+        if cx.has_global::<FollowableItemBuilders>() {
+            let builders = cx.global::<FollowableItemBuilders>();
+            let item = self.as_any();
+            Some(builders.get(&item.view_type())?.1(item))
+        } else {
+            None
+        }
+    }
 
-//     fn on_release(
-//         &self,
-//         cx: &mut AppContext,
-//         callback: Box<dyn FnOnce(&mut AppContext)>,
-//     ) -> gpui2::Subscription {
-//         cx.observe_release(self, move |_, cx| callback(cx))
-//     }
+    fn on_release(
+        &self,
+        cx: &mut AppContext,
+        callback: Box<dyn FnOnce(&mut AppContext)>,
+    ) -> gpui2::Subscription {
+        cx.observe_release(self, move |_, cx| callback(cx))
+    }
 
-//     fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
-//         self.read(cx).as_searchable(self)
-//     }
+    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+        self.read(cx).as_searchable(self)
+    }
 
-//     fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
-//         self.read(cx).breadcrumb_location()
-//     }
+    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
+        self.read(cx).breadcrumb_location()
+    }
 
-//     fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-//         self.read(cx).breadcrumbs(theme, cx)
-//     }
+    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+        self.read(cx).breadcrumbs(theme, cx)
+    }
 
-//     fn serialized_item_kind(&self) -> Option<&'static str> {
-//         T::serialized_item_kind()
-//     }
+    fn serialized_item_kind(&self) -> Option<&'static str> {
+        T::serialized_item_kind()
+    }
 
-//     fn show_toolbar(&self, cx: &AppContext) -> bool {
-//         self.read(cx).show_toolbar()
-//     }
+    fn show_toolbar(&self, cx: &AppContext) -> bool {
+        self.read(cx).show_toolbar()
+    }
 
-//     fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
-//         self.read(cx).pixel_position_of_cursor(cx)
-//     }
-// }
+    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
+        self.read(cx).pixel_position_of_cursor(cx)
+    }
+}
 
 // impl From<Box<dyn ItemHandle>> for AnyViewHandle {
 //     fn from(val: Box<dyn ItemHandle>) -> Self {
@@ -747,7 +749,7 @@ pub trait FollowableItemHandle: ItemHandle {
     fn is_project_item(&self, cx: &AppContext) -> bool;
 }
 
-// impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
+// impl<T: FollowableItem> FollowableItemHandle for View<T> {
 //     fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
 //         self.read(cx).remote_id().or_else(|| {
 //             client.peer_id().map(|creator| ViewId {
@@ -780,7 +782,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //     fn apply_update_proto(
 //         &self,
-//         project: &ModelHandle<Project>,
+//         project: &Handle<Project>,
 //         message: proto::update_view::Variant,
 //         cx: &mut WindowContext,
 //     ) -> Task<Result<()>> {
@@ -805,8 +807,8 @@ pub trait FollowableItemHandle: ItemHandle {
 //     use super::{Item, ItemEvent};
 //     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
 //     use gpui2::{
-//         elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View,
-//         ViewContext, ViewHandle, WeakViewHandle,
+//         elements::Empty, AnyElement, AppContext, Element, Entity, Handle, Task, View,
+//         ViewContext, View, WeakViewHandle,
 //     };
 //     use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
 //     use smallvec::SmallVec;
@@ -827,7 +829,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //         pub is_dirty: bool,
 //         pub is_singleton: bool,
 //         pub has_conflict: bool,
-//         pub project_items: Vec<ModelHandle<TestProjectItem>>,
+//         pub project_items: Vec<Handle<TestProjectItem>>,
 //         pub nav_history: Option<ItemNavHistory>,
 //         pub tab_descriptions: Option<Vec<&'static str>>,
 //         pub tab_detail: Cell<Option<usize>>,
@@ -872,7 +874,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //     }
 
 //     impl TestProjectItem {
-//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> ModelHandle<Self> {
+//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Handle<Self> {
 //             let entry_id = Some(ProjectEntryId::from_proto(id));
 //             let project_path = Some(ProjectPath {
 //                 worktree_id: WorktreeId::from_usize(0),
@@ -884,7 +886,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //             })
 //         }
 
-//         pub fn new_untitled(cx: &mut AppContext) -> ModelHandle<Self> {
+//         pub fn new_untitled(cx: &mut AppContext) -> Handle<Self> {
 //             cx.add_model(|_| Self {
 //                 project_path: None,
 //                 entry_id: None,
@@ -937,7 +939,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //             self
 //         }
 
-//         pub fn with_project_items(mut self, items: &[ModelHandle<TestProjectItem>]) -> Self {
+//         pub fn with_project_items(mut self, items: &[Handle<TestProjectItem>]) -> Self {
 //             self.project_items.clear();
 //             self.project_items.extend(items.iter().cloned());
 //             self
@@ -1048,7 +1050,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //         fn save(
 //             &mut self,
-//             _: ModelHandle<Project>,
+//             _: Handle<Project>,
 //             _: &mut ViewContext<Self>,
 //         ) -> Task<anyhow::Result<()>> {
 //             self.save_count += 1;
@@ -1058,7 +1060,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //         fn save_as(
 //             &mut self,
-//             _: ModelHandle<Project>,
+//             _: Handle<Project>,
 //             _: std::path::PathBuf,
 //             _: &mut ViewContext<Self>,
 //         ) -> Task<anyhow::Result<()>> {
@@ -1069,7 +1071,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //         fn reload(
 //             &mut self,
-//             _: ModelHandle<Project>,
+//             _: Handle<Project>,
 //             _: &mut ViewContext<Self>,
 //         ) -> Task<anyhow::Result<()>> {
 //             self.reload_count += 1;
@@ -1086,12 +1088,12 @@ pub trait FollowableItemHandle: ItemHandle {
 //         }
 
 //         fn deserialize(
-//             _project: ModelHandle<Project>,
+//             _project: Handle<Project>,
 //             _workspace: WeakViewHandle<Workspace>,
 //             workspace_id: WorkspaceId,
 //             _item_id: ItemId,
 //             cx: &mut ViewContext<Pane>,
-//         ) -> Task<anyhow::Result<ViewHandle<Self>>> {
+//         ) -> Task<anyhow::Result<View<Self>>> {
 //             let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
 //             Task::Ready(Some(anyhow::Ok(view)))
 //         }

crates/workspace2/src/pane.rs 🔗

@@ -29,7 +29,7 @@
 //     WindowContext,
 // };
 // use project2::{Project, ProjectEntryId, ProjectPath};
-// use serde::Deserialize;
+use serde::Deserialize;
 // use std::{
 //     any::Any,
 //     cell::RefCell,
@@ -44,24 +44,24 @@
 // use theme2::{Theme, ThemeSettings};
 // use util::truncate_and_remove_front;
 
-// #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
-// #[serde(rename_all = "camelCase")]
-// pub enum SaveIntent {
-//     /// write all files (even if unchanged)
-//     /// prompt before overwriting on-disk changes
-//     Save,
-//     /// write any files that have local changes
-//     /// prompt before overwriting on-disk changes
-//     SaveAll,
-//     /// always prompt for a new path
-//     SaveAs,
-//     /// prompt "you have unsaved changes" before writing
-//     Close,
-//     /// write all dirty files, don't prompt on conflict
-//     Overwrite,
-//     /// skip all save-related behavior
-//     Skip,
-// }
+#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub enum SaveIntent {
+    /// write all files (even if unchanged)
+    /// prompt before overwriting on-disk changes
+    Save,
+    /// write any files that have local changes
+    /// prompt before overwriting on-disk changes
+    SaveAll,
+    /// always prompt for a new path
+    SaveAs,
+    /// prompt "you have unsaved changes" before writing
+    Close,
+    /// write all dirty files, don't prompt on conflict
+    Overwrite,
+    /// skip all save-related behavior
+    Skip,
+}
 
 // #[derive(Clone, Deserialize, PartialEq)]
 // pub struct ActivateItem(pub usize);
@@ -159,7 +159,10 @@ pub enum Event {
     ZoomOut,
 }
 
-use crate::item::{ItemHandle, WeakItemHandle};
+use crate::{
+    item::{ItemHandle, WeakItemHandle},
+    SplitDirection,
+};
 use collections::{HashMap, VecDeque};
 use gpui2::{Handle, ViewContext, WeakView};
 use project2::{Project, ProjectEntryId, ProjectPath};

crates/workspace2/src/pane_group.rs 🔗

@@ -1,8 +1,8 @@
-use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace};
+use crate::{AppState, FollowerState, Pane, Workspace};
 use anyhow::{anyhow, Result};
-use call2::{ActiveCall, ParticipantLocation};
+use call2::ActiveCall;
 use collections::HashMap;
-use gpui2::{Bounds, Handle, Pixels, Point, View, ViewContext};
+use gpui2::{size, AnyElement, AnyView, Bounds, Handle, Pixels, Point, View, ViewContext};
 use project2::Project;
 use serde::Deserialize;
 use std::{cell::RefCell, rc::Rc, sync::Arc};
@@ -12,7 +12,7 @@ const HANDLE_HITBOX_SIZE: f32 = 4.0;
 const HORIZONTAL_MIN_SIZE: f32 = 80.;
 const VERTICAL_MIN_SIZE: f32 = 100.;
 
-enum Axis {
+pub enum Axis {
     Vertical,
     Horizontal,
 }
@@ -96,7 +96,7 @@ impl PaneGroup {
         follower_states: &HashMap<View<Pane>, FollowerState>,
         active_call: Option<&Handle<ActiveCall>>,
         active_pane: &View<Pane>,
-        zoomed: Option<&AnyViewHandle>,
+        zoomed: Option<&AnyView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
     ) -> AnyElement<Workspace> {
@@ -159,136 +159,138 @@ impl Member {
         follower_states: &HashMap<View<Pane>, FollowerState>,
         active_call: Option<&Handle<ActiveCall>>,
         active_pane: &View<Pane>,
-        zoomed: Option<&AnyViewHandle>,
+        zoomed: Option<&AnyView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
     ) -> AnyElement<Workspace> {
-        enum FollowIntoExternalProject {}
-
-        match self {
-            Member::Pane(pane) => {
-                let pane_element = if Some(&**pane) == zoomed {
-                    Empty::new().into_any()
-                } else {
-                    ChildView::new(pane, cx).into_any()
-                };
-
-                let leader = follower_states.get(pane).and_then(|state| {
-                    let room = active_call?.read(cx).room()?.read(cx);
-                    room.remote_participant_for_peer_id(state.leader_id)
-                });
-
-                let mut leader_border = Border::default();
-                let mut leader_status_box = None;
-                if let Some(leader) = &leader {
-                    let leader_color = theme
-                        .editor
-                        .selection_style_for_room_participant(leader.participant_index.0)
-                        .cursor;
-                    leader_border = Border::all(theme.workspace.leader_border_width, leader_color);
-                    leader_border
-                        .color
-                        .fade_out(1. - theme.workspace.leader_border_opacity);
-                    leader_border.overlay = true;
-
-                    leader_status_box = match leader.location {
-                        ParticipantLocation::SharedProject {
-                            project_id: leader_project_id,
-                        } => {
-                            if Some(leader_project_id) == project.read(cx).remote_id() {
-                                None
-                            } else {
-                                let leader_user = leader.user.clone();
-                                let leader_user_id = leader.user.id;
-                                Some(
-                                    MouseEventHandler::new::<FollowIntoExternalProject, _>(
-                                        pane.id(),
-                                        cx,
-                                        |_, _| {
-                                            Label::new(
-                                                format!(
-                                                    "Follow {} to their active project",
-                                                    leader_user.github_login,
-                                                ),
-                                                theme
-                                                    .workspace
-                                                    .external_location_message
-                                                    .text
-                                                    .clone(),
-                                            )
-                                            .contained()
-                                            .with_style(
-                                                theme.workspace.external_location_message.container,
-                                            )
-                                        },
-                                    )
-                                    .with_cursor_style(CursorStyle::PointingHand)
-                                    .on_click(MouseButton::Left, move |_, this, cx| {
-                                        crate::join_remote_project(
-                                            leader_project_id,
-                                            leader_user_id,
-                                            this.app_state().clone(),
-                                            cx,
-                                        )
-                                        .detach_and_log_err(cx);
-                                    })
-                                    .aligned()
-                                    .bottom()
-                                    .right()
-                                    .into_any(),
-                                )
-                            }
-                        }
-                        ParticipantLocation::UnsharedProject => Some(
-                            Label::new(
-                                format!(
-                                    "{} is viewing an unshared Zed project",
-                                    leader.user.github_login
-                                ),
-                                theme.workspace.external_location_message.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.workspace.external_location_message.container)
-                            .aligned()
-                            .bottom()
-                            .right()
-                            .into_any(),
-                        ),
-                        ParticipantLocation::External => Some(
-                            Label::new(
-                                format!(
-                                    "{} is viewing a window outside of Zed",
-                                    leader.user.github_login
-                                ),
-                                theme.workspace.external_location_message.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.workspace.external_location_message.container)
-                            .aligned()
-                            .bottom()
-                            .right()
-                            .into_any(),
-                        ),
-                    };
-                }
-
-                Stack::new()
-                    .with_child(pane_element.contained().with_border(leader_border))
-                    .with_children(leader_status_box)
-                    .into_any()
-            }
-            Member::Axis(axis) => axis.render(
-                project,
-                basis + 1,
-                theme,
-                follower_states,
-                active_call,
-                active_pane,
-                zoomed,
-                app_state,
-                cx,
-            ),
-        }
+        todo!()
+
+        // enum FollowIntoExternalProject {}
+
+        // match self {
+        //     Member::Pane(pane) => {
+        //         let pane_element = if Some(&**pane) == zoomed {
+        //             Empty::new().into_any()
+        //         } else {
+        //             ChildView::new(pane, cx).into_any()
+        //         };
+
+        //         let leader = follower_states.get(pane).and_then(|state| {
+        //             let room = active_call?.read(cx).room()?.read(cx);
+        //             room.remote_participant_for_peer_id(state.leader_id)
+        //         });
+
+        //         let mut leader_border = Border::default();
+        //         let mut leader_status_box = None;
+        //         if let Some(leader) = &leader {
+        //             let leader_color = theme
+        //                 .editor
+        //                 .selection_style_for_room_participant(leader.participant_index.0)
+        //                 .cursor;
+        //             leader_border = Border::all(theme.workspace.leader_border_width, leader_color);
+        //             leader_border
+        //                 .color
+        //                 .fade_out(1. - theme.workspace.leader_border_opacity);
+        //             leader_border.overlay = true;
+
+        //             leader_status_box = match leader.location {
+        //                 ParticipantLocation::SharedProject {
+        //                     project_id: leader_project_id,
+        //                 } => {
+        //                     if Some(leader_project_id) == project.read(cx).remote_id() {
+        //                         None
+        //                     } else {
+        //                         let leader_user = leader.user.clone();
+        //                         let leader_user_id = leader.user.id;
+        //                         Some(
+        //                             MouseEventHandler::new::<FollowIntoExternalProject, _>(
+        //                                 pane.id(),
+        //                                 cx,
+        //                                 |_, _| {
+        //                                     Label::new(
+        //                                         format!(
+        //                                             "Follow {} to their active project",
+        //                                             leader_user.github_login,
+        //                                         ),
+        //                                         theme
+        //                                             .workspace
+        //                                             .external_location_message
+        //                                             .text
+        //                                             .clone(),
+        //                                     )
+        //                                     .contained()
+        //                                     .with_style(
+        //                                         theme.workspace.external_location_message.container,
+        //                                     )
+        //                                 },
+        //                             )
+        //                             .with_cursor_style(CursorStyle::PointingHand)
+        //                             .on_click(MouseButton::Left, move |_, this, cx| {
+        //                                 crate::join_remote_project(
+        //                                     leader_project_id,
+        //                                     leader_user_id,
+        //                                     this.app_state().clone(),
+        //                                     cx,
+        //                                 )
+        //                                 .detach_and_log_err(cx);
+        //                             })
+        //                             .aligned()
+        //                             .bottom()
+        //                             .right()
+        //                             .into_any(),
+        //                         )
+        //                     }
+        //                 }
+        //                 ParticipantLocation::UnsharedProject => Some(
+        //                     Label::new(
+        //                         format!(
+        //                             "{} is viewing an unshared Zed project",
+        //                             leader.user.github_login
+        //                         ),
+        //                         theme.workspace.external_location_message.text.clone(),
+        //                     )
+        //                     .contained()
+        //                     .with_style(theme.workspace.external_location_message.container)
+        //                     .aligned()
+        //                     .bottom()
+        //                     .right()
+        //                     .into_any(),
+        //                 ),
+        //                 ParticipantLocation::External => Some(
+        //                     Label::new(
+        //                         format!(
+        //                             "{} is viewing a window outside of Zed",
+        //                             leader.user.github_login
+        //                         ),
+        //                         theme.workspace.external_location_message.text.clone(),
+        //                     )
+        //                     .contained()
+        //                     .with_style(theme.workspace.external_location_message.container)
+        //                     .aligned()
+        //                     .bottom()
+        //                     .right()
+        //                     .into_any(),
+        //                 ),
+        //             };
+        //         }
+
+        //         Stack::new()
+        //             .with_child(pane_element.contained().with_border(leader_border))
+        //             .with_children(leader_status_box)
+        //             .into_any()
+        //     }
+        //     Member::Axis(axis) => axis.render(
+        //         project,
+        //         basis + 1,
+        //         theme,
+        //         follower_states,
+        //         active_call,
+        //         active_pane,
+        //         zoomed,
+        //         app_state,
+        //         cx,
+        //     ),
+        // }
     }
 
     fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
@@ -308,7 +310,7 @@ pub(crate) struct PaneAxis {
     pub axis: Axis,
     pub members: Vec<Member>,
     pub flexes: Rc<RefCell<Vec<f32>>>,
-    pub bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
+    pub bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
 }
 
 impl PaneAxis {
@@ -428,7 +430,7 @@ impl PaneAxis {
         }
     }
 
-    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<RectF> {
+    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
         debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
 
         for (idx, member) in self.members.iter().enumerate() {
@@ -448,14 +450,14 @@ impl PaneAxis {
         None
     }
 
-    fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&View<Pane>> {
+    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
         debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
 
         let bounding_boxes = self.bounding_boxes.borrow();
 
         for (idx, member) in self.members.iter().enumerate() {
             if let Some(coordinates) = bounding_boxes[idx] {
-                if coordinates.contains_point(coordinate) {
+                if coordinates.contains_point(&coordinate) {
                     return match member {
                         Member::Pane(found) => Some(found),
                         Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
@@ -474,59 +476,60 @@ impl PaneAxis {
         follower_states: &HashMap<View<Pane>, FollowerState>,
         active_call: Option<&Handle<ActiveCall>>,
         active_pane: &View<Pane>,
-        zoomed: Option<&AnyViewHandle>,
+        zoomed: Option<&AnyView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
     ) -> AnyElement<Workspace> {
         debug_assert!(self.members.len() == self.flexes.borrow().len());
 
-        let mut pane_axis = PaneAxisElement::new(
-            self.axis,
-            basis,
-            self.flexes.clone(),
-            self.bounding_boxes.clone(),
-        );
-        let mut active_pane_ix = None;
-
-        let mut members = self.members.iter().enumerate().peekable();
-        while let Some((ix, member)) = members.next() {
-            let last = members.peek().is_none();
-
-            if member.contains(active_pane) {
-                active_pane_ix = Some(ix);
-            }
-
-            let mut member = member.render(
-                project,
-                (basis + ix) * 10,
-                theme,
-                follower_states,
-                active_call,
-                active_pane,
-                zoomed,
-                app_state,
-                cx,
-            );
-
-            if !last {
-                let mut border = theme.workspace.pane_divider;
-                border.left = false;
-                border.right = false;
-                border.top = false;
-                border.bottom = false;
-
-                match self.axis {
-                    Axis::Vertical => border.bottom = true,
-                    Axis::Horizontal => border.right = true,
-                }
-
-                member = member.contained().with_border(border).into_any();
-            }
-
-            pane_axis = pane_axis.with_child(member.into_any());
-        }
-        pane_axis.set_active_pane(active_pane_ix);
-        pane_axis.into_any()
+        todo!()
+        // let mut pane_axis = PaneAxisElement::new(
+        //     self.axis,
+        //     basis,
+        //     self.flexes.clone(),
+        //     self.bounding_boxes.clone(),
+        // );
+        // let mut active_pane_ix = None;
+
+        // let mut members = self.members.iter().enumerate().peekable();
+        // while let Some((ix, member)) = members.next() {
+        //     let last = members.peek().is_none();
+
+        //     if member.contains(active_pane) {
+        //         active_pane_ix = Some(ix);
+        //     }
+
+        //     let mut member = member.render(
+        //         project,
+        //         (basis + ix) * 10,
+        //         theme,
+        //         follower_states,
+        //         active_call,
+        //         active_pane,
+        //         zoomed,
+        //         app_state,
+        //         cx,
+        //     );
+
+        //     if !last {
+        //         let mut border = theme.workspace.pane_divider;
+        //         border.left = false;
+        //         border.right = false;
+        //         border.top = false;
+        //         border.bottom = false;
+
+        //         match self.axis {
+        //             Axis::Vertical => border.bottom = true,
+        //             Axis::Horizontal => border.right = true,
+        //         }
+
+        //         member = member.contained().with_border(border).into_any();
+        //     }
+
+        //     pane_axis = pane_axis.with_child(member.into_any());
+        // }
+        // pane_axis.set_active_pane(active_pane_ix);
+        // pane_axis.into_any()
     }
 }
 
@@ -543,7 +546,7 @@ impl SplitDirection {
         [Self::Up, Self::Down, Self::Left, Self::Right]
     }
 
-    pub fn edge(&self, rect: RectF) -> f32 {
+    pub fn edge(&self, rect: Bounds<Pixels>) -> f32 {
         match self {
             Self::Up => rect.min_y(),
             Self::Down => rect.max_y(),
@@ -552,19 +555,24 @@ impl SplitDirection {
         }
     }
 
-    // Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
-    pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
+    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
         match self {
-            Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
-            Self::Down => RectF::new(
-                rect.lower_left() - Vector2F::new(0., size),
-                Vector2F::new(rect.width(), size),
-            ),
-            Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
-            Self::Right => RectF::new(
-                rect.upper_right() - Vector2F::new(size, 0.),
-                Vector2F::new(size, rect.height()),
-            ),
+            Self::Up => Bounds {
+                origin: bounds.origin(),
+                size: size(bounds.width(), length),
+            },
+            Self::Down => Bounds {
+                origin: size(bounds.min_x(), bounds.max_y() - length),
+                size: size(bounds.width(), length),
+            },
+            Self::Left => Bounds {
+                origin: bounds.origin(),
+                size: size(length, bounds.height()),
+            },
+            Self::Right => Bounds {
+                origin: size(bounds.max_x() - length, bounds.min_y()),
+                size: size(length, bounds.height()),
+            },
         }
     }
 
@@ -583,403 +591,403 @@ impl SplitDirection {
     }
 }
 
-mod element {
-    use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
-
-    use gpui::{
-        geometry::{
-            rect::RectF,
-            vector::{vec2f, Vector2F},
-        },
-        json::{self, ToJson},
-        platform::{CursorStyle, MouseButton},
-        scene::MouseDrag,
-        AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt,
-        SizeConstraint, Vector2FExt, ViewContext,
-    };
-
-    use crate::{
-        pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
-        Workspace, WorkspaceSettings,
-    };
-
-    pub struct PaneAxisElement {
-        axis: Axis,
-        basis: usize,
-        active_pane_ix: Option<usize>,
-        flexes: Rc<RefCell<Vec<f32>>>,
-        children: Vec<AnyElement<Workspace>>,
-        bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
-    }
-
-    impl PaneAxisElement {
-        pub fn new(
-            axis: Axis,
-            basis: usize,
-            flexes: Rc<RefCell<Vec<f32>>>,
-            bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
-        ) -> Self {
-            Self {
-                axis,
-                basis,
-                flexes,
-                bounding_boxes,
-                active_pane_ix: None,
-                children: Default::default(),
-            }
-        }
-
-        pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
-            self.active_pane_ix = active_pane_ix;
-        }
-
-        fn layout_children(
-            &mut self,
-            active_pane_magnification: f32,
-            constraint: SizeConstraint,
-            remaining_space: &mut f32,
-            remaining_flex: &mut f32,
-            cross_axis_max: &mut f32,
-            view: &mut Workspace,
-            cx: &mut ViewContext<Workspace>,
-        ) {
-            let flexes = self.flexes.borrow();
-            let cross_axis = self.axis.invert();
-            for (ix, child) in self.children.iter_mut().enumerate() {
-                let flex = if active_pane_magnification != 1. {
-                    if let Some(active_pane_ix) = self.active_pane_ix {
-                        if ix == active_pane_ix {
-                            active_pane_magnification
-                        } else {
-                            1.
-                        }
-                    } else {
-                        1.
-                    }
-                } else {
-                    flexes[ix]
-                };
-
-                let child_size = if *remaining_flex == 0.0 {
-                    *remaining_space
-                } else {
-                    let space_per_flex = *remaining_space / *remaining_flex;
-                    space_per_flex * flex
-                };
-
-                let child_constraint = match self.axis {
-                    Axis::Horizontal => SizeConstraint::new(
-                        vec2f(child_size, constraint.min.y()),
-                        vec2f(child_size, constraint.max.y()),
-                    ),
-                    Axis::Vertical => SizeConstraint::new(
-                        vec2f(constraint.min.x(), child_size),
-                        vec2f(constraint.max.x(), child_size),
-                    ),
-                };
-                let child_size = child.layout(child_constraint, view, cx);
-                *remaining_space -= child_size.along(self.axis);
-                *remaining_flex -= flex;
-                *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
-            }
-        }
-
-        fn handle_resize(
-            flexes: Rc<RefCell<Vec<f32>>>,
-            axis: Axis,
-            preceding_ix: usize,
-            child_start: Vector2F,
-            drag_bounds: RectF,
-        ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
-            let size = move |ix, flexes: &[f32]| {
-                drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
-            };
-
-            move |drag, workspace: &mut Workspace, cx| {
-                if drag.end {
-                    // TODO: Clear cascading resize state
-                    return;
-                }
-                let min_size = match axis {
-                    Axis::Horizontal => HORIZONTAL_MIN_SIZE,
-                    Axis::Vertical => VERTICAL_MIN_SIZE,
-                };
-                let mut flexes = flexes.borrow_mut();
-
-                // Don't allow resizing to less than the minimum size, if elements are already too small
-                if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
-                    return;
-                }
-
-                let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
-                    - size(preceding_ix, flexes.as_slice());
-
-                let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
-                    let flex_change = pixel_dx / drag_bounds.length_along(axis);
-                    let current_target_flex = flexes[target_ix] + flex_change;
-                    let next_target_flex =
-                        flexes[(target_ix as isize + next) as usize] - flex_change;
-                    (current_target_flex, next_target_flex)
-                };
-
-                let mut successors = from_fn({
-                    let forward = proposed_current_pixel_change > 0.;
-                    let mut ix_offset = 0;
-                    let len = flexes.len();
-                    move || {
-                        let result = if forward {
-                            (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
-                        } else {
-                            (preceding_ix as isize - ix_offset as isize >= 0)
-                                .then(|| preceding_ix - ix_offset)
-                        };
-
-                        ix_offset += 1;
-
-                        result
-                    }
-                });
-
-                while proposed_current_pixel_change.abs() > 0. {
-                    let Some(current_ix) = successors.next() else {
-                        break;
-                    };
-
-                    let next_target_size = f32::max(
-                        size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
-                        min_size,
-                    );
-
-                    let current_target_size = f32::max(
-                        size(current_ix, flexes.as_slice())
-                            + size(current_ix + 1, flexes.as_slice())
-                            - next_target_size,
-                        min_size,
-                    );
-
-                    let current_pixel_change =
-                        current_target_size - size(current_ix, flexes.as_slice());
-
-                    let (current_target_flex, next_target_flex) =
-                        flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
-
-                    flexes[current_ix] = current_target_flex;
-                    flexes[current_ix + 1] = next_target_flex;
-
-                    proposed_current_pixel_change -= current_pixel_change;
-                }
-
-                workspace.schedule_serialize(cx);
-                cx.notify();
-            }
-        }
-    }
-
-    impl Extend<AnyElement<Workspace>> for PaneAxisElement {
-        fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
-            self.children.extend(children);
-        }
-    }
-
-    impl Element<Workspace> for PaneAxisElement {
-        type LayoutState = f32;
-        type PaintState = ();
-
-        fn layout(
-            &mut self,
-            constraint: SizeConstraint,
-            view: &mut Workspace,
-            cx: &mut ViewContext<Workspace>,
-        ) -> (Vector2F, Self::LayoutState) {
-            debug_assert!(self.children.len() == self.flexes.borrow().len());
-
-            let active_pane_magnification =
-                settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
-
-            let mut remaining_flex = 0.;
-
-            if active_pane_magnification != 1. {
-                let active_pane_flex = self
-                    .active_pane_ix
-                    .map(|_| active_pane_magnification)
-                    .unwrap_or(1.);
-                remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
-            } else {
-                for flex in self.flexes.borrow().iter() {
-                    remaining_flex += flex;
-                }
-            }
-
-            let mut cross_axis_max: f32 = 0.0;
-            let mut remaining_space = constraint.max_along(self.axis);
-
-            if remaining_space.is_infinite() {
-                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
-            }
-
-            self.layout_children(
-                active_pane_magnification,
-                constraint,
-                &mut remaining_space,
-                &mut remaining_flex,
-                &mut cross_axis_max,
-                view,
-                cx,
-            );
-
-            let mut size = match self.axis {
-                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
-                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
-            };
-
-            if constraint.min.x().is_finite() {
-                size.set_x(size.x().max(constraint.min.x()));
-            }
-            if constraint.min.y().is_finite() {
-                size.set_y(size.y().max(constraint.min.y()));
-            }
-
-            if size.x() > constraint.max.x() {
-                size.set_x(constraint.max.x());
-            }
-            if size.y() > constraint.max.y() {
-                size.set_y(constraint.max.y());
-            }
-
-            (size, remaining_space)
-        }
-
-        fn paint(
-            &mut self,
-            bounds: RectF,
-            visible_bounds: RectF,
-            remaining_space: &mut Self::LayoutState,
-            view: &mut Workspace,
-            cx: &mut ViewContext<Workspace>,
-        ) -> Self::PaintState {
-            let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
-            let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-            let overflowing = *remaining_space < 0.;
-            if overflowing {
-                cx.scene().push_layer(Some(visible_bounds));
-            }
-
-            let mut child_origin = bounds.origin();
-
-            let mut bounding_boxes = self.bounding_boxes.borrow_mut();
-            bounding_boxes.clear();
-
-            let mut children_iter = self.children.iter_mut().enumerate().peekable();
-            while let Some((ix, child)) = children_iter.next() {
-                let child_start = child_origin.clone();
-                child.paint(child_origin, visible_bounds, view, cx);
-
-                bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
-
-                match self.axis {
-                    Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
-                    Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
-                }
-
-                if can_resize && children_iter.peek().is_some() {
-                    cx.scene().push_stacking_context(None, None);
-
-                    let handle_origin = match self.axis {
-                        Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
-                        Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
-                    };
-
-                    let handle_bounds = match self.axis {
-                        Axis::Horizontal => RectF::new(
-                            handle_origin,
-                            vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
-                        ),
-                        Axis::Vertical => RectF::new(
-                            handle_origin,
-                            vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
-                        ),
-                    };
-
-                    let style = match self.axis {
-                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
-                        Axis::Vertical => CursorStyle::ResizeUpDown,
-                    };
-
-                    cx.scene().push_cursor_region(CursorRegion {
-                        bounds: handle_bounds,
-                        style,
-                    });
-
-                    enum ResizeHandle {}
-                    let mut mouse_region = MouseRegion::new::<ResizeHandle>(
-                        cx.view_id(),
-                        self.basis + ix,
-                        handle_bounds,
-                    );
-                    mouse_region = mouse_region
-                        .on_drag(
-                            MouseButton::Left,
-                            Self::handle_resize(
-                                self.flexes.clone(),
-                                self.axis,
-                                ix,
-                                child_start,
-                                visible_bounds.clone(),
-                            ),
-                        )
-                        .on_click(MouseButton::Left, {
-                            let flexes = self.flexes.clone();
-                            move |e, v: &mut Workspace, cx| {
-                                if e.click_count >= 2 {
-                                    let mut borrow = flexes.borrow_mut();
-                                    *borrow = vec![1.; borrow.len()];
-                                    v.schedule_serialize(cx);
-                                    cx.notify();
-                                }
-                            }
-                        });
-                    cx.scene().push_mouse_region(mouse_region);
-
-                    cx.scene().pop_stacking_context();
-                }
-            }
-
-            if overflowing {
-                cx.scene().pop_layer();
-            }
-        }
-
-        fn rect_for_text_range(
-            &self,
-            range_utf16: Range<usize>,
-            _: RectF,
-            _: RectF,
-            _: &Self::LayoutState,
-            _: &Self::PaintState,
-            view: &Workspace,
-            cx: &ViewContext<Workspace>,
-        ) -> Option<RectF> {
-            self.children
-                .iter()
-                .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
-        }
-
-        fn debug(
-            &self,
-            bounds: RectF,
-            _: &Self::LayoutState,
-            _: &Self::PaintState,
-            view: &Workspace,
-            cx: &ViewContext<Workspace>,
-        ) -> json::Value {
-            serde_json::json!({
-                "type": "PaneAxis",
-                "bounds": bounds.to_json(),
-                "axis": self.axis.to_json(),
-                "flexes": *self.flexes.borrow(),
-                "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
-            })
-        }
-    }
-}
+// mod element {
+//     // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
+
+//     // use gpui::{
+//     //     geometry::{
+//     //         rect::Bounds<Pixels>,
+//     //         vector::{vec2f, Vector2F},
+//     //     },
+//     //     json::{self, ToJson},
+//     //     platform::{CursorStyle, MouseButton},
+//     //     scene::MouseDrag,
+//     //     AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, Bounds<Pixels>Ext,
+//     //     SizeConstraint, Vector2FExt, ViewContext,
+//     // };
+
+//     use crate::{
+//         pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
+//         Workspace, WorkspaceSettings,
+//     };
+
+//     pub struct PaneAxisElement {
+//         axis: Axis,
+//         basis: usize,
+//         active_pane_ix: Option<usize>,
+//         flexes: Rc<RefCell<Vec<f32>>>,
+//         children: Vec<AnyElement<Workspace>>,
+//         bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
+//     }
+
+//     impl PaneAxisElement {
+//         pub fn new(
+//             axis: Axis,
+//             basis: usize,
+//             flexes: Rc<RefCell<Vec<f32>>>,
+//             bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
+//         ) -> Self {
+//             Self {
+//                 axis,
+//                 basis,
+//                 flexes,
+//                 bounding_boxes,
+//                 active_pane_ix: None,
+//                 children: Default::default(),
+//             }
+//         }
+
+//         pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
+//             self.active_pane_ix = active_pane_ix;
+//         }
+
+//         fn layout_children(
+//             &mut self,
+//             active_pane_magnification: f32,
+//             constraint: SizeConstraint,
+//             remaining_space: &mut f32,
+//             remaining_flex: &mut f32,
+//             cross_axis_max: &mut f32,
+//             view: &mut Workspace,
+//             cx: &mut ViewContext<Workspace>,
+//         ) {
+//             let flexes = self.flexes.borrow();
+//             let cross_axis = self.axis.invert();
+//             for (ix, child) in self.children.iter_mut().enumerate() {
+//                 let flex = if active_pane_magnification != 1. {
+//                     if let Some(active_pane_ix) = self.active_pane_ix {
+//                         if ix == active_pane_ix {
+//                             active_pane_magnification
+//                         } else {
+//                             1.
+//                         }
+//                     } else {
+//                         1.
+//                     }
+//                 } else {
+//                     flexes[ix]
+//                 };
+
+//                 let child_size = if *remaining_flex == 0.0 {
+//                     *remaining_space
+//                 } else {
+//                     let space_per_flex = *remaining_space / *remaining_flex;
+//                     space_per_flex * flex
+//                 };
+
+//                 let child_constraint = match self.axis {
+//                     Axis::Horizontal => SizeConstraint::new(
+//                         vec2f(child_size, constraint.min.y()),
+//                         vec2f(child_size, constraint.max.y()),
+//                     ),
+//                     Axis::Vertical => SizeConstraint::new(
+//                         vec2f(constraint.min.x(), child_size),
+//                         vec2f(constraint.max.x(), child_size),
+//                     ),
+//                 };
+//                 let child_size = child.layout(child_constraint, view, cx);
+//                 *remaining_space -= child_size.along(self.axis);
+//                 *remaining_flex -= flex;
+//                 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
+//             }
+//         }
+
+//         fn handle_resize(
+//             flexes: Rc<RefCell<Vec<f32>>>,
+//             axis: Axis,
+//             preceding_ix: usize,
+//             child_start: Vector2F,
+//             drag_bounds: Bounds<Pixels>,
+//         ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
+//             let size = move |ix, flexes: &[f32]| {
+//                 drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
+//             };
+
+//             move |drag, workspace: &mut Workspace, cx| {
+//                 if drag.end {
+//                     // TODO: Clear cascading resize state
+//                     return;
+//                 }
+//                 let min_size = match axis {
+//                     Axis::Horizontal => HORIZONTAL_MIN_SIZE,
+//                     Axis::Vertical => VERTICAL_MIN_SIZE,
+//                 };
+//                 let mut flexes = flexes.borrow_mut();
+
+//                 // Don't allow resizing to less than the minimum size, if elements are already too small
+//                 if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
+//                     return;
+//                 }
+
+//                 let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
+//                     - size(preceding_ix, flexes.as_slice());
+
+//                 let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
+//                     let flex_change = pixel_dx / drag_bounds.length_along(axis);
+//                     let current_target_flex = flexes[target_ix] + flex_change;
+//                     let next_target_flex =
+//                         flexes[(target_ix as isize + next) as usize] - flex_change;
+//                     (current_target_flex, next_target_flex)
+//                 };
+
+//                 let mut successors = from_fn({
+//                     let forward = proposed_current_pixel_change > 0.;
+//                     let mut ix_offset = 0;
+//                     let len = flexes.len();
+//                     move || {
+//                         let result = if forward {
+//                             (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
+//                         } else {
+//                             (preceding_ix as isize - ix_offset as isize >= 0)
+//                                 .then(|| preceding_ix - ix_offset)
+//                         };
+
+//                         ix_offset += 1;
+
+//                         result
+//                     }
+//                 });
+
+//                 while proposed_current_pixel_change.abs() > 0. {
+//                     let Some(current_ix) = successors.next() else {
+//                         break;
+//                     };
+
+//                     let next_target_size = f32::max(
+//                         size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
+//                         min_size,
+//                     );
+
+//                     let current_target_size = f32::max(
+//                         size(current_ix, flexes.as_slice())
+//                             + size(current_ix + 1, flexes.as_slice())
+//                             - next_target_size,
+//                         min_size,
+//                     );
+
+//                     let current_pixel_change =
+//                         current_target_size - size(current_ix, flexes.as_slice());
+
+//                     let (current_target_flex, next_target_flex) =
+//                         flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
+
+//                     flexes[current_ix] = current_target_flex;
+//                     flexes[current_ix + 1] = next_target_flex;
+
+//                     proposed_current_pixel_change -= current_pixel_change;
+//                 }
+
+//                 workspace.schedule_serialize(cx);
+//                 cx.notify();
+//             }
+//         }
+//     }
+
+//     impl Extend<AnyElement<Workspace>> for PaneAxisElement {
+//         fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
+//             self.children.extend(children);
+//         }
+//     }
+
+//     impl Element<Workspace> for PaneAxisElement {
+//         type LayoutState = f32;
+//         type PaintState = ();
+
+//         fn layout(
+//             &mut self,
+//             constraint: SizeConstraint,
+//             view: &mut Workspace,
+//             cx: &mut ViewContext<Workspace>,
+//         ) -> (Vector2F, Self::LayoutState) {
+//             debug_assert!(self.children.len() == self.flexes.borrow().len());
+
+//             let active_pane_magnification =
+//                 settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
+
+//             let mut remaining_flex = 0.;
+
+//             if active_pane_magnification != 1. {
+//                 let active_pane_flex = self
+//                     .active_pane_ix
+//                     .map(|_| active_pane_magnification)
+//                     .unwrap_or(1.);
+//                 remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
+//             } else {
+//                 for flex in self.flexes.borrow().iter() {
+//                     remaining_flex += flex;
+//                 }
+//             }
+
+//             let mut cross_axis_max: f32 = 0.0;
+//             let mut remaining_space = constraint.max_along(self.axis);
+
+//             if remaining_space.is_infinite() {
+//                 panic!("flex contains flexible children but has an infinite constraint along the flex axis");
+//             }
+
+//             self.layout_children(
+//                 active_pane_magnification,
+//                 constraint,
+//                 &mut remaining_space,
+//                 &mut remaining_flex,
+//                 &mut cross_axis_max,
+//                 view,
+//                 cx,
+//             );
+
+//             let mut size = match self.axis {
+//                 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
+//                 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
+//             };
+
+//             if constraint.min.x().is_finite() {
+//                 size.set_x(size.x().max(constraint.min.x()));
+//             }
+//             if constraint.min.y().is_finite() {
+//                 size.set_y(size.y().max(constraint.min.y()));
+//             }
+
+//             if size.x() > constraint.max.x() {
+//                 size.set_x(constraint.max.x());
+//             }
+//             if size.y() > constraint.max.y() {
+//                 size.set_y(constraint.max.y());
+//             }
+
+//             (size, remaining_space)
+//         }
+
+//         fn paint(
+//             &mut self,
+//             bounds: Bounds<Pixels>,
+//             visible_bounds: Bounds<Pixels>,
+//             remaining_space: &mut Self::LayoutState,
+//             view: &mut Workspace,
+//             cx: &mut ViewContext<Workspace>,
+//         ) -> Self::PaintState {
+//             let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
+//             let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+
+//             let overflowing = *remaining_space < 0.;
+//             if overflowing {
+//                 cx.scene().push_layer(Some(visible_bounds));
+//             }
+
+//             let mut child_origin = bounds.origin();
+
+//             let mut bounding_boxes = self.bounding_boxes.borrow_mut();
+//             bounding_boxes.clear();
+
+//             let mut children_iter = self.children.iter_mut().enumerate().peekable();
+//             while let Some((ix, child)) = children_iter.next() {
+//                 let child_start = child_origin.clone();
+//                 child.paint(child_origin, visible_bounds, view, cx);
+
+//                 bounding_boxes.push(Some(Bounds<Pixels>::new(child_origin, child.size())));
+
+//                 match self.axis {
+//                     Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
+//                     Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
+//                 }
+
+//                 if can_resize && children_iter.peek().is_some() {
+//                     cx.scene().push_stacking_context(None, None);
+
+//                     let handle_origin = match self.axis {
+//                         Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
+//                         Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
+//                     };
+
+//                     let handle_bounds = match self.axis {
+//                         Axis::Horizontal => Bounds<Pixels>::new(
+//                             handle_origin,
+//                             vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
+//                         ),
+//                         Axis::Vertical => Bounds<Pixels>::new(
+//                             handle_origin,
+//                             vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
+//                         ),
+//                     };
+
+//                     let style = match self.axis {
+//                         Axis::Horizontal => CursorStyle::ResizeLeftRight,
+//                         Axis::Vertical => CursorStyle::ResizeUpDown,
+//                     };
+
+//                     cx.scene().push_cursor_region(CursorRegion {
+//                         bounds: handle_bounds,
+//                         style,
+//                     });
+
+//                     enum ResizeHandle {}
+//                     let mut mouse_region = MouseRegion::new::<ResizeHandle>(
+//                         cx.view_id(),
+//                         self.basis + ix,
+//                         handle_bounds,
+//                     );
+//                     mouse_region = mouse_region
+//                         .on_drag(
+//                             MouseButton::Left,
+//                             Self::handle_resize(
+//                                 self.flexes.clone(),
+//                                 self.axis,
+//                                 ix,
+//                                 child_start,
+//                                 visible_bounds.clone(),
+//                             ),
+//                         )
+//                         .on_click(MouseButton::Left, {
+//                             let flexes = self.flexes.clone();
+//                             move |e, v: &mut Workspace, cx| {
+//                                 if e.click_count >= 2 {
+//                                     let mut borrow = flexes.borrow_mut();
+//                                     *borrow = vec![1.; borrow.len()];
+//                                     v.schedule_serialize(cx);
+//                                     cx.notify();
+//                                 }
+//                             }
+//                         });
+//                     cx.scene().push_mouse_region(mouse_region);
+
+//                     cx.scene().pop_stacking_context();
+//                 }
+//             }
+
+//             if overflowing {
+//                 cx.scene().pop_layer();
+//             }
+//         }
+
+//         fn rect_for_text_range(
+//             &self,
+//             range_utf16: Range<usize>,
+//             _: Bounds<Pixels>,
+//             _: Bounds<Pixels>,
+//             _: &Self::LayoutState,
+//             _: &Self::PaintState,
+//             view: &Workspace,
+//             cx: &ViewContext<Workspace>,
+//         ) -> Option<Bounds<Pixels>> {
+//             self.children
+//                 .iter()
+//                 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
+//         }
+
+//         fn debug(
+//             &self,
+//             bounds: Bounds<Pixels>,
+//             _: &Self::LayoutState,
+//             _: &Self::PaintState,
+//             view: &Workspace,
+//             cx: &ViewContext<Workspace>,
+//         ) -> json::Value {
+//             serde_json::json!({
+//                 "type": "PaneAxis",
+//                 "bounds": bounds.to_json(),
+//                 "axis": self.axis.to_json(),
+//                 "flexes": *self.flexes.borrow(),
+//                 "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
+//             })
+//         }
+//     }
+// }

crates/workspace2/src/persistence.rs 🔗

@@ -5,13 +5,13 @@ pub mod model;
 use std::path::Path;
 
 use anyhow::{anyhow, bail, Context, Result};
-use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
-use gpui::{platform::WindowBounds, Axis};
+use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
+use gpui2::WindowBounds;
 
 use util::{unzip_option, ResultExt};
 use uuid::Uuid;
 
-use crate::WorkspaceId;
+use crate::{Axis, WorkspaceId};
 
 use model::{
     GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
@@ -549,424 +549,425 @@ impl WorkspaceDb {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use db::open_test_db;
-
-    #[gpui::test]
-    async fn test_next_id_stability() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_next_id_stability").await);
-
-        db.write(|conn| {
-            conn.migrate(
-                "test_table",
-                &[sql!(
-                    CREATE TABLE test_table(
-                        text TEXT,
-                        workspace_id INTEGER,
-                        FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-                        ON DELETE CASCADE
-                    ) STRICT;
-                )],
-            )
-            .unwrap();
-        })
-        .await;
-
-        let id = db.next_id().await.unwrap();
-        // Assert the empty row got inserted
-        assert_eq!(
-            Some(id),
-            db.select_row_bound::<WorkspaceId, WorkspaceId>(sql!(
-                SELECT workspace_id FROM workspaces WHERE workspace_id = ?
-            ))
-            .unwrap()(id)
-            .unwrap()
-        );
-
-        db.write(move |conn| {
-            conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
-                .unwrap()(("test-text-1", id))
-            .unwrap()
-        })
-        .await;
-
-        let test_text_1 = db
-            .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
-            .unwrap()(1)
-        .unwrap()
-        .unwrap();
-        assert_eq!(test_text_1, "test-text-1");
-    }
-
-    #[gpui::test]
-    async fn test_workspace_id_stability() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await);
-
-        db.write(|conn| {
-            conn.migrate(
-                "test_table",
-                &[sql!(
-                    CREATE TABLE test_table(
-                        text TEXT,
-                        workspace_id INTEGER,
-                        FOREIGN KEY(workspace_id)
-                            REFERENCES workspaces(workspace_id)
-                        ON DELETE CASCADE
-                    ) STRICT;)],
-            )
-        })
-        .await
-        .unwrap();
-
-        let mut workspace_1 = SerializedWorkspace {
-            id: 1,
-            location: (["/tmp", "/tmp2"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        let workspace_2 = SerializedWorkspace {
-            id: 2,
-            location: (["/tmp"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace_1.clone()).await;
-
-        db.write(|conn| {
-            conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
-                .unwrap()(("test-text-1", 1))
-            .unwrap();
-        })
-        .await;
-
-        db.save_workspace(workspace_2.clone()).await;
-
-        db.write(|conn| {
-            conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
-                .unwrap()(("test-text-2", 2))
-            .unwrap();
-        })
-        .await;
-
-        workspace_1.location = (["/tmp", "/tmp3"]).into();
-        db.save_workspace(workspace_1.clone()).await;
-        db.save_workspace(workspace_1).await;
-        db.save_workspace(workspace_2).await;
-
-        let test_text_2 = db
-            .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
-            .unwrap()(2)
-        .unwrap()
-        .unwrap();
-        assert_eq!(test_text_2, "test-text-2");
-
-        let test_text_1 = db
-            .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
-            .unwrap()(1)
-        .unwrap()
-        .unwrap();
-        assert_eq!(test_text_1, "test-text-1");
-    }
-
-    fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
-        SerializedPaneGroup::Group {
-            axis,
-            flexes: None,
-            children,
-        }
-    }
-
-    #[gpui::test]
-    async fn test_full_workspace_serialization() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
-
-        //  -----------------
-        //  | 1,2   | 5,6   |
-        //  | - - - |       |
-        //  | 3,4   |       |
-        //  -----------------
-        let center_group = group(
-            gpui::Axis::Horizontal,
-            vec![
-                group(
-                    gpui::Axis::Vertical,
-                    vec![
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 5, false),
-                                SerializedItem::new("Terminal", 6, true),
-                            ],
-                            false,
-                        )),
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 7, true),
-                                SerializedItem::new("Terminal", 8, false),
-                            ],
-                            false,
-                        )),
-                    ],
-                ),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 9, false),
-                        SerializedItem::new("Terminal", 10, true),
-                    ],
-                    false,
-                )),
-            ],
-        );
-
-        let workspace = SerializedWorkspace {
-            id: 5,
-            location: (["/tmp", "/tmp2"]).into(),
-            center_group,
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace.clone()).await;
-        let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
-
-        assert_eq!(workspace, round_trip_workspace.unwrap());
-
-        // Test guaranteed duplicate IDs
-        db.save_workspace(workspace.clone()).await;
-        db.save_workspace(workspace.clone()).await;
-
-        let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
-        assert_eq!(workspace, round_trip_workspace.unwrap());
-    }
-
-    #[gpui::test]
-    async fn test_workspace_assignment() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_basic_functionality").await);
-
-        let workspace_1 = SerializedWorkspace {
-            id: 1,
-            location: (["/tmp", "/tmp2"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        let mut workspace_2 = SerializedWorkspace {
-            id: 2,
-            location: (["/tmp"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace_1.clone()).await;
-        db.save_workspace(workspace_2.clone()).await;
-
-        // Test that paths are treated as a set
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
-            workspace_1
-        );
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
-            workspace_1
-        );
-
-        // Make sure that other keys work
-        assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
-        assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
-
-        // Test 'mutate' case of updating a pre-existing id
-        workspace_2.location = (["/tmp", "/tmp2"]).into();
-
-        db.save_workspace(workspace_2.clone()).await;
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
-            workspace_2
-        );
-
-        // Test other mechanism for mutating
-        let mut workspace_3 = SerializedWorkspace {
-            id: 3,
-            location: (&["/tmp", "/tmp2"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace_3.clone()).await;
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
-            workspace_3
-        );
-
-        // Make sure that updating paths differently also works
-        workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
-        db.save_workspace(workspace_3.clone()).await;
-        assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
-                .unwrap(),
-            workspace_3
-        );
-    }
-
-    use crate::persistence::model::SerializedWorkspace;
-    use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
-
-    fn default_workspace<P: AsRef<Path>>(
-        workspace_id: &[P],
-        center_group: &SerializedPaneGroup,
-    ) -> SerializedWorkspace {
-        SerializedWorkspace {
-            id: 4,
-            location: workspace_id.into(),
-            center_group: center_group.clone(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        }
-    }
-
-    #[gpui::test]
-    async fn test_simple_split() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("simple_split").await);
-
-        //  -----------------
-        //  | 1,2   | 5,6   |
-        //  | - - - |       |
-        //  | 3,4   |       |
-        //  -----------------
-        let center_pane = group(
-            gpui::Axis::Horizontal,
-            vec![
-                group(
-                    gpui::Axis::Vertical,
-                    vec![
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 1, false),
-                                SerializedItem::new("Terminal", 2, true),
-                            ],
-                            false,
-                        )),
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 4, false),
-                                SerializedItem::new("Terminal", 3, true),
-                            ],
-                            true,
-                        )),
-                    ],
-                ),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 5, true),
-                        SerializedItem::new("Terminal", 6, false),
-                    ],
-                    false,
-                )),
-            ],
-        );
-
-        let workspace = default_workspace(&["/tmp"], &center_pane);
-
-        db.save_workspace(workspace.clone()).await;
-
-        let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
-
-        assert_eq!(workspace.center_group, new_workspace.center_group);
-    }
-
-    #[gpui::test]
-    async fn test_cleanup_panes() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
-
-        let center_pane = group(
-            gpui::Axis::Horizontal,
-            vec![
-                group(
-                    gpui::Axis::Vertical,
-                    vec![
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 1, false),
-                                SerializedItem::new("Terminal", 2, true),
-                            ],
-                            false,
-                        )),
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 4, false),
-                                SerializedItem::new("Terminal", 3, true),
-                            ],
-                            true,
-                        )),
-                    ],
-                ),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 5, false),
-                        SerializedItem::new("Terminal", 6, true),
-                    ],
-                    false,
-                )),
-            ],
-        );
-
-        let id = &["/tmp"];
-
-        let mut workspace = default_workspace(id, &center_pane);
-
-        db.save_workspace(workspace.clone()).await;
-
-        workspace.center_group = group(
-            gpui::Axis::Vertical,
-            vec![
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 1, false),
-                        SerializedItem::new("Terminal", 2, true),
-                    ],
-                    false,
-                )),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 4, true),
-                        SerializedItem::new("Terminal", 3, false),
-                    ],
-                    true,
-                )),
-            ],
-        );
-
-        db.save_workspace(workspace.clone()).await;
-
-        let new_workspace = db.workspace_for_roots(id).unwrap();
-
-        assert_eq!(workspace.center_group, new_workspace.center_group);
-    }
-}
+// todo!()
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use db::open_test_db;
+
+//     #[gpui::test]
+//     async fn test_next_id_stability() {
+//         env_logger::try_init().ok();
+
+//         let db = WorkspaceDb(open_test_db("test_next_id_stability").await);
+
+//         db.write(|conn| {
+//             conn.migrate(
+//                 "test_table",
+//                 &[sql!(
+//                     CREATE TABLE test_table(
+//                         text TEXT,
+//                         workspace_id INTEGER,
+//                         FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+//                         ON DELETE CASCADE
+//                     ) STRICT;
+//                 )],
+//             )
+//             .unwrap();
+//         })
+//         .await;
+
+//         let id = db.next_id().await.unwrap();
+//         // Assert the empty row got inserted
+//         assert_eq!(
+//             Some(id),
+//             db.select_row_bound::<WorkspaceId, WorkspaceId>(sql!(
+//                 SELECT workspace_id FROM workspaces WHERE workspace_id = ?
+//             ))
+//             .unwrap()(id)
+//             .unwrap()
+//         );
+
+//         db.write(move |conn| {
+//             conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
+//                 .unwrap()(("test-text-1", id))
+//             .unwrap()
+//         })
+//         .await;
+
+//         let test_text_1 = db
+//             .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
+//             .unwrap()(1)
+//         .unwrap()
+//         .unwrap();
+//         assert_eq!(test_text_1, "test-text-1");
+//     }
+
+//     #[gpui::test]
+//     async fn test_workspace_id_stability() {
+//         env_logger::try_init().ok();
+
+//         let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await);
+
+//         db.write(|conn| {
+//             conn.migrate(
+//                 "test_table",
+//                 &[sql!(
+//                     CREATE TABLE test_table(
+//                         text TEXT,
+//                         workspace_id INTEGER,
+//                         FOREIGN KEY(workspace_id)
+//                             REFERENCES workspaces(workspace_id)
+//                         ON DELETE CASCADE
+//                     ) STRICT;)],
+//             )
+//         })
+//         .await
+//         .unwrap();
+
+//         let mut workspace_1 = SerializedWorkspace {
+//             id: 1,
+//             location: (["/tmp", "/tmp2"]).into(),
+//             center_group: Default::default(),
+//             bounds: Default::default(),
+//             display: Default::default(),
+//             docks: Default::default(),
+//         };
+
+//         let workspace_2 = SerializedWorkspace {
+//             id: 2,
+//             location: (["/tmp"]).into(),
+//             center_group: Default::default(),
+//             bounds: Default::default(),
+//             display: Default::default(),
+//             docks: Default::default(),
+//         };
+
+//         db.save_workspace(workspace_1.clone()).await;
+
+//         db.write(|conn| {
+//             conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
+//                 .unwrap()(("test-text-1", 1))
+//             .unwrap();
+//         })
+//         .await;
+
+//         db.save_workspace(workspace_2.clone()).await;
+
+//         db.write(|conn| {
+//             conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
+//                 .unwrap()(("test-text-2", 2))
+//             .unwrap();
+//         })
+//         .await;
+
+//         workspace_1.location = (["/tmp", "/tmp3"]).into();
+//         db.save_workspace(workspace_1.clone()).await;
+//         db.save_workspace(workspace_1).await;
+//         db.save_workspace(workspace_2).await;
+
+//         let test_text_2 = db
+//             .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
+//             .unwrap()(2)
+//         .unwrap()
+//         .unwrap();
+//         assert_eq!(test_text_2, "test-text-2");
+
+//         let test_text_1 = db
+//             .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
+//             .unwrap()(1)
+//         .unwrap()
+//         .unwrap();
+//         assert_eq!(test_text_1, "test-text-1");
+//     }
+
+//     fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
+//         SerializedPaneGroup::Group {
+//             axis,
+//             flexes: None,
+//             children,
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_full_workspace_serialization() {
+//         env_logger::try_init().ok();
+
+//         let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
+
+//         //  -----------------
+//         //  | 1,2   | 5,6   |
+//         //  | - - - |       |
+//         //  | 3,4   |       |
+//         //  -----------------
+//         let center_group = group(
+//             gpui::Axis::Horizontal,
+//             vec![
+//                 group(
+//                     gpui::Axis::Vertical,
+//                     vec![
+//                         SerializedPaneGroup::Pane(SerializedPane::new(
+//                             vec![
+//                                 SerializedItem::new("Terminal", 5, false),
+//                                 SerializedItem::new("Terminal", 6, true),
+//                             ],
+//                             false,
+//                         )),
+//                         SerializedPaneGroup::Pane(SerializedPane::new(
+//                             vec![
+//                                 SerializedItem::new("Terminal", 7, true),
+//                                 SerializedItem::new("Terminal", 8, false),
+//                             ],
+//                             false,
+//                         )),
+//                     ],
+//                 ),
+//                 SerializedPaneGroup::Pane(SerializedPane::new(
+//                     vec![
+//                         SerializedItem::new("Terminal", 9, false),
+//                         SerializedItem::new("Terminal", 10, true),
+//                     ],
+//                     false,
+//                 )),
+//             ],
+//         );
+
+//         let workspace = SerializedWorkspace {
+//             id: 5,
+//             location: (["/tmp", "/tmp2"]).into(),
+//             center_group,
+//             bounds: Default::default(),
+//             display: Default::default(),
+//             docks: Default::default(),
+//         };
+
+//         db.save_workspace(workspace.clone()).await;
+//         let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
+
+//         assert_eq!(workspace, round_trip_workspace.unwrap());
+
+//         // Test guaranteed duplicate IDs
+//         db.save_workspace(workspace.clone()).await;
+//         db.save_workspace(workspace.clone()).await;
+
+//         let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
+//         assert_eq!(workspace, round_trip_workspace.unwrap());
+//     }
+
+//     #[gpui::test]
+//     async fn test_workspace_assignment() {
+//         env_logger::try_init().ok();
+
+//         let db = WorkspaceDb(open_test_db("test_basic_functionality").await);
+
+//         let workspace_1 = SerializedWorkspace {
+//             id: 1,
+//             location: (["/tmp", "/tmp2"]).into(),
+//             center_group: Default::default(),
+//             bounds: Default::default(),
+//             display: Default::default(),
+//             docks: Default::default(),
+//         };
+
+//         let mut workspace_2 = SerializedWorkspace {
+//             id: 2,
+//             location: (["/tmp"]).into(),
+//             center_group: Default::default(),
+//             bounds: Default::default(),
+//             display: Default::default(),
+//             docks: Default::default(),
+//         };
+
+//         db.save_workspace(workspace_1.clone()).await;
+//         db.save_workspace(workspace_2.clone()).await;
+
+//         // Test that paths are treated as a set
+//         assert_eq!(
+//             db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
+//             workspace_1
+//         );
+//         assert_eq!(
+//             db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
+//             workspace_1
+//         );
+
+//         // Make sure that other keys work
+//         assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
+//         assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
+
+//         // Test 'mutate' case of updating a pre-existing id
+//         workspace_2.location = (["/tmp", "/tmp2"]).into();
+
+//         db.save_workspace(workspace_2.clone()).await;
+//         assert_eq!(
+//             db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
+//             workspace_2
+//         );
+
+//         // Test other mechanism for mutating
+//         let mut workspace_3 = SerializedWorkspace {
+//             id: 3,
+//             location: (&["/tmp", "/tmp2"]).into(),
+//             center_group: Default::default(),
+//             bounds: Default::default(),
+//             display: Default::default(),
+//             docks: Default::default(),
+//         };
+
+//         db.save_workspace(workspace_3.clone()).await;
+//         assert_eq!(
+//             db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
+//             workspace_3
+//         );
+
+//         // Make sure that updating paths differently also works
+//         workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
+//         db.save_workspace(workspace_3.clone()).await;
+//         assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
+//         assert_eq!(
+//             db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
+//                 .unwrap(),
+//             workspace_3
+//         );
+//     }
+
+//     use crate::persistence::model::SerializedWorkspace;
+//     use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
+
+//     fn default_workspace<P: AsRef<Path>>(
+//         workspace_id: &[P],
+//         center_group: &SerializedPaneGroup,
+//     ) -> SerializedWorkspace {
+//         SerializedWorkspace {
+//             id: 4,
+//             location: workspace_id.into(),
+//             center_group: center_group.clone(),
+//             bounds: Default::default(),
+//             display: Default::default(),
+//             docks: Default::default(),
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_simple_split() {
+//         env_logger::try_init().ok();
+
+//         let db = WorkspaceDb(open_test_db("simple_split").await);
+
+//         //  -----------------
+//         //  | 1,2   | 5,6   |
+//         //  | - - - |       |
+//         //  | 3,4   |       |
+//         //  -----------------
+//         let center_pane = group(
+//             gpui::Axis::Horizontal,
+//             vec![
+//                 group(
+//                     gpui::Axis::Vertical,
+//                     vec![
+//                         SerializedPaneGroup::Pane(SerializedPane::new(
+//                             vec![
+//                                 SerializedItem::new("Terminal", 1, false),
+//                                 SerializedItem::new("Terminal", 2, true),
+//                             ],
+//                             false,
+//                         )),
+//                         SerializedPaneGroup::Pane(SerializedPane::new(
+//                             vec![
+//                                 SerializedItem::new("Terminal", 4, false),
+//                                 SerializedItem::new("Terminal", 3, true),
+//                             ],
+//                             true,
+//                         )),
+//                     ],
+//                 ),
+//                 SerializedPaneGroup::Pane(SerializedPane::new(
+//                     vec![
+//                         SerializedItem::new("Terminal", 5, true),
+//                         SerializedItem::new("Terminal", 6, false),
+//                     ],
+//                     false,
+//                 )),
+//             ],
+//         );
+
+//         let workspace = default_workspace(&["/tmp"], &center_pane);
+
+//         db.save_workspace(workspace.clone()).await;
+
+//         let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
+
+//         assert_eq!(workspace.center_group, new_workspace.center_group);
+//     }
+
+//     #[gpui::test]
+//     async fn test_cleanup_panes() {
+//         env_logger::try_init().ok();
+
+//         let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
+
+//         let center_pane = group(
+//             gpui::Axis::Horizontal,
+//             vec![
+//                 group(
+//                     gpui::Axis::Vertical,
+//                     vec![
+//                         SerializedPaneGroup::Pane(SerializedPane::new(
+//                             vec![
+//                                 SerializedItem::new("Terminal", 1, false),
+//                                 SerializedItem::new("Terminal", 2, true),
+//                             ],
+//                             false,
+//                         )),
+//                         SerializedPaneGroup::Pane(SerializedPane::new(
+//                             vec![
+//                                 SerializedItem::new("Terminal", 4, false),
+//                                 SerializedItem::new("Terminal", 3, true),
+//                             ],
+//                             true,
+//                         )),
+//                     ],
+//                 ),
+//                 SerializedPaneGroup::Pane(SerializedPane::new(
+//                     vec![
+//                         SerializedItem::new("Terminal", 5, false),
+//                         SerializedItem::new("Terminal", 6, true),
+//                     ],
+//                     false,
+//                 )),
+//             ],
+//         );
+
+//         let id = &["/tmp"];
+
+//         let mut workspace = default_workspace(id, &center_pane);
+
+//         db.save_workspace(workspace.clone()).await;
+
+//         workspace.center_group = group(
+//             gpui::Axis::Vertical,
+//             vec![
+//                 SerializedPaneGroup::Pane(SerializedPane::new(
+//                     vec![
+//                         SerializedItem::new("Terminal", 1, false),
+//                         SerializedItem::new("Terminal", 2, true),
+//                     ],
+//                     false,
+//                 )),
+//                 SerializedPaneGroup::Pane(SerializedPane::new(
+//                     vec![
+//                         SerializedItem::new("Terminal", 4, true),
+//                         SerializedItem::new("Terminal", 3, false),
+//                     ],
+//                     true,
+//                 )),
+//             ],
+//         );
+
+//         db.save_workspace(workspace.clone()).await;
+
+//         let new_workspace = db.workspace_for_roots(id).unwrap();
+
+//         assert_eq!(workspace.center_group, new_workspace.center_group);
+//     }
+// }

crates/workspace2/src/persistence/model.rs 🔗

@@ -1,14 +1,14 @@
-use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
+use crate::{
+    item::ItemHandle, Axis, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
+};
 use anyhow::{Context, Result};
 use async_recursion::async_recursion;
-use db::sqlez::{
+use db2::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use gpui::{
-    platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle,
-};
-use project::Project;
+use gpui2::{AsyncAppContext, Handle, Task, View, WeakView, WindowBounds};
+use project2::Project;
 use std::{
     path::{Path, PathBuf},
     sync::Arc,
@@ -151,15 +151,11 @@ impl SerializedPaneGroup {
     #[async_recursion(?Send)]
     pub(crate) async fn deserialize(
         self,
-        project: &ModelHandle<Project>,
+        project: &Handle<Project>,
         workspace_id: WorkspaceId,
-        workspace: &WeakViewHandle<Workspace>,
+        workspace: &WeakView<Workspace>,
         cx: &mut AsyncAppContext,
-    ) -> Option<(
-        Member,
-        Option<ViewHandle<Pane>>,
-        Vec<Option<Box<dyn ItemHandle>>>,
-    )> {
+    ) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
         match self {
             SerializedPaneGroup::Group {
                 axis,
@@ -208,10 +204,10 @@ impl SerializedPaneGroup {
                     .read_with(cx, |pane, _| pane.items_len() != 0)
                     .log_err()?
                 {
-                    let pane = pane.upgrade(cx)?;
+                    let pane = pane.upgrade()?;
                     Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
                 } else {
-                    let pane = pane.upgrade(cx)?;
+                    let pane = pane.upgrade()?;
                     workspace
                         .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
                         .log_err()?;
@@ -235,10 +231,10 @@ impl SerializedPane {
 
     pub async fn deserialize_to(
         &self,
-        project: &ModelHandle<Project>,
-        pane: &WeakViewHandle<Pane>,
+        project: &Handle<Project>,
+        pane: &WeakView<Pane>,
         workspace_id: WorkspaceId,
-        workspace: &WeakViewHandle<Workspace>,
+        workspace: &WeakView<Workspace>,
         cx: &mut AsyncAppContext,
     ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
         let mut items = Vec::new();

crates/workspace2/src/toolbar.rs 🔗

@@ -1,10 +1,7 @@
 use crate::ItemHandle;
-use gpui::{
-    elements::*, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle,
-    WindowContext,
-};
+use gpui2::{AppContext, EventEmitter, View, ViewContext, WindowContext};
 
-pub trait ToolbarItemView: View {
+pub trait ToolbarItemView: EventEmitter + Sized {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
@@ -32,7 +29,7 @@ pub trait ToolbarItemView: View {
 
 trait ToolbarItemViewHandle {
     fn id(&self) -> usize;
-    fn as_any(&self) -> &AnyViewHandle;
+    // fn as_any(&self) -> &AnyViewHandle; todo!()
     fn set_active_pane_item(
         &self,
         active_pane_item: Option<&dyn ItemHandle>,
@@ -57,84 +54,81 @@ pub struct Toolbar {
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
-impl Entity for Toolbar {
-    type Event = ();
-}
-
-impl View for Toolbar {
-    fn ui_name() -> &'static str {
-        "Toolbar"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).workspace.toolbar;
-
-        let mut primary_left_items = Vec::new();
-        let mut primary_right_items = Vec::new();
-        let mut secondary_item = None;
-        let spacing = theme.item_spacing;
-        let mut primary_items_row_count = 1;
-
-        for (item, position) in &self.items {
-            match *position {
-                ToolbarItemLocation::Hidden => {}
-
-                ToolbarItemLocation::PrimaryLeft { flex } => {
-                    primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-                    let left_item = ChildView::new(item.as_any(), cx).aligned();
-                    if let Some((flex, expanded)) = flex {
-                        primary_left_items.push(left_item.flex(flex, expanded).into_any());
-                    } else {
-                        primary_left_items.push(left_item.into_any());
-                    }
-                }
-
-                ToolbarItemLocation::PrimaryRight { flex } => {
-                    primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-                    let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
-                    if let Some((flex, expanded)) = flex {
-                        primary_right_items.push(right_item.flex(flex, expanded).into_any());
-                    } else {
-                        primary_right_items.push(right_item.into_any());
-                    }
-                }
-
-                ToolbarItemLocation::Secondary => {
-                    secondary_item = Some(
-                        ChildView::new(item.as_any(), cx)
-                            .constrained()
-                            .with_height(theme.height * item.row_count(cx) as f32)
-                            .into_any(),
-                    );
-                }
-            }
-        }
-
-        let container_style = theme.container;
-        let height = theme.height * primary_items_row_count as f32;
-
-        let mut primary_items = Flex::row().with_spacing(spacing);
-        primary_items.extend(primary_left_items);
-        primary_items.extend(primary_right_items);
-
-        let mut toolbar = Flex::column();
-        if !primary_items.is_empty() {
-            toolbar.add_child(primary_items.constrained().with_height(height));
-        }
-        if let Some(secondary_item) = secondary_item {
-            toolbar.add_child(secondary_item);
-        }
-
-        if toolbar.is_empty() {
-            toolbar.into_any_named("toolbar")
-        } else {
-            toolbar
-                .contained()
-                .with_style(container_style)
-                .into_any_named("toolbar")
-        }
-    }
-}
+// todo!()
+// impl View for Toolbar {
+//     fn ui_name() -> &'static str {
+//         "Toolbar"
+//     }
+
+//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+//         let theme = &theme::current(cx).workspace.toolbar;
+
+//         let mut primary_left_items = Vec::new();
+//         let mut primary_right_items = Vec::new();
+//         let mut secondary_item = None;
+//         let spacing = theme.item_spacing;
+//         let mut primary_items_row_count = 1;
+
+//         for (item, position) in &self.items {
+//             match *position {
+//                 ToolbarItemLocation::Hidden => {}
+
+//                 ToolbarItemLocation::PrimaryLeft { flex } => {
+//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
+//                     let left_item = ChildView::new(item.as_any(), cx).aligned();
+//                     if let Some((flex, expanded)) = flex {
+//                         primary_left_items.push(left_item.flex(flex, expanded).into_any());
+//                     } else {
+//                         primary_left_items.push(left_item.into_any());
+//                     }
+//                 }
+
+//                 ToolbarItemLocation::PrimaryRight { flex } => {
+//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
+//                     let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
+//                     if let Some((flex, expanded)) = flex {
+//                         primary_right_items.push(right_item.flex(flex, expanded).into_any());
+//                     } else {
+//                         primary_right_items.push(right_item.into_any());
+//                     }
+//                 }
+
+//                 ToolbarItemLocation::Secondary => {
+//                     secondary_item = Some(
+//                         ChildView::new(item.as_any(), cx)
+//                             .constrained()
+//                             .with_height(theme.height * item.row_count(cx) as f32)
+//                             .into_any(),
+//                     );
+//                 }
+//             }
+//         }
+
+//         let container_style = theme.container;
+//         let height = theme.height * primary_items_row_count as f32;
+
+//         let mut primary_items = Flex::row().with_spacing(spacing);
+//         primary_items.extend(primary_left_items);
+//         primary_items.extend(primary_right_items);
+
+//         let mut toolbar = Flex::column();
+//         if !primary_items.is_empty() {
+//             toolbar.add_child(primary_items.constrained().with_height(height));
+//         }
+//         if let Some(secondary_item) = secondary_item {
+//             toolbar.add_child(secondary_item);
+//         }
+
+//         if toolbar.is_empty() {
+//             toolbar.into_any_named("toolbar")
+//         } else {
+//             toolbar
+//                 .contained()
+//                 .with_style(container_style)
+//                 .into_any_named("toolbar")
+//         }
+//     }
+// }
 
 // <<<<<<< HEAD
 // =======
@@ -206,7 +200,7 @@ impl Toolbar {
         cx.notify();
     }
 
-    pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
+    pub fn add_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
     where
         T: 'static + ToolbarItemView,
     {
@@ -252,7 +246,7 @@ impl Toolbar {
         }
     }
 
-    pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
+    pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<View<T>> {
         self.items
             .iter()
             .find_map(|(item, _)| item.as_any().clone().downcast())
@@ -263,14 +257,15 @@ impl Toolbar {
     }
 }
 
-impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
+impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
     fn id(&self) -> usize {
         self.id()
     }
 
-    fn as_any(&self) -> &AnyViewHandle {
-        self
-    }
+    // todo!()
+    // fn as_any(&self) -> &AnyViewHandle {
+    //     self
+    // }
 
     fn set_active_pane_item(
         &self,
@@ -294,8 +289,9 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
     }
 }
 
-impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
-    fn from(val: &dyn ToolbarItemViewHandle) -> Self {
-        val.as_any().clone()
-    }
-}
+// todo!()
+// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
+//     fn from(val: &dyn ToolbarItemViewHandle) -> Self {
+//         val.as_any().clone()
+//     }
+// }

crates/workspace2/src/workspace2.rs 🔗

@@ -3,12 +3,12 @@ pub mod item;
 // pub mod notifications;
 pub mod pane;
 pub mod pane_group;
-// mod persistence;
+mod persistence;
 pub mod searchable;
 // pub mod shared_screen;
 // mod status_bar;
 mod toolbar;
-// mod workspace_settings;
+mod workspace_settings;
 
 use anyhow::{anyhow, Result};
 // use call2::ActiveCall;
@@ -36,13 +36,14 @@ use anyhow::{anyhow, Result};
 //     },
 //     AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext,
 //     Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext,
-//     ViewHandle, WeakViewHandle, WindowContext, WindowHandle,
+//     View, WeakViewHandle, WindowContext, WindowHandle,
 // };
 // use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
 // use itertools::Itertools;
 // use language2::{LanguageRegistry, Rope};
 // use node_runtime::NodeRuntime;// //
 
+use futures::channel::oneshot;
 // use crate::{
 //     notifications::{simple_message_notification::MessageNotification, NotificationTracker},
 //     persistence::model::{
@@ -91,7 +92,7 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
 //     fn has_focus(&self, cx: &WindowContext) -> bool;
 // }
 
-// impl<T: Modal> ModalHandle for ViewHandle<T> {
+// impl<T: Modal> ModalHandle for View<T> {
 //     fn as_any(&self) -> &AnyViewHandle {
 //         self
 //     }
@@ -376,61 +377,61 @@ pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
     });
 }
 
-// type FollowableItemBuilder = fn(
-//     ViewHandle<Pane>,
-//     ViewHandle<Workspace>,
-//     ViewId,
-//     &mut Option<proto::view::Variant>,
-//     &mut AppContext,
-// ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
-// type FollowableItemBuilders = HashMap<
-//     TypeId,
-//     (
-//         FollowableItemBuilder,
-//         fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
-//     ),
-// >;
-// pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
-//     cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
-//         builders.insert(
-//             TypeId::of::<I>(),
-//             (
-//                 |pane, workspace, id, state, cx| {
-//                     I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
-//                         cx.foreground()
-//                             .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
-//                     })
-//                 },
-//                 |this| Box::new(this.clone().downcast::<I>().unwrap()),
-//             ),
-//         );
-//     });
-// }
+type FollowableItemBuilder = fn(
+    View<Pane>,
+    View<Workspace>,
+    ViewId,
+    &mut Option<proto::view::Variant>,
+    &mut AppContext,
+) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
+type FollowableItemBuilders = HashMap<
+    TypeId,
+    (
+        FollowableItemBuilder,
+        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
+    ),
+>;
+pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
+    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
+        builders.insert(
+            TypeId::of::<I>(),
+            (
+                |pane, workspace, id, state, cx| {
+                    I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
+                        cx.foreground()
+                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
+                    })
+                },
+                |this| Box::new(this.clone().downcast::<I>().unwrap()),
+            ),
+        );
+    });
+}
 
-// type ItemDeserializers = HashMap<
-//     Arc<str>,
-//     fn(
-//         ModelHandle<Project>,
-//         WeakViewHandle<Workspace>,
-//         WorkspaceId,
-//         ItemId,
-//         &mut ViewContext<Pane>,
-//     ) -> Task<Result<Box<dyn ItemHandle>>>,
-// >;
-// pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
-//     cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
-//         if let Some(serialized_item_kind) = I::serialized_item_kind() {
-//             deserializers.insert(
-//                 Arc::from(serialized_item_kind),
-//                 |project, workspace, workspace_id, item_id, cx| {
-//                     let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
-//                     cx.foreground()
-//                         .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
-//                 },
-//             );
-//         }
-//     });
-// }
+type ItemDeserializers = HashMap<
+    Arc<str>,
+    fn(
+        Handle<Project>,
+        WeakView<Workspace>,
+        WorkspaceId,
+        ItemId,
+        &mut ViewContext<Pane>,
+    ) -> Task<Result<Box<dyn ItemHandle>>>,
+>;
+pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
+    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
+        if let Some(serialized_item_kind) = I::serialized_item_kind() {
+            deserializers.insert(
+                Arc::from(serialized_item_kind),
+                |project, workspace, workspace_id, item_id, cx| {
+                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
+                    cx.foreground()
+                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
+                },
+            );
+        }
+    });
+}
 
 pub struct AppState {
     pub languages: Arc<LanguageRegistry>,
@@ -493,54 +494,54 @@ struct Follower {
 //     }
 // }
 
-// struct DelayedDebouncedEditAction {
-//     task: Option<Task<()>>,
-//     cancel_channel: Option<oneshot::Sender<()>>,
-// }
+struct DelayedDebouncedEditAction {
+    task: Option<Task<()>>,
+    cancel_channel: Option<oneshot::Sender<()>>,
+}
 
-// impl DelayedDebouncedEditAction {
-//     fn new() -> DelayedDebouncedEditAction {
-//         DelayedDebouncedEditAction {
-//             task: None,
-//             cancel_channel: None,
-//         }
-//     }
+impl DelayedDebouncedEditAction {
+    fn new() -> DelayedDebouncedEditAction {
+        DelayedDebouncedEditAction {
+            task: None,
+            cancel_channel: None,
+        }
+    }
 
-//     fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
-//     where
-//         F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
-//     {
-//         if let Some(channel) = self.cancel_channel.take() {
-//             _ = channel.send(());
-//         }
+    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
+    where
+        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
+    {
+        if let Some(channel) = self.cancel_channel.take() {
+            _ = channel.send(());
+        }
 
-//         let (sender, mut receiver) = oneshot::channel::<()>();
-//         self.cancel_channel = Some(sender);
+        let (sender, mut receiver) = oneshot::channel::<()>();
+        self.cancel_channel = Some(sender);
 
-//         let previous_task = self.task.take();
-//         self.task = Some(cx.spawn(|workspace, mut cx| async move {
-//             let mut timer = cx.background().timer(delay).fuse();
-//             if let Some(previous_task) = previous_task {
-//                 previous_task.await;
-//             }
+        let previous_task = self.task.take();
+        self.task = Some(cx.spawn(|workspace, mut cx| async move {
+            let mut timer = cx.background().timer(delay).fuse();
+            if let Some(previous_task) = previous_task {
+                previous_task.await;
+            }
 
-//             futures::select_biased! {
-//                 _ = receiver => return,
-//                     _ = timer => {}
-//             }
+            futures::select_biased! {
+                _ = receiver => return,
+                    _ = timer => {}
+            }
 
-//             if let Some(result) = workspace
-//                 .update(&mut cx, |workspace, cx| (func)(workspace, cx))
-//                 .log_err()
-//             {
-//                 result.await.log_err();
-//             }
-//         }));
-//     }
-// }
+            if let Some(result) = workspace
+                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
+                .log_err()
+            {
+                result.await.log_err();
+            }
+        }));
+    }
+}
 
 // pub enum Event {
-//     PaneAdded(ViewHandle<Pane>),
+//     PaneAdded(View<Pane>),
 //     ContactRequestedJoin(u64),
 // }
 
@@ -550,19 +551,19 @@ pub struct Workspace {
     //     zoomed: Option<AnyWeakViewHandle>,
     //     zoomed_position: Option<DockPosition>,
     //     center: PaneGroup,
-    //     left_dock: ViewHandle<Dock>,
-    //     bottom_dock: ViewHandle<Dock>,
-    //     right_dock: ViewHandle<Dock>,
+    //     left_dock: View<Dock>,
+    //     bottom_dock: View<Dock>,
+    //     right_dock: View<Dock>,
     panes: Vec<View<Pane>>,
     //     panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
-    //     active_pane: ViewHandle<Pane>,
+    //     active_pane: View<Pane>,
     last_active_center_pane: Option<WeakView<Pane>>,
     //     last_active_view_id: Option<proto::ViewId>,
-    //     status_bar: ViewHandle<StatusBar>,
+    //     status_bar: View<StatusBar>,
     //     titlebar_item: Option<AnyViewHandle>,
     //     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
     project: Handle<Project>,
-    //     follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
+    //     follower_states: HashMap<View<Pane>, FollowerState>,
     //     last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
     //     window_edited: bool,
     //     active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
@@ -930,19 +931,19 @@ impl Workspace {
     //         self.weak_self.clone()
     //     }
 
-    //     pub fn left_dock(&self) -> &ViewHandle<Dock> {
+    //     pub fn left_dock(&self) -> &View<Dock> {
     //         &self.left_dock
     //     }
 
-    //     pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
+    //     pub fn bottom_dock(&self) -> &View<Dock> {
     //         &self.bottom_dock
     //     }
 
-    //     pub fn right_dock(&self) -> &ViewHandle<Dock> {
+    //     pub fn right_dock(&self) -> &View<Dock> {
     //         &self.right_dock
     //     }
 
-    //     pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
+    //     pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>)
     //     where
     //         T::Event: std::fmt::Debug,
     //     {
@@ -951,12 +952,12 @@ impl Workspace {
 
     //     pub fn add_panel_with_extra_event_handler<T: Panel, F>(
     //         &mut self,
-    //         panel: ViewHandle<T>,
+    //         panel: View<T>,
     //         cx: &mut ViewContext<Self>,
     //         handler: F,
     //     ) where
     //         T::Event: std::fmt::Debug,
-    //         F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
+    //         F: Fn(&mut Self, &View<T>, &T::Event, &mut ViewContext<Self>) + 'static,
     //     {
     //         let dock = match panel.position(cx) {
     //             DockPosition::Left => &self.left_dock,
@@ -1033,7 +1034,7 @@ impl Workspace {
     //         dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
     //     }
 
-    //     pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
+    //     pub fn status_bar(&self) -> &View<StatusBar> {
     //         &self.status_bar
     //     }
 
@@ -1617,10 +1618,10 @@ impl Workspace {
     //         &mut self,
     //         cx: &mut ViewContext<Self>,
     //         add_view: F,
-    //     ) -> Option<ViewHandle<V>>
+    //     ) -> Option<View<V>>
     //     where
     //         V: 'static + Modal,
-    //         F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
+    //         F: FnOnce(&mut Self, &mut ViewContext<Self>) -> View<V>,
     //     {
     //         cx.notify();
     //         // Whatever modal was visible is getting clobbered. If its the same type as V, then return
@@ -1649,7 +1650,7 @@ impl Workspace {
     //         }
     //     }
 
-    //     pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
+    //     pub fn modal<V: 'static + View>(&self) -> Option<View<V>> {
     //         self.modal
     //             .as_ref()
     //             .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
@@ -1676,14 +1677,14 @@ impl Workspace {
     //         self.panes.iter().flat_map(|pane| pane.read(cx).items())
     //     }
 
-    //     pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
+    //     pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
     //         self.items_of_type(cx).max_by_key(|item| item.id())
     //     }
 
     //     pub fn items_of_type<'a, T: Item>(
     //         &'a self,
     //         cx: &'a AppContext,
-    //     ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
+    //     ) -> impl 'a + Iterator<Item = View<T>> {
     //         self.panes
     //             .iter()
     //             .flat_map(|pane| pane.read(cx).items_of_type())
@@ -1834,7 +1835,7 @@ impl Workspace {
     //     }
 
     //     /// Transfer focus to the panel of the given type.
-    //     pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
+    //     pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
     //         self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
     //             .as_any()
     //             .clone()
@@ -1888,7 +1889,7 @@ impl Workspace {
     //         None
     //     }
 
-    //     pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
+    //     pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
     //         for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
     //             let dock = dock.read(cx);
     //             if let Some(panel) = dock.panel::<T>() {
@@ -1956,7 +1957,7 @@ impl Workspace {
     //         cx.notify();
     //     }
 
-    //     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
+    //     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
     //         let pane = cx.add_view(|cx| {
     //             Pane::new(
     //                 self.weak_handle(),
@@ -2138,7 +2139,7 @@ impl Workspace {
     //         &mut self,
     //         project_item: ModelHandle<T::Item>,
     //         cx: &mut ViewContext<Self>,
-    //     ) -> ViewHandle<T>
+    //     ) -> View<T>
     //     where
     //         T: ProjectItem,
     //     {
@@ -2162,7 +2163,7 @@ impl Workspace {
     //         &mut self,
     //         project_item: ModelHandle<T::Item>,
     //         cx: &mut ViewContext<Self>,
-    //     ) -> ViewHandle<T>
+    //     ) -> View<T>
     //     where
     //         T: ProjectItem,
     //     {
@@ -2259,7 +2260,7 @@ impl Workspace {
     //         &mut self,
     //         direction: SplitDirection,
     //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<&ViewHandle<Pane>> {
+    //     ) -> Option<&View<Pane>> {
     //         let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
     //             return None;
     //         };
@@ -2280,7 +2281,7 @@ impl Workspace {
     //         self.center.pane_at_pixel_position(target)
     //     }
 
-    //     fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+    //     fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
     //         if self.active_pane != pane {
     //             self.active_pane = pane.clone();
     //             self.status_bar.update(cx, |status_bar, cx| {
@@ -2304,7 +2305,7 @@ impl Workspace {
 
     //     fn handle_pane_event(
     //         &mut self,
-    //         pane: ViewHandle<Pane>,
+    //         pane: View<Pane>,
     //         event: &pane::Event,
     //         cx: &mut ViewContext<Self>,
     //     ) {
@@ -2363,10 +2364,10 @@ impl Workspace {
 
     //     pub fn split_pane(
     //         &mut self,
-    //         pane_to_split: ViewHandle<Pane>,
+    //         pane_to_split: View<Pane>,
     //         split_direction: SplitDirection,
     //         cx: &mut ViewContext<Self>,
-    //     ) -> ViewHandle<Pane> {
+    //     ) -> View<Pane> {
     //         let new_pane = self.add_pane(cx);
     //         self.center
     //             .split(&pane_to_split, &new_pane, split_direction)
@@ -2377,10 +2378,10 @@ impl Workspace {
 
     //     pub fn split_and_clone(
     //         &mut self,
-    //         pane: ViewHandle<Pane>,
+    //         pane: View<Pane>,
     //         direction: SplitDirection,
     //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<ViewHandle<Pane>> {
+    //     ) -> Option<View<Pane>> {
     //         let item = pane.read(cx).active_item()?;
     //         let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
     //             let new_pane = self.add_pane(cx);
@@ -2440,8 +2441,8 @@ impl Workspace {
 
     //     pub fn move_item(
     //         &mut self,
-    //         source: ViewHandle<Pane>,
-    //         destination: ViewHandle<Pane>,
+    //         source: View<Pane>,
+    //         destination: View<Pane>,
     //         item_id_to_move: usize,
     //         destination_index: usize,
     //         cx: &mut ViewContext<Self>,
@@ -2473,7 +2474,7 @@ impl Workspace {
     //         });
     //     }
 
-    //     fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+    //     fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
     //         if self.center.remove(&pane).unwrap() {
     //             self.force_remove_pane(&pane, cx);
     //             self.unfollow(&pane, cx);
@@ -2488,11 +2489,11 @@ impl Workspace {
     //         }
     //     }
 
-    //     pub fn panes(&self) -> &[ViewHandle<Pane>] {
+    //     pub fn panes(&self) -> &[View<Pane>] {
     //         &self.panes
     //     }
 
-    //     pub fn active_pane(&self) -> &ViewHandle<Pane> {
+    //     pub fn active_pane(&self) -> &View<Pane> {
     //         &self.active_pane
     //     }
 
@@ -2651,7 +2652,7 @@ impl Workspace {
 
     //     pub fn unfollow(
     //         &mut self,
-    //         pane: &ViewHandle<Pane>,
+    //         pane: &View<Pane>,
     //         cx: &mut ViewContext<Self>,
     //     ) -> Option<PeerId> {
     //         let state = self.follower_states.remove(pane)?;
@@ -2959,7 +2960,7 @@ impl Workspace {
     //     async fn add_views_from_leader(
     //         this: WeakViewHandle<Self>,
     //         leader_id: PeerId,
-    //         panes: Vec<ViewHandle<Pane>>,
+    //         panes: Vec<View<Pane>>,
     //         views: Vec<proto::View>,
     //         cx: &mut AsyncAppContext,
     //     ) -> Result<()> {
@@ -3060,7 +3061,7 @@ impl Workspace {
     //         })
     //     }
 
-    //     pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
+    //     pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
     //         self.follower_states.get(pane).map(|state| state.leader_id)
     //     }
 
@@ -3133,9 +3134,9 @@ impl Workspace {
     //     fn shared_screen_for_peer(
     //         &self,
     //         peer_id: PeerId,
-    //         pane: &ViewHandle<Pane>,
+    //         pane: &View<Pane>,
     //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<ViewHandle<SharedScreen>> {
+    //     ) -> Option<View<SharedScreen>> {
     //         let call = self.active_call()?;
     //         let room = call.read(cx).room()?.read(cx);
     //         let participant = room.remote_participant_for_peer_id(peer_id)?;
@@ -3229,7 +3230,7 @@ impl Workspace {
     //         }
     //     }
 
-    //     fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
+    //     fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
     //         self.panes.retain(|p| p != pane);
     //         cx.focus(self.panes.last().unwrap());
     //         if self.last_active_center_pane == Some(pane.downgrade()) {
@@ -3248,7 +3249,7 @@ impl Workspace {
 
     //     fn serialize_workspace(&self, cx: &ViewContext<Self>) {
     //         fn serialize_pane_handle(
-    //             pane_handle: &ViewHandle<Pane>,
+    //             pane_handle: &View<Pane>,
     //             cx: &AppContext,
     //         ) -> SerializedPane {
     //             let (items, active) = {
@@ -4075,7 +4076,7 @@ impl Workspace {
 //     fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
 // }
 
-// impl WorkspaceHandle for ViewHandle<Workspace> {
+// impl WorkspaceHandle for View<Workspace> {
 //     fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
 //         self.read(cx)
 //             .worktrees(cx)
@@ -4320,13 +4321,16 @@ pub async fn activate_workspace_for_project(
 //     None
 // }
 
-use client2::{proto::PeerId, Client, UserStore};
+use client2::{
+    proto::{self, PeerId, ViewId},
+    Client, UserStore,
+};
 use collections::{HashMap, HashSet};
 use gpui2::{
-    AnyHandle, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View, ViewContext,
-    WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions,
+    AnyHandle, AnyView, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View,
+    ViewContext, WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions,
 };
-use item::{ItemHandle, ProjectItem};
+use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
 use language2::LanguageRegistry;
 use node_runtime::NodeRuntime;
 use project2::{Project, ProjectEntryId, ProjectPath, Worktree};
@@ -4334,6 +4338,7 @@ use std::{
     any::TypeId,
     path::{Path, PathBuf},
     sync::Arc,
+    time::Duration,
 };
 use util::ResultExt;
 

crates/workspace2/src/workspace_settings.rs 🔗

@@ -1,6 +1,6 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Setting;
+use settings2::Settings;
 
 #[derive(Deserialize)]
 pub struct WorkspaceSettings {
@@ -41,7 +41,7 @@ pub enum GitGutterSetting {
     Hide,
 }
 
-impl Setting for WorkspaceSettings {
+impl Settings for WorkspaceSettings {
     const KEY: Option<&'static str> = None;
 
     type FileContent = WorkspaceSettingsContent;
@@ -49,7 +49,7 @@ impl Setting for WorkspaceSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &gpui2::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }