]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_completion/src/completions/flyimport.rs
simplify
[rust.git] / crates / ide_completion / src / completions / flyimport.rs
index da8375af9757ec2e1a4cbd515cc8dcb05fa3dee7..be9cfbded9bb8d406176a245234aa63e8b49a504 100644 (file)
@@ -1,8 +1,10 @@
 //! Feature: completion with imports-on-the-fly
 //!
 //! When completing names in the current scope, proposes additional imports from other modules or crates,
-//! if they can be qualified in the scope and their name contains all symbols from the completion input
-//! (case-insensitive, in any order or places).
+//! if they can be qualified in the scope and their name contains all symbols from the completion input.
+//!
+//! To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
+//! If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the contaning is checked case-insensitively.
 //!
 //! ```
 //! fn main() {
 //! ```
 //!
 //! Also completes associated items, that require trait imports.
+//! If any unresolved and/or partially-qualified path predeces the input, it will be taken into account.
+//! Currently, only the imports with their import path ending with the whole qialifier will be proposed
+//! (no fuzzy matching for qualifier).
+//!
+//! ```
+//! mod foo {
+//!     pub mod bar {
+//!         pub struct Item;
+//!
+//!         impl Item {
+//!             pub const TEST_ASSOC: usize = 3;
+//!         }
+//!     }
+//! }
+//!
+//! fn main() {
+//!     bar::Item::TEST_A$0
+//! }
+//! ```
+//! ->
+//! ```
+//! use foo::bar;
+//!
+//! mod foo {
+//!     pub mod bar {
+//!         pub struct Item;
+//!
+//!         impl Item {
+//!             pub const TEST_ASSOC: usize = 3;
+//!         }
+//!     }
+//! }
+//!
+//! fn main() {
+//!     bar::Item::TEST_ASSOC
+//! }
+//! ```
+//!
+//! 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,
+//! no imports will be proposed.
 //!
 //! .Fuzzy search details
 //!
 //! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
 //! capability enabled.
 
-use hir::{AsAssocItem, ModPath, ScopeDef};
+use hir::ModPath;
 use ide_db::helpers::{
     import_assets::{ImportAssets, ImportCandidate},
     insert_use::ImportScope,
 };
-use rustc_hash::FxHashSet;
+use itertools::Itertools;
 use syntax::{AstNode, SyntaxNode, T};
-use test_utils::mark;
 
 use crate::{
     context::CompletionContext,
@@ -69,10 +110,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
     if !ctx.config.enable_imports_on_the_fly {
         return None;
     }
-    if ctx.use_item_syntax.is_some()
-        || ctx.attribute_under_caret.is_some()
-        || ctx.mod_declaration_under_caret.is_some()
-    {
+    if ctx.use_item_syntax.is_some() || ctx.is_path_disallowed() {
         return None;
     }
     let potential_import_name = {
@@ -88,55 +126,31 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
 
     let user_input_lowercased = potential_import_name.to_lowercase();
     let import_assets = import_assets(ctx, potential_import_name)?;
-    let import_scope = ImportScope::find_insert_use_container(
+    let import_scope = ImportScope::find_insert_use_container_with_macros(
         position_for_import(ctx, Some(import_assets.import_candidate()))?,
         &ctx.sema,
     )?;
 
-    let scope_definitions = scope_definitions(ctx);
-    let mut all_mod_paths = import_assets
-        .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
-        .into_iter()
-        .map(|(mod_path, item_in_ns)| {
-            let scope_item = match item_in_ns {
-                hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
-                hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
-                hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
-            };
-            (mod_path, scope_item)
-        })
-        .filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def))
-        .collect::<Vec<_>>();
-    all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
-        compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
-    });
-
-    acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
-        let import_for_trait_assoc_item = match definition {
-            ScopeDef::ModuleDef(module_def) => module_def
-                .as_assoc_item(ctx.db)
-                .and_then(|assoc| assoc.containing_trait(ctx.db))
-                .is_some(),
-            _ => false,
-        };
-        let import_edit = ImportEdit {
-            import_path,
-            import_scope: import_scope.clone(),
-            import_for_trait_assoc_item,
-        };
-        render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
-    }));
+    acc.add_all(
+        import_assets
+            .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+            .into_iter()
+            .sorted_by_key(|located_import| {
+                compute_fuzzy_completion_order_key(
+                    &located_import.import_path,
+                    &user_input_lowercased,
+                )
+            })
+            .filter_map(|import| {
+                render_resolution_with_import(
+                    RenderContext::new(ctx),
+                    ImportEdit { import, scope: import_scope.clone() },
+                )
+            }),
+    );
     Some(())
 }
 
-fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> {
-    let mut scope_definitions = FxHashSet::default();
-    ctx.scope.process_all_names(&mut |_, scope_def| {
-        scope_definitions.insert(scope_def);
-    });
-    scope_definitions
-}
-
 pub(crate) fn position_for_import<'a>(
     ctx: &'a CompletionContext,
     import_candidate: Option<&ImportCandidate>,
@@ -161,23 +175,30 @@ fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAs
             current_module,
             ctx.sema.type_of_expr(dot_receiver)?,
             fuzzy_name,
+            dot_receiver.syntax().clone(),
         )
     } else {
         let fuzzy_name_length = fuzzy_name.len();
+        let approximate_node = match current_module.definition_source(ctx.db).value {
+            hir::ModuleSource::SourceFile(s) => s.syntax().clone(),
+            hir::ModuleSource::Module(m) => m.syntax().clone(),
+            hir::ModuleSource::BlockExpr(b) => b.syntax().clone(),
+        };
         let assets_for_path = ImportAssets::for_fuzzy_path(
             current_module,
             ctx.path_qual.clone(),
             fuzzy_name,
             &ctx.sema,
-        );
+            approximate_node,
+        )?;
 
-        if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_))
+        if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_))
             && fuzzy_name_length < 2
         {
-            mark::hit!(ignore_short_input_for_path);
+            cov_mark::hit!(ignore_short_input_for_path);
             None
         } else {
-            assets_for_path
+            Some(assets_for_path)
         }
     }
 }
