diff options
Diffstat (limited to 'crates/ide_completion/src')
-rw-r--r-- | crates/ide_completion/src/completions/dot.rs | 6 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/flyimport.rs | 280 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/keyword.rs | 14 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/postfix.rs | 14 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/qualified_path.rs | 6 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/unqualified_path.rs | 12 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 4 | ||||
-rw-r--r-- | crates/ide_completion/src/item.rs | 134 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 33 | ||||
-rw-r--r-- | crates/ide_completion/src/render.rs | 218 | ||||
-rw-r--r-- | crates/ide_completion/src/render/builder_ext.rs | 9 | ||||
-rw-r--r-- | crates/ide_completion/src/render/enum_variant.rs | 7 | ||||
-rw-r--r-- | crates/ide_completion/src/render/function.rs | 11 | ||||
-rw-r--r-- | crates/ide_completion/src/render/macro_.rs | 7 | ||||
-rw-r--r-- | crates/ide_completion/src/test_utils.rs | 3 |
15 files changed, 502 insertions, 256 deletions
diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index 084d7721d..5ee9a9f07 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs | |||
@@ -2,7 +2,6 @@ | |||
2 | 2 | ||
3 | use hir::{HasVisibility, Type}; | 3 | use hir::{HasVisibility, Type}; |
4 | use rustc_hash::FxHashSet; | 4 | use rustc_hash::FxHashSet; |
5 | use test_utils::mark; | ||
6 | 5 | ||
7 | use crate::{context::CompletionContext, Completions}; | 6 | use crate::{context::CompletionContext, Completions}; |
8 | 7 | ||
@@ -19,7 +18,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | |||
19 | }; | 18 | }; |
20 | 19 | ||
21 | if ctx.is_call { | 20 | if ctx.is_call { |
22 | mark::hit!(test_no_struct_field_completion_for_method_call); | 21 | cov_mark::hit!(test_no_struct_field_completion_for_method_call); |
23 | } else { | 22 | } else { |
24 | complete_fields(acc, ctx, &receiver_ty); | 23 | complete_fields(acc, ctx, &receiver_ty); |
25 | } | 24 | } |
@@ -62,7 +61,6 @@ fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &T | |||
62 | #[cfg(test)] | 61 | #[cfg(test)] |
63 | mod tests { | 62 | mod tests { |
64 | use expect_test::{expect, Expect}; | 63 | use expect_test::{expect, Expect}; |
65 | use test_utils::mark; | ||
66 | 64 | ||
67 | use crate::{test_utils::completion_list, CompletionKind}; | 65 | use crate::{test_utils::completion_list, CompletionKind}; |
68 | 66 | ||
@@ -122,7 +120,7 @@ impl A { | |||
122 | 120 | ||
123 | #[test] | 121 | #[test] |
124 | fn test_no_struct_field_completion_for_method_call() { | 122 | fn test_no_struct_field_completion_for_method_call() { |
125 | mark::check!(test_no_struct_field_completion_for_method_call); | 123 | cov_mark::check!(test_no_struct_field_completion_for_method_call); |
126 | check( | 124 | check( |
127 | r#" | 125 | r#" |
128 | struct A { the_field: u32 } | 126 | struct A { the_field: u32 } |
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index da8375af9..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,14 +88,13 @@ | |||
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 | ||
51 | use hir::{AsAssocItem, ModPath, ScopeDef}; | 91 | use hir::ModPath; |
52 | use ide_db::helpers::{ | 92 | use ide_db::helpers::{ |
53 | import_assets::{ImportAssets, ImportCandidate}, | 93 | import_assets::{ImportAssets, ImportCandidate}, |
54 | insert_use::ImportScope, | 94 | insert_use::ImportScope, |
55 | }; | 95 | }; |
56 | use rustc_hash::FxHashSet; | 96 | use itertools::Itertools; |
57 | use syntax::{AstNode, SyntaxNode, T}; | 97 | use syntax::{AstNode, SyntaxNode, T}; |
58 | use test_utils::mark; | ||
59 | 98 | ||
60 | use crate::{ | 99 | use crate::{ |
61 | context::CompletionContext, | 100 | context::CompletionContext, |
@@ -93,50 +132,26 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) | |||
93 | &ctx.sema, | 132 | &ctx.sema, |
94 | )?; | 133 | )?; |
95 | 134 | ||
96 | let scope_definitions = scope_definitions(ctx); | 135 | acc.add_all( |
97 | let mut all_mod_paths = import_assets | 136 | import_assets |
98 | .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) | 137 | .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) |
99 | .into_iter() | 138 | .into_iter() |
100 | .map(|(mod_path, item_in_ns)| { | 139 | .sorted_by_key(|located_import| { |
101 | let scope_item = match item_in_ns { | 140 | compute_fuzzy_completion_order_key( |
102 | hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()), | 141 | &located_import.import_path, |
103 | hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()), | 142 | &user_input_lowercased, |
104 | hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()), | 143 | ) |
105 | }; | 144 | }) |
106 | (mod_path, scope_item) | 145 | .filter_map(|import| { |
107 | }) | 146 | render_resolution_with_import( |
108 | .filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def)) | 147 | RenderContext::new(ctx), |
109 | .collect::<Vec<_>>(); | 148 | ImportEdit { import, scope: import_scope.clone() }, |
110 | all_mod_paths.sort_by_cached_key(|(mod_path, _)| { | 149 | ) |
111 | compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) | 150 | }), |
112 | }); | 151 | ); |
113 | |||
114 | acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| { | ||
115 | let import_for_trait_assoc_item = match definition { | ||
116 | ScopeDef::ModuleDef(module_def) => module_def | ||
117 | .as_assoc_item(ctx.db) | ||
118 | .and_then(|assoc| assoc.containing_trait(ctx.db)) | ||
119 | .is_some(), | ||
120 | _ => false, | ||
121 | }; | ||
122 | let import_edit = ImportEdit { | ||
123 | import_path, | ||
124 | import_scope: import_scope.clone(), | ||
125 | import_for_trait_assoc_item, | ||
126 | }; | ||
127 | render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition) | ||
128 | })); | ||
129 | Some(()) | 152 | Some(()) |
130 | } | 153 | } |
131 | 154 | ||
132 | fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> { | ||
133 | let mut scope_definitions = FxHashSet::default(); | ||
134 | ctx.scope.process_all_names(&mut |_, scope_def| { | ||
135 | scope_definitions.insert(scope_def); | ||
136 | }); | ||
137 | scope_definitions | ||
138 | } | ||
139 | |||
140 | pub(crate) fn position_for_import<'a>( | 155 | pub(crate) fn position_for_import<'a>( |
141 | ctx: &'a CompletionContext, | 156 | ctx: &'a CompletionContext, |
142 | import_candidate: Option<&ImportCandidate>, | 157 | import_candidate: Option<&ImportCandidate>, |
@@ -161,23 +176,30 @@ fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAs | |||
161 | current_module, | 176 | current_module, |
162 | ctx.sema.type_of_expr(dot_receiver)?, | 177 | ctx.sema.type_of_expr(dot_receiver)?, |
163 | fuzzy_name, | 178 | fuzzy_name, |
179 | dot_receiver.syntax().clone(), | ||
164 | ) | 180 | ) |
165 | } else { | 181 | } else { |
166 | 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 | }; | ||
167 | let assets_for_path = ImportAssets::for_fuzzy_path( | 188 | let assets_for_path = ImportAssets::for_fuzzy_path( |
168 | current_module, | 189 | current_module, |
169 | ctx.path_qual.clone(), | 190 | ctx.path_qual.clone(), |
170 | fuzzy_name, | 191 | fuzzy_name, |
171 | &ctx.sema, | 192 | &ctx.sema, |
172 | ); | 193 | approximate_node, |
194 | )?; | ||
173 | 195 | ||
174 | if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_)) | 196 | if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_)) |
175 | && fuzzy_name_length < 2 | 197 | && fuzzy_name_length < 2 |
176 | { | 198 | { |
177 | mark::hit!(ignore_short_input_for_path); | 199 | cov_mark::hit!(ignore_short_input_for_path); |
178 | None | 200 | None |
179 | } else { | 201 | } else { |
180 | assets_for_path | 202 | Some(assets_for_path) |
181 | } | 203 | } |
182 | } | 204 | } |
183 | } | 205 | } |
@@ -186,12 +208,12 @@ fn compute_fuzzy_completion_order_key( | |||
186 | proposed_mod_path: &ModPath, | 208 | proposed_mod_path: &ModPath, |
187 | user_input_lowercased: &str, | 209 | user_input_lowercased: &str, |
188 | ) -> usize { | 210 | ) -> usize { |
189 | mark::hit!(certain_fuzzy_order_test); | 211 | cov_mark::hit!(certain_fuzzy_order_test); |
190 | let proposed_import_name = match proposed_mod_path.segments().last() { | 212 | let import_name = match proposed_mod_path.segments().last() { |
191 | Some(name) => name.to_string().to_lowercase(), | 213 | Some(name) => name.to_string().to_lowercase(), |
192 | None => return usize::MAX, | 214 | None => return usize::MAX, |
193 | }; | 215 | }; |
194 | match proposed_import_name.match_indices(user_input_lowercased).next() { | 216 | match import_name.match_indices(user_input_lowercased).next() { |
195 | Some((first_matching_index, _)) => first_matching_index, | 217 | Some((first_matching_index, _)) => first_matching_index, |
196 | None => usize::MAX, | 218 | None => usize::MAX, |
197 | } | 219 | } |
@@ -200,7 +222,6 @@ fn compute_fuzzy_completion_order_key( | |||
200 | #[cfg(test)] | 222 | #[cfg(test)] |
201 | mod tests { | 223 | mod tests { |
202 | use expect_test::{expect, Expect}; | 224 | use expect_test::{expect, Expect}; |
203 | use test_utils::mark; | ||
204 | 225 | ||
205 | use crate::{ | 226 | use crate::{ |
206 | item::CompletionKind, | 227 | item::CompletionKind, |
@@ -295,7 +316,7 @@ fn main() { | |||
295 | 316 | ||
296 | #[test] | 317 | #[test] |
297 | fn short_paths_are_ignored() { | 318 | fn short_paths_are_ignored() { |
298 | mark::check!(ignore_short_input_for_path); | 319 | cov_mark::check!(ignore_short_input_for_path); |
299 | 320 | ||
300 | check( | 321 | check( |
301 | r#" | 322 | r#" |
@@ -319,7 +340,7 @@ fn main() { | |||
319 | 340 | ||
320 | #[test] | 341 | #[test] |
321 | fn fuzzy_completions_come_in_specific_order() { | 342 | fn fuzzy_completions_come_in_specific_order() { |
322 | mark::check!(certain_fuzzy_order_test); | 343 | cov_mark::check!(certain_fuzzy_order_test); |
323 | check( | 344 | check( |
324 | r#" | 345 | r#" |
325 | //- /lib.rs crate:dep | 346 | //- /lib.rs crate:dep |
@@ -775,4 +796,155 @@ fn main() { | |||
775 | }"#, | 796 | }"#, |
776 | ); | 797 | ); |
777 | } | 798 | } |
799 | |||
800 | #[test] | ||
801 | fn unresolved_qualifier() { | ||
802 | let fixture = r#" | ||
803 | mod foo { | ||
804 | pub mod bar { | ||
805 | pub mod baz { | ||
806 | pub struct Item; | ||
807 | } | ||
808 | } | ||
809 | } | ||
810 | |||
811 | fn 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#" | ||
845 | mod foo { | ||
846 | pub struct Item; | ||
847 | |||
848 | impl Item { | ||
849 | pub const TEST_ASSOC: usize = 3; | ||
850 | } | ||
851 | } | ||
852 | |||
853 | fn 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#" | ||
868 | use foo::Item; | ||
869 | |||
870 | mod foo { | ||
871 | pub struct Item; | ||
872 | |||
873 | impl Item { | ||
874 | pub const TEST_ASSOC: usize = 3; | ||
875 | } | ||
876 | } | ||
877 | |||
878 | fn main() { | ||
879 | Item::TEST_ASSOC | ||
880 | }"#, | ||
881 | ); | ||
882 | } | ||
883 | |||
884 | #[test] | ||
885 | fn unresolved_assoc_item_container_with_path() { | ||
886 | let fixture = r#" | ||
887 | mod foo { | ||
888 | pub mod bar { | ||
889 | pub struct Item; | ||
890 | |||
891 | impl Item { | ||
892 | pub const TEST_ASSOC: usize = 3; | ||
893 | } | ||
894 | } | ||
895 | } | ||
896 | |||
897 | fn 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#" | ||
912 | use foo::bar; | ||
913 | |||
914 | mod foo { | ||
915 | pub mod bar { | ||
916 | pub struct Item; | ||
917 | |||
918 | impl Item { | ||
919 | pub const TEST_ASSOC: usize = 3; | ||
920 | } | ||
921 | } | ||
922 | } | ||
923 | |||
924 | fn main() { | ||
925 | bar::Item::TEST_ASSOC | ||
926 | }"#, | ||
927 | ); | ||
928 | } | ||
929 | |||
930 | #[test] | ||
931 | fn fuzzy_unresolved_path() { | ||
932 | check( | ||
933 | r#" | ||
934 | mod foo { | ||
935 | pub mod bar { | ||
936 | pub struct Item; | ||
937 | |||
938 | impl Item { | ||
939 | pub const TEST_ASSOC: usize = 3; | ||
940 | } | ||
941 | } | ||
942 | } | ||
943 | |||
944 | fn main() { | ||
945 | bar::Ass$0 | ||
946 | }"#, | ||
947 | expect![[]], | ||
948 | ) | ||
949 | } | ||
778 | } | 950 | } |
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index 03c6dd454..80aa9fb06 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs | |||
@@ -3,7 +3,6 @@ | |||
3 | use std::iter; | 3 | use std::iter; |
4 | 4 | ||
5 | use syntax::SyntaxKind; | 5 | use syntax::SyntaxKind; |
6 | use test_utils::mark; | ||
7 | 6 | ||
8 | use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; | 7 | use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; |
9 | 8 | ||
@@ -47,11 +46,11 @@ pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC | |||
47 | 46 | ||
48 | pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | 47 | pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { |
49 | if ctx.token.kind() == SyntaxKind::COMMENT { | 48 | if ctx.token.kind() == SyntaxKind::COMMENT { |
50 | mark::hit!(no_keyword_completion_in_comments); | 49 | cov_mark::hit!(no_keyword_completion_in_comments); |
51 | return; | 50 | return; |
52 | } | 51 | } |
53 | if ctx.record_lit_syntax.is_some() { | 52 | if ctx.record_lit_syntax.is_some() { |
54 | mark::hit!(no_keyword_completion_in_record_lit); | 53 | cov_mark::hit!(no_keyword_completion_in_record_lit); |
55 | return; | 54 | return; |
56 | } | 55 | } |
57 | 56 | ||
@@ -172,7 +171,7 @@ fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet | |||
172 | Some(cap) => { | 171 | Some(cap) => { |
173 | let tmp; | 172 | let tmp; |
174 | let snippet = if snippet.ends_with('}') && ctx.incomplete_let { | 173 | let snippet = if snippet.ends_with('}') && ctx.incomplete_let { |
175 | mark::hit!(let_semi); | 174 | cov_mark::hit!(let_semi); |
176 | tmp = format!("{};", snippet); | 175 | tmp = format!("{};", snippet); |
177 | &tmp | 176 | &tmp |
178 | } else { | 177 | } else { |
@@ -188,7 +187,6 @@ fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet | |||
188 | #[cfg(test)] | 187 | #[cfg(test)] |
189 | mod tests { | 188 | mod tests { |
190 | use expect_test::{expect, Expect}; | 189 | use expect_test::{expect, Expect}; |
191 | use test_utils::mark; | ||
192 | 190 | ||
193 | use crate::{ | 191 | use crate::{ |
194 | test_utils::{check_edit, completion_list}, | 192 | test_utils::{check_edit, completion_list}, |
@@ -494,7 +492,7 @@ fn quux() -> i32 { | |||
494 | 492 | ||
495 | #[test] | 493 | #[test] |
496 | fn no_keyword_completion_in_comments() { | 494 | fn no_keyword_completion_in_comments() { |
497 | mark::check!(no_keyword_completion_in_comments); | 495 | cov_mark::check!(no_keyword_completion_in_comments); |
498 | check( | 496 | check( |
499 | r#" | 497 | r#" |
500 | fn test() { | 498 | fn test() { |
@@ -599,7 +597,7 @@ struct Foo { | |||
599 | 597 | ||
600 | #[test] | 598 | #[test] |
601 | fn skip_struct_initializer() { | 599 | fn skip_struct_initializer() { |
602 | mark::check!(no_keyword_completion_in_record_lit); | 600 | cov_mark::check!(no_keyword_completion_in_record_lit); |
603 | check( | 601 | check( |
604 | r#" | 602 | r#" |
605 | struct Foo { | 603 | struct Foo { |
@@ -643,7 +641,7 @@ fn foo() { | |||
643 | 641 | ||
644 | #[test] | 642 | #[test] |
645 | fn let_semi() { | 643 | fn let_semi() { |
646 | mark::check!(let_semi); | 644 | cov_mark::check!(let_semi); |
647 | check_edit( | 645 | check_edit( |
648 | "match", | 646 | "match", |
649 | r#" | 647 | r#" |
diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs index 9c34ed0b6..d45ad7944 100644 --- a/crates/ide_completion/src/completions/postfix.rs +++ b/crates/ide_completion/src/completions/postfix.rs | |||
@@ -187,6 +187,16 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
187 | ctx, | 187 | ctx, |
188 | cap, | 188 | cap, |
189 | &dot_receiver, | 189 | &dot_receiver, |
190 | "err", | ||
191 | "Err(expr)", | ||
192 | &format!("Err({})", receiver_text), | ||
193 | ) | ||
194 | .add_to(acc); | ||
195 | |||
196 | postfix_snippet( | ||
197 | ctx, | ||
198 | cap, | ||
199 | &dot_receiver, | ||
190 | "some", | 200 | "some", |
191 | "Some(expr)", | 201 | "Some(expr)", |
192 | &format!("Some({})", receiver_text), | 202 | &format!("Some({})", receiver_text), |
@@ -325,6 +335,7 @@ fn main() { | |||
325 | sn match match expr {} | 335 | sn match match expr {} |
326 | sn box Box::new(expr) | 336 | sn box Box::new(expr) |
327 | sn ok Ok(expr) | 337 | sn ok Ok(expr) |
338 | sn err Err(expr) | ||
328 | sn some Some(expr) | 339 | sn some Some(expr) |
329 | sn dbg dbg!(expr) | 340 | sn dbg dbg!(expr) |
330 | sn dbgr dbg!(&expr) | 341 | sn dbgr dbg!(&expr) |
@@ -357,6 +368,7 @@ fn main() { | |||
357 | sn match match expr {} | 368 | sn match match expr {} |
358 | sn box Box::new(expr) | 369 | sn box Box::new(expr) |
359 | sn ok Ok(expr) | 370 | sn ok Ok(expr) |
371 | sn err Err(expr) | ||
360 | sn some Some(expr) | 372 | sn some Some(expr) |
361 | sn dbg dbg!(expr) | 373 | sn dbg dbg!(expr) |
362 | sn dbgr dbg!(&expr) | 374 | sn dbgr dbg!(&expr) |
@@ -380,6 +392,7 @@ fn main() { | |||
380 | sn match match expr {} | 392 | sn match match expr {} |
381 | sn box Box::new(expr) | 393 | sn box Box::new(expr) |
382 | sn ok Ok(expr) | 394 | sn ok Ok(expr) |
395 | sn err Err(expr) | ||
383 | sn some Some(expr) | 396 | sn some Some(expr) |
384 | sn dbg dbg!(expr) | 397 | sn dbg dbg!(expr) |
385 | sn dbgr dbg!(&expr) | 398 | sn dbgr dbg!(&expr) |
@@ -408,6 +421,7 @@ fn main() { | |||
408 | sn match match expr {} | 421 | sn match match expr {} |
409 | sn box Box::new(expr) | 422 | sn box Box::new(expr) |
410 | sn ok Ok(expr) | 423 | sn ok Ok(expr) |
424 | sn err Err(expr) | ||
411 | sn some Some(expr) | 425 | sn some Some(expr) |
412 | sn dbg dbg!(expr) | 426 | sn dbg dbg!(expr) |
413 | sn dbgr dbg!(&expr) | 427 | sn dbgr dbg!(&expr) |
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index 72fb757b1..df74b739e 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs | |||
@@ -3,7 +3,6 @@ | |||
3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; | 3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; |
4 | use rustc_hash::FxHashSet; | 4 | use rustc_hash::FxHashSet; |
5 | use syntax::AstNode; | 5 | use syntax::AstNode; |
6 | use test_utils::mark; | ||
7 | 6 | ||
8 | use crate::{CompletionContext, Completions}; | 7 | use crate::{CompletionContext, Completions}; |
9 | 8 | ||
@@ -39,7 +38,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
39 | if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { | 38 | if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { |
40 | if name_ref.syntax().text() == name.to_string().as_str() { | 39 | if name_ref.syntax().text() == name.to_string().as_str() { |
41 | // for `use self::foo$0`, don't suggest `foo` as a completion | 40 | // for `use self::foo$0`, don't suggest `foo` as a completion |
42 | mark::hit!(dont_complete_current_use); | 41 | cov_mark::hit!(dont_complete_current_use); |
43 | continue; | 42 | continue; |
44 | } | 43 | } |
45 | } | 44 | } |
@@ -155,7 +154,6 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
155 | #[cfg(test)] | 154 | #[cfg(test)] |
156 | mod tests { | 155 | mod tests { |
157 | use expect_test::{expect, Expect}; | 156 | use expect_test::{expect, Expect}; |
158 | use test_utils::mark; | ||
159 | 157 | ||
160 | use crate::{ | 158 | use crate::{ |
161 | test_utils::{check_edit, completion_list}, | 159 | test_utils::{check_edit, completion_list}, |
@@ -174,7 +172,7 @@ mod tests { | |||
174 | 172 | ||
175 | #[test] | 173 | #[test] |
176 | fn dont_complete_current_use() { | 174 | fn dont_complete_current_use() { |
177 | mark::check!(dont_complete_current_use); | 175 | cov_mark::check!(dont_complete_current_use); |
178 | check(r#"use self::foo$0;"#, expect![[""]]); | 176 | check(r#"use self::foo$0;"#, expect![[""]]); |
179 | } | 177 | } |
180 | 178 | ||
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index e9d0ff665..044dfd160 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs | |||
@@ -2,7 +2,6 @@ | |||
2 | 2 | ||
3 | use hir::ScopeDef; | 3 | use hir::ScopeDef; |
4 | use syntax::AstNode; | 4 | use syntax::AstNode; |
5 | use test_utils::mark; | ||
6 | 5 | ||
7 | use crate::{CompletionContext, Completions}; | 6 | use crate::{CompletionContext, Completions}; |
8 | 7 | ||
@@ -30,13 +29,13 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
30 | 29 | ||
31 | ctx.scope.process_all_names(&mut |name, res| { | 30 | ctx.scope.process_all_names(&mut |name, res| { |
32 | if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res { | 31 | if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res { |
33 | mark::hit!(skip_lifetime_completion); | 32 | cov_mark::hit!(skip_lifetime_completion); |
34 | return; | 33 | return; |
35 | } | 34 | } |
36 | if ctx.use_item_syntax.is_some() { | 35 | if ctx.use_item_syntax.is_some() { |
37 | if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { | 36 | if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { |
38 | if name_ref.syntax().text() == name.to_string().as_str() { | 37 | if name_ref.syntax().text() == name.to_string().as_str() { |
39 | mark::hit!(self_fulfilling_completion); | 38 | cov_mark::hit!(self_fulfilling_completion); |
40 | return; | 39 | return; |
41 | } | 40 | } |
42 | } | 41 | } |
@@ -48,7 +47,6 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
48 | #[cfg(test)] | 47 | #[cfg(test)] |
49 | mod tests { | 48 | mod tests { |
50 | use expect_test::{expect, Expect}; | 49 | use expect_test::{expect, Expect}; |
51 | use test_utils::mark; | ||
52 | 50 | ||
53 | use crate::{ | 51 | use crate::{ |
54 | test_utils::{check_edit, completion_list_with_config, TEST_CONFIG}, | 52 | test_utils::{check_edit, completion_list_with_config, TEST_CONFIG}, |
@@ -66,7 +64,7 @@ mod tests { | |||
66 | 64 | ||
67 | #[test] | 65 | #[test] |
68 | fn self_fulfilling_completion() { | 66 | fn self_fulfilling_completion() { |
69 | mark::check!(self_fulfilling_completion); | 67 | cov_mark::check!(self_fulfilling_completion); |
70 | check( | 68 | check( |
71 | r#" | 69 | r#" |
72 | use foo$0 | 70 | use foo$0 |
@@ -185,7 +183,7 @@ fn quux() { | |||
185 | 183 | ||
186 | #[test] | 184 | #[test] |
187 | fn completes_if_prefix_is_keyword() { | 185 | fn completes_if_prefix_is_keyword() { |
188 | mark::check!(completes_if_prefix_is_keyword); | 186 | cov_mark::check!(completes_if_prefix_is_keyword); |
189 | check_edit( | 187 | check_edit( |
190 | "wherewolf", | 188 | "wherewolf", |
191 | r#" | 189 | r#" |
@@ -223,7 +221,7 @@ fn main() { | |||
223 | 221 | ||
224 | #[test] | 222 | #[test] |
225 | fn does_not_complete_lifetimes() { | 223 | fn does_not_complete_lifetimes() { |
226 | mark::check!(skip_lifetime_completion); | 224 | cov_mark::check!(skip_lifetime_completion); |
227 | check( | 225 | check( |
228 | r#"fn quux<'a>() { $0 }"#, | 226 | r#"fn quux<'a>() { $0 }"#, |
229 | expect![[r#" | 227 | expect![[r#" |
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 3db357855..17d9a3adf 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -7,7 +7,7 @@ use syntax::{ | |||
7 | algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode, | 7 | algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode, |
8 | SyntaxToken, TextRange, TextSize, | 8 | SyntaxToken, TextRange, TextSize, |
9 | }; | 9 | }; |
10 | use test_utils::mark; | 10 | |
11 | use text_edit::Indel; | 11 | use text_edit::Indel; |
12 | 12 | ||
13 | use crate::{ | 13 | use crate::{ |
@@ -240,7 +240,7 @@ impl<'a> CompletionContext<'a> { | |||
240 | // check kind of macro-expanded token, but use range of original token | 240 | // check kind of macro-expanded token, but use range of original token |
241 | let kind = self.token.kind(); | 241 | let kind = self.token.kind(); |
242 | if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { | 242 | if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { |
243 | mark::hit!(completes_if_prefix_is_keyword); | 243 | cov_mark::hit!(completes_if_prefix_is_keyword); |
244 | self.original_token.text_range() | 244 | self.original_token.text_range() |
245 | } else { | 245 | } else { |
246 | TextRange::empty(self.position.offset) | 246 | TextRange::empty(self.position.offset) |
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 884711f11..14afec603 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs | |||
@@ -2,15 +2,16 @@ | |||
2 | 2 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use hir::{Documentation, ModPath, Mutability}; | 5 | use hir::{Documentation, Mutability}; |
6 | use ide_db::{ | 6 | use ide_db::{ |
7 | helpers::{ | 7 | helpers::{ |
8 | insert_use::{self, ImportScope, MergeBehavior}, | 8 | import_assets::LocatedImport, |
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 | }; |
13 | use stdx::{impl_from, never}; | 14 | use stdx::{format_to, impl_from, never}; |
14 | use syntax::{algo, TextRange}; | 15 | use syntax::{algo, TextRange}; |
15 | use text_edit::TextEdit; | 16 | use text_edit::TextEdit; |
16 | 17 | ||
@@ -62,12 +63,18 @@ pub struct CompletionItem { | |||
62 | /// after completion. | 63 | /// after completion. |
63 | trigger_call_info: bool, | 64 | trigger_call_info: bool, |
64 | 65 | ||
65 | /// Score is useful to pre select or display in better order completion items | 66 | /// We use this to sort completion. Relevance records facts like "do the |
66 | score: Option<CompletionScore>, | 67 | /// types align precisely?". We can't sort by relevances directly, they are |
68 | /// only partially ordered. | ||
69 | /// | ||
70 | /// Note that Relevance ignores fuzzy match score. We compute Relevance for | ||
71 | /// all possible items, and then separately build an ordered completion list | ||
72 | /// based on relevance and fuzzy matching with the already typed identifier. | ||
73 | relevance: Relevance, | ||
67 | 74 | ||
68 | /// Indicates that a reference or mutable reference to this variable is a | 75 | /// Indicates that a reference or mutable reference to this variable is a |
69 | /// possible match. | 76 | /// possible match. |
70 | ref_match: Option<(Mutability, CompletionScore)>, | 77 | ref_match: Option<Mutability>, |
71 | 78 | ||
72 | /// The import data to add to completion's edits. | 79 | /// The import data to add to completion's edits. |
73 | import_to_add: Option<ImportEdit>, | 80 | import_to_add: Option<ImportEdit>, |
@@ -100,8 +107,11 @@ impl fmt::Debug for CompletionItem { | |||
100 | if self.deprecated { | 107 | if self.deprecated { |
101 | s.field("deprecated", &true); | 108 | s.field("deprecated", &true); |
102 | } | 109 | } |
103 | if let Some(score) = &self.score { | 110 | if self.relevance.is_relevant() { |
104 | s.field("score", score); | 111 | s.field("relevance", &self.relevance); |
112 | } | ||
113 | if let Some(mutability) = &self.ref_match { | ||
114 | s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref())); | ||
105 | } | 115 | } |
106 | if self.trigger_call_info { | 116 | if self.trigger_call_info { |
107 | s.field("trigger_call_info", &true); | 117 | s.field("trigger_call_info", &true); |
@@ -118,6 +128,36 @@ pub enum CompletionScore { | |||
118 | TypeAndNameMatch, | 128 | TypeAndNameMatch, |
119 | } | 129 | } |
120 | 130 | ||
131 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)] | ||
132 | pub struct Relevance { | ||
133 | /// This is set in cases like these: | ||
134 | /// | ||
135 | /// ``` | ||
136 | /// fn f(spam: String) {} | ||
137 | /// fn main { | ||
138 | /// let spam = 92; | ||
139 | /// f($0) // name of local matches the name of param | ||
140 | /// } | ||
141 | /// ``` | ||
142 | pub exact_name_match: bool, | ||
143 | /// This is set in cases like these: | ||
144 | /// | ||
145 | /// ``` | ||
146 | /// fn f(spam: String) {} | ||
147 | /// fn main { | ||
148 | /// let foo = String::new(); | ||
149 | /// f($0) // type of local matches the type of param | ||
150 | /// } | ||
151 | /// ``` | ||
152 | pub exact_type_match: bool, | ||
153 | } | ||
154 | |||
155 | impl Relevance { | ||
156 | pub fn is_relevant(&self) -> bool { | ||
157 | self != &Relevance::default() | ||
158 | } | ||
159 | } | ||
160 | |||
121 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 161 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
122 | pub enum CompletionItemKind { | 162 | pub enum CompletionItemKind { |
123 | SymbolKind(SymbolKind), | 163 | SymbolKind(SymbolKind), |
@@ -207,9 +247,9 @@ impl CompletionItem { | |||
207 | lookup: None, | 247 | lookup: None, |
208 | kind: None, | 248 | kind: None, |
209 | text_edit: None, | 249 | text_edit: None, |
210 | deprecated: None, | 250 | deprecated: false, |
211 | trigger_call_info: None, | 251 | trigger_call_info: None, |
212 | score: None, | 252 | relevance: Relevance::default(), |
213 | ref_match: None, | 253 | ref_match: None, |
214 | import_to_add: None, | 254 | import_to_add: None, |
215 | } | 255 | } |
@@ -252,15 +292,15 @@ impl CompletionItem { | |||
252 | self.deprecated | 292 | self.deprecated |
253 | } | 293 | } |
254 | 294 | ||
255 | pub fn score(&self) -> Option<CompletionScore> { | 295 | pub fn relevance(&self) -> Relevance { |
256 | self.score | 296 | self.relevance |
257 | } | 297 | } |
258 | 298 | ||
259 | pub fn trigger_call_info(&self) -> bool { | 299 | pub fn trigger_call_info(&self) -> bool { |
260 | self.trigger_call_info | 300 | self.trigger_call_info |
261 | } | 301 | } |
262 | 302 | ||
263 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { | 303 | pub fn ref_match(&self) -> Option<Mutability> { |
264 | self.ref_match | 304 | self.ref_match |
265 | } | 305 | } |
266 | 306 | ||
@@ -272,22 +312,18 @@ impl CompletionItem { | |||
272 | /// An extra import to add after the completion is applied. | 312 | /// An extra import to add after the completion is applied. |
273 | #[derive(Debug, Clone)] | 313 | #[derive(Debug, Clone)] |
274 | pub struct ImportEdit { | 314 | pub struct ImportEdit { |
275 | pub import_path: ModPath, | 315 | pub import: LocatedImport, |
276 | pub import_scope: ImportScope, | 316 | pub scope: ImportScope, |
277 | pub import_for_trait_assoc_item: bool, | ||
278 | } | 317 | } |
279 | 318 | ||
280 | impl ImportEdit { | 319 | impl ImportEdit { |
281 | /// Attempts to insert the import to the given scope, producing a text edit. | 320 | /// Attempts to insert the import to the given scope, producing a text edit. |
282 | /// May return no edit in edge cases, such as scope already containing the import. | 321 | /// May return no edit in edge cases, such as scope already containing the import. |
283 | pub fn to_text_edit(&self, merge_behavior: Option<MergeBehavior>) -> Option<TextEdit> { | 322 | pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> { |
284 | let _p = profile::span("ImportEdit::to_text_edit"); | 323 | let _p = profile::span("ImportEdit::to_text_edit"); |
285 | 324 | ||
286 | let rewriter = insert_use::insert_use( | 325 | let rewriter = |
287 | &self.import_scope, | 326 | insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg); |
288 | mod_path_to_ast(&self.import_path), | ||
289 | merge_behavior, | ||
290 | ); | ||
291 | let old_ast = rewriter.rewrite_root()?; | 327 | let old_ast = rewriter.rewrite_root()?; |
292 | let mut import_insert = TextEdit::builder(); | 328 | let mut import_insert = TextEdit::builder(); |
293 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); | 329 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); |
@@ -311,10 +347,10 @@ pub(crate) struct Builder { | |||
311 | lookup: Option<String>, | 347 | lookup: Option<String>, |
312 | kind: Option<CompletionItemKind>, | 348 | kind: Option<CompletionItemKind>, |
313 | text_edit: Option<TextEdit>, | 349 | text_edit: Option<TextEdit>, |
314 | deprecated: Option<bool>, | 350 | deprecated: bool, |
315 | trigger_call_info: Option<bool>, | 351 | trigger_call_info: Option<bool>, |
316 | score: Option<CompletionScore>, | 352 | relevance: Relevance, |
317 | ref_match: Option<(Mutability, CompletionScore)>, | 353 | ref_match: Option<Mutability>, |
318 | } | 354 | } |
319 | 355 | ||
320 | impl Builder { | 356 | impl Builder { |
@@ -325,20 +361,19 @@ impl Builder { | |||
325 | let mut lookup = self.lookup; | 361 | let mut lookup = self.lookup; |
326 | let mut insert_text = self.insert_text; | 362 | let mut insert_text = self.insert_text; |
327 | 363 | ||
328 | if let Some(import_to_add) = self.import_to_add.as_ref() { | 364 | if let Some(original_path) = self |
329 | if import_to_add.import_for_trait_assoc_item { | 365 | .import_to_add |
330 | lookup = lookup.or_else(|| Some(label.clone())); | 366 | .as_ref() |
331 | insert_text = insert_text.or_else(|| Some(label.clone())); | 367 | .and_then(|import_edit| import_edit.import.original_path.as_ref()) |
332 | label = format!("{} ({})", label, import_to_add.import_path); | 368 | { |
369 | lookup = lookup.or_else(|| Some(label.clone())); | ||
370 | insert_text = insert_text.or_else(|| Some(label.clone())); | ||
371 | |||
372 | let original_path_label = original_path.to_string(); | ||
373 | if original_path_label.ends_with(&label) { | ||
374 | label = original_path_label; | ||
333 | } else { | 375 | } else { |
334 | let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); | 376 | format_to!(label, " ({})", original_path) |
335 | let _ = import_path_without_last_segment.pop_segment(); | ||
336 | |||
337 | if !import_path_without_last_segment.segments().is_empty() { | ||
338 | lookup = lookup.or_else(|| Some(label.clone())); | ||
339 | insert_text = insert_text.or_else(|| Some(label.clone())); | ||
340 | label = format!("{}::{}", import_path_without_last_segment, label); | ||
341 | } | ||
342 | } | 377 | } |
343 | } | 378 | } |
344 | 379 | ||
@@ -359,9 +394,9 @@ impl Builder { | |||
359 | lookup, | 394 | lookup, |
360 | kind: self.kind, | 395 | kind: self.kind, |
361 | completion_kind: self.completion_kind, | 396 | completion_kind: self.completion_kind, |
362 | deprecated: self.deprecated.unwrap_or(false), | 397 | deprecated: self.deprecated, |
363 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | 398 | trigger_call_info: self.trigger_call_info.unwrap_or(false), |
364 | score: self.score, | 399 | relevance: self.relevance, |
365 | ref_match: self.ref_match, | 400 | ref_match: self.ref_match, |
366 | import_to_add: self.import_to_add, | 401 | import_to_add: self.import_to_add, |
367 | } | 402 | } |
@@ -419,11 +454,11 @@ impl Builder { | |||
419 | self | 454 | self |
420 | } | 455 | } |
421 | pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { | 456 | pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { |
422 | self.deprecated = Some(deprecated); | 457 | self.deprecated = deprecated; |
423 | self | 458 | self |
424 | } | 459 | } |
425 | pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder { | 460 | pub(crate) fn set_relevance(mut self, relevance: Relevance) -> Builder { |
426 | self.score = Some(score); | 461 | self.relevance = relevance; |
427 | self | 462 | self |
428 | } | 463 | } |
429 | pub(crate) fn trigger_call_info(mut self) -> Builder { | 464 | pub(crate) fn trigger_call_info(mut self) -> Builder { |
@@ -434,17 +469,8 @@ impl Builder { | |||
434 | self.import_to_add = import_to_add; | 469 | self.import_to_add = import_to_add; |
435 | self | 470 | self |
436 | } | 471 | } |
437 | pub(crate) fn set_ref_match( | 472 | pub(crate) fn ref_match(mut self, mutability: Mutability) -> Builder { |
438 | mut self, | 473 | self.ref_match = Some(mutability); |
439 | ref_match: Option<(Mutability, CompletionScore)>, | ||
440 | ) -> Builder { | ||
441 | self.ref_match = ref_match; | ||
442 | self | 474 | self |
443 | } | 475 | } |
444 | } | 476 | } |
445 | |||
446 | impl<'a> Into<CompletionItem> for Builder { | ||
447 | fn into(self) -> CompletionItem { | ||
448 | self.build() | ||
449 | } | ||
450 | } | ||
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 76f31de9e..d46f521a0 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -13,7 +13,9 @@ mod completions; | |||
13 | 13 | ||
14 | use completions::flyimport::position_for_import; | 14 | use completions::flyimport::position_for_import; |
15 | use ide_db::{ | 15 | use 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 | }; |
18 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
19 | 21 | ||
@@ -21,7 +23,10 @@ use crate::{completions::Completions, context::CompletionContext, item::Completi | |||
21 | 23 | ||
22 | pub use crate::{ | 24 | pub use crate::{ |
23 | config::CompletionConfig, | 25 | config::CompletionConfig, |
24 | item::{CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat}, | 26 | item::{ |
27 | CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat, | ||
28 | Relevance, | ||
29 | }, | ||
25 | }; | 30 | }; |
26 | 31 | ||
27 | //FIXME: split the following feature into fine-grained features. | 32 | //FIXME: split the following feature into fine-grained features. |
@@ -139,25 +144,27 @@ pub fn resolve_completion_edits( | |||
139 | position: FilePosition, | 144 | position: FilePosition, |
140 | full_import_path: &str, | 145 | full_import_path: &str, |
141 | imported_name: String, | 146 | imported_name: String, |
142 | import_for_trait_assoc_item: bool, | ||
143 | ) -> Option<Vec<TextEdit>> { | 147 | ) -> Option<Vec<TextEdit>> { |
144 | let ctx = CompletionContext::new(db, position, config)?; | 148 | let ctx = CompletionContext::new(db, position, config)?; |
145 | let position_for_import = position_for_import(&ctx, None)?; | 149 | let position_for_import = position_for_import(&ctx, None)?; |
146 | let import_scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; | 150 | let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; |
147 | 151 | ||
148 | let current_module = ctx.sema.scope(position_for_import).module()?; | 152 | let current_module = ctx.sema.scope(position_for_import).module()?; |
149 | let current_crate = current_module.krate(); | 153 | let current_crate = current_module.krate(); |
150 | 154 | ||
151 | let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name) | 155 | let (import_path, item_to_import) = |
152 | .filter_map(|candidate| { | 156 | items_locator::with_exact_name(&ctx.sema, current_crate, imported_name) |
153 | let item: hir::ItemInNs = candidate.either(Into::into, Into::into); | 157 | .into_iter() |
154 | current_module.find_use_path_prefixed(db, item, config.insert_use.prefix_kind) | 158 | .filter_map(|candidate| { |
155 | }) | 159 | current_module |
156 | .find(|mod_path| mod_path.to_string() == full_import_path)?; | 160 | .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind) |
161 | .zip(Some(candidate)) | ||
162 | }) | ||
163 | .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?; | ||
164 | let import = | ||
165 | LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path)); | ||
157 | 166 | ||
158 | ImportEdit { import_path, import_scope, import_for_trait_assoc_item } | 167 | ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit]) |
159 | .to_text_edit(config.insert_use.merge) | ||
160 | .map(|edit| vec![edit]) | ||
161 | } | 168 | } |
162 | 169 | ||
163 | #[cfg(test)] | 170 | #[cfg(test)] |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index eddaaa6f3..8c8b149a1 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -13,13 +13,15 @@ mod builder_ext; | |||
13 | use hir::{ | 13 | use hir::{ |
14 | AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, | 14 | AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, |
15 | }; | 15 | }; |
16 | use ide_db::{helpers::SnippetCap, RootDatabase, SymbolKind}; | 16 | use ide_db::{ |
17 | helpers::{item_name, SnippetCap}, | ||
18 | RootDatabase, SymbolKind, | ||
19 | }; | ||
17 | use syntax::TextRange; | 20 | use syntax::TextRange; |
18 | use test_utils::mark; | ||
19 | 21 | ||
20 | use crate::{ | 22 | use crate::{ |
21 | item::ImportEdit, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, | 23 | item::{ImportEdit, Relevance}, |
22 | CompletionScore, | 24 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, |
23 | }; | 25 | }; |
24 | 26 | ||
25 | use crate::render::{enum_variant::render_variant, function::render_fn, macro_::render_macro}; | 27 | use crate::render::{enum_variant::render_variant, function::render_fn, macro_::render_macro}; |
@@ -51,18 +53,20 @@ pub(crate) fn render_resolution<'a>( | |||
51 | pub(crate) fn render_resolution_with_import<'a>( | 53 | pub(crate) fn render_resolution_with_import<'a>( |
52 | ctx: RenderContext<'a>, | 54 | ctx: RenderContext<'a>, |
53 | import_edit: ImportEdit, | 55 | import_edit: ImportEdit, |
54 | resolution: &ScopeDef, | ||
55 | ) -> Option<CompletionItem> { | 56 | ) -> Option<CompletionItem> { |
57 | let resolution = ScopeDef::from(import_edit.import.original_item); | ||
56 | let local_name = match resolution { | 58 | let local_name = match resolution { |
57 | 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(), |
58 | 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(), |
59 | 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(), |
60 | _ => import_edit.import_path.segments().last()?.to_string(), | 62 | _ => item_name(ctx.db(), import_edit.import.original_item)?.to_string(), |
61 | }; | 63 | }; |
62 | 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( |
63 | item.completion_kind = CompletionKind::Magic; | 65 | |mut item| { |
64 | item | 66 | item.completion_kind = CompletionKind::Magic; |
65 | }) | 67 | item |
68 | }, | ||
69 | ) | ||
66 | } | 70 | } |
67 | 71 | ||
68 | /// Interface for data and methods required for items rendering. | 72 | /// Interface for data and methods required for items rendering. |
@@ -113,13 +117,13 @@ impl<'a> RenderContext<'a> { | |||
113 | node.docs(self.db()) | 117 | node.docs(self.db()) |
114 | } | 118 | } |
115 | 119 | ||
116 | fn active_name_and_type(&self) -> Option<(String, Type)> { | 120 | fn expected_name_and_type(&self) -> Option<(String, Type)> { |
117 | if let Some(record_field) = &self.completion.record_field_syntax { | 121 | if let Some(record_field) = &self.completion.record_field_syntax { |
118 | mark::hit!(record_field_type_match); | 122 | cov_mark::hit!(record_field_type_match); |
119 | let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?; | 123 | let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?; |
120 | Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db()))) | 124 | Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db()))) |
121 | } else if let Some(active_parameter) = &self.completion.active_parameter { | 125 | } else if let Some(active_parameter) = &self.completion.active_parameter { |
122 | mark::hit!(active_param_type_match); | 126 | cov_mark::hit!(active_param_type_match); |
123 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | 127 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) |
124 | } else { | 128 | } else { |
125 | None | 129 | None |
@@ -151,8 +155,8 @@ impl<'a> Render<'a> { | |||
151 | .set_documentation(field.docs(self.ctx.db())) | 155 | .set_documentation(field.docs(self.ctx.db())) |
152 | .set_deprecated(is_deprecated); | 156 | .set_deprecated(is_deprecated); |
153 | 157 | ||
154 | if let Some(score) = compute_score(&self.ctx, &ty, &name.to_string()) { | 158 | if let Some(relevance) = compute_relevance(&self.ctx, &ty, &name.to_string()) { |
155 | item = item.set_score(score); | 159 | item = item.set_relevance(relevance); |
156 | } | 160 | } |
157 | 161 | ||
158 | item.build() | 162 | item.build() |
@@ -242,16 +246,23 @@ impl<'a> Render<'a> { | |||
242 | } | 246 | } |
243 | }; | 247 | }; |
244 | 248 | ||
245 | let mut ref_match = None; | ||
246 | if let ScopeDef::Local(local) = resolution { | 249 | if let ScopeDef::Local(local) = resolution { |
247 | if let Some((active_name, active_type)) = self.ctx.active_name_and_type() { | 250 | let ty = local.ty(self.ctx.db()); |
248 | let ty = local.ty(self.ctx.db()); | 251 | if let Some(relevance) = compute_relevance(&self.ctx, &ty, &local_name) { |
249 | if let Some(score) = | 252 | item = item.set_relevance(relevance) |
250 | compute_score_from_active(&active_type, &active_name, &ty, &local_name) | 253 | } |
251 | { | 254 | if let Some((_expected_name, expected_type)) = self.ctx.expected_name_and_type() { |
252 | item = item.set_score(score); | 255 | if let Some(ty_without_ref) = expected_type.remove_ref() { |
256 | if ty_without_ref == ty { | ||
257 | cov_mark::hit!(suggest_ref); | ||
258 | let mutability = if expected_type.is_mutable_reference() { | ||
259 | Mutability::Mut | ||
260 | } else { | ||
261 | Mutability::Shared | ||
262 | }; | ||
263 | item = item.ref_match(mutability) | ||
264 | } | ||
253 | } | 265 | } |
254 | ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name); | ||
255 | } | 266 | } |
256 | } | 267 | } |
257 | 268 | ||
@@ -269,7 +280,7 @@ impl<'a> Render<'a> { | |||
269 | _ => false, | 280 | _ => false, |
270 | }; | 281 | }; |
271 | if has_non_default_type_params { | 282 | if has_non_default_type_params { |
272 | mark::hit!(inserts_angle_brackets_for_generics); | 283 | cov_mark::hit!(inserts_angle_brackets_for_generics); |
273 | item = item | 284 | item = item |
274 | .lookup_by(local_name.clone()) | 285 | .lookup_by(local_name.clone()) |
275 | .label(format!("{}<…>", local_name)) | 286 | .label(format!("{}<…>", local_name)) |
@@ -281,7 +292,6 @@ impl<'a> Render<'a> { | |||
281 | Some( | 292 | Some( |
282 | item.kind(kind) | 293 | item.kind(kind) |
283 | .add_import(import_to_add) | 294 | .add_import(import_to_add) |
284 | .set_ref_match(ref_match) | ||
285 | .set_documentation(self.docs(resolution)) | 295 | .set_documentation(self.docs(resolution)) |
286 | .set_deprecated(self.is_deprecated(resolution)) | 296 | .set_deprecated(self.is_deprecated(resolution)) |
287 | .build(), | 297 | .build(), |
@@ -313,56 +323,23 @@ impl<'a> Render<'a> { | |||
313 | } | 323 | } |
314 | } | 324 | } |
315 | 325 | ||
316 | fn compute_score_from_active( | 326 | fn compute_relevance(ctx: &RenderContext, ty: &Type, name: &str) -> Option<Relevance> { |
317 | active_type: &Type, | 327 | let (expected_name, expected_type) = ctx.expected_name_and_type()?; |
318 | active_name: &str, | 328 | let mut res = Relevance::default(); |
319 | ty: &Type, | 329 | res.exact_type_match = ty == &expected_type; |
320 | name: &str, | 330 | res.exact_name_match = name == &expected_name; |
321 | ) -> Option<CompletionScore> { | ||
322 | // Compute score | ||
323 | // For the same type | ||
324 | if active_type != ty { | ||
325 | return None; | ||
326 | } | ||
327 | |||
328 | let mut res = CompletionScore::TypeMatch; | ||
329 | |||
330 | // If same type + same name then go top position | ||
331 | if active_name == name { | ||
332 | res = CompletionScore::TypeAndNameMatch | ||
333 | } | ||
334 | |||
335 | Some(res) | 331 | Some(res) |
336 | } | 332 | } |
337 | fn refed_type_matches( | ||
338 | active_type: &Type, | ||
339 | active_name: &str, | ||
340 | ty: &Type, | ||
341 | name: &str, | ||
342 | ) -> Option<(Mutability, CompletionScore)> { | ||
343 | let derefed_active = active_type.remove_ref()?; | ||
344 | let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?; | ||
345 | Some(( | ||
346 | if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared }, | ||
347 | score, | ||
348 | )) | ||
349 | } | ||
350 | |||
351 | fn compute_score(ctx: &RenderContext, ty: &Type, name: &str) -> Option<CompletionScore> { | ||
352 | let (active_name, active_type) = ctx.active_name_and_type()?; | ||
353 | compute_score_from_active(&active_type, &active_name, ty, name) | ||
354 | } | ||
355 | 333 | ||
356 | #[cfg(test)] | 334 | #[cfg(test)] |
357 | mod tests { | 335 | mod tests { |
358 | use std::cmp::Reverse; | 336 | use std::cmp::Reverse; |
359 | 337 | ||
360 | use expect_test::{expect, Expect}; | 338 | use expect_test::{expect, Expect}; |
361 | use test_utils::mark; | ||
362 | 339 | ||
363 | use crate::{ | 340 | use crate::{ |
364 | test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG}, | 341 | test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG}, |
365 | CompletionKind, CompletionScore, | 342 | CompletionKind, Relevance, |
366 | }; | 343 | }; |
367 | 344 | ||
368 | fn check(ra_fixture: &str, expect: Expect) { | 345 | fn check(ra_fixture: &str, expect: Expect) { |
@@ -370,24 +347,25 @@ mod tests { | |||
370 | expect.assert_debug_eq(&actual); | 347 | expect.assert_debug_eq(&actual); |
371 | } | 348 | } |
372 | 349 | ||
373 | fn check_scores(ra_fixture: &str, expect: Expect) { | 350 | fn check_relevance(ra_fixture: &str, expect: Expect) { |
374 | fn display_score(score: Option<CompletionScore>) -> &'static str { | 351 | fn display_relevance(relevance: Relevance) -> &'static str { |
375 | match score { | 352 | match relevance { |
376 | Some(CompletionScore::TypeMatch) => "[type]", | 353 | Relevance { exact_type_match: true, exact_name_match: true } => "[type+name]", |
377 | Some(CompletionScore::TypeAndNameMatch) => "[type+name]", | 354 | Relevance { exact_type_match: true, exact_name_match: false } => "[type]", |
378 | None => "[]".into(), | 355 | Relevance { exact_type_match: false, exact_name_match: true } => "[name]", |
356 | Relevance { exact_type_match: false, exact_name_match: false } => "[]", | ||
379 | } | 357 | } |
380 | } | 358 | } |
381 | 359 | ||
382 | let mut completions = get_all_items(TEST_CONFIG, ra_fixture); | 360 | let mut completions = get_all_items(TEST_CONFIG, ra_fixture); |
383 | completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); | 361 | completions.sort_by_key(|it| (Reverse(it.relevance()), it.label().to_string())); |
384 | let actual = completions | 362 | let actual = completions |
385 | .into_iter() | 363 | .into_iter() |
386 | .filter(|it| it.completion_kind == CompletionKind::Reference) | 364 | .filter(|it| it.completion_kind == CompletionKind::Reference) |
387 | .map(|it| { | 365 | .map(|it| { |
388 | let tag = it.kind().unwrap().tag(); | 366 | let tag = it.kind().unwrap().tag(); |
389 | let score = display_score(it.score()); | 367 | let relevance = display_relevance(it.relevance()); |
390 | format!("{} {} {}\n", tag, it.label(), score) | 368 | format!("{} {} {}\n", tag, it.label(), relevance) |
391 | }) | 369 | }) |
392 | .collect::<String>(); | 370 | .collect::<String>(); |
393 | expect.assert_eq(&actual); | 371 | expect.assert_eq(&actual); |
@@ -734,7 +712,7 @@ fn foo(s: S) { s.$0 } | |||
734 | 712 | ||
735 | #[test] | 713 | #[test] |
736 | fn no_call_parens_if_fn_ptr_needed() { | 714 | fn no_call_parens_if_fn_ptr_needed() { |
737 | mark::check!(no_call_parens_if_fn_ptr_needed); | 715 | cov_mark::check!(no_call_parens_if_fn_ptr_needed); |
738 | check_edit( | 716 | check_edit( |
739 | "foo", | 717 | "foo", |
740 | r#" | 718 | r#" |
@@ -758,7 +736,7 @@ fn main() -> ManualVtable { | |||
758 | 736 | ||
759 | #[test] | 737 | #[test] |
760 | fn no_parens_in_use_item() { | 738 | fn no_parens_in_use_item() { |
761 | mark::check!(no_parens_in_use_item); | 739 | cov_mark::check!(no_parens_in_use_item); |
762 | check_edit( | 740 | check_edit( |
763 | "foo", | 741 | "foo", |
764 | r#" | 742 | r#" |
@@ -802,7 +780,7 @@ fn f(foo: &Foo) { foo.foo(); } | |||
802 | 780 | ||
803 | #[test] | 781 | #[test] |
804 | fn inserts_angle_brackets_for_generics() { | 782 | fn inserts_angle_brackets_for_generics() { |
805 | mark::check!(inserts_angle_brackets_for_generics); | 783 | cov_mark::check!(inserts_angle_brackets_for_generics); |
806 | check_edit( | 784 | check_edit( |
807 | "Vec", | 785 | "Vec", |
808 | r#" | 786 | r#" |
@@ -850,9 +828,9 @@ fn foo(xs: Vec<i128>) | |||
850 | } | 828 | } |
851 | 829 | ||
852 | #[test] | 830 | #[test] |
853 | fn active_param_score() { | 831 | fn active_param_relevance() { |
854 | mark::check!(active_param_type_match); | 832 | cov_mark::check!(active_param_type_match); |
855 | check_scores( | 833 | check_relevance( |
856 | r#" | 834 | r#" |
857 | struct S { foo: i64, bar: u32, baz: u32 } | 835 | struct S { foo: i64, bar: u32, baz: u32 } |
858 | fn test(bar: u32) { } | 836 | fn test(bar: u32) { } |
@@ -867,9 +845,9 @@ fn foo(s: S) { test(s.$0) } | |||
867 | } | 845 | } |
868 | 846 | ||
869 | #[test] | 847 | #[test] |
870 | fn record_field_scores() { | 848 | fn record_field_relevances() { |
871 | mark::check!(record_field_type_match); | 849 | cov_mark::check!(record_field_type_match); |
872 | check_scores( | 850 | check_relevance( |
873 | r#" | 851 | r#" |
874 | struct A { foo: i64, bar: u32, baz: u32 } | 852 | struct A { foo: i64, bar: u32, baz: u32 } |
875 | struct B { x: (), y: f32, bar: u32 } | 853 | struct B { x: (), y: f32, bar: u32 } |
@@ -884,8 +862,8 @@ fn foo(a: A) { B { bar: a.$0 }; } | |||
884 | } | 862 | } |
885 | 863 | ||
886 | #[test] | 864 | #[test] |
887 | fn record_field_and_call_scores() { | 865 | fn record_field_and_call_relevances() { |
888 | check_scores( | 866 | check_relevance( |
889 | r#" | 867 | r#" |
890 | struct A { foo: i64, bar: u32, baz: u32 } | 868 | struct A { foo: i64, bar: u32, baz: u32 } |
891 | struct B { x: (), y: f32, bar: u32 } | 869 | struct B { x: (), y: f32, bar: u32 } |
@@ -898,7 +876,7 @@ fn foo(a: A) { B { bar: f(a.$0) }; } | |||
898 | fd baz [] | 876 | fd baz [] |
899 | "#]], | 877 | "#]], |
900 | ); | 878 | ); |
901 | check_scores( | 879 | check_relevance( |
902 | r#" | 880 | r#" |
903 | struct A { foo: i64, bar: u32, baz: u32 } | 881 | struct A { foo: i64, bar: u32, baz: u32 } |
904 | struct B { x: (), y: f32, bar: u32 } | 882 | struct B { x: (), y: f32, bar: u32 } |
@@ -915,7 +893,7 @@ fn foo(a: A) { f(B { bar: a.$0 }); } | |||
915 | 893 | ||
916 | #[test] | 894 | #[test] |
917 | fn prioritize_exact_ref_match() { | 895 | fn prioritize_exact_ref_match() { |
918 | check_scores( | 896 | check_relevance( |
919 | r#" | 897 | r#" |
920 | struct WorldSnapshot { _f: () }; | 898 | struct WorldSnapshot { _f: () }; |
921 | fn go(world: &WorldSnapshot) { go(w$0) } | 899 | fn go(world: &WorldSnapshot) { go(w$0) } |
@@ -930,7 +908,7 @@ fn go(world: &WorldSnapshot) { go(w$0) } | |||
930 | 908 | ||
931 | #[test] | 909 | #[test] |
932 | fn too_many_arguments() { | 910 | fn too_many_arguments() { |
933 | check_scores( | 911 | check_relevance( |
934 | r#" | 912 | r#" |
935 | struct Foo; | 913 | struct Foo; |
936 | fn f(foo: &Foo) { f(foo, w$0) } | 914 | fn f(foo: &Foo) { f(foo, w$0) } |
@@ -942,4 +920,70 @@ fn f(foo: &Foo) { f(foo, w$0) } | |||
942 | "#]], | 920 | "#]], |
943 | ); | 921 | ); |
944 | } | 922 | } |
923 | |||
924 | #[test] | ||
925 | fn suggest_ref_mut() { | ||
926 | cov_mark::check!(suggest_ref); | ||
927 | check( | ||
928 | r#" | ||
929 | struct S; | ||
930 | fn foo(s: &mut S) {} | ||
931 | fn main() { | ||
932 | let mut s = S; | ||
933 | foo($0); | ||
934 | } | ||
935 | "#, | ||
936 | expect![[r#" | ||
937 | [ | ||
938 | CompletionItem { | ||
939 | label: "S", | ||
940 | source_range: 70..70, | ||
941 | delete: 70..70, | ||
942 | insert: "S", | ||
943 | kind: SymbolKind( | ||
944 | Struct, | ||
945 | ), | ||
946 | }, | ||
947 | CompletionItem { | ||
948 | label: "foo(…)", | ||
949 | source_range: 70..70, | ||
950 | delete: 70..70, | ||
951 | insert: "foo(${1:&mut s})$0", | ||
952 | kind: SymbolKind( | ||
953 | Function, | ||
954 | ), | ||
955 | lookup: "foo", | ||
956 | detail: "-> ()", | ||
957 | trigger_call_info: true, | ||
958 | }, | ||
959 | CompletionItem { | ||
960 | label: "main()", | ||
961 | source_range: 70..70, | ||
962 | delete: 70..70, | ||
963 | insert: "main()$0", | ||
964 | kind: SymbolKind( | ||
965 | Function, | ||
966 | ), | ||
967 | lookup: "main", | ||
968 | detail: "-> ()", | ||
969 | }, | ||
970 | CompletionItem { | ||
971 | label: "s", | ||
972 | source_range: 70..70, | ||
973 | delete: 70..70, | ||
974 | insert: "s", | ||
975 | kind: SymbolKind( | ||
976 | Local, | ||
977 | ), | ||
978 | detail: "S", | ||
979 | relevance: Relevance { | ||
980 | exact_name_match: true, | ||
981 | exact_type_match: false, | ||
982 | }, | ||
983 | ref_match: "&mut ", | ||
984 | }, | ||
985 | ] | ||
986 | "#]], | ||
987 | ) | ||
988 | } | ||
945 | } | 989 | } |
diff --git a/crates/ide_completion/src/render/builder_ext.rs b/crates/ide_completion/src/render/builder_ext.rs index d053a988b..95a7596c1 100644 --- a/crates/ide_completion/src/render/builder_ext.rs +++ b/crates/ide_completion/src/render/builder_ext.rs | |||
@@ -1,7 +1,6 @@ | |||
1 | //! Extensions for `Builder` structure required for item rendering. | 1 | //! Extensions for `Builder` structure required for item rendering. |
2 | 2 | ||
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | use test_utils::mark; | ||
5 | 4 | ||
6 | use crate::{item::Builder, CompletionContext}; | 5 | use crate::{item::Builder, CompletionContext}; |
7 | 6 | ||
@@ -30,7 +29,7 @@ impl Builder { | |||
30 | return false; | 29 | return false; |
31 | } | 30 | } |
32 | if ctx.use_item_syntax.is_some() { | 31 | if ctx.use_item_syntax.is_some() { |
33 | mark::hit!(no_parens_in_use_item); | 32 | cov_mark::hit!(no_parens_in_use_item); |
34 | return false; | 33 | return false; |
35 | } | 34 | } |
36 | if ctx.is_pattern_call { | 35 | if ctx.is_pattern_call { |
@@ -43,7 +42,7 @@ impl Builder { | |||
43 | // Don't add parentheses if the expected type is some function reference. | 42 | // Don't add parentheses if the expected type is some function reference. |
44 | if let Some(ty) = &ctx.expected_type { | 43 | if let Some(ty) = &ctx.expected_type { |
45 | if ty.is_fn() { | 44 | if ty.is_fn() { |
46 | mark::hit!(no_call_parens_if_fn_ptr_needed); | 45 | cov_mark::hit!(no_call_parens_if_fn_ptr_needed); |
47 | return false; | 46 | return false; |
48 | } | 47 | } |
49 | } | 48 | } |
@@ -67,7 +66,7 @@ impl Builder { | |||
67 | None => return self, | 66 | None => return self, |
68 | }; | 67 | }; |
69 | // If not an import, add parenthesis automatically. | 68 | // If not an import, add parenthesis automatically. |
70 | mark::hit!(inserts_parens_for_function_calls); | 69 | cov_mark::hit!(inserts_parens_for_function_calls); |
71 | 70 | ||
72 | let (snippet, label) = if params.is_empty() { | 71 | let (snippet, label) = if params.is_empty() { |
73 | (format!("{}()$0", name), format!("{}()", name)) | 72 | (format!("{}()$0", name), format!("{}()", name)) |
@@ -82,7 +81,7 @@ impl Builder { | |||
82 | format!("{}({})$0", name, function_params_snippet) | 81 | format!("{}({})$0", name, function_params_snippet) |
83 | } | 82 | } |
84 | _ => { | 83 | _ => { |
85 | mark::hit!(suppress_arg_snippets); | 84 | cov_mark::hit!(suppress_arg_snippets); |
86 | format!("{}($0)", name) | 85 | format!("{}($0)", name) |
87 | } | 86 | } |
88 | }; | 87 | }; |
diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs index 9214193b4..ed055c1fb 100644 --- a/crates/ide_completion/src/render/enum_variant.rs +++ b/crates/ide_completion/src/render/enum_variant.rs | |||
@@ -3,7 +3,6 @@ | |||
3 | use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; | 3 | use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; |
4 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use test_utils::mark; | ||
7 | 6 | ||
8 | use crate::{ | 7 | use crate::{ |
9 | item::{CompletionItem, CompletionKind, ImportEdit}, | 8 | item::{CompletionItem, CompletionKind, ImportEdit}, |
@@ -68,7 +67,7 @@ impl<'a> EnumRender<'a> { | |||
68 | .detail(self.detail()); | 67 | .detail(self.detail()); |
69 | 68 | ||
70 | if self.variant_kind == StructKind::Tuple { | 69 | if self.variant_kind == StructKind::Tuple { |
71 | mark::hit!(inserts_parens_for_tuple_enums); | 70 | cov_mark::hit!(inserts_parens_for_tuple_enums); |
72 | let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len()); | 71 | let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len()); |
73 | builder = | 72 | builder = |
74 | builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params); | 73 | builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params); |
@@ -103,13 +102,11 @@ impl<'a> EnumRender<'a> { | |||
103 | 102 | ||
104 | #[cfg(test)] | 103 | #[cfg(test)] |
105 | mod tests { | 104 | mod tests { |
106 | use test_utils::mark; | ||
107 | |||
108 | use crate::test_utils::check_edit; | 105 | use crate::test_utils::check_edit; |
109 | 106 | ||
110 | #[test] | 107 | #[test] |
111 | fn inserts_parens_for_tuple_enums() { | 108 | fn inserts_parens_for_tuple_enums() { |
112 | mark::check!(inserts_parens_for_tuple_enums); | 109 | cov_mark::check!(inserts_parens_for_tuple_enums); |
113 | check_edit( | 110 | check_edit( |
114 | "Some", | 111 | "Some", |
115 | r#" | 112 | r#" |
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs index e46e21d24..5931945a8 100644 --- a/crates/ide_completion/src/render/function.rs +++ b/crates/ide_completion/src/render/function.rs | |||
@@ -3,7 +3,6 @@ | |||
3 | use hir::{HasSource, HirDisplay, Type}; | 3 | use hir::{HasSource, HirDisplay, Type}; |
4 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
5 | use syntax::ast::Fn; | 5 | use syntax::ast::Fn; |
6 | use test_utils::mark; | ||
7 | 6 | ||
8 | use crate::{ | 7 | use crate::{ |
9 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit}, | 8 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit}, |
@@ -82,7 +81,7 @@ impl<'a> FunctionRender<'a> { | |||
82 | self.func.method_params(self.ctx.db()).unwrap_or_default() | 81 | self.func.method_params(self.ctx.db()).unwrap_or_default() |
83 | } else { | 82 | } else { |
84 | if let Some(s) = ast_params.self_param() { | 83 | if let Some(s) = ast_params.self_param() { |
85 | mark::hit!(parens_for_method_call_as_assoc_fn); | 84 | cov_mark::hit!(parens_for_method_call_as_assoc_fn); |
86 | params_pats.push(Some(s.to_string())); | 85 | params_pats.push(Some(s.to_string())); |
87 | } | 86 | } |
88 | self.func.assoc_fn_params(self.ctx.db()) | 87 | self.func.assoc_fn_params(self.ctx.db()) |
@@ -114,8 +113,6 @@ impl<'a> FunctionRender<'a> { | |||
114 | 113 | ||
115 | #[cfg(test)] | 114 | #[cfg(test)] |
116 | mod tests { | 115 | mod tests { |
117 | use test_utils::mark; | ||
118 | |||
119 | use crate::{ | 116 | use crate::{ |
120 | test_utils::{check_edit, check_edit_with_config, TEST_CONFIG}, | 117 | test_utils::{check_edit, check_edit_with_config, TEST_CONFIG}, |
121 | CompletionConfig, | 118 | CompletionConfig, |
@@ -123,7 +120,7 @@ mod tests { | |||
123 | 120 | ||
124 | #[test] | 121 | #[test] |
125 | fn inserts_parens_for_function_calls() { | 122 | fn inserts_parens_for_function_calls() { |
126 | mark::check!(inserts_parens_for_function_calls); | 123 | cov_mark::check!(inserts_parens_for_function_calls); |
127 | check_edit( | 124 | check_edit( |
128 | "no_args", | 125 | "no_args", |
129 | r#" | 126 | r#" |
@@ -191,7 +188,7 @@ fn bar(s: &S) { | |||
191 | 188 | ||
192 | #[test] | 189 | #[test] |
193 | fn parens_for_method_call_as_assoc_fn() { | 190 | fn parens_for_method_call_as_assoc_fn() { |
194 | mark::check!(parens_for_method_call_as_assoc_fn); | 191 | cov_mark::check!(parens_for_method_call_as_assoc_fn); |
195 | check_edit( | 192 | check_edit( |
196 | "foo", | 193 | "foo", |
197 | r#" | 194 | r#" |
@@ -213,7 +210,7 @@ fn main() { S::foo(${1:&self})$0 } | |||
213 | 210 | ||
214 | #[test] | 211 | #[test] |
215 | fn suppress_arg_snippets() { | 212 | fn suppress_arg_snippets() { |
216 | mark::check!(suppress_arg_snippets); | 213 | cov_mark::check!(suppress_arg_snippets); |
217 | check_edit_with_config( | 214 | check_edit_with_config( |
218 | CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG }, | 215 | CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG }, |
219 | "with_args", | 216 | "with_args", |
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs index a4535786f..a6cf3e479 100644 --- a/crates/ide_completion/src/render/macro_.rs +++ b/crates/ide_completion/src/render/macro_.rs | |||
@@ -3,7 +3,6 @@ | |||
3 | use hir::{Documentation, HasSource}; | 3 | use hir::{Documentation, HasSource}; |
4 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
5 | use syntax::display::macro_label; | 5 | use syntax::display::macro_label; |
6 | use test_utils::mark; | ||
7 | 6 | ||
8 | use crate::{ | 7 | use crate::{ |
9 | item::{CompletionItem, CompletionKind, ImportEdit}, | 8 | item::{CompletionItem, CompletionKind, ImportEdit}, |
@@ -57,7 +56,7 @@ impl<'a> MacroRender<'a> { | |||
57 | } | 56 | } |
58 | None if needs_bang => builder.insert_text(self.banged_name()), | 57 | None if needs_bang => builder.insert_text(self.banged_name()), |
59 | _ => { | 58 | _ => { |
60 | mark::hit!(dont_insert_macro_call_parens_unncessary); | 59 | cov_mark::hit!(dont_insert_macro_call_parens_unncessary); |
61 | builder.insert_text(&self.name) | 60 | builder.insert_text(&self.name) |
62 | } | 61 | } |
63 | }; | 62 | }; |
@@ -125,13 +124,11 @@ fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static s | |||
125 | 124 | ||
126 | #[cfg(test)] | 125 | #[cfg(test)] |
127 | mod tests { | 126 | mod tests { |
128 | use test_utils::mark; | ||
129 | |||
130 | use crate::test_utils::check_edit; | 127 | use crate::test_utils::check_edit; |
131 | 128 | ||
132 | #[test] | 129 | #[test] |
133 | fn dont_insert_macro_call_parens_unncessary() { | 130 | fn dont_insert_macro_call_parens_unncessary() { |
134 | mark::check!(dont_insert_macro_call_parens_unncessary); | 131 | cov_mark::check!(dont_insert_macro_call_parens_unncessary); |
135 | check_edit( | 132 | check_edit( |
136 | "frobnicate!", | 133 | "frobnicate!", |
137 | r#" | 134 | r#" |
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs index baff83305..9da844031 100644 --- a/crates/ide_completion/src/test_utils.rs +++ b/crates/ide_completion/src/test_utils.rs | |||
@@ -25,6 +25,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { | |||
25 | insert_use: InsertUseConfig { | 25 | insert_use: InsertUseConfig { |
26 | merge: Some(MergeBehavior::Full), | 26 | merge: Some(MergeBehavior::Full), |
27 | prefix_kind: PrefixKind::Plain, | 27 | prefix_kind: PrefixKind::Plain, |
28 | group: true, | ||
28 | }, | 29 | }, |
29 | }; | 30 | }; |
30 | 31 | ||
@@ -119,7 +120,7 @@ pub(crate) fn check_edit_with_config( | |||
119 | 120 | ||
120 | let mut combined_edit = completion.text_edit().to_owned(); | 121 | let mut combined_edit = completion.text_edit().to_owned(); |
121 | if let Some(import_text_edit) = | 122 | if let Some(import_text_edit) = |
122 | completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use.merge)) | 123 | completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use)) |
123 | { | 124 | { |
124 | combined_edit.union(import_text_edit).expect( | 125 | combined_edit.union(import_text_edit).expect( |
125 | "Failed to apply completion resolve changes: change ranges overlap, but should not", | 126 | "Failed to apply completion resolve changes: change ranges overlap, but should not", |