Re-implement actions as derive macros instead of blanket impls
This commit is contained in:
parent
a0e976599c
commit
4de2c0f7ef
22 changed files with 360 additions and 288 deletions
|
@ -130,14 +130,12 @@ impl Render for CollabTitlebarItem {
|
||||||
.color(Some(TextColor::Muted)),
|
.color(Some(TextColor::Muted)),
|
||||||
)
|
)
|
||||||
.tooltip(move |_, cx| {
|
.tooltip(move |_, cx| {
|
||||||
// todo!() Replace with real action.
|
|
||||||
#[gpui::action]
|
|
||||||
struct NoAction {}
|
|
||||||
cx.build_view(|_| {
|
cx.build_view(|_| {
|
||||||
Tooltip::new("Recent Branches")
|
Tooltip::new("Recent Branches")
|
||||||
.key_binding(KeyBinding::new(gpui::KeyBinding::new(
|
.key_binding(KeyBinding::new(gpui::KeyBinding::new(
|
||||||
"cmd-b",
|
"cmd-b",
|
||||||
NoAction {},
|
// todo!() Replace with real action.
|
||||||
|
gpui::NoAction,
|
||||||
None,
|
None,
|
||||||
)))
|
)))
|
||||||
.meta("Only local branches shown")
|
.meta("Only local branches shown")
|
||||||
|
|
|
@ -39,7 +39,7 @@ use futures::FutureExt;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use git::diff_hunk_to_display;
|
use git::diff_hunk_to_display;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action, actions, div, point, prelude::*, px, relative, rems, size, uniform_list, AnyElement,
|
actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
|
||||||
AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
|
AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
|
||||||
EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
|
EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
|
||||||
Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,
|
Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,
|
||||||
|
@ -180,78 +180,78 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||||
// // .with_soft_wrap(true)
|
// // .with_soft_wrap(true)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct SelectNext {
|
pub struct SelectNext {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replace_newest: bool,
|
pub replace_newest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct SelectPrevious {
|
pub struct SelectPrevious {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replace_newest: bool,
|
pub replace_newest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct SelectAllMatches {
|
pub struct SelectAllMatches {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replace_newest: bool,
|
pub replace_newest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct SelectToBeginningOfLine {
|
pub struct SelectToBeginningOfLine {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
stop_at_soft_wraps: bool,
|
stop_at_soft_wraps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct MovePageUp {
|
pub struct MovePageUp {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
center_cursor: bool,
|
center_cursor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct MovePageDown {
|
pub struct MovePageDown {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
center_cursor: bool,
|
center_cursor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct SelectToEndOfLine {
|
pub struct SelectToEndOfLine {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
stop_at_soft_wraps: bool,
|
stop_at_soft_wraps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct ToggleCodeActions {
|
pub struct ToggleCodeActions {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub deployed_from_indicator: bool,
|
pub deployed_from_indicator: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct ConfirmCompletion {
|
pub struct ConfirmCompletion {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub item_ix: Option<usize>,
|
pub item_ix: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct ConfirmCodeAction {
|
pub struct ConfirmCodeAction {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub item_ix: Option<usize>,
|
pub item_ix: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct ToggleComments {
|
pub struct ToggleComments {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub advance_downwards: bool,
|
pub advance_downwards: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct FoldAt {
|
pub struct FoldAt {
|
||||||
pub buffer_row: u32,
|
pub buffer_row: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, Action)]
|
||||||
pub struct UnfoldAt {
|
pub struct UnfoldAt {
|
||||||
pub buffer_row: u32,
|
pub buffer_row: u32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use crate::SharedString;
|
use crate::SharedString;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use lazy_static::lazy_static;
|
pub use no_action::NoAction;
|
||||||
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
|
use serde_json::json;
|
||||||
use serde::Deserialize;
|
use std::any::{Any, TypeId};
|
||||||
use std::any::{type_name, Any, TypeId};
|
|
||||||
|
|
||||||
/// Actions are used to implement keyboard-driven UI.
|
/// Actions are used to implement keyboard-driven UI.
|
||||||
/// When you declare an action, you can bind keys to the action in the keymap and
|
/// When you declare an action, you can bind keys to the action in the keymap and
|
||||||
|
@ -15,24 +14,16 @@ use std::any::{type_name, Any, TypeId};
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
|
/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
|
||||||
/// ```
|
/// ```
|
||||||
/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro,
|
/// More complex data types can also be actions. If you annotate your type with the action derive macro
|
||||||
/// it will automatically
|
/// it will be implemented and registered automatically.
|
||||||
/// ```
|
/// ```
|
||||||
/// #[action]
|
/// #[derive(Clone, PartialEq, serde_derive::Deserialize, Action)]
|
||||||
/// pub struct SelectNext {
|
/// pub struct SelectNext {
|
||||||
/// pub replace_newest: bool,
|
/// pub replace_newest: bool,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// Any type A that satisfies the following bounds is automatically an action:
|
/// 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`.
|
||||||
/// ```
|
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #[gpui::register_action]
|
/// #[gpui::register_action]
|
||||||
|
@ -41,77 +32,29 @@ use std::any::{type_name, Any, TypeId};
|
||||||
/// pub content: SharedString,
|
/// pub content: SharedString,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl std::default::Default for Paste {
|
/// impl gpui::Action for Paste {
|
||||||
/// fn default() -> Self {
|
/// ///...
|
||||||
/// Self {
|
|
||||||
/// content: SharedString::from("🍝"),
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Action: std::fmt::Debug + 'static {
|
pub trait Action: '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;
|
|
||||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||||
fn as_any(&self) -> &dyn Any;
|
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 std::fmt::Debug for dyn Action {
|
||||||
impl<A> Action for A
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
where
|
f.debug_struct("dyn Action")
|
||||||
A: for<'a> Deserialize<'a> + PartialEq + Default + Clone + std::fmt::Debug + 'static,
|
.field("type_name", &self.name())
|
||||||
{
|
.finish()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,69 +62,90 @@ impl dyn Action {
|
||||||
pub fn type_id(&self) -> TypeId {
|
pub fn type_id(&self) -> TypeId {
|
||||||
self.as_any().type_id()
|
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! {
|
pub(crate) struct ActionRegistry {
|
||||||
static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ActionRegistry {
|
|
||||||
builders_by_name: HashMap<SharedString, ActionBuilder>,
|
builders_by_name: HashMap<SharedString, ActionBuilder>,
|
||||||
names_by_type_id: HashMap<TypeId, SharedString>,
|
names_by_type_id: HashMap<TypeId, SharedString>,
|
||||||
all_names: Vec<SharedString>, // So we can return a static slice.
|
all_names: Vec<SharedString>, // So we can return a static slice.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register an action type to allow it to be referenced in keymaps.
|
impl Default for ActionRegistry {
|
||||||
pub fn register_action<A: Action>() {
|
fn default() -> Self {
|
||||||
let name = A::qualified_name();
|
let mut this = ActionRegistry {
|
||||||
let mut lock = ACTION_REGISTRY.write();
|
builders_by_name: Default::default(),
|
||||||
lock.builders_by_name.insert(name.clone(), A::build);
|
names_by_type_id: Default::default(),
|
||||||
lock.names_by_type_id
|
all_names: Default::default(),
|
||||||
.insert(TypeId::of::<A>(), name.clone());
|
};
|
||||||
lock.all_names.push(name);
|
|
||||||
|
this.load_actions();
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
/// This type must be public so that our macros can build it in other crates.
|
||||||
pub fn build_action_from_type(type_id: &TypeId) -> Result<Box<dyn Action>> {
|
/// But this is an implementation detail and should not be used directly.
|
||||||
let lock = ACTION_REGISTRY.read();
|
#[doc(hidden)]
|
||||||
let name = lock
|
pub type MacroActionBuilder = fn() -> ActionData;
|
||||||
.names_by_type_id
|
|
||||||
.get(type_id)
|
|
||||||
.ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?
|
|
||||||
.clone();
|
|
||||||
drop(lock);
|
|
||||||
|
|
||||||
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.
|
/// This constant must be public to be accessible from other crates.
|
||||||
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
|
/// But it's existence is an implementation detail and should not be used directly.
|
||||||
let lock = ACTION_REGISTRY.read();
|
#[doc(hidden)]
|
||||||
|
#[linkme::distributed_slice]
|
||||||
|
pub static __GPUI_ACTIONS: [MacroActionBuilder];
|
||||||
|
|
||||||
let build_action = lock
|
impl ActionRegistry {
|
||||||
.builders_by_name
|
/// Load all registered actions into the registry.
|
||||||
.get(name)
|
pub(crate) fn load_actions(&mut self) {
|
||||||
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
for builder in __GPUI_ACTIONS {
|
||||||
(build_action)(params)
|
let action = builder();
|
||||||
}
|
let name: SharedString = qualify_action(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]> {
|
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
||||||
let lock = ACTION_REGISTRY.read();
|
pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
|
||||||
RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
|
let name = self
|
||||||
registry.all_names.as_slice()
|
.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>> {
|
||||||
|
let build_action = self
|
||||||
|
.builders_by_name
|
||||||
|
.get(name)
|
||||||
|
.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.
|
/// Defines unit structs that can be used as actions.
|
||||||
|
@ -191,7 +155,7 @@ macro_rules! actions {
|
||||||
() => {};
|
() => {};
|
||||||
|
|
||||||
( $name:ident ) => {
|
( $name:ident ) => {
|
||||||
#[gpui::action]
|
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize, gpui::Action)]
|
||||||
pub struct $name;
|
pub struct $name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,42 +165,20 @@ macro_rules! actions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This type must be public so that our macros can build it in other crates.
|
/// This used by our macros to pre-process the action name deterministically
|
||||||
/// But this is an implementation detail and should not be used directly.
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct ActionData {
|
pub fn qualify_action(action_name: &'static str) -> String {
|
||||||
pub name: &'static str,
|
|
||||||
pub build: ActionBuilder,
|
|
||||||
pub type_id: TypeId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
/// 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];
|
|
||||||
|
|
||||||
fn qualify_name(action_name: &'static str) -> SharedString {
|
|
||||||
let mut separator_matches = action_name.rmatch_indices("::");
|
let mut separator_matches = action_name.rmatch_indices("::");
|
||||||
separator_matches.next().unwrap();
|
separator_matches.next().unwrap();
|
||||||
let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
|
let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
|
||||||
// todo!() remove the 2 replacement when migration is done
|
// todo!() remove the 2 replacement when migration is done
|
||||||
action_name[name_start_ix..].replace("2::", "::").into()
|
action_name[name_start_ix..]
|
||||||
|
.replace("2::", "::")
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_actions_2() {
|
mod no_action {
|
||||||
let mut lock = ACTION_REGISTRY.write();
|
use crate as gpui;
|
||||||
|
|
||||||
for action in __GPUI_ACTIONS {
|
actions!(NoAction);
|
||||||
let action = action();
|
|
||||||
let name = qualify_name(action.name);
|
|
||||||
lock.builders_by_name.insert(name.clone(), action.build);
|
|
||||||
lock.names_by_type_id.insert(action.type_id, name.clone());
|
|
||||||
lock.all_names.push(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,13 @@ use smallvec::SmallVec;
|
||||||
pub use test_context::*;
|
pub use test_context::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
|
current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView,
|
||||||
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||||
Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap,
|
DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
|
||||||
LayoutId, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
|
ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,
|
||||||
Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext,
|
PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
|
||||||
Window, WindowContext, WindowHandle, WindowId,
|
TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
|
||||||
|
WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::{HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque};
|
||||||
|
@ -182,6 +183,7 @@ pub struct AppContext {
|
||||||
text_system: Arc<TextSystem>,
|
text_system: Arc<TextSystem>,
|
||||||
flushing_effects: bool,
|
flushing_effects: bool,
|
||||||
pending_updates: usize,
|
pending_updates: usize,
|
||||||
|
pub(crate) actions: Rc<ActionRegistry>,
|
||||||
pub(crate) active_drag: Option<AnyDrag>,
|
pub(crate) active_drag: Option<AnyDrag>,
|
||||||
pub(crate) active_tooltip: Option<AnyTooltip>,
|
pub(crate) active_tooltip: Option<AnyTooltip>,
|
||||||
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
||||||
|
@ -240,6 +242,7 @@ impl AppContext {
|
||||||
platform: platform.clone(),
|
platform: platform.clone(),
|
||||||
app_metadata,
|
app_metadata,
|
||||||
text_system,
|
text_system,
|
||||||
|
actions: Rc::new(ActionRegistry::default()),
|
||||||
flushing_effects: false,
|
flushing_effects: false,
|
||||||
pending_updates: 0,
|
pending_updates: 0,
|
||||||
active_drag: None,
|
active_drag: None,
|
||||||
|
@ -964,6 +967,18 @@ impl AppContext {
|
||||||
pub fn propagate(&mut self) {
|
pub fn propagate(&mut self) {
|
||||||
self.propagate_event = true;
|
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 {
|
impl Context for AppContext {
|
||||||
|
|
|
@ -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!
|
// if we are relying on this side-effect still, removing the debug_assert!
|
||||||
// likely breaks the command_palette tests.
|
// likely breaks the command_palette tests.
|
||||||
debug_assert!(
|
// debug_assert!(
|
||||||
A::is_registered(),
|
// A::is_registered(),
|
||||||
"{:?} is not registered as an action",
|
// "{:?} is not registered as an action",
|
||||||
A::qualified_name()
|
// A::qualified_name()
|
||||||
);
|
// );
|
||||||
self.interactivity().action_listeners.push((
|
self.interactivity().action_listeners.push((
|
||||||
TypeId::of::<A>(),
|
TypeId::of::<A>(),
|
||||||
Box::new(move |view, action, phase, cx| {
|
Box::new(move |view, action, phase, cx| {
|
||||||
|
|
|
@ -55,6 +55,7 @@ use private::Sealed;
|
||||||
pub use refineable::*;
|
pub use refineable::*;
|
||||||
pub use scene::*;
|
pub use scene::*;
|
||||||
pub use serde;
|
pub use serde;
|
||||||
|
pub use serde_derive;
|
||||||
pub use serde_json;
|
pub use serde_json;
|
||||||
pub use smallvec;
|
pub use smallvec;
|
||||||
pub use smol::Timer;
|
pub use smol::Timer;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
build_action_from_type, Action, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch,
|
Action, ActionRegistry, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch, Keymap,
|
||||||
Keymap, Keystroke, KeystrokeMatcher, WindowContext,
|
Keystroke, KeystrokeMatcher, WindowContext,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -10,7 +10,6 @@ use std::{
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct DispatchNodeId(usize);
|
pub struct DispatchNodeId(usize);
|
||||||
|
@ -22,6 +21,7 @@ pub(crate) struct DispatchTree {
|
||||||
focusable_node_ids: HashMap<FocusId, DispatchNodeId>,
|
focusable_node_ids: HashMap<FocusId, DispatchNodeId>,
|
||||||
keystroke_matchers: HashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
|
keystroke_matchers: HashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
|
||||||
keymap: Arc<Mutex<Keymap>>,
|
keymap: Arc<Mutex<Keymap>>,
|
||||||
|
action_registry: Rc<ActionRegistry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -41,7 +41,7 @@ pub(crate) struct DispatchActionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DispatchTree {
|
impl DispatchTree {
|
||||||
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
|
pub fn new(keymap: Arc<Mutex<Keymap>>, action_registry: Rc<ActionRegistry>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
node_stack: Vec::new(),
|
node_stack: Vec::new(),
|
||||||
context_stack: Vec::new(),
|
context_stack: Vec::new(),
|
||||||
|
@ -49,6 +49,7 @@ impl DispatchTree {
|
||||||
focusable_node_ids: HashMap::default(),
|
focusable_node_ids: HashMap::default(),
|
||||||
keystroke_matchers: HashMap::default(),
|
keystroke_matchers: HashMap::default(),
|
||||||
keymap,
|
keymap,
|
||||||
|
action_registry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +154,9 @@ impl DispatchTree {
|
||||||
for node_id in self.dispatch_path(*node) {
|
for node_id in self.dispatch_path(*node) {
|
||||||
let node = &self.nodes[node_id.0];
|
let node = &self.nodes[node_id.0];
|
||||||
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
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 collections::HashSet;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{any::TypeId, collections::HashMap};
|
use std::{
|
||||||
|
any::{Any, TypeId},
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
||||||
pub struct KeymapVersion(usize);
|
pub struct KeymapVersion(usize);
|
||||||
|
@ -37,20 +40,19 @@ impl Keymap {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||||
// todo!("no action")
|
let no_action_id = &(NoAction {}).type_id();
|
||||||
// let no_action_id = (NoAction {}).id();
|
|
||||||
let mut new_bindings = Vec::new();
|
let mut new_bindings = Vec::new();
|
||||||
let has_new_disabled_keystrokes = false;
|
let mut has_new_disabled_keystrokes = false;
|
||||||
for binding in bindings {
|
for binding in bindings {
|
||||||
// if binding.action().id() == no_action_id {
|
if binding.action.type_id() == *no_action_id {
|
||||||
// has_new_disabled_keystrokes |= self
|
has_new_disabled_keystrokes |= self
|
||||||
// .disabled_keystrokes
|
.disabled_keystrokes
|
||||||
// .entry(binding.keystrokes)
|
.entry(binding.keystrokes)
|
||||||
// .or_default()
|
.or_default()
|
||||||
// .insert(binding.context_predicate);
|
.insert(binding.context_predicate);
|
||||||
// } else {
|
} else {
|
||||||
new_bindings.push(binding);
|
new_bindings.push(binding);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_new_disabled_keystrokes {
|
if has_new_disabled_keystrokes {
|
||||||
|
|
|
@ -311,8 +311,8 @@ impl Window {
|
||||||
layout_engine: TaffyLayoutEngine::new(),
|
layout_engine: TaffyLayoutEngine::new(),
|
||||||
root_view: None,
|
root_view: None,
|
||||||
element_id_stack: GlobalElementId::default(),
|
element_id_stack: GlobalElementId::default(),
|
||||||
previous_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())),
|
current_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||||
focus_listeners: SubscriberSet::new(),
|
focus_listeners: SubscriberSet::new(),
|
||||||
default_prevented: true,
|
default_prevented: true,
|
||||||
|
|
45
crates/gpui2/tests/action_macros.rs
Normal file
45
crates/gpui2/tests/action_macros.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_derive() {
|
||||||
|
use gpui2 as gpui;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Deserialize, gpui2_macros::Action)]
|
||||||
|
struct AnotherTestAction;
|
||||||
|
|
||||||
|
#[gpui2_macros::register_action]
|
||||||
|
#[derive(PartialEq, Clone, gpui::serde_derive::Deserialize)]
|
||||||
|
struct RegisterableAction {}
|
||||||
|
|
||||||
|
impl gpui::Action for RegisterableAction {
|
||||||
|
fn boxed_clone(&self) -> Box<dyn gpui::Action> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn partial_eq(&self, _action: &dyn gpui::Action) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_name() -> &'static str
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn gpui::Action>>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,48 +15,81 @@
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, DeriveInput, Error};
|
||||||
|
|
||||||
|
use crate::register_action::register_action;
|
||||||
|
|
||||||
|
pub fn action(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(item as DeriveInput);
|
|
||||||
let name = &input.ident;
|
let name = &input.ident;
|
||||||
let attrs = input
|
|
||||||
.attrs
|
|
||||||
.into_iter()
|
|
||||||
.filter(|attr| !attr.path.is_ident("action"))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let attributes = quote! {
|
if input.generics.lt_token.is_some() {
|
||||||
#[gpui::register_action]
|
return Error::new(name.span(), "Actions must be a concrete type")
|
||||||
#[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
|
.into_compile_error()
|
||||||
#(#attrs)*
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_unit_struct = match input.data {
|
||||||
|
syn::Data::Struct(struct_data) => struct_data.fields.is_empty(),
|
||||||
|
syn::Data::Enum(_) => false,
|
||||||
|
syn::Data::Union(_) => false,
|
||||||
};
|
};
|
||||||
let visibility = input.vis;
|
|
||||||
|
|
||||||
let output = match input.data {
|
let build_impl = if is_unit_struct {
|
||||||
syn::Data::Struct(ref struct_data) => match &struct_data.fields {
|
quote! {
|
||||||
syn::Fields::Named(_) | syn::Fields::Unnamed(_) => {
|
Ok(std::boxed::Box::new(Self {}))
|
||||||
let fields = &struct_data.fields;
|
}
|
||||||
quote! {
|
} else {
|
||||||
#attributes
|
quote! {
|
||||||
#visibility struct #name #fields
|
Ok(std::boxed::Box::new(gpui::serde_json::from_value::<Self>(value)?))
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let register_action = register_action(&name);
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
const _: fn() = || {
|
||||||
|
fn assert_impl<T: ?Sized + for<'a> gpui::serde::Deserialize<'a> + ::std::cmp::PartialEq + ::std::clone::Clone>() {}
|
||||||
|
assert_impl::<#name>();
|
||||||
|
};
|
||||||
|
|
||||||
|
impl gpui::Action for #name {
|
||||||
|
fn name(&self) -> &'static str
|
||||||
|
{
|
||||||
|
::std::any::type_name::<#name>()
|
||||||
}
|
}
|
||||||
syn::Fields::Unit => {
|
|
||||||
quote! {
|
fn debug_name() -> &'static str
|
||||||
#attributes
|
where
|
||||||
#visibility struct #name;
|
Self: ::std::marker::Sized
|
||||||
}
|
{
|
||||||
|
::std::any::type_name::<#name>()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
syn::Data::Enum(ref enum_data) => {
|
fn build(value: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>>
|
||||||
let variants = &enum_data.variants;
|
where
|
||||||
quote! {
|
Self: ::std::marker::Sized {
|
||||||
#attributes
|
#build_impl
|
||||||
#visibility enum #name { #variants }
|
}
|
||||||
|
|
||||||
|
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
|
||||||
|
action
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<Self>()
|
||||||
|
.map_or(false, |a| self == a)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> {
|
||||||
|
::std::boxed::Box::new(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => panic!("Expected a struct or an enum."),
|
|
||||||
|
#register_action
|
||||||
};
|
};
|
||||||
|
|
||||||
TokenStream::from(output)
|
TokenStream::from(output)
|
||||||
|
|
|
@ -11,14 +11,14 @@ pub fn style_helpers(args: TokenStream) -> TokenStream {
|
||||||
style_helpers::style_helpers(args)
|
style_helpers::style_helpers(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_derive(Action)]
|
||||||
pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn action(input: TokenStream) -> TokenStream {
|
||||||
action::action(attr, item)
|
action::action(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
register_action::register_action(attr, item)
|
register_action::register_action_macro(attr, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(Component, attributes(component))]
|
#[proc_macro_derive(Component, attributes(component))]
|
||||||
|
|
|
@ -12,13 +12,54 @@
|
||||||
// gpui2::register_action_builder::<Foo>()
|
// gpui2::register_action_builder::<Foo>()
|
||||||
// }
|
// }
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::Ident;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, DeriveInput, Error};
|
||||||
|
|
||||||
pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn register_action_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(item as DeriveInput);
|
let input = parse_macro_input!(item as DeriveInput);
|
||||||
let type_name = &input.ident;
|
let registration = register_action(&input.ident);
|
||||||
|
|
||||||
|
let has_action_derive = input
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.find(|attr| {
|
||||||
|
(|| {
|
||||||
|
let meta = attr.parse_meta().ok()?;
|
||||||
|
meta.path().is_ident("derive").then(|| match meta {
|
||||||
|
syn::Meta::Path(_) => None,
|
||||||
|
syn::Meta::NameValue(_) => None,
|
||||||
|
syn::Meta::List(list) => list
|
||||||
|
.nested
|
||||||
|
.iter()
|
||||||
|
.find(|list| match list {
|
||||||
|
syn::NestedMeta::Meta(meta) => meta.path().is_ident("Action"),
|
||||||
|
syn::NestedMeta::Lit(_) => false,
|
||||||
|
})
|
||||||
|
.map(|_| true),
|
||||||
|
})?
|
||||||
|
})()
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
if has_action_derive {
|
||||||
|
return Error::new(
|
||||||
|
input.ident.span(),
|
||||||
|
"The Action derive macro has already registered this action",
|
||||||
|
)
|
||||||
|
.into_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenStream::from(quote! {
|
||||||
|
#input
|
||||||
|
|
||||||
|
#registration
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
|
||||||
let static_slice_name =
|
let static_slice_name =
|
||||||
format_ident!("__GPUI_ACTIONS_{}", type_name.to_string().to_uppercase());
|
format_ident!("__GPUI_ACTIONS_{}", type_name.to_string().to_uppercase());
|
||||||
|
|
||||||
|
@ -27,9 +68,7 @@ pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
type_name.to_string().to_lowercase()
|
type_name.to_string().to_lowercase()
|
||||||
);
|
);
|
||||||
|
|
||||||
let expanded = quote! {
|
quote! {
|
||||||
#input
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[gpui::linkme::distributed_slice(gpui::__GPUI_ACTIONS)]
|
#[gpui::linkme::distributed_slice(gpui::__GPUI_ACTIONS)]
|
||||||
#[linkme(crate = gpui::linkme)]
|
#[linkme(crate = gpui::linkme)]
|
||||||
|
@ -44,7 +83,5 @@ pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
build: <#type_name as gpui::Action>::build,
|
build: <#type_name as gpui::Action>::build,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
TokenStream::from(expanded)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::KeyBinding;
|
use gpui::{Action, KeyBinding};
|
||||||
use live_kit_client2::{
|
use live_kit_client2::{
|
||||||
LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
|
LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ use log::LevelFilter;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Action)]
|
||||||
struct Quit;
|
struct Quit;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -73,9 +73,9 @@ impl KeymapFile {
|
||||||
"Expected first item in array to be a string."
|
"Expected first item in array to be a string."
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
gpui::build_action(&name, Some(data))
|
cx.build_action(&name, Some(data))
|
||||||
}
|
}
|
||||||
Value::String(name) => gpui::build_action(&name, None),
|
Value::String(name) => cx.build_action(&name, None),
|
||||||
Value::Null => Ok(no_action()),
|
Value::Null => Ok(no_action()),
|
||||||
_ => {
|
_ => {
|
||||||
return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
|
return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub mod terminal_panel;
|
||||||
// use crate::terminal_element::TerminalElement;
|
// use crate::terminal_element::TerminalElement;
|
||||||
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, img, red, register_action, AnyElement, AppContext, Component, DispatchPhase, Div,
|
actions, div, img, red, Action, AnyElement, AppContext, Component, DispatchPhase, Div,
|
||||||
EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView,
|
EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView,
|
||||||
InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton,
|
InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton,
|
||||||
ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
|
ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
|
||||||
|
@ -55,12 +55,10 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct ScrollTerminal(pub i32);
|
pub struct ScrollTerminal(pub i32);
|
||||||
|
|
||||||
#[register_action]
|
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)]
|
||||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
|
||||||
pub struct SendText(String);
|
pub struct SendText(String);
|
||||||
|
|
||||||
#[register_action]
|
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)]
|
||||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
|
||||||
pub struct SendKeystroke(String);
|
pub struct SendKeystroke(String);
|
||||||
|
|
||||||
actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
|
actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
|
||||||
|
|
|
@ -84,7 +84,8 @@ pub use stories::*;
|
||||||
mod stories {
|
mod stories {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
use gpui::{action, Div, Render};
|
use gpui::{Div, Render};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
pub struct ContextMenuStory;
|
pub struct ContextMenuStory;
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ mod stories {
|
||||||
type Element = Div<Self>;
|
type Element = Div<Self>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
#[action]
|
#[derive(PartialEq, Clone, Deserialize, gpui::Action)]
|
||||||
struct PrintCurrentDate {}
|
struct PrintCurrentDate {}
|
||||||
|
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
|
|
|
@ -81,13 +81,12 @@ pub use stories::*;
|
||||||
mod stories {
|
mod stories {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Story;
|
use crate::Story;
|
||||||
use gpui::{action, Div, Render};
|
use gpui::{actions, Div, Render};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
pub struct KeybindingStory;
|
pub struct KeybindingStory;
|
||||||
|
|
||||||
#[action]
|
actions!(NoAction);
|
||||||
struct NoAction {}
|
|
||||||
|
|
||||||
pub fn binding(key: &str) -> gpui::KeyBinding {
|
pub fn binding(key: &str) -> gpui::KeyBinding {
|
||||||
gpui::KeyBinding::new(key, NoAction {}, None)
|
gpui::KeyBinding::new(key, NoAction {}, None)
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::{HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, prelude::*, register_action, AppContext, AsyncWindowContext, Component, Div, EntityId,
|
actions, prelude::*, Action, AppContext, AsyncWindowContext, Component, Div, EntityId,
|
||||||
EventEmitter, FocusHandle, Focusable, FocusableView, Model, PromptLevel, Render, Task, View,
|
EventEmitter, FocusHandle, Focusable, FocusableView, Model, PromptLevel, Render, Task, View,
|
||||||
ViewContext, VisualContext, WeakView, WindowContext,
|
ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
@ -70,15 +70,13 @@ pub struct ActivateItem(pub usize);
|
||||||
// pub pane: WeakView<Pane>,
|
// pub pane: WeakView<Pane>,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[register_action]
|
#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)]
|
||||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CloseActiveItem {
|
pub struct CloseActiveItem {
|
||||||
pub save_intent: Option<SaveIntent>,
|
pub save_intent: Option<SaveIntent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[register_action]
|
#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)]
|
||||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CloseAllItems {
|
pub struct CloseAllItems {
|
||||||
pub save_intent: Option<SaveIntent>,
|
pub save_intent: Option<SaveIntent>,
|
||||||
|
@ -1917,7 +1915,7 @@ impl Render for Pane {
|
||||||
.on_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx))
|
.on_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx))
|
||||||
.on_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx))
|
.on_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx))
|
||||||
.size_full()
|
.size_full()
|
||||||
.on_action(|pane: &mut Self, action, cx| {
|
.on_action(|pane: &mut Self, action: &CloseActiveItem, cx| {
|
||||||
pane.close_active_item(action, cx)
|
pane.close_active_item(action, cx)
|
||||||
.map(|task| task.detach_and_log_err(cx));
|
.map(|task| task.detach_and_log_err(cx));
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,11 +29,11 @@ use futures::{
|
||||||
Future, FutureExt, StreamExt,
|
Future, FutureExt, StreamExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, point, register_action, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
|
actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
|
||||||
AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter,
|
AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle,
|
||||||
FocusHandle, FocusableView, GlobalPixels, InteractiveComponent, KeyContext, Model,
|
FocusableView, GlobalPixels, InteractiveComponent, KeyContext, Model, ModelContext,
|
||||||
ModelContext, ParentComponent, Point, Render, Size, Styled, Subscription, Task, View,
|
ParentComponent, Point, Render, Size, Styled, Subscription, Task, View, ViewContext,
|
||||||
ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
|
VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -194,8 +194,7 @@ impl Clone for Toast {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[register_action]
|
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Action)]
|
||||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
|
|
||||||
pub struct OpenTerminal {
|
pub struct OpenTerminal {
|
||||||
pub working_directory: PathBuf,
|
pub working_directory: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ impl LspAdapter for JsonLspAdapter {
|
||||||
&self,
|
&self,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> BoxFuture<'static, serde_json::Value> {
|
) -> BoxFuture<'static, serde_json::Value> {
|
||||||
let action_names = gpui::all_action_names();
|
let action_names = cx.all_action_names();
|
||||||
let staff_mode = cx.is_staff();
|
let staff_mode = cx.is_staff();
|
||||||
let language_names = &self.languages.language_names();
|
let language_names = &self.languages.language_names();
|
||||||
let settings_schema = cx.global::<SettingsStore>().json_schema(
|
let settings_schema = cx.global::<SettingsStore>().json_schema(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use gpui::action;
|
use gpui::Action;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
// If the zed binary doesn't use anything in this crate, it will be optimized away
|
// If the zed binary doesn't use anything in this crate, it will be optimized away
|
||||||
// and the actions won't initialize. So we just provide an empty initialization function
|
// and the actions won't initialize. So we just provide an empty initialization function
|
||||||
|
@ -9,12 +10,12 @@ use gpui::action;
|
||||||
// https://github.com/mmastrac/rust-ctor/issues/280
|
// https://github.com/mmastrac/rust-ctor/issues/280
|
||||||
pub fn init() {}
|
pub fn init() {}
|
||||||
|
|
||||||
#[action]
|
#[derive(Clone, PartialEq, Deserialize, Action)]
|
||||||
pub struct OpenBrowser {
|
pub struct OpenBrowser {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[action]
|
#[derive(Clone, PartialEq, Deserialize, Action)]
|
||||||
pub struct OpenZedURL {
|
pub struct OpenZedURL {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue