editor: Move code actions menu closer to the indicator when it is deployed via click (#11214)

Piotr Osiewicz created

Before:

![image](https://github.com/zed-industries/zed/assets/24362066/98d633a7-c982-4522-b4dc-b944b70b8081)

After: 

![image](https://github.com/zed-industries/zed/assets/24362066/79931e12-0e6c-4ece-b734-5af7d02f7e50)

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs  | 26 ++++++++++++++++----------
crates/editor/src/element.rs | 22 +++++++++++++++++++---
2 files changed, 35 insertions(+), 13 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -764,10 +764,10 @@ impl ContextMenu {
         max_height: Pixels,
         workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) -> (DisplayPoint, AnyElement) {
+    ) -> (ContextMenuOrigin, AnyElement) {
         match self {
             ContextMenu::Completions(menu) => (
-                cursor_position,
+                ContextMenuOrigin::EditorPoint(cursor_position),
                 menu.render(style, max_height, workspace, cx),
             ),
             ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
@@ -775,6 +775,11 @@ impl ContextMenu {
     }
 }
 
+enum ContextMenuOrigin {
+    EditorPoint(DisplayPoint),
+    GutterIndicator(u32),
+}
+
 #[derive(Clone)]
 struct CompletionsMenu {
     id: CompletionId,
@@ -1208,11 +1213,11 @@ impl CodeActionsMenu {
 
     fn render(
         &self,
-        mut cursor_position: DisplayPoint,
+        cursor_position: DisplayPoint,
         _style: &EditorStyle,
         max_height: Pixels,
         cx: &mut ViewContext<Editor>,
-    ) -> (DisplayPoint, AnyElement) {
+    ) -> (ContextMenuOrigin, AnyElement) {
         let actions = self.actions.clone();
         let selected_item = self.selected_item;
 
@@ -1277,10 +1282,11 @@ impl CodeActionsMenu {
         )
         .into_any_element();
 
-        if self.deployed_from_indicator {
-            *cursor_position.column_mut() = 0;
-        }
-
+        let cursor_position = if self.deployed_from_indicator {
+            ContextMenuOrigin::GutterIndicator(cursor_position.row())
+        } else {
+            ContextMenuOrigin::EditorPoint(cursor_position)
+        };
         (cursor_position, element)
     }
 }
@@ -4247,13 +4253,13 @@ impl Editor {
             .map_or(false, |menu| menu.visible())
     }
 
-    pub fn render_context_menu(
+    fn render_context_menu(
         &self,
         cursor_position: DisplayPoint,
         style: &EditorStyle,
         max_height: Pixels,
         cx: &mut ViewContext<Editor>,
-    ) -> Option<(DisplayPoint, AnyElement)> {
+    ) -> Option<(ContextMenuOrigin, AnyElement)> {
         self.context_menu.read().as_ref().map(|menu| {
             menu.render(
                 cursor_position,

crates/editor/src/element.rs 🔗

@@ -1949,6 +1949,7 @@ impl EditorElement {
         scroll_pixel_position: gpui::Point<Pixels>,
         line_layouts: &[LineWithInvisibles],
         newest_selection_head: DisplayPoint,
+        gutter_overshoot: Pixels,
         cx: &mut WindowContext,
     ) -> bool {
         let max_height = cmp::min(
@@ -1968,9 +1969,23 @@ impl EditorElement {
         let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
         let context_menu_size = context_menu.layout_as_root(available_space, cx);
 
-        let cursor_row_layout = &line_layouts[(position.row() - start_row) as usize].line;
-        let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
-        let y = (position.row() + 1) as f32 * line_height - scroll_pixel_position.y;
+        let (x, y) = match position {
+            crate::ContextMenuOrigin::EditorPoint(point) => {
+                let cursor_row_layout = &line_layouts[(point.row() - start_row) as usize].line;
+                let x = cursor_row_layout.x_for_index(point.column() as usize)
+                    - scroll_pixel_position.x;
+                let y = (point.row() + 1) as f32 * line_height - scroll_pixel_position.y;
+                (x, y)
+            }
+            crate::ContextMenuOrigin::GutterIndicator(row) => {
+                // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
+                // text field.
+                let x = -gutter_overshoot;
+                let y = (row + 1) as f32 * line_height - scroll_pixel_position.y;
+                (x, y)
+            }
+        };
+
         let mut list_origin = content_origin + point(x, y);
         let list_width = context_menu_size.width;
         let list_height = context_menu_size.height;
@@ -3826,6 +3841,7 @@ impl Element for EditorElement {
                             scroll_pixel_position,
                             &line_layouts,
                             newest_selection_head,
+                            gutter_dimensions.width - gutter_dimensions.left_padding,
                             cx,
                         );
                         if gutter_settings.code_actions {