Implement shift-click to extend the newest selection

Nathan Sobo created

Change summary

crates/editor/src/element.rs          | 18 +++++-
crates/editor/src/lib.rs              | 81 ++++++++++++++++++++--------
crates/gpui/src/app.rs                |  1 
crates/gpui/src/platform/event.rs     |  1 
crates/gpui/src/platform/mac/event.rs |  6 +-
5 files changed, 77 insertions(+), 30 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -1,3 +1,5 @@
+use crate::BeginSelectionMode;
+
 use super::{
     DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
     Select, SelectPhase, Snapshot, MAX_LINE_LEN,
@@ -55,18 +57,27 @@ impl EditorElement {
     fn mouse_down(
         &self,
         position: Vector2F,
+        shift: bool,
         cmd: bool,
         click_count: usize,
         layout: &mut LayoutState,
         paint: &mut PaintState,
         cx: &mut EventContext,
     ) -> bool {
+        let mode = if cmd {
+            BeginSelectionMode::Add
+        } else if shift {
+            BeginSelectionMode::Extend
+        } else {
+            BeginSelectionMode::Update
+        };
+
         if paint.text_bounds.contains_point(position) {
             let snapshot = self.snapshot(cx.app);
             let position = paint.point_for_position(&snapshot, layout, position);
             cx.dispatch_action(Select(SelectPhase::Begin {
                 position,
-                add: cmd,
+                mode,
                 click_count,
             }));
             true
@@ -75,7 +86,7 @@ impl EditorElement {
             let position = paint.point_for_position(&snapshot, layout, position);
             cx.dispatch_action(Select(SelectPhase::Begin {
                 position,
-                add: cmd,
+                mode,
                 click_count: 3,
             }));
             true
@@ -858,9 +869,10 @@ impl Element for EditorElement {
             match event {
                 Event::LeftMouseDown {
                     position,
+                    shift,
                     cmd,
                     click_count,
-                } => self.mouse_down(*position, *cmd, *click_count, layout, paint, cx),
+                } => self.mouse_down(*position, *shift, *cmd, *click_count, layout, paint, cx),
                 Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
                 Event::LeftMouseDragged { position } => {
                     self.mouse_dragged(*position, layout, paint, cx)

crates/editor/src/lib.rs 🔗

@@ -273,7 +273,7 @@ struct SpannedRows {
 pub enum SelectPhase {
     Begin {
         position: DisplayPoint,
-        add: bool,
+        mode: BeginSelectionMode,
         click_count: usize,
     },
     Update {
@@ -283,8 +283,15 @@ pub enum SelectPhase {
     End,
 }
 
+#[derive(Clone, Copy, Debug)]
+pub enum BeginSelectionMode {
+    Update,
+    Extend,
+    Add,
+}
+
 #[derive(Clone, Debug)]
-enum SelectMode {
+enum ExtendSelectionMode {
     Character,
     Word(Range<Anchor>),
     Line(Range<Anchor>),
@@ -338,7 +345,7 @@ pub struct Snapshot {
 
 struct PendingSelection {
     selection: Selection<Anchor>,
-    mode: SelectMode,
+    mode: ExtendSelectionMode,
 }
 
 struct AddSelectionsState {
@@ -643,9 +650,9 @@ impl Editor {
         match phase {
             SelectPhase::Begin {
                 position,
-                add,
+                mode,
                 click_count,
-            } => self.begin_selection(*position, *add, *click_count, cx),
+            } => self.begin_selection(*position, *mode, *click_count, cx),
             SelectPhase::Update {
                 position,
                 scroll_position,
@@ -657,7 +664,7 @@ impl Editor {
     fn begin_selection(
         &mut self,
         position: DisplayPoint,
-        add: bool,
+        begin_mode: BeginSelectionMode,
         click_count: usize,
         cx: &mut ViewContext<Self>,
     ) {
@@ -670,18 +677,18 @@ impl Editor {
         let buffer = self.buffer.read(cx);
         let start;
         let end;
-        let mode;
+        let mut extend_mode;
         match click_count {
             1 => {
                 start = buffer.anchor_before(position.to_point(&display_map));
                 end = start.clone();
-                mode = SelectMode::Character;
+                extend_mode = ExtendSelectionMode::Character;
             }
             2 => {
                 let range = movement::surrounding_word(&display_map, position);
                 start = buffer.anchor_before(range.start.to_point(&display_map));
                 end = buffer.anchor_before(range.end.to_point(&display_map));
-                mode = SelectMode::Word(start.clone()..end.clone());
+                extend_mode = ExtendSelectionMode::Word(start.clone()..end.clone());
             }
             3 => {
                 let position = display_map.clip_point(position, Bias::Left);
@@ -693,15 +700,16 @@ impl Editor {
 
                 start = buffer.anchor_before(line_start.to_point(&display_map));
                 end = buffer.anchor_before(next_line_start.to_point(&display_map));
-                mode = SelectMode::Line(start.clone()..end.clone());
+                extend_mode = ExtendSelectionMode::Line(start.clone()..end.clone());
             }
             _ => {
                 start = buffer.anchor_before(0);
                 end = buffer.anchor_before(buffer.len());
-                mode = SelectMode::All;
+                extend_mode = ExtendSelectionMode::All;
             }
         }
-        let selection = Selection {
+
+        let mut selection = Selection {
             id: post_inc(&mut self.next_selection_id),
             start,
             end,
@@ -709,10 +717,35 @@ impl Editor {
             goal: SelectionGoal::None,
         };
 
-        if !add {
-            self.update_selections::<usize>(Vec::new(), false, cx);
+        match begin_mode {
+            BeginSelectionMode::Update => {
+                self.update_selections::<usize>(Vec::new(), false, cx);
+            }
+            BeginSelectionMode::Extend => {
+                let position = position.to_offset(&display_map, Bias::Left);
+                let tail = self.newest_selection::<usize>(cx).tail();
+                let tail_anchor = buffer.anchor_before(tail);
+                if position >= tail {
+                    selection.start = tail_anchor.clone();
+                } else {
+                    selection.end = tail_anchor.clone();
+                    selection.reversed = true;
+                }
+                match &mut extend_mode {
+                    ExtendSelectionMode::Word(range) | ExtendSelectionMode::Line(range) => {
+                        *range = tail_anchor.clone()..tail_anchor
+                    }
+                    _ => {}
+                }
+                self.update_selections::<usize>(Vec::new(), false, cx);
+            }
+            BeginSelectionMode::Add => {}
         }
-        self.pending_selection = Some(PendingSelection { selection, mode });
+
+        self.pending_selection = Some(PendingSelection {
+            selection,
+            mode: extend_mode,
+        });
 
         cx.notify();
     }
@@ -729,11 +762,11 @@ impl Editor {
             let head;
             let tail;
             match mode {
-                SelectMode::Character => {
+                ExtendSelectionMode::Character => {
                     head = position.to_point(&display_map);
                     tail = selection.tail().to_point(buffer);
                 }
-                SelectMode::Word(original_range) => {
+                ExtendSelectionMode::Word(original_range) => {
                     let original_display_range = original_range.start.to_display_point(&display_map)
                         ..original_range.end.to_display_point(&display_map);
                     let original_buffer_range = original_display_range.start.to_point(&display_map)
@@ -757,7 +790,7 @@ impl Editor {
                         tail = original_buffer_range.start;
                     }
                 }
-                SelectMode::Line(original_range) => {
+                ExtendSelectionMode::Line(original_range) => {
                     let original_display_range = original_range.start.to_display_point(&display_map)
                         ..original_range.end.to_display_point(&display_map);
                     let original_buffer_range = original_display_range.start.to_point(&display_map)
@@ -780,7 +813,7 @@ impl Editor {
                         tail = original_buffer_range.start;
                     }
                 }
-                SelectMode::All => {
+                ExtendSelectionMode::All => {
                     return;
                 }
             };
@@ -3254,7 +3287,7 @@ mod tests {
             cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
 
         editor.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
+            view.begin_selection(DisplayPoint::new(2, 2), BeginSelectionMode::Update, 1, cx);
         });
 
         assert_eq!(
@@ -3291,7 +3324,7 @@ mod tests {
         );
 
         editor.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
+            view.begin_selection(DisplayPoint::new(3, 3), BeginSelectionMode::Add, 1, cx);
             view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx);
         });
 
@@ -3320,7 +3353,7 @@ mod tests {
         let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
 
         view.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
+            view.begin_selection(DisplayPoint::new(2, 2), BeginSelectionMode::Update, 1, cx);
             assert_eq!(
                 view.selection_ranges(cx),
                 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
@@ -3352,11 +3385,11 @@ mod tests {
         let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
 
         view.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
+            view.begin_selection(DisplayPoint::new(3, 4), BeginSelectionMode::Update, 1, cx);
             view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx);
             view.end_selection(cx);
 
-            view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
+            view.begin_selection(DisplayPoint::new(0, 1), BeginSelectionMode::Add, 1, cx);
             view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), cx);
             view.end_selection(cx);
             assert_eq!(

crates/gpui/src/app.rs 🔗

@@ -3650,6 +3650,7 @@ mod tests {
         presenter.borrow_mut().dispatch_event(
             Event::LeftMouseDown {
                 position: Default::default(),
+                shift: false,
                 cmd: false,
                 click_count: 1,
             },

crates/gpui/src/platform/mac/event.rs 🔗

@@ -86,14 +86,14 @@ impl Event {
                 })
             }
             NSEventType::NSLeftMouseDown => {
+                let modifiers = native_event.modifierFlags();
                 window_height.map(|window_height| Self::LeftMouseDown {
                     position: vec2f(
                         native_event.locationInWindow().x as f32,
                         window_height - native_event.locationInWindow().y as f32,
                     ),
-                    cmd: native_event
-                        .modifierFlags()
-                        .contains(NSEventModifierFlags::NSCommandKeyMask),
+                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
+                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
                     click_count: native_event.clickCount() as usize,
                 })
             }