Clamp UTF-16 to point conversions

Julia created

Change summary

crates/editor/src/editor.rs                   |  2 
crates/editor/src/multi_buffer.rs             | 22 +++++++-----
crates/editor/src/selections_collection.rs    | 27 +++++++++++++++-
crates/project_symbols/src/project_symbols.rs |  2 
crates/rope/src/rope.rs                       | 35 ++++++++------------
crates/text/src/text.rs                       | 22 ++++++------
6 files changed, 65 insertions(+), 45 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -55,7 +55,7 @@ use link_go_to_definition::{
 };
 pub use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
-    ToPoint,
+    ToOffsetClamped, ToPoint,
 };
 use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
 use ordered_float::OrderedFloat;

crates/editor/src/multi_buffer.rs 🔗

@@ -72,6 +72,10 @@ pub trait ToOffset: 'static + fmt::Debug {
     fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
 }
 
+pub trait ToOffsetClamped: 'static + fmt::Debug {
+    fn to_offset_clamped(&self, snapshot: &MultiBufferSnapshot) -> usize;
+}
+
 pub trait ToOffsetUtf16: 'static + fmt::Debug {
     fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16;
 }
@@ -1945,9 +1949,9 @@ impl MultiBufferSnapshot {
         }
     }
 
-    pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
+    pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize {
         if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.point_utf16_to_offset(point);
+            return buffer.point_utf16_to_offset_clamped(point);
         }
 
         let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
@@ -1961,7 +1965,7 @@ impl MultiBufferSnapshot {
                 .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer));
             let buffer_offset = excerpt
                 .buffer
-                .point_utf16_to_offset(excerpt_start_point + overshoot);
+                .point_utf16_to_offset_clamped(excerpt_start_point + overshoot);
             *start_offset + (buffer_offset - excerpt_start_offset)
         } else {
             self.excerpts.summary().text.len
@@ -3274,12 +3278,6 @@ impl ToOffset for Point {
     }
 }
 
-impl ToOffset for PointUtf16 {
-    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
-        snapshot.point_utf16_to_offset(*self)
-    }
-}
-
 impl ToOffset for usize {
     fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
         assert!(*self <= snapshot.len(), "offset is out of range");
@@ -3293,6 +3291,12 @@ impl ToOffset for OffsetUtf16 {
     }
 }
 
