Merge remote-tracking branch 'origin/main' into perf-2

Nathan Sobo created

Change summary

Cargo.lock                                    |  19 
crates/collab_ui2/src/collab_panel.rs         |  11 
crates/editor2/src/editor.rs                  |   4 
crates/feedback2/Cargo.toml                   |   6 
crates/feedback2/src/feedback_modal.rs        |  33 
crates/gpui2/src/app.rs                       |   7 
crates/gpui2/src/elements/div.rs              | 118 +-
crates/gpui2/src/view.rs                      |  15 
crates/gpui2/src/window.rs                    |   3 
crates/project_panel2/src/project_panel.rs    |  37 
crates/search2/src/project_search.rs          |  69 +
crates/terminal_view2/src/terminal_element.rs |   1 
crates/theme2/src/themes/andromeda.rs         |   2 
crates/theme2/src/themes/ayu.rs               |   3 
crates/theme2/src/themes/dracula.rs           |   1 
crates/theme2/src/themes/gruvbox.rs           |   6 
crates/theme2/src/themes/night_owl.rs         |   2 
crates/theme2/src/themes/noctis.rs            |  11 
crates/theme2/src/themes/nord.rs              |   1 
crates/theme2/src/themes/palenight.rs         |   3 
crates/theme2/src/themes/rose_pine.rs         |   3 
crates/theme2/src/themes/solarized.rs         |   2 
crates/theme_importer/src/theme_printer.rs    |   1 
crates/theme_importer/src/vscode/converter.rs |  27 
crates/workspace2/src/dock.rs                 | 183 ----
crates/workspace2/src/item.rs                 |   6 
crates/workspace2/src/modal_layer.rs          |   6 
crates/workspace2/src/notifications.rs        |  43 
crates/workspace2/src/pane.rs                 | 242 +++--
crates/workspace2/src/pane_group.rs           | 418 ----------
crates/workspace2/src/searchable.rs           |   4 
crates/workspace2/src/status_bar.rs           |   4 
crates/workspace2/src/toolbar.rs              |  18 
crates/workspace2/src/workspace2.rs           | 833 +++++++++-----------
34 files changed, 847 insertions(+), 1,295 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -182,7 +182,7 @@ dependencies = [
  "alacritty_config",
  "alacritty_config_derive",
  "base64 0.13.1",
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "home",
  "libc",
  "log",
@@ -1025,9 +1025,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 dependencies = [
  "serde",
 ]
@@ -3280,6 +3280,7 @@ name = "feedback2"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "bitflags 2.4.1",
  "client2",
  "db2",
  "editor2",
@@ -4026,7 +4027,7 @@ dependencies = [
  "async-task",
  "backtrace",
  "bindgen 0.65.1",
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "block",
  "cbindgen",
  "cocoa",
@@ -6195,7 +6196,7 @@ version = "0.10.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "cfg-if 1.0.0",
  "foreign-types",
  "libc",
@@ -7915,7 +7916,7 @@ version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "fallible-iterator",
  "fallible-streaming-iterator",
  "hashlink",
@@ -8031,7 +8032,7 @@ version = "0.38.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "errno 0.3.3",
  "libc",
  "linux-raw-sys 0.4.7",
@@ -9134,7 +9135,7 @@ dependencies = [
  "atoi",
  "base64 0.21.4",
  "bigdecimal",
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "byteorder",
  "bytes 1.5.0",
  "chrono",
@@ -9181,7 +9182,7 @@ dependencies = [
  "atoi",
  "base64 0.21.4",
  "bigdecimal",
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "byteorder",
  "chrono",
  "crc",

crates/collab_ui2/src/collab_panel.rs πŸ”—

@@ -2552,12 +2552,11 @@ impl CollabPanel {
             .group("")
             .flex()
             .w_full()
-            .on_drag({
-                let channel = channel.clone();
-                move |cx| {
-                    let channel = channel.clone();
-                    cx.build_view(|cx| DraggedChannelView { channel, width })
-                }
+            .on_drag(channel.clone(), move |channel, cx| {
+                cx.build_view(|cx| DraggedChannelView {
+                    channel: channel.clone(),
+                    width,
+                })
             })
             .drag_over::<DraggedChannelView>(|style| {
                 style.bg(cx.theme().colors().ghost_element_hover)

crates/editor2/src/editor.rs πŸ”—

@@ -9297,7 +9297,7 @@ impl Render for Editor {
         let settings = ThemeSettings::get_global(cx);
         let text_style = match self.mode {
             EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
-                color: cx.theme().colors().text,
+                color: cx.theme().colors().editor_foreground,
                 font_family: settings.ui_font.family.clone(),
                 font_features: settings.ui_font.features,
                 font_size: rems(0.875).into(),
@@ -9310,7 +9310,7 @@ impl Render for Editor {
             },
 
             EditorMode::Full => TextStyle {
-                color: cx.theme().colors().text,
+                color: cx.theme().colors().editor_foreground,
                 font_family: settings.buffer_font.family.clone(),
                 font_features: settings.buffer_font.features,
                 font_size: settings.buffer_font_size(cx).into(),

crates/feedback2/Cargo.toml πŸ”—

@@ -18,7 +18,6 @@ gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 menu = { package = "menu2", path = "../menu2" }
 project = { package = "project2", path = "../project2" }
-regex.workspace = true
 search = { package = "search2", path = "../search2" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
@@ -26,13 +25,16 @@ ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
 workspace = { package = "workspace2", path = "../workspace2"}
 
+bitflags = "2.4.1"
+human_bytes = "0.4.1"
+
 anyhow.workspace = true
 futures.workspace = true
-human_bytes = "0.4.1"
 isahc.workspace = true
 lazy_static.workspace = true
 log.workspace = true
 postage.workspace = true
+regex.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 smallvec.workspace = true

crates/feedback2/src/feedback_modal.rs πŸ”—

@@ -1,6 +1,7 @@
 use std::{ops::RangeInclusive, sync::Arc, time::Duration};
 
 use anyhow::{anyhow, bail};
+use bitflags::bitflags;
 use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
 use db::kvp::KEY_VALUE_STORE;
 use editor::{Editor, EditorEvent};
@@ -48,15 +49,17 @@ struct FeedbackRequestBody<'a> {
     token: &'a str,
 }
 
-#[derive(Debug, Clone, PartialEq)]
-enum InvalidStateIssue {
-    EmailAddress,
-    CharacterCount,
+bitflags! {
+    #[derive(Debug, Clone, PartialEq)]
+    struct InvalidStateFlags: u8 {
+        const EmailAddress = 0b00000001;
+        const CharacterCount = 0b00000010;
+    }
 }
 
 #[derive(Debug, Clone, PartialEq)]
 enum CannotSubmitReason {
-    InvalidState { issues: Vec<InvalidStateIssue> },
+    InvalidState { flags: InvalidStateFlags },
     AwaitingSubmission,
 }
 
@@ -322,7 +325,7 @@ impl FeedbackModal {
             return;
         }
 
-        let mut invalid_state_issues = Vec::new();
+        let mut invalid_state_flags = InvalidStateFlags::empty();
 
         let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) {
             Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address),
@@ -330,37 +333,37 @@ impl FeedbackModal {
         };
 
         if !valid_email_address {
-            invalid_state_issues.push(InvalidStateIssue::EmailAddress);
+            invalid_state_flags |= InvalidStateFlags::EmailAddress;
         }
 
         if !FEEDBACK_CHAR_LIMIT.contains(&self.character_count) {
-            invalid_state_issues.push(InvalidStateIssue::CharacterCount);
+            invalid_state_flags |= InvalidStateFlags::CharacterCount;
         }
 
-        if invalid_state_issues.is_empty() {
+        if invalid_state_flags.is_empty() {
             self.submission_state = Some(SubmissionState::CanSubmit);
         } else {
             self.submission_state = Some(SubmissionState::CannotSubmit {
                 reason: CannotSubmitReason::InvalidState {
-                    issues: invalid_state_issues,
+                    flags: invalid_state_flags,
                 },
             });
         }
     }
 
     fn valid_email_address(&self) -> bool {
-        !self.in_invalid_state(InvalidStateIssue::EmailAddress)
+        !self.in_invalid_state(InvalidStateFlags::EmailAddress)
     }
 
     fn valid_character_count(&self) -> bool {
-        !self.in_invalid_state(InvalidStateIssue::CharacterCount)
+        !self.in_invalid_state(InvalidStateFlags::CharacterCount)
     }
 
-    fn in_invalid_state(&self, a: InvalidStateIssue) -> bool {
+    fn in_invalid_state(&self, flag: InvalidStateFlags) -> bool {
         match self.submission_state {
             Some(SubmissionState::CannotSubmit {
-                reason: CannotSubmitReason::InvalidState { ref issues },
-            }) => issues.contains(&a),
+                reason: CannotSubmitReason::InvalidState { ref flags },
+            }) => flags.contains(flag),
             _ => false,
         }
     }

crates/gpui2/src/app.rs πŸ”—

@@ -1139,8 +1139,10 @@ impl AppContext {
         self.active_drag.is_some()
     }
 
-    pub fn active_drag(&self) -> Option<AnyView> {
-        self.active_drag.as_ref().map(|drag| drag.view.clone())
+    pub fn active_drag<T: 'static>(&self) -> Option<&T> {
+        self.active_drag
+            .as_ref()
+            .and_then(|drag| drag.value.downcast_ref())
     }
 }
 
@@ -1296,6 +1298,7 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
 /// within the window or by dragging into the app from the underlying platform.
 pub struct AnyDrag {
     pub view: AnyView,
+    pub value: Box<dyn Any>,
     pub cursor_offset: Point<Pixels>,
 }
 

crates/gpui2/src/elements/div.rs πŸ”—

@@ -15,6 +15,7 @@ use std::{
     cell::RefCell,
     cmp::Ordering,
     fmt::Debug,
+    marker::PhantomData,
     mem,
     rc::Rc,
     time::Duration,
@@ -30,9 +31,18 @@ pub struct GroupStyle {
     pub style: Box<StyleRefinement>,
 }
 
-pub struct DragMoveEvent<W: Render> {
+pub struct DragMoveEvent<T> {
     pub event: MouseMoveEvent,
-    pub drag: View<W>,
+    drag: PhantomData<T>,
+}
+
+impl<T: 'static> DragMoveEvent<T> {
+    pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T {
+        cx.active_drag
+            .as_ref()
+            .and_then(|drag| drag.value.downcast_ref::<T>())
+            .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
+    }
 }
 
 impl Interactivity {
@@ -133,23 +143,26 @@ impl Interactivity {
             }));
     }
 
-    pub fn on_drag_move<W>(
+    pub fn on_drag_move<T>(
         &mut self,
-        listener: impl Fn(&DragMoveEvent<W>, &mut WindowContext) + 'static,
+        listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
     ) where
-        W: Render,
+        T: 'static,
     {
         self.mouse_move_listeners
             .push(Box::new(move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
                     && bounds.drag_target_contains(&event.position, cx)
                 {
-                    if let Some(view) = cx.active_drag().and_then(|view| view.downcast::<W>().ok())
+                    if cx
+                        .active_drag
+                        .as_ref()
+                        .is_some_and(|drag| drag.value.type_id() == TypeId::of::<T>())
                     {
                         (listener)(
                             &DragMoveEvent {
                                 event: event.clone(),
-                                drag: view,
+                                drag: PhantomData,
                             },
                             cx,
                         );
@@ -252,14 +265,11 @@ impl Interactivity {
             }));
     }
 
-    pub fn on_drop<W: 'static>(
-        &mut self,
-        listener: impl Fn(&View<W>, &mut WindowContext) + 'static,
-    ) {
+    pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) {
         self.drop_listeners.push((
-            TypeId::of::<W>(),
-            Box::new(move |dragged_view, cx| {
-                listener(&dragged_view.downcast().unwrap(), cx);
+            TypeId::of::<T>(),
+            Box::new(move |dragged_value, cx| {
+                listener(dragged_value.downcast_ref().unwrap(), cx);
             }),
         ));
     }
@@ -272,19 +282,23 @@ impl Interactivity {
             .push(Box::new(move |event, cx| listener(event, cx)));
     }
 
-    pub fn on_drag<W>(&mut self, constructor: impl Fn(&mut WindowContext) -> View<W> + 'static)
-    where
+    pub fn on_drag<T, W>(
+        &mut self,
+        value: T,
+        constructor: impl Fn(&T, &mut WindowContext) -> View<W> + 'static,
+    ) where
         Self: Sized,
+        T: 'static,
         W: 'static + Render,
     {
         debug_assert!(
             self.drag_listener.is_none(),
             "calling on_drag more than once on the same element is not supported"
         );
-        self.drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag {
-            view: constructor(cx).into(),
-            cursor_offset,
-        }));
+        self.drag_listener = Some((
+            Box::new(value),
+            Box::new(move |value, cx| constructor(value.downcast_ref().unwrap(), cx).into()),
+        ));
     }
 
     pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static)
@@ -413,12 +427,12 @@ pub trait InteractiveElement: Sized {
         self
     }
 
-    fn on_drag_move<W>(
+    fn on_drag_move<T>(
         mut self,
-        listener: impl Fn(&DragMoveEvent<W>, &mut WindowContext) + 'static,
+        listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
     ) -> Self
     where
-        W: Render,
+        T: Render,
     {
         self.interactivity().on_drag_move(listener);
         self
@@ -485,14 +499,6 @@ pub trait InteractiveElement: Sized {
         self
     }
 
-    fn on_drop<W: 'static>(
-        mut self,
-        listener: impl Fn(&View<W>, &mut WindowContext) + 'static,
-    ) -> Self {
-        self.interactivity().on_drop(listener);
-        self
-    }
-
     fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
         self.interactivity()
             .drag_over_styles
@@ -514,6 +520,11 @@ pub trait InteractiveElement: Sized {
         ));
         self
     }
+
+    fn on_drop<T: 'static>(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self {
+        self.interactivity().on_drop(listener);
+        self
+    }
 }
 
 pub trait StatefulInteractiveElement: InteractiveElement {
@@ -574,12 +585,17 @@ pub trait StatefulInteractiveElement: InteractiveElement {
         self
     }
 
-    fn on_drag<W>(mut self, constructor: impl Fn(&mut WindowContext) -> View<W> + 'static) -> Self
+    fn on_drag<T, W>(
+        mut self,
+        value: T,
+        constructor: impl Fn(&T, &mut WindowContext) -> View<W> + 'static,
+    ) -> Self
     where
         Self: Sized,
+        T: 'static,
         W: 'static + Render,
     {
-        self.interactivity().on_drag(constructor);
+        self.interactivity().on_drag(value, constructor);
         self
     }
 
@@ -635,9 +651,9 @@ pub type ScrollWheelListener =
 
 pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
 
-pub type DragListener = Box<dyn Fn(Point<Pixels>, &mut WindowContext) -> AnyDrag + 'static>;
+pub type DragListener = Box<dyn Fn(&dyn Any, &mut WindowContext) -> AnyView + 'static>;
 
-type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static;
+type DropListener = Box<dyn Fn(&dyn Any, &mut WindowContext) + 'static>;
 
 pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
 
@@ -834,9 +850,9 @@ pub struct Interactivity {
     pub key_down_listeners: Vec<KeyDownListener>,
     pub key_up_listeners: Vec<KeyUpListener>,
     pub action_listeners: Vec<(TypeId, ActionListener)>,
-    pub drop_listeners: Vec<(TypeId, Box<DropListener>)>,
+    pub drop_listeners: Vec<(TypeId, DropListener)>,
     pub click_listeners: Vec<ClickListener>,
-    pub drag_listener: Option<DragListener>,
+    pub drag_listener: Option<(Box<dyn Any>, DragListener)>,
     pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
     pub tooltip_builder: Option<TooltipBuilder>,
 
@@ -1124,8 +1140,10 @@ impl Interactivity {
                     if phase == DispatchPhase::Bubble
                         && interactive_bounds.drag_target_contains(&event.position, cx)
                     {
-                        if let Some(drag_state_type) =
-                            cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
+                        if let Some(drag_state_type) = cx
+                            .active_drag
+                            .as_ref()
+                            .map(|drag| drag.value.as_ref().type_id())
                         {
                             for (drop_state_type, listener) in &drop_listeners {
                                 if *drop_state_type == drag_state_type {
@@ -1134,7 +1152,7 @@ impl Interactivity {
                                         .take()
                                         .expect("checked for type drag state type above");
 
-                                    listener(drag.view.clone(), cx);
+                                    listener(drag.value.as_ref(), cx);
                                     cx.notify();
                                     cx.stop_propagation();
                                 }
@@ -1148,7 +1166,7 @@ impl Interactivity {
         }
 
         let click_listeners = mem::take(&mut self.click_listeners);
-        let drag_listener = mem::take(&mut self.drag_listener);
+        let mut drag_listener = mem::take(&mut self.drag_listener);
 
         if !click_listeners.is_empty() || drag_listener.is_some() {
             let pending_mouse_down = element_state
@@ -1157,7 +1175,7 @@ impl Interactivity {
                 .clone();
             let mouse_down = pending_mouse_down.borrow().clone();
             if let Some(mouse_down) = mouse_down {
-                if let Some(drag_listener) = drag_listener {
+                if drag_listener.is_some() {
                     let active_state = element_state
                         .clicked_state
                         .get_or_insert_with(Default::default)
@@ -1173,10 +1191,18 @@ impl Interactivity {
                             && interactive_bounds.visibly_contains(&event.position, cx)
                             && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
                         {
+                            let (drag_value, drag_listener) = drag_listener
+                                .take()
+                                .expect("The notify below should invalidate this callback");
+
                             *active_state.borrow_mut() = ElementClickedState::default();
                             let cursor_offset = event.position - bounds.origin;
-                            let drag = drag_listener(cursor_offset, cx);
-                            cx.active_drag = Some(drag);
+                            let drag = (drag_listener)(drag_value.as_ref(), cx);
+                            cx.active_drag = Some(AnyDrag {
+                                view: drag,
+                                value: drag_value,
+                                cursor_offset,
+                            });
                             cx.notify();
                             cx.stop_propagation();
                         }
@@ -1470,7 +1496,7 @@ impl Interactivity {
                 if let Some(drag) = cx.active_drag.take() {
                     for (state_type, group_drag_style) in &self.group_drag_over_styles {
                         if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
-                            if *state_type == drag.view.entity_type()
+                            if *state_type == drag.value.as_ref().type_id()
                                 && group_bounds.contains(&mouse_position)
                             {
                                 style.refine(&group_drag_style.style);
@@ -1479,7 +1505,7 @@ impl Interactivity {
                     }
 
                     for (state_type, drag_over_style) in &self.drag_over_styles {
-                        if *state_type == drag.view.entity_type()
+                        if *state_type == drag.value.as_ref().type_id()
                             && bounds
                                 .intersect(&cx.content_mask().bounds)
                                 .contains(&mouse_position)

crates/gpui2/src/view.rs πŸ”—

@@ -7,6 +7,7 @@ use crate::{
 use anyhow::{Context, Result};
 use std::{
     any::TypeId,
+    fmt,
     hash::{Hash, Hasher},
 };
 
@@ -308,6 +309,20 @@ impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
     }
 }
 
+impl PartialEq for AnyWeakView {
+    fn eq(&self, other: &Self) -> bool {
+        self.model == other.model
+    }
+}
+
+impl std::fmt::Debug for AnyWeakView {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("AnyWeakView")
+            .field("entity_id", &self.model.entity_id)
+            .finish_non_exhaustive()
+    }
+}
+
 impl<T, E> Render for T
 where
     T: 'static + FnMut(&mut WindowContext) -> E,

crates/gpui2/src/window.rs πŸ”—

@@ -822,7 +822,7 @@ impl<'a> WindowContext<'a> {
     /// a specific need to register a global listener.
     pub fn on_mouse_event<Event: 'static>(
         &mut self,
-        handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
+        mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static,
     ) {
         let order = self.window.next_frame.z_index_stack.clone();
         self.window
@@ -1400,6 +1400,7 @@ impl<'a> WindowContext<'a> {
                     self.window.mouse_position = position;
                     if self.active_drag.is_none() {
                         self.active_drag = Some(AnyDrag {
+                            value: Box::new(files.clone()),
                             view: self.build_view(|_| files).into(),
                             cursor_offset: position,
                         });

crates/project_panel2/src/project_panel.rs πŸ”—

@@ -1377,33 +1377,28 @@ impl ProjectPanel {
             })
             .unwrap_or(theme.status().info);
 
+        let file_name = details.filename.clone();
+        let icon = details.icon.clone();
+        let depth = details.depth;
         div()
             .id(entry_id.to_proto() as usize)
-            .on_drag({
-                let details = details.clone();
-                move |cx| {
-                    let details = details.clone();
-                    cx.build_view(|_| DraggedProjectEntryView {
-                        details,
-                        width,
-                        entry_id,
-                    })
-                }
-            })
-            .drag_over::<DraggedProjectEntryView>(|style| {
-                style.bg(cx.theme().colors().ghost_element_hover)
+            .on_drag(entry_id, move |entry_id, cx| {
+                cx.build_view(|_| DraggedProjectEntryView {
+                    details: details.clone(),
+                    width,
+                    entry_id: *entry_id,
+                })
             })
-            .on_drop(cx.listener(
-                move |this, dragged_view: &View<DraggedProjectEntryView>, cx| {
-                    this.move_entry(dragged_view.read(cx).entry_id, entry_id, kind.is_file(), cx);
-                },
-            ))
+            .drag_over::<ProjectEntryId>(|style| style.bg(cx.theme().colors().ghost_element_hover))
+            .on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| {
+                this.move_entry(*dragged_id, entry_id, kind.is_file(), cx);
+            }))
             .child(
                 ListItem::new(entry_id.to_proto() as usize)
-                    .indent_level(details.depth)
+                    .indent_level(depth)
                     .indent_step_size(px(settings.indent_size))
                     .selected(is_selected)
-                    .child(if let Some(icon) = &details.icon {
+                    .child(if let Some(icon) = &icon {
                         div().child(IconElement::from_path(icon.to_string()))
                     } else {
                         div()
@@ -1414,7 +1409,7 @@ impl ProjectPanel {
                         } else {
                             div()
                                 .text_color(filename_text_color)
-                                .child(Label::new(details.filename.clone()))
+                                .child(Label::new(file_name))
                         }
                         .ml_1(),
                     )

crates/search2/src/project_search.rs πŸ”—

@@ -1590,12 +1590,16 @@ impl Render for ProjectSearchBar {
         );
         let replace_column = if search.replace_enabled {
             h_stack()
-                .p_1()
                 .flex_1()
-                .border_2()
+                .h_full()
+                .gap_2()
+                .px_2()
+                .py_1()
+                .border_1()
+                .border_color(cx.theme().colors().border)
                 .rounded_lg()
                 .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small))
-                .child(search.replacement_editor.clone())
+                .child(self.render_text_input(&search.replacement_editor, cx))
         } else {
             // Fill out the space if we don't have a replacement editor.
             h_stack().flex_1()
@@ -1658,10 +1662,10 @@ impl Render for ProjectSearchBar {
             ]);
         v_stack()
             .key_context(key_context)
+            .flex_grow()
             .p_1()
             .m_2()
             .gap_2()
-            .justify_between()
             .on_action(cx.listener(|this, _: &ToggleFilters, cx| {
                 this.toggle_filters(cx);
             }))
@@ -1713,10 +1717,59 @@ impl Render for ProjectSearchBar {
                     }))
                 })
             })
-            .child(query_column)
-            .child(mode_column)
-            .child(replace_column)
-            .child(actions_column)
+            .child(
+                h_stack()
+                    .justify_between()
+                    .child(query_column)
+                    .child(mode_column)
+                    .child(replace_column)
+                    .child(actions_column),
+            )
+            .when(search.filters_enabled, |this| {
+                this.child(
+                    h_stack()
+                        .flex_1()
+                        .gap_2()
+                        .justify_between()
+                        .child(
+                            h_stack()
+                                .flex_1()
+                                .h_full()
+                                .px_2()
+                                .py_1()
+                                .border_1()
+                                .border_color(cx.theme().colors().border)
+                                .rounded_lg()
+                                .child(self.render_text_input(&search.included_files_editor, cx))
+                                .when(search.current_mode != SearchMode::Semantic, |this| {
+                                    this.child(
+                                        SearchOptions::INCLUDE_IGNORED.as_button(
+                                            search
+                                                .search_options
+                                                .contains(SearchOptions::INCLUDE_IGNORED),
+                                            cx.listener(|this, _, cx| {
+                                                this.toggle_search_option(
+                                                    SearchOptions::INCLUDE_IGNORED,
+                                                    cx,
+                                                );
+                                            }),
+                                        ),
+                                    )
+                                }),
+                        )
+                        .child(
+                            h_stack()
+                                .flex_1()
+                                .h_full()
+                                .px_2()
+                                .py_1()
+                                .border_1()
+                                .border_color(cx.theme().colors().border)
+                                .rounded_lg()
+                                .child(self.render_text_input(&search.excluded_files_editor, cx)),
+                        ),
+                )
+            })
     }
 }
 // impl Entity for ProjectSearchBar {

crates/theme2/src/themes/andromeda.rs πŸ”—

@@ -47,6 +47,7 @@ pub fn andromeda() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x3a3f4c77).into()),
                         scrollbar_track_background: Some(rgba(0x23262eff).into()),
                         scrollbar_track_border: Some(rgba(0x1b1d23ff).into()),
+                        editor_foreground: Some(rgba(0xd5ced9ff).into()),
                         editor_background: Some(rgba(0x23262eff).into()),
                         editor_gutter_background: Some(rgba(0x23262eff).into()),
                         editor_line_number: Some(rgba(0x746f77ff).into()),
@@ -261,6 +262,7 @@ pub fn andromeda() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x3a3f4c77).into()),
                         scrollbar_track_background: Some(rgba(0x262a33ff).into()),
                         scrollbar_track_border: Some(rgba(0x1b1d23ff).into()),
+                        editor_foreground: Some(rgba(0xd5ced9ff).into()),
                         editor_background: Some(rgba(0x262a33ff).into()),
                         editor_gutter_background: Some(rgba(0x262a33ff).into()),
                         editor_line_number: Some(rgba(0x746f77ff).into()),

crates/theme2/src/themes/ayu.rs πŸ”—

@@ -46,6 +46,7 @@ pub fn ayu() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x8a919966).into()),
                         scrollbar_track_background: Some(rgba(0xf8f9faff).into()),
                         scrollbar_track_border: Some(rgba(0x6b7d8f1f).into()),
+                        editor_foreground: Some(rgba(0x5c6166ff).into()),
                         editor_background: Some(rgba(0xf8f9faff).into()),
                         editor_gutter_background: Some(rgba(0xf8f9faff).into()),
                         editor_line_number: Some(rgba(0x8a919966).into()),
@@ -352,6 +353,7 @@ pub fn ayu() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x707a8c66).into()),
                         scrollbar_track_background: Some(rgba(0x1f2430ff).into()),
                         scrollbar_track_border: Some(rgba(0x171b24ff).into()),
+                        editor_foreground: Some(rgba(0xcccac2ff).into()),
                         editor_background: Some(rgba(0x1f2430ff).into()),
                         editor_gutter_background: Some(rgba(0x1f2430ff).into()),
                         editor_line_number: Some(rgba(0x8a919966).into()),
@@ -658,6 +660,7 @@ pub fn ayu() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x565b6666).into()),
                         scrollbar_track_background: Some(rgba(0x0b0e14ff).into()),
                         scrollbar_track_border: Some(rgba(0x1e232bff).into()),
+                        editor_foreground: Some(rgba(0xbfbdb6ff).into()),
                         editor_background: Some(rgba(0x0b0e14ff).into()),
                         editor_gutter_background: Some(rgba(0x0b0e14ff).into()),
                         editor_line_number: Some(rgba(0x6c738099).into()),

crates/theme2/src/themes/dracula.rs πŸ”—

@@ -43,6 +43,7 @@ pub fn dracula() -> UserThemeFamily {
                     tab_active_background: Some(rgba(0x282a36ff).into()),
                     scrollbar_track_background: Some(rgba(0x282a36ff).into()),
                     scrollbar_track_border: Some(rgba(0x191a21ff).into()),
+                    editor_foreground: Some(rgba(0xf8f8f2ff).into()),
                     editor_background: Some(rgba(0x282a36ff).into()),
                     editor_gutter_background: Some(rgba(0x282a36ff).into()),
                     editor_line_number: Some(rgba(0x6272a4ff).into()),

crates/theme2/src/themes/gruvbox.rs πŸ”—

@@ -46,6 +46,7 @@ pub fn gruvbox() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x50494599).into()),
                         scrollbar_track_background: Some(rgba(0x1d2021ff).into()),
                         scrollbar_track_border: Some(rgba(0x1d202100).into()),
+                        editor_foreground: Some(rgba(0xebdbb2ff).into()),
                         editor_background: Some(rgba(0x1d2021ff).into()),
                         editor_gutter_background: Some(rgba(0x1d2021ff).into()),
                         editor_line_number: Some(rgba(0x665c54ff).into()),
@@ -338,6 +339,7 @@ pub fn gruvbox() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x50494599).into()),
                         scrollbar_track_background: Some(rgba(0x282828ff).into()),
                         scrollbar_track_border: Some(rgba(0x28282800).into()),
+                        editor_foreground: Some(rgba(0xebdbb2ff).into()),
                         editor_background: Some(rgba(0x282828ff).into()),
                         editor_gutter_background: Some(rgba(0x282828ff).into()),
                         editor_line_number: Some(rgba(0x665c54ff).into()),
@@ -630,6 +632,7 @@ pub fn gruvbox() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x50494599).into()),
                         scrollbar_track_background: Some(rgba(0x32302fff).into()),
                         scrollbar_track_border: Some(rgba(0x32302f00).into()),
+                        editor_foreground: Some(rgba(0xebdbb2ff).into()),
                         editor_background: Some(rgba(0x32302fff).into()),
                         editor_gutter_background: Some(rgba(0x32302fff).into()),
                         editor_line_number: Some(rgba(0x665c54ff).into()),
@@ -922,6 +925,7 @@ pub fn gruvbox() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()),
                         scrollbar_track_background: Some(rgba(0xf9f5d7ff).into()),
                         scrollbar_track_border: Some(rgba(0xf9f5d700).into()),
+                        editor_foreground: Some(rgba(0x3c3836ff).into()),
                         editor_background: Some(rgba(0xf9f5d7ff).into()),
                         editor_gutter_background: Some(rgba(0xf9f5d7ff).into()),
                         editor_line_number: Some(rgba(0xbdae93ff).into()),
@@ -1214,6 +1218,7 @@ pub fn gruvbox() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()),
                         scrollbar_track_background: Some(rgba(0xfbf1c7ff).into()),
                         scrollbar_track_border: Some(rgba(0xfbf1c700).into()),
+                        editor_foreground: Some(rgba(0x3c3836ff).into()),
                         editor_background: Some(rgba(0xfbf1c7ff).into()),
                         editor_gutter_background: Some(rgba(0xfbf1c7ff).into()),
                         editor_line_number: Some(rgba(0xbdae93ff).into()),
@@ -1506,6 +1511,7 @@ pub fn gruvbox() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()),
                         scrollbar_track_background: Some(rgba(0xf2e5bcff).into()),
                         scrollbar_track_border: Some(rgba(0xf2e5bc00).into()),
+                        editor_foreground: Some(rgba(0x3c3836ff).into()),
                         editor_background: Some(rgba(0xf2e5bcff).into()),
                         editor_gutter_background: Some(rgba(0xf2e5bcff).into()),
                         editor_line_number: Some(rgba(0xbdae93ff).into()),

crates/theme2/src/themes/night_owl.rs πŸ”—

@@ -46,6 +46,7 @@ pub fn night_owl() -> UserThemeFamily {
                         scrollbar_thumb_hover_background: Some(rgba(0x084d8180).into()),
                         scrollbar_thumb_border: Some(rgba(0x084d8180).into()),
                         scrollbar_track_background: Some(rgba(0x011627ff).into()),
+                        editor_foreground: Some(rgba(0xd6deebff).into()),
                         editor_background: Some(rgba(0x011627ff).into()),
                         editor_gutter_background: Some(rgba(0x011627ff).into()),
                         editor_line_number: Some(rgba(0x4b6479ff).into()),
@@ -305,6 +306,7 @@ pub fn night_owl() -> UserThemeFamily {
                         tab_inactive_background: Some(rgba(0xf0f0f0ff).into()),
                         tab_active_background: Some(rgba(0xf6f6f6ff).into()),
                         scrollbar_track_background: Some(rgba(0xfbfbfbff).into()),
+                        editor_foreground: Some(rgba(0x403f53ff).into()),
                         editor_background: Some(rgba(0xfbfbfbff).into()),
                         editor_gutter_background: Some(rgba(0xfbfbfbff).into()),
                         editor_line_number: Some(rgba(0x90a7b2ff).into()),

crates/theme2/src/themes/noctis.rs πŸ”—

@@ -47,6 +47,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x008ee633).into()),
                         scrollbar_track_background: Some(rgba(0x07273bff).into()),
                         scrollbar_track_border: Some(rgba(0x07273bff).into()),
+                        editor_foreground: Some(rgba(0xbecfdaff).into()),
                         editor_background: Some(rgba(0x07273bff).into()),
                         editor_gutter_background: Some(rgba(0x07273bff).into()),
                         editor_line_number: Some(rgba(0x4d6c80ff).into()),
@@ -324,6 +325,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0xeb609133).into()),
                         scrollbar_track_background: Some(rgba(0x322a2dff).into()),
                         scrollbar_track_border: Some(rgba(0x322a2dff).into()),
+                        editor_foreground: Some(rgba(0xcbbec2ff).into()),
                         editor_background: Some(rgba(0x322a2dff).into()),
                         editor_gutter_background: Some(rgba(0x322a2dff).into()),
                         editor_line_number: Some(rgba(0x715b63ff).into()),
@@ -601,6 +603,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6a90955b).into()),
                         scrollbar_track_background: Some(rgba(0xf4f6f6ff).into()),
                         scrollbar_track_border: Some(rgba(0xf4f6f6ff).into()),
