Add a ToPointUtf16 trait in text and multibuffer

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/multi_buffer.rs | 66 ++++++++++++++++++++++++++++++++
crates/language/src/language.rs   | 23 ++++-------
crates/project/src/project.rs     |  7 ++-
crates/text/src/rope.rs           | 34 +++++++++++++++++
crates/text/src/text.rs           | 32 ++++++++++++++++
5 files changed, 144 insertions(+), 18 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -8,7 +8,7 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
 pub use language::Completion;
 use language::{
     Buffer, BufferChunks, BufferSnapshot, Chunk, DiagnosticEntry, Event, File, Language, Outline,
-    OutlineItem, Selection, ToOffset as _, ToPoint as _, TransactionId,
+    OutlineItem, Selection, ToOffset as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
 };
 use std::{
     cell::{Ref, RefCell},
@@ -73,6 +73,10 @@ pub trait ToPoint: 'static + fmt::Debug {
     fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
 }
 
+pub trait ToPointUtf16: 'static + fmt::Debug {
+    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16;
+}
+
 struct BufferState {
     buffer: ModelHandle<Buffer>,
     last_version: clock::Global,
@@ -1360,6 +1364,48 @@ impl MultiBufferSnapshot {
         }
     }
 
+    pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
+        if let Some(excerpt) = self.as_singleton() {
+            return excerpt.buffer.offset_to_point_utf16(offset);
+        }
+
+        let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>();
+        cursor.seek(&offset, Bias::Right, &());
+        if let Some(excerpt) = cursor.item() {
+            let (start_offset, start_point) = cursor.start();
+            let overshoot = offset - start_offset;
+            let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
+            let excerpt_start_point = excerpt.range.start.to_point_utf16(&excerpt.buffer);
+            let buffer_point = excerpt
+                .buffer
+                .offset_to_point_utf16(excerpt_start_offset + overshoot);
+            *start_point + (buffer_point - excerpt_start_point)
+        } else {
+            self.excerpts.summary().text.lines_utf16
+        }
+    }
+
+    pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
+        if let Some(excerpt) = self.as_singleton() {
+            return excerpt.buffer.point_to_point_utf16(point);
+        }
+
+        let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>();
+        cursor.seek(&point, Bias::Right, &());
+        if let Some(excerpt) = cursor.item() {
+            let (start_offset, start_point) = cursor.start();
+            let overshoot = point - start_offset;
+            let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
+            let excerpt_start_point_utf16 = excerpt.range.start.to_point_utf16(&excerpt.buffer);
+            let buffer_point = excerpt
+                .buffer
+                .point_to_point_utf16(excerpt_start_point + overshoot);
+            *start_point + (buffer_point - excerpt_start_point_utf16)
+        } else {
+            self.excerpts.summary().text.lines_utf16
+        }
+    }
+
     pub fn point_to_offset(&self, point: Point) -> usize {
         if let Some(excerpt) = self.as_singleton() {
             return excerpt.buffer.point_to_offset(point);
@@ -2487,6 +2533,24 @@ impl ToPoint for Point {
     }
 }
 
+impl ToPointUtf16 for usize {
+    fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 {
+        snapshot.offset_to_point_utf16(*self)
+    }
+}
+
+impl ToPointUtf16 for Point {
+    fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 {
+        snapshot.point_to_point_utf16(*self)
+    }
+}
+
+impl ToPointUtf16 for PointUtf16 {
+    fn to_point_utf16<'a>(&self, _: &MultiBufferSnapshot) -> PointUtf16 {
+        *self
+    }
+}
+
 pub fn char_kind(c: char) -> CharKind {
     if c == '\n' {
         CharKind::Newline

crates/language/src/language.rs 🔗

@@ -7,19 +7,20 @@ pub mod proto;
 mod tests;
 
 use anyhow::{anyhow, Result};
-pub use buffer::Operation;
-pub use buffer::*;
 use collections::HashSet;
-pub use diagnostic_set::DiagnosticEntry;
 use gpui::AppContext;
 use highlight_map::HighlightMap;
 use lazy_static::lazy_static;
-pub use outline::{Outline, OutlineItem};
 use parking_lot::Mutex;
 use serde::Deserialize;
 use std::{cell::RefCell, ops::Range, path::Path, str, sync::Arc};
 use theme::SyntaxTheme;
 use tree_sitter::{self, Query};
+
+pub use buffer::Operation;
+pub use buffer::*;
+pub use diagnostic_set::DiagnosticEntry;
+pub use outline::{Outline, OutlineItem};
 pub use tree_sitter::{Parser, Tree};
 
 thread_local! {
@@ -39,10 +40,6 @@ lazy_static! {
     ));
 }
 
-pub trait ToPointUtf16 {
-    fn to_point_utf16(self) -> PointUtf16;
-}
-
 pub trait ToLspPosition {
     fn to_lsp_position(self) -> lsp::Position;
 }
@@ -386,18 +383,16 @@ impl LanguageServerConfig {
     }
 }
 
