Allow left-biased anchors at the beginnings of excerpts

Max Brunsfeld and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>

Change summary

crates/editor/src/editor.rs       | 53 ++++++++++++++++++++++++++++++++
crates/editor/src/multi_buffer.rs | 15 +++------
2 files changed, 57 insertions(+), 11 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -3958,7 +3958,6 @@ mod tests {
             view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
         });
 
-        eprintln!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
         assert_eq!(
             editor.update(cx, |view, cx| view.selected_display_ranges(cx)),
             [
@@ -5857,6 +5856,58 @@ mod tests {
         });
     }
 
+    #[gpui::test]
+    fn test_multi_buffer_editing(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(0, 4),
+                    header_height: 0,
+                },
+                cx,
+            );
+            multibuffer.push_excerpt(
+                ExcerptProperties {
+                    buffer: &buffer,
+                    range: Point::new(1, 0)..Point::new(1, 4),
+                    header_height: 0,
+                },
+                cx,
+            );
+            multibuffer
+        });
+
+        assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb\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(0, 0)..DisplayPoint::new(0, 0),
+                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+                ],
+                cx,
+            )
+            .unwrap();
+
+            view.handle_input(&Input("X".to_string()), cx);
+            assert_eq!(view.text(cx), "Xaaaa\nXbbbb\n");
+            assert_eq!(
+                view.selected_display_ranges(cx),
+                &[
+                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                    DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+                ]
+            )
+        });
+    }
+
     #[gpui::test]
     async fn test_extra_newline_insertion(mut cx: gpui::TestAppContext) {
         let settings = cx.read(EditorSettings::test);

crates/editor/src/multi_buffer.rs 🔗

@@ -55,11 +55,11 @@ struct Transaction {
     last_edit_at: Instant,
 }
 
-pub trait ToOffset: 'static {
+pub trait ToOffset: 'static + std::fmt::Debug {
     fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
 }
 
-pub trait ToPoint: 'static {
+pub trait ToPoint: 'static + std::fmt::Debug {
     fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
 }
 
@@ -171,7 +171,7 @@ impl MultiBuffer {
     }
 
     pub fn as_singleton(&self) -> Option<&ModelHandle<Buffer>> {
-        if self.buffers.len() == 1 {
+        if self.singleton {
             return Some(&self.buffers.values().next().unwrap().buffer);
         } else {
             None
@@ -1150,16 +1150,11 @@ impl MultiBufferSnapshot {
     pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
         let offset = position.to_offset(self);
         let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>();
-        cursor.seek(&offset, bias, &());
+        cursor.seek(&offset, Bias::Right, &());
         if let Some(excerpt) = cursor.item() {
             let start_after_header = cursor.start().0 + excerpt.header_height as usize;
-            let mut end_before_newline = cursor.end(&()).0;
-            if excerpt.has_trailing_newline {
-                end_before_newline -= 1;
-            }
-
             let buffer_start = excerpt.range.start.to_offset(&excerpt.buffer);
-            let overshoot = cmp::min(offset, end_before_newline).saturating_sub(start_after_header);
+            let overshoot = offset.saturating_sub(start_after_header);
             Anchor {
                 excerpt_id: excerpt.id.clone(),
                 text_anchor: excerpt.buffer.anchor_at(buffer_start + overshoot, bias),