Remove 2 suffix from gpui
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
3c81dda8e2
commit
f5ba22659b
225 changed files with 8511 additions and 41063 deletions
|
@ -1,120 +0,0 @@
|
|||
use std::any::{Any, TypeId};
|
||||
|
||||
use crate::TypeTag;
|
||||
|
||||
pub trait Action: 'static {
|
||||
fn id(&self) -> TypeId;
|
||||
fn namespace(&self) -> &'static str;
|
||||
fn name(&self) -> &'static str;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn type_tag(&self) -> TypeTag;
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
fn eq(&self, other: &dyn Action) -> bool;
|
||||
|
||||
fn qualified_name() -> &'static str
|
||||
where
|
||||
Self: Sized;
|
||||
fn from_json_str(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn Action {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("dyn Action")
|
||||
.field("namespace", &self.namespace())
|
||||
.field("name", &self.name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
/// Define a set of unit struct types that all implement the `Action` trait.
|
||||
///
|
||||
/// The first argument is a namespace that will be associated with each of
|
||||
/// the given action types, to ensure that they have globally unique
|
||||
/// qualified names for use in keymap files.
|
||||
#[macro_export]
|
||||
macro_rules! actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct $name;
|
||||
$crate::__impl_action! {
|
||||
$namespace,
|
||||
$name,
|
||||
fn from_json_str(_: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
|
||||
Ok(Box::new(Self))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement the `Action` trait for a set of existing types.
|
||||
///
|
||||
/// The first argument is a namespace that will be associated with each of
|
||||
/// the given action types, to ensure that they have globally unique
|
||||
/// qualified names for use in keymap files.
|
||||
#[macro_export]
|
||||
macro_rules! impl_actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
$crate::__impl_action! {
|
||||
$namespace,
|
||||
$name,
|
||||
fn from_json_str(json: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
|
||||
Ok(Box::new($crate::serde_json::from_value::<Self>(json)?))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __impl_action {
|
||||
($namespace:path, $name:ident, $from_json_fn:item) => {
|
||||
impl $crate::action::Action for $name {
|
||||
fn namespace(&self) -> &'static str {
|
||||
stringify!($namespace)
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
stringify!($name)
|
||||
}
|
||||
|
||||
fn qualified_name() -> &'static str {
|
||||
concat!(
|
||||
stringify!($namespace),
|
||||
"::",
|
||||
stringify!($name),
|
||||
)
|
||||
}
|
||||
|
||||
fn id(&self) -> std::any::TypeId {
|
||||
std::any::TypeId::of::<$name>()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> Box<dyn $crate::Action> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn eq(&self, other: &dyn $crate::Action) -> bool {
|
||||
if let Some(other) = other.as_any().downcast_ref::<Self>() {
|
||||
self == other
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn type_tag(&self) -> $crate::TypeTag {
|
||||
$crate::TypeTag::new::<Self>()
|
||||
}
|
||||
|
||||
$from_json_fn
|
||||
}
|
||||
};
|
||||
}
|
330
crates/gpui/src/app/async_context.rs
Normal file
330
crates/gpui/src/app/async_context.rs
Normal file
|
@ -0,0 +1,330 @@
|
|||
use crate::{
|
||||
AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, DismissEvent,
|
||||
FocusableView, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View,
|
||||
ViewContext, VisualContext, WindowContext, WindowHandle,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use std::{future::Future, rc::Weak};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncAppContext {
|
||||
pub(crate) app: Weak<AppCell>,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
}
|
||||
|
||||
impl Context for AsyncAppContext {
|
||||
type Result<T> = Result<T>;
|
||||
|
||||
fn new_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.new_model(build_model))
|
||||
}
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update_model(handle, update))
|
||||
}
|
||||
|
||||
fn read_model<T, R>(
|
||||
&self,
|
||||
handle: &Model<T>,
|
||||
callback: impl FnOnce(&T, &AppContext) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let lock = app.borrow();
|
||||
Ok(lock.read_model(handle, callback))
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
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)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let lock = app.borrow();
|
||||
lock.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAppContext {
|
||||
pub fn refresh(&mut self) -> Result<()> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.refresh();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn background_executor(&self) -> &BackgroundExecutor {
|
||||
&self.background_executor
|
||||
}
|
||||
|
||||
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
||||
&self.foreground_executor
|
||||
}
|
||||
|
||||
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.borrow_mut();
|
||||
Ok(f(&mut lock))
|
||||
}
|
||||
|
||||
pub fn open_window<V>(
|
||||
&self,
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
|
||||
) -> Result<WindowHandle<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.borrow_mut();
|
||||
Ok(lock.open_window(options, build_root_view))
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
self.foreground_executor.spawn(f(self.clone()))
|
||||
}
|
||||
|
||||
pub fn has_global<G: 'static>(&self) -> Result<bool> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let app = app.borrow_mut();
|
||||
Ok(app.has_global::<G>())
|
||||
}
|
||||
|
||||
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result<R> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let app = app.borrow_mut();
|
||||
Ok(read(app.global(), &app))
|
||||
}
|
||||
|
||||
pub fn try_read_global<G: 'static, R>(
|
||||
&self,
|
||||
read: impl FnOnce(&G, &AppContext) -> R,
|
||||
) -> Option<R> {
|
||||
let app = self.app.upgrade()?;
|
||||
let app = app.borrow_mut();
|
||||
Some(read(app.try_global()?, &app))
|
||||
}
|
||||
|
||||
pub fn update_global<G: 'static, R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(&mut G, &mut AppContext) -> R,
|
||||
) -> Result<R> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update_global(update))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, DerefMut)]
|
||||
pub struct AsyncWindowContext {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
app: AsyncAppContext,
|
||||
window: AnyWindowHandle,
|
||||
}
|
||||
|
||||
impl AsyncWindowContext {
|
||||
pub fn window_handle(&self) -> AnyWindowHandle {
|
||||
self.window
|
||||
}
|
||||
|
||||
pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
|
||||
Self { app, window }
|
||||
}
|
||||
|
||||
pub fn update<R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
self.app.update_window(self.window, update)
|
||||
}
|
||||
|
||||
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<G: 'static, R>(
|
||||
&mut self,
|
||||
read: impl FnOnce(&G, &WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
self.window.update(self, |_, cx| read(cx.global(), cx))
|
||||
}
|
||||
|
||||
pub fn update_global<G, R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(&mut G, &mut WindowContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
G: 'static,
|
||||
{
|
||||
self.window.update(self, |_, cx| cx.update_global(update))
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
self.foreground_executor.spawn(f(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AsyncWindowContext {
|
||||
type Result<T> = Result<T>;
|
||||
|
||||
fn new_model<T>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Result<Model<T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.window.update(self, |_, cx| cx.new_model(build_model))
|
||||
}
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Result<R> {
|
||||
self.window
|
||||
.update(self, |_, cx| cx.update_model(handle, update))
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
self.app.update_window(window, update)
|
||||
}
|
||||
|
||||
fn read_model<T, R>(
|
||||
&self,
|
||||
handle: &Model<T>,
|
||||
read: impl FnOnce(&T, &AppContext) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.app.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisualContext for AsyncWindowContext {
|
||||
fn new_view<V>(
|
||||
&mut self,
|
||||
build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
self.window
|
||||
.update(self, |_, cx| cx.new_view(build_view_state))
|
||||
}
|
||||
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.window
|
||||
.update(self, |_, cx| cx.update_view(view, update))
|
||||
}
|
||||
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
self.window
|
||||
.update(self, |_, cx| cx.replace_root_view(build_view))
|
||||
}
|
||||
|
||||
fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||
where
|
||||
V: FocusableView,
|
||||
{
|
||||
self.window.update(self, |_, cx| {
|
||||
view.read(cx).focus_handle(cx).clone().focus(cx);
|
||||
})
|
||||
}
|
||||
|
||||
fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||
where
|
||||
V: crate::ManagedView,
|
||||
{
|
||||
self.window
|
||||
.update(self, |_, cx| view.update(cx, |_, cx| cx.emit(DismissEvent)))
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::{hash::Hash, sync::Weak};
|
||||
|
||||
pub struct CallbackCollection<K: Clone + Hash + Eq, F> {
|
||||
internal: Arc<Mutex<Mapping<K, F>>>,
|
||||
}
|
||||
|
||||
pub struct Subscription<K: Clone + Hash + Eq, F> {
|
||||
key: K,
|
||||
id: usize,
|
||||
mapping: Option<Weak<Mutex<Mapping<K, F>>>>,
|
||||
}
|
||||
|
||||
struct Mapping<K, F> {
|
||||
callbacks: HashMap<K, BTreeMap<usize, F>>,
|
||||
dropped_subscriptions: HashMap<K, HashSet<usize>>,
|
||||
}
|
||||
|
||||
impl<K: Hash + Eq, F> Mapping<K, F> {
|
||||
fn clear_dropped_state(&mut self, key: &K, subscription_id: usize) -> bool {
|
||||
if let Some(subscriptions) = self.dropped_subscriptions.get_mut(&key) {
|
||||
subscriptions.remove(&subscription_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, F> Default for Mapping<K, F> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
callbacks: Default::default(),
|
||||
dropped_subscriptions: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone + Hash + Eq, F> Clone for CallbackCollection<K, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
internal: self.internal.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone + Hash + Eq + Copy, F> Default for CallbackCollection<K, F> {
|
||||
fn default() -> Self {
|
||||
CallbackCollection {
|
||||
internal: Arc::new(Mutex::new(Default::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
|
||||
#[cfg(test)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.internal.lock().callbacks.is_empty()
|
||||
}
|
||||
|
||||
pub fn subscribe(&mut self, key: K, subscription_id: usize) -> Subscription<K, F> {
|
||||
Subscription {
|
||||
key,
|
||||
id: subscription_id,
|
||||
mapping: Some(Arc::downgrade(&self.internal)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_callback(&mut self, key: K, subscription_id: usize, callback: F) {
|
||||
let mut this = self.internal.lock();
|
||||
|
||||
// If this callback's subscription was dropped before the callback was
|
||||
// added, then just drop the callback.
|
||||
if this.clear_dropped_state(&key, subscription_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callbacks
|
||||
.entry(key)
|
||||
.or_default()
|
||||
.insert(subscription_id, callback);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: K) {
|
||||
// Drop these callbacks after releasing the lock, in case one of them
|
||||
// owns a subscription to this callback collection.
|
||||
let mut this = self.internal.lock();
|
||||
let callbacks = this.callbacks.remove(&key);
|
||||
this.dropped_subscriptions.remove(&key);
|
||||
drop(this);
|
||||
drop(callbacks);
|
||||
}
|
||||
|
||||
pub fn emit<C>(&mut self, key: K, mut call_callback: C)
|
||||
where
|
||||
C: FnMut(&mut F) -> bool,
|
||||
{
|
||||
let callbacks = self.internal.lock().callbacks.remove(&key);
|
||||
if let Some(callbacks) = callbacks {
|
||||
for (subscription_id, mut callback) in callbacks {
|
||||
// If this callback's subscription was dropped while invoking an
|
||||
// earlier callback, then just drop the callback.
|
||||
let mut this = self.internal.lock();
|
||||
if this.clear_dropped_state(&key, subscription_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
drop(this);
|
||||
let alive = call_callback(&mut callback);
|
||||
|
||||
// If this callback's subscription was dropped while invoking the callback
|
||||
// itself, or if the callback returns false, then just drop the callback.
|
||||
let mut this = self.internal.lock();
|
||||
if this.clear_dropped_state(&key, subscription_id) || !alive {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.callbacks
|
||||
.entry(key)
|
||||
.or_default()
|
||||
.insert(subscription_id, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone + Hash + Eq, F> Subscription<K, F> {
|
||||
pub fn id(&self) -> usize {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn detach(&mut self) {
|
||||
self.mapping.take();
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone + Hash + Eq, F> Drop for Subscription<K, F> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(mapping) = self.mapping.as_ref().and_then(|mapping| mapping.upgrade()) {
|
||||
let mut mapping = mapping.lock();
|
||||
|
||||
// If the callback is present in the mapping, then just remove it.
|
||||
if let Some(callbacks) = mapping.callbacks.get_mut(&self.key) {
|
||||
let callback = callbacks.remove(&self.id);
|
||||
if callback.is_some() {
|
||||
drop(mapping);
|
||||
drop(callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If this subscription's callback is not present, then either it has been
|
||||
// temporarily removed during emit, or it has not yet been added. Record
|
||||
// that this subscription has been dropped so that the callback can be
|
||||
// removed later.
|
||||
mapping
|
||||
.dropped_subscriptions
|
||||
.entry(self.key.clone())
|
||||
.or_default()
|
||||
.insert(self.id);
|
||||
}
|
||||
}
|
||||
}
|
592
crates/gpui/src/app/entity_map.rs
Normal file
592
crates/gpui/src/app/entity_map.rs
Normal file
|
@ -0,0 +1,592 @@
|
|||
use crate::{private::Sealed, AppContext, Context, Entity, ModelContext};
|
||||
use anyhow::{anyhow, Result};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use slotmap::{SecondaryMap, SlotMap};
|
||||
use std::{
|
||||
any::{type_name, Any, TypeId},
|
||||
fmt::{self, Display},
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc, Weak,
|
||||
},
|
||||
thread::panicking,
|
||||
};
|
||||
|
||||
slotmap::new_key_type! { pub struct EntityId; }
|
||||
|
||||
impl EntityId {
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0.as_ffi()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EntityId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_u64())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EntityMap {
|
||||
entities: SecondaryMap<EntityId, Box<dyn Any>>,
|
||||
ref_counts: Arc<RwLock<EntityRefCounts>>,
|
||||
}
|
||||
|
||||
struct EntityRefCounts {
|
||||
counts: SlotMap<EntityId, AtomicUsize>,
|
||||
dropped_entity_ids: Vec<EntityId>,
|
||||
}
|
||||
|
||||
impl EntityMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entities: SecondaryMap::new(),
|
||||
ref_counts: Arc::new(RwLock::new(EntityRefCounts {
|
||||
counts: SlotMap::with_key(),
|
||||
dropped_entity_ids: Vec::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserve a slot for an entity, which you can subsequently use with `insert`.
|
||||
pub fn reserve<T: 'static>(&self) -> Slot<T> {
|
||||
let id = self.ref_counts.write().counts.insert(1.into());
|
||||
Slot(Model::new(id, Arc::downgrade(&self.ref_counts)))
|
||||
}
|
||||
|
||||
/// Insert an entity into a slot obtained by calling `reserve`.
|
||||
pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Model<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let model = slot.0;
|
||||
self.entities.insert(model.entity_id, Box::new(entity));
|
||||
model
|
||||
}
|
||||
|
||||
/// Move an entity to the stack.
|
||||
#[track_caller]
|
||||
pub fn lease<'a, T>(&mut self, model: &'a Model<T>) -> Lease<'a, T> {
|
||||
self.assert_valid_context(model);
|
||||
let entity = Some(self.entities.remove(model.entity_id).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Circular entity lease of {}. Is it already being updated?",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
}));
|
||||
Lease {
|
||||
model,
|
||||
entity,
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an entity after moving it to the stack.
|
||||
pub fn end_lease<T>(&mut self, mut lease: Lease<T>) {
|
||||
self.entities
|
||||
.insert(lease.model.entity_id, lease.entity.take().unwrap());
|
||||
}
|
||||
|
||||
pub fn read<T: 'static>(&self, model: &Model<T>) -> &T {
|
||||
self.assert_valid_context(model);
|
||||
self.entities[model.entity_id].downcast_ref().unwrap()
|
||||
}
|
||||
|
||||
fn assert_valid_context(&self, model: &AnyModel) {
|
||||
debug_assert!(
|
||||
Weak::ptr_eq(&model.entity_map, &Arc::downgrade(&self.ref_counts)),
|
||||
"used a model with the wrong context"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> {
|
||||
let mut ref_counts = self.ref_counts.write();
|
||||
let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids);
|
||||
|
||||
dropped_entity_ids
|
||||
.into_iter()
|
||||
.map(|entity_id| {
|
||||
let count = ref_counts.counts.remove(entity_id).unwrap();
|
||||
debug_assert_eq!(
|
||||
count.load(SeqCst),
|
||||
0,
|
||||
"dropped an entity that was referenced"
|
||||
);
|
||||
(entity_id, self.entities.remove(entity_id).unwrap())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lease<'a, T> {
|
||||
entity: Option<Box<dyn Any>>,
|
||||
pub model: &'a Model<T>,
|
||||
entity_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> core::ops::Deref for Lease<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.entity.as_ref().unwrap().downcast_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.entity.as_mut().unwrap().downcast_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for Lease<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.entity.is_some() && !panicking() {
|
||||
panic!("Leases must be ended with EntityMap::end_lease")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Slot<T>(Model<T>);
|
||||
|
||||
pub struct AnyModel {
|
||||
pub(crate) entity_id: EntityId,
|
||||
pub(crate) entity_type: TypeId,
|
||||
entity_map: Weak<RwLock<EntityRefCounts>>,
|
||||
}
|
||||
|
||||
impl AnyModel {
|
||||
fn new(id: EntityId, entity_type: TypeId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self {
|
||||
Self {
|
||||
entity_id: id,
|
||||
entity_type,
|
||||
entity_map,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.entity_id
|
||||
}
|
||||
|
||||
pub fn entity_type(&self) -> TypeId {
|
||||
self.entity_type
|
||||
}
|
||||
|
||||
pub fn downgrade(&self) -> AnyWeakModel {
|
||||
AnyWeakModel {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_ref_counts: self.entity_map.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downcast<T: 'static>(self) -> Result<Model<T>, AnyModel> {
|
||||
if TypeId::of::<T>() == self.entity_type {
|
||||
Ok(Model {
|
||||
any_model: self,
|
||||
entity_type: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AnyModel {
|
||||
fn clone(&self) -> Self {
|
||||
if let Some(entity_map) = self.entity_map.upgrade() {
|
||||
let entity_map = entity_map.read();
|
||||
let count = entity_map
|
||||
.counts
|
||||
.get(self.entity_id)
|
||||
.expect("detected over-release of a model");
|
||||
let prev_count = count.fetch_add(1, SeqCst);
|
||||
assert_ne!(prev_count, 0, "Detected over-release of a model.");
|
||||
}
|
||||
|
||||
Self {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_map: self.entity_map.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AnyModel {
|
||||
fn drop(&mut self) {
|
||||
if let Some(entity_map) = self.entity_map.upgrade() {
|
||||
let entity_map = entity_map.upgradable_read();
|
||||
let count = entity_map
|
||||
.counts
|
||||
.get(self.entity_id)
|
||||
.expect("detected over-release of a handle.");
|
||||
let prev_count = count.fetch_sub(1, SeqCst);
|
||||
assert_ne!(prev_count, 0, "Detected over-release of a model.");
|
||||
if prev_count == 1 {
|
||||
// We were the last reference to this entity, so we can remove it.
|
||||
let mut entity_map = RwLockUpgradableReadGuard::upgrade(entity_map);
|
||||
entity_map.dropped_entity_ids.push(self.entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Model<T>> for AnyModel {
|
||||
fn from(model: Model<T>) -> Self {
|
||||
model.any_model
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for AnyModel {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.entity_id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyModel {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.entity_id == other.entity_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AnyModel {}
|
||||
|
||||
impl std::fmt::Debug for AnyModel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AnyModel")
|
||||
.field("entity_id", &self.entity_id.as_u64())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Model<T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub(crate) any_model: AnyModel,
|
||||
pub(crate) entity_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for Model<T> {}
|
||||
unsafe impl<T> Sync for Model<T> {}
|
||||
impl<T> Sealed for Model<T> {}
|
||||
|
||||
impl<T: 'static> Entity<T> for Model<T> {
|
||||
type Weak = WeakModel<T>;
|
||||
|
||||
fn entity_id(&self) -> EntityId {
|
||||
self.any_model.entity_id
|
||||
}
|
||||
|
||||
fn downgrade(&self) -> Self::Weak {
|
||||
WeakModel {
|
||||
any_model: self.any_model.downgrade(),
|
||||
entity_type: self.entity_type,
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Some(Model {
|
||||
any_model: weak.any_model.upgrade()?,
|
||||
entity_type: weak.entity_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Model<T> {
|
||||
fn new(id: EntityId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
Self {
|
||||
any_model: AnyModel::new(id, TypeId::of::<T>(), entity_map),
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Downgrade the this to a weak model reference
|
||||
pub fn downgrade(&self) -> WeakModel<T> {
|
||||
// Delegate to the trait implementation to keep behavior in one place.
|
||||
// This method was included to improve method resolution in the presence of
|
||||
// the Model's deref
|
||||
Entity::downgrade(self)
|
||||
}
|
||||
|
||||
/// Convert this into a dynamically typed model.
|
||||
pub fn into_any(self) -> AnyModel {
|
||||
self.any_model
|
||||
}
|
||||
|
||||
pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
|
||||
cx.entities.read(self)
|
||||
}
|
||||
|
||||
pub fn read_with<R, C: Context>(
|
||||
&self,
|
||||
cx: &C,
|
||||
f: impl FnOnce(&T, &AppContext) -> R,
|
||||
) -> C::Result<R> {
|
||||
cx.read_model(self, f)
|
||||
}
|
||||
|
||||
/// Update the entity referenced by this model with the given function.
|
||||
///
|
||||
/// The update function receives a context appropriate for its environment.
|
||||
/// When updating in an `AppContext`, it receives a `ModelContext`.
|
||||
/// When updating an a `WindowContext`, it receives a `ViewContext`.
|
||||
pub fn update<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> C::Result<R>
|
||||
where
|
||||
C: Context,
|
||||
{
|
||||
cx.update_model(self, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Model<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
any_model: self.any_model.clone(),
|
||||
entity_type: self.entity_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Model<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Model {{ entity_id: {:?}, entity_type: {:?} }}",
|
||||
self.any_model.entity_id,
|
||||
type_name::<T>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Hash for Model<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.any_model.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for Model<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.any_model == other.any_model
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for Model<T> {}
|
||||
|
||||
impl<T> PartialEq<WeakModel<T>> for Model<T> {
|
||||
fn eq(&self, other: &WeakModel<T>) -> bool {
|
||||
self.any_model.entity_id() == other.entity_id()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AnyWeakModel {
|
||||
pub(crate) entity_id: EntityId,
|
||||
entity_type: TypeId,
|
||||
entity_ref_counts: Weak<RwLock<EntityRefCounts>>,
|
||||
}
|
||||
|
||||
impl AnyWeakModel {
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.entity_id
|
||||
}
|
||||
|
||||
pub fn is_upgradable(&self) -> bool {
|
||||
let ref_count = self
|
||||
.entity_ref_counts
|
||||
.upgrade()
|
||||
.and_then(|ref_counts| Some(ref_counts.read().counts.get(self.entity_id)?.load(SeqCst)))
|
||||
.unwrap_or(0);
|
||||
ref_count > 0
|
||||
}
|
||||
|
||||
pub fn upgrade(&self) -> Option<AnyModel> {
|
||||
let ref_counts = &self.entity_ref_counts.upgrade()?;
|
||||
let ref_counts = ref_counts.read();
|
||||
let ref_count = ref_counts.counts.get(self.entity_id)?;
|
||||
|
||||
// entity_id is in dropped_entity_ids
|
||||
if ref_count.load(SeqCst) == 0 {
|
||||
return None;
|
||||
}
|
||||
ref_count.fetch_add(1, SeqCst);
|
||||
|
||||
Some(AnyModel {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_map: self.entity_ref_counts.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<WeakModel<T>> for AnyWeakModel {
|
||||
fn from(model: WeakModel<T>) -> Self {
|
||||
model.any_model
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for AnyWeakModel {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.entity_id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyWeakModel {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.entity_id == other.entity_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AnyWeakModel {}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct WeakModel<T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
any_model: AnyWeakModel,
|
||||
entity_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for WeakModel<T> {}
|
||||
unsafe impl<T> Sync for WeakModel<T> {}
|
||||
|
||||
impl<T> Clone for WeakModel<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
any_model: self.any_model.clone(),
|
||||
entity_type: self.entity_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> WeakModel<T> {
|
||||
/// Upgrade this weak model reference into a strong model reference
|
||||
pub fn upgrade(&self) -> Option<Model<T>> {
|
||||
// Delegate to the trait implementation to keep behavior in one place.
|
||||
Model::upgrade_from(self)
|
||||
}
|
||||
|
||||
/// Update the entity referenced by this model with the given function if
|
||||
/// the referenced entity still exists. Returns an error if the entity has
|
||||
/// been released.
|
||||
pub fn update<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
C: Context,
|
||||
Result<C::Result<R>>: crate::Flatten<R>,
|
||||
{
|
||||
crate::Flatten::flatten(
|
||||
self.upgrade()
|
||||
.ok_or_else(|| anyhow!("entity release"))
|
||||
.map(|this| cx.update_model(&this, update)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Reads the entity referenced by this model with the given function if
|
||||
/// the referenced entity still exists. Returns an error if the entity has
|
||||
/// been released.
|
||||
pub fn read_with<C, R>(&self, cx: &C, read: impl FnOnce(&T, &AppContext) -> R) -> Result<R>
|
||||
where
|
||||
C: Context,
|
||||
Result<C::Result<R>>: crate::Flatten<R>,
|
||||
{
|
||||
crate::Flatten::flatten(
|
||||
self.upgrade()
|
||||
.ok_or_else(|| anyhow!("entity release"))
|
||||
.map(|this| cx.read_model(&this, read)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Hash for WeakModel<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.any_model.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for WeakModel<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.any_model == other.any_model
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for WeakModel<T> {}
|
||||
|
||||
impl<T> PartialEq<Model<T>> for WeakModel<T> {
|
||||
fn eq(&self, other: &Model<T>) -> bool {
|
||||
self.entity_id() == other.any_model.entity_id()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::EntityMap;
|
||||
|
||||
struct TestEntity {
|
||||
pub i: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entity_map_slot_assignment_before_cleanup() {
|
||||
// Tests that slots are not re-used before take_dropped.
|
||||
let mut entity_map = EntityMap::new();
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
entity_map.insert(slot, TestEntity { i: 1 });
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
entity_map.insert(slot, TestEntity { i: 2 });
|
||||
|
||||
let dropped = entity_map.take_dropped();
|
||||
assert_eq!(dropped.len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
dropped
|
||||
.into_iter()
|
||||
.map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
|
||||
.collect::<Vec<i32>>(),
|
||||
vec![1, 2],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entity_map_weak_upgrade_before_cleanup() {
|
||||
// Tests that weak handles are not upgraded before take_dropped
|
||||
let mut entity_map = EntityMap::new();
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
let handle = entity_map.insert(slot, TestEntity { i: 1 });
|
||||
let weak = handle.downgrade();
|
||||
drop(handle);
|
||||
|
||||
let strong = weak.upgrade();
|
||||
assert_eq!(strong, None);
|
||||
|
||||
let dropped = entity_map.take_dropped();
|
||||
assert_eq!(dropped.len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
dropped
|
||||
.into_iter()
|
||||
.map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
|
||||
.collect::<Vec<i32>>(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
use crate::{platform::ForegroundPlatform, Action, App, AppContext};
|
||||
|
||||
pub struct Menu<'a> {
|
||||
pub name: &'a str,
|
||||
pub items: Vec<MenuItem<'a>>,
|
||||
}
|
||||
|
||||
pub enum MenuItem<'a> {
|
||||
Separator,
|
||||
Submenu(Menu<'a>),
|
||||
Action {
|
||||
name: &'a str,
|
||||
action: Box<dyn Action>,
|
||||
os_action: Option<OsAction>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> MenuItem<'a> {
|
||||
pub fn separator() -> Self {
|
||||
Self::Separator
|
||||
}
|
||||
|
||||
pub fn submenu(menu: Menu<'a>) -> Self {
|
||||
Self::Submenu(menu)
|
||||
}
|
||||
|
||||
pub fn action(name: &'a str, action: impl Action) -> Self {
|
||||
Self::Action {
|
||||
name,
|
||||
action: Box::new(action),
|
||||
os_action: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
|
||||
Self::Action {
|
||||
name,
|
||||
action: Box::new(action),
|
||||
os_action: Some(os_action),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum OsAction {
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
SelectAll,
|
||||
Undo,
|
||||
Redo,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
pub fn set_menus(&mut self, menus: Vec<Menu>) {
|
||||
self.foreground_platform
|
||||
.set_menus(menus, &self.keystroke_matcher);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform, app: &App) {
|
||||
foreground_platform.on_will_open_menu(Box::new({
|
||||
let cx = app.0.clone();
|
||||
move || {
|
||||
let mut cx = cx.borrow_mut();
|
||||
cx.keystroke_matcher.clear_pending();
|
||||
}
|
||||
}));
|
||||
foreground_platform.on_validate_menu_command(Box::new({
|
||||
let cx = app.0.clone();
|
||||
move |action| {
|
||||
let cx = cx.borrow_mut();
|
||||
!cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action)
|
||||
}
|
||||
}));
|
||||
foreground_platform.on_menu_command(Box::new({
|
||||
let cx = app.0.clone();
|
||||
move |action| {
|
||||
let mut cx = cx.borrow_mut();
|
||||
if let Some(main_window) = cx.active_window() {
|
||||
let dispatched = main_window
|
||||
.update(&mut *cx, |cx| {
|
||||
if let Some(view_id) = cx.focused_view_id() {
|
||||
cx.dispatch_action(Some(view_id), action);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if dispatched {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cx.dispatch_global_action_any(action);
|
||||
}
|
||||
}));
|
||||
}
|
273
crates/gpui/src/app/model_context.rs
Normal file
273
crates/gpui/src/app/model_context.rs
Normal file
|
@ -0,0 +1,273 @@
|
|||
use crate::{
|
||||
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId,
|
||||
EventEmitter, Model, Subscription, Task, View, WeakModel, WindowContext, WindowHandle,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::FutureExt;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::{Borrow, BorrowMut},
|
||||
future::Future,
|
||||
};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct ModelContext<'a, T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
app: &'a mut AppContext,
|
||||
model_state: WeakModel<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
pub(crate) fn new(app: &'a mut AppContext, model_state: WeakModel<T>) -> Self {
|
||||
Self { app, model_state }
|
||||
}
|
||||
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.model_state.entity_id
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> Model<T> {
|
||||
self.weak_model()
|
||||
.upgrade()
|
||||
.expect("The entity must be alive if we have a model context")
|
||||
}
|
||||
|
||||
pub fn weak_model(&self) -> WeakModel<T> {
|
||||
self.model_state.clone()
|
||||
}
|
||||
|
||||
pub fn observe<W, E>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
W: 'static,
|
||||
E: Entity<W>,
|
||||
{
|
||||
let this = self.weak_model();
|
||||
self.app.observe_internal(entity, move |e, cx| {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| on_notify(this, e, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn subscribe<T2, E, Evt>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
mut on_event: impl FnMut(&mut T, E, &Evt, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
T2: 'static + EventEmitter<Evt>,
|
||||
E: Entity<T2>,
|
||||
Evt: 'static,
|
||||
{
|
||||
let this = self.weak_model();
|
||||
self.app.subscribe_internal(entity, move |e, event, cx| {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| on_event(this, e, event, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_release(
|
||||
&mut self,
|
||||
on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
self.model_state.entity_id,
|
||||
Box::new(move |this, cx| {
|
||||
let this = this.downcast_mut().expect("invalid entity type");
|
||||
on_release(this, cx);
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_release<T2, E>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
on_release: impl FnOnce(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: Any,
|
||||
T2: 'static,
|
||||
E: Entity<T2>,
|
||||
{
|
||||
let entity_id = entity.entity_id();
|
||||
let this = self.weak_model();
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
entity_id,
|
||||
Box::new(move |entity, cx| {
|
||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| on_release(this, entity, cx));
|
||||
}
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_global<G: 'static>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_model();
|
||||
let (subscription, activate) = self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
Box::new(move |cx| handle.update(cx, |view, cx| f(view, cx)).is_ok()),
|
||||
);
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn on_app_quit<Fut>(
|
||||
&mut self,
|
||||
mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
Fut: 'static + Future<Output = ()>,
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_model();
|
||||
let (subscription, activate) = self.app.quit_observers.insert(
|
||||
(),
|
||||
Box::new(move |cx| {
|
||||
let future = handle.update(cx, |entity, cx| on_quit(entity, cx)).ok();
|
||||
async move {
|
||||
if let Some(future) = future {
|
||||
future.await;
|
||||
}
|
||||
}
|
||||
.boxed_local()
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn notify(&mut self) {
|
||||
if self
|
||||
.app
|
||||
.pending_notifications
|
||||
.insert(self.model_state.entity_id)
|
||||
{
|
||||
self.app.pending_effects.push_back(Effect::Notify {
|
||||
emitter: self.model_state.entity_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
|
||||
where
|
||||
G: 'static,
|
||||
{
|
||||
let mut global = self.app.lease_global::<G>();
|
||||
let result = f(&mut global, self);
|
||||
self.app.end_global_lease(global);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(WeakModel<T>, AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
T: 'static,
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let this = self.weak_model();
|
||||
self.app.spawn(|cx| f(this, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ModelContext<'a, T> {
|
||||
pub fn emit<Evt>(&mut self, event: Evt)
|
||||
where
|
||||
T: EventEmitter<Evt>,
|
||||
Evt: 'static,
|
||||
{
|
||||
self.app.pending_effects.push_back(Effect::Emit {
|
||||
emitter: self.model_state.entity_id,
|
||||
event_type: TypeId::of::<Evt>(),
|
||||
event: Box::new(event),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Context for ModelContext<'a, T> {
|
||||
type Result<U> = U;
|
||||
|
||||
fn new_model<U: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, U>) -> U,
|
||||
) -> Model<U> {
|
||||
self.app.new_model(build_model)
|
||||
}
|
||||
|
||||
fn update_model<U: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<U>,
|
||||
update: impl FnOnce(&mut U, &mut ModelContext<'_, U>) -> R,
|
||||
) -> R {
|
||||
self.app.update_model(handle, update)
|
||||
}
|
||||
|
||||
fn update_window<R, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> R,
|
||||
{
|
||||
self.app.update_window(window, update)
|
||||
}
|
||||
|
||||
fn read_model<U, R>(
|
||||
&self,
|
||||
handle: &Model<U>,
|
||||
read: impl FnOnce(&U, &AppContext) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
U: 'static,
|
||||
{
|
||||
self.app.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn read_window<U, R>(
|
||||
&self,
|
||||
window: &WindowHandle<U>,
|
||||
read: impl FnOnce(View<U>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
U: 'static,
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<AppContext> for ModelContext<'_, T> {
|
||||
fn borrow(&self) -> &AppContext {
|
||||
self.app
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BorrowMut<AppContext> for ModelContext<'_, T> {
|
||||
fn borrow_mut(&mut self) -> &mut AppContext {
|
||||
self.app
|
||||
}
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
#[cfg(any(test, feature = "test-support"))]
|
||||
use std::sync::Arc;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use collections::{hash_map::Entry, HashMap, HashSet};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use crate::util::post_inc;
|
||||
use crate::{AnyWindowHandle, ElementStateId};
|
||||
|
||||
lazy_static! {
|
||||
static ref LEAK_BACKTRACE: bool =
|
||||
std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
|
||||
}
|
||||
|
||||
struct ElementStateRefCount {
|
||||
ref_count: usize,
|
||||
frame_id: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RefCounts {
|
||||
entity_counts: HashMap<usize, usize>,
|
||||
element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
|
||||
dropped_models: HashSet<usize>,
|
||||
dropped_views: HashSet<(AnyWindowHandle, usize)>,
|
||||
dropped_element_states: HashSet<ElementStateId>,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub leak_detector: Arc<Mutex<LeakDetector>>,
|
||||
}
|
||||
|
||||
impl RefCounts {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn new(leak_detector: Arc<Mutex<LeakDetector>>) -> Self {
|
||||
Self {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
leak_detector,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc_model(&mut self, model_id: usize) {
|
||||
match self.entity_counts.entry(model_id) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
*entry.get_mut() += 1;
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(1);
|
||||
self.dropped_models.remove(&model_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc_view(&mut self, window: AnyWindowHandle, view_id: usize) {
|
||||
match self.entity_counts.entry(view_id) {
|
||||
Entry::Occupied(mut entry) => *entry.get_mut() += 1,
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(1);
|
||||
self.dropped_views.remove(&(window, view_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) {
|
||||
match self.element_state_counts.entry(id) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let entry = entry.get_mut();
|
||||
if entry.frame_id == frame_id || entry.ref_count >= 2 {
|
||||
panic!("used the same element state more than once in the same frame");
|
||||
}
|
||||
entry.ref_count += 1;
|
||||
entry.frame_id = frame_id;
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(ElementStateRefCount {
|
||||
ref_count: 1,
|
||||
frame_id,
|
||||
});
|
||||
self.dropped_element_states.remove(&id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dec_model(&mut self, model_id: usize) {
|
||||
let count = self.entity_counts.get_mut(&model_id).unwrap();
|
||||
*count -= 1;
|
||||
if *count == 0 {
|
||||
self.entity_counts.remove(&model_id);
|
||||
self.dropped_models.insert(model_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dec_view(&mut self, window: AnyWindowHandle, view_id: usize) {
|
||||
let count = self.entity_counts.get_mut(&view_id).unwrap();
|
||||
*count -= 1;
|
||||
if *count == 0 {
|
||||
self.entity_counts.remove(&view_id);
|
||||
self.dropped_views.insert((window, view_id));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dec_element_state(&mut self, id: ElementStateId) {
|
||||
let entry = self.element_state_counts.get_mut(&id).unwrap();
|
||||
entry.ref_count -= 1;
|
||||
if entry.ref_count == 0 {
|
||||
self.element_state_counts.remove(&id);
|
||||
self.dropped_element_states.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_entity_alive(&self, entity_id: usize) -> bool {
|
||||
self.entity_counts.contains_key(&entity_id)
|
||||
}
|
||||
|
||||
pub fn take_dropped(
|
||||
&mut self,
|
||||
) -> (
|
||||
HashSet<usize>,
|
||||
HashSet<(AnyWindowHandle, usize)>,
|
||||
HashSet<ElementStateId>,
|
||||
) {
|
||||
(
|
||||
std::mem::take(&mut self.dropped_models),
|
||||
std::mem::take(&mut self.dropped_views),
|
||||
std::mem::take(&mut self.dropped_element_states),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[derive(Default)]
|
||||
pub struct LeakDetector {
|
||||
next_handle_id: usize,
|
||||
#[allow(clippy::type_complexity)]
|
||||
handle_backtraces: HashMap<
|
||||
usize,
|
||||
(
|
||||
Option<&'static str>,
|
||||
HashMap<usize, Option<backtrace::Backtrace>>,
|
||||
),
|
||||
>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl LeakDetector {
|
||||
pub fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
|
||||
let handle_id = post_inc(&mut self.next_handle_id);
|
||||
let entry = self.handle_backtraces.entry(entity_id).or_default();
|
||||
let backtrace = if *LEAK_BACKTRACE {
|
||||
Some(backtrace::Backtrace::new_unresolved())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(type_name) = type_name {
|
||||
entry.0.get_or_insert(type_name);
|
||||
}
|
||||
entry.1.insert(handle_id, backtrace);
|
||||
handle_id
|
||||
}
|
||||
|
||||
pub fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) {
|
||||
if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
|
||||
assert!(backtraces.remove(&handle_id).is_some());
|
||||
if backtraces.is_empty() {
|
||||
self.handle_backtraces.remove(&entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_dropped(&mut self, entity_id: usize) {
|
||||
if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
|
||||
for trace in backtraces.values_mut().flatten() {
|
||||
trace.resolve();
|
||||
eprintln!("{:?}", crate::util::CwdBacktrace(trace));
|
||||
}
|
||||
|
||||
let hint = if *LEAK_BACKTRACE {
|
||||
""
|
||||
} else {
|
||||
" – set LEAK_BACKTRACE=1 for more information"
|
||||
};
|
||||
|
||||
panic!(
|
||||
"{} handles to {} {} still exist{}",
|
||||
backtraces.len(),
|
||||
type_name.unwrap_or("entity"),
|
||||
entity_id,
|
||||
hint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect(&mut self) {
|
||||
let mut found_leaks = false;
|
||||
for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
|
||||
eprintln!(
|
||||
"leaked {} handles to {} {}",
|
||||
backtraces.len(),
|
||||
type_name.unwrap_or("entity"),
|
||||
id
|
||||
);
|
||||
for trace in backtraces.values_mut().flatten() {
|
||||
trace.resolve();
|
||||
eprintln!("{:?}", crate::util::CwdBacktrace(trace));
|
||||
}
|
||||
found_leaks = true;
|
||||
}
|
||||
|
||||
let hint = if *LEAK_BACKTRACE {
|
||||
""
|
||||
} else {
|
||||
" – set LEAK_BACKTRACE=1 for more information"
|
||||
};
|
||||
assert!(!found_leaks, "detected leaked handles{}", hint);
|
||||
}
|
||||
}
|
|
@ -1,661 +0,0 @@
|
|||
use crate::{
|
||||
executor,
|
||||
geometry::vector::Vector2F,
|
||||
keymap_matcher::{Binding, Keystroke},
|
||||
platform,
|
||||
platform::{Event, InputHandler, KeyDownEvent, Platform},
|
||||
Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
|
||||
Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
|
||||
WeakHandle, WindowContext, WindowHandle,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
use futures::Future;
|
||||
use itertools::Itertools;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use smallvec::SmallVec;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
mem,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{
|
||||
ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestAppContext {
|
||||
cx: Rc<RefCell<AppContext>>,
|
||||
foreground_platform: Rc<platform::test::ForegroundPlatform>,
|
||||
condition_duration: Option<Duration>,
|
||||
pub function_name: String,
|
||||
assertion_context: AssertionContextManager,
|
||||
}
|
||||
|
||||
impl TestAppContext {
|
||||
pub fn new(
|
||||
foreground_platform: Rc<platform::test::ForegroundPlatform>,
|
||||
platform: Arc<dyn Platform>,
|
||||
foreground: Rc<executor::Foreground>,
|
||||
background: Arc<executor::Background>,
|
||||
font_cache: Arc<FontCache>,
|
||||
leak_detector: Arc<Mutex<LeakDetector>>,
|
||||
first_entity_id: usize,
|
||||
function_name: String,
|
||||
) -> Self {
|
||||
let mut cx = AppContext::new(
|
||||
foreground,
|
||||
background,
|
||||
platform,
|
||||
foreground_platform.clone(),
|
||||
font_cache,
|
||||
util::http::FakeHttpClient::with_404_response(),
|
||||
RefCounts::new(leak_detector),
|
||||
(),
|
||||
);
|
||||
cx.next_id = first_entity_id;
|
||||
let cx = TestAppContext {
|
||||
cx: Rc::new(RefCell::new(cx)),
|
||||
foreground_platform,
|
||||
condition_duration: None,
|
||||
function_name,
|
||||
assertion_context: AssertionContextManager::new(),
|
||||
};
|
||||
cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
|
||||
cx
|
||||
}
|
||||
|
||||
pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
|
||||
self.update_window(window, |window| {
|
||||
window.dispatch_action(window.focused_view_id(), &action);
|
||||
})
|
||||
.expect("window not found");
|
||||
}
|
||||
|
||||
pub fn available_actions(
|
||||
&self,
|
||||
window: AnyWindowHandle,
|
||||
view_id: usize,
|
||||
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
|
||||
self.read_window(window, |cx| cx.available_actions(view_id))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn dispatch_global_action<A: Action>(&mut self, action: A) {
|
||||
self.update(|cx| cx.dispatch_global_action_any(&action));
|
||||
}
|
||||
|
||||
pub fn dispatch_keystroke(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
keystroke: Keystroke,
|
||||
is_held: bool,
|
||||
) {
|
||||
let handled = window.update(self, |cx| {
|
||||
if cx.dispatch_keystroke(&keystroke) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if cx.dispatch_event(
|
||||
Event::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
is_held,
|
||||
}),
|
||||
false,
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
});
|
||||
|
||||
if !handled && !keystroke.cmd && !keystroke.ctrl {
|
||||
WindowInputHandler {
|
||||
app: self.cx.clone(),
|
||||
window,
|
||||
}
|
||||
.replace_text_in_range(None, &keystroke.key)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
|
||||
&self,
|
||||
window: AnyWindowHandle,
|
||||
callback: F,
|
||||
) -> Option<T> {
|
||||
self.cx.borrow().read_window(window, callback)
|
||||
}
|
||||
|
||||
pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
callback: F,
|
||||
) -> Option<T> {
|
||||
self.cx.borrow_mut().update_window(window, callback)
|
||||
}
|
||||
|
||||
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
|
||||
where
|
||||
T: Entity,
|
||||
F: FnOnce(&mut ModelContext<T>) -> T,
|
||||
{
|
||||
self.cx.borrow_mut().add_model(build_model)
|
||||
}
|
||||
|
||||
pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
|
||||
where
|
||||
V: View,
|
||||
F: FnOnce(&mut ViewContext<V>) -> V,
|
||||
{
|
||||
let window = self
|
||||
.cx
|
||||
.borrow_mut()
|
||||
.add_window(Default::default(), build_root_view);
|
||||
window.simulate_activation(self);
|
||||
window
|
||||
}
|
||||
|
||||
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
|
||||
where
|
||||
E: Any,
|
||||
F: 'static + FnMut(&mut AppContext),
|
||||
{
|
||||
self.cx.borrow_mut().observe_global::<E, F>(callback)
|
||||
}
|
||||
|
||||
pub fn set_global<T: 'static>(&mut self, state: T) {
|
||||
self.cx.borrow_mut().set_global(state);
|
||||
}
|
||||
|
||||
pub fn subscribe_global<E, F>(&mut self, callback: F) -> Subscription
|
||||
where
|
||||
E: Any,
|
||||
F: 'static + FnMut(&E, &mut AppContext),
|
||||
{
|
||||
self.cx.borrow_mut().subscribe_global(callback)
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> Vec<AnyWindowHandle> {
|
||||
self.cx.borrow().windows().collect()
|
||||
}
|
||||
|
||||
pub fn remove_all_windows(&mut self) {
|
||||
self.update(|cx| cx.windows.clear());
|
||||
}
|
||||
|
||||
pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
|
||||
callback(&*self.cx.borrow())
|
||||
}
|
||||
|
||||
pub fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
|
||||
let mut state = self.cx.borrow_mut();
|
||||
// Don't increment pending flushes in order for effects to be flushed before the callback
|
||||
// completes, which is helpful in tests.
|
||||
let result = callback(&mut *state);
|
||||
// Flush effects after the callback just in case there are any. This can happen in edge
|
||||
// cases such as the closure dropping handles.
|
||||
state.flush_effects();
|
||||
result
|
||||
}
|
||||
|
||||
pub fn to_async(&self) -> AsyncAppContext {
|
||||
AsyncAppContext(self.cx.clone())
|
||||
}
|
||||
|
||||
pub fn font_cache(&self) -> Arc<FontCache> {
|
||||
self.cx.borrow().font_cache.clone()
|
||||
}
|
||||
|
||||
pub fn foreground_platform(&self) -> Rc<platform::test::ForegroundPlatform> {
|
||||
self.foreground_platform.clone()
|
||||
}
|
||||
|
||||
pub fn platform(&self) -> Arc<dyn platform::Platform> {
|
||||
self.cx.borrow().platform.clone()
|
||||
}
|
||||
|
||||
pub fn foreground(&self) -> Rc<executor::Foreground> {
|
||||
self.cx.borrow().foreground().clone()
|
||||
}
|
||||
|
||||
pub fn background(&self) -> Arc<executor::Background> {
|
||||
self.cx.borrow().background().clone()
|
||||
}
|
||||
|
||||
pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
|
||||
where
|
||||
F: FnOnce(AsyncAppContext) -> Fut,
|
||||
Fut: 'static + Future<Output = T>,
|
||||
T: 'static,
|
||||
{
|
||||
let foreground = self.foreground();
|
||||
let future = f(self.to_async());
|
||||
let cx = self.to_async();
|
||||
foreground.spawn(async move {
|
||||
let result = future.await;
|
||||
cx.0.borrow_mut().flush_effects();
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
|
||||
self.foreground_platform.simulate_new_path_selection(result);
|
||||
}
|
||||
|
||||
pub fn did_prompt_for_new_path(&self) -> bool {
|
||||
self.foreground_platform.as_ref().did_prompt_for_new_path()
|
||||
}
|
||||
|
||||
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
|
||||
self.cx.borrow().leak_detector()
|
||||
}
|
||||
|
||||
pub fn assert_dropped(&self, handle: impl WeakHandle) {
|
||||
self.cx
|
||||
.borrow()
|
||||
.leak_detector()
|
||||
.lock()
|
||||
.assert_dropped(handle.id())
|
||||
}
|
||||
|
||||
/// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
|
||||
/// where the stray handles were created.
|
||||
pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
|
||||
let weak = handle.downgrade();
|
||||
self.update(|_| drop(handle));
|
||||
self.assert_dropped(weak);
|
||||
}
|
||||
|
||||
pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
|
||||
self.condition_duration = duration;
|
||||
}
|
||||
|
||||
pub fn condition_duration(&self) -> Duration {
|
||||
self.condition_duration.unwrap_or_else(|| {
|
||||
if std::env::var("CI").is_ok() {
|
||||
Duration::from_secs(2)
|
||||
} else {
|
||||
Duration::from_millis(500)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
|
||||
self.update(|cx| {
|
||||
let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
|
||||
let expected_content = expected_content.map(|content| content.to_owned());
|
||||
assert_eq!(actual_content, expected_content);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_assertion_context(&self, context: String) -> ContextHandle {
|
||||
self.assertion_context.add_context(context)
|
||||
}
|
||||
|
||||
pub fn assertion_context(&self) -> String {
|
||||
self.assertion_context.context()
|
||||
}
|
||||
}
|
||||
|
||||
impl BorrowAppContext for TestAppContext {
|
||||
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
|
||||
self.cx.borrow().read_with(f)
|
||||
}
|
||||
|
||||
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
|
||||
self.cx.borrow_mut().update(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorrowWindowContext for TestAppContext {
|
||||
type Result<T> = T;
|
||||
|
||||
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
|
||||
self.cx
|
||||
.borrow()
|
||||
.read_window(window, f)
|
||||
.expect("window was closed")
|
||||
}
|
||||
|
||||
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&WindowContext) -> Option<T>,
|
||||
{
|
||||
BorrowWindowContext::read_window(self, window, f)
|
||||
}
|
||||
|
||||
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
f: F,
|
||||
) -> T {
|
||||
self.cx
|
||||
.borrow_mut()
|
||||
.update_window(window, f)
|
||||
.expect("window was closed")
|
||||
}
|
||||
|
||||
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut WindowContext) -> Option<T>,
|
||||
{
|
||||
BorrowWindowContext::update_window(self, window, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Entity> ModelHandle<T> {
|
||||
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
let mut cx = cx.cx.borrow_mut();
|
||||
let subscription = cx.observe(self, move |_, _| {
|
||||
tx.unbounded_send(()).ok();
|
||||
});
|
||||
|
||||
let duration = if std::env::var("CI").is_ok() {
|
||||
Duration::from_secs(5)
|
||||
} else {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
let executor = cx.background().clone();
|
||||
async move {
|
||||
executor.start_waiting();
|
||||
let notification = crate::util::timeout(duration, rx.next())
|
||||
.await
|
||||
.expect("next notification timed out");
|
||||
drop(subscription);
|
||||
notification.expect("model dropped while test was waiting for its next notification")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
|
||||
where
|
||||
T::Event: Clone,
|
||||
{
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
let mut cx = cx.cx.borrow_mut();
|
||||
let subscription = cx.subscribe(self, move |_, event, _| {
|
||||
tx.unbounded_send(event.clone()).ok();
|
||||
});
|
||||
|
||||
let duration = if std::env::var("CI").is_ok() {
|
||||
Duration::from_secs(5)
|
||||
} else {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
cx.foreground.start_waiting();
|
||||
async move {
|
||||
let event = crate::util::timeout(duration, rx.next())
|
||||
.await
|
||||
.expect("next event timed out");
|
||||
drop(subscription);
|
||||
event.expect("model dropped while test was waiting for its next event")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn condition(
|
||||
&self,
|
||||
cx: &TestAppContext,
|
||||
mut predicate: impl FnMut(&T, &AppContext) -> bool,
|
||||
) -> impl Future<Output = ()> {
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
let mut cx = cx.cx.borrow_mut();
|
||||
let subscriptions = (
|
||||
cx.observe(self, {
|
||||
let tx = tx.clone();
|
||||
move |_, _| {
|
||||
tx.unbounded_send(()).ok();
|
||||
}
|
||||
}),
|
||||
cx.subscribe(self, {
|
||||
move |_, _, _| {
|
||||
tx.unbounded_send(()).ok();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
|
||||
let handle = self.downgrade();
|
||||
let duration = if std::env::var("CI").is_ok() {
|
||||
Duration::from_secs(5)
|
||||
} else {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
async move {
|
||||
crate::util::timeout(duration, async move {
|
||||
loop {
|
||||
{
|
||||
let cx = cx.borrow();
|
||||
let cx = &*cx;
|
||||
if predicate(
|
||||
handle
|
||||
.upgrade(cx)
|
||||
.expect("model dropped with pending condition")
|
||||
.read(cx),
|
||||
cx,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cx.borrow().foreground().start_waiting();
|
||||
rx.next()
|
||||
.await
|
||||
.expect("model dropped with pending condition");
|
||||
cx.borrow().foreground().finish_waiting();
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("condition timed out");
|
||||
drop(subscriptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyWindowHandle {
|
||||
pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
|
||||
let window = self.platform_window_mut(cx);
|
||||
let prompts = window.pending_prompts.borrow_mut();
|
||||
!prompts.is_empty()
|
||||
}
|
||||
|
||||
pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
|
||||
self.platform_window_mut(cx).title.clone()
|
||||
}
|
||||
|
||||
pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
|
||||
let handler = self.platform_window_mut(cx).should_close_handler.take();
|
||||
if let Some(mut handler) = handler {
|
||||
let should_close = handler();
|
||||
self.platform_window_mut(cx).should_close_handler = Some(handler);
|
||||
should_close
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
|
||||
let mut window = self.platform_window_mut(cx);
|
||||
window.size = size;
|
||||
let mut handlers = mem::take(&mut window.resize_handlers);
|
||||
drop(window);
|
||||
for handler in &mut handlers {
|
||||
handler();
|
||||
}
|
||||
self.platform_window_mut(cx).resize_handlers = handlers;
|
||||
}
|
||||
|
||||
pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
|
||||
self.platform_window_mut(cx).edited
|
||||
}
|
||||
|
||||
pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
|
||||
use postage::prelude::Sink as _;
|
||||
|
||||
let mut done_tx = self
|
||||
.platform_window_mut(cx)
|
||||
.pending_prompts
|
||||
.borrow_mut()
|
||||
.pop_front()
|
||||
.expect("prompt was not called");
|
||||
done_tx.try_send(answer).ok();
|
||||
}
|
||||
|
||||
fn platform_window_mut<'a>(
|
||||
&self,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> std::cell::RefMut<'a, platform::test::Window> {
|
||||
std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
|
||||
let window = state.windows.get_mut(&self).unwrap();
|
||||
let test_window = window
|
||||
.platform_window
|
||||
.as_any_mut()
|
||||
.downcast_mut::<platform::test::Window>()
|
||||
.unwrap();
|
||||
test_window
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: View> ViewHandle<T> {
|
||||
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
|
||||
use postage::prelude::{Sink as _, Stream as _};
|
||||
|
||||
let (mut tx, mut rx) = postage::mpsc::channel(1);
|
||||
let mut cx = cx.cx.borrow_mut();
|
||||
let subscription = cx.observe(self, move |_, _| {
|
||||
tx.try_send(()).ok();
|
||||
});
|
||||
|
||||
let duration = if std::env::var("CI").is_ok() {
|
||||
Duration::from_secs(5)
|
||||
} else {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
async move {
|
||||
let notification = crate::util::timeout(duration, rx.recv())
|
||||
.await
|
||||
.expect("next notification timed out");
|
||||
drop(subscription);
|
||||
notification.expect("model dropped while test was waiting for its next notification")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn condition(
|
||||
&self,
|
||||
cx: &TestAppContext,
|
||||
mut predicate: impl FnMut(&T, &AppContext) -> bool,
|
||||
) -> impl Future<Output = ()> {
|
||||
use postage::prelude::{Sink as _, Stream as _};
|
||||
|
||||
let (tx, mut rx) = postage::mpsc::channel(1024);
|
||||
let timeout_duration = cx.condition_duration();
|
||||
|
||||
let mut cx = cx.cx.borrow_mut();
|
||||
let subscriptions = (
|
||||
cx.observe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
cx.subscribe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
|
||||
let handle = self.downgrade();
|
||||
|
||||
async move {
|
||||
crate::util::timeout(timeout_duration, async move {
|
||||
loop {
|
||||
{
|
||||
let cx = cx.borrow();
|
||||
let cx = &*cx;
|
||||
if predicate(
|
||||
handle
|
||||
.upgrade(cx)
|
||||
.expect("view dropped with pending condition")
|
||||
.read(cx),
|
||||
cx,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cx.borrow().foreground().start_waiting();
|
||||
rx.recv()
|
||||
.await
|
||||
.expect("view dropped with pending condition");
|
||||
cx.borrow().foreground().finish_waiting();
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("condition timed out");
|
||||
drop(subscriptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks string context to be printed when assertions fail.
|
||||
/// Often this is done by storing a context string in the manager and returning the handle.
|
||||
#[derive(Clone)]
|
||||
pub struct AssertionContextManager {
|
||||
id: Arc<AtomicUsize>,
|
||||
contexts: Arc<RwLock<BTreeMap<usize, String>>>,
|
||||
}
|
||||
|
||||
impl AssertionContextManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: Arc::new(AtomicUsize::new(0)),
|
||||
contexts: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_context(&self, context: String) -> ContextHandle {
|
||||
let id = self.id.fetch_add(1, Ordering::Relaxed);
|
||||
let mut contexts = self.contexts.write();
|
||||
contexts.insert(id, context);
|
||||
ContextHandle {
|
||||
id,
|
||||
manager: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> String {
|
||||
let contexts = self.contexts.read();
|
||||
format!("\n{}\n", contexts.values().join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
|
||||
/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
|
||||
/// the state that was set initially for the failure can be printed in the error message
|
||||
pub struct ContextHandle {
|
||||
id: usize,
|
||||
manager: AssertionContextManager,
|
||||
}
|
||||
|
||||
impl Drop for ContextHandle {
|
||||
fn drop(&mut self) {
|
||||
let mut contexts = self.manager.contexts.write();
|
||||
contexts.remove(&self.id);
|
||||
}
|
||||
}
|
737
crates/gpui/src/app/test_context.rs
Normal file
737
crates/gpui/src/app/test_context.rs
Normal file
|
@ -0,0 +1,737 @@
|
|||
use crate::{
|
||||
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||
BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
|
||||
InputEvent, IntoElement, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
|
||||
PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
|
||||
TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
|
||||
WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestAppContext {
|
||||
pub app: Rc<AppCell>,
|
||||
pub background_executor: BackgroundExecutor,
|
||||
pub foreground_executor: ForegroundExecutor,
|
||||
pub dispatcher: TestDispatcher,
|
||||
pub test_platform: Rc<TestPlatform>,
|
||||
text_system: Arc<TextSystem>,
|
||||
}
|
||||
|
||||
impl Context for TestAppContext {
|
||||
type Result<T> = T;
|
||||
|
||||
fn new_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let mut app = self.app.borrow_mut();
|
||||
app.new_model(build_model)
|
||||
}
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
let mut app = self.app.borrow_mut();
|
||||
app.update_model(handle, update)
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
let mut lock = self.app.borrow_mut();
|
||||
lock.update_window(window, f)
|
||||
}
|
||||
|
||||
fn read_model<T, R>(
|
||||
&self,
|
||||
handle: &Model<T>,
|
||||
read: impl FnOnce(&T, &AppContext) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.borrow();
|
||||
app.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.borrow();
|
||||
app.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestAppContext {
|
||||
pub fn new(dispatcher: TestDispatcher) -> Self {
|
||||
let arc_dispatcher = Arc::new(dispatcher.clone());
|
||||
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
|
||||
let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
|
||||
let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
|
||||
let asset_source = Arc::new(());
|
||||
let http_client = util::http::FakeHttpClient::with_404_response();
|
||||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||
|
||||
Self {
|
||||
app: AppContext::new(platform.clone(), asset_source, http_client),
|
||||
background_executor,
|
||||
foreground_executor,
|
||||
dispatcher: dispatcher.clone(),
|
||||
test_platform: platform,
|
||||
text_system,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_app(&self) -> TestAppContext {
|
||||
Self::new(self.dispatcher.clone())
|
||||
}
|
||||
|
||||
pub fn quit(&self) {
|
||||
self.app.borrow_mut().shutdown();
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> Result<()> {
|
||||
let mut app = self.app.borrow_mut();
|
||||
app.refresh();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn executor(&self) -> BackgroundExecutor {
|
||||
self.background_executor.clone()
|
||||
}
|
||||
|
||||
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
||||
&self.foreground_executor
|
||||
}
|
||||
|
||||
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
|
||||
let mut cx = self.app.borrow_mut();
|
||||
cx.update(f)
|
||||
}
|
||||
|
||||
pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
|
||||
let cx = self.app.borrow();
|
||||
f(&*cx)
|
||||
}
|
||||
|
||||
pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
|
||||
where
|
||||
F: FnOnce(&mut ViewContext<V>) -> V,
|
||||
V: 'static + Render,
|
||||
{
|
||||
let mut cx = self.app.borrow_mut();
|
||||
cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
|
||||
}
|
||||
|
||||
pub fn add_empty_window(&mut self) -> AnyWindowHandle {
|
||||
let mut cx = self.app.borrow_mut();
|
||||
cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
|
||||
.any_handle
|
||||
}
|
||||
|
||||
pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
|
||||
where
|
||||
F: FnOnce(&mut ViewContext<V>) -> V,
|
||||
V: 'static + Render,
|
||||
{
|
||||
let mut cx = self.app.borrow_mut();
|
||||
let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window));
|
||||
drop(cx);
|
||||
let view = window.root_view(self).unwrap();
|
||||
let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
|
||||
// it might be nice to try and cleanup these at the end of each test.
|
||||
(view, Box::leak(cx))
|
||||
}
|
||||
|
||||
pub fn text_system(&self) -> &Arc<TextSystem> {
|
||||
&self.text_system
|
||||
}
|
||||
|
||||
pub fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
self.test_platform.write_to_clipboard(item)
|
||||
}
|
||||
|
||||
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
self.test_platform.read_from_clipboard()
|
||||
}
|
||||
|
||||
pub fn simulate_new_path_selection(
|
||||
&self,
|
||||
select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
|
||||
) {
|
||||
self.test_platform.simulate_new_path_selection(select_path);
|
||||
}
|
||||
|
||||
pub fn simulate_prompt_answer(&self, button_ix: usize) {
|
||||
self.test_platform.simulate_prompt_answer(button_ix);
|
||||
}
|
||||
|
||||
pub fn has_pending_prompt(&self) -> bool {
|
||||
self.test_platform.has_pending_prompt()
|
||||
}
|
||||
|
||||
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
|
||||
let (mut handlers, scale_factor) = self
|
||||
.app
|
||||
.borrow_mut()
|
||||
.update_window(window_handle, |_, cx| {
|
||||
let platform_window = cx.window.platform_window.as_test().unwrap();
|
||||
let scale_factor = platform_window.scale_factor();
|
||||
match &mut platform_window.bounds {
|
||||
WindowBounds::Fullscreen | WindowBounds::Maximized => {
|
||||
platform_window.bounds = WindowBounds::Fixed(Bounds {
|
||||
origin: Point::default(),
|
||||
size: size.map(|pixels| f64::from(pixels).into()),
|
||||
});
|
||||
}
|
||||
WindowBounds::Fixed(bounds) => {
|
||||
bounds.size = size.map(|pixels| f64::from(pixels).into());
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
mem::take(&mut platform_window.handlers.lock().resize),
|
||||
scale_factor,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
for handler in &mut handlers {
|
||||
handler(size, scale_factor);
|
||||
}
|
||||
|
||||
self.app
|
||||
.borrow_mut()
|
||||
.update_window(window_handle, |_, cx| {
|
||||
let platform_window = cx.window.platform_window.as_test().unwrap();
|
||||
platform_window.handlers.lock().resize = handlers;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
self.foreground_executor.spawn(f(self.to_async()))
|
||||
}
|
||||
|
||||
pub fn has_global<G: 'static>(&self) -> bool {
|
||||
let app = self.app.borrow();
|
||||
app.has_global::<G>()
|
||||
}
|
||||
|
||||
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
|
||||
let app = self.app.borrow();
|
||||
read(app.global(), &app)
|
||||
}
|
||||
|
||||
pub fn try_read_global<G: 'static, R>(
|
||||
&self,
|
||||
read: impl FnOnce(&G, &AppContext) -> R,
|
||||
) -> Option<R> {
|
||||
let lock = self.app.borrow();
|
||||
Some(read(lock.try_global()?, &lock))
|
||||
}
|
||||
|
||||
pub fn set_global<G: 'static>(&mut self, global: G) {
|
||||
let mut lock = self.app.borrow_mut();
|
||||
lock.set_global(global);
|
||||
}
|
||||
|
||||
pub fn update_global<G: 'static, R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(&mut G, &mut AppContext) -> R,
|
||||
) -> R {
|
||||
let mut lock = self.app.borrow_mut();
|
||||
lock.update_global(update)
|
||||
}
|
||||
|
||||
pub fn to_async(&self) -> AsyncAppContext {
|
||||
AsyncAppContext {
|
||||
app: Rc::downgrade(&self.app),
|
||||
background_executor: self.background_executor.clone(),
|
||||
foreground_executor: self.foreground_executor.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
|
||||
where
|
||||
A: Action,
|
||||
{
|
||||
window
|
||||
.update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
.unwrap();
|
||||
|
||||
self.background_executor.run_until_parked()
|
||||
}
|
||||
|
||||
/// simulate_keystrokes takes a space-separated list of keys to type.
|
||||
/// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
|
||||
/// will run backspace on the current editor through the command palette.
|
||||
pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
|
||||
for keystroke in keystrokes
|
||||
.split(" ")
|
||||
.map(Keystroke::parse)
|
||||
.map(Result::unwrap)
|
||||
{
|
||||
self.dispatch_keystroke(window, keystroke.into(), false);
|
||||
}
|
||||
|
||||
self.background_executor.run_until_parked()
|
||||
}
|
||||
|
||||
/// simulate_input takes a string of text to type.
|
||||
/// cx.simulate_input("abc")
|
||||
/// will type abc into your current editor.
|
||||
pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
|
||||
for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
|
||||
self.dispatch_keystroke(window, keystroke.into(), false);
|
||||
}
|
||||
|
||||
self.background_executor.run_until_parked()
|
||||
}
|
||||
|
||||
pub fn dispatch_keystroke(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
keystroke: Keystroke,
|
||||
is_held: bool,
|
||||
) {
|
||||
let keystroke2 = keystroke.clone();
|
||||
let handled = window
|
||||
.update(self, |_, cx| {
|
||||
cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
|
||||
})
|
||||
.is_ok_and(|handled| handled);
|
||||
if handled {
|
||||
return;
|
||||
}
|
||||
|
||||
let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
|
||||
let Some(input_handler) = input_handler else {
|
||||
panic!(
|
||||
"dispatch_keystroke {:?} failed to dispatch action or input",
|
||||
&keystroke2
|
||||
);
|
||||
};
|
||||
let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
|
||||
input_handler.lock().replace_text_in_range(None, &text);
|
||||
}
|
||||
|
||||
pub fn update_test_window<R>(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
f: impl FnOnce(&mut TestWindow) -> R,
|
||||
) -> R {
|
||||
window
|
||||
.update(self, |_, cx| {
|
||||
f(cx.window
|
||||
.platform_window
|
||||
.as_any_mut()
|
||||
.downcast_mut::<TestWindow>()
|
||||
.unwrap())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
self.update(|cx| {
|
||||
cx.observe(entity, {
|
||||
let tx = tx.clone();
|
||||
move |_, _| {
|
||||
let _ = tx.unbounded_send(());
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
cx.observe_release(entity, move |_, _| tx.close_channel())
|
||||
.detach()
|
||||
});
|
||||
rx
|
||||
}
|
||||
|
||||
pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
|
||||
&mut self,
|
||||
entity: &Model<T>,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<Evt>
|
||||
where
|
||||
Evt: 'static + Clone,
|
||||
{
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
entity
|
||||
.update(self, |_, cx: &mut ModelContext<T>| {
|
||||
cx.subscribe(entity, move |_model, _handle, event, _cx| {
|
||||
let _ = tx.unbounded_send(event.clone());
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
rx
|
||||
}
|
||||
|
||||
pub async fn condition<T: 'static>(
|
||||
&mut self,
|
||||
model: &Model<T>,
|
||||
mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
|
||||
) {
|
||||
let timer = self.executor().timer(Duration::from_secs(3));
|
||||
let mut notifications = self.notifications(model);
|
||||
|
||||
use futures::FutureExt as _;
|
||||
use smol::future::FutureExt as _;
|
||||
|
||||
async {
|
||||
loop {
|
||||
if model.update(self, &mut predicate) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if notifications.next().await.is_none() {
|
||||
bail!("model dropped")
|
||||
}
|
||||
}
|
||||
}
|
||||
.race(timer.map(|_| Err(anyhow!("condition timed out"))))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> Model<T> {
|
||||
pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
|
||||
where
|
||||
Evt: Send + Clone + 'static,
|
||||
T: EventEmitter<Evt>,
|
||||
{
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
let _subscription = self.update(cx, |_, cx| {
|
||||
cx.subscribe(self, move |_, _, event, _| {
|
||||
tx.unbounded_send(event.clone()).ok();
|
||||
})
|
||||
});
|
||||
|
||||
// Run other tasks until the event is emitted.
|
||||
loop {
|
||||
match rx.try_next() {
|
||||
Ok(Some(event)) => return event,
|
||||
Ok(None) => panic!("model was dropped"),
|
||||
Err(_) => {
|
||||
if !cx.executor().tick() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("no event received")
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> View<V> {
|
||||
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
|
||||
use postage::prelude::{Sink as _, Stream as _};
|
||||
|
||||
let (mut tx, mut rx) = postage::mpsc::channel(1);
|
||||
let mut cx = cx.app.app.borrow_mut();
|
||||
let subscription = cx.observe(self, move |_, _| {
|
||||
tx.try_send(()).ok();
|
||||
});
|
||||
|
||||
let duration = if std::env::var("CI").is_ok() {
|
||||
Duration::from_secs(5)
|
||||
} else {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
async move {
|
||||
let notification = crate::util::timeout(duration, rx.recv())
|
||||
.await
|
||||
.expect("next notification timed out");
|
||||
drop(subscription);
|
||||
notification.expect("model dropped while test was waiting for its next notification")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> View<V> {
|
||||
pub fn condition<Evt>(
|
||||
&self,
|
||||
cx: &TestAppContext,
|
||||
mut predicate: impl FnMut(&V, &AppContext) -> bool,
|
||||
) -> impl Future<Output = ()>
|
||||
where
|
||||
Evt: 'static,
|
||||
V: EventEmitter<Evt>,
|
||||
{
|
||||
use postage::prelude::{Sink as _, Stream as _};
|
||||
|
||||
let (tx, mut rx) = postage::mpsc::channel(1024);
|
||||
let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
|
||||
|
||||
let mut cx = cx.app.borrow_mut();
|
||||
let subscriptions = (
|
||||
cx.observe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
cx.subscribe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _: &Evt, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let cx = cx.this.upgrade().unwrap();
|
||||
let handle = self.downgrade();
|
||||
|
||||
async move {
|
||||
crate::util::timeout(timeout_duration, async move {
|
||||
loop {
|
||||
{
|
||||
let cx = cx.borrow();
|
||||
let cx = &*cx;
|
||||
if predicate(
|
||||
handle
|
||||
.upgrade()
|
||||
.expect("view dropped with pending condition")
|
||||
.read(cx),
|
||||
cx,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// todo!(start_waiting)
|
||||
// cx.borrow().foreground_executor().start_waiting();
|
||||
rx.recv()
|
||||
.await
|
||||
.expect("view dropped with pending condition");
|
||||
// cx.borrow().foreground_executor().finish_waiting();
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("condition timed out");
|
||||
drop(subscriptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct VisualTestContext<'a> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
cx: &'a mut TestAppContext,
|
||||
window: AnyWindowHandle,
|
||||
}
|
||||
|
||||
impl<'a> VisualTestContext<'a> {
|
||||
pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
|
||||
self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
|
||||
}
|
||||
|
||||
pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
|
||||
Self { cx, window }
|
||||
}
|
||||
|
||||
pub fn run_until_parked(&self) {
|
||||
self.cx.background_executor.run_until_parked();
|
||||
}
|
||||
|
||||
pub fn dispatch_action<A>(&mut self, action: A)
|
||||
where
|
||||
A: Action,
|
||||
{
|
||||
self.cx.dispatch_action(self.window, action)
|
||||
}
|
||||
|
||||
pub fn window_title(&mut self) -> Option<String> {
|
||||
self.cx
|
||||
.update_window(self.window, |_, cx| {
|
||||
cx.window.platform_window.as_test().unwrap().title.clone()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
|
||||
self.cx.simulate_keystrokes(self.window, keystrokes)
|
||||
}
|
||||
|
||||
pub fn simulate_input(&mut self, input: &str) {
|
||||
self.cx.simulate_input(self.window, input)
|
||||
}
|
||||
|
||||
pub fn simulate_activation(&mut self) {
|
||||
self.simulate_window_events(&mut |handlers| {
|
||||
handlers
|
||||
.active_status_change
|
||||
.iter_mut()
|
||||
.for_each(|f| f(true));
|
||||
})
|
||||
}
|
||||
|
||||
pub fn simulate_deactivation(&mut self) {
|
||||
self.simulate_window_events(&mut |handlers| {
|
||||
handlers
|
||||
.active_status_change
|
||||
.iter_mut()
|
||||
.for_each(|f| f(false));
|
||||
})
|
||||
}
|
||||
|
||||
fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
|
||||
let handlers = self
|
||||
.cx
|
||||
.update_window(self.window, |_, cx| {
|
||||
cx.window
|
||||
.platform_window
|
||||
.as_test()
|
||||
.unwrap()
|
||||
.handlers
|
||||
.clone()
|
||||
})
|
||||
.unwrap();
|
||||
f(&mut *handlers.lock());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Context for VisualTestContext<'a> {
|
||||
type Result<T> = <TestAppContext as Context>::Result<T>;
|
||||
|
||||
fn new_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>> {
|
||||
self.cx.new_model(build_model)
|
||||
}
|
||||
|
||||
fn update_model<T, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.cx.update_model(handle, update)
|
||||
}
|
||||
|
||||
fn read_model<T, R>(
|
||||
&self,
|
||||
handle: &Model<T>,
|
||||
read: impl FnOnce(&T, &AppContext) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.cx.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
self.cx.update_window(window, f)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.cx.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VisualContext for VisualTestContext<'a> {
|
||||
fn new_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
self.window
|
||||
.update(self.cx, |_, cx| cx.new_view(build_view))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.window
|
||||
.update(self.cx, |_, cx| cx.update_view(view, update))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
self.window
|
||||
.update(self.cx, |_, cx| cx.replace_root_view(build_view))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
|
||||
self.window
|
||||
.update(self.cx, |_, cx| {
|
||||
view.read(cx).focus_handle(cx).clone().focus(cx)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||
where
|
||||
V: crate::ManagedView,
|
||||
{
|
||||
self.window
|
||||
.update(self.cx, |_, cx| {
|
||||
view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyWindowHandle {
|
||||
pub fn build_view<V: Render + 'static>(
|
||||
&self,
|
||||
cx: &mut TestAppContext,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> View<V> {
|
||||
self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmptyView {}
|
||||
|
||||
impl Render for EmptyView {
|
||||
fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,90 +0,0 @@
|
|||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
|
||||
use crate::{platform::InputHandler, window::WindowContext, AnyView, AnyWindowHandle, AppContext};
|
||||
|
||||
pub struct WindowInputHandler {
|
||||
pub app: Rc<RefCell<AppContext>>,
|
||||
pub window: AnyWindowHandle,
|
||||
}
|
||||
|
||||
impl WindowInputHandler {
|
||||
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&dyn AnyView, &WindowContext) -> T,
|
||||
{
|
||||
// Input-related application hooks are sometimes called by the OS during
|
||||
// a call to a window-manipulation API, like prompting the user for file
|
||||
// paths. In that case, the AppContext will already be borrowed, so any
|
||||
// InputHandler methods need to fail gracefully.
|
||||
//
|
||||
// See https://github.com/zed-industries/community/issues/444
|
||||
let mut app = self.app.try_borrow_mut().ok()?;
|
||||
self.window.update_optional(&mut *app, |cx| {
|
||||
let view_id = cx.window.focused_view_id?;
|
||||
let view = cx.views.get(&(self.window, view_id))?;
|
||||
let result = f(view.as_ref(), &cx);
|
||||
Some(result)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
|
||||
{
|
||||
let mut app = self.app.try_borrow_mut().ok()?;
|
||||
self.window
|
||||
.update(&mut *app, |cx| {
|
||||
let view_id = cx.window.focused_view_id?;
|
||||
cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl InputHandler for WindowInputHandler {
|
||||
fn text_for_range(&self, range: Range<usize>) -> Option<String> {
|
||||
self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn selected_text_range(&self) -> Option<Range<usize>> {
|
||||
self.read_focused_view(|view, cx| view.selected_text_range(cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
|
||||
self.update_focused_view(|view, cx, view_id| {
|
||||
view.replace_text_in_range(range, text, cx, view_id);
|
||||
});
|
||||
}
|
||||
|
||||
fn marked_text_range(&self) -> Option<Range<usize>> {
|
||||
self.read_focused_view(|view, cx| view.marked_text_range(cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self) {
|
||||
self.update_focused_view(|view, cx, view_id| {
|
||||
view.unmark_text(cx, view_id);
|
||||
});
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
) {
|
||||
self.update_focused_view(|view, cx, view_id| {
|
||||
view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
|
||||
});
|
||||
}
|
||||
|
||||
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
|
||||
self.window.read_optional_with(&*self.app.borrow(), |cx| {
|
||||
cx.rect_for_text_range(range_utf16)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue