Detailed changes
@@ -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();
@@ -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(),
@@ -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,
@@ -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,
@@ -305,7 +305,7 @@ impl CompletionBuilder {
icon_path: None,
insert_text_mode: None,
confirm: None,
- buffer_match: None,
+ match_start: None,
}
}
}
@@ -217,10 +217,8 @@ pub struct CompletionsMenu {
pub is_incomplete: bool,
pub buffer: Entity<Buffer>,
pub completions: Rc<RefCell<Box<[Completion]>>>,
- /// 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<text::Anchor>, Vec<StringMatchCandidate>)]>,
pub entries: Rc<RefCell<Box<[StringMatch]>>>,
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<Arc<String>>,
+ query: Arc<String>,
+ query_end: text::Anchor,
+ buffer: &Entity<Buffer>,
provider: Option<Rc<dyn CompletionProvider>>,
window: &mut Window,
cx: &mut Context<Editor>,
) {
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<String>,
+ query_end: text::Anchor,
+ buffer: &Entity<Buffer>,
cx: &Context<Editor>,
) -> Task<Vec<StringMatch>> {
- 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<StringMatch> {
- 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<StringMatch>,
@@ -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 {
@@ -4937,7 +4937,6 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
- 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<Item = &str> +
/// 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<Item = &str> {
- let mut prev_index = 0;
- let mut prev_codepoint: Option<char> = 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)
}
})
@@ -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]
@@ -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
@@ -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())
@@ -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,
})
@@ -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,
});
}
}
@@ -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<SharedString>,
- /// String match against part of the buffer contents (typically the last word).
- pub buffer_match: Option<StringMatch>,
+ /// 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<text::Anchor>,
/// Whether to adjust indentation (the default) or not.
pub insert_text_mode: Option<InsertTextMode>,
/// 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
@@ -241,7 +241,6 @@ impl SnippetProvider {
language: SnippetKind,
path: PathBuf,
snippet: Vec<Arc<Snippet>>,
- cx: &mut Context<Self>,
) {
self.snippets
.entry(language)