Add test for common_prefix_at and rewrite it to be more readable and

Keith Simmons created

pass the new test cases

Change summary

crates/project/src/project.rs |  2 +-
crates/text/src/tests.rs      | 10 +++++++---
crates/text/src/text.rs       | 29 ++++++++++++++++++++++++++---
3 files changed, 34 insertions(+), 7 deletions(-)

Detailed changes

crates/project/src/project.rs 🔗

@@ -2364,7 +2364,7 @@ impl Project {
                                     (range_from_lsp(edit.range), edit.new_text.clone())
                                 }
                                 None => (
-                                    this.common_prefix_at_position(position, &lsp_completion.label),
+                                    this.common_prefix_at(position, &lsp_completion.label),
                                     lsp_completion.label.clone(),
                                 ),
                                 Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {

crates/text/src/tests.rs 🔗

@@ -7,6 +7,7 @@ use std::{
     iter::Iterator,
     time::{Duration, Instant},
 };
+use util::test::marked_text_ranges;
 
 #[cfg(test)]
 #[ctor::ctor]
@@ -166,10 +167,13 @@ fn test_line_len() {
 
 #[test]
 fn test_common_prefix_at_positionn() {
-    let buffer = Buffer::new(0, 0, History::new("a = (bcd)".into()));
+    let (text, ranges) = marked_text_ranges("a = [bcd]");
+    let buffer = Buffer::new(0, 0, History::new(text.into()));
+    let snapshot = &buffer.snapshot();
+    let expected_range = ranges[0].to_offset(&snapshot);
     assert_eq!(
-        buffer.common_prefix_at_position(Point::new(0, 8), "bcdef"),
-        Point::new(0, 5)..Point::new(0, 8)
+        buffer.common_prefix_at(expected_range.end, "bcdef"),
+        expected_range
     )
 }
 

crates/text/src/text.rs 🔗

@@ -1508,11 +1508,34 @@ impl BufferSnapshot {
                 .eq(needle.bytes())
     }
 
-    pub fn common_prefix_at_position<T>(&self, position: T, needle: &str) -> Range<T>
+    pub fn common_prefix_at<T>(&self, position: T, needle: &str) -> Range<T>
     where
-        T: TextDimension + ToOffset,
+        T: Clone + ToOffset + FromAnchor,
     {
-        todo!()
+        let position_offset = position.to_offset(self);
+        // Get byte indices and char counts for every character in needle in reverse order
+        let char_indices = needle
+            .char_indices()
+            .map(|(index, _)| index)
+            .chain(std::iter::once(needle.len()))
+            .enumerate()
+            // Don't test any prefixes that are bigger than the requested position
+            .take_while(|(_, prefix_length)| *prefix_length <= position_offset);
+
+        let start = char_indices
+            // Compute the prefix string and prefix start location
+            .map(move |(byte_position, char_length)| {
+                (position_offset - char_length, &needle[..byte_position])
+            })
+            // Only take strings when the prefix is contained at the expected prefix position
+            .filter(|(prefix_offset, prefix)| self.contains_str_at(prefix_offset, prefix))
+            // Convert offset to T
+            .map(|(prefix_offset, _)| T::from_anchor(&self.anchor_before(prefix_offset), self))
+            .last()
+            // If no prefix matches, return the passed in position to create an empty range
+            .unwrap_or(position.clone());
+
+        start..position
     }
 
     pub fn text(&self) -> String {