text: Implement `Rope::clip_offset` in terms of the new utf8 boundary methods (#38630)

Lukas Wirth created

Release Notes:

- N/A

Change summary

crates/assistant_context/src/assistant_context.rs |  2 
crates/language/src/buffer.rs                     |  4 +-
crates/project/src/git_store/conflict_set.rs      |  4 +-
crates/rope/src/rope.rs                           | 24 ++--------------
crates/text/src/text.rs                           |  4 ++
5 files changed, 12 insertions(+), 26 deletions(-)

Detailed changes

crates/assistant_context/src/assistant_context.rs 🔗

@@ -2445,7 +2445,7 @@ impl AssistantContext {
                 .message_anchors
                 .get(next_message_ix)
                 .map_or(buffer.len(), |message| {
-                    buffer.clip_offset(message.start.to_offset(buffer) - 1, Bias::Left)
+                    buffer.clip_offset(message.start.to_previous_offset(buffer), Bias::Left)
                 });
             Some(self.insert_message_at_offset(offset, role, status, cx))
         } else {

crates/language/src/buffer.rs 🔗

@@ -4198,8 +4198,8 @@ impl BufferSnapshot {
         range: Range<T>,
         options: TreeSitterOptions,
     ) -> impl Iterator<Item = (Range<usize>, TextObject)> + '_ {
-        let range = range.start.to_offset(self).saturating_sub(1)
-            ..self.len().min(range.end.to_offset(self) + 1);
+        let range =
+            range.start.to_previous_offset(self)..self.len().min(range.end.to_next_offset(self));
 
         let mut matches =
             self.syntax

crates/project/src/git_store/conflict_set.rs 🔗

@@ -344,8 +344,8 @@ mod tests {
         assert_eq!(conflicts_in_range.len(), 1);
 
         // Test with a range that doesn't include any conflicts
-        let range = buffer.anchor_after(first_conflict_end.to_offset(&buffer) + 1)
-            ..buffer.anchor_before(second_conflict_start.to_offset(&buffer) - 1);
+        let range = buffer.anchor_after(first_conflict_end.to_next_offset(&buffer))
+            ..buffer.anchor_before(second_conflict_start.to_previous_offset(&buffer));
         let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);
         assert_eq!(conflicts_in_range.len(), 0);
     }

crates/rope/src/rope.rs 🔗

@@ -459,26 +459,10 @@ impl Rope {
             })
     }
 
-    pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize {
-        let mut cursor = self.chunks.cursor::<usize>(&());
-        cursor.seek(&offset, Bias::Left);
-        if let Some(chunk) = cursor.item() {
-            let mut ix = offset - cursor.start();
-            while !chunk.text.is_char_boundary(ix) {
-                match bias {
-                    Bias::Left => {
-                        ix -= 1;
-                        offset -= 1;
-                    }
-                    Bias::Right => {
-                        ix += 1;
-                        offset += 1;
-                    }
-                }
-            }
-            offset
-        } else {
-            self.summary().len
+    pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
+        match bias {
+            Bias::Left => self.floor_char_boundary(offset),
+            Bias::Right => self.ceil_char_boundary(offset),
         }
     }
 

crates/text/src/text.rs 🔗

@@ -2128,7 +2128,7 @@ impl BufferSnapshot {
         let row_end_offset = if row >= self.max_point().row {
             self.len()
         } else {
-            Point::new(row + 1, 0).to_offset(self) - 1
+            Point::new(row + 1, 0).to_previous_offset(self)
         };
         (row_end_offset - row_start_offset) as u32
     }
@@ -3076,11 +3076,13 @@ impl operation_queue::Operation for Operation {
 
 pub trait ToOffset {
     fn to_offset(&self, snapshot: &BufferSnapshot) -> usize;
+    /// Turns this point into the next offset in the buffer that comes after this, respecting utf8 boundaries.
     fn to_next_offset(&self, snapshot: &BufferSnapshot) -> usize {
         snapshot
             .visible_text
             .ceil_char_boundary(self.to_offset(snapshot) + 1)
     }
+    /// Turns this point into the previous offset in the buffer that comes before this, respecting utf8 boundaries.
     fn to_previous_offset(&self, snapshot: &BufferSnapshot) -> usize {
         snapshot
             .visible_text