Tabs n splits (#3340)

Mikayla Maki created

Adds back:

- [x] Tab clicking
- [x] Splits ~
- [x] Workspace deserialization

Also adds a new `FocusableView` API to GPUI

Release Notes:

- N/A

Change summary

Cargo.toml                                 |   3 
crates/collab2/src/tests/test_server.rs    |   1 
crates/collab_ui2/src/collab_panel.rs      |  92 +++---
crates/editor2/src/editor.rs               |  10 
crates/editor2/src/items.rs                |   4 
crates/gpui2/src/action.rs                 |   2 
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                   |  12 
crates/gpui2/src/window.rs                 |  23 +
crates/project_panel2/src/project_panel.rs |  73 ++---
crates/settings2/src/keymap_file.rs        |   6 
crates/workspace2/src/dock.rs              |  51 ++-
crates/workspace2/src/item.rs              |  10 
crates/workspace2/src/pane.rs              |  67 ++---
crates/workspace2/src/pane_group.rs        |  38 +++
crates/workspace2/src/status_bar.rs        | 112 ---------
crates/workspace2/src/workspace2.rs        | 137 ++++------
crates/zed2/src/main.rs                    |   7 
crates/zed2/src/zed2.rs                    | 293 +++++++++++------------
22 files changed, 457 insertions(+), 512 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -204,6 +204,9 @@ core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "07
 [profile.dev]
 split-debuginfo = "unpacked"
 
+[profile.dev.package.taffy]
+opt-level = 3
+
 [profile.release]
 debug = true
 lto = "thin"

crates/collab2/src/tests/test_server.rs 🔗

@@ -220,7 +220,6 @@ impl TestServer {
             languages: Arc::new(language_registry),
             fs: fs.clone(),
             build_window_options: |_, _, _| Default::default(),
-            initialize_workspace: |_, _, _, _| gpui::Task::ready(Ok(())),
             node_runtime: FakeNodeRuntime::new(),
         });
 

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -153,17 +153,20 @@ actions!(
 //     channel_id: ChannelId,
 // }
 
-// const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
+const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
 
 use std::sync::Arc;
 
+use db::kvp::KEY_VALUE_STORE;
 use gpui::{
-    actions, div, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, Focusable,
-    InteractiveComponent, ParentComponent, Render, Task, View, ViewContext, VisualContext,
-    WeakView,
+    actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
+    Focusable, FocusableView, InteractiveComponent, ParentComponent, Render, View, ViewContext,
+    VisualContext, WeakView,
 };
 use project::Fs;
+use serde_derive::{Deserialize, Serialize};
 use settings::Settings;
+use util::ResultExt;
 use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
     Workspace,
@@ -317,11 +320,11 @@ pub struct CollabPanel {
 //     Channel(ChannelId),
 // }
 
-// #[derive(Serialize, Deserialize)]
-// struct SerializedCollabPanel {
-//     width: Option<f32>,
-//     collapsed_channels: Option<Vec<ChannelId>>,
-// }
+#[derive(Serialize, Deserialize)]
+struct SerializedCollabPanel {
+    width: Option<f32>,
+    collapsed_channels: Option<Vec<u64>>,
+}
 
 // #[derive(Debug)]
 // pub enum Event {
@@ -660,43 +663,34 @@ impl CollabPanel {
         })
     }
 
