diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 76092bbecf..9feb3f11d0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -828,6 +828,23 @@ impl CompletionsMenu { }) .collect() }; + + //Remove all candidates where the query's start does not match the start of any word in the candidate + if let Some(query) = query { + if let Some(query_start) = query.chars().next() { + matches.retain(|string_match| { + 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()) + .all(|(word_cp, query_cp)| word_cp == query_cp) + }) + }); + } + } + matches.sort_unstable_by_key(|mat| { let completion = &self.completions[mat.candidate_id]; ( @@ -6802,6 +6819,34 @@ pub fn styled_runs_for_code_label<'a>( }) } +pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator + '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 { fn sorted(&self) -> Range; fn to_inclusive(&self) -> RangeInclusive; diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index b9f3c67f38..ff59c5dc14 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5445,6 +5445,20 @@ async fn go_to_hunk(deterministic: Arc, 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 { let point = DisplayPoint::new(row as u32, column as u32); point..point