diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 550eda882beb27099ea221a7eaf87569f6e7391d..c1d2457ed42f8d44feb0e1bb8587fa81e2e6fcc9 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -6,26 +6,27 @@ jobs: discord_release: runs-on: ubuntu-latest steps: - - name: Get release URL - id: get-release-url - run: | - if [ "${{ github.event.release.prerelease }}" == "true" ]; then - URL="https://zed.dev/releases/preview/latest" - else - URL="https://zed.dev/releases/stable/latest" - fi - echo "::set-output name=URL::$URL" - - name: Get content - uses: 2428392/gh-truncate-string-action@v1.2.0 - id: get-content - with: - stringToTruncate: | - 📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released! + - name: Get release URL + id: get-release-url + run: | + if [ "${{ github.event.release.prerelease }}" == "true" ]; then + URL="https://zed.dev/releases/preview/latest" + else + URL="https://zed.dev/releases/stable/latest" + fi + echo "::set-output name=URL::$URL" + - name: Get content + uses: 2428392/gh-truncate-string-action@v1.3.0 + id: get-content + with: + stringToTruncate: | + 📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released! - ${{ github.event.release.body }} - maxLength: 2000 - - name: Discord Webhook Action - uses: tsickert/discord-webhook@v5.3.0 - with: - webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} - content: ${{ steps.get-content.outputs.string }} + ${{ github.event.release.body }} + maxLength: 2000 + truncationSymbol: "..." + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v5.3.0 + with: + webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} + content: ${{ steps.get-content.outputs.string }} diff --git a/Cargo.lock b/Cargo.lock index 5d6ce29b14457db8fb22505e788a83ac4b5a3622..903dd125c5809670142481c544cb28db0b6e06f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1603,7 +1603,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.27.0" +version = "0.28.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/ai/src/test.rs b/crates/ai/src/test.rs index d4165f3cca897c4adbf11c2babf6038a8d86f0a6..3f331da1175e32da22fa9b2acd6ba31800c85757 100644 --- a/crates/ai/src/test.rs +++ b/crates/ai/src/test.rs @@ -153,10 +153,17 @@ impl FakeCompletionProvider { pub fn send_completion(&self, completion: impl Into) { let mut tx = self.last_completion_tx.lock(); - tx.as_mut().unwrap().try_send(completion.into()).unwrap(); + + println!("COMPLETION TX: {:?}", &tx); + + let a = tx.as_mut().unwrap(); + a.try_send(completion.into()).unwrap(); + + // tx.as_mut().unwrap().try_send(completion.into()).unwrap(); } pub fn finish_completion(&self) { + println!("FINISHING COMPLETION"); self.last_completion_tx.lock().take().unwrap(); } } @@ -181,8 +188,10 @@ impl CompletionProvider for FakeCompletionProvider { &self, _prompt: Box, ) -> BoxFuture<'static, anyhow::Result>>> { + println!("COMPLETING"); let (tx, rx) = mpsc::channel(1); *self.last_completion_tx.lock() = Some(tx); + println!("TX: {:?}", *self.last_completion_tx.lock()); async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed() } fn box_clone(&self) -> Box { diff --git a/crates/ai2/src/auth.rs b/crates/ai2/src/auth.rs index e4670bb449025d5ecc5f0cabe65ad6ff4727c10c..995f20d39cb9e07a2d40c58be36ee3e5d4c205a1 100644 --- a/crates/ai2/src/auth.rs +++ b/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); } diff --git a/crates/ai2/src/providers/open_ai/completion.rs b/crates/ai2/src/providers/open_ai/completion.rs index eca56110271a3be407a1c8b9f82cbb63c41bef23..bf9dc704a26dd96b42fb2cc72a0a79ab384c0f9b 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/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: Arc, request: Box, ) -> Result>> { let api_key = match credential { @@ -198,11 +197,11 @@ pub async fn stream_completion( pub struct OpenAICompletionProvider { model: OpenAILanguageModel, credential: Arc>, - executor: Arc, + executor: Arc, } impl OpenAICompletionProvider { - pub fn new(model_name: &str, executor: Arc) -> Self { + pub fn new(model_name: &str, executor: Arc) -> 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; } } diff --git a/crates/ai2/src/providers/open_ai/embedding.rs b/crates/ai2/src/providers/open_ai/embedding.rs index fc49c15134d0aba787968acbd412daff60ce6106..27a01328f373933c47af1321c52d90e720cb7334 100644 --- a/crates/ai2/src/providers/open_ai/embedding.rs +++ b/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>, pub client: Arc, - pub executor: Arc, + pub executor: Arc, rate_limit_count_rx: watch::Receiver>, rate_limit_count_tx: Arc>>>, } @@ -66,7 +66,7 @@ struct OpenAIEmbeddingUsage { } impl OpenAIEmbeddingProvider { - pub fn new(client: Arc, executor: Arc) -> Self { + pub fn new(client: Arc, executor: Arc) -> 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; } } diff --git a/crates/ai2/src/test.rs b/crates/ai2/src/test.rs index ee88529aecb004ce3b725fb61abd679359673404..b061a47139cf08ca62b6c3571ff45dd5c8feca7b 100644 --- a/crates/ai2/src/test.rs +++ b/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 { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 03eb3c238f98d55e7bd40793dafa931244bcc073..6ab96093a74e3f30ee44b21c396eb76a41a1e179 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -142,7 +142,7 @@ pub struct AssistantPanel { zoomed: bool, has_focus: bool, toolbar: ViewHandle, - completion_provider: Box, + completion_provider: Arc, api_key_editor: Option>, languages: Arc, fs: Arc, @@ -204,7 +204,7 @@ impl AssistantPanel { let semantic_index = SemanticIndex::global(cx); // Defaulting currently to GPT4, allow for this to be set via config. - let completion_provider = Box::new(OpenAICompletionProvider::new( + let completion_provider = Arc::new(OpenAICompletionProvider::new( "gpt-4", cx.background().clone(), )); @@ -259,7 +259,13 @@ impl AssistantPanel { cx: &mut ViewContext, ) { let this = if let Some(this) = workspace.panel::(cx) { - if this.update(cx, |assistant, _| assistant.has_credentials()) { + if this.update(cx, |assistant, cx| { + if !assistant.has_credentials() { + assistant.load_credentials(cx); + }; + + assistant.has_credentials() + }) { this } else { workspace.focus_panel::(cx); @@ -320,13 +326,10 @@ impl AssistantPanel { }; let inline_assist_id = post_inc(&mut self.next_inline_assist_id); - let provider = Arc::new(OpenAICompletionProvider::new( - "gpt-4", - cx.background().clone(), - )); + let provider = self.completion_provider.clone(); // Retrieve Credentials Authenticates the Provider - // provider.retrieve_credentials(cx); + provider.retrieve_credentials(cx); let codegen = cx.add_model(|cx| { Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) @@ -1439,7 +1442,7 @@ struct Conversation { pending_save: Task>, path: Option, _subscriptions: Vec, - completion_provider: Box, + completion_provider: Arc, } impl Entity for Conversation { @@ -1450,7 +1453,7 @@ impl Conversation { fn new( language_registry: Arc, cx: &mut ModelContext, - completion_provider: Box, + completion_provider: Arc, ) -> Self { let markdown = language_registry.language_for_name("Markdown"); let buffer = cx.add_model(|cx| { @@ -1544,7 +1547,7 @@ impl Conversation { None => Some(Uuid::new_v4().to_string()), }; let model = saved_conversation.model; - let completion_provider: Box = Box::new( + let completion_provider: Arc = Arc::new( OpenAICompletionProvider::new(model.full_name(), cx.background().clone()), ); completion_provider.retrieve_credentials(cx); @@ -2201,7 +2204,7 @@ struct ConversationEditor { impl ConversationEditor { fn new( - completion_provider: Box, + completion_provider: Arc, language_registry: Arc, fs: Arc, workspace: WeakViewHandle, @@ -3406,7 +3409,7 @@ mod tests { init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); @@ -3535,7 +3538,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); @@ -3633,7 +3636,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); @@ -3716,7 +3719,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry.clone(), cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index f62c91fcb7e8a2f5891e2e93ae9cba7b13b437eb..25c9deef7f6ef972dd67911046f430e63bb48d40 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -367,6 +367,8 @@ fn strip_invalid_spans_from_codeblock( #[cfg(test)] mod tests { + use std::sync::Arc; + use super::*; use ai::test::FakeCompletionProvider; use futures::stream::{self}; @@ -437,6 +439,7 @@ mod tests { let max_len = cmp::min(new_text.len(), 10); let len = rng.gen_range(1..=max_len); let (chunk, suffix) = new_text.split_at(len); + println!("CHUNK: {:?}", &chunk); provider.send_completion(chunk); new_text = suffix; deterministic.run_until_parked(); @@ -569,6 +572,7 @@ mod tests { let max_len = cmp::min(new_text.len(), 10); let len = rng.gen_range(1..=max_len); let (chunk, suffix) = new_text.split_at(len); + println!("{:?}", &chunk); provider.send_completion(chunk); new_text = suffix; deterministic.run_until_parked(); diff --git a/crates/audio2/src/audio2.rs b/crates/audio2/src/audio2.rs index d04587d74e545bc2579502cd567b65de15dfceb1..286b07aba17c4516b34c990a56b1c5fb9f4beeed 100644 --- a/crates/audio2/src/audio2.rs +++ b/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>, -} - -struct AudioState { _output_stream: Option, output_handle: Option, } -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::>(); - 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::() { return; } - let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else { - return; - }; - - let this = cx.global::(); - 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::(|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::() { return; } - let this = cx.global::(); - - this.tx - .unbounded_send(Box::new(move |state| state.take())) - .ok(); + cx.update_global::(|this, _| { + this._output_stream.take(); + this.output_handle.take(); + }); } } diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index fd09dc31803c63081dea1ebcbe8dc07442d82eba..9383f9845fc49ba023f75ecaf8ee620fe0045ea7 100644 --- a/crates/call2/src/call2.rs +++ b/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, diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index cf98db015bd2baedc64341fe53b686d441f30c3b..deeec1df24203fbe67e69516c5f0faa53d6a8106 100644 --- a/crates/call2/src/room.rs +++ b/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) -> impl Future { 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, )) } diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 19e8685c28cd55094b064ea0af60bbd6744fa475..b933b62a6f60cb8cb224047f79f24e5e90b3ec7e 100644 --- a/crates/client2/src/client2.rs +++ b/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, &Arc, AsyncAppContext, - ) -> BoxFuture<'static, Result<()>>, + ) -> LocalBoxFuture<'static, Result<()>>, >, >, } @@ -310,10 +311,7 @@ pub struct PendingEntitySubscription { consumed: bool, } -impl PendingEntitySubscription -where - T: 'static + Send, -{ +impl PendingEntitySubscription { pub fn set_model(mut self, model: &Model, cx: &mut AsyncAppContext) -> Subscription { self.consumed = true; let mut state = self.client.state.write(); @@ -341,10 +339,7 @@ where } } -impl Drop for PendingEntitySubscription -where - T: 'static, -{ +impl Drop for PendingEntitySubscription { 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> where - T: 'static + Send, + T: 'static, { let id = (TypeId::of::(), remote_id); @@ -557,9 +552,13 @@ impl Client { ) -> Subscription where M: EnvelopedMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + + Sync + + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + + Send + + Sync, + F: 'static + Future>, { let message_type_id = TypeId::of::(); @@ -573,7 +572,7 @@ impl Client { Arc::new(move |subscriber, envelope, client, cx| { let subscriber = subscriber.downcast::().unwrap(); let envelope = envelope.into_any().downcast::>().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, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + + Sync + + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + + Send + + Sync, + F: 'static + Future>, { 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(self: &Arc, handler: H) where M: EntityMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, + F: 'static + Future>, { self.add_entity_message_handler::(move |subscriber, message, client, cx| { handler(subscriber.downcast::().unwrap(), message, client, cx) @@ -627,9 +630,9 @@ impl Client { fn add_entity_message_handler(self: &Arc, handler: H) where M: EntityMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, + F: 'static + Future>, { let model_type_id = TypeId::of::(); let message_type_id = TypeId::of::(); @@ -655,7 +658,7 @@ impl Client { message_type_id, Arc::new(move |handle, envelope, client, cx| { let envelope = envelope.into_any().downcast::>().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(self: &Arc, handler: H) where M: EntityMessage + RequestMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, + F: 'static + Future>, { self.add_model_message_handler(move |entity, envelope, client, cx| { Self::respond_to_request::( @@ -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, 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 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; diff --git a/crates/client2/src/telemetry.rs b/crates/client2/src/telemetry.rs index 47d1c143e120b1c4fa3b8f180e00ab240e76f1d9..0ef5f0d140987327d4db035afdb990fbe7aba763 100644 --- a/crates/client2/src/telemetry.rs +++ b/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, - executor: Executor, + executor: BackgroundExecutor, state: Mutex, } @@ -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, diff --git a/crates/client2/src/test.rs b/crates/client2/src/test.rs index f30547dcfc67b000ec73b1ddb0d122cdbb50160c..61f94580c3afbcc6c24cfbfc1efe0a052098bd7f 100644 --- a/crates/client2/src/test.rs +++ b/crates/client2/src/test.rs @@ -1,7 +1,7 @@ use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore}; use anyhow::{anyhow, Result}; use futures::{stream::BoxStream, StreamExt}; -use gpui2::{Context, Executor, 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, state: Arc>, 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); diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 987c295407b7f8a23cade7d2e5148d806ec8772e..dea6e09245bd3539d3e1b120dc9a8ee830dc7321 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.27.0" +version = "0.28.0" publish = false [[bin]] diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 083c491656579efe1b9b0cb1df96c0052b8bcd74..3b059775cdf397459c8deec64548032093c1dd09 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/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::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)> { @@ -760,7 +760,7 @@ impl Copilot { .request::(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::(request::GetCompletionsParams { diff --git a/crates/db2/src/db2.rs b/crates/db2/src/db2.rs index e2e1ae9eaad72df37a3cc0b6f16035960bb3c772..fe79dfbb0c2662c8583ee11b04ddad590fa178f7 100644 --- a/crates/db2/src/db2.rs +++ b/crates/db2/src/db2.rs @@ -185,7 +185,7 @@ pub fn write_and_log(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send where F: Future> + Send, { - cx.executor() + cx.background_executor() .spawn(async move { db_write().await.log_err() }) .detach() } diff --git a/crates/feature_flags2/src/feature_flags2.rs b/crates/feature_flags2/src/feature_flags2.rs index 7b1c0dd4d71de9d1c867aba541381f4928847530..446a2867e501a325472184949fdcfd91f00fb317 100644 --- a/crates/feature_flags2/src/feature_flags2.rs +++ b/crates/feature_flags2/src/feature_flags2.rs @@ -28,7 +28,7 @@ pub trait FeatureFlagViewExt { F: Fn(bool, &mut V, &mut ViewContext) + Send + Sync + 'static; } -impl FeatureFlagViewExt for ViewContext<'_, '_, V> +impl FeatureFlagViewExt for ViewContext<'_, V> where V: 'static + Send + Sync, { diff --git a/crates/fs2/src/fs2.rs b/crates/fs2/src/fs2.rs index 6ff8676473889301215cd8f26910d34365f49205..82b5aead0726a35a86b003085f5e51a7e957924f 100644 --- a/crates/fs2/src/fs2.rs +++ b/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, - 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 { + pub fn new(executor: gpui2::BackgroundExecutor) -> Arc { 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", diff --git a/crates/fuzzy2/src/paths.rs b/crates/fuzzy2/src/paths.rs index f6c5fba6c9f641d4b1b132bf2ae932fa5d69bb02..4990b9e5b55b003ee794a7817556accd64d95f53 100644 --- a/crates/fuzzy2/src/paths.rs +++ b/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 { let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum(); if path_count == 0 { diff --git a/crates/fuzzy2/src/strings.rs b/crates/fuzzy2/src/strings.rs index 6f7533ddd0a7b4d0a9e1c4cb190274d233ebdb50..7c71496a13f0797c34885fbc6c8e0eaa9991b4b4 100644 --- a/crates/fuzzy2/src/strings.rs +++ b/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 { if candidates.is_empty() || max_results == 0 { return Default::default(); diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 638e5c6ca3c918ccec786778e4d3eb94294934cb..84843c98762572e591d76c774360fe48b21ee148 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -4,7 +4,7 @@ use collections::{HashMap, HashSet}; use serde::Deserialize; use std::any::{type_name, Any}; -pub trait Action: Any + Send { +pub trait Action: 'static { fn qualified_name() -> SharedString where Self: Sized; @@ -19,7 +19,7 @@ pub trait Action: Any + Send { impl Action for A where - A: for<'a> Deserialize<'a> + PartialEq + Any + Send + Clone + Default, + A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + 'static, { fn qualified_name() -> SharedString { type_name::().into() diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index cdc3f8172d3874188e8dc8f531c5d08b0c149ef5..5a6e36080291e03600674349332295e2e62a4c7f 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -13,30 +13,32 @@ use smallvec::SmallVec; 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, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, - TextSystem, View, Window, WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, + AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, + Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, + PathPromptOptions, Pixels, Platform, PlatformDisplay, 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::{channel::oneshot, 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}, + path::{Path, PathBuf}, + rc::{Rc, Weak}, + sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, }; use util::http::{self, HttpClient}; -pub struct App(Arc>); +pub struct App(Rc>); /// Represents an application before it is fully launched. Once your app is /// configured, you'll start the app with `App::run`. @@ -54,13 +56,12 @@ impl App { /// app is fully launched. pub fn run(self, on_finish_launching: F) where - F: 'static + FnOnce(&mut MainThread), + 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>(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 +72,12 @@ impl App { where F: 'static + FnMut(Vec, &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 +85,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 background_executor(&self) -> BackgroundExecutor { + self.0.borrow().background_executor.clone() } - pub fn executor(&self) -> Executor { - self.0.lock().executor.clone() + pub fn foreground_executor(&self) -> ForegroundExecutor { + self.0.borrow().foreground_executor.clone() } pub fn text_system(&self) -> Arc { - self.0.lock().text_system.clone() + self.0.borrow().text_system.clone() } } type ActionBuilder = fn(json: Option) -> anyhow::Result>; -type FrameCallback = Box; -type Handler = Box bool + Send + 'static>; -type Listener = Box bool + Send + 'static>; -type QuitHandler = Box BoxFuture<'static, ()> + Send + 'static>; -type ReleaseListener = Box; +type FrameCallback = Box; +type Handler = Box bool + 'static>; +type Listener = Box bool + 'static>; +type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; +type ReleaseListener = Box; pub struct AppContext { - this: Weak>, - pub(crate) platform: MainThreadOnly, + this: Weak>, + pub(crate) platform: Rc, app_metadata: AppMetadata, text_system: Arc, flushing_effects: bool, pending_updates: usize, pub(crate) active_drag: Option, pub(crate) next_frame_callbacks: HashMap>, - pub(crate) executor: Executor, + pub(crate) background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, pub(crate) svg_renderer: SvgRenderer, asset_source: Arc, pub(crate) image_cache: ImageCache, @@ -140,7 +138,7 @@ pub struct AppContext { pub(crate) windows: SlotMap>, pub(crate) keymap: Arc>, pub(crate) global_action_listeners: - HashMap>>, + HashMap>>, action_builders: HashMap, pending_effects: VecDeque, pub(crate) pending_notifications: HashSet, @@ -156,11 +154,12 @@ pub struct AppContext { impl AppContext { pub(crate) fn new( - platform: Arc, + platform: Rc, asset_source: Arc, http_client: Arc, - ) -> Arc> { - let executor = platform.executor(); + ) -> Rc> { + let executor = platform.background_executor(); + let foreground_executor = platform.foreground_executor(); assert!( executor.is_main_thread(), "must construct App on main thread" @@ -175,16 +174,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), @@ -215,17 +215,16 @@ impl AppContext { pub fn quit(&mut self) { let mut futures = Vec::new(); - self.quit_observers.clone().retain(&(), |observer| { + for observer in self.quit_observers.remove(&()) { futures.push(observer(self)); - true - }); + } self.windows.clear(); self.flush_effects(); 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(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); @@ -257,42 +255,89 @@ impl AppContext { result } - pub(crate) fn read_window( - &mut self, - id: WindowId, - read: impl FnOnce(&WindowContext) -> R, - ) -> Result { - let window = self - .windows - .get(id) - .ok_or_else(|| anyhow!("window not found"))? - .as_ref() - .unwrap(); - Ok(read(&WindowContext::immutable(self, &window))) + pub fn windows(&self) -> Vec { + self.windows + .values() + .filter_map(|window| Some(window.as_ref()?.handle.clone())) + .collect() } - pub(crate) fn update_window( + /// 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( &mut self, - id: WindowId, - update: impl FnOnce(&mut WindowContext) -> R, - ) -> Result { + options: crate::WindowOptions, + build_root_view: impl FnOnce(&mut WindowContext) -> View, + ) -> WindowHandle { self.update(|cx| { - let mut window = cx - .windows - .get_mut(id) - .ok_or_else(|| anyhow!("window not found"))? - .take() - .unwrap(); + 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::new(cx, &mut window)); + window.root_view.replace(root_view.into()); + cx.windows.get_mut(id).unwrap().replace(window); + handle + }) + } - let result = update(&mut WindowContext::mutable(cx, &mut window)); + /// 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); + } - cx.windows - .get_mut(id) - .ok_or_else(|| anyhow!("window not found"))? - .replace(window); + /// Returns the list of currently active displays. + pub fn displays(&self) -> Vec> { + self.platform.displays() + } - Ok(result) - }) + /// 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 { + 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)>> { + 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 { + self.platform.path_for_auxiliary_executable(name) + } + + pub fn prompt_for_paths( + &self, + options: PathPromptOptions, + ) -> oneshot::Receiver>> { + self.platform.prompt_for_paths(options) + } + + pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { + self.platform.prompt_for_new_path(directory) + } + + pub fn reveal_path(&self, path: &Path) { + self.platform.reveal_path(path) } pub(crate) fn push_effect(&mut self, effect: Effect) { @@ -326,8 +371,11 @@ impl AppContext { self.apply_notify_effect(emitter); } Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event), - Effect::FocusChanged { window_id, focused } => { - self.apply_focus_changed_effect(window_id, focused); + Effect::FocusChanged { + window_handle, + focused, + } => { + self.apply_focus_changed_effect(window_handle, focused); } Effect::Refresh => { self.apply_refresh_effect(); @@ -347,18 +395,18 @@ impl AppContext { let dirty_window_ids = self .windows .iter() - .filter_map(|(window_id, window)| { + .filter_map(|(_, window)| { let window = window.as_ref().unwrap(); if window.dirty { - Some(window_id) + Some(window.handle.clone()) } else { None } }) .collect::>(); - for dirty_window_id in dirty_window_ids { - self.update_window(dirty_window_id, |cx| cx.draw()).unwrap(); + for dirty_window_handle in dirty_window_ids { + dirty_window_handle.update(self, |_, cx| cx.draw()).unwrap(); } } @@ -375,7 +423,7 @@ impl AppContext { for (entity_id, mut entity) in dropped { self.observers.remove(&entity_id); self.event_listeners.remove(&entity_id); - for mut release_callback in self.release_listeners.remove(&entity_id) { + for release_callback in self.release_listeners.remove(&entity_id) { release_callback(entity.as_mut(), self); } } @@ -386,27 +434,27 @@ impl AppContext { /// For now, we simply blur the window if this happens, but we may want to support invoking /// a window blur handler to restore focus to some logical element. fn release_dropped_focus_handles(&mut self) { - let window_ids = self.windows.keys().collect::>(); - for window_id in window_ids { - self.update_window(window_id, |cx| { - let mut blur_window = false; - let focus = cx.window.focus; - cx.window.focus_handles.write().retain(|handle_id, count| { - if count.load(SeqCst) == 0 { - if focus == Some(handle_id) { - blur_window = true; + for window_handle in self.windows() { + window_handle + .update(self, |_, cx| { + let mut blur_window = false; + let focus = cx.window.focus; + cx.window.focus_handles.write().retain(|handle_id, count| { + if count.load(SeqCst) == 0 { + if focus == Some(handle_id) { + blur_window = true; + } + false + } else { + true } - false - } else { - true - } - }); + }); - if blur_window { - cx.blur(); - } - }) - .unwrap(); + if blur_window { + cx.blur(); + } + }) + .unwrap(); } } @@ -423,30 +471,35 @@ impl AppContext { .retain(&emitter, |handler| handler(event.as_ref(), self)); } - fn apply_focus_changed_effect(&mut self, window_id: WindowId, focused: Option) { - self.update_window(window_id, |cx| { - if cx.window.focus == focused { - let mut listeners = mem::take(&mut cx.window.focus_listeners); - let focused = - focused.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); - let blurred = cx - .window - .last_blur - .take() - .unwrap() - .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); - if focused.is_some() || blurred.is_some() { - let event = FocusEvent { focused, blurred }; - for listener in &listeners { - listener(&event, cx); + fn apply_focus_changed_effect( + &mut self, + window_handle: AnyWindowHandle, + focused: Option, + ) { + window_handle + .update(self, |_, cx| { + if cx.window.focus == focused { + let mut listeners = mem::take(&mut cx.window.focus_listeners); + let focused = focused + .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); + let blurred = cx + .window + .last_blur + .take() + .unwrap() + .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); + if focused.is_some() || blurred.is_some() { + let event = FocusEvent { focused, blurred }; + for listener in &listeners { + listener(&event, cx); + } } - } - listeners.extend(cx.window.focus_listeners.drain(..)); - cx.window.focus_listeners = listeners; - } - }) - .ok(); + listeners.extend(cx.window.focus_listeners.drain(..)); + cx.window.focus_listeners = listeners; + } + }) + .ok(); } fn apply_refresh_effect(&mut self) { @@ -464,7 +517,7 @@ impl AppContext { .retain(&type_id, |observer| observer(self)); } - fn apply_defer_effect(&mut self, callback: Box) { + fn apply_defer_effect(&mut self, callback: Box) { callback(self); } @@ -473,72 +526,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`, which - /// has platform-specific methods that aren't present on `AppContext`. - pub fn run_on_main( - &mut self, - f: impl FnOnce(&mut MainThread) -> R + Send + 'static, - ) -> Task - where - R: Send + 'static, - { - if self.executor.is_main_thread() { - Task::ready(f(unsafe { - mem::transmute::<&mut AppContext, &mut MainThread>(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>(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`, - /// 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 from implementing Send") - pub fn spawn_on_main( - &self, - f: impl FnOnce(MainThread) -> F + Send + 'static, - ) -> Task - where - F: Future + '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(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where - Fut: Future + Send + 'static, - R: Send + 'static, + Fut: Future + '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 +612,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(&mut self) -> &mut G { + pub fn default_global(&mut self) -> &mut G { let global_type = TypeId::of::(); self.push_effect(Effect::NotifyGlobalObservers { global_type }); self.globals_by_type @@ -608,7 +623,7 @@ impl AppContext { } /// Set the value of the global of the given type. - pub fn set_global(&mut self, global: G) { + pub fn set_global(&mut self, global: G) { let global_type = TypeId::of::(); self.push_effect(Effect::NotifyGlobalObservers { global_type }); self.globals_by_type.insert(global_type, Box::new(global)); @@ -626,7 +641,7 @@ impl AppContext { /// Register a callback to be invoked when a global of the given type is updated. pub fn observe_global( &mut self, - mut f: impl FnMut(&mut Self) + Send + 'static, + mut f: impl FnMut(&mut Self) + 'static, ) -> Subscription { self.global_observers.insert( TypeId::of::(), @@ -658,6 +673,24 @@ impl AppContext { self.globals_by_type.insert(global_type, lease.global); } + pub fn observe_release( + &mut self, + handle: &E, + on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, + ) -> Subscription + where + E: Entity, + T: 'static, + { + self.release_listeners.insert( + handle.entity_id(), + Box::new(move |entity, cx| { + let entity = entity.downcast_mut().expect("invalid entity type"); + on_release(entity, cx) + }), + ) + } + pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) { self.text_style_stack.push(text_style); } @@ -673,7 +706,7 @@ impl AppContext { } /// Register a global listener for actions invoked via the keyboard. - pub fn on_action(&mut self, listener: impl Fn(&A, &mut Self) + Send + 'static) { + pub fn on_action(&mut self, listener: impl Fn(&A, &mut Self) + 'static) { self.global_action_listeners .entry(TypeId::of::()) .or_default() @@ -711,19 +744,18 @@ impl AppContext { } impl Context for AppContext { - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; /// Build an entity that is owned by the application. The given function will be invoked with /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned /// which can be used to access the entity in a context. - fn build_model( + fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Model { self.update(|cx| { let slot = cx.entities.reserve(); - let entity = build_model(&mut ModelContext::mutable(cx, slot.downgrade())); + let entity = build_model(&mut ModelContext::new(cx, slot.downgrade())); cx.entities.insert(slot, entity) }) } @@ -733,117 +765,36 @@ impl Context for AppContext { fn update_model( &mut self, model: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> R { self.update(|cx| { let mut entity = cx.entities.lease(model); - let result = update( - &mut entity, - &mut ModelContext::mutable(cx, model.downgrade()), - ); + let result = update(&mut entity, &mut ModelContext::new(cx, model.downgrade())); cx.entities.end_lease(entity); result }) } -} - -impl MainThread -where - C: Borrow, -{ - 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 { - 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)>> { - 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 { - self.platform().path_for_auxiliary_executable(name) - } -} - -impl MainThread { - fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { - self.0.update(|cx| { - update(unsafe { - std::mem::transmute::<&mut AppContext, &mut MainThread>(cx) - }) - }) - } - - pub(crate) fn update_window( - &mut self, - id: WindowId, - update: impl FnOnce(&mut MainThread) -> R, - ) -> Result { - self.0.update_window(id, |cx| { - update(unsafe { - std::mem::transmute::<&mut WindowContext, &mut MainThread>(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( - &mut self, - options: crate::WindowOptions, - build_root_view: impl FnOnce(&mut WindowContext) -> View + Send + 'static, - ) -> WindowHandle { + fn update_window(&mut self, handle: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { 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 - }) - } + let mut window = cx + .windows + .get_mut(handle.id) + .ok_or_else(|| anyhow!("window not found"))? + .take() + .unwrap(); - /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides - /// your closure with mutable access to the `MainThread` and the global simultaneously. - pub fn update_global( - &mut self, - update: impl FnOnce(&mut G, &mut MainThread) -> R, - ) -> R { - self.0.update_global(|global, cx| { - let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }; - update(global, cx) + let root_view = window.root_view.clone().unwrap(); + let result = update(root_view, &mut WindowContext::new(cx, &mut window)); + cx.windows + .get_mut(handle.id) + .ok_or_else(|| anyhow!("window not found"))? + .replace(window); + + Ok(result) }) } } @@ -855,10 +806,10 @@ pub(crate) enum Effect { }, Emit { emitter: EntityId, - event: Box, + event: Box, }, FocusChanged { - window_id: WindowId, + window_handle: AnyWindowHandle, focused: Option, }, Refresh, @@ -866,7 +817,7 @@ pub(crate) enum Effect { global_type: TypeId, }, Defer { - callback: Box, + callback: Box, }, } @@ -905,15 +856,3 @@ pub(crate) struct AnyDrag { pub view: AnyView, pub cursor_offset: Point, } - -#[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() {} - assert_send::(); - } -} diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 042a75848e467fd452db80447e477a5c42179965..4bbab43446265a8a2c7c456b9d80702e2c027803 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,48 +1,57 @@ use crate::{ - AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task, - WindowContext, + AnyView, AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, + ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext, + WindowHandle, }; -use anyhow::anyhow; +use anyhow::{anyhow, Context as _}; 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>, - pub(crate) executor: Executor, + pub(crate) app: Weak>, + pub(crate) background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, } impl Context for AsyncAppContext { - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = Result; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Self::Result> 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( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result { let app = self .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)) + } + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + let app = self.app.upgrade().context("app was released")?; + let mut lock = app.borrow_mut(); + lock.update_window(window, f) } } @@ -52,84 +61,50 @@ 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 update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); - Ok(f(&mut *lock)) + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor } - pub fn read_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(&WindowContext) -> R, - ) -> Result { + pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { let app = self .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); - app_context.read_window(handle.id, update) + let mut lock = app.borrow_mut(); + Ok(f(&mut *lock)) } - pub fn update_window( + pub fn open_window( &self, - handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, - ) -> Result { + options: crate::WindowOptions, + build_root_view: impl FnOnce(&mut WindowContext) -> View, + ) -> Result> + where + V: Render, + { let app = self .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); - app_context.update_window(handle.id, update) - } - - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task - where - Fut: Future + Send + 'static, - R: Send + 'static, - { - let this = self.clone(); - self.executor.spawn(async move { f(this).await }) + let mut lock = app.borrow_mut(); + Ok(lock.open_window(options, build_root_view)) } - pub fn spawn_on_main( - &self, - f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, - R: Send + 'static, + R: 'static, { - let this = self.clone(); - self.executor.spawn_on_main(|| f(this)) - } - - pub fn run_on_main( - &self, - f: impl FnOnce(&mut MainThread) -> R + Send + 'static, - ) -> Result> - where - R: Send + '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(&self) -> Result { @@ -137,8 +112,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let lock = app.lock(); // Need this to compile - Ok(lock.has_global::()) + let app = app.borrow_mut(); + Ok(app.has_global::()) } pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result { @@ -146,8 +121,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( @@ -155,8 +130,8 @@ impl AsyncAppContext { read: impl FnOnce(&G, &AppContext) -> R, ) -> Option { 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( @@ -167,8 +142,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)) } } @@ -185,22 +160,22 @@ impl AsyncWindowContext { Self { app, window } } - pub fn update(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result { + pub fn update( + &mut self, + update: impl FnOnce(AnyView, &mut WindowContext) -> R, + ) -> Result { self.app.update_window(self.window, update) } - pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { - self.app - .update_window(self.window, |cx| cx.on_next_frame(f)) - .ok(); + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { + self.window.update(self, |_, cx| cx.on_next_frame(f)).ok(); } pub fn read_global( - &self, + &mut self, read: impl FnOnce(&G, &WindowContext) -> R, ) -> Result { - self.app - .read_window(self.window, |cx| read(cx.global(), cx)) + self.window.update(self, |_, cx| read(cx.global(), cx)) } pub fn update_global( @@ -210,43 +185,78 @@ impl AsyncWindowContext { where G: 'static, { - self.app - .update_window(self.window, |cx| cx.update_global(update)) + self.window.update(self, |_, cx| cx.update_global(update)) + } + + pub fn spawn(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task + where + Fut: Future + 'static, + R: 'static, + { + self.foreground_executor.spawn(f(self.clone())) } } impl Context for AsyncWindowContext { - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = Result; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Result> where - T: 'static + Send, + T: 'static, { - self.app - .update_window(self.window, |cx| cx.build_model(build_model)) + self.window + .update(self, |_, cx| cx.build_model(build_model)) } fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Result { - self.app - .update_window(self.window, |cx| cx.update_model(handle, update)) + self.window + .update(self, |_, cx| cx.update_model(handle, update)) + } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + self.app.update_window(window, update) } } -#[cfg(test)] -mod tests { - use super::*; +impl VisualContext for AsyncWindowContext { + fn build_view( + &mut self, + build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static, + { + self.window + .update(self, |_, cx| cx.build_view(build_view_state)) + } - #[test] - fn test_async_app_context_send_sync() { - fn assert_send_sync() {} - assert_send_sync::(); + fn update_view( + &mut self, + view: &View, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, + ) -> Self::Result { + self.window + .update(self, |_, cx| cx.update_view(view, update)) + } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: Render, + { + self.window + .update(self, |_, cx| cx.replace_root_view(build_view)) } } diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index ecd171d1f8768538675385bdd8cb049d54b326a9..e626f8c409d2a80ac9f7ac2116741855f7e5beb3 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -1,4 +1,4 @@ -use crate::{private::Sealed, AnyBox, AppContext, Context, Entity}; +use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; @@ -59,7 +59,7 @@ impl EntityMap { /// Insert an entity into a slot obtained by calling `reserve`. pub fn insert(&mut self, slot: Slot, entity: T) -> Model where - T: 'static + Send, + T: 'static, { let model = slot.0; self.entities.insert(model.entity_id, Box::new(entity)); @@ -169,6 +169,10 @@ impl AnyModel { self.entity_id } + pub fn entity_type(&self) -> TypeId { + self.entity_type + } + pub fn downgrade(&self) -> AnyWeakModel { AnyWeakModel { entity_id: self.entity_id, @@ -329,7 +333,7 @@ impl Model { pub fn update( &self, cx: &mut C, - update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> C::Result where C: Context, @@ -475,7 +479,7 @@ impl WeakModel { pub fn update( &self, cx: &mut C, - update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Result where C: Context, diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index 8a4576c052b6c4355576f2959028b568dae8692d..cb25adfb63414a5111b0cd2a8d93b66742dc46a0 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -1,7 +1,8 @@ use crate::{ - AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread, - Model, Reference, Subscription, Task, WeakModel, + AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, + EventEmitter, Model, Subscription, Task, WeakModel, WindowContext, }; +use anyhow::Result; use derive_more::{Deref, DerefMut}; use futures::FutureExt; use std::{ @@ -14,16 +15,13 @@ use std::{ pub struct ModelContext<'a, T> { #[deref] #[deref_mut] - app: Reference<'a, AppContext>, + app: &'a mut AppContext, model_state: WeakModel, } impl<'a, T: 'static> ModelContext<'a, T> { - pub(crate) fn mutable(app: &'a mut AppContext, model_state: WeakModel) -> Self { - Self { - app: Reference::Mutable(app), - model_state, - } + pub(crate) fn new(app: &'a mut AppContext, model_state: WeakModel) -> Self { + Self { app, model_state } } pub fn entity_id(&self) -> EntityId { @@ -40,15 +38,15 @@ impl<'a, T: 'static> ModelContext<'a, T> { self.model_state.clone() } - pub fn observe( + pub fn observe( &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, + T: 'static, + W: 'static, + E: Entity, { let this = self.weak_model(); let entity_id = entity.entity_id(); @@ -69,10 +67,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn subscribe( &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, { @@ -95,7 +93,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, + on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, ) -> Subscription where T: 'static, @@ -112,10 +110,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn observe_release( &mut self, entity: &E, - mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + Send + 'static, + on_release: impl FnOnce(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where - T: Any + Send, + T: Any, T2: 'static, E: Entity, { @@ -134,10 +132,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn observe_global( &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 +146,11 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn on_app_quit( &mut self, - mut on_quit: impl FnMut(&mut T, &mut ModelContext) -> Fut + Send + 'static, + mut on_quit: impl FnMut(&mut T, &mut ModelContext) -> Fut + 'static, ) -> Subscription where - Fut: 'static + Future + Send, - T: 'static + Send, + Fut: 'static + Future, + T: 'static, { let handle = self.weak_model(); self.app.quit_observers.insert( @@ -164,7 +162,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { future.await; } } - .boxed() + .boxed_local() }), ) } @@ -183,7 +181,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where - G: 'static + Send, + G: 'static, { let mut global = self.app.lease_global::(); let result = f(&mut global, self); @@ -191,36 +189,20 @@ impl<'a, T: 'static> ModelContext<'a, T> { result } - pub fn spawn( - &self, - f: impl FnOnce(WeakModel, AsyncAppContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&self, f: impl FnOnce(WeakModel, AsyncAppContext) -> Fut) -> Task where T: 'static, - Fut: Future + Send + 'static, - R: Send + 'static, - { - let this = self.weak_model(); - self.app.spawn(|cx| f(this, cx)) - } - - pub fn spawn_on_main( - &self, - f: impl FnOnce(WeakModel, MainThread) -> Fut + Send + 'static, - ) -> Task - where Fut: Future + '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 { @@ -231,26 +213,29 @@ where } impl<'a, T> Context for ModelContext<'a, T> { - type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; - fn build_model( + fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U, - ) -> Model - where - U: 'static + Send, - { + build_model: impl FnOnce(&mut ModelContext<'_, U>) -> U, + ) -> Model { self.app.build_model(build_model) } fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R, + update: impl FnOnce(&mut U, &mut ModelContext<'_, U>) -> R, ) -> R { self.app.update_model(handle, update) } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> R, + { + self.app.update_window(window, update) + } } impl Borrow for ModelContext<'_, T> { diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index cd59f9234f782f3886a5b3ffe958a4d30944283f..aaf42dd4a27ba2b401516293f1f027aaeedbde65 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,138 +1,115 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, MainThread, - Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, + AnyView, 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>, - pub executor: Executor, + pub app: Rc>, + pub background_executor: BackgroundExecutor, + pub foreground_executor: ForegroundExecutor, } impl Context for TestAppContext { - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Self::Result> 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( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result { - let mut lock = self.app.lock(); - lock.update_model(handle, update) + let mut app = self.app.borrow_mut(); + app.update_model(handle, update) + } + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + let mut lock = self.app.borrow_mut(); + lock.update_window(window, f) } } 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 update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { - let mut cx = self.app.lock(); - cx.update(f) - } - - pub fn read_window( - &self, - handle: AnyWindowHandle, - read: impl FnOnce(&WindowContext) -> R, - ) -> R { - let mut app_context = self.app.lock(); - app_context.read_window(handle.id, read).unwrap() + pub fn executor(&self) -> &BackgroundExecutor { + &self.background_executor } - pub fn update_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, - ) -> R { - let mut app = self.app.lock(); - app.update_window(handle.id, update).unwrap() + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor } - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task - where - Fut: Future + Send + 'static, - R: Send + 'static, - { - let cx = self.to_async(); - self.executor.spawn(async move { f(cx).await }) + pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { + let mut cx = self.app.borrow_mut(); + cx.update(f) } - pub fn spawn_on_main( - &self, - f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, - R: Send + 'static, + R: 'static, { - let cx = self.to_async(); - self.executor.spawn_on_main(|| f(cx)) - } - - pub fn run_on_main( - &self, - f: impl FnOnce(&mut MainThread) -> R + Send + 'static, - ) -> Task - where - R: Send + '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(&self) -> bool { - let lock = self.app.lock(); - lock.has_global::() + let app = self.app.borrow(); + app.has_global::() } pub fn read_global(&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( &self, read: impl FnOnce(&G, &AppContext) -> R, ) -> Option { - let lock = self.app.lock(); + let lock = self.app.borrow(); Some(read(lock.try_global()?, &lock)) } @@ -140,32 +117,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( + pub fn notifications(&mut self, entity: &Model) -> impl Stream { + let (tx, rx) = futures::channel::mpsc::unbounded(); + + entity.update(self, move |_, cx: &mut ModelContext| { + 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( &mut self, entity: &Model, ) -> futures::channel::mpsc::UnboundedReceiver 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| { - 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( + &mut self, + model: &Model, + mut predicate: impl FnMut(&mut T, &mut ModelContext) -> 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(); + } } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index a715ed30ee739cee6f0834be5be497415b0ff8b1..4890b79a9acc811ea72816a4a01dbd45634e1667 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -4,7 +4,7 @@ pub(crate) use smallvec::SmallVec; use std::{any::Any, mem}; pub trait Element { - type ElementState: 'static + Send; + type ElementState: 'static; fn id(&self) -> Option; @@ -97,7 +97,7 @@ impl> RenderedElement { impl ElementObject for RenderedElement where E: Element, - E::ElementState: 'static + Send, + E::ElementState: 'static, { fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext) { let frame_state = if let Some(id) = self.element.id() { @@ -170,16 +170,14 @@ where } } -pub struct AnyElement(Box + Send>); - -unsafe impl Send for AnyElement {} +pub struct AnyElement(Box>); impl AnyElement { pub fn new(element: E) -> Self where V: 'static, - E: 'static + Element + Send, - E::ElementState: Any + Send, + E: 'static + Element, + E::ElementState: Any, { AnyElement(Box::new(RenderedElement::new(element))) } @@ -220,8 +218,8 @@ impl Component for AnyElement { impl Element for Option where V: 'static, - E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + E: 'static + Component, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { type ElementState = AnyElement; @@ -264,8 +262,8 @@ where impl Component for Option where V: 'static, - E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + E: 'static + Component, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -275,8 +273,8 @@ where impl Component for F where V: 'static, - E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + E: 'static + Component, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { fn render(self) -> AnyElement { AnyElement::new(Some(self)) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 6fe10d94a31324984df8431c13a0747d704110b4..56940efce4a0982c4255b613f682137ed3ce1582 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -305,7 +305,6 @@ where impl Component for Div where - // V: Any + Send + Sync, I: ElementInteraction, F: ElementFocus, { diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 3aff568c4c2f71ed4002006b3a7c9ae306db3b99..4bc37054902653a85f931d82354183edc9142fe3 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -44,9 +44,6 @@ pub struct Text { state_type: PhantomData, } -unsafe impl Send for Text {} -unsafe impl Sync for Text {} - impl Component for Text { fn render(self) -> AnyElement { AnyElement::new(self) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 70c8f4b3ecc10059bd91c0e0d8683e81273046fe..25e88068c30d93f58d10aec2f9c0a998cbb1b6fb 100644 --- a/crates/gpui2/src/executor.rs +++ b/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, } +#[derive(Clone)] +pub struct ForegroundExecutor { + dispatcher: Arc, + not_send: PhantomData>, +} + #[must_use] pub enum Task { Ready(Option), @@ -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 Future for Task { } } -impl Executor { +impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> 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(&self, func: F) -> Task - 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(&self, func: impl FnOnce() -> F + Send + 'static) -> Task - where - F: Future + '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(&self, future: impl Future) -> 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(&self, future: impl Future + 'static) -> Task - 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(&self, future: impl Future) -> R { + self.block_internal(true, future) } - pub fn block(&self, future: impl Future) -> R { + pub(crate) fn block_internal( + &self, + background_only: bool, + future: impl Future, + ) -> 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 { - 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) -> 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(&self, future: impl Future + 'static) -> Task + 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 + Send + 'static>>>, tx: Option>, 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, diff --git a/crates/gpui2/src/focusable.rs b/crates/gpui2/src/focusable.rs index d7cfc5fe8fc388a546d324d7a40649671f1060ea..99f8bb1dd612631ad3dde3c97c1675f5072524bc 100644 --- a/crates/gpui2/src/focusable.rs +++ b/crates/gpui2/src/focusable.rs @@ -8,7 +8,7 @@ use smallvec::SmallVec; pub type FocusListeners = SmallVec<[FocusListener; 2]>; pub type FocusListener = - Box) + Send + 'static>; + Box) + 'static>; pub trait Focusable: Element { fn focus_listeners(&mut self) -> &mut FocusListeners; @@ -42,7 +42,7 @@ pub trait Focusable: Element { fn on_focus( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -58,7 +58,7 @@ pub trait Focusable: Element { fn on_blur( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -74,7 +74,7 @@ pub trait Focusable: Element { fn on_focus_in( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -99,7 +99,7 @@ pub trait Focusable: Element { fn on_focus_out( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -122,7 +122,7 @@ pub trait Focusable: Element { } } -pub trait ElementFocus: 'static + Send { +pub trait ElementFocus: 'static { fn as_focusable(&self) -> Option<&FocusEnabled>; fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled>; diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index eedf8bbb2c11b8fc686ba386887fe3a875d40472..7d4073144c33281e67d4b1845479162c1712d057 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -931,6 +931,18 @@ impl From for GlobalPixels { } } +impl sqlez::bindable::StaticColumnCount for GlobalPixels {} + +impl sqlez::bindable::Bind for GlobalPixels { + fn bind( + &self, + statement: &sqlez::statement::Statement, + start_index: i32, + ) -> anyhow::Result { + self.0.bind(statement, start_index) + } +} + #[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)] pub struct Rems(f32); diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 8625866a441159a3b89ce3dade64e3190c074678..49cc3cebc23bf2d61f7615a02e48a77b7d11ba2a 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -68,51 +68,56 @@ 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; +type AnyBox = Box; pub trait Context { - type ModelContext<'a, T>; type Result; - fn build_model( + fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, - ) -> Self::Result> - where - T: 'static + Send; + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, + ) -> Self::Result>; - fn update_model( + fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, - ) -> Self::Result; + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, + ) -> Self::Result + where + T: 'static; + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T; } pub trait VisualContext: Context { - type ViewContext<'a, 'w, V>; - fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Send; + V: 'static; fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> Self::Result; + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: Render; } pub trait Entity: Sealed { - type Weak: 'static + Send; + type Weak: 'static; fn entity_id(&self) -> EntityId; fn downgrade(&self) -> Self::Weak; @@ -127,106 +132,12 @@ pub enum GlobalKey { Type(TypeId), } -#[repr(transparent)] -pub struct MainThread(T); - -impl Deref for MainThread { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MainThread { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Context for MainThread { - type ModelContext<'a, T> = MainThread>; - type Result = C::Result; - - fn build_model( - &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, - ) -> Self::Result> - where - T: 'static + Send, - { - self.0.build_model(|cx| { - let cx = unsafe { - mem::transmute::< - &mut C::ModelContext<'_, T>, - &mut MainThread>, - >(cx) - }; - build_model(cx) - }) - } - - fn update_model( - &mut self, - handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, - ) -> Self::Result { - self.0.update_model(handle, |entity, cx| { - let cx = unsafe { - mem::transmute::< - &mut C::ModelContext<'_, T>, - &mut MainThread>, - >(cx) - }; - update(entity, cx) - }) - } -} - -impl VisualContext for MainThread { - type ViewContext<'a, 'w, V> = MainThread>; - - fn build_view( - &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, - ) -> Self::Result> - where - V: 'static + Send, - { - self.0.build_view(|cx| { - let cx = unsafe { - mem::transmute::< - &mut C::ViewContext<'_, '_, V>, - &mut MainThread>, - >(cx) - }; - build_view_state(cx) - }) - } - - fn update_view( - &mut self, - view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R, - ) -> Self::Result { - self.0.update_view(view, |view_state, cx| { - let cx = unsafe { - mem::transmute::< - &mut C::ViewContext<'_, '_, V>, - &mut MainThread>, - >(cx) - }; - update(view_state, cx) - }) - } -} - pub trait BorrowAppContext { fn with_text_style(&mut self, style: TextStyleRefinement, f: F) -> R where F: FnOnce(&mut Self) -> R; - fn set_global(&mut self, global: T); + fn set_global(&mut self, global: T); } impl BorrowAppContext for C @@ -243,7 +154,7 @@ where result } - fn set_global(&mut self, global: G) { + fn set_global(&mut self, global: G) { self.borrow_mut().set_global(global) } } @@ -306,59 +217,3 @@ impl>> From for SharedString { Self(value.into()) } } - -pub enum Reference<'a, T> { - Immutable(&'a T), - Mutable(&'a mut T), -} - -impl<'a, T> Deref for Reference<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - match self { - Reference::Immutable(target) => target, - Reference::Mutable(target) => target, - } - } -} - -impl<'a, T> DerefMut for Reference<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Reference::Immutable(_) => { - panic!("cannot mutably deref an immutable reference. this is a bug in GPUI."); - } - Reference::Mutable(target) => target, - } - } -} - -pub(crate) struct MainThreadOnly { - executor: Executor, - value: Arc, -} - -impl Clone for MainThreadOnly { - 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 MainThreadOnly { - pub(crate) fn new(value: Arc, 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 Send for MainThreadOnly {} diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 5a37c3ee7abafb30bc1c8aa132b0793c30d0d92e..020cb82cd2a26bf7a011b44db7dd78711c386024 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -50,7 +50,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_down( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -71,7 +71,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_up( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -92,7 +92,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_down_out( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -113,7 +113,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_up_out( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -133,7 +133,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_move( mut self, - handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -150,7 +150,7 @@ pub trait StatelessInteractive: Element { fn on_scroll_wheel( mut self, - handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -178,7 +178,7 @@ pub trait StatelessInteractive: Element { fn on_action( mut self, - listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -196,7 +196,7 @@ pub trait StatelessInteractive: Element { fn on_key_down( mut self, - listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -214,7 +214,7 @@ pub trait StatelessInteractive: Element { fn on_key_up( mut self, - listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -258,9 +258,9 @@ pub trait StatelessInteractive: Element { self } - fn on_drop( + fn on_drop( mut self, - listener: impl Fn(&mut V, View, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, View, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -303,7 +303,7 @@ pub trait StatefulInteractive: StatelessInteractive { fn on_click( mut self, - listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -316,11 +316,11 @@ pub trait StatefulInteractive: StatelessInteractive { fn on_drag( mut self, - listener: impl Fn(&mut V, &mut ViewContext) -> View + Send + 'static, + listener: impl Fn(&mut V, &mut ViewContext) -> View + '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: StatelessInteractive { } } -pub trait ElementInteraction: 'static + Send { +pub trait ElementInteraction: 'static { fn as_stateless(&self) -> &StatelessInteraction; fn as_stateless_mut(&mut self) -> &mut StatelessInteraction; fn as_stateful(&self) -> Option<&StatefulInteraction>; @@ -672,7 +672,7 @@ impl From for StatefulInteraction { } } -type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static + Send; +type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static; pub struct StatelessInteraction { pub dispatch_context: DispatchContext, @@ -1077,32 +1077,25 @@ pub struct FocusEvent { } pub type MouseDownListener = Box< - dyn Fn(&mut V, &MouseDownEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + 'static, + dyn Fn(&mut V, &MouseDownEvent, &Bounds, DispatchPhase, &mut ViewContext) + 'static, >; pub type MouseUpListener = Box< - dyn Fn(&mut V, &MouseUpEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + 'static, + dyn Fn(&mut V, &MouseUpEvent, &Bounds, DispatchPhase, &mut ViewContext) + 'static, >; pub type MouseMoveListener = Box< - dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + 'static, + dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) + 'static, >; pub type ScrollWheelListener = Box< dyn Fn(&mut V, &ScrollWheelEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send + 'static, >; -pub type ClickListener = Box) + Send + 'static>; +pub type ClickListener = Box) + 'static>; pub(crate) type DragListener = - Box, &mut ViewContext) -> AnyDrag + Send + 'static>; + Box, &mut ViewContext) -> AnyDrag + 'static>; pub type KeyListener = Box< dyn Fn( @@ -1112,6 +1105,5 @@ pub type KeyListener = Box< DispatchPhase, &mut ViewContext, ) -> Option> - + Send + 'static, >; diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index cacb1922f6c1bcb2bcd23e42213a729059a67015..cdce67d8c128b9bb04044ec6bf7a2252ae1c3bff 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -5,15 +5,19 @@ 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, + point, 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 anyhow::{anyhow, bail}; use async_task::Runnable; use futures::channel::oneshot; +use parking::Unparker; use seahash::SeaHasher; use serde::{Deserialize, Serialize}; +use sqlez::bindable::{Bind, Column, StaticColumnCount}; +use sqlez::statement::Statement; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::time::Duration; @@ -26,6 +30,7 @@ use std::{ str::FromStr, sync::Arc, }; +use uuid::Uuid; pub use keystroke::*; #[cfg(target_os = "macos")] @@ -35,12 +40,13 @@ pub use test::*; pub use time::UtcOffset; #[cfg(target_os = "macos")] -pub(crate) fn current_platform() -> Arc { - Arc::new(MacPlatform::new()) +pub(crate) fn current_platform() -> Rc { + 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; fn run(&self, on_finish_launching: Box); @@ -104,6 +110,9 @@ pub(crate) trait Platform: 'static { pub trait PlatformDisplay: Send + Sync + Debug { fn id(&self) -> DisplayId; + /// Returns a stable identifier for this display that can be persisted and used + /// across system restarts. + fn uuid(&self) -> Result; fn as_any(&self) -> &dyn Any; fn bounds(&self) -> Bounds; } @@ -129,12 +138,7 @@ pub(crate) trait PlatformWindow { fn mouse_position(&self) -> Point; fn as_any_mut(&mut self) -> &mut dyn Any; fn set_input_handler(&mut self, input_handler: Box); - fn prompt( - &self, - level: WindowPromptLevel, - msg: &str, - answers: &[&str], - ) -> oneshot::Receiver; + fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); fn set_title(&mut self, title: &str); fn set_edited(&mut self, edited: bool); @@ -161,7 +165,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> { @@ -368,6 +374,67 @@ pub enum WindowBounds { Fixed(Bounds), } +impl StaticColumnCount for WindowBounds { + fn column_count() -> usize { + 5 + } +} + +impl Bind for WindowBounds { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let (region, next_index) = match self { + WindowBounds::Fullscreen => { + let next_index = statement.bind(&"Fullscreen", start_index)?; + (None, next_index) + } + WindowBounds::Maximized => { + let next_index = statement.bind(&"Maximized", start_index)?; + (None, next_index) + } + WindowBounds::Fixed(region) => { + let next_index = statement.bind(&"Fixed", start_index)?; + (Some(*region), next_index) + } + }; + + statement.bind( + ®ion.map(|region| { + ( + region.origin.x, + region.origin.y, + region.size.width, + region.size.height, + ) + }), + next_index, + ) + } +} + +impl Column for WindowBounds { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (window_state, next_index) = String::column(statement, start_index)?; + let bounds = match window_state.as_str() { + "Fullscreen" => WindowBounds::Fullscreen, + "Maximized" => WindowBounds::Maximized, + "Fixed" => { + let ((x, y, width, height), _) = Column::column(statement, next_index)?; + let x: f64 = x; + let y: f64 = y; + let width: f64 = width; + let height: f64 = height; + WindowBounds::Fixed(Bounds { + origin: point(x.into(), y.into()), + size: size(width.into(), height.into()), + }) + } + _ => bail!("Window State did not have a valid string"), + }; + + Ok((bounds, next_index + 4)) + } +} + #[derive(Copy, Clone, Debug)] pub enum WindowAppearance { Light, @@ -382,14 +449,6 @@ impl Default for WindowAppearance { } } -#[derive(Copy, Clone, Debug, PartialEq, Default)] -pub enum WindowPromptLevel { - #[default] - Info, - Warning, - Critical, -} - #[derive(Copy, Clone, Debug)] pub struct PathPromptOptions { pub files: bool, diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index a4ae2cc0280636e238d89453fb2fb38e24563833..f5334912c6b7aec93fed2af3c33832ff241313c9 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/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>, +} + +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 - -// 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; -// } -// ``` diff --git a/crates/gpui2/src/platform/mac/display.rs b/crates/gpui2/src/platform/mac/display.rs index dc064293f342a9106f49ecf500dc8d3bb140efc0..b326eaa66d07bc674ebf6fb0c09d549db2dcb61f 100644 --- a/crates/gpui2/src/platform/mac/display.rs +++ b/crates/gpui2/src/platform/mac/display.rs @@ -1,9 +1,12 @@ use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay}; +use anyhow::Result; +use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}; use core_graphics::{ display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList}, geometry::{CGPoint, CGRect, CGSize}, }; use std::any::Any; +use uuid::Uuid; #[derive(Debug)] pub struct MacDisplay(pub(crate) CGDirectDisplayID); @@ -11,17 +14,23 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID); unsafe impl Send for MacDisplay {} impl MacDisplay { - /// Get the screen with the given UUID. + /// Get the screen with the given [DisplayId]. pub fn find_by_id(id: DisplayId) -> Option { Self::all().find(|screen| screen.id() == id) } + /// Get the screen with the given persistent [Uuid]. + pub fn find_by_uuid(uuid: Uuid) -> Option { + Self::all().find(|screen| screen.uuid().ok() == Some(uuid)) + } + /// Get the primary screen - the one with the menu bar, and whose bottom left /// corner is at the origin of the AppKit coordinate system. pub fn primary() -> Self { Self::all().next().unwrap() } + /// Obtains an iterator over all currently active system displays. pub fn all() -> impl Iterator { unsafe { let mut display_count: u32 = 0; @@ -40,6 +49,11 @@ impl MacDisplay { } } +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; +} + /// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space. /// /// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen, @@ -88,6 +102,34 @@ impl PlatformDisplay for MacDisplay { DisplayId(self.0) } + fn uuid(&self) -> Result { + let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) }; + anyhow::ensure!( + !cfuuid.is_null(), + "AppKit returned a null from CGDisplayCreateUUIDFromDisplayID" + ); + + let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) }; + Ok(Uuid::from_bytes([ + bytes.byte0, + bytes.byte1, + bytes.byte2, + bytes.byte3, + bytes.byte4, + bytes.byte5, + bytes.byte6, + bytes.byte7, + bytes.byte8, + bytes.byte9, + bytes.byte10, + bytes.byte11, + bytes.byte12, + bytes.byte13, + bytes.byte14, + bytes.byte15, + ])) + } + fn as_any(&self) -> &dyn Any { self } diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 881dd69bc88eb486cf5820b4d30898ec58e62aec..fdc7fd6ae5d416ab284a08249e4176c5f3c10892 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/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); pub struct MacPlatformState { - executor: Executor, + background_executor: BackgroundExecutor, + foreground_executor: ForegroundExecutor, text_system: Arc, 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 { @@ -457,6 +464,10 @@ impl Platform for MacPlatform { } } + // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box { + // Box::new(StatusItem::add(self.fonts())) + // } + fn displays(&self) -> Vec> { MacDisplay::all() .into_iter() @@ -464,10 +475,6 @@ impl Platform for MacPlatform { .collect() } - // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box { - // Box::new(StatusItem::add(self.fonts())) - // } - fn display(&self, id: DisplayId) -> Option> { 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 { - 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 { - 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) { // self.0.lock().menu_command = Some(callback); // } @@ -717,6 +707,23 @@ impl Platform for MacPlatform { // } // } + fn path_for_auxiliary_executable(&self, name: &str) -> Result { + 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 { diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index dbdb60469b3f4f66f8cbad81262b4ecb593a085e..52dcf3160372b9df00006d8ff03986f037826746 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/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, + PromptLevel, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, }; 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, @@ -451,7 +451,11 @@ unsafe impl Send for MacWindowState {} pub struct MacWindow(Arc>); 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(); + } } } @@ -741,12 +742,7 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().input_handler = Some(input_handler); } - fn prompt( - &self, - level: WindowPromptLevel, - msg: &str, - answers: &[&str], - ) -> oneshot::Receiver { + fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver { // macOs applies overrides to modal window buttons after they are added. // Two most important for this logic are: // * Buttons with "Cancel" title will be displayed as the last buttons in the modal @@ -776,9 +772,9 @@ impl PlatformWindow for MacWindow { let alert: id = msg_send![class!(NSAlert), alloc]; let alert: id = msg_send![alert, init]; let alert_style = match level { - WindowPromptLevel::Info => 1, - WindowPromptLevel::Warning => 0, - WindowPromptLevel::Critical => 2, + PromptLevel::Info => 1, + PromptLevel::Warning => 0, + PromptLevel::Critical => 2, }; let _: () = msg_send![alert, setAlertStyle: alert_style]; let _: () = msg_send![alert, setMessageText: ns_string(msg)]; @@ -807,7 +803,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 +820,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 +868,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 bool>) { @@ -1189,7 +1177,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 +1305,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); diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 52a25d352c07c7378f22049bae168c5b43da7864..618d8c791779281fa6d117d5d6d61e03d1b7cdab 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/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>, + parker: Arc>, + 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) } } diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 4d86c434d0e0dce5090036bfa34e6a29bac19415..524611b620ea62eeb5e48a3ce5430a992791ae7c 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/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 { diff --git a/crates/gpui2/src/subscription.rs b/crates/gpui2/src/subscription.rs index c2799d2fe62c376477b5fe3aa821eeebe9fcb339..64fcd74dd2a1ab47d4d997d8de52f98fe719c850 100644 --- a/crates/gpui2/src/subscription.rs +++ b/crates/gpui2/src/subscription.rs @@ -21,8 +21,8 @@ struct SubscriberSetState { impl SubscriberSet 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>, + unsubscribe: Option>, } impl Subscription { diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index f1d54e7ae01695c00e2d08786e444bf5161a90e6..d81df5b21c9e8a64af4932462423009a0e291fbd 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,13 +1,16 @@ use crate::{ private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, - BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, LayoutId, Model, Pixels, - Size, ViewContext, VisualContext, WeakModel, WindowContext, + BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, + Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; -use std::{any::TypeId, marker::PhantomData}; +use std::{ + any::{Any, TypeId}, + hash::{Hash, Hasher}, +}; pub trait Render: 'static + Sized { - type Element: Element + 'static + Send; + type Element: Element + 'static; fn render(&mut self, cx: &mut ViewContext) -> Self::Element; } @@ -49,7 +52,7 @@ impl View { pub fn update( &self, cx: &mut C, - f: impl FnOnce(&mut V, &mut C::ViewContext<'_, '_, V>) -> R, + f: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> C::Result where C: VisualContext, @@ -70,55 +73,23 @@ impl Clone for View { } } -impl Component for View { - fn render(self) -> AnyElement { - AnyElement::new(EraseViewState { - view: self, - parent_view_state_type: PhantomData, - }) +impl Hash for View { + fn hash(&self, state: &mut H) { + self.model.hash(state); } } -impl Element<()> for View -where - V: Render, -{ - type ElementState = AnyElement; - - fn id(&self) -> Option { - Some(ElementId::View(self.model.entity_id)) - } - - fn initialize( - &mut self, - _: &mut (), - _: Option, - cx: &mut ViewContext<()>, - ) -> Self::ElementState { - self.update(cx, |state, cx| { - let mut any_element = AnyElement::new(state.render(cx)); - any_element.initialize(state, cx); - any_element - }) +impl PartialEq for View { + fn eq(&self, other: &Self) -> bool { + self.model == other.model } +} - fn layout( - &mut self, - _: &mut (), - element: &mut Self::ElementState, - cx: &mut ViewContext<()>, - ) -> LayoutId { - self.update(cx, |state, cx| element.layout(state, cx)) - } +impl Eq for View {} - fn paint( - &mut self, - _: Bounds, - _: &mut (), - element: &mut Self::ElementState, - cx: &mut ViewContext<()>, - ) { - self.update(cx, |state, cx| element.paint(state, cx)) +impl Component for View { + fn render(self) -> AnyElement { + AnyElement::new(AnyView::from(self)) } } @@ -127,17 +98,25 @@ pub struct WeakView { } impl WeakView { + pub fn entity_id(&self) -> EntityId { + self.model.entity_id + } + pub fn upgrade(&self) -> Option> { Entity::upgrade_from(self) } - pub fn update( + pub fn update( &self, - cx: &mut WindowContext, - f: impl FnOnce(&mut V, &mut ViewContext) -> R, - ) -> Result { + cx: &mut C, + f: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, + ) -> Result + where + C: VisualContext, + Result>: Flatten, + { let view = self.upgrade().context("error upgrading view")?; - Ok(view.update(cx, f)) + Ok(view.update(cx, f)).flatten() } } @@ -149,115 +128,19 @@ impl Clone for WeakView { } } -struct EraseViewState { - view: View, - parent_view_state_type: PhantomData, -} - -unsafe impl Send for EraseViewState {} - -impl Component for EraseViewState { - fn render(self) -> AnyElement { - AnyElement::new(self) +impl Hash for WeakView { + fn hash(&self, state: &mut H) { + self.model.hash(state); } } -impl Element for EraseViewState { - type ElementState = AnyBox; - - fn id(&self) -> Option { - Element::id(&self.view) +impl PartialEq for WeakView { + fn eq(&self, other: &Self) -> bool { + self.model == other.model } - - fn initialize( - &mut self, - _: &mut ParentV, - _: Option, - cx: &mut ViewContext, - ) -> Self::ElementState { - ViewObject::initialize(&mut self.view, cx) - } - - fn layout( - &mut self, - _: &mut ParentV, - element: &mut Self::ElementState, - cx: &mut ViewContext, - ) -> LayoutId { - ViewObject::layout(&mut self.view, element, cx) - } - - fn paint( - &mut self, - bounds: Bounds, - _: &mut ParentV, - element: &mut Self::ElementState, - cx: &mut ViewContext, - ) { - ViewObject::paint(&mut self.view, bounds, element, cx) - } -} - -trait ViewObject: Send + Sync { - fn entity_type(&self) -> TypeId; - fn entity_id(&self) -> EntityId; - fn model(&self) -> AnyModel; - fn initialize(&self, cx: &mut WindowContext) -> AnyBox; - fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId; - fn paint(&self, bounds: Bounds, element: &mut AnyBox, cx: &mut WindowContext); - fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; } -impl ViewObject for View -where - V: Render, -{ - fn entity_type(&self) -> TypeId { - TypeId::of::() - } - - fn entity_id(&self) -> EntityId { - Entity::entity_id(self) - } - - fn model(&self) -> AnyModel { - self.model.clone().into_any() - } - - fn initialize(&self, cx: &mut WindowContext) -> AnyBox { - cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| { - self.update(cx, |state, cx| { - let mut any_element = Box::new(AnyElement::new(state.render(cx))); - any_element.initialize(state, cx); - any_element - }) - }) - } - - fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId { - cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| { - self.update(cx, |state, cx| { - let element = element.downcast_mut::>().unwrap(); - element.layout(state, cx) - }) - }) - } - - fn paint(&self, _: Bounds, element: &mut AnyBox, cx: &mut WindowContext) { - cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| { - self.update(cx, |state, cx| { - let element = element.downcast_mut::>().unwrap(); - element.paint(state, cx); - }); - }); - } - - fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct(&format!("AnyView<{}>", std::any::type_name::())) - .field("entity_id", &ViewObject::entity_id(self).as_u64()) - .finish() - } -} +impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { @@ -289,7 +172,7 @@ impl AnyView { } } - pub(crate) fn entity_type(&self) -> TypeId { + pub fn entity_type(&self) -> TypeId { self.model.entity_type } @@ -343,7 +226,7 @@ impl From> for AnyView { } impl Element for AnyView { - type ElementState = AnyBox; + type ElementState = Box; fn id(&self) -> Option { Some(self.model.entity_id.into()) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 7223826ab0d10b2b6801aca75950ac9f9289b1d1..0202b7521eebc36735077e3199f0322e25a80e91 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -3,16 +3,16 @@ 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, + PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, + SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, + Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; +use futures::channel::oneshot; use parking_lot::RwLock; use slotmap::SlotMap; use smallvec::SmallVec; @@ -21,6 +21,7 @@ use std::{ borrow::{Borrow, BorrowMut, Cow}, fmt::Debug, future::Future, + hash::{Hash, Hasher}, marker::PhantomData, mem, sync::{ @@ -52,7 +53,7 @@ pub enum DispatchPhase { Capture, } -type AnyListener = Box; +type AnyListener = Box; type AnyKeyListener = Box< dyn Fn( &dyn Any, @@ -60,10 +61,9 @@ type AnyKeyListener = Box< DispatchPhase, &mut WindowContext, ) -> Option> - + Send + 'static, >; -type AnyFocusListener = Box; +type AnyFocusListener = Box; slotmap::new_key_type! { pub struct FocusId; } @@ -158,8 +158,8 @@ impl Drop for FocusHandle { // Holds the state for a specific window. pub struct Window { - handle: AnyWindowHandle, - platform_window: MainThreadOnly>, + pub(crate) handle: AnyWindowHandle, + platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, rem_size: Pixels, @@ -194,44 +194,39 @@ impl Window { pub(crate) fn new( handle: AnyWindowHandle, options: WindowOptions, - cx: &mut MainThread, + cx: &mut AppContext, ) -> Self { - let platform_window = cx.platform().open_window(handle, options); + let platform_window = cx.platform.open_window(handle, options); let display_id = platform_window.display().id(); let sprite_atlas = platform_window.sprite_atlas(); let mouse_position = platform_window.mouse_position(); let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); platform_window.on_resize(Box::new({ - let cx = cx.to_async(); + let mut cx = cx.to_async(); move |content_size, scale_factor| { - cx.update_window(handle, |cx| { - 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.dirty = true; - }) - .log_err(); + handle + .update(&mut cx, |_, cx| { + 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.display().id(); + cx.window.dirty = true; + }) + .log_err(); } })); platform_window.on_input({ - let cx = cx.to_async(); + let mut cx = cx.to_async(); Box::new(move |event| { - cx.update_window(handle, |cx| cx.dispatch_event(event)) + handle + .update(&mut cx, |_, cx| cx.dispatch_event(event)) .log_err() .unwrap_or(true) }) }); - let platform_window = MainThreadOnly::new(Arc::new(platform_window), cx.executor.clone()); - Window { handle, platform_window, @@ -305,24 +300,14 @@ impl ContentMask { /// Provides access to application state in the context of a single window. Derefs /// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes /// an `AppContext` and call any `AppContext` methods. -pub struct WindowContext<'a, 'w> { - pub(crate) app: Reference<'a, AppContext>, - pub(crate) window: Reference<'w, Window>, +pub struct WindowContext<'a> { + pub(crate) app: &'a mut AppContext, + pub(crate) window: &'a mut Window, } -impl<'a, 'w> WindowContext<'a, 'w> { - pub(crate) fn immutable(app: &'a AppContext, window: &'w Window) -> Self { - Self { - app: Reference::Immutable(app), - window: Reference::Immutable(window), - } - } - - pub(crate) fn mutable(app: &'a mut AppContext, window: &'w mut Window) -> Self { - Self { - app: Reference::Mutable(app), - window: Reference::Mutable(window), - } +impl<'a> WindowContext<'a> { + pub(crate) fn new(app: &'a mut AppContext, window: &'a mut Window) -> Self { + Self { app, window } } /// Obtain a handle to the window that belongs to this context. @@ -354,10 +339,9 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.last_blur = Some(self.window.focus); } - let window_id = self.window.handle.id; self.window.focus = Some(handle.id); self.app.push_effect(Effect::FocusChanged { - window_id, + window_handle: self.window.handle, focused: Some(handle.id), }); self.notify(); @@ -369,33 +353,51 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.last_blur = Some(self.window.focus); } - let window_id = self.window.handle.id; self.window.focus = None; self.app.push_effect(Effect::FocusChanged { - window_id, + window_handle: self.window.handle, focused: None, }); self.notify(); } - /// Schedule the given closure to be run on the main thread. It will be invoked with - /// a `MainThread`, which provides access to platform-specific functionality - /// of the window. - pub fn run_on_main( + /// 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 WindowContext) + 'static) { + let handle = self.window.handle; + self.app.defer(move |cx| { + handle.update(cx, |_, cx| f(cx)).ok(); + }); + } + + pub fn subscribe( &mut self, - f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, - ) -> Task> + entity: &E, + mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + 'static, + ) -> Subscription where - R: Send + 'static, + Emitter: EventEmitter, + E: Entity, { - if self.executor.is_main_thread() { - Task::ready(Ok(f(unsafe { - mem::transmute::<&mut Self, &mut MainThread>(self) - }))) - } else { - let id = self.window.handle.id; - self.app.run_on_main(move |cx| cx.update_window(id, f)) - } + let entity_id = entity.entity_id(); + let entity = entity.downgrade(); + let window_handle = self.window.handle; + self.app.event_listeners.insert( + entity_id, + Box::new(move |event, cx| { + window_handle + .update(cx, |_, cx| { + if let Some(handle) = E::upgrade_from(&entity) { + let event = event.downcast_ref().expect("invalid event type"); + on_event(handle, event, cx); + true + } else { + false + } + }) + .unwrap_or(false) + }), + ) } /// Create an `AsyncWindowContext`, which has a static lifetime and can be held across @@ -405,66 +407,54 @@ impl<'a, 'w> WindowContext<'a, 'w> { } /// Schedule the given closure to be run directly after the current frame is rendered. - pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + '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::>(); - 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 mut 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::>(); + 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. /// The closure is provided a handle to the current window and an `AsyncWindowContext` for /// use within your future. - pub fn spawn( - &mut self, - f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&mut self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task where - R: Send + 'static, - Fut: Future + Send + 'static, + R: 'static, + Fut: Future + '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 } - }) + self.app + .spawn(|app| f(AsyncWindowContext::new(app, self.window.handle))) } /// Update the global of the given type. The given closure is given simultaneous mutable @@ -575,7 +565,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { /// a specific need to register a global listener. pub fn on_mouse_event( &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 +902,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) { @@ -1155,15 +1139,28 @@ impl<'a, 'w> WindowContext<'a, 'w> { /// is updated. pub fn observe_global( &mut self, - f: impl Fn(&mut WindowContext<'_, '_>) + Send + 'static, + f: impl Fn(&mut WindowContext<'_>) + 'static, ) -> Subscription { - let window_id = self.window.handle.id; + let window_handle = self.window.handle; self.global_observers.insert( TypeId::of::(), - Box::new(move |cx| cx.update_window(window_id, |cx| f(cx)).is_ok()), + Box::new(move |cx| window_handle.update(cx, |_, cx| f(cx)).is_ok()), ) } + pub fn activate_window(&self) { + self.window.platform_window.activate(); + } + + pub fn prompt( + &self, + level: PromptLevel, + msg: &str, + answers: &[&str], + ) -> oneshot::Receiver { + self.window.platform_window.prompt(level, msg, answers) + } + fn dispatch_action( &mut self, action: Box, @@ -1243,52 +1240,61 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } -impl Context for WindowContext<'_, '_> { - type ModelContext<'a, T> = ModelContext<'a, T>; +impl Context for WindowContext<'_> { type Result = T; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Model where - T: 'static + Send, + T: 'static, { let slot = self.app.entities.reserve(); - let model = build_model(&mut ModelContext::mutable(&mut *self.app, slot.downgrade())); + let model = build_model(&mut ModelContext::new(&mut *self.app, slot.downgrade())); self.entities.insert(slot, model) } fn update_model( &mut self, model: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> R { let mut entity = self.entities.lease(model); let result = update( &mut *entity, - &mut ModelContext::mutable(&mut *self.app, model.downgrade()), + &mut ModelContext::new(&mut *self.app, model.downgrade()), ); self.entities.end_lease(entity); result } -} -impl VisualContext for WindowContext<'_, '_> { - type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>; + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + if window == self.window.handle { + let root_view = self.window.root_view.clone().unwrap(); + Ok(update(root_view, self)) + } else { + window.update(self.app, update) + } + } +} +impl VisualContext for WindowContext<'_> { fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Send, + V: 'static, { let slot = self.app.entities.reserve(); let view = View { model: slot.clone(), }; - let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade()); + let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); let entity = build_view_state(&mut cx); self.entities.insert(slot, entity); view @@ -1298,17 +1304,35 @@ impl VisualContext for WindowContext<'_, '_> { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut T, &mut Self::ViewContext<'_, '_, T>) -> R, + update: impl FnOnce(&mut T, &mut ViewContext<'_, T>) -> R, ) -> Self::Result { let mut lease = self.app.entities.lease(&view.model); - let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade()); + let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); let result = update(&mut *lease, &mut cx); cx.app.entities.end_lease(lease); result } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: Render, + { + let slot = self.app.entities.reserve(); + let view = View { + model: slot.clone(), + }; + let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); + let entity = build_view(&mut cx); + self.entities.insert(slot, entity); + self.window.root_view = Some(view.clone().into()); + view + } } -impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> { +impl<'a> std::ops::Deref for WindowContext<'a> { type Target = AppContext; fn deref(&self) -> &Self::Target { @@ -1316,19 +1340,19 @@ impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> { } } -impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> { +impl<'a> std::ops::DerefMut for WindowContext<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.app } } -impl<'a, 'w> Borrow for WindowContext<'a, 'w> { +impl<'a> Borrow for WindowContext<'a> { fn borrow(&self) -> &AppContext { &self.app } } -impl<'a, 'w> BorrowMut for WindowContext<'a, 'w> { +impl<'a> BorrowMut for WindowContext<'a> { fn borrow_mut(&mut self) -> &mut AppContext { &mut self.app } @@ -1428,7 +1452,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { f: impl FnOnce(Option, &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 +1490,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { f: impl FnOnce(Option, &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) @@ -1496,13 +1520,13 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } } -impl Borrow for WindowContext<'_, '_> { +impl Borrow for WindowContext<'_> { fn borrow(&self) -> &Window { &self.window } } -impl BorrowMut for WindowContext<'_, '_> { +impl BorrowMut for WindowContext<'_> { fn borrow_mut(&mut self) -> &mut Window { &mut self.window } @@ -1510,52 +1534,48 @@ impl BorrowMut for WindowContext<'_, '_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} -pub struct ViewContext<'a, 'w, V> { - window_cx: WindowContext<'a, 'w>, - view: WeakView, +pub struct ViewContext<'a, V> { + window_cx: WindowContext<'a>, + view: &'a View, } -impl Borrow for ViewContext<'_, '_, V> { +impl Borrow for ViewContext<'_, V> { fn borrow(&self) -> &AppContext { &*self.window_cx.app } } -impl BorrowMut for ViewContext<'_, '_, V> { +impl BorrowMut for ViewContext<'_, V> { fn borrow_mut(&mut self) -> &mut AppContext { &mut *self.window_cx.app } } -impl Borrow for ViewContext<'_, '_, V> { +impl Borrow for ViewContext<'_, V> { fn borrow(&self) -> &Window { &*self.window_cx.window } } -impl BorrowMut for ViewContext<'_, '_, V> { +impl BorrowMut for ViewContext<'_, V> { fn borrow_mut(&mut self) -> &mut Window { &mut *self.window_cx.window } } -impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { - pub(crate) fn mutable( - app: &'a mut AppContext, - window: &'w mut Window, - view: WeakView, - ) -> Self { +impl<'a, V: 'static> ViewContext<'a, V> { + pub(crate) fn new(app: &'a mut AppContext, window: &'a mut Window, view: &'a View) -> Self { Self { - window_cx: WindowContext::mutable(app, window), + window_cx: WindowContext::new(app, window), view, } } - pub fn view(&self) -> WeakView { + pub fn view(&self) -> View { self.view.clone() } - pub fn model(&self) -> WeakModel { + pub fn model(&self) -> Model { self.view.model.clone() } @@ -1566,40 +1586,50 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { result } - pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + Send + 'static) + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static) where - V: Any + Send, + V: 'static, { - let view = self.view().upgrade().unwrap(); + let view = self.view(); self.window_cx.on_next_frame(move |cx| view.update(cx, f)); } + /// 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 V, &mut ViewContext) + 'static) { + let view = self.view().downgrade(); + self.window_cx.defer(move |cx| { + view.update(cx, f).ok(); + }); + } + pub fn observe( &mut self, entity: &E, - mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, '_, V>) + Send + 'static, + mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, V>) + 'static, ) -> Subscription where V2: 'static, - V: Any + Send, + V: 'static, E: Entity, { - let view = self.view(); + let view = self.view().downgrade(); let entity_id = entity.entity_id(); let entity = entity.downgrade(); let window_handle = self.window.handle; self.app.observers.insert( entity_id, Box::new(move |cx| { - cx.update_window(window_handle.id, |cx| { - if let Some(handle) = E::upgrade_from(&entity) { - view.update(cx, |this, cx| on_notify(this, handle, cx)) - .is_ok() - } else { - false - } - }) - .unwrap_or(false) + window_handle + .update(cx, |_, cx| { + if let Some(handle) = E::upgrade_from(&entity) { + view.update(cx, |this, cx| on_notify(this, handle, cx)) + .is_ok() + } else { + false + } + }) + .unwrap_or(false) }), ) } @@ -1607,44 +1637,44 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, '_, V>) + Send + 'static, + mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, V>) + 'static, ) -> Subscription where V2: EventEmitter, E: Entity, { - let view = self.view(); + let view = self.view().downgrade(); let entity_id = entity.entity_id(); let handle = entity.downgrade(); let window_handle = self.window.handle; self.app.event_listeners.insert( entity_id, Box::new(move |event, cx| { - cx.update_window(window_handle.id, |cx| { - if let Some(handle) = E::upgrade_from(&handle) { - let event = event.downcast_ref().expect("invalid event type"); - view.update(cx, |this, cx| on_event(this, handle, event, cx)) - .is_ok() - } else { - false - } - }) - .unwrap_or(false) + window_handle + .update(cx, |_, cx| { + if let Some(handle) = E::upgrade_from(&handle) { + let event = event.downcast_ref().expect("invalid event type"); + view.update(cx, |this, cx| on_event(this, handle, event, cx)) + .is_ok() + } else { + false + } + }) + .unwrap_or(false) }), ) } pub fn on_release( &mut self, - mut on_release: impl FnMut(&mut V, &mut WindowContext) + Send + 'static, + on_release: impl FnOnce(&mut V, &mut WindowContext) + 'static, ) -> Subscription { let window_handle = self.window.handle; self.app.release_listeners.insert( self.view.model.entity_id, Box::new(move |this, cx| { let this = this.downcast_mut().expect("invalid entity type"); - // todo!("are we okay with silently swallowing the error?") - let _ = cx.update_window(window_handle.id, |cx| on_release(this, cx)); + let _ = window_handle.update(cx, |_, cx| on_release(this, cx)); }), ) } @@ -1652,21 +1682,21 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn observe_release( &mut self, entity: &E, - mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, '_, V>) + Send + 'static, + mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, V>) + 'static, ) -> Subscription where - V: Any + Send, + V: 'static, V2: 'static, E: Entity, { - let view = self.view(); + let view = self.view().downgrade(); let entity_id = entity.entity_id(); let window_handle = self.window.handle; self.app.release_listeners.insert( entity_id, Box::new(move |entity, cx| { let entity = entity.downcast_mut().expect("invalid entity type"); - let _ = cx.update_window(window_handle.id, |cx| { + let _ = window_handle.update(cx, |_, cx| { view.update(cx, |this, cx| on_release(this, entity, cx)) }); }), @@ -1682,9 +1712,9 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn on_focus_changed( &mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) { - let handle = self.view(); + let handle = self.view().downgrade(); self.window.focus_listeners.push(Box::new(move |event, cx| { handle .update(cx, |view, cx| listener(view, event, cx)) @@ -1700,12 +1730,12 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { let old_stack_len = self.window.key_dispatch_stack.len(); if !self.window.freeze_key_dispatch_stack { for (event_type, listener) in key_listeners { - let handle = self.view(); + let handle = self.view().downgrade(); let listener = Box::new( move |event: &dyn Any, context_stack: &[&DispatchContext], phase: DispatchPhase, - cx: &mut WindowContext<'_, '_>| { + cx: &mut WindowContext<'_>| { handle .update(cx, |view, cx| { listener(view, event, context_stack, phase, cx) @@ -1778,41 +1808,21 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { result } - pub fn run_on_main( - &mut self, - view: &mut V, - f: impl FnOnce(&mut V, &mut MainThread>) -> R + Send + 'static, - ) -> Task> - where - R: Send + 'static, - { - if self.executor.is_main_thread() { - let cx = unsafe { mem::transmute::<&mut Self, &mut MainThread>(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( &mut self, - f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut + Send + 'static, + f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut, ) -> Task where - R: Send + 'static, - Fut: Future + Send + 'static, + R: 'static, + Fut: Future + 'static, { - let view = self.view(); - self.window_cx.spawn(move |_, cx| { - let result = f(view, cx); - async move { result.await } - }) + let view = self.view().downgrade(); + self.window_cx.spawn(|cx| f(view, cx)) } pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where - G: 'static + Send, + G: 'static, { let mut global = self.app.lease_global::(); let result = f(&mut global, self); @@ -1822,26 +1832,25 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn observe_global( &mut self, - f: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) + Send + 'static, + f: impl Fn(&mut V, &mut ViewContext<'_, V>) + 'static, ) -> Subscription { - let window_id = self.window.handle.id; - let handle = self.view(); + let window_handle = self.window.handle; + let view = self.view().downgrade(); self.global_observers.insert( TypeId::of::(), Box::new(move |cx| { - cx.update_window(window_id, |cx| { - handle.update(cx, |view, cx| f(view, cx)).is_ok() - }) - .unwrap_or(false) + window_handle + .update(cx, |_, cx| view.update(cx, |view, cx| f(view, cx)).is_ok()) + .unwrap_or(false) }), ) } pub fn on_mouse_event( &mut self, - handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, ) { - let handle = self.view().upgrade().unwrap(); + let handle = self.view(); self.window_cx.on_mouse_event(move |event, phase, cx| { handle.update(cx, |view, cx| { handler(view, event, phase, cx); @@ -1850,10 +1859,10 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { } } -impl<'a, 'w, V> ViewContext<'a, 'w, V> +impl ViewContext<'_, V> where V: EventEmitter, - V::Event: Any + Send, + V::Event: 'static, { pub fn emit(&mut self, event: V::Event) { let emitter = self.view.model.entity_id; @@ -1864,35 +1873,36 @@ where } } -impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { - type ModelContext<'b, U> = ModelContext<'b, U>; +impl Context for ViewContext<'_, V> { type Result = U; - fn build_model( + fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, - ) -> Model - where - T: 'static + Send, - { + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, + ) -> Model { self.window_cx.build_model(build_model) } fn update_model( &mut self, model: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> R { self.window_cx.update_model(model, update) } -} -impl VisualContext for ViewContext<'_, '_, V> { - type ViewContext<'a, 'w, V2> = ViewContext<'a, 'w, V2>; + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + self.window_cx.update_window(window, update) + } +} - fn build_view( +impl VisualContext for ViewContext<'_, V> { + fn build_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, W>) -> W, + build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, ) -> Self::Result> { self.window_cx.build_view(build_view) } @@ -1900,21 +1910,31 @@ impl VisualContext for ViewContext<'_, '_, V> { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V2, &mut Self::ViewContext<'_, '_, V2>) -> R, + update: impl FnOnce(&mut V2, &mut ViewContext<'_, V2>) -> R, ) -> Self::Result { self.window_cx.update_view(view, update) } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, + ) -> Self::Result> + where + W: Render, + { + self.window_cx.replace_root_view(build_view) + } } -impl<'a, 'w, V> std::ops::Deref for ViewContext<'a, 'w, V> { - type Target = WindowContext<'a, 'w>; +impl<'a, V> std::ops::Deref for ViewContext<'a, V> { + type Target = WindowContext<'a>; fn deref(&self) -> &Self::Target { &self.window_cx } } -impl<'a, 'w, V> std::ops::DerefMut for ViewContext<'a, 'w, V> { +impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.window_cx } @@ -1929,42 +1949,74 @@ impl WindowId { } } -#[derive(PartialEq, Eq)] +#[derive(Deref, DerefMut)] pub struct WindowHandle { - id: WindowId, + #[deref] + #[deref_mut] + pub(crate) any_handle: AnyWindowHandle, state_type: PhantomData, } -impl Copy for WindowHandle {} - -impl Clone for WindowHandle { - fn clone(&self) -> Self { +impl WindowHandle { + pub fn new(id: WindowId) -> Self { WindowHandle { - id: self.id, + any_handle: AnyWindowHandle { + id, + state_type: TypeId::of::(), + }, state_type: PhantomData, } } + + pub fn update( + self, + cx: &mut C, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, + ) -> Result + where + C: Context, + { + cx.update_window(self.any_handle, |root_view, cx| { + let view = root_view + .downcast::() + .map_err(|_| anyhow!("the type of the window's root view has changed"))?; + Ok(cx.update_view(&view, update)) + })? + } } -impl WindowHandle { - pub fn new(id: WindowId) -> Self { +impl Copy for WindowHandle {} + +impl Clone for WindowHandle { + fn clone(&self) -> Self { WindowHandle { - id, + any_handle: self.any_handle, state_type: PhantomData, } } } -impl Into for WindowHandle { +impl PartialEq for WindowHandle { + fn eq(&self, other: &Self) -> bool { + self.any_handle == other.any_handle + } +} + +impl Eq for WindowHandle {} + +impl Hash for WindowHandle { + fn hash(&self, state: &mut H) { + self.any_handle.hash(state); + } +} + +impl Into for WindowHandle { fn into(self) -> AnyWindowHandle { - AnyWindowHandle { - id: self.id, - state_type: TypeId::of::(), - } + self.any_handle } } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct AnyWindowHandle { pub(crate) id: WindowId, state_type: TypeId, @@ -1974,6 +2026,28 @@ impl AnyWindowHandle { pub fn window_id(&self) -> WindowId { self.id } + + pub fn downcast(&self) -> Option> { + if TypeId::of::() == self.state_type { + Some(WindowHandle { + any_handle: *self, + state_type: PhantomData, + }) + } else { + None + } + } + + pub fn update( + self, + cx: &mut C, + update: impl FnOnce(AnyView, &mut WindowContext<'_>) -> R, + ) -> Result + where + C: Context, + { + cx.update_window(self, update) + } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/gpui2_macros/src/derive_component.rs b/crates/gpui2_macros/src/derive_component.rs index d1919c8bc4f475b596bff5119ce132f99ea37f95..a9467033101721082fb3c509251489605f945379 100644 --- a/crates/gpui2_macros/src/derive_component.rs +++ b/crates/gpui2_macros/src/derive_component.rs @@ -30,7 +30,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let expanded = quote! { impl #impl_generics gpui2::Component<#view_type> for #name #ty_generics #where_clause { fn render(self) -> gpui2::AnyElement<#view_type> { - (move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, '_, #view_type>| self.render(view_state, cx)) + (move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, #view_type>| self.render(view_state, cx)) .render() } } diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index b5a2111e1977dff386df87fe8022d621cdebe39e..f7e45a90f97ced0e1ba89d84ddf305c5f2025dfd 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/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!( diff --git a/crates/install_cli2/src/install_cli2.rs b/crates/install_cli2/src/install_cli2.rs index ecdf2a0f2ac37970dfac387ae876d3032e4d8cda..e24a48ef077345fb1b33640bcbf74a91490568e1 100644 --- a/crates/install_cli2/src/install_cli2.rs +++ b/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(); diff --git a/crates/journal2/src/journal2.rs b/crates/journal2/src/journal2.rs index 62685485301d3f7d6fdcf7ddbc3e8536692e282d..d875cb3834a4b5f0aea7bb9db9eee8eebd726cdd 100644 --- a/crates/journal2/src/journal2.rs +++ b/crates/journal2/src/journal2.rs @@ -77,7 +77,7 @@ pub fn new_journal_entry(_: Arc, 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) diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index 3ab68d9f44969f2787e57b02d1e9148232f51bbe..3999f275f2aeb581504b3adc66639142e4e65c2d 100644 --- a/crates/language2/src/buffer.rs +++ b/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) { 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 { 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::::from(""); Diff { diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index d2d886dd84317a09eaeb5bf08b97463f8766cc76..16306fe2ce66accec1fa3aeef36dd7ecf971efc3 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/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)> { 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) diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 21746bf43c19922b4d0cd380b778fb59e41149e2..ea13dc185fd722dde9ac22e7b669a26beeae7c69 100644 --- a/crates/language2/src/language2.rs +++ b/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>>>>, >, - executor: Option, + executor: Option, 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::() diff --git a/crates/language2/src/outline.rs b/crates/language2/src/outline.rs index dd3a4acf6bd5906dfde6dd908f298b02ed70c743..94dfaa0e118bbcaf2eca02d2b4f6b8a93720fe17 100644 --- a/crates/language2/src/outline.rs +++ b/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 Outline { } } - pub async fn search(&self, query: &str, executor: Executor) -> Vec { + pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec { let query = query.trim_start(); let is_path_query = query.contains(' '); let smart_case = query.chars().any(|c| c.is_uppercase()); diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs index ad10a4c95d06acf47507ed968878836d26f1969d..4062441a06b4e7bede19a3d57dc4835111996b29 100644 --- a/crates/live_kit_client2/examples/test_app.rs +++ b/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") diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs index f1c3d39b8e9620af6cbb28d6944ceb738412714d..10c97e8d815b1798ed7711cfb33d7dc011635aef 100644 --- a/crates/live_kit_client2/src/test.rs +++ b/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>, - executor: Arc, + executor: Arc, } impl TestServer { @@ -24,7 +24,7 @@ impl TestServer { url: String, api_key: String, secret_key: String, - executor: Arc, + executor: Arc, ) -> Result> { let mut servers = SERVERS.lock(); if servers.contains_key(&url) { diff --git a/crates/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 27b7b36e738c8889f2cec43cede3c981b57dcb07..ed67a5c9c211c49e5cea0d20515e174e1c0d0a93 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/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>>, response_handlers: Arc>>>, io_handlers: Arc>>, - executor: Executor, + executor: BackgroundExecutor, #[allow(clippy::type_complexity)] io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, @@ -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> + Send, + F: 'static + FnMut(T::Params, AsyncAppContext) -> Fut + Send, + Fut: 'static + Future>, { self.on_custom_request(T::METHOD, f) } @@ -629,7 +629,7 @@ impl LanguageServer { #[must_use] pub fn on_custom_notification(&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> + Send, + F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send, + Fut: 'static + Future>, 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>>, outbound_tx: &channel::Sender, - executor: &Executor, + executor: &BackgroundExecutor, params: T::Params, ) -> impl 'static + Future> where @@ -1047,8 +1047,9 @@ impl FakeLanguageServer { .on_request::(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 diff --git a/crates/multi_buffer2/src/multi_buffer2.rs b/crates/multi_buffer2/src/multi_buffer2.rs index c5827b8b132ea78575a19ee9738a2305eb2d7ae5..b5a7ced517f0088c72590325ea23bc87e2e8e76f 100644 --- a/crates/multi_buffer2/src/multi_buffer2.rs +++ b/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 diff --git a/crates/prettier2/src/prettier2.rs b/crates/prettier2/src/prettier2.rs index a71bf1a8b03c2117d44387d2502c65f7a766aefe..6d9664b234bcf497ebb9070182a3d11e1a4d5f1b 100644 --- a/crates/prettier2/src/prettier2.rs +++ b/crates/prettier2/src/prettier2.rs @@ -143,7 +143,7 @@ impl Prettier { ) -> anyhow::Result { 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" diff --git a/crates/project2/src/lsp_command.rs b/crates/project2/src/lsp_command.rs index 84a6c0517c69fedd9383b1fbfc39dce8bb32d296..9e6a96e15e8de163d29a9b54b68964ab1de37909 100644 --- a/crates/project2/src/lsp_command.rs +++ b/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 for FormattingOptions { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for PrepareRename { type Response = Option>; 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; 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; 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; 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; 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; 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; 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; 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; type LspRequest = lsp2::request::OnTypeFormatting; @@ -2192,7 +2192,7 @@ impl InlayHints { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for InlayHints { type Response = Vec; type LspRequest = lsp2::InlayHintRequest; diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 41b6296367ae2b37b33e6b52b136ff6e94c52ee5..05434b93b4c38cfc082cbd8fde01a9739b7c7676 100644 --- a/crates/project2/src/project2.rs +++ b/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::>(); - 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>, opened_buffers: HashMap, (Model, BufferSnapshot)>, - executor: Executor, + executor: BackgroundExecutor, fs: Arc, 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::>(); - 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, ) -> Task, 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; } diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index fd8c1951015a20d964917ac35b0cc7a30c34a1a6..5a2f82c3758d215a018d154ae4fe53b56eefd504 100644 --- a/crates/project2/src/project_tests.rs +++ b/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::>(); - -// 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::>(); + + 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( 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::(|_, _| 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::(|_, _| 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::(|_, _| 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::(|_, _| 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::(|_, _| 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::(|_, _| 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::>(), -// 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::>(), -// 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::>(), + 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::>(), + expected_paths + ); + }); +} + #[gpui2::test(iterations = 10)] async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) { init_test(cx); @@ -2942,7 +2975,6 @@ async fn test_buffer_deduping(cx: &mut gpui2::TestAppContext) { #[gpui2::test] async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { init_test(cx); - dbg!("GAH"); let fs = FakeFs::new(cx.executor().clone()); fs.insert_tree( @@ -2954,7 +2986,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { }), ) .await; - dbg!("NOOP"); let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; @@ -2964,8 +2995,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { .unwrap(); let events = Arc::new(Mutex::new(Vec::new())); - dbg!("BOOP"); - // initially, the buffer isn't dirty. buffer1.update(cx, |buffer, cx| { cx.subscribe(&buffer1, { @@ -2982,7 +3011,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { buffer.edit([(1..2, "")], None, cx); }); - dbg!("ADSASD"); // after the first edit, the buffer is dirty, and emits a dirtied event. buffer1.update(cx, |buffer, cx| { @@ -3000,7 +3028,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { cx, ); }); - dbg!("1111"); // after saving, the buffer is not dirty, and emits a saved event. buffer1.update(cx, |buffer, cx| { @@ -3012,8 +3039,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { buffer.edit([(2..2, "D")], None, cx); }); - dbg!("5555555"); - // after editing again, the buffer is dirty, and emits another dirty event. buffer1.update(cx, |buffer, cx| { assert!(buffer.text() == "aBDc"); @@ -3035,7 +3060,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { assert!(!buffer.is_dirty()); }); - dbg!("666666"); assert_eq!( *events.lock(), &[language2::Event::Edited, language2::Event::DirtyChanged] @@ -3055,8 +3079,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { .detach(); }); - dbg!("0000000"); - fs.remove_file("/dir/file2".as_ref(), Default::default()) .await .unwrap(); @@ -3084,7 +3106,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { .detach(); }); - dbg!(";;;;;;"); buffer3.update(cx, |buffer, cx| { buffer.edit([(0..0, "x")], None, cx); }); diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index 6a13bc35994e144f8e26f28d172eeea4ed588214..060fefe6b3aa70a86df98335d733157482ac4d1b 100644 --- a/crates/project2/src/worktree.rs +++ b/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>> { 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, fs: Arc, status_updates_tx: UnboundedSender, - executor: Executor, + executor: BackgroundExecutor, scan_requests_rx: channel::Receiver, path_prefixes_to_scan_rx: channel::Receiver>, next_entry_id: Arc, @@ -3032,7 +3035,7 @@ impl BackgroundScanner { next_entry_id: Arc, fs: Arc, status_updates_tx: UnboundedSender, - executor: Executor, + executor: BackgroundExecutor, scan_requests_rx: channel::Receiver, path_prefixes_to_scan_rx: channel::Receiver>, ) -> Self { @@ -4050,7 +4053,8 @@ impl WorktreeModelHandle for Model { &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 { }); 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; diff --git a/crates/rpc2/src/conn.rs b/crates/rpc2/src/conn.rs index 902e9822d576f8e96e8b3da4fad44453985c44a4..ec3c5b68cf5fe54e4af778e33df10df4adb8798c 100644 --- a/crates/rpc2/src/conn.rs +++ b/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) { use std::sync::{ atomic::{AtomicBool, Ordering::SeqCst}, @@ -53,7 +53,7 @@ impl Connection { #[allow(clippy::type_complexity)] fn channel( killed: Arc, - executor: gpui2::Executor, + executor: gpui2::BackgroundExecutor, ) -> ( Box>, Box>>, diff --git a/crates/rpc2/src/peer.rs b/crates/rpc2/src/peer.rs index 6dfb170f4c51b6048e9499cd72abe6a9873c274b..104ab1b4211271dbc982184a01461a29d8874a75 100644 --- a/crates/rpc2/src/peer.rs +++ b/crates/rpc2/src/peer.rs @@ -342,7 +342,7 @@ impl Peer { pub fn add_test_connection( self: &Arc, connection: Connection, - executor: gpui2::Executor, + executor: gpui2::BackgroundExecutor, ) -> ( ConnectionId, impl Future> + 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 diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index 21050356053bd69dd084fa2422ae8bec28cc2132..002c9daf124f44b38be264f59718dd5099c3a9de 100644 --- a/crates/settings2/src/settings_file.rs +++ b/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, path: PathBuf, ) -> mpsc::UnboundedReceiver { @@ -63,7 +63,10 @@ pub fn handle_settings_file_changes( mut user_settings_file_rx: mpsc::UnboundedReceiver, 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) diff --git a/crates/sqlez/src/thread_safe_connection.rs b/crates/sqlez/src/thread_safe_connection.rs index ffadb3af41d165737af55538661ec7100e62daeb..54241b6d72a45d412178d6be3d48cba41500fee6 100644 --- a/crates/sqlez/src/thread_safe_connection.rs +++ b/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; "] diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 5cf73576bc4b0cc9639c6d3889209a4d3a8f5b4f..adc5dd3511039de6d292c281d5dc54ca54d3e5f2 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/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 { //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>) { + fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext) { 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, - cx: &mut MainThread>, + cx: &mut ModelContext, ) { let setting = TerminalSettings::get_global(cx); @@ -1300,7 +1302,7 @@ impl Terminal { cx: &mut ModelContext, ) -> Task>> { 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() diff --git a/crates/ui2/src/components/panes.rs b/crates/ui2/src/components/panes.rs index 167e8370832a2182915dffcd4e40105b53854a7e..bf0f27d43f32742abfe61c527c67dd6d8fd0f96a 100644 --- a/crates/ui2/src/components/panes.rs +++ b/crates/ui2/src/components/panes.rs @@ -51,7 +51,7 @@ impl Pane { .id("drag-target") .drag_over::(|d| d.bg(red())) .on_drop(|_, files: View, cx| { - dbg!("dropped files!", files.read(cx)); + eprintln!("dropped files! {:?}", files.read(cx)); }) .absolute() .inset_0(), diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index d7b52c7ab3cfc0e405d080bb7b3f65192f3e8663..e8b0ee3be59b19fd8307f3485971b2fafb2a89f4 100644 --- a/crates/ui2/src/components/tab.rs +++ b/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::(|d| d.bg(cx.theme().colors().element_drop_target)) .on_drop(|_view, state: View, cx| { - dbg!(state.read(cx)); + eprintln!("{:?}", state.read(cx)); }) .px_2() .py_0p5() diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 82eacd97106a375c9ab88db6f73b5b46a7595b20..1233bee3278392ef63e3efa876efd2849340bacb 100644 --- a/crates/zed2/src/main.rs +++ b/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, session_id: Strin fn upload_previous_panics(http: Arc, 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 {