WIP: Make the item module compile again

Antonio Scandurra created

Change summary

crates/gpui2/src/app.rs             |  16 
crates/gpui2/src/view.rs            |   2 
crates/workspace2/src/item.rs       | 309 +++++++++++----------------
crates/workspace2/src/pane.rs       | 340 +++++++++++++++---------------
crates/workspace2/src/toolbar.rs    |  21 -
crates/workspace2/src/workspace2.rs | 317 ++++++++++++++--------------
6 files changed, 481 insertions(+), 524 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -14,7 +14,7 @@ pub use test_context::*;
 
 use crate::{
     current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
-    AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Executor,
+    AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, Executor,
     FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly,
     Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
     TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
@@ -694,13 +694,17 @@ impl AppContext {
         self.globals_by_type.insert(global_type, lease.global);
     }
 
-    pub fn observe_release<E: 'static>(
+    pub fn observe_release<E, T>(
         &mut self,
-        handle: &Model<E>,
-        mut on_release: impl FnMut(&mut E, &mut AppContext) + Send + 'static,
-    ) -> Subscription {
+        handle: &E,
+        mut on_release: impl FnMut(&mut T, &mut AppContext) + Send + 'static,
+    ) -> Subscription
+    where
+        E: Entity<T>,
+        T: 'static,
+    {
         self.release_listeners.insert(
-            handle.entity_id,
+            handle.entity_id(),
             Box::new(move |entity, cx| {
                 let entity = entity.downcast_mut().expect("invalid entity type");
                 on_release(entity, cx)

crates/gpui2/src/view.rs 🔗

@@ -314,7 +314,7 @@ impl AnyView {
             .map_err(|_| self)
     }
 
-    pub(crate) fn entity_type(&self) -> TypeId {
+    pub fn entity_type(&self) -> TypeId {
         self.0.entity_type()
     }
 

crates/workspace2/src/item.rs 🔗

@@ -1,88 +1,80 @@
-// use crate::{
-//     pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders,
-//     ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
-// };
-// use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
+use crate::{
+    pane::{self, Pane},
+    persistence::model::ItemId,
+    searchable::SearchableItemHandle,
+    workspace_settings::{AutosaveSetting, WorkspaceSettings},
+    DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
+    ViewId, Workspace, WorkspaceId,
+};
 use anyhow::Result;
 use client2::{
     proto::{self, PeerId},
     Client,
 };
+use gpui2::{
+    AnyElement, AnyView, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, Render,
+    SharedString, Task, View, ViewContext, WeakView, WindowContext,
+};
+use parking_lot::Mutex;
+use project2::{Project, ProjectEntryId, ProjectPath};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
 use settings2::Settings;
+use smallvec::SmallVec;
+use std::{
+    any::{Any, TypeId},
+    ops::Range,
+    path::PathBuf,
+    sync::{
+        atomic::{AtomicBool, Ordering},
+        Arc,
+    },
+    time::Duration,
+};
 use theme2::Theme;
-// use client2::{
-//     proto::{self, PeerId},
-//     Client,
-// };
-// use gpui2::geometry::vector::Vector2F;
-// use gpui2::AnyWindowHandle;
-// use gpui2::{
-//     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Model, Task, View,
-//     ViewContext, View, WeakViewHandle, WindowContext,
-// };
-// use project2::{Project, ProjectEntryId, ProjectPath};
-// use schemars::JsonSchema;
-// use serde_derive::{Deserialize, Serialize};
-// use settings2::Setting;
-// use smallvec::SmallVec;
-// use std::{
-//     any::{Any, TypeId},
-//     borrow::Cow,
-//     cell::RefCell,
-//     fmt,
-//     ops::Range,
-//     path::PathBuf,
-//     rc::Rc,
-//     sync::{
-//         atomic::{AtomicBool, Ordering},
-//         Arc,
-//     },
-//     time::Duration,
-// };
-// use theme2::Theme;
-
-// #[derive(Deserialize)]
-// pub struct ItemSettings {
-//     pub git_status: bool,
-//     pub close_position: ClosePosition,
-// }
 
-// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-// #[serde(rename_all = "lowercase")]
-// pub enum ClosePosition {
-//     Left,
-//     #[default]
-//     Right,
-// }
+#[derive(Deserialize)]
+pub struct ItemSettings {
+    pub git_status: bool,
+    pub close_position: ClosePosition,
+}
 
-// impl ClosePosition {
-//     pub fn right(&self) -> bool {
-//         match self {
-//             ClosePosition::Left => false,
-//             ClosePosition::Right => true,
-//         }
-//     }
-// }
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum ClosePosition {
+    Left,
+    #[default]
+    Right,
+}
 
-// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-// pub struct ItemSettingsContent {
-//     git_status: Option<bool>,
-//     close_position: Option<ClosePosition>,
-// }
+impl ClosePosition {
+    pub fn right(&self) -> bool {
+        match self {
+            ClosePosition::Left => false,
+            ClosePosition::Right => true,
+        }
+    }
+}
 
-// impl Setting for ItemSettings {
-//     const KEY: Option<&'static str> = Some("tabs");
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+pub struct ItemSettingsContent {
+    git_status: Option<bool>,
+    close_position: Option<ClosePosition>,
+}
 
-//     type FileContent = ItemSettingsContent;
+impl Settings for ItemSettings {
+    const KEY: Option<&'static str> = Some("tabs");
 
-//     fn load(
-//         default_value: &Self::FileContent,
-//         user_values: &[&Self::FileContent],
-//         _: &gpui2::AppContext,
-//     ) -> anyhow::Result<Self> {
-//         Self::load_via_json_merge(default_value, user_values)
-//     }
-// }
+    type FileContent = ItemSettingsContent;
+
+    fn load(
+        default_value: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+        _: &mut AppContext,
+    ) -> Result<Self> {
+        Self::load_via_json_merge(default_value, user_values)
+    }
+}
 
 #[derive(Eq, PartialEq, Hash, Debug)]
 pub enum ItemEvent {
@@ -165,18 +157,18 @@ pub trait Item: Render + EventEmitter + Send {
         false
     }
 
-    //     fn act_as_type<'a>(
-    //         &'a self,
-    //         type_id: TypeId,
-    //         self_handle: &'a View<Self>,
-    //         _: &'a AppContext,
-    //     ) -> Option<&AnyViewHandle> {
-    //         if TypeId::of::<Self>() == type_id {
-    //             Some(self_handle)
-    //         } else {
-    //             None
-    //         }
-    //     }
+    fn act_as_type<'a>(
+        &'a self,
+        type_id: TypeId,
+        self_handle: &'a View<Self>,
+        _: &'a AppContext,
+    ) -> Option<AnyView> {
+        if TypeId::of::<Self>() == type_id {
+            Some(self_handle.clone().into_any())
+        } else {
+            None
+        }
+    }
 
     fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
         None
@@ -215,35 +207,6 @@ pub trait Item: Render + EventEmitter + Send {
     }
 }
 
-use std::{
-    any::Any,
-    cell::RefCell,
-    ops::Range,
-    path::PathBuf,
-    rc::Rc,
-    sync::{
-        atomic::{AtomicBool, Ordering},
-        Arc,
-    },
-    time::Duration,
-};
-
-use gpui2::{
-    AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, HighlightStyle, Model, Pixels,
-    Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext,
-};
-use project2::{Project, ProjectEntryId, ProjectPath};
-use smallvec::SmallVec;
-
-use crate::{
-    pane::{self, Pane},
-    persistence::model::ItemId,
-    searchable::SearchableItemHandle,
-    workspace_settings::{AutosaveSetting, WorkspaceSettings},
-    DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
-    ViewId, Workspace, WorkspaceId,
-};
-
 pub trait ItemHandle: 'static + Send {
     fn subscribe_to_item_events(
         &self,
@@ -275,7 +238,6 @@ pub trait ItemHandle: 'static + Send {
     fn workspace_deactivated(&self, cx: &mut WindowContext);
     fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
     fn id(&self) -> usize;
-    fn window(&self) -> AnyWindowHandle;
     fn to_any(&self) -> AnyView;
     fn is_dirty(&self, cx: &AppContext) -> bool;
     fn has_conflict(&self, cx: &AppContext) -> bool;
@@ -288,12 +250,12 @@ pub trait ItemHandle: 'static + Send {
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
     fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
-    // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!()
+    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
     fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
     fn on_release(
         &self,
         cx: &mut AppContext,
-        callback: Box<dyn FnOnce(&mut AppContext)>,
+        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
     ) -> gpui2::Subscription;
     fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
     fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
@@ -303,23 +265,21 @@ pub trait ItemHandle: 'static + Send {
     fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
 }
 
-pub trait WeakItemHandle: Send {
+pub trait WeakItemHandle: Send + Sync {
     fn id(&self) -> usize;
-    fn window(&self) -> AnyWindowHandle;
     fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
 }
 
-// todo!()
-// impl dyn ItemHandle {
-//     pub fn downcast<T: View>(&self) -> Option<View<T>> {
-//         self.as_any().clone().downcast()
-//     }
+impl dyn ItemHandle {
+    pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
+        self.to_any().downcast().ok()
+    }
 
-//     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())
-//     }
-// }
+    pub fn act_as<V: 'static>(&self, cx: &AppContext) -> Option<View<V>> {
+        self.act_as_type(TypeId::of::<V>(), cx)
+            .and_then(|t| t.downcast().ok())
+    }
+}
 
 impl<T: Item> ItemHandle for View<T> {
     fn subscribe_to_item_events(
@@ -438,8 +398,8 @@ impl<T: Item> ItemHandle for View<T> {
             .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 pending_update = Arc::new(Mutex::new(None));
+            let pending_update_scheduled = Arc::new(AtomicBool::new(false));
 
             let mut event_subscription =
                 Some(cx.subscribe(self, move |workspace, item, event, cx| {
@@ -462,33 +422,31 @@ impl<T: Item> ItemHandle for View<T> {
                             workspace.unfollow(&pane, cx);
                         }
 
-                        if item.add_event_to_update_proto(
-                            event,
-                            &mut *pending_update.borrow_mut(),
-                            cx,
-                        ) && !pending_update_scheduled.load(Ordering::SeqCst)
+                        if item.add_event_to_update_proto(event, &mut *pending_update.lock(), 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,
-                                    );
-                                }
-                            });
+                            todo!("replace with on_next_frame?");
+                            // 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,
+                            //         );
+                            //     }
+                            // });
                         }
                     }
 
@@ -525,15 +483,16 @@ impl<T: Item> ItemHandle for View<T> {
                     }
                 }));
 
-            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();
+            todo!("observe focus");
+            // 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, _, _| {
@@ -564,11 +523,6 @@ impl<T: Item> ItemHandle for View<T> {
         self.id()
     }
 
-    fn window(&self) -> AnyWindowHandle {
-        todo!()
-        // AnyViewHandle::window(self)
-    }
-
     fn to_any(&self) -> AnyView {
         self.clone().into_any()
     }
@@ -602,16 +556,15 @@ impl<T: Item> ItemHandle for View<T> {
         self.update(cx, |item, cx| item.reload(project, cx))
     }
 
-    // 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 act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<AnyView> {
+        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))
+            let item = self.to_any();
+            Some(builders.get(&item.entity_type())?.1(&item))
         } else {
             None
         }
