Order debug JSON and allow elements to be named; copy to clipboard

Nathan Sobo created

Change summary

Cargo.lock                          | 17 +++++++++++
gpui/src/app.rs                     |  4 ++
gpui/src/elements/align.rs          |  2 
gpui/src/elements/container.rs      |  2 
gpui/src/elements/flex.rs           |  2 
gpui/src/elements/label.rs          |  6 +-
gpui/src/elements/line_box.rs       |  2 
gpui/src/elements/new.rs            | 47 ++++++++++++++++++++++++------
gpui/src/platform/mac/app.rs        | 21 ++++++++++++-
gpui/src/platform/mod.rs            |  1 
gpui/src/platform/test.rs           |  2 +
zed/Cargo.toml                      |  4 +-
zed/src/file_finder.rs              |  8 ++--
zed/src/workspace/pane.rs           | 10 +++---
zed/src/workspace/workspace_view.rs |  8 +++-
15 files changed, 104 insertions(+), 32 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -940,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"
@@ -973,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"
@@ -1703,6 +1719,7 @@ version = "1.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
 dependencies = [
+ "indexmap",
  "itoa",
  "ryu",
  "serde",

gpui/src/app.rs 🔗

@@ -1120,6 +1120,10 @@ impl MutableAppContext {
             }
         }
     }
+
+    pub fn copy(&self, text: &str) {
+        self.platform.copy(text);
+    }
 }
 
 impl ModelAsRef for MutableAppContext {

gpui/src/elements/align.rs 🔗

@@ -91,8 +91,8 @@ impl Element for Align {
     ) -> json::Value {
         json!({
             "type": "Align",
-            "alignment": self.alignment.to_json(),
             "bounds": bounds.to_json(),
+            "alignment": self.alignment.to_json(),
             "child": self.child.debug(ctx),
         })
     }

gpui/src/elements/container.rs 🔗

@@ -201,6 +201,7 @@ impl Element for Container {
     ) -> serde_json::Value {
         json!({
             "type": "Container",
+            "bounds": bounds.to_json(),
             "details": {
                 "margin": self.margin.to_json(),
                 "padding": self.padding.to_json(),
@@ -209,7 +210,6 @@ impl Element for Container {
                 "corner_radius": self.corner_radius,
                 "shadow": self.shadow.to_json(),
             },
-            "bounds": bounds.to_json(),
             "child": self.child.debug(ctx),
         })
     }

gpui/src/elements/flex.rs 🔗

@@ -173,8 +173,8 @@ impl Element for Flex {
     ) -> json::Value {
         json!({
             "type": "Flex",
-            "axis": self.axis.to_json(),
             "bounds": bounds.to_json(),
+            "axis": self.axis.to_json(),
             "children": self.children.iter().map(|child| child.debug(ctx)).collect::<Vec<json::Value>>()
         })
     }

gpui/src/elements/label.rs 🔗

@@ -166,11 +166,11 @@ impl Element for Label {
     ) -> Value {
         json!({
             "type": "Label",
-            "font_size": self.font_size,
             "bounds": bounds.to_json(),
-            "text": &self.text,
-            "family_id": ctx.font_cache.family_name(self.family_id).unwrap(),
+            "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(),
         })
     }

gpui/src/elements/line_box.rs 🔗

@@ -99,7 +99,7 @@ impl Element for LineBox {
     ) -> serde_json::Value {
         json!({
             "bounds": bounds.to_json(),
-            "family_id": ctx.font_cache.family_name(self.family_id).unwrap(),
+            "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),

gpui/src/elements/new.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
 };
 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;
@@ -67,7 +67,20 @@ pub trait Element {
     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 }),
+        }
     }
 }
 
@@ -87,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 {
@@ -191,30 +207,41 @@ impl<T: Element> AnyElement for Lifecycle<T> {
 
 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 {
-        self.0.debug(ctx)
+        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
     }
 }

gpui/src/platform/mac/app.rs 🔗

@@ -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);
+        }
+    }
 }

gpui/src/platform/mod.rs 🔗

@@ -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 {

gpui/src/platform/test.rs 🔗

@@ -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 {

zed/Cargo.toml 🔗

@@ -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"

zed/src/file_finder.rs 🔗

@@ -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")
         })
     }
 

zed/src/workspace/pane.rs 🔗

@@ -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")
         }
     }
 

zed/src/workspace/workspace_view.rs 🔗

@@ -258,7 +258,11 @@ impl WorkspaceView {
     pub fn debug_elements(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         match to_string_pretty(&ctx.debug_elements()) {
             Ok(json) => {
-                log::info!("{}", 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);
@@ -373,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>) {