diff --git a/crates/agent_ui/src/acp/completion_provider.rs b/crates/agent_ui/src/acp/completion_provider.rs index 3e02921341fb38a597064ad1a0829e5a922dd5ac..0329ee85e65a079771a0b878c38b743f7e874c8e 100644 --- a/crates/agent_ui/src/acp/completion_provider.rs +++ b/crates/agent_ui/src/acp/completion_provider.rs @@ -109,7 +109,7 @@ impl ContextPickerCompletionProvider { icon_path: Some(mode.icon().path().into()), documentation: None, source: project::CompletionSource::Custom, - buffer_match: None, + match_start: None, insert_text_mode: None, // This ensures that when a user accepts this completion, the // completion menu will still be shown after "@category " is @@ -147,7 +147,7 @@ impl ContextPickerCompletionProvider { documentation: None, insert_text_mode: None, source: project::CompletionSource::Custom, - buffer_match: None, + match_start: None, icon_path: Some(icon_for_completion), confirm: Some(confirm_completion_callback( thread_entry.title().clone(), @@ -179,7 +179,7 @@ impl ContextPickerCompletionProvider { documentation: None, insert_text_mode: None, source: project::CompletionSource::Custom, - buffer_match: None, + match_start: None, icon_path: Some(icon_path), confirm: Some(confirm_completion_callback( rule.title, @@ -236,7 +236,7 @@ impl ContextPickerCompletionProvider { documentation: None, source: project::CompletionSource::Custom, icon_path: Some(completion_icon_path), - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: Some(confirm_completion_callback( file_name, @@ -283,7 +283,7 @@ impl ContextPickerCompletionProvider { documentation: None, source: project::CompletionSource::Custom, icon_path: Some(icon_path), - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: Some(confirm_completion_callback( symbol.name.into(), @@ -316,7 +316,7 @@ impl ContextPickerCompletionProvider { documentation: None, source: project::CompletionSource::Custom, icon_path: Some(icon_path), - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: Some(confirm_completion_callback( url_to_fetch.to_string().into(), @@ -385,7 +385,7 @@ impl ContextPickerCompletionProvider { icon_path: Some(action.icon().path().into()), documentation: None, source: project::CompletionSource::Custom, - buffer_match: None, + match_start: None, insert_text_mode: None, // This ensures that when a user accepts this completion, the // completion menu will still be shown after "@category " is @@ -759,7 +759,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { )), source: project::CompletionSource::Custom, icon_path: None, - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: Some(Arc::new({ let editor = editor.clone(); diff --git a/crates/agent_ui/src/context_picker/completion_provider.rs b/crates/agent_ui/src/context_picker/completion_provider.rs index ab0db5db950c8589382b1d5423e21fe7a18e61f6..fc77f92c8fd4cb5b6d8cbe4095904aa3ffac3741 100644 --- a/crates/agent_ui/src/context_picker/completion_provider.rs +++ b/crates/agent_ui/src/context_picker/completion_provider.rs @@ -278,7 +278,7 @@ impl ContextPickerCompletionProvider { icon_path: Some(mode.icon().path().into()), documentation: None, source: project::CompletionSource::Custom, - buffer_match: None, + match_start: None, insert_text_mode: None, // This ensures that when a user accepts this completion, the // completion menu will still be shown after "@category " is @@ -387,7 +387,7 @@ impl ContextPickerCompletionProvider { icon_path: Some(action.icon().path().into()), documentation: None, source: project::CompletionSource::Custom, - buffer_match: None, + match_start: None, insert_text_mode: None, // This ensures that when a user accepts this completion, the // completion menu will still be shown after "@category " is @@ -419,7 +419,7 @@ impl ContextPickerCompletionProvider { replace_range: source_range.clone(), new_text, label: CodeLabel::plain(thread_entry.title().to_string(), None), - buffer_match: None, + match_start: None, documentation: None, insert_text_mode: None, source: project::CompletionSource::Custom, @@ -487,7 +487,7 @@ impl ContextPickerCompletionProvider { replace_range: source_range.clone(), new_text, label: CodeLabel::plain(rules.title.to_string(), None), - buffer_match: None, + match_start: None, documentation: None, insert_text_mode: None, source: project::CompletionSource::Custom, @@ -528,7 +528,7 @@ impl ContextPickerCompletionProvider { documentation: None, source: project::CompletionSource::Custom, icon_path: Some(IconName::ToolWeb.path().into()), - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: Some(confirm_completion_callback( IconName::ToolWeb.path().into(), @@ -617,7 +617,7 @@ impl ContextPickerCompletionProvider { documentation: None, source: project::CompletionSource::Custom, icon_path: Some(completion_icon_path), - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: Some(confirm_completion_callback( crease_icon_path, @@ -696,7 +696,7 @@ impl ContextPickerCompletionProvider { documentation: None, source: project::CompletionSource::Custom, icon_path: Some(IconName::Code.path().into()), - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: Some(confirm_completion_callback( IconName::Code.path().into(), diff --git a/crates/agent_ui/src/slash_command.rs b/crates/agent_ui/src/slash_command.rs index 7f45a5ac6747649adf7a84f01888a6c56100732f..496a71f6f9229e2d20e9eff2bca7fff7724c1f38 100644 --- a/crates/agent_ui/src/slash_command.rs +++ b/crates/agent_ui/src/slash_command.rs @@ -127,7 +127,7 @@ impl SlashCommandCompletionProvider { new_text, label: command.label(cx), icon_path: None, - buffer_match: None, + match_start: None, insert_text_mode: None, confirm, source: CompletionSource::Custom, @@ -233,7 +233,7 @@ impl SlashCommandCompletionProvider { icon_path: None, new_text, documentation: None, - buffer_match: None, + match_start: None, confirm, insert_text_mode: None, source: CompletionSource::Custom, diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index 8520d3f7cd66fcb1a7f8ec6bdbe123dab502e530..b1ed1a501b21a6519da099986ffe59dd2ec3d37a 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -670,7 +670,7 @@ impl ConsoleQueryBarCompletionProvider { ), new_text: string_match.string.clone(), label: CodeLabel::plain(string_match.string.clone(), None), - buffer_match: None, + match_start: None, icon_path: None, documentation: Some(CompletionDocumentation::MultiLineMarkdown( variable_value.into(), @@ -784,7 +784,7 @@ impl ConsoleQueryBarCompletionProvider { documentation: completion.detail.map(|detail| { CompletionDocumentation::MultiLineMarkdown(detail.into()) }), - buffer_match: None, + match_start: None, confirm: None, source: project::CompletionSource::Dap { sort_text }, insert_text_mode: None, diff --git a/crates/editor/src/code_completion_tests.rs b/crates/editor/src/code_completion_tests.rs index 10c708eaf6788a9bc7b34b377c5d7c9d2494531d..f412433776016895ab04680d31b0c164c9578e0a 100644 --- a/crates/editor/src/code_completion_tests.rs +++ b/crates/editor/src/code_completion_tests.rs @@ -305,7 +305,7 @@ impl CompletionBuilder { icon_path: None, insert_text_mode: None, confirm: None, - buffer_match: None, + match_start: None, } } } diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 054186341ea9ee19fe0e7b1259b21b53028f3b23..6b673ddecbd7607df5fd50aa4b3945a08faa817f 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -217,10 +217,8 @@ pub struct CompletionsMenu { pub is_incomplete: bool, pub buffer: Entity, pub completions: Rc>>, - /// Match candidates for completions that have `buffer_match = None` - match_candidates: Arc<[StringMatchCandidate]>, - /// Precomputed `buffer_match` for candidates that have it - precomputed_entries: Arc<[StringMatch]>, + /// String match candidate for each completion, grouped by `match_start`. + match_candidates: Arc<[(Option, Vec)]>, pub entries: Rc>>, pub selected_item: usize, filter_task: Task<()>, @@ -284,17 +282,10 @@ impl CompletionsMenu { let match_candidates = completions .iter() .enumerate() - .filter(|(_id, completion)| completion.buffer_match.is_none()) .map(|(id, completion)| StringMatchCandidate::new(id, completion.label.filter_text())) - .collect(); - let precomputed_entries = completions - .iter() - .enumerate() - .filter_map(|(id, completion)| { - let mut m = completion.buffer_match.clone()?; - m.candidate_id = id; - Some(m) - }) + .into_group_map_by(|candidate| completions[candidate.id].match_start) + .into_iter() + .map(|(k, v)| (k, v)) .collect(); let completions_menu = Self { @@ -308,7 +299,6 @@ impl CompletionsMenu { show_completion_documentation, completions: RefCell::new(completions).into(), match_candidates, - precomputed_entries, entries: Rc::new(RefCell::new(Box::new([]))), selected_item: 0, filter_task: Task::ready(()), @@ -343,7 +333,7 @@ impl CompletionsMenu { replace_range: selection.start.text_anchor..selection.end.text_anchor, new_text: choice.to_string(), label: CodeLabel::plain(choice.to_string(), None), - buffer_match: None, + match_start: None, icon_path: None, documentation: None, confirm: None, @@ -352,11 +342,14 @@ impl CompletionsMenu { }) .collect(); - let match_candidates = choices - .iter() - .enumerate() - .map(|(id, completion)| StringMatchCandidate::new(id, completion)) - .collect(); + let match_candidates = Arc::new([( + None, + choices + .iter() + .enumerate() + .map(|(id, completion)| StringMatchCandidate::new(id, completion)) + .collect(), + )]); let entries = choices .iter() .enumerate() @@ -376,7 +369,6 @@ impl CompletionsMenu { is_incomplete: false, buffer, completions: RefCell::new(completions).into(), - precomputed_entries: Arc::new([]), match_candidates, entries: RefCell::new(entries).into(), selected_item: 0, @@ -1006,60 +998,74 @@ impl CompletionsMenu { pub fn filter( &mut self, - query: Option>, + query: Arc, + query_end: text::Anchor, + buffer: &Entity, provider: Option>, window: &mut Window, cx: &mut Context, ) { self.cancel_filter.store(true, Ordering::Relaxed); - if let Some(query) = query { - self.cancel_filter = Arc::new(AtomicBool::new(false)); - let matches = self.do_async_filtering(query, cx); - let id = self.id; - self.filter_task = cx.spawn_in(window, async move |editor, cx| { - let matches = matches.await; - editor - .update_in(cx, |editor, window, cx| { - editor.with_completions_menu_matching_id(id, |this| { - if let Some(this) = this { - this.set_filter_results(matches, provider, window, cx); - } - }); - }) - .ok(); - }); - } else { - self.filter_task = Task::ready(()); - let matches = self.unfiltered_matches(); - self.set_filter_results(matches, provider, window, cx); - } + self.cancel_filter = Arc::new(AtomicBool::new(false)); + let matches = self.do_async_filtering(query, query_end, buffer, cx); + let id = self.id; + self.filter_task = cx.spawn_in(window, async move |editor, cx| { + let matches = matches.await; + editor + .update_in(cx, |editor, window, cx| { + editor.with_completions_menu_matching_id(id, |this| { + if let Some(this) = this { + this.set_filter_results(matches, provider, window, cx); + } + }); + }) + .ok(); + }); } pub fn do_async_filtering( &self, query: Arc, + query_end: text::Anchor, + buffer: &Entity, cx: &Context, ) -> Task> { - let matches_task = cx.background_spawn({ - let query = query.clone(); - let match_candidates = self.match_candidates.clone(); - let precomputed_entries = self.precomputed_entries.clone(); - let cancel_filter = self.cancel_filter.clone(); - let background_executor = cx.background_executor().clone(); - async move { - let mut matches = fuzzy::match_strings( - &match_candidates, - &query, - query.chars().any(|c| c.is_uppercase()), - false, - 1000, - &cancel_filter, - background_executor, - ) - .await; - matches.extend(precomputed_entries.iter().cloned()); - matches + let buffer_snapshot = buffer.read(cx).snapshot(); + let background_executor = cx.background_executor().clone(); + let match_candidates = self.match_candidates.clone(); + let cancel_filter = self.cancel_filter.clone(); + let default_query = query.clone(); + + let matches_task = cx.background_spawn(async move { + let queries_and_candidates = match_candidates + .iter() + .map(|(query_start, candidates)| { + let query_for_batch = match query_start { + Some(start) => { + Arc::new(buffer_snapshot.text_for_range(*start..query_end).collect()) + } + None => default_query.clone(), + }; + (query_for_batch, candidates) + }) + .collect_vec(); + + let mut results = vec![]; + for (query, match_candidates) in queries_and_candidates { + results.extend( + fuzzy::match_strings( + &match_candidates, + &query, + query.chars().any(|c| c.is_uppercase()), + false, + 1000, + &cancel_filter, + background_executor.clone(), + ) + .await, + ); } + results }); let completions = self.completions.clone(); @@ -1071,7 +1077,7 @@ impl CompletionsMenu { if sort_completions { matches = Self::sort_string_matches( matches, - Some(&query), + Some(&query), // used for non-snippets only snippet_sort_order, completions.borrow().as_ref(), ); @@ -1081,33 +1087,6 @@ impl CompletionsMenu { }) } - /// Like `do_async_filtering` but there is no filter query, so no need to spawn tasks. - pub fn unfiltered_matches(&self) -> Vec { - let mut matches = self - .match_candidates - .iter() - .enumerate() - .map(|(candidate_id, candidate)| StringMatch { - candidate_id, - score: Default::default(), - positions: Default::default(), - string: candidate.string.clone(), - }) - .chain(self.precomputed_entries.iter().cloned()) - .collect(); - - if self.sort_completions { - matches = Self::sort_string_matches( - matches, - None, - self.snippet_sort_order, - self.completions.borrow().as_ref(), - ); - } - - matches - } - pub fn set_filter_results( &mut self, matches: Vec, @@ -1150,7 +1129,8 @@ impl CompletionsMenu { .and_then(|c| c.to_lowercase().next()); if snippet_sort_order == SnippetSortOrder::None { - matches.retain(|string_match| !completions[string_match.candidate_id].is_snippet()); + matches + .retain(|string_match| !completions[string_match.candidate_id].is_snippet_kind()); } matches.sort_unstable_by_key(|string_match| { @@ -1168,7 +1148,7 @@ impl CompletionsMenu { let sort_score = Reverse(OrderedFloat(score)); // Snippets do their own first-letter matching logic elsewhere. - let is_snippet = completion.is_snippet(); + let is_snippet = completion.is_snippet_kind(); let query_start_doesnt_match_split_words = !is_snippet && query_start_lower .map(|query_char| { @@ -1189,6 +1169,7 @@ impl CompletionsMenu { SnippetSortOrder::None => Reverse(0), }; let sort_positions = string_match.positions.clone(); + // This exact matching won't work for multi-word snippets, but it's fine let sort_exact = Reverse(if Some(completion.label.filter_text()) == query { 1 } else { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f417fb9db9e9ac02fc632953edb9813fdd1e0a63..d5c5fa2c13e0e772dae325892389425ad31c6a02 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4937,7 +4937,6 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - dbg!(&text); let completions_source = self .context_menu .borrow() @@ -4977,9 +4976,6 @@ impl Editor { cx, ); } - _ => { - self.hide_context_menu(window, cx); - } } } @@ -5379,7 +5375,14 @@ impl Editor { if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() { if filter_completions { - menu.filter(query.clone(), provider.clone(), window, cx); + menu.filter( + query.clone().unwrap_or_default(), + buffer_position.text_anchor, + &buffer, + provider.clone(), + window, + cx, + ); } // When `is_incomplete` is false, no need to re-query completions when the current query // is a suffix of the initial query. @@ -5572,7 +5575,7 @@ impl Editor { replace_range: word_replace_range.clone(), new_text: word.clone(), label: CodeLabel::plain(word, None), - buffer_match: None, + match_start: None, icon_path: None, documentation: None, source: CompletionSource::BufferWord { @@ -5610,11 +5613,12 @@ impl Editor { ); let query = if filter_completions { query } else { None }; - let matches_task = if let Some(query) = query { - menu.do_async_filtering(query, cx) - } else { - Task::ready(menu.unfiltered_matches()) - }; + let matches_task = menu.do_async_filtering( + query.unwrap_or_default(), + buffer_position, + &buffer, + cx, + ); (menu, matches_task) }) else { return; @@ -22871,7 +22875,7 @@ fn snippet_completions( let buffer_windows = snippet_candidate_suffixes(&max_buffer_window) .take( sorted_snippet_candidates - .last() + .first() .map(|(_, _, word_count)| *word_count) .unwrap_or_default(), ) @@ -22901,7 +22905,7 @@ fn snippet_completions( let candidates = snippet_candidates_at_word_len .iter() - .map(|(snippet_ix, prefix, snippet_word_count)| prefix) + .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix) .enumerate() // index in `sorted_snippet_candidates` // First char must match .filter(|(_ix, prefix)| { @@ -22960,7 +22964,7 @@ fn snippet_completions( matches .iter() .filter_map(|(string_match, buffer_window_len)| { - let (snippet_index, matching_prefix) = + let (snippet_index, matching_prefix, _snippet_word_count) = sorted_snippet_candidates[string_match.candidate_id]; let snippet = &snippets[snippet_index]; let start = buffer_offset - buffer_window_len; @@ -23018,7 +23022,7 @@ fn snippet_completions( ), insert_text_mode: None, confirm: None, - buffer_match: Some(string_match.clone()), + match_start: Some(start), }) }), ); @@ -24281,19 +24285,22 @@ pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator + /// strings a snippet could match to. More precisely: returns an iterator over /// suffixes of `text` created by splitting at word boundaries (for a particular /// definition of "word"). +/// +/// Shorter suffixes are returned first. pub(crate) fn snippet_candidate_suffixes(text: &str) -> impl std::iter::Iterator { - let mut prev_index = 0; - let mut prev_codepoint: Option = None; + let mut prev_index = text.len(); + let mut prev_codepoint = None; let is_word_char = |c: char| c.is_alphanumeric() || c == '_'; text.char_indices() - .chain([(text.len(), '\0')]) + .rev() + .chain([(0, '\0')]) .filter_map(move |(index, codepoint)| { + let prev_index = std::mem::replace(&mut prev_index, index); let prev_codepoint = prev_codepoint.replace(codepoint)?; if is_word_char(prev_codepoint) && is_word_char(codepoint) { None } else { let chunk = &text[prev_index..]; // go to end of string - prev_index = index; Some(chunk) } }) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5092bfa38399f43ec95992f4b93e34aa9097da1c..4194c317324650883cf1cb49569aa4c6c5b84a90 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -11196,7 +11196,7 @@ async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) { let mut cx = EditorTestContext::new(cx).await; cx.update_editor(|editor, _, cx| { editor.project().unwrap().update(cx, |project, cx| { - project.snippets().update(cx, |snippets, cx| { + project.snippets().update(cx, |snippets, _cx| { let snippet = project::snippet_provider::Snippet { prefix: vec!["multi word".to_string()], body: "this is many words".to_string(), @@ -11207,34 +11207,33 @@ async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) { None, PathBuf::from("test_snippets.json"), vec![Arc::new(snippet)], - cx, ); }); }) }); - cx.set_state("ˇ"); - // cx.simulate_input("m"); - // cx.simulate_input("m "); - // cx.simulate_input("m w"); - // cx.simulate_input("aa m w"); - cx.simulate_input("aa m g"); // fails correctly - - cx.update_editor(|editor, _, _| { - let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow() - else { - panic!("expected completion menu"); - }; - assert!(context_menu.visible()); - let completions = context_menu.completions.borrow(); + for (input_to_simulate, should_match_snippet) in [ + ("m", true), + ("m ", true), + ("m w", true), + ("aa m w", true), + ("aa m g", false), + ] { + cx.set_state("ˇ"); + cx.simulate_input(input_to_simulate); // fails correctly + + cx.update_editor(|editor, _, _| { + let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow() + else { + assert!(!should_match_snippet); // no completions! don't even show the menu + return; + }; + assert!(context_menu.visible()); + let completions = context_menu.completions.borrow(); - assert!( - completions - .iter() - .any(|c| c.new_text == "this is many words"), - "Expected to find 'multi word' snippet in completions" - ); - }); + assert_eq!(!completions.is_empty(), should_match_snippet); + }); + } } #[gpui::test] @@ -17255,23 +17254,24 @@ fn test_split_words_for_snippet_prefix() { assert_eq!( split("this@is!@#$^many . symbols"), &[ - "this@is!@#$^many . symbols", - "@is!@#$^many . symbols", - "is!@#$^many . symbols", - "!@#$^many . symbols", - "@#$^many . symbols", - "#$^many . symbols", - "$^many . symbols", - "^many . symbols", - "many . symbols", - " . symbols", - " . symbols", - " . symbols", - ". symbols", + "symbols", " symbols", - "symbols" + ". symbols", + " . symbols", + " . symbols", + " . symbols", + "many . symbols", + "^many . symbols", + "$^many . symbols", + "#$^many . symbols", + "@#$^many . symbols", + "!@#$^many . symbols", + "is!@#$^many . symbols", + "@is!@#$^many . symbols", + "this@is!@#$^many . symbols", ], ); + assert_eq!(split("a.s"), &["s", ".s", "a.s"]); } #[gpui::test] diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 34f7408c868859acfffb5adacae482ab26045b6a..c87a337db8508c76a0f49e8376df9e71c3a66c96 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -60,7 +60,7 @@ impl EditorTestContext { .unwrap(); let language = project - .read_with(cx, |project, cx| { + .read_with(cx, |project, _cx| { project.languages().language_for_name("Plain Text") }) .await diff --git a/crates/inspector_ui/src/div_inspector.rs b/crates/inspector_ui/src/div_inspector.rs index 212e936f105a306e439e61d0f55bcbe55e33d3eb..7088a98727e7bd605f7aa6b8a373df7fe067189c 100644 --- a/crates/inspector_ui/src/div_inspector.rs +++ b/crates/inspector_ui/src/div_inspector.rs @@ -665,7 +665,7 @@ impl CompletionProvider for RustStyleCompletionProvider { replace_range: replace_range.clone(), new_text: format!(".{}()", method.name), label: CodeLabel::plain(method.name.to_string(), None), - buffer_match: None, + match_start: None, icon_path: None, documentation: method.documentation.map(|documentation| { CompletionDocumentation::MultiLineMarkdown(documentation.into()) diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index a3f8a644e1c1a79144c10645c6d38bb22812ea7b..ed433c59db5828e68a629d09c84da0e4d9a42a6a 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -2938,7 +2938,7 @@ impl CompletionProvider for KeyContextCompletionProvider { documentation: None, source: project::CompletionSource::Custom, icon_path: None, - buffer_match: None, + match_start: None, insert_text_mode: None, confirm: None, }) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index d527ef1e9b1e835d1eddfcf4ed466192897a20b6..4833536a17aa3cebce83fd59ee5a8e2fc4b10d59 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -9970,7 +9970,7 @@ impl LspStore { source: completion.source, documentation: None, label: CodeLabel::default(), - buffer_match: None, + match_start: None, insert_text_mode: None, icon_path: None, confirm: None, @@ -12554,7 +12554,7 @@ async fn populate_labels_for_completions( source: completion.source, icon_path: None, confirm: None, - buffer_match: None, + match_start: None, }); } None => { @@ -12569,7 +12569,7 @@ async fn populate_labels_for_completions( insert_text_mode: None, icon_path: None, confirm: None, - buffer_match: None, + match_start: None, }); } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9e29916b0e4664adaa47e020617cc602112ebda8..97cf44464ca75962e5b40e04522f56d47ffd9272 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -26,8 +26,7 @@ mod project_tests; mod environment; use buffer_diff::BufferDiff; use context_server_store::ContextServerStore; -pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent}; -use fuzzy::StringMatch; +pub use environment::ProjectEnvironmentEvent; use git::repository::get_git_committer; use git_store::{Repository, RepositoryId}; pub mod search_history; @@ -477,8 +476,10 @@ pub struct Completion { pub source: CompletionSource, /// A path to an icon for this completion that is shown in the menu. pub icon_path: Option, - /// String match against part of the buffer contents (typically the last word). - pub buffer_match: Option, + /// Text starting here and ending at the cursor will be used as the query for filtering this completion. + /// + /// If None, the start of the surrounding word is used. + pub match_start: Option, /// Whether to adjust indentation (the default) or not. pub insert_text_mode: Option, /// An optional callback to invoke when this completion is confirmed. @@ -5638,6 +5639,15 @@ impl Completion { } /// Whether this completion is a snippet. + pub fn is_snippet_kind(&self) -> bool { + matches!( + &self.source, + CompletionSource::Lsp { lsp_completion, .. } + if lsp_completion.kind == Some(CompletionItemKind::SNIPPET) + ) + } + + /// Whether this completion is a snippet or snippet-style LSP completion. pub fn is_snippet(&self) -> bool { self.source // `lsp::CompletionListItemDefaults` has `insert_text_format` field diff --git a/crates/snippet_provider/src/lib.rs b/crates/snippet_provider/src/lib.rs index 2af740483157c69e651246d000600c5e2ef88aa6..64711cfc3a7247f6250b65e4f7325dd0bfdc1dcb 100644 --- a/crates/snippet_provider/src/lib.rs +++ b/crates/snippet_provider/src/lib.rs @@ -241,7 +241,6 @@ impl SnippetProvider { language: SnippetKind, path: PathBuf, snippet: Vec>, - cx: &mut Context, ) { self.snippets .entry(language)