+                        editor_foreground: Some(rgba(0x005661ff).into()),
                         editor_background: Some(rgba(0xf4f6f6ff).into()),
                         editor_gutter_background: Some(rgba(0xf4f6f6ff).into()),
                         editor_line_number: Some(rgba(0xa0abacff).into()),
@@ -878,6 +881,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6a90955b).into()),
                         scrollbar_track_background: Some(rgba(0xf2f1f8ff).into()),
                         scrollbar_track_border: Some(rgba(0xf2f1f8ff).into()),
+                        editor_foreground: Some(rgba(0x0c006bff).into()),
                         editor_background: Some(rgba(0xf2f1f8ff).into()),
                         editor_gutter_background: Some(rgba(0xf2f1f8ff).into()),
                         editor_line_number: Some(rgba(0x9d9ab1ff).into()),
@@ -1155,6 +1159,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6a90955b).into()),
                         scrollbar_track_background: Some(rgba(0xfef8ecff).into()),
                         scrollbar_track_border: Some(rgba(0xfef8ecff).into()),
+                        editor_foreground: Some(rgba(0x005661ff).into()),
                         editor_background: Some(rgba(0xfef8ecff).into()),
                         editor_gutter_background: Some(rgba(0xfef8ecff).into()),
                         editor_line_number: Some(rgba(0xa0abacff).into()),
