1 //! See [`import_on_the_fly`].
2 use hir::{ItemInNs, ModuleDef};
4 import_assets::{ImportAssets, LocatedImport},
5 insert_use::ImportScope,
7 use itertools::Itertools;
10 AstNode, SyntaxNode, T,
15 CompletionContext, DotAccess, PathCompletionCtx, PathKind, PatternContext, Qualified,
18 render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
21 use super::Completions;
23 // Feature: Completion With Autoimport
25 // When completing names in the current scope, proposes additional imports from other modules or crates,
26 // if they can be qualified in the scope, and their name contains all symbols from the completion input.
28 // To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
29 // If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the containing is checked case-insensitively.
35 // # pub mod std { pub mod marker { pub struct PhantomData { } } }
39 // use std::marker::PhantomData;
44 // # pub mod std { pub mod marker { pub struct PhantomData { } } }
47 // Also completes associated items, that require trait imports.
48 // If any unresolved and/or partially-qualified path precedes the input, it will be taken into account.
49 // Currently, only the imports with their import path ending with the whole qualifier will be proposed
50 // (no fuzzy matching for qualifier).
58 // pub const TEST_ASSOC: usize = 3;
64 // bar::Item::TEST_A$0
76 // pub const TEST_ASSOC: usize = 3;
82 // bar::Item::TEST_ASSOC
86 // NOTE: currently, if an assoc item comes from a trait that's not currently imported, and it also has an unresolved and/or partially-qualified path,
87 // no imports will be proposed.
89 // .Fuzzy search details
91 // To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
92 // (i.e. in `HashMap` in the `std::collections::HashMap` path).
93 // For the same reasons, avoids searching for any path imports for inputs with their length less than 2 symbols
94 // (but shows all associated items for any input length).
96 // .Import configuration
98 // It is possible to configure how use-trees are merged with the `imports.granularity.group` setting.
99 // Mimics the corresponding behavior of the `Auto Import` feature.
101 // .LSP and performance implications
103 // The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
104 // (case-sensitive) resolve client capability in its client capabilities.
105 // This way the server is able to defer the costly computations, doing them for a selected completion item only.
106 // For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
107 // which might be slow ergo the feature is automatically disabled.
111 // The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.autoimport.enable` flag.
112 // Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corresponding
113 // capability enabled.
114 pub(crate) fn import_on_the_fly_path(
115 acc: &mut Completions,
116 ctx: &CompletionContext<'_>,
117 path_ctx: &PathCompletionCtx,
119 if !ctx.config.enable_imports_on_the_fly {
122 let qualified = match path_ctx {
125 PathKind::Expr { .. }
126 | PathKind::Type { .. }
127 | PathKind::Attr { .. }
128 | PathKind::Derive { .. }
129 | PathKind::Item { .. }
130 | PathKind::Pat { .. },
136 let potential_import_name = import_name(ctx);
137 let qualifier = match qualified {
138 Qualified::With { path, .. } => Some(path.clone()),
141 let import_assets = import_assets_for_path(ctx, &potential_import_name, qualifier.clone())?;
148 qualifier.map(|it| it.syntax().clone()).or_else(|| ctx.original_token.parent())?,
149 potential_import_name,
153 pub(crate) fn import_on_the_fly_pat(
154 acc: &mut Completions,
155 ctx: &CompletionContext<'_>,
156 pattern_ctx: &PatternContext,
158 if !ctx.config.enable_imports_on_the_fly {
161 if let PatternContext { record_pat: Some(_), .. } = pattern_ctx {
165 let potential_import_name = import_name(ctx);
166 let import_assets = import_assets_for_path(ctx, &potential_import_name, None)?;
168 import_on_the_fly_pat_(
173 ctx.original_token.parent()?,
174 potential_import_name,
178 pub(crate) fn import_on_the_fly_dot(
179 acc: &mut Completions,
180 ctx: &CompletionContext<'_>,
181 dot_access: &DotAccess,
183 if !ctx.config.enable_imports_on_the_fly {
186 let receiver = dot_access.receiver.as_ref()?;
187 let ty = dot_access.receiver_ty.as_ref()?;
188 let potential_import_name = import_name(ctx);
189 let import_assets = ImportAssets::for_fuzzy_method_call(
192 potential_import_name.clone(),
193 receiver.syntax().clone(),
196 import_on_the_fly_method(
201 receiver.syntax().clone(),
202 potential_import_name,
206 fn import_on_the_fly(
207 acc: &mut Completions,
208 ctx: &CompletionContext<'_>,
209 path_ctx @ PathCompletionCtx { kind, .. }: &PathCompletionCtx,
210 import_assets: ImportAssets,
211 position: SyntaxNode,
212 potential_import_name: String,
214 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.clone());
216 if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
220 let ns_filter = |import: &LocatedImport| {
221 match (kind, import.original_item) {
222 // Aren't handled in flyimport
223 (PathKind::Vis { .. } | PathKind::Use, _) => false,
224 // modules are always fair game
225 (_, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
226 // and so are macros(except for attributes)
228 PathKind::Expr { .. }
229 | PathKind::Type { .. }
230 | PathKind::Item { .. }
231 | PathKind::Pat { .. },
232 ItemInNs::Macros(mac),
233 ) => mac.is_fn_like(ctx.db),
234 (PathKind::Item { .. }, ..) => false,
236 (PathKind::Expr { .. }, ItemInNs::Types(_) | ItemInNs::Values(_)) => true,
238 (PathKind::Pat { .. }, ItemInNs::Types(_)) => true,
239 (PathKind::Pat { .. }, ItemInNs::Values(def)) => {
240 matches!(def, hir::ModuleDef::Const(_))
243 (PathKind::Type { location }, ItemInNs::Types(ty)) => {
244 if matches!(location, TypeLocation::TypeBound) {
245 matches!(ty, ModuleDef::Trait(_))
250 (PathKind::Type { .. }, ItemInNs::Values(_)) => false,
252 (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(ctx.db),
253 (PathKind::Attr { .. }, _) => false,
255 (PathKind::Derive { existing_derives }, ItemInNs::Macros(mac)) => {
256 mac.is_derive(ctx.db) && !existing_derives.contains(&mac)
258 (PathKind::Derive { .. }, _) => false,
261 let user_input_lowercased = potential_import_name.to_lowercase();
267 ctx.config.insert_use.prefix_kind,
268 ctx.config.prefer_no_std,
273 !ctx.is_item_hidden(&import.item_to_import)
274 && !ctx.is_item_hidden(&import.original_item)
276 .sorted_by_key(|located_import| {
277 compute_fuzzy_completion_order_key(
278 &located_import.import_path,
279 &user_input_lowercased,
282 .filter_map(|import| {
283 render_resolution_with_import(RenderContext::new(ctx), path_ctx, import)
285 .map(|builder| builder.build()),
290 fn import_on_the_fly_pat_(
291 acc: &mut Completions,
292 ctx: &CompletionContext<'_>,
293 pattern_ctx: &PatternContext,
294 import_assets: ImportAssets,
295 position: SyntaxNode,
296 potential_import_name: String,
298 let _p = profile::span("import_on_the_fly_pat").detail(|| potential_import_name.clone());
300 if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
304 let ns_filter = |import: &LocatedImport| match import.original_item {
305 ItemInNs::Macros(mac) => mac.is_fn_like(ctx.db),
306 ItemInNs::Types(_) => true,
307 ItemInNs::Values(def) => matches!(def, hir::ModuleDef::Const(_)),
309 let user_input_lowercased = potential_import_name.to_lowercase();
315 ctx.config.insert_use.prefix_kind,
316 ctx.config.prefer_no_std,
321 !ctx.is_item_hidden(&import.item_to_import)
322 && !ctx.is_item_hidden(&import.original_item)
324 .sorted_by_key(|located_import| {
325 compute_fuzzy_completion_order_key(
326 &located_import.import_path,
327 &user_input_lowercased,
330 .filter_map(|import| {
331 render_resolution_with_import_pat(RenderContext::new(ctx), pattern_ctx, import)
333 .map(|builder| builder.build()),
338 fn import_on_the_fly_method(
339 acc: &mut Completions,
340 ctx: &CompletionContext<'_>,
341 dot_access: &DotAccess,
342 import_assets: ImportAssets,
343 position: SyntaxNode,
344 potential_import_name: String,
346 let _p = profile::span("import_on_the_fly_method").detail(|| potential_import_name.clone());
348 if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
352 let user_input_lowercased = potential_import_name.to_lowercase();
355 .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std)
358 !ctx.is_item_hidden(&import.item_to_import)
359 && !ctx.is_item_hidden(&import.original_item)
361 .sorted_by_key(|located_import| {
362 compute_fuzzy_completion_order_key(&located_import.import_path, &user_input_lowercased)
364 .for_each(|import| match import.original_item {
365 ItemInNs::Values(hir::ModuleDef::Function(f)) => {
366 acc.add_method_with_import(ctx, dot_access, f, import);
373 fn import_name(ctx: &CompletionContext<'_>) -> String {
374 let token_kind = ctx.token.kind();
375 if matches!(token_kind, T![.] | T![::]) {
378 ctx.token.to_string()
382 fn import_assets_for_path(
383 ctx: &CompletionContext<'_>,
384 potential_import_name: &str,
385 qualifier: Option<ast::Path>,
386 ) -> Option<ImportAssets> {
387 let fuzzy_name_length = potential_import_name.len();
388 let mut assets_for_path = ImportAssets::for_fuzzy_path(
391 potential_import_name.to_owned(),
395 if fuzzy_name_length < 3 {
396 cov_mark::hit!(flyimport_exact_on_short_path);
397 assets_for_path.path_fuzzy_name_to_exact(false);
399 Some(assets_for_path)
402 fn compute_fuzzy_completion_order_key(
403 proposed_mod_path: &hir::ModPath,
404 user_input_lowercased: &str,
406 cov_mark::hit!(certain_fuzzy_order_test);
407 let import_name = match proposed_mod_path.segments().last() {
408 Some(name) => name.to_smol_str().to_lowercase(),
409 None => return usize::MAX,
411 match import_name.match_indices(user_input_lowercased).next() {
412 Some((first_matching_index, _)) => first_matching_index,