diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ea03d027c4..69383ceb82 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -13812,80 +13812,130 @@ fn snippet_completions( buffer: &Model, buffer_position: text::Anchor, cx: &mut AppContext, -) -> Vec { +) -> Task>> { let language = buffer.read(cx).language_at(buffer_position); let language_name = language.as_ref().map(|language| language.lsp_id()); let snippet_store = project.snippets().read(cx); let snippets = snippet_store.snippets_for(language_name, cx); if snippets.is_empty() { - return vec![]; + return Task::ready(Ok(vec![])); } let snapshot = buffer.read(cx).text_snapshot(); - let chars = snapshot.reversed_chars_for_range(text::Anchor::MIN..buffer_position); + let chars: String = snapshot + .reversed_chars_for_range(text::Anchor::MIN..buffer_position) + .collect(); let scope = language.map(|language| language.default_scope()); - let classifier = CharClassifier::new(scope).for_completion(true); - let mut last_word = chars - .take_while(|c| classifier.is_word(*c)) - .collect::(); - last_word = last_word.chars().rev().collect(); - let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot); - let to_lsp = |point: &text::Anchor| { - let end = text::ToPointUtf16::to_point_utf16(point, &snapshot); - point_to_lsp(end) - }; - let lsp_end = to_lsp(&buffer_position); - snippets - .into_iter() - .filter_map(|snippet| { - let matching_prefix = snippet - .prefix - .iter() - .find(|prefix| prefix.starts_with(&last_word))?; - let start = as_offset - last_word.len(); - let start = snapshot.anchor_before(start); - let range = start..buffer_position; - let lsp_start = to_lsp(&start); - let lsp_range = lsp::Range { - start: lsp_start, - end: lsp_end, - }; - Some(Completion { - old_range: range, - new_text: snippet.body.clone(), - label: CodeLabel { - text: matching_prefix.clone(), - runs: vec![], - filter_range: 0..matching_prefix.len(), - }, - server_id: LanguageServerId(usize::MAX), - documentation: snippet.description.clone().map(Documentation::SingleLine), - lsp_completion: lsp::CompletionItem { - label: snippet.prefix.first().unwrap().clone(), - kind: Some(CompletionItemKind::SNIPPET), - label_details: snippet.description.as_ref().map(|description| { - lsp::CompletionItemLabelDetails { - detail: Some(description.clone()), - description: None, - } - }), - insert_text_format: Some(InsertTextFormat::SNIPPET), - text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace( - lsp::InsertReplaceEdit { - new_text: snippet.body.clone(), - insert: lsp_range, - replace: lsp_range, - }, - )), - filter_text: Some(snippet.body.clone()), - sort_text: Some(char::MAX.to_string()), - ..Default::default() - }, - confirm: None, + let executor = cx.background_executor().clone(); + + cx.background_executor().spawn(async move { + let classifier = CharClassifier::new(scope).for_completion(true); + let mut last_word = chars + .chars() + .take_while(|c| classifier.is_word(*c)) + .collect::(); + last_word = last_word.chars().rev().collect(); + let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot); + let to_lsp = |point: &text::Anchor| { + let end = text::ToPointUtf16::to_point_utf16(point, &snapshot); + point_to_lsp(end) + }; + let lsp_end = to_lsp(&buffer_position); + + let candidates = snippets + .iter() + .enumerate() + .flat_map(|(ix, snippet)| { + snippet + .prefix + .iter() + .map(move |prefix| StringMatchCandidate::new(ix, prefix.clone())) }) - }) - .collect() + .collect::>(); + + let mut matches = fuzzy::match_strings( + &candidates, + &last_word, + last_word.chars().any(|c| c.is_uppercase()), + 100, + &Default::default(), + executor, + ) + .await; + + // Remove all candidates where the query's start does not match the start of any word in the candidate + if let Some(query_start) = last_word.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) + }) + }); + } + + let matched_strings = matches + .into_iter() + .map(|m| m.string) + .collect::>(); + + let result: Vec = snippets + .into_iter() + .filter_map(|snippet| { + let matching_prefix = snippet + .prefix + .iter() + .find(|prefix| matched_strings.contains(*prefix))?; + let start = as_offset - last_word.len(); + let start = snapshot.anchor_before(start); + let range = start..buffer_position; + let lsp_start = to_lsp(&start); + let lsp_range = lsp::Range { + start: lsp_start, + end: lsp_end, + }; + Some(Completion { + old_range: range, + new_text: snippet.body.clone(), + label: CodeLabel { + text: matching_prefix.clone(), + runs: vec![], + filter_range: 0..matching_prefix.len(), + }, + server_id: LanguageServerId(usize::MAX), + documentation: snippet.description.clone().map(Documentation::SingleLine), + lsp_completion: lsp::CompletionItem { + label: snippet.prefix.first().unwrap().clone(), + kind: Some(CompletionItemKind::SNIPPET), + label_details: snippet.description.as_ref().map(|description| { + lsp::CompletionItemLabelDetails { + detail: Some(description.clone()), + description: None, + } + }), + insert_text_format: Some(InsertTextFormat::SNIPPET), + text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace( + lsp::InsertReplaceEdit { + new_text: snippet.body.clone(), + insert: lsp_range, + replace: lsp_range, + }, + )), + filter_text: Some(snippet.body.clone()), + sort_text: Some(char::MAX.to_string()), + ..Default::default() + }, + confirm: None, + }) + }) + .collect(); + + Ok(result) + }) } impl CompletionProvider for Model { @@ -13901,8 +13951,8 @@ impl CompletionProvider for Model { let project_completions = project.completions(buffer, buffer_position, options, cx); cx.background_executor().spawn(async move { let mut completions = project_completions.await?; - //let snippets = snippets.into_iter().; - completions.extend(snippets); + let snippets_completions = snippets.await?; + completions.extend(snippets_completions); Ok(completions) }) })