aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-03-20 22:32:25 +0000
committerGitHub <[email protected]>2021-03-20 22:32:25 +0000
commit090e013161ab5b1679554ddd53683e81e3fe845a (patch)
treeffcf20a2b041f8fada69f7dfbc4adb1e129327f1 /crates/ide_completion
parentbe3dc673e2f00eaa7cfbf4727cc69032ed0b6179 (diff)
parent3c000c6364ebcf94652d221ee9ffe8970540589c (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.rs29
-rw-r--r--crates/ide_completion/src/completions/lifetime.rs181
-rw-r--r--crates/ide_completion/src/completions/pattern.rs2
-rw-r--r--crates/ide_completion/src/context.rs34
-rw-r--r--crates/ide_completion/src/lib.rs1
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
3pub(crate) mod attribute; 3pub(crate) mod attribute;
4pub(crate) mod dot; 4pub(crate) mod dot;
5pub(crate) mod record; 5pub(crate) mod flyimport;
6pub(crate) mod pattern;
7pub(crate) mod fn_param; 6pub(crate) mod fn_param;
8pub(crate) mod keyword; 7pub(crate) mod keyword;
9pub(crate) mod snippet; 8pub(crate) mod lifetime;
10pub(crate) mod qualified_path;
11pub(crate) mod unqualified_path;
12pub(crate) mod postfix;
13pub(crate) mod macro_in_item_position; 9pub(crate) mod macro_in_item_position;
14pub(crate) mod trait_impl;
15pub(crate) mod mod_; 10pub(crate) mod mod_;
16pub(crate) mod flyimport; 11pub(crate) mod pattern;
12pub(crate) mod postfix;
13pub(crate) mod qualified_path;
14pub(crate) mod record;
15pub(crate) mod snippet;
16pub(crate) mod trait_impl;
17pub(crate) mod unqualified_path;
17 18
18use std::iter; 19use std::iter;
19 20
20use hir::{known, ModPath, ScopeDef, Type}; 21use hir::{known, ModPath, ScopeDef, Type};
22use ide_db::SymbolKind;
21 23
22use crate::{ 24use 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.
2use hir::ScopeDef;
3
4use crate::{completions::Completions, context::CompletionContext};
5
6/// Completes lifetimes.
7pub(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)]
33mod 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#"
55fn func<'lifetime>(foo: &'li$0) {}
56"#,
57 r#"
58fn func<'lifetime>(foo: &'lifetime) {}
59"#,
60 );
61 }
62
63 #[test]
64 fn complete_lifetime_in_ref() {
65 check(
66 r#"
67fn 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#"
80fn 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#"
92struct Foo;
93impl<'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#"
109struct Foo<'lt>;
110fn 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#"
123fn 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#"
136fn foo2<'lifetime, T>() where T: 'a$0 {}
137"#,
138 expect![[r#"
139 lt 'lifetime
140 lt 'static
141 "#]],
142 );
143 check(
144 r#"
145fn 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#"
158fn 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#"
168fn foo<'a$0>() {}
169"#,
170 expect![[r#""#]],
171 );
172 check(
173 r#"
174fn 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
3use crate::{CompletionContext, Completions}; 3use 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}