Trigger columnar selection behavior on middle mouse down (#12005)

Mikayla Maki created

fixes https://github.com/zed-industries/zed/issues/11990

Release Notes:

- Changed middle mouse down to trigger a columnar selection, creating a
rectangle of multi cursors over a dragged region.
([#11990](https://github.com/zed-industries/zed/issues/11990))

Change summary

crates/editor/src/editor.rs  | 36 +++++++++++++++++++-----
crates/editor/src/element.rs | 56 +++++++++++++++++++++++++------------
2 files changed, 66 insertions(+), 26 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -302,6 +302,7 @@ pub enum SelectPhase {
     },
     BeginColumnar {
         position: DisplayPoint,
+        reset: bool,
         goal_column: u32,
     },
     Extend {
@@ -2277,7 +2278,8 @@ impl Editor {
             SelectPhase::BeginColumnar {
                 position,
                 goal_column,
-            } => self.begin_columnar_selection(position, goal_column, cx),
+                reset,
+            } => self.begin_columnar_selection(position, goal_column, reset, cx),
             SelectPhase::Extend {
                 position,
                 click_count,
@@ -2397,6 +2399,7 @@ impl Editor {
         &mut self,
         position: DisplayPoint,
         goal_column: u32,
+        reset: bool,
         cx: &mut ViewContext<Self>,
     ) {
         if !self.focus_handle.is_focused(cx) {
@@ -2404,16 +2407,33 @@ impl Editor {
         }
 
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+
+        if reset {
+            let pointer_position = display_map
+                .buffer_snapshot
+                .anchor_before(position.to_point(&display_map));
+
+            self.change_selections(Some(Autoscroll::newest()), cx, |s| {
+                s.clear_disjoint();
+                s.set_pending_anchor_range(
+                    pointer_position..pointer_position,
+                    SelectMode::Character,
+                );
+            });
+        }
+
         let tail = self.selections.newest::<Point>(cx).tail();
         self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
 
-        self.select_columns(
-            tail.to_display_point(&display_map),
-            position,
-            goal_column,
-            &display_map,
-            cx,
-        );
+        if !reset {
+            self.select_columns(
+                tail.to_display_point(&display_map),
+                position,
+                goal_column,
+                &display_map,
+                cx,
+            );
+        }
     }
 
     fn update_selection(

crates/editor/src/element.rs 🔗

@@ -477,6 +477,7 @@ impl EditorElement {
             editor.select(
                 SelectPhase::BeginColumnar {
                     position,
+                    reset: false,
                     goal_column: point_for_position.exact_unclipped.column(),
                 },
                 cx,
@@ -530,7 +531,6 @@ impl EditorElement {
         cx.stop_propagation();
     }
 
-    #[cfg(target_os = "linux")]
     fn mouse_middle_down(
         editor: &mut Editor,
         event: &MouseDownEvent,
@@ -538,25 +538,22 @@ impl EditorElement {
         text_hitbox: &Hitbox,
         cx: &mut ViewContext<Editor>,
     ) {
-        if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
+        if cx.default_prevented() {
             return;
         }
 
-        if let Some(item) = cx.read_from_primary() {
-            let point_for_position =
-                position_map.point_for_position(text_hitbox.bounds, event.position);
-            let position = point_for_position.previous_valid;
+        let point_for_position =
+            position_map.point_for_position(text_hitbox.bounds, event.position);
+        let position = point_for_position.previous_valid;
 
-            editor.select(
-                SelectPhase::Begin {
-                    position,
-                    add: false,
-                    click_count: 1,
-                },
-                cx,
-            );
-            editor.insert(item.text(), cx);
-        }
+        editor.select(
+            SelectPhase::BeginColumnar {
+                position,
+                reset: true,
+                goal_column: point_for_position.exact_unclipped.column(),
+            },
+            cx,
+        );
     }
 
     fn mouse_up(
@@ -586,6 +583,28 @@ impl EditorElement {
             cx.stop_propagation();
         } else if end_selection {
             cx.stop_propagation();
+        } else if cfg!(target_os = "linux") && event.button == MouseButton::Middle {
+            if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
+                return;
+            }
+
+            #[cfg(target_os = "linux")]
+            if let Some(item) = cx.read_from_clipboard() {
+                let point_for_position =
+                    position_map.point_for_position(text_hitbox.bounds, event.position);
+                let position = point_for_position.previous_valid;
+
+                editor.select(
+                    SelectPhase::Begin {
+                        position,
+                        add: false,
+                        click_count: 1,
+                    },
+                    cx,
+                );
+                editor.insert(item.text(), cx);
+            }
+            cx.stop_propagation()
         }
     }
 
@@ -3241,7 +3260,6 @@ impl EditorElement {
                         MouseButton::Right => editor.update(cx, |editor, cx| {
                             Self::mouse_right_down(editor, event, &position_map, &text_hitbox, cx);
                         }),
-                        #[cfg(target_os = "linux")]
                         MouseButton::Middle => editor.update(cx, |editor, cx| {
                             Self::mouse_middle_down(editor, event, &position_map, &text_hitbox, cx);
                         }),
@@ -3273,7 +3291,9 @@ impl EditorElement {
             move |event: &MouseMoveEvent, phase, cx| {
                 if phase == DispatchPhase::Bubble {
                     editor.update(cx, |editor, cx| {
-                        if event.pressed_button == Some(MouseButton::Left) {
+                        if event.pressed_button == Some(MouseButton::Left)
+                            || event.pressed_button == Some(MouseButton::Middle)
+                        {
                             Self::mouse_dragged(
                                 editor,
                                 event,