Add initial element inspector for Zed development (#31315)

Open inspector with `dev: toggle inspector` from command palette or
`cmd-alt-i` on mac or `ctrl-alt-i` on linux.

https://github.com/user-attachments/assets/54c43034-d40b-414e-ba9b-190bed2e6d2f

* Picking of elements via the mouse, with scroll wheel to inspect
occluded elements.

* Temporary manipulation of the selected element.

* Layout info and JSON-based style manipulation for `Div`.

* Navigation to code that constructed the element.

Big thanks to @as-cii and @maxdeviant for sorting out how to implement
the core of an inspector.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Federico Dionisi <code@fdionisi.me>
This commit is contained in:
Michael Sloan 2025-05-23 17:08:59 -06:00 committed by GitHub
parent 685933b5c8
commit ab59982bf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 2631 additions and 406 deletions

View file

@ -2,13 +2,15 @@
//! can be used to describe common units, concepts, and the relationships
//! between them.
use anyhow::{Context as _, anyhow};
use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
use refineable::Refineable;
use serde_derive::{Deserialize, Serialize};
use schemars::{JsonSchema, SchemaGenerator, schema::Schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::{
cmp::{self, PartialOrd},
fmt,
fmt::{self, Display},
hash::Hash,
ops::{Add, Div, Mul, MulAssign, Neg, Sub},
};
@ -71,9 +73,10 @@ pub trait Along {
Eq,
Serialize,
Deserialize,
JsonSchema,
Hash,
)]
#[refineable(Debug)]
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Point<T: Default + Clone + Debug> {
/// The x coordinate of the point.
@ -375,12 +378,18 @@ impl<T: Clone + Default + Debug> Clone for Point<T> {
}
}
impl<T: Default + Clone + Debug + Display> Display for Point<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
/// A structure representing a two-dimensional size with width and height in a given unit.
///
/// This struct is generic over the type `T`, which can be any type that implements `Clone`, `Default`, and `Debug`.
/// It is commonly used to specify dimensions for elements in a UI, such as a window or element.
#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
#[refineable(Debug)]
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Size<T: Clone + Default + Debug> {
/// The width component of the size.
@ -649,6 +658,12 @@ where
}
}
impl<T: Default + Clone + Debug + Display> Display for Size<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} × {}", self.width, self.height)
}
}
impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
fn from(point: Point<T>) -> Self {
Self {
@ -1541,6 +1556,18 @@ impl<T: PartialOrd + Default + Debug + Clone> Bounds<T> {
}
}
impl<T: Default + Clone + Debug + Display + Add<T, Output = T>> Display for Bounds<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} - {} (size {})",
self.origin,
self.bottom_right(),
self.size
)
}
}
impl Size<DevicePixels> {
/// Converts the size from physical to logical pixels.
pub(crate) fn to_pixels(self, scale_factor: f32) -> Size<Pixels> {
@ -1647,7 +1674,7 @@ impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
/// assert_eq!(edges.left, 40.0);
/// ```
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(Debug)]
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Edges<T: Clone + Default + Debug> {
/// The size of the top edge.
@ -2124,7 +2151,7 @@ impl Corner {
///
/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(Debug)]
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Corners<T: Clone + Default + Debug> {
/// The value associated with the top left corner.
@ -2508,16 +2535,11 @@ impl From<Percentage> for Radians {
PartialEq,
Serialize,
Deserialize,
JsonSchema,
)]
#[repr(transparent)]
pub struct Pixels(pub f32);
impl std::fmt::Display for Pixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{}px", self.0))
}
}
impl Div for Pixels {
type Output = f32;
@ -2584,6 +2606,30 @@ impl MulAssign<f32> for Pixels {
}
}
impl Display for Pixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}px", self.0)
}
}
impl Debug for Pixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
impl TryFrom<&'_ str> for Pixels {
type Error = anyhow::Error;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
value
.strip_suffix("px")
.context("expected 'px' suffix")
.and_then(|number| Ok(number.parse()?))
.map(Self)
}
}
impl Pixels {
/// Represents zero pixels.
pub const ZERO: Pixels = Pixels(0.0);
@ -2706,12 +2752,6 @@ impl From<f32> for Pixels {
}
}
impl Debug for Pixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} px", self.0)
}
}
impl From<Pixels> for f32 {
fn from(pixels: Pixels) -> Self {
pixels.0
@ -2910,7 +2950,7 @@ impl Ord for ScaledPixels {
impl Debug for ScaledPixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} px (scaled)", self.0)
write!(f, "{}px (scaled)", self.0)
}
}
@ -3032,9 +3072,27 @@ impl Mul<Pixels> for Rems {
}
}
impl Display for Rems {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}rem", self.0)
}
}
impl Debug for Rems {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} rem", self.0)
Display::fmt(self, f)
}
}
impl TryFrom<&'_ str> for Rems {
type Error = anyhow::Error;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
value
.strip_suffix("rem")
.context("expected 'rem' suffix")
.and_then(|number| Ok(number.parse()?))
.map(Self)
}
}
@ -3044,7 +3102,7 @@ impl Debug for Rems {
/// affected by the current font size, or a number of rems, which is relative to the font size of
/// the root element. It is used for specifying dimensions that are either independent of or
/// related to the typographic scale.
#[derive(Clone, Copy, Debug, Neg, PartialEq)]
#[derive(Clone, Copy, Neg, PartialEq)]
pub enum AbsoluteLength {
/// A length in pixels.
Pixels(Pixels),
@ -3126,6 +3184,87 @@ impl Default for AbsoluteLength {
}
}
impl Display for AbsoluteLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pixels(pixels) => write!(f, "{pixels}"),
Self::Rems(rems) => write!(f, "{rems}"),
}
}
}
impl Debug for AbsoluteLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
const EXPECTED_ABSOLUTE_LENGTH: &str = "number with 'px' or 'rem' suffix";
impl TryFrom<&'_ str> for AbsoluteLength {
type Error = anyhow::Error;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
if let Ok(pixels) = value.try_into() {
Ok(Self::Pixels(pixels))
} else if let Ok(rems) = value.try_into() {
Ok(Self::Rems(rems))
} else {
Err(anyhow!(
"invalid AbsoluteLength '{value}', expected {EXPECTED_ABSOLUTE_LENGTH}"
))
}
}
}
impl JsonSchema for AbsoluteLength {
fn schema_name() -> String {
"AbsoluteLength".to_string()
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
use schemars::schema::{InstanceType, SchemaObject, StringValidation};
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
string: Some(Box::new(StringValidation {
pattern: Some(r"^-?\d+(\.\d+)?(px|rem)$".to_string()),
..Default::default()
})),
..Default::default()
})
}
}
impl<'de> Deserialize<'de> for AbsoluteLength {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct StringVisitor;
impl de::Visitor<'_> for StringVisitor {
type Value = AbsoluteLength;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{EXPECTED_ABSOLUTE_LENGTH}")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
AbsoluteLength::try_from(value).map_err(E::custom)
}
}
deserializer.deserialize_str(StringVisitor)
}
}
impl Serialize for AbsoluteLength {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{self}"))
}
}
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
///
/// This enum represents lengths that have a specific value, as opposed to lengths that are automatically
@ -3180,14 +3319,89 @@ impl DefiniteLength {
}
impl Debug for DefiniteLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Display for DefiniteLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
DefiniteLength::Absolute(length) => write!(f, "{length}"),
DefiniteLength::Fraction(fraction) => write!(f, "{}%", (fraction * 100.0) as i32),
}
}
}
const EXPECTED_DEFINITE_LENGTH: &str = "expected number with 'px', 'rem', or '%' suffix";
impl TryFrom<&'_ str> for DefiniteLength {
type Error = anyhow::Error;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
if let Some(percentage) = value.strip_suffix('%') {
let fraction: f32 = percentage.parse::<f32>().with_context(|| {
format!("invalid DefiniteLength '{value}', expected {EXPECTED_DEFINITE_LENGTH}")
})?;
Ok(DefiniteLength::Fraction(fraction / 100.0))
} else if let Ok(absolute_length) = value.try_into() {
Ok(DefiniteLength::Absolute(absolute_length))
} else {
Err(anyhow!(
"invalid DefiniteLength '{value}', expected {EXPECTED_DEFINITE_LENGTH}"
))
}
}
}
impl JsonSchema for DefiniteLength {
fn schema_name() -> String {
"DefiniteLength".to_string()
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
use schemars::schema::{InstanceType, SchemaObject, StringValidation};
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
string: Some(Box::new(StringValidation {
pattern: Some(r"^-?\d+(\.\d+)?(px|rem|%)$".to_string()),
..Default::default()
})),
..Default::default()
})
}
}
impl<'de> Deserialize<'de> for DefiniteLength {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct StringVisitor;
impl de::Visitor<'_> for StringVisitor {
type Value = DefiniteLength;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{EXPECTED_DEFINITE_LENGTH}")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
DefiniteLength::try_from(value).map_err(E::custom)
}
}
deserializer.deserialize_str(StringVisitor)
}
}
impl Serialize for DefiniteLength {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{self}"))
}
}
impl From<Pixels> for DefiniteLength {
fn from(pixels: Pixels) -> Self {
Self::Absolute(pixels.into())
@ -3222,14 +3436,86 @@ pub enum Length {
}
impl Debug for Length {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Display for Length {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
Length::Definite(definite_length) => write!(f, "{}", definite_length),
Length::Auto => write!(f, "auto"),
}
}
}
const EXPECTED_LENGTH: &str = "expected 'auto' or number with 'px', 'rem', or '%' suffix";
impl TryFrom<&'_ str> for Length {
type Error = anyhow::Error;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
if value == "auto" {
Ok(Length::Auto)
} else if let Ok(definite_length) = value.try_into() {
Ok(Length::Definite(definite_length))
} else {
Err(anyhow!(
"invalid Length '{value}', expected {EXPECTED_LENGTH}"
))
}
}
}
impl JsonSchema for Length {
fn schema_name() -> String {
"Length".to_string()
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
use schemars::schema::{InstanceType, SchemaObject, StringValidation};
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
string: Some(Box::new(StringValidation {
pattern: Some(r"^(auto|-?\d+(\.\d+)?(px|rem|%))$".to_string()),
..Default::default()
})),
..Default::default()
})
}
}
impl<'de> Deserialize<'de> for Length {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct StringVisitor;
impl de::Visitor<'_> for StringVisitor {
type Value = Length;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{EXPECTED_LENGTH}")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
Length::try_from(value).map_err(E::custom)
}
}
deserializer.deserialize_str(StringVisitor)
}
}
impl Serialize for Length {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{self}"))
}
}
/// Constructs a `DefiniteLength` representing a relative fraction of a parent size.
///
/// This function creates a `DefiniteLength` that is a specified fraction of a parent's dimension.