diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 40576d90c54cd80e637c536ee990496e3fc1c396..41711de5701874517e46727890a5fb3211c9a667 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -11551,7 +11551,7 @@ impl Editor { end } else { text.push('\n'); - Point::new(rows.end.0, 0) + Point::new(rows.start.0, 0) } } else { text.push('\n'); @@ -11567,11 +11567,57 @@ impl Editor { } } - self.transact(window, cx, |this, _, cx| { + self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); + // When duplicating upward with whole lines, move the cursor to the duplicated line + if upwards && whole_lines { + let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx)); + + this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + let mut new_ranges = Vec::new(); + let selections = s.all::(&display_map); + let mut selections_iter = selections.iter().peekable(); + + while let Some(first_selection) = selections_iter.next() { + // Group contiguous selections together to find the total row span + let mut group_selections = vec![first_selection]; + let mut rows = first_selection.spanned_rows(false, &display_map); + + while let Some(next_selection) = selections_iter.peek() { + let next_rows = next_selection.spanned_rows(false, &display_map); + if next_rows.start < rows.end { + rows.end = next_rows.end; + group_selections.push(selections_iter.next().unwrap()); + } else { + break; + } + } + + let row_count = rows.end.0 - rows.start.0; + + // Move all selections in this group up by the total number of duplicated rows + for selection in group_selections { + let new_start = Point::new( + selection.start.row.saturating_sub(row_count), + selection.start.column, + ); + + let new_end = Point::new( + selection.end.row.saturating_sub(row_count), + selection.end.column, + ); + + new_ranges.push(new_start..new_end); + } + } + + s.select_ranges(new_ranges); + }); + } + this.request_autoscroll(Autoscroll::fit(), cx); }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4ccf37b021244b87bccd090ec691b09903d7b0f6..a319ad654d016204dbad748d0aa169dee545a44f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5646,8 +5646,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) { ); }); - // With `move_upwards` the selections stay in place, except for - // the lines inserted above them + // With `duplicate_line_up` the selections move to the duplicated lines, + // which are inserted above the original lines let editor = cx.add_window(|window, cx| { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, window, cx) @@ -5669,7 +5669,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0), - DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0), + DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0), ] ); }); @@ -26888,8 +26888,8 @@ fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) assert_eq!( editor.selections.display_ranges(cx), - vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)], - "Selection should remain on the original line" + vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)], + "Selection should move to the duplicated line" ); }) .unwrap();