Merge branch 'zed2' of github.com:zed-industries/zed into zed2

Marshall Bowers created

Change summary

crates/gpui2/src/app.rs                      |   4 
crates/gpui2/src/app/test_context.rs         | 137 ++++++++++++++++++++++
crates/gpui2/src/platform/test/dispatcher.rs |  88 ++++++++++---
3 files changed, 206 insertions(+), 23 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -1,12 +1,16 @@
 mod async_context;
 mod entity_map;
 mod model_context;
+#[cfg(any(test, feature = "test-support"))]
+mod test_context;
 
 pub use async_context::*;
 pub use entity_map::*;
 pub use model_context::*;
 use refineable::Refineable;
 use smallvec::SmallVec;
+#[cfg(any(test, feature = "test-support"))]
+pub use test_context::*;
 
 use crate::{
     current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource,

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

@@ -0,0 +1,137 @@
+use crate::{
+    AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, Handle, MainThread,
+    ModelContext, Result, Task, WindowContext,
+};
+use parking_lot::Mutex;
+use std::{any::Any, future::Future, sync::Arc};
+
+#[derive(Clone)]
+pub struct TestAppContext {
+    pub(crate) app: Arc<Mutex<AppContext>>,
+    pub(crate) executor: Executor,
+}
+
+impl Context for TestAppContext {
+    type EntityContext<'a, 'w, T> = ModelContext<'a, T>;
+    type Result<T> = T;
+
+    fn entity<T: 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Self::Result<Handle<T>>
+    where
+        T: Any + Send + Sync,
+    {
+        let mut lock = self.app.lock();
+        lock.entity(build_entity)
+    }
+
+    fn update_entity<T: 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> Self::Result<R> {
+        let mut lock = self.app.lock();
+        lock.update_entity(handle, update)
+    }
+}
+
+impl TestAppContext {
+    pub fn refresh(&mut self) -> Result<()> {
+        let mut lock = self.app.lock();
+        lock.refresh();
+        Ok(())
+    }
+
+    pub fn executor(&self) -> &Executor {
+        &self.executor
+    }
+
+    pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
+        let mut lock = self.app.lock();
+        Ok(f(&mut *lock))
+    }
+
+    pub fn read_window<R>(
+        &self,
+        handle: AnyWindowHandle,
+        update: impl FnOnce(&WindowContext) -> R,
+    ) -> Result<R> {
+        let mut app_context = self.app.lock();
+        app_context.read_window(handle.id, update)
+    }
+
+    pub fn update_window<R>(
+        &self,
+        handle: AnyWindowHandle,
+        update: impl FnOnce(&mut WindowContext) -> R,
+    ) -> Result<R> {
+        let mut app = self.app.lock();
+        app.update_window(handle.id, update)
+    }
+
+    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
+    where
+        Fut: Future<Output = R> + Send + 'static,
+        R: Send + 'static,
+    {
+        let cx = self.to_async();
+        self.executor.spawn(async move { f(cx).await })
+    }
+
+    pub fn spawn_on_main<Fut, R>(
+        &self,
+        f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
+    ) -> Task<R>
+    where
+        Fut: Future<Output = R> + 'static,
+        R: Send + 'static,
+    {
+        let cx = self.to_async();
+        self.executor.spawn_on_main(|| f(cx))
+    }
+
+    pub fn run_on_main<R>(
+        &self,
+        f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
+    ) -> Result<Task<R>>
+    where
+        R: Send + 'static,
+    {
+        let mut app_context = self.app.lock();
+        Ok(app_context.run_on_main(f))
+    }
+
+    pub fn has_global<G: 'static>(&self) -> Result<bool> {
+        let lock = self.app.lock();
+        Ok(lock.has_global::<G>())
+    }
+
+    pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result<R> {
+        let lock = self.app.lock();
+        Ok(read(lock.global(), &lock))
+    }
+
+    pub fn try_read_global<G: 'static, R>(
+        &self,
+        read: impl FnOnce(&G, &AppContext) -> R,
+    ) -> Option<R> {
+        let lock = self.app.lock();
+        Some(read(lock.try_global()?, &lock))
+    }
+
+    pub fn update_global<G: 'static, R>(
+        &mut self,
+        update: impl FnOnce(&mut G, &mut AppContext) -> R,
+    ) -> Result<R> {
+        let mut lock = self.app.lock();
+        Ok(lock.update_global(update))
+    }
+
+    fn to_async(&self) -> AsyncAppContext {
+        AsyncAppContext {
+            app: Arc::downgrade(&self.app),
+            executor: self.executor.clone(),
+        }
+    }
+}

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