@@ -620,7 +573,7 @@ impl<T: Item> ItemHandle for View<T> {
     fn on_release(
         &self,
         cx: &mut AppContext,
-        callback: Box<dyn FnOnce(&mut AppContext)>,
+        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
     ) -> gpui2::Subscription {
         cx.observe_release(self, move |_, cx| callback(cx))
     }
@@ -673,10 +626,6 @@ impl<T: Item> WeakItemHandle for WeakView<T> {
         self.id()
     }
 
-    fn window(&self) -> AnyWindowHandle {
-        self.window()
-    }
-
     fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
         self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
     }

crates/workspace2/src/pane.rs 🔗

@@ -2,10 +2,15 @@
 
 use crate::{
     item::{Item, ItemHandle, WeakItemHandle},
+    toolbar::Toolbar,
     SplitDirection, Workspace,
 };
+use anyhow::Result;
 use collections::{HashMap, VecDeque};
-use gpui2::{EventEmitter, Model, View, ViewContext, WeakView};
+use gpui2::{
+    AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView,
+    WindowContext,
+};
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
@@ -68,6 +73,7 @@ pub enum SaveIntent {
 //     pub save_intent: Option<SaveIntent>,
 // }
 
+// todo!()
 // actions!(
 //     pane,
 //     [
@@ -90,8 +96,9 @@ pub enum SaveIntent {
 
 // impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
 
-// const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
+const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
+// todo!()
 // pub fn init(cx: &mut AppContext) {
 //     cx.add_action(Pane::toggle_zoom);
 //     cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
@@ -330,7 +337,7 @@ impl Pane {
                 pane: handle.clone(),
                 next_timestamp,
             }))),
