Add focusable view and restore workspace deserialization. Partially restore split and tab functions

Mikayla created

Change summary

crates/editor2/src/editor.rs               |  10 +
crates/editor2/src/items.rs                |   4 
crates/gpui2/src/app/async_context.rs      |  15 ++
crates/gpui2/src/app/entity_map.rs         |   1 
crates/gpui2/src/app/test_context.rs       |   8 +
crates/gpui2/src/gpui2.rs                  |   4 
crates/gpui2/src/view.rs                   |  11 ++
crates/gpui2/src/window.rs                 |  23 ++++
crates/project_panel2/src/project_panel.rs |  22 +---
crates/workspace2/src/dock.rs              |  49 ++++++----
crates/workspace2/src/item.rs              |  10 +-
crates/workspace2/src/pane.rs              |  10 +
crates/workspace2/src/pane_group.rs        |  11 ++
crates/workspace2/src/workspace2.rs        | 105 ++++++++++-------------
14 files changed, 173 insertions(+), 110 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -41,8 +41,8 @@ use git::diff_hunk_to_display;
 use gpui::{
     action, actions, div, point, prelude::*, px, relative, rems, size, uniform_list, AnyElement,
     AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
-    EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
-    InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,
+    EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
+    Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,
     Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,
     WeakView, WindowContext,
 };
@@ -9367,6 +9367,12 @@ pub struct EditorReleased(pub WeakView<Editor>);
 //
 impl EventEmitter<Event> for Editor {}
 
+impl FocusableView for Editor {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
+    }
+}
+
 impl Render for Editor {
     type Element = EditorElement;
 

crates/editor2/src/items.rs 🔗

@@ -527,10 +527,6 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
 }
 
 impl Item for Editor {
-    fn focus_handle(&self) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
         todo!();
         // if let Ok(data) = data.downcast::<NavigationData>() {

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

@@ -1,7 +1,7 @@
 use crate::{
-    AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, ForegroundExecutor,
-    Model, ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext,
-    WindowHandle,
+    AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, FocusableView,
+    ForegroundExecutor, Model, ModelContext, Render, Result, Task, View, ViewContext,
+    VisualContext, WindowContext, WindowHandle,
 };
 use anyhow::{anyhow, Context as _};
 use derive_more::{Deref, DerefMut};
@@ -307,4 +307,13 @@ impl VisualContext for AsyncWindowContext {
         self.window
             .update(self, |_, cx| cx.replace_root_view(build_view))
     }
+
+    fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
+    where
+        V: FocusableView,
+    {
+        self.window.update(self, |_, cx| {
+            view.read(cx).focus_handle(cx).clone().focus(cx);
+        })
+    }
 }

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

@@ -68,6 +68,7 @@ impl EntityMap {
     }
 
     /// Move an entity to the stack.
+    #[track_caller]
     pub fn lease<'a, T>(&mut self, model: &'a Model<T>) -> Lease<'a, T> {
         self.assert_valid_context(model);
         let entity = Some(

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

@@ -560,6 +560,14 @@ impl<'a> VisualContext for VisualTestContext<'a> {
             .update(self.cx, |_, cx| cx.replace_root_view(build_view))
             .unwrap()
     }
+
+    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
+        self.window
+            .update(self.cx, |_, cx| {
+                view.read(cx).focus_handle(cx).clone().focus(cx)
+            })
+            .unwrap()
+    }
 }
 
 impl AnyWindowHandle {

crates/gpui2/src/gpui2.rs 🔗

@@ -135,6 +135,10 @@ pub trait VisualContext: Context {
     ) -> Self::Result<View<V>>
     where
         V: Render;
+
+    fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
+    where
+        V: FocusableView;
 }
 
 pub trait Entity<T>: Sealed {

crates/gpui2/src/view.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,
-    Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, Model, Pixels,
-    Size, ViewContext, VisualContext, WeakModel, WindowContext,
+    Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView,
+    LayoutId, Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext,
 };
 use anyhow::{Context, Result};
 use std::{
@@ -73,6 +73,13 @@ impl<V: 'static> View<V> {
             component: Some(component),
         }
     }
+
+    pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle
+    where
+        V: FocusableView,
+    {
+        self.read(cx).focus_handle(cx)
+    }
 }
 
 impl<V> Clone for View<V> {

crates/gpui2/src/window.rs 🔗

@@ -185,6 +185,10 @@ impl Drop for FocusHandle {
     }
 }
 
