mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Remove 2 suffix from gpui_macros, fix compile errors in tests
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
f5ba22659b
commit
83f4c61657
@ -1,6 +1,3 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
|
||||
[build]
|
||||
# v0 mangling scheme provides more detailed backtraces around closures
|
||||
rustflags = ["-C", "symbol-mangling-version=v0"]
|
||||
|
56
Cargo.lock
generated
56
Cargo.lock
generated
@ -1618,19 +1618,6 @@ dependencies = [
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "component_test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gpui",
|
||||
"project",
|
||||
"settings",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.2.0"
|
||||
@ -1665,17 +1652,6 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||
|
||||
[[package]]
|
||||
name = "context_menu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"menu",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@ -2354,14 +2330,6 @@ version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "drag_and_drop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
version = "0.11.0"
|
||||
@ -3185,7 +3153,7 @@ dependencies = [
|
||||
"font-kit",
|
||||
"foreign-types",
|
||||
"futures 0.3.28",
|
||||
"gpui2_macros",
|
||||
"gpui_macros",
|
||||
"image",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
@ -3225,20 +3193,10 @@ dependencies = [
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpui2_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
@ -7677,6 +7635,7 @@ dependencies = [
|
||||
"story",
|
||||
"strum",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
]
|
||||
|
||||
@ -9936,17 +9895,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.4.4",
|
||||
"schemars",
|
||||
"serde_json",
|
||||
"theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
@ -15,15 +15,12 @@ members = [
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/component_test",
|
||||
"crates/context_menu",
|
||||
"crates/copilot",
|
||||
"crates/copilot_button",
|
||||
"crates/db",
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/diagnostics",
|
||||
"crates/drag_and_drop",
|
||||
"crates/editor",
|
||||
"crates/feature_flags",
|
||||
"crates/feedback",
|
||||
@ -36,7 +33,7 @@ members = [
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui",
|
||||
"crates/gpui2_macros",
|
||||
"crates/gpui_macros",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/journal",
|
||||
@ -85,7 +82,6 @@ members = [
|
||||
"crates/vcs_menu",
|
||||
"crates/workspace",
|
||||
"crates/welcome",
|
||||
"crates/xtask",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use gpui::{
|
||||
div, AnyElement, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
|
||||
div, AnyElement, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "component_test"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/component_test.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
settings = { path = "../settings" }
|
||||
util = { path = "../util" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace" }
|
||||
project = { path = "../project" }
|
@ -1,121 +0,0 @@
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{Component, Flex, ParentElement, SafeStylable},
|
||||
AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::Project;
|
||||
use theme::components::{action_button::Button, label::Label, ComponentExt};
|
||||
use workspace::{
|
||||
item::Item, register_deserializable_item, ItemId, Pane, PaneBackdrop, Workspace, WorkspaceId,
|
||||
};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ComponentTest::toggle_disclosure);
|
||||
cx.add_action(ComponentTest::toggle_toggle);
|
||||
cx.add_action(ComponentTest::deploy);
|
||||
register_deserializable_item::<ComponentTest>(cx);
|
||||
}
|
||||
|
||||
actions!(
|
||||
test,
|
||||
[NoAction, ToggleDisclosure, ToggleToggle, NewComponentTest]
|
||||
);
|
||||
|
||||
struct ComponentTest {
|
||||
disclosed: bool,
|
||||
toggled: bool,
|
||||
}
|
||||
|
||||
impl ComponentTest {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
disclosed: false,
|
||||
toggled: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy(workspace: &mut Workspace, _: &NewComponentTest, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.add_item(Box::new(cx.add_view(|_| ComponentTest::new())), cx);
|
||||
}
|
||||
|
||||
fn toggle_disclosure(&mut self, _: &ToggleDisclosure, cx: &mut ViewContext<Self>) {
|
||||
self.disclosed = !self.disclosed;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn toggle_toggle(&mut self, _: &ToggleToggle, cx: &mut ViewContext<Self>) {
|
||||
self.toggled = !self.toggled;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ComponentTest {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for ComponentTest {
|
||||
fn ui_name() -> &'static str {
|
||||
"Component Test"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
|
||||
let theme = theme::current(cx);
|
||||
|
||||
PaneBackdrop::new(
|
||||
cx.view_id(),
|
||||
Flex::column()
|
||||
.with_spacing(10.)
|
||||
.with_child(
|
||||
Button::action(NoAction)
|
||||
.with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
|
||||
.with_contents(Label::new("Click me!"))
|
||||
.with_style(theme.component_test.button.clone())
|
||||
.element(),
|
||||
)
|
||||
.with_child(
|
||||
Button::action(ToggleToggle)
|
||||
.with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
|
||||
.with_contents(Label::new("Toggle me!"))
|
||||
.toggleable(self.toggled)
|
||||
.with_style(theme.component_test.toggle.clone())
|
||||
.element(),
|
||||
)
|
||||
.with_child(
|
||||
Label::new("A disclosure")
|
||||
.disclosable(Some(self.disclosed), Box::new(ToggleDisclosure))
|
||||
.with_style(theme.component_test.disclosure.clone())
|
||||
.element(),
|
||||
)
|
||||
.constrained()
|
||||
.with_width(200.)
|
||||
.aligned()
|
||||
.into_any(),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ComponentTest {
|
||||
fn tab_content<V: 'static>(
|
||||
&self,
|
||||
_: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
_: &AppContext,
|
||||
) -> gpui::AnyElement<V> {
|
||||
gpui::elements::Label::new("Component test", style.label.clone()).into_any()
|
||||
}
|
||||
|
||||
fn serialized_item_kind() -> Option<&'static str> {
|
||||
Some("ComponentTest")
|
||||
}
|
||||
|
||||
fn deserialize(
|
||||
_project: ModelHandle<Project>,
|
||||
_workspace: WeakViewHandle<Workspace>,
|
||||
_workspace_id: WorkspaceId,
|
||||
_item_id: ItemId,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) -> Task<anyhow::Result<ViewHandle<Self>>> {
|
||||
Task::ready(Ok(cx.add_view(|_| Self::new())))
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "context_menu"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/context_menu.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
menu = { path = "../menu" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
smallvec.workspace = true
|
@ -1,528 +0,0 @@
|
||||
use gpui::{
|
||||
anyhow::{self, anyhow},
|
||||
elements::*,
|
||||
geometry::vector::Vector2F,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
|
||||
View, ViewContext,
|
||||
};
|
||||
use menu::*;
|
||||
use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ContextMenu::select_first);
|
||||
cx.add_action(ContextMenu::select_last);
|
||||
cx.add_action(ContextMenu::select_next);
|
||||
cx.add_action(ContextMenu::select_prev);
|
||||
cx.add_action(ContextMenu::confirm);
|
||||
cx.add_action(ContextMenu::cancel);
|
||||
}
|
||||
|
||||
pub type StaticItem = Box<dyn Fn(&mut AppContext) -> AnyElement<ContextMenu>>;
|
||||
|
||||
type ContextMenuItemBuilder =
|
||||
Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement<ContextMenu>>;
|
||||
|
||||
pub enum ContextMenuItemLabel {
|
||||
String(Cow<'static, str>),
|
||||
Element(ContextMenuItemBuilder),
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for ContextMenuItemLabel {
|
||||
fn from(s: Cow<'static, str>) -> Self {
|
||||
Self::String(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for ContextMenuItemLabel {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Self::String(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ContextMenuItemLabel {
|
||||
fn from(s: String) -> Self {
|
||||
Self::String(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for ContextMenuItemLabel
|
||||
where
|
||||
T: 'static + Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement<ContextMenu>,
|
||||
{
|
||||
fn from(f: T) -> Self {
|
||||
Self::Element(Box::new(f))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ContextMenuItemAction {
|
||||
Action(Box<dyn Action>),
|
||||
Handler(Arc<dyn Fn(&mut ViewContext<ContextMenu>)>),
|
||||
}
|
||||
|
||||
impl Clone for ContextMenuItemAction {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Action(action) => Self::Action(action.boxed_clone()),
|
||||
Self::Handler(handler) => Self::Handler(handler.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ContextMenuItem {
|
||||
Item {
|
||||
label: ContextMenuItemLabel,
|
||||
action: ContextMenuItemAction,
|
||||
},
|
||||
Static(StaticItem),
|
||||
Separator,
|
||||
}
|
||||
|
||||
impl ContextMenuItem {
|
||||
pub fn action(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
|
||||
Self::Item {
|
||||
label: label.into(),
|
||||
action: ContextMenuItemAction::Action(Box::new(action)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handler(
|
||||
label: impl Into<ContextMenuItemLabel>,
|
||||
handler: impl 'static + Fn(&mut ViewContext<ContextMenu>),
|
||||
) -> Self {
|
||||
Self::Item {
|
||||
label: label.into(),
|
||||
action: ContextMenuItemAction::Handler(Arc::new(handler)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separator() -> Self {
|
||||
Self::Separator
|
||||
}
|
||||
|
||||
fn is_action(&self) -> bool {
|
||||
matches!(self, Self::Item { .. })
|
||||
}
|
||||
|
||||
fn action_id(&self) -> Option<TypeId> {
|
||||
match self {
|
||||
ContextMenuItem::Item { action, .. } => match action {
|
||||
ContextMenuItemAction::Action(action) => Some(action.id()),
|
||||
ContextMenuItemAction::Handler(_) => None,
|
||||
},
|
||||
ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextMenu {
|
||||
show_count: usize,
|
||||
anchor_position: Vector2F,
|
||||
anchor_corner: AnchorCorner,
|
||||
position_mode: OverlayPositionMode,
|
||||
items: Vec<ContextMenuItem>,
|
||||
selected_index: Option<usize>,
|
||||
visible: bool,
|
||||
delay_cancel: bool,
|
||||
previously_focused_view_id: Option<usize>,
|
||||
parent_view_id: usize,
|
||||
_actions_observation: Subscription,
|
||||
}
|
||||
|
||||
impl Entity for ContextMenu {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for ContextMenu {
|
||||
fn ui_name() -> &'static str {
|
||||
"ContextMenu"
|
||||
}
|
||||
|
||||
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||
Self::reset_to_default_keymap_context(keymap);
|
||||
keymap.add_identifier("menu");
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
if !self.visible {
|
||||
return Empty::new().into_any();
|
||||
}
|
||||
|
||||
// Render the menu once at minimum width.
|
||||
let mut collapsed_menu = self.render_menu_for_measurement(cx);
|
||||
let expanded_menu =
|
||||
self.render_menu(cx)
|
||||
.constrained()
|
||||
.dynamically(move |constraint, view, cx| {
|
||||
SizeConstraint::strict_along(
|
||||
Axis::Horizontal,
|
||||
collapsed_menu.layout(constraint, view, cx).0.x(),
|
||||
)
|
||||
});
|
||||
|
||||
Overlay::new(expanded_menu)
|
||||
.with_hoverable(true)
|
||||
.with_fit_mode(OverlayFitMode::SnapToWindow)
|
||||
.with_anchor_position(self.anchor_position)
|
||||
.with_anchor_corner(self.anchor_corner)
|
||||
.with_position_mode(self.position_mode)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
self.reset(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextMenu {
|
||||
pub fn new(parent_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
show_count: 0,
|
||||
delay_cancel: false,
|
||||
anchor_position: Default::default(),
|
||||
anchor_corner: AnchorCorner::TopLeft,
|
||||
position_mode: OverlayPositionMode::Window,
|
||||
items: Default::default(),
|
||||
selected_index: Default::default(),
|
||||
visible: Default::default(),
|
||||
previously_focused_view_id: Default::default(),
|
||||
parent_view_id,
|
||||
_actions_observation: cx.observe_actions(Self::action_dispatched),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn action_dispatched(&mut self, action_id: TypeId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|item| item.action_id() == Some(action_id))
|
||||
{
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background().timer(Duration::from_millis(50)).await;
|
||||
this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
|
||||
match action {
|
||||
ContextMenuItemAction::Action(action) => {
|
||||
let window = cx.window();
|
||||
let view_id = self.parent_view_id;
|
||||
let action = action.boxed_clone();
|
||||
cx.app_context()
|
||||
.spawn(|mut cx| async move {
|
||||
window
|
||||
.dispatch_action(view_id, action.as_ref(), &mut cx)
|
||||
.ok_or_else(|| anyhow!("window was closed"))
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
ContextMenuItemAction::Handler(handler) => handler(cx),
|
||||
}
|
||||
self.reset(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delay_cancel(&mut self) {
|
||||
self.delay_cancel = true;
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
if !self.delay_cancel {
|
||||
self.reset(cx);
|
||||
let show_count = self.show_count;
|
||||
cx.defer(move |this, cx| {
|
||||
if cx.handle().is_focused(cx) && this.show_count == show_count {
|
||||
(**cx).focus(this.previously_focused_view_id.take());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.delay_cancel = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.items.clear();
|
||||
self.visible = false;
|
||||
self.selected_index.take();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
|
||||
self.selected_index = self.items.iter().position(|item| item.is_action());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
|
||||
for (ix, item) in self.items.iter().enumerate().rev() {
|
||||
if item.is_action() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
for (ix, item) in self.items.iter().enumerate().skip(ix + 1) {
|
||||
if item.is_action() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.select_first(&Default::default(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
|
||||
if item.is_action() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.select_last(&Default::default(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(
|
||||
&mut self,
|
||||
anchor_position: Vector2F,
|
||||
anchor_corner: AnchorCorner,
|
||||
items: Vec<ContextMenuItem>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.visible() {
|
||||
self.cancel(&Cancel, cx);
|
||||
} else {
|
||||
let mut items = items.into_iter().peekable();
|
||||
if items.peek().is_some() {
|
||||
self.items = items.collect();
|
||||
self.anchor_position = anchor_position;
|
||||
self.anchor_corner = anchor_corner;
|
||||
self.visible = true;
|
||||
self.show_count += 1;
|
||||
if !cx.is_self_focused() {
|
||||
self.previously_focused_view_id = cx.focused_view_id();
|
||||
}
|
||||
cx.focus_self();
|
||||
} else {
|
||||
self.visible = false;
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn show(
|
||||
&mut self,
|
||||
anchor_position: Vector2F,
|
||||
anchor_corner: AnchorCorner,
|
||||
items: Vec<ContextMenuItem>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let mut items = items.into_iter().peekable();
|
||||
if items.peek().is_some() {
|
||||
self.items = items.collect();
|
||||
self.anchor_position = anchor_position;
|
||||
self.anchor_corner = anchor_corner;
|
||||
self.visible = true;
|
||||
self.show_count += 1;
|
||||
if !cx.is_self_focused() {
|
||||
self.previously_focused_view_id = cx.focused_view_id();
|
||||
}
|
||||
cx.focus_self();
|
||||
} else {
|
||||
self.visible = false;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_position_mode(&mut self, mode: OverlayPositionMode) {
|
||||
self.position_mode = mode;
|
||||
}
|
||||
|
||||
fn render_menu_for_measurement(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
|
||||
let style = theme::current(cx).context_menu.clone();
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { label, .. } => {
|
||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
||||
let style = style.style_for(&mut Default::default());
|
||||
|
||||
match label {
|
||||
ContextMenuItemLabel::String(label) => {
|
||||
Label::new(label.to_string(), style.label.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.into_any()
|
||||
}
|
||||
ContextMenuItemLabel::Element(element) => {
|
||||
element(&mut Default::default(), style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(f) => f(cx),
|
||||
|
||||
ContextMenuItem::Separator => Empty::new()
|
||||
.collapsed()
|
||||
.contained()
|
||||
.with_style(style.separator)
|
||||
.constrained()
|
||||
.with_height(1.)
|
||||
.into_any(),
|
||||
}
|
||||
})),
|
||||
)
|
||||
.with_child(
|
||||
Flex::column()
|
||||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { action, .. } => {
|
||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
||||
let style = style.style_for(&mut Default::default());
|
||||
|
||||
match action {
|
||||
ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
|
||||
self.parent_view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
)
|
||||
.into_any(),
|
||||
ContextMenuItemAction::Handler(_) => Empty::new().into_any(),
|
||||
}
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(_) => Empty::new().into_any(),
|
||||
|
||||
ContextMenuItem::Separator => Empty::new()
|
||||
.collapsed()
|
||||
.constrained()
|
||||
.with_height(1.)
|
||||
.contained()
|
||||
.with_style(style.separator)
|
||||
.into_any(),
|
||||
}
|
||||
}))
|
||||
.contained()
|
||||
.with_margin_left(style.keystroke_margin),
|
||||
)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
}
|
||||
|
||||
fn render_menu(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
|
||||
enum Menu {}
|
||||
enum MenuItem {}
|
||||
|
||||
let style = theme::current(cx).context_menu.clone();
|
||||
|
||||
MouseEventHandler::new::<Menu, _>(0, cx, |_, cx| {
|
||||
Flex::column()
|
||||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { label, action } => {
|
||||
let action = action.clone();
|
||||
let view_id = self.parent_view_id;
|
||||
MouseEventHandler::new::<MenuItem, _>(ix, cx, |state, _| {
|
||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
||||
let style = style.style_for(state);
|
||||
let keystroke = match &action {
|
||||
ContextMenuItemAction::Action(action) => Some(
|
||||
KeystrokeLabel::new(
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
)
|
||||
.flex_float(),
|
||||
),
|
||||
ContextMenuItemAction::Handler(_) => None,
|
||||
};
|
||||
|
||||
Flex::row()
|
||||
.with_child(match label {
|
||||
ContextMenuItemLabel::String(label) => {
|
||||
Label::new(label.clone(), style.label.clone())
|
||||
.contained()
|
||||
.into_any()
|
||||
}
|
||||
ContextMenuItemLabel::Element(element) => {
|
||||
element(state, style)
|
||||
}
|
||||
})
|
||||
.with_children(keystroke)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
|
||||
.on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
|
||||
.on_click(MouseButton::Left, move |_, menu, cx| {
|
||||
menu.cancel(&Default::default(), cx);
|
||||
let window = cx.window();
|
||||
match &action {
|
||||
ContextMenuItemAction::Action(action) => {
|
||||
let action = action.boxed_clone();
|
||||
cx.app_context()
|
||||
.spawn(|mut cx| async move {
|
||||
window
|
||||
.dispatch_action(
|
||||
view_id,
|
||||
action.as_ref(),
|
||||
&mut cx,
|
||||
)
|
||||
.ok_or_else(|| anyhow!("window was closed"))
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
ContextMenuItemAction::Handler(handler) => handler(cx),
|
||||
}
|
||||
})
|
||||
.on_drag(MouseButton::Left, |_, _, _| {})
|
||||
.into_any()
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(f) => f(cx),
|
||||
|
||||
ContextMenuItem::Separator => Empty::new()
|
||||
.constrained()
|
||||
.with_height(1.)
|
||||
.contained()
|
||||
.with_style(style.separator)
|
||||
.into_any(),
|
||||
}
|
||||
}))
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
})
|
||||
.on_down_out(MouseButton::Left, |_, this, cx| {
|
||||
this.cancel(&Default::default(), cx);
|
||||
})
|
||||
.on_down_out(MouseButton::Right, |_, this, cx| {
|
||||
this.cancel(&Default::default(), cx);
|
||||
})
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "drag_and_drop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/drag_and_drop.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
@ -1,378 +0,0 @@
|
||||
use std::{any::Any, rc::Rc};
|
||||
|
||||
use collections::HashSet;
|
||||
use gpui::{
|
||||
elements::{Empty, MouseEventHandler, Overlay},
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
platform::{CursorStyle, Modifiers, MouseButton},
|
||||
scene::{MouseDown, MouseDrag},
|
||||
AnyElement, AnyWindowHandle, Element, View, ViewContext, WeakViewHandle, WindowContext,
|
||||
};
|
||||
|
||||
const DEAD_ZONE: f32 = 4.;
|
||||
|
||||
enum State<V> {
|
||||
Down {
|
||||
region_offset: Vector2F,
|
||||
region: RectF,
|
||||
},
|
||||
DeadZone {
|
||||
region_offset: Vector2F,
|
||||
region: RectF,
|
||||
},
|
||||
Dragging {
|
||||
modifiers: Modifiers,
|
||||
window: AnyWindowHandle,
|
||||
position: Vector2F,
|
||||
region_offset: Vector2F,
|
||||
region: RectF,
|
||||
payload: Rc<dyn Any + 'static>,
|
||||
render: Rc<dyn Fn(&Modifiers, Rc<dyn Any>, &mut ViewContext<V>) -> AnyElement<V>>,
|
||||
},
|
||||
Canceled,
|
||||
}
|
||||
|
||||
impl<V> Clone for State<V> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
&State::Down {
|
||||
region_offset,
|
||||
region,
|
||||
} => State::Down {
|
||||
region_offset,
|
||||
region,
|
||||
},
|
||||
&State::DeadZone {
|
||||
region_offset,
|
||||
region,
|
||||
} => State::DeadZone {
|
||||
region_offset,
|
||||
region,
|
||||
},
|
||||
State::Dragging {
|
||||
modifiers,
|
||||
window,
|
||||
position,
|
||||
region_offset,
|
||||
region,
|
||||
payload,
|
||||
render,
|
||||
} => Self::Dragging {
|
||||
window: window.clone(),
|
||||
position: position.clone(),
|
||||
region_offset: region_offset.clone(),
|
||||
region: region.clone(),
|
||||
payload: payload.clone(),
|
||||
render: render.clone(),
|
||||
modifiers: modifiers.clone(),
|
||||
},
|
||||
State::Canceled => State::Canceled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DragAndDrop<V> {
|
||||
containers: HashSet<WeakViewHandle<V>>,
|
||||
currently_dragged: Option<State<V>>,
|
||||
}
|
||||
|
||||
impl<V> Default for DragAndDrop<V> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
containers: Default::default(),
|
||||
currently_dragged: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> DragAndDrop<V> {
|
||||
pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
|
||||
self.containers.insert(handle);
|
||||
}
|
||||
|
||||
pub fn currently_dragged<T: Any>(&self, window: AnyWindowHandle) -> Option<(Vector2F, Rc<T>)> {
|
||||
self.currently_dragged.as_ref().and_then(|state| {
|
||||
if let State::Dragging {
|
||||
position,
|
||||
payload,
|
||||
window: window_dragged_from,
|
||||
..
|
||||
} = state
|
||||
{
|
||||
if &window != window_dragged_from {
|
||||
return None;
|
||||
}
|
||||
|
||||
payload
|
||||
.is::<T>()
|
||||
.then(|| payload.clone().downcast::<T>().ok())
|
||||
.flatten()
|
||||
.map(|payload| (position.clone(), payload))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn any_currently_dragged(&self, window: AnyWindowHandle) -> bool {
|
||||
self.currently_dragged
|
||||
.as_ref()
|
||||
.map(|state| {
|
||||
if let State::Dragging {
|
||||
window: window_dragged_from,
|
||||
..
|
||||
} = state
|
||||
{
|
||||
if &window != window_dragged_from {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn drag_started(event: MouseDown, cx: &mut WindowContext) {
|
||||
cx.update_global(|this: &mut Self, _| {
|
||||
this.currently_dragged = Some(State::Down {
|
||||
region_offset: event.position - event.region.origin(),
|
||||
region: event.region,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dragging<T: Any>(
|
||||
event: MouseDrag,
|
||||
payload: Rc<T>,
|
||||
cx: &mut WindowContext,
|
||||
render: Rc<impl 'static + Fn(&Modifiers, &T, &mut ViewContext<V>) -> AnyElement<V>>,
|
||||
) {
|
||||
let window = cx.window();
|
||||
cx.update_global(|this: &mut Self, cx| {
|
||||
this.notify_containers_for_window(window, cx);
|
||||
|
||||
match this.currently_dragged.as_ref() {
|
||||
Some(&State::Down {
|
||||
region_offset,
|
||||
region,
|
||||
})
|
||||
| Some(&State::DeadZone {
|
||||
region_offset,
|
||||
region,
|
||||
}) => {
|
||||
if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE {
|
||||
this.currently_dragged = Some(State::Dragging {
|
||||
modifiers: event.modifiers,
|
||||
window,
|
||||
region_offset,
|
||||
region,
|
||||
position: event.position,
|
||||
payload,
|
||||
render: Rc::new(move |modifiers, payload, cx| {
|
||||
render(modifiers, payload.downcast_ref::<T>().unwrap(), cx)
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
this.currently_dragged = Some(State::DeadZone {
|
||||
region_offset,
|
||||
region,
|
||||
})
|
||||
}
|
||||
}
|
||||
Some(&State::Dragging {
|
||||
region_offset,
|
||||
region,
|
||||
modifiers,
|
||||
..
|
||||
}) => {
|
||||
this.currently_dragged = Some(State::Dragging {
|
||||
modifiers,
|
||||
window,
|
||||
region_offset,
|
||||
region,
|
||||
position: event.position,
|
||||
payload,
|
||||
render: Rc::new(move |modifiers, payload, cx| {
|
||||
render(modifiers, payload.downcast_ref::<T>().unwrap(), cx)
|
||||
}),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_modifiers(new_modifiers: Modifiers, cx: &mut ViewContext<V>) -> bool {
|
||||
let result = cx.update_global(|this: &mut Self, _| match &mut this.currently_dragged {
|
||||
Some(state) => match state {
|
||||
State::Dragging { modifiers, .. } => {
|
||||
*modifiers = new_modifiers;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
None => false,
|
||||
});
|
||||
|
||||
if result {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn render(cx: &mut ViewContext<V>) -> Option<AnyElement<V>> {
|
||||
enum DraggedElementHandler {}
|
||||
cx.global::<Self>()
|
||||
.currently_dragged
|
||||
.clone()
|
||||
.and_then(|state| {
|
||||
match state {
|
||||
State::Down { .. } => None,
|
||||
State::DeadZone { .. } => None,
|
||||
State::Dragging {
|
||||
modifiers,
|
||||
window,
|
||||
region_offset,
|
||||
position,
|
||||
region,
|
||||
payload,
|
||||
render,
|
||||
} => {
|
||||
if cx.window() != window {
|
||||
return None;
|
||||
}
|
||||
|
||||
let position = (position - region_offset).round();
|
||||
Some(
|
||||
Overlay::new(
|
||||
MouseEventHandler::new::<DraggedElementHandler, _>(
|
||||
0,
|
||||
cx,
|
||||
|_, cx| render(&modifiers, payload, cx),
|
||||
)
|
||||
.with_cursor_style(CursorStyle::Arrow)
|
||||
.on_up(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, cx| {
|
||||
this.finish_dragging(cx)
|
||||
});
|
||||
});
|
||||
cx.propagate_event();
|
||||
})
|
||||
.on_up_out(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, cx| {
|
||||
this.finish_dragging(cx)
|
||||
});
|
||||
});
|
||||
})
|
||||
// Don't block hover events or invalidations
|
||||
.with_hoverable(false)
|
||||
.constrained()
|
||||
.with_width(region.width())
|
||||
.with_height(region.height()),
|
||||
)
|
||||
.with_anchor_position(position)
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
State::Canceled => Some(
|
||||
MouseEventHandler::new::<DraggedElementHandler, _>(0, cx, |_, _| {
|
||||
Empty::new().constrained().with_width(0.).with_height(0.)
|
||||
})
|
||||
.on_up(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, _| {
|
||||
this.currently_dragged = None;
|
||||
});
|
||||
});
|
||||
})
|
||||
.on_up_out(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, _| {
|
||||
this.currently_dragged = None;
|
||||
});
|
||||
});
|
||||
})
|
||||
.into_any(),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
|
||||
if let Some(State::Dragging {
|
||||
payload, window, ..
|
||||
}) = &self.currently_dragged
|
||||
{
|
||||
if payload.is::<P>() {
|
||||
let window = *window;
|
||||
self.currently_dragged = Some(State::Canceled);
|
||||
self.notify_containers_for_window(window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_dragging(&mut self, cx: &mut WindowContext) {
|
||||
if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
|
||||
self.notify_containers_for_window(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) {
|
||||
self.containers.retain(|container| {
|
||||
if let Some(container) = container.upgrade(cx) {
|
||||
if container.window() == window {
|
||||
container.update(cx, |_, cx| cx.notify());
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Draggable<V> {
|
||||
fn as_draggable<D: View, P: Any>(
|
||||
self,
|
||||
payload: P,
|
||||
render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext<D>) -> AnyElement<D>,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<V: 'static> Draggable<V> for MouseEventHandler<V> {
|
||||
fn as_draggable<D: View, P: Any>(
|
||||
self,
|
||||
payload: P,
|
||||
render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext<D>) -> AnyElement<D>,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let payload = Rc::new(payload);
|
||||
let render = Rc::new(render);
|
||||
self.on_down(MouseButton::Left, move |e, _, cx| {
|
||||
cx.propagate_event();
|
||||
DragAndDrop::<D>::drag_started(e, cx);
|
||||
})
|
||||
.on_drag(MouseButton::Left, move |e, _, cx| {
|
||||
if e.end {
|
||||
cx.update_global::<DragAndDrop<D>, _, _>(|drag_and_drop, cx| {
|
||||
drag_and_drop.finish_dragging(cx)
|
||||
})
|
||||
} else {
|
||||
let payload = payload.clone();
|
||||
let render = render.clone();
|
||||
DragAndDrop::<D>::dragging(e, payload, cx, render)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
gpui2_macros = { path = "../gpui2_macros" }
|
||||
gpui_macros = { path = "../gpui_macros" }
|
||||
util = { path = "../util" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
|
@ -47,7 +47,7 @@ pub use element::*;
|
||||
pub use elements::*;
|
||||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use gpui2_macros::*;
|
||||
pub use gpui_macros::*;
|
||||
pub use image_cache::*;
|
||||
pub use input::*;
|
||||
pub use interactive::*;
|
||||
|
@ -10,7 +10,7 @@ use taffy::style::Overflow;
|
||||
pub trait Styled: Sized {
|
||||
fn style(&mut self) -> &mut StyleRefinement;
|
||||
|
||||
gpui2_macros::style_helpers!();
|
||||
gpui_macros::style_helpers!();
|
||||
|
||||
fn z_index(mut self, z_index: u8) -> Self {
|
||||
self.style().z_index = Some(z_index);
|
||||
|
@ -1,11 +1,9 @@
|
||||
use gpui2::{actions, impl_actions};
|
||||
use gpui2_macros::register_action;
|
||||
use gpui::{actions, impl_actions};
|
||||
use gpui_macros::register_action;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[test]
|
||||
fn test_action_macros() {
|
||||
use gpui2 as gpui;
|
||||
|
||||
actions!(test, [TestAction]);
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize)]
|
||||
|
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "gpui2_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/gpui2_macros.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0.72", features = ["full"] }
|
||||
quote = "1.0.9"
|
||||
proc-macro2 = "1.0.66"
|
@ -1,32 +0,0 @@
|
||||
mod derive_into_element;
|
||||
mod derive_render;
|
||||
mod register_action;
|
||||
mod style_helpers;
|
||||
mod test;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn register_action(ident: TokenStream) -> TokenStream {
|
||||
register_action::register_action_macro(ident)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(IntoElement)]
|
||||
pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
||||
derive_into_element::derive_into_element(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Render)]
|
||||
pub fn derive_render(input: TokenStream) -> TokenStream {
|
||||
derive_render::derive_render(input)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn style_helpers(input: TokenStream) -> TokenStream {
|
||||
style_helpers::style_helpers(input)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
test::test(args, function)
|
||||
}
|
@ -7,10 +7,8 @@ publish = false
|
||||
[lib]
|
||||
path = "src/gpui_macros.rs"
|
||||
proc-macro = true
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lazy_static.workspace = true
|
||||
proc-macro2 = "1.0"
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.72", features = ["full"] }
|
||||
quote = "1.0.9"
|
||||
proc-macro2 = "1.0.66"
|
||||
|
@ -13,7 +13,7 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
||||
{
|
||||
type Element = gpui::Component<Self>;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
fn element_id(&self) -> Option<gpui::ElementId> {
|
||||
None
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
|
||||
impl #impl_generics gpui::Render for #type_name #type_generics
|
||||
#where_clause
|
||||
{
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl Element {
|
||||
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::Element {
|
||||
()
|
||||
}
|
||||
}
|
@ -1,381 +1,32 @@
|
||||
mod derive_into_element;
|
||||
mod derive_render;
|
||||
mod register_action;
|
||||
mod style_helpers;
|
||||
mod test;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Ident;
|
||||
use quote::{format_ident, quote};
|
||||
use std::mem;
|
||||
use syn::{
|
||||
parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg,
|
||||
GenericParam, Generics, ItemFn, Lit, Meta, NestedMeta, Type, WhereClause,
|
||||
};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn register_action(ident: TokenStream) -> TokenStream {
|
||||
register_action::register_action_macro(ident)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(IntoElement)]
|
||||
pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
||||
derive_into_element::derive_into_element(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Render)]
|
||||
pub fn derive_render(input: TokenStream) -> TokenStream {
|
||||
derive_render::derive_render(input)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn style_helpers(input: TokenStream) -> TokenStream {
|
||||
style_helpers::style_helpers(input)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
let mut namespace = format_ident!("gpui");
|
||||
|
||||
let args = syn::parse_macro_input!(args as AttributeArgs);
|
||||
let mut max_retries = 0;
|
||||
let mut num_iterations = 1;
|
||||
let mut starting_seed = 0;
|
||||
let mut detect_nondeterminism = false;
|
||||
let mut on_failure_fn_name = quote!(None);
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
NestedMeta::Meta(Meta::Path(name))
|
||||
if name.get_ident().map_or(false, |n| n == "self") =>
|
||||
{
|
||||
namespace = format_ident!("crate");
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(meta)) => {
|
||||
let key_name = meta.path.get_ident().map(|i| i.to_string());
|
||||
let result = (|| {
|
||||
match key_name.as_deref() {
|
||||
Some("detect_nondeterminism") => {
|
||||
detect_nondeterminism = parse_bool(&meta.lit)?
|
||||
}
|
||||
Some("retries") => max_retries = parse_int(&meta.lit)?,
|
||||
Some("iterations") => num_iterations = parse_int(&meta.lit)?,
|
||||
Some("seed") => starting_seed = parse_int(&meta.lit)?,
|
||||
Some("on_failure") => {
|
||||
if let Lit::Str(name) = meta.lit {
|
||||
let mut path = syn::Path {
|
||||
leading_colon: None,
|
||||
segments: Default::default(),
|
||||
};
|
||||
for part in name.value().split("::") {
|
||||
path.segments.push(Ident::new(part, name.span()).into());
|
||||
}
|
||||
on_failure_fn_name = quote!(Some(#path));
|
||||
} else {
|
||||
return Err(TokenStream::from(
|
||||
syn::Error::new(
|
||||
meta.lit.span(),
|
||||
"on_failure argument must be a string",
|
||||
)
|
||||
.into_compile_error(),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(TokenStream::from(
|
||||
syn::Error::new(meta.path.span(), "invalid argument")
|
||||
.into_compile_error(),
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
if let Err(tokens) = result {
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return TokenStream::from(
|
||||
syn::Error::new_spanned(other, "invalid argument").into_compile_error(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut inner_fn = parse_macro_input!(function as ItemFn);
|
||||
if max_retries > 0 && num_iterations > 1 {
|
||||
return TokenStream::from(
|
||||
syn::Error::new_spanned(inner_fn, "retries and randomized iterations can't be mixed")
|
||||
.into_compile_error(),
|
||||
);
|
||||
}
|
||||
let inner_fn_attributes = mem::take(&mut inner_fn.attrs);
|
||||
let inner_fn_name = format_ident!("_{}", inner_fn.sig.ident);
|
||||
let outer_fn_name = mem::replace(&mut inner_fn.sig.ident, inner_fn_name.clone());
|
||||
|
||||
let mut outer_fn: ItemFn = if inner_fn.sig.asyncness.is_some() {
|
||||
// Pass to the test function the number of app contexts that it needs,
|
||||
// based on its parameter list.
|
||||
let mut cx_vars = proc_macro2::TokenStream::new();
|
||||
let mut cx_teardowns = proc_macro2::TokenStream::new();
|
||||
let mut inner_fn_args = proc_macro2::TokenStream::new();
|
||||
for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
|
||||
if let FnArg::Typed(arg) = arg {
|
||||
if let Type::Path(ty) = &*arg.ty {
|
||||
let last_segment = ty.path.segments.last();
|
||||
match last_segment.map(|s| s.ident.to_string()).as_deref() {
|
||||
Some("StdRng") => {
|
||||
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
|
||||
continue;
|
||||
}
|
||||
Some("Arc") => {
|
||||
if let syn::PathArguments::AngleBracketed(args) =
|
||||
&last_segment.unwrap().arguments
|
||||
{
|
||||
if let Some(syn::GenericArgument::Type(syn::Type::Path(ty))) =
|
||||
args.args.last()
|
||||
{
|
||||
let last_segment = ty.path.segments.last();
|
||||
if let Some("Deterministic") =
|
||||
last_segment.map(|s| s.ident.to_string()).as_deref()
|
||||
{
|
||||
inner_fn_args.extend(quote!(deterministic.clone(),));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if let Type::Reference(ty) = &*arg.ty {
|
||||
if let Type::Path(ty) = &*ty.elem {
|
||||
let last_segment = ty.path.segments.last();
|
||||
if let Some("TestAppContext") =
|
||||
last_segment.map(|s| s.ident.to_string()).as_deref()
|
||||
{
|
||||
let first_entity_id = ix * 100_000;
|
||||
let cx_varname = format_ident!("cx_{}", ix);
|
||||
cx_vars.extend(quote!(
|
||||
let mut #cx_varname = #namespace::TestAppContext::new(
|
||||
foreground_platform.clone(),
|
||||
cx.platform().clone(),
|
||||
deterministic.build_foreground(#ix),
|
||||
deterministic.build_background(),
|
||||
cx.font_cache().clone(),
|
||||
cx.leak_detector(),
|
||||
#first_entity_id,
|
||||
stringify!(#outer_fn_name).to_string(),
|
||||
);
|
||||
));
|
||||
cx_teardowns.extend(quote!(
|
||||
#cx_varname.remove_all_windows();
|
||||
deterministic.run_until_parked();
|
||||
#cx_varname.update(|cx| cx.clear_globals());
|
||||
));
|
||||
inner_fn_args.extend(quote!(&mut #cx_varname,));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TokenStream::from(
|
||||
syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
|
||||
);
|
||||
}
|
||||
|
||||
parse_quote! {
|
||||
#[test]
|
||||
fn #outer_fn_name() {
|
||||
#inner_fn
|
||||
|
||||
#namespace::test::run_test(
|
||||
#num_iterations as u64,
|
||||
#starting_seed as u64,
|
||||
#max_retries,
|
||||
#detect_nondeterminism,
|
||||
&mut |cx, foreground_platform, deterministic, seed| {
|
||||
// some of the macro contents do not use all variables, silence the warnings
|
||||
let _ = (&cx, &foreground_platform, &deterministic, &seed);
|
||||
#cx_vars
|
||||
cx.foreground().run(#inner_fn_name(#inner_fn_args));
|
||||
#cx_teardowns
|
||||
},
|
||||
#on_failure_fn_name,
|
||||
stringify!(#outer_fn_name).to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pass to the test function the number of app contexts that it needs,
|
||||
// based on its parameter list.
|
||||
let mut cx_vars = proc_macro2::TokenStream::new();
|
||||
let mut cx_teardowns = proc_macro2::TokenStream::new();
|
||||
let mut inner_fn_args = proc_macro2::TokenStream::new();
|
||||
for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
|
||||
if let FnArg::Typed(arg) = arg {
|
||||
if let Type::Path(ty) = &*arg.ty {
|
||||
let last_segment = ty.path.segments.last();
|
||||
|
||||
if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() {
|
||||
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
|
||||
continue;
|
||||
}
|
||||
} else if let Type::Reference(ty) = &*arg.ty {
|
||||
if let Type::Path(ty) = &*ty.elem {
|
||||
let last_segment = ty.path.segments.last();
|
||||
match last_segment.map(|s| s.ident.to_string()).as_deref() {
|
||||
Some("AppContext") => {
|
||||
inner_fn_args.extend(quote!(cx,));
|
||||
continue;
|
||||
}
|
||||
Some("TestAppContext") => {
|
||||
let first_entity_id = ix * 100_000;
|
||||
let cx_varname = format_ident!("cx_{}", ix);
|
||||
cx_vars.extend(quote!(
|
||||
let mut #cx_varname = #namespace::TestAppContext::new(
|
||||
foreground_platform.clone(),
|
||||
cx.platform().clone(),
|
||||
deterministic.build_foreground(#ix),
|
||||
deterministic.build_background(),
|
||||
cx.font_cache().clone(),
|
||||
cx.leak_detector(),
|
||||
#first_entity_id,
|
||||
stringify!(#outer_fn_name).to_string(),
|
||||
);
|
||||
));
|
||||
cx_teardowns.extend(quote!(
|
||||
#cx_varname.remove_all_windows();
|
||||
deterministic.run_until_parked();
|
||||
#cx_varname.update(|cx| cx.clear_globals());
|
||||
));
|
||||
inner_fn_args.extend(quote!(&mut #cx_varname,));
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TokenStream::from(
|
||||
syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
|
||||
);
|
||||
}
|
||||
|
||||
parse_quote! {
|
||||
#[test]
|
||||
fn #outer_fn_name() {
|
||||
#inner_fn
|
||||
|
||||
#namespace::test::run_test(
|
||||
#num_iterations as u64,
|
||||
#starting_seed as u64,
|
||||
#max_retries,
|
||||
#detect_nondeterminism,
|
||||
&mut |cx, foreground_platform, deterministic, seed| {
|
||||
// some of the macro contents do not use all variables, silence the warnings
|
||||
let _ = (&cx, &foreground_platform, &deterministic, &seed);
|
||||
#cx_vars
|
||||
#inner_fn_name(#inner_fn_args);
|
||||
#cx_teardowns
|
||||
},
|
||||
#on_failure_fn_name,
|
||||
stringify!(#outer_fn_name).to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
outer_fn.attrs.extend(inner_fn_attributes);
|
||||
|
||||
TokenStream::from(quote!(#outer_fn))
|
||||
}
|
||||
|
||||
fn parse_int(literal: &Lit) -> Result<usize, TokenStream> {
|
||||
let result = if let Lit::Int(int) = &literal {
|
||||
int.base10_parse()
|
||||
} else {
|
||||
Err(syn::Error::new(literal.span(), "must be an integer"))
|
||||
};
|
||||
|
||||
result.map_err(|err| TokenStream::from(err.into_compile_error()))
|
||||
}
|
||||
|
||||
fn parse_bool(literal: &Lit) -> Result<bool, TokenStream> {
|
||||
let result = if let Lit::Bool(result) = &literal {
|
||||
Ok(result.value)
|
||||
} else {
|
||||
Err(syn::Error::new(literal.span(), "must be a boolean"))
|
||||
};
|
||||
|
||||
result.map_err(|err| TokenStream::from(err.into_compile_error()))
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Element)]
|
||||
pub fn element_derive(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let type_name = ast.ident;
|
||||
|
||||
let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
|
||||
let placeholder_view_type_name: Ident = parse_quote! { V };
|
||||
let view_type_name: Ident;
|
||||
let impl_generics: syn::ImplGenerics<'_>;
|
||||
let type_generics: Option<syn::TypeGenerics<'_>>;
|
||||
let where_clause: Option<&'_ WhereClause>;
|
||||
|
||||
match ast.generics.params.iter().find_map(|param| {
|
||||
if let GenericParam::Type(type_param) = param {
|
||||
Some(type_param.ident.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
Some(type_name) => {
|
||||
view_type_name = type_name;
|
||||
let generics = ast.generics.split_for_impl();
|
||||
impl_generics = generics.0;
|
||||
type_generics = Some(generics.1);
|
||||
where_clause = generics.2;
|
||||
}
|
||||
_ => {
|
||||
view_type_name = placeholder_view_type_name;
|
||||
let generics = placeholder_view_generics.split_for_impl();
|
||||
impl_generics = generics.0;
|
||||
type_generics = None;
|
||||
where_clause = generics.2;
|
||||
}
|
||||
}
|
||||
|
||||
let gen = quote! {
|
||||
impl #impl_generics Element<#view_type_name> for #type_name #type_generics
|
||||
#where_clause
|
||||
{
|
||||
|
||||
type LayoutState = gpui::elements::AnyElement<V>;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: gpui::SizeConstraint,
|
||||
view: &mut V,
|
||||
cx: &mut gpui::ViewContext<V>,
|
||||
) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
|
||||
let mut element = self.render(view, cx).into_any();
|
||||
let size = element.layout(constraint, view, cx);
|
||||
(size, element)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: gpui::geometry::rect::RectF,
|
||||
visible_bounds: gpui::geometry::rect::RectF,
|
||||
element: &mut gpui::elements::AnyElement<V>,
|
||||
view: &mut V,
|
||||
cx: &mut gpui::ViewContext<V>,
|
||||
) {
|
||||
element.paint(bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
&self,
|
||||
range_utf16: std::ops::Range<usize>,
|
||||
_: gpui::geometry::rect::RectF,
|
||||
_: gpui::geometry::rect::RectF,
|
||||
element: &gpui::elements::AnyElement<V>,
|
||||
_: &(),
|
||||
view: &V,
|
||||
cx: &gpui::ViewContext<V>,
|
||||
) -> Option<gpui::geometry::rect::RectF> {
|
||||
element.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
&self,
|
||||
_: gpui::geometry::rect::RectF,
|
||||
element: &gpui::elements::AnyElement<V>,
|
||||
_: &(),
|
||||
view: &V,
|
||||
cx: &gpui::ViewContext<V>,
|
||||
) -> gpui::json::Value {
|
||||
element.debug(view, cx)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gen.into()
|
||||
test::test(args, function)
|
||||
}
|
||||
|
@ -342,7 +342,7 @@ impl Peer {
|
||||
pub fn add_test_connection(
|
||||
self: &Arc<Self>,
|
||||
connection: Connection,
|
||||
executor: Arc<gpui::executor::Background>,
|
||||
executor: gpui::BackgroundExecutor,
|
||||
) -> (
|
||||
ConnectionId,
|
||||
impl Future<Output = anyhow::Result<()>> + Send,
|
||||
@ -559,7 +559,6 @@ mod tests {
|
||||
use async_tungstenite::tungstenite::Message as WebSocketMessage;
|
||||
use gpui::TestAppContext;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::init();
|
||||
@ -568,7 +567,9 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
async fn test_request_response(cx: &mut TestAppContext) {
|
||||
let executor = cx.foreground();
|
||||
init_logger();
|
||||
|
||||
let executor = cx.executor();
|
||||
|
||||
// create 2 clients connected to 1 server
|
||||
let server = Peer::new(0);
|
||||
@ -576,18 +577,18 @@ mod tests {
|
||||
let client2 = Peer::new(0);
|
||||
|
||||
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
||||
Connection::in_memory(cx.background());
|
||||
Connection::in_memory(cx.executor());
|
||||
let (client1_conn_id, io_task1, client1_incoming) =
|
||||
client1.add_test_connection(client1_to_server_conn, cx.background());
|
||||
client1.add_test_connection(client1_to_server_conn, cx.executor());
|
||||
let (_, io_task2, server_incoming1) =
|
||||
server.add_test_connection(server_to_client_1_conn, cx.background());
|
||||
server.add_test_connection(server_to_client_1_conn, cx.executor());
|
||||
|
||||
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
|
||||
Connection::in_memory(cx.background());
|
||||
Connection::in_memory(cx.executor());
|
||||
let (client2_conn_id, io_task3, client2_incoming) =
|
||||
client2.add_test_connection(client2_to_server_conn, cx.background());
|
||||
client2.add_test_connection(client2_to_server_conn, cx.executor());
|
||||
let (_, io_task4, server_incoming2) =
|
||||
server.add_test_connection(server_to_client_2_conn, cx.background());
|
||||
server.add_test_connection(server_to_client_2_conn, cx.executor());
|
||||
|
||||
executor.spawn(io_task1).detach();
|
||||
executor.spawn(io_task2).detach();
|
||||
@ -664,25 +665,25 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
|
||||
let executor = cx.foreground();
|
||||
let executor = cx.executor();
|
||||
let server = Peer::new(0);
|
||||
let client = Peer::new(0);
|
||||
|
||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||
Connection::in_memory(cx.background());
|
||||
Connection::in_memory(executor.clone());
|
||||
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
||||
client.add_test_connection(client_to_server_conn, cx.background());
|
||||
client.add_test_connection(client_to_server_conn, executor.clone());
|
||||
|
||||
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
||||
server.add_test_connection(server_to_client_conn, cx.background());
|
||||
server.add_test_connection(server_to_client_conn, executor.clone());
|
||||
|
||||
executor.spawn(io_task1).detach();
|
||||
executor.spawn(io_task2).detach();
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let request = server_incoming
|
||||
.next()
|
||||
.await
|
||||
let future = server_incoming.next().await;
|
||||
let request = future
|
||||
.unwrap()
|
||||
.into_any()
|
||||
.downcast::<TypedEnvelope<proto::Ping>>()
|
||||
@ -762,16 +763,16 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
|
||||
let executor = cx.foreground();
|
||||
let executor = cx.executor();
|
||||
let server = Peer::new(0);
|
||||
let client = Peer::new(0);
|
||||
|
||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||
Connection::in_memory(cx.background());
|
||||
Connection::in_memory(cx.executor());
|
||||
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
||||
client.add_test_connection(client_to_server_conn, cx.background());
|
||||
client.add_test_connection(client_to_server_conn, cx.executor());
|
||||
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
||||
server.add_test_connection(server_to_client_conn, cx.background());
|
||||
server.add_test_connection(server_to_client_conn, cx.executor());
|
||||
|
||||
executor.spawn(io_task1).detach();
|
||||
executor.spawn(io_task2).detach();
|
||||
@ -858,7 +859,7 @@ mod tests {
|
||||
.detach();
|
||||
|
||||
// Allow the request to make some progress before dropping it.
|
||||
cx.background().simulate_random_delay().await;
|
||||
cx.executor().simulate_random_delay().await;
|
||||
drop(request1_task);
|
||||
|
||||
request2_task.await;
|
||||
@ -874,13 +875,13 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
async fn test_disconnect(cx: &mut TestAppContext) {
|
||||
let executor = cx.foreground();
|
||||
let executor = cx.executor();
|
||||
|
||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
|
||||
|
||||
let client = Peer::new(0);
|
||||
let (connection_id, io_handler, mut incoming) =
|
||||
client.add_test_connection(client_conn, cx.background());
|
||||
client.add_test_connection(client_conn, executor.clone());
|
||||
|
||||
let (io_ended_tx, io_ended_rx) = oneshot::channel();
|
||||
executor
|
||||
@ -910,12 +911,12 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
async fn test_io_error(cx: &mut TestAppContext) {
|
||||
let executor = cx.foreground();
|
||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
||||
let executor = cx.executor();
|
||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
|
||||
|
||||
let client = Peer::new(0);
|
||||
let (connection_id, io_handler, mut incoming) =
|
||||
client.add_test_connection(client_conn, cx.background());
|
||||
client.add_test_connection(client_conn, executor.clone());
|
||||
executor.spawn(io_handler).detach();
|
||||
executor
|
||||
.spawn(async move { incoming.next().await })
|
||||
|
@ -31,6 +31,7 @@ smallvec.workspace = true
|
||||
story = { path = "../story" }
|
||||
theme = { path = "../theme" }
|
||||
menu = { path = "../menu" }
|
||||
ui = { path = "../ui", features = ["stories"] }
|
||||
util = { path = "../util" }
|
||||
picker = { path = "../picker" }
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
use gpui::{
|
||||
div, AnyElement, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
|
||||
div, AnyElement, Element, IntoElement, ParentElement, RenderOnce, Styled,
|
||||
WindowContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::v_stack;
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = {version = "4.0", features = ["derive"]}
|
||||
theme = {path = "../theme"}
|
||||
serde_json.workspace = true
|
||||
schemars.workspace = true
|
@ -1,23 +0,0 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::path::PathBuf;
|
||||
/// Common utilities for Zed developers.
|
||||
// For more information, see [matklad's repository README](https://github.com/matklad/cargo-xtask/)
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
/// Command to run.
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Builds theme types for interop with Typescript.
|
||||
BuildThemeTypes {
|
||||
#[clap(short, long, default_value = "schemas")]
|
||||
out_dir: PathBuf,
|
||||
#[clap(short, long, default_value = "theme.json")]
|
||||
file_name: PathBuf,
|
||||
},
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
mod cli;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use schemars::schema_for;
|
||||
use theme::Theme;
|
||||
|
||||
fn build_themes(out_dir: PathBuf, file_name: PathBuf) -> Result<()> {
|
||||
let theme = schema_for!(Theme);
|
||||
let output = serde_json::to_string_pretty(&theme)?;
|
||||
|
||||
std::fs::create_dir(&out_dir)?;
|
||||
|
||||
let mut file_path = out_dir;
|
||||
file_path.push(file_name);
|
||||
|
||||
std::fs::write(file_path, output)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = cli::Cli::parse();
|
||||
match args.command {
|
||||
cli::Commands::BuildThemeTypes { out_dir, file_name } => build_themes(out_dir, file_name),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user