diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-10-17 11:31:37 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-10-17 11:31:37 +0100 |
commit | b1315be010f0d9f67ee4794cb57f7d1baa88a30d (patch) | |
tree | b4259ce1810c3cd6c2bea62bae499415b764728d | |
parent | 59483c217662fc5d89ef9da1cb93760e14a48418 (diff) | |
parent | 99c435939c941fe5216c39bc136769098abbbfea (diff) |
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]>
-rw-r--r-- | crates/ide/src/completion.rs | 49 | ||||
-rw-r--r-- | crates/ide/src/completion/completion_context.rs | 27 | ||||
-rw-r--r-- | crates/ide/src/completion/patterns.rs | 48 | ||||
-rw-r--r-- | crates/ide/src/completion/test_utils.rs | 12 |
4 files changed, 131 insertions, 5 deletions
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs index b0e35b2bd..69e875014 100644 --- a/crates/ide/src/completion.rs +++ b/crates/ide/src/completion.rs | |||
@@ -112,6 +112,11 @@ pub(crate) fn completions( | |||
112 | ) -> Option<Completions> { | 112 | ) -> Option<Completions> { |
113 | let ctx = CompletionContext::new(db, position, config)?; | 113 | let ctx = CompletionContext::new(db, position, config)?; |
114 | 114 | ||
115 | if ctx.no_completion_required() { | ||
116 | // No work required here. | ||
117 | return None; | ||
118 | } | ||
119 | |||
115 | let mut acc = Completions::default(); | 120 | let mut acc = Completions::default(); |
116 | complete_attribute::complete_attribute(&mut acc, &ctx); | 121 | complete_attribute::complete_attribute(&mut acc, &ctx); |
117 | complete_fn_param::complete_fn_param(&mut acc, &ctx); | 122 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
@@ -157,6 +162,23 @@ mod tests { | |||
157 | panic!("completion detail not found: {}", expected.detail) | 162 | panic!("completion detail not found: {}", expected.detail) |
158 | } | 163 | } |
159 | 164 | ||
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 | |||
160 | #[test] | 182 | #[test] |
161 | fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { | 183 | fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { |
162 | check_detail_and_documentation( | 184 | check_detail_and_documentation( |
@@ -208,4 +230,31 @@ mod tests { | |||
208 | DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, | 230 | DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, |
209 | ); | 231 | ); |
210 | } | 232 | } |
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 | } | ||
211 | } | 260 | } |
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs index 8dea8a4bf..d9f90477c 100644 --- a/crates/ide/src/completion/completion_context.rs +++ b/crates/ide/src/completion/completion_context.rs | |||
@@ -16,10 +16,11 @@ use crate::{ | |||
16 | call_info::ActiveParameter, | 16 | call_info::ActiveParameter, |
17 | completion::{ | 17 | completion::{ |
18 | patterns::{ | 18 | patterns::{ |
19 | has_bind_pat_parent, has_block_expr_parent, has_field_list_parent, | 19 | fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent, |
20 | has_impl_as_prev_sibling, has_impl_parent, has_item_list_or_source_file_parent, | 20 | has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent, |
21 | has_ref_parent, has_trait_as_prev_sibling, has_trait_parent, if_is_prev, | 21 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, |
22 | is_in_loop_body, is_match_arm, unsafe_is_prev, | 22 | has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm, |
23 | unsafe_is_prev, | ||
23 | }, | 24 | }, |
24 | CompletionConfig, | 25 | CompletionConfig, |
25 | }, | 26 | }, |
@@ -86,11 +87,14 @@ pub(crate) struct CompletionContext<'a> { | |||
86 | pub(super) in_loop_body: bool, | 87 | pub(super) in_loop_body: bool, |
87 | pub(super) has_trait_parent: bool, | 88 | pub(super) has_trait_parent: bool, |
88 | pub(super) has_impl_parent: bool, | 89 | pub(super) has_impl_parent: bool, |
90 | pub(super) inside_impl_trait_block: bool, | ||
89 | pub(super) has_field_list_parent: bool, | 91 | pub(super) has_field_list_parent: bool, |
90 | pub(super) trait_as_prev_sibling: bool, | 92 | pub(super) trait_as_prev_sibling: bool, |
91 | pub(super) impl_as_prev_sibling: bool, | 93 | pub(super) impl_as_prev_sibling: bool, |
92 | pub(super) is_match_arm: bool, | 94 | pub(super) is_match_arm: bool, |
93 | pub(super) has_item_list_or_source_file_parent: bool, | 95 | pub(super) has_item_list_or_source_file_parent: bool, |
96 | pub(super) for_is_prev2: bool, | ||
97 | pub(super) fn_is_prev: bool, | ||
94 | pub(super) locals: Vec<(String, Local)>, | 98 | pub(super) locals: Vec<(String, Local)>, |
95 | } | 99 | } |
96 | 100 | ||
@@ -168,12 +172,15 @@ impl<'a> CompletionContext<'a> { | |||
168 | block_expr_parent: false, | 172 | block_expr_parent: false, |
169 | has_trait_parent: false, | 173 | has_trait_parent: false, |
170 | has_impl_parent: false, | 174 | has_impl_parent: false, |
175 | inside_impl_trait_block: false, | ||
171 | has_field_list_parent: false, | 176 | has_field_list_parent: false, |
172 | trait_as_prev_sibling: false, | 177 | trait_as_prev_sibling: false, |
173 | impl_as_prev_sibling: false, | 178 | impl_as_prev_sibling: false, |
174 | if_is_prev: false, | 179 | if_is_prev: false, |
175 | is_match_arm: false, | 180 | is_match_arm: false, |
176 | has_item_list_or_source_file_parent: false, | 181 | has_item_list_or_source_file_parent: false, |
182 | for_is_prev2: false, | ||
183 | fn_is_prev: false, | ||
177 | locals, | 184 | locals, |
178 | }; | 185 | }; |
179 | 186 | ||
@@ -221,6 +228,15 @@ impl<'a> CompletionContext<'a> { | |||
221 | Some(ctx) | 228 | Some(ctx) |
222 | } | 229 | } |
223 | 230 | ||
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 | |||
224 | /// The range of the identifier that is being completed. | 240 | /// The range of the identifier that is being completed. |
225 | pub(crate) fn source_range(&self) -> TextRange { | 241 | pub(crate) fn source_range(&self) -> TextRange { |
226 | // check kind of macro-expanded token, but use range of original token | 242 | // check kind of macro-expanded token, but use range of original token |
@@ -244,6 +260,7 @@ impl<'a> CompletionContext<'a> { | |||
244 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | 260 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); |
245 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | 261 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); |
246 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | 262 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); |
263 | self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | ||
247 | self.has_field_list_parent = has_field_list_parent(syntax_element.clone()); | 264 | self.has_field_list_parent = has_field_list_parent(syntax_element.clone()); |
248 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | 265 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); |
249 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | 266 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); |
@@ -253,6 +270,8 @@ impl<'a> CompletionContext<'a> { | |||
253 | self.mod_declaration_under_caret = | 270 | self.mod_declaration_under_caret = |
254 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) | 271 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) |
255 | .filter(|module| module.item_list().is_none()); | 272 | .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()); | ||
256 | } | 275 | } |
257 | 276 | ||
258 | fn fill( | 277 | fn fill( |
diff --git a/crates/ide/src/completion/patterns.rs b/crates/ide/src/completion/patterns.rs index b17ddf133..cf6d5947d 100644 --- a/crates/ide/src/completion/patterns.rs +++ b/crates/ide/src/completion/patterns.rs | |||
@@ -9,7 +9,7 @@ use syntax::{ | |||
9 | }; | 9 | }; |
10 | 10 | ||
11 | #[cfg(test)] | 11 | #[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}; |
13 | 13 | ||
14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | 14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { |
15 | not_same_range_ancestor(element) | 15 | not_same_range_ancestor(element) |
@@ -34,6 +34,25 @@ pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | |||
34 | fn test_has_impl_parent() { | 34 | fn test_has_impl_parent() { |
35 | check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); | 35 | check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); |
36 | } | 36 | } |
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 | |||
37 | pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool { | 56 | pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool { |
38 | not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some() | 57 | not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some() |
39 | } | 58 | } |
@@ -116,6 +135,33 @@ pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | |||
116 | .is_some() | 135 | .is_some() |
117 | } | 136 | } |
118 | 137 | ||
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 | |||
119 | #[test] | 165 | #[test] |
120 | fn test_if_is_prev() { | 166 | fn test_if_is_prev() { |
121 | check_pattern_is_applicable(r"if l<|>", if_is_prev); | 167 | check_pattern_is_applicable(r"if l<|>", if_is_prev); |
diff --git a/crates/ide/src/completion/test_utils.rs b/crates/ide/src/completion/test_utils.rs index feb8cd2a6..dabbef888 100644 --- a/crates/ide/src/completion/test_utils.rs +++ b/crates/ide/src/completion/test_utils.rs | |||
@@ -104,6 +104,18 @@ pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) - | |||
104 | .unwrap(); | 104 | .unwrap(); |
105 | } | 105 | } |
106 | 106 | ||
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 | |||
107 | pub(crate) fn get_all_completion_items( | 119 | pub(crate) fn get_all_completion_items( |
108 | config: CompletionConfig, | 120 | config: CompletionConfig, |
109 | code: &str, | 121 | code: &str, |