Fix selection positions after typing with old selection anchors

This commit is contained in:
Max Brunsfeld 2022-02-03 18:14:00 -08:00
parent 40f9d2fc5d
commit d23115f628

View file

@ -1334,15 +1334,15 @@ impl Editor {
self.start_transaction(cx);
let mut old_selections = SmallVec::<[_; 32]>::new();
{
let selections = self.local_selections::<Point>(cx);
let selections = self.local_selections::<usize>(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<PendingEdit> = 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>) {
self.start_transaction(cx);
let old_selections = self.local_selections::<usize>(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::<Vec<_>>()
};
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::<usize>(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);
}
@ -5899,6 +5931,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);