diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 789ea4c0cd..065bc2d74a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1334,15 +1334,15 @@ impl Editor { self.start_transaction(cx); let mut old_selections = SmallVec::<[_; 32]>::new(); { - let selections = self.local_selections::(cx); + let selections = self.local_selections::(cx); let buffer = self.buffer.read(cx).snapshot(cx); for selection in selections.iter() { - let start_point = selection.start; + let start_point = selection.start.to_point(&buffer); let indent = buffer .indent_column_for_line(start_point.row) .min(start_point.column); - let start = selection.start.to_offset(&buffer); - let end = selection.end.to_offset(&buffer); + let start = selection.start; + let end = selection.end; let mut insert_extra_newline = false; if let Some(language) = buffer.language() { @@ -1371,14 +1371,20 @@ impl Editor { }); } - old_selections.push((selection.id, start..end, indent, insert_extra_newline)); + old_selections.push(( + selection.id, + buffer.anchor_after(end), + start..end, + indent, + insert_extra_newline, + )); } } self.buffer.update(cx, |buffer, cx| { let mut delta = 0_isize; let mut pending_edit: Option = None; - for (_, range, indent, insert_extra_newline) in &old_selections { + for (_, _, range, indent, insert_extra_newline) in &old_selections { if pending_edit.as_ref().map_or(false, |pending| { pending.indent != *indent || pending.insert_extra_newline != *insert_extra_newline @@ -1423,17 +1429,19 @@ impl Editor { .iter() .cloned() .zip(old_selections) - .map(|(mut new_selection, (_, _, _, insert_extra_newline))| { - if insert_extra_newline { - let mut cursor = new_selection.start.to_point(&buffer); - cursor.row -= 1; - cursor.column = buffer.line_len(cursor.row); + .map( + |(mut new_selection, (_, end_anchor, _, _, insert_extra_newline))| { + let mut cursor = end_anchor.to_point(&buffer); + if insert_extra_newline { + cursor.row -= 1; + cursor.column = buffer.line_len(cursor.row); + } let anchor = buffer.anchor_after(cursor); new_selection.start = anchor.clone(); new_selection.end = anchor; - } - new_selection - }) + new_selection + }, + ) .collect(); }); @@ -1451,13 +1459,37 @@ impl Editor { pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { self.start_transaction(cx); + let old_selections = self.local_selections::(cx); - self.buffer.update(cx, |buffer, cx| { + let selection_anchors = self.buffer.update(cx, |buffer, cx| { + let anchors = { + let snapshot = buffer.read(cx); + old_selections + .iter() + .map(|s| (s.id, s.goal, snapshot.anchor_after(s.end))) + .collect::>() + }; let edit_ranges = old_selections.iter().map(|s| s.start..s.end); buffer.edit_with_autoindent(edit_ranges, text, cx); + anchors }); - let selections = self.local_selections::(cx); + let selections = { + let snapshot = self.buffer.read(cx).read(cx); + selection_anchors + .into_iter() + .map(|(id, goal, position)| { + let position = position.to_offset(&snapshot); + Selection { + id, + start: position, + end: position, + goal, + reversed: false, + } + }) + .collect() + }; self.update_selections(selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -5901,6 +5933,119 @@ mod tests { }); } + #[gpui::test] + fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) { + let buffer = MultiBuffer::build_simple( + " + a + b( + X + ) + c( + X + ) + " + .unindent() + .as_str(), + cx, + ); + + let settings = EditorSettings::test(&cx); + let (_, editor) = cx.add_window(Default::default(), |cx| { + let mut editor = build_editor(buffer.clone(), settings, cx); + editor.select_ranges( + [ + Point::new(2, 4)..Point::new(2, 5), + Point::new(5, 4)..Point::new(5, 5), + ], + None, + cx, + ); + editor + }); + + // Edit the buffer directly, deleting ranges surrounding the editor's selections + buffer.update(cx, |buffer, cx| { + buffer.edit( + [ + Point::new(1, 2)..Point::new(3, 0), + Point::new(4, 2)..Point::new(6, 0), + ], + "", + cx, + ); + assert_eq!( + buffer.read(cx).text(), + " + a + b() + c() + " + .unindent() + ); + }); + + editor.update(cx, |editor, cx| { + assert_eq!( + editor.selected_ranges(cx), + &[ + Point::new(1, 2)..Point::new(1, 2), + Point::new(2, 2)..Point::new(2, 2), + ], + ); + + editor.newline(&Newline, cx); + assert_eq!( + editor.text(cx), + " + a + b( + ) + c( + ) + " + .unindent() + ); + + // The selections are moved after the inserted newlines + assert_eq!( + editor.selected_ranges(cx), + &[ + Point::new(2, 0)..Point::new(2, 0), + Point::new(4, 0)..Point::new(4, 0), + ], + ); + }); + } + + #[gpui::test] + fn test_insert_with_old_selections(cx: &mut gpui::MutableAppContext) { + let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); + + let settings = EditorSettings::test(&cx); + let (_, editor) = cx.add_window(Default::default(), |cx| { + let mut editor = build_editor(buffer.clone(), settings, cx); + editor.select_ranges([3..4, 11..12, 19..20], None, cx); + editor + }); + + // Edit the buffer directly, deleting ranges surrounding the editor's selections + buffer.update(cx, |buffer, cx| { + buffer.edit([2..5, 10..13, 18..21], "", cx); + assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent()); + }); + + editor.update(cx, |editor, cx| { + assert_eq!(editor.selected_ranges(cx), &[2..2, 7..7, 12..12],); + + editor.insert("Z", cx); + assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)"); + + // The selections are moved after the inserted characters + assert_eq!(editor.selected_ranges(cx), &[3..3, 9..9, 15..15],); + }); + } + #[gpui::test] fn test_indent_outdent(cx: &mut gpui::MutableAppContext) { let buffer = MultiBuffer::build_simple(" one two\nthree\n four", cx);