diff options
Diffstat (limited to 'crates/ide_completion')
-rw-r--r-- | crates/ide_completion/src/completions.rs | 29 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/flyimport.rs | 93 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/lifetime.rs | 316 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/pattern.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 90 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 29 | ||||
-rw-r--r-- | crates/ide_completion/src/patterns.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/render.rs | 6 | ||||
-rw-r--r-- | crates/ide_completion/src/render/macro_.rs | 2 |
9 files changed, 521 insertions, 48 deletions
diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 09882c4f3..cdac4e41a 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs | |||
@@ -2,25 +2,27 @@ | |||
2 | 2 | ||
3 | pub(crate) mod attribute; | 3 | pub(crate) mod attribute; |
4 | pub(crate) mod dot; | 4 | pub(crate) mod dot; |
5 | pub(crate) mod record; | 5 | pub(crate) mod flyimport; |
6 | pub(crate) mod pattern; | ||
7 | pub(crate) mod fn_param; | 6 | pub(crate) mod fn_param; |
8 | pub(crate) mod keyword; | 7 | pub(crate) mod keyword; |
9 | pub(crate) mod snippet; | 8 | pub(crate) mod lifetime; |
10 | pub(crate) mod qualified_path; | ||
11 | pub(crate) mod unqualified_path; | ||
12 | pub(crate) mod postfix; | ||
13 | pub(crate) mod macro_in_item_position; | 9 | pub(crate) mod macro_in_item_position; |
14 | pub(crate) mod trait_impl; | ||
15 | pub(crate) mod mod_; | 10 | pub(crate) mod mod_; |
16 | pub(crate) mod flyimport; | 11 | pub(crate) mod pattern; |
12 | pub(crate) mod postfix; | ||
13 | pub(crate) mod qualified_path; | ||
14 | pub(crate) mod record; | ||
15 | pub(crate) mod snippet; | ||
16 | pub(crate) mod trait_impl; | ||
17 | pub(crate) mod unqualified_path; | ||
17 | 18 | ||
18 | use std::iter; | 19 | use std::iter; |
19 | 20 | ||
20 | use hir::{known, ModPath, ScopeDef, Type}; | 21 | use hir::{known, ModPath, ScopeDef, Type}; |
22 | use ide_db::SymbolKind; | ||
21 | 23 | ||
22 | use crate::{ | 24 | use crate::{ |
23 | item::Builder, | 25 | item::{Builder, CompletionKind}, |
24 | render::{ | 26 | render::{ |
25 | const_::render_const, | 27 | const_::render_const, |
26 | enum_variant::render_variant, | 28 | enum_variant::render_variant, |
@@ -31,7 +33,7 @@ use crate::{ | |||
31 | type_alias::render_type_alias, | 33 | type_alias::render_type_alias, |
32 | RenderContext, | 34 | RenderContext, |
33 | }, | 35 | }, |
34 | CompletionContext, CompletionItem, | 36 | CompletionContext, CompletionItem, CompletionItemKind, |
35 | }; | 37 | }; |
36 | 38 | ||
37 | /// Represents an in-progress set of completions being built. | 39 | /// Represents an in-progress set of completions being built. |
@@ -77,6 +79,13 @@ impl Completions { | |||
77 | self.add(item); | 79 | self.add(item); |
78 | } | 80 | } |
79 | 81 | ||
82 | pub(crate) fn add_static_lifetime(&mut self, ctx: &CompletionContext) { | ||
83 | let mut item = | ||
84 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), "'static"); | ||
85 | item.kind(CompletionItemKind::SymbolKind(SymbolKind::LifetimeParam)); | ||
86 | self.add(item.build()); | ||
87 | } | ||
88 | |||
80 | pub(crate) fn add_resolution( | 89 | pub(crate) fn add_resolution( |
81 | &mut self, | 90 | &mut self, |
82 | ctx: &CompletionContext, | 91 | ctx: &CompletionContext, |
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index 08df2df3f..1ad017198 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs | |||
@@ -1,8 +1,10 @@ | |||
1 | //! Feature: completion with imports-on-the-fly | 1 | //! Feature: completion with imports-on-the-fly |
2 | //! | 2 | //! |
3 | //! When completing names in the current scope, proposes additional imports from other modules or crates, | 3 | //! When completing names in the current scope, proposes additional imports from other modules or crates, |
4 | //! if they can be qualified in the scope and their name contains all symbols from the completion input | 4 | //! if they can be qualified in the scope and their name contains all symbols from the completion input. |
5 | //! (case-insensitive, in any order or places). | 5 | //! |
6 | //! To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent. | ||
7 | //! If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the contaning is checked case-insensitively. | ||
6 | //! | 8 | //! |
7 | //! ``` | 9 | //! ``` |
8 | //! fn main() { | 10 | //! fn main() { |
@@ -942,9 +944,94 @@ mod foo { | |||
942 | } | 944 | } |
943 | 945 | ||
944 | fn main() { | 946 | fn main() { |
945 | bar::Ass$0 | 947 | bar::ASS$0 |
946 | }"#, | 948 | }"#, |
947 | expect![[]], | 949 | expect![[]], |
948 | ) | 950 | ) |
949 | } | 951 | } |
952 | |||
953 | #[test] | ||
954 | fn unqualified_assoc_items_are_omitted() { | ||
955 | check( | ||
956 | r#" | ||
957 | mod something { | ||
958 | pub trait BaseTrait { | ||
959 | fn test_function() -> i32; | ||
960 | } | ||
961 | |||
962 | pub struct Item1; | ||
963 | pub struct Item2; | ||
964 | |||
965 | impl BaseTrait for Item1 { | ||
966 | fn test_function() -> i32 { | ||
967 | 1 | ||
968 | } | ||
969 | } | ||
970 | |||
971 | impl BaseTrait for Item2 { | ||
972 | fn test_function() -> i32 { | ||
973 | 2 | ||
974 | } | ||
975 | } | ||
976 | } | ||
977 | |||
978 | fn main() { | ||
979 | test_f$0 | ||
980 | }"#, | ||
981 | expect![[]], | ||
982 | ) | ||
983 | } | ||
984 | |||
985 | #[test] | ||
986 | fn case_matters() { | ||
987 | check( | ||
988 | r#" | ||
989 | mod foo { | ||
990 | pub const TEST_CONST: usize = 3; | ||
991 | pub fn test_function() -> i32 { | ||
992 | 4 | ||
993 | } | ||
994 | } | ||
995 | |||
996 | fn main() { | ||
997 | TE$0 | ||
998 | }"#, | ||
999 | expect![[r#" | ||
1000 | ct foo::TEST_CONST | ||
1001 | "#]], | ||
1002 | ); | ||
1003 | |||
1004 | check( | ||
1005 | r#" | ||
1006 | mod foo { | ||
1007 | pub const TEST_CONST: usize = 3; | ||
1008 | pub fn test_function() -> i32 { | ||
1009 | 4 | ||
1010 | } | ||
1011 | } | ||
1012 | |||
1013 | fn main() { | ||
1014 | te$0 | ||
1015 | }"#, | ||
1016 | expect![[r#" | ||
1017 | ct foo::TEST_CONST | ||
1018 | fn test_function() (foo::test_function) fn() -> i32 | ||
1019 | "#]], | ||
1020 | ); | ||
1021 | |||
1022 | check( | ||
1023 | r#" | ||
1024 | mod foo { | ||
1025 | pub const TEST_CONST: usize = 3; | ||
1026 | pub fn test_function() -> i32 { | ||
1027 | 4 | ||
1028 | } | ||
1029 | } | ||
1030 | |||
1031 | fn main() { | ||
1032 | Te$0 | ||
1033 | }"#, | ||
1034 | expect![[]], | ||
1035 | ); | ||
1036 | } | ||
950 | } | 1037 | } |
diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs new file mode 100644 index 000000000..5eeddf7a4 --- /dev/null +++ b/crates/ide_completion/src/completions/lifetime.rs | |||
@@ -0,0 +1,316 @@ | |||
1 | //! Completes lifetimes and labels. | ||
2 | use hir::ScopeDef; | ||
3 | |||
4 | use crate::{completions::Completions, context::CompletionContext}; | ||
5 | |||
6 | /// Completes lifetimes. | ||
7 | pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) { | ||
8 | if !ctx.lifetime_allowed { | ||
9 | return; | ||
10 | } | ||
11 | let param_lifetime = match ( | ||
12 | &ctx.lifetime_syntax, | ||
13 | ctx.lifetime_param_syntax.as_ref().and_then(|lp| lp.lifetime()), | ||
14 | ) { | ||
15 | (Some(lt), Some(lp)) if lp == lt.clone() => return, | ||
16 | (Some(_), Some(lp)) => Some(lp.to_string()), | ||
17 | _ => None, | ||
18 | }; | ||
19 | |||
20 | ctx.scope.process_all_names(&mut |name, res| { | ||
21 | if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res { | ||
22 | if param_lifetime != Some(name.to_string()) { | ||
23 | acc.add_resolution(ctx, name.to_string(), &res); | ||
24 | } | ||
25 | } | ||
26 | }); | ||
27 | if param_lifetime.is_none() { | ||
28 | acc.add_static_lifetime(ctx); | ||
29 | } | ||
30 | } | ||
31 | |||
32 | /// Completes labels. | ||
33 | pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) { | ||
34 | if !ctx.is_label_ref { | ||
35 | return; | ||
36 | } | ||
37 | ctx.scope.process_all_names(&mut |name, res| { | ||
38 | if let ScopeDef::Label(_) = res { | ||
39 | acc.add_resolution(ctx, name.to_string(), &res); | ||
40 | } | ||
41 | }); | ||
42 | } | ||
43 | |||
44 | #[cfg(test)] | ||
45 | mod tests { | ||
46 | use expect_test::{expect, Expect}; | ||
47 | |||
48 | use crate::{ | ||
49 | test_utils::{check_edit, completion_list_with_config, TEST_CONFIG}, | ||
50 | CompletionConfig, CompletionKind, | ||
51 | }; | ||
52 | |||
53 | fn check(ra_fixture: &str, expect: Expect) { | ||
54 | check_with_config(TEST_CONFIG, ra_fixture, expect); | ||
55 | } | ||
56 | |||
57 | fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) { | ||
58 | let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference); | ||
59 | expect.assert_eq(&actual) | ||
60 | } | ||
61 | |||
62 | #[test] | ||
63 | fn check_lifetime_edit() { | ||
64 | check_edit( | ||
65 | "'lifetime", | ||
66 | r#" | ||
67 | fn func<'lifetime>(foo: &'li$0) {} | ||
68 | "#, | ||
69 | r#" | ||
70 | fn func<'lifetime>(foo: &'lifetime) {} | ||
71 | "#, | ||
72 | ); | ||
73 | cov_mark::check!(completes_if_lifetime_without_idents); | ||
74 | check_edit( | ||
75 | "'lifetime", | ||
76 | r#" | ||
77 | fn func<'lifetime>(foo: &'$0) {} | ||
78 | "#, | ||
79 | r#" | ||
80 | fn func<'lifetime>(foo: &'lifetime) {} | ||
81 | "#, | ||
82 | ); | ||
83 | } | ||
84 | |||
85 | #[test] | ||
86 | fn complete_lifetime_in_ref() { | ||
87 | check( | ||
88 | r#" | ||
89 | fn foo<'lifetime>(foo: &'a$0 usize) {} | ||
90 | "#, | ||
91 | expect![[r#" | ||
92 | lt 'lifetime | ||
93 | lt 'static | ||
94 | "#]], | ||
95 | ); | ||
96 | } | ||
97 | |||
98 | #[test] | ||
99 | fn complete_lifetime_in_ref_missing_ty() { | ||
100 | check( | ||
101 | r#" | ||
102 | fn foo<'lifetime>(foo: &'a$0) {} | ||
103 | "#, | ||
104 | expect![[r#" | ||
105 | lt 'lifetime | ||
106 | lt 'static | ||
107 | "#]], | ||
108 | ); | ||
109 | } | ||
110 | #[test] | ||
111 | fn complete_lifetime_in_self_ref() { | ||
112 | check( | ||
113 | r#" | ||
114 | struct Foo; | ||
115 | impl<'impl> Foo { | ||
116 | fn foo<'func>(&'a$0 self) {} | ||
117 | } | ||
118 | "#, | ||
119 | expect![[r#" | ||
120 | lt 'func | ||
121 | lt 'impl | ||
122 | lt 'static | ||
123 | "#]], | ||
124 | ); | ||
125 | } | ||
126 | |||
127 | #[test] | ||
128 | fn complete_lifetime_in_arg_list() { | ||
129 | check( | ||
130 | r#" | ||
131 | struct Foo<'lt>; | ||
132 | fn foo<'lifetime>(_: Foo<'a$0>) {} | ||
133 | "#, | ||
134 | expect![[r#" | ||
135 | lt 'lifetime | ||
136 | lt 'static | ||
137 | "#]], | ||
138 | ); | ||
139 | } | ||
140 | |||
141 | #[test] | ||
142 | fn complete_lifetime_in_where_pred() { | ||
143 | check( | ||
144 | r#" | ||
145 | fn foo2<'lifetime, T>() where 'a$0 {} | ||
146 | "#, | ||
147 | expect![[r#" | ||
148 | lt 'lifetime | ||
149 | lt 'static | ||
150 | "#]], | ||
151 | ); | ||
152 | } | ||
153 | |||
154 | #[test] | ||
155 | fn complete_lifetime_in_ty_bound() { | ||
156 | check( | ||
157 | r#" | ||
158 | fn foo2<'lifetime, T>() where T: 'a$0 {} | ||
159 | "#, | ||
160 | expect![[r#" | ||
161 | lt 'lifetime | ||
162 | lt 'static | ||
163 | "#]], | ||
164 | ); | ||
165 | check( | ||
166 | r#" | ||
167 | fn foo2<'lifetime, T>() where T: Trait<'a$0> {} | ||
168 | "#, | ||
169 | expect![[r#" | ||
170 | lt 'lifetime | ||
171 | lt 'static | ||
172 | "#]], | ||
173 | ); | ||
174 | } | ||
175 | |||
176 | #[test] | ||
177 | fn dont_complete_lifetime_in_assoc_ty_bound() { | ||
178 | check( | ||
179 | r#" | ||
180 | fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {} | ||
181 | "#, | ||
182 | expect![[r#""#]], | ||
183 | ); | ||
184 | } | ||
185 | |||
186 | #[test] | ||
187 | fn complete_lifetime_in_param_list() { | ||
188 | check( | ||
189 | r#" | ||
190 | fn foo<'a$0>() {} | ||
191 | "#, | ||
192 | expect![[r#""#]], | ||
193 | ); | ||
194 | check( | ||
195 | r#" | ||
196 | fn foo<'footime, 'lifetime: 'a$0>() {} | ||
197 | "#, | ||
198 | expect![[r#" | ||
199 | lt 'footime | ||
200 | "#]], | ||
201 | ); | ||
202 | } | ||
203 | |||
204 | #[test] | ||
205 | fn check_label_edit() { | ||
206 | check_edit( | ||
207 | "'label", | ||
208 | r#" | ||
209 | fn foo() { | ||
210 | 'label: loop { | ||
211 | break '$0 | ||
212 | } | ||
213 | } | ||
214 | "#, | ||
215 | r#" | ||
216 | fn foo() { | ||
217 | 'label: loop { | ||
218 | break 'label | ||
219 | } | ||
220 | } | ||
221 | "#, | ||
222 | ); | ||
223 | } | ||
224 | |||
225 | #[test] | ||
226 | fn complete_label_in_loop() { | ||
227 | check( | ||
228 | r#" | ||
229 | fn foo() { | ||
230 | 'foop: loop { | ||
231 | break '$0 | ||
232 | } | ||
233 | } | ||
234 | "#, | ||
235 | expect![[r#" | ||
236 | lb 'foop | ||
237 | "#]], | ||
238 | ); | ||
239 | check( | ||
240 | r#" | ||
241 | fn foo() { | ||
242 | 'foop: loop { | ||
243 | continue '$0 | ||
244 | } | ||
245 | } | ||
246 | "#, | ||
247 | expect![[r#" | ||
248 | lb 'foop | ||
249 | "#]], | ||
250 | ); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn complete_label_in_block_nested() { | ||
255 | check( | ||
256 | r#" | ||
257 | fn foo() { | ||
258 | 'foop: { | ||
259 | 'baap: { | ||
260 | break '$0 | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | "#, | ||
265 | expect![[r#" | ||
266 | lb 'baap | ||
267 | lb 'foop | ||
268 | "#]], | ||
269 | ); | ||
270 | } | ||
271 | |||
272 | #[test] | ||
273 | fn complete_label_in_loop_with_value() { | ||
274 | check( | ||
275 | r#" | ||
276 | fn foo() { | ||
277 | 'foop: loop { | ||
278 | break '$0 i32; | ||
279 | } | ||
280 | } | ||
281 | "#, | ||
282 | expect![[r#" | ||
283 | lb 'foop | ||
284 | "#]], | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn complete_label_in_while_cond() { | ||
290 | check( | ||
291 | r#" | ||
292 | fn foo() { | ||
293 | 'outer: while { 'inner: loop { break '$0 } } {} | ||
294 | } | ||
295 | "#, | ||
296 | expect![[r#" | ||
297 | lb 'inner | ||
298 | lb 'outer | ||
299 | "#]], | ||
300 | ); | ||
301 | } | ||
302 | |||
303 | #[test] | ||
304 | fn complete_label_in_for_iterable() { | ||
305 | check( | ||
306 | r#" | ||
307 | fn foo() { | ||
308 | 'outer: for _ in [{ 'inner: loop { break '$0 } }] {} | ||
309 | } | ||
310 | "#, | ||
311 | expect![[r#" | ||
312 | lb 'inner | ||
313 | "#]], | ||
314 | ); | ||
315 | } | ||
316 | } | ||
diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs index 476eecff0..b06498e6d 100644 --- a/crates/ide_completion/src/completions/pattern.rs +++ b/crates/ide_completion/src/completions/pattern.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! Completes constats and paths in patterns. | 1 | //! Completes constants and paths in patterns. |
2 | 2 | ||
3 | use crate::{CompletionContext, Completions}; | 3 | use crate::{CompletionContext, Completions}; |
4 | 4 | ||
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 6d57da06a..32f81aec1 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -4,8 +4,11 @@ use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; | |||
4 | use ide_db::base_db::{FilePosition, SourceDatabase}; | 4 | use ide_db::base_db::{FilePosition, SourceDatabase}; |
5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; | 5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode, | 7 | algo::find_node_at_offset, |
8 | SyntaxToken, TextRange, TextSize, | 8 | ast::{self, NameOrNameRef, NameOwner}, |
9 | match_ast, AstNode, NodeOrToken, | ||
10 | SyntaxKind::*, | ||
11 | SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
9 | }; | 12 | }; |
10 | 13 | ||
11 | use text_edit::Indel; | 14 | use text_edit::Indel; |
@@ -35,18 +38,22 @@ pub(crate) struct CompletionContext<'a> { | |||
35 | /// The token before the cursor, in the macro-expanded file. | 38 | /// The token before the cursor, in the macro-expanded file. |
36 | pub(super) token: SyntaxToken, | 39 | pub(super) token: SyntaxToken, |
37 | pub(super) krate: Option<hir::Crate>, | 40 | pub(super) krate: Option<hir::Crate>, |
38 | pub(super) expected_name: Option<String>, | 41 | pub(super) expected_name: Option<NameOrNameRef>, |
39 | pub(super) expected_type: Option<Type>, | 42 | pub(super) expected_type: Option<Type>, |
40 | pub(super) name_ref_syntax: Option<ast::NameRef>, | 43 | pub(super) name_ref_syntax: Option<ast::NameRef>, |
44 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | ||
45 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, | ||
41 | pub(super) function_syntax: Option<ast::Fn>, | 46 | pub(super) function_syntax: Option<ast::Fn>, |
42 | pub(super) use_item_syntax: Option<ast::Use>, | 47 | pub(super) use_item_syntax: Option<ast::Use>, |
43 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | 48 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, |
44 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | 49 | pub(super) record_pat_syntax: Option<ast::RecordPat>, |
45 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | 50 | pub(super) record_field_syntax: Option<ast::RecordExprField>, |
46 | pub(super) impl_def: Option<ast::Impl>, | 51 | pub(super) impl_def: Option<ast::Impl>, |
52 | pub(super) lifetime_allowed: bool, | ||
47 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 53 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
48 | pub(super) active_parameter: Option<ActiveParameter>, | 54 | pub(super) active_parameter: Option<ActiveParameter>, |
49 | pub(super) is_param: bool, | 55 | pub(super) is_param: bool, |
56 | pub(super) is_label_ref: bool, | ||
50 | /// If a name-binding or reference to a const in a pattern. | 57 | /// If a name-binding or reference to a const in a pattern. |
51 | /// Irrefutable patterns (like let) are excluded. | 58 | /// Irrefutable patterns (like let) are excluded. |
52 | pub(super) is_pat_binding_or_const: bool, | 59 | pub(super) is_pat_binding_or_const: bool, |
@@ -136,9 +143,12 @@ impl<'a> CompletionContext<'a> { | |||
136 | original_token, | 143 | original_token, |
137 | token, | 144 | token, |
138 | krate, | 145 | krate, |
146 | lifetime_allowed: false, | ||
139 | expected_name: None, | 147 | expected_name: None, |
140 | expected_type: None, | 148 | expected_type: None, |
141 | name_ref_syntax: None, | 149 | name_ref_syntax: None, |
150 | lifetime_syntax: None, | ||
151 | lifetime_param_syntax: None, | ||
142 | function_syntax: None, | 152 | function_syntax: None, |
143 | use_item_syntax: None, | 153 | use_item_syntax: None, |
144 | record_lit_syntax: None, | 154 | record_lit_syntax: None, |
@@ -146,6 +156,7 @@ impl<'a> CompletionContext<'a> { | |||
146 | record_field_syntax: None, | 156 | record_field_syntax: None, |
147 | impl_def: None, | 157 | impl_def: None, |
148 | active_parameter: ActiveParameter::at(db, position), | 158 | active_parameter: ActiveParameter::at(db, position), |
159 | is_label_ref: false, | ||
149 | is_param: false, | 160 | is_param: false, |
150 | is_pat_binding_or_const: false, | 161 | is_pat_binding_or_const: false, |
151 | is_irrefutable_pat_binding: false, | 162 | is_irrefutable_pat_binding: false, |
@@ -241,9 +252,13 @@ impl<'a> CompletionContext<'a> { | |||
241 | pub(crate) fn source_range(&self) -> TextRange { | 252 | pub(crate) fn source_range(&self) -> TextRange { |
242 | // check kind of macro-expanded token, but use range of original token | 253 | // check kind of macro-expanded token, but use range of original token |
243 | let kind = self.token.kind(); | 254 | let kind = self.token.kind(); |
244 | if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { | 255 | if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() { |
245 | cov_mark::hit!(completes_if_prefix_is_keyword); | 256 | cov_mark::hit!(completes_if_prefix_is_keyword); |
246 | self.original_token.text_range() | 257 | self.original_token.text_range() |
258 | } else if kind == CHAR { | ||
259 | // assume we are completing a lifetime but the user has only typed the ' | ||
260 | cov_mark::hit!(completes_if_lifetime_without_idents); | ||
261 | TextRange::at(self.original_token.text_range().start(), TextSize::from(1)) | ||
247 | } else { | 262 | } else { |
248 | TextRange::empty(self.position.offset) | 263 | TextRange::empty(self.position.offset) |
249 | } | 264 | } |
@@ -292,13 +307,13 @@ impl<'a> CompletionContext<'a> { | |||
292 | file_with_fake_ident: SyntaxNode, | 307 | file_with_fake_ident: SyntaxNode, |
293 | offset: TextSize, | 308 | offset: TextSize, |
294 | ) { | 309 | ) { |
295 | let expected = { | 310 | let (expected_type, expected_name) = { |
296 | let mut node = match self.token.parent() { | 311 | let mut node = match self.token.parent() { |
297 | Some(it) => it, | 312 | Some(it) => it, |
298 | None => return, | 313 | None => return, |
299 | }; | 314 | }; |
300 | loop { | 315 | loop { |
301 | let ret = match_ast! { | 316 | break match_ast! { |
302 | match node { | 317 | match node { |
303 | ast::LetStmt(it) => { | 318 | ast::LetStmt(it) => { |
304 | cov_mark::hit!(expected_type_let_with_leading_char); | 319 | cov_mark::hit!(expected_type_let_with_leading_char); |
@@ -306,7 +321,7 @@ impl<'a> CompletionContext<'a> { | |||
306 | let ty = it.pat() | 321 | let ty = it.pat() |
307 | .and_then(|pat| self.sema.type_of_pat(&pat)); | 322 | .and_then(|pat| self.sema.type_of_pat(&pat)); |
308 | let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { | 323 | let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { |
309 | Some(ident.syntax().text().to_string()) | 324 | ident.name().map(NameOrNameRef::Name) |
310 | } else { | 325 | } else { |
311 | None | 326 | None |
312 | }; | 327 | }; |
@@ -319,7 +334,10 @@ impl<'a> CompletionContext<'a> { | |||
319 | ActiveParameter::at_token( | 334 | ActiveParameter::at_token( |
320 | &self.sema, | 335 | &self.sema, |
321 | self.token.clone(), | 336 | self.token.clone(), |
322 | ).map(|ap| (Some(ap.ty), Some(ap.name))) | 337 | ).map(|ap| { |
338 | let name = ap.ident().map(NameOrNameRef::Name); | ||
339 | (Some(ap.ty), name) | ||
340 | }) | ||
323 | .unwrap_or((None, None)) | 341 | .unwrap_or((None, None)) |
324 | }, | 342 | }, |
325 | ast::RecordExprFieldList(_it) => { | 343 | ast::RecordExprFieldList(_it) => { |
@@ -327,10 +345,10 @@ impl<'a> CompletionContext<'a> { | |||
327 | self.token.prev_sibling_or_token() | 345 | self.token.prev_sibling_or_token() |
328 | .and_then(|se| se.into_node()) | 346 | .and_then(|se| se.into_node()) |
329 | .and_then(|node| ast::RecordExprField::cast(node)) | 347 | .and_then(|node| ast::RecordExprField::cast(node)) |
330 | .and_then(|rf| self.sema.resolve_record_field(&rf)) | 348 | .and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf))) |
331 | .map(|f|( | 349 | .map(|(f, rf)|( |
332 | Some(f.0.signature_ty(self.db)), | 350 | Some(f.0.signature_ty(self.db)), |
333 | Some(f.0.name(self.db).to_string()), | 351 | rf.field_name().map(NameOrNameRef::NameRef), |
334 | )) | 352 | )) |
335 | .unwrap_or((None, None)) | 353 | .unwrap_or((None, None)) |
336 | }, | 354 | }, |
@@ -340,7 +358,7 @@ impl<'a> CompletionContext<'a> { | |||
340 | .resolve_record_field(&it) | 358 | .resolve_record_field(&it) |
341 | .map(|f|( | 359 | .map(|f|( |
342 | Some(f.0.signature_ty(self.db)), | 360 | Some(f.0.signature_ty(self.db)), |
343 | Some(f.0.name(self.db).to_string()), | 361 | it.field_name().map(NameOrNameRef::NameRef), |
344 | )) | 362 | )) |
345 | .unwrap_or((None, None)) | 363 | .unwrap_or((None, None)) |
346 | }, | 364 | }, |
@@ -378,14 +396,17 @@ impl<'a> CompletionContext<'a> { | |||
378 | }, | 396 | }, |
379 | } | 397 | } |
380 | }; | 398 | }; |
381 | |||
382 | break ret; | ||
383 | } | 399 | } |
384 | }; | 400 | }; |
385 | self.expected_type = expected.0; | 401 | self.expected_type = expected_type; |
386 | self.expected_name = expected.1; | 402 | self.expected_name = expected_name; |
387 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | 403 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); |
388 | 404 | ||
405 | if let Some(lifetime) = find_node_at_offset::<ast::Lifetime>(&file_with_fake_ident, offset) | ||
406 | { | ||
407 | self.classify_lifetime(original_file, lifetime, offset); | ||
408 | } | ||
409 | |||
389 | // First, let's try to complete a reference to some declaration. | 410 | // First, let's try to complete a reference to some declaration. |
390 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | 411 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
391 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | 412 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. |
@@ -445,6 +466,35 @@ impl<'a> CompletionContext<'a> { | |||
445 | } | 466 | } |
446 | } | 467 | } |
447 | 468 | ||
469 | fn classify_lifetime( | ||
470 | &mut self, | ||
471 | original_file: &SyntaxNode, | ||
472 | lifetime: ast::Lifetime, | ||
473 | offset: TextSize, | ||
474 | ) { | ||
475 | self.lifetime_syntax = | ||
476 | find_node_at_offset(original_file, lifetime.syntax().text_range().start()); | ||
477 | if let Some(parent) = lifetime.syntax().parent() { | ||
478 | if parent.kind() == ERROR { | ||
479 | return; | ||
480 | } | ||
481 | |||
482 | match_ast! { | ||
483 | match parent { | ||
484 | ast::LifetimeParam(_it) => { | ||
485 | self.lifetime_allowed = true; | ||
486 | self.lifetime_param_syntax = | ||
487 | self.sema.find_node_at_offset_with_macros(original_file, offset); | ||
488 | }, | ||
489 | ast::BreakExpr(_it) => self.is_label_ref = true, | ||
490 | ast::ContinueExpr(_it) => self.is_label_ref = true, | ||
491 | ast::Label(_it) => (), | ||
492 | _ => self.lifetime_allowed = true, | ||
493 | } | ||
494 | } | ||
495 | } | ||
496 | } | ||
497 | |||
448 | fn classify_name_ref( | 498 | fn classify_name_ref( |
449 | &mut self, | 499 | &mut self, |
450 | original_file: &SyntaxNode, | 500 | original_file: &SyntaxNode, |
@@ -452,11 +502,11 @@ impl<'a> CompletionContext<'a> { | |||
452 | offset: TextSize, | 502 | offset: TextSize, |
453 | ) { | 503 | ) { |
454 | self.name_ref_syntax = | 504 | self.name_ref_syntax = |
455 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); | 505 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); |
456 | let name_range = name_ref.syntax().text_range(); | 506 | let name_range = name_ref.syntax().text_range(); |
457 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | 507 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { |
458 | self.record_lit_syntax = | 508 | self.record_lit_syntax = |
459 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | 509 | self.sema.find_node_at_offset_with_macros(original_file, offset); |
460 | } | 510 | } |
461 | 511 | ||
462 | self.fill_impl_def(); | 512 | self.fill_impl_def(); |
@@ -631,7 +681,9 @@ mod tests { | |||
631 | .map(|t| t.display_test(&db).to_string()) | 681 | .map(|t| t.display_test(&db).to_string()) |
632 | .unwrap_or("?".to_owned()); | 682 | .unwrap_or("?".to_owned()); |
633 | 683 | ||
634 | let name = completion_context.expected_name.unwrap_or("?".to_owned()); | 684 | let name = completion_context |
685 | .expected_name | ||
686 | .map_or_else(|| "?".to_owned(), |name| name.to_string()); | ||
635 | 687 | ||
636 | expect.assert_eq(&format!("ty: {}, name: {}", ty, name)); | 688 | expect.assert_eq(&format!("ty: {}, name: {}", ty, name)); |
637 | } | 689 | } |
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index d9ea7b7ea..5ac1cb48d 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -14,7 +14,10 @@ mod completions; | |||
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, | 16 | base_db::FilePosition, |
17 | helpers::{import_assets::LocatedImport, insert_use::ImportScope}, | 17 | helpers::{ |
18 | import_assets::{LocatedImport, NameToImport}, | ||
19 | insert_use::ImportScope, | ||
20 | }, | ||
18 | items_locator, RootDatabase, | 21 | items_locator, RootDatabase, |
19 | }; | 22 | }; |
20 | use text_edit::TextEdit; | 23 | use text_edit::TextEdit; |
@@ -130,6 +133,8 @@ pub fn completions( | |||
130 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); | 133 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); |
131 | completions::mod_::complete_mod(&mut acc, &ctx); | 134 | completions::mod_::complete_mod(&mut acc, &ctx); |
132 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); | 135 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); |
136 | completions::lifetime::complete_lifetime(&mut acc, &ctx); | ||
137 | completions::lifetime::complete_label(&mut acc, &ctx); | ||
133 | 138 | ||
134 | Some(acc) | 139 | Some(acc) |
135 | } | 140 | } |
@@ -149,15 +154,19 @@ pub fn resolve_completion_edits( | |||
149 | let current_module = ctx.sema.scope(position_for_import).module()?; | 154 | let current_module = ctx.sema.scope(position_for_import).module()?; |
150 | let current_crate = current_module.krate(); | 155 | let current_crate = current_module.krate(); |
151 | 156 | ||
152 | let (import_path, item_to_import) = | 157 | let (import_path, item_to_import) = items_locator::items_with_name( |
153 | items_locator::with_exact_name(&ctx.sema, current_crate, imported_name) | 158 | &ctx.sema, |
154 | .into_iter() | 159 | current_crate, |
155 | .filter_map(|candidate| { | 160 | NameToImport::Exact(imported_name), |
156 | current_module | 161 | items_locator::AssocItemSearch::Include, |
157 | .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind) | 162 | Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), |
158 | .zip(Some(candidate)) | 163 | ) |
159 | }) | 164 | .filter_map(|candidate| { |
160 | .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?; | 165 | current_module |
166 | .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind) | ||
167 | .zip(Some(candidate)) | ||
168 | }) | ||
169 | .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?; | ||
161 | let import = | 170 | let import = |
162 | LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path)); | 171 | LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path)); |
163 | 172 | ||
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index cf5ef07b7..d82564381 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs | |||
@@ -71,7 +71,7 @@ fn test_has_block_expr_parent() { | |||
71 | } | 71 | } |
72 | 72 | ||
73 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | 73 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { |
74 | element.ancestors().find(|it| it.kind() == IDENT_PAT).is_some() | 74 | element.ancestors().any(|it| it.kind() == IDENT_PAT) |
75 | } | 75 | } |
76 | #[test] | 76 | #[test] |
77 | fn test_has_bind_pat_parent() { | 77 | fn test_has_bind_pat_parent() { |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 12921e12b..23e00aa47 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -219,6 +219,7 @@ impl<'a> Render<'a> { | |||
219 | hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, | 219 | hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, |
220 | }), | 220 | }), |
221 | ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), | 221 | ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), |
222 | ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label), | ||
222 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { | 223 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { |
223 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) | 224 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) |
224 | } | 225 | } |
@@ -243,7 +244,7 @@ impl<'a> Render<'a> { | |||
243 | 244 | ||
244 | item.set_relevance(CompletionRelevance { | 245 | item.set_relevance(CompletionRelevance { |
245 | exact_type_match: compute_exact_type_match(self.ctx.completion, &ty), | 246 | exact_type_match: compute_exact_type_match(self.ctx.completion, &ty), |
246 | exact_name_match: compute_exact_name_match(self.ctx.completion, local_name.clone()), | 247 | exact_name_match: compute_exact_name_match(self.ctx.completion, &local_name), |
247 | is_local: true, | 248 | is_local: true, |
248 | ..CompletionRelevance::default() | 249 | ..CompletionRelevance::default() |
249 | }); | 250 | }); |
@@ -319,8 +320,7 @@ fn compute_exact_type_match(ctx: &CompletionContext, completion_ty: &hir::Type) | |||
319 | 320 | ||
320 | fn compute_exact_name_match(ctx: &CompletionContext, completion_name: impl Into<String>) -> bool { | 321 | fn compute_exact_name_match(ctx: &CompletionContext, completion_name: impl Into<String>) -> bool { |
321 | let completion_name = completion_name.into(); | 322 | let completion_name = completion_name.into(); |
322 | 323 | ctx.expected_name.as_ref().map_or(false, |name| name.text() == completion_name) | |
323 | Some(&completion_name) == ctx.expected_name.as_ref() | ||
324 | } | 324 | } |
325 | 325 | ||
326 | fn compute_ref_match(ctx: &CompletionContext, completion_ty: &hir::Type) -> Option<Mutability> { | 326 | fn compute_ref_match(ctx: &CompletionContext, completion_ty: &hir::Type) -> Option<Mutability> { |
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs index 3fa21ba7c..7578ad50b 100644 --- a/crates/ide_completion/src/render/macro_.rs +++ b/crates/ide_completion/src/render/macro_.rs | |||
@@ -91,7 +91,7 @@ impl<'a> MacroRender<'a> { | |||
91 | } | 91 | } |
92 | 92 | ||
93 | fn detail(&self) -> Option<String> { | 93 | fn detail(&self) -> Option<String> { |
94 | let ast_node = self.macro_.source(self.ctx.db())?.value; | 94 | let ast_node = self.macro_.source(self.ctx.db())?.value.left()?; |
95 | Some(macro_label(&ast_node)) | 95 | Some(macro_label(&ast_node)) |
96 | } | 96 | } |
97 | } | 97 | } |