diff --git a/Cargo.lock b/Cargo.lock index e51e1aaaf2..4d46794322 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -568,9 +568,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" dependencies = [ "quote", "syn", @@ -1005,6 +1005,7 @@ dependencies = [ "pathfinder_color", "pathfinder_geometry", "png", + "postage", "rand 0.8.3", "replace_with", "resvg", @@ -2449,6 +2450,7 @@ dependencies = [ "anyhow", "arrayvec", "crossbeam-channel 0.5.0", + "ctor", "dirs", "easy-parallel", "fsevent", diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index 0f11bd1de2..9b7e9eef3d 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -15,6 +15,7 @@ ordered-float = "2.1.1" parking_lot = "0.11.1" pathfinder_color = "0.5" pathfinder_geometry = "0.5" +postage = {version = "0.4.1", features = ["futures-traits"]} rand = "0.8.3" replace_with = "0.1.7" resvg = "0.14" diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 910489a4d9..25fe3cbc15 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -13,11 +13,12 @@ use keymap::MatchResult; use parking_lot::Mutex; use pathfinder_geometry::{rect::RectF, vector::vec2f}; use platform::Event; +use postage::{sink::Sink as _, stream::Stream as _}; use smol::prelude::*; use std::{ any::{type_name, Any, TypeId}, cell::RefCell, - collections::{HashMap, HashSet, VecDeque}, + collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, fmt::{self, Debug}, hash::{Hash, Hasher}, marker::PhantomData, @@ -384,6 +385,7 @@ pub struct MutableAppContext { next_task_id: usize, subscriptions: HashMap>, observations: HashMap>, + async_observations: HashMap>, window_invalidations: HashMap, presenters_and_platform_windows: HashMap>, Box)>, @@ -424,6 +426,7 @@ impl MutableAppContext { next_task_id: 0, subscriptions: HashMap::new(), observations: HashMap::new(), + async_observations: HashMap::new(), window_invalidations: HashMap::new(), presenters_and_platform_windows: HashMap::new(), debug_elements_callbacks: HashMap::new(), @@ -877,11 +880,13 @@ impl MutableAppContext { self.ctx.models.remove(&model_id); self.subscriptions.remove(&model_id); self.observations.remove(&model_id); + self.async_observations.remove(&model_id); } for (window_id, view_id) in dropped_views { self.subscriptions.remove(&view_id); self.observations.remove(&view_id); + self.async_observations.remove(&view_id); if let Some(window) = self.ctx.windows.get_mut(&window_id) { self.window_invalidations .entry(window_id) @@ -1047,6 +1052,12 @@ impl MutableAppContext { } } } + + if let Entry::Occupied(mut entry) = self.async_observations.entry(observed_id) { + if entry.get_mut().blocking_send(()).is_err() { + entry.remove_entry(); + } + } } fn notify_view_observers(&mut self, window_id: usize, view_id: usize) { @@ -2012,6 +2023,36 @@ impl ModelHandle { { app.update_model(self, update) } + + pub fn condition( + &self, + ctx: &TestAppContext, + mut predicate: impl 'static + FnMut(&T, &AppContext) -> bool, + ) -> impl 'static + Future { + let mut ctx = ctx.0.borrow_mut(); + let tx = ctx + .async_observations + .entry(self.id()) + .or_insert_with(|| postage::broadcast::channel(128).0); + let mut rx = tx.subscribe(); + let ctx = ctx.weak_self.as_ref().unwrap().upgrade().unwrap(); + let handle = self.clone(); + + async move { + loop { + { + let ctx = ctx.borrow(); + let ctx = ctx.as_ref(); + if predicate(handle.read(ctx), ctx) { + break; + } + } + if rx.recv().await.is_none() { + break; + } + } + } + } } impl Clone for ModelHandle { @@ -2145,6 +2186,36 @@ impl ViewHandle { app.focused_view_id(self.window_id) .map_or(false, |focused_id| focused_id == self.view_id) } + + pub fn condition( + &self, + ctx: &TestAppContext, + mut predicate: impl 'static + FnMut(&T, &AppContext) -> bool, + ) -> impl 'static + Future { + let mut ctx = ctx.0.borrow_mut(); + let tx = ctx + .async_observations + .entry(self.id()) + .or_insert_with(|| postage::broadcast::channel(128).0); + let mut rx = tx.subscribe(); + let ctx = ctx.weak_self.as_ref().unwrap().upgrade().unwrap(); + let handle = self.clone(); + + async move { + loop { + { + let ctx = ctx.borrow(); + let ctx = ctx.as_ref(); + if predicate(handle.read(ctx), ctx) { + break; + } + } + if rx.recv().await.is_none() { + break; + } + } + } + } } impl Clone for ViewHandle { diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 981f09c2b8..f27f79fa01 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -16,10 +16,11 @@ path = "src/main.rs" anyhow = "1.0.38" arrayvec = "0.5.2" crossbeam-channel = "0.5.0" +ctor = "0.1.20" dirs = "3.0" easy-parallel = "3.1.0" -futures-core = "0.3" fsevent = {path = "../fsevent"} +futures-core = "0.3" gpui = {path = "../gpui"} ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"} lazy_static = "1.4.0" @@ -31,8 +32,8 @@ postage = {version = "0.4.1", features = ["futures-traits"]} rand = "0.8.3" rust-embed = "5.9.0" seahash = "4.1" +serde = {version = "1", features = ["derive"]} simplelog = "0.9" -serde = { version = "1", features = ["derive"] } smallvec = "1.6.1" smol = "1.2.5" diff --git a/zed/src/test.rs b/zed/src/test.rs index da965b257b..3850616671 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -1,4 +1,8 @@ +use crate::time::ReplicaId; +use ctor::ctor; +use log::LevelFilter; use rand::Rng; +use simplelog::SimpleLogger; use std::{ collections::BTreeMap, path::{Path, PathBuf}, @@ -6,7 +10,10 @@ use std::{ }; use tempdir::TempDir; -use crate::time::ReplicaId; +#[ctor] +fn init_logger() { + SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); +} #[derive(Clone)] struct Envelope { diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 23ab0960df..bf741edbab 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -402,7 +402,21 @@ mod tests { // Open the first entry workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); - app.finish_pending_tasks().await; + + workspace_view + .condition(&app, |workspace_view, ctx| { + workspace_view.active_pane().read(ctx).items().len() == 1 + }) + .await; + + // Open the second entry + workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx)); + + workspace_view + .condition(&app, |workspace_view, ctx| { + workspace_view.active_pane().read(ctx).items().len() == 2 + }) + .await; app.read(|ctx| { assert_eq!( @@ -410,36 +424,34 @@ mod tests { .read(ctx) .active_pane() .read(ctx) - .items() - .len(), - 1 - ) - }); - - // Open the second entry - workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx)); - app.finish_pending_tasks().await; - - app.read(|ctx| { - let active_pane = workspace_view.read(ctx).active_pane().read(ctx); - assert_eq!(active_pane.items().len(), 2); - assert_eq!( - active_pane.active_item().unwrap().entry_id(ctx), + .active_item() + .unwrap() + .entry_id(ctx), Some(entries[1]) ); }); // Open the first entry again workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); - app.finish_pending_tasks().await; + + { + let entries = entries.clone(); + workspace_view + .condition(&app, move |workspace_view, ctx| { + workspace_view + .active_pane() + .read(ctx) + .active_item() + .unwrap() + .entry_id(ctx) + == Some(entries[0]) + }) + .await; + } app.read(|ctx| { let active_pane = workspace_view.read(ctx).active_pane().read(ctx); assert_eq!(active_pane.items().len(), 2); - assert_eq!( - active_pane.active_item().unwrap().entry_id(ctx), - Some(entries[0]) - ); }); // Open the third entry twice concurrently @@ -447,19 +459,12 @@ mod tests { w.open_entry(entries[2], ctx); w.open_entry(entries[2], ctx); }); - app.finish_pending_tasks().await; - app.read(|ctx| { - assert_eq!( - workspace_view - .read(ctx) - .active_pane() - .read(ctx) - .items() - .len(), - 3 - ); - }); + workspace_view + .condition(&app, |workspace_view, ctx| { + workspace_view.active_pane().read(ctx).items().len() == 3 + }) + .await; }); }