aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-10-17 11:31:37 +0100
committerGitHub <[email protected]>2020-10-17 11:31:37 +0100
commitb1315be010f0d9f67ee4794cb57f7d1baa88a30d (patch)
treeb4259ce1810c3cd6c2bea62bae499415b764728d
parent59483c217662fc5d89ef9da1cb93760e14a48418 (diff)
parent99c435939c941fe5216c39bc136769098abbbfea (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.rs49
-rw-r--r--crates/ide/src/completion/completion_context.rs27
-rw-r--r--crates/ide/src/completion/patterns.rs48
-rw-r--r--crates/ide/src/completion/test_utils.rs12
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)]
12use crate::completion::test_utils::check_pattern_is_applicable; 12use crate::completion::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
13 13
14pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { 14pub(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 {
34fn test_has_impl_parent() { 34fn 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
38pub(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]
49fn 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
37pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool { 56pub(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
138pub(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]
146fn 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.
152pub(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]
161fn test_for_is_prev2() {
162 check_pattern_is_applicable(r"for i i<|>", for_is_prev2);
163}
164
119#[test] 165#[test]
120fn test_if_is_prev() { 166fn 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
107pub(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
107pub(crate) fn get_all_completion_items( 119pub(crate) fn get_all_completion_items(
108 config: CompletionConfig, 120 config: CompletionConfig,
109 code: &str, 121 code: &str,