Merge branch 'zed2' of github.com:zed-industries/zed into zed2-ai

KCaverly created

Change summary

crates/call2/src/call2.rs                     |   40 
crates/call2/src/room.rs                      |   38 
crates/client2/src/client2.rs                 |   28 
crates/client2/src/test.rs                    |    6 
crates/client2/src/user.rs                    |   10 
crates/copilot2/src/copilot2.rs               |   24 
crates/gpui2/src/app.rs                       |   14 
crates/gpui2/src/app/async_context.rs         |   30 
crates/gpui2/src/app/entity_map.rs            |   52 
crates/gpui2/src/app/model_context.rs         |   30 
crates/gpui2/src/app/test_context.rs          |   16 
crates/gpui2/src/gpui2.rs                     |   42 
crates/gpui2/src/view.rs                      |    7 
crates/gpui2/src/window.rs                    |   48 
crates/language2/src/buffer_tests.rs          |   73 
crates/prettier2/src/prettier2.rs             |    4 
crates/project2/src/lsp_command.rs            |  158 
crates/project2/src/project2.rs               |  236 
crates/project2/src/terminals.rs              |    8 
crates/project2/src/worktree.rs               |   24 
crates/storybook2/src/stories/kitchen_sink.rs |    2 
crates/storybook2/src/storybook2.rs           |   21 
crates/ui2/src/components/buffer_search.rs    |    2 
crates/ui2/src/components/editor_pane.rs      |    2 
crates/ui2/src/components/title_bar.rs        |    4 
crates/ui2/src/components/workspace.rs        |   20 
crates/ui2/src/elements/icon.rs               |   21 
crates/ui2/src/elements/label.rs              |   14 
crates/ui2/src/prelude.rs                     |    2 
crates/ui2/src/theme.rs                       |   93 
crates/workspace2/src/item.rs                 | 1096 ++++
crates/workspace2/src/pane.rs                 | 2754 ++++++++++
crates/workspace2/src/pane_group.rs           |  993 +++
crates/workspace2/src/persistence/model.rs    |  340 +
crates/workspace2/src/workspace2.rs           | 5535 +++++++++++++++++++++
crates/zed2/src/main.rs                       |    2 
crates/zed2/src/zed2.rs                       |    4 
37 files changed, 11,203 insertions(+), 590 deletions(-)

Detailed changes

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

@@ -12,7 +12,7 @@ use client2::{
 use collections::HashSet;
 use futures::{future::Shared, FutureExt};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Subscription, Task,
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
     WeakHandle,
 };
 use postage::watch;
@@ -23,10 +23,10 @@ use std::sync::Arc;
 pub use participant::ParticipantLocation;
 pub use room::Room;
 
-pub fn init(client: Arc<Client>, user_store: Handle<UserStore>, cx: &mut AppContext) {
+pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
     CallSettings::register(cx);
 
-    let active_call = cx.entity(|cx| ActiveCall::new(client, user_store, cx));
+    let active_call = cx.build_model(|cx| ActiveCall::new(client, user_store, cx));
     cx.set_global(active_call);
 }
 
@@ -40,8 +40,8 @@ pub struct IncomingCall {
 
 /// Singleton global maintaining the user's participation in a room across workspaces.
 pub struct ActiveCall {
-    room: Option<(Handle<Room>, Vec<Subscription>)>,
-    pending_room_creation: Option<Shared<Task<Result<Handle<Room>, Arc<anyhow::Error>>>>>,
+    room: Option<(Model<Room>, Vec<Subscription>)>,
+    pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>,
     location: Option<WeakHandle<Project>>,
     pending_invites: HashSet<u64>,
     incoming_call: (
@@ -49,7 +49,7 @@ pub struct ActiveCall {
         watch::Receiver<Option<IncomingCall>>,
     ),
     client: Arc<Client>,
-    user_store: Handle<UserStore>,
+    user_store: Model<UserStore>,
     _subscriptions: Vec<client2::Subscription>,
 }
 
@@ -58,11 +58,7 @@ impl EventEmitter for ActiveCall {
 }
 
 impl ActiveCall {
-    fn new(
-        client: Arc<Client>,
-        user_store: Handle<UserStore>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
+    fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
         Self {
             room: None,
             pending_room_creation: None,
@@ -84,7 +80,7 @@ impl ActiveCall {
     }
 
     async fn handle_incoming_call(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::IncomingCall>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -112,7 +108,7 @@ impl ActiveCall {
     }
 
     async fn handle_call_canceled(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CallCanceled>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -129,14 +125,14 @@ impl ActiveCall {
         Ok(())
     }
 
-    pub fn global(cx: &AppContext) -> Handle<Self> {
-        cx.global::<Handle<Self>>().clone()
+    pub fn global(cx: &AppContext) -> Model<Self> {
+        cx.global::<Model<Self>>().clone()
     }
 
     pub fn invite(
         &mut self,
         called_user_id: u64,
-        initial_project: Option<Handle<Project>>,
+        initial_project: Option<Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if !self.pending_invites.insert(called_user_id) {
@@ -291,7 +287,7 @@ impl ActiveCall {
         &mut self,
         channel_id: u64,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Room>>> {
+    ) -> Task<Result<Model<Room>>> {
         if let Some(room) = self.room().cloned() {
             if room.read(cx).channel_id() == Some(channel_id) {
                 return Task::ready(Ok(room));
@@ -327,7 +323,7 @@ impl ActiveCall {
 
     pub fn share_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<u64>> {
         if let Some((room, _)) = self.room.as_ref() {
@@ -340,7 +336,7 @@ impl ActiveCall {
 
     pub fn unshare_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         if let Some((room, _)) = self.room.as_ref() {
@@ -357,7 +353,7 @@ impl ActiveCall {
 
     pub fn set_location(
         &mut self,
-        project: Option<&Handle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if project.is_some() || !*ZED_ALWAYS_ACTIVE {
@@ -371,7 +367,7 @@ impl ActiveCall {
 
     fn set_room(
         &mut self,
-        room: Option<Handle<Room>>,
+        room: Option<Model<Room>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
@@ -407,7 +403,7 @@ impl ActiveCall {
         }
     }
 
-    pub fn room(&self) -> Option<&Handle<Room>> {
+    pub fn room(&self) -> Option<&Model<Room>> {
         self.room.as_ref().map(|(room, _)| room)
     }
 

crates/call2/src/room.rs πŸ”—

@@ -16,7 +16,7 @@ use collections::{BTreeMap, HashMap, HashSet};
 use fs::Fs;
 use futures::{FutureExt, StreamExt};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Task, WeakHandle,
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakHandle,
 };
 use language2::LanguageRegistry;
 use live_kit_client::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate};
@@ -70,7 +70,7 @@ pub struct Room {
     pending_call_count: usize,
     leave_when_empty: bool,
     client: Arc<Client>,
-    user_store: Handle<UserStore>,
+    user_store: Model<UserStore>,
     follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
     client_subscriptions: Vec<client2::Subscription>,
     _subscriptions: Vec<gpui2::Subscription>,
@@ -111,7 +111,7 @@ impl Room {
         channel_id: Option<u64>,
         live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         todo!()
@@ -237,15 +237,15 @@ impl Room {
 
     pub(crate) fn create(
         called_user_id: u64,
-        initial_project: Option<Handle<Project>>,
+        initial_project: Option<Model<Project>>,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut AppContext,
-    ) -> Task<Result<Handle<Self>>> {
+    ) -> Task<Result<Model<Self>>> {
         cx.spawn(move |mut cx| async move {
             let response = client.request(proto::CreateRoom {}).await?;
             let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-            let room = cx.entity(|cx| {
+            let room = cx.build_model(|cx| {
                 Self::new(
                     room_proto.id,
                     None,
@@ -283,9 +283,9 @@ impl Room {
     pub(crate) fn join_channel(
         channel_id: u64,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut AppContext,
-    ) -> Task<Result<Handle<Self>>> {
+    ) -> Task<Result<Model<Self>>> {
         cx.spawn(move |cx| async move {
             Self::from_join_response(
                 client.request(proto::JoinChannel { channel_id }).await?,
@@ -299,9 +299,9 @@ impl Room {
     pub(crate) fn join(
         call: &IncomingCall,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut AppContext,
-    ) -> Task<Result<Handle<Self>>> {
+    ) -> Task<Result<Model<Self>>> {
         let id = call.room_id;
         cx.spawn(move |cx| async move {
             Self::from_join_response(
@@ -343,11 +343,11 @@ impl Room {
     fn from_join_response(
         response: proto::JoinRoomResponse,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         mut cx: AsyncAppContext,
-    ) -> Result<Handle<Self>> {
+    ) -> Result<Model<Self>> {
         let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-        let room = cx.entity(|cx| {
+        let room = cx.build_model(|cx| {
             Self::new(
                 room_proto.id,
                 response.channel_id,
@@ -661,7 +661,7 @@ impl Room {
     }
 
     async fn handle_room_updated(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::RoomUpdated>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -1101,7 +1101,7 @@ impl Room {
         language_registry: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Project>>> {
+    ) -> Task<Result<Model<Project>>> {
         let client = self.client.clone();
         let user_store = self.user_store.clone();
         cx.emit(Event::RemoteProjectJoined { project_id: id });
@@ -1125,7 +1125,7 @@ impl Room {
 
     pub(crate) fn share_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<u64>> {
         if let Some(project_id) = project.read(cx).remote_id() {
@@ -1161,7 +1161,7 @@ impl Room {
 
     pub(crate) fn unshare_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         let project_id = match project.read(cx).remote_id() {
@@ -1175,7 +1175,7 @@ impl Room {
 
     pub(crate) fn set_location(
         &mut self,
-        project: Option<&Handle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if self.status.is_offline() {

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

@@ -14,7 +14,7 @@ use futures::{
     future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt,
 };
 use gpui2::{
-    serde_json, AnyHandle, AnyWeakHandle, AppContext, AsyncAppContext, Handle, SemanticVersion,
+    serde_json, AnyHandle, AnyWeakHandle, AppContext, AsyncAppContext, Model, SemanticVersion,
     Task, WeakHandle,
 };
 use lazy_static::lazy_static;
@@ -314,7 +314,7 @@ impl<T> PendingEntitySubscription<T>
 where
     T: 'static + Send,
 {
-    pub fn set_model(mut self, model: &Handle<T>, cx: &mut AsyncAppContext) -> Subscription {
+    pub fn set_model(mut self, model: &Model<T>, cx: &mut AsyncAppContext) -> Subscription {
         self.consumed = true;
         let mut state = self.client.state.write();
         let id = (TypeId::of::<T>(), self.remote_id);
@@ -558,7 +558,7 @@ impl Client {
     where
         M: EnvelopedMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<()>> + Send,
     {
         let message_type_id = TypeId::of::<M>();
@@ -600,7 +600,7 @@ impl Client {
     where
         M: RequestMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<M::Response>> + Send,
     {
         self.add_message_handler(model, move |handle, envelope, this, cx| {
@@ -616,7 +616,7 @@ impl Client {
     where
         M: EntityMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<()>> + Send,
     {
         self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
@@ -667,7 +667,7 @@ impl Client {
     where
         M: EntityMessage + RequestMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<M::Response>> + Send,
     {
         self.add_model_message_handler(move |entity, envelope, client, cx| {
@@ -1546,7 +1546,7 @@ mod tests {
         let (done_tx1, mut done_rx1) = smol::channel::unbounded();
         let (done_tx2, mut done_rx2) = smol::channel::unbounded();
         client.add_model_message_handler(
-            move |model: Handle<Model>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
+            move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
                 match model.update(&mut cx, |model, _| model.id).unwrap() {
                     1 => done_tx1.try_send(()).unwrap(),
                     2 => done_tx2.try_send(()).unwrap(),
@@ -1555,15 +1555,15 @@ mod tests {
                 async { Ok(()) }
             },
         );
-        let model1 = cx.entity(|_| Model {
+        let model1 = cx.build_model(|_| TestModel {
             id: 1,
             subscription: None,
         });
-        let model2 = cx.entity(|_| Model {
+        let model2 = cx.build_model(|_| TestModel {
             id: 2,
             subscription: None,
         });
-        let model3 = cx.entity(|_| Model {
+        let model3 = cx.build_model(|_| TestModel {
             id: 3,
             subscription: None,
         });
@@ -1596,7 +1596,7 @@ mod tests {
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let server = FakeServer::for_client(user_id, &client, cx).await;
 
-        let model = cx.entity(|_| Model::default());
+        let model = cx.build_model(|_| TestModel::default());
         let (done_tx1, _done_rx1) = smol::channel::unbounded();
         let (done_tx2, mut done_rx2) = smol::channel::unbounded();
         let subscription1 = client.add_message_handler(
@@ -1624,11 +1624,11 @@ mod tests {
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let server = FakeServer::for_client(user_id, &client, cx).await;
 
-        let model = cx.entity(|_| Model::default());
+        let model = cx.build_model(|_| TestModel::default());
         let (done_tx, mut done_rx) = smol::channel::unbounded();
         let subscription = client.add_message_handler(
             model.clone().downgrade(),
-            move |model: Handle<Model>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
+            move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
                 model
                     .update(&mut cx, |model, _| model.subscription.take())
                     .unwrap();
@@ -1644,7 +1644,7 @@ mod tests {
     }
 
     #[derive(Default)]
-    struct Model {
+    struct TestModel {
         id: usize,
         subscription: Option<Subscription>,
     }

crates/client2/src/test.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
 use anyhow::{anyhow, Result};
 use futures::{stream::BoxStream, StreamExt};
-use gpui2::{Context, Executor, Handle, TestAppContext};
+use gpui2::{Context, Executor, Model, TestAppContext};
 use parking_lot::Mutex;
 use rpc2::{
     proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
@@ -194,9 +194,9 @@ impl FakeServer {
         &self,
         client: Arc<Client>,
         cx: &mut TestAppContext,
-    ) -> Handle<UserStore> {
+    ) -> Model<UserStore> {
         let http_client = FakeHttpClient::with_404_response();
-        let user_store = cx.entity(|cx| UserStore::new(client, http_client, cx));
+        let user_store = cx.build_model(|cx| UserStore::new(client, http_client, cx));
         assert_eq!(
             self.receive::<proto::GetUsers>()
                 .await

crates/client2/src/user.rs πŸ”—

@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
 use collections::{hash_map::Entry, HashMap, HashSet};
 use feature_flags2::FeatureFlagAppExt;
 use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
-use gpui2::{AsyncAppContext, EventEmitter, Handle, ImageData, ModelContext, Task};
+use gpui2::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task};
 use postage::{sink::Sink, watch};
 use rpc2::proto::{RequestMessage, UsersResponse};
 use std::sync::{Arc, Weak};
@@ -213,7 +213,7 @@ impl UserStore {
     }
 
     async fn handle_update_invite_info(
-        this: Handle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateInviteInfo>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -229,7 +229,7 @@ impl UserStore {
     }
 
     async fn handle_show_contacts(
-        this: Handle<Self>,
+        this: Model<Self>,
         _: TypedEnvelope<proto::ShowContacts>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -243,7 +243,7 @@ impl UserStore {
     }
 
     async fn handle_update_contacts(
-        this: Handle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateContacts>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -690,7 +690,7 @@ impl User {
 impl Contact {
     async fn from_proto(
         contact: proto::Contact,
-        user_store: &Handle<UserStore>,
+        user_store: &Model<UserStore>,
         cx: &mut AsyncAppContext,
     ) -> Result<Self> {
         let user = user_store

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

@@ -7,7 +7,7 @@ use async_tar::Archive;
 use collections::{HashMap, HashSet};
 use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EntityId, EventEmitter, Handle, ModelContext, Task,
+    AppContext, AsyncAppContext, Context, EntityId, EventEmitter, Model, ModelContext, Task,
     WeakHandle,
 };
 use language2::{
@@ -49,7 +49,7 @@ pub fn init(
     node_runtime: Arc<dyn NodeRuntime>,
     cx: &mut AppContext,
 ) {
-    let copilot = cx.entity({
+    let copilot = cx.build_model({
         let node_runtime = node_runtime.clone();
         move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
     });
@@ -183,7 +183,7 @@ struct RegisteredBuffer {
 impl RegisteredBuffer {
     fn report_changes(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Copilot>,
     ) -> oneshot::Receiver<(i32, BufferSnapshot)> {
         let (done_tx, done_rx) = oneshot::channel();
@@ -292,9 +292,9 @@ impl EventEmitter for Copilot {
 }
 
 impl Copilot {
-    pub fn global(cx: &AppContext) -> Option<Handle<Self>> {
-        if cx.has_global::<Handle<Self>>() {
-            Some(cx.global::<Handle<Self>>().clone())
+    pub fn global(cx: &AppContext) -> Option<Model<Self>> {
+        if cx.has_global::<Model<Self>>() {
+            Some(cx.global::<Model<Self>>().clone())
         } else {
             None
         }
@@ -590,7 +590,7 @@ impl Copilot {
         }
     }
 
-    pub fn register_buffer(&mut self, buffer: &Handle<Buffer>, cx: &mut ModelContext<Self>) {
+    pub fn register_buffer(&mut self, buffer: &Model<Buffer>, cx: &mut ModelContext<Self>) {
         let weak_buffer = buffer.downgrade();
         self.buffers.insert(weak_buffer.clone());
 
@@ -646,7 +646,7 @@ impl Copilot {
 
     fn handle_buffer_event(
         &mut self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         event: &language2::Event,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
@@ -723,7 +723,7 @@ impl Copilot {
 
     pub fn completions<T>(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -735,7 +735,7 @@ impl Copilot {
 
     pub fn completions_cycling<T>(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -792,7 +792,7 @@ impl Copilot {
 
     fn request_completions<R, T>(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -926,7 +926,7 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
     }
 }
 
-fn uri_for_buffer(buffer: &Handle<Buffer>, cx: &AppContext) -> lsp2::Url {
+fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp2::Url {
     if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
         lsp2::Url::from_file_path(file.abs_path(cx)).unwrap()
     } else {

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

@@ -707,19 +707,19 @@ impl AppContext {
 }
 
 impl Context for AppContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = T;
 
     /// Build an entity that is owned by the application. The given function will be invoked with
     /// a `ModelContext` and must return an object representing the entity. A `Handle` will be returned
     /// which can be used to access the entity in a context.
-    fn entity<T: 'static + Send>(
+    fn build_model<T: 'static + Send>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Handle<T> {
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Model<T> {
         self.update(|cx| {
             let slot = cx.entities.reserve();
-            let entity = build_entity(&mut ModelContext::mutable(cx, slot.downgrade()));
+            let entity = build_model(&mut ModelContext::mutable(cx, slot.downgrade()));
             cx.entities.insert(slot, entity)
         })
     }
@@ -728,8 +728,8 @@ impl Context for AppContext {
     /// entity along with a `ModelContext` for the entity.
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> R {
         self.update(|cx| {
             let mut entity = cx.entities.lease(handle);

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

@@ -1,5 +1,5 @@
 use crate::{
-    AnyWindowHandle, AppContext, Context, Executor, Handle, MainThread, ModelContext, Result, Task,
+    AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task,
     WindowContext,
 };
 use anyhow::anyhow;
@@ -14,13 +14,13 @@ pub struct AsyncAppContext {
 }
 
 impl Context for AsyncAppContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = Result<T>;
 
-    fn entity<T: 'static>(
+    fn build_model<T: 'static>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send,
     {
@@ -29,13 +29,13 @@ impl Context for AsyncAppContext {
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
         let mut lock = app.lock(); // Need this to compile
-        Ok(lock.entity(build_entity))
+        Ok(lock.build_model(build_model))
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R> {
         let app = self
             .app
@@ -216,24 +216,24 @@ impl AsyncWindowContext {
 }
 
 impl Context for AsyncWindowContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = Result<T>;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Result<Model<T>>
     where
         T: 'static + Send,
     {
         self.app
-            .update_window(self.window, |cx| cx.entity(build_entity))
+            .update_window(self.window, |cx| cx.build_model(build_model))
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Result<R> {
         self.app
             .update_window(self.window, |cx| cx.update_entity(handle, update))

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

@@ -53,11 +53,11 @@ impl EntityMap {
     /// Reserve a slot for an entity, which you can subsequently use with `insert`.
     pub fn reserve<T: 'static>(&self) -> Slot<T> {
         let id = self.ref_counts.write().counts.insert(1.into());
-        Slot(Handle::new(id, Arc::downgrade(&self.ref_counts)))
+        Slot(Model::new(id, Arc::downgrade(&self.ref_counts)))
     }
 
     /// Insert an entity into a slot obtained by calling `reserve`.
-    pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Handle<T>
+    pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Model<T>
     where
         T: 'static + Send,
     {
@@ -67,7 +67,7 @@ impl EntityMap {
     }
 
     /// Move an entity to the stack.
-    pub fn lease<'a, T>(&mut self, handle: &'a Handle<T>) -> Lease<'a, T> {
+    pub fn lease<'a, T>(&mut self, handle: &'a Model<T>) -> Lease<'a, T> {
         self.assert_valid_context(handle);
         let entity = Some(
             self.entities
@@ -87,7 +87,7 @@ impl EntityMap {
             .insert(lease.handle.entity_id, lease.entity.take().unwrap());
     }
 
-    pub fn read<T: 'static>(&self, handle: &Handle<T>) -> &T {
+    pub fn read<T: 'static>(&self, handle: &Model<T>) -> &T {
         self.assert_valid_context(handle);
         self.entities[handle.entity_id].downcast_ref().unwrap()
     }
@@ -115,7 +115,7 @@ impl EntityMap {
 
 pub struct Lease<'a, T> {
     entity: Option<AnyBox>,
-    pub handle: &'a Handle<T>,
+    pub handle: &'a Model<T>,
     entity_type: PhantomData<T>,
 }
 
@@ -143,7 +143,7 @@ impl<'a, T> Drop for Lease<'a, T> {
 }
 
 #[derive(Deref, DerefMut)]
-pub struct Slot<T>(Handle<T>);
+pub struct Slot<T>(Model<T>);
 
 pub struct AnyHandle {
     pub(crate) entity_id: EntityId,
@@ -172,9 +172,9 @@ impl AnyHandle {
         }
     }
 
-    pub fn downcast<T: 'static>(&self) -> Option<Handle<T>> {
+    pub fn downcast<T: 'static>(&self) -> Option<Model<T>> {
         if TypeId::of::<T>() == self.entity_type {
-            Some(Handle {
+            Some(Model {
                 any_handle: self.clone(),
                 entity_type: PhantomData,
             })
@@ -223,8 +223,8 @@ impl Drop for AnyHandle {
     }
 }
 
-impl<T> From<Handle<T>> for AnyHandle {
-    fn from(handle: Handle<T>) -> Self {
+impl<T> From<Model<T>> for AnyHandle {
+    fn from(handle: Model<T>) -> Self {
         handle.any_handle
     }
 }
@@ -244,17 +244,17 @@ impl PartialEq for AnyHandle {
 impl Eq for AnyHandle {}
 
 #[derive(Deref, DerefMut)]
-pub struct Handle<T> {
+pub struct Model<T> {
     #[deref]
     #[deref_mut]
     any_handle: AnyHandle,
     entity_type: PhantomData<T>,
 }
 
-unsafe impl<T> Send for Handle<T> {}
-unsafe impl<T> Sync for Handle<T> {}
+unsafe impl<T> Send for Model<T> {}
+unsafe impl<T> Sync for Model<T> {}
 
-impl<T: 'static> Handle<T> {
+impl<T: 'static> Model<T> {
     fn new(id: EntityId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self
     where
         T: 'static,
@@ -284,7 +284,7 @@ impl<T: 'static> Handle<T> {
     pub fn update<C, R>(
         &self,
         cx: &mut C,
-        update: impl FnOnce(&mut T, &mut C::EntityContext<'_, T>) -> R,
+        update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R,
     ) -> C::Result<R>
     where
         C: Context,
@@ -293,7 +293,7 @@ impl<T: 'static> Handle<T> {
     }
 }
 
-impl<T> Clone for Handle<T> {
+impl<T> Clone for Model<T> {
     fn clone(&self) -> Self {
         Self {
             any_handle: self.any_handle.clone(),
@@ -302,7 +302,7 @@ impl<T> Clone for Handle<T> {
     }
 }
 
-impl<T> std::fmt::Debug for Handle<T> {
+impl<T> std::fmt::Debug for Model<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(
             f,
@@ -313,21 +313,21 @@ impl<T> std::fmt::Debug for Handle<T> {
     }
 }
 
-impl<T> Hash for Handle<T> {
+impl<T> Hash for Model<T> {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.any_handle.hash(state);
     }
 }
 
-impl<T> PartialEq for Handle<T> {
+impl<T> PartialEq for Model<T> {
     fn eq(&self, other: &Self) -> bool {
         self.any_handle == other.any_handle
     }
 }
 
-impl<T> Eq for Handle<T> {}
+impl<T> Eq for Model<T> {}
 
-impl<T> PartialEq<WeakHandle<T>> for Handle<T> {
+impl<T> PartialEq<WeakHandle<T>> for Model<T> {
     fn eq(&self, other: &WeakHandle<T>) -> bool {
         self.entity_id() == other.entity_id()
     }
@@ -410,8 +410,8 @@ impl<T> Clone for WeakHandle<T> {
 }
 
 impl<T: 'static> WeakHandle<T> {
-    pub fn upgrade(&self) -> Option<Handle<T>> {
-        Some(Handle {
+    pub fn upgrade(&self) -> Option<Model<T>> {
+        Some(Model {
             any_handle: self.any_handle.upgrade()?,
             entity_type: self.entity_type,
         })
@@ -427,7 +427,7 @@ impl<T: 'static> WeakHandle<T> {
     pub fn update<C, R>(
         &self,
         cx: &mut C,
-        update: impl FnOnce(&mut T, &mut C::EntityContext<'_, T>) -> R,
+        update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R,
     ) -> Result<R>
     where
         C: Context,
@@ -455,8 +455,8 @@ impl<T> PartialEq for WeakHandle<T> {
 
 impl<T> Eq for WeakHandle<T> {}
 
-impl<T> PartialEq<Handle<T>> for WeakHandle<T> {
-    fn eq(&self, other: &Handle<T>) -> bool {
+impl<T> PartialEq<Model<T>> for WeakHandle<T> {
+    fn eq(&self, other: &Model<T>) -> bool {
         self.entity_id() == other.entity_id()
     }
 }

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

@@ -1,5 +1,5 @@
 use crate::{
-    AppContext, AsyncAppContext, Context, Effect, EntityId, EventEmitter, Handle, MainThread,
+    AppContext, AsyncAppContext, Context, Effect, EntityId, EventEmitter, MainThread, Model,
     Reference, Subscription, Task, WeakHandle,
 };
 use derive_more::{Deref, DerefMut};
@@ -30,7 +30,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
         self.model_state.entity_id
     }
 
-    pub fn handle(&self) -> Handle<T> {
+    pub fn handle(&self) -> Model<T> {
         self.weak_handle()
             .upgrade()
             .expect("The entity must be alive if we have a model context")
@@ -42,8 +42,8 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn observe<T2: 'static>(
         &mut self,
-        handle: &Handle<T2>,
-        mut on_notify: impl FnMut(&mut T, Handle<T2>, &mut ModelContext<'_, T>) + Send + 'static,
+        handle: &Model<T2>,
+        mut on_notify: impl FnMut(&mut T, Model<T2>, &mut ModelContext<'_, T>) + Send + 'static,
     ) -> Subscription
     where
         T: 'static + Send,
@@ -65,10 +65,8 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn subscribe<E: 'static + EventEmitter>(
         &mut self,
-        handle: &Handle<E>,
-        mut on_event: impl FnMut(&mut T, Handle<E>, &E::Event, &mut ModelContext<'_, T>)
-            + Send
-            + 'static,
+        handle: &Model<E>,
+        mut on_event: impl FnMut(&mut T, Model<E>, &E::Event, &mut ModelContext<'_, T>) + Send + 'static,
     ) -> Subscription
     where
         T: 'static + Send,
@@ -107,7 +105,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn observe_release<E: 'static>(
         &mut self,
-        handle: &Handle<E>,
+        handle: &Model<E>,
         mut on_release: impl FnMut(&mut T, &mut E, &mut ModelContext<'_, T>) + Send + 'static,
     ) -> Subscription
     where
@@ -224,23 +222,23 @@ where
 }
 
 impl<'a, T> Context for ModelContext<'a, T> {
-    type EntityContext<'b, U> = ModelContext<'b, U>;
+    type ModelContext<'b, U> = ModelContext<'b, U>;
     type Result<U> = U;
 
-    fn entity<U>(
+    fn build_model<U>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, U>) -> U,
-    ) -> Handle<U>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U,
+    ) -> Model<U>
     where
         U: 'static + Send,
     {
-        self.app.entity(build_entity)
+        self.app.build_model(build_model)
     }
 
     fn update_entity<U: 'static, R>(
         &mut self,
-        handle: &Handle<U>,
-        update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, U>) -> R,
+        handle: &Model<U>,
+        update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R,
     ) -> R {
         self.app.update_entity(handle, update)
     }

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

@@ -1,5 +1,5 @@
 use crate::{
-    AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, Handle, MainThread,
+    AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, MainThread, Model,
     ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext,
 };
 use parking_lot::Mutex;
@@ -12,24 +12,24 @@ pub struct TestAppContext {
 }
 
 impl Context for TestAppContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = T;
 
-    fn entity<T: 'static>(
+    fn build_model<T: 'static>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send,
     {
         let mut lock = self.app.lock();
-        lock.entity(build_entity)
+        lock.build_model(build_model)
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R> {
         let mut lock = self.app.lock();
         lock.update_entity(handle, update)

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

@@ -70,20 +70,20 @@ use taffy::TaffyLayoutEngine;
 type AnyBox = Box<dyn Any + Send>;
 
 pub trait Context {
-    type EntityContext<'a, T>;
+    type ModelContext<'a, T>;
     type Result<T>;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send;
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R>;
 }
 
@@ -92,7 +92,7 @@ pub trait VisualContext: Context {
 
     fn build_view<E, V>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
+        build_model: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
         render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
     ) -> Self::Result<View<V>>
     where
@@ -130,37 +130,37 @@ impl<T> DerefMut for MainThread<T> {
 }
 
 impl<C: Context> Context for MainThread<C> {
-    type EntityContext<'a, T> = MainThread<C::EntityContext<'a, T>>;
+    type ModelContext<'a, T> = MainThread<C::ModelContext<'a, T>>;
     type Result<T> = C::Result<T>;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send,
     {
-        self.0.entity(|cx| {
+        self.0.build_model(|cx| {
             let cx = unsafe {
                 mem::transmute::<
-                    &mut C::EntityContext<'_, T>,
-                    &mut MainThread<C::EntityContext<'_, T>>,
+                    &mut C::ModelContext<'_, T>,
+                    &mut MainThread<C::ModelContext<'_, T>>,
                 >(cx)
             };
-            build_entity(cx)
+            build_model(cx)
         })
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R> {
         self.0.update_entity(handle, |entity, cx| {
             let cx = unsafe {
                 mem::transmute::<
-                    &mut C::EntityContext<'_, T>,
-                    &mut MainThread<C::EntityContext<'_, T>>,
+                    &mut C::ModelContext<'_, T>,
+                    &mut MainThread<C::ModelContext<'_, T>>,
                 >(cx)
             };
             update(entity, cx)
@@ -173,7 +173,7 @@ impl<C: VisualContext> VisualContext for MainThread<C> {
 
     fn build_view<E, V>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
+        build_model: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
         render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
     ) -> Self::Result<View<V>>
     where
@@ -188,7 +188,7 @@ impl<C: VisualContext> VisualContext for MainThread<C> {
                         &mut MainThread<C::ViewContext<'_, '_, V>>,
                     >(cx)
                 };
-                build_entity(cx)
+                build_model(cx)
             },
             render,
         )

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

@@ -1,7 +1,6 @@
 use crate::{
     AnyBox, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
-    EntityId, Handle, LayoutId, Pixels, Size, ViewContext, VisualContext, WeakHandle,
-    WindowContext,
+    EntityId, LayoutId, Model, Pixels, Size, ViewContext, VisualContext, WeakHandle, WindowContext,
 };
 use anyhow::{Context, Result};
 use parking_lot::Mutex;
@@ -11,13 +10,13 @@ use std::{
 };
 
 pub struct View<V> {
-    pub(crate) state: Handle<V>,
+    pub(crate) state: Model<V>,
     render: Arc<Mutex<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyElement<V> + Send + 'static>>,
 }
 
 impl<V: 'static> View<V> {
     pub fn for_handle<E>(
-        state: Handle<V>,
+        state: Model<V>,
         render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
     ) -> View<V>
     where

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

@@ -2,8 +2,8 @@ use crate::{
     px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
     EntityId, EventEmitter, ExternalPaths, FileDropEvent, FocusEvent, FontId, GlobalElementId,
-    GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher,
-    Keystroke, LayoutId, MainThread, MainThreadOnly, ModelContext, Modifiers, MonochromeSprite,
+    GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke,
+    LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite,
     MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
     PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
     RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
@@ -1240,25 +1240,25 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 }
 
 impl Context for WindowContext<'_, '_> {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = T;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Handle<T>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Model<T>
     where
         T: 'static + Send,
     {
         let slot = self.app.entities.reserve();
-        let entity = build_entity(&mut ModelContext::mutable(&mut *self.app, slot.downgrade()));
-        self.entities.insert(slot, entity)
+        let model = build_model(&mut ModelContext::mutable(&mut *self.app, slot.downgrade()));
+        self.entities.insert(slot, model)
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> R {
         let mut entity = self.entities.lease(handle);
         let result = update(
@@ -1576,8 +1576,8 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
 
     pub fn observe<E>(
         &mut self,
-        handle: &Handle<E>,
-        mut on_notify: impl FnMut(&mut V, Handle<E>, &mut ViewContext<'_, '_, V>) + Send + 'static,
+        handle: &Model<E>,
+        mut on_notify: impl FnMut(&mut V, Model<E>, &mut ViewContext<'_, '_, V>) + Send + 'static,
     ) -> Subscription
     where
         E: 'static,
@@ -1604,8 +1604,8 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
 
     pub fn subscribe<E: EventEmitter>(
         &mut self,
-        handle: &Handle<E>,
-        mut on_event: impl FnMut(&mut V, Handle<E>, &E::Event, &mut ViewContext<'_, '_, V>)
+        handle: &Model<E>,
+        mut on_event: impl FnMut(&mut V, Model<E>, &E::Event, &mut ViewContext<'_, '_, V>)
             + Send
             + 'static,
     ) -> Subscription {
@@ -1646,7 +1646,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
 
     pub fn observe_release<T: 'static>(
         &mut self,
-        handle: &Handle<T>,
+        handle: &Model<T>,
         mut on_release: impl FnMut(&mut V, &mut T, &mut ViewContext<'_, '_, V>) + Send + 'static,
     ) -> Subscription
     where
@@ -1857,23 +1857,23 @@ where
 }
 
 impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> {
-    type EntityContext<'b, U> = ModelContext<'b, U>;
+    type ModelContext<'b, U> = ModelContext<'b, U>;
     type Result<U> = U;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Handle<T>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Model<T>
     where
         T: 'static + Send,
     {
-        self.window_cx.entity(build_entity)
+        self.window_cx.build_model(build_model)
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> R {
         self.window_cx.update_entity(handle, update)
     }
@@ -1884,14 +1884,14 @@ impl<V: 'static> VisualContext for ViewContext<'_, '_, V> {
 
     fn build_view<E, V2>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V2>) -> V2,
+        build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, V2>) -> V2,
         render: impl Fn(&mut V2, &mut ViewContext<'_, '_, V2>) -> E + Send + 'static,
     ) -> Self::Result<View<V2>>
     where
         E: crate::Component<V2>,
         V2: 'static + Send,
     {
-        self.window_cx.build_view(build_entity, render)
+        self.window_cx.build_view(build_view, render)
     }
 
     fn update_view<V2: 'static, R>(

crates/language2/src/buffer_tests.rs πŸ”—

@@ -5,7 +5,7 @@ use crate::language_settings::{
 use crate::Buffer;
 use clock::ReplicaId;
 use collections::BTreeMap;
-use gpui2::{AppContext, Handle};
+use gpui2::{AppContext, Model};
 use gpui2::{Context, TestAppContext};
 use indoc::indoc;
 use proto::deserialize_operation;
@@ -42,7 +42,7 @@ fn init_logger() {
 fn test_line_endings(cx: &mut gpui2::AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "one\r\ntwo\rthree")
             .with_language(Arc::new(rust_lang()), cx);
         assert_eq!(buffer.text(), "one\ntwo\nthree");
@@ -138,8 +138,8 @@ fn test_edit_events(cx: &mut gpui2::AppContext) {
     let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
     let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
 
-    let buffer1 = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef"));
-    let buffer2 = cx.entity(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
+    let buffer1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef"));
+    let buffer2 = cx.build_model(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
     let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
     buffer1.update(cx, {
         let buffer1_ops = buffer1_ops.clone();
@@ -218,7 +218,7 @@ fn test_edit_events(cx: &mut gpui2::AppContext) {
 #[gpui2::test]
 async fn test_apply_diff(cx: &mut TestAppContext) {
     let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
-    let buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
+    let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
     let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
 
     let text = "a\nccc\ndddd\nffffff\n";
@@ -250,7 +250,7 @@ async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) {
     ]
     .join("\n");
 
-    let buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
+    let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
 
     // Spawn a task to format the buffer's whitespace.
     // Pause so that the foratting task starts running.
@@ -314,7 +314,7 @@ async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) {
 #[gpui2::test]
 async fn test_reparse(cx: &mut gpui2::TestAppContext) {
     let text = "fn a() {}";
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
 
@@ -442,7 +442,7 @@ async fn test_reparse(cx: &mut gpui2::TestAppContext) {
 
 #[gpui2::test]
 async fn test_resetting_language(cx: &mut gpui2::TestAppContext) {
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx);
         buffer.set_sync_parse_timeout(Duration::ZERO);
@@ -492,7 +492,7 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
@@ -578,7 +578,7 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
@@ -616,7 +616,7 @@ async fn test_outline_with_extra_context(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx)
     });
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
@@ -660,7 +660,7 @@ async fn test_symbols_containing(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
@@ -881,7 +881,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &
 
 #[gpui2::test]
 fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "fn a() { b(|c| {}) }";
         let buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -922,7 +922,7 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
 fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "fn a() {}";
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -965,7 +965,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
         settings.defaults.hard_tabs = Some(true);
     });
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "fn a() {}";
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -1006,7 +1006,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
 fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let entity_id = cx.entity_id();
         let mut buffer = Buffer::new(
             0,
@@ -1080,7 +1080,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
         buffer
     });
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         eprintln!("second buffer: {:?}", cx.entity_id());
 
         let mut buffer = Buffer::new(
@@ -1147,7 +1147,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
 fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer = Buffer::new(
             0,
             cx.entity_id().as_u64(),
@@ -1209,7 +1209,7 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
 fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer = Buffer::new(
             0,
             cx.entity_id().as_u64(),
@@ -1266,7 +1266,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
 fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "a\nb";
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -1284,7 +1284,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
 fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "
             const a: usize = 1;
             fn b() {
@@ -1326,7 +1326,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
 fn test_autoindent_block_mode(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = r#"
             fn a() {
                 b();
@@ -1410,7 +1410,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
 fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = r#"
             fn a() {
                 if b() {
@@ -1490,7 +1490,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
 fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "
             * one
                 - a
@@ -1559,7 +1559,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
     language_registry.add(html_language.clone());
     language_registry.add(javascript_language.clone());
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let (text, ranges) = marked_text_ranges(
             &"
                 <div>Λ‡
@@ -1610,7 +1610,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
         settings.defaults.tab_size = Some(2.try_into().unwrap());
     });
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), "").with_language(Arc::new(ruby_lang()), cx);
 
@@ -1653,7 +1653,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
 fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let language = Language::new(
             LanguageConfig {
                 name: "JavaScript".into(),
@@ -1742,7 +1742,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
 fn test_language_scope_at_with_rust(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
@@ -1810,7 +1810,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
 fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = r#"
             <ol>
             <% people.each do |person| %>
@@ -1858,7 +1858,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
 fn test_serialization(cx: &mut gpui2::AppContext) {
     let mut now = Instant::now();
 
-    let buffer1 = cx.entity(|cx| {
+    let buffer1 = cx.build_model(|cx| {
         let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "abc");
         buffer.edit([(3..3, "D")], None, cx);
 
@@ -1881,7 +1881,7 @@ fn test_serialization(cx: &mut gpui2::AppContext) {
     let ops = cx
         .executor()
         .block(buffer1.read(cx).serialize_ops(None, cx));
-    let buffer2 = cx.entity(|cx| {
+    let buffer2 = cx.build_model(|cx| {
         let mut buffer = Buffer::from_proto(1, state, None).unwrap();
         buffer
             .apply_ops(
@@ -1914,10 +1914,11 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
     let mut replica_ids = Vec::new();
     let mut buffers = Vec::new();
     let network = Arc::new(Mutex::new(Network::new(rng.clone())));
-    let base_buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str()));
+    let base_buffer =
+        cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str()));
 
     for i in 0..rng.gen_range(min_peers..=max_peers) {
-        let buffer = cx.entity(|cx| {
+        let buffer = cx.build_model(|cx| {
             let state = base_buffer.read(cx).to_proto();
             let ops = cx
                 .executor()
@@ -2034,7 +2035,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                     new_replica_id,
                     replica_id
                 );
-                new_buffer = Some(cx.entity(|cx| {
+                new_buffer = Some(cx.build_model(|cx| {
                     let mut new_buffer =
                         Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
                     new_buffer
@@ -2396,7 +2397,7 @@ fn javascript_lang() -> Language {
     .unwrap()
 }
 
-fn get_tree_sexp(buffer: &Handle<Buffer>, cx: &mut gpui2::TestAppContext) -> String {
+fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui2::TestAppContext) -> String {
     buffer.update(cx, |buffer, _| {
         let snapshot = buffer.snapshot();
         let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
@@ -2412,7 +2413,7 @@ fn assert_bracket_pairs(
     cx: &mut AppContext,
 ) {
     let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), expected_text.clone())
             .with_language(Arc::new(language), cx)
     });

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

@@ -1,7 +1,7 @@
 use anyhow::Context;
 use collections::{HashMap, HashSet};
 use fs::Fs;
-use gpui2::{AsyncAppContext, Handle};
+use gpui2::{AsyncAppContext, Model};
 use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff};
 use lsp2::{LanguageServer, LanguageServerId};
 use node_runtime::NodeRuntime;
@@ -183,7 +183,7 @@ impl Prettier {
 
     pub async fn format(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         buffer_path: Option<PathBuf>,
         cx: &mut AsyncAppContext,
     ) -> anyhow::Result<Diff> {

crates/project2/src/lsp_command.rs πŸ”—

@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client2::proto::{self, PeerId};
 use futures::future;
-use gpui2::{AppContext, AsyncAppContext, Handle};
+use gpui2::{AppContext, AsyncAppContext, Model};
 use language2::{
     language_settings::{language_settings, InlayHintKind},
     point_from_lsp, point_to_lsp,
@@ -53,8 +53,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send {
     async fn response_from_lsp(
         self,
         message: <Self::LspRequest as lsp2::request::Request>::Result,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
@@ -63,8 +63,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self>;
 
@@ -79,8 +79,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send {
     async fn response_from_proto(
         self,
         message: <Self::ProtoRequest as proto::RequestMessage>::Response,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
 
@@ -180,8 +180,8 @@ impl LspCommand for PrepareRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::PrepareRenameResponse>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
@@ -215,8 +215,8 @@ impl LspCommand for PrepareRename {
 
     async fn from_proto(
         message: proto::PrepareRename,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -256,8 +256,8 @@ impl LspCommand for PrepareRename {
     async fn response_from_proto(
         self,
         message: proto::PrepareRenameResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
         if message.can_rename {
@@ -307,8 +307,8 @@ impl LspCommand for PerformRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::WorkspaceEdit>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
@@ -343,8 +343,8 @@ impl LspCommand for PerformRename {
 
     async fn from_proto(
         message: proto::PerformRename,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -379,8 +379,8 @@ impl LspCommand for PerformRename {
     async fn response_from_proto(
         self,
         message: proto::PerformRenameResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
         let message = message
@@ -426,8 +426,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::GotoDefinitionResponse>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -447,8 +447,8 @@ impl LspCommand for GetDefinition {
 
     async fn from_proto(
         message: proto::GetDefinition,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -479,8 +479,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetDefinitionResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -527,8 +527,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::GotoTypeDefinitionResponse>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -548,8 +548,8 @@ impl LspCommand for GetTypeDefinition {
 
     async fn from_proto(
         message: proto::GetTypeDefinition,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -580,8 +580,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetTypeDefinitionResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -593,8 +593,8 @@ impl LspCommand for GetTypeDefinition {
 }
 
 fn language_server_for_buffer(
-    project: &Handle<Project>,
-    buffer: &Handle<Buffer>,
+    project: &Model<Project>,
+    buffer: &Model<Buffer>,
     server_id: LanguageServerId,
     cx: &mut AsyncAppContext,
 ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
@@ -609,7 +609,7 @@ fn language_server_for_buffer(
 
 async fn location_links_from_proto(
     proto_links: Vec<proto::LocationLink>,
-    project: Handle<Project>,
+    project: Model<Project>,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
     let mut links = Vec::new();
@@ -671,8 +671,8 @@ async fn location_links_from_proto(
 
 async fn location_links_from_lsp(
     message: Option<lsp2::GotoDefinitionResponse>,
-    project: Handle<Project>,
-    buffer: Handle<Buffer>,
+    project: Model<Project>,
+    buffer: Model<Buffer>,
     server_id: LanguageServerId,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
@@ -814,8 +814,8 @@ impl LspCommand for GetReferences {
     async fn response_from_lsp(
         self,
         locations: Option<Vec<lsp2::Location>>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
@@ -868,8 +868,8 @@ impl LspCommand for GetReferences {
 
     async fn from_proto(
         message: proto::GetReferences,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -910,8 +910,8 @@ impl LspCommand for GetReferences {
     async fn response_from_proto(
         self,
         message: proto::GetReferencesResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
         let mut locations = Vec::new();
@@ -977,8 +977,8 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_lsp(
         self,
         lsp_highlights: Option<Vec<lsp2::DocumentHighlight>>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
@@ -1016,8 +1016,8 @@ impl LspCommand for GetDocumentHighlights {
 
     async fn from_proto(
         message: proto::GetDocumentHighlights,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1060,8 +1060,8 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_proto(
         self,
         message: proto::GetDocumentHighlightsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
         let mut highlights = Vec::new();
@@ -1123,8 +1123,8 @@ impl LspCommand for GetHover {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::Hover>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
@@ -1206,8 +1206,8 @@ impl LspCommand for GetHover {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1272,8 +1272,8 @@ impl LspCommand for GetHover {
     async fn response_from_proto(
         self,
         message: proto::GetHoverResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
         let contents: Vec<_> = message
@@ -1341,8 +1341,8 @@ impl LspCommand for GetCompletions {
     async fn response_from_lsp(
         self,
         completions: Option<lsp2::CompletionResponse>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
@@ -1484,8 +1484,8 @@ impl LspCommand for GetCompletions {
 
     async fn from_proto(
         message: proto::GetCompletions,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let version = deserialize_version(&message.version);
@@ -1523,8 +1523,8 @@ impl LspCommand for GetCompletions {
     async fn response_from_proto(
         self,
         message: proto::GetCompletionsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         buffer
@@ -1589,8 +1589,8 @@ impl LspCommand for GetCodeActions {
     async fn response_from_lsp(
         self,
         actions: Option<lsp2::CodeActionResponse>,
-        _: Handle<Project>,
-        _: Handle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         server_id: LanguageServerId,
         _: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
@@ -1623,8 +1623,8 @@ impl LspCommand for GetCodeActions {
 
     async fn from_proto(
         message: proto::GetCodeActions,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -1663,8 +1663,8 @@ impl LspCommand for GetCodeActions {
     async fn response_from_proto(
         self,
         message: proto::GetCodeActionsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
         buffer
@@ -1726,8 +1726,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp2::TextEdit>>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
@@ -1763,8 +1763,8 @@ impl LspCommand for OnTypeFormatting {
 
     async fn from_proto(
         message: proto::OnTypeFormatting,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1805,8 +1805,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_proto(
         self,
         message: proto::OnTypeFormattingResponse,
-        _: Handle<Project>,
-        _: Handle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
         let Some(transaction) = message.transaction else {
@@ -1825,7 +1825,7 @@ impl LspCommand for OnTypeFormatting {
 impl InlayHints {
     pub async fn lsp_to_project_hint(
         lsp_hint: lsp2::InlayHint,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         server_id: LanguageServerId,
         resolve_state: ResolveState,
         force_no_type_left_padding: bool,
@@ -2230,8 +2230,8 @@ impl LspCommand for InlayHints {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp2::InlayHint>>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
@@ -2286,8 +2286,8 @@ impl LspCommand for InlayHints {
 
     async fn from_proto(
         message: proto::InlayHints,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -2326,8 +2326,8 @@ impl LspCommand for InlayHints {
     async fn response_from_proto(
         self,
         message: proto::InlayHintsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
         buffer

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

@@ -26,7 +26,7 @@ use futures::{
 };
 use globset::{Glob, GlobSet, GlobSetBuilder};
 use gpui2::{
-    AnyHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Handle, ModelContext,
+    AnyHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext,
     Task, WeakHandle,
 };
 use itertools::Itertools;
@@ -128,7 +128,7 @@ pub struct Project {
     next_entry_id: Arc<AtomicUsize>,
     join_project_response_message_id: u32,
     next_diagnostic_group_id: usize,
-    user_store: Handle<UserStore>,
+    user_store: Model<UserStore>,
     fs: Arc<dyn Fs>,
     client_state: Option<ProjectClientState>,
     collaborators: HashMap<proto::PeerId, Collaborator>,
@@ -140,17 +140,17 @@ pub struct Project {
     #[allow(clippy::type_complexity)]
     loading_buffers_by_path: HashMap<
         ProjectPath,
-        postage::watch::Receiver<Option<Result<Handle<Buffer>, Arc<anyhow::Error>>>>,
+        postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
     >,
     #[allow(clippy::type_complexity)]
     loading_local_worktrees:
-        HashMap<Arc<Path>, Shared<Task<Result<Handle<Worktree>, Arc<anyhow::Error>>>>>,
+        HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
     opened_buffers: HashMap<u64, OpenBuffer>,
     local_buffer_ids_by_path: HashMap<ProjectPath, u64>,
     local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, u64>,
     /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
     /// Used for re-issuing buffer requests when peers temporarily disconnect
-    incomplete_remote_buffers: HashMap<u64, Option<Handle<Buffer>>>,
+    incomplete_remote_buffers: HashMap<u64, Option<Model<Buffer>>>,
     buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
     buffers_being_formatted: HashSet<u64>,
     buffers_needing_diff: HashSet<WeakHandle<Buffer>>,
@@ -244,14 +244,14 @@ enum LocalProjectUpdate {
 }
 
 enum OpenBuffer {
-    Strong(Handle<Buffer>),
+    Strong(Model<Buffer>),
     Weak(WeakHandle<Buffer>),
     Operations(Vec<Operation>),
 }
 
 #[derive(Clone)]
 enum WorktreeHandle {
-    Strong(Handle<Worktree>),
+    Strong(Model<Worktree>),
     Weak(WeakHandle<Worktree>),
 }
 
@@ -344,7 +344,7 @@ pub struct DiagnosticSummary {
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Location {
-    pub buffer: Handle<Buffer>,
+    pub buffer: Model<Buffer>,
     pub range: Range<language2::Anchor>,
 }
 
@@ -457,7 +457,7 @@ impl Hover {
 }
 
 #[derive(Default)]
-pub struct ProjectTransaction(pub HashMap<Handle<Buffer>, language2::Transaction>);
+pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language2::Transaction>);
 
 impl DiagnosticSummary {
     fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
@@ -527,7 +527,7 @@ pub enum FormatTrigger {
 }
 
 struct ProjectLspAdapterDelegate {
-    project: Handle<Project>,
+    project: Model<Project>,
     http_client: Arc<dyn HttpClient>,
 }
 
@@ -543,7 +543,7 @@ impl FormatTrigger {
 #[derive(Clone, Debug, PartialEq)]
 enum SearchMatchCandidate {
     OpenBuffer {
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         // This might be an unnamed file without representation on filesystem
         path: Option<Arc<Path>>,
     },
@@ -621,12 +621,12 @@ impl Project {
     pub fn local(
         client: Arc<Client>,
         node: Arc<dyn NodeRuntime>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         cx: &mut AppContext,
-    ) -> Handle<Self> {
-        cx.entity(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.build_model(|cx: &mut ModelContext<Self>| {
             let (tx, rx) = mpsc::unbounded();
             cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
@@ -687,11 +687,11 @@ impl Project {
     pub async fn remote(
         remote_id: u64,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         mut cx: AsyncAppContext,
-    ) -> Result<Handle<Self>> {
+    ) -> Result<Model<Self>> {
         client.authenticate_and_connect(true, &cx).await?;
 
         let subscription = client.subscribe_to_entity(remote_id)?;
@@ -700,7 +700,7 @@ impl Project {
                 project_id: remote_id,
             })
             .await?;
-        let this = cx.entity(|cx| {
+        let this = cx.build_model(|cx| {
             let replica_id = response.payload.replica_id as ReplicaId;
 
             let mut worktrees = Vec::new();
@@ -981,7 +981,7 @@ impl Project {
         cx.notify();
     }
 
-    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Handle<Buffer>> {
+    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Model<Buffer>> {
         self.opened_buffers
             .get(&remote_id)
             .and_then(|buffer| buffer.upgrade())
@@ -995,11 +995,11 @@ impl Project {
         self.client.clone()
     }
 
-    pub fn user_store(&self) -> Handle<UserStore> {
+    pub fn user_store(&self) -> Model<UserStore> {
         self.user_store.clone()
     }
 
-    pub fn opened_buffers(&self) -> Vec<Handle<Buffer>> {
+    pub fn opened_buffers(&self) -> Vec<Model<Buffer>> {
         self.opened_buffers
             .values()
             .filter_map(|b| b.upgrade())
@@ -1061,7 +1061,7 @@ impl Project {
     }
 
     /// Collect all worktrees, including ones that don't appear in the project panel
-    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Handle<Worktree>> {
+    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees
             .iter()
             .filter_map(move |worktree| worktree.upgrade())
@@ -1071,7 +1071,7 @@ impl Project {
     pub fn visible_worktrees<'a>(
         &'a self,
         cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = Handle<Worktree>> {
+    ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees.iter().filter_map(|worktree| {
             worktree.upgrade().and_then(|worktree| {
                 if worktree.read(cx).is_visible() {
@@ -1088,7 +1088,7 @@ impl Project {
             .map(|tree| tree.read(cx).root_name())
     }
 
-    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Handle<Worktree>> {
+    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
         self.worktrees()
             .find(|worktree| worktree.read(cx).id() == id)
     }
@@ -1097,7 +1097,7 @@ impl Project {
         &self,
         entry_id: ProjectEntryId,
         cx: &AppContext,
-    ) -> Option<Handle<Worktree>> {
+    ) -> Option<Model<Worktree>> {
         self.worktrees()
             .find(|worktree| worktree.read(cx).contains_entry(entry_id))
     }
@@ -1652,12 +1652,12 @@ impl Project {
         text: &str,
         language: Option<Arc<Language>>,
         cx: &mut ModelContext<Self>,
-    ) -> Result<Handle<Buffer>> {
+    ) -> Result<Model<Buffer>> {
         if self.is_remote() {
             return Err(anyhow!("creating buffers as a guest is not supported yet"));
         }
         let id = post_inc(&mut self.next_buffer_id);
-        let buffer = cx.entity(|cx| {
+        let buffer = cx.build_model(|cx| {
             Buffer::new(self.replica_id(), id, text).with_language(
                 language.unwrap_or_else(|| language2::PLAIN_TEXT.clone()),
                 cx,
@@ -1690,7 +1690,7 @@ impl Project {
         &mut self,
         abs_path: impl AsRef<Path>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if let Some((worktree, relative_path)) = self.find_local_worktree(abs_path.as_ref(), cx) {
             self.open_buffer((worktree.read(cx).id(), relative_path), cx)
         } else {
@@ -1702,7 +1702,7 @@ impl Project {
         &mut self,
         path: impl Into<ProjectPath>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let project_path = path.into();
         let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
             worktree
@@ -1757,9 +1757,9 @@ impl Project {
     fn open_local_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let buffer_id = post_inc(&mut self.next_buffer_id);
         let load_buffer = worktree.update(cx, |worktree, cx| {
             let worktree = worktree.as_local_mut().unwrap();
@@ -1775,9 +1775,9 @@ impl Project {
     fn open_remote_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let rpc = self.client.clone();
         let project_id = self.remote_id().unwrap();
         let remote_worktree_id = worktree.read(cx).id();
@@ -1805,7 +1805,7 @@ impl Project {
         language_server_id: LanguageServerId,
         language_server_name: LanguageServerName,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         cx.spawn(move |this, mut cx| async move {
             let abs_path = abs_path
                 .to_file_path()
@@ -1843,7 +1843,7 @@ impl Project {
         &mut self,
         id: u64,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if let Some(buffer) = self.buffer_for_id(id) {
             Task::ready(Ok(buffer))
         } else if self.is_local() {
@@ -1866,7 +1866,7 @@ impl Project {
 
     pub fn save_buffers(
         &self,
-        buffers: HashSet<Handle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         cx.spawn(move |this, mut cx| async move {
@@ -1881,7 +1881,7 @@ impl Project {
 
     pub fn save_buffer(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
@@ -1897,7 +1897,7 @@ impl Project {
 
     pub fn save_buffer_as(
         &mut self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         abs_path: PathBuf,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
@@ -1933,7 +1933,7 @@ impl Project {
         &mut self,
         path: &ProjectPath,
         cx: &mut ModelContext<Self>,
-    ) -> Option<Handle<Buffer>> {
+    ) -> Option<Model<Buffer>> {
         let worktree = self.worktree_for_id(path.worktree_id, cx)?;
         self.opened_buffers.values().find_map(|buffer| {
             let buffer = buffer.upgrade()?;
@@ -1948,7 +1948,7 @@ impl Project {
 
     fn register_buffer(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         self.request_buffer_diff_recalculation(buffer, cx);
@@ -2030,7 +2030,7 @@ impl Project {
 
     fn register_buffer_with_language_servers(
         &mut self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) {
         let buffer = buffer_handle.read(cx);
@@ -2114,7 +2114,7 @@ impl Project {
 
     fn unregister_buffer_from_language_servers(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         old_file: &File,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2149,7 +2149,7 @@ impl Project {
 
     fn register_buffer_with_copilot(
         &self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) {
         if let Some(copilot) = Copilot::global(cx) {
@@ -2267,7 +2267,7 @@ impl Project {
 
     fn on_buffer_event(
         &mut self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         event: &BufferEvent,
         cx: &mut ModelContext<Self>,
     ) -> Option<()> {
@@ -2480,7 +2480,7 @@ impl Project {
 
     fn request_buffer_diff_recalculation(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) {
         self.buffers_needing_diff.insert(buffer.downgrade());
@@ -2676,7 +2676,7 @@ impl Project {
 
     fn detect_language_for_buffer(
         &mut self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Option<()> {
         // If the buffer has a language, set it and start the language server if we haven't already.
@@ -2694,7 +2694,7 @@ impl Project {
 
     pub fn set_language_for_buffer(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         new_language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2735,7 +2735,7 @@ impl Project {
 
     fn start_language_servers(
         &mut self,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         worktree_path: Arc<Path>,
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
@@ -3350,10 +3350,10 @@ impl Project {
 
     pub fn restart_language_servers_for_buffers(
         &mut self,
-        buffers: impl IntoIterator<Item = Handle<Buffer>>,
+        buffers: impl IntoIterator<Item = Model<Buffer>>,
         cx: &mut ModelContext<Self>,
     ) -> Option<()> {
-        let language_server_lookup_info: HashSet<(Handle<Worktree>, Arc<Language>)> = buffers
+        let language_server_lookup_info: HashSet<(Model<Worktree>, Arc<Language>)> = buffers
             .into_iter()
             .filter_map(|buffer| {
                 let buffer = buffer.read(cx);
@@ -3377,7 +3377,7 @@ impl Project {
     // TODO This will break in the case where the adapter's root paths and worktrees are not equal
     fn restart_language_servers(
         &mut self,
-        worktree: Handle<Worktree>,
+        worktree: Model<Worktree>,
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
@@ -3949,7 +3949,7 @@ impl Project {
 
     fn update_buffer_diagnostics(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         server_id: LanguageServerId,
         version: Option<i32>,
         mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
@@ -4022,7 +4022,7 @@ impl Project {
 
     pub fn reload_buffers(
         &self,
-        buffers: HashSet<Handle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<ProjectTransaction>> {
@@ -4088,7 +4088,7 @@ impl Project {
 
     pub fn format(
         &self,
-        buffers: HashSet<Handle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         push_to_history: bool,
         trigger: FormatTrigger,
         cx: &mut ModelContext<Project>,
@@ -4361,7 +4361,7 @@ impl Project {
 
     async fn format_via_lsp(
         this: &WeakHandle<Self>,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         abs_path: &Path,
         language_server: &Arc<LanguageServer>,
         tab_size: NonZeroU32,
@@ -4410,7 +4410,7 @@ impl Project {
     }
 
     async fn format_via_external_command(
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         buffer_abs_path: &Path,
         command: &str,
         arguments: &[String],
@@ -4470,7 +4470,7 @@ impl Project {
 
     pub fn definition<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<LocationLink>>> {
@@ -4485,7 +4485,7 @@ impl Project {
 
     pub fn type_definition<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<LocationLink>>> {
@@ -4500,7 +4500,7 @@ impl Project {
 
     pub fn references<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Location>>> {
@@ -4515,7 +4515,7 @@ impl Project {
 
     pub fn document_highlights<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<DocumentHighlight>>> {
@@ -4694,7 +4694,7 @@ impl Project {
         &mut self,
         symbol: &Symbol,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if self.is_local() {
             let language_server_id = if let Some(id) = self.language_server_ids.get(&(
                 symbol.source_worktree_id,
@@ -4748,7 +4748,7 @@ impl Project {
 
     pub fn hover<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Option<Hover>>> {
@@ -4763,7 +4763,7 @@ impl Project {
 
     pub fn completions<T: ToOffset + ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>> {
@@ -4817,7 +4817,7 @@ impl Project {
 
     pub fn apply_additional_edits_for_completion(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         completion: Completion,
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
@@ -4928,7 +4928,7 @@ impl Project {
 
     pub fn code_actions<T: Clone + ToOffset>(
         &self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         range: Range<T>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<CodeAction>>> {
@@ -4944,7 +4944,7 @@ impl Project {
 
     pub fn apply_code_action(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         mut action: CodeAction,
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
@@ -5052,7 +5052,7 @@ impl Project {
 
     fn apply_on_type_formatting(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: Anchor,
         trigger: String,
         cx: &mut ModelContext<Self>,
@@ -5113,8 +5113,8 @@ impl Project {
     }
 
     async fn deserialize_edits(
-        this: Handle<Self>,
-        buffer_to_edit: Handle<Buffer>,
+        this: Model<Self>,
+        buffer_to_edit: Model<Buffer>,
         edits: Vec<lsp2::TextEdit>,
         push_to_history: bool,
         _: Arc<CachedLspAdapter>,
@@ -5155,7 +5155,7 @@ impl Project {
     }
 
     async fn deserialize_workspace_edit(
-        this: Handle<Self>,
+        this: Model<Self>,
         edit: lsp2::WorkspaceEdit,
         push_to_history: bool,
         lsp_adapter: Arc<CachedLspAdapter>,
@@ -5310,7 +5310,7 @@ impl Project {
 
     pub fn prepare_rename<T: ToPointUtf16>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Option<Range<Anchor>>>> {
@@ -5325,7 +5325,7 @@ impl Project {
 
     pub fn perform_rename<T: ToPointUtf16>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: T,
         new_name: String,
         push_to_history: bool,
@@ -5346,7 +5346,7 @@ impl Project {
 
     pub fn on_type_format<T: ToPointUtf16>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: T,
         trigger: String,
         push_to_history: bool,
@@ -5375,7 +5375,7 @@ impl Project {
 
     pub fn inlay_hints<T: ToOffset>(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         range: Range<T>,
         cx: &mut ModelContext<Self>,
     ) -> Task<anyhow::Result<Vec<InlayHint>>> {
@@ -5436,7 +5436,7 @@ impl Project {
     pub fn resolve_inlay_hint(
         &self,
         hint: InlayHint,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         server_id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) -> Task<anyhow::Result<InlayHint>> {
@@ -5501,7 +5501,7 @@ impl Project {
         &self,
         query: SearchQuery,
         cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Handle<Buffer>, Vec<Range<Anchor>>)> {
+    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
         if self.is_local() {
             self.search_local(query, cx)
         } else if let Some(project_id) = self.remote_id() {
@@ -5545,7 +5545,7 @@ impl Project {
         &self,
         query: SearchQuery,
         cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Handle<Buffer>, Vec<Range<Anchor>>)> {
+    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
         // Local search is split into several phases.
         // TL;DR is that we do 2 passes; initial pass to pick files which contain at least one match
         // and the second phase that finds positions of all the matches found in the candidate files.
@@ -5638,7 +5638,7 @@ impl Project {
                     .scoped(|scope| {
                         #[derive(Clone)]
                         struct FinishedStatus {
-                            entry: Option<(Handle<Buffer>, Vec<Range<Anchor>>)>,
+                            entry: Option<(Model<Buffer>, Vec<Range<Anchor>>)>,
                             buffer_index: SearchMatchCandidateIndex,
                         }
 
@@ -5728,8 +5728,8 @@ impl Project {
 
     /// Pick paths that might potentially contain a match of a given search query.
     async fn background_search(
-        unnamed_buffers: Vec<Handle<Buffer>>,
-        opened_buffers: HashMap<Arc<Path>, (Handle<Buffer>, BufferSnapshot)>,
+        unnamed_buffers: Vec<Model<Buffer>>,
+        opened_buffers: HashMap<Arc<Path>, (Model<Buffer>, BufferSnapshot)>,
         executor: Executor,
         fs: Arc<dyn Fs>,
         workers: usize,
@@ -5829,7 +5829,7 @@ impl Project {
 
     fn request_lsp<R: LspCommand>(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         server: LanguageServerToQuery,
         request: R,
         cx: &mut ModelContext<Self>,
@@ -5893,7 +5893,7 @@ impl Project {
 
     fn send_lsp_proto_request<R: LspCommand>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         project_id: u64,
         request: R,
         cx: &mut ModelContext<'_, Project>,
@@ -5922,7 +5922,7 @@ impl Project {
     ) -> (
         futures::channel::oneshot::Receiver<Vec<SearchMatchCandidate>>,
         Receiver<(
-            Option<(Handle<Buffer>, BufferSnapshot)>,
+            Option<(Model<Buffer>, BufferSnapshot)>,
             SearchMatchCandidateIndex,
         )>,
     ) {
@@ -5976,7 +5976,7 @@ impl Project {
         abs_path: impl AsRef<Path>,
         visible: bool,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Handle<Worktree>, PathBuf)>> {
+    ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
         let abs_path = abs_path.as_ref();
         if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
             Task::ready(Ok((tree, relative_path)))
@@ -5991,7 +5991,7 @@ impl Project {
         &self,
         abs_path: &Path,
         cx: &AppContext,
-    ) -> Option<(Handle<Worktree>, PathBuf)> {
+    ) -> Option<(Model<Worktree>, PathBuf)> {
         for tree in &self.worktrees {
             if let Some(tree) = tree.upgrade() {
                 if let Some(relative_path) = tree
@@ -6018,7 +6018,7 @@ impl Project {
         abs_path: impl AsRef<Path>,
         visible: bool,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Worktree>>> {
+    ) -> Task<Result<Model<Worktree>>> {
         let fs = self.fs.clone();
         let client = self.client.clone();
         let next_entry_id = self.next_entry_id.clone();
@@ -6078,7 +6078,7 @@ impl Project {
         self.metadata_changed(cx);
     }
 
-    fn add_worktree(&mut self, worktree: &Handle<Worktree>, cx: &mut ModelContext<Self>) {
+    fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
         cx.observe(worktree, |_, _, cx| cx.notify()).detach();
         if worktree.read(cx).is_local() {
             cx.subscribe(worktree, |this, worktree, event, cx| match event {
@@ -6128,7 +6128,7 @@ impl Project {
 
     fn update_local_worktree_buffers(
         &mut self,
-        worktree_handle: &Handle<Worktree>,
+        worktree_handle: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<Self>,
     ) {
@@ -6242,7 +6242,7 @@ impl Project {
 
     fn update_local_worktree_language_servers(
         &mut self,
-        worktree_handle: &Handle<Worktree>,
+        worktree_handle: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<Self>,
     ) {
@@ -6304,7 +6304,7 @@ impl Project {
 
     fn update_local_worktree_buffers_git_repos(
         &mut self,
-        worktree_handle: Handle<Worktree>,
+        worktree_handle: Model<Worktree>,
         changed_repos: &UpdatedGitRepositoriesSet,
         cx: &mut ModelContext<Self>,
     ) {
@@ -6407,7 +6407,7 @@ impl Project {
 
     fn update_local_worktree_settings(
         &mut self,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         changes: &UpdatedEntriesSet,
         cx: &mut ModelContext<Self>,
     ) {
@@ -6473,7 +6473,7 @@ impl Project {
 
     fn update_prettier_settings(
         &self,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<'_, Project>,
     ) {
@@ -6636,7 +6636,7 @@ impl Project {
     // RPC message handlers
 
     async fn handle_unshare_project(
-        this: Handle<Self>,
+        this: Model<Self>,
         _: TypedEnvelope<proto::UnshareProject>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6652,7 +6652,7 @@ impl Project {
     }
 
     async fn handle_add_collaborator(
-        this: Handle<Self>,
+        this: Model<Self>,
         mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6676,7 +6676,7 @@ impl Project {
     }
 
     async fn handle_update_project_collaborator(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6726,7 +6726,7 @@ impl Project {
     }
 
     async fn handle_remove_collaborator(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6755,7 +6755,7 @@ impl Project {
     }
 
     async fn handle_update_project(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateProject>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6770,7 +6770,7 @@ impl Project {
     }
 
     async fn handle_update_worktree(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateWorktree>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6788,7 +6788,7 @@ impl Project {
     }
 
     async fn handle_update_worktree_settings(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6812,7 +6812,7 @@ impl Project {
     }
 
     async fn handle_create_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CreateProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6837,7 +6837,7 @@ impl Project {
     }
 
     async fn handle_rename_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::RenameProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6865,7 +6865,7 @@ impl Project {
     }
 
     async fn handle_copy_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CopyProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6893,7 +6893,7 @@ impl Project {
     }
 
     async fn handle_delete_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::DeleteProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6923,7 +6923,7 @@ impl Project {
     }
 
     async fn handle_expand_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::ExpandProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6946,7 +6946,7 @@ impl Project {
     }
 
     async fn handle_update_diagnostic_summary(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6976,7 +6976,7 @@ impl Project {
     }
 
     async fn handle_start_language_server(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::StartLanguageServer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7001,7 +7001,7 @@ impl Project {
     }
 
     async fn handle_update_language_server(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateLanguageServer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7058,7 +7058,7 @@ impl Project {
     }
 
     async fn handle_update_buffer(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateBuffer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7094,7 +7094,7 @@ impl Project {
     }
 
     async fn handle_create_buffer_for_peer(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CreateBufferForPeer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7117,7 +7117,7 @@ impl Project {
                     }
 
                     let buffer_id = state.id;
-                    let buffer = cx.entity(|_| {
+                    let buffer = cx.build_model(|_| {
                         Buffer::from_proto(this.replica_id(), state, buffer_file).unwrap()
                     });
                     this.incomplete_remote_buffers
@@ -7154,7 +7154,7 @@ impl Project {
     }
 
     async fn handle_update_diff_base(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateDiffBase>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7180,7 +7180,7 @@ impl Project {
     }
 
     async fn handle_update_buffer_file(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateBufferFile>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7215,7 +7215,7 @@ impl Project {
     }
 
     async fn handle_save_buffer(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::SaveBuffer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7251,7 +7251,7 @@ impl Project {
     }
 
     async fn handle_reload_buffers(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::ReloadBuffers>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7280,7 +7280,7 @@ impl Project {
     }
 
     async fn handle_synchronize_buffers(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::SynchronizeBuffers>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7373,7 +7373,7 @@ impl Project {
     }
 
     async fn handle_format_buffers(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::FormatBuffers>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7403,7 +7403,7 @@ impl Project {
     }
 
     async fn handle_apply_additional_edits_for_completion(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7440,7 +7440,7 @@ impl Project {
     }
 
     async fn handle_apply_code_action(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::ApplyCodeAction>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7471,7 +7471,7 @@ impl Project {
     }
 
     async fn handle_on_type_formatting(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::OnTypeFormatting>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,

crates/project2/src/terminals.rs πŸ”—

@@ -1,5 +1,5 @@
 use crate::Project;
-use gpui2::{AnyWindowHandle, Context, Handle, ModelContext, WeakHandle};
+use gpui2::{AnyWindowHandle, Context, Model, ModelContext, WeakHandle};
 use settings2::Settings;
 use std::path::{Path, PathBuf};
 use terminal2::{
@@ -20,7 +20,7 @@ impl Project {
         working_directory: Option<PathBuf>,
         window: AnyWindowHandle,
         cx: &mut ModelContext<Self>,
-    ) -> anyhow::Result<Handle<Terminal>> {
+    ) -> anyhow::Result<Model<Terminal>> {
         if self.is_remote() {
             return Err(anyhow::anyhow!(
                 "creating terminals as a guest is not supported yet"
@@ -40,7 +40,7 @@ impl Project {
                 |_, _| todo!("color_for_index"),
             )
             .map(|builder| {
-                let terminal_handle = cx.entity(|cx| builder.subscribe(cx));
+                let terminal_handle = cx.build_model(|cx| builder.subscribe(cx));
 
                 self.terminals
                     .local_handles
@@ -108,7 +108,7 @@ impl Project {
     fn activate_python_virtual_environment(
         &mut self,
         activate_script: Option<PathBuf>,
-        terminal_handle: &Handle<Terminal>,
+        terminal_handle: &Model<Terminal>,
         cx: &mut ModelContext<Project>,
     ) {
         if let Some(activate_script) = activate_script {

crates/project2/src/worktree.rs πŸ”—

@@ -22,7 +22,7 @@ use futures::{
 use fuzzy2::CharBag;
 use git::{DOT_GIT, GITIGNORE};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Executor, Handle, ModelContext, Task,
+    AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext, Task,
 };
 use language2::{
     proto::{
@@ -292,7 +292,7 @@ impl Worktree {
         fs: Arc<dyn Fs>,
         next_entry_id: Arc<AtomicUsize>,
         cx: &mut AsyncAppContext,
-    ) -> Result<Handle<Self>> {
+    ) -> Result<Model<Self>> {
         // After determining whether the root entry is a file or a directory, populate the
         // snapshot's "root name", which will be used for the purpose of fuzzy matching.
         let abs_path = path.into();
@@ -301,7 +301,7 @@ impl Worktree {
             .await
             .context("failed to stat worktree path")?;
 
-        cx.entity(move |cx: &mut ModelContext<Worktree>| {
+        cx.build_model(move |cx: &mut ModelContext<Worktree>| {
             let root_name = abs_path
                 .file_name()
                 .map_or(String::new(), |f| f.to_string_lossy().to_string());
@@ -406,8 +406,8 @@ impl Worktree {
         worktree: proto::WorktreeMetadata,
         client: Arc<Client>,
         cx: &mut AppContext,
-    ) -> Handle<Self> {
-        cx.entity(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.build_model(|cx: &mut ModelContext<Self>| {
             let snapshot = Snapshot {
                 id: WorktreeId(worktree.id as usize),
                 abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
@@ -593,7 +593,7 @@ impl LocalWorktree {
         id: u64,
         path: &Path,
         cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let path = Arc::from(path);
         cx.spawn(move |this, mut cx| async move {
             let (file, contents, diff_base) = this
@@ -603,7 +603,7 @@ impl LocalWorktree {
                 .executor()
                 .spawn(async move { text::Buffer::new(0, id, contents) })
                 .await;
-            cx.entity(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
+            cx.build_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
         })
     }
 
@@ -920,7 +920,7 @@ impl LocalWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         path: Arc<Path>,
         has_changed_file: bool,
         cx: &mut ModelContext<Worktree>,
@@ -1331,7 +1331,7 @@ impl RemoteWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
         let buffer = buffer_handle.read(cx);
@@ -2577,7 +2577,7 @@ impl fmt::Debug for Snapshot {
 
 #[derive(Clone, PartialEq)]
 pub struct File {
-    pub worktree: Handle<Worktree>,
+    pub worktree: Model<Worktree>,
     pub path: Arc<Path>,
     pub mtime: SystemTime,
     pub(crate) entry_id: ProjectEntryId,
@@ -2701,7 +2701,7 @@ impl language2::LocalFile for File {
 }
 
 impl File {
-    pub fn for_entry(entry: Entry, worktree: Handle<Worktree>) -> Arc<Self> {
+    pub fn for_entry(entry: Entry, worktree: Model<Worktree>) -> Arc<Self> {
         Arc::new(Self {
             worktree,
             path: entry.path.clone(),
@@ -2714,7 +2714,7 @@ impl File {
 
     pub fn from_proto(
         proto: rpc2::proto::File,
-        worktree: Handle<Worktree>,
+        worktree: Model<Worktree>,
         cx: &AppContext,
     ) -> Result<Self> {
         let worktree_id = worktree

crates/storybook2/src/stories/kitchen_sink.rs πŸ”—

@@ -14,7 +14,7 @@ impl KitchenSinkStory {
 
     pub fn view(cx: &mut AppContext) -> View<Self> {
         {
-            let state = cx.entity(|cx| Self::new());
+            let state = cx.build_model(|cx| Self::new());
             let render = Self::render;
             View::for_handle(state, render)
         }

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

@@ -18,7 +18,7 @@ use settings2::{default_settings, Settings, SettingsStore};
 use simplelog::SimpleLogger;
 use story_selector::ComponentStory;
 use theme2::{ThemeRegistry, ThemeSettings};
-use ui::{prelude::*, themed};
+use ui::prelude::*;
 
 use crate::assets::Assets;
 use crate::story_selector::StorySelector;
@@ -86,7 +86,7 @@ fn main() {
             },
             move |cx| {
                 cx.build_view(
-                    |cx| StoryWrapper::new(selector.story(cx), theme),
+                    |cx| StoryWrapper::new(selector.story(cx)),
                     StoryWrapper::render,
                 )
             },
@@ -99,22 +99,19 @@ fn main() {
 #[derive(Clone)]
 pub struct StoryWrapper {
     story: AnyView,
-    theme: Theme,
 }
 
 impl StoryWrapper {
-    pub(crate) fn new(story: AnyView, theme: Theme) -> Self {
-        Self { story, theme }
+    pub(crate) fn new(story: AnyView) -> Self {
+        Self { story }
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
-        themed(self.theme.clone(), cx, |cx| {
-            div()
-                .flex()
-                .flex_col()
-                .size_full()
-                .child(self.story.clone())
-        })
+        div()
+            .flex()
+            .flex_col()
+            .size_full()
+            .child(self.story.clone())
     }
 }
 

crates/ui2/src/components/buffer_search.rs πŸ”—

@@ -23,7 +23,7 @@ impl BufferSearch {
 
     pub fn view(cx: &mut AppContext) -> View<Self> {
         {
-            let state = cx.entity(|cx| Self::new());
+            let state = cx.build_model(|cx| Self::new());
             let render = Self::render;
             View::for_handle(state, render)
         }

crates/ui2/src/components/editor_pane.rs πŸ”—

@@ -44,7 +44,7 @@ impl EditorPane {
 
     pub fn view(cx: &mut AppContext) -> View<Self> {
         {
-            let state = cx.entity(|cx| hello_world_rust_editor_with_status_example(cx));
+            let state = cx.build_model(|cx| hello_world_rust_editor_with_status_example(cx));
             let render = Self::render;
             View::for_handle(state, render)
         }

crates/ui2/src/components/title_bar.rs πŸ”—

@@ -82,7 +82,7 @@ impl TitleBar {
 
     pub fn view(cx: &mut AppContext, livestream: Option<Livestream>) -> View<Self> {
         {
-            let state = cx.entity(|cx| Self::new(cx).set_livestream(livestream));
+            let state = cx.build_model(|cx| Self::new(cx).set_livestream(livestream));
             let render = Self::render;
             View::for_handle(state, render)
         }
@@ -198,7 +198,7 @@ mod stories {
     impl TitleBarStory {
         pub fn view(cx: &mut AppContext) -> View<Self> {
             {
-                let state = cx.entity(|cx| Self {
+                let state = cx.build_model(|cx| Self {
                     title_bar: TitleBar::view(cx, None),
                 });
                 let render = Self::render;

crates/ui2/src/components/workspace.rs πŸ”—

@@ -3,13 +3,13 @@ use std::sync::Arc;
 use chrono::DateTime;
 use gpui2::{px, relative, rems, AppContext, Context, Size, View};
 
+use crate::{prelude::*, NotificationsPanel};
 use crate::{
-    old_theme, static_livestream, user_settings_mut, v_stack, AssistantPanel, Button, ChatMessage,
-    ChatPanel, CollabPanel, EditorPane, FakeSettings, Label, LanguageSelector, Pane, PaneGroup,
-    Panel, PanelAllowedSides, PanelSide, ProjectPanel, SettingValue, SplitDirection, StatusBar,
-    Terminal, TitleBar, Toast, ToastOrigin,
+    static_livestream, user_settings_mut, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel,
+    CollabPanel, EditorPane, FakeSettings, Label, LanguageSelector, Pane, PaneGroup, Panel,
+    PanelAllowedSides, PanelSide, ProjectPanel, SettingValue, SplitDirection, StatusBar, Terminal,
+    TitleBar, Toast, ToastOrigin,
 };
-use crate::{prelude::*, NotificationsPanel};
 
 #[derive(Clone)]
 pub struct Gpui2UiDebug {
@@ -172,14 +172,14 @@ impl Workspace {
 
     pub fn view(cx: &mut AppContext) -> View<Self> {
         {
-            let state = cx.entity(|cx| Self::new(cx));
+            let state = cx.build_model(|cx| Self::new(cx));
             let render = Self::render;
             View::for_handle(state, render)
         }
     }
 
     pub fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
-        let theme = old_theme(cx).clone();
+        let theme = theme(cx);
 
         // HACK: This should happen inside of `debug_toggle_user_settings`, but
         // we don't have `cx.global::<FakeSettings>()` in event handlers at the moment.
@@ -216,8 +216,8 @@ impl Workspace {
             .gap_0()
             .justify_start()
             .items_start()
-            .text_color(theme.lowest.base.default.foreground)
-            .bg(theme.lowest.base.default.background)
+            .text_color(theme.text)
+            .bg(theme.background)
             .child(self.title_bar.clone())
             .child(
                 div()
@@ -228,7 +228,7 @@ impl Workspace {
                     .overflow_hidden()
                     .border_t()
                     .border_b()
-                    .border_color(theme.lowest.base.default.border)
+                    .border_color(theme.border)
                     .children(
                         Some(
                             Panel::new("project-panel-outer", cx)

crates/ui2/src/elements/icon.rs πŸ”—

@@ -2,7 +2,6 @@ use gpui2::{svg, Hsla};
 use strum::EnumIter;
 
 use crate::prelude::*;
-use crate::theme::old_theme;
 
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum IconSize {
@@ -27,17 +26,17 @@ pub enum IconColor {
 
 impl IconColor {
     pub fn color(self, cx: &WindowContext) -> Hsla {
-        let theme = old_theme(cx);
+        let theme = theme(cx);
         match self {
-            IconColor::Default => theme.lowest.base.default.foreground,
-            IconColor::Muted => theme.lowest.variant.default.foreground,
-            IconColor::Disabled => theme.lowest.base.disabled.foreground,
-            IconColor::Placeholder => theme.lowest.base.disabled.foreground,
-            IconColor::Accent => theme.lowest.accent.default.foreground,
-            IconColor::Error => theme.lowest.negative.default.foreground,
-            IconColor::Warning => theme.lowest.warning.default.foreground,
-            IconColor::Success => theme.lowest.positive.default.foreground,
-            IconColor::Info => theme.lowest.accent.default.foreground,
+            IconColor::Default => gpui2::red(),
+            IconColor::Muted => gpui2::red(),
+            IconColor::Disabled => gpui2::red(),
+            IconColor::Placeholder => gpui2::red(),
+            IconColor::Accent => gpui2::red(),
+            IconColor::Error => gpui2::red(),
+            IconColor::Warning => gpui2::red(),
+            IconColor::Success => gpui2::red(),
+            IconColor::Info => gpui2::red()
         }
     }
 }

crates/ui2/src/elements/label.rs πŸ”—

@@ -2,8 +2,6 @@ use gpui2::{relative, Hsla, WindowContext};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
-use crate::theme::old_theme;
-
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum LabelColor {
     #[default]
@@ -21,19 +19,17 @@ pub enum LabelColor {
 impl LabelColor {
     pub fn hsla(&self, cx: &WindowContext) -> Hsla {
         let theme = theme(cx);
-        // TODO: Remove
-        let old_theme = old_theme(cx);
 
         match self {
             Self::Default => theme.text,
             Self::Muted => theme.text_muted,
-            Self::Created => old_theme.middle.positive.default.foreground,
-            Self::Modified => old_theme.middle.warning.default.foreground,
-            Self::Deleted => old_theme.middle.negative.default.foreground,
+            Self::Created => gpui2::red(),
+            Self::Modified => gpui2::red(),
+            Self::Deleted => gpui2::red(),
             Self::Disabled => theme.text_disabled,
-            Self::Hidden => old_theme.middle.variant.default.foreground,
+            Self::Hidden => gpui2::red(),
             Self::Placeholder => theme.text_placeholder,
-            Self::Accent => old_theme.middle.accent.default.foreground,
+            Self::Accent => gpui2::red(),
         }
     }
 }

crates/ui2/src/prelude.rs πŸ”—

@@ -5,7 +5,7 @@ pub use gpui2::{
 
 pub use crate::elevation::*;
 use crate::settings::user_settings;
-pub use crate::{old_theme, theme, ButtonVariant, Theme};
+pub use crate::{theme, ButtonVariant};
 
 use gpui2::{rems, Hsla, Rems};
 use strum::EnumIter;

crates/ui2/src/theme.rs πŸ”—

@@ -1,7 +1,4 @@
-use gpui2::{
-    AnyElement, AppContext, Bounds, Component, Element, Hsla, LayoutId, Pixels, Result,
-    ViewContext, WindowContext,
-};
+use gpui2::{AppContext, Hsla, Result};
 use serde::{de::Visitor, Deserialize, Deserializer};
 use std::collections::HashMap;
 use std::fmt;
@@ -132,94 +129,6 @@ where
     deserializer.deserialize_map(SyntaxVisitor)
 }
 
-pub fn themed<V, E, F>(theme: Theme, cx: &mut ViewContext<V>, build_child: F) -> Themed<E>
-where
-    V: 'static,
-    E: Element<V>,
-    F: FnOnce(&mut ViewContext<V>) -> E,
-{
-    cx.default_global::<ThemeStack>().0.push(theme.clone());
-    let child = build_child(cx);
-    cx.default_global::<ThemeStack>().0.pop();
-    Themed { theme, child }
-}
-
-pub struct Themed<E> {
-    pub(crate) theme: Theme,
-    pub(crate) child: E,
-}
-
-impl<V, E> Component<V> for Themed<E>
-where
-    V: 'static,
-    E: 'static + Element<V> + Send,
-    E::ElementState: Send,
-{
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
-#[derive(Default)]
-struct ThemeStack(Vec<Theme>);
-
-impl<V, E: 'static + Element<V> + Send> Element<V> for Themed<E>
-where
-    V: 'static,
-    E::ElementState: Send,
-{
-    type ElementState = E::ElementState;
-
-    fn id(&self) -> Option<gpui2::ElementId> {
-        None
-    }
-
-    fn initialize(
-        &mut self,
-        view_state: &mut V,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<V>,
-    ) -> Self::ElementState {
-        cx.default_global::<ThemeStack>().0.push(self.theme.clone());
-        let element_state = self.child.initialize(view_state, element_state, cx);
-        cx.default_global::<ThemeStack>().0.pop();
-        element_state
-    }
-
-    fn layout(
-        &mut self,
-        view_state: &mut V,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<V>,
-    ) -> LayoutId
-    where
-        Self: Sized,
-    {
-        cx.default_global::<ThemeStack>().0.push(self.theme.clone());
-        let layout_id = self.child.layout(view_state, element_state, cx);
-        cx.default_global::<ThemeStack>().0.pop();
-        layout_id
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        view_state: &mut V,
-        frame_state: &mut Self::ElementState,
-        cx: &mut ViewContext<V>,
-    ) where
-        Self: Sized,
-    {
-        cx.default_global::<ThemeStack>().0.push(self.theme.clone());
-        self.child.paint(bounds, view_state, frame_state, cx);
-        cx.default_global::<ThemeStack>().0.pop();
-    }
-}
-
-pub fn old_theme(cx: &WindowContext) -> Arc<Theme> {
-    Arc::new(cx.global::<Theme>().clone())
-}
-
 pub fn theme(cx: &AppContext) -> Arc<theme2::Theme> {
     theme2::active_theme(cx).clone()
 }

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

@@ -0,0 +1,1096 @@
+// use crate::{
+//     pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders,
+//     ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
+// };
+// use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
+use anyhow::Result;
+use client2::{
+    proto::{self, PeerId, ViewId},
+    Client,
+};
+use settings2::Settings;
+use theme2::Theme;
+// use client2::{
+//     proto::{self, PeerId},
+//     Client,
+// };
+// use gpui2::geometry::vector::Vector2F;
+// use gpui2::AnyWindowHandle;
+// use gpui2::{
+//     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Handle, Task, View,
+//     ViewContext, View, WeakViewHandle, WindowContext,
+// };
+// use project2::{Project, ProjectEntryId, ProjectPath};
+// use schemars::JsonSchema;
+// use serde_derive::{Deserialize, Serialize};
+// use settings2::Setting;
+// use smallvec::SmallVec;
+// use std::{
+//     any::{Any, TypeId},
+//     borrow::Cow,
+//     cell::RefCell,
+//     fmt,
+//     ops::Range,
+//     path::PathBuf,
+//     rc::Rc,
+//     sync::{
+//         atomic::{AtomicBool, Ordering},
+//         Arc,
+//     },
+//     time::Duration,
+// };
+// use theme2::Theme;
+
+// #[derive(Deserialize)]
+// pub struct ItemSettings {
+//     pub git_status: bool,
+//     pub close_position: ClosePosition,
+// }
+
+// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+// #[serde(rename_all = "lowercase")]
+// pub enum ClosePosition {
+//     Left,
+//     #[default]
+//     Right,
+// }
+
+// impl ClosePosition {
+//     pub fn right(&self) -> bool {
+//         match self {
+//             ClosePosition::Left => false,
+//             ClosePosition::Right => true,
+//         }
+//     }
+// }
+
+// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+// pub struct ItemSettingsContent {
+//     git_status: Option<bool>,
+//     close_position: Option<ClosePosition>,
+// }
+
+// impl Setting for ItemSettings {
+//     const KEY: Option<&'static str> = Some("tabs");
+
+//     type FileContent = ItemSettingsContent;
+
+//     fn load(
+//         default_value: &Self::FileContent,
+//         user_values: &[&Self::FileContent],
+//         _: &gpui2::AppContext,
+//     ) -> anyhow::Result<Self> {
+//         Self::load_via_json_merge(default_value, user_values)
+//     }
+// }
+
+#[derive(Eq, PartialEq, Hash, Debug)]
+pub enum ItemEvent {
+    CloseItem,
+    UpdateTab,
+    UpdateBreadcrumbs,
+    Edit,
+}
+
+// TODO: Combine this with existing HighlightedText struct?
+pub struct BreadcrumbText {
+    pub text: String,
+    pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
+}
+
+pub trait Item: EventEmitter + Sized {
+    //     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
+    //     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
+    //     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+    //         false
+    //     }
+    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
+        None
+    }
+    fn tab_description(&self, _: usize, _: &AppContext) -> Option<SharedString> {
+        None
+    }
+    fn tab_content<V: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<V>;
+
+    fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) {
+    } // (model id, Item)
+    fn is_singleton(&self, _cx: &AppContext) -> bool {
+        false
+    }
+    //     fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
+    fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
+    where
+        Self: Sized,
+    {
+        None
+    }
+    //     fn is_dirty(&self, _: &AppContext) -> bool {
+    //         false
+    //     }
+    //     fn has_conflict(&self, _: &AppContext) -> bool {
+    //         false
+    //     }
+    //     fn can_save(&self, _cx: &AppContext) -> bool {
+    //         false
+    //     }
+    //     fn save(
+    //         &mut self,
+    //         _project: Handle<Project>,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         unimplemented!("save() must be implemented if can_save() returns true")
+    //     }
+    //     fn save_as(
+    //         &mut self,
+    //         _project: Handle<Project>,
+    //         _abs_path: PathBuf,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         unimplemented!("save_as() must be implemented if can_save() returns true")
+    //     }
+    //     fn reload(
+    //         &mut self,
+    //         _project: Handle<Project>,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         unimplemented!("reload() must be implemented if can_save() returns true")
+    //     }
+    fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+        SmallVec::new()
+    }
+    //     fn should_close_item_on_event(_: &Self::Event) -> bool {
+    //         false
+    //     }
+    //     fn should_update_tab_on_event(_: &Self::Event) -> bool {
+    //         false
+    //     }
+
+    //     fn act_as_type<'a>(
+    //         &'a self,
+    //         type_id: TypeId,
+    //         self_handle: &'a View<Self>,
+    //         _: &'a AppContext,
+    //     ) -> Option<&AnyViewHandle> {
+    //         if TypeId::of::<Self>() == type_id {
+    //             Some(self_handle)
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    //         None
+    //     }
+
+    //     fn breadcrumb_location(&self) -> ToolbarItemLocation {
+    //         ToolbarItemLocation::Hidden
+    //     }
+
+    //     fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+    //         None
+    //     }
+
+    //     fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
+
+    //     fn serialized_item_kind() -> Option<&'static str> {
+    //         None
+    //     }
+
+    //     fn deserialize(
+    //         _project: Handle<Project>,
+    //         _workspace: WeakViewHandle<Workspace>,
+    //         _workspace_id: WorkspaceId,
+    //         _item_id: ItemId,
+    //         _cx: &mut ViewContext<Pane>,
+    //     ) -> Task<Result<View<Self>>> {
+    //         unimplemented!(
+    //             "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
+    //         )
+    //     }
+    //     fn show_toolbar(&self) -> bool {
+    //         true
+    //     }
+    //     fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
+    //         None
+    //     }
+}
+
+use std::{
+    any::Any,
+    cell::RefCell,
+    ops::Range,
+    path::PathBuf,
+    rc::Rc,
+    sync::{
+        atomic::{AtomicBool, Ordering},
+        Arc,
+    },
+    time::Duration,
+};
+
+use gpui2::{
+    AnyElement, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, Point,
+    SharedString, Task, View, ViewContext, VisualContext, WindowContext,
+};
+use project2::{Project, ProjectEntryId, ProjectPath};
+use smallvec::SmallVec;
+
+use crate::{
+    pane::{self, Pane},
+    searchable::SearchableItemHandle,
+    workspace_settings::{AutosaveSetting, WorkspaceSettings},
+    DelayedDebouncedEditAction, FollowableItemBuilders, ToolbarItemLocation, Workspace,
+    WorkspaceId,
+};
+
+pub trait ItemHandle: 'static + Send {
+    fn subscribe_to_item_events(
+        &self,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
+    ) -> gpui2::Subscription;
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
+    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
+    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane>;
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace>;
+    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
+    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
+    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
+    fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item));
+    fn is_singleton(&self, cx: &AppContext) -> bool;
+    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
+    fn clone_on_split(
+        &self,
+        workspace_id: WorkspaceId,
+        cx: &mut WindowContext,
+    ) -> Option<Box<dyn ItemHandle>>;
+    fn added_to_pane(
+        &self,
+        workspace: &mut Workspace,
+        pane: View<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    );
+    fn deactivated(&self, cx: &mut WindowContext);
+    fn workspace_deactivated(&self, cx: &mut WindowContext);
+    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
+    fn id(&self) -> usize;
+    fn window(&self) -> AnyWindowHandle;
+    // fn as_any(&self) -> &AnyView; todo!()
+    fn is_dirty(&self, cx: &AppContext) -> bool;
+    fn has_conflict(&self, cx: &AppContext) -> bool;
+    fn can_save(&self, cx: &AppContext) -> bool;
+    fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    fn save_as(
+        &self,
+        project: Handle<Project>,
+        abs_path: PathBuf,
+        cx: &mut WindowContext,
+    ) -> Task<Result<()>>;
+    fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!()
+    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
+    fn on_release(
+        &self,
+        cx: &mut AppContext,
+        callback: Box<dyn FnOnce(&mut AppContext)>,
+    ) -> gpui2::Subscription;
+    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
+    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
+    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
+    fn serialized_item_kind(&self) -> Option<&'static str>;
+    fn show_toolbar(&self, cx: &AppContext) -> bool;
+    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
+}
+
+pub trait WeakItemHandle {
+    fn id(&self) -> usize;
+    fn window(&self) -> AnyWindowHandle;
+    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
+}
+
+// todo!()
+// impl dyn ItemHandle {
+//     pub fn downcast<T: View>(&self) -> Option<View<T>> {
+//         self.as_any().clone().downcast()
+//     }
+
+//     pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<View<T>> {
+//         self.act_as_type(TypeId::of::<T>(), cx)
+//             .and_then(|t| t.clone().downcast())
+//     }
+// }
+
+impl<T: Item> ItemHandle for View<T> {
+    fn subscribe_to_item_events(
+        &self,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
+    ) -> gpui2::Subscription {
+        cx.subscribe(self, move |_, event, cx| {
+            for item_event in T::to_item_events(event) {
+                handler(item_event, cx)
+            }
+        })
+    }
+
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
+        self.read(cx).tab_tooltip_text(cx)
+    }
+
+    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString> {
+        self.read(cx).tab_description(detail, cx)
+    }
+
+    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane> {
+        self.read(cx).tab_content(detail, cx)
+    }
+
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace> {
+        self.read(cx).tab_content(detail, cx)
+    }
+
+    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
+        let this = self.read(cx);
+        let mut result = None;
+        if this.is_singleton(cx) {
+            this.for_each_project_item(cx, &mut |_, item| {
+                result = item.project_path(cx);
+            });
+        }
+        result
+    }
+
+    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
+        let mut result = SmallVec::new();
+        self.read(cx).for_each_project_item(cx, &mut |_, item| {
+            if let Some(id) = item.entry_id(cx) {
+                result.push(id);
+            }
+        });
+        result
+    }
+
+    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> {
+        let mut result = SmallVec::new();
+        self.read(cx).for_each_project_item(cx, &mut |id, _| {
+            result.push(id);
+        });
+        result
+    }
+
+    fn for_each_project_item(
+        &self,
+        cx: &AppContext,
+        f: &mut dyn FnMut(usize, &dyn project2::Item),
+    ) {
+        self.read(cx).for_each_project_item(cx, f)
+    }
+
+    fn is_singleton(&self, cx: &AppContext) -> bool {
+        self.read(cx).is_singleton(cx)
+    }
+
+    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
+        Box::new(self.clone())
+    }
+
+    fn clone_on_split(
+        &self,
+        workspace_id: WorkspaceId,
+        cx: &mut WindowContext,
+    ) -> Option<Box<dyn ItemHandle>> {
+        self.update(cx, |item, cx| {
+            cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
+        })
+        .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
+    }
+
+    fn added_to_pane(
+        &self,
+        workspace: &mut Workspace,
+        pane: View<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let history = pane.read(cx).nav_history_for_item(self);
+        self.update(cx, |this, cx| {
+            this.set_nav_history(history, cx);
+            this.added_to_workspace(workspace, cx);
+        });
+
+        if let Some(followed_item) = self.to_followable_item_handle(cx) {
+            if let Some(message) = followed_item.to_state_proto(cx) {
+                workspace.update_followers(
+                    followed_item.is_project_item(cx),
+                    proto::update_followers::Variant::CreateView(proto::View {
+                        id: followed_item
+                            .remote_id(&workspace.app_state.client, cx)
+                            .map(|id| id.to_proto()),
+                        variant: Some(message),
+                        leader_id: workspace.leader_for_pane(&pane),
+                    }),
+                    cx,
+                );
+            }
+        }
+
+        if workspace
+            .panes_by_item
+            .insert(self.id(), pane.downgrade())
+            .is_none()
+        {
+            let mut pending_autosave = DelayedDebouncedEditAction::new();
+            let pending_update = Rc::new(RefCell::new(None));
+            let pending_update_scheduled = Rc::new(AtomicBool::new(false));
+
+            let mut event_subscription =
+                Some(cx.subscribe(self, move |workspace, item, event, cx| {
+                    let pane = if let Some(pane) = workspace
+                        .panes_by_item
+                        .get(&item.id())
+                        .and_then(|pane| pane.upgrade(cx))
+                    {
+                        pane
+                    } else {
+                        log::error!("unexpected item event after pane was dropped");
+                        return;
+                    };
+
+                    if let Some(item) = item.to_followable_item_handle(cx) {
+                        let is_project_item = item.is_project_item(cx);
+                        let leader_id = workspace.leader_for_pane(&pane);
+
+                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
+                            workspace.unfollow(&pane, cx);
+                        }
+
+                        if item.add_event_to_update_proto(
+                            event,
+                            &mut *pending_update.borrow_mut(),
+                            cx,
+                        ) && !pending_update_scheduled.load(Ordering::SeqCst)
+                        {
+                            pending_update_scheduled.store(true, Ordering::SeqCst);
+                            cx.after_window_update({
+                                let pending_update = pending_update.clone();
+                                let pending_update_scheduled = pending_update_scheduled.clone();
+                                move |this, cx| {
+                                    pending_update_scheduled.store(false, Ordering::SeqCst);
+                                    this.update_followers(
+                                        is_project_item,
+                                        proto::update_followers::Variant::UpdateView(
+                                            proto::UpdateView {
+                                                id: item
+                                                    .remote_id(&this.app_state.client, cx)
+                                                    .map(|id| id.to_proto()),
+                                                variant: pending_update.borrow_mut().take(),
+                                                leader_id,
+                                            },
+                                        ),
+                                        cx,
+                                    );
+                                }
+                            });
+                        }
+                    }
+
+                    for item_event in T::to_item_events(event).into_iter() {
+                        match item_event {
+                            ItemEvent::CloseItem => {
+                                pane.update(cx, |pane, cx| {
+                                    pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
+                                })
+                                .detach_and_log_err(cx);
+                                return;
+                            }
+
+                            ItemEvent::UpdateTab => {
+                                pane.update(cx, |_, cx| {
+                                    cx.emit(pane::Event::ChangeItemTitle);
+                                    cx.notify();
+                                });
+                            }
+
+                            ItemEvent::Edit => {
+                                let autosave = WorkspaceSettings::get_global(cx).autosave;
+                                if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
+                                    let delay = Duration::from_millis(milliseconds);
+                                    let item = item.clone();
+                                    pending_autosave.fire_new(delay, cx, move |workspace, cx| {
+                                        Pane::autosave_item(&item, workspace.project().clone(), cx)
+                                    });
+                                }
+                            }
+
+                            _ => {}
+                        }
+                    }
+                }));
+
+            cx.observe_focus(self, move |workspace, item, focused, cx| {
+                if !focused
+                    && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
+                {
+                    Pane::autosave_item(&item, workspace.project.clone(), cx)
+                        .detach_and_log_err(cx);
+                }
+            })
+            .detach();
+
+            let item_id = self.id();
+            cx.observe_release(self, move |workspace, _, _| {
+                workspace.panes_by_item.remove(&item_id);
+                event_subscription.take();
+            })
+            .detach();
+        }
+
+        cx.defer(|workspace, cx| {
+            workspace.serialize_workspace(cx);
+        });
+    }
+
+    fn deactivated(&self, cx: &mut WindowContext) {
+        self.update(cx, |this, cx| this.deactivated(cx));
+    }
+
+    fn workspace_deactivated(&self, cx: &mut WindowContext) {
+        self.update(cx, |this, cx| this.workspace_deactivated(cx));
+    }
+
+    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
+        self.update(cx, |this, cx| this.navigate(data, cx))
+    }
+
+    fn id(&self) -> usize {
+        self.id()
+    }
+
+    fn window(&self) -> AnyWindowHandle {
+        todo!()
+        // AnyViewHandle::window(self)
+    }
+
+    // todo!()
+    // fn as_any(&self) -> &AnyViewHandle {
+    //     self
+    // }
+
+    fn is_dirty(&self, cx: &AppContext) -> bool {
+        self.read(cx).is_dirty(cx)
+    }
+
+    fn has_conflict(&self, cx: &AppContext) -> bool {
+        self.read(cx).has_conflict(cx)
+    }
+
+    fn can_save(&self, cx: &AppContext) -> bool {
+        self.read(cx).can_save(cx)
+    }
+
+    fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+        self.update(cx, |item, cx| item.save(project, cx))
+    }
+
+    fn save_as(
+        &self,
+        project: Handle<Project>,
+        abs_path: PathBuf,
+        cx: &mut WindowContext,
+    ) -> Task<anyhow::Result<()>> {
+        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
+    }
+
+    fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+        self.update(cx, |item, cx| item.reload(project, cx))
+    }
+
+    // todo!()
+    // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
+    //     self.read(cx).act_as_type(type_id, self, cx)
+    // }
+
+    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
+        if cx.has_global::<FollowableItemBuilders>() {
+            let builders = cx.global::<FollowableItemBuilders>();
+            let item = self.as_any();
+            Some(builders.get(&item.view_type())?.1(item))
+        } else {
+            None
+        }
+    }
+
+    fn on_release(
+        &self,
+        cx: &mut AppContext,
+        callback: Box<dyn FnOnce(&mut AppContext)>,
+    ) -> gpui2::Subscription {
+        cx.observe_release(self, move |_, cx| callback(cx))
+    }
+
+    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+        self.read(cx).as_searchable(self)
+    }
+
+    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
+        self.read(cx).breadcrumb_location()
+    }
+
+    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+        self.read(cx).breadcrumbs(theme, cx)
+    }
+
+    fn serialized_item_kind(&self) -> Option<&'static str> {
+        T::serialized_item_kind()
+    }
+
+    fn show_toolbar(&self, cx: &AppContext) -> bool {
+        self.read(cx).show_toolbar()
+    }
+
+    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
+        self.read(cx).pixel_position_of_cursor(cx)
+    }
+}
+
+// impl From<Box<dyn ItemHandle>> for AnyViewHandle {
+//     fn from(val: Box<dyn ItemHandle>) -> Self {
+//         val.as_any().clone()
+//     }
+// }
+
+// impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
+//     fn from(val: &Box<dyn ItemHandle>) -> Self {
+//         val.as_any().clone()
+//     }
+// }
+
+impl Clone for Box<dyn ItemHandle> {
+    fn clone(&self) -> Box<dyn ItemHandle> {
+        self.boxed_clone()
+    }
+}
+
+// impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
+//     fn id(&self) -> usize {
+//         self.id()
+//     }
+
+//     fn window(&self) -> AnyWindowHandle {
+//         self.window()
+//     }
+
+//     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+//         self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
+//     }
+// }
+
+pub trait ProjectItem: Item {
+    type Item: project2::Item;
+
+    fn for_project_item(
+        project: Handle<Project>,
+        item: Handle<Self::Item>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self
+    where
+        Self: Sized;
+}
+
+pub trait FollowableItem: Item {
+    fn remote_id(&self) -> Option<ViewId>;
+    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn from_state_proto(
+        pane: View<Pane>,
+        project: View<Workspace>,
+        id: ViewId,
+        state: &mut Option<proto::view::Variant>,
+        cx: &mut AppContext,
+    ) -> Option<Task<Result<View<Self>>>>;
+    fn add_event_to_update_proto(
+        &self,
+        event: &Self::Event,
+        update: &mut Option<proto::update_view::Variant>,
+        cx: &AppContext,
+    ) -> bool;
+    fn apply_update_proto(
+        &mut self,
+        project: &Handle<Project>,
+        message: proto::update_view::Variant,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Result<()>>;
+    fn is_project_item(&self, cx: &AppContext) -> bool;
+
+    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
+    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
+}
+
+pub trait FollowableItemHandle: ItemHandle {
+    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
+    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
+    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn add_event_to_update_proto(
+        &self,
+        event: &dyn Any,
+        update: &mut Option<proto::update_view::Variant>,
+        cx: &AppContext,
+    ) -> bool;
+    fn apply_update_proto(
+        &self,
+        project: &Handle<Project>,
+        message: proto::update_view::Variant,
+        cx: &mut WindowContext,
+    ) -> Task<Result<()>>;
+    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
+    fn is_project_item(&self, cx: &AppContext) -> bool;
+}
+
+// impl<T: FollowableItem> FollowableItemHandle for View<T> {
+//     fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
+//         self.read(cx).remote_id().or_else(|| {
+//             client.peer_id().map(|creator| ViewId {
+//                 creator,
+//                 id: self.id() as u64,
+//             })
+//         })
+//     }
+
+//     fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
+//         self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
+//     }
+
+//     fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+//         self.read(cx).to_state_proto(cx)
+//     }
+
+//     fn add_event_to_update_proto(
+//         &self,
+//         event: &dyn Any,
+//         update: &mut Option<proto::update_view::Variant>,
+//         cx: &AppContext,
+//     ) -> bool {
+//         if let Some(event) = event.downcast_ref() {
+//             self.read(cx).add_event_to_update_proto(event, update, cx)
+//         } else {
+//             false
+//         }
+//     }
+
+//     fn apply_update_proto(
+//         &self,
+//         project: &Handle<Project>,
+//         message: proto::update_view::Variant,
+//         cx: &mut WindowContext,
+//     ) -> Task<Result<()>> {
+//         self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
+//     }
+
+//     fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
+//         if let Some(event) = event.downcast_ref() {
+//             T::should_unfollow_on_event(event, cx)
+//         } else {
+//             false
+//         }
+//     }
+
+//     fn is_project_item(&self, cx: &AppContext) -> bool {
+//         self.read(cx).is_project_item(cx)
+//     }
+// }
+
+// #[cfg(any(test, feature = "test-support"))]
+// pub mod test {
+//     use super::{Item, ItemEvent};
+//     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
+//     use gpui2::{
+//         elements::Empty, AnyElement, AppContext, Element, Entity, Handle, Task, View,
+//         ViewContext, View, WeakViewHandle,
+//     };
+//     use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
+//     use smallvec::SmallVec;
+//     use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
+
+//     pub struct TestProjectItem {
+//         pub entry_id: Option<ProjectEntryId>,
+//         pub project_path: Option<ProjectPath>,
+//     }
+
+//     pub struct TestItem {
+//         pub workspace_id: WorkspaceId,
+//         pub state: String,
+//         pub label: String,
+//         pub save_count: usize,
+//         pub save_as_count: usize,
+//         pub reload_count: usize,
+//         pub is_dirty: bool,
+//         pub is_singleton: bool,
+//         pub has_conflict: bool,
+//         pub project_items: Vec<Handle<TestProjectItem>>,
+//         pub nav_history: Option<ItemNavHistory>,
+//         pub tab_descriptions: Option<Vec<&'static str>>,
+//         pub tab_detail: Cell<Option<usize>>,
+//     }
+
+//     impl Entity for TestProjectItem {
+//         type Event = ();
+//     }
+
+//     impl project2::Item for TestProjectItem {
+//         fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
+//             self.entry_id
+//         }
+
+//         fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
+//             self.project_path.clone()
+//         }
+//     }
+
+//     pub enum TestItemEvent {
+//         Edit,
+//     }
+
+//     impl Clone for TestItem {
+//         fn clone(&self) -> Self {
+//             Self {
+//                 state: self.state.clone(),
+//                 label: self.label.clone(),
+//                 save_count: self.save_count,
+//                 save_as_count: self.save_as_count,
+//                 reload_count: self.reload_count,
+//                 is_dirty: self.is_dirty,
+//                 is_singleton: self.is_singleton,
+//                 has_conflict: self.has_conflict,
+//                 project_items: self.project_items.clone(),
+//                 nav_history: None,
+//                 tab_descriptions: None,
+//                 tab_detail: Default::default(),
+//                 workspace_id: self.workspace_id,
+//             }
+//         }
+//     }
+
+//     impl TestProjectItem {
+//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Handle<Self> {
+//             let entry_id = Some(ProjectEntryId::from_proto(id));
+//             let project_path = Some(ProjectPath {
+//                 worktree_id: WorktreeId::from_usize(0),
+//                 path: Path::new(path).into(),
+//             });
+//             cx.add_model(|_| Self {
+//                 entry_id,
+//                 project_path,
+//             })
+//         }
+
+//         pub fn new_untitled(cx: &mut AppContext) -> Handle<Self> {
+//             cx.add_model(|_| Self {
+//                 project_path: None,
+//                 entry_id: None,
+//             })
+//         }
+//     }
+
+//     impl TestItem {
+//         pub fn new() -> Self {
+//             Self {
+//                 state: String::new(),
+//                 label: String::new(),
+//                 save_count: 0,
+//                 save_as_count: 0,
+//                 reload_count: 0,
+//                 is_dirty: false,
+//                 has_conflict: false,
+//                 project_items: Vec::new(),
+//                 is_singleton: true,
+//                 nav_history: None,
+//                 tab_descriptions: None,
+//                 tab_detail: Default::default(),
+//                 workspace_id: 0,
+//             }
+//         }
+
+//         pub fn new_deserialized(id: WorkspaceId) -> Self {
+//             let mut this = Self::new();
+//             this.workspace_id = id;
+//             this
+//         }
+
+//         pub fn with_label(mut self, state: &str) -> Self {
+//             self.label = state.to_string();
+//             self
+//         }
+
+//         pub fn with_singleton(mut self, singleton: bool) -> Self {
+//             self.is_singleton = singleton;
+//             self
+//         }
+
+//         pub fn with_dirty(mut self, dirty: bool) -> Self {
+//             self.is_dirty = dirty;
+//             self
+//         }
+
+//         pub fn with_conflict(mut self, has_conflict: bool) -> Self {
+//             self.has_conflict = has_conflict;
+//             self
+//         }
+
+//         pub fn with_project_items(mut self, items: &[Handle<TestProjectItem>]) -> Self {
+//             self.project_items.clear();
+//             self.project_items.extend(items.iter().cloned());
+//             self
+//         }
+
+//         pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
+//             self.push_to_nav_history(cx);
+//             self.state = state;
+//         }
+
+//         fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
+//             if let Some(history) = &mut self.nav_history {
+//                 history.push(Some(Box::new(self.state.clone())), cx);
+//             }
+//         }
+//     }
+
+//     impl Entity for TestItem {
+//         type Event = TestItemEvent;
+//     }
+
+//     impl View for TestItem {
+//         fn ui_name() -> &'static str {
+//             "TestItem"
+//         }
+
+//         fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
+//             Empty::new().into_any()
+//         }
+//     }
+
+//     impl Item for TestItem {
+//         fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
+//             self.tab_descriptions.as_ref().and_then(|descriptions| {
+//                 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
+//                 Some(description.into())
+//             })
+//         }
+
+//         fn tab_content<V: 'static>(
+//             &self,
+//             detail: Option<usize>,
+//             _: &theme2::Tab,
+//             _: &AppContext,
+//         ) -> AnyElement<V> {
+//             self.tab_detail.set(detail);
+//             Empty::new().into_any()
+//         }
+
+//         fn for_each_project_item(
+//             &self,
+//             cx: &AppContext,
+//             f: &mut dyn FnMut(usize, &dyn project2::Item),
+//         ) {
+//             self.project_items
+//                 .iter()
+//                 .for_each(|item| f(item.id(), item.read(cx)))
+//         }
+
+// fn is_singleton(&self, _: &AppContext) -> bool {
+//     self.is_singleton
+// }
+
+//         fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+//             self.nav_history = Some(history);
+//         }
+
+//         fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+//             let state = *state.downcast::<String>().unwrap_or_default();
+//             if state != self.state {
+//                 self.state = state;
+//                 true
+//             } else {
+//                 false
+//             }
+//         }
+
+//         fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+//             self.push_to_nav_history(cx);
+//         }
+
+//         fn clone_on_split(
+//             &self,
+//             _workspace_id: WorkspaceId,
+//             _: &mut ViewContext<Self>,
+//         ) -> Option<Self>
+//         where
+//             Self: Sized,
+//         {
+//             Some(self.clone())
+//         }
+
+//         fn is_dirty(&self, _: &AppContext) -> bool {
+//             self.is_dirty
+//         }
+
+//         fn has_conflict(&self, _: &AppContext) -> bool {
+//             self.has_conflict
+//         }
+
+//         fn can_save(&self, cx: &AppContext) -> bool {
+//             !self.project_items.is_empty()
+//                 && self
+//                     .project_items
+//                     .iter()
+//                     .all(|item| item.read(cx).entry_id.is_some())
+//         }
+
+//         fn save(
+//             &mut self,
+//             _: Handle<Project>,
+//             _: &mut ViewContext<Self>,
+//         ) -> Task<anyhow::Result<()>> {
+//             self.save_count += 1;
+//             self.is_dirty = false;
+//             Task::ready(Ok(()))
+//         }
+
+//         fn save_as(
+//             &mut self,
+//             _: Handle<Project>,
+//             _: std::path::PathBuf,
+//             _: &mut ViewContext<Self>,
+//         ) -> Task<anyhow::Result<()>> {
+//             self.save_as_count += 1;
+//             self.is_dirty = false;
+//             Task::ready(Ok(()))
+//         }
+
+//         fn reload(
+//             &mut self,
+//             _: Handle<Project>,
+//             _: &mut ViewContext<Self>,
+//         ) -> Task<anyhow::Result<()>> {
+//             self.reload_count += 1;
+//             self.is_dirty = false;
+//             Task::ready(Ok(()))
+//         }
+
+//         fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+//             [ItemEvent::UpdateTab, ItemEvent::Edit].into()
+//         }
+
+//         fn serialized_item_kind() -> Option<&'static str> {
+//             Some("TestItem")
+//         }
+
+//         fn deserialize(
+//             _project: Handle<Project>,
+//             _workspace: WeakViewHandle<Workspace>,
+//             workspace_id: WorkspaceId,
+//             _item_id: ItemId,
+//             cx: &mut ViewContext<Pane>,
+//         ) -> Task<anyhow::Result<View<Self>>> {
+//             let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
+//             Task::Ready(Some(anyhow::Ok(view)))
+//         }
+//     }
+// }

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

@@ -0,0 +1,2754 @@
+// mod dragged_item_receiver;
+
+// use super::{ItemHandle, SplitDirection};
+// pub use crate::toolbar::Toolbar;
+// use crate::{
+//     item::{ItemSettings, WeakItemHandle},
+//     notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
+//     Workspace, WorkspaceSettings,
+// };
+// use anyhow::Result;
+// use collections::{HashMap, HashSet, VecDeque};
+// // use context_menu::{ContextMenu, ContextMenuItem};
+
+// use dragged_item_receiver::dragged_item_receiver;
+// use fs2::repository::GitFileStatus;
+// use futures::StreamExt;
+// use gpui2::{
+//     actions,
+//     elements::*,
+//     geometry::{
+//         rect::RectF,
+//         vector::{vec2f, Vector2F},
+//     },
+//     impl_actions,
+//     keymap_matcher::KeymapContext,
+//     platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
+//     Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
+//     ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+//     WindowContext,
+// };
+// use project2::{Project, ProjectEntryId, ProjectPath};
+use serde::Deserialize;
+// use std::{
+//     any::Any,
+//     cell::RefCell,
+//     cmp, mem,
+//     path::{Path, PathBuf},
+//     rc::Rc,
+//     sync::{
+//         atomic::{AtomicUsize, Ordering},
+//         Arc,
+//     },
+// };
+// use theme2::{Theme, ThemeSettings};
+// use util::truncate_and_remove_front;
+
+#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub enum SaveIntent {
+    /// write all files (even if unchanged)
+    /// prompt before overwriting on-disk changes
+    Save,
+    /// write any files that have local changes
+    /// prompt before overwriting on-disk changes
+    SaveAll,
+    /// always prompt for a new path
+    SaveAs,
+    /// prompt "you have unsaved changes" before writing
+    Close,
+    /// write all dirty files, don't prompt on conflict
+    Overwrite,
+    /// skip all save-related behavior
+    Skip,
+}
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct ActivateItem(pub usize);
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemById {
+//     pub item_id: usize,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheLeftById {
+//     pub item_id: usize,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheRightById {
+//     pub item_id: usize,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+// #[serde(rename_all = "camelCase")]
+// pub struct CloseActiveItem {
+//     pub save_intent: Option<SaveIntent>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize)]
+// #[serde(rename_all = "camelCase")]
+// pub struct CloseAllItems {
+//     pub save_intent: Option<SaveIntent>,
+// }
+
+// actions!(
+//     pane,
+//     [
+//         ActivatePrevItem,
+//         ActivateNextItem,
+//         ActivateLastItem,
+//         CloseInactiveItems,
+//         CloseCleanItems,
+//         CloseItemsToTheLeft,
+//         CloseItemsToTheRight,
+//         GoBack,
+//         GoForward,
+//         ReopenClosedItem,
+//         SplitLeft,
+//         SplitUp,
+//         SplitRight,
+//         SplitDown,
+//     ]
+// );
+
+// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
+
+// const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
+
+// pub fn init(cx: &mut AppContext) {
+//     cx.add_action(Pane::toggle_zoom);
+//     cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
+//         pane.activate_item(action.0, true, true, cx);
+//     });
+//     cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
+//         pane.activate_item(pane.items.len() - 1, true, true, cx);
+//     });
+//     cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
+//         pane.activate_prev_item(true, cx);
+//     });
+//     cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
+//         pane.activate_next_item(true, cx);
+//     });
+//     cx.add_async_action(Pane::close_active_item);
+//     cx.add_async_action(Pane::close_inactive_items);
+//     cx.add_async_action(Pane::close_clean_items);
+//     cx.add_async_action(Pane::close_items_to_the_left);
+//     cx.add_async_action(Pane::close_items_to_the_right);
+//     cx.add_async_action(Pane::close_all_items);
+//     cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
+//     cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
+//     cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
+//     cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
+// }
+
+#[derive(Debug)]
+pub enum Event {
+    AddItem { item: Box<dyn ItemHandle> },
+    ActivateItem { local: bool },
+    Remove,
+    RemoveItem { item_id: usize },
+    Split(SplitDirection),
+    ChangeItemTitle,
+    Focus,
+    ZoomIn,
+    ZoomOut,
+}
+
+use crate::{
+    item::{ItemHandle, WeakItemHandle},
+    SplitDirection,
+};
+use collections::{HashMap, VecDeque};
+use gpui2::{Handle, ViewContext, WeakView};
+use project2::{Project, ProjectEntryId, ProjectPath};
+use std::{
+    any::Any,
+    cell::RefCell,
+    cmp, mem,
+    path::PathBuf,
+    rc::Rc,
+    sync::{atomic::AtomicUsize, Arc},
+};
+
+pub struct Pane {
+    items: Vec<Box<dyn ItemHandle>>,
+    //     activation_history: Vec<usize>,
+    //     zoomed: bool,
+    //     active_item_index: usize,
+    //     last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
+    //     autoscroll: bool,
+    nav_history: NavHistory,
+    //     toolbar: ViewHandle<Toolbar>,
+    //     tab_bar_context_menu: TabBarContextMenu,
+    //     tab_context_menu: ViewHandle<ContextMenu>,
+    //     workspace: WeakViewHandle<Workspace>,
+    project: Handle<Project>,
+    //     has_focus: bool,
+    //     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>>,
+}
+
+pub struct ItemNavHistory {
+    history: NavHistory,
+    item: Rc<dyn WeakItemHandle>,
+}
+
+#[derive(Clone)]
+pub struct NavHistory(Rc<RefCell<NavHistoryState>>);
+
+struct NavHistoryState {
+    mode: NavigationMode,
+    backward_stack: VecDeque<NavigationEntry>,
+    forward_stack: VecDeque<NavigationEntry>,
+    closed_stack: VecDeque<NavigationEntry>,
+    paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
+    pane: WeakView<Pane>,
+    next_timestamp: Arc<AtomicUsize>,
+}
+
+#[derive(Copy, Clone)]
+pub enum NavigationMode {
+    Normal,
+    GoingBack,
+    GoingForward,
+    ClosingItem,
+    ReopeningClosedItem,
+    Disabled,
+}
+
+impl Default for NavigationMode {
+    fn default() -> Self {
+        Self::Normal
+    }
+}
+
+pub struct NavigationEntry {
+    pub item: Rc<dyn WeakItemHandle>,
+    pub data: Option<Box<dyn Any>>,
+    pub timestamp: usize,
+}
+
+// pub struct DraggedItem {
+//     pub handle: Box<dyn ItemHandle>,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// pub enum ReorderBehavior {
+//     None,
+//     MoveAfterActive,
+//     MoveToIndex(usize),
+// }
+
+// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+// enum TabBarContextMenuKind {
+//     New,
+//     Split,
+// }
+
+// struct TabBarContextMenu {
+//     kind: TabBarContextMenuKind,
+//     handle: ViewHandle<ContextMenu>,
+// }
+
+// impl TabBarContextMenu {
+//     fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
+//         if self.kind == kind {
+//             return Some(self.handle.clone());
+//         }
+//         None
+//     }
+// }
+
+// #[allow(clippy::too_many_arguments)]
+// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
+//     svg_path: &'static str,
+//     style: theme2::Interactive<theme2::IconButton>,
+//     nav_button_height: f32,
+//     tooltip_style: TooltipStyle,
+//     enabled: bool,
+//     on_click: F,
+//     tooltip_action: A,
+//     action_name: &str,
+//     cx: &mut ViewContext<Pane>,
+// ) -> AnyElement<Pane> {
+//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
+//         let style = if enabled {
+//             style.style_for(state)
+//         } else {
+//             style.disabled_style()
+//         };
+//         Svg::new(svg_path)
+//             .with_color(style.color)
+//             .constrained()
+//             .with_width(style.icon_width)
+//             .aligned()
+//             .contained()
+//             .with_style(style.container)
+//             .constrained()
+//             .with_width(style.button_width)
+//             .with_height(nav_button_height)
+//             .aligned()
+//             .top()
+//     })
+//     .with_cursor_style(if enabled {
+//         CursorStyle::PointingHand
+//     } else {
+//         CursorStyle::default()
+//     })
+//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
+//         on_click(toolbar, cx)
+//     })
+//     .with_tooltip::<A>(
+//         0,
+//         action_name.to_string(),
+//         Some(Box::new(tooltip_action)),
+//         tooltip_style,
+//         cx,
+//     )
+//     .contained()
+//     .into_any_named("nav button")
+// }
+
+impl Pane {
+    //     pub fn new(
+    //         workspace: WeakViewHandle<Workspace>,
+    //         project: ModelHandle<Project>,
+    //         next_timestamp: Arc<AtomicUsize>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Self {
+    //         let pane_view_id = cx.view_id();
+    //         let handle = cx.weak_handle();
+    //         let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
+    //         context_menu.update(cx, |menu, _| {
+    //             menu.set_position_mode(OverlayPositionMode::Local)
+    //         });
+
+    //         Self {
+    //             items: Vec::new(),
+    //             activation_history: Vec::new(),
+    //             zoomed: false,
+    //             active_item_index: 0,
+    //             last_focused_view_by_item: Default::default(),
+    //             autoscroll: false,
+    //             nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState {
+    //                 mode: NavigationMode::Normal,
+    //                 backward_stack: Default::default(),
+    //                 forward_stack: Default::default(),
+    //                 closed_stack: Default::default(),
+    //                 paths_by_item: Default::default(),
+    //                 pane: handle.clone(),
+    //                 next_timestamp,
+    //             }))),
+    //             toolbar: cx.add_view(|_| Toolbar::new()),
+    //             tab_bar_context_menu: TabBarContextMenu {
+    //                 kind: TabBarContextMenuKind::New,
+    //                 handle: context_menu,
+    //             },
+    //             tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
+    //             workspace,
+    //             project,
+    //             has_focus: false,
+    //             can_drop: Rc::new(|_, _| true),
+    //             can_split: true,
+    //             render_tab_bar_buttons: Rc::new(move |pane, cx| {
+    //                 Flex::row()
+    //                     // New menu
+    //                     .with_child(Self::render_tab_bar_button(
+    //                         0,
+    //                         "icons/plus.svg",
+    //                         false,
+    //                         Some(("New...".into(), None)),
+    //                         cx,
+    //                         |pane, cx| pane.deploy_new_menu(cx),
+    //                         |pane, cx| {
+    //                             pane.tab_bar_context_menu
+    //                                 .handle
+    //                                 .update(cx, |menu, _| menu.delay_cancel())
+    //                         },
+    //                         pane.tab_bar_context_menu
+    //                             .handle_if_kind(TabBarContextMenuKind::New),
+    //                     ))
+    //                     .with_child(Self::render_tab_bar_button(
+    //                         1,
+    //                         "icons/split.svg",
+    //                         false,
+    //                         Some(("Split Pane".into(), None)),
+    //                         cx,
+    //                         |pane, cx| pane.deploy_split_menu(cx),
+    //                         |pane, cx| {
+    //                             pane.tab_bar_context_menu
+    //                                 .handle
+    //                                 .update(cx, |menu, _| menu.delay_cancel())
+    //                         },
+    //                         pane.tab_bar_context_menu
+    //                             .handle_if_kind(TabBarContextMenuKind::Split),
+    //                     ))
+    //                     .with_child({
+    //                         let icon_path;
+    //                         let tooltip_label;
+    //                         if pane.is_zoomed() {
+    //                             icon_path = "icons/minimize.svg";
+    //                             tooltip_label = "Zoom In";
+    //                         } else {
+    //                             icon_path = "icons/maximize.svg";
+    //                             tooltip_label = "Zoom In";
+    //                         }
+
+    //                         Pane::render_tab_bar_button(
+    //                             2,
+    //                             icon_path,
+    //                             pane.is_zoomed(),
+    //                             Some((tooltip_label, Some(Box::new(ToggleZoom)))),
+    //                             cx,
+    //                             move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
+    //                             move |_, _| {},
+    //                             None,
+    //                         )
+    //                     })
+    //                     .into_any()
+    //             }),
+    //         }
+    //     }
+
+    //     pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
+    //         &self.workspace
+    //     }
+
+    //     pub fn has_focus(&self) -> bool {
+    //         self.has_focus
+    //     }
+
+    //     pub fn active_item_index(&self) -> usize {
+    //         self.active_item_index
+    //     }
+
+    //     pub fn on_can_drop<F>(&mut self, can_drop: F)
+    //     where
+    //         F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
+    //     {
+    //         self.can_drop = Rc::new(can_drop);
+    //     }
+
+    //     pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
+    //         self.can_split = can_split;
+    //         cx.notify();
+    //     }
+
+    //     pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.set_can_navigate(can_navigate, cx);
+    //         });
+    //         cx.notify();
+    //     }
+
+    //     pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
+    //     where
+    //         F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
+    //     {
+    //         self.render_tab_bar_buttons = Rc::new(render);
+    //         cx.notify();
+    //     }
+
+    //     pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
+    //         ItemNavHistory {
+    //             history: self.nav_history.clone(),
+    //             item: Rc::new(item.downgrade()),
+    //         }
+    //     }
+
+    //     pub fn nav_history(&self) -> &NavHistory {
+    //         &self.nav_history
+    //     }
+
+    //     pub fn nav_history_mut(&mut self) -> &mut NavHistory {
+    //         &mut self.nav_history
+    //     }
+
+    //     pub fn disable_history(&mut self) {
+    //         self.nav_history.disable();
+    //     }
+
+    //     pub fn enable_history(&mut self) {
+    //         self.nav_history.enable();
+    //     }
+
+    //     pub fn can_navigate_backward(&self) -> bool {
+    //         !self.nav_history.0.borrow().backward_stack.is_empty()
+    //     }
+
+    //     pub fn can_navigate_forward(&self) -> bool {
+    //         !self.nav_history.0.borrow().forward_stack.is_empty()
+    //     }
+
+    //     fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.toolbar.update(cx, |_, cx| cx.notify());
+    //     }
+
+    pub(crate) fn open_item(
+        &mut self,
+        project_entry_id: ProjectEntryId,
+        focus_item: bool,
+        cx: &mut ViewContext<Self>,
+        build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
+    ) -> Box<dyn ItemHandle> {
+        let mut existing_item = None;
+        for (index, item) in self.items.iter().enumerate() {
+            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id]
+            {
+                let item = item.boxed_clone();
+                existing_item = Some((index, item));
+                break;
+            }
+        }
+
+        if let Some((index, existing_item)) = existing_item {
+            self.activate_item(index, focus_item, focus_item, cx);
+            existing_item
+        } else {
+            let new_item = build_item(cx);
+            self.add_item(new_item.clone(), true, focus_item, None, cx);
+            new_item
+        }
+    }
+
+    pub fn add_item(
+        &mut self,
+        item: Box<dyn ItemHandle>,
+        activate_pane: bool,
+        focus_item: bool,
+        destination_index: Option<usize>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if item.is_singleton(cx) {
+            if let Some(&entry_id) = item.project_entry_ids(cx).get(0) {
+                let project = self.project.read(cx);
+                if let Some(project_path) = project.path_for_entry(entry_id, cx) {
+                    let abs_path = project.absolute_path(&project_path, cx);
+                    self.nav_history
+                        .0
+                        .borrow_mut()
+                        .paths_by_item
+                        .insert(item.id(), (project_path, abs_path));
+                }
+            }
+        }
+        // If no destination index is specified, add or move the item after the active item.
+        let mut insertion_index = {
+            cmp::min(
+                if let Some(destination_index) = destination_index {
+                    destination_index
+                } else {
+                    self.active_item_index + 1
+                },
+                self.items.len(),
+            )
+        };
+
+        // Does the item already exist?
+        let project_entry_id = if item.is_singleton(cx) {
+            item.project_entry_ids(cx).get(0).copied()
+        } else {
+            None
+        };
+
+        let existing_item_index = self.items.iter().position(|existing_item| {
+            if existing_item.id() == item.id() {
+                true
+            } else if existing_item.is_singleton(cx) {
+                existing_item
+                    .project_entry_ids(cx)
+                    .get(0)
+                    .map_or(false, |existing_entry_id| {
+                        Some(existing_entry_id) == project_entry_id.as_ref()
+                    })
+            } else {
+                false
+            }
+        });
+
+        if let Some(existing_item_index) = existing_item_index {
+            // If the item already exists, move it to the desired destination and activate it
+
+            if existing_item_index != insertion_index {
+                let existing_item_is_active = existing_item_index == self.active_item_index;
+
+                // If the caller didn't specify a destination and the added item is already
+                // the active one, don't move it
+                if existing_item_is_active && destination_index.is_none() {
+                    insertion_index = existing_item_index;
+                } else {
+                    self.items.remove(existing_item_index);
+                    if existing_item_index < self.active_item_index {
+                        self.active_item_index -= 1;
+                    }
+                    insertion_index = insertion_index.min(self.items.len());
+
+                    self.items.insert(insertion_index, item.clone());
+
+                    if existing_item_is_active {
+                        self.active_item_index = insertion_index;
+                    } else if insertion_index <= self.active_item_index {
+                        self.active_item_index += 1;
+                    }
+                }
+
+                cx.notify();
+            }
+
+            self.activate_item(insertion_index, activate_pane, focus_item, cx);
+        } else {
+            self.items.insert(insertion_index, item.clone());
+            if insertion_index <= self.active_item_index {
+                self.active_item_index += 1;
+            }
+
+            self.activate_item(insertion_index, activate_pane, focus_item, cx);
+            cx.notify();
+        }
+
+        cx.emit(Event::AddItem { item });
+    }
+
+    //     pub fn items_len(&self) -> usize {
+    //         self.items.len()
+    //     }
+
+    //     pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
+    //         self.items.iter()
+    //     }
+
+    //     pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
+    //         self.items
+    //             .iter()
+    //             .filter_map(|item| item.as_any().clone().downcast())
+    //     }
+
+    //     pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
+    //         self.items.get(self.active_item_index).cloned()
+    //     }
+
+    //     pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
+    //         self.items
+    //             .get(self.active_item_index)?
+    //             .pixel_position_of_cursor(cx)
+    //     }
+
+    //     pub fn item_for_entry(
+    //         &self,
+    //         entry_id: ProjectEntryId,
+    //         cx: &AppContext,
+    //     ) -> Option<Box<dyn ItemHandle>> {
+    //         self.items.iter().find_map(|item| {
+    //             if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
+    //                 Some(item.boxed_clone())
+    //             } else {
+    //                 None
+    //             }
+    //         })
+    //     }
+
+    //     pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
+    //         self.items.iter().position(|i| i.id() == item.id())
+    //     }
+
+    //     pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
+    //         // Potentially warn the user of the new keybinding
+    //         let workspace_handle = self.workspace().clone();
+    //         cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
+    //             .detach();
+
+    //         if self.zoomed {
+    //             cx.emit(Event::ZoomOut);
+    //         } else if !self.items.is_empty() {
+    //             if !self.has_focus {
+    //                 cx.focus_self();
+    //             }
+    //             cx.emit(Event::ZoomIn);
+    //         }
+    //     }
+
+    pub fn activate_item(
+        &mut self,
+        index: usize,
+        activate_pane: bool,
+        focus_item: bool,
+        cx: &mut ViewContext<Self>,
+    ) {
+        use NavigationMode::{GoingBack, GoingForward};
+
+        if index < self.items.len() {
+            let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
+            if prev_active_item_ix != self.active_item_index
+                || matches!(self.nav_history.mode(), GoingBack | GoingForward)
+            {
+                if let Some(prev_item) = self.items.get(prev_active_item_ix) {
+                    prev_item.deactivated(cx);
+                }
+
+                cx.emit(Event::ActivateItem {
+                    local: activate_pane,
+                });
+            }
+
+            if let Some(newly_active_item) = self.items.get(index) {
+                self.activation_history
+                    .retain(|&previously_active_item_id| {
+                        previously_active_item_id != newly_active_item.id()
+                    });
+                self.activation_history.push(newly_active_item.id());
+            }
+
+            self.update_toolbar(cx);
+
+            if focus_item {
+                self.focus_active_item(cx);
+            }
+
+            self.autoscroll = true;
+            cx.notify();
+        }
+    }
+
+    //     pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
+    //         let mut index = self.active_item_index;
+    //         if index > 0 {
+    //             index -= 1;
+    //         } else if !self.items.is_empty() {
+    //             index = self.items.len() - 1;
+    //         }
+    //         self.activate_item(index, activate_pane, activate_pane, cx);
+    //     }
+
+    //     pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
+    //         let mut index = self.active_item_index;
+    //         if index + 1 < self.items.len() {
+    //             index += 1;
+    //         } else {
+    //             index = 0;
+    //         }
+    //         self.activate_item(index, activate_pane, activate_pane, cx);
+    //     }
+
+    //     pub fn close_active_item(
+    //         &mut self,
+    //         action: &CloseActiveItem,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_item_by_id(
+    //             active_item_id,
+    //             action.save_intent.unwrap_or(SaveIntent::Close),
+    //             cx,
+    //         ))
+    //     }
+
+    //     pub fn close_item_by_id(
+    //         &mut self,
+    //         item_id_to_close: usize,
+    //         save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
+    //     }
+
+    //     pub fn close_inactive_items(
+    //         &mut self,
+    //         _: &CloseInactiveItems,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_id != active_item_id
+    //         }))
+    //     }
+
+    //     pub fn close_clean_items(
+    //         &mut self,
+    //         _: &CloseCleanItems,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let item_ids: Vec<_> = self
+    //             .items()
+    //             .filter(|item| !item.is_dirty(cx))
+    //             .map(|item| item.id())
+    //             .collect();
+    //         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_ids.contains(&item_id)
+    //         }))
+    //     }
+
+    //     pub fn close_items_to_the_left(
+    //         &mut self,
+    //         _: &CloseItemsToTheLeft,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_items_to_the_left_by_id(active_item_id, cx))
+    //     }
+
+    //     pub fn close_items_to_the_left_by_id(
+    //         &mut self,
+    //         item_id: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let item_ids: Vec<_> = self
+    //             .items()
+    //             .take_while(|item| item.id() != item_id)
+    //             .map(|item| item.id())
+    //             .collect();
+    //         self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_ids.contains(&item_id)
+    //         })
+    //     }
+
+    //     pub fn close_items_to_the_right(
+    //         &mut self,
+    //         _: &CloseItemsToTheRight,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_items_to_the_right_by_id(active_item_id, cx))
+    //     }
+
+    //     pub fn close_items_to_the_right_by_id(
+    //         &mut self,
+    //         item_id: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let item_ids: Vec<_> = self
+    //             .items()
+    //             .rev()
+    //             .take_while(|item| item.id() != item_id)
+    //             .map(|item| item.id())
+    //             .collect();
+    //         self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_ids.contains(&item_id)
+    //         })
+    //     }
+
+    //     pub fn close_all_items(
+    //         &mut self,
+    //         action: &CloseAllItems,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+
+    //         Some(
+    //             self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
+    //                 true
+    //             }),
+    //         )
+    //     }
+
+    //     pub(super) fn file_names_for_prompt(
+    //         items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
+    //         all_dirty_items: usize,
+    //         cx: &AppContext,
+    //     ) -> String {
+    //         /// Quantity of item paths displayed in prompt prior to cutoff..
+    //         const FILE_NAMES_CUTOFF_POINT: usize = 10;
+    //         let mut file_names: Vec<_> = items
+    //             .filter_map(|item| {
+    //                 item.project_path(cx).and_then(|project_path| {
+    //                     project_path
+    //                         .path
+    //                         .file_name()
+    //                         .and_then(|name| name.to_str().map(ToOwned::to_owned))
+    //                 })
+    //             })
+    //             .take(FILE_NAMES_CUTOFF_POINT)
+    //             .collect();
+    //         let should_display_followup_text =
+    //             all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
+    //         if should_display_followup_text {
+    //             let not_shown_files = all_dirty_items - file_names.len();
+    //             if not_shown_files == 1 {
+    //                 file_names.push(".. 1 file not shown".into());
+    //             } else {
+    //                 file_names.push(format!(".. {} files not shown", not_shown_files).into());
+    //             }
+    //         }
+    //         let file_names = file_names.join("\n");
+    //         format!(
+    //             "Do you want to save changes to the following {} files?\n{file_names}",
+    //             all_dirty_items
+    //         )
+    //     }
+
+    //     pub fn close_items(
+    //         &mut self,
+    //         cx: &mut ViewContext<Pane>,
+    //         mut save_intent: SaveIntent,
+    //         should_close: impl 'static + Fn(usize) -> bool,
+    //     ) -> Task<Result<()>> {
+    //         // Find the items to close.
+    //         let mut items_to_close = Vec::new();
+    //         let mut dirty_items = Vec::new();
+    //         for item in &self.items {
+    //             if should_close(item.id()) {
+    //                 items_to_close.push(item.boxed_clone());
+    //                 if item.is_dirty(cx) {
+    //                     dirty_items.push(item.boxed_clone());
+    //                 }
+    //             }
+    //         }
+
+    //         // If a buffer is open both in a singleton editor and in a multibuffer, make sure
+    //         // to focus the singleton buffer when prompting to save that buffer, as opposed
+    //         // to focusing the multibuffer, because this gives the user a more clear idea
+    //         // of what content they would be saving.
+    //         items_to_close.sort_by_key(|item| !item.is_singleton(cx));
+
+    //         let workspace = self.workspace.clone();
+    //         cx.spawn(|pane, mut cx| async move {
+    //             if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
+    //                 let mut answer = pane.update(&mut cx, |_, cx| {
+    //                     let prompt =
+    //                         Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
+    //                     cx.prompt(
+    //                         PromptLevel::Warning,
+    //                         &prompt,
+    //                         &["Save all", "Discard all", "Cancel"],
+    //                     )
+    //                 })?;
+    //                 match answer.next().await {
+    //                     Some(0) => save_intent = SaveIntent::SaveAll,
+    //                     Some(1) => save_intent = SaveIntent::Skip,
+    //                     _ => {}
+    //                 }
+    //             }
+    //             let mut saved_project_items_ids = HashSet::default();
+    //             for item in items_to_close.clone() {
+    //                 // Find the item's current index and its set of project item models. Avoid
+    //                 // storing these in advance, in case they have changed since this task
+    //                 // was started.
+    //                 let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
+    //                     (pane.index_for_item(&*item), item.project_item_model_ids(cx))
+    //                 })?;
+    //                 let item_ix = if let Some(ix) = item_ix {
+    //                     ix
+    //                 } else {
+    //                     continue;
+    //                 };
+
+    //                 // Check if this view has any project items that are not open anywhere else
+    //                 // in the workspace, AND that the user has not already been prompted to save.
+    //                 // If there are any such project entries, prompt the user to save this item.
+    //                 let project = workspace.read_with(&cx, |workspace, cx| {
+    //                     for item in workspace.items(cx) {
+    //                         if !items_to_close
+    //                             .iter()
+    //                             .any(|item_to_close| item_to_close.id() == item.id())
+    //                         {
+    //                             let other_project_item_ids = item.project_item_model_ids(cx);
+    //                             project_item_ids.retain(|id| !other_project_item_ids.contains(id));
+    //                         }
+    //                     }
+    //                     workspace.project().clone()
+    //                 })?;
+    //                 let should_save = project_item_ids
+    //                     .iter()
+    //                     .any(|id| saved_project_items_ids.insert(*id));
+
+    //                 if should_save
+    //                     && !Self::save_item(
+    //                         project.clone(),
+    //                         &pane,
+    //                         item_ix,
+    //                         &*item,
+    //                         save_intent,
+    //                         &mut cx,
+    //                     )
+    //                     .await?
+    //                 {
+    //                     break;
+    //                 }
+
+    //                 // Remove the item from the pane.
+    //                 pane.update(&mut cx, |pane, cx| {
+    //                     if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
+    //                         pane.remove_item(item_ix, false, cx);
+    //                     }
+    //                 })?;
+    //             }
+
+    //             pane.update(&mut cx, |_, cx| cx.notify())?;
+    //             Ok(())
+    //         })
+    //     }
+
+    //     pub fn remove_item(
+    //         &mut self,
+    //         item_index: usize,
+    //         activate_pane: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.activation_history
+    //             .retain(|&history_entry| history_entry != self.items[item_index].id());
+
+    //         if item_index == self.active_item_index {
+    //             let index_to_activate = self
+    //                 .activation_history
+    //                 .pop()
+    //                 .and_then(|last_activated_item| {
+    //                     self.items.iter().enumerate().find_map(|(index, item)| {
+    //                         (item.id() == last_activated_item).then_some(index)
+    //                     })
+    //                 })
+    //                 // We didn't have a valid activation history entry, so fallback
+    //                 // to activating the item to the left
+    //                 .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
+
+    //             let should_activate = activate_pane || self.has_focus;
+    //             self.activate_item(index_to_activate, should_activate, should_activate, cx);
+    //         }
+
+    //         let item = self.items.remove(item_index);
+
+    //         cx.emit(Event::RemoveItem { item_id: item.id() });
+    //         if self.items.is_empty() {
+    //             item.deactivated(cx);
+    //             self.update_toolbar(cx);
+    //             cx.emit(Event::Remove);
+    //         }
+
+    //         if item_index < self.active_item_index {
+    //             self.active_item_index -= 1;
+    //         }
+
+    //         self.nav_history.set_mode(NavigationMode::ClosingItem);
+    //         item.deactivated(cx);
+    //         self.nav_history.set_mode(NavigationMode::Normal);
+
+    //         if let Some(path) = item.project_path(cx) {
+    //             let abs_path = self
+    //                 .nav_history
+    //                 .0
+    //                 .borrow()
+    //                 .paths_by_item
+    //                 .get(&item.id())
+    //                 .and_then(|(_, abs_path)| abs_path.clone());
+
+    //             self.nav_history
+    //                 .0
+    //                 .borrow_mut()
+    //                 .paths_by_item
+    //                 .insert(item.id(), (path, abs_path));
+    //         } else {
+    //             self.nav_history
+    //                 .0
+    //                 .borrow_mut()
+    //                 .paths_by_item
+    //                 .remove(&item.id());
+    //         }
+
+    //         if self.items.is_empty() && self.zoomed {
+    //             cx.emit(Event::ZoomOut);
+    //         }
+
+    //         cx.notify();
+    //     }
+
+    //     pub async fn save_item(
+    //         project: ModelHandle<Project>,
+    //         pane: &WeakViewHandle<Pane>,
+    //         item_ix: usize,
+    //         item: &dyn ItemHandle,
+    //         save_intent: SaveIntent,
+    //         cx: &mut AsyncAppContext,
+    //     ) -> Result<bool> {
+    //         const CONFLICT_MESSAGE: &str =
+    //             "This file has changed on disk since you started editing it. Do you want to overwrite it?";
+
+    //         if save_intent == SaveIntent::Skip {
+    //             return Ok(true);
+    //         }
+
+    //         let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
+    //             (
+    //                 item.has_conflict(cx),
+    //                 item.is_dirty(cx),
+    //                 item.can_save(cx),
+    //                 item.is_singleton(cx),
+    //             )
+    //         });
+
+    //         // when saving a single buffer, we ignore whether or not it's dirty.
+    //         if save_intent == SaveIntent::Save {
+    //             is_dirty = true;
+    //         }
+
+    //         if save_intent == SaveIntent::SaveAs {
+    //             is_dirty = true;
+    //             has_conflict = false;
+    //             can_save = false;
+    //         }
+
+    //         if save_intent == SaveIntent::Overwrite {
+    //             has_conflict = false;
+    //         }
+
+    //         if has_conflict && can_save {
+    //             let mut answer = pane.update(cx, |pane, cx| {
+    //                 pane.activate_item(item_ix, true, true, cx);
+    //                 cx.prompt(
+    //                     PromptLevel::Warning,
+    //                     CONFLICT_MESSAGE,
+    //                     &["Overwrite", "Discard", "Cancel"],
+    //                 )
+    //             })?;
+    //             match answer.next().await {
+    //                 Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
+    //                 Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
+    //                 _ => return Ok(false),
+    //             }
+    //         } else if is_dirty && (can_save || can_save_as) {
+    //             if save_intent == SaveIntent::Close {
+    //                 let will_autosave = cx.read(|cx| {
+    //                     matches!(
+    //                         settings::get::<WorkspaceSettings>(cx).autosave,
+    //                         AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
+    //                     ) && Self::can_autosave_item(&*item, cx)
+    //                 });
+    //                 if !will_autosave {
+    //                     let mut answer = pane.update(cx, |pane, cx| {
+    //                         pane.activate_item(item_ix, true, true, cx);
+    //                         let prompt = dirty_message_for(item.project_path(cx));
+    //                         cx.prompt(
+    //                             PromptLevel::Warning,
+    //                             &prompt,
+    //                             &["Save", "Don't Save", "Cancel"],
+    //                         )
+    //                     })?;
+    //                     match answer.next().await {
+    //                         Some(0) => {}
+    //                         Some(1) => return Ok(true), // Don't save his file
+    //                         _ => return Ok(false),      // Cancel
+    //                     }
+    //                 }
+    //             }
+
+    //             if can_save {
+    //                 pane.update(cx, |_, cx| item.save(project, cx))?.await?;
+    //             } else if can_save_as {
+    //                 let start_abs_path = project
+    //                     .read_with(cx, |project, cx| {
+    //                         let worktree = project.visible_worktrees(cx).next()?;
+    //                         Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
+    //                     })
+    //                     .unwrap_or_else(|| Path::new("").into());
+
+    //                 let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
+    //                 if let Some(abs_path) = abs_path.next().await.flatten() {
+    //                     pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
+    //                         .await?;
+    //                 } else {
+    //                     return Ok(false);
+    //                 }
+    //             }
+    //         }
+    //         Ok(true)
+    //     }
+
+    //     fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
+    //         let is_deleted = item.project_entry_ids(cx).is_empty();
+    //         item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
+    //     }
+
+    //     pub fn autosave_item(
+    //         item: &dyn ItemHandle,
+    //         project: ModelHandle<Project>,
+    //         cx: &mut WindowContext,
+    //     ) -> Task<Result<()>> {
+    //         if Self::can_autosave_item(item, cx) {
+    //             item.save(project, cx)
+    //         } else {
+    //             Task::ready(Ok(()))
+    //         }
+    //     }
+
+    //     pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
+    //         if let Some(active_item) = self.active_item() {
+    //             cx.focus(active_item.as_any());
+    //         }
+    //     }
+
+    //     pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
+    //         cx.emit(Event::Split(direction));
+    //     }
+
+    //     fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
+    //             menu.toggle(
+    //                 Default::default(),
+    //                 AnchorCorner::TopRight,
+    //                 vec![
+    //                     ContextMenuItem::action("Split Right", SplitRight),
+    //                     ContextMenuItem::action("Split Left", SplitLeft),
+    //                     ContextMenuItem::action("Split Up", SplitUp),
+    //                     ContextMenuItem::action("Split Down", SplitDown),
+    //                 ],
+    //                 cx,
+    //             );
+    //         });
+
+    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
+    //     }
+
+    //     fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
+    //             menu.toggle(
+    //                 Default::default(),
+    //                 AnchorCorner::TopRight,
+    //                 vec![
+    //                     ContextMenuItem::action("New File", NewFile),
+    //                     ContextMenuItem::action("New Terminal", NewCenterTerminal),
+    //                     ContextMenuItem::action("New Search", NewSearch),
+    //                 ],
+    //                 cx,
+    //             );
+    //         });
+
+    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
+    //     }
+
+    //     fn deploy_tab_context_menu(
+    //         &mut self,
+    //         position: Vector2F,
+    //         target_item_id: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         let is_active_item = target_item_id == active_item_id;
+    //         let target_pane = cx.weak_handle();
+
+    //         // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on.  Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
+
+    //         self.tab_context_menu.update(cx, |menu, cx| {
+    //             menu.show(
+    //                 position,
+    //                 AnchorCorner::TopLeft,
+    //                 if is_active_item {
+    //                     vec![
+    //                         ContextMenuItem::action(
+    //                             "Close Active Item",
+    //                             CloseActiveItem { save_intent: None },
+    //                         ),
+    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+    //                         ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
+    //                         ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
+    //                         ContextMenuItem::action(
+    //                             "Close All Items",
+    //                             CloseAllItems { save_intent: None },
+    //                         ),
+    //                     ]
+    //                 } else {
+    //                     // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
+    //                     vec![
+    //                         ContextMenuItem::handler("Close Inactive Item", {
+    //                             let pane = target_pane.clone();
+    //                             move |cx| {
+    //                                 if let Some(pane) = pane.upgrade(cx) {
+    //                                     pane.update(cx, |pane, cx| {
+    //                                         pane.close_item_by_id(
+    //                                             target_item_id,
+    //                                             SaveIntent::Close,
+    //                                             cx,
+    //                                         )
+    //                                         .detach_and_log_err(cx);
+    //                                     })
+    //                                 }
+    //                             }
+    //                         }),
+    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+    //                         ContextMenuItem::handler("Close Items To The Left", {
+    //                             let pane = target_pane.clone();
+    //                             move |cx| {
+    //                                 if let Some(pane) = pane.upgrade(cx) {
+    //                                     pane.update(cx, |pane, cx| {
+    //                                         pane.close_items_to_the_left_by_id(target_item_id, cx)
+    //                                             .detach_and_log_err(cx);
+    //                                     })
+    //                                 }
+    //                             }
+    //                         }),
+    //                         ContextMenuItem::handler("Close Items To The Right", {
+    //                             let pane = target_pane.clone();
+    //                             move |cx| {
+    //                                 if let Some(pane) = pane.upgrade(cx) {
+    //                                     pane.update(cx, |pane, cx| {
+    //                                         pane.close_items_to_the_right_by_id(target_item_id, cx)
+    //                                             .detach_and_log_err(cx);
+    //                                     })
+    //                                 }
+    //                             }
+    //                         }),
+    //                         ContextMenuItem::action(
+    //                             "Close All Items",
+    //                             CloseAllItems { save_intent: None },
+    //                         ),
+    //                     ]
+    //                 },
+    //                 cx,
+    //             );
+    //         });
+    //     }
+
+    //     pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
+    //         &self.toolbar
+    //     }
+
+    //     pub fn handle_deleted_project_item(
+    //         &mut self,
+    //         entry_id: ProjectEntryId,
+    //         cx: &mut ViewContext<Pane>,
+    //     ) -> Option<()> {
+    //         let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
+    //             if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
+    //                 Some((i, item.id()))
+    //             } else {
+    //                 None
+    //             }
+    //         })?;
+
+    //         self.remove_item(item_index_to_delete, false, cx);
+    //         self.nav_history.remove_item(item_id);
+
+    //         Some(())
+    //     }
+
+    //     fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
+    //         let active_item = self
+    //             .items
+    //             .get(self.active_item_index)
+    //             .map(|item| item.as_ref());
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.set_active_item(active_item, cx);
+    //         });
+    //     }
+
+    //     fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
+    //         let theme = theme::current(cx).clone();
+
+    //         let pane = cx.handle().downgrade();
+    //         let autoscroll = if mem::take(&mut self.autoscroll) {
+    //             Some(self.active_item_index)
+    //         } else {
+    //             None
+    //         };
+
+    //         let pane_active = self.has_focus;
+
+    //         enum Tabs {}
+    //         let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
+    //         for (ix, (item, detail)) in self
+    //             .items
+    //             .iter()
+    //             .cloned()
+    //             .zip(self.tab_details(cx))
+    //             .enumerate()
+    //         {
+    //             let git_status = item
+    //                 .project_path(cx)
+    //                 .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
+    //                 .and_then(|entry| entry.git_status());
+
+    //             let detail = if detail == 0 { None } else { Some(detail) };
+    //             let tab_active = ix == self.active_item_index;
+
+    //             row.add_child({
+    //                 enum TabDragReceiver {}
+    //                 let mut receiver =
+    //                     dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
+    //                         let item = item.clone();
+    //                         let pane = pane.clone();
+    //                         let detail = detail.clone();
+
+    //                         let theme = theme::current(cx).clone();
+    //                         let mut tooltip_theme = theme.tooltip.clone();
+    //                         tooltip_theme.max_text_width = None;
+    //                         let tab_tooltip_text =
+    //                             item.tab_tooltip_text(cx).map(|text| text.into_owned());
+
+    //                         let mut tab_style = theme
+    //                             .workspace
+    //                             .tab_bar
+    //                             .tab_style(pane_active, tab_active)
+    //                             .clone();
+    //                         let should_show_status = settings::get::<ItemSettings>(cx).git_status;
+    //                         if should_show_status && git_status != None {
+    //                             tab_style.label.text.color = match git_status.unwrap() {
+    //                                 GitFileStatus::Added => tab_style.git.inserted,
+    //                                 GitFileStatus::Modified => tab_style.git.modified,
+    //                                 GitFileStatus::Conflict => tab_style.git.conflict,
+    //                             };
+    //                         }
+
+    //                         move |mouse_state, cx| {
+    //                             let hovered = mouse_state.hovered();
+
+    //                             enum Tab {}
+    //                             let mouse_event_handler =
+    //                                 MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
+    //                                     Self::render_tab(
+    //                                         &item,
+    //                                         pane.clone(),
+    //                                         ix == 0,
+    //                                         detail,
+    //                                         hovered,
+    //                                         &tab_style,
+    //                                         cx,
+    //                                     )
+    //                                 })
+    //                                 .on_down(MouseButton::Left, move |_, this, cx| {
+    //                                     this.activate_item(ix, true, true, cx);
+    //                                 })
+    //                                 .on_click(MouseButton::Middle, {
+    //                                     let item_id = item.id();
+    //                                     move |_, pane, cx| {
+    //                                         pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+    //                                             .detach_and_log_err(cx);
+    //                                     }
+    //                                 })
+    //                                 .on_down(
+    //                                     MouseButton::Right,
+    //                                     move |event, pane, cx| {
+    //                                         pane.deploy_tab_context_menu(event.position, item.id(), cx);
+    //                                     },
+    //                                 );
+
+    //                             if let Some(tab_tooltip_text) = tab_tooltip_text {
+    //                                 mouse_event_handler
+    //                                     .with_tooltip::<Self>(
+    //                                         ix,
+    //                                         tab_tooltip_text,
+    //                                         None,
+    //                                         tooltip_theme,
+    //                                         cx,
+    //                                     )
+    //                                     .into_any()
+    //                             } else {
+    //                                 mouse_event_handler.into_any()
+    //                             }
+    //                         }
+    //                     });
+
+    //                 if !pane_active || !tab_active {
+    //                     receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
+    //                 }
+
+    //                 receiver.as_draggable(
+    //                     DraggedItem {
+    //                         handle: item,
+    //                         pane: pane.clone(),
+    //                     },
+    //                     {
+    //                         let theme = theme::current(cx).clone();
+
+    //                         let detail = detail.clone();
+    //                         move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
+    //                             let tab_style = &theme.workspace.tab_bar.dragged_tab;
+    //                             Self::render_dragged_tab(
+    //                                 &dragged_item.handle,
+    //                                 dragged_item.pane.clone(),
+    //                                 false,
+    //                                 detail,
+    //                                 false,
+    //                                 &tab_style,
+    //                                 cx,
+    //                             )
+    //                         }
+    //                     },
+    //                 )
+    //             })
+    //         }
+
+    //         // Use the inactive tab style along with the current pane's active status to decide how to render
+    //         // the filler
+    //         let filler_index = self.items.len();
+    //         let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
+    //         enum Filler {}
+    //         row.add_child(
+    //             dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
+    //                 Empty::new()
+    //                     .contained()
+    //                     .with_style(filler_style.container)
+    //                     .with_border(filler_style.container.border)
+    //             })
+    //             .flex(1., true)
+    //             .into_any_named("filler"),
+    //         );
+
+    //         row
+    //     }
+
+    //     fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
+    //         let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
+
+    //         let mut tab_descriptions = HashMap::default();
+    //         let mut done = false;
+    //         while !done {
+    //             done = true;
+
+    //             // Store item indices by their tab description.
+    //             for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
+    //                 if let Some(description) = item.tab_description(*detail, cx) {
+    //                     if *detail == 0
+    //                         || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
+    //                     {
+    //                         tab_descriptions
+    //                             .entry(description)
+    //                             .or_insert(Vec::new())
+    //                             .push(ix);
+    //                     }
+    //                 }
+    //             }
+
+    //             // If two or more items have the same tab description, increase their level
+    //             // of detail and try again.
+    //             for (_, item_ixs) in tab_descriptions.drain() {
+    //                 if item_ixs.len() > 1 {
+    //                     done = false;
+    //                     for ix in item_ixs {
+    //                         tab_details[ix] += 1;
+    //                     }
+    //                 }
+    //             }
+    //         }
+
+    //         tab_details
+    //     }
+
+    //     fn render_tab(
+    //         item: &Box<dyn ItemHandle>,
+    //         pane: WeakViewHandle<Pane>,
+    //         first: bool,
+    //         detail: Option<usize>,
+    //         hovered: bool,
+    //         tab_style: &theme::Tab,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         let title = item.tab_content(detail, &tab_style, cx);
+    //         Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+    //     }
+
+    //     fn render_dragged_tab(
+    //         item: &Box<dyn ItemHandle>,
+    //         pane: WeakViewHandle<Pane>,
+    //         first: bool,
+    //         detail: Option<usize>,
+    //         hovered: bool,
+    //         tab_style: &theme::Tab,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> AnyElement<Workspace> {
+    //         let title = item.dragged_tab_content(detail, &tab_style, cx);
+    //         Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+    //     }
+
+    //     fn render_tab_with_title<T: View>(
+    //         title: AnyElement<T>,
+    //         item: &Box<dyn ItemHandle>,
+    //         pane: WeakViewHandle<Pane>,
+    //         first: bool,
+    //         hovered: bool,
+    //         tab_style: &theme::Tab,
+    //         cx: &mut ViewContext<T>,
+    //     ) -> AnyElement<T> {
+    //         let mut container = tab_style.container.clone();
+    //         if first {
+    //             container.border.left = false;
+    //         }
+
+    //         let buffer_jewel_element = {
+    //             let diameter = 7.0;
+    //             let icon_color = if item.has_conflict(cx) {
+    //                 Some(tab_style.icon_conflict)
+    //             } else if item.is_dirty(cx) {
+    //                 Some(tab_style.icon_dirty)
+    //             } else {
+    //                 None
+    //             };
+
+    //             Canvas::new(move |bounds, _, _, cx| {
+    //                 if let Some(color) = icon_color {
+    //                     let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
+    //                     cx.scene().push_quad(Quad {
+    //                         bounds: square,
+    //                         background: Some(color),
+    //                         border: Default::default(),
+    //                         corner_radii: (diameter / 2.).into(),
+    //                     });
+    //                 }
+    //             })
+    //             .constrained()
+    //             .with_width(diameter)
+    //             .with_height(diameter)
+    //             .aligned()
+    //         };
+
+    //         let title_element = title.aligned().contained().with_style(ContainerStyle {
+    //             margin: Margin {
+    //                 left: tab_style.spacing,
+    //                 right: tab_style.spacing,
+    //                 ..Default::default()
+    //             },
+    //             ..Default::default()
+    //         });
+
+    //         let close_element = if hovered {
+    //             let item_id = item.id();
+    //             enum TabCloseButton {}
+    //             let icon = Svg::new("icons/x.svg");
+    //             MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
+    //                 if mouse_state.hovered() {
+    //                     icon.with_color(tab_style.icon_close_active)
+    //                 } else {
+    //                     icon.with_color(tab_style.icon_close)
+    //                 }
+    //             })
+    //             .with_padding(Padding::uniform(4.))
+    //             .with_cursor_style(CursorStyle::PointingHand)
+    //             .on_click(MouseButton::Left, {
+    //                 let pane = pane.clone();
+    //                 move |_, _, cx| {
+    //                     let pane = pane.clone();
+    //                     cx.window_context().defer(move |cx| {
+    //                         if let Some(pane) = pane.upgrade(cx) {
+    //                             pane.update(cx, |pane, cx| {
+    //                                 pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+    //                                     .detach_and_log_err(cx);
+    //                             });
+    //                         }
+    //                     });
+    //                 }
+    //             })
+    //             .into_any_named("close-tab-icon")
+    //             .constrained()
+    //         } else {
+    //             Empty::new().constrained()
+    //         }
+    //         .with_width(tab_style.close_icon_width)
+    //         .aligned();
+
+    //         let close_right = settings::get::<ItemSettings>(cx).close_position.right();
+
+    //         if close_right {
+    //             Flex::row()
+    //                 .with_child(buffer_jewel_element)
+    //                 .with_child(title_element)
+    //                 .with_child(close_element)
+    //         } else {
+    //             Flex::row()
+    //                 .with_child(close_element)
+    //                 .with_child(title_element)
+    //                 .with_child(buffer_jewel_element)
+    //         }
+    //         .contained()
+    //         .with_style(container)
+    //         .constrained()
+    //         .with_height(tab_style.height)
+    //         .into_any()
+    //     }
+
+    //     pub fn render_tab_bar_button<
+    //         F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
+    //         F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
+    //     >(
+    //         index: usize,
+    //         icon: &'static str,
+    //         is_active: bool,
+    //         tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
+    //         cx: &mut ViewContext<Pane>,
+    //         on_click: F1,
+    //         on_down: F2,
+    //         context_menu: Option<ViewHandle<ContextMenu>>,
+    //     ) -> AnyElement<Pane> {
+    //         enum TabBarButton {}
+
+    //         let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
+    //             let theme = &settings2::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
+    //             let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
+    //             Svg::new(icon)
+    //                 .with_color(style.color)
+    //                 .constrained()
+    //                 .with_width(style.icon_width)
+    //                 .aligned()
+    //                 .constrained()
+    //                 .with_width(style.button_width)
+    //                 .with_height(style.button_width)
+    //         })
+    //         .with_cursor_style(CursorStyle::PointingHand)
+    //         .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
+    //         .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
+    //         .into_any();
+    //         if let Some((tooltip, action)) = tooltip {
+    //             let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
+    //             button = button
+    //                 .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
+    //                 .into_any();
+    //         }
+
+    //         Stack::new()
+    //             .with_child(button)
+    //             .with_children(
+    //                 context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
+    //             )
+    //             .flex(1., false)
+    //             .into_any_named("tab bar button")
+    //     }
+
+    //     fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+    //         let background = theme.workspace.background;
+    //         Empty::new()
+    //             .contained()
+    //             .with_background_color(background)
+    //             .into_any()
+    //     }
+
+    //     pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
+    //         self.zoomed = zoomed;
+    //         cx.notify();
+    //     }
+
+    //     pub fn is_zoomed(&self) -> bool {
+    //         self.zoomed
+    //     }
+    // }
+
+    // impl Entity for Pane {
+    //     type Event = Event;
+    // }
+
+    // impl View for Pane {
+    //     fn ui_name() -> &'static str {
+    //         "Pane"
+    //     }
+
+    //     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+    //         enum MouseNavigationHandler {}
+
+    //         MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
+    //             let active_item_index = self.active_item_index;
+
+    //             if let Some(active_item) = self.active_item() {
+    //                 Flex::column()
+    //                     .with_child({
+    //                         let theme = theme::current(cx).clone();
+
+    //                         let mut stack = Stack::new();
+
+    //                         enum TabBarEventHandler {}
+    //                         stack.add_child(
+    //                             MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
+    //                                 Empty::new()
+    //                                     .contained()
+    //                                     .with_style(theme.workspace.tab_bar.container)
+    //                             })
+    //                             .on_down(
+    //                                 MouseButton::Left,
+    //                                 move |_, this, cx| {
+    //                                     this.activate_item(active_item_index, true, true, cx);
+    //                                 },
+    //                             ),
+    //                         );
+    //                         let tooltip_style = theme.tooltip.clone();
+    //                         let tab_bar_theme = theme.workspace.tab_bar.clone();
+
+    //                         let nav_button_height = tab_bar_theme.height;
+    //                         let button_style = tab_bar_theme.nav_button;
+    //                         let border_for_nav_buttons = tab_bar_theme
+    //                             .tab_style(false, false)
+    //                             .container
+    //                             .border
+    //                             .clone();
+
+    //                         let mut tab_row = Flex::row()
+    //                             .with_child(nav_button(
+    //                                 "icons/arrow_left.svg",
+    //                                 button_style.clone(),
+    //                                 nav_button_height,
+    //                                 tooltip_style.clone(),
+    //                                 self.can_navigate_backward(),
+    //                                 {
+    //                                     move |pane, cx| {
+    //                                         if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                                             let pane = cx.weak_handle();
+    //                                             cx.window_context().defer(move |cx| {
+    //                                                 workspace.update(cx, |workspace, cx| {
+    //                                                     workspace
+    //                                                         .go_back(pane, cx)
+    //                                                         .detach_and_log_err(cx)
+    //                                                 })
+    //                                             })
+    //                                         }
+    //                                     }
+    //                                 },
+    //                                 super::GoBack,
+    //                                 "Go Back",
+    //                                 cx,
+    //                             ))
+    //                             .with_child(
+    //                                 nav_button(
+    //                                     "icons/arrow_right.svg",
+    //                                     button_style.clone(),
+    //                                     nav_button_height,
+    //                                     tooltip_style,
+    //                                     self.can_navigate_forward(),
+    //                                     {
+    //                                         move |pane, cx| {
+    //                                             if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                                                 let pane = cx.weak_handle();
+    //                                                 cx.window_context().defer(move |cx| {
+    //                                                     workspace.update(cx, |workspace, cx| {
+    //                                                         workspace
+    //                                                             .go_forward(pane, cx)
+    //                                                             .detach_and_log_err(cx)
+    //                                                     })
+    //                                                 })
+    //                                             }
+    //                                         }
+    //                                     },
+    //                                     super::GoForward,
+    //                                     "Go Forward",
+    //                                     cx,
+    //                                 )
+    //                                 .contained()
+    //                                 .with_border(border_for_nav_buttons),
+    //                             )
+    //                             .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
+
+    //                         if self.has_focus {
+    //                             let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
+    //                             tab_row.add_child(
+    //                                 (render_tab_bar_buttons)(self, cx)
+    //                                     .contained()
+    //                                     .with_style(theme.workspace.tab_bar.pane_button_container)
+    //                                     .flex(1., false)
+    //                                     .into_any(),
+    //                             )
+    //                         }
+
+    //                         stack.add_child(tab_row);
+    //                         stack
+    //                             .constrained()
+    //                             .with_height(theme.workspace.tab_bar.height)
+    //                             .flex(1., false)
+    //                             .into_any_named("tab bar")
+    //                     })
+    //                     .with_child({
+    //                         enum PaneContentTabDropTarget {}
+    //                         dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
+    //                             self,
+    //                             0,
+    //                             self.active_item_index + 1,
+    //                             !self.can_split,
+    //                             if self.can_split { Some(100.) } else { None },
+    //                             cx,
+    //                             {
+    //                                 let toolbar = self.toolbar.clone();
+    //                                 let toolbar_hidden = toolbar.read(cx).hidden();
+    //                                 move |_, cx| {
+    //                                     Flex::column()
+    //                                         .with_children(
+    //                                             (!toolbar_hidden)
+    //                                                 .then(|| ChildView::new(&toolbar, cx).expanded()),
+    //                                         )
+    //                                         .with_child(
+    //                                             ChildView::new(active_item.as_any(), cx).flex(1., true),
+    //                                         )
+    //                                 }
+    //                             },
+    //                         )
+    //                         .flex(1., true)
+    //                     })
+    //                     .with_child(ChildView::new(&self.tab_context_menu, cx))
+    //                     .into_any()
+    //             } else {
+    //                 enum EmptyPane {}
+    //                 let theme = theme::current(cx).clone();
+
+    //                 dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
+    //                     self.render_blank_pane(&theme, cx)
+    //                 })
+    //                 .on_down(MouseButton::Left, |_, _, cx| {
+    //                     cx.focus_parent();
+    //                 })
+    //                 .into_any()
+    //             }
+    //         })
+    //         .on_down(
+    //             MouseButton::Navigate(NavigationDirection::Back),
+    //             move |_, pane, cx| {
+    //                 if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                     let pane = cx.weak_handle();
+    //                     cx.window_context().defer(move |cx| {
+    //                         workspace.update(cx, |workspace, cx| {
+    //                             workspace.go_back(pane, cx).detach_and_log_err(cx)
+    //                         })
+    //                     })
+    //                 }
+    //             },
+    //         )
+    //         .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
+    //             move |_, pane, cx| {
+    //                 if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                     let pane = cx.weak_handle();
+    //                     cx.window_context().defer(move |cx| {
+    //                         workspace.update(cx, |workspace, cx| {
+    //                             workspace.go_forward(pane, cx).detach_and_log_err(cx)
+    //                         })
+    //                     })
+    //                 }
+    //             }
+    //         })
+    //         .into_any_named("pane")
+    //     }
+
+    //     fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    //         if !self.has_focus {
+    //             self.has_focus = true;
+    //             cx.emit(Event::Focus);
+    //             cx.notify();
+    //         }
+
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.focus_changed(true, cx);
+    //         });
+
+    //         if let Some(active_item) = self.active_item() {
+    //             if cx.is_self_focused() {
+    //                 // Pane was focused directly. We need to either focus a view inside the active item,
+    //                 // or focus the active item itself
+    //                 if let Some(weak_last_focused_view) =
+    //                     self.last_focused_view_by_item.get(&active_item.id())
+    //                 {
+    //                     if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
+    //                         cx.focus(&last_focused_view);
+    //                         return;
+    //                     } else {
+    //                         self.last_focused_view_by_item.remove(&active_item.id());
+    //                     }
+    //                 }
+
+    //                 cx.focus(active_item.as_any());
+    //             } else if focused != self.tab_bar_context_menu.handle {
+    //                 self.last_focused_view_by_item
+    //                     .insert(active_item.id(), focused.downgrade());
+    //             }
+    //         }
+    //     }
+
+    //     fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    //         self.has_focus = false;
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.focus_changed(false, cx);
+    //         });
+    //         cx.notify();
+    //     }
+
+    //     fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+    //         Self::reset_to_default_keymap_context(keymap);
+    //     }
+    // }
+
+    // impl ItemNavHistory {
+    //     pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
+    //         self.history.push(data, self.item.clone(), cx);
+    //     }
+
+    //     pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
+    //         self.history.pop(NavigationMode::GoingBack, cx)
+    //     }
+
+    //     pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
+    //         self.history.pop(NavigationMode::GoingForward, cx)
+    //     }
+    // }
+
+    // impl NavHistory {
+    //     pub fn for_each_entry(
+    //         &self,
+    //         cx: &AppContext,
+    //         mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
+    //     ) {
+    //         let borrowed_history = self.0.borrow();
+    //         borrowed_history
+    //             .forward_stack
+    //             .iter()
+    //             .chain(borrowed_history.backward_stack.iter())
+    //             .chain(borrowed_history.closed_stack.iter())
+    //             .for_each(|entry| {
+    //                 if let Some(project_and_abs_path) =
+    //                     borrowed_history.paths_by_item.get(&entry.item.id())
+    //                 {
+    //                     f(entry, project_and_abs_path.clone());
+    //                 } else if let Some(item) = entry.item.upgrade(cx) {
+    //                     if let Some(path) = item.project_path(cx) {
+    //                         f(entry, (path, None));
+    //                     }
+    //                 }
+    //             })
+    //     }
+
+    //     pub fn set_mode(&mut self, mode: NavigationMode) {
+    //         self.0.borrow_mut().mode = mode;
+    //     }
+
+    pub fn mode(&self) -> NavigationMode {
+        self.0.borrow().mode
+    }
+
+    //     pub fn disable(&mut self) {
+    //         self.0.borrow_mut().mode = NavigationMode::Disabled;
+    //     }
+
+    //     pub fn enable(&mut self) {
+    //         self.0.borrow_mut().mode = NavigationMode::Normal;
+    //     }
+
+    //     pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
+    //         let mut state = self.0.borrow_mut();
+    //         let entry = match mode {
+    //             NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
+    //                 return None
+    //             }
+    //             NavigationMode::GoingBack => &mut state.backward_stack,
+    //             NavigationMode::GoingForward => &mut state.forward_stack,
+    //             NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
+    //         }
+    //         .pop_back();
+    //         if entry.is_some() {
+    //             state.did_update(cx);
+    //         }
+    //         entry
+    //     }
+
+    //     pub fn push<D: 'static + Any>(
+    //         &mut self,
+    //         data: Option<D>,
+    //         item: Rc<dyn WeakItemHandle>,
+    //         cx: &mut WindowContext,
+    //     ) {
+    //         let state = &mut *self.0.borrow_mut();
+    //         match state.mode {
+    //             NavigationMode::Disabled => {}
+    //             NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
+    //                 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.backward_stack.pop_front();
+    //                 }
+    //                 state.backward_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //                 state.forward_stack.clear();
+    //             }
+    //             NavigationMode::GoingBack => {
+    //                 if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.forward_stack.pop_front();
+    //                 }
+    //                 state.forward_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //             }
+    //             NavigationMode::GoingForward => {
+    //                 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.backward_stack.pop_front();
+    //                 }
+    //                 state.backward_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //             }
+    //             NavigationMode::ClosingItem => {
+    //                 if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.closed_stack.pop_front();
+    //                 }
+    //                 state.closed_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //             }
+    //         }
+    //         state.did_update(cx);
+    //     }
+
+    //     pub fn remove_item(&mut self, item_id: usize) {
+    //         let mut state = self.0.borrow_mut();
+    //         state.paths_by_item.remove(&item_id);
+    //         state
+    //             .backward_stack
+    //             .retain(|entry| entry.item.id() != item_id);
+    //         state
+    //             .forward_stack
+    //             .retain(|entry| entry.item.id() != item_id);
+    //         state
+    //             .closed_stack
+    //             .retain(|entry| entry.item.id() != item_id);
+    //     }
+
+    //     pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
+    //         self.0.borrow().paths_by_item.get(&item_id).cloned()
+    //     }
+}
+
+// impl NavHistoryState {
+//     pub fn did_update(&self, cx: &mut WindowContext) {
+//         if let Some(pane) = self.pane.upgrade(cx) {
+//             cx.defer(move |cx| {
+//                 pane.update(cx, |pane, cx| pane.history_updated(cx));
+//             });
+//         }
+//     }
+// }
+
+// pub struct PaneBackdrop<V> {
+//     child_view: usize,
+//     child: AnyElement<V>,
+// }
+
+// impl<V> PaneBackdrop<V> {
+//     pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
+//         PaneBackdrop {
+//             child,
+//             child_view: pane_item_view,
+//         }
+//     }
+// }
+
+// impl<V: 'static> Element<V> for PaneBackdrop<V> {
+//     type LayoutState = ();
+
+//     type PaintState = ();
+
+//     fn layout(
+//         &mut self,
+//         constraint: gpui::SizeConstraint,
+//         view: &mut V,
+//         cx: &mut ViewContext<V>,
+//     ) -> (Vector2F, Self::LayoutState) {
+//         let size = self.child.layout(constraint, view, cx);
+//         (size, ())
+//     }
+
+//     fn paint(
+//         &mut self,
+//         bounds: RectF,
+//         visible_bounds: RectF,
+//         _: &mut Self::LayoutState,
+//         view: &mut V,
+//         cx: &mut ViewContext<V>,
+//     ) -> Self::PaintState {
+//         let background = theme::current(cx).editor.background;
+
+//         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+
+//         cx.scene().push_quad(gpui::Quad {
+//             bounds: RectF::new(bounds.origin(), bounds.size()),
+//             background: Some(background),
+//             ..Default::default()
+//         });
+
+//         let child_view_id = self.child_view;
+//         cx.scene().push_mouse_region(
+//             MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
+//                 gpui::platform::MouseButton::Left,
+//                 move |_, _: &mut V, cx| {
+//                     let window = cx.window();
+//                     cx.app_context().focus(window, Some(child_view_id))
+//                 },
+//             ),
+//         );
+
+//         cx.scene().push_layer(Some(bounds));
+//         self.child.paint(bounds.origin(), visible_bounds, view, cx);
+//         cx.scene().pop_layer();
+//     }
+
+//     fn rect_for_text_range(
+//         &self,
+//         range_utf16: std::ops::Range<usize>,
+//         _bounds: RectF,
+//         _visible_bounds: RectF,
+//         _layout: &Self::LayoutState,
+//         _paint: &Self::PaintState,
+//         view: &V,
+//         cx: &gpui::ViewContext<V>,
+//     ) -> Option<RectF> {
+//         self.child.rect_for_text_range(range_utf16, view, cx)
+//     }
+
+//     fn debug(
+//         &self,
+//         _bounds: RectF,
+//         _layout: &Self::LayoutState,
+//         _paint: &Self::PaintState,
+//         view: &V,
+//         cx: &gpui::ViewContext<V>,
+//     ) -> serde_json::Value {
+//         gpui::json::json!({
+//             "type": "Pane Back Drop",
+//             "view": self.child_view,
+//             "child": self.child.debug(view, cx),
+//         })
+//     }
+// }
+
+// fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
+//     let path = buffer_path
+//         .as_ref()
+//         .and_then(|p| p.path.to_str())
+//         .unwrap_or(&"This buffer");
+//     let path = truncate_and_remove_front(path, 80);
+//     format!("{path} contains unsaved edits. Do you want to save it?")
+// }
+
+// todo!("uncomment tests")
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use crate::item::test::{TestItem, TestProjectItem};
+//     use gpui::TestAppContext;
+//     use project::FakeFs;
+//     use settings::SettingsStore;
+
+//     #[gpui::test]
+//     async fn test_remove_active_empty(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         pane.update(cx, |pane, cx| {
+//             assert!(pane
+//                 .close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//                 .is_none())
+//         });
+//     }
+
+//     #[gpui::test]
+//     async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
+//         cx.foreground().forbid_parking();
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         // 1. Add with a destination index
+//         //   a. Add before the active item
+//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 Some(0),
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
+
+//         //   b. Add after the active item
+//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 Some(2),
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
+
+//         //   c. Add at the end of the item list (including off the length)
+//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 Some(5),
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+//         // 2. Add without a destination index
+//         //   a. Add with active item at the start of the item list
+//         set_labeled_items(&pane, ["A*", "B", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 None,
+//                 cx,
+//             );
+//         });
+//         set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
+
+//         //   b. Add with active item at the end of the item list
+//         set_labeled_items(&pane, ["A", "B", "C*"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 None,
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
+//         cx.foreground().forbid_parking();
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         // 1. Add with a destination index
+//         //   1a. Add before the active item
+//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(d, false, false, Some(0), cx);
+//         });
+//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
+
+//         //   1b. Add after the active item
+//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(d, false, false, Some(2), cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
+
+//         //   1c. Add at the end of the item list (including off the length)
+//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(a, false, false, Some(5), cx);
+//         });
+//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
+
+//         //   1d. Add same item to active index
+//         let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(b, false, false, Some(1), cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
+
+//         //   1e. Add item to index after same item in last position
+//         let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(c, false, false, Some(2), cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+//         // 2. Add without a destination index
+//         //   2a. Add with active item at the start of the item list
+//         let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(d, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
+
+//         //   2b. Add with active item at the end of the item list
+//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(a, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
+
+//         //   2c. Add active item to active item at end of list
+//         let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(c, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+//         //   2d. Add active item to active item at start of list
+//         let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(a, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["A*", "B", "C"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
+//         cx.foreground().forbid_parking();
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         // singleton view
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(true)
+//                 .with_label("buffer 1")
+//                 .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1*"], cx);
+
+//         // new singleton view with the same project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(true)
+//                 .with_label("buffer 1")
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1*"], cx);
+
+//         // new singleton view with different project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(true)
+//                 .with_label("buffer 2")
+//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
+
+//         // new multibuffer view with the same project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(false)
+//                 .with_label("multibuffer 1")
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
+
+//         // another multibuffer view with the same project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(false)
+//                 .with_label("multibuffer 1b")
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(
+//             &pane,
+//             ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
+//             cx,
+//         );
+//     }
+
+//     #[gpui::test]
+//     async fn test_remove_item_ordering(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         add_labeled_item(&pane, "A", false, cx);
+//         add_labeled_item(&pane, "B", false, cx);
+//         add_labeled_item(&pane, "C", false, cx);
+//         add_labeled_item(&pane, "D", false, cx);
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+//         pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
+//         add_labeled_item(&pane, "1", false, cx);
+//         assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
+
+//         pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "C*"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_inactive_items(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_inactive_items(&CloseInactiveItems, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["C*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_clean_items(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         add_labeled_item(&pane, "A", true, cx);
+//         add_labeled_item(&pane, "B", false, cx);
+//         add_labeled_item(&pane, "C", true, cx);
+//         add_labeled_item(&pane, "D", false, cx);
+//         add_labeled_item(&pane, "E", false, cx);
+//         assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
+
+//         pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
+//             .unwrap()
+//             .await
+//             .unwrap();
+//         assert_item_labels(&pane, ["A^", "C*^"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["C*", "D", "E"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_all_items(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         add_labeled_item(&pane, "A", false, cx);
+//         add_labeled_item(&pane, "B", false, cx);
+//         add_labeled_item(&pane, "C", false, cx);
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, [], cx);
+
+//         add_labeled_item(&pane, "A", true, cx);
+//         add_labeled_item(&pane, "B", true, cx);
+//         add_labeled_item(&pane, "C", true, cx);
+//         assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
+
+//         let save = pane
+//             .update(cx, |pane, cx| {
+//                 pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
+//             })
+//             .unwrap();
+
+//         cx.foreground().run_until_parked();
+//         window.simulate_prompt_answer(2, cx);
+//         save.await.unwrap();
+//         assert_item_labels(&pane, [], cx);
+//     }
+
+//     fn init_test(cx: &mut TestAppContext) {
+//         cx.update(|cx| {
+//             cx.set_global(SettingsStore::test(cx));
+//             theme::init((), cx);
+//             crate::init_settings(cx);
+//             Project::init_settings(cx);
+//         });
+//     }
+
+//     fn add_labeled_item(
+//         pane: &ViewHandle<Pane>,
+//         label: &str,
+//         is_dirty: bool,
+//         cx: &mut TestAppContext,
+//     ) -> Box<ViewHandle<TestItem>> {
+//         pane.update(cx, |pane, cx| {
+//             let labeled_item =
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
+//             pane.add_item(labeled_item.clone(), false, false, None, cx);
+//             labeled_item
+//         })
+//     }
+
+//     fn set_labeled_items<const COUNT: usize>(
+//         pane: &ViewHandle<Pane>,
+//         labels: [&str; COUNT],
+//         cx: &mut TestAppContext,
+//     ) -> [Box<ViewHandle<TestItem>>; COUNT] {
+//         pane.update(cx, |pane, cx| {
+//             pane.items.clear();
+//             let mut active_item_index = 0;
+
+//             let mut index = 0;
+//             let items = labels.map(|mut label| {
+//                 if label.ends_with("*") {
+//                     label = label.trim_end_matches("*");
+//                     active_item_index = index;
+//                 }
+
+//                 let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
+//                 pane.add_item(labeled_item.clone(), false, false, None, cx);
+//                 index += 1;
+//                 labeled_item
+//             });
+
+//             pane.activate_item(active_item_index, false, false, cx);
+
+//             items
+//         })
+//     }
+
+//     // Assert the item label, with the active item label suffixed with a '*'
+//     fn assert_item_labels<const COUNT: usize>(
+//         pane: &ViewHandle<Pane>,
+//         expected_states: [&str; COUNT],
+//         cx: &mut TestAppContext,
+//     ) {
+//         pane.read_with(cx, |pane, cx| {
+//             let actual_states = pane
+//                 .items
+//                 .iter()
+//                 .enumerate()
+//                 .map(|(ix, item)| {
+//                     let mut state = item
+//                         .as_any()
+//                         .downcast_ref::<TestItem>()
+//                         .unwrap()
+//                         .read(cx)
+//                         .label
+//                         .clone();
+//                     if ix == pane.active_item_index {
+//                         state.push('*');
+//                     }
+//                     if item.is_dirty(cx) {
+//                         state.push('^');
+//                     }
+//                     state
+//                 })
+//                 .collect::<Vec<_>>();
+
+//             assert_eq!(
+//                 actual_states, expected_states,
+//                 "pane items do not match expectation"
+//             );
+//         })
+//     }
+// }

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

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

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

@@ -0,0 +1,340 @@
+use crate::{
+    item::ItemHandle, Axis, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
+};
+use anyhow::{Context, Result};
+use async_recursion::async_recursion;
+use db2::sqlez::{
+    bindable::{Bind, Column, StaticColumnCount},
+    statement::Statement,
+};
+use gpui2::{AsyncAppContext, Handle, Task, View, WeakView, WindowBounds};
+use project2::Project;
+use std::{
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+use util::ResultExt;
+use uuid::Uuid;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct WorkspaceLocation(Arc<Vec<PathBuf>>);
+
+impl WorkspaceLocation {
+    pub fn paths(&self) -> Arc<Vec<PathBuf>> {
+        self.0.clone()
+    }
+}
+
+impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
+    fn from(iterator: T) -> Self {
+        let mut roots = iterator
+            .into_iter()
+            .map(|p| p.as_ref().to_path_buf())
+            .collect::<Vec<_>>();
+        roots.sort();
+        Self(Arc::new(roots))
+    }
+}
+
+impl StaticColumnCount for WorkspaceLocation {}
+impl Bind for &WorkspaceLocation {
+    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
+        bincode::serialize(&self.0)
+            .expect("Bincode serialization of paths should not fail")
+            .bind(statement, start_index)
+    }
+}
+
+impl Column for WorkspaceLocation {
+    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
+        let blob = statement.column_blob(start_index)?;
+        Ok((
+            WorkspaceLocation(bincode::deserialize(blob).context("Bincode failed")?),
+            start_index + 1,
+        ))
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct SerializedWorkspace {
+    pub id: WorkspaceId,
+    pub location: WorkspaceLocation,
+    pub center_group: SerializedPaneGroup,
+    pub bounds: Option<WindowBounds>,
+    pub display: Option<Uuid>,
+    pub docks: DockStructure,
+}
+
+#[derive(Debug, PartialEq, Clone, Default)]
+pub struct DockStructure {
+    pub(crate) left: DockData,
+    pub(crate) right: DockData,
+    pub(crate) bottom: DockData,
+}
+
+impl Column for DockStructure {
+    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
+        let (left, next_index) = DockData::column(statement, start_index)?;
+        let (right, next_index) = DockData::column(statement, next_index)?;
+        let (bottom, next_index) = DockData::column(statement, next_index)?;
+        Ok((
+            DockStructure {
+                left,
+                right,
+                bottom,
+            },
+            next_index,
+        ))
+    }
+}
+
+impl Bind for DockStructure {
+    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
+        let next_index = statement.bind(&self.left, start_index)?;
+        let next_index = statement.bind(&self.right, next_index)?;
+        statement.bind(&self.bottom, next_index)
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Default)]
+pub struct DockData {
+    pub(crate) visible: bool,
+    pub(crate) active_panel: Option<String>,
+    pub(crate) zoom: bool,
+}
+
+impl Column for DockData {
+    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
+        let (visible, next_index) = Option::<bool>::column(statement, start_index)?;
+        let (active_panel, next_index) = Option::<String>::column(statement, next_index)?;
+        let (zoom, next_index) = Option::<bool>::column(statement, next_index)?;
+        Ok((
+            DockData {
+                visible: visible.unwrap_or(false),
+                active_panel,
+                zoom: zoom.unwrap_or(false),
+            },
+            next_index,
+        ))
+    }
+}
+
+impl Bind for DockData {
+    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
+        let next_index = statement.bind(&self.visible, start_index)?;
+        let next_index = statement.bind(&self.active_panel, next_index)?;
+        statement.bind(&self.zoom, next_index)
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum SerializedPaneGroup {
+    Group {
+        axis: Axis,
+        flexes: Option<Vec<f32>>,
+        children: Vec<SerializedPaneGroup>,
+    },
+    Pane(SerializedPane),
+}
+
+#[cfg(test)]
+impl Default for SerializedPaneGroup {
+    fn default() -> Self {
+        Self::Pane(SerializedPane {
+            children: vec![SerializedItem::default()],
+            active: false,
+        })
+    }
+}
+
+impl SerializedPaneGroup {
+    #[async_recursion(?Send)]
+    pub(crate) async fn deserialize(
+        self,
+        project: &Handle<Project>,
+        workspace_id: WorkspaceId,
+        workspace: &WeakView<Workspace>,
+        cx: &mut AsyncAppContext,
+    ) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
+        match self {
+            SerializedPaneGroup::Group {
+                axis,
+                children,
+                flexes,
+            } => {
+                let mut current_active_pane = None;
+                let mut members = Vec::new();
+                let mut items = Vec::new();
+                for child in children {
+                    if let Some((new_member, active_pane, new_items)) = child
+                        .deserialize(project, workspace_id, workspace, cx)
+                        .await
+                    {
+                        members.push(new_member);
+                        items.extend(new_items);
+                        current_active_pane = current_active_pane.or(active_pane);
+                    }
+                }
+
+                if members.is_empty() {
+                    return None;
+                }
+
+                if members.len() == 1 {
+                    return Some((members.remove(0), current_active_pane, items));
+                }
+
+                Some((
+                    Member::Axis(PaneAxis::load(axis, members, flexes)),
+                    current_active_pane,
+                    items,
+                ))
+            }
+            SerializedPaneGroup::Pane(serialized_pane) => {
+                let pane = workspace
+                    .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade())
+                    .log_err()?;
+                let active = serialized_pane.active;
+                let new_items = serialized_pane
+                    .deserialize_to(project, &pane, workspace_id, workspace, cx)
+                    .await
+                    .log_err()?;
+
+                if pane
+                    .read_with(cx, |pane, _| pane.items_len() != 0)
+                    .log_err()?
+                {
+                    let pane = pane.upgrade()?;
+                    Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
+                } else {
+                    let pane = pane.upgrade()?;
+                    workspace
+                        .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
+                        .log_err()?;
+                    None
+                }
+            }
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Default, Clone)]
+pub struct SerializedPane {
+    pub(crate) active: bool,
+    pub(crate) children: Vec<SerializedItem>,
+}
+
+impl SerializedPane {
+    pub fn new(children: Vec<SerializedItem>, active: bool) -> Self {
+        SerializedPane { children, active }
+    }
+
+    pub async fn deserialize_to(
+        &self,
+        project: &Handle<Project>,
+        pane: &WeakView<Pane>,
+        workspace_id: WorkspaceId,
+        workspace: &WeakView<Workspace>,
+        cx: &mut AsyncAppContext,
+    ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
+        let mut items = Vec::new();
+        let mut active_item_index = None;
+        for (index, item) in self.children.iter().enumerate() {
+            let project = project.clone();
+            let item_handle = pane
+                .update(cx, |_, cx| {
+                    if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
+                        deserializer(project, workspace.clone(), workspace_id, item.item_id, cx)
+                    } else {
+                        Task::ready(Err(anyhow::anyhow!(
+                            "Deserializer does not exist for item kind: {}",
+                            item.kind
+                        )))
+                    }
+                })?
+                .await
+                .log_err();
+
+            items.push(item_handle.clone());
+
+            if let Some(item_handle) = item_handle {
+                pane.update(cx, |pane, cx| {
+                    pane.add_item(item_handle.clone(), true, true, None, cx);
+                })?;
+            }
+
+            if item.active {
+                active_item_index = Some(index);
+            }
+        }
+
+        if let Some(active_item_index) = active_item_index {
+            pane.update(cx, |pane, cx| {
+                pane.activate_item(active_item_index, false, false, cx);
+            })?;
+        }
+
+        anyhow::Ok(items)
+    }
+}
+
+pub type GroupId = i64;
+pub type PaneId = i64;
+pub type ItemId = usize;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct SerializedItem {
+    pub kind: Arc<str>,
+    pub item_id: ItemId,
+    pub active: bool,
+}
+
+impl SerializedItem {
+    pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool) -> Self {
+        Self {
+            kind: Arc::from(kind.as_ref()),
+            item_id,
+            active,
+        }
+    }
+}
+
+#[cfg(test)]
+impl Default for SerializedItem {
+    fn default() -> Self {
+        SerializedItem {
+            kind: Arc::from("Terminal"),
+            item_id: 100000,
+            active: false,
+        }
+    }
+}
+
+impl StaticColumnCount for SerializedItem {
+    fn column_count() -> usize {
+        3
+    }
+}
+impl Bind for &SerializedItem {
+    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
+        let next_index = statement.bind(&self.kind, start_index)?;
+        let next_index = statement.bind(&self.item_id, next_index)?;
+        statement.bind(&self.active, next_index)
+    }
+}
+
+impl Column for SerializedItem {
+    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
+        let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
+        let (item_id, next_index) = ItemId::column(statement, next_index)?;
+        let (active, next_index) = bool::column(statement, next_index)?;
+        Ok((
+            SerializedItem {
+                kind,
+                item_id,
+                active,
+            },
+            next_index,
+        ))
+    }
+}

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

@@ -0,0 +1,5535 @@
+// pub mod dock;
+pub mod item;
+// pub mod notifications;
+pub mod pane;
+pub mod pane_group;
+mod persistence;
+pub mod searchable;
+// pub mod shared_screen;
+// mod status_bar;
+mod toolbar;
+mod workspace_settings;
+
+use anyhow::{anyhow, Result};
+// use call2::ActiveCall;
+// use client2::{
+//     proto::{self, PeerId},
+//     Client, Status, TypedEnvelope, UserStore,
+// };
+// use collections::{hash_map, HashMap, HashSet};
+// use futures::{
+//     channel::{mpsc, oneshot},
+//     future::try_join_all,
+//     FutureExt, StreamExt,
+// };
+// use gpui2::{
+//     actions,
+//     elements::*,
+//     geometry::{
+//         rect::RectF,
+//         vector::{vec2f, Vector2F},
+//     },
+//     impl_actions,
+//     platform::{
+//         CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel,
+//         WindowBounds, WindowOptions,
+//     },
+//     AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext,
+//     Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext,
+//     View, WeakViewHandle, WindowContext, WindowHandle,
+// };
+// use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
+// use itertools::Itertools;
+// use language2::{LanguageRegistry, Rope};
+// use node_runtime::NodeRuntime;// //
+
+use futures::channel::oneshot;
+// use crate::{
+//     notifications::{simple_message_notification::MessageNotification, NotificationTracker},
+//     persistence::model::{
+//         DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
+//     },
+// };
+// use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
+// use lazy_static::lazy_static;
+// use notifications::{NotificationHandle, NotifyResultExt};
+pub use pane::*;
+pub use pane_group::*;
+// use persistence::{model::SerializedItem, DB};
+// pub use persistence::{
+//     model::{ItemId, WorkspaceLocation},
+//     WorkspaceDb, DB as WORKSPACE_DB,
+// };
+// use postage::prelude::Stream;
+// use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
+// use serde::Deserialize;
+// use shared_screen::SharedScreen;
+// use status_bar::StatusBar;
+// pub use status_bar::StatusItemView;
+// use theme::{Theme, ThemeSettings};
+pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
+// use util::ResultExt;
+// pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
+
+// lazy_static! {
+//     static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
+//         .ok()
+//         .as_deref()
+//         .and_then(parse_pixel_position_env_var);
+//     static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
+//         .ok()
+//         .as_deref()
+//         .and_then(parse_pixel_position_env_var);
+// }
+
+// pub trait Modal: View {
+//     fn has_focus(&self) -> bool;
+//     fn dismiss_on_event(event: &Self::Event) -> bool;
+// }
+
+// trait ModalHandle {
+//     fn as_any(&self) -> &AnyViewHandle;
+//     fn has_focus(&self, cx: &WindowContext) -> bool;
+// }
+
+// impl<T: Modal> ModalHandle for View<T> {
+//     fn as_any(&self) -> &AnyViewHandle {
+//         self
+//     }
+
+//     fn has_focus(&self, cx: &WindowContext) -> bool {
+//         self.read(cx).has_focus()
+//     }
+// }
+
+// #[derive(Clone, PartialEq)]
+// pub struct RemoveWorktreeFromProject(pub WorktreeId);
+
+// actions!(
+//     workspace,
+//     [
+//         Open,
+//         NewFile,
+//         NewWindow,
+//         CloseWindow,
+//         CloseInactiveTabsAndPanes,
+//         AddFolderToProject,
+//         Unfollow,
+//         SaveAs,
+//         ReloadActiveItem,
+//         ActivatePreviousPane,
+//         ActivateNextPane,
+//         FollowNextCollaborator,
+//         NewTerminal,
+//         NewCenterTerminal,
+//         ToggleTerminalFocus,
+//         NewSearch,
+//         Feedback,
+//         Restart,
+//         Welcome,
+//         ToggleZoom,
+//         ToggleLeftDock,
+//         ToggleRightDock,
+//         ToggleBottomDock,
+//         CloseAllDocks,
+//     ]
+// );
+
+// #[derive(Clone, PartialEq)]
+// pub struct OpenPaths {
+//     pub paths: Vec<PathBuf>,
+// }
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct ActivatePane(pub usize);
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct ActivatePaneInDirection(pub SplitDirection);
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct SwapPaneInDirection(pub SplitDirection);
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct NewFileInDirection(pub SplitDirection);
+
+// #[derive(Clone, PartialEq, Debug, Deserialize)]
+// #[serde(rename_all = "camelCase")]
+// pub struct SaveAll {
+//     pub save_intent: Option<SaveIntent>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize)]
+// #[serde(rename_all = "camelCase")]
+// pub struct Save {
+//     pub save_intent: Option<SaveIntent>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+// #[serde(rename_all = "camelCase")]
+// pub struct CloseAllItemsAndPanes {
+//     pub save_intent: Option<SaveIntent>,
+// }
+
+// #[derive(Deserialize)]
+// pub struct Toast {
+//     id: usize,
+//     msg: Cow<'static, str>,
+//     #[serde(skip)]
+//     on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
+// }
+
+// impl Toast {
+//     pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
+//         Toast {
+//             id,
+//             msg: msg.into(),
+//             on_click: None,
+//         }
+//     }
+
+//     pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
+//     where
+//         M: Into<Cow<'static, str>>,
+//         F: Fn(&mut WindowContext) + 'static,
+//     {
+//         self.on_click = Some((message.into(), Arc::new(on_click)));
+//         self
+//     }
+// }
+
+// impl PartialEq for Toast {
+//     fn eq(&self, other: &Self) -> bool {
+//         self.id == other.id
+//             && self.msg == other.msg
+//             && self.on_click.is_some() == other.on_click.is_some()
+//     }
+// }
+
+// impl Clone for Toast {
+//     fn clone(&self) -> Self {
+//         Toast {
+//             id: self.id,
+//             msg: self.msg.to_owned(),
+//             on_click: self.on_click.clone(),
+//         }
+//     }
+// }
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct OpenTerminal {
+//     pub working_directory: PathBuf,
+// }
+
+// impl_actions!(
+//     workspace,
+//     [
+//         ActivatePane,
+//         ActivatePaneInDirection,
+//         SwapPaneInDirection,
+//         NewFileInDirection,
+//         Toast,
+//         OpenTerminal,
+//         SaveAll,
+//         Save,
+//         CloseAllItemsAndPanes,
+//     ]
+// );
+
+pub type WorkspaceId = i64;
+
+// pub fn init_settings(cx: &mut AppContext) {
+//     settings::register::<WorkspaceSettings>(cx);
+//     settings::register::<item::ItemSettings>(cx);
+// }
+
+// pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
+//     init_settings(cx);
+//     pane::init(cx);
+//     notifications::init(cx);
+
+//     cx.add_global_action({
+//         let app_state = Arc::downgrade(&app_state);
+//         move |_: &Open, cx: &mut AppContext| {
+//             let mut 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 {
+//                     if let Some(paths) = paths.recv().await.flatten() {
+//                         cx.update(|cx| {
+//                             open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
+//                         });
+//                     }
+//                 })
+//                 .detach();
+//             }
+//         }
+//     });
+//     cx.add_async_action(Workspace::open);
+
+//     cx.add_async_action(Workspace::follow_next_collaborator);
+//     cx.add_async_action(Workspace::close);
+//     cx.add_async_action(Workspace::close_inactive_items_and_panes);
+//     cx.add_async_action(Workspace::close_all_items_and_panes);
+//     cx.add_global_action(Workspace::close_global);
+//     cx.add_global_action(restart);
+//     cx.add_async_action(Workspace::save_all);
+//     cx.add_action(Workspace::add_folder_to_project);
+//     cx.add_action(
+//         |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
+//             let pane = workspace.active_pane().clone();
+//             workspace.unfollow(&pane, cx);
+//         },
+//     );
+//     cx.add_action(
+//         |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
+//             workspace
+//                 .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
+//                 .detach_and_log_err(cx);
+//         },
+//     );
+//     cx.add_action(
+//         |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
+//             workspace
+//                 .save_active_item(SaveIntent::SaveAs, cx)
+//                 .detach_and_log_err(cx);
+//         },
+//     );
+//     cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
+//         workspace.activate_previous_pane(cx)
+//     });
+//     cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
+//         workspace.activate_next_pane(cx)
+//     });
+
+//     cx.add_action(
+//         |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
+//             workspace.activate_pane_in_direction(action.0, cx)
+//         },
+//     );
+
+//     cx.add_action(
+//         |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
+//             workspace.swap_pane_in_direction(action.0, cx)
+//         },
+//     );
+
+//     cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
+//         workspace.toggle_dock(DockPosition::Left, cx);
+//     });
+//     cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
+//         workspace.toggle_dock(DockPosition::Right, cx);
+//     });
+//     cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
+//         workspace.toggle_dock(DockPosition::Bottom, cx);
+//     });
+//     cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
+//         workspace.close_all_docks(cx);
+//     });
+//     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.add_view(|_| {
+//                             MessageNotification::new("Successfully installed the `zed` binary")
+//                         })
+//                     });
+//                 }
+//             })
+//         })
+//         .detach();
+//     });
+// }
+
+type ProjectItemBuilders =
+    HashMap<TypeId, fn(Handle<Project>, AnyHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
+pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
+    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
+        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
+            let item = model.downcast::<I::Item>().unwrap();
+            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
+        });
+    });
+}
+
+type FollowableItemBuilder = fn(
+    View<Pane>,
+    View<Workspace>,
+    ViewId,
+    &mut Option<proto::view::Variant>,
+    &mut AppContext,
+) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
+type FollowableItemBuilders = HashMap<
+    TypeId,
+    (
+        FollowableItemBuilder,
+        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
+    ),
+>;
+pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
+    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
+        builders.insert(
+            TypeId::of::<I>(),
+            (
+                |pane, workspace, id, state, cx| {
+                    I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
+                        cx.foreground()
+                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
+                    })
+                },
+                |this| Box::new(this.clone().downcast::<I>().unwrap()),
+            ),
+        );
+    });
+}
+
+type ItemDeserializers = HashMap<
+    Arc<str>,
+    fn(
+        Handle<Project>,
+        WeakView<Workspace>,
+        WorkspaceId,
+        ItemId,
+        &mut ViewContext<Pane>,
+    ) -> Task<Result<Box<dyn ItemHandle>>>,
+>;
+pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
+    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
+        if let Some(serialized_item_kind) = I::serialized_item_kind() {
+            deserializers.insert(
+                Arc::from(serialized_item_kind),
+                |project, workspace, workspace_id, item_id, cx| {
+                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
+                    cx.foreground()
+                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
+                },
+            );
+        }
+    });
+}
+
+pub struct AppState {
+    pub languages: Arc<LanguageRegistry>,
+    pub client: Arc<Client>,
+    pub user_store: Handle<UserStore>,
+    pub workspace_store: Handle<WorkspaceStore>,
+    pub fs: Arc<dyn fs2::Fs>,
+    pub build_window_options:
+        fn(Option<WindowBounds>, Option<DisplayId>, &MainThread<AppContext>) -> WindowOptions,
+    pub initialize_workspace:
+        fn(WeakHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<anyhow::Result<()>>,
+    pub node_runtime: Arc<dyn NodeRuntime>,
+}
+
+pub struct WorkspaceStore {
+    workspaces: HashSet<WeakHandle<Workspace>>,
+    followers: Vec<Follower>,
+    client: Arc<Client>,
+    _subscriptions: Vec<client2::Subscription>,
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
+struct Follower {
+    project_id: Option<u64>,
+    peer_id: PeerId,
+}
+
+// impl AppState {
+//     #[cfg(any(test, feature = "test-support"))]
+//     pub fn test(cx: &mut AppContext) -> Arc<Self> {
+//         use node_runtime::FakeNodeRuntime;
+//         use settings::SettingsStore;
+
+//         if !cx.has_global::<SettingsStore>() {
+//             cx.set_global(SettingsStore::test(cx));
+//         }
+
+//         let fs = fs::FakeFs::new(cx.background().clone());
+//         let languages = Arc::new(LanguageRegistry::test());
+//         let http_client = util::http::FakeHttpClient::with_404_response();
+//         let client = Client::new(http_client.clone(), cx);
+//         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
+//         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
+
+//         theme::init((), cx);
+//         client::init(&client, cx);
+//         crate::init_settings(cx);
+
+//         Arc::new(Self {
+//             client,
+//             fs,
+//             languages,
+//             user_store,
+//             // channel_store,
+//             workspace_store,
+//             node_runtime: FakeNodeRuntime::new(),
+//             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+//             build_window_options: |_, _, _| Default::default(),
+//         })
+//     }
+// }
+
+struct DelayedDebouncedEditAction {
+    task: Option<Task<()>>,
+    cancel_channel: Option<oneshot::Sender<()>>,
+}
+
+impl DelayedDebouncedEditAction {
+    fn new() -> DelayedDebouncedEditAction {
+        DelayedDebouncedEditAction {
+            task: None,
+            cancel_channel: None,
+        }
+    }
+
+    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
+    where
+        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
+    {
+        if let Some(channel) = self.cancel_channel.take() {
+            _ = channel.send(());
+        }
+
+        let (sender, mut receiver) = oneshot::channel::<()>();
+        self.cancel_channel = Some(sender);
+
+        let previous_task = self.task.take();
+        self.task = Some(cx.spawn(|workspace, mut cx| async move {
+            let mut timer = cx.background().timer(delay).fuse();
+            if let Some(previous_task) = previous_task {
+                previous_task.await;
+            }
+
+            futures::select_biased! {
+                _ = receiver => return,
+                    _ = timer => {}
+            }
+
+            if let Some(result) = workspace
+                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
+                .log_err()
+            {
+                result.await.log_err();
+            }
+        }));
+    }
+}
+
+// pub enum Event {
+//     PaneAdded(View<Pane>),
+//     ContactRequestedJoin(u64),
+// }
+
+pub struct Workspace {
+    weak_self: WeakHandle<Self>,
+    //     modal: Option<ActiveModal>,
+    //     zoomed: Option<AnyWeakViewHandle>,
+    //     zoomed_position: Option<DockPosition>,
+    //     center: PaneGroup,
+    //     left_dock: View<Dock>,
+    //     bottom_dock: View<Dock>,
+    //     right_dock: View<Dock>,
+    panes: Vec<View<Pane>>,
+    //     panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
+    //     active_pane: View<Pane>,
+    last_active_center_pane: Option<WeakView<Pane>>,
+    //     last_active_view_id: Option<proto::ViewId>,
+    //     status_bar: View<StatusBar>,
+    //     titlebar_item: Option<AnyViewHandle>,
+    //     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
+    project: Handle<Project>,
+    //     follower_states: HashMap<View<Pane>, FollowerState>,
+    //     last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
+    //     window_edited: bool,
+    //     active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
+    //     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
+    //     database_id: WorkspaceId,
+    app_state: Arc<AppState>,
+    //     subscriptions: Vec<Subscription>,
+    //     _apply_leader_updates: Task<Result<()>>,
+    //     _observe_current_user: Task<Result<()>>,
+    //     _schedule_serialize: Option<Task<()>>,
+    //     pane_history_timestamp: Arc<AtomicUsize>,
+}
+
+// struct ActiveModal {
+//     view: Box<dyn ModalHandle>,
+//     previously_focused_view_id: Option<usize>,
+// }
+
+// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+// pub struct ViewId {
+//     pub creator: PeerId,
+//     pub id: u64,
+// }
+
+#[derive(Default)]
+struct FollowerState {
+    leader_id: PeerId,
+    active_view_id: Option<ViewId>,
+    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
+}
+
+// enum WorkspaceBounds {}
+
+impl Workspace {
+    //     pub fn new(
+    //         workspace_id: WorkspaceId,
+    //         project: ModelHandle<Project>,
+    //         app_state: Arc<AppState>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Self {
+    //         cx.observe(&project, |_, _, cx| cx.notify()).detach();
+    //         cx.subscribe(&project, move |this, _, event, cx| {
+    //             match event {
+    //                 project::Event::RemoteIdChanged(_) => {
+    //                     this.update_window_title(cx);
+    //                 }
+
+    //                 project::Event::CollaboratorLeft(peer_id) => {
+    //                     this.collaborator_left(*peer_id, cx);
+    //                 }
+
+    //                 project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
+    //                     this.update_window_title(cx);
+    //                     this.serialize_workspace(cx);
+    //                 }
+
+    //                 project::Event::DisconnectedFromHost => {
+    //                     this.update_window_edited(cx);
+    //                     cx.blur();
+    //                 }
+
+    //                 project::Event::Closed => {
+    //                     cx.remove_window();
+    //                 }
+
+    //                 project::Event::DeletedEntry(entry_id) => {
+    //                     for pane in this.panes.iter() {
+    //                         pane.update(cx, |pane, cx| {
+    //                             pane.handle_deleted_project_item(*entry_id, cx)
+    //                         });
+    //                     }
+    //                 }
+
+    //                 project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+    //                     cx.add_view(|_| MessageNotification::new(message.clone()))
+    //                 }),
+
+    //                 _ => {}
+    //             }
+    //             cx.notify()
+    //         })
+    //         .detach();
+
+    //         let weak_handle = cx.weak_handle();
+    //         let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
+
+    //         let center_pane = cx.add_view(|cx| {
+    //             Pane::new(
+    //                 weak_handle.clone(),
+    //                 project.clone(),
+    //                 pane_history_timestamp.clone(),
+    //                 cx,
+    //             )
+    //         });
+    //         cx.subscribe(&center_pane, Self::handle_pane_event).detach();
+    //         cx.focus(&center_pane);
+    //         cx.emit(Event::PaneAdded(center_pane.clone()));
+
+    //         app_state.workspace_store.update(cx, |store, _| {
+    //             store.workspaces.insert(weak_handle.clone());
+    //         });
+
+    //         let mut current_user = app_state.user_store.read(cx).watch_current_user();
+    //         let mut connection_status = app_state.client.status();
+    //         let _observe_current_user = cx.spawn(|this, mut cx| async move {
+    //             current_user.recv().await;
+    //             connection_status.recv().await;
+    //             let mut stream =
+    //                 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
+
+    //             while stream.recv().await.is_some() {
+    //                 this.update(&mut cx, |_, cx| cx.notify())?;
+    //             }
+    //             anyhow::Ok(())
+    //         });
+
+    //         // All leader updates are enqueued and then processed in a single task, so
+    //         // that each asynchronous operation can be run in order.
+    //         let (leader_updates_tx, mut leader_updates_rx) =
+    //             mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
+    //         let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
+    //             while let Some((leader_id, update)) = leader_updates_rx.next().await {
+    //                 Self::process_leader_update(&this, leader_id, update, &mut cx)
+    //                     .await
+    //                     .log_err();
+    //             }
+
+    //             Ok(())
+    //         });
+
+    //         cx.emit_global(WorkspaceCreated(weak_handle.clone()));
+
+    //         let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
+    //         let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
+    //         let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
+    //         let left_dock_buttons =
+    //             cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
+    //         let bottom_dock_buttons =
+    //             cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
+    //         let right_dock_buttons =
+    //             cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
+    //         let status_bar = cx.add_view(|cx| {
+    //             let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
+    //             status_bar.add_left_item(left_dock_buttons, cx);
+    //             status_bar.add_right_item(right_dock_buttons, cx);
+    //             status_bar.add_right_item(bottom_dock_buttons, cx);
+    //             status_bar
+    //         });
+
+    //         cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
+    //             drag_and_drop.register_container(weak_handle.clone());
+    //         });
+
+    //         let mut active_call = None;
+    //         if cx.has_global::<ModelHandle<ActiveCall>>() {
+    //             let call = cx.global::<ModelHandle<ActiveCall>>().clone();
+    //             let mut subscriptions = Vec::new();
+    //             subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
+    //             active_call = Some((call, subscriptions));
+    //         }
+
+    //         let subscriptions = vec![
+    //             cx.observe_fullscreen(|_, _, cx| cx.notify()),
+    //             cx.observe_window_activation(Self::on_window_activation_changed),
+    //             cx.observe_window_bounds(move |_, mut bounds, display, cx| {
+    //                 // Transform fixed bounds to be stored in terms of the containing display
+    //                 if let WindowBounds::Fixed(mut window_bounds) = bounds {
+    //                     if let Some(screen) = cx.platform().screen_by_id(display) {
+    //                         let screen_bounds = screen.bounds();
+    //                         window_bounds
+    //                             .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
+    //                         window_bounds
+    //                             .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
+    //                         bounds = WindowBounds::Fixed(window_bounds);
+    //                     }
+    //                 }
+
+    //                 cx.background()
+    //                     .spawn(DB.set_window_bounds(workspace_id, bounds, display))
+    //                     .detach_and_log_err(cx);
+    //             }),
+    //             cx.observe(&left_dock, |this, _, cx| {
+    //                 this.serialize_workspace(cx);
+    //                 cx.notify();
+    //             }),
+    //             cx.observe(&bottom_dock, |this, _, cx| {
+    //                 this.serialize_workspace(cx);
+    //                 cx.notify();
+    //             }),
+    //             cx.observe(&right_dock, |this, _, cx| {
+    //                 this.serialize_workspace(cx);
+    //                 cx.notify();
+    //             }),
+    //         ];
+
+    //         cx.defer(|this, cx| this.update_window_title(cx));
+    //         Workspace {
+    //             weak_self: weak_handle.clone(),
+    //             modal: None,
+    //             zoomed: None,
+    //             zoomed_position: None,
+    //             center: PaneGroup::new(center_pane.clone()),
+    //             panes: vec![center_pane.clone()],
+    //             panes_by_item: Default::default(),
+    //             active_pane: center_pane.clone(),
+    //             last_active_center_pane: Some(center_pane.downgrade()),
+    //             last_active_view_id: None,
+    //             status_bar,
+    //             titlebar_item: None,
+    //             notifications: Default::default(),
+    //             left_dock,
+    //             bottom_dock,
+    //             right_dock,
+    //             project: project.clone(),
+    //             follower_states: Default::default(),
+    //             last_leaders_by_pane: Default::default(),
+    //             window_edited: false,
+    //             active_call,
+    //             database_id: workspace_id,
+    //             app_state,
+    //             _observe_current_user,
+    //             _apply_leader_updates,
+    //             _schedule_serialize: None,
+    //             leader_updates_tx,
+    //             subscriptions,
+    //             pane_history_timestamp,
+    //         }
+    //     }
+
+    //     fn new_local(
+    //         abs_paths: Vec<PathBuf>,
+    //         app_state: Arc<AppState>,
+    //         requesting_window: Option<WindowHandle<Workspace>>,
+    //         cx: &mut AppContext,
+    //     ) -> Task<(
+    //         WeakViewHandle<Workspace>,
+    //         Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+    //     )> {
+    //         let project_handle = Project::local(
+    //             app_state.client.clone(),
+    //             app_state.node_runtime.clone(),
+    //             app_state.user_store.clone(),
+    //             app_state.languages.clone(),
+    //             app_state.fs.clone(),
+    //             cx,
+    //         );
+
+    //         cx.spawn(|mut cx| async move {
+    //             let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+
+    //             let paths_to_open = Arc::new(abs_paths);
+
+    //             // Get project paths for all of the abs_paths
+    //             let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
+    //             let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
+    //                 Vec::with_capacity(paths_to_open.len());
+    //             for path in paths_to_open.iter().cloned() {
+    //                 if let Some((worktree, project_entry)) = cx
+    //                     .update(|cx| {
+    //                         Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
+    //                     })
+    //                     .await
+    //                     .log_err()
+    //                 {
+    //                     worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
+    //                     project_paths.push((path, Some(project_entry)));
+    //                 } else {
+    //                     project_paths.push((path, None));
+    //                 }
+    //             }
+
+    //             let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
+    //                 serialized_workspace.id
+    //             } else {
+    //                 DB.next_id().await.unwrap_or(0)
+    //             };
+
+    //             let window = if let Some(window) = requesting_window {
+    //                 window.replace_root(&mut cx, |cx| {
+    //                     Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+    //                 });
+    //                 window
+    //             } else {
+    //                 {
+    //                     let window_bounds_override = window_bounds_env_override(&cx);
+    //                     let (bounds, display) = if let Some(bounds) = window_bounds_override {
+    //                         (Some(bounds), None)
+    //                     } else {
+    //                         serialized_workspace
+    //                             .as_ref()
+    //                             .and_then(|serialized_workspace| {
+    //                                 let display = serialized_workspace.display?;
+    //                                 let mut bounds = serialized_workspace.bounds?;
+
+    //                                 // Stored bounds are relative to the containing display.
+    //                                 // So convert back to global coordinates if that screen still exists
+    //                                 if let WindowBounds::Fixed(mut window_bounds) = bounds {
+    //                                     if let Some(screen) = cx.platform().screen_by_id(display) {
+    //                                         let screen_bounds = screen.bounds();
+    //                                         window_bounds.set_origin_x(
+    //                                             window_bounds.origin_x() + screen_bounds.origin_x(),
+    //                                         );
+    //                                         window_bounds.set_origin_y(
+    //                                             window_bounds.origin_y() + screen_bounds.origin_y(),
+    //                                         );
+    //                                         bounds = WindowBounds::Fixed(window_bounds);
+    //                                     } else {
+    //                                         // Screen no longer exists. Return none here.
+    //                                         return None;
+    //                                     }
+    //                                 }
+
+    //                                 Some((bounds, display))
+    //                             })
+    //                             .unzip()
+    //                     };
+
+    //                     // Use the serialized workspace to construct the new window
+    //                     cx.add_window(
+    //                         (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
+    //                         |cx| {
+    //                             Workspace::new(
+    //                                 workspace_id,
+    //                                 project_handle.clone(),
+    //                                 app_state.clone(),
+    //                                 cx,
+    //                             )
+    //                         },
+    //                     )
+    //                 }
+    //             };
+
+    //             // We haven't yielded the main thread since obtaining the window handle,
+    //             // so the window exists.
+    //             let workspace = window.root(&cx).unwrap();
+
+    //             (app_state.initialize_workspace)(
+    //                 workspace.downgrade(),
+    //                 serialized_workspace.is_some(),
+    //                 app_state.clone(),
+    //                 cx.clone(),
+    //             )
+    //             .await
+    //             .log_err();
+
+    //             window.update(&mut cx, |cx| cx.activate_window());
+
+    //             let workspace = workspace.downgrade();
+    //             notify_if_database_failed(&workspace, &mut cx);
+    //             let opened_items = open_items(
+    //                 serialized_workspace,
+    //                 &workspace,
+    //                 project_paths,
+    //                 app_state,
+    //                 cx,
+    //             )
+    //             .await
+    //             .unwrap_or_default();
+
+    //             (workspace, opened_items)
+    //         })
+    //     }
+
+    //     pub fn weak_handle(&self) -> WeakViewHandle<Self> {
+    //         self.weak_self.clone()
+    //     }
+
+    //     pub fn left_dock(&self) -> &View<Dock> {
+    //         &self.left_dock
+    //     }
+
+    //     pub fn bottom_dock(&self) -> &View<Dock> {
+    //         &self.bottom_dock
+    //     }
+
+    //     pub fn right_dock(&self) -> &View<Dock> {
+    //         &self.right_dock
+    //     }
+
+    //     pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>)
+    //     where
+    //         T::Event: std::fmt::Debug,
+    //     {
+    //         self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
+    //     }
+
+    //     pub fn add_panel_with_extra_event_handler<T: Panel, F>(
+    //         &mut self,
+    //         panel: View<T>,
+    //         cx: &mut ViewContext<Self>,
+    //         handler: F,
+    //     ) where
+    //         T::Event: std::fmt::Debug,
+    //         F: Fn(&mut Self, &View<T>, &T::Event, &mut ViewContext<Self>) + 'static,
+    //     {
+    //         let dock = match panel.position(cx) {
+    //             DockPosition::Left => &self.left_dock,
+    //             DockPosition::Bottom => &self.bottom_dock,
+    //             DockPosition::Right => &self.right_dock,
+    //         };
+
+    //         self.subscriptions.push(cx.subscribe(&panel, {
+    //             let mut dock = dock.clone();
+    //             let mut prev_position = panel.position(cx);
+    //             move |this, panel, event, cx| {
+    //                 if T::should_change_position_on_event(event) {
+    //                     let new_position = panel.read(cx).position(cx);
+    //                     let mut was_visible = false;
+    //                     dock.update(cx, |dock, cx| {
+    //                         prev_position = new_position;
+
+    //                         was_visible = dock.is_open()
+    //                             && dock
+    //                                 .visible_panel()
+    //                                 .map_or(false, |active_panel| active_panel.id() == panel.id());
+    //                         dock.remove_panel(&panel, cx);
+    //                     });
+
+    //                     if panel.is_zoomed(cx) {
+    //                         this.zoomed_position = Some(new_position);
+    //                     }
+
+    //                     dock = match panel.read(cx).position(cx) {
+    //                         DockPosition::Left => &this.left_dock,
+    //                         DockPosition::Bottom => &this.bottom_dock,
+    //                         DockPosition::Right => &this.right_dock,
+    //                     }
+    //                     .clone();
+    //                     dock.update(cx, |dock, cx| {
+    //                         dock.add_panel(panel.clone(), cx);
+    //                         if was_visible {
+    //                             dock.set_open(true, cx);
+    //                             dock.activate_panel(dock.panels_len() - 1, cx);
+    //                         }
+    //                     });
+    //                 } else if T::should_zoom_in_on_event(event) {
+    //                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
+    //                     if !panel.has_focus(cx) {
+    //                         cx.focus(&panel);
+    //                     }
+    //                     this.zoomed = Some(panel.downgrade().into_any());
+    //                     this.zoomed_position = Some(panel.read(cx).position(cx));
+    //                 } else if T::should_zoom_out_on_event(event) {
+    //                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
+    //                     if this.zoomed_position == Some(prev_position) {
+    //                         this.zoomed = None;
+    //                         this.zoomed_position = None;
+    //                     }
+    //                     cx.notify();
+    //                 } else if T::is_focus_event(event) {
+    //                     let position = panel.read(cx).position(cx);
+    //                     this.dismiss_zoomed_items_to_reveal(Some(position), cx);
+    //                     if panel.is_zoomed(cx) {
+    //                         this.zoomed = Some(panel.downgrade().into_any());
+    //                         this.zoomed_position = Some(position);
+    //                     } else {
+    //                         this.zoomed = None;
+    //                         this.zoomed_position = None;
+    //                     }
+    //                     this.update_active_view_for_followers(cx);
+    //                     cx.notify();
+    //                 } else {
+    //                     handler(this, &panel, event, cx)
+    //                 }
+    //             }
+    //         }));
+
+    //         dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
+    //     }
+
+    //     pub fn status_bar(&self) -> &View<StatusBar> {
+    //         &self.status_bar
+    //     }
+
+    //     pub fn app_state(&self) -> &Arc<AppState> {
+    //         &self.app_state
+    //     }
+
+    //     pub fn user_store(&self) -> &ModelHandle<UserStore> {
+    //         &self.app_state.user_store
+    //     }
+
+    pub fn project(&self) -> &Handle<Project> {
+        &self.project
+    }
+
+    //     pub fn recent_navigation_history(
+    //         &self,
+    //         limit: Option<usize>,
+    //         cx: &AppContext,
+    //     ) -> Vec<(ProjectPath, Option<PathBuf>)> {
+    //         let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
+    //         let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
+    //         for pane in &self.panes {
+    //             let pane = pane.read(cx);
+    //             pane.nav_history()
+    //                 .for_each_entry(cx, |entry, (project_path, fs_path)| {
+    //                     if let Some(fs_path) = &fs_path {
+    //                         abs_paths_opened
+    //                             .entry(fs_path.clone())
+    //                             .or_default()
+    //                             .insert(project_path.clone());
+    //                     }
+    //                     let timestamp = entry.timestamp;
+    //                     match history.entry(project_path) {
+    //                         hash_map::Entry::Occupied(mut entry) => {
+    //                             let (_, old_timestamp) = entry.get();
+    //                             if &timestamp > old_timestamp {
+    //                                 entry.insert((fs_path, timestamp));
+    //                             }
+    //                         }
+    //                         hash_map::Entry::Vacant(entry) => {
+    //                             entry.insert((fs_path, timestamp));
+    //                         }
+    //                     }
+    //                 });
+    //         }
+
+    //         history
+    //             .into_iter()
+    //             .sorted_by_key(|(_, (_, timestamp))| *timestamp)
+    //             .map(|(project_path, (fs_path, _))| (project_path, fs_path))
+    //             .rev()
+    //             .filter(|(history_path, abs_path)| {
+    //                 let latest_project_path_opened = abs_path
+    //                     .as_ref()
+    //                     .and_then(|abs_path| abs_paths_opened.get(abs_path))
+    //                     .and_then(|project_paths| {
+    //                         project_paths
+    //                             .iter()
+    //                             .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
+    //                     });
+
+    //                 match latest_project_path_opened {
+    //                     Some(latest_project_path_opened) => latest_project_path_opened == history_path,
+    //                     None => true,
+    //                 }
+    //             })
+    //             .take(limit.unwrap_or(usize::MAX))
+    //             .collect()
+    //     }
+
+    //     fn navigate_history(
+    //         &mut self,
+    //         pane: WeakViewHandle<Pane>,
+    //         mode: NavigationMode,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Task<Result<()>> {
+    //         let to_load = if let Some(pane) = pane.upgrade(cx) {
+    //             cx.focus(&pane);
+
+    //             pane.update(cx, |pane, cx| {
+    //                 loop {
+    //                     // Retrieve the weak item handle from the history.
+    //                     let entry = pane.nav_history_mut().pop(mode, cx)?;
+
+    //                     // If the item is still present in this pane, then activate it.
+    //                     if let Some(index) = entry
+    //                         .item
+    //                         .upgrade(cx)
+    //                         .and_then(|v| pane.index_for_item(v.as_ref()))
+    //                     {
+    //                         let prev_active_item_index = pane.active_item_index();
+    //                         pane.nav_history_mut().set_mode(mode);
+    //                         pane.activate_item(index, true, true, cx);
+    //                         pane.nav_history_mut().set_mode(NavigationMode::Normal);
+
+    //                         let mut navigated = prev_active_item_index != pane.active_item_index();
+    //                         if let Some(data) = entry.data {
+    //                             navigated |= pane.active_item()?.navigate(data, cx);
+    //                         }
+
+    //                         if navigated {
+    //                             break None;
+    //                         }
+    //                     }
+    //                     // If the item is no longer present in this pane, then retrieve its
+    //                     // project path in order to reopen it.
+    //                     else {
+    //                         break pane
+    //                             .nav_history()
+    //                             .path_for_item(entry.item.id())
+    //                             .map(|(project_path, _)| (project_path, entry));
+    //                     }
+    //                 }
+    //             })
+    //         } else {
+    //             None
+    //         };
+
+    //         if let Some((project_path, entry)) = to_load {
+    //             // If the item was no longer present, then load it again from its previous path.
+    //             let task = self.load_path(project_path, cx);
+    //             cx.spawn(|workspace, mut cx| async move {
+    //                 let task = task.await;
+    //                 let mut navigated = false;
+    //                 if let Some((project_entry_id, build_item)) = task.log_err() {
+    //                     let prev_active_item_id = pane.update(&mut cx, |pane, _| {
+    //                         pane.nav_history_mut().set_mode(mode);
+    //                         pane.active_item().map(|p| p.id())
+    //                     })?;
+
+    //                     pane.update(&mut cx, |pane, cx| {
+    //                         let item = pane.open_item(project_entry_id, true, cx, build_item);
+    //                         navigated |= Some(item.id()) != prev_active_item_id;
+    //                         pane.nav_history_mut().set_mode(NavigationMode::Normal);
+    //                         if let Some(data) = entry.data {
+    //                             navigated |= item.navigate(data, cx);
+    //                         }
+    //                     })?;
+    //                 }
+
+    //                 if !navigated {
+    //                     workspace
+    //                         .update(&mut cx, |workspace, cx| {
+    //                             Self::navigate_history(workspace, pane, mode, cx)
+    //                         })?
+    //                         .await?;
+    //                 }
+
+    //                 Ok(())
+    //             })
+    //         } else {
+    //             Task::ready(Ok(()))
+    //         }
+    //     }
+
+    //     pub fn go_back(
+    //         &mut self,
+    //         pane: WeakViewHandle<Pane>,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Task<Result<()>> {
+    //         self.navigate_history(pane, NavigationMode::GoingBack, cx)
+    //     }
+
+    //     pub fn go_forward(
+    //         &mut self,
+    //         pane: WeakViewHandle<Pane>,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Task<Result<()>> {
+    //         self.navigate_history(pane, NavigationMode::GoingForward, cx)
+    //     }
+
+    //     pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
+    //         self.navigate_history(
+    //             self.active_pane().downgrade(),
+    //             NavigationMode::ReopeningClosedItem,
+    //             cx,
+    //         )
+    //     }
+
+    //     pub fn client(&self) -> &Client {
+    //         &self.app_state.client
+    //     }
+
+    //     pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    //         self.titlebar_item = Some(item);
+    //         cx.notify();
+    //     }
+
+    //     pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
+    //         self.titlebar_item.clone()
+    //     }
+
+    //     /// Call the given callback with a workspace whose project is local.
+    //     ///
+    //     /// If the given workspace has a local project, then it will be passed
+    //     /// to the callback. Otherwise, a new empty window will be created.
+    //     pub fn with_local_workspace<T, F>(
+    //         &mut self,
+    //         cx: &mut ViewContext<Self>,
+    //         callback: F,
+    //     ) -> Task<Result<T>>
+    //     where
+    //         T: 'static,
+    //         F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+    //     {
+    //         if self.project.read(cx).is_local() {
+    //             Task::Ready(Some(Ok(callback(self, cx))))
+    //         } else {
+    //             let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
+    //             cx.spawn(|_vh, mut cx| async move {
+    //                 let (workspace, _) = task.await;
+    //                 workspace.update(&mut cx, callback)
+    //             })
+    //         }
+    //     }
+
+    //     pub fn worktrees<'a>(
+    //         &self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
+    //         self.project.read(cx).worktrees(cx)
+    //     }
+
+    //     pub fn visible_worktrees<'a>(
+    //         &self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
+    //         self.project.read(cx).visible_worktrees(cx)
+    //     }
+
+    //     pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
+    //         let futures = self
+    //             .worktrees(cx)
+    //             .filter_map(|worktree| worktree.read(cx).as_local())
+    //             .map(|worktree| worktree.scan_complete())
+    //             .collect::<Vec<_>>();
+    //         async move {
+    //             for future in futures {
+    //                 future.await;
+    //             }
+    //         }
+    //     }
+
+    //     pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
+    //         cx.spawn(|mut cx| async move {
+    //             let window = cx
+    //                 .windows()
+    //                 .into_iter()
+    //                 .find(|window| window.is_active(&cx).unwrap_or(false));
+    //             if let Some(window) = window {
+    //                 //This can only get called when the window's project connection has been lost
+    //                 //so we don't need to prompt the user for anything and instead just close the window
+    //                 window.remove(&mut cx);
+    //             }
+    //         })
+    //         .detach();
+    //     }
+
+    //     pub fn close(
+    //         &mut self,
+    //         _: &CloseWindow,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let window = cx.window();
+    //         let prepare = self.prepare_to_close(false, cx);
+    //         Some(cx.spawn(|_, mut cx| async move {
+    //             if prepare.await? {
+    //                 window.remove(&mut cx);
+    //             }
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn prepare_to_close(
+    //         &mut self,
+    //         quitting: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<bool>> {
+    //         let active_call = self.active_call().cloned();
+    //         let window = cx.window();
+
+    //         cx.spawn(|this, mut cx| async move {
+    //             let workspace_count = cx
+    //                 .windows()
+    //                 .into_iter()
+    //                 .filter(|window| window.root_is::<Workspace>())
+    //                 .count();
+
+    //             if let Some(active_call) = active_call {
+    //                 if !quitting
+    //                     && workspace_count == 1
+    //                     && active_call.read_with(&cx, |call, _| call.room().is_some())
+    //                 {
+    //                     let answer = window.prompt(
+    //                         PromptLevel::Warning,
+    //                         "Do you want to leave the current call?",
+    //                         &["Close window and hang up", "Cancel"],
+    //                         &mut cx,
+    //                     );
+
+    //                     if let Some(mut answer) = answer {
+    //                         if answer.next().await == Some(1) {
+    //                             return anyhow::Ok(false);
+    //                         } else {
+    //                             active_call
+    //                                 .update(&mut cx, |call, cx| call.hang_up(cx))
+    //                                 .await
+    //                                 .log_err();
+    //                         }
+    //                     }
+    //                 }
+    //             }
+
+    //             Ok(this
+    //                 .update(&mut cx, |this, cx| {
+    //                     this.save_all_internal(SaveIntent::Close, cx)
+    //                 })?
+    //                 .await?)
+    //         })
+    //     }
+
+    //     fn save_all(
+    //         &mut self,
+    //         action: &SaveAll,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let save_all =
+    //             self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx);
+    //         Some(cx.foreground().spawn(async move {
+    //             save_all.await?;
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     fn save_all_internal(
+    //         &mut self,
+    //         mut save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<bool>> {
+    //         if self.project.read(cx).is_read_only() {
+    //             return Task::ready(Ok(true));
+    //         }
+    //         let dirty_items = self
+    //             .panes
+    //             .iter()
+    //             .flat_map(|pane| {
+    //                 pane.read(cx).items().filter_map(|item| {
+    //                     if item.is_dirty(cx) {
+    //                         Some((pane.downgrade(), item.boxed_clone()))
+    //                     } else {
+    //                         None
+    //                     }
+    //                 })
+    //             })
+    //             .collect::<Vec<_>>();
+
+    //         let project = self.project.clone();
+    //         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 prompt = Pane::file_names_for_prompt(
+    //                         &mut dirty_items.iter().map(|(_, handle)| handle),
+    //                         dirty_items.len(),
+    //                         cx,
+    //                     );
+    //                     cx.prompt(
+    //                         PromptLevel::Warning,
+    //                         &prompt,
+    //                         &["Save all", "Discard all", "Cancel"],
+    //                     )
+    //                 })?;
+    //                 match answer.next().await {
+    //                     Some(0) => save_intent = SaveIntent::SaveAll,
+    //                     Some(1) => save_intent = SaveIntent::Skip,
+    //                     _ => {}
+    //                 }
+    //             }
+    //             for (pane, item) in dirty_items {
+    //                 let (singleton, project_entry_ids) =
+    //                     cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
+    //                 if singleton || !project_entry_ids.is_empty() {
+    //                     if let Some(ix) =
+    //                         pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
+    //                     {
+    //                         if !Pane::save_item(
+    //                             project.clone(),
+    //                             &pane,
+    //                             ix,
+    //                             &*item,
+    //                             save_intent,
+    //                             &mut cx,
+    //                         )
+    //                         .await?
+    //                         {
+    //                             return Ok(false);
+    //                         }
+    //                     }
+    //                 }
+    //             }
+    //             Ok(true)
+    //         })
+    //     }
+
+    //     pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
+    //         let mut paths = cx.prompt_for_paths(PathPromptOptions {
+    //             files: true,
+    //             directories: true,
+    //             multiple: true,
+    //         });
+
+    //         Some(cx.spawn(|this, mut cx| async move {
+    //             if let Some(paths) = paths.recv().await.flatten() {
+    //                 if let Some(task) = this
+    //                     .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
+    //                     .log_err()
+    //                 {
+    //                     task.await?
+    //                 }
+    //             }
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn open_workspace_for_paths(
+    //         &mut self,
+    //         paths: Vec<PathBuf>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let window = cx.window().downcast::<Self>();
+    //         let is_remote = self.project.read(cx).is_remote();
+    //         let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
+    //         let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
+    //         let close_task = if is_remote || has_worktree || has_dirty_items {
+    //             None
+    //         } else {
+    //             Some(self.prepare_to_close(false, cx))
+    //         };
+    //         let app_state = self.app_state.clone();
+
+    //         cx.spawn(|_, mut cx| async move {
+    //             let window_to_replace = if let Some(close_task) = close_task {
+    //                 if !close_task.await? {
+    //                     return Ok(());
+    //                 }
+    //                 window
+    //             } else {
+    //                 None
+    //             };
+    //             cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
+    //                 .await?;
+    //             Ok(())
+    //         })
+    //     }
+
+    #[allow(clippy::type_complexity)]
+    pub fn open_paths(
+        &mut self,
+        mut abs_paths: Vec<PathBuf>,
+        visible: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
+        log::info!("open paths {:?}", abs_paths);
+
+        let fs = self.app_state.fs.clone();
+
+        // Sort the paths to ensure we add worktrees for parents before their children.
+        abs_paths.sort_unstable();
+        cx.spawn(|this, mut cx| async move {
+            let mut tasks = Vec::with_capacity(abs_paths.len());
+            for abs_path in &abs_paths {
+                let project_path = match this
+                    .update(&mut cx, |this, cx| {
+                        Workspace::project_path_for_path(
+                            this.project.clone(),
+                            abs_path,
+                            visible,
+                            cx,
+                        )
+                    })
+                    .log_err()
+                {
+                    Some(project_path) => project_path.await.log_err(),
+                    None => None,
+                };
+
+                let this = this.clone();
+                let task = cx.spawn(|mut cx| {
+                    let fs = fs.clone();
+                    let abs_path = abs_path.clone();
+                    async move {
+                        let (worktree, project_path) = project_path?;
+                        if fs.is_file(&abs_path).await {
+                            Some(
+                                this.update(&mut cx, |this, cx| {
+                                    this.open_path(project_path, None, true, cx)
+                                })
+                                .log_err()?
+                                .await,
+                            )
+                        } else {
+                            this.update(&mut cx, |workspace, cx| {
+                                let worktree = worktree.read(cx);
+                                let worktree_abs_path = worktree.abs_path();
+                                let entry_id = if abs_path == worktree_abs_path.as_ref() {
+                                    worktree.root_entry()
+                                } else {
+                                    abs_path
+                                        .strip_prefix(worktree_abs_path.as_ref())
+                                        .ok()
+                                        .and_then(|relative_path| {
+                                            worktree.entry_for_path(relative_path)
+                                        })
+                                }
+                                .map(|entry| entry.id);
+                                if let Some(entry_id) = entry_id {
+                                    workspace.project.update(cx, |_, cx| {
+                                        cx.emit(project2::Event::ActiveEntryChanged(Some(
+                                            entry_id,
+                                        )));
+                                    })
+                                }
+                            })
+                            .log_err()?;
+                            None
+                        }
+                    }
+                });
+                tasks.push(task);
+            }
+
+            futures::future::join_all(tasks).await
+        })
+    }
+
+    //     fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
+    //         let mut paths = cx.prompt_for_paths(PathPromptOptions {
+    //             files: false,
+    //             directories: true,
+    //             multiple: true,
+    //         });
+    //         cx.spawn(|this, mut cx| async move {
+    //             if let Some(paths) = paths.recv().await.flatten() {
+    //                 let results = this
+    //                     .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
+    //                     .await;
+    //                 for result in results.into_iter().flatten() {
+    //                     result.log_err();
+    //                 }
+    //             }
+    //             anyhow::Ok(())
+    //         })
+    //         .detach_and_log_err(cx);
+    //     }
+
+    fn project_path_for_path(
+        project: Handle<Project>,
+        abs_path: &Path,
+        visible: bool,
+        cx: &mut AppContext,
+    ) -> Task<Result<(Handle<Worktree>, ProjectPath)>> {
+        let entry = project.update(cx, |project, cx| {
+            project.find_or_create_local_worktree(abs_path, visible, cx)
+        });
+        cx.spawn(|cx| async move {
+            let (worktree, path) = entry.await?;
+            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
+            Ok((
+                worktree,
+                ProjectPath {
+                    worktree_id,
+                    path: path.into(),
+                },
+            ))
+        })
+    }
+
+    //     /// Returns the modal that was toggled closed if it was open.
+    //     pub fn toggle_modal<V, F>(
+    //         &mut self,
+    //         cx: &mut ViewContext<Self>,
+    //         add_view: F,
+    //     ) -> Option<View<V>>
+    //     where
+    //         V: 'static + Modal,
+    //         F: FnOnce(&mut Self, &mut ViewContext<Self>) -> View<V>,
+    //     {
+    //         cx.notify();
+    //         // Whatever modal was visible is getting clobbered. If its the same type as V, then return
+    //         // it. Otherwise, create a new modal and set it as active.
+    //         if let Some(already_open_modal) = self
+    //             .dismiss_modal(cx)
+    //             .and_then(|modal| modal.downcast::<V>())
+    //         {
+    //             cx.focus_self();
+    //             Some(already_open_modal)
+    //         } else {
+    //             let modal = add_view(self, cx);
+    //             cx.subscribe(&modal, |this, _, event, cx| {
+    //                 if V::dismiss_on_event(event) {
+    //                     this.dismiss_modal(cx);
+    //                 }
+    //             })
+    //             .detach();
+    //             let previously_focused_view_id = cx.focused_view_id();
+    //             cx.focus(&modal);
+    //             self.modal = Some(ActiveModal {
+    //                 view: Box::new(modal),
+    //                 previously_focused_view_id,
+    //             });
+    //             None
+    //         }
+    //     }
+
+    //     pub fn modal<V: 'static + View>(&self) -> Option<View<V>> {
+    //         self.modal
+    //             .as_ref()
+    //             .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
+    //     }
+
+    //     pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
+    //         if let Some(modal) = self.modal.take() {
+    //             if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
+    //                 if modal.view.has_focus(cx) {
+    //                     cx.window_context().focus(Some(previously_focused_view_id));
+    //                 }
+    //             }
+    //             cx.notify();
+    //             Some(modal.view.as_any().clone())
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     pub fn items<'a>(
+    //         &'a self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
+    //         self.panes.iter().flat_map(|pane| pane.read(cx).items())
+    //     }
+
+    //     pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
+    //         self.items_of_type(cx).max_by_key(|item| item.id())
+    //     }
+
+    //     pub fn items_of_type<'a, T: Item>(
+    //         &'a self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = View<T>> {
+    //         self.panes
+    //             .iter()
+    //             .flat_map(|pane| pane.read(cx).items_of_type())
+    //     }
+
+    //     pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+    //         self.active_pane().read(cx).active_item()
+    //     }
+
+    //     fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
+    //         self.active_item(cx).and_then(|item| item.project_path(cx))
+    //     }
+
+    //     pub fn save_active_item(
+    //         &mut self,
+    //         save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let project = self.project.clone();
+    //         let pane = self.active_pane();
+    //         let item_ix = pane.read(cx).active_item_index();
+    //         let item = pane.read(cx).active_item();
+    //         let pane = pane.downgrade();
+
+    //         cx.spawn(|_, mut cx| async move {
+    //             if let Some(item) = item {
+    //                 Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
+    //                     .await
+    //                     .map(|_| ())
+    //             } else {
+    //                 Ok(())
+    //             }
+    //         })
+    //     }
+
+    //     pub fn close_inactive_items_and_panes(
+    //         &mut self,
+    //         _: &CloseInactiveTabsAndPanes,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         self.close_all_internal(true, SaveIntent::Close, cx)
+    //     }
+
+    //     pub fn close_all_items_and_panes(
+    //         &mut self,
+    //         action: &CloseAllItemsAndPanes,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
+    //     }
+
+    //     fn close_all_internal(
+    //         &mut self,
+    //         retain_active_pane: bool,
+    //         save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let current_pane = self.active_pane();
+
+    //         let mut tasks = Vec::new();
+
+    //         if retain_active_pane {
+    //             if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
+    //                 pane.close_inactive_items(&CloseInactiveItems, cx)
+    //             }) {
+    //                 tasks.push(current_pane_close);
+    //             };
+    //         }
+
+    //         for pane in self.panes() {
+    //             if retain_active_pane && pane.id() == current_pane.id() {
+    //                 continue;
+    //             }
+
+    //             if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
+    //                 pane.close_all_items(
+    //                     &CloseAllItems {
+    //                         save_intent: Some(save_intent),
+    //                     },
+    //                     cx,
+    //                 )
+    //             }) {
+    //                 tasks.push(close_pane_items)
+    //             }
+    //         }
+
+    //         if tasks.is_empty() {
+    //             None
+    //         } else {
+    //             Some(cx.spawn(|_, _| async move {
+    //                 for task in tasks {
+    //                     task.await?
+    //                 }
+    //                 Ok(())
+    //             }))
+    //         }
+    //     }
+
+    //     pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
+    //         let dock = match dock_side {
+    //             DockPosition::Left => &self.left_dock,
+    //             DockPosition::Bottom => &self.bottom_dock,
+    //             DockPosition::Right => &self.right_dock,
+    //         };
+    //         let mut focus_center = false;
+    //         let mut reveal_dock = false;
+    //         dock.update(cx, |dock, cx| {
+    //             let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
+    //             let was_visible = dock.is_open() && !other_is_zoomed;
+    //             dock.set_open(!was_visible, cx);
+
+    //             if let Some(active_panel) = dock.active_panel() {
+    //                 if was_visible {
+    //                     if active_panel.has_focus(cx) {
+    //                         focus_center = true;
+    //                     }
+    //                 } else {
+    //                     cx.focus(active_panel.as_any());
+    //                     reveal_dock = true;
+    //                 }
+    //             }
+    //         });
+
+    //         if reveal_dock {
+    //             self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
+    //         }
+
+    //         if focus_center {
+    //             cx.focus_self();
+    //         }
+
+    //         cx.notify();
+    //         self.serialize_workspace(cx);
+    //     }
+
+    //     pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
+    //         let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
+
+    //         for dock in docks {
+    //             dock.update(cx, |dock, cx| {
+    //                 dock.set_open(false, cx);
+    //             });
+    //         }
+
+    //         cx.focus_self();
+    //         cx.notify();
+    //         self.serialize_workspace(cx);
+    //     }
+
+    //     /// Transfer focus to the panel of the given type.
+    //     pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
+    //         self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
+    //             .as_any()
+    //             .clone()
+    //             .downcast()
+    //     }
+
+    //     /// Focus the panel of the given type if it isn't already focused. If it is
+    //     /// already focused, then transfer focus back to the workspace center.
+    //     pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
+    //     }
+
+    //     /// Focus or unfocus the given panel type, depending on the given callback.
+    //     fn focus_or_unfocus_panel<T: Panel>(
+    //         &mut self,
+    //         cx: &mut ViewContext<Self>,
+    //         should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
+    //     ) -> Option<Rc<dyn PanelHandle>> {
+    //         for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
+    //             if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
+    //                 let mut focus_center = false;
+    //                 let mut reveal_dock = false;
+    //                 let panel = dock.update(cx, |dock, cx| {
+    //                     dock.activate_panel(panel_index, cx);
+
+    //                     let panel = dock.active_panel().cloned();
+    //                     if let Some(panel) = panel.as_ref() {
+    //                         if should_focus(&**panel, cx) {
+    //                             dock.set_open(true, cx);
+    //                             cx.focus(panel.as_any());
+    //                             reveal_dock = true;
+    //                         } else {
+    //                             // if panel.is_zoomed(cx) {
+    //                             //     dock.set_open(false, cx);
+    //                             // }
+    //                             focus_center = true;
+    //                         }
+    //                     }
+    //                     panel
+    //                 });
+
+    //                 if focus_center {
+    //                     cx.focus_self();
+    //                 }
+
+    //                 self.serialize_workspace(cx);
+    //                 cx.notify();
+    //                 return panel;
+    //             }
+    //         }
+    //         None
+    //     }
+
+    //     pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
+    //         for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
+    //             let dock = dock.read(cx);
+    //             if let Some(panel) = dock.panel::<T>() {
+    //                 return Some(panel);
+    //             }
+    //         }
+    //         None
+    //     }
+
+    //     fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
+    //         for pane in &self.panes {
+    //             pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+    //         }
+
+    //         self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+    //         self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+    //         self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+    //         self.zoomed = None;
+    //         self.zoomed_position = None;
+
+    //         cx.notify();
+    //     }
+
+    //     #[cfg(any(test, feature = "test-support"))]
+    //     pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
+    //         self.zoomed.and_then(|view| view.upgrade(cx))
+    //     }
+
+    //     fn dismiss_zoomed_items_to_reveal(
+    //         &mut self,
+    //         dock_to_reveal: Option<DockPosition>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         // If a center pane is zoomed, unzoom it.
+    //         for pane in &self.panes {
+    //             if pane != &self.active_pane || dock_to_reveal.is_some() {
+    //                 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+    //             }
+    //         }
+
+    //         // If another dock is zoomed, hide it.
+    //         let mut focus_center = false;
+    //         for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
+    //             dock.update(cx, |dock, cx| {
+    //                 if Some(dock.position()) != dock_to_reveal {
+    //                     if let Some(panel) = dock.active_panel() {
+    //                         if panel.is_zoomed(cx) {
+    //                             focus_center |= panel.has_focus(cx);
+    //                             dock.set_open(false, cx);
+    //                         }
+    //                     }
+    //                 }
+    //             });
+    //         }
+
+    //         if focus_center {
+    //             cx.focus_self();
+    //         }
+
+    //         if self.zoomed_position != dock_to_reveal {
+    //             self.zoomed = None;
+    //             self.zoomed_position = None;
+    //         }
+
+    //         cx.notify();
+    //     }
+
+    //     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
+    //         let pane = cx.add_view(|cx| {
+    //             Pane::new(
+    //                 self.weak_handle(),
+    //                 self.project.clone(),
+    //                 self.pane_history_timestamp.clone(),
+    //                 cx,
+    //             )
+    //         });
+    //         cx.subscribe(&pane, Self::handle_pane_event).detach();
+    //         self.panes.push(pane.clone());
+    //         cx.focus(&pane);
+    //         cx.emit(Event::PaneAdded(pane.clone()));
+    //         pane
+    //     }
+
+    //     pub fn add_item_to_center(
+    //         &mut self,
+    //         item: Box<dyn ItemHandle>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> bool {
+    //         if let Some(center_pane) = self.last_active_center_pane.clone() {
+    //             if let Some(center_pane) = center_pane.upgrade(cx) {
+    //                 center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
+    //                 true
+    //             } else {
+    //                 false
+    //             }
+    //         } else {
+    //             false
+    //         }
+    //     }
+
+    //     pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+    //         self.active_pane
+    //             .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
+    //     }
+
+    //     pub fn split_item(
+    //         &mut self,
+    //         split_direction: SplitDirection,
+    //         item: Box<dyn ItemHandle>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
+    //         new_pane.update(cx, move |new_pane, cx| {
+    //             new_pane.add_item(item, true, true, None, cx)
+    //         })
+    //     }
+
+    //     pub fn open_abs_path(
+    //         &mut self,
+    //         abs_path: PathBuf,
+    //         visible: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
+    //         cx.spawn(|workspace, mut cx| async move {
+    //             let open_paths_task_result = workspace
+    //                 .update(&mut cx, |workspace, cx| {
+    //                     workspace.open_paths(vec![abs_path.clone()], visible, cx)
+    //                 })
+    //                 .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
+    //                 .await;
+    //             anyhow::ensure!(
+    //                 open_paths_task_result.len() == 1,
+    //                 "open abs path {abs_path:?} task returned incorrect number of results"
+    //             );
+    //             match open_paths_task_result
+    //                 .into_iter()
+    //                 .next()
+    //                 .expect("ensured single task result")
+    //             {
+    //                 Some(open_result) => {
+    //                     open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
+    //                 }
+    //                 None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
+    //             }
+    //         })
+    //     }
+
+    //     pub fn split_abs_path(
+    //         &mut self,
+    //         abs_path: PathBuf,
+    //         visible: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
+    //         let project_path_task =
+    //             Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
+    //         cx.spawn(|this, mut cx| async move {
+    //             let (_, path) = project_path_task.await?;
+    //             this.update(&mut cx, |this, cx| this.split_path(path, cx))?
+    //                 .await
+    //         })
+    //     }
+
+    pub fn open_path(
+        &mut self,
+        path: impl Into<ProjectPath>,
+        pane: Option<WeakView<Pane>>,
+        focus_item: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
+        let pane = pane.unwrap_or_else(|| {
+            self.last_active_center_pane.clone().unwrap_or_else(|| {
+                self.panes
+                    .first()
+                    .expect("There must be an active pane")
+                    .downgrade()
+            })
+        });
+
+        let task = self.load_path(path.into(), cx);
+        cx.spawn(|_, mut cx| async move {
+            let (project_entry_id, build_item) = task.await?;
+            pane.update(&mut cx, |pane, cx| {
+                pane.open_item(project_entry_id, focus_item, cx, build_item)
+            })
+        })
+    }
+
+    //     pub fn split_path(
+    //         &mut self,
+    //         path: impl Into<ProjectPath>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
+    //         let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
+    //             self.panes
+    //                 .first()
+    //                 .expect("There must be an active pane")
+    //                 .downgrade()
+    //         });
+
+    //         if let Member::Pane(center_pane) = &self.center.root {
+    //             if center_pane.read(cx).items_len() == 0 {
+    //                 return self.open_path(path, Some(pane), true, cx);
+    //             }
+    //         }
+
+    //         let task = self.load_path(path.into(), cx);
+    //         cx.spawn(|this, mut cx| async move {
+    //             let (project_entry_id, build_item) = task.await?;
+    //             this.update(&mut cx, move |this, cx| -> Option<_> {
+    //                 let pane = pane.upgrade(cx)?;
+    //                 let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
+    //                 new_pane.update(cx, |new_pane, cx| {
+    //                     Some(new_pane.open_item(project_entry_id, true, cx, build_item))
+    //                 })
+    //             })
+    //             .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
+    //         })
+    //     }
+
+    pub(crate) fn load_path(
+        &mut self,
+        path: ProjectPath,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<
+        Result<(
+            ProjectEntryId,
+            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
+        )>,
+    > {
+        let project = self.project().clone();
+        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
+        cx.spawn(|_, mut cx| async move {
+            let (project_entry_id, project_item) = project_item.await?;
+            let build_item = cx.update(|cx| {
+                cx.default_global::<ProjectItemBuilders>()
+                    .get(&project_item.model_type())
+                    .ok_or_else(|| anyhow!("no item builder for project item"))
+                    .cloned()
+            })?;
+            let build_item =
+                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
+            Ok((project_entry_id, build_item))
+        })
+    }
+
+    //     pub fn open_project_item<T>(
+    //         &mut self,
+    //         project_item: ModelHandle<T::Item>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> View<T>
+    //     where
+    //         T: ProjectItem,
+    //     {
+    //         use project::Item as _;
+
+    //         let entry_id = project_item.read(cx).entry_id(cx);
+    //         if let Some(item) = entry_id
+    //             .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
+    //             .and_then(|item| item.downcast())
+    //         {
+    //             self.activate_item(&item, cx);
+    //             return item;
+    //         }
+
+    //         let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
+    //         self.add_item(Box::new(item.clone()), cx);
+    //         item
+    //     }
+
+    //     pub fn split_project_item<T>(
+    //         &mut self,
+    //         project_item: ModelHandle<T::Item>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> View<T>
+    //     where
+    //         T: ProjectItem,
+    //     {
+    //         use project::Item as _;
+
+    //         let entry_id = project_item.read(cx).entry_id(cx);
+    //         if let Some(item) = entry_id
+    //             .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
+    //             .and_then(|item| item.downcast())
+    //         {
+    //             self.activate_item(&item, cx);
+    //             return item;
+    //         }
+
+    //         let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
+    //         self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
+    //         item
+    //     }
+
+    //     pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+    //         if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
+    //             self.active_pane.update(cx, |pane, cx| {
+    //                 pane.add_item(Box::new(shared_screen), false, true, None, cx)
+    //             });
+    //         }
+    //     }
+
+    //     pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
+    //         let result = self.panes.iter().find_map(|pane| {
+    //             pane.read(cx)
+    //                 .index_for_item(item)
+    //                 .map(|ix| (pane.clone(), ix))
+    //         });
+    //         if let Some((pane, ix)) = result {
+    //             pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
+    //             true
+    //         } else {
+    //             false
+    //         }
+    //     }
+
+    //     fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
+    //         let panes = self.center.panes();
+    //         if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
+    //             cx.focus(&pane);
+    //         } else {
+    //             self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
+    //         }
+    //     }
+
+    //     pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
+    //         let panes = self.center.panes();
+    //         if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
+    //             let next_ix = (ix + 1) % panes.len();
+    //             let next_pane = panes[next_ix].clone();
+    //             cx.focus(&next_pane);
+    //         }
+    //     }
+
+    //     pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
+    //         let panes = self.center.panes();
+    //         if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
+    //             let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
+    //             let prev_pane = panes[prev_ix].clone();
+    //             cx.focus(&prev_pane);
+    //         }
+    //     }
+
+    //     pub fn activate_pane_in_direction(
+    //         &mut self,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         if let Some(pane) = self.find_pane_in_direction(direction, cx) {
+    //             cx.focus(pane);
+    //         }
+    //     }
+
+    //     pub fn swap_pane_in_direction(
+    //         &mut self,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         if let Some(to) = self
+    //             .find_pane_in_direction(direction, cx)
+    //             .map(|pane| pane.clone())
+    //         {
+    //             self.center.swap(&self.active_pane.clone(), &to);
+    //             cx.notify();
+    //         }
+    //     }
+
+    //     fn find_pane_in_direction(
+    //         &mut self,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<&View<Pane>> {
+    //         let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
+    //             return None;
+    //         };
+    //         let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
+    //         let center = match cursor {
+    //             Some(cursor) if bounding_box.contains_point(cursor) => cursor,
+    //             _ => bounding_box.center(),
+    //         };
+
+    //         let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
+
+    //         let target = match direction {
+    //             SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
+    //             SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
+    //             SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
+    //             SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
+    //         };
+    //         self.center.pane_at_pixel_position(target)
+    //     }
+
+    //     fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
+    //         if self.active_pane != pane {
+    //             self.active_pane = pane.clone();
+    //             self.status_bar.update(cx, |status_bar, cx| {
+    //                 status_bar.set_active_pane(&self.active_pane, cx);
+    //             });
+    //             self.active_item_path_changed(cx);
+    //             self.last_active_center_pane = Some(pane.downgrade());
+    //         }
+
+    //         self.dismiss_zoomed_items_to_reveal(None, cx);
+    //         if pane.read(cx).is_zoomed() {
+    //             self.zoomed = Some(pane.downgrade().into_any());
+    //         } else {
+    //             self.zoomed = None;
+    //         }
+    //         self.zoomed_position = None;
+    //         self.update_active_view_for_followers(cx);
+
+    //         cx.notify();
+    //     }
+
+    //     fn handle_pane_event(
+    //         &mut self,
+    //         pane: View<Pane>,
+    //         event: &pane::Event,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         match event {
+    //             pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
+    //             pane::Event::Split(direction) => {
+    //                 self.split_and_clone(pane, *direction, cx);
+    //             }
+    //             pane::Event::Remove => self.remove_pane(pane, cx),
+    //             pane::Event::ActivateItem { local } => {
+    //                 if *local {
+    //                     self.unfollow(&pane, cx);
+    //                 }
+    //                 if &pane == self.active_pane() {
+    //                     self.active_item_path_changed(cx);
+    //                 }
+    //             }
+    //             pane::Event::ChangeItemTitle => {
+    //                 if pane == self.active_pane {
+    //                     self.active_item_path_changed(cx);
+    //                 }
+    //                 self.update_window_edited(cx);
+    //             }
+    //             pane::Event::RemoveItem { item_id } => {
+    //                 self.update_window_edited(cx);
+    //                 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
+    //                     if entry.get().id() == pane.id() {
+    //                         entry.remove();
+    //                     }
+    //                 }
+    //             }
+    //             pane::Event::Focus => {
+    //                 self.handle_pane_focused(pane.clone(), cx);
+    //             }
+    //             pane::Event::ZoomIn => {
+    //                 if pane == self.active_pane {
+    //                     pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
+    //                     if pane.read(cx).has_focus() {
+    //                         self.zoomed = Some(pane.downgrade().into_any());
+    //                         self.zoomed_position = None;
+    //                     }
+    //                     cx.notify();
+    //                 }
+    //             }
+    //             pane::Event::ZoomOut => {
+    //                 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+    //                 if self.zoomed_position.is_none() {
+    //                     self.zoomed = None;
+    //                 }
+    //                 cx.notify();
+    //             }
+    //         }
+
+    //         self.serialize_workspace(cx);
+    //     }
+
+    //     pub fn split_pane(
+    //         &mut self,
+    //         pane_to_split: View<Pane>,
+    //         split_direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> View<Pane> {
+    //         let new_pane = self.add_pane(cx);
+    //         self.center
+    //             .split(&pane_to_split, &new_pane, split_direction)
+    //             .unwrap();
+    //         cx.notify();
+    //         new_pane
+    //     }
+
+    //     pub fn split_and_clone(
+    //         &mut self,
+    //         pane: View<Pane>,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<View<Pane>> {
+    //         let item = pane.read(cx).active_item()?;
+    //         let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
+    //             let new_pane = self.add_pane(cx);
+    //             new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
+    //             self.center.split(&pane, &new_pane, direction).unwrap();
+    //             Some(new_pane)
+    //         } else {
+    //             None
+    //         };
+    //         cx.notify();
+    //         maybe_pane_handle
+    //     }
+
+    //     pub fn split_pane_with_item(
+    //         &mut self,
+    //         pane_to_split: WeakViewHandle<Pane>,
+    //         split_direction: SplitDirection,
+    //         from: WeakViewHandle<Pane>,
+    //         item_id_to_move: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
+    //             return;
+    //         };
+    //         let Some(from) = from.upgrade(cx) else {
+    //             return;
+    //         };
+
+    //         let new_pane = self.add_pane(cx);
+    //         self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
+    //         self.center
+    //             .split(&pane_to_split, &new_pane, split_direction)
+    //             .unwrap();
+    //         cx.notify();
+    //     }
+
+    //     pub fn split_pane_with_project_entry(
+    //         &mut self,
+    //         pane_to_split: WeakViewHandle<Pane>,
+    //         split_direction: SplitDirection,
+    //         project_entry: ProjectEntryId,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let pane_to_split = pane_to_split.upgrade(cx)?;
+    //         let new_pane = self.add_pane(cx);
+    //         self.center
+    //             .split(&pane_to_split, &new_pane, split_direction)
+    //             .unwrap();
+
+    //         let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
+    //         let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
+    //         Some(cx.foreground().spawn(async move {
+    //             task.await?;
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn move_item(
+    //         &mut self,
+    //         source: View<Pane>,
+    //         destination: View<Pane>,
+    //         item_id_to_move: usize,
+    //         destination_index: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let item_to_move = source
+    //             .read(cx)
+    //             .items()
+    //             .enumerate()
+    //             .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
+
+    //         if item_to_move.is_none() {
+    //             log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
+    //             return;
+    //         }
+    //         let (item_ix, item_handle) = item_to_move.unwrap();
+    //         let item_handle = item_handle.clone();
+
+    //         if source != destination {
+    //             // Close item from previous pane
+    //             source.update(cx, |source, cx| {
+    //                 source.remove_item(item_ix, false, cx);
+    //             });
+    //         }
+
+    //         // This automatically removes duplicate items in the pane
+    //         destination.update(cx, |destination, cx| {
+    //             destination.add_item(item_handle, true, true, Some(destination_index), cx);
+    //             cx.focus_self();
+    //         });
+    //     }
+
+    //     fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
+    //         if self.center.remove(&pane).unwrap() {
+    //             self.force_remove_pane(&pane, cx);
+    //             self.unfollow(&pane, cx);
+    //             self.last_leaders_by_pane.remove(&pane.downgrade());
+    //             for removed_item in pane.read(cx).items() {
+    //                 self.panes_by_item.remove(&removed_item.id());
+    //             }
+
+    //             cx.notify();
+    //         } else {
+    //             self.active_item_path_changed(cx);
+    //         }
+    //     }
+
+    //     pub fn panes(&self) -> &[View<Pane>] {
+    //         &self.panes
+    //     }
+
+    //     pub fn active_pane(&self) -> &View<Pane> {
+    //         &self.active_pane
+    //     }
+
+    //     fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+    //         self.follower_states.retain(|_, state| {
+    //             if state.leader_id == peer_id {
+    //                 for item in state.items_by_leader_view_id.values() {
+    //                     item.set_leader_peer_id(None, cx);
+    //                 }
+    //                 false
+    //             } else {
+    //                 true
+    //             }
+    //         });
+    //         cx.notify();
+    //     }
+
+    //     fn start_following(
+    //         &mut self,
+    //         leader_id: PeerId,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let pane = self.active_pane().clone();
+
+    //         self.last_leaders_by_pane
+    //             .insert(pane.downgrade(), leader_id);
+    //         self.unfollow(&pane, cx);
+    //         self.follower_states.insert(
+    //             pane.clone(),
+    //             FollowerState {
+    //                 leader_id,
+    //                 active_view_id: None,
+    //                 items_by_leader_view_id: Default::default(),
+    //             },
+    //         );
+    //         cx.notify();
+
+    //         let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+    //         let project_id = self.project.read(cx).remote_id();
+    //         let request = self.app_state.client.request(proto::Follow {
+    //             room_id,
+    //             project_id,
+    //             leader_id: Some(leader_id),
+    //         });
+
+    //         Some(cx.spawn(|this, mut cx| async move {
+    //             let response = request.await?;
+    //             this.update(&mut cx, |this, _| {
+    //                 let state = this
+    //                     .follower_states
+    //                     .get_mut(&pane)
+    //                     .ok_or_else(|| anyhow!("following interrupted"))?;
+    //                 state.active_view_id = if let Some(active_view_id) = response.active_view_id {
+    //                     Some(ViewId::from_proto(active_view_id)?)
+    //                 } else {
+    //                     None
+    //                 };
+    //                 Ok::<_, anyhow::Error>(())
+    //             })??;
+    //             Self::add_views_from_leader(
+    //                 this.clone(),
+    //                 leader_id,
+    //                 vec![pane],
+    //                 response.views,
+    //                 &mut cx,
+    //             )
+    //             .await?;
+    //             this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn follow_next_collaborator(
+    //         &mut self,
+    //         _: &FollowNextCollaborator,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let collaborators = self.project.read(cx).collaborators();
+    //         let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
+    //             let mut collaborators = collaborators.keys().copied();
+    //             for peer_id in collaborators.by_ref() {
+    //                 if peer_id == leader_id {
+    //                     break;
+    //                 }
+    //             }
+    //             collaborators.next()
+    //         } else if let Some(last_leader_id) =
+    //             self.last_leaders_by_pane.get(&self.active_pane.downgrade())
+    //         {
+    //             if collaborators.contains_key(last_leader_id) {
+    //                 Some(*last_leader_id)
+    //             } else {
+    //                 None
+    //             }
+    //         } else {
+    //             None
+    //         };
+
+    //         let pane = self.active_pane.clone();
+    //         let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
+    //         else {
+    //             return None;
+    //         };
+    //         if Some(leader_id) == self.unfollow(&pane, cx) {
+    //             return None;
+    //         }
+    //         self.follow(leader_id, cx)
+    //     }
+
+    //     pub fn follow(
+    //         &mut self,
+    //         leader_id: PeerId,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
+    //         let project = self.project.read(cx);
+
+    //         let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
+    //             return None;
+    //         };
+
+    //         let other_project_id = match remote_participant.location {
+    //             call::ParticipantLocation::External => None,
+    //             call::ParticipantLocation::UnsharedProject => None,
+    //             call::ParticipantLocation::SharedProject { project_id } => {
+    //                 if Some(project_id) == project.remote_id() {
+    //                     None
+    //                 } else {
+    //                     Some(project_id)
+    //                 }
+    //             }
+    //         };
+
+    //         // if they are active in another project, follow there.
+    //         if let Some(project_id) = other_project_id {
+    //             let app_state = self.app_state.clone();
+    //             return Some(crate::join_remote_project(
+    //                 project_id,
+    //                 remote_participant.user.id,
+    //                 app_state,
+    //                 cx,
+    //             ));
+    //         }
+
+    //         // if you're already following, find the right pane and focus it.
+    //         for (pane, state) in &self.follower_states {
+    //             if leader_id == state.leader_id {
+    //                 cx.focus(pane);
+    //                 return None;
+    //             }
+    //         }
+
+    //         // Otherwise, follow.
+    //         self.start_following(leader_id, cx)
+    //     }
+
+    //     pub fn unfollow(
+    //         &mut self,
+    //         pane: &View<Pane>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<PeerId> {
+    //         let state = self.follower_states.remove(pane)?;
+    //         let leader_id = state.leader_id;
+    //         for (_, item) in state.items_by_leader_view_id {
+    //             item.set_leader_peer_id(None, cx);
+    //         }
+
+    //         if self
+    //             .follower_states
+    //             .values()
+    //             .all(|state| state.leader_id != state.leader_id)
+    //         {
+    //             let project_id = self.project.read(cx).remote_id();
+    //             let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+    //             self.app_state
+    //                 .client
+    //                 .send(proto::Unfollow {
+    //                     room_id,
+    //                     project_id,
+    //                     leader_id: Some(leader_id),
+    //                 })
+    //                 .log_err();
+    //         }
+
+    //         cx.notify();
+    //         Some(leader_id)
+    //     }
+
+    //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
+    //         self.follower_states
+    //             .values()
+    //             .any(|state| state.leader_id == peer_id)
+    //     }
+
+    //     fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+    //         // TODO: There should be a better system in place for this
+    //         // (https://github.com/zed-industries/zed/issues/1290)
+    //         let is_fullscreen = cx.window_is_fullscreen();
+    //         let container_theme = if is_fullscreen {
+    //             let mut container_theme = theme.titlebar.container;
+    //             container_theme.padding.left = container_theme.padding.right;
+    //             container_theme
+    //         } else {
+    //             theme.titlebar.container
+    //         };
+
+    //         enum TitleBar {}
+    //         MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
+    //             Stack::new()
+    //                 .with_children(
+    //                     self.titlebar_item
+    //                         .as_ref()
+    //                         .map(|item| ChildView::new(item, cx)),
+    //                 )
+    //                 .contained()
+    //                 .with_style(container_theme)
+    //         })
+    //         .on_click(MouseButton::Left, |event, _, cx| {
+    //             if event.click_count == 2 {
+    //                 cx.zoom_window();
+    //             }
+    //         })
+    //         .constrained()
+    //         .with_height(theme.titlebar.height)
+    //         .into_any_named("titlebar")
+    //     }
+
+    //     fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
+    //         let active_entry = self.active_project_path(cx);
+    //         self.project
+    //             .update(cx, |project, cx| project.set_active_path(active_entry, cx));
+    //         self.update_window_title(cx);
+    //     }
+
+    //     fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
+    //         let project = self.project().read(cx);
+    //         let mut title = String::new();
+
+    //         if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
+    //             let filename = path
+    //                 .path
+    //                 .file_name()
+    //                 .map(|s| s.to_string_lossy())
+    //                 .or_else(|| {
+    //                     Some(Cow::Borrowed(
+    //                         project
+    //                             .worktree_for_id(path.worktree_id, cx)?
+    //                             .read(cx)
+    //                             .root_name(),
+    //                     ))
+    //                 });
+
+    //             if let Some(filename) = filename {
+    //                 title.push_str(filename.as_ref());
+    //                 title.push_str(" β€” ");
+    //             }
+    //         }
+
+    //         for (i, name) in project.worktree_root_names(cx).enumerate() {
+    //             if i > 0 {
+    //                 title.push_str(", ");
+    //             }
+    //             title.push_str(name);
+    //         }
+
+    //         if title.is_empty() {
+    //             title = "empty project".to_string();
+    //         }
+
+    //         if project.is_remote() {
+    //             title.push_str(" ↙");
+    //         } else if project.is_shared() {
+    //             title.push_str(" β†—");
+    //         }
+
+    //         cx.set_window_title(&title);
+    //     }
+
+    //     fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
+    //         let is_edited = !self.project.read(cx).is_read_only()
+    //             && self
+    //                 .items(cx)
+    //                 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
+    //         if is_edited != self.window_edited {
+    //             self.window_edited = is_edited;
+    //             cx.set_window_edited(self.window_edited)
+    //         }
+    //     }
+
+    //     fn render_disconnected_overlay(
+    //         &self,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Option<AnyElement<Workspace>> {
+    //         if self.project.read(cx).is_read_only() {
+    //             enum DisconnectedOverlay {}
+    //             Some(
+    //                 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
+    //                     let theme = &theme::current(cx);
+    //                     Label::new(
+    //                         "Your connection to the remote project has been lost.",
+    //                         theme.workspace.disconnected_overlay.text.clone(),
+    //                     )
+    //                     .aligned()
+    //                     .contained()
+    //                     .with_style(theme.workspace.disconnected_overlay.container)
+    //                 })
+    //                 .with_cursor_style(CursorStyle::Arrow)
+    //                 .capture_all()
+    //                 .into_any_named("disconnected overlay"),
+    //             )
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     fn render_notifications(
+    //         &self,
+    //         theme: &theme::Workspace,
+    //         cx: &AppContext,
+    //     ) -> Option<AnyElement<Workspace>> {
+    //         if self.notifications.is_empty() {
+    //             None
+    //         } else {
+    //             Some(
+    //                 Flex::column()
+    //                     .with_children(self.notifications.iter().map(|(_, _, notification)| {
+    //                         ChildView::new(notification.as_any(), cx)
+    //                             .contained()
+    //                             .with_style(theme.notification)
+    //                     }))
+    //                     .constrained()
+    //                     .with_width(theme.notifications.width)
+    //                     .contained()
+    //                     .with_style(theme.notifications.container)
+    //                     .aligned()
+    //                     .bottom()
+    //                     .right()
+    //                     .into_any(),
+    //             )
+    //         }
+    //     }
+
+    //     // RPC handlers
+
+    //     fn handle_follow(
+    //         &mut self,
+    //         follower_project_id: Option<u64>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> proto::FollowResponse {
+    //         let client = &self.app_state.client;
+    //         let project_id = self.project.read(cx).remote_id();
+
+    //         let active_view_id = self.active_item(cx).and_then(|i| {
+    //             Some(
+    //                 i.to_followable_item_handle(cx)?
+    //                     .remote_id(client, cx)?
+    //                     .to_proto(),
+    //             )
+    //         });
+
+    //         cx.notify();
+
+    //         self.last_active_view_id = active_view_id.clone();
+    //         proto::FollowResponse {
+    //             active_view_id,
+    //             views: self
+    //                 .panes()
+    //                 .iter()
+    //                 .flat_map(|pane| {
+    //                     let leader_id = self.leader_for_pane(pane);
+    //                     pane.read(cx).items().filter_map({
+    //                         let cx = &cx;
+    //                         move |item| {
+    //                             let item = item.to_followable_item_handle(cx)?;
+    //                             if (project_id.is_none() || project_id != follower_project_id)
+    //                                 && item.is_project_item(cx)
+    //                             {
+    //                                 return None;
+    //                             }
+    //                             let id = item.remote_id(client, cx)?.to_proto();
+    //                             let variant = item.to_state_proto(cx)?;
+    //                             Some(proto::View {
+    //                                 id: Some(id),
+    //                                 leader_id,
+    //                                 variant: Some(variant),
+    //                             })
+    //                         }
+    //                     })
+    //                 })
+    //                 .collect(),
+    //         }
+    //     }
+
+    //     fn handle_update_followers(
+    //         &mut self,
+    //         leader_id: PeerId,
+    //         message: proto::UpdateFollowers,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.leader_updates_tx
+    //             .unbounded_send((leader_id, message))
+    //             .ok();
+    //     }
+
+    //     async fn process_leader_update(
+    //         this: &WeakViewHandle<Self>,
+    //         leader_id: PeerId,
+    //         update: proto::UpdateFollowers,
+    //         cx: &mut AsyncAppContext,
+    //     ) -> Result<()> {
+    //         match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
+    //             proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
+    //                 this.update(cx, |this, _| {
+    //                     for (_, state) in &mut this.follower_states {
+    //                         if state.leader_id == leader_id {
+    //                             state.active_view_id =
+    //                                 if let Some(active_view_id) = update_active_view.id.clone() {
+    //                                     Some(ViewId::from_proto(active_view_id)?)
+    //                                 } else {
+    //                                     None
+    //                                 };
+    //                         }
+    //                     }
+    //                     anyhow::Ok(())
+    //                 })??;
+    //             }
+    //             proto::update_followers::Variant::UpdateView(update_view) => {
+    //                 let variant = update_view
+    //                     .variant
+    //                     .ok_or_else(|| anyhow!("missing update view variant"))?;
+    //                 let id = update_view
+    //                     .id
+    //                     .ok_or_else(|| anyhow!("missing update view id"))?;
+    //                 let mut tasks = Vec::new();
+    //                 this.update(cx, |this, cx| {
+    //                     let project = this.project.clone();
+    //                     for (_, state) in &mut this.follower_states {
+    //                         if state.leader_id == leader_id {
+    //                             let view_id = ViewId::from_proto(id.clone())?;
+    //                             if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
+    //                                 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
+    //                             }
+    //                         }
+    //                     }
+    //                     anyhow::Ok(())
+    //                 })??;
+    //                 try_join_all(tasks).await.log_err();
+    //             }
+    //             proto::update_followers::Variant::CreateView(view) => {
+    //                 let panes = this.read_with(cx, |this, _| {
+    //                     this.follower_states
+    //                         .iter()
+    //                         .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
+    //                         .cloned()
+    //                         .collect()
+    //                 })?;
+    //                 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
+    //             }
+    //         }
+    //         this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
+    //         Ok(())
+    //     }
+
+    //     async fn add_views_from_leader(
+    //         this: WeakViewHandle<Self>,
+    //         leader_id: PeerId,
+    //         panes: Vec<View<Pane>>,
+    //         views: Vec<proto::View>,
+    //         cx: &mut AsyncAppContext,
+    //     ) -> Result<()> {
+    //         let this = this
+    //             .upgrade(cx)
+    //             .ok_or_else(|| anyhow!("workspace dropped"))?;
+
+    //         let item_builders = cx.update(|cx| {
+    //             cx.default_global::<FollowableItemBuilders>()
+    //                 .values()
+    //                 .map(|b| b.0)
+    //                 .collect::<Vec<_>>()
+    //         });
+
+    //         let mut item_tasks_by_pane = HashMap::default();
+    //         for pane in panes {
+    //             let mut item_tasks = Vec::new();
+    //             let mut leader_view_ids = Vec::new();
+    //             for view in &views {
+    //                 let Some(id) = &view.id else { continue };
+    //                 let id = ViewId::from_proto(id.clone())?;
+    //                 let mut variant = view.variant.clone();
+    //                 if variant.is_none() {
+    //                     Err(anyhow!("missing view variant"))?;
+    //                 }
+    //                 for build_item in &item_builders {
+    //                     let task = cx
+    //                         .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
+    //                     if let Some(task) = task {
+    //                         item_tasks.push(task);
+    //                         leader_view_ids.push(id);
+    //                         break;
+    //                     } else {
+    //                         assert!(variant.is_some());
+    //                     }
+    //                 }
+    //             }
+
+    //             item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
+    //         }
+
+    //         for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
+    //             let items = futures::future::try_join_all(item_tasks).await?;
+    //             this.update(cx, |this, cx| {
+    //                 let state = this.follower_states.get_mut(&pane)?;
+    //                 for (id, item) in leader_view_ids.into_iter().zip(items) {
+    //                     item.set_leader_peer_id(Some(leader_id), cx);
+    //                     state.items_by_leader_view_id.insert(id, item);
+    //                 }
+
+    //                 Some(())
+    //             });
+    //         }
+    //         Ok(())
+    //     }
+
+    //     fn update_active_view_for_followers(&mut self, cx: &AppContext) {
+    //         let mut is_project_item = true;
+    //         let mut update = proto::UpdateActiveView::default();
+    //         if self.active_pane.read(cx).has_focus() {
+    //             let item = self
+    //                 .active_item(cx)
+    //                 .and_then(|item| item.to_followable_item_handle(cx));
+    //             if let Some(item) = item {
+    //                 is_project_item = item.is_project_item(cx);
+    //                 update = proto::UpdateActiveView {
+    //                     id: item
+    //                         .remote_id(&self.app_state.client, cx)
+    //                         .map(|id| id.to_proto()),
+    //                     leader_id: self.leader_for_pane(&self.active_pane),
+    //                 };
+    //             }
+    //         }
+
+    //         if update.id != self.last_active_view_id {
+    //             self.last_active_view_id = update.id.clone();
+    //             self.update_followers(
+    //                 is_project_item,
+    //                 proto::update_followers::Variant::UpdateActiveView(update),
+    //                 cx,
+    //             );
+    //         }
+    //     }
+
+    //     fn update_followers(
+    //         &self,
+    //         project_only: bool,
+    //         update: proto::update_followers::Variant,
+    //         cx: &AppContext,
+    //     ) -> Option<()> {
+    //         let project_id = if project_only {
+    //             self.project.read(cx).remote_id()
+    //         } else {
+    //             None
+    //         };
+    //         self.app_state().workspace_store.read_with(cx, |store, cx| {
+    //             store.update_followers(project_id, update, cx)
+    //         })
+    //     }
+
+    //     pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
+    //         self.follower_states.get(pane).map(|state| state.leader_id)
+    //     }
+
+    //     fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
+    //         cx.notify();
+
+    //         let call = self.active_call()?;
+    //         let room = call.read(cx).room()?.read(cx);
+    //         let participant = room.remote_participant_for_peer_id(leader_id)?;
+    //         let mut items_to_activate = Vec::new();
+
+    //         let leader_in_this_app;
+    //         let leader_in_this_project;
+    //         match participant.location {
+    //             call::ParticipantLocation::SharedProject { project_id } => {
+    //                 leader_in_this_app = true;
+    //                 leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
+    //             }
+    //             call::ParticipantLocation::UnsharedProject => {
+    //                 leader_in_this_app = true;
+    //                 leader_in_this_project = false;
+    //             }
+    //             call::ParticipantLocation::External => {
+    //                 leader_in_this_app = false;
+    //                 leader_in_this_project = false;
+    //             }
+    //         };
+
+    //         for (pane, state) in &self.follower_states {
+    //             if state.leader_id != leader_id {
+    //                 continue;
+    //             }
+    //             if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
+    //                 if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
+    //                     if leader_in_this_project || !item.is_project_item(cx) {
+    //                         items_to_activate.push((pane.clone(), item.boxed_clone()));
+    //                     }
+    //                 } else {
+    //                     log::warn!(
+    //                         "unknown view id {:?} for leader {:?}",
+    //                         active_view_id,
+    //                         leader_id
+    //                     );
+    //                 }
+    //                 continue;
+    //             }
+    //             if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
+    //                 items_to_activate.push((pane.clone(), Box::new(shared_screen)));
+    //             }
+    //         }
+
+    //         for (pane, item) in items_to_activate {
+    //             let pane_was_focused = pane.read(cx).has_focus();
+    //             if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
+    //                 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
+    //             } else {
+    //                 pane.update(cx, |pane, cx| {
+    //                     pane.add_item(item.boxed_clone(), false, false, None, cx)
+    //                 });
+    //             }
+
+    //             if pane_was_focused {
+    //                 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
+    //             }
+    //         }
+
+    //         None
+    //     }
+
+    //     fn shared_screen_for_peer(
+    //         &self,
+    //         peer_id: PeerId,
+    //         pane: &View<Pane>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<View<SharedScreen>> {
+    //         let call = self.active_call()?;
+    //         let room = call.read(cx).room()?.read(cx);
+    //         let participant = room.remote_participant_for_peer_id(peer_id)?;
+    //         let track = participant.video_tracks.values().next()?.clone();
+    //         let user = participant.user.clone();
+
+    //         for item in pane.read(cx).items_of_type::<SharedScreen>() {
+    //             if item.read(cx).peer_id == peer_id {
+    //                 return Some(item);
+    //             }
+    //         }
+
+    //         Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
+    //     }
+
+    //     pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
+    //         if active {
+    //             self.update_active_view_for_followers(cx);
+    //             cx.background()
+    //                 .spawn(persistence::DB.update_timestamp(self.database_id()))
+    //                 .detach();
+    //         } else {
+    //             for pane in &self.panes {
+    //                 pane.update(cx, |pane, cx| {
+    //                     if let Some(item) = pane.active_item() {
+    //                         item.workspace_deactivated(cx);
+    //                     }
+    //                     if matches!(
+    //                         settings::get::<WorkspaceSettings>(cx).autosave,
+    //                         AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
+    //                     ) {
+    //                         for item in pane.items() {
+    //                             Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
+    //                                 .detach_and_log_err(cx);
+    //                         }
+    //                     }
+    //                 });
+    //             }
+    //         }
+    //     }
+
+    //     fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
+    //         self.active_call.as_ref().map(|(call, _)| call)
+    //     }
+
+    //     fn on_active_call_event(
+    //         &mut self,
+    //         _: ModelHandle<ActiveCall>,
+    //         event: &call::room::Event,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         match event {
+    //             call::room::Event::ParticipantLocationChanged { participant_id }
+    //             | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
+    //                 self.leader_updated(*participant_id, cx);
+    //             }
+    //             _ => {}
+    //         }
+    //     }
+
+    //     pub fn database_id(&self) -> WorkspaceId {
+    //         self.database_id
+    //     }
+
+    //     fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
+    //         let project = self.project().read(cx);
+
+    //         if project.is_local() {
+    //             Some(
+    //                 project
+    //                     .visible_worktrees(cx)
+    //                     .map(|worktree| worktree.read(cx).abs_path())
+    //                     .collect::<Vec<_>>()
+    //                     .into(),
+    //             )
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
+    //         match member {
+    //             Member::Axis(PaneAxis { members, .. }) => {
+    //                 for child in members.iter() {
+    //                     self.remove_panes(child.clone(), cx)
+    //                 }
+    //             }
+    //             Member::Pane(pane) => {
+    //                 self.force_remove_pane(&pane, cx);
+    //             }
+    //         }
+    //     }
+
+    //     fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
+    //         self.panes.retain(|p| p != pane);
+    //         cx.focus(self.panes.last().unwrap());
+    //         if self.last_active_center_pane == Some(pane.downgrade()) {
+    //             self.last_active_center_pane = None;
+    //         }
+    //         cx.notify();
+    //     }
+
+    //     fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
+    //         self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
+    //             cx.background().timer(Duration::from_millis(100)).await;
+    //             this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
+    //                 .ok();
+    //         }));
+    //     }
+
+    //     fn serialize_workspace(&self, cx: &ViewContext<Self>) {
+    //         fn serialize_pane_handle(
+    //             pane_handle: &View<Pane>,
+    //             cx: &AppContext,
+    //         ) -> SerializedPane {
+    //             let (items, active) = {
+    //                 let pane = pane_handle.read(cx);
+    //                 let active_item_id = pane.active_item().map(|item| item.id());
+    //                 (
+    //                     pane.items()
+    //                         .filter_map(|item_handle| {
+    //                             Some(SerializedItem {
+    //                                 kind: Arc::from(item_handle.serialized_item_kind()?),
+    //                                 item_id: item_handle.id(),
+    //                                 active: Some(item_handle.id()) == active_item_id,
+    //                             })
+    //                         })
+    //                         .collect::<Vec<_>>(),
+    //                     pane.has_focus(),
+    //                 )
+    //             };
+
+    //             SerializedPane::new(items, active)
+    //         }
+
+    //         fn build_serialized_pane_group(
+    //             pane_group: &Member,
+    //             cx: &AppContext,
+    //         ) -> SerializedPaneGroup {
+    //             match pane_group {
+    //                 Member::Axis(PaneAxis {
+    //                     axis,
+    //                     members,
+    //                     flexes,
+    //                     bounding_boxes: _,
+    //                 }) => SerializedPaneGroup::Group {
+    //                     axis: *axis,
+    //                     children: members
+    //                         .iter()
+    //                         .map(|member| build_serialized_pane_group(member, cx))
+    //                         .collect::<Vec<_>>(),
+    //                     flexes: Some(flexes.borrow().clone()),
+    //                 },
+    //                 Member::Pane(pane_handle) => {
+    //                     SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
+    //                 }
+    //             }
+    //         }
+
+    //         fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
+    //             let left_dock = this.left_dock.read(cx);
+    //             let left_visible = left_dock.is_open();
+    //             let left_active_panel = left_dock.visible_panel().and_then(|panel| {
+    //                 Some(
+    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
+    //                         .to_string(),
+    //                 )
+    //             });
+    //             let left_dock_zoom = left_dock
+    //                 .visible_panel()
+    //                 .map(|panel| panel.is_zoomed(cx))
+    //                 .unwrap_or(false);
+
+    //             let right_dock = this.right_dock.read(cx);
+    //             let right_visible = right_dock.is_open();
+    //             let right_active_panel = right_dock.visible_panel().and_then(|panel| {
+    //                 Some(
+    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
+    //                         .to_string(),
+    //                 )
+    //             });
+    //             let right_dock_zoom = right_dock
+    //                 .visible_panel()
+    //                 .map(|panel| panel.is_zoomed(cx))
+    //                 .unwrap_or(false);
+
+    //             let bottom_dock = this.bottom_dock.read(cx);
+    //             let bottom_visible = bottom_dock.is_open();
+    //             let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
+    //                 Some(
+    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
+    //                         .to_string(),
+    //                 )
+    //             });
+    //             let bottom_dock_zoom = bottom_dock
+    //                 .visible_panel()
+    //                 .map(|panel| panel.is_zoomed(cx))
+    //                 .unwrap_or(false);
+
+    //             DockStructure {
+    //                 left: DockData {
+    //                     visible: left_visible,
+    //                     active_panel: left_active_panel,
+    //                     zoom: left_dock_zoom,
+    //                 },
+    //                 right: DockData {
+    //                     visible: right_visible,
+    //                     active_panel: right_active_panel,
+    //                     zoom: right_dock_zoom,
+    //                 },
+    //                 bottom: DockData {
+    //                     visible: bottom_visible,
+    //                     active_panel: bottom_active_panel,
+    //                     zoom: bottom_dock_zoom,
+    //                 },
+    //             }
+    //         }
+
+    //         if let Some(location) = self.location(cx) {
+    //             // Load bearing special case:
+    //             //  - with_local_workspace() relies on this to not have other stuff open
+    //             //    when you open your log
+    //             if !location.paths().is_empty() {
+    //                 let center_group = build_serialized_pane_group(&self.center.root, cx);
+    //                 let docks = build_serialized_docks(self, cx);
+
+    //                 let serialized_workspace = SerializedWorkspace {
+    //                     id: self.database_id,
+    //                     location,
+    //                     center_group,
+    //                     bounds: Default::default(),
+    //                     display: Default::default(),
+    //                     docks,
+    //                 };
+
+    //                 cx.background()
+    //                     .spawn(persistence::DB.save_workspace(serialized_workspace))
+    //                     .detach();
+    //             }
+    //         }
+    //     }
+
+    //     pub(crate) fn load_workspace(
+    //         workspace: WeakViewHandle<Workspace>,
+    //         serialized_workspace: SerializedWorkspace,
+    //         paths_to_open: Vec<Option<ProjectPath>>,
+    //         cx: &mut AppContext,
+    //     ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
+    //         cx.spawn(|mut cx| async move {
+    //             let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
+    //                 (
+    //                     workspace.project().clone(),
+    //                     workspace.last_active_center_pane.clone(),
+    //                 )
+    //             })?;
+
+    //             let mut center_group = None;
+    //             let mut center_items = None;
+    //             // Traverse the splits tree and add to things
+    //             if let Some((group, active_pane, items)) = serialized_workspace
+    //                 .center_group
+    //                 .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
+    //                 .await
+    //             {
+    //                 center_items = Some(items);
+    //                 center_group = Some((group, active_pane))
+    //             }
+
+    //             let mut items_by_project_path = cx.read(|cx| {
+    //                 center_items
+    //                     .unwrap_or_default()
+    //                     .into_iter()
+    //                     .filter_map(|item| {
+    //                         let item = item?;
+    //                         let project_path = item.project_path(cx)?;
+    //                         Some((project_path, item))
+    //                     })
+    //                     .collect::<HashMap<_, _>>()
+    //             });
+
+    //             let opened_items = paths_to_open
+    //                 .into_iter()
+    //                 .map(|path_to_open| {
+    //                     path_to_open
+    //                         .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
+    //                 })
+    //                 .collect::<Vec<_>>();
+
+    //             // Remove old panes from workspace panes list
+    //             workspace.update(&mut cx, |workspace, cx| {
+    //                 if let Some((center_group, active_pane)) = center_group {
+    //                     workspace.remove_panes(workspace.center.root.clone(), cx);
+
+    //                     // Swap workspace center group
+    //                     workspace.center = PaneGroup::with_root(center_group);
+
+    //                     // Change the focus to the workspace first so that we retrigger focus in on the pane.
+    //                     cx.focus_self();
+
+    //                     if let Some(active_pane) = active_pane {
+    //                         cx.focus(&active_pane);
+    //                     } else {
+    //                         cx.focus(workspace.panes.last().unwrap());
+    //                     }
+    //                 } else {
+    //                     let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
+    //                     if let Some(old_center_handle) = old_center_handle {
+    //                         cx.focus(&old_center_handle)
+    //                     } else {
+    //                         cx.focus_self()
+    //                     }
+    //                 }
+
+    //                 let docks = serialized_workspace.docks;
+    //                 workspace.left_dock.update(cx, |dock, cx| {
+    //                     dock.set_open(docks.left.visible, cx);
+    //                     if let Some(active_panel) = docks.left.active_panel {
+    //                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+    //                             dock.activate_panel(ix, cx);
+    //                         }
+    //                     }
+    //                     dock.active_panel()
+    //                         .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
+    //                     if docks.left.visible && docks.left.zoom {
+    //                         cx.focus_self()
+    //                     }
+    //                 });
+    //                 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
+    //                 workspace.right_dock.update(cx, |dock, cx| {
+    //                     dock.set_open(docks.right.visible, cx);
+    //                     if let Some(active_panel) = docks.right.active_panel {
+    //                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+    //                             dock.activate_panel(ix, cx);
+    //                         }
+    //                     }
+    //                     dock.active_panel()
+    //                         .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
+
+    //                     if docks.right.visible && docks.right.zoom {
+    //                         cx.focus_self()
+    //                     }
+    //                 });
+    //                 workspace.bottom_dock.update(cx, |dock, cx| {
+    //                     dock.set_open(docks.bottom.visible, cx);
+    //                     if let Some(active_panel) = docks.bottom.active_panel {
+    //                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+    //                             dock.activate_panel(ix, cx);
+    //                         }
+    //                     }
+
+    //                     dock.active_panel()
+    //                         .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
+
+    //                     if docks.bottom.visible && docks.bottom.zoom {
+    //                         cx.focus_self()
+    //                     }
+    //                 });
+
+    //                 cx.notify();
+    //             })?;
+
+    //             // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
+    //             workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
+
+    //             Ok(opened_items)
+    //         })
+    //     }
+
+    //     #[cfg(any(test, feature = "test-support"))]
+    //     pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+    //         use node_runtime::FakeNodeRuntime;
+
+    //         let client = project.read(cx).client();
+    //         let user_store = project.read(cx).user_store();
+
+    //         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
+    //         let app_state = Arc::new(AppState {
+    //             languages: project.read(cx).languages().clone(),
+    //             workspace_store,
+    //             client,
+    //             user_store,
+    //             fs: project.read(cx).fs().clone(),
+    //             build_window_options: |_, _, _| Default::default(),
+    //             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+    //             node_runtime: FakeNodeRuntime::new(),
+    //         });
+    //         Self::new(0, project, app_state, cx)
+    //     }
+
+    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
+    //         let dock = match position {
+    //             DockPosition::Left => &self.left_dock,
+    //             DockPosition::Right => &self.right_dock,
+    //             DockPosition::Bottom => &self.bottom_dock,
+    //         };
+    //         let active_panel = dock.read(cx).visible_panel()?;
+    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
+    //             dock.read(cx).render_placeholder(cx)
+    //         } else {
+    //             ChildView::new(dock, cx).into_any()
+    //         };
+
+    //         Some(
+    //             element
+    //                 .constrained()
+    //                 .dynamically(move |constraint, _, cx| match position {
+    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
+    //                         Vector2F::new(20., constraint.min.y()),
+    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
+    //                     ),
+    //                     DockPosition::Bottom => SizeConstraint::new(
+    //                         Vector2F::new(constraint.min.x(), 20.),
+    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
+    //                     ),
+    //                 })
+    //                 .into_any(),
+    //         )
+    //     }
+    // }
+
+    // fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
+    //     ZED_WINDOW_POSITION
+    //         .zip(*ZED_WINDOW_SIZE)
+    //         .map(|(position, size)| {
+    //             WindowBounds::Fixed(RectF::new(
+    //                 cx.platform().screens()[0].bounds().origin() + position,
+    //                 size,
+    //             ))
+    //         })
+    // }
+
+    // async fn open_items(
+    //     serialized_workspace: Option<SerializedWorkspace>,
+    //     workspace: &WeakViewHandle<Workspace>,
+    //     mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
+    //     app_state: Arc<AppState>,
+    //     mut cx: AsyncAppContext,
+    // ) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
+    //     let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
+
+    //     if let Some(serialized_workspace) = serialized_workspace {
+    //         let workspace = workspace.clone();
+    //         let restored_items = cx
+    //             .update(|cx| {
+    //                 Workspace::load_workspace(
+    //                     workspace,
+    //                     serialized_workspace,
+    //                     project_paths_to_open
+    //                         .iter()
+    //                         .map(|(_, project_path)| project_path)
+    //                         .cloned()
+    //                         .collect(),
+    //                     cx,
+    //                 )
+    //             })
+    //             .await?;
+
+    //         let restored_project_paths = cx.read(|cx| {
+    //             restored_items
+    //                 .iter()
+    //                 .filter_map(|item| item.as_ref()?.project_path(cx))
+    //                 .collect::<HashSet<_>>()
+    //         });
+
+    //         for restored_item in restored_items {
+    //             opened_items.push(restored_item.map(Ok));
+    //         }
+
+    //         project_paths_to_open
+    //             .iter_mut()
+    //             .for_each(|(_, project_path)| {
+    //                 if let Some(project_path_to_open) = project_path {
+    //                     if restored_project_paths.contains(project_path_to_open) {
+    //                         *project_path = None;
+    //                     }
+    //                 }
+    //             });
+    //     } else {
+    //         for _ in 0..project_paths_to_open.len() {
+    //             opened_items.push(None);
+    //         }
+    //     }
+    //     assert!(opened_items.len() == project_paths_to_open.len());
+
+    //     let tasks =
+    //         project_paths_to_open
+    //             .into_iter()
+    //             .enumerate()
+    //             .map(|(i, (abs_path, project_path))| {
+    //                 let workspace = workspace.clone();
+    //                 cx.spawn(|mut cx| {
+    //                     let fs = app_state.fs.clone();
+    //                     async move {
+    //                         let file_project_path = project_path?;
+    //                         if fs.is_file(&abs_path).await {
+    //                             Some((
+    //                                 i,
+    //                                 workspace
+    //                                     .update(&mut cx, |workspace, cx| {
+    //                                         workspace.open_path(file_project_path, None, true, cx)
+    //                                     })
+    //                                     .log_err()?
+    //                                     .await,
+    //                             ))
+    //                         } else {
+    //                             None
+    //                         }
+    //                     }
+    //                 })
+    //             });
+
+    //     for maybe_opened_path in futures::future::join_all(tasks.into_iter())
+    //         .await
+    //         .into_iter()
+    //     {
+    //         if let Some((i, path_open_result)) = maybe_opened_path {
+    //             opened_items[i] = Some(path_open_result);
+    //         }
+    //     }
+
+    //     Ok(opened_items)
+    // }
+
+    // fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
+    //     const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
+    //     const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
+    //     const MESSAGE_ID: usize = 2;
+
+    //     if workspace
+    //         .read_with(cx, |workspace, cx| {
+    //             workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
+    //         })
+    //         .unwrap_or(false)
+    //     {
+    //         return;
+    //     }
+
+    //     if db::kvp::KEY_VALUE_STORE
+    //         .read_kvp(NEW_DOCK_HINT_KEY)
+    //         .ok()
+    //         .flatten()
+    //         .is_some()
+    //     {
+    //         if !workspace
+    //             .read_with(cx, |workspace, cx| {
+    //                 workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
+    //             })
+    //             .unwrap_or(false)
+    //         {
+    //             cx.update(|cx| {
+    //                 cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
+    //                     let entry = tracker
+    //                         .entry(TypeId::of::<MessageNotification>())
+    //                         .or_default();
+    //                     if !entry.contains(&MESSAGE_ID) {
+    //                         entry.push(MESSAGE_ID);
+    //                     }
+    //                 });
+    //             });
+    //         }
+
+    //         return;
+    //     }
+
+    //     cx.spawn(|_| async move {
+    //         db::kvp::KEY_VALUE_STORE
+    //             .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
+    //             .await
+    //             .ok();
+    //     })
+    //     .detach();
+
+    //     workspace
+    //         .update(cx, |workspace, cx| {
+    //             workspace.show_notification_once(2, cx, |cx| {
+    //                 cx.add_view(|_| {
+    //                     MessageNotification::new_element(|text, _| {
+    //                         Text::new(
+    //                             "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
+    //                             text,
+    //                         )
+    //                         .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
+    //                             let code_span_background_color = settings::get::<ThemeSettings>(cx)
+    //                                 .theme
+    //                                 .editor
+    //                                 .document_highlight_read_background;
+
+    //                             cx.scene().push_quad(gpui::Quad {
+    //                                 bounds,
+    //                                 background: Some(code_span_background_color),
+    //                                 border: Default::default(),
+    //                                 corner_radii: (2.0).into(),
+    //                             })
+    //                         })
+    //                         .into_any()
+    //                     })
+    //                     .with_click_message("Read more about the new panel system")
+    //                     .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
+    //                 })
+    //             })
+    //         })
+    //         .ok();
+}
+
+// fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
+//     const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
+
+//     workspace
+//         .update(cx, |workspace, cx| {
+//             if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
+//                 workspace.show_notification_once(0, cx, |cx| {
+//                     cx.add_view(|_| {
+//                         MessageNotification::new("Failed to load the database file.")
+//                             .with_click_message("Click to let us know about this error")
+//                             .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
+//                     })
+//                 });
+//             }
+//         })
+//         .log_err();
+// }
+
+// impl Entity for Workspace {
+//     type Event = Event;
+
+//     fn release(&mut self, cx: &mut AppContext) {
+//         self.app_state.workspace_store.update(cx, |store, _| {
+//             store.workspaces.remove(&self.weak_self);
+//         })
+//     }
+// }
+
+// impl View for Workspace {
+//     fn ui_name() -> &'static str {
+//         "Workspace"
+//     }
+
+//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+//         let theme = theme::current(cx).clone();
+//         Stack::new()
+//             .with_child(
+//                 Flex::column()
+//                     .with_child(self.render_titlebar(&theme, cx))
+//                     .with_child(
+//                         Stack::new()
+//                             .with_child({
+//                                 let project = self.project.clone();
+//                                 Flex::row()
+//                                     .with_children(self.render_dock(DockPosition::Left, cx))
+//                                     .with_child(
+//                                         Flex::column()
+//                                             .with_child(
+//                                                 FlexItem::new(
+//                                                     self.center.render(
+//                                                         &project,
+//                                                         &theme,
+//                                                         &self.follower_states,
+//                                                         self.active_call(),
+//                                                         self.active_pane(),
+//                                                         self.zoomed
+//                                                             .as_ref()
+//                                                             .and_then(|zoomed| zoomed.upgrade(cx))
+//                                                             .as_ref(),
+//                                                         &self.app_state,
+//                                                         cx,
+//                                                     ),
+//                                                 )
+//                                                 .flex(1., true),
+//                                             )
+//                                             .with_children(
+//                                                 self.render_dock(DockPosition::Bottom, cx),
+//                                             )
+//                                             .flex(1., true),
+//                                     )
+//                                     .with_children(self.render_dock(DockPosition::Right, cx))
+//                             })
+//                             .with_child(Overlay::new(
+//                                 Stack::new()
+//                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
+//                                         enum ZoomBackground {}
+//                                         let zoomed = zoomed.upgrade(cx)?;
+
+//                                         let mut foreground_style =
+//                                             theme.workspace.zoomed_pane_foreground;
+//                                         if let Some(zoomed_dock_position) = self.zoomed_position {
+//                                             foreground_style =
+//                                                 theme.workspace.zoomed_panel_foreground;
+//                                             let margin = foreground_style.margin.top;
+//                                             let border = foreground_style.border.top;
+
+//                                             // Only include a margin and border on the opposite side.
+//                                             foreground_style.margin.top = 0.;
+//                                             foreground_style.margin.left = 0.;
+//                                             foreground_style.margin.bottom = 0.;
+//                                             foreground_style.margin.right = 0.;
+//                                             foreground_style.border.top = false;
+//                                             foreground_style.border.left = false;
+//                                             foreground_style.border.bottom = false;
+//                                             foreground_style.border.right = false;
+//                                             match zoomed_dock_position {
+//                                                 DockPosition::Left => {
+//                                                     foreground_style.margin.right = margin;
+//                                                     foreground_style.border.right = border;
+//                                                 }
+//                                                 DockPosition::Right => {
+//                                                     foreground_style.margin.left = margin;
+//                                                     foreground_style.border.left = border;
+//                                                 }
+//                                                 DockPosition::Bottom => {
+//                                                     foreground_style.margin.top = margin;
+//                                                     foreground_style.border.top = border;
+//                                                 }
+//                                             }
+//                                         }
+
+//                                         Some(
+//                                             ChildView::new(&zoomed, cx)
+//                                                 .contained()
+//                                                 .with_style(foreground_style)
+//                                                 .aligned()
+//                                                 .contained()
+//                                                 .with_style(theme.workspace.zoomed_background)
+//                                                 .mouse::<ZoomBackground>(0)
+//                                                 .capture_all()
+//                                                 .on_down(
+//                                                     MouseButton::Left,
+//                                                     |_, this: &mut Self, cx| {
+//                                                         this.zoom_out(cx);
+//                                                     },
+//                                                 ),
+//                                         )
+//                                     }))
+//                                     .with_children(self.modal.as_ref().map(|modal| {
+//                                         // Prevent clicks within the modal from falling
+//                                         // through to the rest of the workspace.
+//                                         enum ModalBackground {}
+//                                         MouseEventHandler::new::<ModalBackground, _>(
+//                                             0,
+//                                             cx,
+//                                             |_, cx| ChildView::new(modal.view.as_any(), cx),
+//                                         )
+//                                         .on_click(MouseButton::Left, |_, _, _| {})
+//                                         .contained()
+//                                         .with_style(theme.workspace.modal)
+//                                         .aligned()
+//                                         .top()
+//                                     }))
+//                                     .with_children(self.render_notifications(&theme.workspace, cx)),
+//                             ))
+//                             .provide_resize_bounds::<WorkspaceBounds>()
+//                             .flex(1.0, true),
+//                     )
+//                     .with_child(ChildView::new(&self.status_bar, cx))
+//                     .contained()
+//                     .with_background_color(theme.workspace.background),
+//             )
+//             .with_children(DragAndDrop::render(cx))
+//             .with_children(self.render_disconnected_overlay(cx))
+//             .into_any_named("workspace")
+//     }
+
+//     fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+//         if cx.is_self_focused() {
+//             cx.focus(&self.active_pane);
+//         }
+//     }
+
+//     fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
+//         DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
+//     }
+// }
+
+// impl WorkspaceStore {
+//     pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
+//         Self {
+//             workspaces: Default::default(),
+//             followers: Default::default(),
+//             _subscriptions: vec![
+//                 client.add_request_handler(cx.handle(), Self::handle_follow),
+//                 client.add_message_handler(cx.handle(), Self::handle_unfollow),
+//                 client.add_message_handler(cx.handle(), Self::handle_update_followers),
+//             ],
+//             client,
+//         }
+//     }
+
+//     pub fn update_followers(
+//         &self,
+//         project_id: Option<u64>,
+//         update: proto::update_followers::Variant,
+//         cx: &AppContext,
+//     ) -> Option<()> {
+//         if !cx.has_global::<ModelHandle<ActiveCall>>() {
+//             return None;
+//         }
+
+//         let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
+//         let follower_ids: Vec<_> = self
+//             .followers
+//             .iter()
+//             .filter_map(|follower| {
+//                 if follower.project_id == project_id || project_id.is_none() {
+//                     Some(follower.peer_id.into())
+//                 } else {
+//                     None
+//                 }
+//             })
+//             .collect();
+//         if follower_ids.is_empty() {
+//             return None;
+//         }
+//         self.client
+//             .send(proto::UpdateFollowers {
+//                 room_id,
+//                 project_id,
+//                 follower_ids,
+//                 variant: Some(update),
+//             })
+//             .log_err()
+//     }
+
+//     async fn handle_follow(
+//         this: ModelHandle<Self>,
+//         envelope: TypedEnvelope<proto::Follow>,
+//         _: Arc<Client>,
+//         mut cx: AsyncAppContext,
+//     ) -> Result<proto::FollowResponse> {
+//         this.update(&mut cx, |this, cx| {
+//             let follower = Follower {
+//                 project_id: envelope.payload.project_id,
+//                 peer_id: envelope.original_sender_id()?,
+//             };
+//             let active_project = ActiveCall::global(cx)
+//                 .read(cx)
+//                 .location()
+//                 .map(|project| project.id());
+
+//             let mut response = proto::FollowResponse::default();
+//             for workspace in &this.workspaces {
+//                 let Some(workspace) = workspace.upgrade(cx) else {
+//                     continue;
+//                 };
+
+//                 workspace.update(cx.as_mut(), |workspace, cx| {
+//                     let handler_response = workspace.handle_follow(follower.project_id, cx);
+//                     if response.views.is_empty() {
+//                         response.views = handler_response.views;
+//                     } else {
+//                         response.views.extend_from_slice(&handler_response.views);
+//                     }
+
+//                     if let Some(active_view_id) = handler_response.active_view_id.clone() {
+//                         if response.active_view_id.is_none()
+//                             || Some(workspace.project.id()) == active_project
+//                         {
+//                             response.active_view_id = Some(active_view_id);
+//                         }
+//                     }
+//                 });
+//             }
+
+//             if let Err(ix) = this.followers.binary_search(&follower) {
+//                 this.followers.insert(ix, follower);
+//             }
+
+//             Ok(response)
+//         })
+//     }
+
+//     async fn handle_unfollow(
+//         this: ModelHandle<Self>,
+//         envelope: TypedEnvelope<proto::Unfollow>,
+//         _: Arc<Client>,
+//         mut cx: AsyncAppContext,
+//     ) -> Result<()> {
+//         this.update(&mut cx, |this, _| {
+//             let follower = Follower {
+//                 project_id: envelope.payload.project_id,
+//                 peer_id: envelope.original_sender_id()?,
+//             };
+//             if let Ok(ix) = this.followers.binary_search(&follower) {
+//                 this.followers.remove(ix);
+//             }
+//             Ok(())
+//         })
+//     }
+
+//     async fn handle_update_followers(
+//         this: ModelHandle<Self>,
+//         envelope: TypedEnvelope<proto::UpdateFollowers>,
+//         _: Arc<Client>,
+//         mut cx: AsyncAppContext,
+//     ) -> Result<()> {
+//         let leader_id = envelope.original_sender_id()?;
+//         let update = envelope.payload;
+//         this.update(&mut cx, |this, cx| {
+//             for workspace in &this.workspaces {
+//                 let Some(workspace) = workspace.upgrade(cx) else {
+//                     continue;
+//                 };
+//                 workspace.update(cx.as_mut(), |workspace, cx| {
+//                     let project_id = workspace.project.read(cx).remote_id();
+//                     if update.project_id != project_id && update.project_id.is_some() {
+//                         return;
+//                     }
+//                     workspace.handle_update_followers(leader_id, update.clone(), cx);
+//                 });
+//             }
+//             Ok(())
+//         })
+//     }
+// }
+
+// impl Entity for WorkspaceStore {
+//     type Event = ();
+// }
+
+// impl ViewId {
+//     pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
+//         Ok(Self {
+//             creator: message
+//                 .creator
+//                 .ok_or_else(|| anyhow!("creator is missing"))?,
+//             id: message.id,
+//         })
+//     }
+
+//     pub(crate) fn to_proto(&self) -> proto::ViewId {
+//         proto::ViewId {
+//             creator: Some(self.creator),
+//             id: self.id,
+//         }
+//     }
+// }
+
+// pub trait WorkspaceHandle {
+//     fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
+// }
+
+// impl WorkspaceHandle for View<Workspace> {
+//     fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
+//         self.read(cx)
+//             .worktrees(cx)
+//             .flat_map(|worktree| {
+//                 let worktree_id = worktree.read(cx).id();
+//                 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
+//                     worktree_id,
+//                     path: f.path.clone(),
+//                 })
+//             })
+//             .collect::<Vec<_>>()
+//     }
+// }
+
+// impl std::fmt::Debug for OpenPaths {
+//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+//         f.debug_struct("OpenPaths")
+//             .field("paths", &self.paths)
+//             .finish()
+//     }
+// }
+
+// pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
+
+pub async fn activate_workspace_for_project(
+    cx: &mut AsyncAppContext,
+    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
+) -> Option<WindowHandle<Workspace>> {
+    cx.run_on_main(move |cx| {
+        for window in cx.windows() {
+            let Some(workspace) = window.downcast::<Workspace>() else {
+                continue;
+            };
+
+            let predicate = cx
+                .update_window_root(&workspace, |workspace, cx| {
+                    let project = workspace.project.read(cx);
+                    if predicate(project, cx) {
+                        cx.activate_window();
+                        true
+                    } else {
+                        false
+                    }
+                })
+                .log_err()
+                .unwrap_or(false);
+
+            if predicate {
+                return Some(workspace);
+            }
+        }
+
+        None
+    })
+    .ok()?
+    .await
+}
+
+// pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
+//     DB.last_workspace().await.log_err().flatten()
+// }
+
+// async fn join_channel_internal(
+//     channel_id: u64,
+//     app_state: &Arc<AppState>,
+//     requesting_window: Option<WindowHandle<Workspace>>,
+//     active_call: &ModelHandle<ActiveCall>,
+//     cx: &mut AsyncAppContext,
+// ) -> Result<bool> {
+//     let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
+//         let Some(room) = active_call.room().map(|room| room.read(cx)) else {
+//             return (false, None);
+//         };
+
+//         let already_in_channel = room.channel_id() == Some(channel_id);
+//         let should_prompt = room.is_sharing_project()
+//             && room.remote_participants().len() > 0
+//             && !already_in_channel;
+//         let open_room = if already_in_channel {
+//             active_call.room().cloned()
+//         } else {
+//             None
+//         };
+//         (should_prompt, open_room)
+//     });
+
+//     if let Some(room) = open_room {
+//         let task = room.update(cx, |room, cx| {
+//             if let Some((project, host)) = room.most_active_project(cx) {
+//                 return Some(join_remote_project(project, host, app_state.clone(), cx));
+//             }
+
+//             None
+//         });
+//         if let Some(task) = task {
+//             task.await?;
+//         }
+//         return anyhow::Ok(true);
+//     }
+
+//     if should_prompt {
+//         if let Some(workspace) = requesting_window {
+//             if let Some(window) = workspace.update(cx, |cx| cx.window()) {
+//                 let answer = window.prompt(
+//                     PromptLevel::Warning,
+//                     "Leaving this call will unshare your current project.\nDo you want to switch channels?",
+//                     &["Yes, Join Channel", "Cancel"],
+//                     cx,
+//                 );
+
+//                 if let Some(mut answer) = answer {
+//                     if answer.next().await == Some(1) {
+//                         return Ok(false);
+//                     }
+//                 }
+//             } else {
+//                 return Ok(false); // unreachable!() hopefully
+//             }
+//         } else {
+//             return Ok(false); // unreachable!() hopefully
+//         }
+//     }
+
+//     let client = cx.read(|cx| active_call.read(cx).client());
+
+//     let mut client_status = client.status();
+
+//     // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
+//     'outer: loop {
+//         let Some(status) = client_status.recv().await else {
+//             return Err(anyhow!("error connecting"));
+//         };
+
+//         match status {
+//             Status::Connecting
+//             | Status::Authenticating
+//             | Status::Reconnecting
+//             | Status::Reauthenticating => continue,
+//             Status::Connected { .. } => break 'outer,
+//             Status::SignedOut => return Err(anyhow!("not signed in")),
+//             Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
+//             Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
+//                 return Err(anyhow!("zed is offline"))
+//             }
+//         }
+//     }
+
+//     let room = active_call
+//         .update(cx, |active_call, cx| {
+//             active_call.join_channel(channel_id, cx)
+//         })
+//         .await?;
+
+//     room.update(cx, |room, _| room.room_update_completed())
+//         .await;
+
+//     let task = room.update(cx, |room, cx| {
+//         if let Some((project, host)) = room.most_active_project(cx) {
+//             return Some(join_remote_project(project, host, app_state.clone(), cx));
+//         }
+
+//         None
+//     });
+//     if let Some(task) = task {
+//         task.await?;
+//         return anyhow::Ok(true);
+//     }
+//     anyhow::Ok(false)
+// }
+
+// pub fn join_channel(
+//     channel_id: u64,
+//     app_state: Arc<AppState>,
+//     requesting_window: Option<WindowHandle<Workspace>>,
+//     cx: &mut AppContext,
+// ) -> Task<Result<()>> {
+//     let active_call = ActiveCall::global(cx);
+//     cx.spawn(|mut cx| async move {
+//         let result = join_channel_internal(
+//             channel_id,
+//             &app_state,
+//             requesting_window,
+//             &active_call,
+//             &mut cx,
+//         )
+//         .await;
+
+//         // join channel succeeded, and opened a window
+//         if matches!(result, Ok(true)) {
+//             return anyhow::Ok(());
+//         }
+
+//         if requesting_window.is_some() {
+//             return anyhow::Ok(());
+//         }
+
+//         // find an existing workspace to focus and show call controls
+//         let mut active_window = activate_any_workspace_window(&mut cx);
+//         if active_window.is_none() {
+//             // no open workspaces, make one to show the error in (blergh)
+//             cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
+//                 .await;
+//         }
+
+//         active_window = activate_any_workspace_window(&mut cx);
+//         if active_window.is_none() {
+//             return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
+//         }
+
+//         if let Err(err) = result {
+//             let prompt = active_window.unwrap().prompt(
+//                 PromptLevel::Critical,
+//                 &format!("Failed to join channel: {}", err),
+//                 &["Ok"],
+//                 &mut cx,
+//             );
+//             if let Some(mut prompt) = prompt {
+//                 prompt.next().await;
+//             } else {
+//                 return Err(err);
+//             }
+//         }
+
+//         // return ok, we showed the error to the user.
+//         return anyhow::Ok(());
+//     })
+// }
+
+// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
+//     for window in cx.windows() {
+//         let found = window.update(cx, |cx| {
+//             let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
+//             if is_workspace {
+//                 cx.activate_window();
+//             }
+//             is_workspace
+//         });
+//         if found == Some(true) {
+//             return Some(window);
+//         }
+//     }
+//     None
+// }
+
+use client2::{
+    proto::{self, PeerId, ViewId},
+    Client, UserStore,
+};
+use collections::{HashMap, HashSet};
+use gpui2::{
+    AnyHandle, AnyView, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View,
+    ViewContext, WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions,
+};
+use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
+use language2::LanguageRegistry;
+use node_runtime::NodeRuntime;
+use project2::{Project, ProjectEntryId, ProjectPath, Worktree};
+use std::{
+    any::TypeId,
+    path::{Path, PathBuf},
+    sync::Arc,
+    time::Duration,
+};
+use util::ResultExt;
+
+#[allow(clippy::type_complexity)]
+pub fn open_paths(
+    abs_paths: &[PathBuf],
+    app_state: &Arc<AppState>,
+    requesting_window: Option<WindowHandle<Workspace>>,
+    cx: &mut AppContext,
+) -> Task<
+    anyhow::Result<(
+        WindowHandle<Workspace>,
+        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+    )>,
+> {
+    let app_state = app_state.clone();
+    let abs_paths = abs_paths.to_vec();
+    cx.spawn(|mut cx| async move {
+        // Open paths in existing workspace if possible
+        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
+            project.contains_paths(&abs_paths, cx)
+        })
+        .await;
+
+        if let Some(existing) = existing {
+            Ok((
+                existing.clone(),
+                cx.update_window_root(&existing, |workspace, cx| {
+                    workspace.open_paths(abs_paths, true, cx)
+                })?
+                .await,
+            ))
+        } else {
+            todo!()
+            // Ok(cx
+            //     .update(|cx| {
+            //         Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
+            //     })
+            //     .await)
+        }
+    })
+}
+
+// pub fn open_new(
+//     app_state: &Arc<AppState>,
+//     cx: &mut AppContext,
+//     init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
+// ) -> Task<()> {
+//     let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
+//     cx.spawn(|mut cx| async move {
+//         let (workspace, opened_paths) = task.await;
+
+//         workspace
+//             .update(&mut cx, |workspace, cx| {
+//                 if opened_paths.is_empty() {
+//                     init(workspace, cx)
+//                 }
+//             })
+//             .log_err();
+//     })
+// }
+
+// pub fn create_and_open_local_file(
+//     path: &'static Path,
+//     cx: &mut ViewContext<Workspace>,
+//     default_content: impl 'static + Send + FnOnce() -> Rope,
+// ) -> Task<Result<Box<dyn ItemHandle>>> {
+//     cx.spawn(|workspace, mut cx| async move {
+//         let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
+//         if !fs.is_file(path).await {
+//             fs.create_file(path, Default::default()).await?;
+//             fs.save(path, &default_content(), Default::default())
+//                 .await?;
+//         }
+
+//         let mut items = workspace
+//             .update(&mut cx, |workspace, cx| {
+//                 workspace.with_local_workspace(cx, |workspace, cx| {
+//                     workspace.open_paths(vec![path.to_path_buf()], false, cx)
+//                 })
+//             })?
+//             .await?
+//             .await;
+
+//         let item = items.pop().flatten();
+//         item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
+//     })
+// }
+
+// pub fn join_remote_project(
+//     project_id: u64,
+//     follow_user_id: u64,
+//     app_state: Arc<AppState>,
+//     cx: &mut AppContext,
+// ) -> Task<Result<()>> {
+//     cx.spawn(|mut cx| async move {
+//         let windows = cx.windows();
+//         let existing_workspace = windows.into_iter().find_map(|window| {
+//             window.downcast::<Workspace>().and_then(|window| {
+//                 window
+//                     .read_root_with(&cx, |workspace, cx| {
+//                         if workspace.project().read(cx).remote_id() == Some(project_id) {
+//                             Some(cx.handle().downgrade())
+//                         } else {
+//                             None
+//                         }
+//                     })
+//                     .unwrap_or(None)
+//             })
+//         });
+
+//         let workspace = if let Some(existing_workspace) = existing_workspace {
+//             existing_workspace
+//         } else {
+//             let active_call = cx.read(ActiveCall::global);
+//             let room = active_call
+//                 .read_with(&cx, |call, _| call.room().cloned())
+//                 .ok_or_else(|| anyhow!("not in a call"))?;
+//             let project = room
+//                 .update(&mut cx, |room, cx| {
+//                     room.join_project(
+//                         project_id,
+//                         app_state.languages.clone(),
+//                         app_state.fs.clone(),
+//                         cx,
+//                     )
+//                 })
+//                 .await?;
+
+//             let window_bounds_override = window_bounds_env_override(&cx);
+//             let window = cx.add_window(
+//                 (app_state.build_window_options)(
+//                     window_bounds_override,
+//                     None,
+//                     cx.platform().as_ref(),
+//                 ),
+//                 |cx| Workspace::new(0, project, app_state.clone(), cx),
+//             );
+//             let workspace = window.root(&cx).unwrap();
+//             (app_state.initialize_workspace)(
+//                 workspace.downgrade(),
+//                 false,
+//                 app_state.clone(),
+//                 cx.clone(),
+//             )
+//             .await
+//             .log_err();
+
+//             workspace.downgrade()
+//         };
+
+//         workspace.window().activate(&mut cx);
+//         cx.platform().activate(true);
+
+//         workspace.update(&mut cx, |workspace, cx| {
+//             if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+//                 let follow_peer_id = room
+//                     .read(cx)
+//                     .remote_participants()
+//                     .iter()
+//                     .find(|(_, participant)| participant.user.id == follow_user_id)
+//                     .map(|(_, p)| p.peer_id)
+//                     .or_else(|| {
+//                         // If we couldn't follow the given user, follow the host instead.
+//                         let collaborator = workspace
+//                             .project()
+//                             .read(cx)
+//                             .collaborators()
+//                             .values()
+//                             .find(|collaborator| collaborator.replica_id == 0)?;
+//                         Some(collaborator.peer_id)
+//                     });
+
+//                 if let Some(follow_peer_id) = follow_peer_id {
+//                     workspace
+//                         .follow(follow_peer_id, cx)
+//                         .map(|follow| follow.detach_and_log_err(cx));
+//                 }
+//             }
+//         })?;
+
+//         anyhow::Ok(())
+//     })
+// }
+
+// pub fn restart(_: &Restart, cx: &mut AppContext) {
+//     let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
+//     cx.spawn(|mut cx| async move {
+//         let mut workspace_windows = cx
+//             .windows()
+//             .into_iter()
+//             .filter_map(|window| window.downcast::<Workspace>())
+//             .collect::<Vec<_>>();
+
+//         // If multiple windows have unsaved changes, and need a save prompt,
+//         // prompt in the active window before switching to a different window.
+//         workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
+
+//         if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
+//             let answer = window.prompt(
+//                 PromptLevel::Info,
+//                 "Are you sure you want to restart?",
+//                 &["Restart", "Cancel"],
+//                 &mut cx,
+//             );
+
+//             if let Some(mut answer) = answer {
+//                 let answer = answer.next().await;
+//                 if answer != Some(0) {
+//                     return Ok(());
+//                 }
+//             }
+//         }
+
+//         // If the user cancels any save prompt, then keep the app open.
+//         for window in workspace_windows {
+//             if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
+//                 workspace.prepare_to_close(true, cx)
+//             }) {
+//                 if !should_close.await? {
+//                     return Ok(());
+//                 }
+//             }
+//         }
+//         cx.platform().restart();
+//         anyhow::Ok(())
+//     })
+//     .detach_and_log_err(cx);
+// }
+
+// fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
+//     let mut parts = value.split(',');
+//     let width: usize = parts.next()?.parse().ok()?;
+//     let height: usize = parts.next()?.parse().ok()?;
+//     Some(vec2f(width as f32, height as f32))
+// }
+
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use crate::{
+//         dock::test::{TestPanel, TestPanelEvent},
+//         item::test::{TestItem, TestItemEvent, TestProjectItem},
+//     };
+//     use fs::FakeFs;
+//     use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
+//     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) {
+//         init_test(cx);
+
+//         let fs = FakeFs::new(cx.background());
+//         let project = Project::test(fs, [], cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+
+//         // Adding an item with no ambiguity renders the tab without detail.
+//         let item1 = window.add_view(cx, |_| {
+//             let mut item = TestItem::new();
+//             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
+//             item
+//         });
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item1.clone()), cx);
+//         });
+//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
+
+//         // Adding an item that creates ambiguity increases the level of detail on
+//         // both tabs.
+//         let item2 = window.add_view(cx, |_| {
+//             let mut item = TestItem::new();
+//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+//             item
+//         });
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item2.clone()), cx);
+//         });
+//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+
+//         // Adding an item that creates ambiguity increases the level of detail only
+//         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
+//         // we stop at the highest detail available.
+//         let item3 = window.add_view(cx, |_| {
+//             let mut item = TestItem::new();
+//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+//             item
+//         });
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item3.clone()), cx);
+//         });
+//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+//         item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+//     }
+
+//     #[gpui::test]
+//     async fn test_tracking_active_path(cx: &mut TestAppContext) {
+//         init_test(cx);
+
+//         let fs = FakeFs::new(cx.background());
+//         fs.insert_tree(
+//             "/root1",
+//             json!({
+//                 "one.txt": "",
+//                 "two.txt": "",
+//             }),
+//         )
+//         .await;
+//         fs.insert_tree(
+//             "/root2",
+//             json!({
+//                 "three.txt": "",
+//             }),
+//         )
+//         .await;
+
+//         let project = Project::test(fs, ["root1".as_ref()], cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+//         let worktree_id = project.read_with(cx, |project, cx| {
+//             project.worktrees(cx).next().unwrap().read(cx).id()
+//         });
+
+//         let item1 = window.add_view(cx, |cx| {
+//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
+//         });
+//         let item2 = window.add_view(cx, |cx| {
+//             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
+//         });
+
+//         // 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| {
+//             assert_eq!(
+//                 project.active_entry(),
+//                 project
+//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+//                     .map(|e| e.id)
+//             );
+//         });
+//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
+
+//         // Add a second item to a non-empty pane
+//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
+//         assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
+//         project.read_with(cx, |project, cx| {
+//             assert_eq!(
+//                 project.active_entry(),
+//                 project
+//                     .entry_for_path(&(worktree_id, "two.txt").into(), cx)
+//                     .map(|e| e.id)
+//             );
+//         });
+
+//         // Close the active item
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&Default::default(), cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
+//         project.read_with(cx, |project, cx| {
+//             assert_eq!(
+//                 project.active_entry(),
+//                 project
+//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+//                     .map(|e| e.id)
+//             );
+//         });
+
+//         // Add a project folder
+//         project
+//             .update(cx, |project, cx| {
+//                 project.find_or_create_local_worktree("/root2", true, cx)
+//             })
+//             .await
+//             .unwrap();
+//         assert_eq!(
+//             window.current_title(cx).as_deref(),
+//             Some("one.txt β€” root1, root2")
+//         );
+
+//         // Remove a project folder
+//         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
+//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_window(cx: &mut TestAppContext) {
+//         init_test(cx);
+
+//         let fs = FakeFs::new(cx.background());
+//         fs.insert_tree("/root", json!({ "one": "" })).await;
+
+//         let project = Project::test(fs, ["root".as_ref()], cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+
+//         // When there are no dirty items, there's nothing to do.
+//         let item1 = window.add_view(cx, |_| TestItem::new());
+//         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
+//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+//         assert!(task.await.unwrap());
+
+//         // When there are dirty untitled items, prompt to save each one. If the user
+//         // cancels any prompt, then abort.
+//         let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
+//         let item3 = window.add_view(cx, |cx| {
+//             TestItem::new()
+//                 .with_dirty(true)
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+//         });
+//         workspace.update(cx, |w, cx| {
+//             w.add_item(Box::new(item2.clone()), cx);
+//             w.add_item(Box::new(item3.clone()), cx);
+//         });
+//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+//         cx.foreground().run_until_parked();
+//         window.simulate_prompt_answer(2, cx); // cancel save all
+//         cx.foreground().run_until_parked();
+//         window.simulate_prompt_answer(2, cx); // cancel save all
+//         cx.foreground().run_until_parked();
+//         assert!(!window.has_pending_prompt(cx));
+//         assert!(!task.await.unwrap());
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_pane_items(cx: &mut TestAppContext) {
+//         init_test(cx);
+
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+//         let workspace = window.root(cx);
+
+//         let item1 = window.add_view(cx, |cx| {
+//             TestItem::new()
+//                 .with_dirty(true)
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+//         });
+//         let item2 = window.add_view(cx, |cx| {
+//             TestItem::new()
+//                 .with_dirty(true)
+//                 .with_conflict(true)
+//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
+//         });
+//         let item3 = window.add_view(cx, |cx| {
+//             TestItem::new()
+//                 .with_dirty(true)
+//                 .with_conflict(true)
+//                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
+//         });
+//         let item4 = window.add_view(cx, |cx| {
+//             TestItem::new()
+//                 .with_dirty(true)
+//                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
+//         });
+//         let pane = workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item1.clone()), cx);
+//             workspace.add_item(Box::new(item2.clone()), cx);
+//             workspace.add_item(Box::new(item3.clone()), cx);
+//             workspace.add_item(Box::new(item4.clone()), cx);
+//             workspace.active_pane().clone()
+//         });
+
+//         let close_items = pane.update(cx, |pane, cx| {
+//             pane.activate_item(1, true, true, cx);
+//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
+//             let item1_id = item1.id();
+//             let item3_id = item3.id();
+//             let item4_id = item4.id();
+//             pane.close_items(cx, SaveIntent::Close, move |id| {
+//                 [item1_id, item3_id, item4_id].contains(&id)
+//             })
+//         });
+//         cx.foreground().run_until_parked();
+
+//         assert!(window.has_pending_prompt(cx));
+//         // Ignore "Save all" prompt
+//         window.simulate_prompt_answer(2, cx);
+//         cx.foreground().run_until_parked();
+//         // There's a prompt to save item 1.
+//         pane.read_with(cx, |pane, _| {
+//             assert_eq!(pane.items_len(), 4);
+//             assert_eq!(pane.active_item().unwrap().id(), item1.id());
+//         });
+//         // Confirm saving item 1.
+//         window.simulate_prompt_answer(0, cx);
+//         cx.foreground().run_until_parked();
+
+//         // Item 1 is saved. There's a prompt to save item 3.
+//         pane.read_with(cx, |pane, cx| {
+//             assert_eq!(item1.read(cx).save_count, 1);
+//             assert_eq!(item1.read(cx).save_as_count, 0);
+//             assert_eq!(item1.read(cx).reload_count, 0);
+//             assert_eq!(pane.items_len(), 3);
+//             assert_eq!(pane.active_item().unwrap().id(), item3.id());
+//         });
+//         assert!(window.has_pending_prompt(cx));
+
+//         // Cancel saving item 3.
+//         window.simulate_prompt_answer(1, cx);
+//         cx.foreground().run_until_parked();
+
+//         // Item 3 is reloaded. There's a prompt to save item 4.
+//         pane.read_with(cx, |pane, cx| {
+//             assert_eq!(item3.read(cx).save_count, 0);
+//             assert_eq!(item3.read(cx).save_as_count, 0);
+//             assert_eq!(item3.read(cx).reload_count, 1);
+//             assert_eq!(pane.items_len(), 2);
+//             assert_eq!(pane.active_item().unwrap().id(), item4.id());
+//         });
+//         assert!(window.has_pending_prompt(cx));
+
+//         // Confirm saving item 4.
+//         window.simulate_prompt_answer(0, cx);
+//         cx.foreground().run_until_parked();
+
+//         // There's a prompt for a path for item 4.
+//         cx.simulate_new_path_selection(|_| Some(Default::default()));
+//         close_items.await.unwrap();
+
+//         // The requested items are closed.
+//         pane.read_with(cx, |pane, cx| {
+//             assert_eq!(item4.read(cx).save_count, 0);
+//             assert_eq!(item4.read(cx).save_as_count, 1);
+//             assert_eq!(item4.read(cx).reload_count, 0);
+//             assert_eq!(pane.items_len(), 1);
+//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
+//         });
+//     }
+
+//     #[gpui::test]
+//     async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
+//         init_test(cx);
+
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, [], cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+//         let workspace = window.root(cx);
+
+//         // Create several workspace items with single project entries, and two
+//         // workspace items with multiple project entries.
+//         let single_entry_items = (0..=4)
+//             .map(|project_entry_id| {
+//                 window.add_view(cx, |cx| {
+//                     TestItem::new()
+//                         .with_dirty(true)
+//                         .with_project_items(&[TestProjectItem::new(
+//                             project_entry_id,
+//                             &format!("{project_entry_id}.txt"),
+//                             cx,
+//                         )])
+//                 })
+//             })
+//             .collect::<Vec<_>>();
+//         let item_2_3 = window.add_view(cx, |cx| {
+//             TestItem::new()
+//                 .with_dirty(true)
+//                 .with_singleton(false)
+//                 .with_project_items(&[
+//                     single_entry_items[2].read(cx).project_items[0].clone(),
+//                     single_entry_items[3].read(cx).project_items[0].clone(),
+//                 ])
+//         });
+//         let item_3_4 = window.add_view(cx, |cx| {
+//             TestItem::new()
+//                 .with_dirty(true)
+//                 .with_singleton(false)
+//                 .with_project_items(&[
+//                     single_entry_items[3].read(cx).project_items[0].clone(),
+//                     single_entry_items[4].read(cx).project_items[0].clone(),
+//                 ])
+//         });
+
+//         // Create two panes that contain the following project entries:
+//         //   left pane:
+//         //     multi-entry items:   (2, 3)
+//         //     single-entry items:  0, 1, 2, 3, 4
+//         //   right pane:
+//         //     single-entry items:  1
+//         //     multi-entry items:   (3, 4)
+//         let left_pane = workspace.update(cx, |workspace, cx| {
+//             let left_pane = workspace.active_pane().clone();
+//             workspace.add_item(Box::new(item_2_3.clone()), cx);
+//             for item in single_entry_items {
+//                 workspace.add_item(Box::new(item), cx);
+//             }
+//             left_pane.update(cx, |pane, cx| {
+//                 pane.activate_item(2, true, true, cx);
+//             });
+
+//             workspace
+//                 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
+//                 .unwrap();
+
+//             left_pane
+//         });
+
+//         //Need to cause an effect flush in order to respect new focus
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item_3_4.clone()), cx);
+//             cx.focus(&left_pane);
+//         });
+
+//         // When closing all of the items in the left pane, we should be prompted twice:
+//         // once for project entry 0, and once for project entry 2. After those two
+//         // prompts, the task should complete.
+
+//         let close = left_pane.update(cx, |pane, cx| {
+//             pane.close_items(cx, SaveIntent::Close, move |_| true)
+//         });
+//         cx.foreground().run_until_parked();
+//         // Discard "Save all" prompt
+//         window.simulate_prompt_answer(2, cx);
+
+//         cx.foreground().run_until_parked();
+//         left_pane.read_with(cx, |pane, cx| {
+//             assert_eq!(
+//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+//                 &[ProjectEntryId::from_proto(0)]
+//             );
+//         });
+//         window.simulate_prompt_answer(0, cx);
+
+//         cx.foreground().run_until_parked();
+//         left_pane.read_with(cx, |pane, cx| {
+//             assert_eq!(
+//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+//                 &[ProjectEntryId::from_proto(2)]
+//             );
+//         });
+//         window.simulate_prompt_answer(0, cx);
+
+//         cx.foreground().run_until_parked();
+//         close.await.unwrap();
+//         left_pane.read_with(cx, |pane, _| {
+//             assert_eq!(pane.items_len(), 0);
+//         });
+//     }
+
+//     #[gpui::test]
+//     async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+//         init_test(cx);
+
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, [], cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         let item = window.add_view(cx, |cx| {
+//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+//         });
+//         let item_id = item.id();
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item.clone()), cx);
+//         });
+
+//         // Autosave on window change.
+//         item.update(cx, |item, cx| {
+//             cx.update_global(|settings: &mut SettingsStore, cx| {
+//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+//                     settings.autosave = Some(AutosaveSetting::OnWindowChange);
+//                 })
+//             });
+//             item.is_dirty = true;
+//         });
+
+//         // Deactivating the window saves the file.
+//         window.simulate_deactivation(cx);
+//         deterministic.run_until_parked();
+//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
+
+//         // Autosave on focus change.
+//         item.update(cx, |item, cx| {
+//             cx.focus_self();
+//             cx.update_global(|settings: &mut SettingsStore, cx| {
+//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
+//                 })
+//             });
+//             item.is_dirty = true;
+//         });
+
+//         // Blurring the item saves the file.
+//         item.update(cx, |_, cx| cx.blur());
+//         deterministic.run_until_parked();
+//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
+
+//         // Deactivating the window still saves the file.
+//         window.simulate_activation(cx);
+//         item.update(cx, |item, cx| {
+//             cx.focus_self();
+//             item.is_dirty = true;
+//         });
+//         window.simulate_deactivation(cx);
+
+//         deterministic.run_until_parked();
+//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+
+//         // Autosave after delay.
+//         item.update(cx, |item, cx| {
+//             cx.update_global(|settings: &mut SettingsStore, cx| {
+//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+//                     settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+//                 })
+//             });
+//             item.is_dirty = true;
+//             cx.emit(TestItemEvent::Edit);
+//         });
+
+//         // Delay hasn't fully expired, so the file is still dirty and unsaved.
+//         deterministic.advance_clock(Duration::from_millis(250));
+//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+
+//         // After delay expires, the file is saved.
+//         deterministic.advance_clock(Duration::from_millis(250));
+//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
+
+//         // Autosave on focus change, ensuring closing the tab counts as such.
+//         item.update(cx, |item, cx| {
+//             cx.update_global(|settings: &mut SettingsStore, cx| {
+//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
+//                 })
+//             });
+//             item.is_dirty = true;
+//         });
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+//         })
+//         .await
+//         .unwrap();
+//         assert!(!window.has_pending_prompt(cx));
+//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
+
+//         // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item.clone()), cx);
+//         });
+//         item.update(cx, |item, cx| {
+//             item.project_items[0].update(cx, |item, _| {
+//                 item.entry_id = None;
+//             });
+//             item.is_dirty = true;
+//             cx.blur();
+//         });
+//         deterministic.run_until_parked();
+//         item.read_with(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)
+//         });
+//         deterministic.run_until_parked();
+//         assert!(window.has_pending_prompt(cx));
+//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
+//     }
+
+//     #[gpui::test]
+//     async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
+//         init_test(cx);
+
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, [], cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+//         let workspace = window.root(cx);
+
+//         let item = window.add_view(cx, |cx| {
+//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+//         });
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+//         let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
+//         let toolbar_notify_count = Rc::new(RefCell::new(0));
+
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.add_item(Box::new(item.clone()), cx);
+//             let toolbar_notification_count = toolbar_notify_count.clone();
+//             cx.observe(&toolbar, move |_, _, _| {
+//                 *toolbar_notification_count.borrow_mut() += 1
+//             })
+//             .detach();
+//         });
+
+//         pane.read_with(cx, |pane, _| {
+//             assert!(!pane.can_navigate_backward());
+//             assert!(!pane.can_navigate_forward());
+//         });
+
+//         item.update(cx, |item, cx| {
+//             item.set_state("one".to_string(), cx);
+//         });
+
+//         // Toolbar must be notified to re-render the navigation buttons
+//         assert_eq!(*toolbar_notify_count.borrow(), 1);
+
+//         pane.read_with(cx, |pane, _| {
+//             assert!(pane.can_navigate_backward());
+//             assert!(!pane.can_navigate_forward());
+//         });
+
+//         workspace
+//             .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
+//             .await
+//             .unwrap();
+
+//         assert_eq!(*toolbar_notify_count.borrow(), 3);
+//         pane.read_with(cx, |pane, _| {
+//             assert!(!pane.can_navigate_backward());
+//             assert!(pane.can_navigate_forward());
+//         });
+//     }
+
+//     #[gpui::test]
+//     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         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.add_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.read_with(cx, |workspace, _| workspace.active_pane().clone());
+//         pane.update(cx, |pane, cx| {
+//             let item = cx.add_view(|_| TestItem::new());
+//             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.read_with(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.read_with(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.read_with(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.read_with(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.read_with(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.read_with(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.read_with(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.read_with(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.read_with(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));
+
+//         // Opening a dock unzooms the pane.
+//         workspace.update(cx, |workspace, cx| {
+//             workspace.toggle_dock(DockPosition::Right, cx)
+//         });
+//         workspace.read_with(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.background());
+
+//         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.add_view(|_| TestPanel::new(DockPosition::Left));
+//             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.add_view(|_| TestPanel::new(DockPosition::Right));
+//             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().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.read_with(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.read_with(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.read_with(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.read_with(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 = window.add_view(cx, |_| EmptyView);
+//         focus_receiver.update(cx, |_, cx| cx.focus_self());
+//         workspace.read_with(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.read_with(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.read_with(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.read_with(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.read_with(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.read_with(cx, |workspace, cx| {
+//             let right_dock = workspace.right_dock();
+//             assert!(!right_dock.read(cx).is_open());
+//         });
+//     }
+
+//     pub fn init_test(cx: &mut TestAppContext) {
+//         cx.foreground().forbid_parking();
+//         cx.update(|cx| {
+//             cx.set_global(SettingsStore::test(cx));
+//             theme::init((), cx);
+//             language::init(cx);
+//             crate::init_settings(cx);
+//             Project::init_settings(cx);
+//         });
+//     }
+// }

crates/zed2/src/main.rs πŸ”—

@@ -120,7 +120,7 @@ fn main() {
         let node_runtime = RealNodeRuntime::new(http.clone());
 
         language2::init(cx);
-        let user_store = cx.entity(|cx| UserStore::new(client.clone(), http.clone(), cx));
+        let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
         // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
         cx.set_global(client.clone());

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

@@ -4,7 +4,7 @@ mod open_listener;
 
 pub use assets::*;
 use client2::{Client, UserStore};
-use gpui2::{AsyncAppContext, Handle};
+use gpui2::{AsyncAppContext, Model};
 pub use only_instance::*;
 pub use open_listener::*;
 
@@ -47,7 +47,7 @@ pub fn connect_to_cli(
 
 pub struct AppState {
     pub client: Arc<Client>,
-    pub user_store: Handle<UserStore>,
+    pub user_store: Model<UserStore>,
 }
 
 pub async fn handle_cli_connection(