+pub trait FocusableView: Render {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
+}
+
 // Holds the state for a specific window.
 pub struct Window {
     pub(crate) handle: AnyWindowHandle,
@@ -1550,6 +1554,12 @@ impl VisualContext for WindowContext<'_> {
         self.window.root_view = Some(view.clone().into());
         view
     }
+
+    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
+        self.update_view(view, |view, cx| {
+            view.focus_handle(cx).clone().focus(cx);
+        })
+    }
 }
 
 impl<'a> std::ops::Deref for WindowContext<'a> {
@@ -2213,9 +2223,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                 .set_input_handler(Box::new(input_handler));
         }
     }
-}
 
-impl<V> ViewContext<'_, V> {
     pub fn emit<Evt>(&mut self, event: Evt)
     where
         Evt: 'static,
@@ -2228,6 +2236,13 @@ impl<V> ViewContext<'_, V> {
             event: Box::new(event),
         });
     }
+
+    pub fn focus_self(&mut self)
+    where
+        V: FocusableView,
+    {
+        self.defer(|view, cx| view.focus_handle(cx).focus(cx))
+    }
 }
 
 impl<V> Context for ViewContext<'_, V> {
@@ -2303,6 +2318,10 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> {
     {
         self.window_cx.replace_root_view(build_view)
     }
+
+    fn focus_view<W: FocusableView>(&mut self, view: &View<W>) -> Self::Result<()> {
+        self.window_cx.focus_view(view)
+    }
 }
 
 impl<'a, V> std::ops::Deref for ViewContext<'a, V> {

crates/project_panel2/src/project_panel.rs 🔗

@@ -9,10 +9,10 @@ use file_associations::FileAssociations;
 use anyhow::{anyhow, Result};
 use gpui::{
     actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
-    ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, InteractiveComponent,
-    Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render, Stateful,
-    StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View, ViewContext,
-    VisualContext as _, WeakView, WindowContext,
+    ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
+    InteractiveComponent, Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render,
+    Stateful, StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View,
+    ViewContext, VisualContext as _, WeakView, WindowContext,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{
@@ -1524,25 +1524,19 @@ impl workspace::dock::Panel for ProjectPanel {
         ("Project Panel".into(), Some(Box::new(ToggleFocus)))
     }
 
-    // fn should_change_position_on_event(event: &Self::Event) -> bool {
-    //     matches!(event, Event::DockPositionChanged)
-    // }
-
     fn has_focus(&self, _: &WindowContext) -> bool {
         self.has_focus
     }
 
-    fn persistent_name(&self) -> &'static str {
+    fn persistent_name() -> &'static str {
         "Project Panel"
     }
+}
 
-    fn focus_handle(&self, _cx: &WindowContext) -> FocusHandle {
+impl FocusableView for ProjectPanel {
+    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
         self.focus_handle.clone()
     }
-
-    // fn is_focus_event(event: &Self::Event) -> bool {
-    //     matches!(event, Event::Focus)
-    // }
 }
 
 impl ClipboardEntry {

crates/workspace2/src/dock.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{status_bar::StatusItemView, Axis, Workspace};
 use gpui::{
     div, px, Action, AnyView, AppContext, Component, Div, Entity, EntityId, EventEmitter,
-    FocusHandle, ParentComponent, Render, Styled, Subscription, View, ViewContext, WeakView,
-    WindowContext,
+    FocusHandle, FocusableView, ParentComponent, Render, Styled, Subscription, View, ViewContext,
+    WeakView, WindowContext,
 };
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
@@ -17,8 +17,8 @@ pub enum PanelEvent {
     Focus,
 }
 
-pub trait Panel: Render + EventEmitter<PanelEvent> {
-    fn persistent_name(&self) -> &'static str;
+pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
+    fn persistent_name() -> &'static str;
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition) -> bool;
     fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
@@ -35,12 +35,11 @@ pub trait Panel: Render + EventEmitter<PanelEvent> {
     fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
     fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
     fn has_focus(&self, cx: &WindowContext) -> bool;
-    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
 }
 
 pub trait PanelHandle: Send + Sync {
     fn id(&self) -> EntityId;
-    fn persistent_name(&self, cx: &WindowContext) -> &'static str;
+    fn persistent_name(&self) -> &'static str;
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
     fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
@@ -53,7 +52,7 @@ pub trait PanelHandle: Send + Sync {
     fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option<Box<dyn Action>>);
     fn icon_label(&self, cx: &WindowContext) -> Option<String>;
     fn has_focus(&self, cx: &WindowContext) -> bool;
-    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
     fn to_any(&self) -> AnyView;
 }
 
@@ -65,8 +64,8 @@ where
         self.entity_id()
     }
 
-    fn persistent_name(&self, cx: &WindowContext) -> &'static str {
-        self.read(cx).persistent_name()
+    fn persistent_name(&self) -> &'static str {
+        T::persistent_name()
     }
 
     fn position(&self, cx: &WindowContext) -> DockPosition {
@@ -121,7 +120,7 @@ where
         self.clone().into()
     }
 
