inspector.rs

  1use anyhow::{Context as _, anyhow};
  2use gpui::{App, DivInspectorState, Inspector, InspectorElementId, IntoElement, Window};
  3use std::{cell::OnceCell, path::Path, sync::Arc};
  4use ui::{Label, Tooltip, prelude::*};
  5use util::{ResultExt as _, command::new_smol_command};
  6use workspace::AppState;
  7
  8use crate::div_inspector::DivInspector;
  9
 10pub fn init(app_state: Arc<AppState>, cx: &mut App) {
 11    cx.on_action(|_: &zed_actions::dev::ToggleInspector, cx| {
 12        let Some(active_window) = cx
 13            .active_window()
 14            .context("no active window to toggle inspector")
 15            .log_err()
 16        else {
 17            return;
 18        };
 19        // This is deferred to avoid double lease due to window already being updated.
 20        cx.defer(move |cx| {
 21            active_window
 22                .update(cx, |_, window, cx| window.toggle_inspector(cx))
 23                .log_err();
 24        });
 25    });
 26
 27    // Project used for editor buffers + LSP support
 28    let project = project::Project::local(
 29        app_state.client.clone(),
 30        app_state.node_runtime.clone(),
 31        app_state.user_store.clone(),
 32        app_state.languages.clone(),
 33        app_state.fs.clone(),
 34        None,
 35        cx,
 36    );
 37
 38    let div_inspector = OnceCell::new();
 39    cx.register_inspector_element(move |id, state: &DivInspectorState, window, cx| {
 40        let div_inspector = div_inspector
 41            .get_or_init(|| cx.new(|cx| DivInspector::new(project.clone(), window, cx)));
 42        div_inspector.update(cx, |div_inspector, cx| {
 43            div_inspector.update_inspected_element(&id, state.clone(), window, cx);
 44            div_inspector.render(window, cx).into_any_element()
 45        })
 46    });
 47
 48    cx.set_inspector_renderer(Box::new(render_inspector));
 49}
 50
 51fn render_inspector(
 52    inspector: &mut Inspector,
 53    window: &mut Window,
 54    cx: &mut Context<Inspector>,
 55) -> AnyElement {
 56    let ui_font = theme::setup_ui_font(window, cx);
 57    let colors = cx.theme().colors();
 58    let inspector_id = inspector.active_element_id();
 59    v_flex()
 60        .id("gpui-inspector")
 61        .size_full()
 62        .bg(colors.panel_background)
 63        .text_color(colors.text)
 64        .font(ui_font)
 65        .border_l_1()
 66        .border_color(colors.border)
 67        .overflow_y_scroll()
 68        .child(
 69            h_flex()
 70                .p_2()
 71                .border_b_1()
 72                .border_color(colors.border_variant)
 73                .child(
 74                    IconButton::new("pick-mode", IconName::MagnifyingGlass)
 75                        .tooltip(Tooltip::text("Start inspector pick mode"))
 76                        .selected_icon_color(Color::Selected)
 77                        .toggle_state(inspector.is_picking())
 78                        .on_click(cx.listener(|inspector, _, window, _cx| {
 79                            inspector.start_picking();
 80                            window.refresh();
 81                        })),
 82                )
 83                .child(
 84                    h_flex()
 85                        .w_full()
 86                        .justify_end()
 87                        .child(Label::new("GPUI Inspector").size(LabelSize::Large)),
 88                ),
 89        )
 90        .child(
 91            v_flex()
 92                .p_2()
 93                .gap_2()
 94                .when_some(inspector_id, |this, inspector_id| {
 95                    this.child(render_inspector_id(inspector_id, cx))
 96                })
 97                .children(inspector.render_inspector_states(window, cx)),
 98        )
 99        .into_any_element()
100}
101
102fn render_inspector_id(inspector_id: &InspectorElementId, cx: &App) -> Div {
103    let source_location = inspector_id.path.source_location;
104    v_flex()
105        .child(Label::new("Element ID").size(LabelSize::Large))
106        .when(inspector_id.instance_id != 0, |this| {
107            this.child(
108                div()
109                    .id("instance-id")
110                    .text_ui(cx)
111                    .tooltip(Tooltip::text(
112                        "Disambiguates elements from the same source location",
113                    ))
114                    .child(format!("Instance {}", inspector_id.instance_id)),
115            )
116        })
117        .child(
118            div()
119                .id("source-location")
120                .text_ui(cx)
121                .bg(cx.theme().colors().editor_foreground.opacity(0.025))
122                .underline()
123                .child(format!("{}", source_location))
124                .tooltip(Tooltip::text("Click to open by running zed cli"))
125                .on_click(move |_, _window, cx| {
126                    cx.background_spawn(open_zed_source_location(source_location))
127                        .detach_and_log_err(cx);
128                }),
129        )
130        .child(
131            div()
132                .id("global-id")
133                .text_ui(cx)
134                .min_h_12()
135                .tooltip(Tooltip::text(
136                    "GlobalElementId of the nearest ancestor with an ID",
137                ))
138                .child(inspector_id.path.global_id.to_string()),
139        )
140}
141
142async fn open_zed_source_location(
143    location: &'static std::panic::Location<'static>,
144) -> anyhow::Result<()> {
145    let mut path = Path::new(env!("ZED_REPO_DIR")).to_path_buf();
146    path.push(Path::new(location.file()));
147    let path_arg = format!(
148        "{}:{}:{}",
149        path.display(),
150        location.line(),
151        location.column()
152    );
153
154    let output = new_smol_command("zed")
155        .arg(&path_arg)
156        .output()
157        .await
158        .with_context(|| format!("running zed to open {path_arg} failed"))?;
159
160    if !output.status.success() {
161        Err(anyhow!(
162            "running zed to open {path_arg} failed with stderr: {}",
163            String::from_utf8_lossy(&output.stderr)
164        ))
165    } else {
166        Ok(())
167    }
168}