Merge branch 'main' into collab_ui2
This commit is contained in:
+118
-131
@@ -1,10 +1,12 @@
|
||||
use crate::SharedString;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::HashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
|
||||
use serde::Deserialize;
|
||||
use std::any::{type_name, Any, TypeId};
|
||||
pub use no_action::NoAction;
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
/// Actions are used to implement keyboard-driven UI.
|
||||
/// When you declare an action, you can bind keys to the action in the keymap and
|
||||
@@ -15,24 +17,16 @@ use std::any::{type_name, Any, TypeId};
|
||||
/// ```rust
|
||||
/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
|
||||
/// ```
|
||||
/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro,
|
||||
/// it will automatically
|
||||
/// More complex data types can also be actions. If you annotate your type with the action derive macro
|
||||
/// it will be implemented and registered automatically.
|
||||
/// ```
|
||||
/// #[action]
|
||||
/// #[derive(Clone, PartialEq, serde_derive::Deserialize, Action)]
|
||||
/// pub struct SelectNext {
|
||||
/// pub replace_newest: bool,
|
||||
/// }
|
||||
///
|
||||
/// Any type A that satisfies the following bounds is automatically an action:
|
||||
///
|
||||
/// ```
|
||||
/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
|
||||
/// ```
|
||||
///
|
||||
/// The `#[action]` annotation will derive these implementations for your struct automatically. If you
|
||||
/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only
|
||||
/// generates the code needed to register your action before `main`. Then you'll need to implement all
|
||||
/// the traits manually.
|
||||
/// If you want to control the behavior of the action trait manually, you can use the lower-level `#[register_action]`
|
||||
/// macro, which only generates the code needed to register your action before `main`.
|
||||
///
|
||||
/// ```
|
||||
/// #[gpui::register_action]
|
||||
@@ -41,77 +35,29 @@ use std::any::{type_name, Any, TypeId};
|
||||
/// pub content: SharedString,
|
||||
/// }
|
||||
///
|
||||
/// impl std::default::Default for Paste {
|
||||
/// fn default() -> Self {
|
||||
/// Self {
|
||||
/// content: SharedString::from("🍝"),
|
||||
/// }
|
||||
/// }
|
||||
/// impl gpui::Action for Paste {
|
||||
/// ///...
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Action: std::fmt::Debug + 'static {
|
||||
fn qualified_name() -> SharedString
|
||||
where
|
||||
Self: Sized;
|
||||
fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized;
|
||||
fn is_registered() -> bool
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn partial_eq(&self, action: &dyn Action) -> bool;
|
||||
pub trait Action: 'static {
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn partial_eq(&self, action: &dyn Action) -> bool;
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn debug_name() -> &'static str
|
||||
where
|
||||
Self: Sized;
|
||||
fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
// Types become actions by satisfying a list of trait bounds.
|
||||
impl<A> Action for A
|
||||
where
|
||||
A: for<'a> Deserialize<'a> + PartialEq + Default + Clone + std::fmt::Debug + 'static,
|
||||
{
|
||||
fn qualified_name() -> SharedString {
|
||||
let name = type_name::<A>();
|
||||
let mut separator_matches = name.rmatch_indices("::");
|
||||
separator_matches.next().unwrap();
|
||||
let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
|
||||
// todo!() remove the 2 replacement when migration is done
|
||||
name[name_start_ix..].replace("2::", "::").into()
|
||||
}
|
||||
|
||||
fn build(params: Option<serde_json::Value>) -> Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let action = if let Some(params) = params {
|
||||
serde_json::from_value(params).context("failed to deserialize action")?
|
||||
} else {
|
||||
Self::default()
|
||||
};
|
||||
Ok(Box::new(action))
|
||||
}
|
||||
|
||||
fn is_registered() -> bool {
|
||||
ACTION_REGISTRY
|
||||
.read()
|
||||
.names_by_type_id
|
||||
.get(&TypeId::of::<A>())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn partial_eq(&self, action: &dyn Action) -> bool {
|
||||
action
|
||||
.as_any()
|
||||
.downcast_ref::<Self>()
|
||||
.map_or(false, |a| self == a)
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> Box<dyn Action> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
impl std::fmt::Debug for dyn Action {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("dyn Action")
|
||||
.field("type_name", &self.name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,69 +65,93 @@ impl dyn Action {
|
||||
pub fn type_id(&self) -> TypeId {
|
||||
self.as_any().type_id()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> SharedString {
|
||||
ACTION_REGISTRY
|
||||
.read()
|
||||
.names_by_type_id
|
||||
.get(&self.type_id())
|
||||
.expect("type is not a registered action")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
|
||||
type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
|
||||
|
||||
lazy_static! {
|
||||
static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ActionRegistry {
|
||||
pub(crate) struct ActionRegistry {
|
||||
builders_by_name: HashMap<SharedString, ActionBuilder>,
|
||||
names_by_type_id: HashMap<TypeId, SharedString>,
|
||||
all_names: Vec<SharedString>, // So we can return a static slice.
|
||||
}
|
||||
|
||||
/// Register an action type to allow it to be referenced in keymaps.
|
||||
pub fn register_action<A: Action>() {
|
||||
let name = A::qualified_name();
|
||||
let mut lock = ACTION_REGISTRY.write();
|
||||
lock.builders_by_name.insert(name.clone(), A::build);
|
||||
lock.names_by_type_id
|
||||
.insert(TypeId::of::<A>(), name.clone());
|
||||
lock.all_names.push(name);
|
||||
impl Default for ActionRegistry {
|
||||
fn default() -> Self {
|
||||
let mut this = ActionRegistry {
|
||||
builders_by_name: Default::default(),
|
||||
names_by_type_id: Default::default(),
|
||||
all_names: Default::default(),
|
||||
};
|
||||
|
||||
this.load_actions();
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
||||
pub fn build_action_from_type(type_id: &TypeId) -> Result<Box<dyn Action>> {
|
||||
let lock = ACTION_REGISTRY.read();
|
||||
let name = lock
|
||||
.names_by_type_id
|
||||
.get(type_id)
|
||||
.ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?
|
||||
.clone();
|
||||
drop(lock);
|
||||
/// This type must be public so that our macros can build it in other crates.
|
||||
/// But this is an implementation detail and should not be used directly.
|
||||
#[doc(hidden)]
|
||||
pub type MacroActionBuilder = fn() -> ActionData;
|
||||
|
||||
build_action(&name, None)
|
||||
/// This type must be public so that our macros can build it in other crates.
|
||||
/// But this is an implementation detail and should not be used directly.
|
||||
#[doc(hidden)]
|
||||
pub struct ActionData {
|
||||
pub name: &'static str,
|
||||
pub type_id: TypeId,
|
||||
pub build: ActionBuilder,
|
||||
}
|
||||
|
||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
||||
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
|
||||
let lock = ACTION_REGISTRY.read();
|
||||
/// This constant must be public to be accessible from other crates.
|
||||
/// But it's existence is an implementation detail and should not be used directly.
|
||||
#[doc(hidden)]
|
||||
#[linkme::distributed_slice]
|
||||
pub static __GPUI_ACTIONS: [MacroActionBuilder];
|
||||
|
||||
let build_action = lock
|
||||
.builders_by_name
|
||||
.get(name)
|
||||
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
||||
(build_action)(params)
|
||||
}
|
||||
impl ActionRegistry {
|
||||
/// Load all registered actions into the registry.
|
||||
pub(crate) fn load_actions(&mut self) {
|
||||
for builder in __GPUI_ACTIONS {
|
||||
let action = builder();
|
||||
//todo(remove)
|
||||
let name: SharedString = remove_the_2(action.name).into();
|
||||
self.builders_by_name.insert(name.clone(), action.build);
|
||||
self.names_by_type_id.insert(action.type_id, name.clone());
|
||||
self.all_names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
|
||||
let lock = ACTION_REGISTRY.read();
|
||||
RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
|
||||
registry.all_names.as_slice()
|
||||
})
|
||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
||||
pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
|
||||
let name = self
|
||||
.names_by_type_id
|
||||
.get(type_id)
|
||||
.ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?
|
||||
.clone();
|
||||
|
||||
self.build_action(&name, None)
|
||||
}
|
||||
|
||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
||||
pub fn build_action(
|
||||
&self,
|
||||
name: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<Box<dyn Action>> {
|
||||
//todo(remove)
|
||||
let name = remove_the_2(name);
|
||||
let build_action = self
|
||||
.builders_by_name
|
||||
.get(name.deref())
|
||||
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
||||
(build_action)(params.unwrap_or_else(|| json!({})))
|
||||
.with_context(|| format!("Attempting to build action {}", name))
|
||||
}
|
||||
|
||||
pub fn all_action_names(&self) -> &[SharedString] {
|
||||
self.all_names.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines unit structs that can be used as actions.
|
||||
@@ -191,7 +161,7 @@ macro_rules! actions {
|
||||
() => {};
|
||||
|
||||
( $name:ident ) => {
|
||||
#[gpui::action]
|
||||
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize, gpui::Action)]
|
||||
pub struct $name;
|
||||
};
|
||||
|
||||
@@ -200,3 +170,20 @@ macro_rules! actions {
|
||||
actions!($($rest)*);
|
||||
};
|
||||
}
|
||||
|
||||
//todo!(remove)
|
||||
pub fn remove_the_2(action_name: &str) -> String {
|
||||
let mut separator_matches = action_name.rmatch_indices("::");
|
||||
separator_matches.next().unwrap();
|
||||
let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
|
||||
// todo!() remove the 2 replacement when migration is done
|
||||
action_name[name_start_ix..]
|
||||
.replace("2::", "::")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
mod no_action {
|
||||
use crate as gpui;
|
||||
|
||||
actions!(NoAction);
|
||||
}
|
||||
|
||||
+21
-6
@@ -14,12 +14,13 @@ use smallvec::SmallVec;
|
||||
pub use test_context::*;
|
||||
|
||||
use crate::{
|
||||
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
|
||||
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
||||
Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap,
|
||||
LayoutId, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
|
||||
Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext,
|
||||
Window, WindowContext, WindowHandle, WindowId,
|
||||
current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView,
|
||||
AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||
DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
|
||||
ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
|
||||
TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
|
||||
WindowHandle, WindowId,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
@@ -182,6 +183,7 @@ pub struct AppContext {
|
||||
text_system: Arc<TextSystem>,
|
||||
flushing_effects: bool,
|
||||
pending_updates: usize,
|
||||
pub(crate) actions: Rc<ActionRegistry>,
|
||||
pub(crate) active_drag: Option<AnyDrag>,
|
||||
pub(crate) active_tooltip: Option<AnyTooltip>,
|
||||
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
||||
@@ -240,6 +242,7 @@ impl AppContext {
|
||||
platform: platform.clone(),
|
||||
app_metadata,
|
||||
text_system,
|
||||
actions: Rc::new(ActionRegistry::default()),
|
||||
flushing_effects: false,
|
||||
pending_updates: 0,
|
||||
active_drag: None,
|
||||
@@ -964,6 +967,18 @@ impl AppContext {
|
||||
pub fn propagate(&mut self) {
|
||||
self.propagate_event = true;
|
||||
}
|
||||
|
||||
pub fn build_action(
|
||||
&self,
|
||||
name: &str,
|
||||
data: Option<serde_json::Value>,
|
||||
) -> Result<Box<dyn Action>> {
|
||||
self.actions.build_action(name, data)
|
||||
}
|
||||
|
||||
pub fn all_action_names(&self) -> &[SharedString] {
|
||||
self.actions.all_action_names()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
|
||||
@@ -370,10 +370,19 @@ impl<T: Send> Model<T> {
|
||||
})
|
||||
});
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
rx.try_next()
|
||||
.expect("no event received")
|
||||
.expect("model was dropped")
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -237,11 +237,11 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {
|
||||
//
|
||||
// if we are relying on this side-effect still, removing the debug_assert!
|
||||
// likely breaks the command_palette tests.
|
||||
debug_assert!(
|
||||
A::is_registered(),
|
||||
"{:?} is not registered as an action",
|
||||
A::qualified_name()
|
||||
);
|
||||
// debug_assert!(
|
||||
// A::is_registered(),
|
||||
// "{:?} is not registered as an action",
|
||||
// A::qualified_name()
|
||||
// );
|
||||
self.interactivity().action_listeners.push((
|
||||
TypeId::of::<A>(),
|
||||
Box::new(move |view, action, phase, cx| {
|
||||
|
||||
@@ -5,10 +5,11 @@ use std::{
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
task::{Context, Poll},
|
||||
@@ -71,30 +72,57 @@ impl<T> Future for Task<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TaskLabel(NonZeroUsize);
|
||||
|
||||
impl TaskLabel {
|
||||
pub fn new() -> Self {
|
||||
static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
|
||||
Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
|
||||
|
||||
type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
|
||||
|
||||
impl BackgroundExecutor {
|
||||
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
||||
Self { dispatcher }
|
||||
}
|
||||
|
||||
/// Enqueues the given closure to be run on any thread. The closure returns
|
||||
/// a future which will be run to completion on any available thread.
|
||||
/// Enqueues the given future to be run to completion on a background thread.
|
||||
pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.spawn_internal::<R>(Box::pin(future), None)
|
||||
}
|
||||
|
||||
/// Enqueues the given future to be run to completion on a background thread.
|
||||
/// The given label can be used to control the priority of the task in tests.
|
||||
pub fn spawn_labeled<R>(
|
||||
&self,
|
||||
label: TaskLabel,
|
||||
future: impl Future<Output = R> + Send + 'static,
|
||||
) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.spawn_internal::<R>(Box::pin(future), Some(label))
|
||||
}
|
||||
|
||||
fn spawn_internal<R: Send + 'static>(
|
||||
&self,
|
||||
future: AnyFuture<R>,
|
||||
label: Option<TaskLabel>,
|
||||
) -> Task<R> {
|
||||
let dispatcher = self.dispatcher.clone();
|
||||
fn inner<R: Send + 'static>(
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
future: AnyFuture<R>,
|
||||
) -> Task<R> {
|
||||
let (runnable, task) =
|
||||
async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
|
||||
runnable.schedule();
|
||||
Task::Spawned(task)
|
||||
}
|
||||
inner::<R>(dispatcher, Box::pin(future))
|
||||
let (runnable, task) =
|
||||
async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
|
||||
runnable.schedule();
|
||||
Task::Spawned(task)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -130,7 +158,7 @@ impl BackgroundExecutor {
|
||||
match future.as_mut().poll(&mut cx) {
|
||||
Poll::Ready(result) => return result,
|
||||
Poll::Pending => {
|
||||
if !self.dispatcher.poll(background_only) {
|
||||
if !self.dispatcher.tick(background_only) {
|
||||
if awoken.swap(false, SeqCst) {
|
||||
continue;
|
||||
}
|
||||
@@ -216,11 +244,21 @@ impl BackgroundExecutor {
|
||||
self.dispatcher.as_test().unwrap().simulate_random_delay()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn deprioritize(&self, task_label: TaskLabel) {
|
||||
self.dispatcher.as_test().unwrap().deprioritize(task_label)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn advance_clock(&self, duration: Duration) {
|
||||
self.dispatcher.as_test().unwrap().advance_clock(duration)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn tick(&self) -> bool {
|
||||
self.dispatcher.as_test().unwrap().tick(false)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn run_until_parked(&self) {
|
||||
self.dispatcher.as_test().unwrap().run_until_parked()
|
||||
|
||||
@@ -49,11 +49,13 @@ pub use input::*;
|
||||
pub use interactive::*;
|
||||
pub use key_dispatch::*;
|
||||
pub use keymap::*;
|
||||
pub use linkme;
|
||||
pub use platform::*;
|
||||
use private::Sealed;
|
||||
pub use refineable::*;
|
||||
pub use scene::*;
|
||||
pub use serde;
|
||||
pub use serde_derive;
|
||||
pub use serde_json;
|
||||
pub use smallvec;
|
||||
pub use smol::Timer;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
build_action_from_type, Action, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch,
|
||||
Keymap, Keystroke, KeystrokeMatcher, WindowContext,
|
||||
Action, ActionRegistry, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch, Keymap,
|
||||
Keystroke, KeystrokeMatcher, WindowContext,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
@@ -10,7 +10,6 @@ use std::{
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct DispatchNodeId(usize);
|
||||
@@ -22,6 +21,7 @@ pub(crate) struct DispatchTree {
|
||||
focusable_node_ids: HashMap<FocusId, DispatchNodeId>,
|
||||
keystroke_matchers: HashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
|
||||
keymap: Arc<Mutex<Keymap>>,
|
||||
action_registry: Rc<ActionRegistry>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -41,7 +41,7 @@ pub(crate) struct DispatchActionListener {
|
||||
}
|
||||
|
||||
impl DispatchTree {
|
||||
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
|
||||
pub fn new(keymap: Arc<Mutex<Keymap>>, action_registry: Rc<ActionRegistry>) -> Self {
|
||||
Self {
|
||||
node_stack: Vec::new(),
|
||||
context_stack: Vec::new(),
|
||||
@@ -49,6 +49,7 @@ impl DispatchTree {
|
||||
focusable_node_ids: HashMap::default(),
|
||||
keystroke_matchers: HashMap::default(),
|
||||
keymap,
|
||||
action_registry,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +154,9 @@ impl DispatchTree {
|
||||
for node_id in self.dispatch_path(*node) {
|
||||
let node = &self.nodes[node_id.0];
|
||||
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
||||
actions.extend(build_action_from_type(action_type).log_err());
|
||||
// Intentionally silence these errors without logging.
|
||||
// If an action cannot be built by default, it's not available.
|
||||
actions.extend(self.action_registry.build_action_type(action_type).ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke};
|
||||
use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke, NoAction};
|
||||
use collections::HashSet;
|
||||
use smallvec::SmallVec;
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub struct KeymapVersion(usize);
|
||||
@@ -37,20 +40,19 @@ impl Keymap {
|
||||
}
|
||||
|
||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||
// todo!("no action")
|
||||
// let no_action_id = (NoAction {}).id();
|
||||
let no_action_id = &(NoAction {}).type_id();
|
||||
let mut new_bindings = Vec::new();
|
||||
let has_new_disabled_keystrokes = false;
|
||||
let mut has_new_disabled_keystrokes = false;
|
||||
for binding in bindings {
|
||||
// if binding.action().id() == no_action_id {
|
||||
// has_new_disabled_keystrokes |= self
|
||||
// .disabled_keystrokes
|
||||
// .entry(binding.keystrokes)
|
||||
// .or_default()
|
||||
// .insert(binding.context_predicate);
|
||||
// } else {
|
||||
new_bindings.push(binding);
|
||||
// }
|
||||
if binding.action.type_id() == *no_action_id {
|
||||
has_new_disabled_keystrokes |= self
|
||||
.disabled_keystrokes
|
||||
.entry(binding.keystrokes)
|
||||
.or_default()
|
||||
.insert(binding.context_predicate);
|
||||
} else {
|
||||
new_bindings.push(binding);
|
||||
}
|
||||
}
|
||||
|
||||
if has_new_disabled_keystrokes {
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
point, size, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
|
||||
FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout,
|
||||
Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
|
||||
SharedString, Size,
|
||||
SharedString, Size, TaskLabel,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use async_task::Runnable;
|
||||
@@ -162,10 +162,10 @@ pub(crate) trait PlatformWindow {
|
||||
|
||||
pub trait PlatformDispatcher: Send + Sync {
|
||||
fn is_main_thread(&self) -> bool;
|
||||
fn dispatch(&self, runnable: Runnable);
|
||||
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable);
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
|
||||
fn poll(&self, background_only: bool) -> bool;
|
||||
fn tick(&self, background_only: bool) -> bool;
|
||||
fn park(&self);
|
||||
fn unparker(&self) -> Unparker;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::PlatformDispatcher;
|
||||
use crate::{PlatformDispatcher, TaskLabel};
|
||||
use async_task::Runnable;
|
||||
use objc::{
|
||||
class, msg_send,
|
||||
@@ -37,7 +37,7 @@ impl PlatformDispatcher for MacDispatcher {
|
||||
is_main_thread == YES
|
||||
}
|
||||
|
||||
fn dispatch(&self, runnable: Runnable) {
|
||||
fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
|
||||
@@ -71,7 +71,7 @@ impl PlatformDispatcher for MacDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&self, _background_only: bool) -> bool {
|
||||
fn tick(&self, _background_only: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::PlatformDispatcher;
|
||||
use crate::{PlatformDispatcher, TaskLabel};
|
||||
use async_task::Runnable;
|
||||
use backtrace::Backtrace;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use rand::prelude::*;
|
||||
@@ -28,12 +28,14 @@ struct TestDispatcherState {
|
||||
random: StdRng,
|
||||
foreground: HashMap<TestDispatcherId, VecDeque<Runnable>>,
|
||||
background: Vec<Runnable>,
|
||||
deprioritized_background: Vec<Runnable>,
|
||||
delayed: Vec<(Duration, Runnable)>,
|
||||
time: Duration,
|
||||
is_main_thread: bool,
|
||||
next_id: TestDispatcherId,
|
||||
allow_parking: bool,
|
||||
waiting_backtrace: Option<Backtrace>,
|
||||
deprioritized_task_labels: HashSet<TaskLabel>,
|
||||
}
|
||||
|
||||
impl TestDispatcher {
|
||||
@@ -43,12 +45,14 @@ impl TestDispatcher {
|
||||
random,
|
||||
foreground: HashMap::default(),
|
||||
background: Vec::new(),
|
||||
deprioritized_background: Vec::new(),
|
||||
delayed: Vec::new(),
|
||||
time: Duration::ZERO,
|
||||
is_main_thread: true,
|
||||
next_id: TestDispatcherId(1),
|
||||
allow_parking: false,
|
||||
waiting_backtrace: None,
|
||||
deprioritized_task_labels: Default::default(),
|
||||
};
|
||||
|
||||
TestDispatcher {
|
||||
@@ -101,8 +105,15 @@ impl TestDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deprioritize(&self, task_label: TaskLabel) {
|
||||
self.state
|
||||
.lock()
|
||||
.deprioritized_task_labels
|
||||
.insert(task_label);
|
||||
}
|
||||
|
||||
pub fn run_until_parked(&self) {
|
||||
while self.poll(false) {}
|
||||
while self.tick(false) {}
|
||||
}
|
||||
|
||||
pub fn parking_allowed(&self) -> bool {
|
||||
@@ -150,8 +161,17 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
self.state.lock().is_main_thread
|
||||
}
|
||||
|
||||
fn dispatch(&self, runnable: Runnable) {
|
||||
self.state.lock().background.push(runnable);
|
||||
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
|
||||
{
|
||||
let mut state = self.state.lock();
|
||||
if label.map_or(false, |label| {
|
||||
state.deprioritized_task_labels.contains(&label)
|
||||
}) {
|
||||
state.deprioritized_background.push(runnable);
|
||||
} else {
|
||||
state.background.push(runnable);
|
||||
}
|
||||
}
|
||||
self.unparker.unpark();
|
||||
}
|
||||
|
||||
@@ -174,7 +194,7 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
state.delayed.insert(ix, (next_time, runnable));
|
||||
}
|
||||
|
||||
fn poll(&self, background_only: bool) -> bool {
|
||||
fn tick(&self, background_only: bool) -> bool {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
while let Some((deadline, _)) = state.delayed.first() {
|
||||
@@ -196,34 +216,41 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
};
|
||||
let background_len = state.background.len();
|
||||
|
||||
let runnable;
|
||||
let main_thread;
|
||||
if foreground_len == 0 && background_len == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let main_thread = state.random.gen_ratio(
|
||||
foreground_len as u32,
|
||||
(foreground_len + background_len) as u32,
|
||||
);
|
||||
let was_main_thread = state.is_main_thread;
|
||||
state.is_main_thread = main_thread;
|
||||
|
||||
let runnable = if main_thread {
|
||||
let state = &mut *state;
|
||||
let runnables = state
|
||||
.foreground
|
||||
.values_mut()
|
||||
.filter(|runnables| !runnables.is_empty())
|
||||
.choose(&mut state.random)
|
||||
.unwrap();
|
||||
runnables.pop_front().unwrap()
|
||||
let deprioritized_background_len = state.deprioritized_background.len();
|
||||
if deprioritized_background_len == 0 {
|
||||
return false;
|
||||
}
|
||||
let ix = state.random.gen_range(0..deprioritized_background_len);
|
||||
main_thread = false;
|
||||
runnable = state.deprioritized_background.swap_remove(ix);
|
||||
} else {
|
||||
let ix = state.random.gen_range(0..background_len);
|
||||
state.background.swap_remove(ix)
|
||||
main_thread = state.random.gen_ratio(
|
||||
foreground_len as u32,
|
||||
(foreground_len + background_len) as u32,
|
||||
);
|
||||
if main_thread {
|
||||
let state = &mut *state;
|
||||
runnable = state
|
||||
.foreground
|
||||
.values_mut()
|
||||
.filter(|runnables| !runnables.is_empty())
|
||||
.choose(&mut state.random)
|
||||
.unwrap()
|
||||
.pop_front()
|
||||
.unwrap();
|
||||
} else {
|
||||
let ix = state.random.gen_range(0..background_len);
|
||||
runnable = state.background.swap_remove(ix);
|
||||
};
|
||||
};
|
||||
|
||||
let was_main_thread = state.is_main_thread;
|
||||
state.is_main_thread = main_thread;
|
||||
drop(state);
|
||||
runnable.run();
|
||||
|
||||
self.state.lock().is_main_thread = was_main_thread;
|
||||
|
||||
true
|
||||
|
||||
@@ -311,8 +311,8 @@ impl Window {
|
||||
layout_engine: TaffyLayoutEngine::new(),
|
||||
root_view: None,
|
||||
element_id_stack: GlobalElementId::default(),
|
||||
previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
|
||||
current_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
|
||||
previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
current_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
focus_listeners: SubscriberSet::new(),
|
||||
default_prevented: true,
|
||||
|
||||
Reference in New Issue
Block a user