Skip to content

Commit 090e013

Browse files
bors[bot]Veykril
andauthored
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]>
2 parents be3dc67 + 3c000c6 commit 090e013

File tree

5 files changed

+233
-14
lines changed

5 files changed

+233
-14
lines changed

crates/ide_completion/src/completions.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,27 @@
22
33
pub(crate) mod attribute;
44
pub(crate) mod dot;
5-
pub(crate) mod record;
6-
pub(crate) mod pattern;
5+
pub(crate) mod flyimport;
76
pub(crate) mod fn_param;
87
pub(crate) mod keyword;
9-
pub(crate) mod snippet;
10-
pub(crate) mod qualified_path;
11-
pub(crate) mod unqualified_path;
12-
pub(crate) mod postfix;
8+
pub(crate) mod lifetime;
139
pub(crate) mod macro_in_item_position;
14-
pub(crate) mod trait_impl;
1510
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;
1718

1819
use std::iter;
1920

2021
use hir::{known, ModPath, ScopeDef, Type};
22+
use ide_db::SymbolKind;
2123

2224
use crate::{
23-
item::Builder,
25+
item::{Builder, CompletionKind},
2426
render::{
2527
const_::render_const,
2628
enum_variant::render_variant,
@@ -31,7 +33,7 @@ use crate::{
3133
type_alias::render_type_alias,
3234
RenderContext,
3335
},
34-
CompletionContext, CompletionItem,
36+
CompletionContext, CompletionItem, CompletionItemKind,
3537
};
3638

3739
/// Represents an in-progress set of completions being built.
@@ -77,6 +79,13 @@ impl Completions {
7779
self.add(item);
7880
}
7981

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+
8089
pub(crate) fn add_resolution(
8190
&mut self,
8291
ctx: &CompletionContext,
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

crates/ide_completion/src/completions/pattern.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Completes constats and paths in patterns.
1+
//! Completes constants and paths in patterns.
22
33
use crate::{CompletionContext, Completions};
44

crates/ide_completion/src/context.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@ pub(crate) struct CompletionContext<'a> {
4141
pub(super) expected_name: Option<NameOrNameRef>,
4242
pub(super) expected_type: Option<Type>,
4343
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>,
4446
pub(super) function_syntax: Option<ast::Fn>,
4547
pub(super) use_item_syntax: Option<ast::Use>,
4648
pub(super) record_lit_syntax: Option<ast::RecordExpr>,
4749
pub(super) record_pat_syntax: Option<ast::RecordPat>,
4850
pub(super) record_field_syntax: Option<ast::RecordExprField>,
4951
pub(super) impl_def: Option<ast::Impl>,
52+
pub(super) lifetime_allowed: bool,
5053
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
5154
pub(super) active_parameter: Option<ActiveParameter>,
5255
pub(super) is_param: bool,
@@ -139,9 +142,12 @@ impl<'a> CompletionContext<'a> {
139142
original_token,
140143
token,
141144
krate,
145+
lifetime_allowed: false,
142146
expected_name: None,
143147
expected_type: None,
144148
name_ref_syntax: None,
149+
lifetime_syntax: None,
150+
lifetime_param_syntax: None,
145151
function_syntax: None,
146152
use_item_syntax: None,
147153
record_lit_syntax: None,
@@ -244,7 +250,7 @@ impl<'a> CompletionContext<'a> {
244250
pub(crate) fn source_range(&self) -> TextRange {
245251
// check kind of macro-expanded token, but use range of original token
246252
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() {
248254
cov_mark::hit!(completes_if_prefix_is_keyword);
249255
self.original_token.text_range()
250256
} else {
@@ -390,6 +396,11 @@ impl<'a> CompletionContext<'a> {
390396
self.expected_name = expected_name;
391397
self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
392398

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+
393404
// First, let's try to complete a reference to some declaration.
394405
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
395406
// Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
@@ -449,18 +460,35 @@ impl<'a> CompletionContext<'a> {
449460
}
450461
}
451462

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+
452480
fn classify_name_ref(
453481
&mut self,
454482
original_file: &SyntaxNode,
455483
name_ref: ast::NameRef,
456484
offset: TextSize,
457485
) {
458486
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());
460488
let name_range = name_ref.syntax().text_range();
461489
if ast::RecordExprField::for_field_name(&name_ref).is_some() {
462490
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);
464492
}
465493

466494
self.fill_impl_def();

crates/ide_completion/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ pub fn completions(
130130
completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
131131
completions::mod_::complete_mod(&mut acc, &ctx);
132132
completions::flyimport::import_on_the_fly(&mut acc, &ctx);
133+
completions::lifetime::complete_lifetime(&mut acc, &ctx);
133134

134135
Some(acc)
135136
}

0 commit comments

Comments
 (0)