inspector.rs

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