Split multi-line selection when starting a new columnar selection

Antonio Scandurra created

Change summary

zed/src/editor/buffer_view.rs | 153 ++++++++++++++++++++++--------------
1 file changed, 93 insertions(+), 60 deletions(-)

Detailed changes

zed/src/editor/buffer_view.rs 🔗

@@ -1797,92 +1797,92 @@ impl BufferView {
         use super::RangeExt;
 
         let app = ctx.as_ref();
-        let buffer = self.buffer.read(app);
-        let mut selections = self.selections(app);
+
+        let mut selections = self.selections(app).to_vec();
         let mut state = if let Some(state) = self.add_selections_state.take() {
             state
         } else {
-            let (ix, oldest_selection) = selections
-                .iter()
-                .enumerate()
-                .min_by_key(|(_, s)| s.id)
-                .unwrap();
-            selections = &selections[ix..ix + 1];
-            AddSelectionsState {
-                above,
-                stack: vec![oldest_selection.id],
+            let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
+            let range = oldest_selection
+                .display_range(&self.display_map, app)
+                .sorted();
+
+            selections.clear();
+            let mut stack = Vec::new();
+            if range.start.row() == range.end.row() {
+                stack.push(oldest_selection.id);
+                selections.push(oldest_selection);
+            } else {
+                let columns = cmp::min(range.start.column(), range.end.column())
+                    ..cmp::max(range.start.column(), range.end.column());
+                for row in range.start.row()..=range.end.row() {
+                    if let Some(selection) =
+                        self.build_columnar_selection(row, &columns, oldest_selection.reversed, app)
+                    {
+                        stack.push(selection.id);
+                        selections.push(selection);
+                    }
+                }
+
+                if above {
+                    stack.reverse();
+                }
             }
+
+            AddSelectionsState { above, stack }
         };
 
         let last_added_selection = *state.stack.last().unwrap();
         let mut new_selections = Vec::new();
         if above == state.above {
-            for selection in selections {
+            let end_row = if above {
+                0
+            } else {
+                self.display_map.max_point(app).row()
+            };
+
+            'outer: for selection in selections {
                 if selection.id == last_added_selection {
                     let range = selection.display_range(&self.display_map, app).sorted();
-
-                    let mut row = if above {
-                        range.start.row()
+                    debug_assert_eq!(range.start.row(), range.end.row());
+                    let mut row = range.start.row();
+                    let columns = if let SelectionGoal::ColumnRange { start, end } = selection.goal
+                    {
+                        start..end
                     } else {
-                        range.end.row()
+                        cmp::min(range.start.column(), range.end.column())
+                            ..cmp::max(range.start.column(), range.end.column())
                     };
-                    let start_column;
-                    let end_column;
-                    if let SelectionGoal::ColumnRange { start, end } = selection.goal {
-                        start_column = start;
-                        end_column = end;
-                    } else {
-                        start_column = cmp::min(range.start.column(), range.end.column());
-                        end_column = cmp::max(range.start.column(), range.end.column());
-                    }
-                    let is_empty = start_column == end_column;
 
-                    while row > 0 && row < self.display_map.max_point(app).row() {
+                    while row != end_row {
                         if above {
                             row -= 1;
                         } else {
                             row += 1;
                         }
 
-                        let line_len = self.display_map.line_len(row, app).unwrap();
-                        if start_column < line_len || (is_empty && start_column == line_len) {
-                            let id = post_inc(&mut self.next_selection_id);
-                            let start = DisplayPoint::new(row, start_column);
-                            let end = DisplayPoint::new(row, cmp::min(end_column, line_len));
-                            new_selections.push(Selection {
-                                id,
-                                start: self
-                                    .display_map
-                                    .anchor_before(start, Bias::Left, app)
-                                    .unwrap(),
-                                end: self
-                                    .display_map
-                                    .anchor_before(end, Bias::Left, app)
-                                    .unwrap(),
-                                reversed: selection.reversed
-                                    && range.start.row() == range.end.row(),
-                                goal: SelectionGoal::ColumnRange {
-                                    start: start_column,
-                                    end: end_column,
-                                },
-                            });
-                            state.stack.push(id);
-                            break;
+                        if let Some(new_selection) =
+                            self.build_columnar_selection(row, &columns, selection.reversed, app)
+                        {
+                            state.stack.push(new_selection.id);
+                            if above {
+                                new_selections.push(new_selection);
+                                new_selections.push(selection);
+                            } else {
+                                new_selections.push(selection);
+                                new_selections.push(new_selection);
+                            }
+
+                            continue 'outer;
                         }
                     }
                 }
 
-                new_selections.push(selection.clone());
+                new_selections.push(selection);
             }
-
-            new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap());
         } else {
-            new_selections.extend(
-                selections
-                    .into_iter()
-                    .filter(|s| s.id != last_added_selection)
-                    .cloned(),
-            );
+            new_selections = selections;
+            new_selections.retain(|s| s.id != last_added_selection);
             state.stack.pop();
         }
 
@@ -1892,6 +1892,39 @@ impl BufferView {
         }
     }
 
+    fn build_columnar_selection(
+        &mut self,
+        row: u32,
+        columns: &Range<u32>,
+        reversed: bool,
+        ctx: &AppContext,
+    ) -> Option<Selection> {
+        let is_empty = columns.start == columns.end;
+        let line_len = self.display_map.line_len(row, ctx).unwrap();
+        if columns.start < line_len || (is_empty && columns.start == line_len) {
+            let start = DisplayPoint::new(row, columns.start);
+            let end = DisplayPoint::new(row, cmp::min(columns.end, line_len));
+            Some(Selection {
+                id: post_inc(&mut self.next_selection_id),
+                start: self
+                    .display_map
+                    .anchor_before(start, Bias::Left, ctx)
+                    .unwrap(),
+                end: self
+                    .display_map
+                    .anchor_before(end, Bias::Left, ctx)
+                    .unwrap(),
+                reversed,
+                goal: SelectionGoal::ColumnRange {
+                    start: columns.start,
+                    end: columns.end,
+                },
+            })
+        } else {
+            None
+        }
+    }
+
     pub fn selections_in_range<'a>(
         &'a self,
         range: Range<DisplayPoint>,