diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 1a514164ba..ffa2e5e9dc 100644 --- a/crates/call2/src/call2.rs +++ b/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, user_store: Handle, cx: &mut AppContext) { +pub fn init(client: Arc, user_store: Model, 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, Vec)>, - pending_room_creation: Option, Arc>>>>, + room: Option<(Model, Vec)>, + pending_room_creation: Option, Arc>>>>, location: Option>, pending_invites: HashSet, incoming_call: ( @@ -49,7 +49,7 @@ pub struct ActiveCall { watch::Receiver>, ), client: Arc, - user_store: Handle, + user_store: Model, _subscriptions: Vec, } @@ -58,11 +58,7 @@ impl EventEmitter for ActiveCall { } impl ActiveCall { - fn new( - client: Arc, - user_store: Handle, - cx: &mut ModelContext, - ) -> Self { + fn new(client: Arc, user_store: Model, cx: &mut ModelContext) -> Self { Self { room: None, pending_room_creation: None, @@ -84,7 +80,7 @@ impl ActiveCall { } async fn handle_incoming_call( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -112,7 +108,7 @@ impl ActiveCall { } async fn handle_call_canceled( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -129,14 +125,14 @@ impl ActiveCall { Ok(()) } - pub fn global(cx: &AppContext) -> Handle { - cx.global::>().clone() + pub fn global(cx: &AppContext) -> Model { + cx.global::>().clone() } pub fn invite( &mut self, called_user_id: u64, - initial_project: Option>, + initial_project: Option>, cx: &mut ModelContext, ) -> Task> { if !self.pending_invites.insert(called_user_id) { @@ -291,7 +287,7 @@ impl ActiveCall { &mut self, channel_id: u64, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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: Model, cx: &mut ModelContext, ) -> Task> { if let Some((room, _)) = self.room.as_ref() { @@ -340,7 +336,7 @@ impl ActiveCall { pub fn unshare_project( &mut self, - project: Handle, + project: Model, cx: &mut ModelContext, ) -> 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: Option<&Model>, cx: &mut ModelContext, ) -> Task> { if project.is_some() || !*ZED_ALWAYS_ACTIVE { @@ -371,7 +367,7 @@ impl ActiveCall { fn set_room( &mut self, - room: Option>, + room: Option>, cx: &mut ModelContext, ) -> Task> { if room.as_ref() != self.room.as_ref().map(|room| &room.0) { @@ -407,7 +403,7 @@ impl ActiveCall { } } - pub fn room(&self) -> Option<&Handle> { + pub fn room(&self) -> Option<&Model> { self.room.as_ref().map(|(room, _)| room) } diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index 639191123f..07873c4cd5 100644 --- a/crates/call2/src/room.rs +++ b/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, - user_store: Handle, + user_store: Model, follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec>, client_subscriptions: Vec, _subscriptions: Vec, @@ -111,7 +111,7 @@ impl Room { channel_id: Option, live_kit_connection_info: Option, client: Arc, - user_store: Handle, + user_store: Model, cx: &mut ModelContext, ) -> Self { todo!() @@ -237,15 +237,15 @@ impl Room { pub(crate) fn create( called_user_id: u64, - initial_project: Option>, + initial_project: Option>, client: Arc, - user_store: Handle, + user_store: Model, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { 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, - user_store: Handle, + user_store: Model, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { 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, - user_store: Handle, + user_store: Model, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { 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, - user_store: Handle, + user_store: Model, mut cx: AsyncAppContext, - ) -> Result> { + ) -> Result> { 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, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -1101,7 +1101,7 @@ impl Room { language_registry: Arc, fs: Arc, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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: Model, cx: &mut ModelContext, ) -> Task> { 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: Model, cx: &mut ModelContext, ) -> 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: Option<&Model>, cx: &mut ModelContext, ) -> Task> { if self.status.is_offline() { diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 8eaf248521..dcea6ded4e 100644 --- a/crates/client2/src/client2.rs +++ b/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 PendingEntitySubscription where T: 'static + Send, { - pub fn set_model(mut self, model: &Handle, cx: &mut AsyncAppContext) -> Subscription { + pub fn set_model(mut self, model: &Model, cx: &mut AsyncAppContext) -> Subscription { self.consumed = true; let mut state = self.client.state.write(); let id = (TypeId::of::(), self.remote_id); @@ -558,7 +558,7 @@ impl Client { where M: EnvelopedMessage, E: 'static + Send, - H: 'static + Send + Sync + Fn(Handle, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, F: 'static + Future> + Send, { let message_type_id = TypeId::of::(); @@ -600,7 +600,7 @@ impl Client { where M: RequestMessage, E: 'static + Send, - H: 'static + Send + Sync + Fn(Handle, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, F: 'static + Future> + 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, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, F: 'static + Future> + Send, { self.add_entity_message_handler::(move |subscriber, message, client, cx| { @@ -667,7 +667,7 @@ impl Client { where M: EntityMessage + RequestMessage, E: 'static + Send, - H: 'static + Send + Sync + Fn(Handle, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, F: 'static + Future> + 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, _: TypedEnvelope, _, mut cx| { + move |model: Model, _: TypedEnvelope, _, 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, _: TypedEnvelope, _, mut cx| { + move |model: Model, _: TypedEnvelope, _, 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, } diff --git a/crates/client2/src/test.rs b/crates/client2/src/test.rs index 1b32d35092..f30547dcfc 100644 --- a/crates/client2/src/test.rs +++ b/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, cx: &mut TestAppContext, - ) -> Handle { + ) -> Model { 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::() .await diff --git a/crates/client2/src/user.rs b/crates/client2/src/user.rs index 41cf46ea8f..a8be4b6401 100644 --- a/crates/client2/src/user.rs +++ b/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, + this: Model, message: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -229,7 +229,7 @@ impl UserStore { } async fn handle_show_contacts( - this: Handle, + this: Model, _: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -243,7 +243,7 @@ impl UserStore { } async fn handle_update_contacts( - this: Handle, + this: Model, message: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -690,7 +690,7 @@ impl User { impl Contact { async fn from_proto( contact: proto::Contact, - user_store: &Handle, + user_store: &Model, cx: &mut AsyncAppContext, ) -> Result { let user = user_store diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 149b01aa82..42b0e3aa41 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/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, 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: &Model, cx: &mut ModelContext, ) -> 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> { - if cx.has_global::>() { - Some(cx.global::>().clone()) + pub fn global(cx: &AppContext) -> Option> { + if cx.has_global::>() { + Some(cx.global::>().clone()) } else { None } @@ -590,7 +590,7 @@ impl Copilot { } } - pub fn register_buffer(&mut self, buffer: &Handle, cx: &mut ModelContext) { + pub fn register_buffer(&mut self, buffer: &Model, cx: &mut ModelContext) { 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: Model, event: &language2::Event, cx: &mut ModelContext, ) -> Result<()> { @@ -723,7 +723,7 @@ impl Copilot { pub fn completions( &mut self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> @@ -735,7 +735,7 @@ impl Copilot { pub fn completions_cycling( &mut self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> @@ -792,7 +792,7 @@ impl Copilot { fn request_completions( &mut self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> @@ -926,7 +926,7 @@ fn id_for_language(language: Option<&Arc>) -> String { } } -fn uri_for_buffer(buffer: &Handle, cx: &AppContext) -> lsp2::Url { +fn uri_for_buffer(buffer: &Model, 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 { diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 57ea79afbe..c4ba6a4724 100644 --- a/crates/gpui2/src/app.rs +++ b/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; /// 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( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Handle { + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Model { 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( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> R { self.update(|cx| { let mut entity = cx.entities.lease(handle); diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index d49f2ab934..71417f2a5e 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/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 = Result; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Self::Result> + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Self::Result> 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( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Self::Result { 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 = Result; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Result> + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Result> 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( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Result { self.app .update_window(self.window, |cx| cx.update_entity(handle, update)) diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index f3ae67836d..68f0a8fa48 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/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(&self) -> Slot { 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(&mut self, slot: Slot, entity: T) -> Handle + pub fn insert(&mut self, slot: Slot, entity: T) -> Model 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) -> Lease<'a, T> { + pub fn lease<'a, T>(&mut self, handle: &'a Model) -> 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(&self, handle: &Handle) -> &T { + pub fn read(&self, handle: &Model) -> &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, - pub handle: &'a Handle, + pub handle: &'a Model, entity_type: PhantomData, } @@ -143,7 +143,7 @@ impl<'a, T> Drop for Lease<'a, T> { } #[derive(Deref, DerefMut)] -pub struct Slot(Handle); +pub struct Slot(Model); pub struct AnyHandle { pub(crate) entity_id: EntityId, @@ -172,9 +172,9 @@ impl AnyHandle { } } - pub fn downcast(&self) -> Option> { + pub fn downcast(&self) -> Option> { if TypeId::of::() == self.entity_type { - Some(Handle { + Some(Model { any_handle: self.clone(), entity_type: PhantomData, }) @@ -223,8 +223,8 @@ impl Drop for AnyHandle { } } -impl From> for AnyHandle { - fn from(handle: Handle) -> Self { +impl From> for AnyHandle { + fn from(handle: Model) -> Self { handle.any_handle } } @@ -244,17 +244,17 @@ impl PartialEq for AnyHandle { impl Eq for AnyHandle {} #[derive(Deref, DerefMut)] -pub struct Handle { +pub struct Model { #[deref] #[deref_mut] any_handle: AnyHandle, entity_type: PhantomData, } -unsafe impl Send for Handle {} -unsafe impl Sync for Handle {} +unsafe impl Send for Model {} +unsafe impl Sync for Model {} -impl Handle { +impl Model { fn new(id: EntityId, entity_map: Weak>) -> Self where T: 'static, @@ -284,7 +284,7 @@ impl Handle { pub fn update( &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 where C: Context, @@ -293,7 +293,7 @@ impl Handle { } } -impl Clone for Handle { +impl Clone for Model { fn clone(&self) -> Self { Self { any_handle: self.any_handle.clone(), @@ -302,7 +302,7 @@ impl Clone for Handle { } } -impl std::fmt::Debug for Handle { +impl std::fmt::Debug for Model { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -313,21 +313,21 @@ impl std::fmt::Debug for Handle { } } -impl Hash for Handle { +impl Hash for Model { fn hash(&self, state: &mut H) { self.any_handle.hash(state); } } -impl PartialEq for Handle { +impl PartialEq for Model { fn eq(&self, other: &Self) -> bool { self.any_handle == other.any_handle } } -impl Eq for Handle {} +impl Eq for Model {} -impl PartialEq> for Handle { +impl PartialEq> for Model { fn eq(&self, other: &WeakHandle) -> bool { self.entity_id() == other.entity_id() } @@ -410,8 +410,8 @@ impl Clone for WeakHandle { } impl WeakHandle { - pub fn upgrade(&self) -> Option> { - Some(Handle { + pub fn upgrade(&self) -> Option> { + Some(Model { any_handle: self.any_handle.upgrade()?, entity_type: self.entity_type, }) @@ -427,7 +427,7 @@ impl WeakHandle { pub fn update( &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 where C: Context, @@ -455,8 +455,8 @@ impl PartialEq for WeakHandle { impl Eq for WeakHandle {} -impl PartialEq> for WeakHandle { - fn eq(&self, other: &Handle) -> bool { +impl PartialEq> for WeakHandle { + fn eq(&self, other: &Model) -> bool { self.entity_id() == other.entity_id() } } diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index effda37b73..b5f78fbc46 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/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 { + pub fn handle(&self) -> Model { 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( &mut self, - handle: &Handle, - mut on_notify: impl FnMut(&mut T, Handle, &mut ModelContext<'_, T>) + Send + 'static, + handle: &Model, + mut on_notify: impl FnMut(&mut T, Model, &mut ModelContext<'_, T>) + Send + 'static, ) -> Subscription where T: 'static + Send, @@ -65,10 +65,8 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn subscribe( &mut self, - handle: &Handle, - mut on_event: impl FnMut(&mut T, Handle, &E::Event, &mut ModelContext<'_, T>) - + Send - + 'static, + handle: &Model, + mut on_event: impl FnMut(&mut T, Model, &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( &mut self, - handle: &Handle, + handle: &Model, 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; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, U>) -> U, - ) -> Handle + build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U, + ) -> Model where U: 'static + Send, { - self.app.entity(build_entity) + self.app.build_model(build_model) } fn update_entity( &mut self, - handle: &Handle, - update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, U>) -> R, + handle: &Model, + update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R, ) -> R { self.app.update_entity(handle, update) } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 435349ca2a..dc5896fe06 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/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; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Self::Result> + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Self::Result> where T: 'static + Send, { let mut lock = self.app.lock(); - lock.entity(build_entity) + lock.build_model(build_model) } fn update_entity( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Self::Result { let mut lock = self.app.lock(); lock.update_entity(handle, update) diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index f6fa280c76..d85d1e0c74 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -70,20 +70,20 @@ use taffy::TaffyLayoutEngine; type AnyBox = Box; pub trait Context { - type EntityContext<'a, T>; + type ModelContext<'a, T>; type Result; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Self::Result> + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Self::Result> where T: 'static + Send; fn update_entity( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Self::Result; } @@ -92,7 +92,7 @@ pub trait VisualContext: Context { fn build_view( &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> where @@ -130,37 +130,37 @@ impl DerefMut for MainThread { } impl Context for MainThread { - type EntityContext<'a, T> = MainThread>; + type ModelContext<'a, T> = MainThread>; type Result = C::Result; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Self::Result> + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Self::Result> where T: 'static + Send, { - self.0.entity(|cx| { + self.0.build_model(|cx| { let cx = unsafe { mem::transmute::< - &mut C::EntityContext<'_, T>, - &mut MainThread>, + &mut C::ModelContext<'_, T>, + &mut MainThread>, >(cx) }; - build_entity(cx) + build_model(cx) }) } fn update_entity( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Self::Result { self.0.update_entity(handle, |entity, cx| { let cx = unsafe { mem::transmute::< - &mut C::EntityContext<'_, T>, - &mut MainThread>, + &mut C::ModelContext<'_, T>, + &mut MainThread>, >(cx) }; update(entity, cx) @@ -173,7 +173,7 @@ impl VisualContext for MainThread { fn build_view( &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> where @@ -188,7 +188,7 @@ impl VisualContext for MainThread { &mut MainThread>, >(cx) }; - build_entity(cx) + build_model(cx) }, render, ) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 4bb9c3d3a8..c988223fd0 100644 --- a/crates/gpui2/src/view.rs +++ b/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 { - pub(crate) state: Handle, + pub(crate) state: Model, render: Arc) -> AnyElement + Send + 'static>>, } impl View { pub fn for_handle( - state: Handle, + state: Model, render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, ) -> View where diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index fd5aba6057..073ffa56bd 100644 --- a/crates/gpui2/src/window.rs +++ b/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; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Handle + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Model 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( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + 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( &mut self, - handle: &Handle, - mut on_notify: impl FnMut(&mut V, Handle, &mut ViewContext<'_, '_, V>) + Send + 'static, + handle: &Model, + mut on_notify: impl FnMut(&mut V, Model, &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( &mut self, - handle: &Handle, - mut on_event: impl FnMut(&mut V, Handle, &E::Event, &mut ViewContext<'_, '_, V>) + handle: &Model, + mut on_event: impl FnMut(&mut V, Model, &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( &mut self, - handle: &Handle, + handle: &Model, 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; - fn entity( + fn build_model( &mut self, - build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T, - ) -> Handle + build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + ) -> Model where T: 'static + Send, { - self.window_cx.entity(build_entity) + self.window_cx.build_model(build_model) } fn update_entity( &mut self, - handle: &Handle, - update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R, + handle: &Model, + update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> R { self.window_cx.update_entity(handle, update) } @@ -1884,14 +1884,14 @@ impl VisualContext for ViewContext<'_, '_, V> { fn build_view( &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> where E: crate::Component, V2: 'static + Send, { - self.window_cx.build_view(build_entity, render) + self.window_cx.build_view(build_view, render) } fn update_view( diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index fc60f31018..d2d886dd84 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/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( &"
ˇ @@ -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#"
    <% 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, cx: &mut gpui2::TestAppContext) -> String { +fn get_tree_sexp(buffer: &Model, 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) }); diff --git a/crates/prettier2/src/prettier2.rs b/crates/prettier2/src/prettier2.rs index 874ed10b91..52e5971a80 100644 --- a/crates/prettier2/src/prettier2.rs +++ b/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: &Model, buffer_path: Option, cx: &mut AsyncAppContext, ) -> anyhow::Result { diff --git a/crates/project2/src/lsp_command.rs b/crates/project2/src/lsp_command.rs index 3e5165b079..84a6c0517c 100644 --- a/crates/project2/src/lsp_command.rs +++ b/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: ::Result, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, cx: AsyncAppContext, ) -> Result; @@ -63,8 +63,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send { async fn from_proto( message: Self::ProtoRequest, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, cx: AsyncAppContext, ) -> Result; @@ -79,8 +79,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send { async fn response_from_proto( self, message: ::Response, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, cx: AsyncAppContext, ) -> Result; @@ -180,8 +180,8 @@ impl LspCommand for PrepareRename { async fn response_from_lsp( self, message: Option, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, _: LanguageServerId, mut cx: AsyncAppContext, ) -> Result>> { @@ -215,8 +215,8 @@ impl LspCommand for PrepareRename { async fn from_proto( message: proto::PrepareRename, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -256,8 +256,8 @@ impl LspCommand for PrepareRename { async fn response_from_proto( self, message: proto::PrepareRenameResponse, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result>> { if message.can_rename { @@ -307,8 +307,8 @@ impl LspCommand for PerformRename { async fn response_from_lsp( self, message: Option, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, mut cx: AsyncAppContext, ) -> Result { @@ -343,8 +343,8 @@ impl LspCommand for PerformRename { async fn from_proto( message: proto::PerformRename, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -379,8 +379,8 @@ impl LspCommand for PerformRename { async fn response_from_proto( self, message: proto::PerformRenameResponse, - project: Handle, - _: Handle, + project: Model, + _: Model, mut cx: AsyncAppContext, ) -> Result { let message = message @@ -426,8 +426,8 @@ impl LspCommand for GetDefinition { async fn response_from_lsp( self, message: Option, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, cx: AsyncAppContext, ) -> Result> { @@ -447,8 +447,8 @@ impl LspCommand for GetDefinition { async fn from_proto( message: proto::GetDefinition, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -479,8 +479,8 @@ impl LspCommand for GetDefinition { async fn response_from_proto( self, message: proto::GetDefinitionResponse, - project: Handle, - _: Handle, + project: Model, + _: Model, cx: AsyncAppContext, ) -> Result> { 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, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, cx: AsyncAppContext, ) -> Result> { @@ -548,8 +548,8 @@ impl LspCommand for GetTypeDefinition { async fn from_proto( message: proto::GetTypeDefinition, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -580,8 +580,8 @@ impl LspCommand for GetTypeDefinition { async fn response_from_proto( self, message: proto::GetTypeDefinitionResponse, - project: Handle, - _: Handle, + project: Model, + _: Model, cx: AsyncAppContext, ) -> Result> { location_links_from_proto(message.links, project, cx).await @@ -593,8 +593,8 @@ impl LspCommand for GetTypeDefinition { } fn language_server_for_buffer( - project: &Handle, - buffer: &Handle, + project: &Model, + buffer: &Model, server_id: LanguageServerId, cx: &mut AsyncAppContext, ) -> Result<(Arc, Arc)> { @@ -609,7 +609,7 @@ fn language_server_for_buffer( async fn location_links_from_proto( proto_links: Vec, - project: Handle, + project: Model, mut cx: AsyncAppContext, ) -> Result> { let mut links = Vec::new(); @@ -671,8 +671,8 @@ async fn location_links_from_proto( async fn location_links_from_lsp( message: Option, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, mut cx: AsyncAppContext, ) -> Result> { @@ -814,8 +814,8 @@ impl LspCommand for GetReferences { async fn response_from_lsp( self, locations: Option>, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, mut cx: AsyncAppContext, ) -> Result> { @@ -868,8 +868,8 @@ impl LspCommand for GetReferences { async fn from_proto( message: proto::GetReferences, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -910,8 +910,8 @@ impl LspCommand for GetReferences { async fn response_from_proto( self, message: proto::GetReferencesResponse, - project: Handle, - _: Handle, + project: Model, + _: Model, mut cx: AsyncAppContext, ) -> Result> { let mut locations = Vec::new(); @@ -977,8 +977,8 @@ impl LspCommand for GetDocumentHighlights { async fn response_from_lsp( self, lsp_highlights: Option>, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, _: LanguageServerId, mut cx: AsyncAppContext, ) -> Result> { @@ -1016,8 +1016,8 @@ impl LspCommand for GetDocumentHighlights { async fn from_proto( message: proto::GetDocumentHighlights, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -1060,8 +1060,8 @@ impl LspCommand for GetDocumentHighlights { async fn response_from_proto( self, message: proto::GetDocumentHighlightsResponse, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result> { let mut highlights = Vec::new(); @@ -1123,8 +1123,8 @@ impl LspCommand for GetHover { async fn response_from_lsp( self, message: Option, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, _: LanguageServerId, mut cx: AsyncAppContext, ) -> Result { @@ -1206,8 +1206,8 @@ impl LspCommand for GetHover { async fn from_proto( message: Self::ProtoRequest, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -1272,8 +1272,8 @@ impl LspCommand for GetHover { async fn response_from_proto( self, message: proto::GetHoverResponse, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let contents: Vec<_> = message @@ -1341,8 +1341,8 @@ impl LspCommand for GetCompletions { async fn response_from_lsp( self, completions: Option, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, server_id: LanguageServerId, mut cx: AsyncAppContext, ) -> Result> { @@ -1484,8 +1484,8 @@ impl LspCommand for GetCompletions { async fn from_proto( message: proto::GetCompletions, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let version = deserialize_version(&message.version); @@ -1523,8 +1523,8 @@ impl LspCommand for GetCompletions { async fn response_from_proto( self, message: proto::GetCompletionsResponse, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result> { buffer @@ -1589,8 +1589,8 @@ impl LspCommand for GetCodeActions { async fn response_from_lsp( self, actions: Option, - _: Handle, - _: Handle, + _: Model, + _: Model, server_id: LanguageServerId, _: AsyncAppContext, ) -> Result> { @@ -1623,8 +1623,8 @@ impl LspCommand for GetCodeActions { async fn from_proto( message: proto::GetCodeActions, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let start = message @@ -1663,8 +1663,8 @@ impl LspCommand for GetCodeActions { async fn response_from_proto( self, message: proto::GetCodeActionsResponse, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result> { buffer @@ -1726,8 +1726,8 @@ impl LspCommand for OnTypeFormatting { async fn response_from_lsp( self, message: Option>, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, mut cx: AsyncAppContext, ) -> Result> { @@ -1763,8 +1763,8 @@ impl LspCommand for OnTypeFormatting { async fn from_proto( message: proto::OnTypeFormatting, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let position = message @@ -1805,8 +1805,8 @@ impl LspCommand for OnTypeFormatting { async fn response_from_proto( self, message: proto::OnTypeFormattingResponse, - _: Handle, - _: Handle, + _: Model, + _: Model, _: AsyncAppContext, ) -> Result> { 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_handle: &Model, 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>, - project: Handle, - buffer: Handle, + project: Model, + buffer: Model, server_id: LanguageServerId, mut cx: AsyncAppContext, ) -> anyhow::Result> { @@ -2286,8 +2286,8 @@ impl LspCommand for InlayHints { async fn from_proto( message: proto::InlayHints, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> Result { let start = message @@ -2326,8 +2326,8 @@ impl LspCommand for InlayHints { async fn response_from_proto( self, message: proto::InlayHintsResponse, - _: Handle, - buffer: Handle, + _: Model, + buffer: Model, mut cx: AsyncAppContext, ) -> anyhow::Result> { buffer diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 9a3c2b34f2..e91c3b7263 100644 --- a/crates/project2/src/project2.rs +++ b/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, join_project_response_message_id: u32, next_diagnostic_group_id: usize, - user_store: Handle, + user_store: Model, fs: Arc, client_state: Option, collaborators: HashMap, @@ -140,17 +140,17 @@ pub struct Project { #[allow(clippy::type_complexity)] loading_buffers_by_path: HashMap< ProjectPath, - postage::watch::Receiver, Arc>>>, + postage::watch::Receiver, Arc>>>, >, #[allow(clippy::type_complexity)] loading_local_worktrees: - HashMap, Shared, Arc>>>>, + HashMap, Shared, Arc>>>>, opened_buffers: HashMap, local_buffer_ids_by_path: HashMap, local_buffer_ids_by_entry_id: HashMap, /// 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>>, + incomplete_remote_buffers: HashMap>>, buffer_snapshots: HashMap>>, // buffer_id -> server_id -> vec of snapshots buffers_being_formatted: HashSet, buffers_needing_diff: HashSet>, @@ -244,14 +244,14 @@ enum LocalProjectUpdate { } enum OpenBuffer { - Strong(Handle), + Strong(Model), Weak(WeakHandle), Operations(Vec), } #[derive(Clone)] enum WorktreeHandle { - Strong(Handle), + Strong(Model), Weak(WeakHandle), } @@ -344,7 +344,7 @@ pub struct DiagnosticSummary { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Location { - pub buffer: Handle, + pub buffer: Model, pub range: Range, } @@ -457,7 +457,7 @@ impl Hover { } #[derive(Default)] -pub struct ProjectTransaction(pub HashMap, language2::Transaction>); +pub struct ProjectTransaction(pub HashMap, language2::Transaction>); impl DiagnosticSummary { fn new<'a, T: 'a>(diagnostics: impl IntoIterator>) -> Self { @@ -527,7 +527,7 @@ pub enum FormatTrigger { } struct ProjectLspAdapterDelegate { - project: Handle, + project: Model, http_client: Arc, } @@ -543,7 +543,7 @@ impl FormatTrigger { #[derive(Clone, Debug, PartialEq)] enum SearchMatchCandidate { OpenBuffer { - buffer: Handle, + buffer: Model, // This might be an unnamed file without representation on filesystem path: Option>, }, @@ -621,12 +621,12 @@ impl Project { pub fn local( client: Arc, node: Arc, - user_store: Handle, + user_store: Model, languages: Arc, fs: Arc, cx: &mut AppContext, - ) -> Handle { - cx.entity(|cx: &mut ModelContext| { + ) -> Model { + cx.build_model(|cx: &mut ModelContext| { 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, - user_store: Handle, + user_store: Model, languages: Arc, fs: Arc, mut cx: AsyncAppContext, - ) -> Result> { + ) -> Result> { 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> { + pub fn buffer_for_id(&self, remote_id: u64) -> Option> { 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 { + pub fn user_store(&self) -> Model { self.user_store.clone() } - pub fn opened_buffers(&self) -> Vec> { + pub fn opened_buffers(&self) -> Vec> { 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> { + pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator> { 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> { + ) -> impl 'a + DoubleEndedIterator> { 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> { + pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option> { self.worktrees() .find(|worktree| worktree.read(cx).id() == id) } @@ -1097,7 +1097,7 @@ impl Project { &self, entry_id: ProjectEntryId, cx: &AppContext, - ) -> Option> { + ) -> Option> { self.worktrees() .find(|worktree| worktree.read(cx).contains_entry(entry_id)) } @@ -1652,12 +1652,12 @@ impl Project { text: &str, language: Option>, cx: &mut ModelContext, - ) -> Result> { + ) -> Result> { 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, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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, - worktree: &Handle, + worktree: &Model, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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, - worktree: &Handle, + worktree: &Model, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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, - ) -> Task>> { + ) -> Task>> { 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, - ) -> Task>> { + ) -> Task>> { 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>, + buffers: HashSet>, cx: &mut ModelContext, ) -> Task> { cx.spawn(move |this, mut cx| async move { @@ -1881,7 +1881,7 @@ impl Project { pub fn save_buffer( &self, - buffer: Handle, + buffer: Model, cx: &mut ModelContext, ) -> Task> { 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: Model, abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { @@ -1933,7 +1933,7 @@ impl Project { &mut self, path: &ProjectPath, cx: &mut ModelContext, - ) -> Option> { + ) -> Option> { 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: &Model, cx: &mut ModelContext, ) -> 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_handle: &Model, cx: &mut ModelContext, ) { let buffer = buffer_handle.read(cx); @@ -2114,7 +2114,7 @@ impl Project { fn unregister_buffer_from_language_servers( &mut self, - buffer: &Handle, + buffer: &Model, old_file: &File, cx: &mut ModelContext, ) { @@ -2149,7 +2149,7 @@ impl Project { fn register_buffer_with_copilot( &self, - buffer_handle: &Handle, + buffer_handle: &Model, cx: &mut ModelContext, ) { if let Some(copilot) = Copilot::global(cx) { @@ -2267,7 +2267,7 @@ impl Project { fn on_buffer_event( &mut self, - buffer: Handle, + buffer: Model, event: &BufferEvent, cx: &mut ModelContext, ) -> Option<()> { @@ -2480,7 +2480,7 @@ impl Project { fn request_buffer_diff_recalculation( &mut self, - buffer: &Handle, + buffer: &Model, cx: &mut ModelContext, ) { self.buffers_needing_diff.insert(buffer.downgrade()); @@ -2676,7 +2676,7 @@ impl Project { fn detect_language_for_buffer( &mut self, - buffer_handle: &Handle, + buffer_handle: &Model, cx: &mut ModelContext, ) -> 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: &Model, new_language: Arc, cx: &mut ModelContext, ) { @@ -2735,7 +2735,7 @@ impl Project { fn start_language_servers( &mut self, - worktree: &Handle, + worktree: &Model, worktree_path: Arc, language: Arc, cx: &mut ModelContext, @@ -3350,10 +3350,10 @@ impl Project { pub fn restart_language_servers_for_buffers( &mut self, - buffers: impl IntoIterator>, + buffers: impl IntoIterator>, cx: &mut ModelContext, ) -> Option<()> { - let language_server_lookup_info: HashSet<(Handle, Arc)> = buffers + let language_server_lookup_info: HashSet<(Model, Arc)> = 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: Model, language: Arc, cx: &mut ModelContext, ) { @@ -3949,7 +3949,7 @@ impl Project { fn update_buffer_diagnostics( &mut self, - buffer: &Handle, + buffer: &Model, server_id: LanguageServerId, version: Option, mut diagnostics: Vec>>, @@ -4022,7 +4022,7 @@ impl Project { pub fn reload_buffers( &self, - buffers: HashSet>, + buffers: HashSet>, push_to_history: bool, cx: &mut ModelContext, ) -> Task> { @@ -4088,7 +4088,7 @@ impl Project { pub fn format( &self, - buffers: HashSet>, + buffers: HashSet>, push_to_history: bool, trigger: FormatTrigger, cx: &mut ModelContext, @@ -4361,7 +4361,7 @@ impl Project { async fn format_via_lsp( this: &WeakHandle, - buffer: &Handle, + buffer: &Model, abs_path: &Path, language_server: &Arc, tab_size: NonZeroU32, @@ -4410,7 +4410,7 @@ impl Project { } async fn format_via_external_command( - buffer: &Handle, + buffer: &Model, buffer_abs_path: &Path, command: &str, arguments: &[String], @@ -4470,7 +4470,7 @@ impl Project { pub fn definition( &self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> { @@ -4485,7 +4485,7 @@ impl Project { pub fn type_definition( &self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> { @@ -4500,7 +4500,7 @@ impl Project { pub fn references( &self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> { @@ -4515,7 +4515,7 @@ impl Project { pub fn document_highlights( &self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> { @@ -4694,7 +4694,7 @@ impl Project { &mut self, symbol: &Symbol, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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( &self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> { @@ -4763,7 +4763,7 @@ impl Project { pub fn completions( &self, - buffer: &Handle, + buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> { @@ -4817,7 +4817,7 @@ impl Project { pub fn apply_additional_edits_for_completion( &self, - buffer_handle: Handle, + buffer_handle: Model, completion: Completion, push_to_history: bool, cx: &mut ModelContext, @@ -4928,7 +4928,7 @@ impl Project { pub fn code_actions( &self, - buffer_handle: &Handle, + buffer_handle: &Model, range: Range, cx: &mut ModelContext, ) -> Task>> { @@ -4944,7 +4944,7 @@ impl Project { pub fn apply_code_action( &self, - buffer_handle: Handle, + buffer_handle: Model, mut action: CodeAction, push_to_history: bool, cx: &mut ModelContext, @@ -5052,7 +5052,7 @@ impl Project { fn apply_on_type_formatting( &self, - buffer: Handle, + buffer: Model, position: Anchor, trigger: String, cx: &mut ModelContext, @@ -5113,8 +5113,8 @@ impl Project { } async fn deserialize_edits( - this: Handle, - buffer_to_edit: Handle, + this: Model, + buffer_to_edit: Model, edits: Vec, push_to_history: bool, _: Arc, @@ -5155,7 +5155,7 @@ impl Project { } async fn deserialize_workspace_edit( - this: Handle, + this: Model, edit: lsp2::WorkspaceEdit, push_to_history: bool, lsp_adapter: Arc, @@ -5310,7 +5310,7 @@ impl Project { pub fn prepare_rename( &self, - buffer: Handle, + buffer: Model, position: T, cx: &mut ModelContext, ) -> Task>>> { @@ -5325,7 +5325,7 @@ impl Project { pub fn perform_rename( &self, - buffer: Handle, + buffer: Model, position: T, new_name: String, push_to_history: bool, @@ -5346,7 +5346,7 @@ impl Project { pub fn on_type_format( &self, - buffer: Handle, + buffer: Model, position: T, trigger: String, push_to_history: bool, @@ -5375,7 +5375,7 @@ impl Project { pub fn inlay_hints( &self, - buffer_handle: Handle, + buffer_handle: Model, range: Range, cx: &mut ModelContext, ) -> Task>> { @@ -5436,7 +5436,7 @@ impl Project { pub fn resolve_inlay_hint( &self, hint: InlayHint, - buffer_handle: Handle, + buffer_handle: Model, server_id: LanguageServerId, cx: &mut ModelContext, ) -> Task> { @@ -5501,7 +5501,7 @@ impl Project { &self, query: SearchQuery, cx: &mut ModelContext, - ) -> Receiver<(Handle, Vec>)> { + ) -> Receiver<(Model, Vec>)> { 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, - ) -> Receiver<(Handle, Vec>)> { + ) -> Receiver<(Model, Vec>)> { // 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, Vec>)>, + entry: Option<(Model, Vec>)>, 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>, - opened_buffers: HashMap, (Handle, BufferSnapshot)>, + unnamed_buffers: Vec>, + opened_buffers: HashMap, (Model, BufferSnapshot)>, executor: Executor, fs: Arc, workers: usize, @@ -5829,7 +5829,7 @@ impl Project { fn request_lsp( &self, - buffer_handle: Handle, + buffer_handle: Model, server: LanguageServerToQuery, request: R, cx: &mut ModelContext, @@ -5893,7 +5893,7 @@ impl Project { fn send_lsp_proto_request( &self, - buffer: Handle, + buffer: Model, project_id: u64, request: R, cx: &mut ModelContext<'_, Project>, @@ -5922,7 +5922,7 @@ impl Project { ) -> ( futures::channel::oneshot::Receiver>, Receiver<( - Option<(Handle, BufferSnapshot)>, + Option<(Model, BufferSnapshot)>, SearchMatchCandidateIndex, )>, ) { @@ -5976,7 +5976,7 @@ impl Project { abs_path: impl AsRef, visible: bool, cx: &mut ModelContext, - ) -> Task, PathBuf)>> { + ) -> Task, 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, PathBuf)> { + ) -> Option<(Model, 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, visible: bool, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { 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, cx: &mut ModelContext) { + fn add_worktree(&mut self, worktree: &Model, cx: &mut ModelContext) { 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_handle: &Model, changes: &[(Arc, ProjectEntryId, PathChange)], cx: &mut ModelContext, ) { @@ -6242,7 +6242,7 @@ impl Project { fn update_local_worktree_language_servers( &mut self, - worktree_handle: &Handle, + worktree_handle: &Model, changes: &[(Arc, ProjectEntryId, PathChange)], cx: &mut ModelContext, ) { @@ -6304,7 +6304,7 @@ impl Project { fn update_local_worktree_buffers_git_repos( &mut self, - worktree_handle: Handle, + worktree_handle: Model, changed_repos: &UpdatedGitRepositoriesSet, cx: &mut ModelContext, ) { @@ -6407,7 +6407,7 @@ impl Project { fn update_local_worktree_settings( &mut self, - worktree: &Handle, + worktree: &Model, changes: &UpdatedEntriesSet, cx: &mut ModelContext, ) { @@ -6473,7 +6473,7 @@ impl Project { fn update_prettier_settings( &self, - worktree: &Handle, + worktree: &Model, changes: &[(Arc, ProjectEntryId, PathChange)], cx: &mut ModelContext<'_, Project>, ) { @@ -6636,7 +6636,7 @@ impl Project { // RPC message handlers async fn handle_unshare_project( - this: Handle, + this: Model, _: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6652,7 +6652,7 @@ impl Project { } async fn handle_add_collaborator( - this: Handle, + this: Model, mut envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6676,7 +6676,7 @@ impl Project { } async fn handle_update_project_collaborator( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6726,7 +6726,7 @@ impl Project { } async fn handle_remove_collaborator( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6755,7 +6755,7 @@ impl Project { } async fn handle_update_project( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6770,7 +6770,7 @@ impl Project { } async fn handle_update_worktree( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6788,7 +6788,7 @@ impl Project { } async fn handle_update_worktree_settings( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6812,7 +6812,7 @@ impl Project { } async fn handle_create_project_entry( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6837,7 +6837,7 @@ impl Project { } async fn handle_rename_project_entry( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6865,7 +6865,7 @@ impl Project { } async fn handle_copy_project_entry( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6893,7 +6893,7 @@ impl Project { } async fn handle_delete_project_entry( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6923,7 +6923,7 @@ impl Project { } async fn handle_expand_project_entry( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6946,7 +6946,7 @@ impl Project { } async fn handle_update_diagnostic_summary( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -6976,7 +6976,7 @@ impl Project { } async fn handle_start_language_server( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7001,7 +7001,7 @@ impl Project { } async fn handle_update_language_server( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7058,7 +7058,7 @@ impl Project { } async fn handle_update_buffer( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7094,7 +7094,7 @@ impl Project { } async fn handle_create_buffer_for_peer( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, 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, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7180,7 +7180,7 @@ impl Project { } async fn handle_update_buffer_file( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7215,7 +7215,7 @@ impl Project { } async fn handle_save_buffer( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7251,7 +7251,7 @@ impl Project { } async fn handle_reload_buffers( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7280,7 +7280,7 @@ impl Project { } async fn handle_synchronize_buffers( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7373,7 +7373,7 @@ impl Project { } async fn handle_format_buffers( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7403,7 +7403,7 @@ impl Project { } async fn handle_apply_additional_edits_for_completion( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7440,7 +7440,7 @@ impl Project { } async fn handle_apply_code_action( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7471,7 +7471,7 @@ impl Project { } async fn handle_on_type_formatting( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7503,7 +7503,7 @@ impl Project { } async fn handle_inlay_hints( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7553,7 +7553,7 @@ impl Project { } async fn handle_resolve_inlay_hint( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7587,7 +7587,7 @@ impl Project { } async fn handle_refresh_inlay_hints( - this: Handle, + this: Model, _: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7599,7 +7599,7 @@ impl Project { } async fn handle_lsp_command( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7641,7 +7641,7 @@ impl Project { } async fn handle_get_project_symbols( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7658,7 +7658,7 @@ impl Project { } async fn handle_search_project( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7689,7 +7689,7 @@ impl Project { } async fn handle_open_buffer_for_symbol( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7730,7 +7730,7 @@ impl Project { } async fn handle_open_buffer_by_id( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7749,7 +7749,7 @@ impl Project { } async fn handle_open_buffer_by_path( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -7834,7 +7834,7 @@ impl Project { fn create_buffer_for_peer( &mut self, - buffer: &Handle, + buffer: &Model, peer_id: proto::PeerId, cx: &mut AppContext, ) -> u64 { @@ -7851,7 +7851,7 @@ impl Project { &mut self, id: u64, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { let mut opened_buffer_rx = self.opened_buffer.1.clone(); cx.spawn(move |this, mut cx| async move { @@ -8108,7 +8108,7 @@ impl Project { } async fn handle_buffer_saved( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -8141,7 +8141,7 @@ impl Project { } async fn handle_buffer_reloaded( - this: Handle, + this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, @@ -8180,7 +8180,7 @@ impl Project { #[allow(clippy::type_complexity)] fn edits_from_lsp( &mut self, - buffer: &Handle, + buffer: &Model, lsp_edits: impl 'static + Send + IntoIterator, server_id: LanguageServerId, version: Option, @@ -8284,7 +8284,7 @@ impl Project { fn buffer_snapshot_for_lsp_version( &mut self, - buffer: &Handle, + buffer: &Model, server_id: LanguageServerId, version: Option, cx: &AppContext, @@ -8402,7 +8402,7 @@ impl Project { fn prettier_instance_for_buffer( &mut self, - buffer: &Handle, + buffer: &Model, cx: &mut ModelContext, ) -> Task, Arc>>>>> { let buffer = buffer.read(cx); @@ -8638,7 +8638,7 @@ impl Project { } fn subscribe_for_copilot_events( - copilot: &Handle, + copilot: &Model, cx: &mut ModelContext<'_, Project>, ) -> gpui2::Subscription { cx.subscribe( @@ -8691,7 +8691,7 @@ fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str { } impl WorktreeHandle { - pub fn upgrade(&self) -> Option> { + pub fn upgrade(&self) -> Option> { match self { WorktreeHandle::Strong(handle) => Some(handle.clone()), WorktreeHandle::Weak(handle) => handle.upgrade(), @@ -8707,7 +8707,7 @@ impl WorktreeHandle { } impl OpenBuffer { - pub fn upgrade(&self) -> Option> { + pub fn upgrade(&self) -> Option> { match self { OpenBuffer::Strong(handle) => Some(handle.clone()), OpenBuffer::Weak(handle) => handle.upgrade(), @@ -8871,8 +8871,8 @@ impl Item for Buffer { } async fn wait_for_loading_buffer( - mut receiver: postage::watch::Receiver, Arc>>>, -) -> Result, Arc> { + mut receiver: postage::watch::Receiver, Arc>>>, +) -> Result, Arc> { loop { if let Some(result) = receiver.borrow().as_ref() { match result { diff --git a/crates/project2/src/terminals.rs b/crates/project2/src/terminals.rs index f958142437..239cb99d86 100644 --- a/crates/project2/src/terminals.rs +++ b/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, window: AnyWindowHandle, cx: &mut ModelContext, - ) -> anyhow::Result> { + ) -> anyhow::Result> { 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, - terminal_handle: &Handle, + terminal_handle: &Model, cx: &mut ModelContext, ) { if let Some(activate_script) = activate_script { diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index c1b762640b..00ab10d8e8 100644 --- a/crates/project2/src/worktree.rs +++ b/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, next_entry_id: Arc, cx: &mut AsyncAppContext, - ) -> Result> { + ) -> Result> { // 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| { + cx.build_model(move |cx: &mut ModelContext| { 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, cx: &mut AppContext, - ) -> Handle { - cx.entity(|cx: &mut ModelContext| { + ) -> Model { + cx.build_model(|cx: &mut ModelContext| { 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, - ) -> Task>> { + ) -> Task>> { 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_handle: Model, path: Arc, has_changed_file: bool, cx: &mut ModelContext, @@ -1331,7 +1331,7 @@ impl RemoteWorktree { pub fn save_buffer( &self, - buffer_handle: Handle, + buffer_handle: Model, cx: &mut ModelContext, ) -> Task> { let buffer = buffer_handle.read(cx); @@ -2577,7 +2577,7 @@ impl fmt::Debug for Snapshot { #[derive(Clone, PartialEq)] pub struct File { - pub worktree: Handle, + pub worktree: Model, pub path: Arc, 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) -> Arc { + pub fn for_entry(entry: Entry, worktree: Model) -> Arc { 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: Model, cx: &AppContext, ) -> Result { let worktree_id = worktree diff --git a/crates/storybook2/src/stories/kitchen_sink.rs b/crates/storybook2/src/stories/kitchen_sink.rs index 3a4b127b55..406eb33853 100644 --- a/crates/storybook2/src/stories/kitchen_sink.rs +++ b/crates/storybook2/src/stories/kitchen_sink.rs @@ -14,7 +14,7 @@ impl KitchenSinkStory { pub fn view(cx: &mut AppContext) -> View { { - let state = cx.entity(|cx| Self::new()); + let state = cx.build_model(|cx| Self::new()); let render = Self::render; View::for_handle(state, render) } diff --git a/crates/ui2/src/components/buffer_search.rs b/crates/ui2/src/components/buffer_search.rs index fa7f752ffe..85a74930ce 100644 --- a/crates/ui2/src/components/buffer_search.rs +++ b/crates/ui2/src/components/buffer_search.rs @@ -23,7 +23,7 @@ impl BufferSearch { pub fn view(cx: &mut AppContext) -> View { { - let state = cx.entity(|cx| Self::new()); + let state = cx.build_model(|cx| Self::new()); let render = Self::render; View::for_handle(state, render) } diff --git a/crates/ui2/src/components/editor_pane.rs b/crates/ui2/src/components/editor_pane.rs index ec73bb805f..33f9051dc9 100644 --- a/crates/ui2/src/components/editor_pane.rs +++ b/crates/ui2/src/components/editor_pane.rs @@ -44,7 +44,7 @@ impl EditorPane { pub fn view(cx: &mut AppContext) -> View { { - 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) } diff --git a/crates/ui2/src/components/title_bar.rs b/crates/ui2/src/components/title_bar.rs index 11dd1b99f6..29622d1395 100644 --- a/crates/ui2/src/components/title_bar.rs +++ b/crates/ui2/src/components/title_bar.rs @@ -82,7 +82,7 @@ impl TitleBar { pub fn view(cx: &mut AppContext, livestream: Option) -> View { { - 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 { { - let state = cx.entity(|cx| Self { + let state = cx.build_model(|cx| Self { title_bar: TitleBar::view(cx, None), }); let render = Self::render; diff --git a/crates/ui2/src/components/workspace.rs b/crates/ui2/src/components/workspace.rs index af2485a7a9..f2314fb377 100644 --- a/crates/ui2/src/components/workspace.rs +++ b/crates/ui2/src/components/workspace.rs @@ -3,12 +3,13 @@ use std::sync::Arc; use chrono::DateTime; use gpui2::{px, relative, rems, AppContext, Context, Size, View}; -use crate::{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}; +use crate::{ + 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, +}; #[derive(Clone)] pub struct Gpui2UiDebug { @@ -171,7 +172,7 @@ impl Workspace { pub fn view(cx: &mut AppContext) -> View { { - 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) } diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs new file mode 100644 index 0000000000..90d08d6c4a --- /dev/null +++ b/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, +// close_position: Option, +// } + +// 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::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, HighlightStyle)>>, +} + +pub trait Item: EventEmitter + Sized { + // fn deactivated(&mut self, _: &mut ViewContext) {} + // fn workspace_deactivated(&mut self, _: &mut ViewContext) {} + // fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { + // false + // } + fn tab_tooltip_text(&self, _: &AppContext) -> Option { + None + } + fn tab_description(&self, _: usize, _: &AppContext) -> Option { + None + } + fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; + + 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) {} + fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option + 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, + // _cx: &mut ViewContext, + // ) -> Task> { + // unimplemented!("save() must be implemented if can_save() returns true") + // } + // fn save_as( + // &mut self, + // _project: Handle, + // _abs_path: PathBuf, + // _cx: &mut ViewContext, + // ) -> Task> { + // unimplemented!("save_as() must be implemented if can_save() returns true") + // } + // fn reload( + // &mut self, + // _project: Handle, + // _cx: &mut ViewContext, + // ) -> Task> { + // 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, + // _: &'a AppContext, + // ) -> Option<&AnyViewHandle> { + // if TypeId::of::() == type_id { + // Some(self_handle) + // } else { + // None + // } + // } + + // fn as_searchable(&self, _: &View) -> Option> { + // None + // } + + // fn breadcrumb_location(&self) -> ToolbarItemLocation { + // ToolbarItemLocation::Hidden + // } + + // fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { + // None + // } + + // fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} + + // fn serialized_item_kind() -> Option<&'static str> { + // None + // } + + // fn deserialize( + // _project: Handle, + // _workspace: WeakViewHandle, + // _workspace_id: WorkspaceId, + // _item_id: ItemId, + // _cx: &mut ViewContext, + // ) -> Task>> { + // 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 { + // 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, + ) -> gpui2::Subscription; + fn tab_tooltip_text(&self, cx: &AppContext) -> Option; + fn tab_description(&self, detail: usize, cx: &AppContext) -> Option; + fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; + fn dragged_tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; + fn project_path(&self, cx: &AppContext) -> Option; + 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; + fn clone_on_split( + &self, + workspace_id: WorkspaceId, + cx: &mut WindowContext, + ) -> Option>; + fn added_to_pane( + &self, + workspace: &mut Workspace, + pane: View, + cx: &mut ViewContext, + ); + fn deactivated(&self, cx: &mut WindowContext); + fn workspace_deactivated(&self, cx: &mut WindowContext); + fn navigate(&self, data: Box, 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, cx: &mut WindowContext) -> Task>; + fn save_as( + &self, + project: Handle, + abs_path: PathBuf, + cx: &mut WindowContext, + ) -> Task>; + fn reload(&self, project: Handle, cx: &mut WindowContext) -> Task>; + // 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>; + fn on_release( + &self, + cx: &mut AppContext, + callback: Box, + ) -> gpui2::Subscription; + fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; + fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; + fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; + fn serialized_item_kind(&self) -> Option<&'static str>; + fn show_toolbar(&self, cx: &AppContext) -> bool; + fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option>; +} + +pub trait WeakItemHandle { + fn id(&self) -> usize; + fn window(&self) -> AnyWindowHandle; + fn upgrade(&self, cx: &AppContext) -> Option>; +} + +// todo!() +// impl dyn ItemHandle { +// pub fn downcast(&self) -> Option> { +// self.as_any().clone().downcast() +// } + +// pub fn act_as(&self, cx: &AppContext) -> Option> { +// self.act_as_type(TypeId::of::(), cx) +// .and_then(|t| t.clone().downcast()) +// } +// } + +impl ItemHandle for View { + fn subscribe_to_item_events( + &self, + cx: &mut WindowContext, + handler: Box, + ) -> 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 { + self.read(cx).tab_tooltip_text(cx) + } + + fn tab_description(&self, detail: usize, cx: &AppContext) -> Option { + self.read(cx).tab_description(detail, cx) + } + + fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement { + self.read(cx).tab_content(detail, cx) + } + + fn dragged_tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement { + self.read(cx).tab_content(detail, cx) + } + + fn project_path(&self, cx: &AppContext) -> Option { + 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 { + Box::new(self.clone()) + } + + fn clone_on_split( + &self, + workspace_id: WorkspaceId, + cx: &mut WindowContext, + ) -> Option> { + self.update(cx, |item, cx| { + cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx)) + }) + .map(|handle| Box::new(handle) as Box) + } + + fn added_to_pane( + &self, + workspace: &mut Workspace, + pane: View, + cx: &mut ViewContext, + ) { + 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, 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, cx: &mut WindowContext) -> Task> { + self.update(cx, |item, cx| item.save(project, cx)) + } + + fn save_as( + &self, + project: Handle, + abs_path: PathBuf, + cx: &mut WindowContext, + ) -> Task> { + self.update(cx, |item, cx| item.save_as(project, abs_path, cx)) + } + + fn reload(&self, project: Handle, cx: &mut WindowContext) -> Task> { + 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> { + if cx.has_global::() { + let builders = cx.global::(); + let item = self.as_any(); + Some(builders.get(&item.view_type())?.1(item)) + } else { + None + } + } + + fn on_release( + &self, + cx: &mut AppContext, + callback: Box, + ) -> gpui2::Subscription { + cx.observe_release(self, move |_, cx| callback(cx)) + } + + fn to_searchable_item_handle(&self, cx: &AppContext) -> Option> { + 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> { + 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> { + self.read(cx).pixel_position_of_cursor(cx) + } +} + +// impl From> for AnyViewHandle { +// fn from(val: Box) -> Self { +// val.as_any().clone() +// } +// } + +// impl From<&Box> for AnyViewHandle { +// fn from(val: &Box) -> Self { +// val.as_any().clone() +// } +// } + +impl Clone for Box { + fn clone(&self) -> Box { + self.boxed_clone() + } +} + +// impl WeakItemHandle for WeakViewHandle { +// fn id(&self) -> usize { +// self.id() +// } + +// fn window(&self) -> AnyWindowHandle { +// self.window() +// } + +// fn upgrade(&self, cx: &AppContext) -> Option> { +// self.upgrade(cx).map(|v| Box::new(v) as Box) +// } +// } + +pub trait ProjectItem: Item { + type Item: project2::Item; + + fn for_project_item( + project: Handle, + item: Handle, + cx: &mut ViewContext, + ) -> Self + where + Self: Sized; +} + +pub trait FollowableItem: Item { + fn remote_id(&self) -> Option; + fn to_state_proto(&self, cx: &AppContext) -> Option; + fn from_state_proto( + pane: View, + project: View, + id: ViewId, + state: &mut Option, + cx: &mut AppContext, + ) -> Option>>>; + fn add_event_to_update_proto( + &self, + event: &Self::Event, + update: &mut Option, + cx: &AppContext, + ) -> bool; + fn apply_update_proto( + &mut self, + project: &Handle, + message: proto::update_view::Variant, + cx: &mut ViewContext, + ) -> Task>; + fn is_project_item(&self, cx: &AppContext) -> bool; + + fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext); + fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool; +} + +pub trait FollowableItemHandle: ItemHandle { + fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; + fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); + fn to_state_proto(&self, cx: &AppContext) -> Option; + fn add_event_to_update_proto( + &self, + event: &dyn Any, + update: &mut Option, + cx: &AppContext, + ) -> bool; + fn apply_update_proto( + &self, + project: &Handle, + message: proto::update_view::Variant, + cx: &mut WindowContext, + ) -> Task>; + fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool; + fn is_project_item(&self, cx: &AppContext) -> bool; +} + +// impl FollowableItemHandle for View { +// fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option { +// 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, 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 { +// self.read(cx).to_state_proto(cx) +// } + +// fn add_event_to_update_proto( +// &self, +// event: &dyn Any, +// update: &mut Option, +// 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, +// message: proto::update_view::Variant, +// cx: &mut WindowContext, +// ) -> Task> { +// 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, +// pub project_path: Option, +// } + +// 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>, +// pub nav_history: Option, +// pub tab_descriptions: Option>, +// pub tab_detail: Cell>, +// } + +// impl Entity for TestProjectItem { +// type Event = (); +// } + +// impl project2::Item for TestProjectItem { +// fn entry_id(&self, _: &AppContext) -> Option { +// self.entry_id +// } + +// fn project_path(&self, _: &AppContext) -> Option { +// 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 { +// 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 { +// 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]) -> 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.push_to_nav_history(cx); +// self.state = state; +// } + +// fn push_to_nav_history(&mut self, cx: &mut ViewContext) { +// 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) -> AnyElement { +// Empty::new().into_any() +// } +// } + +// impl Item for TestItem { +// fn tab_description(&self, detail: usize, _: &AppContext) -> Option> { +// self.tab_descriptions.as_ref().and_then(|descriptions| { +// let description = *descriptions.get(detail).or_else(|| descriptions.last())?; +// Some(description.into()) +// }) +// } + +// fn tab_content( +// &self, +// detail: Option, +// _: &theme2::Tab, +// _: &AppContext, +// ) -> AnyElement { +// 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.nav_history = Some(history); +// } + +// fn navigate(&mut self, state: Box, _: &mut ViewContext) -> bool { +// let state = *state.downcast::().unwrap_or_default(); +// if state != self.state { +// self.state = state; +// true +// } else { +// false +// } +// } + +// fn deactivated(&mut self, cx: &mut ViewContext) { +// self.push_to_nav_history(cx); +// } + +// fn clone_on_split( +// &self, +// _workspace_id: WorkspaceId, +// _: &mut ViewContext, +// ) -> Option +// 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, +// _: &mut ViewContext, +// ) -> Task> { +// self.save_count += 1; +// self.is_dirty = false; +// Task::ready(Ok(())) +// } + +// fn save_as( +// &mut self, +// _: Handle, +// _: std::path::PathBuf, +// _: &mut ViewContext, +// ) -> Task> { +// self.save_as_count += 1; +// self.is_dirty = false; +// Task::ready(Ok(())) +// } + +// fn reload( +// &mut self, +// _: Handle, +// _: &mut ViewContext, +// ) -> Task> { +// 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, +// _workspace: WeakViewHandle, +// workspace_id: WorkspaceId, +// _item_id: ItemId, +// cx: &mut ViewContext, +// ) -> Task>> { +// let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id)); +// Task::Ready(Some(anyhow::Ok(view))) +// } +// } +// } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs new file mode 100644 index 0000000000..e0eb1b7ec2 --- /dev/null +++ b/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, +// } + +// #[derive(Clone, PartialEq)] +// pub struct CloseItemsToTheLeftById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } + +// #[derive(Clone, PartialEq)] +// pub struct CloseItemsToTheRightById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize, Default)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseActiveItem { +// pub save_intent: Option, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseAllItems { +// pub save_intent: Option, +// } + +// 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 }, + 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>, + // activation_history: Vec, + // zoomed: bool, + // active_item_index: usize, + // last_focused_view_by_item: HashMap, + // autoscroll: bool, + nav_history: NavHistory, + // toolbar: ViewHandle, + // tab_bar_context_menu: TabBarContextMenu, + // tab_context_menu: ViewHandle, + // workspace: WeakViewHandle, + project: Handle, + // has_focus: bool, + // can_drop: Rc, &WindowContext) -> bool>, + // can_split: bool, + // render_tab_bar_buttons: Rc) -> AnyElement>, +} + +pub struct ItemNavHistory { + history: NavHistory, + item: Rc, +} + +#[derive(Clone)] +pub struct NavHistory(Rc>); + +struct NavHistoryState { + mode: NavigationMode, + backward_stack: VecDeque, + forward_stack: VecDeque, + closed_stack: VecDeque, + paths_by_item: HashMap)>, + pane: WeakView, + next_timestamp: Arc, +} + +#[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, + pub data: Option>, + pub timestamp: usize, +} + +// pub struct DraggedItem { +// pub handle: Box, +// pub pane: WeakViewHandle, +// } + +// pub enum ReorderBehavior { +// None, +// MoveAfterActive, +// MoveToIndex(usize), +// } + +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// enum TabBarContextMenuKind { +// New, +// Split, +// } + +// struct TabBarContextMenu { +// kind: TabBarContextMenuKind, +// handle: ViewHandle, +// } + +// impl TabBarContextMenu { +// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { +// if self.kind == kind { +// return Some(self.handle.clone()); +// } +// None +// } +// } + +// #[allow(clippy::too_many_arguments)] +// fn nav_button)>( +// svg_path: &'static str, +// style: theme2::Interactive, +// nav_button_height: f32, +// tooltip_style: TooltipStyle, +// enabled: bool, +// on_click: F, +// tooltip_action: A, +// action_name: &str, +// cx: &mut ViewContext, +// ) -> AnyElement { +// MouseEventHandler::new::(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::( +// 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, + // project: ModelHandle, + // next_timestamp: Arc, + // cx: &mut ViewContext, + // ) -> 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 { + // &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(&mut self, can_drop: F) + // where + // F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, + // { + // self.can_drop = Rc::new(can_drop); + // } + + // pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { + // self.can_split = can_split; + // cx.notify(); + // } + + // pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext) { + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.set_can_navigate(can_navigate, cx); + // }); + // cx.notify(); + // } + + // pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) + // where + // F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, + // { + // self.render_tab_bar_buttons = Rc::new(render); + // cx.notify(); + // } + + // pub fn nav_history_for_item(&self, item: &ViewHandle) -> 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.toolbar.update(cx, |_, cx| cx.notify()); + // } + + pub(crate) fn open_item( + &mut self, + project_entry_id: ProjectEntryId, + focus_item: bool, + cx: &mut ViewContext, + build_item: impl FnOnce(&mut ViewContext) -> Box, + ) -> Box { + 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, + activate_pane: bool, + focus_item: bool, + destination_index: Option, + cx: &mut ViewContext, + ) { + 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> + DoubleEndedIterator { + // self.items.iter() + // } + + // pub fn items_of_type(&self) -> impl '_ + Iterator> { + // self.items + // .iter() + // .filter_map(|item| item.as_any().clone().downcast()) + // } + + // pub fn active_item(&self) -> Option> { + // self.items.get(self.active_item_index).cloned() + // } + + // pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { + // self.items + // .get(self.active_item_index)? + // .pixel_position_of_cursor(cx) + // } + + // pub fn item_for_entry( + // &self, + // entry_id: ProjectEntryId, + // cx: &AppContext, + // ) -> Option> { + // 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 { + // self.items.iter().position(|i| i.id() == item.id()) + // } + + // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // // 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, + ) { + 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) { + // 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) { + // 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, + // ) -> Option>> { + // 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, + // ) -> Task> { + // 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, + // ) -> Option>> { + // 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, + // ) -> Option>> { + // 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, + // ) -> Option>> { + // 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, + // ) -> Task> { + // 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, + // ) -> Option>> { + // 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, + // ) -> Task> { + // 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, + // ) -> Option>> { + // 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>, + // 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, + // mut save_intent: SaveIntent, + // should_close: impl 'static + Fn(usize) -> bool, + // ) -> Task> { + // // 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.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, + // pane: &WeakViewHandle, + // item_ix: usize, + // item: &dyn ItemHandle, + // save_intent: SaveIntent, + // cx: &mut AsyncAppContext, + // ) -> Result { + // 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::(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, + // cx: &mut WindowContext, + // ) -> Task> { + // 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) { + // if let Some(active_item) = self.active_item() { + // cx.focus(active_item.as_any()); + // } + // } + + // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { + // cx.emit(Event::Split(direction)); + // } + + // fn deploy_split_menu(&mut self, cx: &mut ViewContext) { + // 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.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, + // ) { + // 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 { + // &self.toolbar + // } + + // pub fn handle_deleted_project_item( + // &mut self, + // entry_id: ProjectEntryId, + // cx: &mut ViewContext, + // ) -> 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) { + // 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) -> impl Element { + // 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::(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::(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::(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::(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::( + // 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| { + // 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::(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 { + // let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); + + // 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, + // pane: WeakViewHandle, + // first: bool, + // detail: Option, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // 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, + // pane: WeakViewHandle, + // first: bool, + // detail: Option, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // 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( + // title: AnyElement, + // item: &Box, + // pane: WeakViewHandle, + // first: bool, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // 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::(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::(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), + // F2: 'static + Fn(&mut Pane, &mut EventContext), + // >( + // index: usize, + // icon: &'static str, + // is_active: bool, + // tooltip: Option<(&'static str, Option>)>, + // cx: &mut ViewContext, + // on_click: F1, + // on_down: F2, + // context_menu: Option>, + // ) -> AnyElement { + // enum TabBarButton {} + + // let mut button = MouseEventHandler::new::(index, cx, |mouse_state, cx| { + // let theme = &settings2::get::(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::(cx).theme.tooltip.clone(); + // button = button + // .with_tooltip::(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) -> AnyElement { + // 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.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) -> AnyElement { + // enum MouseNavigationHandler {} + + // MouseEventHandler::new::(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::(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::( + // 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::(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) { + // 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.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(&mut self, data: Option, cx: &mut WindowContext) { + // self.history.push(data, self.item.clone(), cx); + // } + + // pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { + // self.history.pop(NavigationMode::GoingBack, cx) + // } + + // pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { + // self.history.pop(NavigationMode::GoingForward, cx) + // } + // } + + // impl NavHistory { + // pub fn for_each_entry( + // &self, + // cx: &AppContext, + // mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), + // ) { + // 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 { + // 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( + // &mut self, + // data: Option, + // item: Rc, + // 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), + // 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), + // 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), + // 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), + // 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)> { + // 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 { +// child_view: usize, +// child: AnyElement, +// } + +// impl PaneBackdrop { +// pub fn new(pane_item_view: usize, child: AnyElement) -> Self { +// PaneBackdrop { +// child, +// child_view: pane_item_view, +// } +// } +// } + +// impl Element for PaneBackdrop { +// type LayoutState = (); + +// type PaintState = (); + +// fn layout( +// &mut self, +// constraint: gpui::SizeConstraint, +// view: &mut V, +// cx: &mut ViewContext, +// ) -> (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, +// ) -> 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::(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, +// _bounds: RectF, +// _visible_bounds: RectF, +// _layout: &Self::LayoutState, +// _paint: &Self::PaintState, +// view: &V, +// cx: &gpui::ViewContext, +// ) -> Option { +// 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, +// ) -> 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) -> 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, +// label: &str, +// is_dirty: bool, +// cx: &mut TestAppContext, +// ) -> Box> { +// 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( +// pane: &ViewHandle, +// labels: [&str; COUNT], +// cx: &mut TestAppContext, +// ) -> [Box>; 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( +// pane: &ViewHandle, +// 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::() +// .unwrap() +// .read(cx) +// .label +// .clone(); +// if ix == pane.active_item_index { +// state.push('*'); +// } +// if item.is_dirty(cx) { +// state.push('^'); +// } +// state +// }) +// .collect::>(); + +// assert_eq!( +// actual_states, expected_states, +// "pane items do not match expectation" +// ); +// }) +// } +// } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs new file mode 100644 index 0000000000..f226f7fc43 --- /dev/null +++ b/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) -> Self { + Self { + root: Member::Pane(pane), + } + } + + pub fn split( + &mut self, + old_pane: &View, + new_pane: &View, + 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) -> Option> { + match &self.root { + Member::Pane(_) => None, + Member::Axis(axis) => axis.bounding_box_for_pane(pane), + } + } + + pub fn pane_at_pixel_position(&self, coordinate: Point) -> Option<&View> { + 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) -> Result { + 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, to: &View) { + match &mut self.root { + Member::Pane(_) => {} + Member::Axis(axis) => axis.swap(from, to), + }; + } + + pub(crate) fn render( + &self, + project: &Handle, + theme: &Theme, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, + zoomed: Option<&AnyView>, + app_state: &Arc, + cx: &mut ViewContext, + ) -> AnyElement { + self.root.render( + project, + 0, + theme, + follower_states, + active_call, + active_pane, + zoomed, + app_state, + cx, + ) + } + + pub(crate) fn panes(&self) -> Vec<&View> { + let mut panes = Vec::new(); + self.root.collect_panes(&mut panes); + panes + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Member { + Axis(PaneAxis), + Pane(View), +} + +impl Member { + fn new_axis(old_pane: View, new_pane: View, 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) -> 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, + basis: usize, + theme: &Theme, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, + zoomed: Option<&AnyView>, + app_state: &Arc, + cx: &mut ViewContext, + ) -> AnyElement { + 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::( + // 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>) { + 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, + pub flexes: Rc>>, + pub bounding_boxes: Rc>>>>, +} + +impl PaneAxis { + pub fn new(axis: Axis, members: Vec) -> 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, flexes: Option>) -> 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, + new_pane: &View, + 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) -> Result> { + 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, to: &View) { + 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) -> Option> { + 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) -> Option<&View> { + 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, + basis: usize, + theme: &Theme, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, + zoomed: Option<&AnyView>, + app_state: &Arc, + cx: &mut ViewContext, + ) -> AnyElement { + 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) -> 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, length: Pixels) -> Bounds { + 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, +// // vector::{vec2f, Vector2F}, +// // }, +// // json::{self, ToJson}, +// // platform::{CursorStyle, MouseButton}, +// // scene::MouseDrag, +// // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, BoundsExt, +// // 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, +// flexes: Rc>>, +// children: Vec>, +// bounding_boxes: Rc>>>>, +// } + +// impl PaneAxisElement { +// pub fn new( +// axis: Axis, +// basis: usize, +// flexes: Rc>>, +// bounding_boxes: Rc>>>>, +// ) -> 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) { +// 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, +// ) { +// 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>>, +// axis: Axis, +// preceding_ix: usize, +// child_start: Vector2F, +// drag_bounds: Bounds, +// ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { +// 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> for PaneAxisElement { +// fn extend>>(&mut self, children: T) { +// self.children.extend(children); +// } +// } + +// impl Element for PaneAxisElement { +// type LayoutState = f32; +// type PaintState = (); + +// fn layout( +// &mut self, +// constraint: SizeConstraint, +// view: &mut Workspace, +// cx: &mut ViewContext, +// ) -> (Vector2F, Self::LayoutState) { +// debug_assert!(self.children.len() == self.flexes.borrow().len()); + +// let active_pane_magnification = +// settings::get::(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, +// visible_bounds: Bounds, +// remaining_space: &mut Self::LayoutState, +// view: &mut Workspace, +// cx: &mut ViewContext, +// ) -> Self::PaintState { +// let can_resize = settings::get::(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::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::new( +// handle_origin, +// vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), +// ), +// Axis::Vertical => Bounds::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::( +// 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, +// _: Bounds, +// _: Bounds, +// _: &Self::LayoutState, +// _: &Self::PaintState, +// view: &Workspace, +// cx: &ViewContext, +// ) -> Option> { +// self.children +// .iter() +// .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) +// } + +// fn debug( +// &self, +// bounds: Bounds, +// _: &Self::LayoutState, +// _: &Self::PaintState, +// view: &Workspace, +// cx: &ViewContext, +// ) -> 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::>() +// }) +// } +// } +// } diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs new file mode 100644 index 0000000000..2e28dabffb --- /dev/null +++ b/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>); + +impl WorkspaceLocation { + pub fn paths(&self) -> Arc> { + self.0.clone() + } +} + +impl, T: IntoIterator> From for WorkspaceLocation { + fn from(iterator: T) -> Self { + let mut roots = iterator + .into_iter() + .map(|p| p.as_ref().to_path_buf()) + .collect::>(); + roots.sort(); + Self(Arc::new(roots)) + } +} + +impl StaticColumnCount for WorkspaceLocation {} +impl Bind for &WorkspaceLocation { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + 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, + pub display: Option, + 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 { + 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, + pub(crate) zoom: bool, +} + +impl Column for DockData { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (visible, next_index) = Option::::column(statement, start_index)?; + let (active_panel, next_index) = Option::::column(statement, next_index)?; + let (zoom, next_index) = Option::::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 { + 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>, + children: Vec, + }, + 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, + workspace_id: WorkspaceId, + workspace: &WeakView, + cx: &mut AsyncAppContext, + ) -> Option<(Member, Option>, Vec>>)> { + 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, +} + +impl SerializedPane { + pub fn new(children: Vec, active: bool) -> Self { + SerializedPane { children, active } + } + + pub async fn deserialize_to( + &self, + project: &Handle, + pane: &WeakView, + workspace_id: WorkspaceId, + workspace: &WeakView, + cx: &mut AsyncAppContext, + ) -> Result>>> { + 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::().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, + pub item_id: ItemId, + pub active: bool, +} + +impl SerializedItem { + pub fn new(kind: impl AsRef, 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 { + 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::::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, + )) + } +} diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs new file mode 100644 index 0000000000..3731094b26 --- /dev/null +++ b/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 = env::var("ZED_WINDOW_SIZE") +// .ok() +// .as_deref() +// .and_then(parse_pixel_position_env_var); +// static ref ZED_WINDOW_POSITION: Option = 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 ModalHandle for View { +// 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, +// } + +// #[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, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct Save { +// pub save_intent: Option, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize, Default)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseAllItemsAndPanes { +// pub save_intent: Option, +// } + +// #[derive(Deserialize)] +// pub struct Toast { +// id: usize, +// msg: Cow<'static, str>, +// #[serde(skip)] +// on_click: Option<(Cow<'static, str>, Arc)>, +// } + +// impl Toast { +// pub fn new>>(id: usize, msg: I) -> Self { +// Toast { +// id, +// msg: msg.into(), +// on_click: None, +// } +// } + +// pub fn on_click(mut self, message: M, on_click: F) -> Self +// where +// M: Into>, +// 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::(cx); +// settings::register::(cx); +// } + +// pub fn init(app_state: Arc, 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| { +// let pane = workspace.active_pane().clone(); +// workspace.unfollow(&pane, cx); +// }, +// ); +// cx.add_action( +// |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext| { +// 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 +// .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, AnyHandle, &mut ViewContext) -> Box>; +pub fn register_project_item(cx: &mut AppContext) { + cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { + builders.insert(TypeId::of::(), |project, model, cx| { + let item = model.downcast::().unwrap(); + Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) + }); + }); +} + +type FollowableItemBuilder = fn( + View, + View, + ViewId, + &mut Option, + &mut AppContext, +) -> Option>>>; +type FollowableItemBuilders = HashMap< + TypeId, + ( + FollowableItemBuilder, + fn(&AnyView) -> Box, + ), +>; +pub fn register_followable_item(cx: &mut AppContext) { + cx.update_default_global(|builders: &mut FollowableItemBuilders, _| { + builders.insert( + TypeId::of::(), + ( + |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::().unwrap()), + ), + ); + }); +} + +type ItemDeserializers = HashMap< + Arc, + fn( + Handle, + WeakView, + WorkspaceId, + ItemId, + &mut ViewContext, + ) -> Task>>, +>; +pub fn register_deserializable_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, + pub client: Arc, + pub user_store: Handle, + pub workspace_store: Handle, + pub fs: Arc, + pub build_window_options: + fn(Option, Option, &MainThread) -> WindowOptions, + pub initialize_workspace: + fn(WeakHandle, bool, Arc, AsyncAppContext) -> Task>, + pub node_runtime: Arc, +} + +pub struct WorkspaceStore { + workspaces: HashSet>, + followers: Vec, + client: Arc, + _subscriptions: Vec, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +struct Follower { + project_id: Option, + peer_id: PeerId, +} + +// impl AppState { +// #[cfg(any(test, feature = "test-support"))] +// pub fn test(cx: &mut AppContext) -> Arc { +// use node_runtime::FakeNodeRuntime; +// use settings::SettingsStore; + +// if !cx.has_global::() { +// 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>, + cancel_channel: Option>, +} + +impl DelayedDebouncedEditAction { + fn new() -> DelayedDebouncedEditAction { + DelayedDebouncedEditAction { + task: None, + cancel_channel: None, + } + } + + fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) + where + F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, + { + 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), +// ContactRequestedJoin(u64), +// } + +pub struct Workspace { + weak_self: WeakHandle, + // modal: Option, + // zoomed: Option, + // zoomed_position: Option, + // center: PaneGroup, + // left_dock: View, + // bottom_dock: View, + // right_dock: View, + panes: Vec>, + // panes_by_item: HashMap>, + // active_pane: View, + last_active_center_pane: Option>, + // last_active_view_id: Option, + // status_bar: View, + // titlebar_item: Option, + // notifications: Vec<(TypeId, usize, Box)>, + project: Handle, + // follower_states: HashMap, FollowerState>, + // last_leaders_by_pane: HashMap, PeerId>, + // window_edited: bool, + // active_call: Option<(ModelHandle, Vec)>, + // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, + // database_id: WorkspaceId, + app_state: Arc, + // subscriptions: Vec, + // _apply_leader_updates: Task>, + // _observe_current_user: Task>, + // _schedule_serialize: Option>, + // pane_history_timestamp: Arc, +} + +// struct ActiveModal { +// view: Box, +// previously_focused_view_id: Option, +// } + +// #[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, + items_by_leader_view_id: HashMap>, +} + +// enum WorkspaceBounds {} + +impl Workspace { + // pub fn new( + // workspace_id: WorkspaceId, + // project: ModelHandle, + // app_state: Arc, + // cx: &mut ViewContext, + // ) -> 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(¢er_pane, Self::handle_pane_event).detach(); + // cx.focus(¢er_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(¢er_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::, _, _>(|drag_and_drop, _| { + // drag_and_drop.register_container(weak_handle.clone()); + // }); + + // let mut active_call = None; + // if cx.has_global::>() { + // let call = cx.global::>().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, + // app_state: Arc, + // requesting_window: Option>, + // cx: &mut AppContext, + // ) -> Task<( + // WeakViewHandle, + // Vec, 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> = Default::default(); + // let mut project_paths: Vec<(PathBuf, Option)> = + // 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.weak_self.clone() + // } + + // pub fn left_dock(&self) -> &View { + // &self.left_dock + // } + + // pub fn bottom_dock(&self) -> &View { + // &self.bottom_dock + // } + + // pub fn right_dock(&self) -> &View { + // &self.right_dock + // } + + // pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) + // where + // T::Event: std::fmt::Debug, + // { + // self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) + // } + + // pub fn add_panel_with_extra_event_handler( + // &mut self, + // panel: View, + // cx: &mut ViewContext, + // handler: F, + // ) where + // T::Event: std::fmt::Debug, + // F: Fn(&mut Self, &View, &T::Event, &mut ViewContext) + '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 { + // &self.status_bar + // } + + // pub fn app_state(&self) -> &Arc { + // &self.app_state + // } + + // pub fn user_store(&self) -> &ModelHandle { + // &self.app_state.user_store + // } + + pub fn project(&self) -> &Handle { + &self.project + } + + // pub fn recent_navigation_history( + // &self, + // limit: Option, + // cx: &AppContext, + // ) -> Vec<(ProjectPath, Option)> { + // let mut abs_paths_opened: HashMap> = HashMap::default(); + // let mut history: HashMap, 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 ×tamp > 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, + // mode: NavigationMode, + // cx: &mut ViewContext, + // ) -> Task> { + // 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, + // cx: &mut ViewContext, + // ) -> Task> { + // self.navigate_history(pane, NavigationMode::GoingBack, cx) + // } + + // pub fn go_forward( + // &mut self, + // pane: WeakViewHandle, + // cx: &mut ViewContext, + // ) -> Task> { + // self.navigate_history(pane, NavigationMode::GoingForward, cx) + // } + + // pub fn reopen_closed_item(&mut self, cx: &mut ViewContext) -> Task> { + // 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.titlebar_item = Some(item); + // cx.notify(); + // } + + // pub fn titlebar_item(&self) -> Option { + // 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( + // &mut self, + // cx: &mut ViewContext, + // callback: F, + // ) -> Task> + // where + // T: 'static, + // F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> 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> { + // self.project.read(cx).worktrees(cx) + // } + + // pub fn visible_worktrees<'a>( + // &self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.project.read(cx).visible_worktrees(cx) + // } + + // pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { + // let futures = self + // .worktrees(cx) + // .filter_map(|worktree| worktree.read(cx).as_local()) + // .map(|worktree| worktree.scan_complete()) + // .collect::>(); + // 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, + // ) -> Option>> { + // 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, + // ) -> Task> { + // 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::()) + // .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, + // ) -> Option>> { + // 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, + // ) -> Task> { + // 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::>(); + + // 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) -> Option>> { + // 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, + // cx: &mut ViewContext, + // ) -> Task> { + // let window = cx.window().downcast::(); + // 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, + visible: bool, + cx: &mut ViewContext, + ) -> Task, 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) { + // 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, + abs_path: &Path, + visible: bool, + cx: &mut AppContext, + ) -> Task, 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( + // &mut self, + // cx: &mut ViewContext, + // add_view: F, + // ) -> Option> + // where + // V: 'static + Modal, + // F: FnOnce(&mut Self, &mut ViewContext) -> View, + // { + // 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::()) + // { + // 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(&self) -> Option> { + // self.modal + // .as_ref() + // .and_then(|modal| modal.view.as_any().clone().downcast::()) + // } + + // pub fn dismiss_modal(&mut self, cx: &mut ViewContext) -> Option { + // 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> { + // self.panes.iter().flat_map(|pane| pane.read(cx).items()) + // } + + // pub fn item_of_type(&self, cx: &AppContext) -> Option> { + // 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> { + // self.panes + // .iter() + // .flat_map(|pane| pane.read(cx).items_of_type()) + // } + + // pub fn active_item(&self, cx: &AppContext) -> Option> { + // self.active_pane().read(cx).active_item() + // } + + // fn active_project_path(&self, cx: &ViewContext) -> Option { + // self.active_item(cx).and_then(|item| item.project_path(cx)) + // } + + // pub fn save_active_item( + // &mut self, + // save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Task> { + // 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, + // ) -> Option>> { + // self.close_all_internal(true, SaveIntent::Close, cx) + // } + + // pub fn close_all_items_and_panes( + // &mut self, + // action: &CloseAllItemsAndPanes, + // cx: &mut ViewContext, + // ) -> Option>> { + // 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, + // ) -> Option>> { + // 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) { + // 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) { + // 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(&mut self, cx: &mut ViewContext) -> Option> { + // self.focus_or_unfocus_panel::(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(&mut self, cx: &mut ViewContext) { + // self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); + // } + + // /// Focus or unfocus the given panel type, depending on the given callback. + // fn focus_or_unfocus_panel( + // &mut self, + // cx: &mut ViewContext, + // should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, + // ) -> Option> { + // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + // if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { + // 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(&self, cx: &WindowContext) -> Option> { + // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + // let dock = dock.read(cx); + // if let Some(panel) = dock.panel::() { + // return Some(panel); + // } + // } + // None + // } + + // fn zoom_out(&mut self, cx: &mut ViewContext) { + // 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 { + // self.zoomed.and_then(|view| view.upgrade(cx)) + // } + + // fn dismiss_zoomed_items_to_reveal( + // &mut self, + // dock_to_reveal: Option, + // cx: &mut ViewContext, + // ) { + // // 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) -> View { + // 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, + // cx: &mut ViewContext, + // ) -> 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, cx: &mut ViewContext) { + // 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, + // cx: &mut ViewContext, + // ) { + // 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, + // ) -> Task>> { + // 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, + // ) -> Task>> { + // 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, + pane: Option>, + focus_item: bool, + cx: &mut ViewContext, + ) -> Task, 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, + // cx: &mut ViewContext, + // ) -> Task, 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, + ) -> Task< + Result<( + ProjectEntryId, + impl 'static + FnOnce(&mut ViewContext) -> Box, + )>, + > { + 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::() + .get(&project_item.model_type()) + .ok_or_else(|| anyhow!("no item builder for project item")) + .cloned() + })?; + let build_item = + move |cx: &mut ViewContext| build_item(project, project_item, cx); + Ok((project_entry_id, build_item)) + }) + } + + // pub fn open_project_item( + // &mut self, + // project_item: ModelHandle, + // cx: &mut ViewContext, + // ) -> View + // 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( + // &mut self, + // project_item: ModelHandle, + // cx: &mut ViewContext, + // ) -> View + // 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) { + // 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) -> 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) { + // 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) { + // 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) { + // 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, + // ) { + // 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, + // ) { + // 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, + // ) -> Option<&View> { + // 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, cx: &mut ViewContext) { + // 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, + // event: &pane::Event, + // cx: &mut ViewContext, + // ) { + // 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, + // split_direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> View { + // 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, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> Option> { + // 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, + // split_direction: SplitDirection, + // from: WeakViewHandle, + // item_id_to_move: usize, + // cx: &mut ViewContext, + // ) { + // 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, + // split_direction: SplitDirection, + // project_entry: ProjectEntryId, + // cx: &mut ViewContext, + // ) -> Option>> { + // 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, + // destination: View, + // item_id_to_move: usize, + // destination_index: usize, + // cx: &mut ViewContext, + // ) { + // 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, cx: &mut ViewContext) { + // 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] { + // &self.panes + // } + + // pub fn active_pane(&self) -> &View { + // &self.active_pane + // } + + // fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { + // 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, + // ) -> Option>> { + // 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, + // ) -> Option>> { + // 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, + // ) -> Option>> { + // 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, + // cx: &mut ViewContext, + // ) -> Option { + // 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) -> AnyElement { + // // 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::(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) { + // 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) { + // 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) { + // 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, + // ) -> Option> { + // if self.project.read(cx).is_read_only() { + // enum DisconnectedOverlay {} + // Some( + // MouseEventHandler::new::(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> { + // 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, + // cx: &mut ViewContext, + // ) -> 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.leader_updates_tx + // .unbounded_send((leader_id, message)) + // .ok(); + // } + + // async fn process_leader_update( + // this: &WeakViewHandle, + // 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, + // leader_id: PeerId, + // panes: Vec>, + // views: Vec, + // cx: &mut AsyncAppContext, + // ) -> Result<()> { + // let this = this + // .upgrade(cx) + // .ok_or_else(|| anyhow!("workspace dropped"))?; + + // let item_builders = cx.update(|cx| { + // cx.default_global::() + // .values() + // .map(|b| b.0) + // .collect::>() + // }); + + // 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) -> Option { + // self.follower_states.get(pane).map(|state| state.leader_id) + // } + + // fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> 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, + // cx: &mut ViewContext, + // ) -> Option> { + // 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::() { + // 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) { + // 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::(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> { + // self.active_call.as_ref().map(|(call, _)| call) + // } + + // fn on_active_call_event( + // &mut self, + // _: ModelHandle, + // event: &call::room::Event, + // cx: &mut ViewContext, + // ) { + // 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 { + // let project = self.project().read(cx); + + // if project.is_local() { + // Some( + // project + // .visible_worktrees(cx) + // .map(|worktree| worktree.read(cx).abs_path()) + // .collect::>() + // .into(), + // ) + // } else { + // None + // } + // } + + // fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { + // 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, cx: &mut ViewContext) { + // 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._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) { + // fn serialize_pane_handle( + // pane_handle: &View, + // 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::>(), + // 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::>(), + // 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) -> 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, + // serialized_workspace: SerializedWorkspace, + // paths_to_open: Vec>, + // cx: &mut AppContext, + // ) -> Task>>>> { + // 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::>() + // }); + + // 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::>(); + + // // 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, cx: &mut ViewContext) -> 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> { + // 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 { + // 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, + // workspace: &WeakViewHandle, + // mut project_paths_to_open: Vec<(PathBuf, Option)>, + // app_state: Arc, + // mut cx: AsyncAppContext, + // ) -> Result>>>> { + // 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::>() + // }); + + // 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, 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::(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::(MESSAGE_ID, cx) + // }) + // .unwrap_or(false) + // { + // cx.update(|cx| { + // cx.update_global::(|tracker, _| { + // let entry = tracker + // .entry(TypeId::of::()) + // .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::(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, 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) -> AnyElement { +// 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::(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::( +// 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::() +// .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) { +// if cx.is_self_focused() { +// cx.focus(&self.active_pane); +// } +// } + +// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext) -> bool { +// DragAndDrop::::update_modifiers(e.modifiers, cx) +// } +// } + +// impl WorkspaceStore { +// pub fn new(client: Arc, cx: &mut ModelContext) -> 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, +// update: proto::update_followers::Variant, +// cx: &AppContext, +// ) -> Option<()> { +// if !cx.has_global::>() { +// 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, +// envelope: TypedEnvelope, +// _: Arc, +// mut cx: AsyncAppContext, +// ) -> Result { +// 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, +// envelope: TypedEnvelope, +// _: Arc, +// 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, +// envelope: TypedEnvelope, +// _: Arc, +// 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 { +// 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; +// } + +// impl WorkspaceHandle for View { +// fn file_project_paths(&self, cx: &AppContext) -> Vec { +// 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::>() +// } +// } + +// 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); + +pub async fn activate_workspace_for_project( + cx: &mut AsyncAppContext, + predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static, +) -> Option> { + cx.run_on_main(move |cx| { + for window in cx.windows() { + let Some(workspace) = window.downcast::() 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 { +// DB.last_workspace().await.log_err().flatten() +// } + +// async fn join_channel_internal( +// channel_id: u64, +// app_state: &Arc, +// requesting_window: Option>, +// active_call: &ModelHandle, +// cx: &mut AsyncAppContext, +// ) -> Result { +// 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, +// requesting_window: Option>, +// cx: &mut AppContext, +// ) -> Task> { +// 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 { +// for window in cx.windows() { +// let found = window.update(cx, |cx| { +// let is_workspace = cx.root_view().clone().downcast::().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, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task< + anyhow::Result<( + WindowHandle, + Vec, 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, +// cx: &mut AppContext, +// init: impl FnOnce(&mut Workspace, &mut ViewContext) + '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, +// default_content: impl 'static + Send + FnOnce() -> Rope, +// ) -> Task>> { +// 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, +// cx: &mut AppContext, +// ) -> Task> { +// cx.spawn(|mut cx| async move { +// let windows = cx.windows(); +// let existing_workspace = windows.into_iter().find_map(|window| { +// window.downcast::().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::(cx).confirm_quit; +// cx.spawn(|mut cx| async move { +// let mut workspace_windows = cx +// .windows() +// .into_iter() +// .filter_map(|window| window.downcast::()) +// .collect::>(); + +// // 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 { +// 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::>(); +// 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, 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::(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::(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::(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::(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::(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::(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::(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::(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); +// }); +// } +// } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index b9968a3ef5..a4270856b8 100644 --- a/crates/zed2/src/main.rs +++ b/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()); diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index d78908dfb5..45a9276b2b 100644 --- a/crates/zed2/src/zed2.rs +++ b/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, - pub user_store: Handle, + pub user_store: Model, } pub async fn handle_cli_connection(