+impl ToOffsetClamped for PointUtf16 {
+    fn to_offset_clamped<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
+        snapshot.point_utf16_to_offset_clamped(*self)
+    }
+}
+
 impl ToOffsetUtf16 for OffsetUtf16 {
     fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
         *self

crates/editor/src/selections_collection.rs 🔗

@@ -14,6 +14,7 @@ use util::post_inc;
 use crate::{
     display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
     Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset,
+    ToOffsetClamped,
 };
 
 #[derive(Clone)]
@@ -544,11 +545,33 @@ impl<'a> MutableSelectionsCollection<'a> {
         T: ToOffset,
     {
         let buffer = self.buffer.read(self.cx).snapshot(self.cx);
+        let ranges = ranges
+            .into_iter()
+            .map(|range| range.start.to_offset(&buffer)..range.end.to_offset(&buffer));
+        self.select_offset_ranges(ranges);
+    }
+
+    pub fn select_clamped_ranges<I, T>(&mut self, ranges: I)
+    where
+        I: IntoIterator<Item = Range<T>>,
+        T: ToOffsetClamped,
+    {
+        let buffer = self.buffer.read(self.cx).snapshot(self.cx);
+        let ranges = ranges.into_iter().map(|range| {
+            range.start.to_offset_clamped(&buffer)..range.end.to_offset_clamped(&buffer)
+        });
+        self.select_offset_ranges(ranges);
+    }
+
+    fn select_offset_ranges<I>(&mut self, ranges: I)
+    where
+        I: IntoIterator<Item = Range<usize>>,
+    {
         let selections = ranges
             .into_iter()
             .map(|range| {
-                let mut start = range.start.to_offset(&buffer);
-                let mut end = range.end.to_offset(&buffer);
+                let mut start = range.start;
+                let mut end = range.end;
                 let reversed = if start > end {
                     mem::swap(&mut start, &mut end);
                     true

crates/project_symbols/src/project_symbols.rs 🔗

@@ -151,7 +151,7 @@ impl ProjectSymbolsView {
                         let editor = workspace.open_project_item::<Editor>(buffer, cx);
                         editor.update(cx, |editor, cx| {
                             editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                                s.select_ranges([position..position])
+                                s.select_clamped_ranges([position..position])
                             });
                         });
                     });

crates/rope/src/rope.rs 🔗

@@ -259,7 +259,7 @@ impl Rope {
                 .map_or(0, |chunk| chunk.point_to_offset(overshoot))
     }
 
-    pub fn point_utf16_to_offset(&self, point: PointUtf16, clamp: bool) -> usize {
+    pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize {
         if point >= self.summary().lines_utf16() {
             return self.summary().len;
         }
@@ -269,7 +269,7 @@ impl Rope {
         cursor.start().1
             + cursor
                 .item()
-                .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot, clamp))
+                .map_or(0, |chunk| chunk.point_utf16_to_offset_clamped(overshoot))
     }
 
     pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
@@ -711,7 +711,7 @@ impl Chunk {
         point_utf16
     }
 
-    fn point_utf16_to_offset(&self, target: PointUtf16, clamp: bool) -> usize {
+    fn point_utf16_to_offset_clamped(&self, target: PointUtf16) -> usize {
         let mut offset = 0;
         let mut point = PointUtf16::new(0, 0);
         for ch in self.0.chars() {
@@ -724,26 +724,19 @@ impl Chunk {
                 point.column = 0;
 
                 if point.row > target.row {
-                    if clamp {
-                        //Return the offset up to but not including the newline
-                        return offset;
-                    }
-                    panic!(
-                        "point {:?} is beyond the end of a line with length {}",
-                        target, point.column
-                    );
+                    //point is beyond the end of the line
+                    //Return the offset up to but not including the newline
+                    return offset;
                 }
             } else {
                 point.column += ch.len_utf16() as u32;
             }
 
             if point > target {
-                if clamp {
-                    //Return the offset before adding the len of the codepoint which
-                    //we have landed within, bias left
-                    return offset;
-                }
-                panic!("point {:?} is inside of character {:?}", target, ch);
+                //point is inside of a codepoint
+                //Return the offset before adding the len of the codepoint which
+                //we have landed within, bias left
+                return offset;
             }
 
             offset += ch.len_utf8();
@@ -1222,7 +1215,7 @@ mod tests {
                     point
                 );
                 assert_eq!(
-                    actual.point_utf16_to_offset(point_utf16, false),
+                    actual.point_utf16_to_offset_clamped(point_utf16),
                     ix,
                     "point_utf16_to_offset({:?})",
                     point_utf16
@@ -1262,9 +1255,9 @@ mod tests {
                 let left_point = actual.clip_point_utf16(point_utf16, Bias::Left);
                 let right_point = actual.clip_point_utf16(point_utf16, Bias::Right);
                 assert!(right_point >= left_point);
-                // Ensure translating valid UTF-16 points to offsets doesn't panic.
-                actual.point_utf16_to_offset(left_point, false);
-                actual.point_utf16_to_offset(right_point, false);
+                // Ensure translating UTF-16 points to offsets doesn't panic.
+                actual.point_utf16_to_offset_clamped(left_point);
+                actual.point_utf16_to_offset_clamped(right_point);
 
                 offset_utf16.0 += 1;
                 if unit == b'\n' as u16 {

crates/text/src/text.rs 🔗

@@ -1590,12 +1590,8 @@ impl BufferSnapshot {
         self.visible_text.point_to_offset(point)
     }
 
-    pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
-        self.visible_text.point_utf16_to_offset(point, false)
-    }
-
     pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize {
-        self.visible_text.point_utf16_to_offset(point, true)
+        self.visible_text.point_utf16_to_offset_clamped(point)
     }
 
     pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
@@ -2425,18 +2421,22 @@ impl ToPoint for usize {
     }
 }
 
-impl ToPoint for PointUtf16 {
-    fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point {
-        snapshot.point_utf16_to_point(*self)
-    }
-}
-
 impl ToPoint for Point {
     fn to_point<'a>(&self, _: &BufferSnapshot) -> Point {
         *self
     }
 }
 
+pub trait ToPointClamped {
+    fn to_point_clamped(&self, snapshot: &BufferSnapshot) -> Point;
+}
+
+impl ToPointClamped for PointUtf16 {
+    fn to_point_clamped<'a>(&self, snapshot: &BufferSnapshot) -> Point {
+        snapshot.point_utf16_to_point(*self)
+    }
+}
+
 pub trait ToPointUtf16 {
     fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16;
 }