@@ -1432,6 +1437,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x3f7fa633).into()),
                         scrollbar_track_background: Some(rgba(0x1b2932ff).into()),
                         scrollbar_track_border: Some(rgba(0x1b2932ff).into()),
+                        editor_foreground: Some(rgba(0xc5cdd3ff).into()),
                         editor_background: Some(rgba(0x1b2932ff).into()),
                         editor_gutter_background: Some(rgba(0x1b2932ff).into()),
                         editor_line_number: Some(rgba(0x5d6e79ff).into()),
@@ -1709,6 +1715,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6a90955b).into()),
                         scrollbar_track_background: Some(rgba(0x052529ff).into()),
                         scrollbar_track_border: Some(rgba(0x052529ff).into()),
+                        editor_foreground: Some(rgba(0xb2cacdff).into()),
                         editor_background: Some(rgba(0x052529ff).into()),
                         editor_gutter_background: Some(rgba(0x052529ff).into()),
                         editor_line_number: Some(rgba(0x4e6b6eff).into()),
@@ -1986,6 +1993,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6a90955b).into()),
                         scrollbar_track_background: Some(rgba(0x031417ff).into()),
                         scrollbar_track_border: Some(rgba(0x031417ff).into()),
+                        editor_foreground: Some(rgba(0xb2cacdff).into()),
                         editor_background: Some(rgba(0x031417ff).into()),
                         editor_gutter_background: Some(rgba(0x031417ff).into()),
                         editor_line_number: Some(rgba(0x4e6b6eff).into()),
@@ -2263,6 +2271,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6a90955b).into()),
                         scrollbar_track_background: Some(rgba(0x031417ff).into()),
                         scrollbar_track_border: Some(rgba(0x031417ff).into()),
+                        editor_foreground: Some(rgba(0xb2cacdff).into()),
                         editor_background: Some(rgba(0x031417ff).into()),
                         editor_gutter_background: Some(rgba(0x031417ff).into()),
                         editor_line_number: Some(rgba(0x4e6b6eff).into()),
@@ -2540,6 +2549,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x7060eb33).into()),
                         scrollbar_track_background: Some(rgba(0x292640ff).into()),
                         scrollbar_track_border: Some(rgba(0x292640ff).into()),
+                        editor_foreground: Some(rgba(0xc5c2d6ff).into()),
                         editor_background: Some(rgba(0x292640ff).into()),
                         editor_gutter_background: Some(rgba(0x292640ff).into()),
                         editor_line_number: Some(rgba(0x5c5973ff).into()),
@@ -2817,6 +2827,7 @@ pub fn noctis() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0xa660eb33).into()),
                         scrollbar_track_background: Some(rgba(0x30243dff).into()),
                         scrollbar_track_border: Some(rgba(0x30243dff).into()),
+                        editor_foreground: Some(rgba(0xccbfd9ff).into()),
                         editor_background: Some(rgba(0x30243dff).into()),
                         editor_gutter_background: Some(rgba(0x30243dff).into()),
                         editor_line_number: Some(rgba(0x665973ff).into()),

crates/theme2/src/themes/nord.rs πŸ”—

@@ -46,6 +46,7 @@ pub fn nord() -> UserThemeFamily {
                     scrollbar_thumb_border: Some(rgba(0x434c5e99).into()),
                     scrollbar_track_background: Some(rgba(0x2e3440ff).into()),
                     scrollbar_track_border: Some(rgba(0x3b4252ff).into()),
+                    editor_foreground: Some(rgba(0xd8dee9ff).into()),
                     editor_background: Some(rgba(0x2e3440ff).into()),
                     editor_gutter_background: Some(rgba(0x2e3440ff).into()),
                     editor_line_number: Some(rgba(0x4c566aff).into()),

crates/theme2/src/themes/palenight.rs πŸ”—

@@ -46,6 +46,7 @@ pub fn palenight() -> UserThemeFamily {
                         scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()),
                         scrollbar_thumb_border: Some(rgba(0x694ca466).into()),
                         scrollbar_track_background: Some(rgba(0x292d3eff).into()),
+                        editor_foreground: Some(rgba(0xbfc7d5ff).into()),
                         editor_background: Some(rgba(0x292d3eff).into()),
                         editor_gutter_background: Some(rgba(0x292d3eff).into()),
                         editor_line_number: Some(rgba(0x4c5374ff).into()),
@@ -331,6 +332,7 @@ pub fn palenight() -> UserThemeFamily {
                         scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()),
                         scrollbar_thumb_border: Some(rgba(0x694ca466).into()),
                         scrollbar_track_background: Some(rgba(0x292d3eff).into()),
+                        editor_foreground: Some(rgba(0xbfc7d5ff).into()),
                         editor_background: Some(rgba(0x292d3eff).into()),
                         editor_gutter_background: Some(rgba(0x292d3eff).into()),
                         editor_line_number: Some(rgba(0x4c5374ff).into()),
@@ -616,6 +618,7 @@ pub fn palenight() -> UserThemeFamily {
                         scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()),
                         scrollbar_thumb_border: Some(rgba(0x694ca466).into()),
                         scrollbar_track_background: Some(rgba(0x292d3eff).into()),
+                        editor_foreground: Some(rgba(0xbfc7d5ff).into()),
                         editor_background: Some(rgba(0x292d3eff).into()),
                         editor_gutter_background: Some(rgba(0x292d3eff).into()),
                         editor_line_number: Some(rgba(0x4c5374ff).into()),

crates/theme2/src/themes/rose_pine.rs πŸ”—

@@ -47,6 +47,7 @@ pub fn rose_pine() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6e6a8633).into()),
                         scrollbar_track_background: Some(rgba(0x191724ff).into()),
                         scrollbar_track_border: Some(rgba(0x6e6a8666).into()),
+                        editor_foreground: Some(rgba(0xe0def4ff).into()),
                         editor_background: Some(rgba(0x191724ff).into()),
                         editor_gutter_background: Some(rgba(0x191724ff).into()),
                         editor_line_number: Some(rgba(0x908caaff).into()),
@@ -306,6 +307,7 @@ pub fn rose_pine() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x817c9c26).into()),
                         scrollbar_track_background: Some(rgba(0x232136ff).into()),
                         scrollbar_track_border: Some(rgba(0x817c9c4d).into()),
+                        editor_foreground: Some(rgba(0xe0def4ff).into()),
                         editor_background: Some(rgba(0x232136ff).into()),
                         editor_gutter_background: Some(rgba(0x232136ff).into()),
                         editor_line_number: Some(rgba(0x908caaff).into()),