@@ -1,57 +1,87 @@
 use crate::PlatformDispatcher;
 use async_task::Runnable;
-use collections::{BTreeMap, VecDeque};
+use collections::{BTreeMap, HashMap, VecDeque};
 use parking_lot::Mutex;
 use rand::prelude::*;
-use std::time::{Duration, Instant};
-
-pub struct TestDispatcher(Mutex<TestDispatcherState>);
+use std::{
+    sync::Arc,
+    time::{Duration, Instant},
+};
+use util::post_inc;
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+struct TestDispatcherId(usize);
+
+pub struct TestDispatcher {
+    id: TestDispatcherId,
+    state: Arc<Mutex<TestDispatcherState>>,
+}
 
 struct TestDispatcherState {
     random: StdRng,
-    foreground: VecDeque<Runnable>,
+    foreground: HashMap<TestDispatcherId, VecDeque<Runnable>>,
     background: Vec<Runnable>,
     delayed: BTreeMap<Instant, Runnable>,
     time: Instant,
     is_main_thread: bool,
+    next_id: TestDispatcherId,
 }
 
 impl TestDispatcher {
     pub fn new(random: StdRng) -> Self {
         let state = TestDispatcherState {
             random,
-            foreground: VecDeque::new(),
+            foreground: HashMap::default(),
             background: Vec::new(),
             delayed: BTreeMap::new(),
             time: Instant::now(),
             is_main_thread: true,
+            next_id: TestDispatcherId(1),
         };
 
-        TestDispatcher(Mutex::new(state))
+        TestDispatcher {
+            id: TestDispatcherId(0),
+            state: Arc::new(Mutex::new(state)),
+        }
+    }
+}
+
+impl Clone for TestDispatcher {
+    fn clone(&self) -> Self {
+        let id = post_inc(&mut self.state.lock().next_id.0);
+        Self {
+            id: TestDispatcherId(id),
+            state: self.state.clone(),
+        }
     }
 }
 
 impl PlatformDispatcher for TestDispatcher {
     fn is_main_thread(&self) -> bool {
-        self.0.lock().is_main_thread
+        self.state.lock().is_main_thread
     }
 
     fn dispatch(&self, runnable: Runnable) {
-        self.0.lock().background.push(runnable);
+        self.state.lock().background.push(runnable);
     }
 
     fn dispatch_on_main_thread(&self, runnable: Runnable) {
-        self.0.lock().foreground.push_back(runnable);
+        self.state
+            .lock()
+            .foreground
+            .entry(self.id)
+            .or_default()
+            .push_back(runnable);
     }
 
     fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
-        let mut state = self.0.lock();
+        let mut state = self.state.lock();
         let next_time = state.time + duration;
         state.delayed.insert(next_time, runnable);
     }
 
     fn poll(&self) -> bool {
-        let mut state = self.0.lock();
+        let mut state = self.state.lock();
 
         while let Some((deadline, _)) = state.delayed.first_key_value() {
             if *deadline > state.time {
@@ -61,36 +91,48 @@ impl PlatformDispatcher for TestDispatcher {
             state.background.push(runnable);
         }
 
-        if state.foreground.is_empty() && state.background.is_empty() {
+        let foreground_len: usize = state
+            .foreground
+            .values()
+            .map(|runnables| runnables.len())
+            .sum();
+        let background_len = state.background.len();
+
+        if foreground_len == 0 && background_len == 0 {
             return false;
         }
 
-        let foreground_len = state.foreground.len();
-        let background_len = state.background.len();
-        let main_thread = background_len == 0
-            || state
-                .random
-                .gen_ratio(foreground_len as u32, background_len as u32);
+        let main_thread = state.random.gen_ratio(
+            foreground_len as u32,
+            (foreground_len + background_len) as u32,
+        );
         let was_main_thread = state.is_main_thread;
         state.is_main_thread = main_thread;
 
         let runnable = if main_thread {
-            state.foreground.pop_front().unwrap()
+            let state = &mut *state;
+            let runnables = state
+                .foreground
+                .values_mut()
+                .filter(|runnables| !runnables.is_empty())
+                .choose(&mut state.random)
+                .unwrap();
+            runnables.pop_front().unwrap()
         } else {
             let ix = state.random.gen_range(0..background_len);
-            state.background.remove(ix)
+            state.background.swap_remove(ix)
         };
 
         drop(state);
         runnable.run();
 
-        self.0.lock().is_main_thread = was_main_thread;
+        self.state.lock().is_main_thread = was_main_thread;
 
         true
     }
 
     fn advance_clock(&self, by: Duration) {
-        self.0.lock().time += by;
+        self.state.lock().time += by;
     }
 }