add cmd-click-to-jump-to-element

Mikayla created

Change summary

crates/gpui2/src/elements/div.rs          | 120 ++++++++++++++++++++++--
crates/gpui2/src/elements/uniform_list.rs |   5 +
crates/gpui2/src/platform.rs              |   1 
crates/gpui2/src/platform/mac/window.rs   |  27 +++++
crates/gpui2/src/platform/test/window.rs  |   4 
crates/gpui2/src/window.rs                |  29 +++++
6 files changed, 169 insertions(+), 17 deletions(-)

Detailed changes

crates/gpui2/src/elements/div.rs 🔗

@@ -6,6 +6,7 @@ use crate::{
     SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility,
     WindowContext,
 };
+
 use collections::HashMap;
 use refineable::Refineable;
 use smallvec::SmallVec;
@@ -526,11 +527,19 @@ pub type DragEventListener = Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + '
 
 pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
 
+#[track_caller]
 pub fn div() -> Div {
-    Div {
+    let mut div = Div {
         interactivity: Interactivity::default(),
         children: SmallVec::default(),
+    };
+
+    #[cfg(debug_assertions)]
+    {
+        div.interactivity.location = Some(*core::panic::Location::caller());
     }
+
+    div
 }
 
 pub struct Div {
@@ -708,6 +717,9 @@ pub struct Interactivity {
     pub drag_listener: Option<DragListener>,
     pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
     pub tooltip_builder: Option<TooltipBuilder>,
+
+    #[cfg(debug_assertions)]
+    pub location: Option<core::panic::Location<'static>>,
 }
 
 #[derive(Clone, Debug)]
@@ -775,6 +787,96 @@ impl Interactivity {
             const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
             let element_id = format!("{:?}", self.element_id.unwrap());
             let str_len = element_id.len();
+
+            let render_debug_text = |cx: &mut WindowContext| {
+                if let Some(text) = cx
+                    .text_system()
+                    .shape_text(
+                        &element_id,
+                        FONT_SIZE,
+                        &[cx.text_style().to_run(str_len)],
+                        None,
+                    )
+                    .ok()
+                    .map(|mut text| text.pop())
+                    .flatten()
+                {
+                    text.paint(bounds.origin, FONT_SIZE, cx).ok();
+
+                    let text_bounds = crate::Bounds {
+                        origin: bounds.origin,
+                        size: text.size(FONT_SIZE),
+                    };
+                    if self.location.is_some()
+                        && text_bounds.contains(&cx.mouse_position())
+                        && cx.modifiers().command
+                    {
+                        let command_held = cx.modifiers().command;
+                        cx.on_key_event({
+                            let text_bounds = text_bounds.clone();
+                            move |e: &crate::ModifiersChangedEvent, _phase, cx| {
+                                if e.modifiers.command != command_held
+                                    && text_bounds.contains(&cx.mouse_position())
+                                {
+                                    cx.notify();
+                                }
+                            }
+                        });
+
+                        let hovered = bounds.contains(&cx.mouse_position());
+                        cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
+                            if phase == DispatchPhase::Capture {
+                                if bounds.contains(&event.position) != hovered {
+                                    cx.notify();
+                                }
+                            }
+                        });
+
+                        cx.on_mouse_event({
+                            let location = self.location.clone().unwrap();
+                            let text_bounds = text_bounds.clone();
+                            move |e: &crate::MouseDownEvent, phase, cx| {
+                                if text_bounds.contains(&e.position) && phase.capture() {
+                                    cx.stop_propagation();
+                                    let Ok(dir) = std::env::current_dir() else {
+                                        return;
+                                    };
+
+                                    eprintln!(
+                                        "This element is created at:\n{}:{}:{}",
+                                        location.file(),
+                                        location.line(),
+                                        location.column()
+                                    );
+
+                                    std::process::Command::new("zed")
+                                        .arg(format!(
+                                            "{}/{}:{}:{}",
+                                            dir.to_string_lossy(),
+                                            location.file(),
+                                            location.line(),
+                                            location.column()
+                                        ))
+                                        .spawn()
+                                        .ok();
+                                }
+                            }
+                        });
+                        cx.paint_quad(crate::outline(
+                            crate::Bounds {
+                                origin: bounds.origin
+                                    + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
+                                size: crate::Size {
+                                    width: text_bounds.size.width,
+                                    height: crate::px(1.),
+                                },
+                            },
+                            crate::red(),
+                        ))
+                    }
+                }
+            };
+
             cx.with_z_index(1, |cx| {
                 cx.with_text_style(
                     Some(crate::TextStyleRefinement {
@@ -783,18 +885,7 @@ impl Interactivity {
                         background_color: Some(crate::white()),
                         ..Default::default()
                     }),
-                    |cx| {
-                        if let Ok(text) = cx.text_system().shape_text(
-                            &element_id,
-                            FONT_SIZE,
-                            &[cx.text_style().to_run(str_len)],
-                            None,
-                        ) {
-                            if let Some(text) = text.first() {
-                                text.paint(bounds.origin, FONT_SIZE, cx).ok();
-                            }
-                        }
-                    },
+                    render_debug_text,
                 )
             });
         }
@@ -1290,6 +1381,9 @@ impl Default for Interactivity {
             drag_listener: None,
             hover_listener: None,
             tooltip_builder: None,
+
+            #[cfg(debug_assertions)]
+            location: None,
         }
     }
 }

crates/gpui2/src/elements/uniform_list.rs 🔗

@@ -10,6 +10,7 @@ use taffy::style::Overflow;
 /// uniform_list provides lazy rendering for a set of items that are of uniform height.
 /// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
 /// uniform_list will only render the visible subset of items.