@@ -565,6 +567,7 @@ pub fn rose_pine() -> UserThemeFamily {
                         scrollbar_thumb_border: Some(rgba(0x6e6a8614).into()),
                         scrollbar_track_background: Some(rgba(0xfaf4edff).into()),
                         scrollbar_track_border: Some(rgba(0x6e6a8626).into()),
+                        editor_foreground: Some(rgba(0x575279ff).into()),
                         editor_background: Some(rgba(0xfaf4edff).into()),
                         editor_gutter_background: Some(rgba(0xfaf4edff).into()),
                         editor_line_number: Some(rgba(0x797593ff).into()),

crates/theme2/src/themes/solarized.rs πŸ”—

@@ -42,6 +42,7 @@ pub fn solarized() -> UserThemeFamily {
                         tab_inactive_background: Some(rgba(0x004052ff).into()),
                         tab_active_background: Some(rgba(0x002b37ff).into()),
                         scrollbar_track_background: Some(rgba(0x002b36ff).into()),
+                        editor_foreground: Some(rgba(0xbbbbbbff).into()),
                         editor_background: Some(rgba(0x002b36ff).into()),
                         editor_gutter_background: Some(rgba(0x002b36ff).into()),
                         editor_line_number: Some(rgba(0x566c74ff).into()),
@@ -307,6 +308,7 @@ pub fn solarized() -> UserThemeFamily {
                         tab_inactive_background: Some(rgba(0xd3cbb7ff).into()),
                         tab_active_background: Some(rgba(0xfdf6e3ff).into()),
                         scrollbar_track_background: Some(rgba(0xfdf6e3ff).into()),
+                        editor_foreground: Some(rgba(0x333333ff).into()),
                         editor_background: Some(rgba(0xfdf6e3ff).into()),
                         editor_gutter_background: Some(rgba(0xfdf6e3ff).into()),
                         editor_line_number: Some(rgba(0x9ca8a6ff).into()),

crates/theme_importer/src/theme_printer.rs πŸ”—

@@ -201,6 +201,7 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> {
                 self.0.scrollbar_track_background,
             ),
             ("scrollbar_track_border", self.0.scrollbar_track_border),
+            ("editor_foreground", self.0.editor_foreground),
             ("editor_background", self.0.editor_background),
             ("editor_gutter_background", self.0.editor_gutter_background),
             (

crates/theme_importer/src/vscode/converter.rs πŸ”—

@@ -132,6 +132,11 @@ impl VsCodeThemeConverter {
             .as_ref()
             .traverse(|color| try_parse_color(&color))?;
 
+        let vscode_editor_foreground = vscode_colors
+            .editor_foreground
+            .as_ref()
+            .traverse(|color| try_parse_color(&color))?;
+
         let vscode_editor_background = vscode_colors
             .editor_background
             .as_ref()
@@ -142,6 +147,16 @@ impl VsCodeThemeConverter {
             .as_ref()
             .traverse(|color| try_parse_color(&color))?;
 
+        let vscode_token_colors_foreground = self
+            .theme
+            .token_colors
+            .iter()
+            .find(|token_color| token_color.scope.is_none())
+            .and_then(|token_color| token_color.settings.foreground.as_ref())
+            .traverse(|color| try_parse_color(&color))
+            .ok()
+            .flatten();
+
         Ok(ThemeColorsRefinement {
             border: vscode_panel_border,
             border_variant: vscode_panel_border,
@@ -197,16 +212,7 @@ impl VsCodeThemeConverter {
                 .foreground
                 .as_ref()
                 .traverse(|color| try_parse_color(&color))?
-                .or_else(|| {
-                    self.theme
-                        .token_colors
-                        .iter()
-                        .find(|token_color| token_color.scope.is_none())
-                        .and_then(|token_color| token_color.settings.foreground.as_ref())
-                        .traverse(|color| try_parse_color(&color))
-                        .ok()
-                        .flatten()
-                }),
+                .or(vscode_token_colors_foreground),
             text_muted: vscode_colors
                 .tab_inactive_foreground
                 .as_ref()
@@ -226,6 +232,7 @@ impl VsCodeThemeConverter {
                 .as_ref()
                 .traverse(|color| try_parse_color(&color))?
                 .or(vscode_editor_background),
+            editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground),
             editor_background: vscode_editor_background,
             editor_gutter_background: vscode_editor_background,
             editor_line_number: vscode_colors

crates/workspace2/src/dock.rs πŸ”—

@@ -41,7 +41,7 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
 }
 
 pub trait PanelHandle: Send + Sync {
-    fn entity_id(&self) -> EntityId;
+    fn panel_id(&self) -> EntityId;
     fn persistent_name(&self) -> &'static str;
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
@@ -62,7 +62,7 @@ impl<T> PanelHandle for View<T>
 where
     T: Panel,
 {
-    fn entity_id(&self) -> EntityId {
+    fn panel_id(&self) -> EntityId {
         Entity::entity_id(self)
     }
 
@@ -135,7 +135,7 @@ pub struct Dock {
     is_open: bool,
     active_panel_index: usize,
     focus_handle: FocusHandle,
-    focus_subscription: Subscription,
+    _focus_subscription: Subscription,
 }
 
 impl FocusableView for Dock {
@@ -187,7 +187,6 @@ struct PanelEntry {
 
 pub struct PanelButtons {
     dock: View<Dock>,
-    workspace: WeakView<Workspace>,
 }
 
 impl Dock {
@@ -204,7 +203,7 @@ impl Dock {
             active_panel_index: 0,
             is_open: false,
             focus_handle,
-            focus_subscription,
+            _focus_subscription: focus_subscription,
         }
     }
 
@@ -261,7 +260,7 @@ impl Dock {
 
     pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
         for entry in &mut self.panel_entries {
-            if entry.panel.entity_id() == panel.entity_id() {
+            if entry.panel.panel_id() == panel.entity_id() {
                 if zoomed != entry.panel.is_zoomed(cx) {
                     entry.panel.set_zoomed(zoomed, cx);
                 }
@@ -309,7 +308,7 @@ impl Dock {
 
                     let was_visible = this.is_open()
                         && this.visible_panel().map_or(false, |active_panel| {
-                            active_panel.entity_id() == Entity::entity_id(&panel)
+                            active_panel.panel_id() == Entity::entity_id(&panel)
                         });
 
                     this.remove_panel(&panel, cx);
@@ -351,7 +350,7 @@ impl Dock {
                     if let Some(ix) = this
                         .panel_entries
                         .iter()
-                        .position(|entry| entry.panel.entity_id() == Entity::entity_id(&panel))
+                        .position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel))
                     {
                         this.set_open(true, cx);
                         this.activate_panel(ix, cx);
@@ -361,7 +360,7 @@ impl Dock {
                 PanelEvent::Close => {
                     if this
                         .visible_panel()
-                        .map_or(false, |p| p.entity_id() == Entity::entity_id(&panel))
+                        .map_or(false, |p| p.panel_id() == Entity::entity_id(&panel))
                     {
                         this.set_open(false, cx);
                     }
@@ -389,7 +388,7 @@ impl Dock {
         if let Some(panel_ix) = self
             .panel_entries
             .iter()
-            .position(|entry| entry.panel.entity_id() == Entity::entity_id(panel))
+            .position(|entry| entry.panel.panel_id() == Entity::entity_id(panel))
         {
             if panel_ix == self.active_panel_index {
                 self.active_panel_index = 0;
@@ -450,7 +449,7 @@ impl Dock {
     pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<f32> {
         self.panel_entries
             .iter()
-            .find(|entry| entry.panel.entity_id() == panel.entity_id())
+            .find(|entry| entry.panel.panel_id() == panel.panel_id())
             .map(|entry| entry.panel.size(cx))
     }
 
@@ -493,7 +492,9 @@ impl Render for Dock {
             let handler = div()
                 .id("resize-handle")
                 .bg(cx.theme().colors().border)
-                .on_drag(move |cx| cx.build_view(|_| DraggedDock(position)))
+                .on_drag(DraggedDock(position), |dock, cx| {
+                    cx.build_view(|_| dock.clone())
+                })
                 .on_click(cx.listener(|v, e: &ClickEvent, cx| {
                     if e.down.button == MouseButton::Left && e.down.click_count == 2 {
                         v.resize_active_panel(None, cx)
@@ -547,166 +548,12 @@ impl Render for Dock {
 }
 
 impl PanelButtons {
-    pub fn new(
-        dock: View<Dock>,
-        workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
+    pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> Self {
         cx.observe(&dock, |_, _, cx| cx.notify()).detach();
-        Self { dock, workspace }
+        Self { dock }
     }
 }
 
-// impl Render for PanelButtons {
-//     type Element = ();
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-//         todo!("")
-//     }
-
-//     fn ui_name() -> &'static str {
-//         "PanelButtons"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let theme = &settings::get::<ThemeSettings>(cx).theme;
-//         let tooltip_style = theme.tooltip.clone();
-//         let theme = &theme.workspace.status_bar.panel_buttons;
-//         let button_style = theme.button.clone();
-//         let dock = self.dock.read(cx);
-//         let active_ix = dock.active_panel_index;
-//         let is_open = dock.is_open;
-//         let dock_position = dock.position;
-//         let group_style = match dock_position {
-//             DockPosition::Left => theme.group_left,
-//             DockPosition::Bottom => theme.group_bottom,
-//             DockPosition::Right => theme.group_right,
-//         };
-//         let menu_corner = match dock_position {
-//             DockPosition::Left => AnchorCorner::BottomLeft,
-//             DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight,
-//         };
-
-//         let panels = dock
-//             .panel_entries
-//             .iter()
-//             .map(|item| (item.panel.clone(), item.context_menu.clone()))
-//             .collect::<Vec<_>>();
-//         Flex::row()
-//             .with_children(panels.into_iter().enumerate().filter_map(
-//                 |(panel_ix, (view, context_menu))| {
-//                     let icon_path = view.icon_path(cx)?;
-//                     let is_active = is_open && panel_ix == active_ix;
-//                     let (tooltip, tooltip_action) = if is_active {
-//                         (
-//                             format!("Close {} dock", dock_position.to_label()),
-//                             Some(match dock_position {
-//                                 DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
-//                                 DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
-//                                 DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
-//                             }),
-//                         )
-//                     } else {
-//                         view.icon_tooltip(cx)
-//                     };
-//                     Some(
-//                         Stack::new()
-//                             .with_child(
-//                                 MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
-//                                     let style = button_style.in_state(is_active);
-
-//                                     let style = style.style_for(state);
-//                                     Flex::row()
-//                                         .with_child(
-//                                             Svg::new(icon_path)
-//                                                 .with_color(style.icon_color)
-//                                                 .constrained()
-//                                                 .with_width(style.icon_size)
-//                                                 .aligned(),
-//                                         )
-//                                         .with_children(if let Some(label) = view.icon_label(cx) {
-//                                             Some(
-//                                                 Label::new(label, style.label.text.clone())
-//                                                     .contained()
-//                                                     .with_style(style.label.container)
-//                                                     .aligned(),
-//                                             )
-//                                         } else {
-//                                             None
-//                                         })
-//                                         .constrained()
-//                                         .with_height(style.icon_size)
-//                                         .contained()
-//                                         .with_style(style.container)
-//                                 })
-//                                 .with_cursor_style(CursorStyle::PointingHand)
-//                                 .on_click(MouseButton::Left, {
-//                                     let tooltip_action =
-//                                         tooltip_action.as_ref().map(|action| action.boxed_clone());
-//                                     move |_, this, cx| {
-//                                         if let Some(tooltip_action) = &tooltip_action {
-//                                             let window = cx.window();
-//                                             let view_id = this.workspace.id();
-//                                             let tooltip_action = tooltip_action.boxed_clone();
-//                                             cx.spawn(|_, mut cx| async move {
-//                                                 window.dispatch_action(
-//                                                     view_id,
-//                                                     &*tooltip_action,
-//                                                     &mut cx,
-//                                                 );
-//                                             })
-//                                             .detach();
-//                                         }
-//                                     }
-//                                 })
-//                                 .on_click(MouseButton::Right, {
-//                                     let view = view.clone();
-//                                     let menu = context_menu.clone();
-//                                     move |_, _, cx| {
-//                                         const POSITIONS: [DockPosition; 3] = [
-//                                             DockPosition::Left,
-//                                             DockPosition::Right,
-//                                             DockPosition::Bottom,
-//                                         ];
-
-//                                         menu.update(cx, |menu, cx| {
-//                                             let items = POSITIONS
-//                                                 .into_iter()
-//                                                 .filter(|position| {
-//                                                     *position != dock_position
-//                                                         && view.position_is_valid(*position, cx)
-//                                                 })
-//                                                 .map(|position| {
-//                                                     let view = view.clone();
-//                                                     ContextMenuItem::handler(
-//                                                         format!("Dock {}", position.to_label()),
-//                                                         move |cx| view.set_position(position, cx),
-//                                                     )
-//                                                 })
-//                                                 .collect();
-//                                             menu.show(Default::default(), menu_corner, items, cx);
-//                                         })
-//                                     }
-//                                 })
-//                                 .with_tooltip::<Self>(
-//                                     panel_ix,
-//                                     tooltip,
-//                                     tooltip_action,
-//                                     tooltip_style.clone(),
-//                                     cx,
-//                                 ),
-//                             )
-//                             .with_child(ChildView::new(&context_menu, cx)),
-//                     )
-//                 },
-//             ))
-//             .contained()
-//             .with_style(group_style)
-//             .into_any()
-//     }
-// }
-
-// here be kittens
 impl Render for PanelButtons {
     type Element = Div;
 

crates/workspace2/src/item.rs πŸ”—

@@ -919,7 +919,7 @@ pub mod test {
     impl EventEmitter<ItemEvent> for TestItem {}
 
     impl FocusableView for TestItem {
-        fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+        fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
             self.focus_handle.clone()
         }
     }
@@ -941,8 +941,8 @@ pub mod test {
         fn tab_content(
             &self,
             detail: Option<usize>,
-            selected: bool,
-            cx: &ui::prelude::WindowContext,
+            _selected: bool,
+            _cx: &ui::prelude::WindowContext,
         ) -> AnyElement {
             self.tab_detail.set(detail);
             gpui::div().into_any_element()

crates/workspace2/src/modal_layer.rs πŸ”—

@@ -5,7 +5,7 @@ use gpui::{
 use ui::{h_stack, v_stack};
 
 pub trait ModalView: ManagedView {
-    fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> bool {
+    fn on_before_dismiss(&mut self, _: &mut ViewContext<Self>) -> bool {
         true
     }
 }
@@ -27,7 +27,7 @@ impl<V: ModalView> ModalViewHandle for View<V> {
 
 pub struct ActiveModal {
     modal: Box<dyn ModalViewHandle>,
-    subscription: Subscription,
+    _subscription: Subscription,
     previous_focus_handle: Option<FocusHandle>,
     focus_handle: FocusHandle,
 }
@@ -63,7 +63,7 @@ impl ModalLayer {
     {
         self.active_modal = Some(ActiveModal {
             modal: Box::new(new_modal.clone()),
-            subscription: cx.subscribe(&new_modal, |this, modal, _: &DismissEvent, cx| {
+            _subscription: cx.subscribe(&new_modal, |this, _, _: &DismissEvent, cx| {
                 this.hide_modal(cx);
             }),
             previous_focus_handle: cx.focused(),

crates/workspace2/src/notifications.rs πŸ”—

@@ -104,12 +104,9 @@ impl Workspace {
             })
         {
             let notification = build_notification(cx);
-            cx.subscribe(
-                &notification,
-                move |this, handle, event: &DismissEvent, cx| {
-                    this.dismiss_notification_internal(type_id, id, cx);
-                },
-            )
+            cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| {
+                this.dismiss_notification_internal(type_id, id, cx);
+            })
             .detach();
             self.notifications
                 .push((type_id, id, Box::new(notification)));
@@ -173,21 +170,15 @@ impl Workspace {
 
 pub mod simple_message_notification {
     use gpui::{
-        div, AnyElement, AppContext, DismissEvent, Div, EventEmitter, InteractiveElement,
-        ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle,
-        ViewContext,
+        div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
+        SharedString, StatefulInteractiveElement, Styled, ViewContext,
     };
     use std::sync::Arc;
     use ui::prelude::*;
     use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
 
-    enum NotificationMessage {
-        Text(SharedString),
-        Element(fn(TextStyle, &AppContext) -> AnyElement),
-    }
-
     pub struct MessageNotification {
-        message: NotificationMessage,
+        message: SharedString,
         on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
         click_message: Option<SharedString>,
     }
@@ -200,23 +191,12 @@ pub mod simple_message_notification {
             S: Into<SharedString>,
         {
             Self {
-                message: NotificationMessage::Text(message.into()),
+                message: message.into(),
                 on_click: None,
                 click_message: None,
             }
         }
 
-        // not needed I think (only for the "new panel" toast, which is outdated now)
-        // pub fn new_element(
-        //     message: fn(TextStyle, &AppContext) -> AnyElement,
-        // ) -> MessageNotification {
-        //     Self {
-        //         message: NotificationMessage::Element(message),
-        //         on_click: None,
-        //         click_message: None,
-        //     }
-        // }
-
         pub fn with_click_message<S>(mut self, message: S) -> Self
         where
             S: Into<SharedString>,
@@ -248,18 +228,13 @@ pub mod simple_message_notification {
                 .child(
                     h_stack()
                         .justify_between()
-                        .child(div().max_w_80().child(match &self.message {
-                            NotificationMessage::Text(text) => Label::new(text.clone()),
-                            NotificationMessage::Element(element) => {
-                                todo!()
-                            }
-                        }))
+                        .child(div().max_w_80().child(Label::new(self.message.clone())))
                         .child(
                             div()
                                 .id("cancel")
                                 .child(IconElement::new(Icon::Close))
                                 .cursor_pointer()
-                                .on_click(cx.listener(|this, event, cx| this.dismiss(cx))),
+                                .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
                         ),
                 )
                 .children(self.click_message.iter().map(|message| {

crates/workspace2/src/pane.rs πŸ”—

@@ -7,7 +7,7 @@ use crate::{
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
-    actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext,
+    actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AppContext,
     AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable,
     FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render,
     ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
@@ -164,11 +164,6 @@ impl fmt::Debug for Event {
     }
 }
 
-struct FocusedView {
-    view: AnyWeakView,
-    focus_handle: FocusHandle,
-}
-
 pub struct Pane {
     focus_handle: FocusHandle,
     items: Vec<Box<dyn ItemHandle>>,
@@ -187,7 +182,7 @@ pub struct Pane {
     //     can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
     can_split: bool,
     //     render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
-    subscriptions: Vec<Subscription>,
+    _subscriptions: Vec<Subscription>,
     tab_bar_scroll_handle: ScrollHandle,
 }
 
@@ -231,6 +226,7 @@ pub struct NavigationEntry {
     pub timestamp: usize,
 }
 
+#[derive(Clone)]
 struct DraggedTab {
     pub pane: View<Pane>,
     pub ix: usize,
@@ -431,14 +427,10 @@ impl Pane {
             //         })
             //         .into_any()
             // }),
-            subscriptions,
+            _subscriptions: subscriptions,
         }
     }
 
-    pub(crate) fn workspace(&self) -> &WeakView<Workspace> {
-        &self.workspace
-    }
-
     pub fn has_focus(&self, cx: &WindowContext) -> bool {
         // todo!(); // inline this manually
         self.focus_handle.contains_focused(cx)
@@ -1467,21 +1459,6 @@ impl Pane {
         let label = item.tab_content(Some(detail), is_active, cx);
         let close_side = &ItemSettings::get_global(cx).close_position;
 
-        let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
-            false => (
-                cx.theme().colors().text_muted,
-                cx.theme().colors().tab_inactive_background,
-                cx.theme().colors().ghost_element_hover,
-                cx.theme().colors().ghost_element_active,
-            ),
-            true => (
-                cx.theme().colors().text,
-                cx.theme().colors().tab_active_background,
-                cx.theme().colors().element_hover,
-                cx.theme().colors().element_active,
-            ),
-        };
-
         let indicator = maybe!({
             let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
                 (true, _) => Color::Warning,
@@ -1497,56 +1474,55 @@ impl Pane {
         let is_last_item = ix == self.items.len() - 1;
         let position_relative_to_active_item = ix.cmp(&self.active_item_index);
 
-        let tab =
-            Tab::new(ix)
-                .position(if is_first_item {
-                    TabPosition::First
-                } else if is_last_item {
-                    TabPosition::Last
-                } else {
-                    TabPosition::Middle(position_relative_to_active_item)
-                })
-                .close_side(match close_side {
-                    ClosePosition::Left => ui::TabCloseSide::Start,
-                    ClosePosition::Right => ui::TabCloseSide::End,
-                })
-                .selected(is_active)
-                .on_click(cx.listener(move |pane: &mut Self, event, cx| {
-                    pane.activate_item(ix, true, true, cx)
-                }))
-                .on_drag({
-                    let pane = cx.view().clone();
-                    move |cx| {
-                        cx.build_view(|cx| DraggedTab {
-                            pane: pane.clone(),
-                            detail,
-                            item_id,
-                            is_active,
-                            ix,
-                        })
-                    }
-                })
-                .drag_over::<DraggedTab>(|tab| tab.bg(cx.theme().colors().tab_active_background))
-                .on_drop(
-                    cx.listener(move |this, dragged_tab: &View<DraggedTab>, cx| {
-                        this.handle_tab_drop(dragged_tab, ix, cx)
-                    }),
-                )
-                .when_some(item.tab_tooltip_text(cx), |tab, text| {
-                    tab.tooltip(move |cx| Tooltip::text(text.clone(), cx))
-                })
-                .start_slot::<Indicator>(indicator)
-                .end_slot(
-                    IconButton::new("close tab", Icon::Close)
-                        .icon_color(Color::Muted)
-                        .size(ButtonSize::None)
-                        .icon_size(IconSize::XSmall)
-                        .on_click(cx.listener(move |pane, _, cx| {
-                            pane.close_item_by_id(item_id, SaveIntent::Close, cx)
-                                .detach_and_log_err(cx);
-                        })),
-                )
-                .child(label);
+        let tab = Tab::new(ix)
+            .position(if is_first_item {
+                TabPosition::First
+            } else if is_last_item {
+                TabPosition::Last
+            } else {
+                TabPosition::Middle(position_relative_to_active_item)
+            })
+            .close_side(match close_side {
+                ClosePosition::Left => ui::TabCloseSide::Start,
+                ClosePosition::Right => ui::TabCloseSide::End,
+            })
+            .selected(is_active)
+            .on_click(
+                cx.listener(move |pane: &mut Self, _, cx| pane.activate_item(ix, true, true, cx)),
+            )
+            .on_drag(
+                DraggedTab {
+                    pane: cx.view().clone(),
+                    detail,
+                    item_id,
+                    is_active,
+                    ix,
+                },
+                |tab, cx| cx.build_view(|_| tab.clone()),
+            )
+            .drag_over::<DraggedTab>(|tab| tab.bg(cx.theme().colors().tab_active_background))
+            .drag_over::<ProjectEntryId>(|tab| tab.bg(gpui::red()))
+            .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
+                this.handle_tab_drop(dragged_tab, ix, cx)
+            }))
+            .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
+                this.handle_project_entry_drop(entry_id, cx)
+            }))
+            .when_some(item.tab_tooltip_text(cx), |tab, text| {
+                tab.tooltip(move |cx| Tooltip::text(text.clone(), cx))
+            })
+            .start_slot::<Indicator>(indicator)
+            .end_slot(
+                IconButton::new("close tab", Icon::Close)
+                    .icon_color(Color::Muted)
+                    .size(ButtonSize::None)
+                    .icon_size(IconSize::XSmall)
+                    .on_click(cx.listener(move |pane, _, cx| {
+                        pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+                            .detach_and_log_err(cx);
+                    })),
+            )
+            .child(label);
 
         let single_entry_to_resolve = {
             let item_entries = self.items[ix].project_entry_ids(cx);
@@ -1616,12 +1592,12 @@ impl Pane {
                         IconButton::new("plus", Icon::Plus)
                             .icon_size(IconSize::Small)
                             .on_click(cx.listener(|this, _, cx| {
-                                let menu = ContextMenu::build(cx, |menu, cx| {
+                                let menu = ContextMenu::build(cx, |menu, _| {
                                     menu.action("New File", NewFile.boxed_clone())
                                         .action("New Terminal", NewCenterTerminal.boxed_clone())
                                         .action("New Search", NewSearch.boxed_clone())
                                 });
-                                cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
+                                cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| {
                                     this.focus(cx);
                                     this.new_item_menu = None;
                                 })
@@ -1640,13 +1616,13 @@ impl Pane {
                         IconButton::new("split", Icon::Split)
                             .icon_size(IconSize::Small)
                             .on_click(cx.listener(|this, _, cx| {
-                                let menu = ContextMenu::build(cx, |menu, cx| {
+                                let menu = ContextMenu::build(cx, |menu, _| {
                                     menu.action("Split Right", SplitRight.boxed_clone())
                                         .action("Split Left", SplitLeft.boxed_clone())
                                         .action("Split Up", SplitUp.boxed_clone())
                                         .action("Split Down", SplitDown.boxed_clone())
                                 });
-                                cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
+                                cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| {
                                     this.focus(cx);
                                     this.split_item_menu = None;
                                 })
@@ -1677,11 +1653,13 @@ impl Pane {
                     .drag_over::<DraggedTab>(|bar| {
                         bar.bg(cx.theme().colors().tab_active_background)
                     })
-                    .on_drop(
-                        cx.listener(move |this, dragged_tab: &View<DraggedTab>, cx| {
-                            this.handle_tab_drop(dragged_tab, this.items.len(), cx)
-                        }),
-                    ),
+                    .drag_over::<ProjectEntryId>(|bar| bar.bg(gpui::red()))
+                    .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
+                        this.handle_tab_drop(dragged_tab, this.items.len(), cx)
+                    }))
+                    .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
+                        this.handle_project_entry_drop(entry_id, cx)
+                    })),
             )
     }
 
@@ -1743,16 +1721,15 @@ impl Pane {
 
     fn handle_tab_drop(
         &mut self,
-        dragged_tab: &View<DraggedTab>,
+        dragged_tab: &DraggedTab,
         ix: usize,
         cx: &mut ViewContext<'_, Pane>,
     ) {
-        let dragged_tab = dragged_tab.read(cx);
         let item_id = dragged_tab.item_id;
         let from_pane = dragged_tab.pane.clone();
         let to_pane = cx.view().clone();
         self.workspace
-            .update(cx, |workspace, cx| {
+            .update(cx, |_, cx| {
                 cx.defer(move |workspace, cx| {
                     workspace.move_item(from_pane, to_pane, item_id, ix, cx);
                 });
@@ -1760,28 +1737,69 @@ impl Pane {
             .log_err();
     }
 
+    fn handle_project_entry_drop(
+        &mut self,
+        project_entry_id: &ProjectEntryId,
+        cx: &mut ViewContext<'_, Pane>,
+    ) {
+        let to_pane = cx.view().downgrade();
+        let project_entry_id = *project_entry_id;
+        self.workspace
+            .update(cx, |_, cx| {
+                cx.defer(move |workspace, cx| {
+                    if let Some(path) = workspace
+                        .project()
+                        .read(cx)
+                        .path_for_entry(project_entry_id, cx)
+                    {
+                        workspace
+                            .open_path(path, Some(to_pane), true, cx)
+                            .detach_and_log_err(cx);
+                    }
+                });
+            })
+            .log_err();
+    }
+
     fn handle_split_tab_drop(
         &mut self,
-        dragged_tab: &View<DraggedTab>,
+        dragged_tab: &DraggedTab,
         split_direction: SplitDirection,
         cx: &mut ViewContext<'_, Pane>,
     ) {
-        let dragged_tab = dragged_tab.read(cx);
         let item_id = dragged_tab.item_id;
         let from_pane = dragged_tab.pane.clone();
         let to_pane = cx.view().clone();
         self.workspace
-            .update(cx, |workspace, cx| {
+            .update(cx, |_, cx| {
+                cx.defer(move |workspace, cx| {
+                    let pane = workspace.split_pane(to_pane, split_direction, cx);
+                    workspace.move_item(from_pane, pane, item_id, 0, cx);
+                });
+            })
+            .log_err();
+    }
+
+    fn handle_split_project_entry_drop(
+        &mut self,
+        project_entry_id: &ProjectEntryId,
+        split_direction: SplitDirection,
+        cx: &mut ViewContext<'_, Pane>,
+    ) {
+        let project_entry_id = *project_entry_id;
+        let current_pane = cx.view().clone();
+        self.workspace
+            .update(cx, |_, cx| {
                 cx.defer(move |workspace, cx| {
-                    let item = from_pane
+                    if let Some(path) = workspace
+                        .project()
                         .read(cx)
-                        .items()
-                        .find(|item| item.item_id() == item_id)
-                        .map(|item| item.boxed_clone());
-                    if let Some(item) = item {
-                        if let Some(item) = item.clone_on_split(workspace.database_id(), cx) {
-                            workspace.split_item(split_direction, item, cx);
-                        }
+                        .path_for_entry(project_entry_id, cx)
+                    {
+                        let pane = workspace.split_pane(current_pane, split_direction, cx);
+                        workspace
+                            .open_path(path, Some(pane.downgrade()), true, cx)
+                            .detach_and_log_err(cx);
                     }
                 });
             })
@@ -1799,8 +1817,6 @@ impl Render for Pane {
     type Element = Focusable<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        let this = cx.view().downgrade();
-
         v_stack()
             .key_context("Pane")
             .track_focus(&self.focus_handle)
@@ -1894,11 +1910,13 @@ impl Render for Pane {
                             .full()
                             .z_index(1)
                             .drag_over::<DraggedTab>(|style| style.bg(drag_target_color))
-                            .on_drop(cx.listener(
-                                move |this, dragged_tab: &View<DraggedTab>, cx| {
-                                    this.handle_tab_drop(dragged_tab, this.active_item_index(), cx)
-                                },
-                            )),
+                            .drag_over::<ProjectEntryId>(|style| style.bg(gpui::red()))
+                            .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
+                                this.handle_tab_drop(dragged_tab, this.active_item_index(), cx)
+                            }))
+                            .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
+                                this.handle_project_entry_drop(entry_id, cx)
+                            })),
                     )
                     .children(
                         [
@@ -1915,9 +1933,15 @@ impl Render for Pane {
                                 .invisible()
                                 .bg(drag_target_color)
                                 .drag_over::<DraggedTab>(|style| style.visible())
+                                .drag_over::<ProjectEntryId>(|style| style.visible())
+                                .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
+                                    this.handle_split_tab_drop(dragged_tab, direction, cx)
+                                }))
                                 .on_drop(cx.listener(
-                                    move |this, dragged_tab: &View<DraggedTab>, cx| {
-                                        this.handle_split_tab_drop(dragged_tab, direction, cx)
+                                    move |this, entry_id: &ProjectEntryId, cx| {
+                                        this.handle_split_project_entry_drop(
+                                            entry_id, direction, cx,
+                                        )
                                     },
                                 ));
                             match direction {

crates/workspace2/src/pane_group.rs πŸ”—

@@ -16,7 +16,7 @@ const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)
 const HORIZONTAL_MIN_SIZE: f32 = 80.;
 const VERTICAL_MIN_SIZE: f32 = 100.;
 
-#[derive(Clone, PartialEq)]
+#[derive(Clone)]
 pub struct PaneGroup {
     pub(crate) root: Member,
 }
@@ -121,7 +121,7 @@ impl PaneGroup {
     }
 }
 
-#[derive(Clone, PartialEq)]
+#[derive(Clone)]
 pub(crate) enum Member {
     Axis(PaneAxis),
     Pane(View<Pane>),
@@ -426,12 +426,6 @@ pub(crate) struct PaneAxis {
     pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 }
 
-impl PartialEq for PaneAxis {
-    fn eq(&self, other: &Self) -> bool {
-        todo!()
-    }
-}
-
 impl PaneAxis {
     pub fn new(axis: Axis, members: Vec<Member>) -> Self {
         let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
@@ -816,7 +810,7 @@ mod element {
                 proposed_current_pixel_change -= current_pixel_change;
             }
 
-            // todo!(reserialize workspace)
+            // todo!(schedule serialize)
             // workspace.schedule_serialize(cx);
             cx.notify();
         }
@@ -851,7 +845,7 @@ mod element {
 
                 cx.on_mouse_event({
                     let dragged_handle = dragged_handle.clone();
-                    move |e: &MouseDownEvent, phase, cx| {
+                    move |e: &MouseDownEvent, phase, _cx| {
                         if phase.bubble() && handle_bounds.contains(&e.position) {
                             dragged_handle.replace(Some(ix));
                         }
@@ -859,7 +853,7 @@ mod element {
                 });
                 cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
                     let dragged_handle = dragged_handle.borrow();
-                    if *dragged_handle == Some(ix) {
+                    if phase.bubble() && *dragged_handle == Some(ix) {
                         Self::compute_resize(&flexes, e, ix, axis, axis_bounds, cx)
                     }
                 });
@@ -912,7 +906,7 @@ mod element {
             let mut bounding_boxes = self.bounding_boxes.lock();
             bounding_boxes.clear();
 
-            for (ix, mut child) in self.children.iter_mut().enumerate() {
+            for (ix, child) in self.children.iter_mut().enumerate() {
                 //todo!(active_pane_magnification)
                 // If usign active pane magnification, need to switch to using
                 // 1 for all non-active panes, and then the magnification for the
@@ -949,7 +943,7 @@ mod element {
             cx.with_z_index(1, |cx| {
                 cx.on_mouse_event({
                     let state = state.clone();
-                    move |e: &MouseUpEvent, phase, cx| {
+                    move |_: &MouseUpEvent, phase, _cx| {
                         if phase.bubble() {
                             state.replace(None);
                         }
@@ -968,402 +962,4 @@ mod element {
     fn flex_values_in_bounds(flexes: &[f32]) -> bool {
         (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
     }
-    //     // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
-
-    //     // use gpui::{
-    //     //     geometry::{
-    //     //         rect::Bounds<Pixels>,
-    //     //         vector::{vec2f, Vector2F},
-    //     //     },
-    //     //     json::{self, ToJson},
-    //     //     platform::{CursorStyle, MouseButton},
-    //     //     scene::MouseDrag,
-    //     //     AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, Bounds<Pixels>Ext,
-    //     //     SizeConstraint, Vector2FExt, ViewContext,
-    //     // };
-
-    //     use crate::{
-    //         pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
-    //         Workspace, WorkspaceSettings,
-    //     };
-
-    //     pub struct PaneAxisElement {
-    //         axis: Axis,
-    //         basis: usize,
-    //         active_pane_ix: Option<usize>,
-    //         flexes: Rc<RefCell<Vec<f32>>>,
-    //         children: Vec<AnyElement<Workspace>>,
-    //         bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
-    //     }
-
-    //     impl PaneAxisElement {
-    //         pub fn new(
-    //             axis: Axis,
-    //             basis: usize,
-    //             flexes: Rc<RefCell<Vec<f32>>>,
-    //             bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
-    //         ) -> Self {
-    //             Self {
-    //                 axis,
-    //                 basis,
-    //                 flexes,
-    //                 bounding_boxes,
-    //                 active_pane_ix: None,
-    //                 children: Default::default(),
-    //             }
-    //         }
-
-    //         pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
-    //             self.active_pane_ix = active_pane_ix;
-    //         }
-
-    //         fn layout_children(
-    //             &mut self,
-    //             active_pane_magnification: f32,
-    //             constraint: SizeConstraint,
-    //             remaining_space: &mut f32,
-    //             remaining_flex: &mut f32,
-    //             cross_axis_max: &mut f32,
-    //             view: &mut Workspace,
-    //             cx: &mut ViewContext<Workspace>,
-    //         ) {
-    //             let flexes = self.flexes.borrow();
-    //             let cross_axis = self.axis.invert();
-    //             for (ix, child) in self.children.iter_mut().enumerate() {
-    //                 let flex = if active_pane_magnification != 1. {
-    //                     if let Some(active_pane_ix) = self.active_pane_ix {
-    //                         if ix == active_pane_ix {
-    //                             active_pane_magnification
-    //                         } else {
-    //                             1.
-    //                         }
-    //                     } else {
-    //                         1.
-    //                     }
-    //                 } else {
-    //                     flexes[ix]
-    //                 };
-
-    //                 let child_size = if *remaining_flex == 0.0 {
-    //                     *remaining_space
-    //                 } else {
-    //                     let space_per_flex = *remaining_space / *remaining_flex;
-    //                     space_per_flex * flex
-    //                 };
-
-    //                 let child_constraint = match self.axis {
-    //                     Axis::Horizontal => SizeConstraint::new(
-    //                         vec2f(child_size, constraint.min.y()),
-    //                         vec2f(child_size, constraint.max.y()),
-    //                     ),
-    //                     Axis::Vertical => SizeConstraint::new(
-    //                         vec2f(constraint.min.x(), child_size),
-    //                         vec2f(constraint.max.x(), child_size),
-    //                     ),
-    //                 };
-    //                 let child_size = child.layout(child_constraint, view, cx);
-    //                 *remaining_space -= child_size.along(self.axis);
-    //                 *remaining_flex -= flex;
-    //                 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
-    //             }
-    //         }
-
-    //         fn handle_resize(
-    //             flexes: Rc<RefCell<Vec<f32>>>,
-    //             axis: Axis,
-    //             preceding_ix: usize,
-    //             child_start: Vector2F,
-    //             drag_bounds: Bounds<Pixels>,
-    //         ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
-    //             let size = move |ix, flexes: &[f32]| {
-    //                 drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
-    //             };
-
-    //             move |drag, workspace: &mut Workspace, cx| {
-    //                 if drag.end {
-    //                     // TODO: Clear cascading resize state
-    //                     return;
-    //                 }
-    //                 let min_size = match axis {
-    //                     Axis::Horizontal => HORIZONTAL_MIN_SIZE,
-    //                     Axis::Vertical => VERTICAL_MIN_SIZE,
-    //                 };
-    //                 let mut flexes = flexes.borrow_mut();
-
-    //                 // Don't allow resizing to less than the minimum size, if elements are already too small
-    //                 if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
-    //                     return;
-    //                 }
-
-    //                 let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
-    //                     - size(preceding_ix, flexes.as_slice());
-
-    //                 let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
-    //                     let flex_change = pixel_dx / drag_bounds.length_along(axis);
-    //                     let current_target_flex = flexes[target_ix] + flex_change;
-    //                     let next_target_flex =
-    //                         flexes[(target_ix as isize + next) as usize] - flex_change;
-    //                     (current_target_flex, next_target_flex)
-    //                 };
-
-    //                 let mut successors = from_fn({
-    //                     let forward = proposed_current_pixel_change > 0.;
-    //                     let mut ix_offset = 0;
-    //                     let len = flexes.len();
-    //                     move || {
-    //                         let result = if forward {
-    //                             (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
-    //                         } else {
-    //                             (preceding_ix as isize - ix_offset as isize >= 0)
-    //                                 .then(|| preceding_ix - ix_offset)
-    //                         };
-
-    //                         ix_offset += 1;
-
-    //                         result
-    //                     }
-    //                 });
-
-    //                 while proposed_current_pixel_change.abs() > 0. {
-    //                     let Some(current_ix) = successors.next() else {
-    //                         break;
-    //                     };
-
-    //                     let next_target_size = f32::max(
-    //                         size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
-    //                         min_size,
-    //                     );
-
-    //                     let current_target_size = f32::max(
-    //                         size(current_ix, flexes.as_slice())
-    //                             + size(current_ix + 1, flexes.as_slice())
-    //                             - next_target_size,
-    //                         min_size,
-    //                     );
-
-    //                     let current_pixel_change =
-    //                         current_target_size - size(current_ix, flexes.as_slice());
-
-    //                     let (current_target_flex, next_target_flex) =
-    //                         flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
-
-    //                     flexes[current_ix] = current_target_flex;
-    //                     flexes[current_ix + 1] = next_target_flex;
-
-    //                     proposed_current_pixel_change -= current_pixel_change;
-    //                 }
-
-    //                 workspace.schedule_serialize(cx);
-    //                 cx.notify();
-    //             }
-    //         }
-    //     }
-
-    //     impl Extend<AnyElement<Workspace>> for PaneAxisElement {
-    //         fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
-    //             self.children.extend(children);
-    //         }
-    //     }
-
-    //     impl Element<Workspace> for PaneAxisElement {
-    //         type LayoutState = f32;
-    //         type PaintState = ();
-
-    //         fn layout(
-    //             &mut self,
-    //             constraint: SizeConstraint,
-    //             view: &mut Workspace,
-    //             cx: &mut ViewContext<Workspace>,
-    //         ) -> (Vector2F, Self::LayoutState) {
-    //             debug_assert!(self.children.len() == self.flexes.borrow().len());
-
-    //             let active_pane_magnification =
-    //                 settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
-
-    //             let mut remaining_flex = 0.;
-
-    //             if active_pane_magnification != 1. {
-    //                 let active_pane_flex = self
-    //                     .active_pane_ix
-    //                     .map(|_| active_pane_magnification)
-    //                     .unwrap_or(1.);
-    //                 remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
-    //             } else {
-    //                 for flex in self.flexes.borrow().iter() {
-    //                     remaining_flex += flex;
-    //                 }
-    //             }
-
-    //             let mut cross_axis_max: f32 = 0.0;
-    //             let mut remaining_space = constraint.max_along(self.axis);
-
-    //             if remaining_space.is_infinite() {
-    //                 panic!("flex contains flexible children but has an infinite constraint along the flex axis");
-    //             }
-
-    //             self.layout_children(
-    //                 active_pane_magnification,
-    //                 constraint,
-    //                 &mut remaining_space,
-    //                 &mut remaining_flex,
-    //                 &mut cross_axis_max,
-    //                 view,
-    //                 cx,
-    //             );
-
-    //             let mut size = match self.axis {
-    //                 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
-    //                 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
-    //             };
-
-    //             if constraint.min.x().is_finite() {
-    //                 size.set_x(size.x().max(constraint.min.x()));
-    //             }
-    //             if constraint.min.y().is_finite() {
-    //                 size.set_y(size.y().max(constraint.min.y()));
-    //             }
-
-    //             if size.x() > constraint.max.x() {
-    //                 size.set_x(constraint.max.x());
-    //             }
-    //             if size.y() > constraint.max.y() {
-    //                 size.set_y(constraint.max.y());
-    //             }
-
-    //             (size, remaining_space)
-    //         }
-
-    //         fn paint(
-    //             &mut self,
-    //             bounds: Bounds<Pixels>,
-    //             visible_bounds: Bounds<Pixels>,
-    //             remaining_space: &mut Self::LayoutState,
-    //             view: &mut Workspace,
-    //             cx: &mut ViewContext<Workspace>,
-    //         ) -> Self::PaintState {
-    //             let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
-    //             let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-    //             let overflowing = *remaining_space < 0.;
-    //             if overflowing {
-    //                 cx.scene().push_layer(Some(visible_bounds));
-    //             }
-
-    //             let mut child_origin = bounds.origin();
-
-    //             let mut bounding_boxes = self.bounding_boxes.borrow_mut();
-    //             bounding_boxes.clear();
-
-    //             let mut children_iter = self.children.iter_mut().enumerate().peekable();
-    //             while let Some((ix, child)) = children_iter.next() {
-    //                 let child_start = child_origin.clone();
-    //                 child.paint(child_origin, visible_bounds, view, cx);
-
-    //                 bounding_boxes.push(Some(Bounds<Pixels>::new(child_origin, child.size())));
-
-    //                 match self.axis {
-    //                     Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
-    //                     Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
-    //                 }
-
-    //                 if can_resize && children_iter.peek().is_some() {
-    //                     cx.scene().push_stacking_context(None, None);
-
-    //                     let handle_origin = match self.axis {
-    //                         Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
-    //                         Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
-    //                     };
-
-    //                     let handle_bounds = match self.axis {
-    //                         Axis::Horizontal => Bounds<Pixels>::new(
-    //                             handle_origin,
-    //                             vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
-    //                         ),
-    //                         Axis::Vertical => Bounds<Pixels>::new(
-    //                             handle_origin,
-    //                             vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
-    //                         ),
-    //                     };
-
-    //                     let style = match self.axis {
-    //                         Axis::Horizontal => CursorStyle::ResizeLeftRight,
-    //                         Axis::Vertical => CursorStyle::ResizeUpDown,
-    //                     };
-
-    //                     cx.scene().push_cursor_region(CursorRegion {
-    //                         bounds: handle_bounds,
-    //                         style,
-    //                     });
-
-    //                     enum ResizeHandle {}
-    //                     let mut mouse_region = MouseRegion::new::<ResizeHandle>(
-    //                         cx.view_id(),
-    //                         self.basis + ix,
-    //                         handle_bounds,
-    //                     );
-    //                     mouse_region = mouse_region
-    //                         .on_drag(
-    //                             MouseButton::Left,
-    //                             Self::handle_resize(
-    //                                 self.flexes.clone(),
-    //                                 self.axis,
-    //                                 ix,
-    //                                 child_start,
-    //                                 visible_bounds.clone(),
-    //                             ),
-    //                         )
-    //                         .on_click(MouseButton::Left, {
-    //                             let flexes = self.flexes.clone();
-    //                             move |e, v: &mut Workspace, cx| {
-    //                                 if e.click_count >= 2 {
-    //                                     let mut borrow = flexes.borrow_mut();
-    //                                     *borrow = vec![1.; borrow.len()];
-    //                                     v.schedule_serialize(cx);
-    //                                     cx.notify();
-    //                                 }
-    //                             }
-    //                         });
-    //                     cx.scene().push_mouse_region(mouse_region);
-
-    //                     cx.scene().pop_stacking_context();
-    //                 }
-    //             }
-
-    //             if overflowing {
-    //                 cx.scene().pop_layer();
-    //             }
-    //         }
-
-    //         fn rect_for_text_range(
-    //             &self,
-    //             range_utf16: Range<usize>,
-    //             _: Bounds<Pixels>,
-    //             _: Bounds<Pixels>,
-    //             _: &Self::LayoutState,
-    //             _: &Self::PaintState,
-    //             view: &Workspace,
-    //             cx: &ViewContext<Workspace>,
-    //         ) -> Option<Bounds<Pixels>> {
-    //             self.children
-    //                 .iter()
-    //                 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
-    //         }
-
-    //         fn debug(
-    //             &self,
-    //             bounds: Bounds<Pixels>,
-    //             _: &Self::LayoutState,
-    //             _: &Self::PaintState,
-    //             view: &Workspace,
-    //             cx: &ViewContext<Workspace>,
-    //         ) -> json::Value {
-    //             serde_json::json!({
-    //                 "type": "PaneAxis",
-    //                 "bounds": bounds.to_json(),
-    //                 "axis": self.axis.to_json(),
-    //                 "flexes": *self.flexes.borrow(),
-    //                 "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
-    //             })
-    //         }
-    //     }
 }

crates/workspace2/src/searchable.rs πŸ”—

@@ -193,7 +193,7 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
         cx: &mut WindowContext,
     ) -> Task<Vec<Box<dyn Any + Send>>> {
         let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
-        cx.spawn(|cx| async {
+        cx.spawn(|_| async {
             let matches = matches.await;
             matches
                 .into_iter()
@@ -253,7 +253,7 @@ pub trait WeakSearchableItemHandle: WeakItemHandle {
 }
 
 impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+    fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
         Some(Box::new(self.upgrade()?))
     }
 

crates/workspace2/src/status_bar.rs πŸ”—

@@ -51,14 +51,14 @@ impl Render for StatusBar {
 }
 
 impl StatusBar {
-    fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+    fn render_left_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
         h_stack()
             .items_center()
             .gap_2()
             .children(self.left_items.iter().map(|item| item.to_any()))
     }
 
-    fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+    fn render_right_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
         h_stack()
             .items_center()
             .gap_2()

crates/workspace2/src/toolbar.rs πŸ”—

@@ -109,8 +109,22 @@ impl Render for Toolbar {
             .child(
                 h_stack()
                     .justify_between()
-                    .child(h_stack().children(self.left_items().map(|item| item.to_any())))
-                    .child(h_stack().children(self.right_items().map(|item| item.to_any()))),
+                    .when(self.left_items().count() > 0, |this| {
+                        this.child(
+                            h_stack()
+                                .flex_1()
+                                .justify_start()
+                                .children(self.left_items().map(|item| item.to_any())),
+                        )
+                    })
+                    .when(self.right_items().count() > 0, |this| {
+                        this.child(
+                            h_stack()
+                                .flex_1()
+                                .justify_end()
+                                .children(self.right_items().map(|item| item.to_any())),
+                        )
+                    }),
             )
             .children(secondary_item)
     }

crates/workspace2/src/workspace2.rs πŸ”—

@@ -1,15 +1,11 @@
-#![allow(unused_variables, dead_code, unused_mut)]
-// todo!() this is to make transition easier.
-
 pub mod dock;
 pub mod item;
+mod modal_layer;
 pub mod notifications;
 pub mod pane;
 pub mod pane_group;
 mod persistence;
 pub mod searchable;
-// todo!()
-mod modal_layer;
 pub mod shared_screen;
 mod status_bar;
 mod toolbar;
@@ -236,14 +232,14 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     cx.on_action({
         let app_state = Arc::downgrade(&app_state);
         move |_: &Open, cx: &mut AppContext| {
-            let mut paths = cx.prompt_for_paths(PathPromptOptions {
+            let paths = cx.prompt_for_paths(PathPromptOptions {
                 files: true,
                 directories: true,
                 multiple: true,
             });
 
             if let Some(app_state) = app_state.upgrade() {
-                cx.spawn(move |mut cx| async move {
+                cx.spawn(move |cx| async move {
                     if let Some(paths) = paths.await.log_err().flatten() {
                         cx.update(|cx| {
                             open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
@@ -458,7 +454,7 @@ pub struct Workspace {
     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
     database_id: WorkspaceId,
     app_state: Arc<AppState>,
-    subscriptions: Vec<Subscription>,
+    _subscriptions: Vec<Subscription>,
     _apply_leader_updates: Task<Result<()>>,
     _observe_current_user: Task<Result<()>>,
     _schedule_serialize: Option<Task<()>>,
@@ -590,12 +586,9 @@ impl Workspace {
         let left_dock = cx.build_view(|cx| Dock::new(DockPosition::Left, cx));
         let bottom_dock = cx.build_view(|cx| Dock::new(DockPosition::Bottom, cx));
         let right_dock = cx.build_view(|cx| Dock::new(DockPosition::Right, cx));
-        let left_dock_buttons =
-            cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
-        let bottom_dock_buttons =
-            cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
-        let right_dock_buttons =
-            cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
+        let left_dock_buttons = cx.build_view(|cx| PanelButtons::new(left_dock.clone(), cx));
+        let bottom_dock_buttons = cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
+        let right_dock_buttons = cx.build_view(|cx| PanelButtons::new(right_dock.clone(), cx));
         let status_bar = cx.build_view(|cx| {
             let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
             status_bar.add_left_item(left_dock_buttons, cx);
@@ -604,8 +597,7 @@ impl Workspace {
             status_bar
         });
 
-        let workspace_handle = cx.view().downgrade();
-        let modal_layer = cx.build_view(|cx| ModalLayer::new());
+        let modal_layer = cx.build_view(|_| ModalLayer::new());
 
         // todo!()
         // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
@@ -703,7 +695,7 @@ impl Workspace {
             _apply_leader_updates,
             _schedule_serialize: None,
             leader_updates_tx,
-            subscriptions,
+            _subscriptions: subscriptions,
             pane_history_timestamp,
             workspace_actions: Default::default(),
             // This data will be incorrect, but it will be overwritten by the time it needs to be used.
@@ -763,7 +755,7 @@ impl Workspace {
             };
 
             let window = if let Some(window) = requesting_window {
-                cx.update_window(window.into(), |old_workspace, cx| {
+                cx.update_window(window.into(), |_, cx| {
                     cx.replace_root_view(|cx| {
                         Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
                     });
@@ -1185,8 +1177,7 @@ impl Workspace {
     }
 
     fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
-        let save_all = self
-            .save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
+        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
             .detach_and_log_err(cx);
     }
 
@@ -1216,7 +1207,7 @@ impl Workspace {
         cx.spawn(|workspace, mut cx| async move {
             // Override save mode and display "Save all files" prompt
             if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-                let mut answer = workspace.update(&mut cx, |_, cx| {
+                let answer = workspace.update(&mut cx, |_, cx| {
                     let prompt = Pane::file_names_for_prompt(
                         &mut dirty_items.iter().map(|(_, handle)| handle),
                         dirty_items.len(),
@@ -1261,7 +1252,7 @@ impl Workspace {
     }
 
     pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
-        let mut paths = cx.prompt_for_paths(PathPromptOptions {
+        let paths = cx.prompt_for_paths(PathPromptOptions {
             files: true,
             directories: true,
             multiple: true,
@@ -1390,7 +1381,7 @@ impl Workspace {
     }
 
     fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
-        let mut paths = cx.prompt_for_paths(PathPromptOptions {
+        let paths = cx.prompt_for_paths(PathPromptOptions {
             files: false,
             directories: true,
             multiple: true,
@@ -1670,6 +1661,8 @@ impl Workspace {
         None
     }
 
+    // todo!("implement zoom")
+    #[allow(unused)]
     fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
         for pane in &self.panes {
             pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
@@ -2043,20 +2036,20 @@ impl Workspace {
             _ => bounding_box.center(),
         };
 
-        let distance_to_next = 1.; //todo(pane dividers styling)
+        let distance_to_next = 8.; //todo(pane dividers styling)
 
         let target = match direction {
             SplitDirection::Left => {
-                Point::new(bounding_box.origin.x - distance_to_next.into(), center.y)
+                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
             }
             SplitDirection::Right => {
                 Point::new(bounding_box.right() + distance_to_next.into(), center.y)
             }
             SplitDirection::Up => {
-                Point::new(center.x, bounding_box.origin.y - distance_to_next.into())
+                Point::new(center.x, bounding_box.top() - distance_to_next.into())
             }
             SplitDirection::Down => {
-                Point::new(center.x, bounding_box.top() + distance_to_next.into())
+                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
             }
         };
         self.center.pane_at_pixel_position(target)
@@ -2574,7 +2567,7 @@ impl Workspace {
     //         }
     //     }
 
-    fn render_notifications(&self, cx: &ViewContext<Self>) -> Option<Div> {
+    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
         if self.notifications.is_empty() {
             None
         } else {
@@ -3005,6 +2998,7 @@ impl Workspace {
         cx.notify();
     }
 
+    #[allow(unused)]
     fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
         self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
             cx.background_executor()
@@ -3143,12 +3137,7 @@ impl Workspace {
         cx: &mut ViewContext<Workspace>,
     ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
         cx.spawn(|workspace, mut cx| async move {
-            let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| {
-                (
-                    workspace.project().clone(),
-                    workspace.last_active_center_pane.clone(),
-                )
-            })?;
+            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
 
             let mut center_group = None;
             let mut center_items = None;
@@ -3293,7 +3282,7 @@ impl Workspace {
             .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
                 workspace.swap_pane_in_direction(action.0, cx)
             }))
-            .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
+            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
                 this.toggle_dock(DockPosition::Left, cx);
             }))
             .on_action(
@@ -3313,42 +3302,12 @@ impl Workspace {
             )
             .on_action(cx.listener(Workspace::open))
             .on_action(cx.listener(Workspace::close_window))
-
-        //     cx.add_action(Workspace::activate_pane_at_index);
-        //     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
-        //         workspace.reopen_closed_item(cx).detach();
-        //     });
-        //     cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
-        //         workspace
-        //             .go_back(workspace.active_pane().downgrade(), cx)
-        //             .detach();
-        //     });
-        //     cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
-        //         workspace
-        //             .go_forward(workspace.active_pane().downgrade(), cx)
-        //             .detach();
-        //     });
-
-        //     cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
-        //         cx.spawn(|workspace, mut cx| async move {
-        //             let err = install_cli::install_cli(&cx)
-        //                 .await
-        //                 .context("Failed to create CLI symlink");
-
-        //             workspace.update(&mut cx, |workspace, cx| {
-        //                 if matches!(err, Err(_)) {
-        //                     err.notify_err(workspace, cx);
-        //                 } else {
-        //                     workspace.show_notification(1, cx, |cx| {
-        //                         cx.build_view(|_| {
-        //                             MessageNotification::new("Successfully installed the `zed` binary")
-        //                         })
-        //                     });
-        //                 }
-        //             })
-        //         })
-        //         .detach();
-        //     });
+            .on_action(cx.listener(Workspace::activate_pane_at_index))
+            .on_action(
+                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
+                    workspace.reopen_closed_item(cx).detach();
+                }),
+            )
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -3418,7 +3377,7 @@ impl Workspace {
         self
     }
 
-    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
+    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
         let mut div = div
             .on_action(cx.listener(Self::close_inactive_items_and_panes))
             .on_action(cx.listener(Self::close_all_items_and_panes))
@@ -3578,9 +3537,7 @@ impl FocusableView for Workspace {
     }
 }
 
-struct WorkspaceBounds(Bounds<Pixels>);
-
-#[derive(Render)]
+#[derive(Clone, Render)]
 struct DraggedDock(DockPosition);
 
 impl Render for Workspace {
@@ -3628,7 +3585,7 @@ impl Render for Workspace {
                     .border_b()
                     .border_color(cx.theme().colors().border)
                     .child(
-                        canvas(cx.listener(|workspace, bounds, cx| {
+                        canvas(cx.listener(|workspace, bounds, _| {
                             workspace.bounds = *bounds;
                         }))
                         .absolute()
@@ -3636,7 +3593,7 @@ impl Render for Workspace {
                     )
                     .on_drag_move(
                         cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
-                            match e.drag.read(cx).0 {
+                            match e.drag(cx).0 {
                                 DockPosition::Left => {
                                     let size = workspace.bounds.left() + e.event.position.x;
                                     workspace.left_dock.update(cx, |left_dock, cx| {
@@ -4056,7 +4013,7 @@ async fn join_channel_internal(
     active_call: &Model<ActiveCall>,
     cx: &mut AsyncAppContext,
 ) -> Result<bool> {
-    let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
+    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
         let Some(room) = active_call.room().map(|room| room.read(cx)) else {
             return (false, None);
         };
@@ -4421,7 +4378,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
     }
 
     cx.spawn(|mut cx| async move {
-        if let Some(mut prompt) = prompt {
+        if let Some(prompt) = prompt {
             let answer = prompt.await?;
             if answer != 0 {
                 return Ok(());
@@ -4460,6 +4417,8 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
 
 #[cfg(test)]
 mod tests {
+    use std::{cell::RefCell, rc::Rc};
+
     use super::*;
     use crate::item::{
         test::{TestItem, TestProjectItem},
@@ -4470,7 +4429,6 @@ mod tests {
     use project::{Project, ProjectEntryId};
     use serde_json::json;
     use settings::SettingsStore;
-    use std::{cell::RefCell, rc::Rc};
 
     #[gpui::test]
     async fn test_tab_disambiguation(cx: &mut TestAppContext) {
@@ -4544,7 +4502,7 @@ mod tests {
         let project = Project::test(fs, ["root1".as_ref()], cx).await;
         let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
         let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
-        let worktree_id = project.read_with(cx, |project, cx| {
+        let worktree_id = project.update(cx, |project, cx| {
             project.worktrees().next().unwrap().read(cx).id()
         });
 
@@ -4557,7 +4515,7 @@ mod tests {
 
         // Add an item to an empty pane
         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
-        project.read_with(cx, |project, cx| {
+        project.update(cx, |project, cx| {
             assert_eq!(
                 project.active_entry(),
                 project
@@ -4570,7 +4528,7 @@ mod tests {
         // Add a second item to a non-empty pane
         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
         assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
-        project.read_with(cx, |project, cx| {
+        project.update(cx, |project, cx| {
             assert_eq!(
                 project.active_entry(),
                 project
@@ -4586,7 +4544,7 @@ mod tests {
         .await
         .unwrap();
         assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
-        project.read_with(cx, |project, cx| {
+        project.update(cx, |project, cx| {
             assert_eq!(
                 project.active_entry(),
                 project
@@ -4970,14 +4928,14 @@ mod tests {
             item.is_dirty = true;
             cx.blur();
         });
-        cx.executor().run_until_parked();
+        cx.run_until_parked();
         item.update(cx, |item, _| assert_eq!(item.save_count, 5));
 
         // Ensure autosave is prevented for deleted files also when closing the buffer.
         let _close_items = pane.update(cx, |pane, cx| {
             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
         });
-        cx.executor().run_until_parked();
+        cx.run_until_parked();
         assert!(cx.has_pending_prompt());
         item.update(cx, |item, _| assert_eq!(item.save_count, 5));
     }
@@ -5036,363 +4994,362 @@ mod tests {
         });
     }
 
-    //     #[gpui::test]
-    //     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
-    //         init_test(cx);
-    //         let fs = FakeFs::new(cx.executor());
-
-    //         let project = Project::test(fs, [], cx).await;
-    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-    //         let workspace = window.root(cx);
-
-    //         let panel = workspace.update(cx, |workspace, cx| {
-    //             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-    //             workspace.add_panel(panel.clone(), cx);
-
-    //             workspace
-    //                 .right_dock()
-    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-    //             panel
-    //         });
-
-    //         let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
-    //         pane.update(cx, |pane, cx| {
-    //             let item = cx.build_view(|_| TestItem::new(cx));
-    //             pane.add_item(Box::new(item), true, true, None, cx);
-    //         });
-
-    //         // Transfer focus from center to panel
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_panel_focus::<TestPanel>(cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert!(!panel.is_zoomed(cx));
-    //             assert!(panel.has_focus(cx));
-    //         });
-
-    //         // Transfer focus from panel to center
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_panel_focus::<TestPanel>(cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert!(!panel.is_zoomed(cx));
-    //             assert!(!panel.has_focus(cx));
-    //         });
-
-    //         // Close the dock
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_dock(DockPosition::Right, cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(!workspace.right_dock().read(cx).is_open());
-    //             assert!(!panel.is_zoomed(cx));
-    //             assert!(!panel.has_focus(cx));
-    //         });
-
-    //         // Open the dock
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_dock(DockPosition::Right, cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert!(!panel.is_zoomed(cx));
-    //             assert!(panel.has_focus(cx));
-    //         });
-
-    //         // Focus and zoom panel
-    //         panel.update(cx, |panel, cx| {
-    //             cx.focus_self();
-    //             panel.set_zoomed(true, cx)
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert!(panel.is_zoomed(cx));
-    //             assert!(panel.has_focus(cx));
-    //         });
-
-    //         // Transfer focus to the center closes the dock
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_panel_focus::<TestPanel>(cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(!workspace.right_dock().read(cx).is_open());
-    //             assert!(panel.is_zoomed(cx));
-    //             assert!(!panel.has_focus(cx));
-    //         });
-
-    //         // Transferring focus back to the panel keeps it zoomed
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_panel_focus::<TestPanel>(cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert!(panel.is_zoomed(cx));
-    //             assert!(panel.has_focus(cx));
-    //         });
-
-    //         // Close the dock while it is zoomed
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_dock(DockPosition::Right, cx)
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(!workspace.right_dock().read(cx).is_open());
-    //             assert!(panel.is_zoomed(cx));
-    //             assert!(workspace.zoomed.is_none());
-    //             assert!(!panel.has_focus(cx));
-    //         });
-
-    //         // Opening the dock, when it's zoomed, retains focus
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_dock(DockPosition::Right, cx)
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert!(panel.is_zoomed(cx));
-    //             assert!(workspace.zoomed.is_some());
-    //             assert!(panel.has_focus(cx));
-    //         });
-
-    //         // Unzoom and close the panel, zoom the active pane.
-    //         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_dock(DockPosition::Right, cx)
-    //         });
-    //         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
+    // #[gpui::test]
+    // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
+    //     init_test(cx);
+    //     let fs = FakeFs::new(cx.executor());
+
+    //     let project = Project::test(fs, [], cx).await;
+    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+    //     let panel = workspace.update(cx, |workspace, cx| {
+    //         let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
+    //         workspace.add_panel(panel.clone(), cx);
+
+    //         workspace
+    //             .right_dock()
+    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+    //         panel
+    //     });
+
+    //     let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+    //     pane.update(cx, |pane, cx| {
+    //         let item = cx.build_view(|cx| TestItem::new(cx));
+    //         pane.add_item(Box::new(item), true, true, None, cx);
+    //     });
+
+    //     // Transfer focus from center to panel
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_panel_focus::<TestPanel>(cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert!(!panel.is_zoomed(cx));
+    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Transfer focus from panel to center
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_panel_focus::<TestPanel>(cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert!(!panel.is_zoomed(cx));
+    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Close the dock
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_dock(DockPosition::Right, cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(!workspace.right_dock().read(cx).is_open());
+    //         assert!(!panel.is_zoomed(cx));
+    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Open the dock
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_dock(DockPosition::Right, cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert!(!panel.is_zoomed(cx));
+    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Focus and zoom panel
+    //     panel.update(cx, |panel, cx| {
+    //         cx.focus_self();
+    //         panel.set_zoomed(true, cx)
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert!(panel.is_zoomed(cx));
+    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Transfer focus to the center closes the dock
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_panel_focus::<TestPanel>(cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(!workspace.right_dock().read(cx).is_open());
+    //         assert!(panel.is_zoomed(cx));
+    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Transferring focus back to the panel keeps it zoomed
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_panel_focus::<TestPanel>(cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert!(panel.is_zoomed(cx));
+    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Close the dock while it is zoomed
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_dock(DockPosition::Right, cx)
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(!workspace.right_dock().read(cx).is_open());
+    //         assert!(panel.is_zoomed(cx));
+    //         assert!(workspace.zoomed.is_none());
+    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Opening the dock, when it's zoomed, retains focus
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_dock(DockPosition::Right, cx)
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert!(panel.is_zoomed(cx));
+    //         assert!(workspace.zoomed.is_some());
+    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
+    //     });
+
+    //     // Unzoom and close the panel, zoom the active pane.
+    //     panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_dock(DockPosition::Right, cx)
+    //     });
+    //     pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
+
+    //     // Opening a dock unzooms the pane.
+    //     workspace.update(cx, |workspace, cx| {
+    //         workspace.toggle_dock(DockPosition::Right, cx)
+    //     });
+    //     workspace.update(cx, |workspace, cx| {
+    //         let pane = pane.read(cx);
+    //         assert!(!pane.is_zoomed());
+    //         assert!(!pane.focus_handle(cx).is_focused(cx));
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert!(workspace.zoomed.is_none());
+    //     });
+    // }
 
-    //         // Opening a dock unzooms the pane.
-    //         workspace.update(cx, |workspace, cx| {
-    //             workspace.toggle_dock(DockPosition::Right, cx)
+    // #[gpui::test]
+    // async fn test_panels(cx: &mut gpui::TestAppContext) {
+    //     init_test(cx);
+    //     let fs = FakeFs::new(cx.executor());
+
+    //     let project = Project::test(fs, [], cx).await;
+    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+    //     let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
+    //         // Add panel_1 on the left, panel_2 on the right.
+    //         let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx));
+    //         workspace.add_panel(panel_1.clone(), cx);
+    //         workspace
+    //             .left_dock()
+    //             .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
+    //         let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
+    //         workspace.add_panel(panel_2.clone(), cx);
+    //         workspace
+    //             .right_dock()
+    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+    //         let left_dock = workspace.left_dock();
+    //         assert_eq!(
+    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
+    //             panel_1.panel_id()
+    //         );
+    //         assert_eq!(
+    //             left_dock.read(cx).active_panel_size(cx).unwrap(),
+    //             panel_1.size(cx)
+    //         );
+
+    //         left_dock.update(cx, |left_dock, cx| {
+    //             left_dock.resize_active_panel(Some(1337.), cx)
     //         });
-    //         workspace.update(cx, |workspace, cx| {
-    //             let pane = pane.read(cx);
-    //             assert!(!pane.is_zoomed());
-    //             assert!(!pane.has_focus());
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert!(workspace.zoomed.is_none());
-    //         });
-    //     }
-
-    //     #[gpui::test]
-    //     async fn test_panels(cx: &mut gpui::TestAppContext) {
-    //         init_test(cx);
-    //         let fs = FakeFs::new(cx.executor());
-
-    //         let project = Project::test(fs, [], cx).await;
-    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-    //         let workspace = window.root(cx);
-
-    //         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
-    //             // Add panel_1 on the left, panel_2 on the right.
-    //             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
-    //             workspace.add_panel(panel_1.clone(), cx);
+    //         assert_eq!(
     //             workspace
-    //                 .left_dock()
-    //                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
-    //             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-    //             workspace.add_panel(panel_2.clone(), cx);
+    //                 .right_dock()
+    //                 .read(cx)
+    //                 .visible_panel()
+    //                 .unwrap()
+    //                 .panel_id(),
+    //             panel_2.panel_id(),
+    //         );
+
+    //         (panel_1, panel_2)
+    //     });
+
+    //     // Move panel_1 to the right
+    //     panel_1.update(cx, |panel_1, cx| {
+    //         panel_1.set_position(DockPosition::Right, cx)
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
+    //         // Since it was the only panel on the left, the left dock should now be closed.
+    //         assert!(!workspace.left_dock().read(cx).is_open());
+    //         assert!(workspace.left_dock().read(cx).visible_panel().is_none());
+    //         let right_dock = workspace.right_dock();
+    //         assert_eq!(
+    //             right_dock.read(cx).visible_panel().unwrap().panel_id(),
+    //             panel_1.panel_id()
+    //         );
+    //         assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+
+    //         // Now we move panel_2Β to the left
+    //         panel_2.set_position(DockPosition::Left, cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         // Since panel_2 was not visible on the right, we don't open the left dock.
+    //         assert!(!workspace.left_dock().read(cx).is_open());
+    //         // And the right dock is unaffected in it's displaying of panel_1
+    //         assert!(workspace.right_dock().read(cx).is_open());
+    //         assert_eq!(
     //             workspace
     //                 .right_dock()
-    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-    //             let left_dock = workspace.left_dock();
-    //             assert_eq!(
-    //                 left_dock.read(cx).visible_panel().unwrap().id(),
-    //                 panel_1.id()
-    //             );
-    //             assert_eq!(
-    //                 left_dock.read(cx).active_panel_size(cx).unwrap(),
-    //                 panel_1.size(cx)
-    //             );
-
-    //             left_dock.update(cx, |left_dock, cx| {
-    //                 left_dock.resize_active_panel(Some(1337.), cx)
-    //             });
-    //             assert_eq!(
-    //                 workspace
-    //                     .right_dock()
-    //                     .read(cx)
-    //                     .visible_panel()
-    //                     .unwrap()
-    //                     .id(),
-    //                 panel_2.id()
-    //             );
-
-    //             (panel_1, panel_2)
-    //         });
-
-    //         // Move panel_1 to the right
-    //         panel_1.update(cx, |panel_1, cx| {
-    //             panel_1.set_position(DockPosition::Right, cx)
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
-    //             // Since it was the only panel on the left, the left dock should now be closed.
-    //             assert!(!workspace.left_dock().read(cx).is_open());
-    //             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
-    //             let right_dock = workspace.right_dock();
-    //             assert_eq!(
-    //                 right_dock.read(cx).visible_panel().unwrap().id(),
-    //                 panel_1.id()
-    //             );
-    //             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
-
-    //             // Now we move panel_2Β to the left
-    //             panel_2.set_position(DockPosition::Left, cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             // Since panel_2 was not visible on the right, we don't open the left dock.
-    //             assert!(!workspace.left_dock().read(cx).is_open());
-    //             // And the right dock is unaffected in it's displaying of panel_1
-    //             assert!(workspace.right_dock().read(cx).is_open());
-    //             assert_eq!(
-    //                 workspace
-    //                     .right_dock()
-    //                     .read(cx)
-    //                     .visible_panel()
-    //                     .unwrap()
-    //                     .id(),
-    //                 panel_1.id()
-    //             );
-    //         });
-
-    //         // Move panel_1 back to the left
-    //         panel_1.update(cx, |panel_1, cx| {
-    //             panel_1.set_position(DockPosition::Left, cx)
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
-    //             let left_dock = workspace.left_dock();
-    //             assert!(left_dock.read(cx).is_open());
-    //             assert_eq!(
-    //                 left_dock.read(cx).visible_panel().unwrap().id(),
-    //                 panel_1.id()
-    //             );
-    //             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
-    //             // And right the dock should be closed as it no longer has any panels.
-    //             assert!(!workspace.right_dock().read(cx).is_open());
-
-    //             // Now we move panel_1 to the bottom
-    //             panel_1.set_position(DockPosition::Bottom, cx);
-    //         });
-
-    //         workspace.update(cx, |workspace, cx| {
-    //             // Since panel_1 was visible on the left, we close the left dock.
-    //             assert!(!workspace.left_dock().read(cx).is_open());
-    //             // The bottom dock is sized based on the panel's default size,
-    //             // since the panel orientation changed from vertical to horizontal.
-    //             let bottom_dock = workspace.bottom_dock();
-    //             assert_eq!(
-    //                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
-    //                 panel_1.size(cx),
-    //             );
-    //             // Close bottom dock and move panel_1 back to the left.
-    //             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
-    //             panel_1.set_position(DockPosition::Left, cx);
-    //         });
-
-    //         // Emit activated event on panel 1
-    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
-
-    //         // Now the left dock is open and panel_1 is active and focused.
-    //         workspace.update(cx, |workspace, cx| {
-    //             let left_dock = workspace.left_dock();
-    //             assert!(left_dock.read(cx).is_open());
-    //             assert_eq!(
-    //                 left_dock.read(cx).visible_panel().unwrap().id(),
-    //                 panel_1.id()
-    //             );
-    //             assert!(panel_1.is_focused(cx));
-    //         });
-
-    //         // Emit closed event on panel 2, which is not active
-    //         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
-
-    //         // Wo don't close the left dock, because panel_2 wasn't the active panel
-    //         workspace.update(cx, |workspace, cx| {
-    //             let left_dock = workspace.left_dock();
-    //             assert!(left_dock.read(cx).is_open());
-    //             assert_eq!(
-    //                 left_dock.read(cx).visible_panel().unwrap().id(),
-    //                 panel_1.id()
-    //             );
-    //         });
-
-    //         // Emitting a ZoomIn event shows the panel as zoomed.
-    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
-    //         workspace.update(cx, |workspace, _| {
-    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
-    //         });
-
-    //         // Move panel to another dock while it is zoomed
-    //         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
-    //         workspace.update(cx, |workspace, _| {
-    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-    //         });
-
-    //         // If focus is transferred to another view that's not a panel or another pane, we still show
-    //         // the panel as zoomed.
-    //         let focus_receiver = cx.build_view(|_| EmptyView);
-    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
-    //         workspace.update(cx, |workspace, _| {
-    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-    //         });
-
-    //         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
-    //         workspace.update(cx, |_, cx| cx.focus_self());
-    //         workspace.update(cx, |workspace, _| {
-    //             assert_eq!(workspace.zoomed, None);
-    //             assert_eq!(workspace.zoomed_position, None);
-    //         });
-
-    //         // If focus is transferred again to another view that's not a panel or a pane, we won't
-    //         // show the panel as zoomed because it wasn't zoomed before.
-    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
-    //         workspace.update(cx, |workspace, _| {
-    //             assert_eq!(workspace.zoomed, None);
-    //             assert_eq!(workspace.zoomed_position, None);
-    //         });
-
-    //         // When focus is transferred back to the panel, it is zoomed again.
-    //         panel_1.update(cx, |_, cx| cx.focus_self());
-    //         workspace.update(cx, |workspace, _| {
-    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-    //         });
-
-    //         // Emitting a ZoomOut event unzooms the panel.
-    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
-    //         workspace.update(cx, |workspace, _| {
-    //             assert_eq!(workspace.zoomed, None);
-    //             assert_eq!(workspace.zoomed_position, None);
-    //         });
-
-    //         // Emit closed event on panel 1, which is active
-    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
-
-    //         // Now the left dock is closed, because panel_1 was the active panel
-    //         workspace.update(cx, |workspace, cx| {
-    //             let right_dock = workspace.right_dock();
-    //             assert!(!right_dock.read(cx).is_open());
-    //         });
-    //     }
+    //                 .read(cx)
+    //                 .visible_panel()
+    //                 .unwrap()
+    //                 .panel_id(),
+    //             panel_1.panel_id(),
+    //         );
+    //     });
+
+    //     // Move panel_1 back to the left
+    //     panel_1.update(cx, |panel_1, cx| {
+    //         panel_1.set_position(DockPosition::Left, cx)
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
+    //         let left_dock = workspace.left_dock();
+    //         assert!(left_dock.read(cx).is_open());
+    //         assert_eq!(
+    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
+    //             panel_1.panel_id()
+    //         );
+    //         assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+    //         // And right the dock should be closed as it no longer has any panels.
+    //         assert!(!workspace.right_dock().read(cx).is_open());
+
+    //         // Now we move panel_1 to the bottom
+    //         panel_1.set_position(DockPosition::Bottom, cx);
+    //     });
+
+    //     workspace.update(cx, |workspace, cx| {
+    //         // Since panel_1 was visible on the left, we close the left dock.
+    //         assert!(!workspace.left_dock().read(cx).is_open());
+    //         // The bottom dock is sized based on the panel's default size,
+    //         // since the panel orientation changed from vertical to horizontal.
+    //         let bottom_dock = workspace.bottom_dock();
+    //         assert_eq!(
+    //             bottom_dock.read(cx).active_panel_size(cx).unwrap(),
+    //             panel_1.size(cx),
+    //         );
+    //         // Close bottom dock and move panel_1 back to the left.
+    //         bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
+    //         panel_1.set_position(DockPosition::Left, cx);
+    //     });
+
+    //     // Emit activated event on panel 1
+    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
+
+    //     // Now the left dock is open and panel_1 is active and focused.
+    //     workspace.update(cx, |workspace, cx| {
+    //         let left_dock = workspace.left_dock();
+    //         assert!(left_dock.read(cx).is_open());
+    //         assert_eq!(
+    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
+    //             panel_1.panel_id(),
+    //         );
+    //         assert!(panel_1.focus_handle(cx).is_focused(cx));
+    //     });
+
+    //     // Emit closed event on panel 2, which is not active
+    //     panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
+
+    //     // Wo don't close the left dock, because panel_2 wasn't the active panel
+    //     workspace.update(cx, |workspace, cx| {
+    //         let left_dock = workspace.left_dock();
+    //         assert!(left_dock.read(cx).is_open());
+    //         assert_eq!(
+    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
+    //             panel_1.panel_id(),
+    //         );
+    //     });
+
+    //     // Emitting a ZoomIn event shows the panel as zoomed.
+    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
+    //     workspace.update(cx, |workspace, _| {
+    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
+    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
+    //     });
+
+    //     // Move panel to another dock while it is zoomed
+    //     panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
+    //     workspace.update(cx, |workspace, _| {
+    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
+
+    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //     });
+
+    //     // If focus is transferred to another view that's not a panel or another pane, we still show
+    //     // the panel as zoomed.
+    //     let other_focus_handle = cx.update(|cx| cx.focus_handle());
+    //     cx.update(|cx| cx.focus(&other_focus_handle));
+    //     workspace.update(cx, |workspace, _| {
+    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
+    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //     });
+
+    //     // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
+    //     workspace.update(cx, |_, cx| cx.focus_self());
+    //     workspace.update(cx, |workspace, _| {
+    //         assert_eq!(workspace.zoomed, None);
+    //         assert_eq!(workspace.zoomed_position, None);
+    //     });
+
+    //     // If focus is transferred again to another view that's not a panel or a pane, we won't
+    //     // show the panel as zoomed because it wasn't zoomed before.
+    //     cx.update(|cx| cx.focus(&other_focus_handle));
+    //     workspace.update(cx, |workspace, _| {
+    //         assert_eq!(workspace.zoomed, None);
+    //         assert_eq!(workspace.zoomed_position, None);
+    //     });
+
+    //     // When focus is transferred back to the panel, it is zoomed again.
+    //     panel_1.update(cx, |_, cx| cx.focus_self());
+    //     workspace.update(cx, |workspace, _| {
+    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
+    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //     });
+
+    //     // Emitting a ZoomOut event unzooms the panel.
+    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
+    //     workspace.update(cx, |workspace, _| {
+    //         assert_eq!(workspace.zoomed, None);
+    //         assert_eq!(workspace.zoomed_position, None);
+    //     });
+
+    //     // Emit closed event on panel 1, which is active
+    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
+
+    //     // Now the left dock is closed, because panel_1 was the active panel
+    //     workspace.update(cx, |workspace, cx| {
+    //         let right_dock = workspace.right_dock();
+    //         assert!(!right_dock.read(cx).is_open());
+    //     });
+    // }
 
     pub fn init_test(cx: &mut TestAppContext) {
         cx.update(|cx| {