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:
parent
685933b5c8
commit
ab59982bf7
74 changed files with 2631 additions and 406 deletions
168
crates/inspector_ui/src/inspector.rs
Normal file
168
crates/inspector_ui/src/inspector.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
use anyhow::{Context as _, anyhow};
|
||||
use gpui::{App, DivInspectorState, Inspector, InspectorElementId, IntoElement, Window};
|
||||
use std::{cell::OnceCell, path::Path, sync::Arc};
|
||||
use ui::{Label, Tooltip, prelude::*};
|
||||
use util::{ResultExt as _, command::new_smol_command};
|
||||
use workspace::AppState;
|
||||
|
||||
use crate::div_inspector::DivInspector;
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
||||
cx.on_action(|_: &zed_actions::dev::ToggleInspector, cx| {
|
||||
let Some(active_window) = cx
|
||||
.active_window()
|
||||
.context("no active window to toggle inspector")
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// This is deferred to avoid double lease due to window already being updated.
|
||||
cx.defer(move |cx| {
|
||||
active_window
|
||||
.update(cx, |_, window, cx| window.toggle_inspector(cx))
|
||||
.log_err();
|
||||
});
|
||||
});
|
||||
|
||||
// Project used for editor buffers + LSP support
|
||||
let project = project::Project::local(
|
||||
app_state.client.clone(),
|
||||
app_state.node_runtime.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
|
||||
let div_inspector = OnceCell::new();
|
||||
cx.register_inspector_element(move |id, state: &DivInspectorState, window, cx| {
|
||||
let div_inspector = div_inspector
|
||||
.get_or_init(|| cx.new(|cx| DivInspector::new(project.clone(), window, cx)));
|
||||
div_inspector.update(cx, |div_inspector, cx| {
|
||||
div_inspector.update_inspected_element(&id, state.clone(), window, cx);
|
||||
div_inspector.render(window, cx).into_any_element()
|
||||
})
|
||||
});
|
||||
|
||||
cx.set_inspector_renderer(Box::new(render_inspector));
|
||||
}
|
||||
|
||||
fn render_inspector(
|
||||
inspector: &mut Inspector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Inspector>,
|
||||
) -> AnyElement {
|
||||
let ui_font = theme::setup_ui_font(window, cx);
|
||||
let colors = cx.theme().colors();
|
||||
let inspector_id = inspector.active_element_id();
|
||||
v_flex()
|
||||
.id("gpui-inspector")
|
||||
.size_full()
|
||||
.bg(colors.panel_background)
|
||||
.text_color(colors.text)
|
||||
.font(ui_font)
|
||||
.border_l_1()
|
||||
.border_color(colors.border)
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
h_flex()
|
||||
.p_2()
|
||||
.border_b_1()
|
||||
.border_color(colors.border_variant)
|
||||
.child(
|
||||
IconButton::new("pick-mode", IconName::MagnifyingGlass)
|
||||
.tooltip(Tooltip::text("Start inspector pick mode"))
|
||||
.selected_icon_color(Color::Selected)
|
||||
.toggle_state(inspector.is_picking())
|
||||
.on_click(cx.listener(|inspector, _, window, _cx| {
|
||||
inspector.start_picking();
|
||||
window.refresh();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_end()
|
||||
.child(Label::new("GPUI Inspector").size(LabelSize::Large)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.when_some(inspector_id, |this, inspector_id| {
|
||||
this.child(render_inspector_id(inspector_id, cx))
|
||||
})
|
||||
.children(inspector.render_inspector_states(window, cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_inspector_id(inspector_id: &InspectorElementId, cx: &App) -> Div {
|
||||
let source_location = inspector_id.path.source_location;
|
||||
v_flex()
|
||||
.child(Label::new("Element ID").size(LabelSize::Large))
|
||||
.when(inspector_id.instance_id != 0, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.id("instance-id")
|
||||
.text_ui(cx)
|
||||
.tooltip(Tooltip::text(
|
||||
"Disambiguates elements from the same source location",
|
||||
))
|
||||
.child(format!("Instance {}", inspector_id.instance_id)),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.id("source-location")
|
||||
.text_ui(cx)
|
||||
.bg(cx.theme().colors().editor_foreground.opacity(0.025))
|
||||
.underline()
|
||||
.child(format!("{}", source_location))
|
||||
.tooltip(Tooltip::text("Click to open by running zed cli"))
|
||||
.on_click(move |_, _window, cx| {
|
||||
cx.background_spawn(open_zed_source_location(source_location))
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("global-id")
|
||||
.text_ui(cx)
|
||||
.min_h_12()
|
||||
.tooltip(Tooltip::text(
|
||||
"GlobalElementId of the nearest ancestor with an ID",
|
||||
))
|
||||
.child(inspector_id.path.global_id.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
async fn open_zed_source_location(
|
||||
location: &'static std::panic::Location<'static>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut path = Path::new(env!("ZED_REPO_DIR")).to_path_buf();
|
||||
path.push(Path::new(location.file()));
|
||||
let path_arg = format!(
|
||||
"{}:{}:{}",
|
||||
path.display(),
|
||||
location.line(),
|
||||
location.column()
|
||||
);
|
||||
|
||||
let output = new_smol_command("zed")
|
||||
.arg(&path_arg)
|
||||
.output()
|
||||
.await
|
||||
.with_context(|| format!("running zed to open {path_arg} failed"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
Err(anyhow!(
|
||||
"running zed to open {path_arg} failed with stderr: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue