]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/flyimport.rs
Merge #11393
[rust.git] / crates / ide_completion / src / completions / flyimport.rs
1 //! See [`import_on_the_fly`].
2 use hir::ItemInNs;
3 use ide_db::helpers::{
4     import_assets::{ImportAssets, ImportCandidate, LocatedImport},
5     insert_use::ImportScope,
6 };
7 use itertools::Itertools;
8 use syntax::{AstNode, SyntaxNode, T};
9
10 use crate::{
11     context::{CompletionContext, PathKind},
12     render::{render_resolution_with_import, RenderContext},
13     ImportEdit,
14 };
15
16 use super::Completions;
17
18 // Feature: Completion With Autoimport
19 //
20 // When completing names in the current scope, proposes additional imports from other modules or crates,
21 // if they can be qualified in the scope, and their name contains all symbols from the completion input.
22 //
23 // To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
24 // If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the containing is checked case-insensitively.
25 //
26 // ```
27 // fn main() {
28 //     pda$0
29 // }
30 // # pub mod std { pub mod marker { pub struct PhantomData { } } }
31 // ```
32 // ->
33 // ```
34 // use std::marker::PhantomData;
35 //
36 // fn main() {
37 //     PhantomData
38 // }
39 // # pub mod std { pub mod marker { pub struct PhantomData { } } }
40 // ```
41 //
42 // Also completes associated items, that require trait imports.
43 // If any unresolved and/or partially-qualified path precedes the input, it will be taken into account.
44 // Currently, only the imports with their import path ending with the whole qualifier will be proposed
45 // (no fuzzy matching for qualifier).
46 //
47 // ```
48 // mod foo {
49 //     pub mod bar {
50 //         pub struct Item;
51 //
52 //         impl Item {
53 //             pub const TEST_ASSOC: usize = 3;
54 //         }
55 //     }
56 // }
57 //
58 // fn main() {
59 //     bar::Item::TEST_A$0
60 // }
61 // ```
62 // ->
63 // ```
64 // use foo::bar;
65 //
66 // mod foo {
67 //     pub mod bar {
68 //         pub struct Item;
69 //
70 //         impl Item {
71 //             pub const TEST_ASSOC: usize = 3;
72 //         }
73 //     }
74 // }
75 //
76 // fn main() {
77 //     bar::Item::TEST_ASSOC
78 // }
79 // ```
80 //
81 // 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,
82 // no imports will be proposed.
83 //
84 // .Fuzzy search details
85 //
86 // To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
87 // (i.e. in `HashMap` in the `std::collections::HashMap` path).
88 // For the same reasons, avoids searching for any path imports for inputs with their length less than 2 symbols
89 // (but shows all associated items for any input length).
90 //
91 // .Import configuration
92 //
93 // It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
94 // Mimics the corresponding behavior of the `Auto Import` feature.
95 //
96 // .LSP and performance implications
97 //
98 // The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
99 // (case-sensitive) resolve client capability in its client capabilities.
100 // This way the server is able to defer the costly computations, doing them for a selected completion item only.
101 // For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
102 // which might be slow ergo the feature is automatically disabled.
103 //
104 // .Feature toggle
105 //
106 // The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.autoimport.enable` flag.
107 // Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corresponding
108 // capability enabled.
109 pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
110     if !ctx.config.enable_imports_on_the_fly {
111         return None;
112     }
113     if matches!(ctx.path_kind(), Some(PathKind::Vis { .. } | PathKind::Use))
114         || ctx.is_path_disallowed()
115         || ctx.expects_item()
116         || ctx.expects_assoc_item()
117         || ctx.expects_variant()
118     {
119         return None;
120     }
121     // FIXME: This should be encoded in a different way
122     if ctx.pattern_ctx.is_none() && ctx.path_context.is_none() && !ctx.has_dot_receiver() {
123         // completion inside `ast::Name` of a item declaration
124         return None;
125     }
126     let potential_import_name = {
127         let token_kind = ctx.token.kind();
128         if matches!(token_kind, T![.] | T![::]) {
129             String::new()
130         } else {
131             ctx.token.to_string()
132         }
133     };
134
135     let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.clone());
136
137     let user_input_lowercased = potential_import_name.to_lowercase();
138     let import_assets = import_assets(ctx, potential_import_name)?;
139     let import_scope = ImportScope::find_insert_use_container(
140         &position_for_import(ctx, Some(import_assets.import_candidate()))?,
141         &ctx.sema,
142     )?;
143
144     let ns_filter = |import: &LocatedImport| {
145         let kind = match ctx.path_kind() {
146             Some(kind) => kind,
147             None => {
148                 return match import.original_item {
149                     ItemInNs::Macros(mac) => mac.is_fn_like(),
150                     _ => true,
151                 }
152             }
153         };
154         match (kind, import.original_item) {
155             // Aren't handled in flyimport
156             (PathKind::Vis { .. } | PathKind::Use, _) => false,
157             // modules are always fair game
158             (_, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
159             // and so are macros(except for attributes)
160             (
161                 PathKind::Expr | PathKind::Type | PathKind::Mac | PathKind::Pat,
162                 ItemInNs::Macros(mac),
163             ) => mac.is_fn_like(),
164             (PathKind::Mac, _) => true,
165
166             (PathKind::Expr, ItemInNs::Types(_) | ItemInNs::Values(_)) => true,
167
168             (PathKind::Pat, ItemInNs::Types(_)) => true,
169             (PathKind::Pat, ItemInNs::Values(def)) => matches!(def, hir::ModuleDef::Const(_)),
170
171             (PathKind::Type, ItemInNs::Types(_)) => true,
172             (PathKind::Type, ItemInNs::Values(_)) => false,
173
174             (PathKind::Attr, ItemInNs::Macros(mac)) => mac.is_attr(),
175             (PathKind::Attr, _) => false,
176         }
177     };
178
179     acc.add_all(
180         import_assets
181             .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
182             .into_iter()
183             .filter(ns_filter)
184             .filter(|import| {
185                 !ctx.is_item_hidden(&import.item_to_import)
186                     && !ctx.is_item_hidden(&import.original_item)
187             })
188             .sorted_by_key(|located_import| {
189                 compute_fuzzy_completion_order_key(
190                     &located_import.import_path,
191                     &user_input_lowercased,
192                 )
193             })
194             .filter_map(|import| {
195                 render_resolution_with_import(
196                     RenderContext::new(ctx),
197                     ImportEdit { import, scope: import_scope.clone() },
198                 )
199             }),
200     );
201     Some(())
202 }
203
204 pub(crate) fn position_for_import(
205     ctx: &CompletionContext,
206     import_candidate: Option<&ImportCandidate>,
207 ) -> Option<SyntaxNode> {
208     Some(
209         match import_candidate {
210             Some(ImportCandidate::Path(_)) => ctx.name_syntax.as_ref()?.syntax(),
211             Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual()?.syntax(),
212             Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver()?.syntax(),
213             None => return ctx.original_token.parent(),
214         }
215         .clone(),
216     )
217 }
218
219 fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
220     let current_module = ctx.module?;
221     if let Some(dot_receiver) = ctx.dot_receiver() {
222         ImportAssets::for_fuzzy_method_call(
223             current_module,
224             ctx.sema.type_of_expr(dot_receiver)?.original,
225             fuzzy_name,
226             dot_receiver.syntax().clone(),
227         )
228     } else {
229         let fuzzy_name_length = fuzzy_name.len();
230         let mut assets_for_path = ImportAssets::for_fuzzy_path(
231             current_module,
232             ctx.path_qual().cloned(),
233             fuzzy_name,
234             &ctx.sema,
235             ctx.token.parent()?,
236         )?;
237         if fuzzy_name_length < 3 {
238             cov_mark::hit!(flyimport_exact_on_short_path);
239             assets_for_path.path_fuzzy_name_to_exact(false);
240         }
241         Some(assets_for_path)
242     }
243 }
244
245 pub(crate) fn compute_fuzzy_completion_order_key(
246     proposed_mod_path: &hir::ModPath,
247     user_input_lowercased: &str,
248 ) -> usize {
249     cov_mark::hit!(certain_fuzzy_order_test);
250     let import_name = match proposed_mod_path.segments().last() {
251         Some(name) => name.to_smol_str().to_lowercase(),
252         None => return usize::MAX,
253     };
254     match import_name.match_indices(user_input_lowercased).next() {
255         Some((first_matching_index, _)) => first_matching_index,
256         None => usize::MAX,
257     }
258 }