-    pub fn load(
+    pub async fn load(
         workspace: WeakView<Workspace>,
-        cx: AsyncWindowContext,
-    ) -> Task<anyhow::Result<View<Self>>> {
-        cx.spawn(|mut cx| async move {
-            // todo!()
-            // let serialized_panel = if let Some(panel) = cx
-            //     .background()
-            //     .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
-            //     .await
-            //     .log_err()
-            //     .flatten()
-            // {
-            //     match serde_json::from_str::<SerializedCollabPanel>(&panel) {
-            //         Ok(panel) => Some(panel),
-            //         Err(err) => {
-            //             log::error!("Failed to deserialize collaboration panel: {}", err);
-            //             None
-            //         }
-            //     }
-            // } else {
-            //     None
-            // };
-
-            workspace.update(&mut cx, |workspace, cx| {
-                let panel = CollabPanel::new(workspace, cx);
-                // if let Some(serialized_panel) = serialized_panel {
-                //     panel.update(cx, |panel, cx| {
-                //         panel.width = serialized_panel.width;
-                //         panel.collapsed_channels = serialized_panel
-                //             .collapsed_channels
-                //             .unwrap_or_else(|| Vec::new());
-                //         cx.notify();
-                //     });
-                // }
-                panel
-            })
+        mut cx: AsyncWindowContext,
+    ) -> anyhow::Result<View<Self>> {
+        let serialized_panel = cx
+            .background_executor()
+            .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
+            .await
+            .map_err(|_| anyhow::anyhow!("Failed to read collaboration panel from key value store"))
+            .log_err()
+            .flatten()
+            .map(|panel| serde_json::from_str::<SerializedCollabPanel>(&panel))
+            .transpose()
+            .log_err()
+            .flatten();
+
+        workspace.update(&mut cx, |workspace, cx| {
+            let panel = CollabPanel::new(workspace, cx);
+            if let Some(serialized_panel) = serialized_panel {
+                panel.update(cx, |panel, cx| {
+                    panel.width = serialized_panel.width;
+                    // panel.collapsed_channels = serialized_panel
+                    //     .collapsed_channels
+                    //     .unwrap_or_else(|| Vec::new());
+                    cx.notify();
+                });
+            }
+            panel
         })
     }
 
@@ -3454,11 +3448,13 @@ impl Panel for CollabPanel {
         self.focus_handle.contains_focused(cx)
     }
 
-    fn persistent_name(&self) -> &'static str {
-        "Collaboration Panel"
+    fn persistent_name() -> &'static str {
+        "CollabPanel"
     }
+}
 
-    fn focus_handle(&self, _cx: &ui::prelude::WindowContext) -> gpui::FocusHandle {
+impl FocusableView for CollabPanel {
+    fn focus_handle(&self, _cx: &AppContext) -> gpui::FocusHandle {
         self.focus_handle.clone()
     }
 }

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/action.rs 🔗

@@ -68,7 +68,7 @@ pub trait Action: std::fmt::Debug + 'static {
 // Types become actions by satisfying a list of trait bounds.
 impl<A> Action for A
 where
-    A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
+    A: for<'a> Deserialize<'a> + PartialEq + Default + Clone + std::fmt::Debug + 'static,
 {
     fn qualified_name() -> SharedString {
         let name = type_name::<A>();

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 🔗

@@ -562,6 +562,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,8 @@
 use crate::{
     private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,
-    BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId,
-    Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel, WindowContext,
+    BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle,
+    FocusableView, LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel,
+    WindowContext,
 };
 use anyhow::{Context, Result};
 use std::{
@@ -73,6 +74,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,
@@ -1546,6 +1550,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> {
@@ -2219,9 +2229,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,
@@ -2234,6 +2242,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> {
@@ -2309,6 +2324,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::{
@@ -32,7 +32,7 @@ use std::{
 use theme::ActiveTheme as _;
 use ui::{h_stack, v_stack, IconElement, Label};
 use unicase::UniCase;
-use util::{maybe, TryFutureExt};
+use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
     dock::{DockPosition, PanelEvent},
     Workspace,
@@ -310,32 +310,31 @@ impl ProjectPanel {
         project_panel
     }
 
-    pub fn load(
+    pub async fn load(
         workspace: WeakView<Workspace>,
-        cx: AsyncWindowContext,
-    ) -> Task<Result<View<Self>>> {
-        cx.spawn(|mut cx| async move {
-            // let serialized_panel = if let Some(panel) = cx
-            //     .background_executor()
-            //     .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) })
-            //     .await
-            //     .log_err()
-            //     .flatten()
-            // {
-            //     Some(serde_json::from_str::<SerializedProjectPanel>(&panel)?)
-            // } else {
-            //     None
-            // };
-            workspace.update(&mut cx, |workspace, cx| {
-                let panel = ProjectPanel::new(workspace, cx);
-                // if let Some(serialized_panel) = serialized_panel {
-                //     panel.update(cx, |panel, cx| {
-                //         panel.width = serialized_panel.width;
-                //         cx.notify();
-                //     });
-                // }
-                panel
-            })
+        mut cx: AsyncWindowContext,
+    ) -> Result<View<Self>> {
+        let serialized_panel = cx
+            .background_executor()
+            .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) })
+            .await
+            .map_err(|e| anyhow!("Failed to load project panel: {}", e))
+            .log_err()
+            .flatten()
+            .map(|panel| serde_json::from_str::<SerializedProjectPanel>(&panel))
+            .transpose()
+            .log_err()
+            .flatten();
+
+        workspace.update(&mut cx, |workspace, cx| {
+            let panel = ProjectPanel::new(workspace, cx);
+            if let Some(serialized_panel) = serialized_panel {
+                panel.update(cx, |panel, cx| {
+                    panel.width = serialized_panel.width;
+                    cx.notify();
+                });
+            }
+            panel
         })
     }
 
@@ -1531,25 +1530,19 @@ impl workspace::dock::Panel for ProjectPanel {
         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/settings2/src/keymap_file.rs 🔗

@@ -9,7 +9,7 @@ use schemars::{
 };
 use serde::Deserialize;
 use serde_json::Value;
-use util::asset_str;
+use util::{asset_str, ResultExt};
 
 #[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
 #[serde(transparent)]
@@ -86,9 +86,7 @@ impl KeymapFile {
                             "invalid binding value for keystroke {keystroke}, context {context:?}"
                         )
                     })
-                    // todo!()
-                    .ok()
-                    // .log_err()
+                    .log_err()
                     .map(|action| KeyBinding::load(&keystroke, action, context.as_deref()))
                 })
                 .collect::<Result<Vec<_>>>()?;

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};
@@ -18,8 +18,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>);
@@ -36,12 +36,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);
@@ -54,7 +53,7 @@ pub trait PanelHandle: Send + Sync {
     fn toggle_action(&self, cx: &WindowContext) -> 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;
 }
 
@@ -66,8 +65,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 {
@@ -122,7 +121,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()
     }
 }
@@ -140,6 +139,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 {
@@ -221,12 +228,14 @@ impl Dock {
             .position(|entry| entry.panel.to_any().downcast::<T>().is_ok())
     }
 
-    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 {
@@ -654,11 +663,11 @@ impl Render for PanelButtons {
             .enumerate()
             .filter_map(|(i, panel)| {
                 let icon = panel.panel.icon(cx)?;
-                let name = panel.panel.persistent_name(cx);
+                let name = panel.panel.persistent_name();
                 let action = panel.panel.toggle_action(cx);
                 let action2 = action.boxed_clone();
 
-                let mut button = IconButton::new(panel.panel.persistent_name(cx), icon)
+                let mut button = IconButton::new(panel.panel.persistent_name(), icon)
                     .when(i == active_index, |el| el.state(InteractionState::Active))
                     .on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
                     .tooltip(move |_, cx| Tooltip::for_action(name, &*action2, cx));
@@ -717,7 +726,7 @@ pub mod test {
     }
 
     impl Panel for TestPanel {
-        fn persistent_name(&self) -> &'static str {
+        fn persistent_name() -> &'static str {
             "TestPanel"
         }
 
@@ -765,8 +774,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};
@@ -125,10 +125,6 @@ pub fn init(cx: &mut AppContext) {
     //     cx.add_async_action(Pane::close_items_to_the_left);
     //     cx.add_async_action(Pane::close_items_to_the_right);
     //     cx.add_async_action(Pane::close_all_items);
-    //     cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
-    //     cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
-    //     cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
-    //     cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
 }
 
 pub enum Event {
@@ -1195,9 +1191,9 @@ impl Pane {
         }
     }
 
-    //     pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
-    //         cx.emit(Event::Split(direction));
-    //     }
+    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
+        cx.emit(Event::Split(direction));
+    }
 
     //     fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
     //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
@@ -1398,6 +1394,7 @@ impl Pane {
             .when_some(item.tab_tooltip_text(cx), |div, text| {
                 div.tooltip(move |_, cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
             })
+            .on_click(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx))
             // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
             // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
             // .on_drop(|_view, state: View<DraggedTab>, cx| {
@@ -1430,32 +1427,22 @@ impl Pane {
                     .items_center()
                     .gap_1()
                     .text_color(text_color)
-                    .children(if item.has_conflict(cx) {
-                        Some(
-                            IconElement::new(Icon::ExclamationTriangle)
-                                .size(ui::IconSize::Small)
-                                .color(TextColor::Warning),
-                        )
-                    } else if item.is_dirty(cx) {
-                        Some(
-                            IconElement::new(Icon::ExclamationTriangle)
-                                .size(ui::IconSize::Small)
-                                .color(TextColor::Info),
-                        )
-                    } else {
-                        None
-                    })
-                    .children(if !close_right {
-                        Some(close_icon())
-                    } else {
-                        None
-                    })
+                    .children(
+                        item.has_conflict(cx)
+                            .then(|| {
+                                IconElement::new(Icon::ExclamationTriangle)
+                                    .size(ui::IconSize::Small)
+                                    .color(TextColor::Warning)
+                            })
+                            .or(item.is_dirty(cx).then(|| {
+                                IconElement::new(Icon::ExclamationTriangle)
+                                    .size(ui::IconSize::Small)
+                                    .color(TextColor::Info)
+                            })),
+                    )
+                    .children((!close_right).then(|| close_icon()))
                     .child(label)
-                    .children(if close_right {
-                        Some(close_icon())
-                    } else {
-                        None
-                    }),
+                    .children(close_right.then(|| close_icon())),
             )
     }
 
@@ -1912,9 +1899,11 @@ impl Pane {
     }
 }
 
-// impl Entity for Pane {
-//     type Event = Event;
-// }
+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>>;
@@ -1923,6 +1912,10 @@ impl Render for Pane {
         v_stack()
             .key_context("Pane")
             .track_focus(&self.focus_handle)
+            .on_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx))
+            .on_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx))
+            .on_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx))
+            .on_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx))
             .size_full()
             .on_action(|pane: &mut Self, action, cx| {
                 pane.close_active_item(action, cx)

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>,
@@ -551,7 +562,32 @@ impl PaneAxis {
     ) -> AnyElement<Workspace> {
         debug_assert!(self.members.len() == self.flexes.lock().len());
 
-        todo!()
+        div()
+            .flex()
+            .flex_auto()
+            .map(|s| match self.axis {
+                Axis::Vertical => s.flex_col(),
+                Axis::Horizontal => s.flex_row(),
+            })
+            .children(self.members.iter().enumerate().map(|(ix, member)| {
+                match member {
+                    Member::Axis(axis) => axis
+                        .render(
+                            project,
+                            basis,
+                            follower_states,
+                            active_call,
+                            active_pane,
+                            zoomed,
+                            app_state,
+                            cx,
+                        )
+                        .render(),
+                    Member::Pane(pane) => pane.clone().render(),
+                }
+            }))
+            .render()
+
         // let mut pane_axis = PaneAxisElement::new(
         //     self.axis,
         //     basis,

crates/workspace2/src/status_bar.rs 🔗

@@ -68,41 +68,6 @@ impl StatusBar {
     }
 }
 
-// todo!()
-// impl View for StatusBar {
-//     fn ui_name() -> &'static str {
-//         "StatusBar"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let theme = &theme::current(cx).workspace.status_bar;
-
-//         StatusBarElement {
-//             left: Flex::row()
-//                 .with_children(self.left_items.iter().map(|i| {
-//                     ChildView::new(i.as_any(), cx)
-//                         .aligned()
-//                         .contained()
-//                         .with_margin_right(theme.item_spacing)
-//                 }))
-//                 .into_any(),
-//             right: Flex::row()
-//                 .with_children(self.right_items.iter().rev().map(|i| {
-//                     ChildView::new(i.as_any(), cx)
-//                         .aligned()
-//                         .contained()
-//                         .with_margin_left(theme.item_spacing)
-//                 }))
-//                 .into_any(),
-//         }
-//         .contained()
-//         .with_style(theme.container)
-//         .constrained()
-//         .with_height(theme.height)
-//         .into_any()
-//     }
-// }
-
 impl StatusBar {
     pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
         let mut this = Self {
@@ -222,80 +187,3 @@ impl From<&dyn StatusItemViewHandle> for AnyView {
         val.to_any().clone()
     }
 }
-
-// todo!()
-// struct StatusBarElement {
-//     left: AnyElement<StatusBar>,
-//     right: AnyElement<StatusBar>,
-// }
-
-// todo!()
-// impl Element<StatusBar> for StatusBarElement {
-//     type LayoutState = ();
-//     type PaintState = ();
-
-//     fn layout(
-//         &mut self,
-//         mut constraint: SizeConstraint,
-//         view: &mut StatusBar,
-//         cx: &mut ViewContext<StatusBar>,
-//     ) -> (Vector2F, Self::LayoutState) {
-//         let max_width = constraint.max.x();
-//         constraint.min = vec2f(0., constraint.min.y());
-
-//         let right_size = self.right.layout(constraint, view, cx);
-//         let constraint = SizeConstraint::new(
-//             vec2f(0., constraint.min.y()),
-//             vec2f(max_width - right_size.x(), constraint.max.y()),
-//         );
-
-//         self.left.layout(constraint, view, cx);
-
-//         (vec2f(max_width, right_size.y()), ())
-//     }
-
-//     fn paint(
-//         &mut self,
-//         bounds: RectF,
-//         visible_bounds: RectF,
-//         _: &mut Self::LayoutState,
-//         view: &mut StatusBar,
-//         cx: &mut ViewContext<StatusBar>,
-//     ) -> Self::PaintState {
-//         let origin_y = bounds.upper_right().y();
-//         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-//         let left_origin = vec2f(bounds.lower_left().x(), origin_y);
-//         self.left.paint(left_origin, visible_bounds, view, cx);
-
-//         let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
-//         self.right.paint(right_origin, visible_bounds, view, cx);
-//     }
-
-//     fn rect_for_text_range(
-//         &self,
-//         _: Range<usize>,
-//         _: RectF,
-//         _: RectF,
-//         _: &Self::LayoutState,
-//         _: &Self::PaintState,
-//         _: &StatusBar,
-//         _: &ViewContext<StatusBar>,
-//     ) -> Option<RectF> {
-//         None
-//     }
-
-//     fn debug(
-//         &self,
-//         bounds: RectF,
-//         _: &Self::LayoutState,
-//         _: &Self::PaintState,
-//         _: &StatusBar,
-//         _: &ViewContext<StatusBar>,
-//     ) -> serde_json::Value {
-//         json!({
-//             "type": "StatusBarElement",
-//             "bounds": bounds.to_json()
-//         })
-//     }
-// }

crates/workspace2/src/workspace2.rs 🔗

@@ -37,9 +37,10 @@ use futures::{
 };
 use gpui::{
     actions, div, point, prelude::*, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
-    AsyncAppContext, AsyncWindowContext, Bounds, Div, Entity, EntityId, EventEmitter, GlobalPixels,
-    KeyContext, Model, ModelContext, ParentComponent, Point, Render, Size, Styled, Subscription,
-    Task, View, ViewContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
+    AsyncAppContext, AsyncWindowContext, Bounds, Div, Entity, EntityId, 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;
@@ -319,12 +320,6 @@ pub struct AppState {
     pub fs: Arc<dyn fs2::Fs>,
     pub build_window_options:
         fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
-    pub initialize_workspace: fn(
-        WeakView<Workspace>,
-        bool,
-        Arc<AppState>,
-        AsyncWindowContext,
-    ) -> Task<anyhow::Result<()>>,
     pub node_runtime: Arc<dyn NodeRuntime>,
 }
 
@@ -370,7 +365,6 @@ impl AppState {
             user_store,
             workspace_store,
             node_runtime: FakeNodeRuntime::new(),
-            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
             build_window_options: |_, _, _| Default::default(),
         })
     }
@@ -682,7 +676,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<(
@@ -700,7 +694,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);
 
@@ -729,15 +724,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)
@@ -751,12 +745,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;
@@ -785,17 +780,17 @@ impl Workspace {
             };
 
             // todo!() Ask how to do this
-            let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?;
-            let async_cx = window.update(&mut cx, |_, cx| cx.to_async())?;
-
-            (app_state.initialize_workspace)(
-                weak_view,
-                serialized_workspace.is_some(),
-                app_state.clone(),
-                async_cx,
-            )
-            .await
-            .log_err();
+            // let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?;
+            // let async_cx = window.update(&mut cx, |_, cx| cx.to_async())?;
+
+            // (app_state.initialize_workspace)(
+            //     weak_view,
+            //     serialized_workspace.is_some(),
+            //     app_state.clone(),
+            //     async_cx,
+            // )
+            // .await
+            // .log_err();
 
             window
                 .update(&mut cx, |_, cx| cx.activate_window())
@@ -804,12 +799,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();
@@ -2970,13 +2960,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 {
@@ -3032,7 +3024,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))
@@ -3042,7 +3034,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))
@@ -3052,7 +3044,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))
@@ -3158,45 +3150,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);
                         }
                     }