-    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
         self.read(cx).focus_handle(cx).clone()
     }
 }
@@ -139,6 +138,14 @@ pub struct Dock {
     active_panel_index: usize,
 }
 
+impl FocusableView for Dock {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.panel_entries[self.active_panel_index]
+            .panel
+            .focus_handle(cx)
+    }
+}
+
 #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
 #[serde(rename_all = "lowercase")]
 pub enum DockPosition {
@@ -220,12 +227,14 @@ impl Dock {
     //             .position(|entry| entry.panel.as_any().is::<T>())
     //     }
 
-    pub fn panel_index_for_ui_name(&self, _ui_name: &str, _cx: &AppContext) -> Option<usize> {
-        todo!()
-        // self.panel_entries.iter().position(|entry| {
-        //     let panel = entry.panel.as_any();
-        //     cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name)
-        // })
+    pub fn panel_index_for_persistent_name(
+        &self,
+        ui_name: &str,
+        _cx: &AppContext,
+    ) -> Option<usize> {
+        self.panel_entries
+            .iter()
+            .position(|entry| entry.panel.persistent_name() == ui_name)
     }
 
     pub fn active_panel_index(&self) -> usize {
@@ -647,7 +656,7 @@ impl Render for PanelButtons {
         div().children(
             dock.panel_entries
                 .iter()
-                .map(|panel| panel.panel.persistent_name(cx)),
+                .map(|panel| panel.panel.persistent_name()),
         )
     }
 }
@@ -698,7 +707,7 @@ pub mod test {
     }
 
     impl Panel for TestPanel {
-        fn persistent_name(&self) -> &'static str {
+        fn persistent_name() -> &'static str {
             "TestPanel"
         }
 
@@ -746,8 +755,10 @@ pub mod test {
         fn has_focus(&self, _cx: &WindowContext) -> bool {
             self.has_focus
         }
+    }
 
-        fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
+    impl FocusableView for TestPanel {
+        fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
             unimplemented!()
         }
     }

crates/workspace2/src/item.rs 🔗

@@ -12,8 +12,9 @@ use client2::{
     Client,
 };
 use gpui::{
-    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, HighlightStyle,
-    Model, Pixels, Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext,
+    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
+    HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
+    WindowContext,
 };
 use project2::{Project, ProjectEntryId, ProjectPath};
 use schemars::JsonSchema;
@@ -91,8 +92,7 @@ pub struct BreadcrumbText {
     pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
 }
 
-pub trait Item: Render + EventEmitter<ItemEvent> {
-    fn focus_handle(&self) -> FocusHandle;
+pub trait Item: FocusableView + EventEmitter<ItemEvent> {
     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 {
@@ -286,7 +286,7 @@ impl dyn ItemHandle {
 
 impl<T: Item> ItemHandle for View<T> {
     fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
-        self.read(cx).focus_handle()
+        self.focus_handle(cx)
     }
 
     fn subscribe_to_item_events(

crates/workspace2/src/pane.rs 🔗

@@ -8,8 +8,8 @@ use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
     actions, prelude::*, register_action, AppContext, AsyncWindowContext, Component, Div, EntityId,
-    EventEmitter, FocusHandle, Focusable, Model, PromptLevel, Render, Task, View, ViewContext,
-    VisualContext, WeakView, WindowContext,
+    EventEmitter, FocusHandle, Focusable, FocusableView, Model, PromptLevel, Render, Task, View,
+    ViewContext, VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
@@ -1899,6 +1899,12 @@ impl Pane {
     }
 }
 
+impl FocusableView for Pane {
+    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
+    }
+}
+
 impl Render for Pane {
     type Element = Focusable<Self, Div<Self>>;
 

crates/workspace2/src/pane_group.rs 🔗

@@ -148,6 +148,10 @@ impl PaneGroup {
         self.root.collect_panes(&mut panes);
         panes
     }
+
+    pub(crate) fn first_pane(&self) -> View<Pane> {
+        self.root.first_pane()
+    }
 }
 
 #[derive(Clone, PartialEq)]
@@ -181,6 +185,13 @@ impl Member {
         }
     }
 
+    fn first_pane(&self) -> View<Pane> {
+        match self {
+            Member::Axis(axis) => axis.members[0].first_pane(),
+            Member::Pane(pane) => pane.clone(),
+        }
+    }
+
     pub fn render(
         &self,
         project: &Model<Project>,

crates/workspace2/src/workspace2.rs 🔗

@@ -38,9 +38,9 @@ use futures::{
 use gpui::{
     actions, div, point, prelude::*, rems, size, Action, AnyModel, AnyView, AnyWeakView,
     AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId,
-    EventEmitter, GlobalPixels, KeyContext, Model, ModelContext, ParentComponent, Point, Render,
-    Size, Styled, Subscription, Task, View, ViewContext, WeakView, WindowBounds, WindowContext,
-    WindowHandle, WindowOptions,
+    EventEmitter, FocusHandle, FocusableView, GlobalPixels, KeyContext, Model, ModelContext,
+    ParentComponent, Point, Render, Size, Styled, Subscription, Task, View, ViewContext, WeakView,
+    WindowBounds, WindowContext, WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -685,7 +685,7 @@ impl Workspace {
     fn new_local(
         abs_paths: Vec<PathBuf>,
         app_state: Arc<AppState>,
-        _requesting_window: Option<WindowHandle<Workspace>>,
+        requesting_window: Option<WindowHandle<Workspace>>,
         cx: &mut AppContext,
     ) -> Task<
         anyhow::Result<(
@@ -703,7 +703,8 @@ impl Workspace {
         );
 
         cx.spawn(|mut cx| async move {
-            let serialized_workspace: Option<SerializedWorkspace> = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+            let serialized_workspace: Option<SerializedWorkspace> =
+                persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 
             let paths_to_open = Arc::new(abs_paths);
 
@@ -732,15 +733,14 @@ impl Workspace {
                 DB.next_id().await.unwrap_or(0)
             };
 
-            // todo!()
-            let window = /*if let Some(window) = requesting_window {
+            let window = if let Some(window) = requesting_window {
                 cx.update_window(window.into(), |old_workspace, cx| {
                     cx.replace_root_view(|cx| {
                         Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
                     });
-                });
+                })?;
                 window
-                } else */ {
+            } else {
                 let window_bounds_override = window_bounds_env_override(&cx);
                 let (bounds, display) = if let Some(bounds) = window_bounds_override {
                     (Some(bounds), None)
@@ -754,12 +754,13 @@ impl Workspace {
                             // Stored bounds are relative to the containing display.
                             // So convert back to global coordinates if that screen still exists
                             if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                                let screen =
-                                    cx.update(|cx|
-                                        cx.displays()
-                                            .into_iter()
-                                            .find(|display| display.uuid().ok() == Some(serialized_display))
-                                    ).ok()??;
+                                let screen = cx
+                                    .update(|cx| {
+                                        cx.displays().into_iter().find(|display| {
+                                            display.uuid().ok() == Some(serialized_display)
+                                        })
+                                    })
+                                    .ok()??;
                                 let screen_bounds = screen.bounds();
                                 window_bounds.origin.x += screen_bounds.origin.x;
                                 window_bounds.origin.y += screen_bounds.origin.y;
@@ -807,12 +808,7 @@ impl Workspace {
             notify_if_database_failed(window, &mut cx);
             let opened_items = window
                 .update(&mut cx, |_workspace, cx| {
-                    open_items(
-                        serialized_workspace,
-                        project_paths,
-                        app_state,
-                        cx,
-                    )
+                    open_items(serialized_workspace, project_paths, app_state, cx)
                 })?
                 .await
                 .unwrap_or_default();
@@ -3043,13 +3039,15 @@ impl Workspace {
         cx.notify();
     }
 
-    //     fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
-    //         self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
-    //             cx.background().timer(Duration::from_millis(100)).await;
-    //             this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
-    //                 .ok();
-    //         }));
-    //     }
+    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
+        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
+            cx.background_executor()
+                .timer(Duration::from_millis(100))
+                .await;
+            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
+                .log_err();
+        }));
+    }
 
     fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
         fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
@@ -3105,7 +3103,7 @@ impl Workspace {
             let left_visible = left_dock.is_open();
             let left_active_panel = left_dock
                 .visible_panel()
-                .and_then(|panel| Some(panel.persistent_name(cx).to_string()));
+                .and_then(|panel| Some(panel.persistent_name().to_string()));
             let left_dock_zoom = left_dock
                 .visible_panel()
                 .map(|panel| panel.is_zoomed(cx))
@@ -3115,7 +3113,7 @@ impl Workspace {
             let right_visible = right_dock.is_open();
             let right_active_panel = right_dock
                 .visible_panel()
-                .and_then(|panel| Some(panel.persistent_name(cx).to_string()));
+                .and_then(|panel| Some(panel.persistent_name().to_string()));
             let right_dock_zoom = right_dock
                 .visible_panel()
                 .map(|panel| panel.is_zoomed(cx))
@@ -3125,7 +3123,7 @@ impl Workspace {
             let bottom_visible = bottom_dock.is_open();
             let bottom_active_panel = bottom_dock
                 .visible_panel()
-                .and_then(|panel| Some(panel.persistent_name(cx).to_string()));
+                .and_then(|panel| Some(panel.persistent_name().to_string()));
             let bottom_dock_zoom = bottom_dock
                 .visible_panel()
                 .map(|panel| panel.is_zoomed(cx))
@@ -3231,45 +3229,34 @@ impl Workspace {
 
                     // Swap workspace center group
                     workspace.center = PaneGroup::with_root(center_group);
-
-                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
-                    // todo!()
-                    // cx.focus_self();
-                    // if let Some(active_pane) = active_pane {
-                    //     cx.focus(&active_pane);
-                    // } else {
-                    //     cx.focus(workspace.panes.last().unwrap());
-                    // }
-                } else {
-                    // todo!()
-                    // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade());
-                    // if let Some(old_center_handle) = old_center_handle {
-                    //     cx.focus(&old_center_handle)
-                    // } else {
-                    //     cx.focus_self()
-                    // }
+                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
+                    if let Some(active_pane) = active_pane {
+                        workspace.active_pane = active_pane;
+                        cx.focus_self();
+                    } else {
+                        workspace.active_pane = workspace.center.first_pane().clone();
+                    }
                 }
 
                 let docks = serialized_workspace.docks;
                 workspace.left_dock.update(cx, |dock, cx| {
                     dock.set_open(docks.left.visible, cx);
                     if let Some(active_panel) = docks.left.active_panel {
-                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
                             dock.activate_panel(ix, cx);
                         }
                     }
                     dock.active_panel()
                         .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
                     if docks.left.visible && docks.left.zoom {
-                        // todo!()
-                        // cx.focus_self()
+                        cx.focus_self()
                     }
                 });
                 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
                 workspace.right_dock.update(cx, |dock, cx| {
                     dock.set_open(docks.right.visible, cx);
                     if let Some(active_panel) = docks.right.active_panel {
-                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
                             dock.activate_panel(ix, cx);
                         }
                     }
@@ -3277,14 +3264,13 @@ impl Workspace {
                         .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
 
                     if docks.right.visible && docks.right.zoom {
-                        // todo!()
-                        // cx.focus_self()
+                        cx.focus_self()
                     }
                 });
                 workspace.bottom_dock.update(cx, |dock, cx| {
                     dock.set_open(docks.bottom.visible, cx);
                     if let Some(active_panel) = docks.bottom.active_panel {
-                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
                             dock.activate_panel(ix, cx);
                         }
                     }
@@ -3293,8 +3279,7 @@ impl Workspace {
                         .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
 
                     if docks.bottom.visible && docks.bottom.zoom {
-                        // todo!()
-                        // cx.focus_self()
+                        cx.focus_self()
                     }
                 });
 
@@ -3699,6 +3684,12 @@ fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncA
 
 impl EventEmitter<Event> for Workspace {}
 
+impl FocusableView for Workspace {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.active_pane.focus_handle(cx)
+    }
+}
+
 impl Render for Workspace {
     type Element = Div<Self>;