aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion')
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs270
-rw-r--r--crates/ide_completion/src/item.rs43
-rw-r--r--crates/ide_completion/src/lib.rs28
-rw-r--r--crates/ide_completion/src/render.rs19
4 files changed, 268 insertions, 92 deletions
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index f34764b61..391a11c91 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -21,6 +21,46 @@
21//! ``` 21//! ```
22//! 22//!
23//! Also completes associated items, that require trait imports. 23//! Also completes associated items, that require trait imports.
24//! If any unresolved and/or partially-qualified path predeces the input, it will be taken into account.
25//! Currently, only the imports with their import path ending with the whole qialifier will be proposed
26//! (no fuzzy matching for qualifier).
27//!
28//! ```
29//! mod foo {
30//! pub mod bar {
31//! pub struct Item;
32//!
33//! impl Item {
34//! pub const TEST_ASSOC: usize = 3;
35//! }
36//! }
37//! }
38//!
39//! fn main() {
40//! bar::Item::TEST_A$0
41//! }
42//! ```
43//! ->
44//! ```
45//! use foo::bar;
46//!
47//! mod foo {
48//! pub mod bar {
49//! pub struct Item;
50//!
51//! impl Item {
52//! pub const TEST_ASSOC: usize = 3;
53//! }
54//! }
55//! }
56//!
57//! fn main() {
58//! bar::Item::TEST_ASSOC
59//! }
60//! ```
61//!
62//! 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,
63//! no imports will be proposed.
24//! 64//!
25//! .Fuzzy search details 65//! .Fuzzy search details
26//! 66//!
@@ -48,12 +88,12 @@
48//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding 88//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
49//! capability enabled. 89//! capability enabled.
50 90
51use hir::{AsAssocItem, ModPath, ScopeDef}; 91use hir::ModPath;
52use ide_db::helpers::{ 92use ide_db::helpers::{
53 import_assets::{ImportAssets, ImportCandidate}, 93 import_assets::{ImportAssets, ImportCandidate},
54 insert_use::ImportScope, 94 insert_use::ImportScope,
55}; 95};
56use rustc_hash::FxHashSet; 96use itertools::Itertools;
57use syntax::{AstNode, SyntaxNode, T}; 97use syntax::{AstNode, SyntaxNode, T};
58 98
59use crate::{ 99use crate::{
@@ -92,50 +132,26 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
92 &ctx.sema, 132 &ctx.sema,
93 )?; 133 )?;
94 134
95 let scope_definitions = scope_definitions(ctx); 135 acc.add_all(
96 let mut all_mod_paths = import_assets 136 import_assets
97 .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) 137 .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
98 .into_iter() 138 .into_iter()
99 .map(|(mod_path, item_in_ns)| { 139 .sorted_by_key(|located_import| {
100 let scope_item = match item_in_ns { 140 compute_fuzzy_completion_order_key(
101 hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()), 141 &located_import.import_path,
102 hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()), 142 &user_input_lowercased,
103 hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()), 143 )
104 }; 144 })
105 (mod_path, scope_item) 145 .filter_map(|import| {
106 }) 146 render_resolution_with_import(
107 .filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def)) 147 RenderContext::new(ctx),
108 .collect::<Vec<_>>(); 148 ImportEdit { import, scope: import_scope.clone() },
109 all_mod_paths.sort_by_cached_key(|(mod_path, _)| { 149 )
110 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) 150 }),
111 }); 151 );
112
113 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
114 let import_for_trait_assoc_item = match definition {
115 ScopeDef::ModuleDef(module_def) => module_def
116 .as_assoc_item(ctx.db)
117 .and_then(|assoc| assoc.containing_trait(ctx.db))
118 .is_some(),
119 _ => false,
120 };
121 let import_edit = ImportEdit {
122 import_path,
123 import_scope: import_scope.clone(),
124 import_for_trait_assoc_item,
125 };
126 render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
127 }));
128 Some(()) 152 Some(())
129} 153}
130 154
131fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> {
132 let mut scope_definitions = FxHashSet::default();
133 ctx.scope.process_all_names(&mut |_, scope_def| {
134 scope_definitions.insert(scope_def);
135 });
136 scope_definitions
137}
138
139pub(crate) fn position_for_import<'a>( 155pub(crate) fn position_for_import<'a>(
140 ctx: &'a CompletionContext, 156 ctx: &'a CompletionContext,
141 import_candidate: Option<&ImportCandidate>, 157 import_candidate: Option<&ImportCandidate>,
@@ -160,23 +176,30 @@ fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAs
160 current_module, 176 current_module,
161 ctx.sema.type_of_expr(dot_receiver)?, 177 ctx.sema.type_of_expr(dot_receiver)?,
162 fuzzy_name, 178 fuzzy_name,
179 dot_receiver.syntax().clone(),
163 ) 180 )
164 } else { 181 } else {
165 let fuzzy_name_length = fuzzy_name.len(); 182 let fuzzy_name_length = fuzzy_name.len();
183 let approximate_node = match current_module.definition_source(ctx.db).value {
184 hir::ModuleSource::SourceFile(s) => s.syntax().clone(),
185 hir::ModuleSource::Module(m) => m.syntax().clone(),
186 hir::ModuleSource::BlockExpr(b) => b.syntax().clone(),
187 };
166 let assets_for_path = ImportAssets::for_fuzzy_path( 188 let assets_for_path = ImportAssets::for_fuzzy_path(
167 current_module, 189 current_module,
168 ctx.path_qual.clone(), 190 ctx.path_qual.clone(),
169 fuzzy_name, 191 fuzzy_name,
170 &ctx.sema, 192 &ctx.sema,
171 ); 193 approximate_node,
194 )?;
172 195
173 if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_)) 196 if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_))
174 && fuzzy_name_length < 2 197 && fuzzy_name_length < 2
175 { 198 {
176 cov_mark::hit!(ignore_short_input_for_path); 199 cov_mark::hit!(ignore_short_input_for_path);
177 None 200 None
178 } else { 201 } else {
179 assets_for_path 202 Some(assets_for_path)
180 } 203 }
181 } 204 }
182} 205}
@@ -186,11 +209,11 @@ fn compute_fuzzy_completion_order_key(
186 user_input_lowercased: &str, 209 user_input_lowercased: &str,
187) -> usize { 210) -> usize {
188 cov_mark::hit!(certain_fuzzy_order_test); 211 cov_mark::hit!(certain_fuzzy_order_test);
189 let proposed_import_name = match proposed_mod_path.segments().last() { 212 let import_name = match proposed_mod_path.segments().last() {
190 Some(name) => name.to_string().to_lowercase(), 213 Some(name) => name.to_string().to_lowercase(),
191 None => return usize::MAX, 214 None => return usize::MAX,
192 }; 215 };
193 match proposed_import_name.match_indices(user_input_lowercased).next() { 216 match import_name.match_indices(user_input_lowercased).next() {
194 Some((first_matching_index, _)) => first_matching_index, 217 Some((first_matching_index, _)) => first_matching_index,
195 None => usize::MAX, 218 None => usize::MAX,
196 } 219 }
@@ -773,4 +796,155 @@ fn main() {
773}"#, 796}"#,
774 ); 797 );
775 } 798 }
799
800 #[test]
801 fn unresolved_qualifier() {
802 let fixture = r#"
803mod foo {
804 pub mod bar {
805 pub mod baz {
806 pub struct Item;
807 }
808 }
809}
810
811fn main() {
812 bar::baz::Ite$0
813}"#;
814
815 check(
816 fixture,
817 expect![[r#"
818 st foo::bar::baz::Item
819 "#]],
820 );
821
822 check_edit(
823 "Item",
824 fixture,
825 r#"
826 use foo::bar;
827
828 mod foo {
829 pub mod bar {
830 pub mod baz {
831 pub struct Item;
832 }
833 }
834 }
835
836 fn main() {
837 bar::baz::Item
838 }"#,
839 );
840 }
841
842 #[test]
843 fn unresolved_assoc_item_container() {
844 let fixture = r#"
845mod foo {
846 pub struct Item;
847
848 impl Item {
849 pub const TEST_ASSOC: usize = 3;
850 }
851}
852
853fn main() {
854 Item::TEST_A$0
855}"#;
856
857 check(
858 fixture,
859 expect![[r#"
860 ct TEST_ASSOC (foo::Item)
861 "#]],
862 );
863
864 check_edit(
865 "TEST_ASSOC",
866 fixture,
867 r#"
868use foo::Item;
869
870mod foo {
871 pub struct Item;
872
873 impl Item {
874 pub const TEST_ASSOC: usize = 3;
875 }
876}
877
878fn main() {
879 Item::TEST_ASSOC
880}"#,
881 );
882 }
883
884 #[test]
885 fn unresolved_assoc_item_container_with_path() {
886 let fixture = r#"
887mod foo {
888 pub mod bar {
889 pub struct Item;
890
891 impl Item {
892 pub const TEST_ASSOC: usize = 3;
893 }
894 }
895}
896
897fn main() {
898 bar::Item::TEST_A$0
899}"#;
900
901 check(
902 fixture,
903 expect![[r#"
904 ct TEST_ASSOC (foo::bar::Item)
905 "#]],
906 );
907
908 check_edit(
909 "TEST_ASSOC",
910 fixture,
911 r#"
912use foo::bar;
913
914mod foo {
915 pub mod bar {
916 pub struct Item;
917
918 impl Item {
919 pub const TEST_ASSOC: usize = 3;
920 }
921 }
922}
923
924fn main() {
925 bar::Item::TEST_ASSOC
926}"#,
927 );
928 }
929
930 #[test]
931 fn fuzzy_unresolved_path() {
932 check(
933 r#"
934mod foo {
935 pub mod bar {
936 pub struct Item;
937
938 impl Item {
939 pub const TEST_ASSOC: usize = 3;
940 }
941 }
942}
943
944fn main() {
945 bar::Ass$0
946}"#,
947 expect![[]],
948 )
949 }
776} 950}
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 9b2435c4b..9b039e3e5 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -2,15 +2,16 @@
2 2
3use std::fmt; 3use std::fmt;
4 4
5use hir::{Documentation, ModPath, Mutability}; 5use hir::{Documentation, Mutability};
6use ide_db::{ 6use ide_db::{
7 helpers::{ 7 helpers::{
8 import_assets::LocatedImport,
8 insert_use::{self, ImportScope, InsertUseConfig}, 9 insert_use::{self, ImportScope, InsertUseConfig},
9 mod_path_to_ast, SnippetCap, 10 mod_path_to_ast, SnippetCap,
10 }, 11 },
11 SymbolKind, 12 SymbolKind,
12}; 13};
13use stdx::{impl_from, never}; 14use stdx::{format_to, impl_from, never};
14use syntax::{algo, TextRange}; 15use syntax::{algo, TextRange};
15use text_edit::TextEdit; 16use text_edit::TextEdit;
16 17
@@ -272,9 +273,8 @@ impl CompletionItem {
272/// An extra import to add after the completion is applied. 273/// An extra import to add after the completion is applied.
273#[derive(Debug, Clone)] 274#[derive(Debug, Clone)]
274pub struct ImportEdit { 275pub struct ImportEdit {
275 pub import_path: ModPath, 276 pub import: LocatedImport,
276 pub import_scope: ImportScope, 277 pub scope: ImportScope,
277 pub import_for_trait_assoc_item: bool,
278} 278}
279 279
280impl ImportEdit { 280impl ImportEdit {
@@ -284,7 +284,7 @@ impl ImportEdit {
284 let _p = profile::span("ImportEdit::to_text_edit"); 284 let _p = profile::span("ImportEdit::to_text_edit");
285 285
286 let rewriter = 286 let rewriter =
287 insert_use::insert_use(&self.import_scope, mod_path_to_ast(&self.import_path), cfg); 287 insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg);
288 let old_ast = rewriter.rewrite_root()?; 288 let old_ast = rewriter.rewrite_root()?;
289 let mut import_insert = TextEdit::builder(); 289 let mut import_insert = TextEdit::builder();
290 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); 290 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
@@ -322,20 +322,19 @@ impl Builder {
322 let mut lookup = self.lookup; 322 let mut lookup = self.lookup;
323 let mut insert_text = self.insert_text; 323 let mut insert_text = self.insert_text;
324 324
325 if let Some(import_to_add) = self.import_to_add.as_ref() { 325 if let Some(original_path) = self
326 if import_to_add.import_for_trait_assoc_item { 326 .import_to_add
327 lookup = lookup.or_else(|| Some(label.clone())); 327 .as_ref()
328 insert_text = insert_text.or_else(|| Some(label.clone())); 328 .and_then(|import_edit| import_edit.import.original_path.as_ref())
329 label = format!("{} ({})", label, import_to_add.import_path); 329 {
330 lookup = lookup.or_else(|| Some(label.clone()));
331 insert_text = insert_text.or_else(|| Some(label.clone()));
332
333 let original_path_label = original_path.to_string();
334 if original_path_label.ends_with(&label) {
335 label = original_path_label;
330 } else { 336 } else {
331 let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); 337 format_to!(label, " ({})", original_path)
332 let _ = import_path_without_last_segment.pop_segment();
333
334 if !import_path_without_last_segment.segments().is_empty() {
335 lookup = lookup.or_else(|| Some(label.clone()));
336 insert_text = insert_text.or_else(|| Some(label.clone()));
337 label = format!("{}::{}", import_path_without_last_segment, label);
338 }
339 } 338 }
340 } 339 }
341 340
@@ -439,9 +438,3 @@ impl Builder {
439 self 438 self
440 } 439 }
441} 440}
442
443impl<'a> Into<CompletionItem> for Builder {
444 fn into(self) -> CompletionItem {
445 self.build()
446 }
447}
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index b0b809791..a0c8c374d 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -13,7 +13,9 @@ mod completions;
13 13
14use completions::flyimport::position_for_import; 14use completions::flyimport::position_for_import;
15use ide_db::{ 15use ide_db::{
16 base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase, 16 base_db::FilePosition,
17 helpers::{import_assets::LocatedImport, insert_use::ImportScope},
18 items_locator, RootDatabase,
17}; 19};
18use text_edit::TextEdit; 20use text_edit::TextEdit;
19 21
@@ -139,25 +141,27 @@ pub fn resolve_completion_edits(
139 position: FilePosition, 141 position: FilePosition,
140 full_import_path: &str, 142 full_import_path: &str,
141 imported_name: String, 143 imported_name: String,
142 import_for_trait_assoc_item: bool,
143) -> Option<Vec<TextEdit>> { 144) -> Option<Vec<TextEdit>> {
144 let ctx = CompletionContext::new(db, position, config)?; 145 let ctx = CompletionContext::new(db, position, config)?;
145 let position_for_import = position_for_import(&ctx, None)?; 146 let position_for_import = position_for_import(&ctx, None)?;
146 let import_scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; 147 let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?;
147 148
148 let current_module = ctx.sema.scope(position_for_import).module()?; 149 let current_module = ctx.sema.scope(position_for_import).module()?;
149 let current_crate = current_module.krate(); 150 let current_crate = current_module.krate();
150 151
151 let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name) 152 let (import_path, item_to_import) =
152 .filter_map(|candidate| { 153 items_locator::with_exact_name(&ctx.sema, current_crate, imported_name)
153 let item: hir::ItemInNs = candidate.either(Into::into, Into::into); 154 .into_iter()
154 current_module.find_use_path_prefixed(db, item, config.insert_use.prefix_kind) 155 .filter_map(|candidate| {
155 }) 156 current_module
156 .find(|mod_path| mod_path.to_string() == full_import_path)?; 157 .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
158 .zip(Some(candidate))
159 })
160 .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?;
161 let import =
162 LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path));
157 163
158 ImportEdit { import_path, import_scope, import_for_trait_assoc_item } 164 ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit])
159 .to_text_edit(config.insert_use)
160 .map(|edit| vec![edit])
161} 165}
162 166
163#[cfg(test)] 167#[cfg(test)]
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index dcfac23c5..fae5685e2 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -13,7 +13,10 @@ mod builder_ext;
13use hir::{ 13use hir::{
14 AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, 14 AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type,
15}; 15};
16use ide_db::{helpers::SnippetCap, RootDatabase, SymbolKind}; 16use ide_db::{
17 helpers::{item_name, SnippetCap},
18 RootDatabase, SymbolKind,
19};
17use syntax::TextRange; 20use syntax::TextRange;
18 21
19use crate::{ 22use crate::{
@@ -50,18 +53,20 @@ pub(crate) fn render_resolution<'a>(
50pub(crate) fn render_resolution_with_import<'a>( 53pub(crate) fn render_resolution_with_import<'a>(
51 ctx: RenderContext<'a>, 54 ctx: RenderContext<'a>,
52 import_edit: ImportEdit, 55 import_edit: ImportEdit,
53 resolution: &ScopeDef,
54) -> Option<CompletionItem> { 56) -> Option<CompletionItem> {
57 let resolution = ScopeDef::from(import_edit.import.original_item);
55 let local_name = match resolution { 58 let local_name = match resolution {
56 ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), 59 ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
57 ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), 60 ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
58 ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), 61 ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
59 _ => import_edit.import_path.segments().last()?.to_string(), 62 _ => item_name(ctx.db(), import_edit.import.original_item)?.to_string(),
60 }; 63 };
61 Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| { 64 Render::new(ctx).render_resolution(local_name, Some(import_edit), &resolution).map(
62 item.completion_kind = CompletionKind::Magic; 65 |mut item| {
63 item 66 item.completion_kind = CompletionKind::Magic;
64 }) 67 item
68 },
69 )
65} 70}
66 71
67/// Interface for data and methods required for items rendering. 72/// Interface for data and methods required for items rendering.