Skip to content

Commit b1315be

Browse files
bors[bot]popzxc
andauthored
Merge #6262
6262: Do not spawn redundant hints r=SomeoneToIgnore a=popzxc Closes #5206 This is a second part of the fix (first was #5997). This PR adds a new method to the `CompletionContext`: `no_completion_required`. If this method returns `true`, it essentially means that user is unlikely to expect any hints from the IDE at this cursor position. Currently, checks for the following cases were added: - Previous item is `fn`: user creates a new function, names of existing functions won't be helpful. Exception for this case is `impl Foo for Bar` -- we must suggest trait function names. - User entered `for _ i<|>`: it's obviously going to be `in` keyword, any hints here will be confusing. More checks may be added there later, but currently I've only figured two cases. ![no_redundant_hints](https://user-images.githubusercontent.com/12111581/96332088-da4d2a00-106a-11eb-89a1-1159ece18f9d.png) Co-authored-by: Igor Aleksanov <[email protected]>
2 parents 59483c2 + 99c4359 commit b1315be

File tree

4 files changed

+131
-5
lines changed

4 files changed

+131
-5
lines changed

crates/ide/src/completion.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ pub(crate) fn completions(
112112
) -> Option<Completions> {
113113
let ctx = CompletionContext::new(db, position, config)?;
114114

115+
if ctx.no_completion_required() {
116+
// No work required here.
117+
return None;
118+
}
119+
115120
let mut acc = Completions::default();
116121
complete_attribute::complete_attribute(&mut acc, &ctx);
117122
complete_fn_param::complete_fn_param(&mut acc, &ctx);
@@ -157,6 +162,23 @@ mod tests {
157162
panic!("completion detail not found: {}", expected.detail)
158163
}
159164

165+
fn check_no_completion(ra_fixture: &str) {
166+
let (analysis, position) = fixture::position(ra_fixture);
167+
let config = CompletionConfig::default();
168+
analysis.completions(&config, position).unwrap();
169+
170+
let completions: Option<Vec<String>> = analysis
171+
.completions(&config, position)
172+
.unwrap()
173+
.and_then(|completions| if completions.is_empty() { None } else { Some(completions) })
174+
.map(|completions| {
175+
completions.into_iter().map(|completion| format!("{:?}", completion)).collect()
176+
});
177+
178+
// `assert_eq` instead of `assert!(completions.is_none())` to get the list of completions if test will panic.
179+
assert_eq!(completions, None, "Completions were generated, but weren't expected");
180+
}
181+
160182
#[test]
161183
fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
162184
check_detail_and_documentation(
@@ -208,4 +230,31 @@ mod tests {
208230
DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
209231
);
210232
}
233+
234+
#[test]
235+
fn test_no_completions_required() {
236+
// There must be no hint for 'in' keyword.
237+
check_no_completion(
238+
r#"
239+
fn foo() {
240+
for i i<|>
241+
}
242+
"#,
243+
);
244+
// After 'in' keyword hints may be spawned.
245+
check_detail_and_documentation(
246+
r#"
247+
/// Do the foo
248+
fn foo() -> &'static str { "foo" }
249+
250+
fn bar() {
251+
for c in fo<|>
252+
}
253+
"#,
254+
DetailAndDocumentation {
255+
detail: "fn foo() -> &'static str",
256+
documentation: "Do the foo",
257+
},
258+
);
259+
}
211260
}

crates/ide/src/completion/completion_context.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ use crate::{
1616
call_info::ActiveParameter,
1717
completion::{
1818
patterns::{
19-
has_bind_pat_parent, has_block_expr_parent, has_field_list_parent,
20-
has_impl_as_prev_sibling, has_impl_parent, has_item_list_or_source_file_parent,
21-
has_ref_parent, has_trait_as_prev_sibling, has_trait_parent, if_is_prev,
22-
is_in_loop_body, is_match_arm, unsafe_is_prev,
19+
fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent,
20+
has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent,
21+
has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
22+
has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm,
23+
unsafe_is_prev,
2324
},
2425
CompletionConfig,
2526
},
@@ -86,11 +87,14 @@ pub(crate) struct CompletionContext<'a> {
8687
pub(super) in_loop_body: bool,
8788
pub(super) has_trait_parent: bool,
8889
pub(super) has_impl_parent: bool,
90+
pub(super) inside_impl_trait_block: bool,
8991
pub(super) has_field_list_parent: bool,
9092
pub(super) trait_as_prev_sibling: bool,
9193
pub(super) impl_as_prev_sibling: bool,
9294
pub(super) is_match_arm: bool,
9395
pub(super) has_item_list_or_source_file_parent: bool,
96+
pub(super) for_is_prev2: bool,
97+
pub(super) fn_is_prev: bool,
9498
pub(super) locals: Vec<(String, Local)>,
9599
}
96100

