aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2020-09-18 21:40:11 +0100
committerKirill Bulatov <[email protected]>2020-11-16 19:19:05 +0000
commitf62e8616c879255e70052ae35ce7f98bffedac11 (patch)
treef1a21bebdd22483a1a503861bbe55b77fa1d2b37 /crates
parent0a658c4a973d934d622957a6fb03916271496385 (diff)
Add imports in auto completion
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/auto_import.rs3
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs3
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs2
-rw-r--r--crates/assists/src/utils.rs3
-rw-r--r--crates/assists/src/utils/insert_use.rs14
-rw-r--r--crates/completion/Cargo.toml2
-rw-r--r--crates/completion/src/completions.rs1
-rw-r--r--crates/completion/src/completions/complete_magic.rs114
-rw-r--r--crates/completion/src/item.rs1
-rw-r--r--crates/completion/src/lib.rs1
-rw-r--r--crates/hir/src/code_model.rs10
-rw-r--r--crates/hir/src/lib.rs1
-rw-r--r--crates/ide_db/src/imports_locator.rs14
13 files changed, 144 insertions, 25 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 37dd61266..d665837a2 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -98,7 +98,8 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
98 98
99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
100 let group = import_group_message(import_assets.import_candidate()); 100 let group = import_group_message(import_assets.import_candidate());
101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; 101 let scope =
102 ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
102 for (import, _) in proposed_imports { 103 for (import, _) in proposed_imports {
103 acc.add_group( 104 acc.add_group(
104 &group, 105 &group,
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 067afabf2..cac77c49b 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -143,8 +143,7 @@ fn insert_import(
143 if let Some(mut mod_path) = mod_path { 143 if let Some(mut mod_path) = mod_path {
144 mod_path.segments.pop(); 144 mod_path.segments.pop();
145 mod_path.segments.push(variant_hir_name.clone()); 145 mod_path.segments.push(variant_hir_name.clone());
146 let scope = ImportScope::find_insert_use_container(scope_node, ctx)?; 146 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
147
148 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge); 147 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
149 } 148 }
150 Some(()) 149 Some(())
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index d7e1d9580..a66db9ae3 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -34,7 +34,7 @@ pub(crate) fn replace_qualified_name_with_use(
34 } 34 }
35 35
36 let target = path.syntax().text_range(); 36 let target = path.syntax().text_range();
37 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 37 let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
38 let syntax = scope.as_syntax_node(); 38 let syntax = scope.as_syntax_node();
39 acc.add( 39 acc.add(
40 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 40 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 7bd338e99..caabc44de 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -21,8 +21,7 @@ use crate::{
21 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 21 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
22}; 22};
23 23
24pub use insert_use::MergeBehaviour; 24pub use insert_use::{insert_use, ImportScope, MergeBehaviour};
25pub(crate) use insert_use::{insert_use, ImportScope};
26 25
27pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { 26pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
28 let mut segments = Vec::new(); 27 let mut segments = Vec::new();
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index af3fc96b6..1aa727e11 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,6 +1,8 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2use std::{cmp::Ordering, iter::successors}; 2use std::{cmp::Ordering, iter::successors};
3 3
4use hir::Semantics;
5use ide_db::RootDatabase;
4use itertools::{EitherOrBoth, Itertools}; 6use itertools::{EitherOrBoth, Itertools};
5use syntax::{ 7use syntax::{
6 algo::SyntaxRewriter, 8 algo::SyntaxRewriter,
@@ -14,7 +16,7 @@ use syntax::{
14use test_utils::mark; 16use test_utils::mark;
15 17
16#[derive(Debug)] 18#[derive(Debug)]
17pub(crate) enum ImportScope { 19pub enum ImportScope {
18 File(ast::SourceFile), 20 File(ast::SourceFile),
19 Module(ast::ItemList), 21 Module(ast::ItemList),
20} 22}
@@ -31,14 +33,14 @@ impl ImportScope {
31 } 33 }
32 34
33 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. 35 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
34 pub(crate) fn find_insert_use_container( 36 pub fn find_insert_use_container(
35 position: &SyntaxNode, 37 position: &SyntaxNode,
36 ctx: &crate::assist_context::AssistContext, 38 sema: &Semantics<'_, RootDatabase>,
37 ) -> Option<Self> { 39 ) -> Option<Self> {
38 ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from) 40 sema.ancestors_with_macros(position.clone()).find_map(Self::from)
39 } 41 }
40 42
41 pub(crate) fn as_syntax_node(&self) -> &SyntaxNode { 43 pub fn as_syntax_node(&self) -> &SyntaxNode {
42 match self { 44 match self {
43 ImportScope::File(file) => file.syntax(), 45 ImportScope::File(file) => file.syntax(),
44 ImportScope::Module(item_list) => item_list.syntax(), 46 ImportScope::Module(item_list) => item_list.syntax(),
@@ -88,7 +90,7 @@ fn is_inner_comment(token: SyntaxToken) -> bool {
88} 90}
89 91
90/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 92/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
91pub(crate) fn insert_use<'a>( 93pub fn insert_use<'a>(
92 scope: &ImportScope, 94 scope: &ImportScope,
93 path: ast::Path, 95 path: ast::Path,
94 merge: Option<MergeBehaviour>, 96 merge: Option<MergeBehaviour>,
diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml
index 3015ec9e0..799b4a3d5 100644
--- a/crates/completion/Cargo.toml
+++ b/crates/completion/Cargo.toml
@@ -13,6 +13,7 @@ doctest = false
13itertools = "0.9.0" 13itertools = "0.9.0"
14log = "0.4.8" 14log = "0.4.8"
15rustc-hash = "1.1.0" 15rustc-hash = "1.1.0"
16either = "1.6.1"
16 17
17assists = { path = "../assists", version = "0.0.0" } 18assists = { path = "../assists", version = "0.0.0" }
18stdx = { path = "../stdx", version = "0.0.0" } 19stdx = { path = "../stdx", version = "0.0.0" }
@@ -21,6 +22,7 @@ text_edit = { path = "../text_edit", version = "0.0.0" }
21base_db = { path = "../base_db", version = "0.0.0" } 22base_db = { path = "../base_db", version = "0.0.0" }
22ide_db = { path = "../ide_db", version = "0.0.0" } 23ide_db = { path = "../ide_db", version = "0.0.0" }
23profile = { path = "../profile", version = "0.0.0" } 24profile = { path = "../profile", version = "0.0.0" }
25assists = { path = "../assists", version = "0.0.0" }
24test_utils = { path = "../test_utils", version = "0.0.0" } 26test_utils = { path = "../test_utils", version = "0.0.0" }
25 27
26# completions crate should depend only on the top-level `hir` package. if you need 28# completions crate should depend only on the top-level `hir` package. if you need
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs
index 75dbb1a23..99db5f998 100644
--- a/crates/completion/src/completions.rs
+++ b/crates/completion/src/completions.rs
@@ -13,6 +13,7 @@ pub(crate) mod postfix;
13pub(crate) mod macro_in_item_position; 13pub(crate) mod macro_in_item_position;
14pub(crate) mod trait_impl; 14pub(crate) mod trait_impl;
15pub(crate) mod mod_; 15pub(crate) mod mod_;
16pub(crate) mod complete_magic;
16 17
17use hir::{ModPath, ScopeDef, Type}; 18use hir::{ModPath, ScopeDef, Type};
18 19
diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs
new file mode 100644
index 000000000..857a0b620
--- /dev/null
+++ b/crates/completion/src/completions/complete_magic.rs
@@ -0,0 +1,114 @@
1//! TODO kb move this into the complete_unqualified_path when starts to work properly
2
3use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour};
4use hir::Query;
5use itertools::Itertools;
6use syntax::AstNode;
7use text_edit::TextEdit;
8
9use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind};
10
11use super::Completions;
12
13pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
14 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
15 return None;
16 }
17 let current_module = ctx.scope.module()?;
18 let anchor = ctx.name_ref_syntax.as_ref()?;
19 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
20 // TODO kb now this is the whole file, which is not disjoint with any other change in the same file, fix it
21 // otherwise it's impossible to correctly add the use statement and also change the completed text into something more meaningful
22 let import_syntax = import_scope.as_syntax_node();
23
24 // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion"
25 // TODO kb module functions are not completed, consider `std::io::stdin` one
26 let potential_import_name = ctx.token.to_string();
27
28 let possible_imports = ctx
29 .krate?
30 // TODO kb use imports_locator instead?
31 .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40))
32 .unique()
33 .filter_map(|import_candidate| match import_candidate {
34 either::Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def),
35 either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def),
36 })
37 .filter_map(|mod_path| {
38 let correct_qualifier = mod_path.segments.last()?.to_string();
39 let rewriter =
40 insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full));
41 let rewritten_node = rewriter.rewrite(import_syntax);
42 let insert_use_edit =
43 TextEdit::replace(import_syntax.text_range(), rewritten_node.to_string());
44 let mut completion_edit =
45 TextEdit::replace(anchor.syntax().text_range(), correct_qualifier);
46 completion_edit.union(insert_use_edit).expect("TODO kb");
47
48 let completion_item: CompletionItem = CompletionItem::new(
49 CompletionKind::Magic,
50 ctx.source_range(),
51 mod_path.to_string(),
52 )
53 .kind(CompletionItemKind::Struct)
54 .text_edit(completion_edit)
55 .into();
56 Some(completion_item)
57 });
58 acc.add_all(possible_imports);
59
60 Some(())
61}
62
63#[cfg(test)]
64mod tests {
65 use expect_test::{expect, Expect};
66
67 use crate::{
68 item::CompletionKind,
69 test_utils::{check_edit, completion_list},
70 };
71
72 fn check(ra_fixture: &str, expect: Expect) {
73 let actual = completion_list(ra_fixture, CompletionKind::Magic);
74 expect.assert_eq(&actual)
75 }
76
77 #[test]
78 fn case_insensitive_magic_completion_works() {
79 check(
80 r#"
81//- /lib.rs crate:dep
82pub struct TestStruct;
83
84//- /main.rs crate:main deps:dep
85fn main() {
86 teru<|>
87}
88"#,
89 expect![[r#"
90 st dep::TestStruct
91 "#]],
92 );
93
94 check_edit(
95 "dep::TestStruct",
96 r#"
97//- /lib.rs crate:dep
98pub struct TestStruct;
99
100//- /main.rs crate:main deps:dep
101fn main() {
102 teru<|>
103}
104"#,
105 r#"
106use dep::TestStruct;
107
108fn main() {
109 TestStruct
110}
111"#,
112 );
113 }
114}
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs
index 6d1d085f4..f23913935 100644
--- a/crates/completion/src/item.rs
+++ b/crates/completion/src/item.rs
@@ -31,6 +31,7 @@ pub struct CompletionItem {
31 /// 31 ///
32 /// Typically, replaces `source_range` with new identifier. 32 /// Typically, replaces `source_range` with new identifier.
33 text_edit: TextEdit, 33 text_edit: TextEdit,
34
34 insert_text_format: InsertTextFormat, 35 insert_text_format: InsertTextFormat,
35 36
36 /// What item (struct, function, etc) are we completing. 37 /// What item (struct, function, etc) are we completing.
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index cb6e0554e..e920fa6b5 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -118,6 +118,7 @@ pub fn completions(
118 completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 118 completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
119 completions::trait_impl::complete_trait_impl(&mut acc, &ctx); 119 completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
120 completions::mod_::complete_mod(&mut acc, &ctx); 120 completions::mod_::complete_mod(&mut acc, &ctx);
121 completions::complete_magic::complete_magic(&mut acc, &ctx);
121 122
122 Some(acc) 123 Some(acc)
123} 124}
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 30a5e4580..37ed092ad 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -110,15 +110,9 @@ impl Crate {
110 pub fn query_external_importables( 110 pub fn query_external_importables(
111 self, 111 self,
112 db: &dyn DefDatabase, 112 db: &dyn DefDatabase,
113 query: &str, 113 query: import_map::Query,
114 ) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 114 ) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
115 import_map::search_dependencies( 115 import_map::search_dependencies(db, self.into(), query).into_iter().map(|item| match item {
116 db,
117 self.into(),
118 import_map::Query::new(query).anchor_end().case_sensitive().limit(40),
119 )
120 .into_iter()
121 .map(|item| match item {
122 ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()), 116 ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()),
123 ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()), 117 ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()),
124 }) 118 })
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 0d184379f..ad58a7cfe 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -49,6 +49,7 @@ pub use hir_def::{
49 builtin_type::BuiltinType, 49 builtin_type::BuiltinType,
50 docs::Documentation, 50 docs::Documentation,
51 find_path::PrefixKind, 51 find_path::PrefixKind,
52 import_map::Query,
52 item_scope::ItemInNs, 53 item_scope::ItemInNs,
53 nameres::ModuleSource, 54 nameres::ModuleSource,
54 path::{ModPath, PathKind}, 55 path::{ModPath, PathKind},
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index df74be00b..e4f4b5427 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -1,12 +1,12 @@
1//! This module contains an import search funcionality that is provided to the assists module. 1//! This module contains an import search funcionality that is provided to the assists module.
2//! Later, this should be moved away to a separate crate that is accessible from the assists module. 2//! Later, this should be moved away to a separate crate that is accessible from the assists module.
3 3
4use hir::{Crate, MacroDef, ModuleDef, Semantics}; 4use hir::{Crate, MacroDef, ModuleDef, Query as ImportMapQuery, Semantics};
5use syntax::{ast, AstNode, SyntaxKind::NAME}; 5use syntax::{ast, AstNode, SyntaxKind::NAME};
6 6
7use crate::{ 7use crate::{
8 defs::{Definition, NameClass}, 8 defs::{Definition, NameClass},
9 symbol_index::{self, FileSymbol, Query}, 9 symbol_index::{self, FileSymbol, Query as SymbolQuery},
10 RootDatabase, 10 RootDatabase,
11}; 11};
12use either::Either; 12use either::Either;
@@ -21,12 +21,16 @@ pub fn find_imports<'a>(
21 let db = sema.db; 21 let db = sema.db;
22 22
23 // Query dependencies first. 23 // Query dependencies first.
24 let mut candidates: FxHashSet<_> = 24 let mut candidates: FxHashSet<_> = krate
25 krate.query_external_importables(db, name_to_import).collect(); 25 .query_external_importables(
26 db,
27 ImportMapQuery::new(name_to_import).anchor_end().case_sensitive().limit(40),
28 )
29 .collect();
26 30
27 // Query the local crate using the symbol index. 31 // Query the local crate using the symbol index.
28 let local_results = { 32 let local_results = {
29 let mut query = Query::new(name_to_import.to_string()); 33 let mut query = SymbolQuery::new(name_to_import.to_string());
30 query.exact(); 34 query.exact();
31 query.limit(40); 35 query.limit(40);
32 symbol_index::crate_symbols(db, krate.into(), query) 36 symbol_index::crate_symbols(db, krate.into(), query)