@@ -186,12 +207,12 @@ fn compute_fuzzy_completion_order_key(
     proposed_mod_path: &ModPath,
     user_input_lowercased: &str,
 ) -> usize {
-    mark::hit!(certain_fuzzy_order_test);
-    let proposed_import_name = match proposed_mod_path.segments().last() {
+    cov_mark::hit!(certain_fuzzy_order_test);
+    let import_name = match proposed_mod_path.segments().last() {
         Some(name) => name.to_string().to_lowercase(),
         None => return usize::MAX,
     };
-    match proposed_import_name.match_indices(user_input_lowercased).next() {
+    match import_name.match_indices(user_input_lowercased).next() {
         Some((first_matching_index, _)) => first_matching_index,
         None => usize::MAX,
     }
@@ -200,7 +221,6 @@ fn compute_fuzzy_completion_order_key(
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
-    use test_utils::mark;
 
     use crate::{
         item::CompletionKind,
@@ -295,7 +315,7 @@ fn main() {
 
     #[test]
     fn short_paths_are_ignored() {
-        mark::check!(ignore_short_input_for_path);
+        cov_mark::check!(ignore_short_input_for_path);
 
         check(
             r#"
@@ -319,7 +339,7 @@ fn main() {
 
     #[test]
     fn fuzzy_completions_come_in_specific_order() {
-        mark::check!(certain_fuzzy_order_test);
+        cov_mark::check!(certain_fuzzy_order_test);
         check(
             r#"
 //- /lib.rs crate:dep
@@ -381,7 +401,7 @@ fn main() {
         check(
             fixture,
             expect![[r#"
-                fn weird_function() (dep::test_mod::TestTrait) -> ()
+                fn weird_function() (dep::test_mod::TestTrait) fn()
             "#]],
         );
 
@@ -474,7 +494,7 @@ fn main() {
         check(
             fixture,
             expect![[r#"
-                me random_method() (dep::test_mod::TestTrait) -> ()
+                me random_method() (dep::test_mod::TestTrait) fn(&self)
             "#]],
         );
 
@@ -644,7 +664,7 @@ fn main() {
 }
         "#,
             expect![[r#"
-                me random_method() (dep::test_mod::TestTrait) -> () DEPRECATED
+                me random_method() (dep::test_mod::TestTrait) fn(&self) DEPRECATED
             "#]],
         );
 
@@ -675,7 +695,7 @@ fn main() {
 "#,
             expect![[r#"
                 ct SPECIAL_CONST (dep::test_mod::TestTrait) DEPRECATED
-                fn weird_function() (dep::test_mod::TestTrait) -> () DEPRECATED
+                fn weird_function() (dep::test_mod::TestTrait) fn() DEPRECATED
             "#]],
         );
     }
@@ -775,4 +795,353 @@ fn main() {
 }"#,
         );
     }
+
+    #[test]
+    fn unresolved_qualifier() {
+        let fixture = r#"
+mod foo {
+    pub mod bar {
+        pub mod baz {
+            pub struct Item;
+        }
+    }
+}
+
+fn main() {
+    bar::baz::Ite$0
+}"#;
+
+        check(
+            fixture,
+            expect![[r#"
+        st foo::bar::baz::Item
+        "#]],
+        );
+
+        check_edit(
+            "Item",
+            fixture,
+            r#"
+        use foo::bar;
+
+        mod foo {
+            pub mod bar {
+                pub mod baz {
+                    pub struct Item;
+                }
+            }
+        }
+
+        fn main() {
+            bar::baz::Item
+        }"#,
+        );
+    }
+
+    #[test]
+    fn unresolved_assoc_item_container() {
+        let fixture = r#"
+mod foo {
+    pub struct Item;
+
+    impl Item {
+        pub const TEST_ASSOC: usize = 3;
+    }
+}
+
+fn main() {
+    Item::TEST_A$0
+}"#;
+
+        check(
+            fixture,
+            expect![[r#"
+        ct TEST_ASSOC (foo::Item)
+        "#]],
+        );
+
+        check_edit(
+            "TEST_ASSOC",
+            fixture,
+            r#"
+use foo::Item;
+
+mod foo {
+    pub struct Item;
+
+    impl Item {
+        pub const TEST_ASSOC: usize = 3;
+    }
+}
+
+fn main() {
+    Item::TEST_ASSOC
+}"#,
+        );
+    }
+
+    #[test]
+    fn unresolved_assoc_item_container_with_path() {
+        let fixture = r#"
+mod foo {
+    pub mod bar {
+        pub struct Item;
+
+        impl Item {
+            pub const TEST_ASSOC: usize = 3;
+        }
+    }
+}
+
+fn main() {
+    bar::Item::TEST_A$0
+}"#;
+
+        check(
+            fixture,
+            expect![[r#"
+        ct TEST_ASSOC (foo::bar::Item)
+    "#]],
+        );
+
+        check_edit(
+            "TEST_ASSOC",
+            fixture,
+            r#"
+use foo::bar;
+
+mod foo {
+    pub mod bar {
+        pub struct Item;
+
+        impl Item {
+            pub const TEST_ASSOC: usize = 3;
+        }
+    }
+}
+
+fn main() {
+    bar::Item::TEST_ASSOC
+}"#,
+        );
+    }
+
+    #[test]
+    fn fuzzy_unresolved_path() {
+        check(
+            r#"
+mod foo {
+    pub mod bar {
+        pub struct Item;
+
+        impl Item {
+            pub const TEST_ASSOC: usize = 3;
+        }
+    }
+}
+
+fn main() {
+    bar::ASS$0
+}"#,
+            expect![[]],
+        )
+    }
+
+    #[test]
+    fn unqualified_assoc_items_are_omitted() {
+        check(
+            r#"
+mod something {
+    pub trait BaseTrait {
+        fn test_function() -> i32;
+    }
+
+    pub struct Item1;
+    pub struct Item2;
+
+    impl BaseTrait for Item1 {
+        fn test_function() -> i32 {
+            1
+        }
+    }
+
+    impl BaseTrait for Item2 {
+        fn test_function() -> i32 {
+            2
+        }
+    }
+}
+
+fn main() {
+    test_f$0
+}"#,
+            expect![[]],
+        )
+    }
+
+    #[test]
+    fn case_matters() {
+        check(
+            r#"
+mod foo {
+    pub const TEST_CONST: usize = 3;
+    pub fn test_function() -> i32 {
+        4
+    }
+}
+
+fn main() {
+    TE$0
+}"#,
+            expect![[r#"
+        ct foo::TEST_CONST
+    "#]],
+        );
+
+        check(
+            r#"
+mod foo {
+    pub const TEST_CONST: usize = 3;
+    pub fn test_function() -> i32 {
+        4
+    }
+}
+
+fn main() {
+    te$0
+}"#,
+            expect![[r#"
+        ct foo::TEST_CONST
+        fn test_function() (foo::test_function) fn() -> i32
+    "#]],
+        );
+
+        check(
+            r#"
+mod foo {
+    pub const TEST_CONST: usize = 3;
+    pub fn test_function() -> i32 {
+        4
+    }
+}
+
+fn main() {
+    Te$0
+}"#,
+            expect![[]],
+        );
+    }
+
+    #[test]
+    fn no_fuzzy_during_fields_of_record_lit_syntax() {
+        check(
+            r#"
+mod m {
+    pub fn some_fn() -> i32 {
+        42
+    }
+}
+struct Foo {
+    some_field: i32,
+}
+fn main() {
+    let _ = Foo { so$0 };
+}
+"#,
+            expect![[]],
+        );
+    }
+
+    #[test]
+    fn fuzzy_after_fields_of_record_lit_syntax() {
+        check(
+            r#"
+mod m {
+    pub fn some_fn() -> i32 {
+        42
+    }
+}
+struct Foo {
+    some_field: i32,
+}
+fn main() {
+    let _ = Foo { some_field: so$0 };
+}
+"#,
+            expect![[r#"
+                fn some_fn() (m::some_fn) fn() -> i32
+            "#]],
+        );
+    }
+
+    #[test]
+    fn no_flyimports_in_traits_and_impl_declarations() {
+        check(
+            r#"
+mod m {
+    pub fn some_fn() -> i32 {
+        42
+    }
+}
+trait Foo {
+    som$0
+}
+"#,
+            expect![[r#""#]],
+        );
+
+        check(
+            r#"
+mod m {
+    pub fn some_fn() -> i32 {
+        42
+    }
+}
+struct Foo;
+impl Foo {
+    som$0
+}
+"#,
+            expect![[r#""#]],
+        );
+
+        check(
+            r#"
+mod m {
+    pub fn some_fn() -> i32 {
+        42
+    }
+}
+struct Foo;
+trait Bar {}
+impl Bar for Foo {
+    som$0
+}
+"#,
+            expect![[r#""#]],
+        );
+    }
+
+    #[test]
+    fn no_inherent_candidates_proposed() {
+        check(
+            r#"
+mod baz {
+    pub trait DefDatabase {
+        fn method1(&self);
+    }
+    pub trait HirDatabase: DefDatabase {
+        fn method2(&self);
+    }
+}
+
+mod bar {
+    fn test(db: &dyn crate::baz::HirDatabase) {
+        db.metho$0
+    }
+}
+            "#,
+            expect![[r#""#]],
+        );
+    }
 }