@@ -168,12 +172,15 @@ impl<'a> CompletionContext<'a> {
168172
block_expr_parent: false,
169173
has_trait_parent: false,
170174
has_impl_parent: false,
175+
inside_impl_trait_block: false,
171176
has_field_list_parent: false,
172177
trait_as_prev_sibling: false,
173178
impl_as_prev_sibling: false,
174179
if_is_prev: false,
175180
is_match_arm: false,
176181
has_item_list_or_source_file_parent: false,
182+
for_is_prev2: false,
183+
fn_is_prev: false,
177184
locals,
178185
};
179186

@@ -221,6 +228,15 @@ impl<'a> CompletionContext<'a> {
221228
Some(ctx)
222229
}
223230

231+
/// Checks whether completions in that particular case don't make much sense.
232+
/// Examples:
233+
/// - `fn <|>` -- we expect function name, it's unlikely that "hint" will be helpful.
234+
/// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names.
235+
/// - `for _ i<|>` -- obviously, it'll be "in" keyword.
236+
pub(crate) fn no_completion_required(&self) -> bool {
237+
(self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2
238+
}
239+
224240
/// The range of the identifier that is being completed.
225241
pub(crate) fn source_range(&self) -> TextRange {
226242
// check kind of macro-expanded token, but use range of original token
@@ -244,6 +260,7 @@ impl<'a> CompletionContext<'a> {
244260
self.in_loop_body = is_in_loop_body(syntax_element.clone());
245261
self.has_trait_parent = has_trait_parent(syntax_element.clone());
246262
self.has_impl_parent = has_impl_parent(syntax_element.clone());
263+
self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone());
247264
self.has_field_list_parent = has_field_list_parent(syntax_element.clone());
248265
self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
249266
self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
@@ -253,6 +270,8 @@ impl<'a> CompletionContext<'a> {
253270
self.mod_declaration_under_caret =
254271
find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
255272
.filter(|module| module.item_list().is_none());
273+
self.for_is_prev2 = for_is_prev2(syntax_element.clone());
274+
self.fn_is_prev = fn_is_prev(syntax_element.clone());
256275
}
257276

258277
fn fill(

crates/ide/src/completion/patterns.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use syntax::{
99
};
1010

1111
#[cfg(test)]
12-
use crate::completion::test_utils::check_pattern_is_applicable;
12+
use crate::completion::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
1313

1414
pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool {
1515
not_same_range_ancestor(element)
@@ -34,6 +34,25 @@ pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool {
3434
fn test_has_impl_parent() {
3535
check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent);
3636
}
37+
38+
pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
39+
// Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
40+
// where we only check the first parent with different text range.
41+
element
42+
.ancestors()
43+
.find(|it| it.kind() == IMPL)
44+
.map(|it| ast::Impl::cast(it).unwrap())
45+
.map(|it| it.trait_().is_some())
46+
.unwrap_or(false)
47+
}
48+
#[test]
49+
fn test_inside_impl_trait_block() {
50+
check_pattern_is_applicable(r"impl Foo for Bar { f<|> }", inside_impl_trait_block);
51+
check_pattern_is_applicable(r"impl Foo for Bar { fn f<|> }", inside_impl_trait_block);
52+
check_pattern_is_not_applicable(r"impl A { f<|> }", inside_impl_trait_block);
53+
check_pattern_is_not_applicable(r"impl A { fn f<|> }", inside_impl_trait_block);
54+
}
55+
3756
pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool {
3857
not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some()
3958
}
@@ -116,6 +135,33 @@ pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
116135
.is_some()
117136
}
118137

138+
pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool {
139+
element
140+
.into_token()
141+
.and_then(|it| previous_non_trivia_token(it))
142+
.filter(|it| it.kind() == FN_KW)
143+
.is_some()
144+
}
145+
#[test]
146+
fn test_fn_is_prev() {
147+
check_pattern_is_applicable(r"fn l<|>", fn_is_prev);
148+
}
149+
150+
/// Check if the token previous to the previous one is `for`.
151+
/// For example, `for _ i<|>` => true.
152+
pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
153+
element
154+
.into_token()
155+
.and_then(|it| previous_non_trivia_token(it))
156+
.and_then(|it| previous_non_trivia_token(it))
157+
.filter(|it| it.kind() == FOR_KW)
158+
.is_some()
159+
}
160+
#[test]
161+
fn test_for_is_prev2() {
162+
check_pattern_is_applicable(r"for i i<|>", for_is_prev2);
163+
}
164+
119165
#[test]
120166
fn test_if_is_prev() {
121167
check_pattern_is_applicable(r"if l<|>", if_is_prev);

crates/ide/src/completion/test_utils.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -
104104
.unwrap();
105105
}
106106

107+
pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
108+
let (analysis, pos) = fixture::position(code);
109+
analysis
110+
.with_db(|db| {
111+
let sema = Semantics::new(db);
112+
let original_file = sema.parse(pos.file_id);
113+
let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
114+
assert!(!check(NodeOrToken::Token(token)));
115+
})
116+
.unwrap();
117+
}
118+
107119
pub(crate) fn get_all_completion_items(
108120
config: CompletionConfig,
109121
code: &str,

0 commit comments

Comments
 (0)