-            // toolbar: cx.add_view(|_| Toolbar::new()),
+            toolbar: cx.build_view(|_| Toolbar::new()),
             // tab_bar_context_menu: TabBarContextMenu {
             //     kind: TabBarContextMenuKind::New,
             //     handle: context_menu,
@@ -447,33 +454,33 @@ impl Pane {
         }
     }
 
-    //     pub fn nav_history(&self) -> &NavHistory {
-    //         &self.nav_history
-    //     }
+    pub fn nav_history(&self) -> &NavHistory {
+        &self.nav_history
+    }
 
-    //     pub fn nav_history_mut(&mut self) -> &mut NavHistory {
-    //         &mut self.nav_history
-    //     }
+    pub fn nav_history_mut(&mut self) -> &mut NavHistory {
+        &mut self.nav_history
+    }
 
-    //     pub fn disable_history(&mut self) {
-    //         self.nav_history.disable();
-    //     }
+    pub fn disable_history(&mut self) {
+        self.nav_history.disable();
+    }
 
-    //     pub fn enable_history(&mut self) {
-    //         self.nav_history.enable();
-    //     }
+    pub fn enable_history(&mut self) {
+        self.nav_history.enable();
+    }
 
-    //     pub fn can_navigate_backward(&self) -> bool {
-    //         !self.nav_history.0.borrow().backward_stack.is_empty()
-    //     }
+    pub fn can_navigate_backward(&self) -> bool {
+        !self.nav_history.0.lock().backward_stack.is_empty()
+    }
 
-    //     pub fn can_navigate_forward(&self) -> bool {
-    //         !self.nav_history.0.borrow().forward_stack.is_empty()
-    //     }
+    pub fn can_navigate_forward(&self) -> bool {
+        !self.nav_history.0.lock().forward_stack.is_empty()
+    }
 
