Test that completion word splitting does reasonable things

Julia created

Change summary

crates/editor/src/editor.rs       | 59 +++++++++++++++++---------------
crates/editor/src/editor_tests.rs | 14 +++++++
2 files changed, 45 insertions(+), 28 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -832,34 +832,9 @@ impl CompletionsMenu {
         if let Some(query) = query {
             if let Some(query_start) = query.chars().next() {
                 matches.retain(|string_match| {
-                    let text = &string_match.string;
-
-                    let mut index = 0;
-                    let mut codepoints = text.char_indices().peekable();
-
-                    std::iter::from_fn(move || {
-                        let start_index = index;
-                        while let Some((new_index, codepoint)) = codepoints.next() {
-                            index = new_index + codepoint.len_utf8();
-                            let current_upper = codepoint.is_uppercase();
-                            let next_upper = codepoints
-                                .peek()
-                                .map(|(_, c)| c.is_uppercase())
-                                .unwrap_or(false);
-
-                            if !current_upper && next_upper {
-                                return Some(&text[start_index..index]);
-                            }
-                        }
-
-                        index = text.len();
-                        if start_index < text.len() {
-                            return Some(&text[start_index..]);
-                        }
-                        None
-                    })
-                    .flat_map(|word| word.split_inclusive('_'))
-                    .any(|word| {
+                    split_words(&string_match.string).any(|word| {
+                        //Check that the first codepoint of the word as lowercase matches the first
+                        //codepoint of the query as lowercase
                         word.chars()
                             .flat_map(|codepoint| codepoint.to_lowercase())
                             .zip(query_start.to_lowercase())
@@ -6841,6 +6816,34 @@ pub fn styled_runs_for_code_label<'a>(
         })
 }
 
+pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
+    let mut index = 0;
+    let mut codepoints = text.char_indices().peekable();
+
+    std::iter::from_fn(move || {
+        let start_index = index;
+        while let Some((new_index, codepoint)) = codepoints.next() {
+            index = new_index + codepoint.len_utf8();
+            let current_upper = codepoint.is_uppercase();
+            let next_upper = codepoints
+                .peek()
+                .map(|(_, c)| c.is_uppercase())
+                .unwrap_or(false);
+
+            if !current_upper && next_upper {
+                return Some(&text[start_index..index]);
+            }
+        }
+
+        index = text.len();
+        if start_index < text.len() {
+            return Some(&text[start_index..]);
+        }
+        None
+    })
+    .flat_map(|word| word.split_inclusive('_'))
+}
+
 trait RangeExt<T> {
     fn sorted(&self) -> Range<T>;
     fn to_inclusive(&self) -> RangeInclusive<T>;

crates/editor/src/editor_tests.rs 🔗

@@ -5439,6 +5439,20 @@ async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppCon
     );
 }
 
+#[test]
+fn test_split_words() {
+    fn split<'a>(text: &'a str) -> Vec<&'a str> {
+        split_words(text).collect()
+    }
+
+    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
+    assert_eq!(split("hello_world"), &["hello_", "world"]);
+    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
+    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
+    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
+    assert_eq!(split("helloworld"), &["helloworld"]);
+}
+
 fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
     let point = DisplayPoint::new(row as u32, column as u32);
     point..point