@@ -3204,14 +3185,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);
                         }
                     }
@@ -3220,8 +3200,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()
                     }
                 });
 
@@ -3283,7 +3262,6 @@ impl Workspace {
             //         },
             //     );
             .on_action(|this, e: &ToggleLeftDock, cx| {
-                println!("TOGGLING DOCK");
                 this.toggle_dock(DockPosition::Left, cx);
             })
         //     cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
@@ -3347,7 +3325,6 @@ impl Workspace {
             user_store,
             fs: project.read(cx).fs().clone(),
             build_window_options: |_, _, _| Default::default(),
-            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
             node_runtime: FakeNodeRuntime::new(),
         });
         let workspace = Self::new(0, project, app_state, cx);
@@ -3626,6 +3603,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>;
 

crates/zed2/src/main.rs 🔗

@@ -50,8 +50,8 @@ use util::{
 use uuid::Uuid;
 use workspace::{AppState, WorkspaceStore};
 use zed2::{
-    build_window_options, ensure_only_instance, handle_cli_connection, init_zed_actions,
-    initialize_workspace, languages, Assets, IsOnlyInstance, OpenListener, OpenRequest,
+    build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace,
+    languages, Assets, IsOnlyInstance, OpenListener, OpenRequest,
 };
 
 mod open_listener;
@@ -176,7 +176,6 @@ fn main() {
             user_store,
             fs,
             build_window_options,
-            initialize_workspace,
             // background_actions: todo!("ask Mikayla"),
             workspace_store,
             node_runtime,
@@ -213,7 +212,7 @@ fn main() {
         // zed::init(&app_state, cx);
 
         // cx.set_menus(menus::menus());
-        init_zed_actions(app_state.clone(), cx);
+        initialize_workspace(app_state.clone(), cx);
 
         if stdout_is_a_pty() {
             cx.activate(true);

crates/zed2/src/zed2.rs 🔗

@@ -10,13 +10,13 @@ pub use assets::*;
 use collections::VecDeque;
 use editor::{Editor, MultiBuffer};
 use gpui::{
-    actions, point, px, AppContext, AsyncWindowContext, Context, PromptLevel, Task,
-    TitlebarOptions, ViewContext, VisualContext, WeakView, WindowBounds, WindowKind, WindowOptions,
+    actions, point, px, AppContext, Context, PromptLevel, TitlebarOptions, ViewContext,
+    VisualContext, WindowBounds, WindowKind, WindowOptions,
 };
 pub use only_instance::*;
 pub use open_listener::*;
 
-use anyhow::{anyhow, Context as _, Result};
+use anyhow::{anyhow, Context as _};
 use project_panel::ProjectPanel;
 use settings::{initial_local_settings_content, Settings};
 use std::{borrow::Cow, ops::Deref, sync::Arc};
@@ -86,8 +86,147 @@ pub fn build_window_options(
     }
 }
 
-pub fn init_zed_actions(app_state: Arc<AppState>, cx: &mut AppContext) {
-    cx.observe_new_views(move |workspace: &mut Workspace, _cx| {
+pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
+    cx.observe_new_views(move |workspace: &mut Workspace, cx| {
+        let workspace_handle = cx.view().clone();
+        cx.subscribe(&workspace_handle, {
+            move |workspace, _, event, cx| {
+                if let workspace::Event::PaneAdded(pane) = event {
+                    pane.update(cx, |pane, cx| {
+                        pane.toolbar().update(cx, |toolbar, cx| {
+                            // todo!()
+                            //     let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
+                            //     toolbar.add_item(breadcrumbs, cx);
+                            //     let buffer_search_bar = cx.add_view(BufferSearchBar::new);
+                            //     toolbar.add_item(buffer_search_bar.clone(), cx);
+                            //     let quick_action_bar = cx.add_view(|_| {
+                            //         QuickActionBar::new(buffer_search_bar, workspace)
+                            //     });
+                            //     toolbar.add_item(quick_action_bar, cx);
+                            //     let diagnostic_editor_controls =
+                            //         cx.add_view(|_| diagnostics2::ToolbarControls::new());
+                            //     toolbar.add_item(diagnostic_editor_controls, cx);
+                            //     let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
+                            //     toolbar.add_item(project_search_bar, cx);
+                            //     let submit_feedback_button =
+                            //         cx.add_view(|_| SubmitFeedbackButton::new());
+                            //     toolbar.add_item(submit_feedback_button, cx);
+                            //     let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
+                            //     toolbar.add_item(feedback_info_text, cx);
+                            //     let lsp_log_item =
+                            //         cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
+                            //     toolbar.add_item(lsp_log_item, cx);
+                            //     let syntax_tree_item = cx
+                            //         .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
+                            //     toolbar.add_item(syntax_tree_item, cx);
+                        })
+                    });
+                }
+            }
+        })
+        .detach();
+
+        //     cx.emit(workspace2::Event::PaneAdded(
+        //         workspace.active_pane().clone(),
+        //     ));
+
+        //     let collab_titlebar_item =
+        //         cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
+        //     workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
+
+        //     let copilot =
+        //         cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
+        //     let diagnostic_summary =
+        //         cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
+        //     let activity_indicator = activity_indicator::ActivityIndicator::new(
+        //         workspace,
+        //         app_state.languages.clone(),
+        //         cx,
+        //     );
+        //     let active_buffer_language =
+        //         cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
+        //     let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
+        //     let feedback_button = cx.add_view(|_| {
+        //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
+        //     });
+        //     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+        workspace.status_bar().update(cx, |status_bar, cx| {
+            // status_bar.add_left_item(diagnostic_summary, cx);
+            // status_bar.add_left_item(activity_indicator, cx);
+
+            // status_bar.add_right_item(feedback_button, cx);
+            // status_bar.add_right_item(copilot, cx);
+            // status_bar.add_right_item(active_buffer_language, cx);
+            // status_bar.add_right_item(vim_mode_indicator, cx);
+            // status_bar.add_right_item(cursor_position, cx);
+        });
+
+        //     auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
+
+        //     vim::observe_keystrokes(cx);
+
+        //     cx.on_window_should_close(|workspace, cx| {
+        //         if let Some(task) = workspace.close(&Default::default(), cx) {
+        //             task.detach_and_log_err(cx);
+        //         }
+        //         false
+        //     });
+
+        cx.spawn(|workspace_handle, mut cx| async move {
+            let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
+            // let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
+            // let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
+            let channels_panel =
+                collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
+            // let chat_panel =
+            //     collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
+            // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
+            //     workspace_handle.clone(),
+            //     cx.clone(),
+            // );
+            let (
+                project_panel,
+                //     terminal_panel,
+                //     assistant_panel,
+                channels_panel,
+                //     chat_panel,
+                //     notification_panel,
+            ) = futures::try_join!(
+                project_panel,
+                //     terminal_panel,
+                //     assistant_panel,
+                channels_panel,
+                //     chat_panel,
+                //     notification_panel,
+            )?;
+
+            workspace_handle.update(&mut cx, |workspace, cx| {
+                let project_panel_position = project_panel.position(cx);
+                workspace.add_panel(project_panel, cx);
+                //     workspace.add_panel(terminal_panel, cx);
+                //     workspace.add_panel(assistant_panel, cx);
+                workspace.add_panel(channels_panel, cx);
+                //     workspace.add_panel(chat_panel, cx);
+                //     workspace.add_panel(notification_panel, cx);
+
+                //     if !was_deserialized
+                //         && workspace
+                //             .project()
+                //             .read(cx)
+                //             .visible_worktrees(cx)
+                //             .any(|tree| {
+                //                 tree.read(cx)
+                //                     .root_entry()
+                //                     .map_or(false, |entry| entry.is_dir())
+                //             })
+                //     {
+                // workspace.toggle_dock(project_panel_position, cx);
+                //     }
+                // cx.focus_self();
+            })
+        })
+        .detach();
+
         workspace
             .register_action(about)
             .register_action(|_, _: &Hide, cx| {
@@ -291,150 +430,6 @@ pub fn init_zed_actions(app_state: Arc<AppState>, cx: &mut AppContext) {
     .detach();
 }
 
-pub fn initialize_workspace(
-    workspace_handle: WeakView<Workspace>,
-    was_deserialized: bool,
-    app_state: Arc<AppState>,
-    cx: AsyncWindowContext,
-) -> Task<Result<()>> {
-    cx.spawn(|mut cx| async move {
-        workspace_handle.update(&mut cx, |workspace, cx| {
-            let workspace_handle = cx.view().clone();
-            cx.subscribe(&workspace_handle, {
-                move |workspace, _, event, cx| {
-                    if let workspace::Event::PaneAdded(pane) = event {
-                        pane.update(cx, |pane, cx| {
-                            pane.toolbar().update(cx, |toolbar, cx| {
-                                // todo!()
-                                //     let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
-                                //     toolbar.add_item(breadcrumbs, cx);
-                                //     let buffer_search_bar = cx.add_view(BufferSearchBar::new);
-                                //     toolbar.add_item(buffer_search_bar.clone(), cx);
-                                //     let quick_action_bar = cx.add_view(|_| {
-                                //         QuickActionBar::new(buffer_search_bar, workspace)
-                                //     });
-                                //     toolbar.add_item(quick_action_bar, cx);
-                                //     let diagnostic_editor_controls =
-                                //         cx.add_view(|_| diagnostics2::ToolbarControls::new());
-                                //     toolbar.add_item(diagnostic_editor_controls, cx);
-                                //     let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
-                                //     toolbar.add_item(project_search_bar, cx);
-                                //     let submit_feedback_button =
-                                //         cx.add_view(|_| SubmitFeedbackButton::new());
-                                //     toolbar.add_item(submit_feedback_button, cx);
-                                //     let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
-                                //     toolbar.add_item(feedback_info_text, cx);
-                                //     let lsp_log_item =
-                                //         cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
-                                //     toolbar.add_item(lsp_log_item, cx);
-                                //     let syntax_tree_item = cx
-                                //         .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
-                                //     toolbar.add_item(syntax_tree_item, cx);
-                            })
-                        });
-                    }
-                }
-            })
-            .detach();
-
-            //     cx.emit(workspace2::Event::PaneAdded(
-            //         workspace.active_pane().clone(),
-            //     ));
-
-            //     let copilot =
-            //         cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
-            //     let diagnostic_summary =
-            //         cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
-            //     let activity_indicator = activity_indicator::ActivityIndicator::new(
-            //         workspace,
-            //         app_state.languages.clone(),
-            //         cx,
-            //     );
-            //     let active_buffer_language =
-            //         cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
-            //     let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
-            //     let feedback_button = cx.add_view(|_| {
-            //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
-            //     });
-            //     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
-            workspace.status_bar().update(cx, |status_bar, cx| {
-                // status_bar.add_left_item(diagnostic_summary, cx);
-                // status_bar.add_left_item(activity_indicator, cx);
-
-                // status_bar.add_right_item(feedback_button, cx);
-                // status_bar.add_right_item(copilot, cx);
-                // status_bar.add_right_item(active_buffer_language, cx);
-                // status_bar.add_right_item(vim_mode_indicator, cx);
-                // status_bar.add_right_item(cursor_position, cx);
-            });
-
-            //     auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
-
-            //     vim::observe_keystrokes(cx);
-
-            //     cx.on_window_should_close(|workspace, cx| {
-            //         if let Some(task) = workspace.close(&Default::default(), cx) {
-            //             task.detach_and_log_err(cx);
-            //         }
-            //         false
-            //     });
-        })?;
-
-        let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
-        // let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
-        // let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
-        let channels_panel =
-            collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
-        // let chat_panel =
-        //     collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
-        // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
-        //     workspace_handle.clone(),
-        //     cx.clone(),
-        // );
-        let (
-            project_panel,
-            //     terminal_panel,
-            //     assistant_panel,
-            channels_panel,
-            //     chat_panel,
-            //     notification_panel,
-        ) = futures::try_join!(
-            project_panel,
-            //     terminal_panel,
-            //     assistant_panel,
-            channels_panel,
-            //     chat_panel,
-            //     notification_panel,/
-        )?;
-
-        workspace_handle.update(&mut cx, |workspace, cx| {
-            let project_panel_position = project_panel.position(cx);
-            workspace.add_panel(project_panel, cx);
-            //     workspace.add_panel(terminal_panel, cx);
-            //     workspace.add_panel(assistant_panel, cx);
-            workspace.add_panel(channels_panel, cx);
-            //     workspace.add_panel(chat_panel, cx);
-            //     workspace.add_panel(notification_panel, cx);
-
-            //     if !was_deserialized
-            //         && workspace
-            //             .project()
-            //             .read(cx)
-            //             .visible_worktrees(cx)
-            //             .any(|tree| {
-            //                 tree.read(cx)
-            //                     .root_entry()
-            //                     .map_or(false, |entry| entry.is_dir())
-            //             })
-            //     {
-            workspace.toggle_dock(project_panel_position, cx);
-            //     }
-            // cx.focus_self();
-        })?;
-        Ok(())
-    })
-}
-
 fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
     let app_name = cx.global::<ReleaseChannel>().display_name();
     let version = env!("CARGO_PKG_VERSION");