-    //     fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
-    //         self.toolbar.update(cx, |_, cx| cx.notify());
-    //     }
+    fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
+        self.toolbar.update(cx, |_, cx| cx.notify());
+    }
 
     pub(crate) fn open_item(
         &mut self,
@@ -736,115 +743,115 @@ impl Pane {
     //         ))
     //     }
 
-    //     pub fn close_item_by_id(
-    //         &mut self,
-    //         item_id_to_close: usize,
-    //         save_intent: SaveIntent,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Task<Result<()>> {
-    //         self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
-    //     }
-
-    //     pub fn close_inactive_items(
-    //         &mut self,
-    //         _: &CloseInactiveItems,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         if self.items.is_empty() {
-    //             return None;
-    //         }
+    pub fn close_item_by_id(
+        &mut self,
+        item_id_to_close: usize,
+        save_intent: SaveIntent,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Result<()>> {
+        self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
+    }
 
-    //         let active_item_id = self.items[self.active_item_index].id();
-    //         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
-    //             item_id != active_item_id
-    //         }))
+    // pub fn close_inactive_items(
+    //     &mut self,
+    //     _: &CloseInactiveItems,
+    //     cx: &mut ViewContext<Self>,
+    // ) -> Option<Task<Result<()>>> {
+    //     if self.items.is_empty() {
+    //         return None;
     //     }
 
-    //     pub fn close_clean_items(
-    //         &mut self,
-    //         _: &CloseCleanItems,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         let item_ids: Vec<_> = self
-    //             .items()
-    //             .filter(|item| !item.is_dirty(cx))
-    //             .map(|item| item.id())
-    //             .collect();
-    //         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
-    //             item_ids.contains(&item_id)
-    //         }))
+    //     let active_item_id = self.items[self.active_item_index].id();
+    //     Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //         item_id != active_item_id
+    //     }))
+    // }
+
+    // pub fn close_clean_items(
+    //     &mut self,
+    //     _: &CloseCleanItems,
+    //     cx: &mut ViewContext<Self>,
+    // ) -> Option<Task<Result<()>>> {
+    //     let item_ids: Vec<_> = self
+    //         .items()
+    //         .filter(|item| !item.is_dirty(cx))
+    //         .map(|item| item.id())
+    //         .collect();
+    //     Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //         item_ids.contains(&item_id)
+    //     }))
+    // }
+
+    // pub fn close_items_to_the_left(
+    //     &mut self,
+    //     _: &CloseItemsToTheLeft,
+    //     cx: &mut ViewContext<Self>,
+    // ) -> Option<Task<Result<()>>> {
+    //     if self.items.is_empty() {
+    //         return None;
     //     }
-
-    //     pub fn close_items_to_the_left(
-    //         &mut self,
-    //         _: &CloseItemsToTheLeft,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         if self.items.is_empty() {
-    //             return None;
-    //         }
-    //         let active_item_id = self.items[self.active_item_index].id();
-    //         Some(self.close_items_to_the_left_by_id(active_item_id, cx))
+    //     let active_item_id = self.items[self.active_item_index].id();
+    //     Some(self.close_items_to_the_left_by_id(active_item_id, cx))
+    // }
+
+    // pub fn close_items_to_the_left_by_id(
+    //     &mut self,
+    //     item_id: usize,
+    //     cx: &mut ViewContext<Self>,
+    // ) -> Task<Result<()>> {
+    //     let item_ids: Vec<_> = self
+    //         .items()
+    //         .take_while(|item| item.id() != item_id)
+    //         .map(|item| item.id())
+    //         .collect();
+    //     self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //         item_ids.contains(&item_id)
+    //     })
+    // }
+
+    // pub fn close_items_to_the_right(
+    //     &mut self,
+    //     _: &CloseItemsToTheRight,
+    //     cx: &mut ViewContext<Self>,
+    // ) -> Option<Task<Result<()>>> {
+    //     if self.items.is_empty() {
+    //         return None;
     //     }
-
-    //     pub fn close_items_to_the_left_by_id(
-    //         &mut self,
-    //         item_id: usize,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Task<Result<()>> {
-    //         let item_ids: Vec<_> = self
-    //             .items()
-    //             .take_while(|item| item.id() != item_id)
-    //             .map(|item| item.id())
-    //             .collect();
-    //         self.close_items(cx, SaveIntent::Close, move |item_id| {
-    //             item_ids.contains(&item_id)
-    //         })
+    //     let active_item_id = self.items[self.active_item_index].id();
+    //     Some(self.close_items_to_the_right_by_id(active_item_id, cx))
+    // }
+
+    // pub fn close_items_to_the_right_by_id(
+    //     &mut self,
+    //     item_id: usize,
+    //     cx: &mut ViewContext<Self>,
+    // ) -> Task<Result<()>> {
+    //     let item_ids: Vec<_> = self
+    //         .items()
+    //         .rev()
+    //         .take_while(|item| item.id() != item_id)
+    //         .map(|item| item.id())
+    //         .collect();
+    //     self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //         item_ids.contains(&item_id)
+    //     })
+    // }
+
+    // pub fn close_all_items(
+    //     &mut self,
+    //     action: &CloseAllItems,
+    //     cx: &mut ViewContext<Self>,
+    // ) -> Option<Task<Result<()>>> {
+    //     if self.items.is_empty() {
+    //         return None;
     //     }
 
-    //     pub fn close_items_to_the_right(
-    //         &mut self,
-    //         _: &CloseItemsToTheRight,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         if self.items.is_empty() {
-    //             return None;
-    //         }
-    //         let active_item_id = self.items[self.active_item_index].id();
-    //         Some(self.close_items_to_the_right_by_id(active_item_id, cx))
-    //     }
-
-    //     pub fn close_items_to_the_right_by_id(
-    //         &mut self,
-    //         item_id: usize,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Task<Result<()>> {
-    //         let item_ids: Vec<_> = self
-    //             .items()
-    //             .rev()
-    //             .take_while(|item| item.id() != item_id)
-    //             .map(|item| item.id())
-    //             .collect();
-    //         self.close_items(cx, SaveIntent::Close, move |item_id| {
-    //             item_ids.contains(&item_id)
-    //         })
-    //     }
-
-    //     pub fn close_all_items(
-    //         &mut self,
-    //         action: &CloseAllItems,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         if self.items.is_empty() {
-    //             return None;
-    //         }
-
-    //         Some(
-    //             self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
-    //                 true
-    //             }),
-    //         )
-    //     }
+    //     Some(
+    //         self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
+    //             true
+    //         }),
+    //     )
+    // }
 
     //     pub(super) fn file_names_for_prompt(
     //         items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
@@ -1156,28 +1163,29 @@ impl Pane {
     //         Ok(true)
     //     }
 
-    //     fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
-    //         let is_deleted = item.project_entry_ids(cx).is_empty();
-    //         item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
-    //     }
+    fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
+        let is_deleted = item.project_entry_ids(cx).is_empty();
+        item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
+    }
 
-    //     pub fn autosave_item(
-    //         item: &dyn ItemHandle,
-    //         project: Model<Project>,
-    //         cx: &mut WindowContext,
-    //     ) -> Task<Result<()>> {
-    //         if Self::can_autosave_item(item, cx) {
-    //             item.save(project, cx)
-    //         } else {
-    //             Task::ready(Ok(()))
-    //         }
-    //     }
+    pub fn autosave_item(
+        item: &dyn ItemHandle,
+        project: Model<Project>,
+        cx: &mut WindowContext,
+    ) -> Task<Result<()>> {
+        if Self::can_autosave_item(item, cx) {
+            item.save(project, cx)
+        } else {
+            Task::ready(Ok(()))
+        }
+    }
 
-    //     pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
-    //         if let Some(active_item) = self.active_item() {
-    //             cx.focus(active_item.as_any());
-    //         }
-    //     }
+    pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
+        todo!();
+        // if let Some(active_item) = self.active_item() {
+        //     cx.focus(active_item.as_any());
+        // }
+    }
 
     //     pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
     //         cx.emit(Event::Split(direction));
@@ -1979,7 +1987,7 @@ impl NavHistory {
         cx: &AppContext,
         mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
     ) {
-        let borrowed_history = self.0.borrow();
+        let borrowed_history = self.0.lock();
         borrowed_history
             .forward_stack
             .iter()
@@ -1990,7 +1998,7 @@ impl NavHistory {
                     borrowed_history.paths_by_item.get(&entry.item.id())
                 {
                     f(entry, project_and_abs_path.clone());
-                } else if let Some(item) = entry.item.upgrade(cx) {
+                } else if let Some(item) = entry.item.upgrade() {
                     if let Some(path) = item.project_path(cx) {
                         f(entry, (path, None));
                     }
@@ -1999,23 +2007,23 @@ impl NavHistory {
     }
 
     pub fn set_mode(&mut self, mode: NavigationMode) {
-        self.0.borrow_mut().mode = mode;
+        self.0.lock().mode = mode;
     }
 
     pub fn mode(&self) -> NavigationMode {
-        self.0.borrow().mode
+        self.0.lock().mode
     }
 
     pub fn disable(&mut self) {
-        self.0.borrow_mut().mode = NavigationMode::Disabled;
+        self.0.lock().mode = NavigationMode::Disabled;
     }
 
     pub fn enable(&mut self) {
-        self.0.borrow_mut().mode = NavigationMode::Normal;
+        self.0.lock().mode = NavigationMode::Normal;
     }
 
     pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
-        let mut state = self.0.borrow_mut();
+        let mut state = self.0.lock();
         let entry = match mode {
             NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
                 return None
@@ -2034,10 +2042,10 @@ impl NavHistory {
     pub fn push<D: 'static + Any>(
         &mut self,
         data: Option<D>,
-        item: Rc<dyn WeakItemHandle>,
+        item: Arc<dyn WeakItemHandle>,
         cx: &mut WindowContext,
     ) {
-        let state = &mut *self.0.borrow_mut();
+        let state = &mut *self.0.lock();
         match state.mode {
             NavigationMode::Disabled => {}
             NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
@@ -2086,7 +2094,7 @@ impl NavHistory {
     }
 
     pub fn remove_item(&mut self, item_id: usize) {
-        let mut state = self.0.borrow_mut();
+        let mut state = self.0.lock();
         state.paths_by_item.remove(&item_id);
         state
             .backward_stack
@@ -2100,19 +2108,19 @@ impl NavHistory {
     }
 
     pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
-        self.0.borrow().paths_by_item.get(&item_id).cloned()
+        self.0.lock().paths_by_item.get(&item_id).cloned()
     }
 }
 
-// impl NavHistoryState {
-//     pub fn did_update(&self, cx: &mut WindowContext) {
-//         if let Some(pane) = self.pane.upgrade(cx) {
-//             cx.defer(move |cx| {
-//                 pane.update(cx, |pane, cx| pane.history_updated(cx));
-//             });
-//         }
-//     }
-// }
+impl NavHistoryState {
+    pub fn did_update(&self, cx: &mut WindowContext) {
+        if let Some(pane) = self.pane.upgrade() {
+            cx.defer(move |cx| {
+                pane.update(cx, |pane, cx| pane.history_updated(cx));
+            });
+        }
+    }
+}
 
 // pub struct PaneBackdrop<V> {
 //     child_view: usize,

crates/workspace2/src/toolbar.rs 🔗

@@ -1,7 +1,7 @@
 use crate::ItemHandle;
-use gpui2::{AppContext, EventEmitter, View, ViewContext, WindowContext};
+use gpui2::{AnyView, AppContext, EventEmitter, Render, View, ViewContext, WindowContext};
 
-pub trait ToolbarItemView: EventEmitter + Sized {
+pub trait ToolbarItemView: Render + EventEmitter {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
@@ -22,14 +22,14 @@ pub trait ToolbarItemView: EventEmitter + Sized {
     /// Number of times toolbar's height will be repeated to get the effective height.
     /// Useful when multiple rows one under each other are needed.
     /// The rows have the same width and act as a whole when reacting to resizes and similar events.
-    fn row_count(&self, _cx: &ViewContext<Self>) -> usize {
+    fn row_count(&self, _cx: &WindowContext) -> usize {
         1
     }
 }
 
-trait ToolbarItemViewHandle {
+trait ToolbarItemViewHandle: Send {
     fn id(&self) -> usize;
-    // fn as_any(&self) -> &AnyViewHandle; todo!()
+    fn to_any(&self) -> AnyView;
     fn set_active_pane_item(
         &self,
         active_pane_item: Option<&dyn ItemHandle>,
@@ -249,7 +249,7 @@ impl Toolbar {
     pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<View<T>> {
         self.items
             .iter()
-            .find_map(|(item, _)| item.as_any().clone().downcast())
+            .find_map(|(item, _)| item.to_any().downcast().ok())
     }
 
     pub fn hidden(&self) -> bool {
@@ -262,10 +262,9 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
         self.id()
     }
 
-    // todo!()
-    // fn as_any(&self) -> &AnyViewHandle {
-    //     self
-    // }
+    fn to_any(&self) -> AnyView {
+        self.clone().into_any()
+    }
 
     fn set_active_pane_item(
         &self,
@@ -285,7 +284,7 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
     }
 
     fn row_count(&self, cx: &WindowContext) -> usize {
-        self.read_with(cx, |this, cx| this.row_count(cx))
+        self.read(cx).row_count(cx)
     }
 }
 

crates/workspace2/src/workspace2.rs 🔗

@@ -36,6 +36,10 @@ use std::{
 pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
 use util::ResultExt;
 
+use crate::persistence::model::{
+    DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
+};
+
 // lazy_static! {
 //     static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
 //         .ok()
@@ -514,9 +518,9 @@ pub struct Workspace {
     //     zoomed: Option<AnyWeakViewHandle>,
     //     zoomed_position: Option<DockPosition>,
     //     center: PaneGroup,
-    //     left_dock: View<Dock>,
-    //     bottom_dock: View<Dock>,
-    //     right_dock: View<Dock>,
+    left_dock: View<Dock>,
+    bottom_dock: View<Dock>,
+    right_dock: View<Dock>,
     panes: Vec<View<Pane>>,
     panes_by_item: HashMap<usize, WeakView<Pane>>,
     //     active_pane: View<Pane>,
@@ -526,8 +530,8 @@ pub struct Workspace {
     //     titlebar_item: Option<AnyViewHandle>,
     //     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
     project: Model<Project>,
-    //     follower_states: HashMap<View<Pane>, FollowerState>,
-    //     last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
+    follower_states: HashMap<View<Pane>, FollowerState>,
+    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
     //     window_edited: bool,
     //     active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
     //     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
@@ -2613,37 +2617,33 @@ impl Workspace {
     //         self.start_following(leader_id, cx)
     //     }
 
-    //     pub fn unfollow(
-    //         &mut self,
-    //         pane: &View<Pane>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<PeerId> {
-    //         let state = self.follower_states.remove(pane)?;
-    //         let leader_id = state.leader_id;
-    //         for (_, item) in state.items_by_leader_view_id {
-    //             item.set_leader_peer_id(None, cx);
-    //         }
+    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
+        let state = self.follower_states.remove(pane)?;
+        let leader_id = state.leader_id;
+        for (_, item) in state.items_by_leader_view_id {
+            item.set_leader_peer_id(None, cx);
+        }
 
-    //         if self
-    //             .follower_states
-    //             .values()
-    //             .all(|state| state.leader_id != state.leader_id)
-    //         {
-    //             let project_id = self.project.read(cx).remote_id();
-    //             let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
-    //             self.app_state
-    //                 .client
-    //                 .send(proto::Unfollow {
-    //                     room_id,
-    //                     project_id,
-    //                     leader_id: Some(leader_id),
-    //                 })
-    //                 .log_err();
-    //         }
+        if self
+            .follower_states
+            .values()
+            .all(|state| state.leader_id != state.leader_id)
+        {
+            let project_id = self.project.read(cx).remote_id();
+            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+            self.app_state
+                .client
+                .send(proto::Unfollow {
+                    room_id,
+                    project_id,
+                    leader_id: Some(leader_id),
+                })
+                .log_err();
+        }
 
-    //         cx.notify();
-    //         Some(leader_id)
-    //     }
+        cx.notify();
+        Some(leader_id)
+    }
 
     //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
     //         self.follower_states
@@ -3210,137 +3210,134 @@ impl Workspace {
     //         }));
     //     }
 
-    //     fn serialize_workspace(&self, cx: &ViewContext<Self>) {
-    //         fn serialize_pane_handle(
-    //             pane_handle: &View<Pane>,
-    //             cx: &AppContext,
-    //         ) -> SerializedPane {
-    //             let (items, active) = {
-    //                 let pane = pane_handle.read(cx);
-    //                 let active_item_id = pane.active_item().map(|item| item.id());
-    //                 (
-    //                     pane.items()
-    //                         .filter_map(|item_handle| {
-    //                             Some(SerializedItem {
-    //                                 kind: Arc::from(item_handle.serialized_item_kind()?),
-    //                                 item_id: item_handle.id(),
-    //                                 active: Some(item_handle.id()) == active_item_id,
-    //                             })
-    //                         })
-    //                         .collect::<Vec<_>>(),
-    //                     pane.has_focus(),
-    //                 )
-    //             };
+    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
+        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &AppContext) -> SerializedPane {
+            let (items, active) = {
+                let pane = pane_handle.read(cx);
+                let active_item_id = pane.active_item().map(|item| item.id());
+                (
+                    pane.items()
+                        .filter_map(|item_handle| {
+                            Some(SerializedItem {
+                                kind: Arc::from(item_handle.serialized_item_kind()?),
+                                item_id: item_handle.id(),
+                                active: Some(item_handle.id()) == active_item_id,
+                            })
+                        })
+                        .collect::<Vec<_>>(),
+                    pane.has_focus(),
+                )
+            };
 
-    //             SerializedPane::new(items, active)
-    //         }
+            SerializedPane::new(items, active)
+        }
 
-    //         fn build_serialized_pane_group(
-    //             pane_group: &Member,
-    //             cx: &AppContext,
-    //         ) -> SerializedPaneGroup {
-    //             match pane_group {
-    //                 Member::Axis(PaneAxis {
-    //                     axis,
-    //                     members,
-    //                     flexes,
-    //                     bounding_boxes: _,
-    //                 }) => SerializedPaneGroup::Group {
-    //                     axis: *axis,
-    //                     children: members
-    //                         .iter()
-    //                         .map(|member| build_serialized_pane_group(member, cx))
-    //                         .collect::<Vec<_>>(),
-    //                     flexes: Some(flexes.borrow().clone()),
-    //                 },
-    //                 Member::Pane(pane_handle) => {
-    //                     SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
-    //                 }
-    //             }
-    //         }
+        fn build_serialized_pane_group(
+            pane_group: &Member,
+            cx: &AppContext,
+        ) -> SerializedPaneGroup {
+            match pane_group {
+                Member::Axis(PaneAxis {
+                    axis,
+                    members,
+                    flexes,
+                    bounding_boxes: _,
+                }) => SerializedPaneGroup::Group {
+                    axis: *axis,
+                    children: members
+                        .iter()
+                        .map(|member| build_serialized_pane_group(member, cx))
+                        .collect::<Vec<_>>(),
+                    flexes: Some(flexes.borrow().clone()),
+                },
+                Member::Pane(pane_handle) => {
+                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
+                }
+            }
+        }
 
-    //         fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
-    //             let left_dock = this.left_dock.read(cx);
-    //             let left_visible = left_dock.is_open();
-    //             let left_active_panel = left_dock.visible_panel().and_then(|panel| {
-    //                 Some(
-    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
-    //                         .to_string(),
-    //                 )
-    //             });
-    //             let left_dock_zoom = left_dock
-    //                 .visible_panel()
-    //                 .map(|panel| panel.is_zoomed(cx))
-    //                 .unwrap_or(false);
-
-    //             let right_dock = this.right_dock.read(cx);
-    //             let right_visible = right_dock.is_open();
-    //             let right_active_panel = right_dock.visible_panel().and_then(|panel| {
-    //                 Some(
-    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
-    //                         .to_string(),
-    //                 )
-    //             });
-    //             let right_dock_zoom = right_dock
-    //                 .visible_panel()
-    //                 .map(|panel| panel.is_zoomed(cx))
-    //                 .unwrap_or(false);
-
-    //             let bottom_dock = this.bottom_dock.read(cx);
-    //             let bottom_visible = bottom_dock.is_open();
-    //             let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
-    //                 Some(
-    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
-    //                         .to_string(),
-    //                 )
-    //             });
-    //             let bottom_dock_zoom = bottom_dock
-    //                 .visible_panel()
-    //                 .map(|panel| panel.is_zoomed(cx))
-    //                 .unwrap_or(false);
-
-    //             DockStructure {
-    //                 left: DockData {
-    //                     visible: left_visible,
-    //                     active_panel: left_active_panel,
-    //                     zoom: left_dock_zoom,
-    //                 },
-    //                 right: DockData {
-    //                     visible: right_visible,
-    //                     active_panel: right_active_panel,
-    //                     zoom: right_dock_zoom,
-    //                 },
-    //                 bottom: DockData {
-    //                     visible: bottom_visible,
-    //                     active_panel: bottom_active_panel,
-    //                     zoom: bottom_dock_zoom,
-    //                 },
-    //             }
-    //         }
+        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
+            let left_dock = this.left_dock.read(cx);
+            let left_visible = left_dock.is_open();
+            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
+                Some(
+                    cx.view_ui_name(panel.as_any().window(), panel.id())?
+                        .to_string(),
+                )
+            });
+            let left_dock_zoom = left_dock
+                .visible_panel()
+                .map(|panel| panel.is_zoomed(cx))
+                .unwrap_or(false);
 
-    //         if let Some(location) = self.location(cx) {
-    //             // Load bearing special case:
-    //             //  - with_local_workspace() relies on this to not have other stuff open
-    //             //    when you open your log
-    //             if !location.paths().is_empty() {
-    //                 let center_group = build_serialized_pane_group(&self.center.root, cx);
-    //                 let docks = build_serialized_docks(self, cx);
-
-    //                 let serialized_workspace = SerializedWorkspace {
-    //                     id: self.database_id,
-    //                     location,
-    //                     center_group,
-    //                     bounds: Default::default(),
-    //                     display: Default::default(),
-    //                     docks,
-    //                 };
+            let right_dock = this.right_dock.read(cx);
+            let right_visible = right_dock.is_open();
+            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
+                Some(
+                    cx.view_ui_name(panel.as_any().window(), panel.id())?
+                        .to_string(),
+                )
+            });
+            let right_dock_zoom = right_dock
+                .visible_panel()
+                .map(|panel| panel.is_zoomed(cx))
+                .unwrap_or(false);
 
-    //                 cx.background()
-    //                     .spawn(persistence::DB.save_workspace(serialized_workspace))
-    //                     .detach();
-    //             }
-    //         }
-    //     }
+            let bottom_dock = this.bottom_dock.read(cx);
+            let bottom_visible = bottom_dock.is_open();
+            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
+                Some(
+                    cx.view_ui_name(panel.as_any().window(), panel.id())?
+                        .to_string(),
+                )
+            });
+            let bottom_dock_zoom = bottom_dock
+                .visible_panel()
+                .map(|panel| panel.is_zoomed(cx))
+                .unwrap_or(false);
+
+            DockStructure {
+                left: DockData {
+                    visible: left_visible,
+                    active_panel: left_active_panel,
+                    zoom: left_dock_zoom,
+                },
+                right: DockData {
+                    visible: right_visible,
+                    active_panel: right_active_panel,
+                    zoom: right_dock_zoom,
+                },
+                bottom: DockData {
+                    visible: bottom_visible,
+                    active_panel: bottom_active_panel,
+                    zoom: bottom_dock_zoom,
+                },
+            }
+        }
+
+        if let Some(location) = self.location(cx) {
+            // Load bearing special case:
+            //  - with_local_workspace() relies on this to not have other stuff open
+            //    when you open your log
+            if !location.paths().is_empty() {
+                let center_group = build_serialized_pane_group(&self.center.root, cx);
+                let docks = build_serialized_docks(self, cx);
+
+                let serialized_workspace = SerializedWorkspace {
+                    id: self.database_id,
+                    location,
+                    center_group,
+                    bounds: Default::default(),
+                    display: Default::default(),
+                    docks,
+                };
+
+                cx.background()
+                    .spawn(persistence::DB.save_workspace(serialized_workspace))
+                    .detach();
+            }
+        }
+    }
 
     //     pub(crate) fn load_workspace(
     //         workspace: WeakView<Workspace>,