diff options
Diffstat (limited to 'crates/ra_ide')
-rw-r--r-- | crates/ra_ide/src/completion.rs | 1 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_keyword.rs | 878 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 47 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_item.rs | 26 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/patterns.rs | 206 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/test_utils.rs | 46 |
6 files changed, 608 insertions, 596 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index a721e23c6..e1fcf379d 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -15,6 +15,7 @@ mod complete_unqualified_path; | |||
15 | mod complete_postfix; | 15 | mod complete_postfix; |
16 | mod complete_macro_in_item_position; | 16 | mod complete_macro_in_item_position; |
17 | mod complete_trait_impl; | 17 | mod complete_trait_impl; |
18 | mod patterns; | ||
18 | #[cfg(test)] | 19 | #[cfg(test)] |
19 | mod test_utils; | 20 | mod test_utils; |
20 | 21 | ||
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs index fd95bc410..025097e49 100644 --- a/crates/ra_ide/src/completion/complete_keyword.rs +++ b/crates/ra_ide/src/completion/complete_keyword.rs | |||
@@ -1,11 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::ast; |
4 | ast::{self, LoopBodyOwner}, | ||
5 | match_ast, AstNode, | ||
6 | SyntaxKind::*, | ||
7 | SyntaxToken, | ||
8 | }; | ||
9 | 4 | ||
10 | use crate::completion::{ | 5 | use crate::completion::{ |
11 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | 6 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, |
@@ -52,59 +47,128 @@ fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { | |||
52 | .build() | 47 | .build() |
53 | } | 48 | } |
54 | 49 | ||
50 | fn add_keyword( | ||
51 | ctx: &CompletionContext, | ||
52 | acc: &mut Completions, | ||
53 | kw: &str, | ||
54 | snippet: &str, | ||
55 | should_add: bool, | ||
56 | ) { | ||
57 | if should_add { | ||
58 | acc.add(keyword(ctx, kw, snippet)); | ||
59 | } | ||
60 | } | ||
61 | |||
55 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | 62 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { |
56 | if !ctx.is_trivial_path { | 63 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; |
64 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { | ||
65 | add_keyword(ctx, acc, "where", "where ", true); | ||
57 | return; | 66 | return; |
58 | } | 67 | } |
68 | if ctx.unsafe_is_prev { | ||
69 | add_keyword(ctx, acc, "fn", "fn $0() {}", ctx.is_new_item || ctx.block_expr_parent); | ||
70 | add_keyword( | ||
71 | ctx, | ||
72 | acc, | ||
73 | "trait", | ||
74 | "trait $0 {}", | ||
75 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
76 | ); | ||
77 | add_keyword( | ||
78 | ctx, | ||
79 | acc, | ||
80 | "impl", | ||
81 | "impl $0 {}", | ||
82 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
83 | ); | ||
84 | return; | ||
85 | } | ||
86 | add_keyword(ctx, acc, "fn", "fn $0() {}", ctx.is_new_item || ctx.block_expr_parent); | ||
87 | add_keyword( | ||
88 | ctx, | ||
89 | acc, | ||
90 | "use", | ||
91 | "use ", | ||
92 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
93 | ); | ||
94 | add_keyword( | ||
95 | ctx, | ||
96 | acc, | ||
97 | "impl", | ||
98 | "impl $0 {}", | ||
99 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
100 | ); | ||
101 | add_keyword( | ||
102 | ctx, | ||
103 | acc, | ||
104 | "trait", | ||
105 | "trait $0 {}", | ||
106 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
107 | ); | ||
108 | add_keyword(ctx, acc, "enum", "enum $0 {}", ctx.is_new_item && !has_trait_or_impl_parent); | ||
109 | add_keyword(ctx, acc, "struct", "struct $0 {}", ctx.is_new_item && !has_trait_or_impl_parent); | ||
110 | add_keyword(ctx, acc, "union", "union $0 {}", ctx.is_new_item && !has_trait_or_impl_parent); | ||
111 | add_keyword(ctx, acc, "match", "match $0 {}", ctx.block_expr_parent || ctx.is_match_arm); | ||
112 | add_keyword(ctx, acc, "loop", "loop {$0}", ctx.block_expr_parent || ctx.is_match_arm); | ||
113 | add_keyword(ctx, acc, "while", "while $0 {}", ctx.block_expr_parent); | ||
114 | add_keyword(ctx, acc, "let", "let ", ctx.if_is_prev || ctx.block_expr_parent); | ||
115 | add_keyword(ctx, acc, "if", "if ", ctx.if_is_prev || ctx.block_expr_parent || ctx.is_match_arm); | ||
116 | add_keyword( | ||
117 | ctx, | ||
118 | acc, | ||
119 | "if let", | ||
120 | "if let ", | ||
121 | ctx.if_is_prev || ctx.block_expr_parent || ctx.is_match_arm, | ||
122 | ); | ||
123 | add_keyword(ctx, acc, "else", "else {$0}", ctx.after_if); | ||
124 | add_keyword(ctx, acc, "else if", "else if $0 {}", ctx.after_if); | ||
125 | add_keyword( | ||
126 | ctx, | ||
127 | acc, | ||
128 | "mod", | ||
129 | "mod $0 {}", | ||
130 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
131 | ); | ||
132 | add_keyword(ctx, acc, "mut", "mut ", ctx.bind_pat_parent || ctx.ref_pat_parent); | ||
133 | add_keyword(ctx, acc, "const", "const ", ctx.is_new_item || ctx.block_expr_parent); | ||
134 | add_keyword(ctx, acc, "type", "type ", ctx.is_new_item || ctx.block_expr_parent); | ||
135 | add_keyword( | ||
136 | ctx, | ||
137 | acc, | ||
138 | "static", | ||
139 | "static ", | ||
140 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
141 | ); | ||
142 | add_keyword( | ||
143 | ctx, | ||
144 | acc, | ||
145 | "extern", | ||
146 | "extern ", | ||
147 | (ctx.is_new_item && !has_trait_or_impl_parent) || ctx.block_expr_parent, | ||
148 | ); | ||
149 | add_keyword( | ||
150 | ctx, | ||
151 | acc, | ||
152 | "unsafe", | ||
153 | "unsafe ", | ||
154 | ctx.is_new_item || ctx.block_expr_parent || ctx.is_match_arm, | ||
155 | ); | ||
156 | add_keyword(ctx, acc, "continue", "continue;", ctx.in_loop_body && ctx.can_be_stmt); | ||
157 | add_keyword(ctx, acc, "break", "break;", ctx.in_loop_body && ctx.can_be_stmt); | ||
158 | add_keyword(ctx, acc, "continue", "continue", ctx.in_loop_body && !ctx.can_be_stmt); | ||
159 | add_keyword(ctx, acc, "break", "break", ctx.in_loop_body && !ctx.can_be_stmt); | ||
160 | add_keyword(ctx, acc, "pub", "pub ", ctx.is_new_item && !ctx.has_trait_parent); | ||
59 | 161 | ||
162 | if !ctx.is_trivial_path { | ||
163 | return; | ||
164 | } | ||
60 | let fn_def = match &ctx.function_syntax { | 165 | let fn_def = match &ctx.function_syntax { |
61 | Some(it) => it, | 166 | Some(it) => it, |
62 | None => return, | 167 | None => return, |
63 | }; | 168 | }; |
64 | acc.add(keyword(ctx, "if", "if $0 {}")); | ||
65 | acc.add(keyword(ctx, "match", "match $0 {}")); | ||
66 | acc.add(keyword(ctx, "while", "while $0 {}")); | ||
67 | acc.add(keyword(ctx, "loop", "loop {$0}")); | ||
68 | |||
69 | if ctx.after_if { | ||
70 | acc.add(keyword(ctx, "else", "else {$0}")); | ||
71 | acc.add(keyword(ctx, "else if", "else if $0 {}")); | ||
72 | } | ||
73 | if is_in_loop_body(&ctx.token) { | ||
74 | if ctx.can_be_stmt { | ||
75 | acc.add(keyword(ctx, "continue", "continue;")); | ||
76 | acc.add(keyword(ctx, "break", "break;")); | ||
77 | } else { | ||
78 | acc.add(keyword(ctx, "continue", "continue")); | ||
79 | acc.add(keyword(ctx, "break", "break")); | ||
80 | } | ||
81 | } | ||
82 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); | 169 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); |
83 | } | 170 | } |
84 | 171 | ||
85 | fn is_in_loop_body(leaf: &SyntaxToken) -> bool { | ||
86 | // FIXME move this to CompletionContext and make it handle macros | ||
87 | for node in leaf.parent().ancestors() { | ||
88 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | ||
89 | break; | ||
90 | } | ||
91 | let loop_body = match_ast! { | ||
92 | match node { | ||
93 | ast::ForExpr(it) => it.loop_body(), | ||
94 | ast::WhileExpr(it) => it.loop_body(), | ||
95 | ast::LoopExpr(it) => it.loop_body(), | ||
96 | _ => None, | ||
97 | } | ||
98 | }; | ||
99 | if let Some(body) = loop_body { | ||
100 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
101 | return true; | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | false | ||
106 | } | ||
107 | |||
108 | fn complete_return( | 172 | fn complete_return( |
109 | ctx: &CompletionContext, | 173 | ctx: &CompletionContext, |
110 | fn_def: &ast::FnDef, | 174 | fn_def: &ast::FnDef, |
@@ -121,157 +185,107 @@ fn complete_return( | |||
121 | 185 | ||
122 | #[cfg(test)] | 186 | #[cfg(test)] |
123 | mod tests { | 187 | mod tests { |
124 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 188 | use crate::completion::{test_utils::get_completions, CompletionKind}; |
125 | use insta::assert_debug_snapshot; | 189 | use insta::assert_debug_snapshot; |
126 | 190 | ||
127 | fn do_keyword_completion(code: &str) -> Vec<CompletionItem> { | 191 | fn get_keyword_completions(code: &str) -> Vec<String> { |
128 | do_completion(code, CompletionKind::Keyword) | 192 | get_completions(code, CompletionKind::Keyword) |
129 | } | 193 | } |
130 | 194 | ||
131 | #[test] | 195 | #[test] |
132 | fn completes_keywords_in_use_stmt() { | 196 | fn test_keywords_in_use_stmt() { |
133 | assert_debug_snapshot!( | 197 | assert_debug_snapshot!( |
134 | do_keyword_completion( | 198 | get_keyword_completions(r"use <|>"), |
135 | r" | ||
136 | use <|> | ||
137 | ", | ||
138 | ), | ||
139 | @r###" | 199 | @r###" |
140 | [ | 200 | [ |
141 | CompletionItem { | 201 | "kw crate", |
142 | label: "crate", | 202 | "kw self", |
143 | source_range: 21..21, | 203 | "kw super", |
144 | delete: 21..21, | ||
145 | insert: "crate::", | ||
146 | kind: Keyword, | ||
147 | }, | ||
148 | CompletionItem { | ||
149 | label: "self", | ||
150 | source_range: 21..21, | ||
151 | delete: 21..21, | ||
152 | insert: "self", | ||
153 | kind: Keyword, | ||
154 | }, | ||
155 | CompletionItem { | ||
156 | label: "super", | ||
157 | source_range: 21..21, | ||
158 | delete: 21..21, | ||
159 | insert: "super::", | ||
160 | kind: Keyword, | ||
161 | }, | ||
162 | ] | 204 | ] |
163 | "### | 205 | "### |
164 | ); | 206 | ); |
165 | 207 | ||
166 | assert_debug_snapshot!( | 208 | assert_debug_snapshot!( |
167 | do_keyword_completion( | 209 | get_keyword_completions(r"use a::<|>"), |
168 | r" | ||
169 | use a::<|> | ||
170 | ", | ||
171 | ), | ||
172 | @r###" | 210 | @r###" |
173 | [ | 211 | [ |
174 | CompletionItem { | 212 | "kw self", |
175 | label: "self", | 213 | "kw super", |
176 | source_range: 24..24, | ||
177 | delete: 24..24, | ||
178 | insert: "self", | ||
179 | kind: Keyword, | ||
180 | }, | ||
181 | CompletionItem { | ||
182 | label: "super", | ||
183 | source_range: 24..24, | ||
184 | delete: 24..24, | ||
185 | insert: "super::", | ||
186 | kind: Keyword, | ||
187 | }, | ||
188 | ] | 214 | ] |
189 | "### | 215 | "### |
190 | ); | 216 | ); |
191 | 217 | ||
192 | assert_debug_snapshot!( | 218 | assert_debug_snapshot!( |
193 | do_keyword_completion( | 219 | get_keyword_completions(r"use a::{b, <|>}"), |
194 | r" | ||
195 | use a::{b, <|>} | ||
196 | ", | ||
197 | ), | ||
198 | @r###" | 220 | @r###" |
199 | [ | 221 | [ |
200 | CompletionItem { | 222 | "kw self", |
201 | label: "self", | 223 | "kw super", |
202 | source_range: 28..28, | ||
203 | delete: 28..28, | ||
204 | insert: "self", | ||
205 | kind: Keyword, | ||
206 | }, | ||
207 | CompletionItem { | ||
208 | label: "super", | ||
209 | source_range: 28..28, | ||
210 | delete: 28..28, | ||
211 | insert: "super::", | ||
212 | kind: Keyword, | ||
213 | }, | ||
214 | ] | 224 | ] |
215 | "### | 225 | "### |
216 | ); | 226 | ); |
217 | } | 227 | } |
218 | 228 | ||
219 | #[test] | 229 | #[test] |
220 | fn completes_various_keywords_in_function() { | 230 | fn test_keywords_in_function() { |
221 | assert_debug_snapshot!( | 231 | assert_debug_snapshot!( |
222 | do_keyword_completion( | 232 | get_keyword_completions(r"fn quux() { <|> }"), |
223 | r" | ||
224 | fn quux() { | ||
225 | <|> | ||
226 | } | ||
227 | ", | ||
228 | ), | ||
229 | @r###" | 233 | @r###" |
230 | [ | 234 | [ |
231 | CompletionItem { | 235 | "kw const", |
232 | label: "if", | 236 | "kw extern", |
233 | source_range: 49..49, | 237 | "kw fn", |
234 | delete: 49..49, | 238 | "kw if", |
235 | insert: "if $0 {}", | 239 | "kw if let", |
236 | kind: Keyword, | 240 | "kw impl", |
237 | }, | 241 | "kw let", |
238 | CompletionItem { | 242 | "kw loop", |
239 | label: "loop", | 243 | "kw match", |
240 | source_range: 49..49, | 244 | "kw mod", |
241 | delete: 49..49, | 245 | "kw return", |
242 | insert: "loop {$0}", | 246 | "kw static", |
243 | kind: Keyword, | 247 | "kw trait", |
244 | }, | 248 | "kw type", |
245 | CompletionItem { | 249 | "kw unsafe", |
246 | label: "match", | 250 | "kw use", |
247 | source_range: 49..49, | 251 | "kw while", |
248 | delete: 49..49, | ||
249 | insert: "match $0 {}", | ||
250 | kind: Keyword, | ||
251 | }, | ||
252 | CompletionItem { | ||
253 | label: "return", | ||
254 | source_range: 49..49, | ||
255 | delete: 49..49, | ||
256 | insert: "return;", | ||
257 | kind: Keyword, | ||
258 | }, | ||
259 | CompletionItem { | ||
260 | label: "while", | ||
261 | source_range: 49..49, | ||
262 | delete: 49..49, | ||
263 | insert: "while $0 {}", | ||
264 | kind: Keyword, | ||
265 | }, | ||
266 | ] | 252 | ] |
267 | "### | 253 | "### |
268 | ); | 254 | ); |
269 | } | 255 | } |
270 | 256 | ||
271 | #[test] | 257 | #[test] |
272 | fn completes_else_after_if() { | 258 | fn test_keywords_inside_block() { |
273 | assert_debug_snapshot!( | 259 | assert_debug_snapshot!( |
274 | do_keyword_completion( | 260 | get_keyword_completions(r"fn quux() { if true { <|> } }"), |
261 | @r###" | ||
262 | [ | ||
263 | "kw const", | ||
264 | "kw extern", | ||
265 | "kw fn", | ||
266 | "kw if", | ||
267 | "kw if let", | ||
268 | "kw impl", | ||
269 | "kw let", | ||
270 | "kw loop", | ||
271 | "kw match", | ||
272 | "kw mod", | ||
273 | "kw return", | ||
274 | "kw static", | ||
275 | "kw trait", | ||
276 | "kw type", | ||
277 | "kw unsafe", | ||
278 | "kw use", | ||
279 | "kw while", | ||
280 | ] | ||
281 | "### | ||
282 | ); | ||
283 | } | ||
284 | |||
285 | #[test] | ||
286 | fn test_keywords_after_if() { | ||
287 | assert_debug_snapshot!( | ||
288 | get_keyword_completions( | ||
275 | r" | 289 | r" |
276 | fn quux() { | 290 | fn quux() { |
277 | if true { | 291 | if true { |
@@ -282,505 +296,189 @@ mod tests { | |||
282 | ), | 296 | ), |
283 | @r###" | 297 | @r###" |
284 | [ | 298 | [ |
285 | CompletionItem { | 299 | "kw const", |
286 | label: "else", | 300 | "kw else", |
287 | source_range: 108..108, | 301 | "kw else if", |
288 | delete: 108..108, | 302 | "kw extern", |
289 | insert: "else {$0}", | 303 | "kw fn", |
290 | kind: Keyword, | 304 | "kw if", |
291 | }, | 305 | "kw if let", |
292 | CompletionItem { | 306 | "kw impl", |
293 | label: "else if", | 307 | "kw let", |
294 | source_range: 108..108, | 308 | "kw loop", |
295 | delete: 108..108, | 309 | "kw match", |
296 | insert: "else if $0 {}", | 310 | "kw mod", |
297 | kind: Keyword, | 311 | "kw return", |
298 | }, | 312 | "kw static", |
299 | CompletionItem { | 313 | "kw trait", |
300 | label: "if", | 314 | "kw type", |
301 | source_range: 108..108, | 315 | "kw unsafe", |
302 | delete: 108..108, | 316 | "kw use", |
303 | insert: "if $0 {}", | 317 | "kw while", |
304 | kind: Keyword, | ||
305 | }, | ||
306 | CompletionItem { | ||
307 | label: "loop", | ||
308 | source_range: 108..108, | ||
309 | delete: 108..108, | ||
310 | insert: "loop {$0}", | ||
311 | kind: Keyword, | ||
312 | }, | ||
313 | CompletionItem { | ||
314 | label: "match", | ||
315 | source_range: 108..108, | ||
316 | delete: 108..108, | ||
317 | insert: "match $0 {}", | ||
318 | kind: Keyword, | ||
319 | }, | ||
320 | CompletionItem { | ||
321 | label: "return", | ||
322 | source_range: 108..108, | ||
323 | delete: 108..108, | ||
324 | insert: "return;", | ||
325 | kind: Keyword, | ||
326 | }, | ||
327 | CompletionItem { | ||
328 | label: "while", | ||
329 | source_range: 108..108, | ||
330 | delete: 108..108, | ||
331 | insert: "while $0 {}", | ||
332 | kind: Keyword, | ||
333 | }, | ||
334 | ] | 318 | ] |
335 | "### | 319 | "### |
336 | ); | 320 | ); |
337 | } | 321 | } |
338 | 322 | ||
339 | #[test] | 323 | #[test] |
340 | fn test_completion_return_value() { | 324 | fn test_keywords_in_match_arm() { |
341 | assert_debug_snapshot!( | 325 | assert_debug_snapshot!( |
342 | do_keyword_completion( | 326 | get_keyword_completions( |
343 | r" | 327 | r" |
344 | fn quux() -> i32 { | 328 | fn quux() -> i32 { |
345 | <|> | 329 | match () { |
346 | 92 | 330 | () => <|> |
331 | } | ||
347 | } | 332 | } |
348 | ", | 333 | ", |
349 | ), | 334 | ), |
350 | @r###" | 335 | @r###" |
351 | [ | 336 | [ |
352 | CompletionItem { | 337 | "kw if", |
353 | label: "if", | 338 | "kw if let", |
354 | source_range: 56..56, | 339 | "kw loop", |
355 | delete: 56..56, | 340 | "kw match", |
356 | insert: "if $0 {}", | 341 | "kw return", |
357 | kind: Keyword, | 342 | "kw unsafe", |
358 | }, | ||
359 | CompletionItem { | ||
360 | label: "loop", | ||
361 | source_range: 56..56, | ||
362 | delete: 56..56, | ||
363 | insert: "loop {$0}", | ||
364 | kind: Keyword, | ||
365 | }, | ||
366 | CompletionItem { | ||
367 | label: "match", | ||
368 | source_range: 56..56, | ||
369 | delete: 56..56, | ||
370 | insert: "match $0 {}", | ||
371 | kind: Keyword, | ||
372 | }, | ||
373 | CompletionItem { | ||
374 | label: "return", | ||
375 | source_range: 56..56, | ||
376 | delete: 56..56, | ||
377 | insert: "return $0;", | ||
378 | kind: Keyword, | ||
379 | }, | ||
380 | CompletionItem { | ||
381 | label: "while", | ||
382 | source_range: 56..56, | ||
383 | delete: 56..56, | ||
384 | insert: "while $0 {}", | ||
385 | kind: Keyword, | ||
386 | }, | ||
387 | ] | 343 | ] |
388 | "### | 344 | "### |
389 | ); | 345 | ); |
346 | } | ||
347 | |||
348 | #[test] | ||
349 | fn test_keywords_in_trait_def() { | ||
390 | assert_debug_snapshot!( | 350 | assert_debug_snapshot!( |
391 | do_keyword_completion( | 351 | get_keyword_completions(r"trait My { <|> }"), |
392 | r" | ||
393 | fn quux() { | ||
394 | <|> | ||
395 | 92 | ||
396 | } | ||
397 | ", | ||
398 | ), | ||
399 | @r###" | 352 | @r###" |
400 | [ | 353 | [ |
401 | CompletionItem { | 354 | "kw const", |
402 | label: "if", | 355 | "kw fn", |
403 | source_range: 49..49, | 356 | "kw type", |
404 | delete: 49..49, | 357 | "kw unsafe", |
405 | insert: "if $0 {}", | ||
406 | kind: Keyword, | ||
407 | }, | ||
408 | CompletionItem { | ||
409 | label: "loop", | ||
410 | source_range: 49..49, | ||
411 | delete: 49..49, | ||
412 | insert: "loop {$0}", | ||
413 | kind: Keyword, | ||
414 | }, | ||
415 | CompletionItem { | ||
416 | label: "match", | ||
417 | source_range: 49..49, | ||
418 | delete: 49..49, | ||
419 | insert: "match $0 {}", | ||
420 | kind: Keyword, | ||
421 | }, | ||
422 | CompletionItem { | ||
423 | label: "return", | ||
424 | source_range: 49..49, | ||
425 | delete: 49..49, | ||
426 | insert: "return;", | ||
427 | kind: Keyword, | ||
428 | }, | ||
429 | CompletionItem { | ||
430 | label: "while", | ||
431 | source_range: 49..49, | ||
432 | delete: 49..49, | ||
433 | insert: "while $0 {}", | ||
434 | kind: Keyword, | ||
435 | }, | ||
436 | ] | 358 | ] |
437 | "### | 359 | "### |
438 | ); | 360 | ); |
439 | } | 361 | } |
440 | 362 | ||
441 | #[test] | 363 | #[test] |
442 | fn dont_add_semi_after_return_if_not_a_statement() { | 364 | fn test_keywords_in_impl_def() { |
443 | assert_debug_snapshot!( | 365 | assert_debug_snapshot!( |
444 | do_keyword_completion( | 366 | get_keyword_completions(r"impl My { <|> }"), |
445 | r" | ||
446 | fn quux() -> i32 { | ||
447 | match () { | ||
448 | () => <|> | ||
449 | } | ||
450 | } | ||
451 | ", | ||
452 | ), | ||
453 | @r###" | 367 | @r###" |
454 | [ | 368 | [ |
455 | CompletionItem { | 369 | "kw const", |
456 | label: "if", | 370 | "kw fn", |
457 | source_range: 97..97, | 371 | "kw pub", |
458 | delete: 97..97, | 372 | "kw type", |
459 | insert: "if $0 {}", | 373 | "kw unsafe", |
460 | kind: Keyword, | ||
461 | }, | ||
462 | CompletionItem { | ||
463 | label: "loop", | ||
464 | source_range: 97..97, | ||
465 | delete: 97..97, | ||
466 | insert: "loop {$0}", | ||
467 | kind: Keyword, | ||
468 | }, | ||
469 | CompletionItem { | ||
470 | label: "match", | ||
471 | source_range: 97..97, | ||
472 | delete: 97..97, | ||
473 | insert: "match $0 {}", | ||
474 | kind: Keyword, | ||
475 | }, | ||
476 | CompletionItem { | ||
477 | label: "return", | ||
478 | source_range: 97..97, | ||
479 | delete: 97..97, | ||
480 | insert: "return $0", | ||
481 | kind: Keyword, | ||
482 | }, | ||
483 | CompletionItem { | ||
484 | label: "while", | ||
485 | source_range: 97..97, | ||
486 | delete: 97..97, | ||
487 | insert: "while $0 {}", | ||
488 | kind: Keyword, | ||
489 | }, | ||
490 | ] | 374 | ] |
491 | "### | 375 | "### |
492 | ); | 376 | ); |
493 | } | 377 | } |
494 | 378 | ||
495 | #[test] | 379 | #[test] |
496 | fn last_return_in_block_has_semi() { | 380 | fn test_keywords_in_loop() { |
497 | assert_debug_snapshot!( | 381 | assert_debug_snapshot!( |
498 | do_keyword_completion( | 382 | get_keyword_completions(r"fn my() { loop { <|> } }"), |
499 | r" | ||
500 | fn quux() -> i32 { | ||
501 | if condition { | ||
502 | <|> | ||
503 | } | ||
504 | } | ||
505 | ", | ||
506 | ), | ||
507 | @r###" | 383 | @r###" |
508 | [ | 384 | [ |
509 | CompletionItem { | 385 | "kw break", |
510 | label: "if", | 386 | "kw const", |
511 | source_range: 95..95, | 387 | "kw continue", |
512 | delete: 95..95, | 388 | "kw extern", |
513 | insert: "if $0 {}", | 389 | "kw fn", |
514 | kind: Keyword, | 390 | "kw if", |
515 | }, | 391 | "kw if let", |
516 | CompletionItem { | 392 | "kw impl", |
517 | label: "loop", | 393 | "kw let", |
518 | source_range: 95..95, | 394 | "kw loop", |
519 | delete: 95..95, | 395 | "kw match", |
520 | insert: "loop {$0}", | 396 | "kw mod", |
521 | kind: Keyword, | 397 | "kw return", |
522 | }, | 398 | "kw static", |
523 | CompletionItem { | 399 | "kw trait", |
524 | label: "match", | 400 | "kw type", |
525 | source_range: 95..95, | 401 | "kw unsafe", |
526 | delete: 95..95, | 402 | "kw use", |
527 | insert: "match $0 {}", | 403 | "kw while", |
528 | kind: Keyword, | ||
529 | }, | ||
530 | CompletionItem { | ||
531 | label: "return", | ||
532 | source_range: 95..95, | ||
533 | delete: 95..95, | ||
534 | insert: "return $0;", | ||
535 | kind: Keyword, | ||
536 | }, | ||
537 | CompletionItem { | ||
538 | label: "while", | ||
539 | source_range: 95..95, | ||
540 | delete: 95..95, | ||
541 | insert: "while $0 {}", | ||
542 | kind: Keyword, | ||
543 | }, | ||
544 | ] | 404 | ] |
545 | "### | 405 | "### |
546 | ); | 406 | ); |
407 | } | ||
408 | |||
409 | #[test] | ||
410 | fn test_keywords_after_unsafe_in_item_list() { | ||
547 | assert_debug_snapshot!( | 411 | assert_debug_snapshot!( |
548 | do_keyword_completion( | 412 | get_keyword_completions(r"unsafe <|>"), |
549 | r" | ||
550 | fn quux() -> i32 { | ||
551 | if condition { | ||
552 | <|> | ||
553 | } | ||
554 | let x = 92; | ||
555 | x | ||
556 | } | ||
557 | ", | ||
558 | ), | ||
559 | @r###" | 413 | @r###" |
560 | [ | 414 | [ |
561 | CompletionItem { | 415 | "kw fn", |
562 | label: "if", | 416 | "kw impl", |
563 | source_range: 95..95, | 417 | "kw trait", |
564 | delete: 95..95, | ||
565 | insert: "if $0 {}", | ||
566 | kind: Keyword, | ||
567 | }, | ||
568 | CompletionItem { | ||
569 | label: "loop", | ||
570 | source_range: 95..95, | ||
571 | delete: 95..95, | ||
572 | insert: "loop {$0}", | ||
573 | kind: Keyword, | ||
574 | }, | ||
575 | CompletionItem { | ||
576 | label: "match", | ||
577 | source_range: 95..95, | ||
578 | delete: 95..95, | ||
579 | insert: "match $0 {}", | ||
580 | kind: Keyword, | ||
581 | }, | ||
582 | CompletionItem { | ||
583 | label: "return", | ||
584 | source_range: 95..95, | ||
585 | delete: 95..95, | ||
586 | insert: "return $0;", | ||
587 | kind: Keyword, | ||
588 | }, | ||
589 | CompletionItem { | ||
590 | label: "while", | ||
591 | source_range: 95..95, | ||
592 | delete: 95..95, | ||
593 | insert: "while $0 {}", | ||
594 | kind: Keyword, | ||
595 | }, | ||
596 | ] | 418 | ] |
597 | "### | 419 | "### |
598 | ); | 420 | ); |
599 | } | 421 | } |
600 | 422 | ||
601 | #[test] | 423 | #[test] |
602 | fn completes_break_and_continue_in_loops() { | 424 | fn test_keywords_after_unsafe_in_block_expr() { |
603 | assert_debug_snapshot!( | 425 | assert_debug_snapshot!( |
604 | do_keyword_completion( | 426 | get_keyword_completions(r"fn my_fn() { unsafe <|> }"), |
605 | r" | ||
606 | fn quux() -> i32 { | ||
607 | loop { <|> } | ||
608 | } | ||
609 | ", | ||
610 | ), | ||
611 | @r###" | 427 | @r###" |
612 | [ | 428 | [ |
613 | CompletionItem { | 429 | "kw fn", |
614 | label: "break", | 430 | "kw impl", |
615 | source_range: 63..63, | 431 | "kw trait", |
616 | delete: 63..63, | ||
617 | insert: "break;", | ||
618 | kind: Keyword, | ||
619 | }, | ||
620 | CompletionItem { | ||
621 | label: "continue", | ||
622 | source_range: 63..63, | ||
623 | delete: 63..63, | ||
624 | insert: "continue;", | ||
625 | kind: Keyword, | ||
626 | }, | ||
627 | CompletionItem { | ||
628 | label: "if", | ||
629 | source_range: 63..63, | ||
630 | delete: 63..63, | ||
631 | insert: "if $0 {}", | ||
632 | kind: Keyword, | ||
633 | }, | ||
634 | CompletionItem { | ||
635 | label: "loop", | ||
636 | source_range: 63..63, | ||
637 | delete: 63..63, | ||
638 | insert: "loop {$0}", | ||
639 | kind: Keyword, | ||
640 | }, | ||
641 | CompletionItem { | ||
642 | label: "match", | ||
643 | source_range: 63..63, | ||
644 | delete: 63..63, | ||
645 | insert: "match $0 {}", | ||
646 | kind: Keyword, | ||
647 | }, | ||
648 | CompletionItem { | ||
649 | label: "return", | ||
650 | source_range: 63..63, | ||
651 | delete: 63..63, | ||
652 | insert: "return $0;", | ||
653 | kind: Keyword, | ||
654 | }, | ||
655 | CompletionItem { | ||
656 | label: "while", | ||
657 | source_range: 63..63, | ||
658 | delete: 63..63, | ||
659 | insert: "while $0 {}", | ||
660 | kind: Keyword, | ||
661 | }, | ||
662 | ] | 432 | ] |
663 | "### | 433 | "### |
664 | ); | 434 | ); |
435 | } | ||
665 | 436 | ||
666 | // No completion: lambda isolates control flow | 437 | #[test] |
438 | fn test_mut_in_ref_and_in_fn_parameters_list() { | ||
667 | assert_debug_snapshot!( | 439 | assert_debug_snapshot!( |
668 | do_keyword_completion( | 440 | get_keyword_completions(r"fn my_fn(&<|>) {}"), |
669 | r" | 441 | @r###" |
670 | fn quux() -> i32 { | 442 | [ |
671 | loop { || { <|> } } | 443 | "kw mut", |
672 | } | 444 | ] |
673 | ", | 445 | "### |
674 | ), | 446 | ); |
447 | assert_debug_snapshot!( | ||
448 | get_keyword_completions(r"fn my_fn(<|>) {}"), | ||
449 | @r###" | ||
450 | [ | ||
451 | "kw mut", | ||
452 | ] | ||
453 | "### | ||
454 | ); | ||
455 | assert_debug_snapshot!( | ||
456 | get_keyword_completions(r"fn my_fn() { let &<|> }"), | ||
675 | @r###" | 457 | @r###" |
676 | [ | 458 | [ |
677 | CompletionItem { | 459 | "kw mut", |
678 | label: "if", | ||
679 | source_range: 68..68, | ||
680 | delete: 68..68, | ||
681 | insert: "if $0 {}", | ||
682 | kind: Keyword, | ||
683 | }, | ||
684 | CompletionItem { | ||
685 | label: "loop", | ||
686 | source_range: 68..68, | ||
687 | delete: 68..68, | ||
688 | insert: "loop {$0}", | ||
689 | kind: Keyword, | ||
690 | }, | ||
691 | CompletionItem { | ||
692 | label: "match", | ||
693 | source_range: 68..68, | ||
694 | delete: 68..68, | ||
695 | insert: "match $0 {}", | ||
696 | kind: Keyword, | ||
697 | }, | ||
698 | CompletionItem { | ||
699 | label: "return", | ||
700 | source_range: 68..68, | ||
701 | delete: 68..68, | ||
702 | insert: "return $0;", | ||
703 | kind: Keyword, | ||
704 | }, | ||
705 | CompletionItem { | ||
706 | label: "while", | ||
707 | source_range: 68..68, | ||
708 | delete: 68..68, | ||
709 | insert: "while $0 {}", | ||
710 | kind: Keyword, | ||
711 | }, | ||
712 | ] | 460 | ] |
713 | "### | 461 | "### |
714 | ); | 462 | ); |
715 | } | 463 | } |
716 | 464 | ||
717 | #[test] | 465 | #[test] |
718 | fn no_semi_after_break_continue_in_expr() { | 466 | fn test_where_keyword() { |
719 | assert_debug_snapshot!( | 467 | assert_debug_snapshot!( |
720 | do_keyword_completion( | 468 | get_keyword_completions(r"trait A <|>"), |
721 | r" | ||
722 | fn f() { | ||
723 | loop { | ||
724 | match () { | ||
725 | () => br<|> | ||
726 | } | ||
727 | } | ||
728 | } | ||
729 | ", | ||
730 | ), | ||
731 | @r###" | 469 | @r###" |
732 | [ | 470 | [ |
733 | CompletionItem { | 471 | "kw where", |
734 | label: "break", | ||
735 | source_range: 122..124, | ||
736 | delete: 122..124, | ||
737 | insert: "break", | ||
738 | kind: Keyword, | ||
739 | }, | ||
740 | CompletionItem { | ||
741 | label: "continue", | ||
742 | source_range: 122..124, | ||
743 | delete: 122..124, | ||
744 | insert: "continue", | ||
745 | kind: Keyword, | ||
746 | }, | ||
747 | CompletionItem { | ||
748 | label: "if", | ||
749 | source_range: 122..124, | ||
750 | delete: 122..124, | ||
751 | insert: "if $0 {}", | ||
752 | kind: Keyword, | ||
753 | }, | ||
754 | CompletionItem { | ||
755 | label: "loop", | ||
756 | source_range: 122..124, | ||
757 | delete: 122..124, | ||
758 | insert: "loop {$0}", | ||
759 | kind: Keyword, | ||
760 | }, | ||
761 | CompletionItem { | ||
762 | label: "match", | ||
763 | source_range: 122..124, | ||
764 | delete: 122..124, | ||
765 | insert: "match $0 {}", | ||
766 | kind: Keyword, | ||
767 | }, | ||
768 | CompletionItem { | ||
769 | label: "return", | ||
770 | source_range: 122..124, | ||
771 | delete: 122..124, | ||
772 | insert: "return", | ||
773 | kind: Keyword, | ||
774 | }, | ||
775 | CompletionItem { | ||
776 | label: "while", | ||
777 | source_range: 122..124, | ||
778 | delete: 122..124, | ||
779 | insert: "while $0 {}", | ||
780 | kind: Keyword, | ||
781 | }, | ||
782 | ] | 472 | ] |
783 | "### | 473 | "### |
784 | ) | 474 | ); |
475 | assert_debug_snapshot!( | ||
476 | get_keyword_completions(r"impl A <|>"), | ||
477 | @r###" | ||
478 | [ | ||
479 | "kw where", | ||
480 | ] | ||
481 | "### | ||
482 | ); | ||
785 | } | 483 | } |
786 | } | 484 | } |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index c4646b727..2f96861ca 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -5,12 +5,17 @@ use ra_db::SourceDatabase; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
8 | ast, match_ast, AstNode, | 8 | ast, match_ast, AstNode, NodeOrToken, |
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 10 | SyntaxNode, SyntaxToken, TextRange, TextSize, |
11 | }; | 11 | }; |
12 | use ra_text_edit::Indel; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use super::patterns::{ | ||
15 | has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, | ||
16 | has_ref_parent, has_trait_as_prev_sibling, has_trait_parent, if_is_prev, is_in_loop_body, | ||
17 | is_match_arm, unsafe_is_prev, | ||
18 | }; | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 19 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
15 | use test_utils::mark; | 20 | use test_utils::mark; |
16 | 21 | ||
@@ -60,6 +65,17 @@ pub(crate) struct CompletionContext<'a> { | |||
60 | pub(super) is_path_type: bool, | 65 | pub(super) is_path_type: bool, |
61 | pub(super) has_type_args: bool, | 66 | pub(super) has_type_args: bool, |
62 | pub(super) attribute_under_caret: Option<ast::Attr>, | 67 | pub(super) attribute_under_caret: Option<ast::Attr>, |
68 | pub(super) unsafe_is_prev: bool, | ||
69 | pub(super) if_is_prev: bool, | ||
70 | pub(super) block_expr_parent: bool, | ||
71 | pub(super) bind_pat_parent: bool, | ||
72 | pub(super) ref_pat_parent: bool, | ||
73 | pub(super) in_loop_body: bool, | ||
74 | pub(super) has_trait_parent: bool, | ||
75 | pub(super) has_impl_parent: bool, | ||
76 | pub(super) trait_as_prev_sibling: bool, | ||
77 | pub(super) impl_as_prev_sibling: bool, | ||
78 | pub(super) is_match_arm: bool, | ||
63 | } | 79 | } |
64 | 80 | ||
65 | impl<'a> CompletionContext<'a> { | 81 | impl<'a> CompletionContext<'a> { |
@@ -118,6 +134,17 @@ impl<'a> CompletionContext<'a> { | |||
118 | has_type_args: false, | 134 | has_type_args: false, |
119 | dot_receiver_is_ambiguous_float_literal: false, | 135 | dot_receiver_is_ambiguous_float_literal: false, |
120 | attribute_under_caret: None, | 136 | attribute_under_caret: None, |
137 | unsafe_is_prev: false, | ||
138 | in_loop_body: false, | ||
139 | ref_pat_parent: false, | ||
140 | bind_pat_parent: false, | ||
141 | block_expr_parent: false, | ||
142 | has_trait_parent: false, | ||
143 | has_impl_parent: false, | ||
144 | trait_as_prev_sibling: false, | ||
145 | impl_as_prev_sibling: false, | ||
146 | if_is_prev: false, | ||
147 | is_match_arm: false, | ||
121 | }; | 148 | }; |
122 | 149 | ||
123 | let mut original_file = original_file.syntax().clone(); | 150 | let mut original_file = original_file.syntax().clone(); |
@@ -159,7 +186,7 @@ impl<'a> CompletionContext<'a> { | |||
159 | break; | 186 | break; |
160 | } | 187 | } |
161 | } | 188 | } |
162 | 189 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | |
163 | ctx.fill(&original_file, hypothetical_file, offset); | 190 | ctx.fill(&original_file, hypothetical_file, offset); |
164 | Some(ctx) | 191 | Some(ctx) |
165 | } | 192 | } |
@@ -188,6 +215,22 @@ impl<'a> CompletionContext<'a> { | |||
188 | self.sema.scope_at_offset(&self.token.parent(), self.offset) | 215 | self.sema.scope_at_offset(&self.token.parent(), self.offset) |
189 | } | 216 | } |
190 | 217 | ||
218 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | ||
219 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
220 | let syntax_element = NodeOrToken::Token(fake_ident_token.clone()); | ||
221 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | ||
222 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | ||
223 | self.if_is_prev = if_is_prev(syntax_element.clone()); | ||
224 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | ||
225 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | ||
226 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
227 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | ||
228 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | ||
229 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | ||
230 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | ||
231 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
232 | } | ||
233 | |||
191 | fn fill( | 234 | fn fill( |
192 | &mut self, | 235 | &mut self, |
193 | original_file: &SyntaxNode, | 236 | original_file: &SyntaxNode, |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index cfb7c1e38..d1a4dd881 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -125,6 +125,32 @@ pub enum CompletionItemKind { | |||
125 | Attribute, | 125 | Attribute, |
126 | } | 126 | } |
127 | 127 | ||
128 | impl CompletionItemKind { | ||
129 | pub fn tag(&self) -> String { | ||
130 | let tag = match self { | ||
131 | CompletionItemKind::Snippet => "sn", | ||
132 | CompletionItemKind::Keyword => "kw", | ||
133 | CompletionItemKind::Module => "md", | ||
134 | CompletionItemKind::Function => "fn", | ||
135 | CompletionItemKind::BuiltinType => "bt", | ||
136 | CompletionItemKind::Struct => "st", | ||
137 | CompletionItemKind::Enum => "en", | ||
138 | CompletionItemKind::EnumVariant => "ev", | ||
139 | CompletionItemKind::Binding => "bn", | ||
140 | CompletionItemKind::Field => "fd", | ||
141 | CompletionItemKind::Static => "sc", | ||
142 | CompletionItemKind::Const => "ct", | ||
143 | CompletionItemKind::Trait => "tt", | ||
144 | CompletionItemKind::TypeAlias => "ta", | ||
145 | CompletionItemKind::Method => "me", | ||
146 | CompletionItemKind::TypeParam => "tp", | ||
147 | CompletionItemKind::Macro => "ma", | ||
148 | CompletionItemKind::Attribute => "at", | ||
149 | }; | ||
150 | tag.to_owned() | ||
151 | } | ||
152 | } | ||
153 | |||
128 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | 154 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] |
129 | pub(crate) enum CompletionKind { | 155 | pub(crate) enum CompletionKind { |
130 | /// Parser-based keyword completion. | 156 | /// Parser-based keyword completion. |
diff --git a/crates/ra_ide/src/completion/patterns.rs b/crates/ra_ide/src/completion/patterns.rs new file mode 100644 index 000000000..464032cb4 --- /dev/null +++ b/crates/ra_ide/src/completion/patterns.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::non_trivia_sibling, | ||
5 | ast::{self, LoopBodyOwner}, | ||
6 | match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, | ||
7 | SyntaxKind::*, | ||
8 | SyntaxNode, SyntaxToken, | ||
9 | }; | ||
10 | |||
11 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | ||
12 | not_same_range_ancestor(element) | ||
13 | .filter(|it| it.kind() == ITEM_LIST) | ||
14 | .and_then(|it| it.parent()) | ||
15 | .filter(|it| it.kind() == TRAIT_DEF) | ||
16 | .is_some() | ||
17 | } | ||
18 | |||
19 | pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | ||
20 | not_same_range_ancestor(element) | ||
21 | .filter(|it| it.kind() == ITEM_LIST) | ||
22 | .and_then(|it| it.parent()) | ||
23 | .filter(|it| it.kind() == IMPL_DEF) | ||
24 | .is_some() | ||
25 | } | ||
26 | |||
27 | pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { | ||
28 | not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() | ||
29 | } | ||
30 | |||
31 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | ||
32 | element.ancestors().find(|it| it.kind() == BIND_PAT).is_some() | ||
33 | } | ||
34 | |||
35 | pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { | ||
36 | not_same_range_ancestor(element) | ||
37 | .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) | ||
38 | .is_some() | ||
39 | } | ||
40 | |||
41 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | ||
42 | not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() | ||
43 | && previous_sibling_or_ancestor_sibling(element) | ||
44 | .and_then(|it| it.into_token()) | ||
45 | .filter(|it| it.kind() == FAT_ARROW) | ||
46 | .is_some() | ||
47 | } | ||
48 | |||
49 | pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { | ||
50 | element | ||
51 | .into_token() | ||
52 | .and_then(|it| previous_non_trivia_token(it)) | ||
53 | .filter(|it| it.kind() == UNSAFE_KW) | ||
54 | .is_some() | ||
55 | } | ||
56 | |||
57 | pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | ||
58 | element | ||
59 | .into_token() | ||
60 | .and_then(|it| previous_non_trivia_token(it)) | ||
61 | .filter(|it| it.kind() == IF_KW) | ||
62 | .is_some() | ||
63 | } | ||
64 | |||
65 | pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { | ||
66 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).is_some() | ||
67 | } | ||
68 | |||
69 | pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { | ||
70 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL_DEF).is_some() | ||
71 | } | ||
72 | |||
73 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | ||
74 | let leaf = match element { | ||
75 | NodeOrToken::Node(node) => node, | ||
76 | NodeOrToken::Token(token) => token.parent(), | ||
77 | }; | ||
78 | for node in leaf.ancestors() { | ||
79 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | ||
80 | break; | ||
81 | } | ||
82 | let loop_body = match_ast! { | ||
83 | match node { | ||
84 | ast::ForExpr(it) => it.loop_body(), | ||
85 | ast::WhileExpr(it) => it.loop_body(), | ||
86 | ast::LoopExpr(it) => it.loop_body(), | ||
87 | _ => None, | ||
88 | } | ||
89 | }; | ||
90 | if let Some(body) = loop_body { | ||
91 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
92 | return true; | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | false | ||
97 | } | ||
98 | |||
99 | fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | ||
100 | element | ||
101 | .ancestors() | ||
102 | .take_while(|it| it.text_range() == element.text_range()) | ||
103 | .last() | ||
104 | .and_then(|it| it.parent()) | ||
105 | } | ||
106 | |||
107 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | ||
108 | let mut token = token.prev_token(); | ||
109 | while let Some(inner) = token.clone() { | ||
110 | if !inner.kind().is_trivia() { | ||
111 | return Some(inner); | ||
112 | } else { | ||
113 | token = inner.prev_token(); | ||
114 | } | ||
115 | } | ||
116 | None | ||
117 | } | ||
118 | |||
119 | fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { | ||
120 | let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); | ||
121 | if let Some(sibling) = token_sibling { | ||
122 | Some(sibling) | ||
123 | } else { | ||
124 | // if not trying to find first ancestor which has such a sibling | ||
125 | let node = match element { | ||
126 | NodeOrToken::Node(node) => node, | ||
127 | NodeOrToken::Token(token) => token.parent(), | ||
128 | }; | ||
129 | let range = node.text_range(); | ||
130 | let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; | ||
131 | let prev_sibling_node = top_node.ancestors().find(|it| { | ||
132 | non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() | ||
133 | })?; | ||
134 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | ||
135 | } | ||
136 | } | ||
137 | |||
138 | #[cfg(test)] | ||
139 | mod tests { | ||
140 | use super::{ | ||
141 | has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, | ||
142 | has_ref_parent, has_trait_as_prev_sibling, has_trait_parent, if_is_prev, is_match_arm, | ||
143 | unsafe_is_prev, | ||
144 | }; | ||
145 | use crate::completion::test_utils::check_pattern_is_applicable; | ||
146 | |||
147 | #[test] | ||
148 | fn test_unsafe_is_prev() { | ||
149 | check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev); | ||
150 | } | ||
151 | |||
152 | #[test] | ||
153 | fn test_if_is_prev() { | ||
154 | check_pattern_is_applicable(r"if l<|>", if_is_prev); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn test_has_trait_parent() { | ||
159 | check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent); | ||
160 | } | ||
161 | |||
162 | #[test] | ||
163 | fn test_has_impl_parent() { | ||
164 | check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn test_has_trait_as_prev_sibling() { | ||
169 | check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling); | ||
170 | } | ||
171 | |||
172 | #[test] | ||
173 | fn test_has_impl_as_prev_sibling() { | ||
174 | check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling); | ||
175 | } | ||
176 | |||
177 | #[test] | ||
178 | fn test_parent_block_expr() { | ||
179 | check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent); | ||
180 | } | ||
181 | |||
182 | #[test] | ||
183 | fn test_has_ref_pat_parent_in_func_parameters() { | ||
184 | check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_has_ref_pat_parent_in_let_statement() { | ||
189 | check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent); | ||
190 | } | ||
191 | |||
192 | #[test] | ||
193 | fn test_has_bind_pat_parent_in_func_parameters() { | ||
194 | check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent); | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn test_has_bind_pat_parent_in_let_statement() { | ||
199 | check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent); | ||
200 | } | ||
201 | |||
202 | #[test] | ||
203 | fn test_is_match_arm() { | ||
204 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm); | ||
205 | } | ||
206 | } | ||
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index bf22452a2..8b838a0a5 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -5,25 +5,63 @@ use crate::{ | |||
5 | mock_analysis::{analysis_and_position, single_file_with_position}, | 5 | mock_analysis::{analysis_and_position, single_file_with_position}, |
6 | CompletionItem, | 6 | CompletionItem, |
7 | }; | 7 | }; |
8 | use hir::Semantics; | ||
9 | use ra_syntax::{AstNode, NodeOrToken, SyntaxElement}; | ||
8 | 10 | ||
9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | 11 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { |
10 | do_completion_with_options(code, kind, &CompletionConfig::default()) | 12 | do_completion_with_options(code, kind, &CompletionConfig::default()) |
11 | } | 13 | } |
12 | 14 | ||
15 | pub(crate) fn get_completions(code: &str, kind: CompletionKind) -> Vec<String> { | ||
16 | get_completions_with_options(code, kind, &CompletionConfig::default()) | ||
17 | } | ||
18 | |||
13 | pub(crate) fn do_completion_with_options( | 19 | pub(crate) fn do_completion_with_options( |
14 | code: &str, | 20 | code: &str, |
15 | kind: CompletionKind, | 21 | kind: CompletionKind, |
16 | options: &CompletionConfig, | 22 | options: &CompletionConfig, |
17 | ) -> Vec<CompletionItem> { | 23 | ) -> Vec<CompletionItem> { |
24 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
25 | .into_iter() | ||
26 | .filter(|c| c.completion_kind == kind) | ||
27 | .collect(); | ||
28 | kind_completions.sort_by_key(|c| c.label().to_owned()); | ||
29 | kind_completions | ||
30 | } | ||
31 | |||
32 | fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec<CompletionItem> { | ||
18 | let (analysis, position) = if code.contains("//-") { | 33 | let (analysis, position) = if code.contains("//-") { |
19 | analysis_and_position(code) | 34 | analysis_and_position(code) |
20 | } else { | 35 | } else { |
21 | single_file_with_position(code) | 36 | single_file_with_position(code) |
22 | }; | 37 | }; |
23 | let completions = analysis.completions(options, position).unwrap().unwrap(); | 38 | analysis.completions(options, position).unwrap().unwrap().into() |
24 | let completion_items: Vec<CompletionItem> = completions.into(); | 39 | } |
25 | let mut kind_completions: Vec<CompletionItem> = | 40 | |
26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | 41 | pub(crate) fn get_completions_with_options( |
42 | code: &str, | ||
43 | kind: CompletionKind, | ||
44 | options: &CompletionConfig, | ||
45 | ) -> Vec<String> { | ||
46 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
47 | .into_iter() | ||
48 | .filter(|c| c.completion_kind == kind) | ||
49 | .collect(); | ||
27 | kind_completions.sort_by_key(|c| c.label().to_owned()); | 50 | kind_completions.sort_by_key(|c| c.label().to_owned()); |
28 | kind_completions | 51 | kind_completions |
52 | .into_iter() | ||
53 | .map(|it| format!("{} {}", it.kind().unwrap().tag(), it.label())) | ||
54 | .collect() | ||
55 | } | ||
56 | |||
57 | pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { | ||
58 | let (analysis, pos) = single_file_with_position(code); | ||
59 | analysis | ||
60 | .with_db(|db| { | ||
61 | let sema = Semantics::new(db); | ||
62 | let original_file = sema.parse(pos.file_id); | ||
63 | let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); | ||
64 | assert!(check(NodeOrToken::Token(token))); | ||
65 | }) | ||
66 | .unwrap(); | ||
29 | } | 67 | } |