From e38c1814d593b666844c3da5e77435d66adce96e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 14 Dec 2021 12:15:26 -0800 Subject: [PATCH] Update selections on text insertion using anchors The delta-based approach doesn't work for multi-excerpt buffers. Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 134 ++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 36 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5a1641b75b..42bfd30c09 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22,6 +22,7 @@ use gpui::{ MutableAppContext, RenderContext, View, ViewContext, WeakModelHandle, WeakViewHandle, }; use items::BufferItemHandle; +use itertools::Itertools as _; use language::{ BracketPair, Buffer, Diagnostic, DiagnosticSeverity, Language, Point, Selection, SelectionGoal, TransactionId, @@ -1267,29 +1268,26 @@ impl Editor { fn insert(&mut self, text: &str, cx: &mut ViewContext) { self.start_transaction(cx); let old_selections = self.local_selections::(cx); - let mut new_selections = Vec::new(); - self.buffer.update(cx, |buffer, cx| { + let new_selections = self.buffer.update(cx, |buffer, cx| { + let snapshot = buffer.read(cx); + let new_selections = old_selections + .iter() + .map(|selection| Selection { + id: selection.id, + start: snapshot.anchor_after(selection.start), + end: snapshot.anchor_after(selection.end), + reversed: false, + goal: SelectionGoal::None, + }) + .collect::>(); + + drop(snapshot); let edit_ranges = old_selections.iter().map(|s| s.start..s.end); buffer.edit_with_autoindent(edit_ranges, text, cx); - let text_len = text.len() as isize; - let mut delta = 0_isize; - new_selections = old_selections - .into_iter() - .map(|selection| { - let start = selection.start as isize; - let end = selection.end as isize; - let cursor = (start + delta + text_len) as usize; - let deleted_count = end - start; - delta += text_len - deleted_count; - Selection { - id: selection.id, - start: cursor, - end: cursor, - reversed: false, - goal: SelectionGoal::None, - } - }) - .collect(); + + let snapshot = buffer.read(cx); + self.resolve_selections::(new_selections.iter(), &snapshot) + .collect() }); self.update_selections(new_selections, Some(Autoscroll::Fit), cx); @@ -3099,21 +3097,8 @@ impl Editor { D: 'a + TextDimension + Ord + Sub, { let buffer = self.buffer.read(cx).snapshot(cx); - - let mut summaries = buffer - .summaries_for_anchors::(self.selections.iter().flat_map(|s| [&s.start, &s.end])) - .into_iter(); - let mut selections = self - .selections - .iter() - .map(|s| Selection { - id: s.id, - start: summaries.next().unwrap(), - end: summaries.next().unwrap(), - reversed: s.reversed, - goal: s.goal, - }) + .resolve_selections::(self.selections.iter(), &buffer) .peekable(); let mut pending_selection = self.pending_selection::(&buffer); @@ -3144,6 +3129,28 @@ impl Editor { .collect() } + fn resolve_selections<'a, D, I>( + &self, + selections: I, + snapshot: &MultiBufferSnapshot, + ) -> impl 'a + Iterator> + where + D: TextDimension + Ord + Sub, + I: 'a + IntoIterator>, + { + let (to_summarize, selections) = selections.into_iter().tee(); + let mut summaries = snapshot + .summaries_for_anchors::(to_summarize.flat_map(|s| [&s.start, &s.end])) + .into_iter(); + selections.map(move |s| Selection { + id: s.id, + start: summaries.next().unwrap(), + end: summaries.next().unwrap(), + reversed: s.reversed, + goal: s.goal, + }) + } + fn pending_selection>( &self, snapshot: &MultiBufferSnapshot, @@ -5857,7 +5864,7 @@ mod tests { } #[gpui::test] - fn test_multi_buffer_editing(cx: &mut gpui::MutableAppContext) { + fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) { let settings = EditorSettings::test(cx); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); let multibuffer = cx.add_model(|cx| { @@ -5908,6 +5915,61 @@ mod tests { }); } + #[gpui::test] + fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) { + let settings = EditorSettings::test(cx); + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); + let multibuffer = cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer, + range: Point::new(0, 0)..Point::new(1, 4), + header_height: 0, + }, + cx, + ); + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer, + range: Point::new(1, 0)..Point::new(2, 4), + header_height: 0, + }, + cx, + ); + multibuffer + }); + + assert_eq!( + multibuffer.read(cx).read(cx).text(), + "aaaa\nbbbb\nbbbb\ncccc\n" + ); + + let (_, view) = cx.add_window(Default::default(), |cx| { + build_editor(multibuffer, settings, cx) + }); + view.update(cx, |view, cx| { + view.select_display_ranges( + &[ + DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), + DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3), + ], + cx, + ) + .unwrap(); + + view.handle_input(&Input("X".to_string()), cx); + assert_eq!(view.text(cx), "aaaa\nbXbbXb\nbXbbXb\ncccc\n"); + assert_eq!( + view.selected_display_ranges(cx), + &[ + DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), + DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), + ] + ) + }); + } + #[gpui::test] async fn test_extra_newline_insertion(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test);