acc.add_resolution(ctx, name.to_string(), &res)
});
- if ctx.config.enable_experimental_completions {
+ if !ctx.config.disable_fuzzy_autoimports && ctx.config.resolve_additional_edits_lazily() {
fuzzy_completion(acc, ctx).unwrap_or_default()
}
}
//
// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the identifiers only
// (i.e. in `HashMap` in the `std::collections::HashMap` path), also not in the module indentifiers.
+// It also avoids searching for any imports for inputs with their length less that 3 symbols.
//
// .Merge Behaviour
//
//
// .LSP and performance implications
//
-// LSP 3.16 provides the way to defer the computation of some completion data, including the import edits for this feature.
-// If the LSP client supports the `additionalTextEdits` (case sensitive) resolve client capability, rust-analyzer computes
-// the completion edits only when a corresponding completion item is selected.
+// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
+// (case sensitive) resolve client capability in its client capabilities.
+// This way the server is able to defer the costly computations, doing them for a selected completion item only.
// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
-// which might be slow.
+// which might be slow ergo the feature is automatically disabled.
//
// .Feature toggle
//
-// The feature can be turned off in the settings with the `rust-analyzer.completion.enableExperimental` flag.
+// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.disableFuzzyAutoimports` flag.
fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
let _p = profile::span("fuzzy_completion");
+ let potential_import_name = ctx.token.to_string();
+
+ if potential_import_name.len() < 3 {
+ return None;
+ }
+
let current_module = ctx.scope.module()?;
let anchor = ctx.name_ref_syntax.as_ref()?;
let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
- let potential_import_name = ctx.token.to_string();
-
- let possible_imports = imports_locator::find_similar_imports(
- &ctx.sema,
- ctx.krate?,
- &potential_import_name,
- 50,
- true,
- )
- .filter_map(|import_candidate| {
- Some(match import_candidate {
- Either::Left(module_def) => {
- (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
- }
- Either::Right(macro_def) => {
- (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
- }
- })
- })
- .filter(|(mod_path, _)| mod_path.len() > 1)
- .take(20)
- .filter_map(|(import_path, definition)| {
- render_resolution_with_import(
- RenderContext::new(ctx),
- ImportEdit {
- import_path: import_path.clone(),
- import_scope: import_scope.clone(),
- merge_behaviour: ctx.config.merge,
- },
- &definition,
- )
- });
+ let possible_imports =
+ imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, true)
+ .filter_map(|import_candidate| {
+ Some(match import_candidate {
+ Either::Left(module_def) => (
+ current_module.find_use_path(ctx.db, module_def)?,
+ ScopeDef::ModuleDef(module_def),
+ ),
+ Either::Right(macro_def) => (
+ current_module.find_use_path(ctx.db, macro_def)?,
+ ScopeDef::MacroDef(macro_def),
+ ),
+ })
+ })
+ .filter(|(mod_path, _)| mod_path.len() > 1)
+ .filter_map(|(import_path, definition)| {
+ render_resolution_with_import(
+ RenderContext::new(ctx),
+ ImportEdit {
+ import_path: import_path.clone(),
+ import_scope: import_scope.clone(),
+ merge_behaviour: ctx.config.merge,
+ },
+ &definition,
+ )
+ });
acc.add_all(possible_imports);
Some(())
#[test]
fn function_fuzzy_completion() {
- check_edit(
+ let mut completion_config = CompletionConfig::default();
+ completion_config
+ .active_resolve_capabilities
+ .insert(crate::CompletionResolveCapability::AdditionalTextEdits);
+
+ check_edit_with_config(
+ completion_config,
"stdin",
r#"
//- /lib.rs crate:dep
#[test]
fn macro_fuzzy_completion() {
- check_edit(
+ let mut completion_config = CompletionConfig::default();
+ completion_config
+ .active_resolve_capabilities
+ .insert(crate::CompletionResolveCapability::AdditionalTextEdits);
+
+ check_edit_with_config(
+ completion_config,
"macro_with_curlies!",
r#"
//- /lib.rs crate:dep
#[test]
fn struct_fuzzy_completion() {
- check_edit(
- "ThirdStruct",
- r#"
-//- /lib.rs crate:dep
-pub struct FirstStruct;
-pub mod some_module {
- pub struct SecondStruct;
- pub struct ThirdStruct;
-}
-
-//- /main.rs crate:main deps:dep
-use dep::{FirstStruct, some_module::SecondStruct};
-
-fn main() {
- this<|>
-}
-"#,
- r#"
-use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
-
-fn main() {
- ThirdStruct
-}
-"#,
- );
- }
-
- /// LSP protocol supports separate completion resolve requests to do the heavy computations there.
- /// This test checks that for a certain resolve capatilities no such operations (autoimport) are done.
- #[test]
- fn no_fuzzy_completions_applied_for_certain_resolve_capability() {
let mut completion_config = CompletionConfig::default();
completion_config
.active_resolve_capabilities
//- /lib.rs crate:dep
pub struct FirstStruct;
pub mod some_module {
-pub struct SecondStruct;
-pub struct ThirdStruct;
+ pub struct SecondStruct;
+ pub struct ThirdStruct;
}
//- /main.rs crate:main deps:dep
use dep::{FirstStruct, some_module::SecondStruct};
fn main() {
-this<|>
+ this<|>
}
"#,
r#"
-use dep::{FirstStruct, some_module::SecondStruct};
+use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
fn main() {
-ThirdStruct
+ ThirdStruct
}
"#,
);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompletionConfig {
pub enable_postfix_completions: bool,
- pub enable_experimental_completions: bool,
+ pub disable_fuzzy_autoimports: bool,
pub add_call_parenthesis: bool,
pub add_call_argument_snippets: bool,
pub snippet_cap: Option<SnippetCap>,
fn default() -> Self {
CompletionConfig {
enable_postfix_completions: true,
- enable_experimental_completions: true,
+ disable_fuzzy_autoimports: false,
add_call_parenthesis: true,
add_call_argument_snippets: true,
snippet_cap: Some(SnippetCap { _private: () }),
score: None,
ref_match: None,
import_to_add: None,
- resolve_import_lazily: false,
}
}
source_range: TextRange,
completion_kind: CompletionKind,
import_to_add: Option<ImportEdit>,
- resolve_import_lazily: bool,
label: String,
insert_text: Option<String>,
insert_text_format: InsertTextFormat,
}
}
- let mut text_edit = match self.text_edit {
+ let text_edit = match self.text_edit {
Some(it) => it,
None => {
TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone()))
}
};
- let import_to_add = if self.resolve_import_lazily {
- self.import_to_add
- } else {
- match apply_import_eagerly(self.import_to_add.as_ref(), &mut text_edit) {
- Ok(()) => self.import_to_add,
- Err(()) => {
- log::error!("Failed to apply eager import edit: original edit and import edit intersect");
- None
- }
- }
- };
-
CompletionItem {
source_range: self.source_range,
label,
trigger_call_info: self.trigger_call_info.unwrap_or(false),
score: self.score,
ref_match: self.ref_match,
- import_to_add,
+ import_to_add: self.import_to_add,
}
}
pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
self.trigger_call_info = Some(true);
self
}
- pub(crate) fn add_import(
- mut self,
- import_to_add: Option<ImportEdit>,
- resolve_import_lazily: bool,
- ) -> Builder {
+ pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder {
self.import_to_add = import_to_add;
- self.resolve_import_lazily = resolve_import_lazily;
self
}
pub(crate) fn set_ref_match(
}
}
-fn apply_import_eagerly(
- import_to_add: Option<&ImportEdit>,
- original_edit: &mut TextEdit,
-) -> Result<(), ()> {
- match import_to_add.and_then(|import_edit| import_edit.to_text_edit()) {
- Some(import_edit) => original_edit.union(import_edit).map_err(|_| ()),
- None => Ok(()),
- }
-}
-
impl<'a> Into<CompletionItem> for Builder {
fn into(self) -> CompletionItem {
self.build()
// }
// ```
//
-// And experimental completions, enabled with the `rust-analyzer.completion.enableExperimental` setting.
+// And experimental completions, enabled with the `rust-analyzer.completion.disableFuzzyAutoimports` setting.
// This flag enables or disables:
//
// - Auto import: additional completion options with automatic `use` import and options from all project importable items, matched for the input
local_name,
)
.kind(CompletionItemKind::UnresolvedReference)
- .add_import(
- import_to_add,
- self.ctx.completion.config.resolve_additional_edits_lazily(),
- )
+ .add_import(import_to_add)
.build();
return Some(item);
}
let item = item
.kind(kind)
- .add_import(import_to_add, self.ctx.completion.config.resolve_additional_edits_lazily())
+ .add_import(import_to_add)
.set_documentation(docs)
.set_ref_match(ref_match)
.build();
insert: "m",
kind: Module,
},
- CompletionItem {
- label: "m::Spam",
- source_range: 75..76,
- text_edit: TextEdit {
- indels: [
- Indel {
- insert: "use m::Spam;",
- delete: 0..0,
- },
- Indel {
- insert: "\n\n",
- delete: 0..0,
- },
- Indel {
- insert: "Spam",
- delete: 75..76,
- },
- ],
- },
- kind: Enum,
- lookup: "Spam",
- },
CompletionItem {
label: "m::Spam::Foo",
source_range: 75..76,
.kind(CompletionItemKind::EnumVariant)
.set_documentation(self.variant.docs(self.ctx.db()))
.set_deprecated(self.ctx.is_deprecated(self.variant))
- .add_import(import_to_add, self.ctx.completion.config.resolve_additional_edits_lazily())
+ .add_import(import_to_add)
.detail(self.detail());
if self.variant_kind == StructKind::Tuple {
.set_deprecated(self.ctx.is_deprecated(self.func))
.detail(self.detail())
.add_call_parens(self.ctx.completion, self.name, params)
- .add_import(import_to_add, self.ctx.completion.config.resolve_additional_edits_lazily())
+ .add_import(import_to_add)
.build()
}
.kind(CompletionItemKind::Macro)
.set_documentation(self.docs.clone())
.set_deprecated(self.ctx.is_deprecated(self.macro_))
- .add_import(
- import_to_add,
- self.ctx.completion.config.resolve_additional_edits_lazily(),
- )
+ .add_import(import_to_add)
.detail(self.detail());
let needs_bang = self.needs_bang();
.collect_tuple()
.unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
let mut actual = db.file_text(position.file_id).to_string();
- completion.text_edit().apply(&mut actual);
+
+ let mut combined_edit = completion.text_edit().to_owned();
+ if let Some(import_text_edit) = completion.import_to_add().and_then(|edit| edit.to_text_edit())
+ {
+ combined_edit.union(import_text_edit).expect(
+ "Failed to apply completion resolve changes: change ranges overlap, but should not",
+ )
+ }
+
+ combined_edit.apply(&mut actual);
assert_eq_text!(&ra_fixture_after, &actual)
}
sema: &Semantics<'a, RootDatabase>,
krate: Crate,
name_to_import: &str,
- limit: usize,
ignore_modules: bool,
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
let _p = profile::span("find_similar_imports");
- let mut external_query = import_map::Query::new(name_to_import).limit(limit);
+ let mut external_query = import_map::Query::new(name_to_import);
if ignore_modules {
external_query = external_query.exclude_import_kind(import_map::ImportKind::Module);
}
- find_imports(
- sema,
- krate,
- {
- let mut local_query = symbol_index::Query::new(name_to_import.to_string());
- local_query.limit(limit);
- local_query
- },
- external_query,
- )
+ find_imports(sema, krate, symbol_index::Query::new(name_to_import.to_string()), external_query)
}
fn find_imports<'a>(
},
completion: CompletionConfig {
enable_postfix_completions: true,
- enable_experimental_completions: true,
+ disable_fuzzy_autoimports: false,
add_call_parenthesis: true,
add_call_argument_snippets: true,
..CompletionConfig::default()
};
self.completion.enable_postfix_completions = data.completion_postfix_enable;
- self.completion.enable_experimental_completions = data.completion_enableExperimental;
+ self.completion.disable_fuzzy_autoimports = data.completion_disableFuzzyAutoimports;
self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
self.completion.merge = self.assist.insert_use.merge;
completion_addCallArgumentSnippets: bool = true,
completion_addCallParenthesis: bool = true,
completion_postfix_enable: bool = true,
- completion_enableExperimental: bool = true,
+ completion_disableFuzzyAutoimports: bool = false,
diagnostics_enable: bool = true,
diagnostics_enableExperimental: bool = true,
"default": true,
"markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc."
},
- "rust-analyzer.completion.enableExperimental": {
+ "rust-analyzer.completion.disableFuzzyAutoimports": {
"type": "boolean",
- "default": true,
- "markdownDescription": "Display additional completions with potential false positives and performance issues"
+ "default": false,
+ "markdownDescription": "Turns off extra completion suggestions that might be too noisy or slow"
},
"rust-analyzer.callInfo.full": {
"type": "boolean",