From 51ecf8e6b46af8c3a0ebaa32da01d3b73bf6edb7 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 1 Nov 2023 14:50:29 -0400 Subject: [PATCH 01/23] collab 0.28.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 755f4440d98c0630b4650a8e98194e2f12a6a540..2b70dcd3cfb89568ffa46a7daec078f25a4790f7 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/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]] From 57ffa8201edcc8ed53f665f261d8b1e6db3a593e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 11:31:23 -0700 Subject: [PATCH 02/23] Start removing the Send impl for App Co-authored-by: Antonio Co-authored-by: Nathan --- .../ai2/src/providers/open_ai/completion.rs | 8 +- crates/ai2/src/providers/open_ai/embedding.rs | 6 +- crates/audio2/src/audio2.rs | 8 +- crates/call2/src/room.rs | 3 +- crates/client2/src/client2.rs | 18 +- crates/client2/src/test.rs | 10 +- crates/copilot2/src/copilot2.rs | 2 +- .../src/test/editor_lsp_test_context.rs | 6 +- crates/fs2/src/fs2.rs | 8 +- crates/fuzzy2/src/paths.rs | 4 +- crates/gpui2/src/app.rs | 330 ++++++------------ crates/gpui2/src/app/async_context.rs | 101 ++---- crates/gpui2/src/app/entity_map.rs | 2 +- crates/gpui2/src/app/model_context.rs | 33 +- crates/gpui2/src/app/test_context.rs | 98 +++--- crates/gpui2/src/assets.rs | 2 +- crates/gpui2/src/element.rs | 12 +- crates/gpui2/src/executor.rs | 95 ++--- crates/gpui2/src/gpui2.rs | 135 +------ crates/gpui2/src/interactive.rs | 50 ++- crates/gpui2/src/platform.rs | 13 +- crates/gpui2/src/platform/mac/platform.rs | 71 ++-- crates/gpui2/src/platform/mac/window.rs | 57 ++- crates/gpui2/src/platform/test/dispatcher.rs | 55 --- crates/gpui2/src/platform/test/platform.rs | 20 +- crates/gpui2/src/subscription.rs | 6 +- crates/gpui2/src/view.rs | 11 +- crates/gpui2/src/window.rs | 177 +++------- crates/language2/src/language2.rs | 8 +- crates/lsp2/src/lsp2.rs | 18 +- crates/project2/src/project2.rs | 20 +- crates/project2/src/worktree.rs | 11 +- crates/rpc2/src/conn.rs | 4 +- crates/rpc2/src/peer.rs | 2 +- crates/settings2/src/settings_file.rs | 4 +- crates/sqlez/src/thread_safe_connection.rs | 6 +- crates/vim/src/test/vim_test_context.rs | 6 +- crates/zed2/src/main.rs | 18 +- 38 files changed, 506 insertions(+), 932 deletions(-) diff --git a/crates/ai2/src/providers/open_ai/completion.rs b/crates/ai2/src/providers/open_ai/completion.rs index eca56110271a3be407a1c8b9f82cbb63c41bef23..1582659e71535137108f0acaaef2540d96865dd5 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/crates/ai2/src/providers/open_ai/completion.rs @@ -4,7 +4,7 @@ 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 +105,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 +198,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 { diff --git a/crates/ai2/src/providers/open_ai/embedding.rs b/crates/ai2/src/providers/open_ai/embedding.rs index fc49c15134d0aba787968acbd412daff60ce6106..dde4af127379fb711a89e47eda782497154b9280 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)); diff --git a/crates/audio2/src/audio2.rs b/crates/audio2/src/audio2.rs index d04587d74e545bc2579502cd567b65de15dfceb1..5a27b7934987c91210253aa9909a86256021c300 100644 --- a/crates/audio2/src/audio2.rs +++ b/crates/audio2/src/audio2.rs @@ -1,6 +1,6 @@ use assets::SoundRegistry; use futures::{channel::mpsc, StreamExt}; -use gpui2::{AppContext, AssetSource, Executor}; +use gpui2::{AppContext, AssetSource, BackgroundExecutor}; use rodio::{OutputStream, OutputStreamHandle}; use util::ResultExt; @@ -34,7 +34,7 @@ impl Sound { } pub struct Audio { - tx: mpsc::UnboundedSender>, + tx: mpsc::UnboundedSender>, } struct AudioState { @@ -60,8 +60,8 @@ impl AudioState { } impl Audio { - pub fn new(executor: &Executor) -> Self { - let (tx, mut rx) = mpsc::unbounded::>(); + pub fn new(executor: &BackgroundExecutor) -> Self { + let (tx, mut rx) = mpsc::unbounded::>(); executor .spawn_on_main(|| async move { let mut audio = AudioState { diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index b7bac52a8bf9c6a812e1d1d8c3ef1bd14dcb0db7..556f9e778e8be6d0d0992618638a99ebf6f3710b 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -445,7 +445,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 { diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 19e8685c28cd55094b064ea0af60bbd6744fa475..435d5f18407cc5a32b447685b125958b55125679 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -505,7 +505,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); @@ -763,7 +763,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 +815,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 +979,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() @@ -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,7 +1423,7 @@ 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(); @@ -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)); 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/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 083c491656579efe1b9b0cb1df96c0052b8bcd74..3d50834e94d88a21d25666086cff04a323b50d6f 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 { diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 3e2f38a0b665e964eb51583c13d4cd4a87422b3d..59b1cbce051ee5c18e8abaf875fb57bafcfe5966 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -266,9 +266,9 @@ impl<'a> EditorLspTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static + Send, - F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Send + Future>, + T::Params: 'static, + F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Future>, { let url = self.buffer_lsp_url.clone(); self.lsp.handle_request::(move |params, cx| { 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/gpui2/src/app.rs b/crates/gpui2/src/app.rs index cdc3f8172d3874188e8dc8f531c5d08b0c149ef5..b3747f3cbf12d36c3b3e1db59ab2ccec5eda1085 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -14,8 +14,8 @@ pub use test_context::*; use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource, - ClipboardItem, Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, - KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, Render, + BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, FocusEvent, FocusHandle, + FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, WindowId, }; @@ -26,17 +26,18 @@ use parking_lot::Mutex; use slotmap::SlotMap; use std::{ any::{type_name, Any, TypeId}, - borrow::Borrow, + cell::RefCell, marker::PhantomData, mem, ops::{Deref, DerefMut}, path::PathBuf, - sync::{atomic::Ordering::SeqCst, Arc, Weak}, + rc::{Rc, Weak}, + sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, }; use util::http::{self, HttpClient}; -pub struct App(Arc>); +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 +55,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 +71,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,29 +84,25 @@ impl App { where F: 'static + FnMut(&mut AppContext), { - let this = Arc::downgrade(&self.0); - self.0 - .lock() - .platform - .borrow_on_main_thread() - .on_reopen(Box::new(move || { - if let Some(app) = this.upgrade() { - callback(&mut app.lock()); - } - })); + let this = Rc::downgrade(&self.0); + self.0.borrow_mut().platform.on_reopen(Box::new(move || { + if let Some(app) = this.upgrade() { + callback(&mut app.borrow_mut()); + } + })); self } pub fn metadata(&self) -> AppMetadata { - self.0.lock().app_metadata.clone() + self.0.borrow().app_metadata.clone() } - pub fn executor(&self) -> Executor { - self.0.lock().executor.clone() + pub fn background_executor(&self) -> BackgroundExecutor { + self.0.borrow().background_executor.clone() } pub fn text_system(&self) -> Arc { - self.0.lock().text_system.clone() + self.0.borrow().text_system.clone() } } @@ -122,15 +114,16 @@ type QuitHandler = Box BoxFuture<'static, ()> + Se 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, @@ -156,11 +149,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 +169,17 @@ impl AppContext { app_version: platform.app_version().ok(), }; - Arc::new_cyclic(|this| { - Mutex::new(AppContext { + Rc::new_cyclic(|this| { + RefCell::new(AppContext { this: this.clone(), text_system, - platform: MainThreadOnly::new(platform, executor.clone()), + platform, app_metadata, flushing_effects: false, pending_updates: 0, next_frame_callbacks: Default::default(), - executor, + background_executor: executor, + foreground_executor, svg_renderer: SvgRenderer::new(asset_source.clone()), asset_source, image_cache: ImageCache::new(http_client), @@ -225,7 +220,7 @@ impl AppContext { let futures = futures::future::join_all(futures); if self - .executor + .background_executor .block_with_timeout(Duration::from_millis(100), futures) .is_err() { @@ -244,7 +239,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); @@ -258,7 +252,7 @@ impl AppContext { } pub(crate) fn read_window( - &mut self, + &self, id: WindowId, read: impl FnOnce(&WindowContext) -> R, ) -> Result { @@ -295,6 +289,68 @@ impl AppContext { }) } + /// Opens a new window with the given option and the root view returned by the given function. + /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific + /// functionality. + pub fn open_window( + &mut self, + options: crate::WindowOptions, + build_root_view: impl FnOnce(&mut WindowContext) -> View + Send + 'static, + ) -> WindowHandle { + self.update(|cx| { + let id = cx.windows.insert(None); + let handle = WindowHandle::new(id); + let mut window = Window::new(handle.into(), options, cx); + let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); + window.root_view.replace(root_view.into()); + cx.windows.get_mut(id).unwrap().replace(window); + handle + }) + } + + pub(crate) fn platform(&self) -> &Rc { + &self.platform + } + + /// Instructs the platform to activate the application by bringing it to the foreground. + pub fn activate(&self, ignoring_other_apps: bool) { + self.platform().activate(ignoring_other_apps); + } + + /// Writes data to the platform clipboard. + pub fn write_to_clipboard(&self, item: ClipboardItem) { + self.platform().write_to_clipboard(item) + } + + /// Reads data from the platform clipboard. + pub fn read_from_clipboard(&self) -> Option { + 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(crate) fn push_effect(&mut self, effect: Effect) { match &effect { Effect::Notify { emitter } => { @@ -473,67 +529,24 @@ 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 - } - - /// 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))) + pub fn executor(&self) -> &BackgroundExecutor { + &self.background_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 @@ -597,7 +610,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 +621,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)); @@ -717,7 +730,7 @@ impl Context for AppContext { /// Build an entity that is owned by the application. The given function will be invoked with /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned /// which can be used to access the entity in a context. - fn build_model( + fn build_model( &mut self, build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Model { @@ -747,107 +760,6 @@ impl Context for AppContext { } } -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 { - self.update(|cx| { - let id = cx.windows.insert(None); - let handle = WindowHandle::new(id); - let mut window = Window::new(handle.into(), options, cx); - let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); - window.root_view.replace(root_view.into()); - cx.windows.get_mut(id).unwrap().replace(window); - handle - }) - } - - /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides - /// your closure with mutable access to the `MainThread` 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) - }) - } -} - /// These effects are processed at the end of each application update cycle. pub(crate) enum Effect { Notify { @@ -855,7 +767,7 @@ pub(crate) enum Effect { }, Emit { emitter: EntityId, - event: Box, + event: Box, }, FocusChanged { window_id: WindowId, @@ -905,15 +817,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..fb941b91b8c398d400ca312e4bdb02773149f84c 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,16 +1,16 @@ use crate::{ - AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task, - WindowContext, + AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, + ModelContext, Result, Task, WindowContext, }; use anyhow::anyhow; use derive_more::{Deref, DerefMut}; -use parking_lot::Mutex; -use std::{future::Future, sync::Weak}; +use std::{cell::RefCell, future::Future, rc::Weak}; #[derive(Clone)] pub struct AsyncAppContext { - pub(crate) app: Weak>, - pub(crate) executor: Executor, + pub(crate) app: Weak>, + pub(crate) background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, } impl Context for AsyncAppContext { @@ -22,14 +22,14 @@ impl Context for AsyncAppContext { build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Self::Result> 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( @@ -41,8 +41,8 @@ impl Context for AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); // Need this to compile - Ok(lock.update_model(handle, update)) + let mut app = app.borrow_mut(); + Ok(app.update_model(handle, update)) } } @@ -52,13 +52,17 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); // Need this to compile + let mut lock = app.borrow_mut(); lock.refresh(); Ok(()) } - pub fn executor(&self) -> &Executor { - &self.executor + pub fn background_executor(&self) -> &BackgroundExecutor { + &self.background_executor + } + + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { @@ -66,7 +70,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); + let mut lock = app.borrow_mut(); Ok(f(&mut *lock)) } @@ -79,7 +83,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); + let app_context = app.borrow(); app_context.read_window(handle.id, update) } @@ -92,44 +96,16 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); + let mut app_context = app.borrow_mut(); app_context.update_window(handle.id, update) } - pub fn spawn(&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 }) - } - - 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, - { - 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, + R: 'static, { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); - Ok(app_context.run_on_main(f)) + self.foreground_executor.spawn(f(self.clone())) } pub fn has_global(&self) -> Result { @@ -137,8 +113,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let lock = app.lock(); // Need this to compile - Ok(lock.has_global::()) + let app = app.borrow_mut(); + Ok(app.has_global::()) } pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result { @@ -146,8 +122,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let lock = app.lock(); // Need this to compile - Ok(read(lock.global(), &lock)) + let app = app.borrow_mut(); // Need this to compile + Ok(read(app.global(), &app)) } pub fn try_read_global( @@ -155,8 +131,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 +143,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); // Need this to compile - Ok(lock.update_global(update)) + let mut app = app.borrow_mut(); + Ok(app.update_global(update)) } } @@ -224,7 +200,7 @@ impl Context for AsyncWindowContext { build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Result> where - T: 'static + Send, + T: 'static, { self.app .update_window(self.window, |cx| cx.build_model(build_model)) @@ -239,14 +215,3 @@ impl Context for AsyncWindowContext { .update_window(self.window, |cx| cx.update_model(handle, update)) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_async_app_context_send_sync() { - fn assert_send_sync() {} - assert_send_sync::(); - } -} diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index ecd171d1f8768538675385bdd8cb049d54b326a9..4a4b178e1e0e1a804b184fec34718ee7cbecf9f2 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -59,7 +59,7 @@ impl EntityMap { /// Insert an entity into a slot obtained by calling `reserve`. pub fn insert(&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)); diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index 8a4576c052b6c4355576f2959028b568dae8692d..f0fc1f07f0301018da3f38535ef78cc051c31fd6 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -1,6 +1,6 @@ use crate::{ - AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread, - Model, Reference, Subscription, Task, WeakModel, + AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, Model, Reference, + Subscription, Task, WeakModel, }; use derive_more::{Deref, DerefMut}; use futures::FutureExt; @@ -191,36 +191,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 { @@ -234,13 +218,10 @@ 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, - { + ) -> Model { self.app.build_model(build_model) } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index cd59f9234f782f3886a5b3ffe958a4d30944283f..e3bf8eb7dac6383a46197e25c41a93ef762ef742 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,15 +1,16 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, MainThread, - Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, + AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, + ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, + WindowContext, }; use futures::SinkExt; -use parking_lot::Mutex; -use std::{future::Future, sync::Arc}; +use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc}; #[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 { @@ -21,10 +22,10 @@ impl Context for TestAppContext { build_model: impl FnOnce(&mut Self::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( @@ -32,39 +33,45 @@ impl Context for TestAppContext { handle: &Model, update: impl FnOnce(&mut T, &mut Self::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) } } impl TestAppContext { pub fn new(dispatcher: TestDispatcher) -> Self { - let executor = Executor::new(Arc::new(dispatcher)); - let platform = Arc::new(TestPlatform::new(executor.clone())); + let dispatcher = Arc::new(dispatcher); + let background_executor = BackgroundExecutor::new(dispatcher.clone()); + let foreground_executor = ForegroundExecutor::new(dispatcher); + let platform = Rc::new(TestPlatform::new( + background_executor.clone(), + foreground_executor.clone(), + )); let asset_source = Arc::new(()); let http_client = util::http::FakeHttpClient::with_404_response(); Self { app: AppContext::new(platform, asset_source, http_client), - executor, + background_executor, + foreground_executor, } } pub fn quit(&self) { - self.app.lock().quit(); + self.app.borrow_mut().quit(); } pub fn refresh(&mut self) -> Result<()> { - let mut lock = self.app.lock(); - lock.refresh(); + let mut app = self.app.borrow_mut(); + app.refresh(); Ok(()) } - pub fn executor(&self) -> &Executor { - &self.executor + pub fn executor(&self) -> &BackgroundExecutor { + &self.background_executor } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { - let mut cx = self.app.lock(); + let mut cx = self.app.borrow_mut(); cx.update(f) } @@ -73,7 +80,7 @@ impl TestAppContext { handle: AnyWindowHandle, read: impl FnOnce(&WindowContext) -> R, ) -> R { - let mut app_context = self.app.lock(); + let app_context = self.app.borrow(); app_context.read_window(handle.id, read).unwrap() } @@ -82,57 +89,33 @@ impl TestAppContext { handle: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> R, ) -> R { - let mut app = self.app.lock(); + let mut app = self.app.borrow_mut(); app.update_window(handle.id, update).unwrap() } - pub fn spawn(&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 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, - { - 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, + R: 'static, { - let mut app_context = self.app.lock(); - app_context.run_on_main(f) + self.foreground_executor.spawn(f(self.to_async())) } pub fn has_global(&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,14 +123,15 @@ 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(), } } diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index 39c8562b69703a959fcbd3ad75bc8a6601b0a839..0437b3d6de81f0f69402a5a4fc12d91164e3b0a0 100644 --- a/crates/gpui2/src/assets.rs +++ b/crates/gpui2/src/assets.rs @@ -8,7 +8,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -pub trait AssetSource: 'static + Send + Sync { +pub trait AssetSource: 'static + Sync { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index a715ed30ee739cee6f0834be5be497415b0ff8b1..8bd6bcc700e5e1d540003af6fcb9e1ba78fc224b 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))) } diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 70c8f4b3ecc10059bd91c0e0d8683e81273046fe..d0b65fa10e525e16a67f0a7381a6d79d9c270a68 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), @@ -61,7 +68,7 @@ impl Future for Task { } } -impl Executor { +impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } } @@ -79,63 +86,6 @@ 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) - } - - /// 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 { pin_mut!(future); let (parker, unparker) = parking::pair(); @@ -261,8 +211,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 +243,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/gpui2.rs b/crates/gpui2/src/gpui2.rs index 8625866a441159a3b89ce3dade64e3190c074678..755df91b930245f72dac543369015a936e63f314 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -68,24 +68,20 @@ use derive_more::{Deref, DerefMut}; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, - mem, ops::{Deref, DerefMut}, - sync::Arc, }; use taffy::TaffyLayoutEngine; -type AnyBox = Box; +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; + ) -> Self::Result>; fn update_model( &mut self, @@ -102,7 +98,7 @@ pub trait VisualContext: Context { build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, ) -> Self::Result> where - V: 'static + Send; + V: 'static; fn update_view( &mut self, @@ -127,100 +123,6 @@ 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 @@ -333,32 +235,3 @@ impl<'a, T> DerefMut for Reference<'a, T> { } } } - -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..7c2dbcce18e33dfce30fba3299ff3d9338f16684 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -5,9 +5,9 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun, - GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams, - RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, + AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, + ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, + RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -35,12 +35,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); diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 881dd69bc88eb486cf5820b4d30898ec58e62aec..8dd94f052eed3939d2b4479d1835cfc7a53f62db 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); Self(Mutex::new(MacPlatformState { - executor: Executor::new(Arc::new(MacDispatcher)), + background_executor: BackgroundExecutor::new(dispatcher.clone()), + foreground_executor: ForegroundExecutor::new(dispatcher.clone()), 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..bf62e2e0dc34bb235798795c19e40efa1e0148fc 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, Scene, + Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, }; use block::ConcreteBlock; use cocoa::{ @@ -315,7 +315,7 @@ struct InsertText { struct MacWindowState { handle: AnyWindowHandle, - executor: Executor, + executor: ForegroundExecutor, native_window: id, renderer: MetalRenderer, scene_to_render: Option, @@ -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(); + } } } @@ -807,7 +808,7 @@ impl PlatformWindow for MacWindow { let native_window = self.0.lock().native_window; let executor = self.0.lock().executor.clone(); executor - .spawn_on_main_local(async move { + .spawn(async move { let _: () = msg_send![ alert, beginSheetModalForWindow: native_window @@ -824,7 +825,7 @@ impl PlatformWindow for MacWindow { let window = self.0.lock().native_window; let executor = self.0.lock().executor.clone(); executor - .spawn_on_main_local(async move { + .spawn(async move { unsafe { let _: () = msg_send![window, makeKeyAndOrderFront: nil]; } @@ -872,25 +873,17 @@ impl PlatformWindow for MacWindow { fn zoom(&self) { let this = self.0.lock(); let window = this.native_window; - this.executor - .spawn_on_main_local(async move { - unsafe { - window.zoom_(nil); - } - }) - .detach(); + unsafe { + window.zoom_(nil); + } } fn toggle_full_screen(&self) { let this = self.0.lock(); let window = this.native_window; - this.executor - .spawn_on_main_local(async move { - unsafe { - window.toggleFullScreen_(nil); - } - }) - .detach(); + unsafe { + window.toggleFullScreen_(nil); + } } fn on_input(&self, callback: Box bool>) { @@ -1189,7 +1182,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { lock.synthetic_drag_counter += 1; let executor = lock.executor.clone(); executor - .spawn_on_main_local(synthetic_drag( + .spawn(synthetic_drag( weak_window_state, lock.synthetic_drag_counter, event.clone(), @@ -1317,7 +1310,7 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) let executor = lock.executor.clone(); drop(lock); executor - .spawn_on_main_local(async move { + .spawn(async move { let mut lock = window_state.as_ref().lock(); if let Some(mut callback) = lock.activate_callback.take() { drop(lock); diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 52a25d352c07c7378f22049bae168c5b43da7864..98a7897752493073f3a57c68c1a0c1ce99664b9a 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -215,58 +215,3 @@ impl PlatformDispatcher for TestDispatcher { Some(self) } } - -#[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); - } -} 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 3bf28792bb00b7c7ccfddb2ab82f1bde4cc60f8d..c42a00762293b19b6fefae6849bf910fba89aab1 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..06b20a308875781f51773a74d8439605b34dc18f 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -4,10 +4,13 @@ use crate::{ Size, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; -use std::{any::TypeId, marker::PhantomData}; +use std::{ + any::{Any, TypeId}, + marker::PhantomData, +}; pub trait Render: 'static + Sized { - type Element: Element + 'static + Send; + type Element: Element + 'static; fn render(&mut self, cx: &mut ViewContext) -> Self::Element; } @@ -163,7 +166,7 @@ impl Component for EraseViewState Element for EraseViewState { - type ElementState = AnyBox; + type ElementState = Box; fn id(&self) -> Option { Element::id(&self.view) @@ -343,7 +346,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 3997a3197f9cc1685b3f455665a8d54852bb8b8c..1ef80128286679eb507d9240d528d08338594641 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -3,12 +3,11 @@ use crate::{ Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, - MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, - Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, - TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView, - WindowOptions, SUBPIXEL_VARIANTS, + Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, + Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, + Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, + UnderlineStyle, View, VisualContext, WeakModel, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -52,7 +51,7 @@ pub enum DispatchPhase { Capture, } -type AnyListener = Box; +type AnyListener = Box; type AnyKeyListener = Box< dyn Fn( &dyn Any, @@ -60,10 +59,9 @@ type AnyKeyListener = Box< DispatchPhase, &mut WindowContext, ) -> Option> - + Send + 'static, >; -type AnyFocusListener = Box; +type AnyFocusListener = Box; slotmap::new_key_type! { pub struct FocusId; } @@ -159,7 +157,7 @@ impl Drop for FocusHandle { // Holds the state for a specific window. pub struct Window { handle: AnyWindowHandle, - platform_window: MainThreadOnly>, + platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, rem_size: Pixels, @@ -194,7 +192,7 @@ 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 display_id = platform_window.display().id(); @@ -209,12 +207,7 @@ impl Window { cx.window.scale_factor = scale_factor; cx.window.scene_builder = SceneBuilder::new(); cx.window.content_size = content_size; - cx.window.display_id = cx - .window - .platform_window - .borrow_on_main_thread() - .display() - .id(); + cx.window.display_id = cx.window.platform_window.display().id(); cx.window.dirty = true; }) .log_err(); @@ -230,8 +223,6 @@ impl Window { }) }); - let platform_window = MainThreadOnly::new(Arc::new(platform_window), cx.executor.clone()); - Window { handle, platform_window, @@ -378,26 +369,6 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.notify(); } - /// Schedule the given closure to be run on the main thread. It will be invoked with - /// a `MainThread`, which provides access to platform-specific functionality - /// of the window. - 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(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)) - } - } - /// Create an `AsyncWindowContext`, which has a static lifetime and can be held across /// await points in async code. pub fn to_async(&self) -> AsyncWindowContext { @@ -408,44 +379,39 @@ impl<'a, 'w> WindowContext<'a, 'w> { pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { let f = Box::new(f); let display_id = self.window.display_id; - self.run_on_main(move |cx| { - if let Some(callbacks) = cx.next_frame_callbacks.get_mut(&display_id) { - callbacks.push(f); - // If there was already a callback, it means that we already scheduled a frame. - if callbacks.len() > 1 { - return; - } - } else { - let async_cx = cx.to_async(); - cx.next_frame_callbacks.insert(display_id, vec![f]); - cx.platform().set_display_link_output_callback( - display_id, - Box::new(move |_current_time, _output_time| { - let _ = async_cx.update(|cx| { - let callbacks = cx - .next_frame_callbacks - .get_mut(&display_id) - .unwrap() - .drain(..) - .collect::>(); - for callback in callbacks { - callback(cx); - } - cx.run_on_main(move |cx| { - if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() { - cx.platform().stop_display_link(display_id); - } - }) - .detach(); - }); - }), - ); + if let Some(callbacks) = self.next_frame_callbacks.get_mut(&display_id) { + callbacks.push(f); + // If there was already a callback, it means that we already scheduled a frame. + if callbacks.len() > 1 { + return; } + } else { + let async_cx = self.to_async(); + self.next_frame_callbacks.insert(display_id, vec![f]); + self.platform().set_display_link_output_callback( + display_id, + Box::new(move |_current_time, _output_time| { + let _ = async_cx.update(|cx| { + let callbacks = cx + .next_frame_callbacks + .get_mut(&display_id) + .unwrap() + .drain(..) + .collect::>(); + for callback in callbacks { + callback(cx); + } - cx.platform().start_display_link(display_id); - }) - .detach(); + if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() { + cx.platform().stop_display_link(display_id); + } + }); + }), + ); + } + + self.platform().start_display_link(display_id); } /// Spawn the future returned by the given closure on the application thread pool. @@ -453,17 +419,16 @@ impl<'a, 'w> WindowContext<'a, 'w> { /// use within your future. pub fn spawn( &mut self, - f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static, + f: impl FnOnce(AnyWindowHandle, 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 } + f(window, cx) }) } @@ -569,7 +534,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 @@ -906,14 +871,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) { @@ -1246,7 +1205,7 @@ impl Context for WindowContext<'_, '_> { build_model: impl FnOnce(&mut Self::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())); @@ -1276,7 +1235,7 @@ impl VisualContext for WindowContext<'_, '_> { build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, ) -> Self::Result> where - V: 'static + Send, + V: 'static, { let slot = self.app.entities.reserve(); let view = View { @@ -1422,7 +1381,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 @@ -1460,7 +1419,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) @@ -1772,30 +1731,13 @@ 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| { @@ -1833,7 +1775,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { 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(); self.window_cx.on_mouse_event(move |event, phase, cx| { @@ -1862,13 +1804,10 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { 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<'_, T>) -> T, - ) -> Model - where - T: 'static + Send, - { + ) -> Model { self.window_cx.build_model(build_model) } @@ -1884,7 +1823,7 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { impl VisualContext for ViewContext<'_, '_, V> { type ViewContext<'a, 'w, V2> = ViewContext<'a, 'w, V2>; - fn build_view( + fn build_view( &mut self, build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, W>) -> W, ) -> Self::Result> { diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 717a80619be28c19cf46d216a89487e95171fdb7..8233caf62faaa997c8573e57c0a9152ed59ece44 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/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 27b7b36e738c8889f2cec43cede3c981b57dcb07..70f908b45eaa9a248ea89dcd1ce0a4647bd13791 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(), @@ -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 @@ -1048,7 +1048,7 @@ impl FakeLanguageServer { let result = handler(params, cx.clone()); let responded_tx = responded_tx.clone(); async move { - cx.executor().simulate_random_delay().await; + cx.background_executor().simulate_random_delay().await; let result = result.await; responded_tx.unbounded_send(()).ok(); result diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 41b6296367ae2b37b33e6b52b136ff6e94c52ee5..748e619e96e907c71e5454f6f347395f0e5a7cb9 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() { @@ -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 { @@ -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, @@ -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() @@ -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(); } @@ -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 { diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index 6a13bc35994e144f8e26f28d172eeea4ed588214..dd90df81b38f12ed6fbae70fe369c7c1c741bebd 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -22,7 +22,8 @@ use futures::{ 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::{ @@ -600,7 +601,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 +889,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) }), ); } @@ -3012,7 +3013,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 +3033,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 { 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..367eba2b4e198a04e630c4990d71e694bd5b099e 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, diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index 21050356053bd69dd084fa2422ae8bec28cc2132..c3903c1c2266b4af0b910bec6fcba2221cc02f46 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 { 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/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 7cee32037393e05e4b097826f71eaeb25f27bc65..94b5820d90d639aad5ae1a8e8660a96f46086d54 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -133,9 +133,9 @@ impl<'a> VimTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static + Send, - F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Send + Future>, + T::Params: 'static, + F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Future>, { self.cx.handle_request::(handler) } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 82eacd97106a375c9ab88db6f73b5b46a7595b20..20b14a249a83e30ae2e67a8ff4ba297aabecc393 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(); }) }; From dd1a2a9e441ea03c79a664c37b56de2cc615db18 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 11:59:49 -0700 Subject: [PATCH 03/23] wip --- crates/ai2/src/auth.rs | 9 +- .../ai2/src/providers/open_ai/completion.rs | 51 +++++------ crates/audio2/src/audio2.rs | 72 +++++---------- crates/client2/src/client2.rs | 90 +++++++++---------- crates/client2/src/telemetry.rs | 4 +- .../src/test/editor_lsp_test_context.rs | 6 +- crates/fuzzy2/src/strings.rs | 4 +- crates/gpui2/src/app.rs | 24 ++--- crates/gpui2/src/element.rs | 12 +-- crates/gpui2_macros/src/derive_component.rs | 4 + crates/gpui2_macros/src/test.rs | 8 +- crates/install_cli2/src/install_cli2.rs | 4 +- crates/language2/src/outline.rs | 4 +- crates/lsp2/src/lsp2.rs | 3 +- crates/terminal2/src/terminal2.rs | 18 ++-- crates/vim/src/test/vim_test_context.rs | 6 +- 16 files changed, 138 insertions(+), 181 deletions(-) diff --git a/crates/ai2/src/auth.rs b/crates/ai2/src/auth.rs index e4670bb449025d5ecc5f0cabe65ad6ff4727c10c..2f689f2cda539df66780c1718597b317e5a6f871 100644 --- a/crates/ai2/src/auth.rs +++ b/crates/ai2/src/auth.rs @@ -8,10 +8,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 1582659e71535137108f0acaaef2540d96865dd5..840841a9368bc80d26ef4334683bbe14fd4848a6 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/crates/ai2/src/providers/open_ai/completion.rs @@ -213,7 +213,6 @@ impl OpenAICompletionProvider { } } -#[async_trait] impl CredentialProvider for OpenAICompletionProvider { fn has_credentials(&self) -> bool { match *self.credential.read() { @@ -221,50 +220,44 @@ 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) { + + fn delete_credentials(&self, cx: &mut AppContext) { cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err()) .await; *self.credential.write() = ProviderCredential::NoCredentials; diff --git a/crates/audio2/src/audio2.rs b/crates/audio2/src/audio2.rs index 5a27b7934987c91210253aa9909a86256021c300..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, BackgroundExecutor}; +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: &BackgroundExecutor) -> 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/client2/src/client2.rs b/crates/client2/src/client2.rs index 435d5f18407cc5a32b447685b125958b55125679..50a6bf1632fca5b8523f93d42c39b3ad73a11f42 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, @@ -233,14 +234,12 @@ struct ClientState { message_handlers: HashMap< TypeId, Arc< - dyn Send - + Sync - + Fn( - AnyModel, - Box, - &Arc, - AsyncAppContext, - ) -> BoxFuture<'static, Result<()>>, + dyn Fn( + AnyModel, + Box, + &Arc, + AsyncAppContext, + ) -> LocalBoxFuture<'static, Result<()>>, >, >, } @@ -310,10 +309,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 +337,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(); @@ -529,7 +522,7 @@ impl Client { remote_id: u64, ) -> Result> where - T: 'static + Send, + T: 'static, { let id = (TypeId::of::(), remote_id); @@ -557,9 +550,9 @@ 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, + F: 'static + Future>, { let message_type_id = TypeId::of::(); @@ -573,7 +566,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 +592,9 @@ 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, + F: 'static + Future>, { self.add_message_handler(model, move |handle, envelope, this, cx| { Self::respond_to_request( @@ -615,9 +608,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, + F: 'static + Future>, { self.add_entity_message_handler::(move |subscriber, message, client, cx| { handler(subscriber.downcast::().unwrap(), message, client, cx) @@ -627,9 +620,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, + F: 'static + Future>, { let model_type_id = TypeId::of::(); let message_type_id = TypeId::of::(); @@ -655,7 +648,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 +659,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, + F: 'static + Future>, { self.add_model_message_handler(move |entity, envelope, client, cx| { Self::respond_to_request::( @@ -705,7 +698,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, @@ -1050,7 +1043,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. @@ -1101,7 +1094,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()?, @@ -1293,7 +1286,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!( @@ -1332,9 +1325,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/"; @@ -1430,7 +1420,7 @@ mod tests { // 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(), @@ -1438,7 +1428,7 @@ mod tests { }) }); client.override_establish_connection(|_, cx| { - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { future::pending::<()>().await; unreachable!() }) @@ -1472,7 +1462,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!() }) @@ -1504,7 +1494,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..7fa57f9fb63967a8a52d37842900e900a844553b 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, } diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 59b1cbce051ee5c18e8abaf875fb57bafcfe5966..3e2f38a0b665e964eb51583c13d4cd4a87422b3d 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -266,9 +266,9 @@ impl<'a> EditorLspTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static, - F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Future>, + T::Params: 'static + Send, + F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Send + Future>, { let url = self.buffer_lsp_url.clone(); self.lsp.handle_request::(move |params, cx| { 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/app.rs b/crates/gpui2/src/app.rs index b3747f3cbf12d36c3b3e1db59ab2ccec5eda1085..3ab2d8c1f8f85094c9e549df27a309f84224fdb0 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -107,11 +107,11 @@ impl App { } 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 BoxFuture<'static, ()> + 'static>; +type ReleaseListener = Box; pub struct AppContext { this: Weak>, @@ -133,7 +133,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, @@ -295,7 +295,7 @@ impl AppContext { pub fn open_window( &mut self, options: crate::WindowOptions, - build_root_view: impl FnOnce(&mut WindowContext) -> View + Send + 'static, + build_root_view: impl FnOnce(&mut WindowContext) -> View + 'static, ) -> WindowHandle { self.update(|cx| { let id = cx.windows.insert(None); @@ -520,7 +520,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); } @@ -551,7 +551,7 @@ impl AppContext { /// 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), }); @@ -639,7 +639,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::(), @@ -686,7 +686,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() @@ -778,7 +778,7 @@ pub(crate) enum Effect { global_type: TypeId, }, Defer { - callback: Box, + callback: Box, }, } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 8bd6bcc700e5e1d540003af6fcb9e1ba78fc224b..e41dc4ebd54e02e19073680bf17347398f0070d0 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -218,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; @@ -262,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) @@ -273,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_macros/src/derive_component.rs b/crates/gpui2_macros/src/derive_component.rs index d1919c8bc4f475b596bff5119ce132f99ea37f95..b5887e562080b44c39e94598fc8bd8bf58c473a5 100644 --- a/crates/gpui2_macros/src/derive_component.rs +++ b/crates/gpui2_macros/src/derive_component.rs @@ -36,6 +36,10 @@ pub fn derive_component(input: TokenStream) -> TokenStream { } }; + if name == "CollabPanel" { + println!("{}", expanded) + } + TokenStream::from(expanded) } diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index b5a2111e1977dff386df87fe8022d621cdebe39e..7fb499f9f104f0541cc8b500cea0d0d113a66791 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/crates/gpui2_macros/src/test.rs @@ -89,8 +89,8 @@ 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( + Some("BackgroundExecutor") => { + inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new( std::sync::Arc::new(dispatcher.clone()) ),)); continue; @@ -134,7 +134,7 @@ 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)); #cx_teardowns @@ -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/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/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 70f908b45eaa9a248ea89dcd1ce0a4647bd13791..120e749d19feb1b50268f767773e9d0e13de3185 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/crates/lsp2/src/lsp2.rs @@ -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.background_executor().simulate_random_delay().await; + executor.simulate_random_delay().await; let result = result.await; responded_tx.unbounded_send(()).ok(); result diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 5cf73576bc4b0cc9639c6d3889209a4d3a8f5b4f..b683fd5b51f5e62f9f49ddb2933c88989290ebc3 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) => { @@ -1189,7 +1191,7 @@ impl Terminal { &mut self, e: &MouseUpEvent, origin: Point, - cx: &mut MainThread>, + cx: &mut ModelContext, ) { let setting = TerminalSettings::get_global(cx); diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 94b5820d90d639aad5ae1a8e8660a96f46086d54..7cee32037393e05e4b097826f71eaeb25f27bc65 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -133,9 +133,9 @@ impl<'a> VimTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static, - F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Future>, + T::Params: 'static + Send, + F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Send + Future>, { self.cx.handle_request::(handler) } From 11b6d9e33ac5b3c4bbaff7f8b21ce7d782378d40 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 13:53:08 -0600 Subject: [PATCH 04/23] Split out a foreground and background executor --- crates/ai2/src/auth.rs | 1 - .../ai2/src/providers/open_ai/completion.rs | 4 +- crates/ai2/src/providers/open_ai/embedding.rs | 52 ++++++++----------- crates/ai2/src/test.rs | 14 +++-- crates/call2/src/call2.rs | 4 +- crates/call2/src/room.rs | 9 ++-- crates/client2/src/client2.rs | 32 ++++++++---- crates/client2/src/telemetry.rs | 2 +- crates/copilot2/src/copilot2.rs | 12 ++--- crates/db2/src/db2.rs | 2 +- crates/gpui2/src/app.rs | 15 ++++-- crates/gpui2/src/app/model_context.rs | 28 +++++----- crates/gpui2/src/app/test_context.rs | 8 +-- crates/gpui2/src/assets.rs | 2 +- crates/gpui2/src/executor.rs | 2 +- crates/journal2/src/journal2.rs | 2 +- crates/language2/src/buffer.rs | 16 +++--- crates/language2/src/buffer_tests.rs | 10 ++-- crates/lsp2/src/lsp2.rs | 10 ++-- crates/prettier2/src/prettier2.rs | 2 +- crates/project2/src/lsp_command.rs | 24 ++++----- crates/project2/src/project2.rs | 24 ++++----- crates/project2/src/worktree.rs | 25 ++++----- crates/settings2/src/settings_file.rs | 5 +- crates/terminal2/src/terminal2.rs | 4 +- crates/zed2/src/main.rs | 6 +-- 26 files changed, 165 insertions(+), 150 deletions(-) diff --git a/crates/ai2/src/auth.rs b/crates/ai2/src/auth.rs index 2f689f2cda539df66780c1718597b317e5a6f871..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)] diff --git a/crates/ai2/src/providers/open_ai/completion.rs b/crates/ai2/src/providers/open_ai/completion.rs index 840841a9368bc80d26ef4334683bbe14fd4848a6..bf9dc704a26dd96b42fb2cc72a0a79ab384c0f9b 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/crates/ai2/src/providers/open_ai/completion.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; -use async_trait::async_trait; use futures::{ future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt, Stream, StreamExt, @@ -258,8 +257,7 @@ impl CredentialProvider for OpenAICompletionProvider { } fn delete_credentials(&self, cx: &mut AppContext) { - cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err()) - .await; + 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 dde4af127379fb711a89e47eda782497154b9280..27a01328f373933c47af1321c52d90e720cb7334 100644 --- a/crates/ai2/src/providers/open_ai/embedding.rs +++ b/crates/ai2/src/providers/open_ai/embedding.rs @@ -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/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 556f9e778e8be6d0d0992618638a99ebf6f3710b..f44bdd35b5478c71d3590838083f4a3ac3523fb4 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -322,7 +322,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 { @@ -390,7 +390,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(()) }) @@ -1202,7 +1202,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, @@ -1569,7 +1569,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 50a6bf1632fca5b8523f93d42c39b3ad73a11f42..b933b62a6f60cb8cb224047f79f24e5e90b3ec7e 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -234,12 +234,14 @@ struct ClientState { message_handlers: HashMap< TypeId, Arc< - dyn Fn( - AnyModel, - Box, - &Arc, - AsyncAppContext, - ) -> LocalBoxFuture<'static, Result<()>>, + dyn Send + + Sync + + Fn( + AnyModel, + Box, + &Arc, + AsyncAppContext, + ) -> LocalBoxFuture<'static, Result<()>>, >, >, } @@ -551,7 +553,11 @@ impl Client { where M: EnvelopedMessage, E: 'static, - H: 'static + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + + Sync + + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + + Send + + Sync, F: 'static + Future>, { let message_type_id = TypeId::of::(); @@ -593,7 +599,11 @@ impl Client { where M: RequestMessage, E: 'static, - H: 'static + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + + Sync + + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + + Send + + Sync, F: 'static + Future>, { self.add_message_handler(model, move |handle, envelope, this, cx| { @@ -609,7 +619,7 @@ impl Client { where M: EntityMessage, E: 'static, - H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, F: 'static + Future>, { self.add_entity_message_handler::(move |subscriber, message, client, cx| { @@ -621,7 +631,7 @@ impl Client { where M: EntityMessage, E: 'static, - H: 'static + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, F: 'static + Future>, { let model_type_id = TypeId::of::(); @@ -660,7 +670,7 @@ impl Client { where M: EntityMessage + RequestMessage, E: 'static, - H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, F: 'static + Future>, { self.add_model_message_handler(move |entity, envelope, client, cx| { diff --git a/crates/client2/src/telemetry.rs b/crates/client2/src/telemetry.rs index 7fa57f9fb63967a8a52d37842900e900a844553b..0ef5f0d140987327d4db035afdb990fbe7aba763 100644 --- a/crates/client2/src/telemetry.rs +++ b/crates/client2/src/telemetry.rs @@ -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/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 3d50834e94d88a21d25666086cff04a323b50d6f..3b059775cdf397459c8deec64548032093c1dd09 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -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/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 3ab2d8c1f8f85094c9e549df27a309f84224fdb0..265ce59a023a724e3048c0afb09fafcefe4796ad 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -21,7 +21,7 @@ use crate::{ }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; -use futures::{future::BoxFuture, Future}; +use futures::{future::LocalBoxFuture, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use std::{ @@ -101,6 +101,10 @@ impl App { self.0.borrow().background_executor.clone() } + pub fn foreground_executor(&self) -> ForegroundExecutor { + self.0.borrow().foreground_executor.clone() + } + pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -110,7 +114,7 @@ type ActionBuilder = fn(json: Option) -> anyhow::Result; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; -type QuitHandler = Box BoxFuture<'static, ()> + 'static>; +type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; type ReleaseListener = Box; pub struct AppContext { @@ -535,10 +539,15 @@ impl AppContext { } /// Obtains a reference to the executor, which can be used to spawn futures. - pub fn executor(&self) -> &BackgroundExecutor { + pub fn background_executor(&self) -> &BackgroundExecutor { &self.background_executor } + /// 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) -> Task diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index f0fc1f07f0301018da3f38535ef78cc051c31fd6..ee8c8871e685bb2b77fd732893bb7822778d46da 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -43,10 +43,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { 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, + T: 'static, T2: 'static, E: Entity, { @@ -69,10 +69,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 +95,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn on_release( &mut self, - mut on_release: impl FnMut(&mut T, &mut AppContext) + Send + 'static, + mut on_release: impl FnMut(&mut T, &mut AppContext) + 'static, ) -> Subscription where T: 'static, @@ -112,10 +112,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn observe_release( &mut self, entity: &E, - mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + Send + 'static, + mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where - T: Any + Send, + T: Any, T2: 'static, E: Entity, { @@ -134,10 +134,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 +148,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 +164,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { future.await; } } - .boxed() + .boxed_local() }), ) } @@ -183,7 +183,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn update_global(&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); diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index e3bf8eb7dac6383a46197e25c41a93ef762ef742..624ec67eaced4605ea996ab7f09d48e6b91db081 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -135,18 +135,20 @@ impl TestAppContext { } } - pub fn subscribe( + pub fn subscribe( &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(); entity .update(self, |_, cx: &mut ModelContext| { cx.subscribe(entity, move |_, _, event, cx| { - cx.executor().block(tx.send(event.clone())).unwrap(); + cx.background_executor() + .block(tx.send(event.clone())) + .unwrap(); }) }) .detach(); diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index 0437b3d6de81f0f69402a5a4fc12d91164e3b0a0..39c8562b69703a959fcbd3ad75bc8a6601b0a839 100644 --- a/crates/gpui2/src/assets.rs +++ b/crates/gpui2/src/assets.rs @@ -8,7 +8,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -pub trait AssetSource: 'static + Sync { +pub trait AssetSource: 'static + Send + Sync { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index d0b65fa10e525e16a67f0a7381a6d79d9c270a68..5a04cf40f0d5eba1c151f35151df4446c9a69abb 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -53,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(); } } 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..d8e0149460731695ec7e084a7d5429590b5e3562 100644 --- a/crates/language2/src/buffer.rs +++ b/crates/language2/src/buffer.rs @@ -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..2012509878973440c412f7c294a858edf17e69d6 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 @@ -2025,7 +2025,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/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 120e749d19feb1b50268f767773e9d0e13de3185..ed67a5c9c211c49e5cea0d20515e174e1c0d0a93 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/crates/lsp2/src/lsp2.rs @@ -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, { 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 748e619e96e907c71e5454f6f347395f0e5a7cb9..1457bd41ccd1eb9950cb8f99749d2dab2df39089 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -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)) @@ -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; @@ -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)), @@ -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 @@ -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() diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index dd90df81b38f12ed6fbae70fe369c7c1c741bebd..2718b5d8f0bc85bc9b619f90c8e49e4eccc7cd24 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -365,10 +365,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( @@ -429,7 +429,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 { @@ -1008,7 +1008,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 { @@ -1058,7 +1058,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 { @@ -1079,7 +1079,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 { @@ -1119,7 +1119,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 }); @@ -1146,7 +1146,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, @@ -1174,7 +1174,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(()) })) @@ -1248,7 +1248,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; @@ -1306,7 +1306,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")) }) } @@ -2672,7 +2672,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( diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index c3903c1c2266b4af0b910bec6fcba2221cc02f46..002c9daf124f44b38be264f59718dd5099c3a9de 100644 --- a/crates/settings2/src/settings_file.rs +++ b/crates/settings2/src/settings_file.rs @@ -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/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index b683fd5b51f5e62f9f49ddb2933c88989290ebc3..adc5dd3511039de6d292c281d5dc54ca54d3e5f2 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -984,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() { @@ -1302,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/zed2/src/main.rs b/crates/zed2/src/main.rs index 20b14a249a83e30ae2e67a8ff4ba297aabecc393..1233bee3278392ef63e3efa876efd2849340bacb 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -117,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()); @@ -514,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?; @@ -644,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 { From 3f34a8e7ec6b7e7604b1fa82b4450c3472707dc4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 14:00:26 -0600 Subject: [PATCH 05/23] Checkpoint --- crates/gpui2/src/executor.rs | 2 +- crates/gpui2/src/platform.rs | 2 +- crates/gpui2/src/platform/mac/dispatcher.rs | 23 +------------------- crates/gpui2/src/platform/test/dispatcher.rs | 18 +++++++++------ 4 files changed, 14 insertions(+), 31 deletions(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 5a04cf40f0d5eba1c151f35151df4446c9a69abb..c25eeac8996e971f3fe622bf3203336d756f8c9c 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -102,7 +102,7 @@ impl BackgroundExecutor { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, Poll::Pending => { - if !self.dispatcher.poll() { + if !self.dispatcher.poll(true) { if awoken.swap(false, SeqCst) { continue; } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 7c2dbcce18e33dfce30fba3299ff3d9338f16684..6e710daf6c9104a068993e6c83a8851849debf6b 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -162,7 +162,7 @@ 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; #[cfg(any(test, feature = "test-support"))] fn as_test(&self) -> Option<&TestDispatcher> { diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index a4ae2cc0280636e238d89453fb2fb38e24563833..f19de6f62772ccc482da03058ec8d320e5a0ba84 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/crates/gpui2/src/platform/mac/dispatcher.rs @@ -68,7 +68,7 @@ impl PlatformDispatcher for MacDispatcher { } } - fn poll(&self) -> bool { + fn poll(&self, _background_only: bool) -> bool { false } } @@ -77,24 +77,3 @@ 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/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 98a7897752493073f3a57c68c1a0c1ce99664b9a..e537f863113e528a85310344fba46c4f47db6e11 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -96,7 +96,7 @@ impl TestDispatcher { } pub fn run_until_parked(&self) { - while self.poll() {} + while self.poll(false) {} } pub fn parking_allowed(&self) -> bool { @@ -160,7 +160,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 +171,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 { From cd10ba9e063f6dde891f5c97bbe2552b2413f445 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 1 Nov 2023 14:17:10 -0600 Subject: [PATCH 06/23] Use run_until_parked instead of blocked in tests --- crates/gpui2/src/executor.rs | 17 +++++++++++++++-- crates/gpui2/src/test.rs | 2 +- crates/gpui2_macros/src/test.rs | 2 +- crates/project2/src/project_tests.rs | 12 ------------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index c25eeac8996e971f3fe622bf3203336d756f8c9c..4b75bad504bbd3ed7fd512cb8bb3c54b807503c9 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -86,7 +86,20 @@ impl BackgroundExecutor { Task::Spawned(task) } + #[cfg(any(test, feature = "test-support"))] + pub fn block_test(&self, future: impl Future) -> R { + self.block_internal(false, future) + } + pub fn block(&self, future: impl Future) -> R { + self.block_internal(true, future) + } + + pub(crate) fn block_internal( + &self, + background_only: bool, + future: impl Future, + ) -> R { pin_mut!(future); let (parker, unparker) = parking::pair(); let awoken = Arc::new(AtomicBool::new(false)); @@ -102,7 +115,7 @@ impl BackgroundExecutor { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, Poll::Pending => { - if !self.dispatcher.poll(true) { + if !self.dispatcher.poll(background_only) { if awoken.swap(false, SeqCst) { continue; } @@ -184,7 +197,7 @@ impl BackgroundExecutor { #[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"))] diff --git a/crates/gpui2/src/test.rs b/crates/gpui2/src/test.rs index 3f2697f7e3f2d2c6c44165728503b7fa1accf6e0..61c70813cdfc0b89156d0700e0425864dbeae36f 100644 --- a/crates/gpui2/src/test.rs +++ b/crates/gpui2/src/test.rs @@ -28,7 +28,7 @@ pub fn run_test( } let result = panic::catch_unwind(|| { let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed)); - test_fn(dispatcher, seed); + test_fn(dispatcher.clone(), seed); }); match result { diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index 7fb499f9f104f0541cc8b500cea0d0d113a66791..e01f39b0bef632d429280268f7dca0eed1bcd334 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/crates/gpui2_macros/src/test.rs @@ -136,7 +136,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { &mut |dispatcher, _seed| { 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, diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index fd8c1951015a20d964917ac35b0cc7a30c34a1a6..80126d82e4c9e5c2f858ec6ace473ea63edce3bf 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -2942,7 +2942,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 +2953,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 +2962,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 +2978,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 +2995,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 +3006,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 +3027,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 +3046,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 +3073,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); }); From 90facc051a2e48b42a8a9e0fe973f87464c82588 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 1 Nov 2023 15:31:37 -0600 Subject: [PATCH 07/23] beautiful diff --- crates/gpui2/src/executor.rs | 16 +- crates/project2/src/project2.rs | 5 + crates/project2/src/project_tests.rs | 1057 +++++++++++++------------- crates/project2/src/worktree.rs | 4 + crates/rpc2/src/peer.rs | 3 +- 5 files changed, 559 insertions(+), 526 deletions(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 4b75bad504bbd3ed7fd512cb8bb3c54b807503c9..63f3b94c79a399984efa2a82cdd960e34e8d90be 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -88,7 +88,16 @@ impl BackgroundExecutor { #[cfg(any(test, feature = "test-support"))] pub fn block_test(&self, future: impl Future) -> R { - self.block_internal(false, future) + let (runnable, task) = unsafe { + async_task::spawn_unchecked(future, { + let dispatcher = self.dispatcher.clone(); + move |runnable| dispatcher.dispatch_on_main_thread(runnable) + }) + }; + + runnable.schedule(); + + self.block_internal(false, task) } pub fn block(&self, future: impl Future) -> R { @@ -100,17 +109,20 @@ impl BackgroundExecutor { background_only: bool, future: impl Future, ) -> R { + dbg!("block_internal"); pin_mut!(future); let (parker, unparker) = parking::pair(); let awoken = Arc::new(AtomicBool::new(false)); let awoken2 = awoken.clone(); let waker = waker_fn(move || { + dbg!("WAKING UP."); awoken2.store(true, SeqCst); unparker.unpark(); }); let mut cx = std::task::Context::from_waker(&waker); + dbg!("BOOOP"); loop { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, @@ -131,7 +143,9 @@ impl BackgroundExecutor { panic!("parked with nothing left to run\n{:?}", backtrace_message) } } + dbg!("PARKING!"); parker.park(); + dbg!("CONTINUING!"); } } } diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 1457bd41ccd1eb9950cb8f99749d2dab2df39089..a598aac79ec28cc961783a9c4fda7a735f37ef5c 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -877,14 +877,17 @@ impl Project { ) }); for path in root_paths { + dbg!(&path); let (tree, _) = project .update(cx, |project, cx| { project.find_or_create_local_worktree(path, true, cx) }) .await .unwrap(); + dbg!("aaa"); tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete()) .await; + dbg!("bbb"); } project } @@ -5990,8 +5993,10 @@ impl Project { ) -> Task, PathBuf)>> { let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) { + dbg!("shortcut"); Task::ready(Ok((tree, relative_path))) } else { + dbg!("long cut"); let worktree = self.create_local_worktree(abs_path, visible, cx); cx.background_executor() .spawn(async move { Ok((worktree.await?, PathBuf::new())) }) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index 80126d82e4c9e5c2f858ec6ace473ea63edce3bf..fba2548451a310d36017444d5b000e41c81edf52 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -4,55 +4,63 @@ 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_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": "", + } + } + })); + + dbg!("GOT HERE"); + + 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(); + + dbg!("GOT HERE 2"); + + let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; + + dbg!("GOT HERE 2.5"); + 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") + ); + }); + + dbg!("GOT HERE 3"); +} #[gpui2::test] async fn test_managing_project_specific_settings(cx: &mut gpui2::TestAppContext) { @@ -2058,121 +2066,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 +2300,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 +2644,213 @@ 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); diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index 2718b5d8f0bc85bc9b619f90c8e49e4eccc7cd24..c15977c5e0145d73170509ad5fa6c2a77bafa82a 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -297,11 +297,15 @@ 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(); + eprintln!("get root metadata"); + let metadata = fs .metadata(&abs_path) .await .context("failed to stat worktree path")?; + eprintln!("got root metadata"); + cx.build_model(move |cx: &mut ModelContext| { let root_name = abs_path .file_name() diff --git a/crates/rpc2/src/peer.rs b/crates/rpc2/src/peer.rs index 367eba2b4e198a04e630c4990d71e694bd5b099e..104ab1b4211271dbc982184a01461a29d8874a75 100644 --- a/crates/rpc2/src/peer.rs +++ b/crates/rpc2/src/peer.rs @@ -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 From 6ee93125d04d04a5b181f6416063a1ab83790c53 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 17:11:42 -0700 Subject: [PATCH 08/23] Fix hangs in new dispatcher Co-authored-by: Nathan Sobo --- crates/gpui2/src/app/test_context.rs | 4 ++ crates/gpui2/src/executor.rs | 31 +++++----------- crates/gpui2/src/platform.rs | 3 ++ crates/gpui2/src/platform/mac/dispatcher.rs | 23 +++++++++++- crates/gpui2/src/platform/mac/platform.rs | 4 +- crates/gpui2/src/platform/test/dispatcher.rs | 18 +++++++++ crates/gpui2/src/test.rs | 2 +- crates/gpui2_macros/src/test.rs | 2 +- crates/project2/src/project2.rs | 5 --- crates/project2/src/project_tests.rs | 39 ++++++++++++++++---- crates/project2/src/worktree.rs | 6 +-- 11 files changed, 93 insertions(+), 44 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 624ec67eaced4605ea996ab7f09d48e6b91db081..3a7705c8cf861804202cd86a9585dea72153a6bf 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -70,6 +70,10 @@ impl TestAppContext { &self.background_executor } + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor + } + pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { let mut cx = self.app.borrow_mut(); cx.update(f) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 63f3b94c79a399984efa2a82cdd960e34e8d90be..25e88068c30d93f58d10aec2f9c0a998cbb1b6fb 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -88,16 +88,7 @@ impl BackgroundExecutor { #[cfg(any(test, feature = "test-support"))] pub fn block_test(&self, future: impl Future) -> R { - let (runnable, task) = unsafe { - async_task::spawn_unchecked(future, { - let dispatcher = self.dispatcher.clone(); - move |runnable| dispatcher.dispatch_on_main_thread(runnable) - }) - }; - - runnable.schedule(); - - self.block_internal(false, task) + self.block_internal(false, future) } pub fn block(&self, future: impl Future) -> R { @@ -109,20 +100,19 @@ impl BackgroundExecutor { background_only: bool, future: impl Future, ) -> R { - dbg!("block_internal"); 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 || { - dbg!("WAKING UP."); - 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); - dbg!("BOOOP"); loop { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, @@ -143,9 +133,8 @@ impl BackgroundExecutor { panic!("parked with nothing left to run\n{:?}", backtrace_message) } } - dbg!("PARKING!"); - parker.park(); - dbg!("CONTINUING!"); + + self.dispatcher.park(); } } } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 6e710daf6c9104a068993e6c83a8851849debf6b..705c2f83bbe0e6ff083f4fdbc759e38f8797c87a 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -12,6 +12,7 @@ use crate::{ use anyhow::anyhow; use async_task::Runnable; use futures::channel::oneshot; +use parking::Unparker; use seahash::SeaHasher; use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -163,6 +164,8 @@ pub trait PlatformDispatcher: Send + Sync { fn dispatch_on_main_thread(&self, runnable: Runnable); fn dispatch_after(&self, duration: Duration, runnable: Runnable); 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> { diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index f19de6f62772ccc482da03058ec8d320e5a0ba84..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 { @@ -71,6 +84,14 @@ impl PlatformDispatcher for MacDispatcher { 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) { diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 8dd94f052eed3939d2b4479d1835cfc7a53f62db..fdc7fd6ae5d416ab284a08249e4176c5f3c10892 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -165,10 +165,10 @@ pub struct MacPlatformState { impl MacPlatform { pub fn new() -> Self { - let dispatcher = Arc::new(MacDispatcher); + let dispatcher = Arc::new(MacDispatcher::new()); Self(Mutex::new(MacPlatformState { background_executor: BackgroundExecutor::new(dispatcher.clone()), - foreground_executor: ForegroundExecutor::new(dispatcher.clone()), + foreground_executor: ForegroundExecutor::new(dispatcher), text_system: Arc::new(MacTextSystem::new()), display_linker: MacDisplayLinker::new(), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index e537f863113e528a85310344fba46c4f47db6e11..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, } } @@ -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) { @@ -215,6 +225,14 @@ impl PlatformDispatcher for TestDispatcher { true } + fn park(&self) { + self.parker.lock().park(); + } + + fn unparker(&self) -> Unparker { + self.unparker.clone() + } + fn as_test(&self) -> Option<&TestDispatcher> { Some(self) } diff --git a/crates/gpui2/src/test.rs b/crates/gpui2/src/test.rs index 61c70813cdfc0b89156d0700e0425864dbeae36f..3f2697f7e3f2d2c6c44165728503b7fa1accf6e0 100644 --- a/crates/gpui2/src/test.rs +++ b/crates/gpui2/src/test.rs @@ -28,7 +28,7 @@ pub fn run_test( } let result = panic::catch_unwind(|| { let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed)); - test_fn(dispatcher.clone(), seed); + test_fn(dispatcher, seed); }); match result { diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index e01f39b0bef632d429280268f7dca0eed1bcd334..f7e45a90f97ced0e1ba89d84ddf305c5f2025dfd 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/crates/gpui2_macros/src/test.rs @@ -91,7 +91,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { } Some("BackgroundExecutor") => { inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new( - std::sync::Arc::new(dispatcher.clone()) + std::sync::Arc::new(dispatcher.clone()), ),)); continue; } diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index a598aac79ec28cc961783a9c4fda7a735f37ef5c..1457bd41ccd1eb9950cb8f99749d2dab2df39089 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -877,17 +877,14 @@ impl Project { ) }); for path in root_paths { - dbg!(&path); let (tree, _) = project .update(cx, |project, cx| { project.find_or_create_local_worktree(path, true, cx) }) .await .unwrap(); - dbg!("aaa"); tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete()) .await; - dbg!("bbb"); } project } @@ -5993,10 +5990,8 @@ impl Project { ) -> Task, PathBuf)>> { let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) { - dbg!("shortcut"); Task::ready(Ok((tree, relative_path))) } else { - dbg!("long cut"); let worktree = self.create_local_worktree(abs_path, visible, cx); cx.background_executor() .spawn(async move { Ok((worktree.await?, PathBuf::new())) }) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index fba2548451a310d36017444d5b000e41c81edf52..ca6cdbccfc7b2bf58bd9fc5fc5cc295bb92c4706 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -15,6 +15,36 @@ use std::{os, task::Poll}; use unindent::Unindent as _; 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); @@ -35,8 +65,6 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) { } })); - dbg!("GOT HERE"); - 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( @@ -45,11 +73,8 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) { ) .unwrap(); - dbg!("GOT HERE 2"); - let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; - dbg!("GOT HERE 2.5"); project.update(cx, |project, cx| { let tree = project.worktrees().next().unwrap().read(cx); assert_eq!(tree.file_count(), 5); @@ -58,8 +83,6 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) { tree.inode_for_path("finnochio/grape") ); }); - - dbg!("GOT HERE 3"); } #[gpui2::test] @@ -2706,7 +2729,7 @@ async fn test_save_as(cx: &mut gpui2::TestAppContext) { #[gpui2::test(retries = 5)] async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { init_test(cx); - // cx.executor().allow_parking(); + cx.executor().allow_parking(); let dir = temp_tree(json!({ "a": { diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index c15977c5e0145d73170509ad5fa6c2a77bafa82a..f824466e490bba99bf1e683fce234d138b72d3b5 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -297,15 +297,12 @@ 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(); - eprintln!("get root metadata"); let metadata = fs .metadata(&abs_path) .await .context("failed to stat worktree path")?; - eprintln!("got root metadata"); - cx.build_model(move |cx: &mut ModelContext| { let root_name = abs_path .file_name() @@ -4067,13 +4064,12 @@ impl WorktreeModelHandle for Model { fs.create_file(&root_path.join(filename), 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()) .await .unwrap(); - cx.executor().run_until_parked(); assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none())); cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete()) From 401ddc6f49d01cf3d64c5e438da4eb8c104ef0f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 17:45:38 -0700 Subject: [PATCH 09/23] WIP - flush_fs_events --- crates/gpui2/src/app/model_context.rs | 6 ++-- crates/gpui2/src/app/test_context.rs | 41 +++++++++++++++++++++++++-- crates/project2/src/worktree.rs | 16 ++++++----- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index ee8c8871e685bb2b77fd732893bb7822778d46da..f6982cdc1ff2e5ca79da561da9623ea0d6beaea8 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -40,15 +40,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>) + 'static, ) -> Subscription where T: 'static, - T2: 'static, - E: Entity, + W: 'static, + E: Entity, { let this = self.weak_model(); let entity_id = entity.entity_id(); diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 3a7705c8cf861804202cd86a9585dea72153a6bf..d4a63c4e37c937a8451fd6361bd00f31278bfb63 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -3,8 +3,9 @@ use crate::{ ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, }; -use futures::SinkExt; -use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc}; +use anyhow::anyhow; +use futures::{SinkExt, StreamExt}; +use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] pub struct TestAppContext { @@ -158,4 +159,40 @@ impl TestAppContext { .detach(); rx } + + pub async fn condition( + &mut self, + model: &Model, + mut predicate: impl FnMut(&mut T, &mut ModelContext) -> bool, + ) { + let (mut tx, mut rx) = futures::channel::mpsc::unbounded::<()>(); + let timer = self.executor().timer(Duration::from_secs(3)); + + let subscriptions = model.update(self, move |_, cx| { + ( + cx.observe(model, move |_, _, _| { + // let _ = tx.send(()); + }), + cx.subscribe(model, move |_, _, _, _| { + let _ = tx.send(()); + }), + ) + }); + + use futures::FutureExt as _; + use smol::future::FutureExt as _; + + async { + while rx.next().await.is_some() { + if model.update(self, &mut predicate) { + return Ok(()); + } + } + drop(subscriptions); + unreachable!() + } + .race(timer.map(|_| Err(anyhow!("condition timed out")))) + .await + .unwrap(); + } } diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index f824466e490bba99bf1e683fce234d138b72d3b5..f146bf79489196d0144a16dbfd37441817b513fa 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -17,7 +17,7 @@ use futures::{ }, select_biased, task::Poll, - FutureExt, Stream, StreamExt, + FutureExt as _, Stream, StreamExt, }; use fuzzy2::CharBag; use git::{DOT_GIT, GITIGNORE}; @@ -4053,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(); @@ -4061,16 +4062,17 @@ 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.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some()) + .await; - assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some())); - - fs.remove_file(&root_path.join(filename), Default::default()) + fs.remove_file(&root_path.join(file_name), Default::default()) .await .unwrap(); - 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; From 53066df52200225d732c298cdf130256cade64a9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 20:14:40 -0600 Subject: [PATCH 10/23] Get project2 tests green --- crates/gpui2/src/app/test_context.rs | 44 ++++++++++++++++------------ crates/project2/src/project_tests.rs | 5 ++-- crates/project2/src/worktree.rs | 1 + crates/ui2/src/components/panes.rs | 2 +- crates/ui2/src/components/tab.rs | 2 +- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index d4a63c4e37c937a8451fd6361bd00f31278bfb63..d7bf2b7087a486a8d92698b6ddca3c84ffccf737 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -3,8 +3,8 @@ use crate::{ ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, }; -use anyhow::anyhow; -use futures::{SinkExt, StreamExt}; +use anyhow::{anyhow, bail}; +use futures::{SinkExt, Stream, StreamExt}; use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] @@ -140,7 +140,25 @@ impl TestAppContext { } } - 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 @@ -160,36 +178,24 @@ impl TestAppContext { rx } - pub async fn condition( + pub async fn condition( &mut self, model: &Model, mut predicate: impl FnMut(&mut T, &mut ModelContext) -> bool, ) { - let (mut tx, mut rx) = futures::channel::mpsc::unbounded::<()>(); let timer = self.executor().timer(Duration::from_secs(3)); - - let subscriptions = model.update(self, move |_, cx| { - ( - cx.observe(model, move |_, _, _| { - // let _ = tx.send(()); - }), - cx.subscribe(model, move |_, _, _, _| { - let _ = tx.send(()); - }), - ) - }); + let mut notifications = self.notifications(model); use futures::FutureExt as _; use smol::future::FutureExt as _; async { - while rx.next().await.is_some() { + while notifications.next().await.is_some() { if model.update(self, &mut predicate) { return Ok(()); } } - drop(subscriptions); - unreachable!() + bail!("model dropped") } .race(timer.map(|_| Err(anyhow!("condition timed out")))) .await diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index ca6cdbccfc7b2bf58bd9fc5fc5cc295bb92c4706..5a2f82c3758d215a018d154ae4fe53b56eefd504 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -947,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!( @@ -1078,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(); @@ -2788,6 +2788,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { }); let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); + cx.executor().run_until_parked(); cx.update(|cx| { diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index f146bf79489196d0144a16dbfd37441817b513fa..060fefe6b3aa70a86df98335d733157482ac4d1b 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -4065,6 +4065,7 @@ impl WorktreeModelHandle for Model { fs.create_file(&root_path.join(file_name), Default::default()) .await .unwrap(); + cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some()) .await; diff --git a/crates/ui2/src/components/panes.rs b/crates/ui2/src/components/panes.rs index 854786ebaa6cbd9476b9a6fcb3ce40574d91f16f..5318e3f3bb00504f50ac805ae2a55c95d57e7d6f 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 d784ec017485aa58701215cc8033b70ffc0c47a2..c89ed2d7eb8fd34fce159f5726b1ea2d9159eee3 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -130,7 +130,7 @@ impl Tab { .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone())) .drag_over::(|d| d.bg(black())) .on_drop(|_view, state: View, cx| { - dbg!(state.read(cx)); + eprintln!("{:?}", state.read(cx)); }) .px_2() .py_0p5() From 57dfc5068760f72416d8194e7de3f61628d3d4d5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 21:04:17 -0600 Subject: [PATCH 11/23] Get language2 tests passing by not blocking on a foreground task --- crates/gpui2/src/app/test_context.rs | 10 ++++------ crates/gpui2_macros/src/derive_component.rs | 4 ---- crates/language2/src/buffer.rs | 2 +- crates/language2/src/buffer_tests.rs | 1 + 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index d7bf2b7087a486a8d92698b6ddca3c84ffccf737..6affabdd238eeac363b1c52f6ef7303ad9da88aa 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -4,7 +4,7 @@ use crate::{ WindowContext, }; use anyhow::{anyhow, bail}; -use futures::{SinkExt, Stream, StreamExt}; +use futures::{Stream, StreamExt}; use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] @@ -165,13 +165,11 @@ impl TestAppContext { where 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.background_executor() - .block(tx.send(event.clone())) - .unwrap(); + cx.subscribe(entity, move |_model, _handle, event, _cx| { + let _ = tx.unbounded_send(event.clone()); }) }) .detach(); diff --git a/crates/gpui2_macros/src/derive_component.rs b/crates/gpui2_macros/src/derive_component.rs index b5887e562080b44c39e94598fc8bd8bf58c473a5..d1919c8bc4f475b596bff5119ce132f99ea37f95 100644 --- a/crates/gpui2_macros/src/derive_component.rs +++ b/crates/gpui2_macros/src/derive_component.rs @@ -36,10 +36,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { } }; - if name == "CollabPanel" { - println!("{}", expanded) - } - TokenStream::from(expanded) } diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index d8e0149460731695ec7e084a7d5429590b5e3562..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 diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index 2012509878973440c412f7c294a858edf17e69d6..16306fe2ce66accec1fa3aeef36dd7ecf971efc3 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/crates/language2/src/buffer_tests.rs @@ -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); From 2079cd641e9ebf9c73e2aaefd4bec246712e33eb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 21:16:41 -0600 Subject: [PATCH 12/23] Fix post-merge compile errors --- crates/call2/src/room.rs | 21 ++++++++++---------- crates/live_kit_client2/examples/test_app.rs | 4 ++-- crates/live_kit_client2/src/test.rs | 6 +++--- crates/multi_buffer2/src/multi_buffer2.rs | 4 ++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index ea4adf038531f513eda09fd2b20d2fceada6b05d..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(); @@ -1301,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, @@ -1344,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 @@ -1387,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, @@ -1454,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?; } 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/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 From 64ee1bb7a5c16d804302eb49a142c20d828105fc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 21:19:06 -0600 Subject: [PATCH 13/23] Fix prod compile error --- crates/project2/src/project2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 1457bd41ccd1eb9950cb8f99749d2dab2df39089..05434b93b4c38cfc082cbd8fde01a9739b7c7676 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -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; } From 64ad8943ba699c9b8da54621eca68b87fcbb4312 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:41:49 +0100 Subject: [PATCH 14/23] Remove more Send bounds and simplify view rendering --- crates/gpui2/src/action.rs | 4 +- crates/gpui2/src/app.rs | 209 ++++++----- crates/gpui2/src/app/async_context.rs | 118 ++++-- crates/gpui2/src/app/entity_map.rs | 10 +- crates/gpui2/src/app/model_context.rs | 30 +- crates/gpui2/src/app/test_context.rs | 32 +- crates/gpui2/src/assets.rs | 2 +- crates/gpui2/src/element.rs | 6 +- crates/gpui2/src/elements/div.rs | 1 - crates/gpui2/src/elements/text.rs | 3 - crates/gpui2/src/focusable.rs | 12 +- crates/gpui2/src/geometry.rs | 12 + crates/gpui2/src/gpui2.rs | 62 ++-- crates/gpui2/src/platform.rs | 66 +++- crates/gpui2/src/platform/mac/display.rs | 44 ++- crates/gpui2/src/view.rs | 188 ++-------- crates/gpui2/src/window.rs | 443 +++++++++++++++-------- 17 files changed, 714 insertions(+), 528 deletions(-) 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 265ce59a023a724e3048c0afb09fafcefe4796ad..48a9324b0598f18ff69c48e72c61bfd1679bce1d 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -13,11 +13,12 @@ use smallvec::SmallVec; pub use test_context::*; use crate::{ - current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource, - BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, FocusEvent, FocusHandle, - FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, Pixels, Platform, Point, Render, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, - TextSystem, View, Window, WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, + AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, + Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, + Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, + TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, + WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -114,8 +115,8 @@ type ActionBuilder = fn(json: Option) -> anyhow::Result; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; -type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; -type ReleaseListener = Box; +type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; +type ReleaseListener = Box; pub struct AppContext { this: Weak>, @@ -214,10 +215,9 @@ 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(); @@ -255,37 +255,31 @@ impl AppContext { result } - pub(crate) fn read_window( - &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( &mut self, - id: WindowId, - update: impl FnOnce(&mut WindowContext) -> R, + handle: AnyWindowHandle, + update: impl FnOnce(AnyView, &mut WindowContext) -> R, ) -> Result { self.update(|cx| { let mut window = cx .windows - .get_mut(id) + .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? .take() .unwrap(); - let result = update(&mut WindowContext::mutable(cx, &mut window)); + let root_view = window.root_view.clone().unwrap(); + let result = update(root_view, &mut WindowContext::new(cx, &mut window)); cx.windows - .get_mut(id) + .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? .replace(window); @@ -305,7 +299,7 @@ impl AppContext { 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)); + 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 @@ -386,8 +380,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(); @@ -407,18 +404,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(); } } @@ -435,7 +432,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); } } @@ -446,27 +443,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(); } } @@ -483,30 +480,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) { @@ -680,6 +682,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); } @@ -733,7 +753,6 @@ 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 @@ -741,11 +760,11 @@ impl Context for AppContext { /// which can be used to access the entity in a context. 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) }) } @@ -755,18 +774,38 @@ 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 }) } + + fn update_window(&mut self, handle: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + self.update(|cx| { + let mut window = cx + .windows + .get_mut(handle.id) + .ok_or_else(|| anyhow!("window not found"))? + .take() + .unwrap(); + + 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) + }) + } } /// These effects are processed at the end of each application update cycle. @@ -779,7 +818,7 @@ pub(crate) enum Effect { event: Box, }, FocusChanged { - window_id: WindowId, + window_handle: AnyWindowHandle, focused: Option, }, Refresh, diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index fb941b91b8c398d400ca312e4bdb02773149f84c..01af7ae194328beaf5a49657e92028ff05464702 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,8 +1,8 @@ use crate::{ - AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, - ModelContext, Result, Task, WindowContext, + AnyView, AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, + ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext, }; -use anyhow::anyhow; +use anyhow::{anyhow, Context as _}; use derive_more::{Deref, DerefMut}; use std::{cell::RefCell, future::Future, rc::Weak}; @@ -14,12 +14,11 @@ pub struct AsyncAppContext { } 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, @@ -35,7 +34,7 @@ impl Context for AsyncAppContext { 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 @@ -44,6 +43,15 @@ impl Context for AsyncAppContext { 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) + } } impl AsyncAppContext { @@ -74,30 +82,17 @@ impl AsyncAppContext { Ok(f(&mut *lock)) } - pub fn read_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(&WindowContext) -> R, - ) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; - let app_context = app.borrow(); - app_context.read_window(handle.id, update) - } - pub fn update_window( &self, handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, + update: impl FnOnce(AnyView, &mut WindowContext) -> R, ) -> Result { let app = self .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; let mut app_context = app.borrow_mut(); - app_context.update_window(handle.id, update) + app_context.update_window(handle, update) } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task @@ -161,22 +156,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( @@ -186,32 +181,79 @@ 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 + 'static) -> Task + where + Fut: Future + 'static, + R: 'static, + { + let this = self.clone(); + self.foreground_executor.spawn(async move { f(this).await }) } } 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, { - 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) + } +} + +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)) + } + + 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 4a4b178e1e0e1a804b184fec34718ee7cbecf9f2..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}; @@ -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 f6982cdc1ff2e5ca79da561da9623ea0d6beaea8..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, 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 { @@ -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) + 'static, + on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, ) -> Subscription where T: 'static, @@ -112,7 +110,7 @@ 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>) + 'static, + on_release: impl FnOnce(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where T: Any, @@ -215,12 +213,11 @@ where } impl<'a, T> Context for ModelContext<'a, T> { - type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U, + build_model: impl FnOnce(&mut ModelContext<'_, U>) -> U, ) -> Model { self.app.build_model(build_model) } @@ -228,10 +225,17 @@ impl<'a, T> Context for ModelContext<'a, T> { 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 6affabdd238eeac363b1c52f6ef7303ad9da88aa..27275d3a044c456bb2e4c00bf0b6995ff0330426 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,7 +1,7 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, - ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, - WindowContext, + AnyView, AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, + EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, + TestPlatform, WindowContext, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -15,12 +15,11 @@ pub struct TestAppContext { } 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, @@ -32,11 +31,19 @@ impl Context for TestAppContext { 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 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 { @@ -80,22 +87,13 @@ impl TestAppContext { cx.update(f) } - pub fn read_window( - &self, - handle: AnyWindowHandle, - read: impl FnOnce(&WindowContext) -> R, - ) -> R { - let app_context = self.app.borrow(); - app_context.read_window(handle.id, read).unwrap() - } - pub fn update_window( &self, handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, + update: impl FnOnce(AnyView, &mut WindowContext) -> R, ) -> R { let mut app = self.app.borrow_mut(); - app.update_window(handle.id, update).unwrap() + app.update_window(handle, update).unwrap() } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index 39c8562b69703a959fcbd3ad75bc8a6601b0a839..baf75b8aabc8acea0d93d0cc1346344dab580459 100644 --- a/crates/gpui2/src/assets.rs +++ b/crates/gpui2/src/assets.rs @@ -8,7 +8,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -pub trait AssetSource: 'static + Send + Sync { +pub trait AssetSource: 'static { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index e41dc4ebd54e02e19073680bf17347398f0070d0..4890b79a9acc811ea72816a4a01dbd45634e1667 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -219,7 +219,7 @@ impl Element for Option where V: 'static, E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { type ElementState = AnyElement; @@ -263,7 +263,7 @@ impl Component for Option where V: 'static, E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -274,7 +274,7 @@ impl Component for F where V: 'static, E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, + 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/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 755df91b930245f72dac543369015a936e63f314..49cc3cebc23bf2d61f7615a02e48a77b7d11ba2a 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -68,34 +68,36 @@ use derive_more::{Deref, DerefMut}; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, - ops::{Deref, DerefMut}, }; use taffy::TaffyLayoutEngine; type AnyBox = Box; pub trait Context { - type ModelContext<'a, T>; type 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>; - 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; @@ -103,12 +105,19 @@ pub trait VisualContext: Context { 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; @@ -128,7 +137,7 @@ pub trait BorrowAppContext { where F: FnOnce(&mut Self) -> R; - fn set_global(&mut self, global: T); + fn set_global(&mut self, global: T); } impl BorrowAppContext for C @@ -145,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) } } @@ -208,30 +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, - } - } -} diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 705c2f83bbe0e6ff083f4fdbc759e38f8797c87a..2dd4d8b666a42496ddb49be442ea535d0209629f 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -9,12 +9,14 @@ use crate::{ 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; @@ -27,6 +29,7 @@ use std::{ str::FromStr, sync::Arc, }; +use uuid::Uuid; pub use keystroke::*; #[cfg(target_os = "macos")] @@ -106,6 +109,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; } @@ -372,6 +378,64 @@ 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)?; + // WindowBounds::Fixed(RectF::new( + // Vector2F::new(x, y), + // Vector2F::new(width, height), + // )) + todo!() + } + _ => bail!("Window State did not have a valid string"), + }; + + Ok((bounds, next_index + 4)) + } +} + #[derive(Copy, Clone, Debug)] pub enum WindowAppearance { Light, 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/view.rs b/crates/gpui2/src/view.rs index 06b20a308875781f51773a74d8439605b34dc18f..3cc4fdd4e34e33bf3f7614ceac54c578273f8924 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,12 +1,12 @@ 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::{Any, TypeId}, - marker::PhantomData, + hash::{Hash, Hasher}, }; pub trait Render: 'static + Sized { @@ -52,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, @@ -73,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)) } } @@ -134,13 +102,17 @@ impl WeakView { 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() } } @@ -152,115 +124,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 = Box; - - fn id(&self) -> Option { - Element::id(&self.view) - } - - 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) +impl PartialEq for WeakView { + fn eq(&self, other: &Self) -> bool { + self.model == other.model } } -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 { @@ -292,7 +168,7 @@ impl AnyView { } } - pub(crate) fn entity_type(&self) -> TypeId { + pub fn entity_type(&self) -> TypeId { self.model.entity_type } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e7cfcf45eed3fb0dcb7438c02947cbdc2d78140e..5f2de2e4282768bd6cb915407f8739226b59c01c 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -5,11 +5,11 @@ use crate::{ Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, - Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, + Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, View, VisualContext, WeakModel, WeakView, WindowOptions, SUBPIXEL_VARIANTS, + UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; use parking_lot::RwLock; @@ -20,6 +20,7 @@ use std::{ borrow::{Borrow, BorrowMut, Cow}, fmt::Debug, future::Future, + hash::{Hash, Hasher}, marker::PhantomData, mem, sync::{ @@ -156,7 +157,7 @@ impl Drop for FocusHandle { // Holds the state for a specific window. pub struct Window { - handle: AnyWindowHandle, + pub(crate) handle: AnyWindowHandle, platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, @@ -201,23 +202,25 @@ impl Window { 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.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) }) @@ -296,24 +299,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. @@ -345,10 +338,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(); @@ -360,15 +352,53 @@ 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(); } + /// 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, + entity: &E, + mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + 'static, + ) -> Subscription + where + Emitter: EventEmitter, + E: Entity, + { + 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 /// await points in async code. pub fn to_async(&self) -> AsyncWindowContext { @@ -376,7 +406,7 @@ 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; @@ -387,12 +417,12 @@ impl<'a, 'w> WindowContext<'a, 'w> { return; } } else { - let async_cx = self.to_async(); + 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 _ = async_cx.update(|_, cx| { let callbacks = cx .next_frame_callbacks .get_mut(&display_id) @@ -1114,12 +1144,12 @@ 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()), ) } @@ -1202,43 +1232,52 @@ 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, { 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, @@ -1247,7 +1286,7 @@ impl VisualContext for WindowContext<'_, '_> { 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 @@ -1257,17 +1296,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 { @@ -1275,19 +1332,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 } @@ -1455,13 +1512,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 } @@ -1469,52 +1526,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() } @@ -1525,40 +1578,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) }), ) } @@ -1566,44 +1629,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)); }), ) } @@ -1611,21 +1674,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)) }); }), @@ -1641,9 +1704,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)) @@ -1659,12 +1722,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) @@ -1745,16 +1808,13 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { 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(move |_, 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); @@ -1764,17 +1824,16 @@ 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) }), ) } @@ -1783,7 +1842,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { &mut self, 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); @@ -1792,10 +1851,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; @@ -1806,13 +1865,12 @@ 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( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Model { self.window_cx.build_model(build_model) } @@ -1820,18 +1878,23 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { 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) + } +} +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) } @@ -1839,21 +1902,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 } @@ -1868,42 +1941,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, @@ -1913,6 +2018,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"))] From 5e50430299006fee53dcecc5b7ba9133c0b2e397 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:47:21 +0100 Subject: [PATCH 15/23] Fix compile errors --- crates/feature_flags2/src/feature_flags2.rs | 2 +- crates/gpui2_macros/src/derive_component.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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() } } From d5f0e91faa28bd2ea9b6680c7b5a919276ae0fde Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:56:45 +0100 Subject: [PATCH 16/23] Remove stray todo --- crates/gpui2/src/app/async_context.rs | 5 ++--- crates/gpui2/src/platform.rs | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 01af7ae194328beaf5a49657e92028ff05464702..f08c7ed0a96410ba3a867b1851ca001fd50ada2f 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -184,13 +184,12 @@ impl AsyncWindowContext { self.window.update(self, |_, cx| cx.update_global(update)) } - pub fn spawn(&self, f: impl FnOnce(AsyncWindowContext) -> Fut + 'static) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task where Fut: Future + 'static, R: 'static, { - let this = self.clone(); - self.foreground_executor.spawn(async move { f(this).await }) + self.foreground_executor.spawn(f(self.clone())) } } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 2dd4d8b666a42496ddb49be442ea535d0209629f..9a6768342f78198ebc8a5b721277fbdc1ec39851 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -5,9 +5,10 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, - ForegroundExecutor, 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, bail}; use async_task::Runnable; @@ -422,12 +423,15 @@ impl Column for WindowBounds { "Fullscreen" => WindowBounds::Fullscreen, "Maximized" => WindowBounds::Maximized, "Fixed" => { - // let ((x, y, width, height), _) = Column::column(statement, next_index)?; - // WindowBounds::Fixed(RectF::new( - // Vector2F::new(x, y), - // Vector2F::new(width, height), - // )) - todo!() + 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"), }; From 9c7b45f38bdc140e8778543375ded0220478a5df Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:58:53 +0100 Subject: [PATCH 17/23] Add back Send and Sync to AssetSource --- crates/gpui2/src/assets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index baf75b8aabc8acea0d93d0cc1346344dab580459..39c8562b69703a959fcbd3ad75bc8a6601b0a839 100644 --- a/crates/gpui2/src/assets.rs +++ b/crates/gpui2/src/assets.rs @@ -8,7 +8,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -pub trait AssetSource: 'static { +pub trait AssetSource: 'static + Send + Sync { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } From 32db64a049502d37c671f07e5c5b89c36d895a1b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 10:53:28 +0100 Subject: [PATCH 18/23] Introduce more GPUI2 APIs needed for transitioning the workspace --- crates/gpui2/src/app.rs | 77 +++++++++++-------------- crates/gpui2/src/app/async_context.rs | 16 +++-- crates/gpui2/src/app/test_context.rs | 9 --- crates/gpui2/src/platform.rs | 15 +---- crates/gpui2/src/platform/mac/window.rs | 15 ++--- crates/gpui2/src/view.rs | 4 ++ crates/gpui2/src/window.rs | 44 ++++++++------ 7 files changed, 80 insertions(+), 100 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 48a9324b0598f18ff69c48e72c61bfd1679bce1d..5a6e36080291e03600674349332295e2e62a4c7f 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -16,13 +16,13 @@ use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, - Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, - TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, - WindowId, + 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::LocalBoxFuture, Future}; +use futures::{channel::oneshot, future::LocalBoxFuture, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use std::{ @@ -31,7 +31,7 @@ use std::{ marker::PhantomData, mem, ops::{Deref, DerefMut}, - path::PathBuf, + path::{Path, PathBuf}, rc::{Rc, Weak}, sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, @@ -262,38 +262,13 @@ impl AppContext { .collect() } - pub(crate) fn update_window( - &mut self, - handle: AnyWindowHandle, - update: impl FnOnce(AnyView, &mut WindowContext) -> R, - ) -> Result { - self.update(|cx| { - let mut window = cx - .windows - .get_mut(handle.id) - .ok_or_else(|| anyhow!("window not found"))? - .take() - .unwrap(); - - 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) - }) - } - /// 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 + 'static, + build_root_view: impl FnOnce(&mut WindowContext) -> View, ) -> WindowHandle { self.update(|cx| { let id = cx.windows.insert(None); @@ -306,47 +281,63 @@ impl AppContext { }) } - pub(crate) fn platform(&self) -> &Rc { - &self.platform - } - /// Instructs the platform to activate the application by bringing it to the foreground. pub fn activate(&self, ignoring_other_apps: bool) { - self.platform().activate(ignoring_other_apps); + self.platform.activate(ignoring_other_apps); + } + + /// Returns the list of currently active displays. + pub fn displays(&self) -> Vec> { + self.platform.displays() } /// Writes data to the platform clipboard. pub fn write_to_clipboard(&self, item: ClipboardItem) { - self.platform().write_to_clipboard(item) + self.platform.write_to_clipboard(item) } /// Reads data from the platform clipboard. pub fn read_from_clipboard(&self) -> Option { - self.platform().read_from_clipboard() + 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) + 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) + self.platform.read_credentials(url) } /// Deletes credentials from the platform keychain. pub fn delete_credentials(&self, url: &str) -> Result<()> { - self.platform().delete_credentials(url) + 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); + self.platform.open_url(url); } pub fn path_for_auxiliary_executable(&self, name: &str) -> Result { - self.platform().path_for_auxiliary_executable(name) + 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) { diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index f08c7ed0a96410ba3a867b1851ca001fd50ada2f..4bbab43446265a8a2c7c456b9d80702e2c027803 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,6 +1,7 @@ use crate::{ AnyView, AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext, + WindowHandle, }; use anyhow::{anyhow, Context as _}; use derive_more::{Deref, DerefMut}; @@ -82,17 +83,20 @@ impl AsyncAppContext { Ok(f(&mut *lock)) } - pub fn update_window( + pub fn open_window( &self, - handle: AnyWindowHandle, - update: impl FnOnce(AnyView, &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.borrow_mut(); - app_context.update_window(handle, update) + let mut lock = app.borrow_mut(); + Ok(lock.open_window(options, build_root_view)) } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 27275d3a044c456bb2e4c00bf0b6995ff0330426..aaf42dd4a27ba2b401516293f1f027aaeedbde65 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -87,15 +87,6 @@ impl TestAppContext { cx.update(f) } - pub fn update_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(AnyView, &mut WindowContext) -> R, - ) -> R { - let mut app = self.app.borrow_mut(); - app.update_window(handle, update).unwrap() - } - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 9a6768342f78198ebc8a5b721277fbdc1ec39851..cdce67d8c128b9bb04044ec6bf7a2252ae1c3bff 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -138,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); @@ -454,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/window.rs b/crates/gpui2/src/platform/mac/window.rs index bf62e2e0dc34bb235798795c19e40efa1e0148fc..77675e3c27ba9a00bd66788ac18daff68f77b04d 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -4,7 +4,7 @@ use crate::{ FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, - Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, + Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, PromptLevel, }; use block::ConcreteBlock; use cocoa::{ @@ -742,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 @@ -777,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)]; diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 3cc4fdd4e34e33bf3f7614ceac54c578273f8924..d81df5b21c9e8a64af4932462423009a0e291fbd 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -98,6 +98,10 @@ pub struct WeakView { } impl WeakView { + pub fn entity_id(&self) -> EntityId { + self.model.entity_id + } + pub fn upgrade(&self) -> Option> { Entity::upgrade_from(self) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5f2de2e4282768bd6cb915407f8739226b59c01c..0202b7521eebc36735077e3199f0322e25a80e91 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -4,14 +4,15 @@ use crate::{ Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, - Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, - Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, + 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::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; +use futures::channel::oneshot; use parking_lot::RwLock; use slotmap::SlotMap; use smallvec::SmallVec; @@ -195,7 +196,7 @@ impl Window { options: WindowOptions, 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(); @@ -419,7 +420,7 @@ impl<'a> WindowContext<'a> { } else { let mut async_cx = self.to_async(); self.next_frame_callbacks.insert(display_id, vec![f]); - self.platform().set_display_link_output_callback( + self.platform.set_display_link_output_callback( display_id, Box::new(move |_current_time, _output_time| { let _ = async_cx.update(|_, cx| { @@ -434,32 +435,26 @@ impl<'a> WindowContext<'a> { } if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() { - cx.platform().stop_display_link(display_id); + cx.platform.stop_display_link(display_id); } }); }), ); } - self.platform().start_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, - ) -> Task + pub fn spawn(&mut self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task where R: 'static, Fut: Future + 'static, { - let window = self.window.handle; - self.app.spawn(move |app| { - let cx = AsyncWindowContext::new(app, window); - f(window, cx) - }) + 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 @@ -1153,6 +1148,19 @@ impl<'a> WindowContext<'a> { ) } + 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, @@ -1809,7 +1817,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { Fut: Future + 'static, { let view = self.view().downgrade(); - self.window_cx.spawn(move |_, cx| f(view, cx)) + self.window_cx.spawn(|cx| f(view, cx)) } pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R From 76c675a63b01937d45eb5f367c8b8f35fc0bdd34 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 10:57:29 +0100 Subject: [PATCH 19/23] :lipstick: --- crates/gpui2/src/platform/mac/window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index 77675e3c27ba9a00bd66788ac18daff68f77b04d..52dcf3160372b9df00006d8ff03986f037826746 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -3,8 +3,8 @@ use crate::{ display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, - Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, PromptLevel, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + PromptLevel, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, }; use block::ConcreteBlock; use cocoa::{ From f724b6d032c8ff0fff237ae2b8ab0bbee63b64e9 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 2 Nov 2023 09:05:16 -0400 Subject: [PATCH 20/23] Cleanly truncate Discord release notes --- .github/workflows/release_actions.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 550eda882beb27099ea221a7eaf87569f6e7391d..a72abf300d4f3204bf3fab230c9551abdedc88d7 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -16,7 +16,7 @@ jobs: fi echo "::set-output name=URL::$URL" - name: Get content - uses: 2428392/gh-truncate-string-action@v1.2.0 + uses: 2428392/gh-truncate-string-action@v1.3.0 id: get-content with: stringToTruncate: | @@ -24,6 +24,7 @@ jobs: ${{ github.event.release.body }} maxLength: 2000 + truncationSymbol: "..." - name: Discord Webhook Action uses: tsickert/discord-webhook@v5.3.0 with: From 971563fd489667624038c117ac8623b4e6d420fc Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 2 Nov 2023 09:05:29 -0400 Subject: [PATCH 21/23] Format YAML --- .github/workflows/release_actions.yml | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index a72abf300d4f3204bf3fab230c9551abdedc88d7..c1d2457ed42f8d44feb0e1bb8587fa81e2e6fcc9 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -6,27 +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.3.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 - 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 }} + ${{ 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 }} From b5fe0d72ee4328d9d7a6d9cf9ec0e371a7d0a3f1 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 2 Nov 2023 09:34:18 -0400 Subject: [PATCH 22/23] authenticate with completion provider on new inline assists --- crates/assistant/src/assistant_panel.rs | 15 +++++++++------ crates/assistant/src/codegen.rs | 14 ++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 03eb3c238f98d55e7bd40793dafa931244bcc073..022c22879050b275d525c2170b0c4d436e301c9f 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -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) diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index f62c91fcb7e8a2f5891e2e93ae9cba7b13b437eb..da7beda2dc5a41e646b296acb15460b100075c80 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -6,7 +6,7 @@ use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{Entity, ModelContext, ModelHandle, Task}; use language::{Rope, TransactionId}; use multi_buffer; -use std::{cmp, future, ops::Range, sync::Arc}; +use std::{cmp, future, ops::Range}; pub enum Event { Finished, @@ -20,7 +20,7 @@ pub enum CodegenKind { } pub struct Codegen { - provider: Arc, + provider: Box, buffer: ModelHandle, snapshot: MultiBufferSnapshot, kind: CodegenKind, @@ -40,7 +40,7 @@ impl Codegen { pub fn new( buffer: ModelHandle, kind: CodegenKind, - provider: Arc, + provider: Box, cx: &mut ModelContext, ) -> Self { let snapshot = buffer.read(cx).snapshot(cx); @@ -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}; @@ -412,7 +414,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) }); - let provider = Arc::new(FakeCompletionProvider::new()); + let provider = Box::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -478,7 +480,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 6)) }); - let provider = Arc::new(FakeCompletionProvider::new()); + let provider = Box::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -544,7 +546,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 2)) }); - let provider = Arc::new(FakeCompletionProvider::new()); + let provider = Box::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), From d5b6300fd7edb6441516be5004887a5090e5a599 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 2 Nov 2023 10:08:47 -0400 Subject: [PATCH 23/23] moved from Boxes to Arcs for shared access of completion providers across the assistant panel and inline assistant --- crates/ai/src/test.rs | 11 ++++++++++- crates/assistant/src/assistant_panel.rs | 20 ++++++++++---------- crates/assistant/src/codegen.rs | 14 ++++++++------ 3 files changed, 28 insertions(+), 17 deletions(-) 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/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 022c22879050b275d525c2170b0c4d436e301c9f..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(), )); @@ -1442,7 +1442,7 @@ struct Conversation { pending_save: Task>, path: Option, _subscriptions: Vec, - completion_provider: Box, + completion_provider: Arc, } impl Entity for Conversation { @@ -1453,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| { @@ -1547,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); @@ -2204,7 +2204,7 @@ struct ConversationEditor { impl ConversationEditor { fn new( - completion_provider: Box, + completion_provider: Arc, language_registry: Arc, fs: Arc, workspace: WeakViewHandle, @@ -3409,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(); @@ -3538,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(); @@ -3636,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(); @@ -3719,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 da7beda2dc5a41e646b296acb15460b100075c80..25c9deef7f6ef972dd67911046f430e63bb48d40 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -6,7 +6,7 @@ use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{Entity, ModelContext, ModelHandle, Task}; use language::{Rope, TransactionId}; use multi_buffer; -use std::{cmp, future, ops::Range}; +use std::{cmp, future, ops::Range, sync::Arc}; pub enum Event { Finished, @@ -20,7 +20,7 @@ pub enum CodegenKind { } pub struct Codegen { - provider: Box, + provider: Arc, buffer: ModelHandle, snapshot: MultiBufferSnapshot, kind: CodegenKind, @@ -40,7 +40,7 @@ impl Codegen { pub fn new( buffer: ModelHandle, kind: CodegenKind, - provider: Box, + provider: Arc, cx: &mut ModelContext, ) -> Self { let snapshot = buffer.read(cx).snapshot(cx); @@ -414,7 +414,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) }); - let provider = Box::new(FakeCompletionProvider::new()); + let provider = Arc::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -439,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(); @@ -480,7 +481,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 6)) }); - let provider = Box::new(FakeCompletionProvider::new()); + let provider = Arc::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -546,7 +547,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 2)) }); - let provider = Box::new(FakeCompletionProvider::new()); + let provider = Arc::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -571,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();