-impl ToPointUtf16 for lsp::Position {
-    fn to_point_utf16(self) -> PointUtf16 {
-        PointUtf16::new(self.line, self.character)
-    }
-}
-
 impl ToLspPosition for PointUtf16 {
     fn to_lsp_position(self) -> lsp::Position {
         lsp::Position::new(self.row, self.column)
     }
 }
 
+pub fn point_from_lsp(point: lsp::Position) -> PointUtf16 {
+    PointUtf16::new(point.line, point.character)
+}
+
 pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
     let start = PointUtf16::new(range.start.line, range.start.character);
     let end = PointUtf16::new(range.end.line, range.end.character);

crates/project/src/project.rs 🔗

@@ -13,9 +13,10 @@ use gpui::{
     WeakModelHandle,
 };
 use language::{
+    point_from_lsp,
     proto::{deserialize_anchor, serialize_anchor},
     range_from_lsp, Bias, Buffer, Diagnostic, DiagnosticEntry, File as _, Language,
-    LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16,
+    LanguageRegistry, PointUtf16, ToOffset,
 };
 use lsp::{DiagnosticSeverity, LanguageServer};
 use postage::{prelude::Stream, watch};
@@ -1098,9 +1099,9 @@ impl Project {
                         cx.read(|cx| {
                             let target_buffer = target_buffer_handle.read(cx);
                             let target_start = target_buffer
-                                .clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left);
+                                .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
                             let target_end = target_buffer
-                                .clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left);
+                                .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
                             definitions.push(Definition {
                                 target_buffer: target_buffer_handle,
                                 target_range: target_buffer.anchor_after(target_start)

crates/text/src/rope.rs 🔗

@@ -179,6 +179,19 @@ impl Rope {
             })
     }
 
+    pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
+        if point >= self.summary().lines {
+            return self.summary().lines_utf16;
+        }
+        let mut cursor = self.chunks.cursor::<(Point, PointUtf16)>();
+        cursor.seek(&point, Bias::Left, &());
+        let overshoot = point - cursor.start().0;
+        cursor.start().1
+            + cursor.item().map_or(PointUtf16::zero(), |chunk| {
+                chunk.point_to_point_utf16(overshoot)
+            })
+    }
+
     pub fn point_to_offset(&self, point: Point) -> usize {
         if point >= self.summary().lines {
             return self.summary().bytes;
@@ -580,6 +593,27 @@ impl Chunk {
         offset
     }
 
+    fn point_to_point_utf16(&self, target: Point) -> PointUtf16 {
+        let mut point = Point::zero();
+        let mut point_utf16 = PointUtf16::new(0, 0);
+        for ch in self.0.chars() {
+            if point >= target {
+                break;
+            }
+
+            if ch == '\n' {
+                point_utf16.row += 1;
+                point_utf16.column = 0;
+                point.row += 1;
+                point.column = 0;
+            } else {
+                point_utf16.column += ch.len_utf16() as u32;
+                point.column += ch.len_utf8() as u32;
+            }
+        }
+        point_utf16
+    }
+
     fn point_utf16_to_offset(&self, target: PointUtf16) -> usize {
         let mut offset = 0;
         let mut point = PointUtf16::new(0, 0);

crates/text/src/text.rs 🔗

@@ -1512,6 +1512,10 @@ impl BufferSnapshot {
         self.visible_text.offset_to_point_utf16(offset)
     }
 
+    pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
+        self.visible_text.point_to_point_utf16(point)
+    }
+
     pub fn version(&self) -> &clock::Global {
         &self.version
     }
@@ -2260,6 +2264,34 @@ impl ToPoint for Point {
     }
 }
 
+pub trait ToPointUtf16 {
+    fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16;
+}
+
+impl ToPointUtf16 for Anchor {
+    fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
+        snapshot.summary_for_anchor(self)
+    }
+}
+
+impl ToPointUtf16 for usize {
+    fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
+        snapshot.offset_to_point_utf16(*self)
+    }
+}
+
+impl ToPointUtf16 for PointUtf16 {
+    fn to_point_utf16<'a>(&self, _: &BufferSnapshot) -> PointUtf16 {
+        *self
+    }
+}
+
+impl ToPointUtf16 for Point {
+    fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
+        snapshot.point_to_point_utf16(*self)
+    }
+}
+
 pub trait Clip {
     fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self;
 }