Restore foreground/background distinction and confine application state to the main thread (#3206)

Nathan Sobo created

The potential for deadlock and other complexity ended up convincing us
that the benefits of making the app state accessible from any thread
were not worth their cost. We probably could have gone back to the old
executors, but we decided to fix forward and continue to get the
benefits of the new dispatcher.

Change summary

crates/ai2/src/auth.rs                         |   10 
crates/ai2/src/providers/open_ai/completion.rs |   63 
crates/ai2/src/providers/open_ai/embedding.rs  |   58 
crates/ai2/src/test.rs                         |   14 
crates/audio2/src/audio2.rs                    |   72 
crates/call2/src/call2.rs                      |    4 
crates/call2/src/room.rs                       |   33 
crates/client2/src/client2.rs                  |  104 
crates/client2/src/telemetry.rs                |    6 
crates/client2/src/test.rs                     |   10 
crates/copilot2/src/copilot2.rs                |   14 
crates/db2/src/db2.rs                          |    2 
crates/fs2/src/fs2.rs                          |    8 
crates/fuzzy2/src/paths.rs                     |    4 
crates/fuzzy2/src/strings.rs                   |    4 
crates/gpui2/src/app.rs                        |  359 ++----
crates/gpui2/src/app/async_context.rs          |  101 -
crates/gpui2/src/app/entity_map.rs             |    2 
crates/gpui2/src/app/model_context.rs          |   67 
crates/gpui2/src/app/test_context.rs           |  157 +-
crates/gpui2/src/element.rs                    |   24 
crates/gpui2/src/executor.rs                   |  123 +-
crates/gpui2/src/gpui2.rs                      |  135 --
crates/gpui2/src/interactive.rs                |   50 
crates/gpui2/src/platform.rs                   |   18 
crates/gpui2/src/platform/mac/dispatcher.rs    |   46 
crates/gpui2/src/platform/mac/platform.rs      |   71 
crates/gpui2/src/platform/mac/window.rs        |   57 
crates/gpui2/src/platform/test/dispatcher.rs   |   91 -
crates/gpui2/src/platform/test/platform.rs     |   20 
crates/gpui2/src/subscription.rs               |    6 
crates/gpui2/src/view.rs                       |   11 
crates/gpui2/src/window.rs                     |  177 +--
crates/gpui2_macros/src/test.rs                |   12 
crates/install_cli2/src/install_cli2.rs        |    4 
crates/journal2/src/journal2.rs                |    2 
crates/language2/src/buffer.rs                 |   18 
crates/language2/src/buffer_tests.rs           |   11 
crates/language2/src/language2.rs              |    8 
crates/language2/src/outline.rs                |    4 
crates/live_kit_client2/examples/test_app.rs   |    4 
crates/live_kit_client2/src/test.rs            |    6 
crates/lsp2/src/lsp2.rs                        |   29 
crates/multi_buffer2/src/multi_buffer2.rs      |    4 
crates/prettier2/src/prettier2.rs              |    2 
crates/project2/src/lsp_command.rs             |   24 
crates/project2/src/project2.rs                |   46 
crates/project2/src/project_tests.rs           | 1085 ++++++++++---------
crates/project2/src/worktree.rs                |   55 
crates/rpc2/src/conn.rs                        |    4 
crates/rpc2/src/peer.rs                        |    5 
crates/settings2/src/settings_file.rs          |    9 
crates/sqlez/src/thread_safe_connection.rs     |    6 
crates/terminal2/src/terminal2.rs              |   22 
crates/ui2/src/components/panes.rs             |    2 
crates/ui2/src/components/tab.rs               |    2 
crates/zed2/src/main.rs                        |   24 
57 files changed, 1,488 insertions(+), 1,821 deletions(-)

Detailed changes

crates/ai2/src/auth.rs 🔗

@@ -1,4 +1,3 @@
-use async_trait::async_trait;
 use gpui2::AppContext;
 
 #[derive(Clone, Debug)]
@@ -8,10 +7,9 @@ pub enum ProviderCredential {
     NotNeeded,
 }
 
-#[async_trait]
-pub trait CredentialProvider: Send + Sync {
+pub trait CredentialProvider {
     fn has_credentials(&self) -> bool;
-    async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
-    async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
-    async fn delete_credentials(&self, cx: &mut AppContext);
+    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
+    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
+    fn delete_credentials(&self, cx: &mut AppContext);
 }

crates/ai2/src/providers/open_ai/completion.rs 🔗

@@ -1,10 +1,9 @@
 use anyhow::{anyhow, Result};
-use async_trait::async_trait;
 use futures::{
     future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt,
     Stream, StreamExt,
 };
-use gpui2::{AppContext, Executor};
+use gpui2::{AppContext, BackgroundExecutor};
 use isahc::{http::StatusCode, Request, RequestExt};
 use parking_lot::RwLock;
 use serde::{Deserialize, Serialize};
@@ -105,7 +104,7 @@ pub struct OpenAIResponseStreamEvent {
 
 pub async fn stream_completion(
     credential: ProviderCredential,
-    executor: Arc<Executor>,
+    executor: Arc<BackgroundExecutor>,
     request: Box<dyn CompletionRequest>,
 ) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
     let api_key = match credential {
@@ -198,11 +197,11 @@ pub async fn stream_completion(
 pub struct OpenAICompletionProvider {
     model: OpenAILanguageModel,
     credential: Arc<RwLock<ProviderCredential>>,
-    executor: Arc<Executor>,
+    executor: Arc<BackgroundExecutor>,
 }
 
 impl OpenAICompletionProvider {
-    pub fn new(model_name: &str, executor: Arc<Executor>) -> Self {
+    pub fn new(model_name: &str, executor: Arc<BackgroundExecutor>) -> Self {
         let model = OpenAILanguageModel::load(model_name);
         let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
         Self {
@@ -213,7 +212,6 @@ impl OpenAICompletionProvider {
     }
 }
 
-#[async_trait]
 impl CredentialProvider for OpenAICompletionProvider {
     fn has_credentials(&self) -> bool {
         match *self.credential.read() {
@@ -221,52 +219,45 @@ impl CredentialProvider for OpenAICompletionProvider {
             _ => false,
         }
     }
-    async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
-        let existing_credential = self.credential.read().clone();
-
-        let retrieved_credential = cx
-            .run_on_main(move |cx| match existing_credential {
-                ProviderCredential::Credentials { .. } => {
-                    return existing_credential.clone();
-                }
-                _ => {
-                    if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
-                        return ProviderCredential::Credentials { api_key };
-                    }
 
-                    if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err()
-                    {
-                        if let Some(api_key) = String::from_utf8(api_key).log_err() {
-                            return ProviderCredential::Credentials { api_key };
-                        } else {
-                            return ProviderCredential::NoCredentials;
-                        }
+    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
+        let existing_credential = self.credential.read().clone();
+        let retrieved_credential = match existing_credential {
+            ProviderCredential::Credentials { .. } => existing_credential.clone(),
+            _ => {
+                if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
+                    ProviderCredential::Credentials { api_key }
+                } else if let Some(Some((_, api_key))) =
+                    cx.read_credentials(OPENAI_API_URL).log_err()
+                {
+                    if let Some(api_key) = String::from_utf8(api_key).log_err() {
+                        ProviderCredential::Credentials { api_key }
                     } else {
-                        return ProviderCredential::NoCredentials;
+                        ProviderCredential::NoCredentials
                     }
+                } else {
+                    ProviderCredential::NoCredentials
                 }
-            })
-            .await;
-
+            }
+        };
         *self.credential.write() = retrieved_credential.clone();
         retrieved_credential
     }
 
-    async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
+    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
         *self.credential.write() = credential.clone();
         let credential = credential.clone();
-        cx.run_on_main(move |cx| match credential {
+        match credential {
             ProviderCredential::Credentials { api_key } => {
                 cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
                     .log_err();
             }
             _ => {}
-        })
-        .await;
+        }
     }
-    async fn delete_credentials(&self, cx: &mut AppContext) {
-        cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err())
-            .await;
+
+    fn delete_credentials(&self, cx: &mut AppContext) {
+        cx.delete_credentials(OPENAI_API_URL).log_err();
         *self.credential.write() = ProviderCredential::NoCredentials;
     }
 }

crates/ai2/src/providers/open_ai/embedding.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use futures::AsyncReadExt;
-use gpui2::Executor;
+use gpui2::BackgroundExecutor;
 use gpui2::{serde_json, AppContext};
 use isahc::http::StatusCode;
 use isahc::prelude::Configurable;
@@ -35,7 +35,7 @@ pub struct OpenAIEmbeddingProvider {
     model: OpenAILanguageModel,
     credential: Arc<RwLock<ProviderCredential>>,
     pub client: Arc<dyn HttpClient>,
-    pub executor: Arc<Executor>,
+    pub executor: Arc<BackgroundExecutor>,
     rate_limit_count_rx: watch::Receiver<Option<Instant>>,
     rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
 }
@@ -66,7 +66,7 @@ struct OpenAIEmbeddingUsage {
 }
 
 impl OpenAIEmbeddingProvider {
-    pub fn new(client: Arc<dyn HttpClient>, executor: Arc<Executor>) -> Self {
+    pub fn new(client: Arc<dyn HttpClient>, executor: Arc<BackgroundExecutor>) -> Self {
         let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
         let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
 
@@ -146,7 +146,6 @@ impl OpenAIEmbeddingProvider {
     }
 }
 
-#[async_trait]
 impl CredentialProvider for OpenAIEmbeddingProvider {
     fn has_credentials(&self) -> bool {
         match *self.credential.read() {
@@ -154,52 +153,45 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
             _ => false,
         }
     }
-    async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
+    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
         let existing_credential = self.credential.read().clone();
 
-        let retrieved_credential = cx
-            .run_on_main(move |cx| match existing_credential {
-                ProviderCredential::Credentials { .. } => {
-                    return existing_credential.clone();
-                }
-                _ => {
-                    if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
-                        return ProviderCredential::Credentials { api_key };
-                    }
-
-                    if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err()
-                    {
-                        if let Some(api_key) = String::from_utf8(api_key).log_err() {
-                            return ProviderCredential::Credentials { api_key };
-                        } else {
-                            return ProviderCredential::NoCredentials;
-                        }
+        let retrieved_credential = match existing_credential {
+            ProviderCredential::Credentials { .. } => existing_credential.clone(),
+            _ => {
+                if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
+                    ProviderCredential::Credentials { api_key }
+                } else if let Some(Some((_, api_key))) =
+                    cx.read_credentials(OPENAI_API_URL).log_err()
+                {
+                    if let Some(api_key) = String::from_utf8(api_key).log_err() {
+                        ProviderCredential::Credentials { api_key }
                     } else {
-                        return ProviderCredential::NoCredentials;
+                        ProviderCredential::NoCredentials
                     }
+                } else {
+                    ProviderCredential::NoCredentials
                 }
-            })
-            .await;
+            }
+        };
 
         *self.credential.write() = retrieved_credential.clone();
         retrieved_credential
     }
 
-    async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
+    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
         *self.credential.write() = credential.clone();
-        let credential = credential.clone();
-        cx.run_on_main(move |cx| match credential {
+        match credential {
             ProviderCredential::Credentials { api_key } => {
                 cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
                     .log_err();
             }
             _ => {}
-        })
-        .await;
+        }
     }
-    async fn delete_credentials(&self, cx: &mut AppContext) {
-        cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err())
-            .await;
+
+    fn delete_credentials(&self, cx: &mut AppContext) {
+        cx.delete_credentials(OPENAI_API_URL).log_err();
         *self.credential.write() = ProviderCredential::NoCredentials;
     }
 }

crates/ai2/src/test.rs 🔗

@@ -100,16 +100,15 @@ impl FakeEmbeddingProvider {
     }
 }
 
-#[async_trait]
 impl CredentialProvider for FakeEmbeddingProvider {
     fn has_credentials(&self) -> bool {
         true
     }
-    async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
+    fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
         ProviderCredential::NotNeeded
     }
-    async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
-    async fn delete_credentials(&self, _cx: &mut AppContext) {}
+    fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
+    fn delete_credentials(&self, _cx: &mut AppContext) {}
 }
 
 #[async_trait]
@@ -162,16 +161,15 @@ impl FakeCompletionProvider {
     }
 }
 
-#[async_trait]
 impl CredentialProvider for FakeCompletionProvider {
     fn has_credentials(&self) -> bool {
         true
     }
-    async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
+    fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
         ProviderCredential::NotNeeded
     }
-    async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
-    async fn delete_credentials(&self, _cx: &mut AppContext) {}
+    fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
+    fn delete_credentials(&self, _cx: &mut AppContext) {}
 }
 
 impl CompletionProvider for FakeCompletionProvider {

crates/audio2/src/audio2.rs 🔗

@@ -1,14 +1,13 @@
 use assets::SoundRegistry;
-use futures::{channel::mpsc, StreamExt};
-use gpui2::{AppContext, AssetSource, Executor};
+use gpui2::{AppContext, AssetSource};
 use rodio::{OutputStream, OutputStreamHandle};
 use util::ResultExt;
 
 mod assets;
 
 pub fn init(source: impl AssetSource, cx: &mut AppContext) {
-    cx.set_global(Audio::new(cx.executor()));
     cx.set_global(SoundRegistry::new(source));
+    cx.set_global(Audio::new());
 }
 
 pub enum Sound {
@@ -34,15 +33,18 @@ impl Sound {
 }
 
 pub struct Audio {
-    tx: mpsc::UnboundedSender<Box<dyn FnOnce(&mut AudioState) + Send>>,
-}
-
-struct AudioState {
     _output_stream: Option<OutputStream>,
     output_handle: Option<OutputStreamHandle>,
 }
 
-impl AudioState {
+impl Audio {
+    pub fn new() -> Self {
+        Self {
+            _output_stream: None,
+            output_handle: None,
+        }
+    }
+
     fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
         if self.output_handle.is_none() {
             let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
@@ -53,59 +55,27 @@ impl AudioState {
         self.output_handle.as_ref()
     }
 
-    fn take(&mut self) {
-        self._output_stream.take();
-        self.output_handle.take();
-    }
-}
-
-impl Audio {
-    pub fn new(executor: &Executor) -> Self {
-        let (tx, mut rx) = mpsc::unbounded::<Box<dyn FnOnce(&mut AudioState) + Send>>();
-        executor
-            .spawn_on_main(|| async move {
-                let mut audio = AudioState {
-                    _output_stream: None,
-                    output_handle: None,
-                };
-
-                while let Some(f) = rx.next().await {
-                    (f)(&mut audio);
-                }
-            })
-            .detach();
-
-        Self { tx }
-    }
-
     pub fn play_sound(sound: Sound, cx: &mut AppContext) {
         if !cx.has_global::<Self>() {
             return;
         }
 
-        let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else {
-            return;
-        };
-
-        let this = cx.global::<Self>();
-        this.tx
-            .unbounded_send(Box::new(move |state| {
-                if let Some(output_handle) = state.ensure_output_exists() {
-                    output_handle.play_raw(source).log_err();
-                }
-            }))
-            .ok();
+        cx.update_global::<Self, _>(|this, cx| {
+            let output_handle = this.ensure_output_exists()?;
+            let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
+            output_handle.play_raw(source).log_err()?;
+            Some(())
+        });
     }
 
-    pub fn end_call(cx: &AppContext) {
+    pub fn end_call(cx: &mut AppContext) {
         if !cx.has_global::<Self>() {
             return;
         }
 
-        let this = cx.global::<Self>();
-
-        this.tx
-            .unbounded_send(Box::new(move |state| state.take()))
-            .ok();
+        cx.update_global::<Self, _>(|this, _| {
+            this._output_stream.take();
+            this.output_handle.take();
+        });
     }
 }

crates/call2/src/call2.rs 🔗

@@ -196,7 +196,7 @@ impl ActiveCall {
                 })
                 .shared();
             self.pending_room_creation = Some(room.clone());
-            cx.executor().spawn(async move {
+            cx.background_executor().spawn(async move {
                 room.await.map_err(|err| anyhow!("{:?}", err))?;
                 anyhow::Ok(())
             })
@@ -230,7 +230,7 @@ impl ActiveCall {
         };
 
         let client = self.client.clone();
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             client
                 .request(proto::CancelCall {
                     room_id,

crates/call2/src/room.rs 🔗

@@ -134,7 +134,7 @@ impl Room {
                 }
             });
 
-            let _maintain_video_tracks = cx.spawn_on_main({
+            let _maintain_video_tracks = cx.spawn({
                 let room = room.clone();
                 move |this, mut cx| async move {
                     let mut track_video_changes = room.remote_video_track_updates();
@@ -153,7 +153,7 @@ impl Room {
                 }
             });
 
-            let _maintain_audio_tracks = cx.spawn_on_main({
+            let _maintain_audio_tracks = cx.spawn({
                 let room = room.clone();
                 |this, mut cx| async move {
                     let mut track_audio_changes = room.remote_audio_track_updates();
@@ -326,7 +326,7 @@ impl Room {
     fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
         let task = if self.status.is_online() {
             let leave = self.leave_internal(cx);
-            Some(cx.executor().spawn(async move {
+            Some(cx.background_executor().spawn(async move {
                 leave.await.log_err();
             }))
         } else {
@@ -394,7 +394,7 @@ impl Room {
         self.clear_state(cx);
 
         let leave_room = self.client.request(proto::LeaveRoom {});
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             leave_room.await?;
             anyhow::Ok(())
         })
@@ -449,7 +449,8 @@ impl Room {
 
                 // Wait for client to re-establish a connection to the server.
                 {
-                    let mut reconnection_timeout = cx.executor().timer(RECONNECT_TIMEOUT).fuse();
+                    let mut reconnection_timeout =
+                        cx.background_executor().timer(RECONNECT_TIMEOUT).fuse();
                     let client_reconnection = async {
                         let mut remaining_attempts = 3;
                         while remaining_attempts > 0 {
@@ -1195,7 +1196,7 @@ impl Room {
         };
 
         cx.notify();
-        cx.executor().spawn_on_main(move || async move {
+        cx.background_executor().spawn(async move {
             client
                 .request(proto::UpdateParticipantLocation {
                     room_id,
@@ -1300,7 +1301,9 @@ impl Room {
                                 live_kit.room.unpublish_track(publication);
                             } else {
                                 if muted {
-                                    cx.executor().spawn(publication.set_mute(muted)).detach();
+                                    cx.background_executor()
+                                        .spawn(publication.set_mute(muted))
+                                        .detach();
                                 }
                                 live_kit.microphone_track = LocalTrack::Published {
                                     track_publication: publication,
@@ -1343,7 +1346,7 @@ impl Room {
             return Task::ready(Err(anyhow!("live-kit was not initialized")));
         };
 
-        cx.spawn_on_main(move |this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let publish_track = async {
                 let displays = displays.await?;
                 let display = displays
@@ -1386,7 +1389,9 @@ impl Room {
                                 live_kit.room.unpublish_track(publication);
                             } else {
                                 if muted {
-                                    cx.executor().spawn(publication.set_mute(muted)).detach();
+                                    cx.background_executor()
+                                        .spawn(publication.set_mute(muted))
+                                        .detach();
                                 }
                                 live_kit.screen_track = LocalTrack::Published {
                                     track_publication: publication,
@@ -1453,14 +1458,11 @@ impl Room {
                     .remote_audio_track_publications(&participant.user.id.to_string())
                 {
                     let deafened = live_kit.deafened;
-                    tasks.push(
-                        cx.executor()
-                            .spawn_on_main(move || track.set_enabled(!deafened)),
-                    );
+                    tasks.push(cx.foreground_executor().spawn(track.set_enabled(!deafened)));
                 }
             }
 
-            Ok(cx.executor().spawn_on_main(|| async {
+            Ok(cx.foreground_executor().spawn(async move {
                 if let Some(mute_task) = mute_task {
                     mute_task.await?;
                 }
@@ -1551,7 +1553,8 @@ impl LiveKitRoom {
                 *muted = should_mute;
                 cx.notify();
                 Ok((
-                    cx.executor().spawn(track_publication.set_mute(*muted)),
+                    cx.background_executor()
+                        .spawn(track_publication.set_mute(*muted)),
                     old_muted,
                 ))
             }

crates/client2/src/client2.rs 🔗

@@ -11,7 +11,8 @@ use async_tungstenite::tungstenite::{
     http::{Request, StatusCode},
 };
 use futures::{
-    future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt,
+    future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _,
+    TryStreamExt,
 };
 use gpui2::{
     serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, SemanticVersion, Task,
@@ -240,7 +241,7 @@ struct ClientState {
                     Box<dyn AnyTypedEnvelope>,
                     &Arc<Client>,
                     AsyncAppContext,
-                ) -> BoxFuture<'static, Result<()>>,
+                ) -> LocalBoxFuture<'static, Result<()>>,
         >,
     >,
 }
@@ -310,10 +311,7 @@ pub struct PendingEntitySubscription<T: 'static> {
     consumed: bool,
 }
 
-impl<T> PendingEntitySubscription<T>
-where
-    T: 'static + Send,
-{
+impl<T: 'static> PendingEntitySubscription<T> {
     pub fn set_model(mut self, model: &Model<T>, cx: &mut AsyncAppContext) -> Subscription {
         self.consumed = true;
         let mut state = self.client.state.write();
@@ -341,10 +339,7 @@ where
     }
 }
 
-impl<T> Drop for PendingEntitySubscription<T>
-where
-    T: 'static,
-{
+impl<T: 'static> Drop for PendingEntitySubscription<T> {
     fn drop(&mut self) {
         if !self.consumed {
             let mut state = self.client.state.write();
@@ -505,7 +500,7 @@ impl Client {
                                 },
                                 &cx,
                             );
-                            cx.executor().timer(delay).await;
+                            cx.background_executor().timer(delay).await;
                             delay = delay
                                 .mul_f32(rng.gen_range(1.0..=2.0))
                                 .min(reconnect_interval);
@@ -529,7 +524,7 @@ impl Client {
         remote_id: u64,
     ) -> Result<PendingEntitySubscription<T>>
     where
-        T: 'static + Send,
+        T: 'static,
     {
         let id = (TypeId::of::<T>(), remote_id);
 
@@ -557,9 +552,13 @@ impl Client {
     ) -> Subscription
     where
         M: EnvelopedMessage,
-        E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
-        F: 'static + Future<Output = Result<()>> + Send,
+        E: 'static,
+        H: 'static
+            + Sync
+            + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
+            + Send
+            + Sync,
+        F: 'static + Future<Output = Result<()>>,
     {
         let message_type_id = TypeId::of::<M>();
 
@@ -573,7 +572,7 @@ impl Client {
             Arc::new(move |subscriber, envelope, client, cx| {
                 let subscriber = subscriber.downcast::<E>().unwrap();
                 let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
-                handler(subscriber, *envelope, client.clone(), cx).boxed()
+                handler(subscriber, *envelope, client.clone(), cx).boxed_local()
             }),
         );
         if prev_handler.is_some() {
@@ -599,9 +598,13 @@ impl Client {
     ) -> Subscription
     where
         M: RequestMessage,
-        E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
-        F: 'static + Future<Output = Result<M::Response>> + Send,
+        E: 'static,
+        H: 'static
+            + Sync
+            + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
+            + Send
+            + Sync,
+        F: 'static + Future<Output = Result<M::Response>>,
     {
         self.add_message_handler(model, move |handle, envelope, this, cx| {
             Self::respond_to_request(
@@ -615,9 +618,9 @@ impl Client {
     pub fn add_model_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
     where
         M: EntityMessage,
-        E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
-        F: 'static + Future<Output = Result<()>> + Send,
+        E: 'static,
+        H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
+        F: 'static + Future<Output = Result<()>>,
     {
         self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
             handler(subscriber.downcast::<E>().unwrap(), message, client, cx)
@@ -627,9 +630,9 @@ impl Client {
     fn add_entity_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
     where
         M: EntityMessage,
-        E: 'static + Send,
-        H: 'static + Send + Sync + Fn(AnyModel, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
-        F: 'static + Future<Output = Result<()>> + Send,
+        E: 'static,
+        H: 'static + Fn(AnyModel, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
+        F: 'static + Future<Output = Result<()>>,
     {
         let model_type_id = TypeId::of::<E>();
         let message_type_id = TypeId::of::<M>();
@@ -655,7 +658,7 @@ impl Client {
             message_type_id,
             Arc::new(move |handle, envelope, client, cx| {
                 let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
-                handler(handle, *envelope, client.clone(), cx).boxed()
+                handler(handle, *envelope, client.clone(), cx).boxed_local()
             }),
         );
         if prev_handler.is_some() {
@@ -666,9 +669,9 @@ impl Client {
     pub fn add_model_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
     where
         M: EntityMessage + RequestMessage,
-        E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
-        F: 'static + Future<Output = Result<M::Response>> + Send,
+        E: 'static,
+        H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
+        F: 'static + Future<Output = Result<M::Response>>,
     {
         self.add_model_message_handler(move |entity, envelope, client, cx| {
             Self::respond_to_request::<M, _>(
@@ -705,7 +708,7 @@ impl Client {
         read_credentials_from_keychain(cx).await.is_some()
     }
 
-    #[async_recursion]
+    #[async_recursion(?Send)]
     pub async fn authenticate_and_connect(
         self: &Arc<Self>,
         try_keychain: bool,
@@ -763,7 +766,8 @@ impl Client {
             self.set_status(Status::Reconnecting, cx);
         }
 
-        let mut timeout = futures::FutureExt::fuse(cx.executor().timer(CONNECTION_TIMEOUT));
+        let mut timeout =
+            futures::FutureExt::fuse(cx.background_executor().timer(CONNECTION_TIMEOUT));
         futures::select_biased! {
             connection = self.establish_connection(&credentials, cx).fuse() => {
                 match connection {
@@ -814,7 +818,7 @@ impl Client {
         conn: Connection,
         cx: &AsyncAppContext,
     ) -> Result<()> {
-        let executor = cx.executor();
+        let executor = cx.background_executor();
         log::info!("add connection to peer");
         let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, {
             let executor = executor.clone();
@@ -978,7 +982,7 @@ impl Client {
             .header("x-zed-protocol-version", rpc2::PROTOCOL_VERSION);
 
         let http = self.http.clone();
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             let mut rpc_url = Self::get_rpc_url(http, use_preview_server).await?;
             let rpc_host = rpc_url
                 .host_str()
@@ -1049,7 +1053,7 @@ impl Client {
                 write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
             }
 
-            cx.run_on_main(move |cx| cx.open_url(&url))?.await;
+            cx.update(|cx| cx.open_url(&url))?;
 
             // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
             // access token from the query params.
@@ -1100,7 +1104,7 @@ impl Client {
             let access_token = private_key
                 .decrypt_string(&access_token)
                 .context("failed to decrypt access token")?;
-            cx.run_on_main(|cx| cx.activate(true))?.await;
+            cx.update(|cx| cx.activate(true))?;
 
             Ok(Credentials {
                 user_id: user_id.parse()?,
@@ -1292,7 +1296,7 @@ impl Client {
                 sender_id,
                 type_name
             );
-            cx.spawn_on_main(move |_| async move {
+            cx.spawn(move |_| async move {
                     match future.await {
                         Ok(()) => {
                             log::debug!(
@@ -1331,9 +1335,8 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti
     }
 
     let (user_id, access_token) = cx
-        .run_on_main(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
-        .ok()?
-        .await?;
+        .update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
+        .ok()??;
 
     Some(Credentials {
         user_id: user_id.parse().ok()?,
@@ -1345,19 +1348,17 @@ async fn write_credentials_to_keychain(
     credentials: Credentials,
     cx: &AsyncAppContext,
 ) -> Result<()> {
-    cx.run_on_main(move |cx| {
+    cx.update(move |cx| {
         cx.write_credentials(
             &ZED_SERVER_URL,
             &credentials.user_id.to_string(),
             credentials.access_token.as_bytes(),
         )
     })?
-    .await
 }
 
 async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
-    cx.run_on_main(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
-        .await
+    cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
 }
 
 const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
@@ -1382,7 +1383,7 @@ mod tests {
     use super::*;
     use crate::test::FakeServer;
 
-    use gpui2::{Context, Executor, TestAppContext};
+    use gpui2::{BackgroundExecutor, Context, TestAppContext};
     use parking_lot::Mutex;
     use std::future;
     use util::http::FakeHttpClient;
@@ -1422,14 +1423,14 @@ mod tests {
     }
 
     #[gpui2::test(iterations = 10)]
-    async fn test_connection_timeout(executor: Executor, cx: &mut TestAppContext) {
+    async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
         let user_id = 5;
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let mut status = client.status();
 
         // Time out when client tries to connect.
         client.override_authenticate(move |cx| {
-            cx.executor().spawn(async move {
+            cx.background_executor().spawn(async move {
                 Ok(Credentials {
                     user_id,
                     access_token: "token".into(),
@@ -1437,7 +1438,7 @@ mod tests {
             })
         });
         client.override_establish_connection(|_, cx| {
-            cx.executor().spawn(async move {
+            cx.background_executor().spawn(async move {
                 future::pending::<()>().await;
                 unreachable!()
             })
@@ -1471,7 +1472,7 @@ mod tests {
         // Time out when re-establishing the connection.
         server.allow_connections();
         client.override_establish_connection(|_, cx| {
-            cx.executor().spawn(async move {
+            cx.background_executor().spawn(async move {
                 future::pending::<()>().await;
                 unreachable!()
             })
@@ -1490,7 +1491,10 @@ mod tests {
     }
 
     #[gpui2::test(iterations = 10)]
-    async fn test_authenticating_more_than_once(cx: &mut TestAppContext, executor: Executor) {
+    async fn test_authenticating_more_than_once(
+        cx: &mut TestAppContext,
+        executor: BackgroundExecutor,
+    ) {
         let auth_count = Arc::new(Mutex::new(0));
         let dropped_auth_count = Arc::new(Mutex::new(0));
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
@@ -1500,7 +1504,7 @@ mod tests {
             move |cx| {
                 let auth_count = auth_count.clone();
                 let dropped_auth_count = dropped_auth_count.clone();
-                cx.executor().spawn(async move {
+                cx.background_executor().spawn(async move {
                     *auth_count.lock() += 1;
                     let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
                     future::pending::<()>().await;

crates/client2/src/telemetry.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
-use gpui2::{serde_json, AppContext, AppMetadata, Executor, Task};
+use gpui2::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use serde::Serialize;
@@ -14,7 +14,7 @@ use util::{channel::ReleaseChannel, TryFutureExt};
 
 pub struct Telemetry {
     http_client: Arc<dyn HttpClient>,
-    executor: Executor,
+    executor: BackgroundExecutor,
     state: Mutex<TelemetryState>,
 }
 
@@ -123,7 +123,7 @@ impl Telemetry {
         // TODO: Replace all hardware stuff with nested SystemSpecs json
         let this = Arc::new(Self {
             http_client: client,
-            executor: cx.executor().clone(),
+            executor: cx.background_executor().clone(),
             state: Mutex::new(TelemetryState {
                 app_metadata: cx.app_metadata(),
                 architecture: env::consts::ARCH,

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, Model, TestAppContext};
+use gpui2::{BackgroundExecutor, Context, Model, TestAppContext};
 use parking_lot::Mutex;
 use rpc2::{
     proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
@@ -14,7 +14,7 @@ pub struct FakeServer {
     peer: Arc<Peer>,
     state: Arc<Mutex<FakeServerState>>,
     user_id: u64,
-    executor: Executor,
+    executor: BackgroundExecutor,
 }
 
 #[derive(Default)]
@@ -79,10 +79,10 @@ impl FakeServer {
                         }
 
                         let (client_conn, server_conn, _) =
-                            Connection::in_memory(cx.executor().clone());
+                            Connection::in_memory(cx.background_executor().clone());
                         let (connection_id, io, incoming) =
-                            peer.add_test_connection(server_conn, cx.executor().clone());
-                        cx.executor().spawn(io).detach();
+                            peer.add_test_connection(server_conn, cx.background_executor().clone());
+                        cx.background_executor().spawn(io).detach();
                         {
                             let mut state = state.lock();
                             state.connection_id = Some(connection_id);

crates/copilot2/src/copilot2.rs 🔗

@@ -208,7 +208,7 @@ impl RegisteredBuffer {
                 let new_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot()).ok()?;
 
                 let content_changes = cx
-                    .executor()
+                    .background_executor()
                     .spawn({
                         let new_snapshot = new_snapshot.clone();
                         async move {
@@ -535,7 +535,7 @@ impl Copilot {
                 }
             };
 
-            cx.executor()
+            cx.background_executor()
                 .spawn(task.map_err(|err| anyhow!("{:?}", err)))
         } else {
             // If we're downloading, wait until download is finished
@@ -549,7 +549,7 @@ impl Copilot {
         self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
         if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
             let server = server.clone();
-            cx.executor().spawn(async move {
+            cx.background_executor().spawn(async move {
                 server
                     .request::<request::SignOut>(request::SignOutParams {})
                     .await?;
@@ -579,7 +579,7 @@ impl Copilot {
 
         cx.notify();
 
-        cx.executor().spawn(start_task)
+        cx.background_executor().spawn(start_task)
     }
 
     pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc<LanguageServer>)> {
@@ -760,7 +760,7 @@ impl Copilot {
                 .request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
                     uuid: completion.uuid.clone(),
                 });
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             request.await?;
             Ok(())
         })
@@ -784,7 +784,7 @@ impl Copilot {
                         .map(|completion| completion.uuid.clone())
                         .collect(),
                 });
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             request.await?;
             Ok(())
         })
@@ -827,7 +827,7 @@ impl Copilot {
             .map(|file| file.path().to_path_buf())
             .unwrap_or_default();
 
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             let (version, snapshot) = snapshot.await?;
             let result = lsp
                 .request::<R>(request::GetCompletionsParams {

crates/db2/src/db2.rs 🔗

@@ -185,7 +185,7 @@ pub fn write_and_log<F>(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send
 where
     F: Future<Output = anyhow::Result<()>> + Send,
 {
-    cx.executor()
+    cx.background_executor()
         .spawn(async move { db_write().await.log_err() })
         .detach()
 }

crates/fs2/src/fs2.rs 🔗

@@ -288,7 +288,7 @@ impl Fs for RealFs {
 pub struct FakeFs {
     // Use an unfair lock to ensure tests are deterministic.
     state: Mutex<FakeFsState>,
-    executor: gpui2::Executor,
+    executor: gpui2::BackgroundExecutor,
 }
 
 #[cfg(any(test, feature = "test-support"))]
@@ -434,7 +434,7 @@ lazy_static::lazy_static! {
 
 #[cfg(any(test, feature = "test-support"))]
 impl FakeFs {
-    pub fn new(executor: gpui2::Executor) -> Arc<Self> {
+    pub fn new(executor: gpui2::BackgroundExecutor) -> Arc<Self> {
         Arc::new(Self {
             executor,
             state: Mutex::new(FakeFsState {
@@ -1222,11 +1222,11 @@ pub fn copy_recursive<'a>(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui2::Executor;
+    use gpui2::BackgroundExecutor;
     use serde_json::json;
 
     #[gpui2::test]
-    async fn test_fake_fs(executor: Executor) {
+    async fn test_fake_fs(executor: BackgroundExecutor) {
         let fs = FakeFs::new(executor.clone());
         fs.insert_tree(
             "/root",

crates/fuzzy2/src/paths.rs 🔗

@@ -1,4 +1,4 @@
-use gpui2::Executor;
+use gpui2::BackgroundExecutor;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -134,7 +134,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
     smart_case: bool,
     max_results: usize,
     cancel_flag: &AtomicBool,
-    executor: Executor,
+    executor: BackgroundExecutor,
 ) -> Vec<PathMatch> {
     let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
     if path_count == 0 {

crates/fuzzy2/src/strings.rs 🔗

@@ -2,7 +2,7 @@ use crate::{
     matcher::{Match, MatchCandidate, Matcher},
     CharBag,
 };
-use gpui2::Executor;
+use gpui2::BackgroundExecutor;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -83,7 +83,7 @@ pub async fn match_strings(
     smart_case: bool,
     max_results: usize,
     cancel_flag: &AtomicBool,
-    executor: Executor,
+    executor: BackgroundExecutor,
 ) -> Vec<StringMatch> {
     if candidates.is_empty() || max_results == 0 {
         return Default::default();

crates/gpui2/src/app.rs 🔗

@@ -14,29 +14,30 @@ pub use test_context::*;
 
 use crate::{
     current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource,
-    ClipboardItem, Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId,
-    KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, Render,
+    BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, FocusEvent, FocusHandle,
+    FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, Pixels, Platform, Point, Render,
     SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
     TextSystem, View, Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
-use futures::{future::BoxFuture, Future};
+use futures::{future::LocalBoxFuture, Future};
 use parking_lot::Mutex;
 use slotmap::SlotMap;
 use std::{
     any::{type_name, Any, TypeId},
-    borrow::Borrow,
+    cell::RefCell,
     marker::PhantomData,
     mem,
     ops::{Deref, DerefMut},
     path::PathBuf,
-    sync::{atomic::Ordering::SeqCst, Arc, Weak},
+    rc::{Rc, Weak},
+    sync::{atomic::Ordering::SeqCst, Arc},
     time::Duration,
 };
 use util::http::{self, HttpClient};
 
-pub struct App(Arc<Mutex<AppContext>>);
+pub struct App(Rc<RefCell<AppContext>>);
 
 /// Represents an application before it is fully launched. Once your app is
 /// configured, you'll start the app with `App::run`.
@@ -54,13 +55,12 @@ impl App {
     /// app is fully launched.
     pub fn run<F>(self, on_finish_launching: F)
     where
-        F: 'static + FnOnce(&mut MainThread<AppContext>),
+        F: 'static + FnOnce(&mut AppContext),
     {
         let this = self.0.clone();
-        let platform = self.0.lock().platform.clone();
-        platform.borrow_on_main_thread().run(Box::new(move || {
-            let cx = &mut *this.lock();
-            let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
+        let platform = self.0.borrow().platform.clone();
+        platform.run(Box::new(move || {
+            let cx = &mut *this.borrow_mut();
             on_finish_launching(cx);
         }));
     }
@@ -71,16 +71,12 @@ impl App {
     where
         F: 'static + FnMut(Vec<String>, &mut AppContext),
     {
-        let this = Arc::downgrade(&self.0);
-        self.0
-            .lock()
-            .platform
-            .borrow_on_main_thread()
-            .on_open_urls(Box::new(move |urls| {
-                if let Some(app) = this.upgrade() {
-                    callback(urls, &mut app.lock());
-                }
-            }));
+        let this = Rc::downgrade(&self.0);
+        self.0.borrow().platform.on_open_urls(Box::new(move |urls| {
+            if let Some(app) = this.upgrade() {
+                callback(urls, &mut *app.borrow_mut());
+            }
+        }));
         self
     }
 
@@ -88,49 +84,50 @@ impl App {
     where
         F: 'static + FnMut(&mut AppContext),
     {
-        let this = Arc::downgrade(&self.0);
-        self.0
-            .lock()
-            .platform
-            .borrow_on_main_thread()
-            .on_reopen(Box::new(move || {
-                if let Some(app) = this.upgrade() {
-                    callback(&mut app.lock());
-                }
-            }));
+        let this = Rc::downgrade(&self.0);
+        self.0.borrow_mut().platform.on_reopen(Box::new(move || {
+            if let Some(app) = this.upgrade() {
+                callback(&mut app.borrow_mut());
+            }
+        }));
         self
     }
 
     pub fn metadata(&self) -> AppMetadata {
-        self.0.lock().app_metadata.clone()
+        self.0.borrow().app_metadata.clone()
     }
 
-    pub fn executor(&self) -> Executor {
-        self.0.lock().executor.clone()
+    pub fn background_executor(&self) -> BackgroundExecutor {
+        self.0.borrow().background_executor.clone()
+    }
+
+    pub fn foreground_executor(&self) -> ForegroundExecutor {
+        self.0.borrow().foreground_executor.clone()
     }
 
     pub fn text_system(&self) -> Arc<TextSystem> {
-        self.0.lock().text_system.clone()
+        self.0.borrow().text_system.clone()
     }
 }
 
 type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
-type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
-type Handler = Box<dyn FnMut(&mut AppContext) -> bool + Send + 'static>;
-type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + Send + 'static>;
-type QuitHandler = Box<dyn FnMut(&mut AppContext) -> BoxFuture<'static, ()> + Send + 'static>;
-type ReleaseListener = Box<dyn FnMut(&mut dyn Any, &mut AppContext) + Send + 'static>;
+type FrameCallback = Box<dyn FnOnce(&mut WindowContext)>;
+type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
+type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
+type QuitHandler = Box<dyn FnMut(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
+type ReleaseListener = Box<dyn FnMut(&mut dyn Any, &mut AppContext) + 'static>;
 
 pub struct AppContext {
-    this: Weak<Mutex<AppContext>>,
-    pub(crate) platform: MainThreadOnly<dyn Platform>,
+    this: Weak<RefCell<AppContext>>,
+    pub(crate) platform: Rc<dyn Platform>,
     app_metadata: AppMetadata,
     text_system: Arc<TextSystem>,
     flushing_effects: bool,
     pending_updates: usize,
     pub(crate) active_drag: Option<AnyDrag>,
     pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
-    pub(crate) executor: Executor,
+    pub(crate) background_executor: BackgroundExecutor,
+    pub(crate) foreground_executor: ForegroundExecutor,
     pub(crate) svg_renderer: SvgRenderer,
     asset_source: Arc<dyn AssetSource>,
     pub(crate) image_cache: ImageCache,
@@ -140,7 +137,7 @@ pub struct AppContext {
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     pub(crate) keymap: Arc<Mutex<Keymap>>,
     pub(crate) global_action_listeners:
-        HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self) + Send>>>,
+        HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
     action_builders: HashMap<SharedString, ActionBuilder>,
     pending_effects: VecDeque<Effect>,
     pub(crate) pending_notifications: HashSet<EntityId>,
@@ -156,11 +153,12 @@ pub struct AppContext {
 
 impl AppContext {
     pub(crate) fn new(
-        platform: Arc<dyn Platform>,
+        platform: Rc<dyn Platform>,
         asset_source: Arc<dyn AssetSource>,
         http_client: Arc<dyn HttpClient>,
-    ) -> Arc<Mutex<Self>> {
-        let executor = platform.executor();
+    ) -> Rc<RefCell<Self>> {
+        let executor = platform.background_executor();
+        let foreground_executor = platform.foreground_executor();
         assert!(
             executor.is_main_thread(),
             "must construct App on main thread"
@@ -175,16 +173,17 @@ impl AppContext {
             app_version: platform.app_version().ok(),
         };
 
-        Arc::new_cyclic(|this| {
-            Mutex::new(AppContext {
+        Rc::new_cyclic(|this| {
+            RefCell::new(AppContext {
                 this: this.clone(),
                 text_system,
-                platform: MainThreadOnly::new(platform, executor.clone()),
+                platform,
                 app_metadata,
                 flushing_effects: false,
                 pending_updates: 0,
                 next_frame_callbacks: Default::default(),
-                executor,
+                background_executor: executor,
+                foreground_executor,
                 svg_renderer: SvgRenderer::new(asset_source.clone()),
                 asset_source,
                 image_cache: ImageCache::new(http_client),
@@ -225,7 +224,7 @@ impl AppContext {
 
         let futures = futures::future::join_all(futures);
         if self
-            .executor
+            .background_executor
             .block_with_timeout(Duration::from_millis(100), futures)
             .is_err()
         {
@@ -244,7 +243,6 @@ impl AppContext {
     pub fn refresh(&mut self) {
         self.pending_effects.push_back(Effect::Refresh);
     }
-
     pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
         self.pending_updates += 1;
         let result = update(self);
@@ -258,7 +256,7 @@ impl AppContext {
     }
 
     pub(crate) fn read_window<R>(
-        &mut self,
+        &self,
         id: WindowId,
         read: impl FnOnce(&WindowContext) -> R,
     ) -> Result<R> {
@@ -295,6 +293,68 @@ impl AppContext {
         })
     }
 
+    /// Opens a new window with the given option and the root view returned by the given function.
+    /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
+    /// functionality.
+    pub fn open_window<V: Render>(
+        &mut self,
+        options: crate::WindowOptions,
+        build_root_view: impl FnOnce(&mut WindowContext) -> View<V> + 'static,
+    ) -> WindowHandle<V> {
+        self.update(|cx| {
+            let id = cx.windows.insert(None);
+            let handle = WindowHandle::new(id);
+            let mut window = Window::new(handle.into(), options, cx);
+            let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
+            window.root_view.replace(root_view.into());
+            cx.windows.get_mut(id).unwrap().replace(window);
+            handle
+        })
+    }
+
+    pub(crate) fn platform(&self) -> &Rc<dyn Platform> {
+        &self.platform
+    }
+
+    /// Instructs the platform to activate the application by bringing it to the foreground.
+    pub fn activate(&self, ignoring_other_apps: bool) {
+        self.platform().activate(ignoring_other_apps);
+    }
+
+    /// Writes data to the platform clipboard.
+    pub fn write_to_clipboard(&self, item: ClipboardItem) {
+        self.platform().write_to_clipboard(item)
+    }
+
+    /// Reads data from the platform clipboard.
+    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+        self.platform().read_from_clipboard()
+    }
+
+    /// Writes credentials to the platform keychain.
+    pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
+        self.platform().write_credentials(url, username, password)
+    }
+
+    /// Reads credentials from the platform keychain.
+    pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
+        self.platform().read_credentials(url)
+    }
+
+    /// Deletes credentials from the platform keychain.
+    pub fn delete_credentials(&self, url: &str) -> Result<()> {
+        self.platform().delete_credentials(url)
+    }
+
+    /// Directs the platform's default browser to open the given URL.
+    pub fn open_url(&self, url: &str) {
+        self.platform().open_url(url);
+    }
+
+    pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+        self.platform().path_for_auxiliary_executable(name)
+    }
+
     pub(crate) fn push_effect(&mut self, effect: Effect) {
         match &effect {
             Effect::Notify { emitter } => {
@@ -464,7 +524,7 @@ impl AppContext {
             .retain(&type_id, |observer| observer(self));
     }
 
-    fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + Send + 'static>) {
+    fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + 'static>) {
         callback(self);
     }
 
@@ -473,72 +533,34 @@ impl AppContext {
     pub fn to_async(&self) -> AsyncAppContext {
         AsyncAppContext {
             app: unsafe { mem::transmute(self.this.clone()) },
-            executor: self.executor.clone(),
+            background_executor: self.background_executor.clone(),
+            foreground_executor: self.foreground_executor.clone(),
         }
     }
 
     /// Obtains a reference to the executor, which can be used to spawn futures.
-    pub fn executor(&self) -> &Executor {
-        &self.executor
+    pub fn background_executor(&self) -> &BackgroundExecutor {
+        &self.background_executor
     }
 
-    /// Runs the given closure on the main thread, where interaction with the platform
-    /// is possible. The given closure will be invoked with a `MainThread<AppContext>`, which
-    /// has platform-specific methods that aren't present on `AppContext`.
-    pub fn run_on_main<R>(
-        &mut self,
-        f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
-    ) -> Task<R>
-    where
-        R: Send + 'static,
-    {
-        if self.executor.is_main_thread() {
-            Task::ready(f(unsafe {
-                mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(self)
-            }))
-        } else {
-            let this = self.this.upgrade().unwrap();
-            self.executor.run_on_main(move || {
-                let cx = &mut *this.lock();
-                cx.update(|cx| f(unsafe { mem::transmute::<&mut Self, &mut MainThread<Self>>(cx) }))
-            })
-        }
-    }
-
-    /// Spawns the future returned by the given function on the main thread, where interaction with
-    /// the platform is possible. The given closure will be invoked with a `MainThread<AsyncAppContext>`,
-    /// which has platform-specific methods that aren't present on `AsyncAppContext`. The future will be
-    /// polled exclusively on the main thread.
-    // todo!("I think we need somehow to prevent the MainThread<AsyncAppContext> from implementing Send")
-    pub fn spawn_on_main<F, R>(
-        &self,
-        f: impl FnOnce(MainThread<AsyncAppContext>) -> F + Send + 'static,
-    ) -> Task<R>
-    where
-        F: Future<Output = R> + 'static,
-        R: Send + 'static,
-    {
-        let cx = self.to_async();
-        self.executor.spawn_on_main(move || f(MainThread(cx)))
+    /// Obtains a reference to the executor, which can be used to spawn futures.
+    pub fn foreground_executor(&self) -> &ForegroundExecutor {
+        &self.foreground_executor
     }
 
     /// Spawns the future returned by the given function on the thread pool. The closure will be invoked
     /// with AsyncAppContext, which allows the application state to be accessed across await points.
-    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
+    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
     where
-        Fut: Future<Output = R> + Send + 'static,
-        R: Send + 'static,
+        Fut: Future<Output = R> + 'static,
+        R: 'static,
     {
-        let cx = self.to_async();
-        self.executor.spawn(async move {
-            let future = f(cx);
-            future.await
-        })
+        self.foreground_executor.spawn(f(self.to_async()))
     }
 
     /// Schedules the given function to be run at the end of the current effect cycle, allowing entities
     /// that are currently on the stack to be returned to the app.
-    pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static + Send) {
+    pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static) {
         self.push_effect(Effect::Defer {
             callback: Box::new(f),
         });
@@ -597,7 +619,7 @@ impl AppContext {
 
     /// Access the global of the given type mutably. A default value is assigned if a global of this type has not
     /// yet been assigned.
-    pub fn default_global<G: 'static + Default + Send>(&mut self) -> &mut G {
+    pub fn default_global<G: 'static + Default>(&mut self) -> &mut G {
         let global_type = TypeId::of::<G>();
         self.push_effect(Effect::NotifyGlobalObservers { global_type });
         self.globals_by_type
@@ -608,7 +630,7 @@ impl AppContext {
     }
 
     /// Set the value of the global of the given type.
-    pub fn set_global<G: Any + Send>(&mut self, global: G) {
+    pub fn set_global<G: Any>(&mut self, global: G) {
         let global_type = TypeId::of::<G>();
         self.push_effect(Effect::NotifyGlobalObservers { global_type });
         self.globals_by_type.insert(global_type, Box::new(global));
@@ -626,7 +648,7 @@ impl AppContext {
     /// Register a callback to be invoked when a global of the given type is updated.
     pub fn observe_global<G: 'static>(
         &mut self,
-        mut f: impl FnMut(&mut Self) + Send + 'static,
+        mut f: impl FnMut(&mut Self) + 'static,
     ) -> Subscription {
         self.global_observers.insert(
             TypeId::of::<G>(),
@@ -673,7 +695,7 @@ impl AppContext {
     }
 
     /// Register a global listener for actions invoked via the keyboard.
-    pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + Send + 'static) {
+    pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
         self.global_action_listeners
             .entry(TypeId::of::<A>())
             .or_default()
@@ -717,7 +739,7 @@ impl Context for AppContext {
     /// 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 `Model` will be returned
     /// which can be used to access the entity in a context.
-    fn build_model<T: 'static + Send>(
+    fn build_model<T: 'static>(
         &mut self,
         build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
     ) -> Model<T> {
@@ -747,107 +769,6 @@ impl Context for AppContext {
     }
 }
 
-impl<C> MainThread<C>
-where
-    C: Borrow<AppContext>,
-{
-    pub(crate) fn platform(&self) -> &dyn Platform {
-        self.0.borrow().platform.borrow_on_main_thread()
-    }
-
-    /// Instructs the platform to activate the application by bringing it to the foreground.
-    pub fn activate(&self, ignoring_other_apps: bool) {
-        self.platform().activate(ignoring_other_apps);
-    }
-
-    /// Writes data to the platform clipboard.
-    pub fn write_to_clipboard(&self, item: ClipboardItem) {
-        self.platform().write_to_clipboard(item)
-    }
-
-    /// Reads data from the platform clipboard.
-    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
-        self.platform().read_from_clipboard()
-    }
-
-    /// Writes credentials to the platform keychain.
-    pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
-        self.platform().write_credentials(url, username, password)
-    }
-
-    /// Reads credentials from the platform keychain.
-    pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
-        self.platform().read_credentials(url)
-    }
-
-    /// Deletes credentials from the platform keychain.
-    pub fn delete_credentials(&self, url: &str) -> Result<()> {
-        self.platform().delete_credentials(url)
-    }
-
-    /// Directs the platform's default browser to open the given URL.
-    pub fn open_url(&self, url: &str) {
-        self.platform().open_url(url);
-    }
-
-    pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
-        self.platform().path_for_auxiliary_executable(name)
-    }
-}
-
-impl MainThread<AppContext> {
-    fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
-        self.0.update(|cx| {
-            update(unsafe {
-                std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
-            })
-        })
-    }
-
-    pub(crate) fn update_window<R>(
-        &mut self,
-        id: WindowId,
-        update: impl FnOnce(&mut MainThread<WindowContext>) -> R,
-    ) -> Result<R> {
-        self.0.update_window(id, |cx| {
-            update(unsafe {
-                std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
-            })
-        })
-    }
-
-    /// Opens a new window with the given option and the root view returned by the given function.
-    /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
-    /// functionality.
-    pub fn open_window<V: Render>(
-        &mut self,
-        options: crate::WindowOptions,
-        build_root_view: impl FnOnce(&mut WindowContext) -> View<V> + Send + 'static,
-    ) -> WindowHandle<V> {
-        self.update(|cx| {
-            let id = cx.windows.insert(None);
-            let handle = WindowHandle::new(id);
-            let mut window = Window::new(handle.into(), options, cx);
-            let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
-            window.root_view.replace(root_view.into());
-            cx.windows.get_mut(id).unwrap().replace(window);
-            handle
-        })
-    }
-
-    /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
-    /// your closure with mutable access to the `MainThread<AppContext>` and the global simultaneously.
-    pub fn update_global<G: 'static + Send, R>(
-        &mut self,
-        update: impl FnOnce(&mut G, &mut MainThread<AppContext>) -> R,
-    ) -> R {
-        self.0.update_global(|global, cx| {
-            let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
-            update(global, cx)
-        })
-    }
-}
-
 /// These effects are processed at the end of each application update cycle.
 pub(crate) enum Effect {
     Notify {
@@ -855,7 +776,7 @@ pub(crate) enum Effect {
     },
     Emit {
         emitter: EntityId,
-        event: Box<dyn Any + Send + 'static>,
+        event: Box<dyn Any>,
     },
     FocusChanged {
         window_id: WindowId,
@@ -866,7 +787,7 @@ pub(crate) enum Effect {
         global_type: TypeId,
     },
     Defer {
-        callback: Box<dyn FnOnce(&mut AppContext) + Send + 'static>,
+        callback: Box<dyn FnOnce(&mut AppContext) + 'static>,
     },
 }
 
@@ -905,15 +826,3 @@ pub(crate) struct AnyDrag {
     pub view: AnyView,
     pub cursor_offset: Point<Pixels>,
 }
-
-#[cfg(test)]
-mod tests {
-    use super::AppContext;
-
-    #[test]
-    fn test_app_context_send_sync() {
-        // This will not compile if `AppContext` does not implement `Send`
-        fn assert_send<T: Send>() {}
-        assert_send::<AppContext>();
-    }
-}

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

@@ -1,16 +1,16 @@
 use crate::{
-    AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task,
-    WindowContext,
+    AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model,
+    ModelContext, Result, Task, WindowContext,
 };
 use anyhow::anyhow;
 use derive_more::{Deref, DerefMut};
-use parking_lot::Mutex;
-use std::{future::Future, sync::Weak};
+use std::{cell::RefCell, future::Future, rc::Weak};
 
 #[derive(Clone)]
 pub struct AsyncAppContext {
-    pub(crate) app: Weak<Mutex<AppContext>>,
-    pub(crate) executor: Executor,
+    pub(crate) app: Weak<RefCell<AppContext>>,
+    pub(crate) background_executor: BackgroundExecutor,
+    pub(crate) foreground_executor: ForegroundExecutor,
 }
 
 impl Context for AsyncAppContext {
@@ -22,14 +22,14 @@ impl Context for AsyncAppContext {
         build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
     ) -> Self::Result<Model<T>>
     where
-        T: 'static + Send,
+        T: 'static,
     {
         let app = self
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock(); // Need this to compile
-        Ok(lock.build_model(build_model))
+        let mut app = app.borrow_mut();
+        Ok(app.build_model(build_model))
     }
 
     fn update_model<T: 'static, R>(
@@ -41,8 +41,8 @@ impl Context for AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock(); // Need this to compile
-        Ok(lock.update_model(handle, update))
+        let mut app = app.borrow_mut();
+        Ok(app.update_model(handle, update))
     }
 }
 
@@ -52,13 +52,17 @@ impl AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock(); // Need this to compile
+        let mut lock = app.borrow_mut();
         lock.refresh();
         Ok(())
     }
 
-    pub fn executor(&self) -> &Executor {
-        &self.executor
+    pub fn background_executor(&self) -> &BackgroundExecutor {
+        &self.background_executor
+    }
+
+    pub fn foreground_executor(&self) -> &ForegroundExecutor {
+        &self.foreground_executor
     }
 
     pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
@@ -66,7 +70,7 @@ impl AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock();
+        let mut lock = app.borrow_mut();
         Ok(f(&mut *lock))
     }
 
@@ -79,7 +83,7 @@ impl AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut app_context = app.lock();
+        let app_context = app.borrow();
         app_context.read_window(handle.id, update)
     }
 
@@ -92,44 +96,16 @@ impl AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut app_context = app.lock();
+        let mut app_context = app.borrow_mut();
         app_context.update_window(handle.id, update)
     }
 
-    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
-    where
-        Fut: Future<Output = R> + Send + 'static,
-        R: Send + 'static,
-    {
-        let this = self.clone();
-        self.executor.spawn(async move { f(this).await })
-    }
-
-    pub fn spawn_on_main<Fut, R>(
-        &self,
-        f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
-    ) -> Task<R>
+    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,
-        R: Send + 'static,
-    {
-        let this = self.clone();
-        self.executor.spawn_on_main(|| f(this))
-    }
-
-    pub fn run_on_main<R>(
-        &self,
-        f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
-    ) -> Result<Task<R>>
-    where
-        R: Send + 'static,
+        R: 'static,
     {
-        let app = self
-            .app
-            .upgrade()
-            .ok_or_else(|| anyhow!("app was released"))?;
-        let mut app_context = app.lock();
-        Ok(app_context.run_on_main(f))
+        self.foreground_executor.spawn(f(self.clone()))
     }
 
     pub fn has_global<G: 'static>(&self) -> Result<bool> {
@@ -137,8 +113,8 @@ impl AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let lock = app.lock(); // Need this to compile
-        Ok(lock.has_global::<G>())
+        let app = app.borrow_mut();
+        Ok(app.has_global::<G>())
     }
 
     pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result<R> {
@@ -146,8 +122,8 @@ impl AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let lock = app.lock(); // Need this to compile
-        Ok(read(lock.global(), &lock))
+        let app = app.borrow_mut(); // Need this to compile
+        Ok(read(app.global(), &app))
     }
 
     pub fn try_read_global<G: 'static, R>(
@@ -155,8 +131,8 @@ impl AsyncAppContext {
         read: impl FnOnce(&G, &AppContext) -> R,
     ) -> Option<R> {
         let app = self.app.upgrade()?;
-        let lock = app.lock(); // Need this to compile
-        Some(read(lock.try_global()?, &lock))
+        let app = app.borrow_mut();
+        Some(read(app.try_global()?, &app))
     }
 
     pub fn update_global<G: 'static, R>(
@@ -167,8 +143,8 @@ impl AsyncAppContext {
             .app
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock(); // Need this to compile
-        Ok(lock.update_global(update))
+        let mut app = app.borrow_mut();
+        Ok(app.update_global(update))
     }
 }
 
@@ -224,7 +200,7 @@ impl Context for AsyncWindowContext {
         build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
     ) -> Result<Model<T>>
     where
-        T: 'static + Send,
+        T: 'static,
     {
         self.app
             .update_window(self.window, |cx| cx.build_model(build_model))
@@ -239,14 +215,3 @@ impl Context for AsyncWindowContext {
             .update_window(self.window, |cx| cx.update_model(handle, update))
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_async_app_context_send_sync() {
-        fn assert_send_sync<T: Send + Sync>() {}
-        assert_send_sync::<AsyncAppContext>();
-    }
-}

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

@@ -59,7 +59,7 @@ impl EntityMap {
     /// Insert an entity into a slot obtained by calling `reserve`.
     pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Model<T>
     where
-        T: 'static + Send,
+        T: 'static,
     {
         let model = slot.0;
         self.entities.insert(model.entity_id, Box::new(entity));

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

@@ -1,6 +1,6 @@
 use crate::{
-    AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread,
-    Model, Reference, Subscription, Task, WeakModel,
+    AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, Model, Reference,
+    Subscription, Task, WeakModel,
 };
 use derive_more::{Deref, DerefMut};
 use futures::FutureExt;
@@ -40,15 +40,15 @@ impl<'a, T: 'static> ModelContext<'a, T> {
         self.model_state.clone()
     }
 
-    pub fn observe<T2, E>(
+    pub fn observe<W, E>(
         &mut self,
         entity: &E,
-        mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + Send + 'static,
+        mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + 'static,
     ) -> Subscription
     where
-        T: 'static + Send,
-        T2: 'static,
-        E: Entity<T2>,
+        T: 'static,
+        W: 'static,
+        E: Entity<W>,
     {
         let this = self.weak_model();
         let entity_id = entity.entity_id();
@@ -69,10 +69,10 @@ impl<'a, T: 'static> ModelContext<'a, T> {
     pub fn subscribe<T2, E>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + Send + 'static,
+        mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + 'static,
     ) -> Subscription
     where
-        T: 'static + Send,
+        T: 'static,
         T2: 'static + EventEmitter,
         E: Entity<T2>,
     {
@@ -95,7 +95,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn on_release(
         &mut self,
-        mut on_release: impl FnMut(&mut T, &mut AppContext) + Send + 'static,
+        mut on_release: impl FnMut(&mut T, &mut AppContext) + 'static,
     ) -> Subscription
     where
         T: 'static,
@@ -112,10 +112,10 @@ impl<'a, T: 'static> ModelContext<'a, T> {
     pub fn observe_release<T2, E>(
         &mut self,
         entity: &E,
-        mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + Send + 'static,
+        mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static,
     ) -> Subscription
     where
-        T: Any + Send,
+        T: Any,
         T2: 'static,
         E: Entity<T2>,
     {
@@ -134,10 +134,10 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn observe_global<G: 'static>(
         &mut self,
-        mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + Send + 'static,
+        mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + 'static,
     ) -> Subscription
     where
-        T: 'static + Send,
+        T: 'static,
     {
         let handle = self.weak_model();
         self.global_observers.insert(
@@ -148,11 +148,11 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn on_app_quit<Fut>(
         &mut self,
-        mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + Send + 'static,
+        mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + 'static,
     ) -> Subscription
     where
-        Fut: 'static + Future<Output = ()> + Send,
-        T: 'static + Send,
+        Fut: 'static + Future<Output = ()>,
+        T: 'static,
     {
         let handle = self.weak_model();
         self.app.quit_observers.insert(
@@ -164,7 +164,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
                         future.await;
                     }
                 }
-                .boxed()
+                .boxed_local()
             }),
         )
     }
@@ -183,7 +183,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
     where
-        G: 'static + Send,
+        G: 'static,
     {
         let mut global = self.app.lease_global::<G>();
         let result = f(&mut global, self);
@@ -191,36 +191,20 @@ impl<'a, T: 'static> ModelContext<'a, T> {
         result
     }
 
-    pub fn spawn<Fut, R>(
-        &self,
-        f: impl FnOnce(WeakModel<T>, AsyncAppContext) -> Fut + Send + 'static,
-    ) -> Task<R>
+    pub fn spawn<Fut, R>(&self, f: impl FnOnce(WeakModel<T>, AsyncAppContext) -> Fut) -> Task<R>
     where
         T: 'static,
-        Fut: Future<Output = R> + Send + 'static,
-        R: Send + 'static,
-    {
-        let this = self.weak_model();
-        self.app.spawn(|cx| f(this, cx))
-    }
-
-    pub fn spawn_on_main<Fut, R>(
-        &self,
-        f: impl FnOnce(WeakModel<T>, MainThread<AsyncAppContext>) -> Fut + Send + 'static,
-    ) -> Task<R>
-    where
         Fut: Future<Output = R> + 'static,
-        R: Send + 'static,
+        R: 'static,
     {
         let this = self.weak_model();
-        self.app.spawn_on_main(|cx| f(this, cx))
+        self.app.spawn(|cx| f(this, cx))
     }
 }
 
 impl<'a, T> ModelContext<'a, T>
 where
     T: EventEmitter,
-    T::Event: Send,
 {
     pub fn emit(&mut self, event: T::Event) {
         self.app.pending_effects.push_back(Effect::Emit {
@@ -234,13 +218,10 @@ impl<'a, T> Context for ModelContext<'a, T> {
     type ModelContext<'b, U> = ModelContext<'b, U>;
     type Result<U> = U;
 
-    fn build_model<U>(
+    fn build_model<U: 'static>(
         &mut self,
         build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U,
-    ) -> Model<U>
-    where
-        U: 'static + Send,
-    {
+    ) -> Model<U> {
         self.app.build_model(build_model)
     }
 

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

@@ -1,15 +1,17 @@
 use crate::{
-    AnyWindowHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, MainThread,
-    Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext,
+    AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter,
+    ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform,
+    WindowContext,
 };
-use futures::SinkExt;
-use parking_lot::Mutex;
-use std::{future::Future, sync::Arc};
+use anyhow::{anyhow, bail};
+use futures::{Stream, StreamExt};
+use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration};
 
 #[derive(Clone)]
 pub struct TestAppContext {
-    pub app: Arc<Mutex<AppContext>>,
-    pub executor: Executor,
+    pub app: Rc<RefCell<AppContext>>,
+    pub background_executor: BackgroundExecutor,
+    pub foreground_executor: ForegroundExecutor,
 }
 
 impl Context for TestAppContext {
@@ -21,10 +23,10 @@ impl Context for TestAppContext {
         build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
     ) -> Self::Result<Model<T>>
     where
-        T: 'static + Send,
+        T: 'static,
     {
-        let mut lock = self.app.lock();
-        lock.build_model(build_model)
+        let mut app = self.app.borrow_mut();
+        app.build_model(build_model)
     }
 
     fn update_model<T: 'static, R>(
@@ -32,39 +34,49 @@ impl Context for TestAppContext {
         handle: &Model<T>,
         update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R> {
-        let mut lock = self.app.lock();
-        lock.update_model(handle, update)
+        let mut app = self.app.borrow_mut();
+        app.update_model(handle, update)
     }
 }
 
 impl TestAppContext {
     pub fn new(dispatcher: TestDispatcher) -> Self {
-        let executor = Executor::new(Arc::new(dispatcher));
-        let platform = Arc::new(TestPlatform::new(executor.clone()));
+        let dispatcher = Arc::new(dispatcher);
+        let background_executor = BackgroundExecutor::new(dispatcher.clone());
+        let foreground_executor = ForegroundExecutor::new(dispatcher);
+        let platform = Rc::new(TestPlatform::new(
+            background_executor.clone(),
+            foreground_executor.clone(),
+        ));
         let asset_source = Arc::new(());
         let http_client = util::http::FakeHttpClient::with_404_response();
         Self {
             app: AppContext::new(platform, asset_source, http_client),
-            executor,
+            background_executor,
+            foreground_executor,
         }
     }
 
     pub fn quit(&self) {
-        self.app.lock().quit();
+        self.app.borrow_mut().quit();
     }
 
     pub fn refresh(&mut self) -> Result<()> {
-        let mut lock = self.app.lock();
-        lock.refresh();
+        let mut app = self.app.borrow_mut();
+        app.refresh();
         Ok(())
     }
 
-    pub fn executor(&self) -> &Executor {
-        &self.executor
+    pub fn executor(&self) -> &BackgroundExecutor {
+        &self.background_executor
+    }
+
+    pub fn foreground_executor(&self) -> &ForegroundExecutor {
+        &self.foreground_executor
     }
 
     pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
-        let mut cx = self.app.lock();
+        let mut cx = self.app.borrow_mut();
         cx.update(f)
     }
 
@@ -73,7 +85,7 @@ impl TestAppContext {
         handle: AnyWindowHandle,
         read: impl FnOnce(&WindowContext) -> R,
     ) -> R {
-        let mut app_context = self.app.lock();
+        let app_context = self.app.borrow();
         app_context.read_window(handle.id, read).unwrap()
     }
 
@@ -82,57 +94,33 @@ impl TestAppContext {
         handle: AnyWindowHandle,
         update: impl FnOnce(&mut WindowContext) -> R,
     ) -> R {
-        let mut app = self.app.lock();
+        let mut app = self.app.borrow_mut();
         app.update_window(handle.id, update).unwrap()
     }
 
-    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
-    where
-        Fut: Future<Output = R> + Send + 'static,
-        R: Send + 'static,
-    {
-        let cx = self.to_async();
-        self.executor.spawn(async move { f(cx).await })
-    }
-
-    pub fn spawn_on_main<Fut, R>(
-        &self,
-        f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
-    ) -> Task<R>
+    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,
-        R: Send + 'static,
-    {
-        let cx = self.to_async();
-        self.executor.spawn_on_main(|| f(cx))
-    }
-
-    pub fn run_on_main<R>(
-        &self,
-        f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
-    ) -> Task<R>
-    where
-        R: Send + 'static,
+        R: 'static,
     {
-        let mut app_context = self.app.lock();
-        app_context.run_on_main(f)
+        self.foreground_executor.spawn(f(self.to_async()))
     }
 
     pub fn has_global<G: 'static>(&self) -> bool {
-        let lock = self.app.lock();
-        lock.has_global::<G>()
+        let app = self.app.borrow();
+        app.has_global::<G>()
     }
 
     pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
-        let lock = self.app.lock();
-        read(lock.global(), &lock)
+        let app = self.app.borrow();
+        read(app.global(), &app)
     }
 
     pub fn try_read_global<G: 'static, R>(
         &self,
         read: impl FnOnce(&G, &AppContext) -> R,
     ) -> Option<R> {
-        let lock = self.app.lock();
+        let lock = self.app.borrow();
         Some(read(lock.try_global()?, &lock))
     }
 
@@ -140,32 +128,75 @@ impl TestAppContext {
         &mut self,
         update: impl FnOnce(&mut G, &mut AppContext) -> R,
     ) -> R {
-        let mut lock = self.app.lock();
+        let mut lock = self.app.borrow_mut();
         lock.update_global(update)
     }
 
     pub fn to_async(&self) -> AsyncAppContext {
         AsyncAppContext {
-            app: Arc::downgrade(&self.app),
-            executor: self.executor.clone(),
+            app: Rc::downgrade(&self.app),
+            background_executor: self.background_executor.clone(),
+            foreground_executor: self.foreground_executor.clone(),
         }
     }
 
-    pub fn subscribe<T: 'static + EventEmitter + Send>(
+    pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
+        let (tx, rx) = futures::channel::mpsc::unbounded();
+
+        entity.update(self, move |_, cx: &mut ModelContext<T>| {
+            cx.observe(entity, {
+                let tx = tx.clone();
+                move |_, _, _| {
+                    let _ = tx.unbounded_send(());
+                }
+            })
+            .detach();
+
+            cx.on_release(move |_, _| tx.close_channel()).detach();
+        });
+
+        rx
+    }
+
+    pub fn events<T: 'static + EventEmitter>(
         &mut self,
         entity: &Model<T>,
     ) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
     where
-        T::Event: 'static + Send + Clone,
+        T::Event: 'static + Clone,
     {
-        let (mut tx, rx) = futures::channel::mpsc::unbounded();
+        let (tx, rx) = futures::channel::mpsc::unbounded();
         entity
             .update(self, |_, cx: &mut ModelContext<T>| {
-                cx.subscribe(entity, move |_, _, event, cx| {
-                    cx.executor().block(tx.send(event.clone())).unwrap();
+                cx.subscribe(entity, move |_model, _handle, event, _cx| {
+                    let _ = tx.unbounded_send(event.clone());
                 })
             })
             .detach();
         rx
     }
+
+    pub async fn condition<T: 'static>(
+        &mut self,
+        model: &Model<T>,
+        mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
+    ) {
+        let timer = self.executor().timer(Duration::from_secs(3));
+        let mut notifications = self.notifications(model);
+
+        use futures::FutureExt as _;
+        use smol::future::FutureExt as _;
+
+        async {
+            while notifications.next().await.is_some() {
+                if model.update(self, &mut predicate) {
+                    return Ok(());
+                }
+            }
+            bail!("model dropped")
+        }
+        .race(timer.map(|_| Err(anyhow!("condition timed out"))))
+        .await
+        .unwrap();
+    }
 }

crates/gpui2/src/element.rs 🔗

@@ -4,7 +4,7 @@ pub(crate) use smallvec::SmallVec;
 use std::{any::Any, mem};
 
 pub trait Element<V: 'static> {
-    type ElementState: 'static + Send;
+    type ElementState: 'static;
 
     fn id(&self) -> Option<ElementId>;
 
@@ -97,7 +97,7 @@ impl<V, E: Element<V>> RenderedElement<V, E> {
 impl<V, E> ElementObject<V> for RenderedElement<V, E>
 where
     E: Element<V>,
-    E::ElementState: 'static + Send,
+    E::ElementState: 'static,
 {
     fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
         let frame_state = if let Some(id) = self.element.id() {
@@ -170,16 +170,14 @@ where
     }
 }
 
-pub struct AnyElement<V>(Box<dyn ElementObject<V> + Send>);
-
-unsafe impl<V> Send for AnyElement<V> {}
+pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
 
 impl<V> AnyElement<V> {
     pub fn new<E>(element: E) -> Self
     where
         V: 'static,
-        E: 'static + Element<V> + Send,
-        E::ElementState: Any + Send,
+        E: 'static + Element<V>,
+        E::ElementState: Any,
     {
         AnyElement(Box::new(RenderedElement::new(element)))
     }
@@ -220,8 +218,8 @@ impl<V> Component<V> for AnyElement<V> {
 impl<V, E, F> Element<V> for Option<F>
 where
     V: 'static,
-    E: 'static + Component<V> + Send,
-    F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+    E: 'static + Component<V>,
+    F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static,
 {
     type ElementState = AnyElement<V>;
 
@@ -264,8 +262,8 @@ where
 impl<V, E, F> Component<V> for Option<F>
 where
     V: 'static,
-    E: 'static + Component<V> + Send,
-    F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+    E: 'static + Component<V>,
+    F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static,
 {
     fn render(self) -> AnyElement<V> {
         AnyElement::new(self)
@@ -275,8 +273,8 @@ where
 impl<V, E, F> Component<V> for F
 where
     V: 'static,
-    E: 'static + Component<V> + Send,
-    F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+    E: 'static + Component<V>,
+    F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static,
 {
     fn render(self) -> AnyElement<V> {
         AnyElement::new(Some(self))

crates/gpui2/src/executor.rs 🔗

@@ -6,6 +6,7 @@ use std::{
     marker::PhantomData,
     mem,
     pin::Pin,
+    rc::Rc,
     sync::{
         atomic::{AtomicBool, Ordering::SeqCst},
         Arc,
@@ -17,10 +18,16 @@ use util::TryFutureExt;
 use waker_fn::waker_fn;
 
 #[derive(Clone)]
-pub struct Executor {
+pub struct BackgroundExecutor {
     dispatcher: Arc<dyn PlatformDispatcher>,
 }
 
+#[derive(Clone)]
+pub struct ForegroundExecutor {
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    not_send: PhantomData<Rc<()>>,
+}
+
 #[must_use]
 pub enum Task<T> {
     Ready(Option<T>),
@@ -46,7 +53,7 @@ where
     E: 'static + Send + Debug,
 {
     pub fn detach_and_log_err(self, cx: &mut AppContext) {
-        cx.executor().spawn(self.log_err()).detach();
+        cx.background_executor().spawn(self.log_err()).detach();
     }
 }
 
@@ -61,7 +68,7 @@ impl<T> Future for Task<T> {
     }
 }
 
-impl Executor {
+impl BackgroundExecutor {
     pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
         Self { dispatcher }
     }
@@ -79,72 +86,30 @@ impl Executor {
         Task::Spawned(task)
     }
 
-    /// Enqueues the given closure to run on the application's event loop.
-    /// Returns the result asynchronously.
-    pub fn run_on_main<F, R>(&self, func: F) -> Task<R>
-    where
-        F: FnOnce() -> R + Send + 'static,
-        R: Send + 'static,
-    {
-        if self.dispatcher.is_main_thread() {
-            Task::ready(func())
-        } else {
-            self.spawn_on_main(move || async move { func() })
-        }
-    }
-
-    /// Enqueues the given closure to be run on the application's event loop. The
-    /// closure returns a future which will be run to completion on the main thread.
-    pub fn spawn_on_main<F, R>(&self, func: impl FnOnce() -> F + Send + 'static) -> Task<R>
-    where
-        F: Future<Output = R> + 'static,
-        R: Send + 'static,
-    {
-        let (runnable, task) = async_task::spawn(
-            {
-                let this = self.clone();
-                async move {
-                    let task = this.spawn_on_main_local(func());
-                    task.await
-                }
-            },
-            {
-                let dispatcher = self.dispatcher.clone();
-                move |runnable| dispatcher.dispatch_on_main_thread(runnable)
-            },
-        );
-        runnable.schedule();
-        Task::Spawned(task)
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
+        self.block_internal(false, future)
     }
 
-    /// Enqueues the given closure to be run on the application's event loop. Must
-    /// be called on the main thread.
-    pub fn spawn_on_main_local<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
-    where
-        R: 'static,
-    {
-        assert!(
-            self.dispatcher.is_main_thread(),
-            "must be called on main thread"
-        );
-
-        let dispatcher = self.dispatcher.clone();
-        let (runnable, task) = async_task::spawn_local(future, move |runnable| {
-            dispatcher.dispatch_on_main_thread(runnable)
-        });
-        runnable.schedule();
-        Task::Spawned(task)
+    pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
+        self.block_internal(true, future)
     }
 
-    pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
+    pub(crate) fn block_internal<R>(
+        &self,
+        background_only: bool,
+        future: impl Future<Output = R>,
+    ) -> R {
         pin_mut!(future);
-        let (parker, unparker) = parking::pair();
+        let unparker = self.dispatcher.unparker();
         let awoken = Arc::new(AtomicBool::new(false));
-        let awoken2 = awoken.clone();
 
-        let waker = waker_fn(move || {
-            awoken2.store(true, SeqCst);
-            unparker.unpark();
+        let waker = waker_fn({
+            let awoken = awoken.clone();
+            move || {
+                awoken.store(true, SeqCst);
+                unparker.unpark();
+            }
         });
         let mut cx = std::task::Context::from_waker(&waker);
 
@@ -152,7 +117,7 @@ impl Executor {
             match future.as_mut().poll(&mut cx) {
                 Poll::Ready(result) => return result,
                 Poll::Pending => {
-                    if !self.dispatcher.poll() {
+                    if !self.dispatcher.poll(background_only) {
                         if awoken.swap(false, SeqCst) {
                             continue;
                         }
@@ -168,7 +133,8 @@ impl Executor {
                                 panic!("parked with nothing left to run\n{:?}", backtrace_message)
                             }
                         }
-                        parker.park();
+
+                        self.dispatcher.park();
                     }
                 }
             }
@@ -234,7 +200,7 @@ impl Executor {
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
-        self.spawn(self.dispatcher.as_test().unwrap().simulate_random_delay())
+        self.dispatcher.as_test().unwrap().simulate_random_delay()
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -261,8 +227,31 @@ impl Executor {
     }
 }
 
+impl ForegroundExecutor {
+    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+        Self {
+            dispatcher,
+            not_send: PhantomData,
+        }
+    }
+
+    /// Enqueues the given closure to be run on any thread. The closure returns
+    /// a future which will be run to completion on any available thread.
+    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
+    where
+        R: 'static,
+    {
+        let dispatcher = self.dispatcher.clone();
+        let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+            dispatcher.dispatch_on_main_thread(runnable)
+        });
+        runnable.schedule();
+        Task::Spawned(task)
+    }
+}
+
 pub struct Scope<'a> {
-    executor: Executor,
+    executor: BackgroundExecutor,
     futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
     tx: Option<mpsc::Sender<()>>,
     rx: mpsc::Receiver<()>,
@@ -270,7 +259,7 @@ pub struct Scope<'a> {
 }
 
 impl<'a> Scope<'a> {
-    fn new(executor: Executor) -> Self {
+    fn new(executor: BackgroundExecutor) -> Self {
         let (tx, rx) = mpsc::channel(1);
         Self {
             executor,

crates/gpui2/src/gpui2.rs 🔗

@@ -68,24 +68,20 @@ use derive_more::{Deref, DerefMut};
 use std::{
     any::{Any, TypeId},
     borrow::{Borrow, BorrowMut},
-    mem,
     ops::{Deref, DerefMut},
-    sync::Arc,
 };
 use taffy::TaffyLayoutEngine;
 
-type AnyBox = Box<dyn Any + Send>;
+type AnyBox = Box<dyn Any>;
 
 pub trait Context {
     type ModelContext<'a, T>;
     type Result<T>;
 
-    fn build_model<T>(
+    fn build_model<T: 'static>(
         &mut self,
         build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
-    ) -> Self::Result<Model<T>>
-    where
-        T: 'static + Send;
+    ) -> Self::Result<Model<T>>;
 
     fn update_model<T: 'static, R>(
         &mut self,
@@ -102,7 +98,7 @@ pub trait VisualContext: Context {
         build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Send;
+        V: 'static;
 
     fn update_view<V: 'static, R>(
         &mut self,
@@ -127,100 +123,6 @@ pub enum GlobalKey {
     Type(TypeId),
 }
 
-#[repr(transparent)]
-pub struct MainThread<T>(T);
-
-impl<T> Deref for MainThread<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl<T> DerefMut for MainThread<T> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-impl<C: Context> Context for MainThread<C> {
-    type ModelContext<'a, T> = MainThread<C::ModelContext<'a, T>>;
-    type Result<T> = C::Result<T>;
-
-    fn build_model<T>(
-        &mut self,
-        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
-    ) -> Self::Result<Model<T>>
-    where
-        T: 'static + Send,
-    {
-        self.0.build_model(|cx| {
-            let cx = unsafe {
-                mem::transmute::<
-                    &mut C::ModelContext<'_, T>,
-                    &mut MainThread<C::ModelContext<'_, T>>,
-                >(cx)
-            };
-            build_model(cx)
-        })
-    }
-
-    fn update_model<T: 'static, R>(
-        &mut self,
-        handle: &Model<T>,
-        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
-    ) -> Self::Result<R> {
-        self.0.update_model(handle, |entity, cx| {
-            let cx = unsafe {
-                mem::transmute::<
-                    &mut C::ModelContext<'_, T>,
-                    &mut MainThread<C::ModelContext<'_, T>>,
-                >(cx)
-            };
-            update(entity, cx)
-        })
-    }
-}
-
-impl<C: VisualContext> VisualContext for MainThread<C> {
-    type ViewContext<'a, 'w, V> = MainThread<C::ViewContext<'a, 'w, V>>;
-
-    fn build_view<V>(
-        &mut self,
-        build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-    ) -> Self::Result<View<V>>
-    where
-        V: 'static + Send,
-    {
-        self.0.build_view(|cx| {
-            let cx = unsafe {
-                mem::transmute::<
-                    &mut C::ViewContext<'_, '_, V>,
-                    &mut MainThread<C::ViewContext<'_, '_, V>>,
-                >(cx)
-            };
-            build_view_state(cx)
-        })
-    }
-
-    fn update_view<V: 'static, R>(
-        &mut self,
-        view: &View<V>,
-        update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R,
-    ) -> Self::Result<R> {
-        self.0.update_view(view, |view_state, cx| {
-            let cx = unsafe {
-                mem::transmute::<
-                    &mut C::ViewContext<'_, '_, V>,
-                    &mut MainThread<C::ViewContext<'_, '_, V>>,
-                >(cx)
-            };
-            update(view_state, cx)
-        })
-    }
-}
-
 pub trait BorrowAppContext {
     fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
     where
@@ -333,32 +235,3 @@ impl<'a, T> DerefMut for Reference<'a, T> {
         }
     }
 }
-
-pub(crate) struct MainThreadOnly<T: ?Sized> {
-    executor: Executor,
-    value: Arc<T>,
-}
-
-impl<T: ?Sized> Clone for MainThreadOnly<T> {
-    fn clone(&self) -> Self {
-        Self {
-            executor: self.executor.clone(),
-            value: self.value.clone(),
-        }
-    }
-}
-
-/// Allows a value to be accessed only on the main thread, allowing a non-`Send` type
-/// to become `Send`.
-impl<T: 'static + ?Sized> MainThreadOnly<T> {
-    pub(crate) fn new(value: Arc<T>, executor: Executor) -> Self {
-        Self { executor, value }
-    }
-
-    pub(crate) fn borrow_on_main_thread(&self) -> &T {
-        assert!(self.executor.is_main_thread());
-        &self.value
-    }
-}
-
-unsafe impl<T: ?Sized> Send for MainThreadOnly<T> {}

crates/gpui2/src/interactive.rs 🔗

@@ -50,7 +50,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     fn on_mouse_down(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + Send + 'static,
+        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -71,7 +71,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     fn on_mouse_up(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + Send + 'static,
+        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -92,7 +92,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     fn on_mouse_down_out(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + Send + 'static,
+        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -113,7 +113,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     fn on_mouse_up_out(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + Send + 'static,
+        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -133,7 +133,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
 
     fn on_mouse_move(
         mut self,
-        handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + Send + 'static,
+        handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -150,7 +150,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
 
     fn on_scroll_wheel(
         mut self,
-        handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + Send + 'static,
+        handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -178,7 +178,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
 
     fn on_action<A: 'static>(
         mut self,
-        listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + Send + 'static,
+        listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -196,7 +196,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
 
     fn on_key_down(
         mut self,
-        listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + 'static,
+        listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -214,7 +214,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
 
     fn on_key_up(
         mut self,
-        listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + Send + 'static,
+        listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -258,9 +258,9 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
         self
     }
 
-    fn on_drop<W: 'static + Send>(
+    fn on_drop<W: 'static>(
         mut self,
-        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + Send + 'static,
+        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -303,7 +303,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
 
     fn on_click(
         mut self,
-        listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + Send + 'static,
+        listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -316,11 +316,11 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
 
     fn on_drag<W>(
         mut self,
-        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + Send + 'static,
+        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
     ) -> Self
     where
         Self: Sized,
-        W: 'static + Send + Render,
+        W: 'static + Render,
     {
         debug_assert!(
             self.stateful_interaction().drag_listener.is_none(),
@@ -335,7 +335,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
     }
 }
 
-pub trait ElementInteraction<V: 'static>: 'static + Send {
+pub trait ElementInteraction<V: 'static>: 'static {
     fn as_stateless(&self) -> &StatelessInteraction<V>;
     fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
     fn as_stateful(&self) -> Option<&StatefulInteraction<V>>;
@@ -672,7 +672,7 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
     }
 }
 
-type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static + Send;
+type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
 
 pub struct StatelessInteraction<V> {
     pub dispatch_context: DispatchContext,
@@ -1077,32 +1077,25 @@ pub struct FocusEvent {
 }
 
 pub type MouseDownListener<V> = Box<
-    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
-        + 'static,
+    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
 >;
 pub type MouseUpListener<V> = Box<
-    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
-        + 'static,
+    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
 >;
 
 pub type MouseMoveListener<V> = Box<
-    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
-        + 'static,
+    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
 >;
 
 pub type ScrollWheelListener<V> = Box<
     dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
         + 'static,
 >;
 
-pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + Send + 'static>;
+pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;
 
 pub(crate) type DragListener<V> =
-    Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + Send + 'static>;
+    Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
 
 pub type KeyListener<V> = Box<
     dyn Fn(
@@ -1112,6 +1105,5 @@ pub type KeyListener<V> = Box<
             DispatchPhase,
             &mut ViewContext<V>,
         ) -> Option<Box<dyn Action>>
-        + Send
         + 'static,
 >;

crates/gpui2/src/platform.rs 🔗

@@ -5,13 +5,14 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun,
-    GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size,
+    AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun,
+    ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point,
+    RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
 use futures::channel::oneshot;
+use parking::Unparker;
 use seahash::SeaHasher;
 use serde::{Deserialize, Serialize};
 use std::borrow::Cow;
@@ -35,12 +36,13 @@ pub use test::*;
 pub use time::UtcOffset;
 
 #[cfg(target_os = "macos")]
-pub(crate) fn current_platform() -> Arc<dyn Platform> {
-    Arc::new(MacPlatform::new())
+pub(crate) fn current_platform() -> Rc<dyn Platform> {
+    Rc::new(MacPlatform::new())
 }
 
 pub(crate) trait Platform: 'static {
-    fn executor(&self) -> Executor;
+    fn background_executor(&self) -> BackgroundExecutor;
+    fn foreground_executor(&self) -> ForegroundExecutor;
     fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 
     fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
@@ -161,7 +163,9 @@ pub trait PlatformDispatcher: Send + Sync {
     fn dispatch(&self, runnable: Runnable);
     fn dispatch_on_main_thread(&self, runnable: Runnable);
     fn dispatch_after(&self, duration: Duration, runnable: Runnable);
-    fn poll(&self) -> bool;
+    fn poll(&self, background_only: bool) -> bool;
+    fn park(&self);
+    fn unparker(&self) -> Unparker;
 
     #[cfg(any(test, feature = "test-support"))]
     fn as_test(&self) -> Option<&TestDispatcher> {

crates/gpui2/src/platform/mac/dispatcher.rs 🔗

@@ -9,8 +9,11 @@ use objc::{
     runtime::{BOOL, YES},
     sel, sel_impl,
 };
+use parking::{Parker, Unparker};
+use parking_lot::Mutex;
 use std::{
     ffi::c_void,
+    sync::Arc,
     time::{Duration, SystemTime},
 };
 
@@ -20,7 +23,17 @@ pub fn dispatch_get_main_queue() -> dispatch_queue_t {
     unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
 }
 
-pub struct MacDispatcher;
+pub struct MacDispatcher {
+    parker: Arc<Mutex<Parker>>,
+}
+
+impl MacDispatcher {
+    pub fn new() -> Self {
+        MacDispatcher {
+            parker: Arc::new(Mutex::new(Parker::new())),
+        }
+    }
+}
 
 impl PlatformDispatcher for MacDispatcher {
     fn is_main_thread(&self) -> bool {
@@ -68,33 +81,20 @@ impl PlatformDispatcher for MacDispatcher {
         }
     }
 
-    fn poll(&self) -> bool {
+    fn poll(&self, _background_only: bool) -> bool {
         false
     }
+
+    fn park(&self) {
+        self.parker.lock().park()
+    }
+
+    fn unparker(&self) -> Unparker {
+        self.parker.lock().unparker()
+    }
 }
 
 extern "C" fn trampoline(runnable: *mut c_void) {
     let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
     task.run();
 }
-
-// #include <dispatch/dispatch.h>
-
-// int main(void) {
-
-//     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-//         // Do some lengthy background work here...
-//         printf("Background Work\n");
-
-//         dispatch_async(dispatch_get_main_queue(), ^{
-//             // Once done, update your UI on the main queue here.
-//             printf("UI Updated\n");
-
-//         });
-//     });
-
-//     sleep(3);  // prevent the program from terminating immediately
-
-//     return 0;
-// }
-// ```

crates/gpui2/src/platform/mac/platform.rs 🔗

@@ -1,9 +1,9 @@
 use super::BoolExt;
 use crate::{
-    AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Executor, InputEvent, MacDispatcher,
-    MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
-    PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp,
-    WindowOptions,
+    AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
+    InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow,
+    PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result,
+    SemanticVersion, VideoTimestamp, WindowOptions,
 };
 use anyhow::anyhow;
 use block::ConcreteBlock;
@@ -143,7 +143,8 @@ unsafe fn build_classes() {
 pub struct MacPlatform(Mutex<MacPlatformState>);
 
 pub struct MacPlatformState {
-    executor: Executor,
+    background_executor: BackgroundExecutor,
+    foreground_executor: ForegroundExecutor,
     text_system: Arc<MacTextSystem>,
     display_linker: MacDisplayLinker,
     pasteboard: id,
@@ -164,8 +165,10 @@ pub struct MacPlatformState {
 
 impl MacPlatform {
     pub fn new() -> Self {
+        let dispatcher = Arc::new(MacDispatcher::new());
         Self(Mutex::new(MacPlatformState {
-            executor: Executor::new(Arc::new(MacDispatcher)),
+            background_executor: BackgroundExecutor::new(dispatcher.clone()),
+            foreground_executor: ForegroundExecutor::new(dispatcher),
             text_system: Arc::new(MacTextSystem::new()),
             display_linker: MacDisplayLinker::new(),
             pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
@@ -345,8 +348,12 @@ impl MacPlatform {
 }
 
 impl Platform for MacPlatform {
-    fn executor(&self) -> Executor {
-        self.0.lock().executor.clone()
+    fn background_executor(&self) -> BackgroundExecutor {
+        self.0.lock().background_executor.clone()
+    }
+
+    fn foreground_executor(&self) -> crate::ForegroundExecutor {
+        self.0.lock().foreground_executor.clone()
     }
 
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
@@ -457,6 +464,10 @@ impl Platform for MacPlatform {
         }
     }
 
+    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
+    //     Box::new(StatusItem::add(self.fonts()))
+    // }
+
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
         MacDisplay::all()
             .into_iter()
@@ -464,10 +475,6 @@ impl Platform for MacPlatform {
             .collect()
     }
 
-    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
-    //     Box::new(StatusItem::add(self.fonts()))
-    // }
-
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
         MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
     }
@@ -481,7 +488,7 @@ impl Platform for MacPlatform {
         handle: AnyWindowHandle,
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow> {
-        Box::new(MacWindow::open(handle, options, self.executor()))
+        Box::new(MacWindow::open(handle, options, self.foreground_executor()))
     }
 
     fn set_display_link_output_callback(
@@ -589,8 +596,8 @@ impl Platform for MacPlatform {
             let path = path.to_path_buf();
             self.0
                 .lock()
-                .executor
-                .spawn_on_main_local(async move {
+                .background_executor
+                .spawn(async move {
                     let full_path = ns_string(path.to_str().unwrap_or(""));
                     let root_full_path = ns_string("");
                     let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
@@ -674,23 +681,6 @@ impl Platform for MacPlatform {
         }
     }
 
-    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
-        unsafe {
-            let bundle: id = NSBundle::mainBundle();
-            if bundle.is_null() {
-                Err(anyhow!("app is not running inside a bundle"))
-            } else {
-                let name = ns_string(name);
-                let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
-                if url.is_null() {
-                    Err(anyhow!("resource not found"))
-                } else {
-                    ns_url_to_path(url)
-                }
-            }
-        }
-    }
-
     // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
     //     self.0.lock().menu_command = Some(callback);
     // }
@@ -717,6 +707,23 @@ impl Platform for MacPlatform {
     //     }
     // }
 
+    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                let name = ns_string(name);
+                let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
+                if url.is_null() {
+                    Err(anyhow!("resource not found"))
+                } else {
+                    ns_url_to_path(url)
+                }
+            }
+        }
+    }
+
     fn set_cursor_style(&self, style: CursorStyle) {
         unsafe {
             let new_cursor: id = match style {

crates/gpui2/src/platform/mac/window.rs 🔗

@@ -1,10 +1,10 @@
 use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
 use crate::{
-    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, ExternalPaths,
-    FileDropEvent, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers,
-    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
-    PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size,
-    Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
+    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths,
+    FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
+    Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
+    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene,
+    Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -315,7 +315,7 @@ struct InsertText {
 
 struct MacWindowState {
     handle: AnyWindowHandle,
-    executor: Executor,
+    executor: ForegroundExecutor,
     native_window: id,
     renderer: MetalRenderer,
     scene_to_render: Option<Scene>,
@@ -451,7 +451,11 @@ unsafe impl Send for MacWindowState {}
 pub struct MacWindow(Arc<Mutex<MacWindowState>>);
 
 impl MacWindow {
-    pub fn open(handle: AnyWindowHandle, options: WindowOptions, executor: Executor) -> Self {
+    pub fn open(
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+        executor: ForegroundExecutor,
+    ) -> Self {
         unsafe {
             let pool = NSAutoreleasePool::new(nil);
 
@@ -674,13 +678,10 @@ impl MacWindow {
 
 impl Drop for MacWindow {
     fn drop(&mut self) {
-        let this = self.0.clone();
-        let executor = self.0.lock().executor.clone();
-        executor
-            .run_on_main(move || unsafe {
-                this.lock().native_window.close();
-            })
-            .detach();
+        let native_window = self.0.lock().native_window;
+        unsafe {
+            native_window.close();
+        }
     }
 }
 
@@ -807,7 +808,7 @@ impl PlatformWindow for MacWindow {
             let native_window = self.0.lock().native_window;
             let executor = self.0.lock().executor.clone();
             executor
-                .spawn_on_main_local(async move {
+                .spawn(async move {
                     let _: () = msg_send![
                         alert,
                         beginSheetModalForWindow: native_window
@@ -824,7 +825,7 @@ impl PlatformWindow for MacWindow {
         let window = self.0.lock().native_window;
         let executor = self.0.lock().executor.clone();
         executor
-            .spawn_on_main_local(async move {
+            .spawn(async move {
                 unsafe {
                     let _: () = msg_send![window, makeKeyAndOrderFront: nil];
                 }
@@ -872,25 +873,17 @@ impl PlatformWindow for MacWindow {
     fn zoom(&self) {
         let this = self.0.lock();
         let window = this.native_window;
-        this.executor
-            .spawn_on_main_local(async move {
-                unsafe {
-                    window.zoom_(nil);
-                }
-            })
-            .detach();
+        unsafe {
+            window.zoom_(nil);
+        }
     }
 
     fn toggle_full_screen(&self) {
         let this = self.0.lock();
         let window = this.native_window;
-        this.executor
-            .spawn_on_main_local(async move {
-                unsafe {
-                    window.toggleFullScreen_(nil);
-                }
-            })
-            .detach();
+        unsafe {
+            window.toggleFullScreen_(nil);
+        }
     }
 
     fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
@@ -1189,7 +1182,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
                 lock.synthetic_drag_counter += 1;
                 let executor = lock.executor.clone();
                 executor
-                    .spawn_on_main_local(synthetic_drag(
+                    .spawn(synthetic_drag(
                         weak_window_state,
                         lock.synthetic_drag_counter,
                         event.clone(),
@@ -1317,7 +1310,7 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id)
     let executor = lock.executor.clone();
     drop(lock);
     executor
-        .spawn_on_main_local(async move {
+        .spawn(async move {
             let mut lock = window_state.as_ref().lock();
             if let Some(mut callback) = lock.activate_callback.take() {
                 drop(lock);

crates/gpui2/src/platform/test/dispatcher.rs 🔗

@@ -2,6 +2,7 @@ use crate::PlatformDispatcher;
 use async_task::Runnable;
 use backtrace::Backtrace;
 use collections::{HashMap, VecDeque};
+use parking::{Parker, Unparker};
 use parking_lot::Mutex;
 use rand::prelude::*;
 use std::{
@@ -19,6 +20,8 @@ struct TestDispatcherId(usize);
 pub struct TestDispatcher {
     id: TestDispatcherId,
     state: Arc<Mutex<TestDispatcherState>>,
+    parker: Arc<Mutex<Parker>>,
+    unparker: Unparker,
 }
 
 struct TestDispatcherState {
@@ -35,6 +38,7 @@ struct TestDispatcherState {
 
 impl TestDispatcher {
     pub fn new(random: StdRng) -> Self {
+        let (parker, unparker) = parking::pair();
         let state = TestDispatcherState {
             random,
             foreground: HashMap::default(),
@@ -50,6 +54,8 @@ impl TestDispatcher {
         TestDispatcher {
             id: TestDispatcherId(0),
             state: Arc::new(Mutex::new(state)),
+            parker: Arc::new(Mutex::new(parker)),
+            unparker,
         }
     }
 
@@ -96,7 +102,7 @@ impl TestDispatcher {
     }
 
     pub fn run_until_parked(&self) {
-        while self.poll() {}
+        while self.poll(false) {}
     }
 
     pub fn parking_allowed(&self) -> bool {
@@ -129,6 +135,8 @@ impl Clone for TestDispatcher {
         Self {
             id: TestDispatcherId(id),
             state: self.state.clone(),
+            parker: self.parker.clone(),
+            unparker: self.unparker.clone(),
         }
     }
 }
@@ -140,6 +148,7 @@ impl PlatformDispatcher for TestDispatcher {
 
     fn dispatch(&self, runnable: Runnable) {
         self.state.lock().background.push(runnable);
+        self.unparker.unpark();
     }
 
     fn dispatch_on_main_thread(&self, runnable: Runnable) {
@@ -149,6 +158,7 @@ impl PlatformDispatcher for TestDispatcher {
             .entry(self.id)
             .or_default()
             .push_back(runnable);
+        self.unparker.unpark();
     }
 
     fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
@@ -160,7 +170,7 @@ impl PlatformDispatcher for TestDispatcher {
         state.delayed.insert(ix, (next_time, runnable));
     }
 
-    fn poll(&self) -> bool {
+    fn poll(&self, background_only: bool) -> bool {
         let mut state = self.state.lock();
 
         while let Some((deadline, _)) = state.delayed.first() {
@@ -171,11 +181,15 @@ impl PlatformDispatcher for TestDispatcher {
             state.background.push(runnable);
         }
 
-        let foreground_len: usize = state
-            .foreground
-            .values()
-            .map(|runnables| runnables.len())
-            .sum();
+        let foreground_len: usize = if background_only {
+            0
+        } else {
+            state
+                .foreground
+                .values()
+                .map(|runnables| runnables.len())
+                .sum()
+        };
         let background_len = state.background.len();
 
         if foreground_len == 0 && background_len == 0 {
@@ -211,62 +225,15 @@ impl PlatformDispatcher for TestDispatcher {
         true
     }
 
-    fn as_test(&self) -> Option<&TestDispatcher> {
-        Some(self)
+    fn park(&self) {
+        self.parker.lock().park();
     }
-}
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::Executor;
-    use std::sync::Arc;
-
-    #[test]
-    fn test_dispatch() {
-        let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
-        let executor = Executor::new(Arc::new(dispatcher));
-
-        let result = executor.block(async { executor.run_on_main(|| 1).await });
-        assert_eq!(result, 1);
-
-        let result = executor.block({
-            let executor = executor.clone();
-            async move {
-                executor
-                    .spawn_on_main({
-                        let executor = executor.clone();
-                        assert!(executor.is_main_thread());
-                        || async move {
-                            assert!(executor.is_main_thread());
-                            let result = executor
-                                .spawn({
-                                    let executor = executor.clone();
-                                    async move {
-                                        assert!(!executor.is_main_thread());
-
-                                        let result = executor
-                                            .spawn_on_main({
-                                                let executor = executor.clone();
-                                                || async move {
-                                                    assert!(executor.is_main_thread());
-                                                    2
-                                                }
-                                            })
-                                            .await;
-
-                                        assert!(!executor.is_main_thread());
-                                        result
-                                    }
-                                })
-                                .await;
-                            assert!(executor.is_main_thread());
-                            result
-                        }
-                    })
-                    .await
-            }
-        });
-        assert_eq!(result, 2);
+    fn unparker(&self) -> Unparker {
+        self.unparker.clone()
+    }
+
+    fn as_test(&self) -> Option<&TestDispatcher> {
+        Some(self)
     }
 }

crates/gpui2/src/platform/test/platform.rs 🔗

@@ -1,21 +1,29 @@
-use crate::{DisplayId, Executor, Platform, PlatformTextSystem};
+use crate::{BackgroundExecutor, DisplayId, ForegroundExecutor, Platform, PlatformTextSystem};
 use anyhow::{anyhow, Result};
 use std::sync::Arc;
 
 pub struct TestPlatform {
-    executor: Executor,
+    background_executor: BackgroundExecutor,
+    foreground_executor: ForegroundExecutor,
 }
 
 impl TestPlatform {
-    pub fn new(executor: Executor) -> Self {
-        TestPlatform { executor }
+    pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Self {
+        TestPlatform {
+            background_executor: executor,
+            foreground_executor,
+        }
     }
 }
 
 // todo!("implement out what our tests needed in GPUI 1")
 impl Platform for TestPlatform {
-    fn executor(&self) -> Executor {
-        self.executor.clone()
+    fn background_executor(&self) -> BackgroundExecutor {
+        self.background_executor.clone()
+    }
+
+    fn foreground_executor(&self) -> ForegroundExecutor {
+        self.foreground_executor.clone()
     }
 
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {

crates/gpui2/src/subscription.rs 🔗

@@ -21,8 +21,8 @@ struct SubscriberSetState<EmitterKey, Callback> {
 
 impl<EmitterKey, Callback> SubscriberSet<EmitterKey, Callback>
 where
-    EmitterKey: 'static + Send + Ord + Clone + Debug,
-    Callback: 'static + Send,
+    EmitterKey: 'static + Ord + Clone + Debug,
+    Callback: 'static,
 {
     pub fn new() -> Self {
         Self(Arc::new(Mutex::new(SubscriberSetState {
@@ -96,7 +96,7 @@ where
 
 #[must_use]
 pub struct Subscription {
-    unsubscribe: Option<Box<dyn FnOnce() + Send + 'static>>,
+    unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
 }
 
 impl Subscription {

crates/gpui2/src/view.rs 🔗

@@ -4,10 +4,13 @@ use crate::{
     Size, ViewContext, VisualContext, WeakModel, WindowContext,
 };
 use anyhow::{Context, Result};
-use std::{any::TypeId, marker::PhantomData};
+use std::{
+    any::{Any, TypeId},
+    marker::PhantomData,
+};
 
 pub trait Render: 'static + Sized {
-    type Element: Element<Self> + 'static + Send;
+    type Element: Element<Self> + 'static;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
 }
@@ -163,7 +166,7 @@ impl<V: Render, ParentV: 'static> Component<ParentV> for EraseViewState<V, Paren
 }
 
 impl<V: Render, ParentV: 'static> Element<ParentV> for EraseViewState<V, ParentV> {
-    type ElementState = AnyBox;
+    type ElementState = Box<dyn Any>;
 
     fn id(&self) -> Option<crate::ElementId> {
         Element::id(&self.view)
@@ -343,7 +346,7 @@ impl<V: Render> From<View<V>> for AnyView {
 }
 
 impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
-    type ElementState = AnyBox;
+    type ElementState = Box<dyn Any>;
 
     fn id(&self) -> Option<ElementId> {
         Some(self.model.entity_id.into())

crates/gpui2/src/window.rs 🔗

@@ -3,12 +3,11 @@ use crate::{
     Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
     Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, 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,
-    TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView,
-    WindowOptions, SUBPIXEL_VARIANTS,
+    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, TaffyLayoutEngine, Task, Underline,
+    UnderlineStyle, View, VisualContext, WeakModel, WeakView, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -52,7 +51,7 @@ pub enum DispatchPhase {
     Capture,
 }
 
-type AnyListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + 'static>;
+type AnyListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
 type AnyKeyListener = Box<
     dyn Fn(
             &dyn Any,
@@ -60,10 +59,9 @@ type AnyKeyListener = Box<
             DispatchPhase,
             &mut WindowContext,
         ) -> Option<Box<dyn Action>>
-        + Send
         + 'static,
 >;
-type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + Send + 'static>;
+type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
 
 slotmap::new_key_type! { pub struct FocusId; }
 
@@ -159,7 +157,7 @@ impl Drop for FocusHandle {
 // Holds the state for a specific window.
 pub struct Window {
     handle: AnyWindowHandle,
-    platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+    platform_window: Box<dyn PlatformWindow>,
     display_id: DisplayId,
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
@@ -194,7 +192,7 @@ impl Window {
     pub(crate) fn new(
         handle: AnyWindowHandle,
         options: WindowOptions,
-        cx: &mut MainThread<AppContext>,
+        cx: &mut AppContext,
     ) -> Self {
         let platform_window = cx.platform().open_window(handle, options);
         let display_id = platform_window.display().id();
@@ -209,12 +207,7 @@ impl Window {
                     cx.window.scale_factor = scale_factor;
                     cx.window.scene_builder = SceneBuilder::new();
                     cx.window.content_size = content_size;
-                    cx.window.display_id = cx
-                        .window
-                        .platform_window
-                        .borrow_on_main_thread()
-                        .display()
-                        .id();
+                    cx.window.display_id = cx.window.platform_window.display().id();
                     cx.window.dirty = true;
                 })
                 .log_err();
@@ -230,8 +223,6 @@ impl Window {
             })
         });
 
-        let platform_window = MainThreadOnly::new(Arc::new(platform_window), cx.executor.clone());
-
         Window {
             handle,
             platform_window,
@@ -378,26 +369,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.notify();
     }
 
-    /// Schedule the given closure to be run on the main thread. It will be invoked with
-    /// a `MainThread<WindowContext>`, which provides access to platform-specific functionality
-    /// of the window.
-    pub fn run_on_main<R>(
-        &mut self,
-        f: impl FnOnce(&mut MainThread<WindowContext<'_, '_>>) -> R + Send + 'static,
-    ) -> Task<Result<R>>
-    where
-        R: Send + 'static,
-    {
-        if self.executor.is_main_thread() {
-            Task::ready(Ok(f(unsafe {
-                mem::transmute::<&mut Self, &mut MainThread<Self>>(self)
-            })))
-        } else {
-            let id = self.window.handle.id;
-            self.app.run_on_main(move |cx| cx.update_window(id, f))
-        }
-    }
-
     /// Create an `AsyncWindowContext`, which has a static lifetime and can be held across
     /// await points in async code.
     pub fn to_async(&self) -> AsyncWindowContext {
@@ -408,44 +379,39 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
         let f = Box::new(f);
         let display_id = self.window.display_id;
-        self.run_on_main(move |cx| {
-            if let Some(callbacks) = cx.next_frame_callbacks.get_mut(&display_id) {
-                callbacks.push(f);
-                // If there was already a callback, it means that we already scheduled a frame.
-                if callbacks.len() > 1 {
-                    return;
-                }
-            } else {
-                let async_cx = cx.to_async();
-                cx.next_frame_callbacks.insert(display_id, vec![f]);
-                cx.platform().set_display_link_output_callback(
-                    display_id,
-                    Box::new(move |_current_time, _output_time| {
-                        let _ = async_cx.update(|cx| {
-                            let callbacks = cx
-                                .next_frame_callbacks
-                                .get_mut(&display_id)
-                                .unwrap()
-                                .drain(..)
-                                .collect::<Vec<_>>();
-                            for callback in callbacks {
-                                callback(cx);
-                            }
 
-                            cx.run_on_main(move |cx| {
-                                if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() {
-                                    cx.platform().stop_display_link(display_id);
-                                }
-                            })
-                            .detach();
-                        });
-                    }),
-                );
+        if let Some(callbacks) = self.next_frame_callbacks.get_mut(&display_id) {
+            callbacks.push(f);
+            // If there was already a callback, it means that we already scheduled a frame.
+            if callbacks.len() > 1 {
+                return;
             }
+        } else {
+            let async_cx = self.to_async();
+            self.next_frame_callbacks.insert(display_id, vec![f]);
+            self.platform().set_display_link_output_callback(
+                display_id,
+                Box::new(move |_current_time, _output_time| {
+                    let _ = async_cx.update(|cx| {
+                        let callbacks = cx
+                            .next_frame_callbacks
+                            .get_mut(&display_id)
+                            .unwrap()
+                            .drain(..)
+                            .collect::<Vec<_>>();
+                        for callback in callbacks {
+                            callback(cx);
+                        }
 
-            cx.platform().start_display_link(display_id);
-        })
-        .detach();
+                        if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() {
+                            cx.platform().stop_display_link(display_id);
+                        }
+                    });
+                }),
+            );
+        }
+
+        self.platform().start_display_link(display_id);
     }
 
     /// Spawn the future returned by the given closure on the application thread pool.
@@ -453,17 +419,16 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     /// use within your future.
     pub fn spawn<Fut, R>(
         &mut self,
-        f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static,
+        f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut,
     ) -> Task<R>
     where
-        R: Send + 'static,
-        Fut: Future<Output = R> + Send + 'static,
+        R: 'static,
+        Fut: Future<Output = R> + 'static,
     {
         let window = self.window.handle;
         self.app.spawn(move |app| {
             let cx = AsyncWindowContext::new(app, window);
-            let future = f(window, cx);
-            async move { future.await }
+            f(window, cx)
         })
     }
 
@@ -575,7 +540,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     /// a specific need to register a global listener.
     pub fn on_mouse_event<Event: 'static>(
         &mut self,
-        handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + Send + 'static,
+        handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
     ) {
         let order = self.window.z_index_stack.clone();
         self.window
@@ -912,14 +877,8 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.window.root_view = Some(root_view);
         let scene = self.window.scene_builder.build();
 
-        self.run_on_main(|cx| {
-            cx.window
-                .platform_window
-                .borrow_on_main_thread()
-                .draw(scene);
-            cx.window.dirty = false;
-        })
-        .detach();
+        self.window.platform_window.draw(scene);
+        self.window.dirty = false;
     }
 
     fn start_frame(&mut self) {
@@ -1252,7 +1211,7 @@ impl Context for WindowContext<'_, '_> {
         build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
     ) -> Model<T>
     where
-        T: 'static + Send,
+        T: 'static,
     {
         let slot = self.app.entities.reserve();
         let model = build_model(&mut ModelContext::mutable(&mut *self.app, slot.downgrade()));
@@ -1282,7 +1241,7 @@ impl VisualContext for WindowContext<'_, '_> {
         build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Send,
+        V: 'static,
     {
         let slot = self.app.entities.reserve();
         let view = View {
@@ -1428,7 +1387,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         f: impl FnOnce(Option<S>, &mut Self) -> (R, S),
     ) -> R
     where
-        S: 'static + Send,
+        S: 'static,
     {
         self.with_element_id(id, |global_id, cx| {
             if let Some(any) = cx
@@ -1466,7 +1425,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         f: impl FnOnce(Option<S>, &mut Self) -> (R, S),
     ) -> R
     where
-        S: 'static + Send,
+        S: 'static,
     {
         if let Some(element_id) = element_id {
             self.with_element_state(element_id, f)
@@ -1778,30 +1737,13 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
         result
     }
 
-    pub fn run_on_main<R>(
-        &mut self,
-        view: &mut V,
-        f: impl FnOnce(&mut V, &mut MainThread<ViewContext<'_, '_, V>>) -> R + Send + 'static,
-    ) -> Task<Result<R>>
-    where
-        R: Send + 'static,
-    {
-        if self.executor.is_main_thread() {
-            let cx = unsafe { mem::transmute::<&mut Self, &mut MainThread<Self>>(self) };
-            Task::ready(Ok(f(view, cx)))
-        } else {
-            let view = self.view().upgrade().unwrap();
-            self.window_cx.run_on_main(move |cx| view.update(cx, f))
-        }
-    }
-
     pub fn spawn<Fut, R>(
         &mut self,
-        f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut + Send + 'static,
+        f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
     ) -> Task<R>
     where
-        R: Send + 'static,
-        Fut: Future<Output = R> + Send + 'static,
+        R: 'static,
+        Fut: Future<Output = R> + 'static,
     {
         let view = self.view();
         self.window_cx.spawn(move |_, cx| {
@@ -1839,7 +1781,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
 
     pub fn on_mouse_event<Event: 'static>(
         &mut self,
-        handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + Send + 'static,
+        handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) {
         let handle = self.view().upgrade().unwrap();
         self.window_cx.on_mouse_event(move |event, phase, cx| {
@@ -1868,13 +1810,10 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> {
     type ModelContext<'b, U> = ModelContext<'b, U>;
     type Result<U> = U;
 
-    fn build_model<T>(
+    fn build_model<T: 'static>(
         &mut self,
         build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
-    ) -> Model<T>
-    where
-        T: 'static + Send,
-    {
+    ) -> Model<T> {
         self.window_cx.build_model(build_model)
     }
 
@@ -1890,7 +1829,7 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> {
 impl<V: 'static> VisualContext for ViewContext<'_, '_, V> {
     type ViewContext<'a, 'w, V2> = ViewContext<'a, 'w, V2>;
 
-    fn build_view<W: 'static + Send>(
+    fn build_view<W: 'static>(
         &mut self,
         build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, W>) -> W,
     ) -> Self::Result<View<W>> {

crates/gpui2_macros/src/test.rs 🔗

@@ -89,9 +89,9 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                             inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(_seed),));
                             continue;
                         }
-                        Some("Executor") => {
-                            inner_fn_args.extend(quote!(gpui2::Executor::new(
-                                std::sync::Arc::new(dispatcher.clone())
+                        Some("BackgroundExecutor") => {
+                            inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new(
+                                std::sync::Arc::new(dispatcher.clone()),
                             ),));
                             continue;
                         }
@@ -134,9 +134,9 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     #num_iterations as u64,
                     #max_retries,
                     &mut |dispatcher, _seed| {
-                        let executor = gpui2::Executor::new(std::sync::Arc::new(dispatcher.clone()));
+                        let executor = gpui2::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
                         #cx_vars
-                        executor.block(#inner_fn_name(#inner_fn_args));
+                        executor.block_test(#inner_fn_name(#inner_fn_args));
                         #cx_teardowns
                     },
                     #on_failure_fn_name,
@@ -170,7 +170,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                                     let mut #cx_varname = gpui2::TestAppContext::new(
                                        dispatcher.clone()
                                     );
-                                    let mut #cx_varname_lock = #cx_varname.app.lock();
+                                    let mut #cx_varname_lock = #cx_varname.app.borrow_mut();
                                 ));
                                 inner_fn_args.extend(quote!(&mut #cx_varname_lock,));
                                 cx_teardowns.extend(quote!(

crates/install_cli2/src/install_cli2.rs 🔗

@@ -7,9 +7,7 @@ use util::ResultExt;
 // actions!(cli, [Install]);
 
 pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
-    let cli_path = cx
-        .run_on_main(|cx| cx.path_for_auxiliary_executable("cli"))?
-        .await?;
+    let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
     let link_path = Path::new("/usr/local/bin/zed");
     let bin_dir_path = link_path.parent().unwrap();
 

crates/journal2/src/journal2.rs 🔗

@@ -77,7 +77,7 @@ pub fn new_journal_entry(_: Arc<AppState>, cx: &mut AppContext) {
     let now = now.time();
     let _entry_heading = heading_entry(now, &settings.hour_format);
 
-    let _create_entry = cx.executor().spawn(async move {
+    let _create_entry = cx.background_executor().spawn(async move {
         std::fs::create_dir_all(month_dir)?;
         OpenOptions::new()
             .create(true)

crates/language2/src/buffer.rs 🔗

@@ -434,7 +434,7 @@ impl Buffer {
         ));
 
         let text_operations = self.text.operations().clone();
-        cx.spawn(|_| async move {
+        cx.background_executor().spawn(async move {
             let since = since.unwrap_or_default();
             operations.extend(
                 text_operations
@@ -652,7 +652,7 @@ impl Buffer {
 
                     if !self.is_dirty() {
                         let reload = self.reload(cx).log_err().map(drop);
-                        task = cx.executor().spawn(reload);
+                        task = cx.background_executor().spawn(reload);
                     }
                 }
             }
@@ -684,7 +684,7 @@ impl Buffer {
         let snapshot = self.snapshot();
 
         let mut diff = self.git_diff.clone();
-        let diff = cx.executor().spawn(async move {
+        let diff = cx.background_executor().spawn(async move {
             diff.update(&diff_base, &snapshot).await;
             diff
         });
@@ -793,7 +793,7 @@ impl Buffer {
         let mut syntax_snapshot = syntax_map.snapshot();
         drop(syntax_map);
 
-        let parse_task = cx.executor().spawn({
+        let parse_task = cx.background_executor().spawn({
             let language = language.clone();
             let language_registry = language_registry.clone();
             async move {
@@ -803,7 +803,7 @@ impl Buffer {
         });
 
         match cx
-            .executor()
+            .background_executor()
             .block_with_timeout(self.sync_parse_timeout, parse_task)
         {
             Ok(new_syntax_snapshot) => {
@@ -866,9 +866,9 @@ impl Buffer {
 
     fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
         if let Some(indent_sizes) = self.compute_autoindents() {
-            let indent_sizes = cx.executor().spawn(indent_sizes);
+            let indent_sizes = cx.background_executor().spawn(indent_sizes);
             match cx
-                .executor()
+                .background_executor()
                 .block_with_timeout(Duration::from_micros(500), indent_sizes)
             {
                 Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx),
@@ -1117,7 +1117,7 @@ impl Buffer {
     pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
         let old_text = self.as_rope().clone();
         let base_version = self.version();
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             let old_text = old_text.to_string();
             let line_ending = LineEnding::detect(&new_text);
             LineEnding::normalize(&mut new_text);
@@ -1155,7 +1155,7 @@ impl Buffer {
         let old_text = self.as_rope().clone();
         let line_ending = self.line_ending();
         let base_version = self.version();
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             let ranges = trailing_whitespace_ranges(&old_text);
             let empty = Arc::<str>::from("");
             Diff {

crates/language2/src/buffer_tests.rs 🔗

@@ -559,7 +559,7 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) {
         cx: &'a gpui2::TestAppContext,
     ) -> Vec<(&'a str, Vec<usize>)> {
         let matches = cx
-            .update(|cx| outline.search(query, cx.executor().clone()))
+            .update(|cx| outline.search(query, cx.background_executor().clone()))
             .await;
         matches
             .into_iter()
@@ -1879,7 +1879,7 @@ fn test_serialization(cx: &mut gpui2::AppContext) {
 
     let state = buffer1.read(cx).to_proto();
     let ops = cx
-        .executor()
+        .background_executor()
         .block(buffer1.read(cx).serialize_ops(None, cx));
     let buffer2 = cx.build_model(|cx| {
         let mut buffer = Buffer::from_proto(1, state, None).unwrap();
@@ -1921,7 +1921,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
         let buffer = cx.build_model(|cx| {
             let state = base_buffer.read(cx).to_proto();
             let ops = cx
-                .executor()
+                .background_executor()
                 .block(base_buffer.read(cx).serialize_ops(None, cx));
             let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
             buffer
@@ -1943,6 +1943,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
             .detach();
             buffer
         });
+
         buffers.push(buffer);
         replica_ids.push(i as ReplicaId);
         network.lock().add_peer(i as ReplicaId);
@@ -2025,7 +2026,9 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
             }
             50..=59 if replica_ids.len() < max_peers => {
                 let old_buffer_state = buffer.read(cx).to_proto();
-                let old_buffer_ops = cx.executor().block(buffer.read(cx).serialize_ops(None, cx));
+                let old_buffer_ops = cx
+                    .background_executor()
+                    .block(buffer.read(cx).serialize_ops(None, cx));
                 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
                     .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
                     .choose(&mut rng)

crates/language2/src/language2.rs 🔗

@@ -17,7 +17,7 @@ use futures::{
     future::{BoxFuture, Shared},
     FutureExt, TryFutureExt as _,
 };
-use gpui2::{AppContext, AsyncAppContext, Executor, Task};
+use gpui2::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
 pub use highlight_map::HighlightMap;
 use lazy_static::lazy_static;
 use lsp2::{CodeActionKind, LanguageServerBinary};
@@ -631,7 +631,7 @@ pub struct LanguageRegistry {
     lsp_binary_paths: Mutex<
         HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
     >,
-    executor: Option<Executor>,
+    executor: Option<BackgroundExecutor>,
     lsp_binary_status_tx: LspBinaryStatusSender,
 }
 
@@ -680,7 +680,7 @@ impl LanguageRegistry {
         Self::new(Task::ready(()))
     }
 
-    pub fn set_executor(&mut self, executor: Executor) {
+    pub fn set_executor(&mut self, executor: BackgroundExecutor) {
         self.executor = Some(executor);
     }
 
@@ -916,7 +916,7 @@ impl LanguageRegistry {
                 }
 
                 let servers_tx = servers_tx.clone();
-                cx.executor()
+                cx.background_executor()
                     .spawn(async move {
                         if fake_server
                             .try_receive_notification::<lsp2::notification::Initialized>()

crates/language2/src/outline.rs 🔗

@@ -1,5 +1,5 @@
 use fuzzy2::{StringMatch, StringMatchCandidate};
-use gpui2::{Executor, HighlightStyle};
+use gpui2::{BackgroundExecutor, HighlightStyle};
 use std::ops::Range;
 
 #[derive(Debug)]
@@ -57,7 +57,7 @@ impl<T> Outline<T> {
         }
     }
 
-    pub async fn search(&self, query: &str, executor: Executor) -> Vec<StringMatch> {
+    pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec<StringMatch> {
         let query = query.trim_start();
         let is_path_query = query.contains(' ');
         let smart_case = query.chars().any(|c| c.is_uppercase());

crates/live_kit_client2/examples/test_app.rs 🔗

@@ -42,7 +42,7 @@ fn main() {
         let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into());
         let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into());
 
-        cx.spawn_on_main(|cx| async move {
+        cx.spawn(|cx| async move {
             let user_a_token = token::create(
                 &live_kit_key,
                 &live_kit_secret,
@@ -104,7 +104,7 @@ fn main() {
             }
 
             println!("Pausing for 5 seconds to test audio, make some noise!");
-            let timer = cx.executor().timer(Duration::from_secs(5));
+            let timer = cx.background_executor().timer(Duration::from_secs(5));
             timer.await;
             let remote_audio_track = room_b
                 .remote_audio_tracks("test-participant-1")

crates/live_kit_client2/src/test.rs 🔗

@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use collections::{BTreeMap, HashMap};
 use futures::Stream;
-use gpui2::Executor;
+use gpui2::BackgroundExecutor;
 use live_kit_server::token;
 use media::core_video::CVImageBuffer;
 use parking_lot::Mutex;
@@ -16,7 +16,7 @@ pub struct TestServer {
     pub api_key: String,
     pub secret_key: String,
     rooms: Mutex<HashMap<String, TestServerRoom>>,
-    executor: Arc<Executor>,
+    executor: Arc<BackgroundExecutor>,
 }
 
 impl TestServer {
@@ -24,7 +24,7 @@ impl TestServer {
         url: String,
         api_key: String,
         secret_key: String,
-        executor: Arc<Executor>,
+        executor: Arc<BackgroundExecutor>,
     ) -> Result<Arc<TestServer>> {
         let mut servers = SERVERS.lock();
         if servers.contains_key(&url) {

crates/lsp2/src/lsp2.rs 🔗

@@ -5,7 +5,7 @@ pub use lsp_types::*;
 use anyhow::{anyhow, Context, Result};
 use collections::HashMap;
 use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite, FutureExt};
-use gpui2::{AsyncAppContext, Executor, Task};
+use gpui2::{AsyncAppContext, BackgroundExecutor, Task};
 use parking_lot::Mutex;
 use postage::{barrier, prelude::Stream};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
@@ -62,7 +62,7 @@ pub struct LanguageServer {
     notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
     response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
     io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
-    executor: Executor,
+    executor: BackgroundExecutor,
     #[allow(clippy::type_complexity)]
     io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
     output_done_rx: Mutex<Option<barrier::Receiver>>,
@@ -248,7 +248,7 @@ impl LanguageServer {
             let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
             stdout.or(stderr)
         });
-        let output_task = cx.executor().spawn({
+        let output_task = cx.background_executor().spawn({
             Self::handle_output(
                 stdin,
                 outbound_rx,
@@ -269,7 +269,7 @@ impl LanguageServer {
             code_action_kinds,
             next_id: Default::default(),
             outbound_tx,
-            executor: cx.executor().clone(),
+            executor: cx.background_executor().clone(),
             io_tasks: Mutex::new(Some((input_task, output_task))),
             output_done_rx: Mutex::new(Some(output_done_rx)),
             root_path: root_path.to_path_buf(),
@@ -595,8 +595,8 @@ impl LanguageServer {
     where
         T: request::Request,
         T::Params: 'static + Send,
-        F: 'static + Send + FnMut(T::Params, AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = Result<T::Result>> + Send,
+        F: 'static + FnMut(T::Params, AsyncAppContext) -> Fut + Send,
+        Fut: 'static + Future<Output = Result<T::Result>>,
     {
         self.on_custom_request(T::METHOD, f)
     }
@@ -629,7 +629,7 @@ impl LanguageServer {
     #[must_use]
     pub fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
     where
-        F: 'static + Send + FnMut(Params, AsyncAppContext),
+        F: 'static + FnMut(Params, AsyncAppContext) + Send,
         Params: DeserializeOwned,
     {
         let prev_handler = self.notification_handlers.lock().insert(
@@ -657,8 +657,8 @@ impl LanguageServer {
         mut f: F,
     ) -> Subscription
     where
-        F: 'static + Send + FnMut(Params, AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = Result<Res>> + Send,
+        F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send,
+        Fut: 'static + Future<Output = Result<Res>>,
         Params: DeserializeOwned + Send + 'static,
         Res: Serialize,
     {
@@ -670,10 +670,10 @@ impl LanguageServer {
                     match serde_json::from_str(params) {
                         Ok(params) => {
                             let response = f(params, cx.clone());
-                            cx.executor()
-                                .spawn_on_main({
+                            cx.foreground_executor()
+                                .spawn({
                                     let outbound_tx = outbound_tx.clone();
-                                    move || async move {
+                                    async move {
                                         let response = match response.await {
                                             Ok(result) => Response {
                                                 jsonrpc: JSON_RPC_VERSION,
@@ -769,7 +769,7 @@ impl LanguageServer {
         next_id: &AtomicUsize,
         response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
         outbound_tx: &channel::Sender<String>,
-        executor: &Executor,
+        executor: &BackgroundExecutor,
         params: T::Params,
     ) -> impl 'static + Future<Output = anyhow::Result<T::Result>>
     where
@@ -1047,8 +1047,9 @@ impl FakeLanguageServer {
             .on_request::<T, _, _>(move |params, cx| {
                 let result = handler(params, cx.clone());
                 let responded_tx = responded_tx.clone();
+                let executor = cx.background_executor().clone();
                 async move {
-                    cx.executor().simulate_random_delay().await;
+                    executor.simulate_random_delay().await;
                     let result = result.await;
                     responded_tx.unbounded_send(()).ok();
                     result

crates/multi_buffer2/src/multi_buffer2.rs 🔗

@@ -878,7 +878,7 @@ impl MultiBuffer {
         cx.spawn(move |this, mut cx| async move {
             let mut excerpt_ranges = Vec::new();
             let mut range_counts = Vec::new();
-            cx.executor()
+            cx.background_executor()
                 .scoped(|scope| {
                     scope.spawn(async {
                         let (ranges, counts) =
@@ -4177,7 +4177,7 @@ mod tests {
         let guest_buffer = cx.build_model(|cx| {
             let state = host_buffer.read(cx).to_proto();
             let ops = cx
-                .executor()
+                .background_executor()
                 .block(host_buffer.read(cx).serialize_ops(None, cx));
             let mut buffer = Buffer::from_proto(1, state, None).unwrap();
             buffer

crates/prettier2/src/prettier2.rs 🔗

@@ -143,7 +143,7 @@ impl Prettier {
     ) -> anyhow::Result<Self> {
         use lsp2::LanguageServerBinary;
 
-        let executor = cx.executor().clone();
+        let executor = cx.background_executor().clone();
         anyhow::ensure!(
             prettier_dir.is_dir(),
             "Prettier dir {prettier_dir:?} is not a directory"

crates/project2/src/lsp_command.rs 🔗

@@ -32,7 +32,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp2::FormattingOptions {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 pub(crate) trait LspCommand: 'static + Sized + Send {
     type Response: 'static + Default + Send;
     type LspRequest: 'static + Send + lsp2::request::Request;
@@ -148,7 +148,7 @@ impl From<lsp2::FormattingOptions> for FormattingOptions {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for PrepareRename {
     type Response = Option<Range<Anchor>>;
     type LspRequest = lsp2::request::PrepareRenameRequest;
@@ -279,7 +279,7 @@ impl LspCommand for PrepareRename {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for PerformRename {
     type Response = ProjectTransaction;
     type LspRequest = lsp2::request::Rename;
@@ -398,7 +398,7 @@ impl LspCommand for PerformRename {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for GetDefinition {
     type Response = Vec<LocationLink>;
     type LspRequest = lsp2::request::GotoDefinition;
@@ -491,7 +491,7 @@ impl LspCommand for GetDefinition {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for GetTypeDefinition {
     type Response = Vec<LocationLink>;
     type LspRequest = lsp2::request::GotoTypeDefinition;
@@ -783,7 +783,7 @@ fn location_links_to_proto(
         .collect()
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for GetReferences {
     type Response = Vec<Location>;
     type LspRequest = lsp2::request::References;
@@ -945,7 +945,7 @@ impl LspCommand for GetReferences {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for GetDocumentHighlights {
     type Response = Vec<DocumentHighlight>;
     type LspRequest = lsp2::request::DocumentHighlightRequest;
@@ -1096,7 +1096,7 @@ impl LspCommand for GetDocumentHighlights {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for GetHover {
     type Response = Option<Hover>;
     type LspRequest = lsp2::request::HoverRequest;
@@ -1314,7 +1314,7 @@ impl LspCommand for GetHover {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for GetCompletions {
     type Response = Vec<Completion>;
     type LspRequest = lsp2::request::Completion;
@@ -1545,7 +1545,7 @@ impl LspCommand for GetCompletions {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for GetCodeActions {
     type Response = Vec<CodeAction>;
     type LspRequest = lsp2::request::CodeActionRequest;
@@ -1684,7 +1684,7 @@ impl LspCommand for GetCodeActions {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for OnTypeFormatting {
     type Response = Option<Transaction>;
     type LspRequest = lsp2::request::OnTypeFormatting;
@@ -2192,7 +2192,7 @@ impl InlayHints {
     }
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl LspCommand for InlayHints {
     type Response = Vec<InlayHint>;
     type LspRequest = lsp2::InlayHintRequest;

crates/project2/src/project2.rs 🔗

@@ -26,8 +26,8 @@ use futures::{
 };
 use globset::{Glob, GlobSet, GlobSetBuilder};
 use gpui2::{
-    AnyModel, AppContext, AsyncAppContext, Context, Entity, EventEmitter, Executor, Model,
-    ModelContext, Task, WeakModel,
+    AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
+    Model, ModelContext, Task, WeakModel,
 };
 use itertools::Itertools;
 use language2::{
@@ -207,7 +207,7 @@ impl DelayedDebounced {
 
         let previous_task = self.task.take();
         self.task = Some(cx.spawn(move |project, mut cx| async move {
-            let mut timer = cx.executor().timer(delay).fuse();
+            let mut timer = cx.background_executor().timer(delay).fuse();
             if let Some(previous_task) = previous_task {
                 previous_task.await;
             }
@@ -1453,7 +1453,7 @@ impl Project {
                             };
                             if client.send(initial_state).log_err().is_some() {
                                 let client = client.clone();
-                                cx.executor()
+                                cx.background_executor()
                                     .spawn(async move {
                                         let mut chunks = split_operations(operations).peekable();
                                         while let Some(chunk) = chunks.next() {
@@ -1758,7 +1758,7 @@ impl Project {
             }
         };
 
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             wait_for_loading_buffer(loading_watch)
                 .await
                 .map_err(|error| anyhow!("{}", error))
@@ -2436,7 +2436,7 @@ impl Project {
                                 Duration::from_secs(1);
 
                             let task = cx.spawn(move |this, mut cx| async move {
-                                cx.executor().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await;
+                                cx.background_executor().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await;
                                 if let Some(this) = this.upgrade() {
                                     this.update(&mut cx, |this, cx| {
                                         this.disk_based_diagnostics_finished(
@@ -3477,7 +3477,7 @@ impl Project {
             });
 
             const PROCESS_TIMEOUT: Duration = Duration::from_secs(5);
-            let mut timeout = cx.executor().timer(PROCESS_TIMEOUT).fuse();
+            let mut timeout = cx.background_executor().timer(PROCESS_TIMEOUT).fuse();
 
             let mut errored = false;
             if let Some(mut process) = process {
@@ -5593,7 +5593,7 @@ impl Project {
             })
             .collect::<Vec<_>>();
 
-        let background = cx.executor().clone();
+        let background = cx.background_executor().clone();
         let path_count: usize = snapshots.iter().map(|s| s.visible_file_count()).sum();
         if path_count == 0 {
             let (_, rx) = smol::channel::bounded(1024);
@@ -5616,11 +5616,11 @@ impl Project {
                 }
             })
             .collect();
-        cx.executor()
+        cx.background_executor()
             .spawn(Self::background_search(
                 unnamed_files,
                 opened_buffers,
-                cx.executor().clone(),
+                cx.background_executor().clone(),
                 self.fs.clone(),
                 workers,
                 query.clone(),
@@ -5631,9 +5631,9 @@ impl Project {
             .detach();
 
         let (buffers, buffers_rx) = Self::sort_candidates_and_open_buffers(matching_paths_rx, cx);
-        let background = cx.executor().clone();
+        let background = cx.background_executor().clone();
         let (result_tx, result_rx) = smol::channel::bounded(1024);
-        cx.executor()
+        cx.background_executor()
             .spawn(async move {
                 let Ok(buffers) = buffers.await else {
                     return;
@@ -5741,7 +5741,7 @@ impl Project {
     async fn background_search(
         unnamed_buffers: Vec<Model<Buffer>>,
         opened_buffers: HashMap<Arc<Path>, (Model<Buffer>, BufferSnapshot)>,
-        executor: Executor,
+        executor: BackgroundExecutor,
         fs: Arc<dyn Fs>,
         workers: usize,
         query: SearchQuery,
@@ -5993,7 +5993,7 @@ impl Project {
             Task::ready(Ok((tree, relative_path)))
         } else {
             let worktree = self.create_local_worktree(abs_path, visible, cx);
-            cx.executor()
+            cx.background_executor()
                 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
         }
     }
@@ -6064,7 +6064,7 @@ impl Project {
                 .shared()
             })
             .clone();
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             match task.await {
                 Ok(worktree) => Ok(worktree),
                 Err(err) => Err(anyhow!("{}", err)),
@@ -6376,7 +6376,7 @@ impl Project {
             let snapshot =
                 worktree_handle.update(&mut cx, |tree, _| tree.as_local().unwrap().snapshot())?;
             let diff_bases_by_buffer = cx
-                .executor()
+                .background_executor()
                 .spawn(async move {
                     future_buffers
                         .into_iter()
@@ -6519,7 +6519,7 @@ impl Project {
                 })
                 .collect::<Vec<_>>();
 
-            cx.executor()
+            cx.background_executor()
                 .spawn(async move {
                     for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| {
                         async move {
@@ -7358,7 +7358,7 @@ impl Project {
                         })
                         .log_err();
 
-                    cx.executor()
+                    cx.background_executor()
                         .spawn(
                             async move {
                                 let operations = operations.await;
@@ -7960,7 +7960,7 @@ impl Project {
                         if let Some(buffer) = this.buffer_for_id(buffer_id) {
                             let operations =
                                 buffer.read(cx).serialize_ops(Some(remote_version), cx);
-                            cx.executor().spawn(async move {
+                            cx.background_executor().spawn(async move {
                                 let operations = operations.await;
                                 for chunk in split_operations(operations) {
                                     client
@@ -7983,7 +7983,7 @@ impl Project {
             // Any incomplete buffers have open requests waiting. Request that the host sends
             // creates these buffers for us again to unblock any waiting futures.
             for id in incomplete_buffer_ids {
-                cx.executor()
+                cx.background_executor()
                     .spawn(client.request(proto::OpenBufferById { project_id, id }))
                     .detach();
             }
@@ -8198,7 +8198,7 @@ impl Project {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<(Range<Anchor>, String)>>> {
         let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx);
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             let snapshot = snapshot?;
             let mut lsp_edits = lsp_edits
                 .into_iter()
@@ -8438,7 +8438,7 @@ impl Project {
             let fs = self.fs.clone();
             cx.spawn(move |this, mut cx| async move {
                 let prettier_dir = match cx
-                    .executor()
+                    .background_executor()
                     .spawn(Prettier::locate(
                         worktree_path.zip(buffer_path).map(
                             |(worktree_root_path, starting_path)| LocateStart {
@@ -8649,7 +8649,7 @@ impl Project {
             .get(&(worktree, default_prettier_dir.to_path_buf()))
             .cloned();
         let fs = Arc::clone(&self.fs);
-        cx.spawn_on_main(move |this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             if let Some(previous_installation_process) = previous_installation_process {
                 previous_installation_process.await;
             }

crates/project2/src/project_tests.rs 🔗

@@ -4,55 +4,86 @@ use futures::{future, StreamExt};
 use gpui2::AppContext;
 use language2::{
     language_settings::{AllLanguageSettings, LanguageSettingsContent},
-    tree_sitter_rust, Diagnostic, FakeLspAdapter, LanguageConfig, LineEnding, OffsetRangeExt,
-    Point, ToPoint,
+    tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
+    LineEnding, OffsetRangeExt, Point, ToPoint,
 };
 use lsp2::Url;
 use parking_lot::Mutex;
 use pretty_assertions::assert_eq;
 use serde_json::json;
-use std::task::Poll;
+use std::{os, task::Poll};
 use unindent::Unindent as _;
-use util::assert_set_eq;
-
-// #[gpui2::test]
-// async fn test_symlinks(cx: &mut gpui2::TestAppContext) {
-//     init_test(cx);
-//     cx.executor().allow_parking();
-
-//     let dir = temp_tree(json!({
-//         "root": {
-//             "apple": "",
-//             "banana": {
-//                 "carrot": {
-//                     "date": "",
-//                     "endive": "",
-//                 }
-//             },
-//             "fennel": {
-//                 "grape": "",
-//             }
-//         }
-//     }));
-
-//     let root_link_path = dir.path().join("root_link");
-//     unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
-//     unix::fs::symlink(
-//         &dir.path().join("root/fennel"),
-//         &dir.path().join("root/finnochio"),
-//     )
-//     .unwrap();
-
-//     let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
-//     project.update(cx, |project, cx| {
-//         let tree = project.worktrees().next().unwrap().read(cx);
-//         assert_eq!(tree.file_count(), 5);
-//         assert_eq!(
-//             tree.inode_for_path("fennel/grape"),
-//             tree.inode_for_path("finnochio/grape")
-//         );
-//     });
-// }
+use util::{assert_set_eq, test::temp_tree};
+
+#[gpui2::test]
+async fn test_block_via_channel(cx: &mut gpui2::TestAppContext) {
+    cx.executor().allow_parking();
+
+    let (tx, mut rx) = futures::channel::mpsc::unbounded();
+    let _thread = std::thread::spawn(move || {
+        std::fs::metadata("/Users").unwrap();
+        std::thread::sleep(Duration::from_millis(1000));
+        tx.unbounded_send(1).unwrap();
+    });
+    rx.next().await.unwrap();
+}
+
+#[gpui2::test]
+async fn test_block_via_smol(cx: &mut gpui2::TestAppContext) {
+    cx.executor().allow_parking();
+
+    let io_task = smol::unblock(move || {
+        println!("sleeping on thread {:?}", std::thread::current().id());
+        std::thread::sleep(Duration::from_millis(10));
+        1
+    });
+
+    let task = cx.foreground_executor().spawn(async move {
+        io_task.await;
+    });
+
+    task.await;
+}
+
+#[gpui2::test]
+async fn test_symlinks(cx: &mut gpui2::TestAppContext) {
+    init_test(cx);
+    cx.executor().allow_parking();
+
+    let dir = temp_tree(json!({
+        "root": {
+            "apple": "",
+            "banana": {
+                "carrot": {
+                    "date": "",
+                    "endive": "",
+                }
+            },
+            "fennel": {
+                "grape": "",
+            }
+        }
+    }));
+
+    let root_link_path = dir.path().join("root_link");
+    os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
+    os::unix::fs::symlink(
+        &dir.path().join("root/fennel"),
+        &dir.path().join("root/finnochio"),
+    )
+    .unwrap();
+
+    let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
+
+    project.update(cx, |project, cx| {
+        let tree = project.worktrees().next().unwrap().read(cx);
+        assert_eq!(tree.file_count(), 5);
+        assert_eq!(
+            tree.inode_for_path("fennel/grape"),
+            tree.inode_for_path("finnochio/grape")
+        );
+    });
+}
 
 #[gpui2::test]
 async fn test_managing_project_specific_settings(cx: &mut gpui2::TestAppContext) {
@@ -916,7 +947,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) {
         .await
         .unwrap();
 
-    let mut events = cx.subscribe(&project);
+    let mut events = cx.events(&project);
 
     let fake_server = fake_servers.next().await.unwrap();
     assert_eq!(
@@ -1047,7 +1078,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui2::TestApp
     project.update(cx, |project, cx| {
         project.restart_language_servers_for_buffers([buffer], cx);
     });
-    let mut events = cx.subscribe(&project);
+    let mut events = cx.events(&project);
 
     // Simulate the newly started server sending more diagnostics.
     let fake_server = fake_servers.next().await.unwrap();
@@ -2058,121 +2089,121 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui2::TestA
     });
 }
 
-// #[gpui2::test]
-// async fn test_invalid_edits_from_lsp2(cx: &mut gpui2::TestAppContext) {
-//     init_test(cx);
-
-//     let text = "
-//         use a::b;
-//         use a::c;
-
-//         fn f() {
-//             b();
-//             c();
-//         }
-//     "
-//     .unindent();
-
-//     let fs = FakeFs::new(cx.executor().clone());
-//     fs.insert_tree(
-//         "/dir",
-//         json!({
-//             "a.rs": text.clone(),
-//         }),
-//     )
-//     .await;
-
-//     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-//     let buffer = project
-//         .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-//         .await
-//         .unwrap();
-
-//     // Simulate the language server sending us edits in a non-ordered fashion,
-//     // with ranges sometimes being inverted or pointing to invalid locations.
-//     let edits = project
-//         .update(cx, |project, cx| {
-//             project.edits_from_lsp(
-//                 &buffer,
-//                 [
-//                     lsp2::TextEdit {
-//                         range: lsp2::Range::new(
-//                             lsp2::Position::new(0, 9),
-//                             lsp2::Position::new(0, 9),
-//                         ),
-//                         new_text: "\n\n".into(),
-//                     },
-//                     lsp2::TextEdit {
-//                         range: lsp2::Range::new(
-//                             lsp2::Position::new(0, 8),
-//                             lsp2::Position::new(0, 4),
-//                         ),
-//                         new_text: "a::{b, c}".into(),
-//                     },
-//                     lsp2::TextEdit {
-//                         range: lsp2::Range::new(
-//                             lsp2::Position::new(1, 0),
-//                             lsp2::Position::new(99, 0),
-//                         ),
-//                         new_text: "".into(),
-//                     },
-//                     lsp2::TextEdit {
-//                         range: lsp2::Range::new(
-//                             lsp2::Position::new(0, 9),
-//                             lsp2::Position::new(0, 9),
-//                         ),
-//                         new_text: "
-//                             fn f() {
-//                                 b();
-//                                 c();
-//                             }"
-//                         .unindent(),
-//                     },
-//                 ],
-//                 LanguageServerId(0),
-//                 None,
-//                 cx,
-//             )
-//         })
-//         .await
-//         .unwrap();
-
-//     buffer.update(cx, |buffer, cx| {
-//         let edits = edits
-//             .into_iter()
-//             .map(|(range, text)| {
-//                 (
-//                     range.start.to_point(buffer)..range.end.to_point(buffer),
-//                     text,
-//                 )
-//             })
-//             .collect::<Vec<_>>();
-
-//         assert_eq!(
-//             edits,
-//             [
-//                 (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
-//                 (Point::new(1, 0)..Point::new(2, 0), "".into())
-//             ]
-//         );
-
-//         for (range, new_text) in edits {
-//             buffer.edit([(range, new_text)], None, cx);
-//         }
-//         assert_eq!(
-//             buffer.text(),
-//             "
-//                 use a::{b, c};
-
-//                 fn f() {
-//                     b();
-//                     c();
-//                 }
-//             "
-//             .unindent()
-//         );
-//     });
-// }
+#[gpui2::test]
+async fn test_invalid_edits_from_lsp2(cx: &mut gpui2::TestAppContext) {
+    init_test(cx);
+
+    let text = "
+        use a::b;
+        use a::c;
+
+        fn f() {
+            b();
+            c();
+        }
+    "
+    .unindent();
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree(
+        "/dir",
+        json!({
+            "a.rs": text.clone(),
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+    let buffer = project
+        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+        .await
+        .unwrap();
+
+    // Simulate the language server sending us edits in a non-ordered fashion,
+    // with ranges sometimes being inverted or pointing to invalid locations.
+    let edits = project
+        .update(cx, |project, cx| {
+            project.edits_from_lsp(
+                &buffer,
+                [
+                    lsp2::TextEdit {
+                        range: lsp2::Range::new(
+                            lsp2::Position::new(0, 9),
+                            lsp2::Position::new(0, 9),
+                        ),
+                        new_text: "\n\n".into(),
+                    },
+                    lsp2::TextEdit {
+                        range: lsp2::Range::new(
+                            lsp2::Position::new(0, 8),
+                            lsp2::Position::new(0, 4),
+                        ),
+                        new_text: "a::{b, c}".into(),
+                    },
+                    lsp2::TextEdit {
+                        range: lsp2::Range::new(
+                            lsp2::Position::new(1, 0),
+                            lsp2::Position::new(99, 0),
+                        ),
+                        new_text: "".into(),
+                    },
+                    lsp2::TextEdit {
+                        range: lsp2::Range::new(
+                            lsp2::Position::new(0, 9),
+                            lsp2::Position::new(0, 9),
+                        ),
+                        new_text: "
+                            fn f() {
+                                b();
+                                c();
+                            }"
+                        .unindent(),
+                    },
+                ],
+                LanguageServerId(0),
+                None,
+                cx,
+            )
+        })
+        .await
+        .unwrap();
+
+    buffer.update(cx, |buffer, cx| {
+        let edits = edits
+            .into_iter()
+            .map(|(range, text)| {
+                (
+                    range.start.to_point(buffer)..range.end.to_point(buffer),
+                    text,
+                )
+            })
+            .collect::<Vec<_>>();
+
+        assert_eq!(
+            edits,
+            [
+                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
+                (Point::new(1, 0)..Point::new(2, 0), "".into())
+            ]
+        );
+
+        for (range, new_text) in edits {
+            buffer.edit([(range, new_text)], None, cx);
+        }
+        assert_eq!(
+            buffer.text(),
+            "
+                use a::{b, c};
+
+                fn f() {
+                    b();
+                    c();
+                }
+            "
+            .unindent()
+        );
+    });
+}
 
 fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
     buffer: &Buffer,
@@ -2292,168 +2323,168 @@ async fn test_definition(cx: &mut gpui2::TestAppContext) {
     }
 }
 
-// #[gpui2::test]
-// async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) {
-//     init_test(cx);
-
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "TypeScript".into(),
-//             path_suffixes: vec!["ts".to_string()],
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_typescript::language_typescript()),
-//     );
-//     let mut fake_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp2::ServerCapabilities {
-//                 completion_provider: Some(lsp2::CompletionOptions {
-//                     trigger_characters: Some(vec![":".to_string()]),
-//                     ..Default::default()
-//                 }),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-
-//     let fs = FakeFs::new(cx.executor().clone());
-//     fs.insert_tree(
-//         "/dir",
-//         json!({
-//             "a.ts": "",
-//         }),
-//     )
-//     .await;
-
-//     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-//     project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-//     let buffer = project
-//         .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-//         .await
-//         .unwrap();
-
-//     let fake_server = fake_language_servers.next().await.unwrap();
-
-//     let text = "let a = b.fqn";
-//     buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-//     let completions = project.update(cx, |project, cx| {
-//         project.completions(&buffer, text.len(), cx)
-//     });
-
-//     fake_server
-//         .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
-//             Ok(Some(lsp2::CompletionResponse::Array(vec![
-//                 lsp2::CompletionItem {
-//                     label: "fullyQualifiedName?".into(),
-//                     insert_text: Some("fullyQualifiedName".into()),
-//                     ..Default::default()
-//                 },
-//             ])))
-//         })
-//         .next()
-//         .await;
-//     let completions = completions.await.unwrap();
-//     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-//     assert_eq!(completions.len(), 1);
-//     assert_eq!(completions[0].new_text, "fullyQualifiedName");
-//     assert_eq!(
-//         completions[0].old_range.to_offset(&snapshot),
-//         text.len() - 3..text.len()
-//     );
-
-//     let text = "let a = \"atoms/cmp\"";
-//     buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-//     let completions = project.update(cx, |project, cx| {
-//         project.completions(&buffer, text.len() - 1, cx)
-//     });
-
-//     fake_server
-//         .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
-//             Ok(Some(lsp2::CompletionResponse::Array(vec![
-//                 lsp2::CompletionItem {
-//                     label: "component".into(),
-//                     ..Default::default()
-//                 },
-//             ])))
-//         })
-//         .next()
-//         .await;
-//     let completions = completions.await.unwrap();
-//     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-//     assert_eq!(completions.len(), 1);
-//     assert_eq!(completions[0].new_text, "component");
-//     assert_eq!(
-//         completions[0].old_range.to_offset(&snapshot),
-//         text.len() - 4..text.len() - 1
-//     );
-// }
-
-// #[gpui2::test]
-// async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) {
-//     init_test(cx);
-
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "TypeScript".into(),
-//             path_suffixes: vec!["ts".to_string()],
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_typescript::language_typescript()),
-//     );
-//     let mut fake_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp2::ServerCapabilities {
-//                 completion_provider: Some(lsp2::CompletionOptions {
-//                     trigger_characters: Some(vec![":".to_string()]),
-//                     ..Default::default()
-//                 }),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-
-//     let fs = FakeFs::new(cx.executor().clone());
-//     fs.insert_tree(
-//         "/dir",
-//         json!({
-//             "a.ts": "",
-//         }),
-//     )
-//     .await;
-
-//     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-//     project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-//     let buffer = project
-//         .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-//         .await
-//         .unwrap();
-
-//     let fake_server = fake_language_servers.next().await.unwrap();
-
-//     let text = "let a = b.fqn";
-//     buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-//     let completions = project.update(cx, |project, cx| {
-//         project.completions(&buffer, text.len(), cx)
-//     });
-
-//     fake_server
-//         .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
-//             Ok(Some(lsp2::CompletionResponse::Array(vec![
-//                 lsp2::CompletionItem {
-//                     label: "fullyQualifiedName?".into(),
-//                     insert_text: Some("fully\rQualified\r\nName".into()),
-//                     ..Default::default()
-//                 },
-//             ])))
-//         })
-//         .next()
-//         .await;
-//     let completions = completions.await.unwrap();
-//     assert_eq!(completions.len(), 1);
-//     assert_eq!(completions[0].new_text, "fully\nQualified\nName");
-// }
+#[gpui2::test]
+async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) {
+    init_test(cx);
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "TypeScript".into(),
+            path_suffixes: vec!["ts".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_typescript::language_typescript()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp2::ServerCapabilities {
+                completion_provider: Some(lsp2::CompletionOptions {
+                    trigger_characters: Some(vec![":".to_string()]),
+                    ..Default::default()
+                }),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree(
+        "/dir",
+        json!({
+            "a.ts": "",
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
+    let buffer = project
+        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
+        .await
+        .unwrap();
+
+    let fake_server = fake_language_servers.next().await.unwrap();
+
+    let text = "let a = b.fqn";
+    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
+    let completions = project.update(cx, |project, cx| {
+        project.completions(&buffer, text.len(), cx)
+    });
+
+    fake_server
+        .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
+            Ok(Some(lsp2::CompletionResponse::Array(vec![
+                lsp2::CompletionItem {
+                    label: "fullyQualifiedName?".into(),
+                    insert_text: Some("fullyQualifiedName".into()),
+                    ..Default::default()
+                },
+            ])))
+        })
+        .next()
+        .await;
+    let completions = completions.await.unwrap();
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
+    assert_eq!(completions.len(), 1);
+    assert_eq!(completions[0].new_text, "fullyQualifiedName");
+    assert_eq!(
+        completions[0].old_range.to_offset(&snapshot),
+        text.len() - 3..text.len()
+    );
+
+    let text = "let a = \"atoms/cmp\"";
+    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
+    let completions = project.update(cx, |project, cx| {
+        project.completions(&buffer, text.len() - 1, cx)
+    });
+
+    fake_server
+        .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
+            Ok(Some(lsp2::CompletionResponse::Array(vec![
+                lsp2::CompletionItem {
+                    label: "component".into(),
+                    ..Default::default()
+                },
+            ])))
+        })
+        .next()
+        .await;
+    let completions = completions.await.unwrap();
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
+    assert_eq!(completions.len(), 1);
+    assert_eq!(completions[0].new_text, "component");
+    assert_eq!(
+        completions[0].old_range.to_offset(&snapshot),
+        text.len() - 4..text.len() - 1
+    );
+}
+
+#[gpui2::test]
+async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) {
+    init_test(cx);
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "TypeScript".into(),
+            path_suffixes: vec!["ts".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_typescript::language_typescript()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp2::ServerCapabilities {
+                completion_provider: Some(lsp2::CompletionOptions {
+                    trigger_characters: Some(vec![":".to_string()]),
+                    ..Default::default()
+                }),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree(
+        "/dir",
+        json!({
+            "a.ts": "",
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
+    let buffer = project
+        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
+        .await
+        .unwrap();
+
+    let fake_server = fake_language_servers.next().await.unwrap();
+
+    let text = "let a = b.fqn";
+    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
+    let completions = project.update(cx, |project, cx| {
+        project.completions(&buffer, text.len(), cx)
+    });
+
+    fake_server
+        .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
+            Ok(Some(lsp2::CompletionResponse::Array(vec![
+                lsp2::CompletionItem {
+                    label: "fullyQualifiedName?".into(),
+                    insert_text: Some("fully\rQualified\r\nName".into()),
+                    ..Default::default()
+                },
+            ])))
+        })
+        .next()
+        .await;
+    let completions = completions.await.unwrap();
+    assert_eq!(completions.len(), 1);
+    assert_eq!(completions[0].new_text, "fully\nQualified\nName");
+}
 
 #[gpui2::test(iterations = 10)]
 async fn test_apply_code_actions_with_commands(cx: &mut gpui2::TestAppContext) {
@@ -2636,212 +2667,214 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui2::TestAppContext) {
     assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
 }
 
-// #[gpui2::test]
-// async fn test_save_as(cx: &mut gpui2::TestAppContext) {
-//     init_test(cx);
-
-//     let fs = FakeFs::new(cx.executor().clone());
-//     fs.insert_tree("/dir", json!({})).await;
-
-//     let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-//     let languages = project.update(cx, |project, _| project.languages().clone());
-//     languages.register(
-//         "/some/path",
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".into()],
-//             ..Default::default()
-//         },
-//         tree_sitter_rust::language(),
-//         vec![],
-//         |_| Default::default(),
-//     );
-
-//     let buffer = project.update(cx, |project, cx| {
-//         project.create_buffer("", None, cx).unwrap()
-//     });
-//     buffer.update(cx, |buffer, cx| {
-//         buffer.edit([(0..0, "abc")], None, cx);
-//         assert!(buffer.is_dirty());
-//         assert!(!buffer.has_conflict());
-//         assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
-//     });
-//     project
-//         .update(cx, |project, cx| {
-//             project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
-
-//     cx.executor().run_until_parked();
-//     buffer.update(cx, |buffer, cx| {
-//         assert_eq!(
-//             buffer.file().unwrap().full_path(cx),
-//             Path::new("dir/file1.rs")
-//         );
-//         assert!(!buffer.is_dirty());
-//         assert!(!buffer.has_conflict());
-//         assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
-//     });
-
-//     let opened_buffer = project
-//         .update(cx, |project, cx| {
-//             project.open_local_buffer("/dir/file1.rs", cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(opened_buffer, buffer);
-// }
+#[gpui2::test]
+async fn test_save_as(cx: &mut gpui2::TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree("/dir", json!({})).await;
+
+    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+
+    let languages = project.update(cx, |project, _| project.languages().clone());
+    languages.register(
+        "/some/path",
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".into()],
+            ..Default::default()
+        },
+        tree_sitter_rust::language(),
+        vec![],
+        |_| Default::default(),
+    );
+
+    let buffer = project.update(cx, |project, cx| {
+        project.create_buffer("", None, cx).unwrap()
+    });
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit([(0..0, "abc")], None, cx);
+        assert!(buffer.is_dirty());
+        assert!(!buffer.has_conflict());
+        assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
+    });
+    project
+        .update(cx, |project, cx| {
+            project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
+
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, cx| {
+        assert_eq!(
+            buffer.file().unwrap().full_path(cx),
+            Path::new("dir/file1.rs")
+        );
+        assert!(!buffer.is_dirty());
+        assert!(!buffer.has_conflict());
+        assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
+    });
+
+    let opened_buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer("/dir/file1.rs", cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(opened_buffer, buffer);
+}
 
 #[gpui2::test(retries = 5)]
-// async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) {
-//     init_test(cx);
-//     cx.executor().allow_parking();
-
-//     let dir = temp_tree(json!({
-//         "a": {
-//             "file1": "",
-//             "file2": "",
-//             "file3": "",
-//         },
-//         "b": {
-//             "c": {
-//                 "file4": "",
-//                 "file5": "",
-//             }
-//         }
-//     }));
-
-//     let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
-//     let rpc = project.update(cx, |p, _| p.client.clone());
-
-//     let buffer_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| {
-//         let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
-//         async move { buffer.await.unwrap() }
-//     };
-//     let id_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| {
-//         project.update(cx, |project, cx| {
-//             let tree = project.worktrees().next().unwrap();
-//             tree.read(cx)
-//                 .entry_for_path(path)
-//                 .unwrap_or_else(|| panic!("no entry for path {}", path))
-//                 .id
-//         })
-//     };
-
-//     let buffer2 = buffer_for_path("a/file2", cx).await;
-//     let buffer3 = buffer_for_path("a/file3", cx).await;
-//     let buffer4 = buffer_for_path("b/c/file4", cx).await;
-//     let buffer5 = buffer_for_path("b/c/file5", cx).await;
-
-//     let file2_id = id_for_path("a/file2", cx);
-//     let file3_id = id_for_path("a/file3", cx);
-//     let file4_id = id_for_path("b/c/file4", cx);
-
-//     // Create a remote copy of this worktree.
-//     let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
-
-//     let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto());
-
-//     let updates = Arc::new(Mutex::new(Vec::new()));
-//     tree.update(cx, |tree, cx| {
-//         let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
-//             let updates = updates.clone();
-//             move |update| {
-//                 updates.lock().push(update);
-//                 async { true }
-//             }
-//         });
-//     });
-
-//     let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx));
-//     cx.executor().run_until_parked();
-
-//     cx.update(|cx| {
-//         assert!(!buffer2.read(cx).is_dirty());
-//         assert!(!buffer3.read(cx).is_dirty());
-//         assert!(!buffer4.read(cx).is_dirty());
-//         assert!(!buffer5.read(cx).is_dirty());
-//     });
-
-//     // Rename and delete files and directories.
-//     tree.flush_fs_events(cx).await;
-//     std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
-//     std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
-//     std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
-//     std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
-//     tree.flush_fs_events(cx).await;
-
-//     let expected_paths = vec![
-//         "a",
-//         "a/file1",
-//         "a/file2.new",
-//         "b",
-//         "d",
-//         "d/file3",
-//         "d/file4",
-//     ];
-
-//     cx.update(|app| {
-//         assert_eq!(
-//             tree.read(app)
-//                 .paths()
-//                 .map(|p| p.to_str().unwrap())
-//                 .collect::<Vec<_>>(),
-//             expected_paths
-//         );
-//     });
-
-//     assert_eq!(id_for_path("a/file2.new", cx), file2_id);
-//     assert_eq!(id_for_path("d/file3", cx), file3_id);
-//     assert_eq!(id_for_path("d/file4", cx), file4_id);
-
-//     cx.update(|cx| {
-//         assert_eq!(
-//             buffer2.read(cx).file().unwrap().path().as_ref(),
-//             Path::new("a/file2.new")
-//         );
-//         assert_eq!(
-//             buffer3.read(cx).file().unwrap().path().as_ref(),
-//             Path::new("d/file3")
-//         );
-//         assert_eq!(
-//             buffer4.read(cx).file().unwrap().path().as_ref(),
-//             Path::new("d/file4")
-//         );
-//         assert_eq!(
-//             buffer5.read(cx).file().unwrap().path().as_ref(),
-//             Path::new("b/c/file5")
-//         );
-
-//         assert!(!buffer2.read(cx).file().unwrap().is_deleted());
-//         assert!(!buffer3.read(cx).file().unwrap().is_deleted());
-//         assert!(!buffer4.read(cx).file().unwrap().is_deleted());
-//         assert!(buffer5.read(cx).file().unwrap().is_deleted());
-//     });
-
-//     // Update the remote worktree. Check that it becomes consistent with the
-//     // local worktree.
-//     cx.executor().run_until_parked();
-
-//     remote.update(cx, |remote, _| {
-//         for update in updates.lock().drain(..) {
-//             remote.as_remote_mut().unwrap().update_from_remote(update);
-//         }
-//     });
-//     cx.executor().run_until_parked();
-//     remote.update(cx, |remote, _| {
-//         assert_eq!(
-//             remote
-//                 .paths()
-//                 .map(|p| p.to_str().unwrap())
-//                 .collect::<Vec<_>>(),
-//             expected_paths
-//         );
-//     });
-// }
+async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) {
+    init_test(cx);
+    cx.executor().allow_parking();
+
+    let dir = temp_tree(json!({
+        "a": {
+            "file1": "",
+            "file2": "",
+            "file3": "",
+        },
+        "b": {
+            "c": {
+                "file4": "",
+                "file5": "",
+            }
+        }
+    }));
+
+    let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
+    let rpc = project.update(cx, |p, _| p.client.clone());
+
+    let buffer_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| {
+        let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
+        async move { buffer.await.unwrap() }
+    };
+    let id_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| {
+        project.update(cx, |project, cx| {
+            let tree = project.worktrees().next().unwrap();
+            tree.read(cx)
+                .entry_for_path(path)
+                .unwrap_or_else(|| panic!("no entry for path {}", path))
+                .id
+        })
+    };
+
+    let buffer2 = buffer_for_path("a/file2", cx).await;
+    let buffer3 = buffer_for_path("a/file3", cx).await;
+    let buffer4 = buffer_for_path("b/c/file4", cx).await;
+    let buffer5 = buffer_for_path("b/c/file5", cx).await;
+
+    let file2_id = id_for_path("a/file2", cx);
+    let file3_id = id_for_path("a/file3", cx);
+    let file4_id = id_for_path("b/c/file4", cx);
+
+    // Create a remote copy of this worktree.
+    let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
+
+    let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto());
+
+    let updates = Arc::new(Mutex::new(Vec::new()));
+    tree.update(cx, |tree, cx| {
+        let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
+            let updates = updates.clone();
+            move |update| {
+                updates.lock().push(update);
+                async { true }
+            }
+        });
+    });
+
+    let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx));
+
+    cx.executor().run_until_parked();
+
+    cx.update(|cx| {
+        assert!(!buffer2.read(cx).is_dirty());
+        assert!(!buffer3.read(cx).is_dirty());
+        assert!(!buffer4.read(cx).is_dirty());
+        assert!(!buffer5.read(cx).is_dirty());
+    });
+
+    // Rename and delete files and directories.
+    tree.flush_fs_events(cx).await;
+    std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
+    std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
+    std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
+    std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
+    tree.flush_fs_events(cx).await;
+
+    let expected_paths = vec![
+        "a",
+        "a/file1",
+        "a/file2.new",
+        "b",
+        "d",
+        "d/file3",
+        "d/file4",
+    ];
+
+    cx.update(|app| {
+        assert_eq!(
+            tree.read(app)
+                .paths()
+                .map(|p| p.to_str().unwrap())
+                .collect::<Vec<_>>(),
+            expected_paths
+        );
+    });
+
+    assert_eq!(id_for_path("a/file2.new", cx), file2_id);
+    assert_eq!(id_for_path("d/file3", cx), file3_id);
+    assert_eq!(id_for_path("d/file4", cx), file4_id);
+
+    cx.update(|cx| {
+        assert_eq!(
+            buffer2.read(cx).file().unwrap().path().as_ref(),
+            Path::new("a/file2.new")
+        );
+        assert_eq!(
+            buffer3.read(cx).file().unwrap().path().as_ref(),
+            Path::new("d/file3")
+        );
+        assert_eq!(
+            buffer4.read(cx).file().unwrap().path().as_ref(),
+            Path::new("d/file4")
+        );
+        assert_eq!(
+            buffer5.read(cx).file().unwrap().path().as_ref(),
+            Path::new("b/c/file5")
+        );
+
+        assert!(!buffer2.read(cx).file().unwrap().is_deleted());
+        assert!(!buffer3.read(cx).file().unwrap().is_deleted());
+        assert!(!buffer4.read(cx).file().unwrap().is_deleted());
+        assert!(buffer5.read(cx).file().unwrap().is_deleted());
+    });
+
+    // Update the remote worktree. Check that it becomes consistent with the
+    // local worktree.
+    cx.executor().run_until_parked();
+
+    remote.update(cx, |remote, _| {
+        for update in updates.lock().drain(..) {
+            remote.as_remote_mut().unwrap().update_from_remote(update);
+        }
+    });
+    cx.executor().run_until_parked();
+    remote.update(cx, |remote, _| {
+        assert_eq!(
+            remote
+                .paths()
+                .map(|p| p.to_str().unwrap())
+                .collect::<Vec<_>>(),
+            expected_paths
+        );
+    });
+}
+
 #[gpui2::test(iterations = 10)]
 async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) {
     init_test(cx);

crates/project2/src/worktree.rs 🔗

@@ -17,12 +17,13 @@ use futures::{
     },
     select_biased,
     task::Poll,
-    FutureExt, Stream, StreamExt,
+    FutureExt as _, Stream, StreamExt,
 };
 use fuzzy2::CharBag;
 use git::{DOT_GIT, GITIGNORE};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext, Task,
+    AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
+    Task,
 };
 use language2::{
     proto::{
@@ -296,6 +297,7 @@ impl Worktree {
         // 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();
+
         let metadata = fs
             .metadata(&abs_path)
             .await
@@ -364,10 +366,10 @@ impl Worktree {
             })
             .detach();
 
-            let background_scanner_task = cx.executor().spawn({
+            let background_scanner_task = cx.background_executor().spawn({
                 let fs = fs.clone();
                 let snapshot = snapshot.clone();
-                let background = cx.executor().clone();
+                let background = cx.background_executor().clone();
                 async move {
                     let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
                     BackgroundScanner::new(
@@ -428,7 +430,7 @@ impl Worktree {
             let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
             let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
 
-            cx.executor()
+            cx.background_executor()
                 .spawn({
                     let background_snapshot = background_snapshot.clone();
                     async move {
@@ -600,7 +602,7 @@ impl LocalWorktree {
                 .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))?
                 .await?;
             let text_buffer = cx
-                .executor()
+                .background_executor()
                 .spawn(async move { text::Buffer::new(0, id, contents) })
                 .await;
             cx.build_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
@@ -888,7 +890,7 @@ impl LocalWorktree {
                 if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
                     let repo = repo.repo_ptr.clone();
                     index_task = Some(
-                        cx.executor()
+                        cx.background_executor()
                             .spawn(async move { repo.lock().load_index_text(&repo_path) }),
                     );
                 }
@@ -1007,7 +1009,7 @@ impl LocalWorktree {
         let lowest_ancestor = self.lowest_ancestor(&path);
         let abs_path = self.absolutize(&path);
         let fs = self.fs.clone();
-        let write = cx.executor().spawn(async move {
+        let write = cx.background_executor().spawn(async move {
             if is_dir {
                 fs.create_dir(&abs_path).await
             } else {
@@ -1057,7 +1059,7 @@ impl LocalWorktree {
         let abs_path = self.absolutize(&path);
         let fs = self.fs.clone();
         let write = cx
-            .executor()
+            .background_executor()
             .spawn(async move { fs.save(&abs_path, &text, line_ending).await });
 
         cx.spawn(|this, mut cx| async move {
@@ -1078,7 +1080,7 @@ impl LocalWorktree {
         let abs_path = self.absolutize(&entry.path);
         let fs = self.fs.clone();
 
-        let delete = cx.executor().spawn(async move {
+        let delete = cx.background_executor().spawn(async move {
             if entry.is_file() {
                 fs.remove_file(&abs_path, Default::default()).await?;
             } else {
@@ -1118,7 +1120,7 @@ impl LocalWorktree {
         let abs_old_path = self.absolutize(&old_path);
         let abs_new_path = self.absolutize(&new_path);
         let fs = self.fs.clone();
-        let rename = cx.executor().spawn(async move {
+        let rename = cx.background_executor().spawn(async move {
             fs.rename(&abs_old_path, &abs_new_path, Default::default())
                 .await
         });
@@ -1145,7 +1147,7 @@ impl LocalWorktree {
         let abs_old_path = self.absolutize(&old_path);
         let abs_new_path = self.absolutize(&new_path);
         let fs = self.fs.clone();
-        let copy = cx.executor().spawn(async move {
+        let copy = cx.background_executor().spawn(async move {
             copy_recursive(
                 fs.as_ref(),
                 &abs_old_path,
@@ -1173,7 +1175,7 @@ impl LocalWorktree {
     ) -> Option<Task<Result<()>>> {
         let path = self.entry_for_id(entry_id)?.path.clone();
         let mut refresh = self.refresh_entries_for_paths(vec![path]);
-        Some(cx.executor().spawn(async move {
+        Some(cx.background_executor().spawn(async move {
             refresh.next().await;
             Ok(())
         }))
@@ -1247,7 +1249,7 @@ impl LocalWorktree {
             .ok();
 
         let worktree_id = cx.entity_id().as_u64();
-        let _maintain_remote_snapshot = cx.executor().spawn(async move {
+        let _maintain_remote_snapshot = cx.background_executor().spawn(async move {
             let mut is_first = true;
             while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await {
                 let update;
@@ -1305,7 +1307,7 @@ impl LocalWorktree {
         let rx = self.observe_updates(project_id, cx, move |update| {
             client.request(update).map(|result| result.is_ok())
         });
-        cx.executor()
+        cx.background_executor()
             .spawn(async move { rx.await.map_err(|_| anyhow!("share ended")) })
     }
 
@@ -2671,7 +2673,8 @@ impl language2::LocalFile for File {
         let worktree = self.worktree.read(cx).as_local().unwrap();
         let abs_path = worktree.absolutize(&self.path);
         let fs = worktree.fs.clone();
-        cx.executor().spawn(async move { fs.load(&abs_path).await })
+        cx.background_executor()
+            .spawn(async move { fs.load(&abs_path).await })
     }
 
     fn buffer_reloaded(
@@ -3012,7 +3015,7 @@ struct BackgroundScanner {
     state: Mutex<BackgroundScannerState>,
     fs: Arc<dyn Fs>,
     status_updates_tx: UnboundedSender<ScanState>,
-    executor: Executor,
+    executor: BackgroundExecutor,
     scan_requests_rx: channel::Receiver<ScanRequest>,
     path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     next_entry_id: Arc<AtomicUsize>,
@@ -3032,7 +3035,7 @@ impl BackgroundScanner {
         next_entry_id: Arc<AtomicUsize>,
         fs: Arc<dyn Fs>,
         status_updates_tx: UnboundedSender<ScanState>,
-        executor: Executor,
+        executor: BackgroundExecutor,
         scan_requests_rx: channel::Receiver<ScanRequest>,
         path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     ) -> Self {
@@ -4050,7 +4053,8 @@ impl WorktreeModelHandle for Model<Worktree> {
         &self,
         cx: &'a mut gpui2::TestAppContext,
     ) -> futures::future::LocalBoxFuture<'a, ()> {
-        let filename = "fs-event-sentinel";
+        let file_name = "fs-event-sentinel";
+
         let tree = self.clone();
         let (fs, root_path) = self.update(cx, |tree, _| {
             let tree = tree.as_local().unwrap();
@@ -4058,17 +4062,18 @@ impl WorktreeModelHandle for Model<Worktree> {
         });
 
         async move {
-            fs.create_file(&root_path.join(filename), Default::default())
+            fs.create_file(&root_path.join(file_name), Default::default())
                 .await
                 .unwrap();
-            cx.executor().run_until_parked();
-            assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some()));
 
-            fs.remove_file(&root_path.join(filename), Default::default())
+            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some())
+                .await;
+
+            fs.remove_file(&root_path.join(file_name), Default::default())
                 .await
                 .unwrap();
-            cx.executor().run_until_parked();
-            assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none()));
+            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_none())
+                .await;
 
             cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
                 .await;

crates/rpc2/src/conn.rs 🔗

@@ -34,7 +34,7 @@ impl Connection {
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn in_memory(
-        executor: gpui2::Executor,
+        executor: gpui2::BackgroundExecutor,
     ) -> (Self, Self, std::sync::Arc<std::sync::atomic::AtomicBool>) {
         use std::sync::{
             atomic::{AtomicBool, Ordering::SeqCst},
@@ -53,7 +53,7 @@ impl Connection {
         #[allow(clippy::type_complexity)]
         fn channel(
             killed: Arc<AtomicBool>,
-            executor: gpui2::Executor,
+            executor: gpui2::BackgroundExecutor,
         ) -> (
             Box<dyn Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
             Box<dyn Send + Unpin + futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>>,

crates/rpc2/src/peer.rs 🔗

@@ -342,7 +342,7 @@ impl Peer {
     pub fn add_test_connection(
         self: &Arc<Self>,
         connection: Connection,
-        executor: gpui2::Executor,
+        executor: gpui2::BackgroundExecutor,
     ) -> (
         ConnectionId,
         impl Future<Output = anyhow::Result<()>> + Send,
@@ -559,7 +559,6 @@ mod tests {
     use async_tungstenite::tungstenite::Message as WebSocketMessage;
     use gpui2::TestAppContext;
 
-    #[ctor::ctor]
     fn init_logger() {
         if std::env::var("RUST_LOG").is_ok() {
             env_logger::init();
@@ -568,6 +567,8 @@ mod tests {
 
     #[gpui2::test(iterations = 50)]
     async fn test_request_response(cx: &mut TestAppContext) {
+        init_logger();
+
         let executor = cx.executor();
 
         // create 2 clients connected to 1 server

crates/settings2/src/settings_file.rs 🔗

@@ -2,7 +2,7 @@ use crate::{settings_store::SettingsStore, Settings};
 use anyhow::Result;
 use fs2::Fs;
 use futures::{channel::mpsc, StreamExt};
-use gpui2::{AppContext, Executor};
+use gpui2::{AppContext, BackgroundExecutor};
 use std::{io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};
 use util::{paths, ResultExt};
 
@@ -28,7 +28,7 @@ pub fn test_settings() -> String {
 }
 
 pub fn watch_config_file(
-    executor: &Executor,
+    executor: &BackgroundExecutor,
     fs: Arc<dyn Fs>,
     path: PathBuf,
 ) -> mpsc::UnboundedReceiver<String> {
@@ -63,7 +63,10 @@ pub fn handle_settings_file_changes(
     mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
     cx: &mut AppContext,
 ) {
-    let user_settings_content = cx.executor().block(user_settings_file_rx.next()).unwrap();
+    let user_settings_content = cx
+        .background_executor()
+        .block(user_settings_file_rx.next())
+        .unwrap();
     cx.update_global(|store: &mut SettingsStore, cx| {
         store
             .set_user_settings(&user_settings_content, cx)

crates/sqlez/src/thread_safe_connection.rs 🔗

@@ -336,13 +336,13 @@ mod test {
                         FOREIGN KEY(dock_pane) REFERENCES panes(pane_id),
                         FOREIGN KEY(active_pane) REFERENCES panes(pane_id)
                     ) STRICT;
-                    
+
                     CREATE TABLE panes(
                         pane_id INTEGER PRIMARY KEY,
                         workspace_id INTEGER NOT NULL,
                         active INTEGER NOT NULL, -- Boolean
-                        FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) 
-                            ON DELETE CASCADE 
+                        FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+                            ON DELETE CASCADE
                             ON UPDATE CASCADE
                     ) STRICT;
                 "]

crates/terminal2/src/terminal2.rs 🔗

@@ -51,8 +51,8 @@ use thiserror::Error;
 
 use gpui2::{
     px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
-    MainThread, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
-    Pixels, Point, ScrollWheelEvent, Size, Task, TouchPhase,
+    ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
+    Point, ScrollWheelEvent, Size, Task, TouchPhase,
 };
 
 use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
@@ -403,7 +403,7 @@ impl TerminalBuilder {
 
     pub fn subscribe(mut self, cx: &mut ModelContext<Terminal>) -> Terminal {
         //Event loop
-        cx.spawn_on_main(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             use futures::StreamExt;
 
             while let Some(event) = self.events_rx.next().await {
@@ -414,7 +414,10 @@ impl TerminalBuilder {
 
                 'outer: loop {
                     let mut events = vec![];
-                    let mut timer = cx.executor().timer(Duration::from_millis(4)).fuse();
+                    let mut timer = cx
+                        .background_executor()
+                        .timer(Duration::from_millis(4))
+                        .fuse();
                     let mut wakeup = false;
                     loop {
                         futures::select_biased! {
@@ -551,7 +554,7 @@ pub struct Terminal {
 }
 
 impl Terminal {
-    fn process_event(&mut self, event: &AlacTermEvent, cx: &mut MainThread<ModelContext<Self>>) {
+    fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext<Self>) {
         match event {
             AlacTermEvent::Title(title) => {
                 self.breadcrumb_text = title.to_string();
@@ -708,8 +711,7 @@ impl Terminal {
 
             InternalEvent::Copy => {
                 if let Some(txt) = term.selection_to_string() {
-                    cx.run_on_main(|cx| cx.write_to_clipboard(ClipboardItem::new(txt)))
-                        .detach();
+                    cx.write_to_clipboard(ClipboardItem::new(txt))
                 }
             }
             InternalEvent::ScrollToAlacPoint(point) => {
@@ -982,7 +984,7 @@ impl Terminal {
             term.lock_unfair() //It's been too long, force block
         } else if let None = self.sync_task {
             //Skip this frame
-            let delay = cx.executor().timer(Duration::from_millis(16));
+            let delay = cx.background_executor().timer(Duration::from_millis(16));
             self.sync_task = Some(cx.spawn(|weak_handle, mut cx| async move {
                 delay.await;
                 if let Some(handle) = weak_handle.upgrade() {
@@ -1189,7 +1191,7 @@ impl Terminal {
         &mut self,
         e: &MouseUpEvent,
         origin: Point<Pixels>,
-        cx: &mut MainThread<ModelContext<Self>>,
+        cx: &mut ModelContext<Self>,
     ) {
         let setting = TerminalSettings::get_global(cx);
 
@@ -1300,7 +1302,7 @@ impl Terminal {
         cx: &mut ModelContext<Self>,
     ) -> Task<Vec<RangeInclusive<AlacPoint>>> {
         let term = self.term.clone();
-        cx.executor().spawn(async move {
+        cx.background_executor().spawn(async move {
             let term = term.lock();
 
             all_search_matches(&term, &searcher).collect()

crates/ui2/src/components/panes.rs 🔗

@@ -51,7 +51,7 @@ impl<V: 'static> Pane<V> {
                     .id("drag-target")
                     .drag_over::<ExternalPaths>(|d| d.bg(red()))
                     .on_drop(|_, files: View<ExternalPaths>, cx| {
-                        dbg!("dropped files!", files.read(cx));
+                        eprintln!("dropped files! {:?}", files.read(cx));
                     })
                     .absolute()
                     .inset_0(),

crates/ui2/src/components/tab.rs 🔗

@@ -129,7 +129,7 @@ impl Tab {
             .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone()))
             .drag_over::<TabDragState>(|d| d.bg(black()))
             .on_drop(|_view, state: View<TabDragState>, cx| {
-                dbg!(state.read(cx));
+                eprintln!("{:?}", state.read(cx));
             })
             .px_2()
             .py_0p5()

crates/zed2/src/main.rs 🔗

@@ -62,20 +62,26 @@ fn main() {
     log::info!("========== starting zed ==========");
     let app = App::production(Arc::new(Assets));
 
-    let installation_id = app.executor().block(installation_id()).ok();
+    let installation_id = app.background_executor().block(installation_id()).ok();
     let session_id = Uuid::new_v4().to_string();
     init_panic_hook(&app, installation_id.clone(), session_id.clone());
 
     let fs = Arc::new(RealFs);
-    let user_settings_file_rx =
-        watch_config_file(&app.executor(), fs.clone(), paths::SETTINGS.clone());
-    let _user_keymap_file_rx =
-        watch_config_file(&app.executor(), fs.clone(), paths::KEYMAP.clone());
+    let user_settings_file_rx = watch_config_file(
+        &app.background_executor(),
+        fs.clone(),
+        paths::SETTINGS.clone(),
+    );
+    let _user_keymap_file_rx = watch_config_file(
+        &app.background_executor(),
+        fs.clone(),
+        paths::KEYMAP.clone(),
+    );
 
     let login_shell_env_loaded = if stdout_is_a_pty() {
         Task::ready(())
     } else {
-        app.executor().spawn(async {
+        app.background_executor().spawn(async {
             load_login_shell_environment().await.log_err();
         })
     };
@@ -111,7 +117,7 @@ fn main() {
         let client = client2::Client::new(http.clone(), cx);
         let mut languages = LanguageRegistry::new(login_shell_env_loaded);
         let copilot_language_server_id = languages.next_language_server_id();
-        languages.set_executor(cx.executor().clone());
+        languages.set_executor(cx.background_executor().clone());
         languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
         let languages = Arc::new(languages);
         let node_runtime = RealNodeRuntime::new(http.clone());
@@ -508,7 +514,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
 fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
     let telemetry_settings = *client2::TelemetrySettings::get_global(cx);
 
-    cx.executor()
+    cx.background_executor()
         .spawn(async move {
             let panic_report_url = format!("{}/api/panic", &*client2::ZED_SERVER_URL);
             let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
@@ -638,7 +644,7 @@ fn load_embedded_fonts(cx: &AppContext) {
     let asset_source = cx.asset_source();
     let font_paths = asset_source.list("fonts").unwrap();
     let embedded_fonts = Mutex::new(Vec::new());
-    let executor = cx.executor();
+    let executor = cx.background_executor();
 
     executor.block(executor.scoped(|scope| {
         for font_path in &font_paths {