diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-03-20 22:32:25 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-03-20 22:32:25 +0000 |
commit | 090e013161ab5b1679554ddd53683e81e3fe845a (patch) | |
tree | ffcf20a2b041f8fada69f7dfbc4adb1e129327f1 /crates/ide_completion | |
parent | be3dc673e2f00eaa7cfbf4727cc69032ed0b6179 (diff) | |
parent | 3c000c6364ebcf94652d221ee9ffe8970540589c (diff) |
Merge #8124
8124: Add basic lifetime completion r=Veykril a=Veykril
This adds basic lifetime completion, basic in the sense that the completions for lifetimes are only shown when the user enters `'` followed by a char. Showing them when nothing is entered is kind of a pain, as we would want them to only show up where they are useful which in turn requires a lot of tree traversal and cursor position checking to verify whether the position is valid for a lifetime. This in itself doesn't seem too bad as usually when you know you want to write a lifetime putting `'` to ask for lifetime completions seems fine.
~~I'll take a look at whether its possible to lift the restriction of having to put a char after `'`.~~ This actually already works so I guess this is the clients responsibility, in which case VSCode doesn't like it.
![TYH9gIlyVo](https://user-images.githubusercontent.com/3757771/111886437-c9b02f80-89cd-11eb-9bee-340f1536b0de.gif)
Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates/ide_completion')
-rw-r--r-- | crates/ide_completion/src/completions.rs | 29 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/lifetime.rs | 181 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/pattern.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 34 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 1 |
5 files changed, 233 insertions, 14 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/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs new file mode 100644 index 000000000..74eb23360 --- /dev/null +++ b/crates/ide_completion/src/completions/lifetime.rs | |||
@@ -0,0 +1,181 @@ | |||
1 | //! Completes lifetimes. | ||
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 | #[cfg(test)] | ||
33 | mod tests { | ||
34 | use expect_test::{expect, Expect}; | ||
35 | |||
36 | use crate::{ | ||
37 | test_utils::{check_edit, completion_list_with_config, TEST_CONFIG}, | ||
38 | CompletionConfig, CompletionKind, | ||
39 | }; | ||
40 | |||
41 | fn check(ra_fixture: &str, expect: Expect) { | ||
42 | check_with_config(TEST_CONFIG, ra_fixture, expect); | ||
43 | } | ||
44 | |||
45 | fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) { | ||
46 | let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference); | ||
47 | expect.assert_eq(&actual) | ||
48 | } | ||
49 | |||
50 | #[test] | ||
51 | fn check_lifetime_edit() { | ||
52 | check_edit( | ||
53 | "'lifetime", | ||
54 | r#" | ||
55 | fn func<'lifetime>(foo: &'li$0) {} | ||
56 | "#, | ||
57 | r#" | ||
58 | fn func<'lifetime>(foo: &'lifetime) {} | ||
59 | "#, | ||
60 | ); | ||
61 | } | ||
62 | |||
63 | #[test] | ||
64 | fn complete_lifetime_in_ref() { | ||
65 | check( | ||
66 | r#" | ||
67 | fn foo<'lifetime>(foo: &'a$0 usize) {} | ||
68 | "#, | ||
69 | expect![[r#" | ||
70 | lt 'lifetime | ||
71 | lt 'static | ||
72 | "#]], | ||
73 | ); | ||
74 | } | ||
75 | |||
76 | #[test] | ||
77 | fn complete_lifetime_in_ref_missing_ty() { | ||
78 | check( | ||
79 | r#" | ||
80 | fn foo<'lifetime>(foo: &'a$0) {} | ||
81 | "#, | ||
82 | expect![[r#" | ||
83 | lt 'lifetime | ||
84 | lt 'static | ||
85 | "#]], | ||
86 | ); | ||
87 | } | ||
88 | #[test] | ||
89 | fn complete_lifetime_in_self_ref() { | ||
90 | check( | ||
91 | r#" | ||
92 | struct Foo; | ||
93 | impl<'impl> Foo { | ||
94 | fn foo<'func>(&'a$0 self) {} | ||
95 | } | ||
96 | "#, | ||
97 | expect![[r#" | ||
98 | lt 'func | ||
99 | lt 'impl | ||
100 | lt 'static | ||
101 | "#]], | ||
102 | ); | ||
103 | } | ||
104 | |||
105 | #[test] | ||
106 | fn complete_lifetime_in_arg_list() { | ||
107 | check( | ||
108 | r#" | ||
109 | struct Foo<'lt>; | ||
110 | fn foo<'lifetime>(_: Foo<'a$0>) {} | ||
111 | "#, | ||
112 | expect![[r#" | ||
113 | lt 'lifetime | ||
114 | lt 'static | ||
115 | "#]], | ||
116 | ); | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn complete_lifetime_in_where_pred() { | ||
121 | check( | ||
122 | r#" | ||
123 | fn foo2<'lifetime, T>() where 'a$0 {} | ||
124 | "#, | ||
125 | expect![[r#" | ||
126 | lt 'lifetime | ||
127 | lt 'static | ||
128 | "#]], | ||
129 | ); | ||
130 | } | ||
131 | |||
132 | #[test] | ||
133 | fn complete_lifetime_in_ty_bound() { | ||
134 | check( | ||
135 | r#" | ||
136 | fn foo2<'lifetime, T>() where T: 'a$0 {} | ||
137 | "#, | ||
138 | expect![[r#" | ||
139 | lt 'lifetime | ||
140 | lt 'static | ||
141 | "#]], | ||
142 | ); | ||
143 | check( | ||
144 | r#" | ||
145 | fn foo2<'lifetime, T>() where T: Trait<'a$0> {} | ||
146 | "#, | ||
147 | expect![[r#" | ||
148 | lt 'lifetime | ||
149 | lt 'static | ||
150 | "#]], | ||
151 | ); | ||
152 | } | ||
153 | |||
154 | #[test] | ||
155 | fn dont_complete_lifetime_in_assoc_ty_bound() { | ||
156 | check( | ||
157 | r#" | ||
158 | fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {} | ||
159 | "#, | ||
160 | expect![[r#""#]], | ||
161 | ); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn complete_lifetime_in_param_list() { | ||
166 | check( | ||
167 | r#" | ||
168 | fn foo<'a$0>() {} | ||
169 | "#, | ||
170 | expect![[r#""#]], | ||
171 | ); | ||
172 | check( | ||
173 | r#" | ||
174 | fn foo<'footime, 'lifetime: 'a$0>() {} | ||
175 | "#, | ||
176 | expect![[r#" | ||
177 | lt 'footime | ||
178 | "#]], | ||
179 | ); | ||
180 | } | ||
181 | } | ||
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 a60b8b09d..4c2b31084 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -41,12 +41,15 @@ pub(crate) struct CompletionContext<'a> { | |||
41 | pub(super) expected_name: Option<NameOrNameRef>, | 41 | pub(super) expected_name: Option<NameOrNameRef>, |
42 | pub(super) expected_type: Option<Type>, | 42 | pub(super) expected_type: Option<Type>, |
43 | 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>, | ||
44 | pub(super) function_syntax: Option<ast::Fn>, | 46 | pub(super) function_syntax: Option<ast::Fn>, |
45 | pub(super) use_item_syntax: Option<ast::Use>, | 47 | pub(super) use_item_syntax: Option<ast::Use>, |
46 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | 48 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, |
47 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | 49 | pub(super) record_pat_syntax: Option<ast::RecordPat>, |
48 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | 50 | pub(super) record_field_syntax: Option<ast::RecordExprField>, |
49 | pub(super) impl_def: Option<ast::Impl>, | 51 | pub(super) impl_def: Option<ast::Impl>, |
52 | pub(super) lifetime_allowed: bool, | ||
50 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 53 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
51 | pub(super) active_parameter: Option<ActiveParameter>, | 54 | pub(super) active_parameter: Option<ActiveParameter>, |
52 | pub(super) is_param: bool, | 55 | pub(super) is_param: bool, |
@@ -139,9 +142,12 @@ impl<'a> CompletionContext<'a> { | |||
139 | original_token, | 142 | original_token, |
140 | token, | 143 | token, |
141 | krate, | 144 | krate, |
145 | lifetime_allowed: false, | ||
142 | expected_name: None, | 146 | expected_name: None, |
143 | expected_type: None, | 147 | expected_type: None, |
144 | name_ref_syntax: None, | 148 | name_ref_syntax: None, |
149 | lifetime_syntax: None, | ||
150 | lifetime_param_syntax: None, | ||
145 | function_syntax: None, | 151 | function_syntax: None, |
146 | use_item_syntax: None, | 152 | use_item_syntax: None, |
147 | record_lit_syntax: None, | 153 | record_lit_syntax: None, |
@@ -244,7 +250,7 @@ impl<'a> CompletionContext<'a> { | |||
244 | pub(crate) fn source_range(&self) -> TextRange { | 250 | pub(crate) fn source_range(&self) -> TextRange { |
245 | // check kind of macro-expanded token, but use range of original token | 251 | // check kind of macro-expanded token, but use range of original token |
246 | let kind = self.token.kind(); | 252 | let kind = self.token.kind(); |
247 | if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { | 253 | if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() { |
248 | cov_mark::hit!(completes_if_prefix_is_keyword); | 254 | cov_mark::hit!(completes_if_prefix_is_keyword); |
249 | self.original_token.text_range() | 255 | self.original_token.text_range() |
250 | } else { | 256 | } else { |
@@ -390,6 +396,11 @@ impl<'a> CompletionContext<'a> { | |||
390 | self.expected_name = expected_name; | 396 | self.expected_name = expected_name; |
391 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | 397 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); |
392 | 398 | ||
399 | if let Some(lifetime) = find_node_at_offset::<ast::Lifetime>(&file_with_fake_ident, offset) | ||
400 | { | ||
401 | self.classify_lifetime(original_file, lifetime, offset); | ||
402 | } | ||
403 | |||
393 | // First, let's try to complete a reference to some declaration. | 404 | // First, let's try to complete a reference to some declaration. |
394 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | 405 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
395 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | 406 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. |
@@ -449,6 +460,23 @@ impl<'a> CompletionContext<'a> { | |||
449 | } | 460 | } |
450 | } | 461 | } |
451 | 462 | ||
463 | fn classify_lifetime( | ||
464 | &mut self, | ||
465 | original_file: &SyntaxNode, | ||
466 | lifetime: ast::Lifetime, | ||
467 | offset: TextSize, | ||
468 | ) { | ||
469 | self.lifetime_syntax = | ||
470 | find_node_at_offset(original_file, lifetime.syntax().text_range().start()); | ||
471 | if lifetime.syntax().parent().map_or(false, |p| p.kind() != syntax::SyntaxKind::ERROR) { | ||
472 | self.lifetime_allowed = true; | ||
473 | } | ||
474 | if let Some(_) = lifetime.syntax().parent().and_then(ast::LifetimeParam::cast) { | ||
475 | self.lifetime_param_syntax = | ||
476 | self.sema.find_node_at_offset_with_macros(original_file, offset); | ||
477 | } | ||
478 | } | ||
479 | |||
452 | fn classify_name_ref( | 480 | fn classify_name_ref( |
453 | &mut self, | 481 | &mut self, |
454 | original_file: &SyntaxNode, | 482 | original_file: &SyntaxNode, |
@@ -456,11 +484,11 @@ impl<'a> CompletionContext<'a> { | |||
456 | offset: TextSize, | 484 | offset: TextSize, |
457 | ) { | 485 | ) { |
458 | self.name_ref_syntax = | 486 | self.name_ref_syntax = |
459 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); | 487 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); |
460 | let name_range = name_ref.syntax().text_range(); | 488 | let name_range = name_ref.syntax().text_range(); |
461 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | 489 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { |
462 | self.record_lit_syntax = | 490 | self.record_lit_syntax = |
463 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | 491 | self.sema.find_node_at_offset_with_macros(original_file, offset); |
464 | } | 492 | } |
465 | 493 | ||
466 | self.fill_impl_def(); | 494 | self.fill_impl_def(); |
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index d9ea7b7ea..7a0eb6a96 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -130,6 +130,7 @@ pub fn completions( | |||
130 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); | 130 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); |
131 | completions::mod_::complete_mod(&mut acc, &ctx); | 131 | completions::mod_::complete_mod(&mut acc, &ctx); |
132 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); | 132 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); |
133 | completions::lifetime::complete_lifetime(&mut acc, &ctx); | ||
133 | 134 | ||
134 | Some(acc) | 135 | Some(acc) |
135 | } | 136 | } |