@@ -3955,36 +3955,55 @@ impl Editor {
}
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
- let selection = self.selections.newest::<Point>(cx);
- let snapshot = self.buffer.read(cx).snapshot(cx);
+ let mut row_ranges = Vec::<Range<u32>>::new();
+ for selection in self.selections.ranges::<Point>(cx) {
+ let start = selection.start.row;
+ let end = if selection.start.row == selection.end.row {
+ selection.start.row + 1
+ } else {
+ selection.end.row
+ };
- let row_range = if selection.start.row == selection.end.row {
- selection.start.row..selection.start.row + 1
- } else {
- selection.start.row..selection.end.row
- };
+ if let Some(last_row_range) = row_ranges.last_mut() {
+ if start <= last_row_range.end {
+ last_row_range.end = end;
+ continue;
+ }
+ }
+ row_ranges.push(start..end);
+ }
- self.transact(cx, |this, cx| {
- for (ix, row) in row_range.rev().enumerate() {
- let end_of_line = Point::new(row, snapshot.line_len(row));
- let start_of_next_line = end_of_line + Point::new(1, 0);
+ let snapshot = self.buffer.read(cx).snapshot(cx);
+ let mut cursor_positions = Vec::new();
+ for row_range in &row_ranges {
+ let anchor = snapshot.anchor_before(Point::new(
+ row_range.end - 1,
+ snapshot.line_len(row_range.end - 1),
+ ));
+ cursor_positions.push(anchor.clone()..anchor);
+ }
- let replace = if snapshot.line_len(row + 1) > 0 {
- " "
- } else {
- ""
- };
+ self.transact(cx, |this, cx| {
+ for row_range in row_ranges.into_iter().rev() {
+ for row in row_range.rev() {
+ let end_of_line = Point::new(row, snapshot.line_len(row));
+ let start_of_next_line = end_of_line + Point::new(1, 0);
- this.buffer.update(cx, |buffer, cx| {
- buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
- });
+ let replace = if snapshot.line_len(row + 1) > 0 {
+ " "
+ } else {
+ ""
+ };
- if ix == 0 {
- this.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.select_ranges([end_of_line..end_of_line])
- })
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
+ });
}
}
+
+ this.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select_anchor_ranges(cursor_positions)
+ });
});
}
@@ -2329,7 +2329,7 @@ fn test_delete_line(cx: &mut TestAppContext) {
}
#[gpui::test]
-fn test_join_lines(cx: &mut TestAppContext) {
+fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
init_test(cx, |_| {});
cx.add_window(|cx| {
@@ -2342,6 +2342,7 @@ fn test_join_lines(cx: &mut TestAppContext) {
&[Point::new(0, 0)..Point::new(0, 0)]
);
+ // When on single line, replace newline at end by space
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
assert_eq!(
@@ -2349,6 +2350,7 @@ fn test_join_lines(cx: &mut TestAppContext) {
&[Point::new(0, 3)..Point::new(0, 3)]
);
+ // When multiple lines are selected, remove newlines that are spanned by the selection
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
});
@@ -2359,6 +2361,7 @@ fn test_join_lines(cx: &mut TestAppContext) {
&[Point::new(0, 11)..Point::new(0, 11)]
);
+ // Undo should be transactional
editor.undo(&Undo, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
assert_eq!(
@@ -2366,6 +2369,7 @@ fn test_join_lines(cx: &mut TestAppContext) {
&[Point::new(0, 5)..Point::new(2, 2)]
);
+ // When joining an empty line don't insert a space
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
});
@@ -2376,6 +2380,7 @@ fn test_join_lines(cx: &mut TestAppContext) {
[Point::new(2, 3)..Point::new(2, 3)]
);
+ // We can remove trailing newlines
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
assert_eq!(
@@ -2383,6 +2388,7 @@ fn test_join_lines(cx: &mut TestAppContext) {
[Point::new(2, 3)..Point::new(2, 3)]
);
+ // We don't blow up on the last line
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
assert_eq!(
@@ -2394,6 +2400,37 @@ fn test_join_lines(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
+ let mut editor = build_editor(buffer.clone(), cx);
+ let buffer = buffer.read(cx).as_singleton().unwrap();
+
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([
+ Point::new(0, 2)..Point::new(1, 1),
+ Point::new(1, 2)..Point::new(1, 2),
+ Point::new(3, 1)..Point::new(3, 2),
+ ])
+ });
+
+ editor.join_lines(&JoinLines, cx);
+ assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
+
+ assert_eq!(
+ editor.selections.ranges::<Point>(cx),
+ [
+ Point::new(0, 7)..Point::new(0, 7),
+ Point::new(1, 3)..Point::new(1, 3)
+ ]
+ );
+ editor
+ });
+}
+
#[gpui::test]
fn test_duplicate_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});