Detailed changes
@@ -930,6 +930,8 @@ dependencies = [
"rand 0.8.3",
"replace_with",
"resvg",
+ "serde",
+ "serde_json",
"simplelog",
"smallvec",
"smol",
@@ -938,6 +940,12 @@ dependencies = [
"usvg",
]
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
[[package]]
name = "hermit-abi"
version = "0.1.18"
@@ -971,6 +979,16 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "indexmap"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
[[package]]
name = "instant"
version = "0.1.9"
@@ -1691,9 +1709,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
-version = "1.0.124"
+version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f"
+checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
[[package]]
name = "serde_json"
@@ -1701,6 +1719,7 @@ version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
+ "indexmap",
"itoa",
"ryu",
"serde",
@@ -4,7 +4,11 @@
Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true.
-Everything is under construction, including this README, but in the meantime, here is a high-level roadmap:
+## Development tips
+
+### Dump element JSON
+
+If you trigger `cmd-shift-i`, Zed will copy a JSON representation of the current window contents to the clipboard. You can paste this in a tool like [DJSON](https://chrome.google.com/webstore/detail/djson-json-viewer-formatt/chaeijjekipecdajnijdldjjipaegdjc?hl=en) to navigate the state of on-screen elements in a structured way.
## Roadmap
@@ -18,6 +18,8 @@ pathfinder_geometry = "0.5"
rand = "0.8.3"
replace_with = "0.1.7"
resvg = "0.14"
+serde = "1.0.125"
+serde_json = "1.0.64"
smallvec = "1.6.1"
smol = "1.2"
tiny-skia = "0.5"
@@ -2,9 +2,10 @@ use gpui::{
color::ColorU,
fonts::{Properties, Weight},
platform::{current as platform, Runner},
- Element as _, Quad,
+ DebugContext, Element as _, Quad,
};
use log::LevelFilter;
+use pathfinder_geometry::rect::RectF;
use simplelog::SimpleLogger;
fn main() {
@@ -59,7 +60,7 @@ impl gpui::Element for TextElement {
fn paint(
&mut self,
- bounds: pathfinder_geometry::rect::RectF,
+ bounds: RectF,
_: &mut Self::LayoutState,
ctx: &mut gpui::PaintContext,
) -> Self::PaintState {
@@ -109,11 +110,21 @@ impl gpui::Element for TextElement {
fn dispatch_event(
&mut self,
_: &gpui::Event,
- _: pathfinder_geometry::rect::RectF,
+ _: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
_: &mut gpui::EventContext,
) -> bool {
false
}
+
+ fn debug(
+ &self,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &DebugContext,
+ ) -> gpui::json::Value {
+ todo!()
+ }
}
@@ -310,6 +310,7 @@ pub struct MutableAppContext {
window_invalidations: HashMap<usize, WindowInvalidation>,
invalidation_callbacks:
HashMap<usize, Box<dyn FnMut(WindowInvalidation, &mut MutableAppContext)>>,
+ debug_elements_callbacks: HashMap<usize, Box<dyn Fn(&AppContext) -> crate::json::Value>>,
foreground: Rc<executor::Foreground>,
future_handlers: Rc<RefCell<HashMap<usize, FutureHandler>>>,
stream_handlers: Rc<RefCell<HashMap<usize, StreamHandler>>>,
@@ -347,6 +348,7 @@ impl MutableAppContext {
observations: HashMap::new(),
window_invalidations: HashMap::new(),
invalidation_callbacks: HashMap::new(),
+ debug_elements_callbacks: HashMap::new(),
foreground,
future_handlers: Default::default(),
stream_handlers: Default::default(),
@@ -373,16 +375,29 @@ impl MutableAppContext {
&self.ctx.background
}
- pub fn on_window_invalidated<F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext)>(
- &mut self,
- window_id: usize,
- callback: F,
- ) {
+ pub fn on_window_invalidated<F>(&mut self, window_id: usize, callback: F)
+ where
+ F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext),
+ {
self.invalidation_callbacks
.insert(window_id, Box::new(callback));
self.update_windows();
}
+ pub fn on_debug_elements<F>(&mut self, window_id: usize, callback: F)
+ where
+ F: 'static + Fn(&AppContext) -> crate::json::Value,
+ {
+ self.debug_elements_callbacks
+ .insert(window_id, Box::new(callback));
+ }
+
+ pub fn debug_elements(&self, window_id: usize) -> Option<crate::json::Value> {
+ self.debug_elements_callbacks
+ .get(&window_id)
+ .map(|debug_elements| debug_elements(&self.ctx))
+ }
+
pub fn add_action<S, V, T, F>(&mut self, name: S, mut handler: F)
where
S: Into<String>,
@@ -692,11 +707,19 @@ impl MutableAppContext {
}));
}
- self.on_window_invalidated(window_id, move |invalidation, ctx| {
- let mut presenter = presenter.borrow_mut();
- presenter.invalidate(invalidation, ctx.downgrade());
- let scene = presenter.build_scene(window.size(), window.scale_factor(), ctx);
- window.present_scene(scene);
+ {
+ let presenter = presenter.clone();
+ self.on_window_invalidated(window_id, move |invalidation, ctx| {
+ let mut presenter = presenter.borrow_mut();
+ presenter.invalidate(invalidation, ctx.downgrade());
+ let scene =
+ presenter.build_scene(window.size(), window.scale_factor(), ctx);
+ window.present_scene(scene);
+ });
+ }
+
+ self.on_debug_elements(window_id, move |ctx| {
+ presenter.borrow().debug_elements(ctx).unwrap()
});
}
}
@@ -1097,6 +1120,10 @@ impl MutableAppContext {
}
}
}
+
+ pub fn copy(&self, text: &str) {
+ self.platform.copy(text);
+ }
}
impl ModelAsRef for MutableAppContext {
@@ -1573,6 +1600,10 @@ impl<'a, T: View> ViewContext<'a, T> {
&self.app.ctx.background
}
+ pub fn debug_elements(&self) -> crate::json::Value {
+ self.app.debug_elements(self.window_id).unwrap()
+ }
+
pub fn focus<S>(&mut self, handle: S)
where
S: Into<AnyViewHandle>,
@@ -0,0 +1,9 @@
+use crate::json::ToJson;
+pub use pathfinder_color::*;
+use serde_json::json;
+
+impl ToJson for ColorU {
+ fn to_json(&self) -> serde_json::Value {
+ json!(format!("0x{:x}{:x}{:x}", self.r, self.g, self.b))
+ }
+}
@@ -1,8 +1,10 @@
use crate::{
- AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
- SizeConstraint,
+ json, AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext,
+ LayoutContext, PaintContext, SizeConstraint,
};
+use json::ToJson;
use pathfinder_geometry::vector::{vec2f, Vector2F};
+use serde_json::json;
pub struct Align {
child: ElementBox,
@@ -79,4 +81,19 @@ impl Element for Align {
) -> bool {
self.child.dispatch_event(event, ctx)
}
+
+ fn debug(
+ &self,
+ bounds: pathfinder_geometry::rect::RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> json::Value {
+ json!({
+ "type": "Align",
+ "bounds": bounds.to_json(),
+ "alignment": self.alignment.to_json(),
+ "child": self.child.debug(ctx),
+ })
+ }
}
@@ -1,5 +1,9 @@
use super::Element;
-use crate::PaintContext;
+use crate::{
+ json::{self, json},
+ DebugContext, PaintContext,
+};
+use json::ToJson;
use pathfinder_geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
@@ -70,4 +74,14 @@ where
) -> bool {
false
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &DebugContext,
+ ) -> json::Value {
+ json!({"type": "Canvas", "bounds": bounds.to_json()})
+ }
}
@@ -1,8 +1,11 @@
+use json::ToJson;
+use serde_json::json;
+
use crate::{
- AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
- SizeConstraint,
+ geometry::{rect::RectF, vector::Vector2F},
+ json, AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext,
+ LayoutContext, PaintContext, SizeConstraint,
};
-use pathfinder_geometry::vector::Vector2F;
pub struct ConstrainedBox {
child: ElementBox,
@@ -63,7 +66,7 @@ impl Element for ConstrainedBox {
fn paint(
&mut self,
- bounds: pathfinder_geometry::rect::RectF,
+ bounds: RectF,
_: &mut Self::LayoutState,
ctx: &mut PaintContext,
) -> Self::PaintState {
@@ -73,11 +76,21 @@ impl Element for ConstrainedBox {
fn dispatch_event(
&mut self,
event: &Event,
- _: pathfinder_geometry::rect::RectF,
+ _: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
ctx: &mut EventContext,
) -> bool {
self.child.dispatch_event(event, ctx)
}
+
+ fn debug(
+ &self,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> json::Value {
+ json!({"type": "ConstrainedBox", "constraint": self.constraint.to_json(), "child": self.child.debug(ctx)})
+ }
}
@@ -1,8 +1,10 @@
use pathfinder_geometry::rect::RectF;
+use serde_json::json;
use crate::{
color::ColorU,
geometry::vector::{vec2f, Vector2F},
+ json::ToJson,
scene::{self, Border, Quad},
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
@@ -189,6 +191,28 @@ impl Element for Container {
) -> bool {
self.child.dispatch_event(event, ctx)
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &crate::DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "type": "Container",
+ "bounds": bounds.to_json(),
+ "details": {
+ "margin": self.margin.to_json(),
+ "padding": self.padding.to_json(),
+ "background_color": self.background_color.to_json(),
+ "border": self.border.to_json(),
+ "corner_radius": self.corner_radius,
+ "shadow": self.shadow.to_json(),
+ },
+ "child": self.child.debug(ctx),
+ })
+ }
}
#[derive(Default)]
@@ -199,6 +223,25 @@ pub struct Margin {
right: f32,
}
+impl ToJson for Margin {
+ fn to_json(&self) -> serde_json::Value {
+ let mut value = json!({});
+ if self.top > 0. {
+ value["top"] = json!(self.top);
+ }
+ if self.right > 0. {
+ value["right"] = json!(self.right);
+ }
+ if self.bottom > 0. {
+ value["bottom"] = json!(self.bottom);
+ }
+ if self.left > 0. {
+ value["left"] = json!(self.left);
+ }
+ value
+ }
+}
+
#[derive(Default)]
pub struct Padding {
top: f32,
@@ -207,9 +250,38 @@ pub struct Padding {
right: f32,
}
+impl ToJson for Padding {
+ fn to_json(&self) -> serde_json::Value {
+ let mut value = json!({});
+ if self.top > 0. {
+ value["top"] = json!(self.top);
+ }
+ if self.right > 0. {
+ value["right"] = json!(self.right);
+ }
+ if self.bottom > 0. {
+ value["bottom"] = json!(self.bottom);
+ }
+ if self.left > 0. {
+ value["left"] = json!(self.left);
+ }
+ value
+ }
+}
+
#[derive(Default)]
pub struct Shadow {
offset: Vector2F,
blur: f32,
color: ColorU,
}
+
+impl ToJson for Shadow {
+ fn to_json(&self) -> serde_json::Value {
+ json!({
+ "offset": self.offset.to_json(),
+ "blur": self.blur,
+ "color": self.color.to_json()
+ })
+ }
+}
@@ -1,6 +1,10 @@
-use crate::geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
+use crate::{
+ geometry::{
+ rect::RectF,
+ vector::{vec2f, Vector2F},
+ },
+ json::{json, ToJson},
+ DebugContext,
};
use crate::{
AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
@@ -58,4 +62,17 @@ impl Element for Empty {
) -> bool {
false
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "type": "Empty",
+ "bounds": bounds.to_json(),
+ })
+ }
}
@@ -1,6 +1,9 @@
+use pathfinder_geometry::rect::RectF;
+use serde_json::json;
+
use crate::{
- geometry::vector::Vector2F, AfterLayoutContext, Element, ElementBox, Event, EventContext,
- LayoutContext, PaintContext, SizeConstraint,
+ geometry::vector::Vector2F, AfterLayoutContext, DebugContext, Element, ElementBox, Event,
+ EventContext, LayoutContext, PaintContext, SizeConstraint,
};
pub struct EventHandler {
@@ -49,7 +52,7 @@ impl Element for EventHandler {
fn paint(
&mut self,
- bounds: pathfinder_geometry::rect::RectF,
+ bounds: RectF,
_: &mut Self::LayoutState,
ctx: &mut PaintContext,
) -> Self::PaintState {
@@ -59,7 +62,7 @@ impl Element for EventHandler {
fn dispatch_event(
&mut self,
event: &Event,
- bounds: pathfinder_geometry::rect::RectF,
+ bounds: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
ctx: &mut EventContext,
@@ -80,4 +83,17 @@ impl Element for EventHandler {
}
}
}
+
+ fn debug(
+ &self,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "type": "EventHandler",
+ "child": self.child.debug(ctx),
+ })
+ }
}
@@ -1,10 +1,15 @@
use std::any::Any;
use crate::{
- AfterLayoutContext, Axis, Element, ElementBox, Event, EventContext, LayoutContext,
- PaintContext, SizeConstraint, Vector2FExt,
+ json::{self, ToJson, Value},
+ AfterLayoutContext, Axis, DebugContext, Element, ElementBox, Event, EventContext,
+ LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
};
-use pathfinder_geometry::vector::{vec2f, Vector2F};
+use pathfinder_geometry::{
+ rect::RectF,
+ vector::{vec2f, Vector2F},
+};
+use serde_json::json;
pub struct Flex {
axis: Axis,
@@ -130,7 +135,7 @@ impl Element for Flex {
fn paint(
&mut self,
- bounds: pathfinder_geometry::rect::RectF,
+ bounds: RectF,
_: &mut Self::LayoutState,
ctx: &mut PaintContext,
) -> Self::PaintState {
@@ -147,7 +152,7 @@ impl Element for Flex {
fn dispatch_event(
&mut self,
event: &Event,
- _: pathfinder_geometry::rect::RectF,
+ _: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
ctx: &mut EventContext,
@@ -158,6 +163,21 @@ impl Element for Flex {
}
handled
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> json::Value {
+ json!({
+ "type": "Flex",
+ "bounds": bounds.to_json(),
+ "axis": self.axis.to_json(),
+ "children": self.children.iter().map(|child| child.debug(ctx)).collect::<Vec<json::Value>>()
+ })
+ }
}
struct FlexParentData {
@@ -202,7 +222,7 @@ impl Element for Expanded {
fn paint(
&mut self,
- bounds: pathfinder_geometry::rect::RectF,
+ bounds: RectF,
_: &mut Self::LayoutState,
ctx: &mut PaintContext,
) -> Self::PaintState {
@@ -212,7 +232,7 @@ impl Element for Expanded {
fn dispatch_event(
&mut self,
event: &Event,
- _: pathfinder_geometry::rect::RectF,
+ _: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
ctx: &mut EventContext,
@@ -223,4 +243,18 @@ impl Element for Expanded {
fn metadata(&self) -> Option<&dyn Any> {
Some(&self.metadata)
}
+
+ fn debug(
+ &self,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> Value {
+ json!({
+ "type": "Expanded",
+ "flex": self.metadata.flex,
+ "child": self.child.debug(ctx)
+ })
+ }
}
@@ -1,3 +1,5 @@
+use serde_json::json;
+
use crate::{
color::ColorU,
font_cache::FamilyId,
@@ -6,8 +8,10 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
+ json::{ToJson, Value},
text_layout::Line,
- AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
+ AfterLayoutContext, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext,
+ SizeConstraint,
};
use std::{ops::Range, sync::Arc};
@@ -152,4 +156,32 @@ impl Element for Label {
) -> bool {
false
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> Value {
+ json!({
+ "type": "Label",
+ "bounds": bounds.to_json(),
+ "font_family": ctx.font_cache.family_name(self.family_id).unwrap(),
+ "font_size": self.font_size,
+ "font_properties": self.font_properties.to_json(),
+ "text": &self.text,
+ "highlights": self.highlights.to_json(),
+ })
+ }
+}
+
+impl ToJson for Highlights {
+ fn to_json(&self) -> Value {
+ json!({
+ "color": self.color.to_json(),
+ "indices": self.indices,
+ "font_properties": self.font_properties.to_json(),
+ })
+ }
}
@@ -1,9 +1,13 @@
use crate::{
font_cache::FamilyId,
fonts::Properties,
- geometry::vector::{vec2f, Vector2F},
- AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
- SizeConstraint,
+ geometry::{
+ rect::RectF,
+ vector::{vec2f, Vector2F},
+ },
+ json::{json, ToJson},
+ AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext,
+ PaintContext, SizeConstraint,
};
pub struct LineBox {
@@ -85,4 +89,20 @@ impl Element for LineBox {
) -> bool {
self.child.dispatch_event(event, ctx)
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "bounds": bounds.to_json(),
+ "font_family": ctx.font_cache.family_name(self.family_id).unwrap(),
+ "font_size": self.font_size,
+ "font_properties": self.font_properties.to_json(),
+ "child": self.child.debug(ctx),
+ })
+ }
}
@@ -1,16 +1,18 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- AfterLayoutContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
+ json, AfterLayoutContext, DebugContext, Event, EventContext, LayoutContext, PaintContext,
+ SizeConstraint,
};
use core::panic;
use replace_with::replace_with_or_abort;
-use std::any::Any;
+use std::{any::Any, borrow::Cow};
trait AnyElement {
fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F;
fn after_layout(&mut self, _: &mut AfterLayoutContext) {}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext);
fn dispatch_event(&mut self, event: &Event, ctx: &mut EventContext) -> bool;
+ fn debug(&self, ctx: &DebugContext) -> serde_json::Value;
fn size(&self) -> Vector2F;
fn metadata(&self) -> Option<&dyn Any>;
@@ -53,11 +55,32 @@ pub trait Element {
None
}
+ fn debug(
+ &self,
+ bounds: RectF,
+ layout: &Self::LayoutState,
+ paint: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> serde_json::Value;
+
fn boxed(self) -> ElementBox
where
Self: 'static + Sized,
{
- ElementBox(Box::new(Lifecycle::Init { element: self }))
+ ElementBox {
+ name: None,
+ element: Box::new(Lifecycle::Init { element: self }),
+ }
+ }
+
+ fn named(self, name: impl Into<Cow<'static, str>>) -> ElementBox
+ where
+ Self: 'static + Sized,
+ {
+ ElementBox {
+ name: Some(name.into()),
+ element: Box::new(Lifecycle::Init { element: self }),
+ }
}
}
@@ -77,7 +100,10 @@ pub enum Lifecycle<T: Element> {
paint: T::PaintState,
},
}
-pub struct ElementBox(Box<dyn AnyElement>);
+pub struct ElementBox {
+ name: Option<Cow<'static, str>>,
+ element: Box<dyn AnyElement>,
+}
impl<T: Element> AnyElement for Lifecycle<T> {
fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F {
@@ -165,30 +191,57 @@ impl<T: Element> AnyElement for Lifecycle<T> {
| Lifecycle::PostPaint { element, .. } => element.metadata(),
}
}
+
+ fn debug(&self, ctx: &DebugContext) -> serde_json::Value {
+ match self {
+ Lifecycle::PostPaint {
+ element,
+ bounds,
+ layout,
+ paint,
+ } => element.debug(*bounds, layout, paint, ctx),
+ _ => panic!("invalid element lifecycle state"),
+ }
+ }
}
impl ElementBox {
pub fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F {
- self.0.layout(constraint, ctx)
+ self.element.layout(constraint, ctx)
}
pub fn after_layout(&mut self, ctx: &mut AfterLayoutContext) {
- self.0.after_layout(ctx);
+ self.element.after_layout(ctx);
}
pub fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext) {
- self.0.paint(origin, ctx);
+ self.element.paint(origin, ctx);
}
pub fn dispatch_event(&mut self, event: &Event, ctx: &mut EventContext) -> bool {
- self.0.dispatch_event(event, ctx)
+ self.element.dispatch_event(event, ctx)
}
pub fn size(&self) -> Vector2F {
- self.0.size()
+ self.element.size()
}
pub fn metadata(&self) -> Option<&dyn Any> {
- self.0.metadata()
+ self.element.metadata()
+ }
+
+ pub fn debug(&self, ctx: &DebugContext) -> json::Value {
+ let mut value = self.element.debug(ctx);
+
+ if let Some(name) = &self.name {
+ if let json::Value::Object(map) = &mut value {
+ let mut new_map: crate::json::Map<String, serde_json::Value> = Default::default();
+ new_map.insert("name".into(), json::Value::String(name.to_string()));
+ new_map.append(map);
+ return json::Value::Object(new_map);
+ }
+ }
+
+ value
}
}
@@ -1,7 +1,8 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
- SizeConstraint,
+ json::{self, json, ToJson},
+ AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext,
+ PaintContext, SizeConstraint,
};
pub struct Stack {
@@ -71,6 +72,20 @@ impl Element for Stack {
}
false
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> json::Value {
+ json!({
+ "type": "Stack",
+ "bounds": bounds.to_json(),
+ "children": self.children.iter().map(|child| child.debug(ctx)).collect::<Vec<json::Value>>()
+ })
+ }
}
impl Extend<ElementBox> for Stack {
@@ -1,11 +1,13 @@
+use serde_json::json;
+
use crate::{
color::ColorU,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- scene, AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext,
- SizeConstraint,
+ scene, AfterLayoutContext, DebugContext, Element, Event, EventContext, LayoutContext,
+ PaintContext, SizeConstraint,
};
pub struct Svg {
@@ -86,8 +88,25 @@ impl Element for Svg {
) -> bool {
false
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "type": "Svg",
+ "bounds": bounds.to_json(),
+ "path": self.path,
+ "color": self.color.to_json(),
+ })
+ }
}
+use crate::json::ToJson;
+
fn from_usvg_rect(rect: usvg::Rect) -> RectF {
RectF::new(
vec2f(rect.x() as f32, rect.y() as f32),
@@ -7,8 +7,10 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
+ json::{self, json},
ElementBox,
};
+use json::ToJson;
use parking_lot::Mutex;
use std::{cmp, ops::Range, sync::Arc};
@@ -236,4 +238,21 @@ where
handled
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ layout: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &crate::DebugContext,
+ ) -> json::Value {
+ json!({
+ "type": "UniformList",
+ "bounds": bounds.to_json(),
+ "scroll_max": layout.scroll_max,
+ "item_height": layout.item_height,
+ "items": layout.items.iter().map(|item| item.debug(ctx)).collect::<Vec<json::Value>>()
+
+ })
+ }
}
@@ -36,6 +36,15 @@ impl FontCache {
}))
}
+ pub fn family_name(&self, family_id: FamilyId) -> Result<String> {
+ self.0
+ .read()
+ .families
+ .get(family_id.0)
+ .ok_or_else(|| anyhow!("invalid family id"))
+ .map(|family| family.name.clone())
+ }
+
pub fn load_family(&self, names: &[&str]) -> Result<FamilyId> {
for name in names {
let state = self.0.upgradable_read();
@@ -1,7 +1,62 @@
+use crate::json::json;
pub use font_kit::metrics::Metrics;
-pub use font_kit::properties::{Properties, Weight};
+pub use font_kit::properties::{Properties, Stretch, Style, Weight};
+
+use crate::json::ToJson;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FontId(pub usize);
pub type GlyphId = u32;
+
+impl ToJson for Properties {
+ fn to_json(&self) -> crate::json::Value {
+ json!({
+ "style": self.style.to_json(),
+ "weight": self.weight.to_json(),
+ "stretch": self.stretch.to_json(),
+ })
+ }
+}
+
+impl ToJson for Style {
+ fn to_json(&self) -> crate::json::Value {
+ match self {
+ Style::Normal => json!("normal"),
+ Style::Italic => json!("italic"),
+ Style::Oblique => json!("oblique"),
+ }
+ }
+}
+
+impl ToJson for Weight {
+ fn to_json(&self) -> crate::json::Value {
+ if self.0 == Weight::THIN.0 {
+ json!("thin")
+ } else if self.0 == Weight::EXTRA_LIGHT.0 {
+ json!("extra light")
+ } else if self.0 == Weight::LIGHT.0 {
+ json!("light")
+ } else if self.0 == Weight::NORMAL.0 {
+ json!("normal")
+ } else if self.0 == Weight::MEDIUM.0 {
+ json!("medium")
+ } else if self.0 == Weight::SEMIBOLD.0 {
+ json!("semibold")
+ } else if self.0 == Weight::BOLD.0 {
+ json!("bold")
+ } else if self.0 == Weight::EXTRA_BOLD.0 {
+ json!("extra bold")
+ } else if self.0 == Weight::BLACK.0 {
+ json!("black")
+ } else {
+ json!(self.0)
+ }
+ }
+}
+
+impl ToJson for Stretch {
+ fn to_json(&self) -> serde_json::Value {
+ json!(self.0)
+ }
+}
@@ -1,7 +1,8 @@
use super::scene::{Path, PathVertex};
-use crate::color::ColorU;
+use crate::{color::ColorU, json::ToJson};
pub use pathfinder_geometry::*;
use rect::RectF;
+use serde_json::json;
use vector::{vec2f, Vector2F};
pub struct PathBuilder {
@@ -106,3 +107,15 @@ impl PathBuilder {
}
}
}
+
+impl ToJson for Vector2F {
+ fn to_json(&self) -> serde_json::Value {
+ json!([self.x(), self.y()])
+ }
+}
+
+impl ToJson for RectF {
+ fn to_json(&self) -> serde_json::Value {
+ json!({"origin": self.origin().to_json(), "size": self.size().to_json()})
+ }
+}
@@ -0,0 +1,15 @@
+pub use serde_json::*;
+
+pub trait ToJson {
+ fn to_json(&self) -> Value;
+}
+
+impl<T: ToJson> ToJson for Option<T> {
+ fn to_json(&self) -> Value {
+ if let Some(value) = self.as_ref() {
+ value.to_json()
+ } else {
+ json!(null)
+ }
+ }
+}
@@ -18,11 +18,12 @@ mod util;
pub use elements::{Element, ElementBox};
pub mod executor;
pub use executor::Task;
+pub mod color;
+pub mod json;
pub mod keymap;
pub mod platform;
-pub use pathfinder_color as color;
pub use platform::Event;
pub use presenter::{
- AfterLayoutContext, Axis, EventContext, LayoutContext, PaintContext, SizeConstraint,
- Vector2FExt,
+ AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
+ SizeConstraint, Vector2FExt,
};
@@ -1,9 +1,13 @@
use super::{BoolExt as _, Dispatcher, FontSystem, Window};
use crate::{executor, platform};
use anyhow::Result;
-use cocoa::base::id;
+use cocoa::{
+ appkit::{NSPasteboard, NSPasteboardTypeString},
+ base::{id, nil},
+ foundation::NSData,
+};
use objc::{class, msg_send, sel, sel_impl};
-use std::{rc::Rc, sync::Arc};
+use std::{ffi::c_void, rc::Rc, sync::Arc};
pub struct App {
dispatcher: Arc<Dispatcher>,
@@ -42,4 +46,17 @@ impl platform::App for App {
fn fonts(&self) -> Arc<dyn platform::FontSystem> {
self.fonts.clone()
}
+
+ fn copy(&self, text: &str) {
+ unsafe {
+ let data = NSData::dataWithBytes_length_(
+ nil,
+ text.as_ptr() as *const c_void,
+ text.len() as u64,
+ );
+ let pasteboard = NSPasteboard::generalPasteboard(nil);
+ pasteboard.clearContents();
+ pasteboard.setData_forType(data, NSPasteboardTypeString);
+ }
+ }
}
@@ -40,6 +40,7 @@ pub trait App {
executor: Rc<executor::Foreground>,
) -> Result<Box<dyn Window>>;
fn fonts(&self) -> Arc<dyn FontSystem>;
+ fn copy(&self, text: &str);
}
pub trait Dispatcher: Send + Sync {
@@ -46,6 +46,8 @@ impl super::App for App {
fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
self.fonts.clone()
}
+
+ fn copy(&self, _: &str) {}
}
impl Window {
@@ -2,11 +2,13 @@ use crate::{
app::{AppContext, MutableAppContext, WindowInvalidation},
elements::Element,
font_cache::FontCache,
+ json::{self, ToJson},
platform::Event,
text_layout::TextLayoutCache,
AssetCache, ElementBox, Scene,
};
use pathfinder_geometry::vector::{vec2f, Vector2F};
+use serde_json::json;
use std::{any::Any, collections::HashMap, sync::Arc};
pub struct Presenter {
@@ -128,6 +130,18 @@ impl Presenter {
Vec::new()
}
}
+
+ pub fn debug_elements(&self, ctx: &AppContext) -> Option<json::Value> {
+ ctx.root_view_id(self.window_id)
+ .and_then(|root_view_id| self.rendered_views.get(&root_view_id))
+ .map(|root_element| {
+ root_element.debug(&DebugContext {
+ rendered_views: &self.rendered_views,
+ font_cache: &self.font_cache,
+ app: ctx,
+ })
+ })
+ }
}
pub struct ActionToDispatch {
@@ -224,6 +238,12 @@ impl<'a> EventContext<'a> {
}
}
+pub struct DebugContext<'a> {
+ rendered_views: &'a HashMap<usize, ElementBox>,
+ pub font_cache: &'a FontCache,
+ pub app: &'a AppContext,
+}
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Axis {
Horizontal,
@@ -239,6 +259,15 @@ impl Axis {
}
}
+impl ToJson for Axis {
+ fn to_json(&self) -> serde_json::Value {
+ match self {
+ Axis::Horizontal => json!("horizontal"),
+ Axis::Vertical => json!("vertical"),
+ }
+ }
+}
+
pub trait Vector2FExt {
fn along(self, axis: Axis) -> f32;
}
@@ -291,6 +320,15 @@ impl SizeConstraint {
}
}
+impl ToJson for SizeConstraint {
+ fn to_json(&self) -> serde_json::Value {
+ json!({
+ "min": self.min.to_json(),
+ "max": self.max.to_json(),
+ })
+ }
+}
+
pub struct ChildView {
view_id: usize,
}
@@ -342,6 +380,25 @@ impl Element for ChildView {
) -> bool {
ctx.dispatch_event(self.view_id, event)
}
+
+ fn debug(
+ &self,
+ bounds: pathfinder_geometry::rect::RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ ctx: &DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "type": "ChildView",
+ "view_id": self.view_id,
+ "bounds": bounds.to_json(),
+ "child": if let Some(view) = ctx.rendered_views.get(&self.view_id) {
+ view.debug(ctx)
+ } else {
+ json!(null)
+ }
+ })
+ }
}
#[cfg(test)]
@@ -1,7 +1,10 @@
+use serde_json::json;
+
use crate::{
color::ColorU,
fonts::{FontId, GlyphId},
geometry::{rect::RectF, vector::Vector2F},
+ json::ToJson,
};
pub struct Scene {
@@ -258,3 +261,22 @@ impl Border {
}
}
}
+
+impl ToJson for Border {
+ fn to_json(&self) -> serde_json::Value {
+ let mut value = json!({});
+ if self.top {
+ value["top"] = json!(self.width);
+ }
+ if self.right {
+ value["right"] = json!(self.width);
+ }
+ if self.bottom {
+ value["bottom"] = json!(self.width);
+ }
+ if self.left {
+ value["left"] = json!(self.width);
+ }
+ value
+ }
+}
@@ -18,9 +18,9 @@ arrayvec = "0.5.2"
crossbeam-channel = "0.5.0"
dirs = "3.0"
easy-parallel = "3.1.0"
+futures-core = "0.3"
gpui = {path = "../gpui"}
ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"}
-futures-core = "0.3"
lazy_static = "1.4.0"
libc = "0.2"
log = "0.4"
@@ -33,6 +33,6 @@ smallvec = "1.6.1"
smol = "1.2.5"
[dev-dependencies]
-serde_json = "1.0.64"
+serde_json = {version = "1.0.64", features = ["preserve_order"]}
tempdir = "0.3.7"
unindent = "0.1.7"
@@ -6,10 +6,12 @@ use gpui::{
vector::{vec2f, Vector2F},
PathBuilder,
},
+ json::{self, ToJson},
text_layout::{self, TextLayoutCache},
AfterLayoutContext, AppContext, Border, Element, Event, EventContext, FontCache, LayoutContext,
PaintContext, Quad, Scene, SizeConstraint, ViewHandle,
};
+use json::json;
use smallvec::SmallVec;
use std::cmp::Ordering;
use std::{
@@ -477,6 +479,19 @@ impl Element for BufferElement {
false
}
}
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &gpui::DebugContext,
+ ) -> json::Value {
+ json!({
+ "type": "BufferElement",
+ "bounds": bounds.to_json()
+ })
+ }
}
pub struct LayoutState {
@@ -78,7 +78,7 @@ impl View for FileFinder {
.boxed(),
)
.top_center()
- .boxed()
+ .named("file finder")
}
fn on_focus(&mut self, ctx: &mut ViewContext<Self>) {
@@ -105,7 +105,7 @@ impl FileFinder {
.boxed(),
)
.with_margin_top(6.0)
- .boxed();
+ .named("empty matches");
}
let handle = self.handle.clone();
@@ -127,7 +127,7 @@ impl FileFinder {
.with_background_color(ColorU::from_u32(0xf7f7f7ff))
.with_border(Border::all(1.0, ColorU::from_u32(0xdbdbdcff)))
.with_margin_top(6.0)
- .boxed()
+ .named("matches")
}
fn render_match(
@@ -226,7 +226,7 @@ impl FileFinder {
ctx.dispatch_action("file_finder:select", (tree_id, entry_id));
true
})
- .boxed()
+ .named("match")
})
}
@@ -244,7 +244,7 @@ impl Pane {
.with_max_width(264.0)
.boxed(),
)
- .boxed(),
+ .named("tab"),
);
}
@@ -263,10 +263,10 @@ impl Pane {
.with_border(Border::bottom(1.0, border_color))
.boxed(),
)
- .boxed(),
+ .named("filler"),
);
- row.boxed()
+ row.named("tabs")
}
fn render_modified_icon(is_modified: bool) -> ElementBox {
@@ -304,9 +304,9 @@ impl View for Pane {
Flex::column()
.with_child(self.render_tabs(app))
.with_child(Expanded::new(1.0, ChildView::new(active_item.id()).boxed()).boxed())
- .boxed()
+ .named("pane")
} else {
- Empty::new().boxed()
+ Empty::new().named("pane")
}
}
@@ -2,15 +2,19 @@ use super::{pane, Pane, PaneGroup, SplitDirection, Workspace};
use crate::{settings::Settings, watch};
use futures_core::future::LocalBoxFuture;
use gpui::{
- color::rgbu, elements::*, keymap::Binding, AnyViewHandle, App, AppContext, Entity, ModelHandle,
- MutableAppContext, View, ViewContext, ViewHandle,
+ color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, App,
+ AppContext, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
};
use log::{error, info};
use std::{collections::HashSet, path::PathBuf};
pub fn init(app: &mut App) {
app.add_action("workspace:save", WorkspaceView::save_active_item);
- app.add_bindings(vec![Binding::new("cmd-s", "workspace:save", None)]);
+ app.add_action("workspace:debug_elements", WorkspaceView::debug_elements);
+ app.add_bindings(vec![
+ Binding::new("cmd-s", "workspace:save", None),
+ Binding::new("cmd-alt-i", "workspace:debug_elements", None),
+ ]);
}
pub trait ItemView: View {
@@ -251,6 +255,21 @@ impl WorkspaceView {
});
}
+ pub fn debug_elements(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+ match to_string_pretty(&ctx.debug_elements()) {
+ Ok(json) => {
+ ctx.app_mut().copy(&json);
+ log::info!(
+ "copied {:.1} KiB of element debug JSON to the clipboard",
+ json.len() as f32 / 1024.
+ );
+ }
+ Err(error) => {
+ log::error!("error debugging elements: {}", error);
+ }
+ };
+ }
+
fn workspace_updated(&mut self, _: ModelHandle<Workspace>, ctx: &mut ViewContext<Self>) {
ctx.notify();
}
@@ -358,7 +377,7 @@ impl View for WorkspaceView {
.boxed(),
)
.with_background_color(rgbu(0xea, 0xea, 0xeb))
- .boxed()
+ .named("workspace")
}
fn on_focus(&mut self, ctx: &mut ViewContext<Self>) {