+#[track_caller]
 pub fn uniform_list<I, R, V>(
     view: View<V>,
     id: I,
@@ -42,6 +43,10 @@ where
         interactivity: Interactivity {
             element_id: Some(id.into()),
             base_style: Box::new(base_style),
+
+            #[cfg(debug_assertions)]
+            location: Some(*core::panic::Location::caller()),
+
             ..Default::default()
         },
         scroll_handle: None,

crates/gpui2/src/platform.rs 🔗

@@ -147,6 +147,7 @@ pub trait PlatformWindow {
     fn appearance(&self) -> WindowAppearance;
     fn display(&self) -> Rc<dyn PlatformDisplay>;
     fn mouse_position(&self) -> Point<Pixels>;
+    fn modifiers(&self) -> Modifiers;
     fn as_any_mut(&mut self) -> &mut dyn Any;
     fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
     fn clear_input_handler(&mut self);

crates/gpui2/src/platform/mac/window.rs 🔗

@@ -9,9 +9,10 @@ use crate::{
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
-        CGPoint, NSApplication, NSBackingStoreBuffered, NSFilenamesPboardType, NSPasteboard,
-        NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton,
-        NSWindowCollectionBehavior, NSWindowStyleMask, NSWindowTitleVisibility,
+        CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags,
+        NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
+        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
+        NSWindowStyleMask, NSWindowTitleVisibility,
     },
     base::{id, nil},
     foundation::{
@@ -744,6 +745,26 @@ impl PlatformWindow for MacWindow {
         convert_mouse_position(position, self.content_size().height)
     }
 
+    fn modifiers(&self) -> Modifiers {
+        unsafe {
+            let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
+
+            let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
+            let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
+            let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
+            let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
+            let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
+
+            Modifiers {
+                control,
+                alt,
+                shift,
+                command,
+                function,
+            }
+        }
+    }
+
     fn as_any_mut(&mut self) -> &mut dyn Any {
         self
     }

crates/gpui2/src/platform/test/window.rs 🔗

@@ -79,6 +79,10 @@ impl PlatformWindow for TestWindow {
         Point::default()
     }
 
+    fn modifiers(&self) -> crate::Modifiers {
+        crate::Modifiers::default()
+    }
+
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
         self
     }

crates/gpui2/src/window.rs 🔗

@@ -231,6 +231,7 @@ pub struct Window {
     pub(crate) blur_listeners: SubscriberSet<(), AnyObserver>,
     default_prevented: bool,
     mouse_position: Point<Pixels>,
+    modifiers: Modifiers,
     requested_cursor_style: Option<CursorStyle>,
     scale_factor: f32,
     bounds: WindowBounds,
@@ -302,6 +303,7 @@ impl Window {
         let display_id = platform_window.display().id();
         let sprite_atlas = platform_window.sprite_atlas();
         let mouse_position = platform_window.mouse_position();
+        let modifiers = platform_window.modifiers();
         let content_size = platform_window.content_size();
         let scale_factor = platform_window.scale_factor();
         let bounds = platform_window.bounds();
@@ -365,6 +367,7 @@ impl Window {
             blur_listeners: SubscriberSet::new(),
             default_prevented: true,
             mouse_position,
+            modifiers,
             requested_cursor_style: None,
             scale_factor,
             bounds,
@@ -877,6 +880,11 @@ impl<'a> WindowContext<'a> {
         self.window.mouse_position
     }
 
+    /// The current state of the keyboard's modifiers
+    pub fn modifiers(&self) -> Modifiers {
+        self.window.modifiers
+    }
+
     pub fn set_cursor_style(&mut self, style: CursorStyle) {
         self.window.requested_cursor_style = Some(style)
     }
@@ -1336,16 +1344,35 @@ impl<'a> WindowContext<'a> {
             // API for the mouse position can only occur on the main thread.
             InputEvent::MouseMove(mouse_move) => {
                 self.window.mouse_position = mouse_move.position;
+                self.window.modifiers = mouse_move.modifiers;
                 InputEvent::MouseMove(mouse_move)
             }
             InputEvent::MouseDown(mouse_down) => {
                 self.window.mouse_position = mouse_down.position;
+                self.window.modifiers = mouse_down.modifiers;
                 InputEvent::MouseDown(mouse_down)
             }
             InputEvent::MouseUp(mouse_up) => {
                 self.window.mouse_position = mouse_up.position;
+                self.window.modifiers = mouse_up.modifiers;
                 InputEvent::MouseUp(mouse_up)
             }
+            InputEvent::MouseExited(mouse_exited) => {
+                // todo!("Should we record that the mouse is outside of the window somehow? Or are these global pixels?")
+                self.window.mouse_position = mouse_exited.position;
+                self.window.modifiers = mouse_exited.modifiers;
+
+                InputEvent::MouseExited(mouse_exited)
+            }
+            InputEvent::ModifiersChanged(modifiers_changed) => {
+                self.window.modifiers = modifiers_changed.modifiers;
+                InputEvent::ModifiersChanged(modifiers_changed)
+            }
+            InputEvent::ScrollWheel(scroll_wheel) => {
+                self.window.mouse_position = scroll_wheel.position;
+                self.window.modifiers = scroll_wheel.modifiers;
+                InputEvent::ScrollWheel(scroll_wheel)
+            }
             // Translate dragging and dropping of external files from the operating system
             // to internal drag and drop events.
             InputEvent::FileDrop(file_drop) => match file_drop {
@@ -1388,7 +1415,7 @@ impl<'a> WindowContext<'a> {
                     click_count: 1,
                 }),
             },
-            _ => event,
+            InputEvent::KeyDown(_) | InputEvent::KeyUp(_) => event,
         };
 
         if let Some(any_mouse_event) = event.mouse_event() {