editor: Fix columnar selection incorrectly uses cursor to start selection instead of mouse position (#31888)

Smit Barmase created

Closes #13905

This PR fixes columnar selection to originate from mouse position
instead of current cursor position. Now columnar selection behaves as
same as Sublime Text.

1. Columnar selection from click-and-drag on text (New):


https://github.com/user-attachments/assets/f2e721f4-109f-4d81-a25b-8534065bfb37

2. Columnar selection from click-and-drag on empty space (New): 


https://github.com/user-attachments/assets/c2bb02e9-c006-4193-8d76-097233a47a3c

3. Multi cursors at end of line when no interecting text found (New): 


https://github.com/user-attachments/assets/e47d5ab3-0b5f-4e55-81b3-dfe450f149b5

4. Converting normal selection to columnar selection (Existing):


https://github.com/user-attachments/assets/e5715679-ebae-4f5a-ad17-d29864e14e1e


Release Notes:

- Fixed the issue where the columnar selection (`opt+shift`) incorrectly
used the cursor to start the selection instead of the mouse position.

Change summary

crates/editor/src/editor.rs  | 27 ++++++++++++++++++++++++---
crates/editor/src/element.rs |  2 +-
2 files changed, 25 insertions(+), 4 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -932,6 +932,7 @@ pub struct Editor {
     /// typing enters text into each of them, even the ones that aren't focused.
     pub(crate) show_cursor_when_unfocused: bool,
     columnar_selection_tail: Option<Anchor>,
+    columnar_display_point: Option<DisplayPoint>,
     add_selections_state: Option<AddSelectionsState>,
     select_next_state: Option<SelectNextState>,
     select_prev_state: Option<SelectNextState>,
@@ -1797,6 +1798,7 @@ impl Editor {
             selections,
             scroll_manager: ScrollManager::new(cx),
             columnar_selection_tail: None,
+            columnar_display_point: None,
             add_selections_state: None,
             select_next_state: None,
             select_prev_state: None,
@@ -3319,12 +3321,18 @@ impl Editor {
                     SelectMode::Character,
                 );
             });
+            if position.column() != goal_column {
+                self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
+            } else {
+                self.columnar_display_point = None;
+            }
         }
 
         let tail = self.selections.newest::<Point>(cx).tail();
         self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
 
         if !reset {
+            self.columnar_display_point = None;
             self.select_columns(
                 tail.to_display_point(&display_map),
                 position,
@@ -3347,7 +3355,9 @@ impl Editor {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
 
         if let Some(tail) = self.columnar_selection_tail.as_ref() {
-            let tail = tail.to_display_point(&display_map);
+            let tail = self
+                .columnar_display_point
+                .unwrap_or_else(|| tail.to_display_point(&display_map));
             self.select_columns(tail, position, goal_column, &display_map, window, cx);
         } else if let Some(mut pending) = self.selections.pending_anchor() {
             let buffer = self.buffer.read(cx).snapshot(cx);
@@ -3463,7 +3473,7 @@ impl Editor {
         let selection_ranges = (start_row.0..=end_row.0)
             .map(DisplayRow)
             .filter_map(|row| {
-                if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
+                if !display_map.is_block_line(row) {
                     let start = display_map
                         .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
                         .to_point(display_map);
@@ -3481,8 +3491,19 @@ impl Editor {
             })
             .collect::<Vec<_>>();
 
+        let mut non_empty_ranges = selection_ranges
+            .iter()
+            .filter(|selection_range| selection_range.start != selection_range.end)
+            .peekable();
+
+        let ranges = if non_empty_ranges.peek().is_some() {
+            non_empty_ranges.cloned().collect()
+        } else {
+            selection_ranges
+        };
+
         self.change_selections(None, window, cx, |s| {
-            s.select_ranges(selection_ranges);
+            s.select_ranges(ranges);
         });
         cx.notify();
     }

crates/editor/src/element.rs 🔗

@@ -682,7 +682,7 @@ impl EditorElement {
             editor.select(
                 SelectPhase::BeginColumnar {
                     position,
-                    reset: false,
+                    reset: true,
                     goal_column: point_for_position.exact_unclipped.column(),
                 },
                 window,