editor: Fix move line up panic when selection is at end of line next to fold marker (#34982)

Smit Barmase created

Closes #34826

In move line up method, make use of `prev_line_boundary` which accounts
for fold map, etc., for selection start row so that we don't incorrectly
calculate row range to move up.

Release Notes:

- Fixed an issue where `editor: move line up` action sometimes crashed
if the cursor was at the end of a line beside a fold marker.

Change summary

crates/editor/src/editor.rs       | 10 +++++++++-
crates/editor/src/editor_tests.rs | 27 +++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)

Detailed changes

crates/editor/src/editor.rs πŸ”—

@@ -22258,7 +22258,7 @@ fn consume_contiguous_rows(
     selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
 ) -> (MultiBufferRow, MultiBufferRow) {
     contiguous_row_selections.push(selection.clone());
-    let start_row = MultiBufferRow(selection.start.row);
+    let start_row = starting_row(selection, display_map);
     let mut end_row = ending_row(selection, display_map);
 
     while let Some(next_selection) = selections.peek() {
@@ -22272,6 +22272,14 @@ fn consume_contiguous_rows(
     (start_row, end_row)
 }
 
+fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
+    if selection.start.column > 0 {
+        MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
+    } else {
+        MultiBufferRow(selection.start.row)
+    }
+}
+
 fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
     if next_selection.end.column > 0 || next_selection.is_empty() {
         MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)

crates/editor/src/editor_tests.rs πŸ”—

@@ -5069,6 +5069,33 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
     });
 }
 
+#[gpui::test]
+fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+    let editor = cx.add_window(|window, cx| {
+        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
+        build_editor(buffer, window, cx)
+    });
+    _ = editor.update(cx, |editor, window, cx| {
+        editor.fold_creases(
+            vec![Crease::simple(
+                Point::new(6, 4)..Point::new(7, 4),
+                FoldPlaceholder::test(),
+            )],
+            true,
+            window,
+            cx,
+        );
+        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
+        });
+        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaaβ‹―\ncccc");
+        editor.move_line_up(&MoveLineUp, window, cx);
+        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
+        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
+    });
+}
+
 #[gpui::test]
 fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
     init_test(cx, |_| {});