aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide/src/completion.rs1
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs878
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs47
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs26
-rw-r--r--crates/ra_ide/src/completion/patterns.rs206
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs46
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;
15mod complete_postfix; 15mod complete_postfix;
16mod complete_macro_in_item_position; 16mod complete_macro_in_item_position;
17mod complete_trait_impl; 17mod complete_trait_impl;
18mod patterns;
18#[cfg(test)] 19#[cfg(test)]
19mod test_utils; 20mod 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
3use ra_syntax::{ 3use ra_syntax::ast;
4 ast::{self, LoopBodyOwner},
5 match_ast, AstNode,
6 SyntaxKind::*,
7 SyntaxToken,
8};
9 4
10use crate::completion::{ 5use 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
50fn 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
55pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { 62pub(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
85fn 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
108fn complete_return( 172fn 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)]
123mod tests { 187mod 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;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use 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};
12use ra_text_edit::Indel; 12use ra_text_edit::Indel;
13 13
14use 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};
14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; 19use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15use test_utils::mark; 20use 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
65impl<'a> CompletionContext<'a> { 81impl<'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
128impl 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)]
129pub(crate) enum CompletionKind { 155pub(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
3use 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
11pub(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
19pub(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
27pub(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
31pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
32 element.ancestors().find(|it| it.kind() == BIND_PAT).is_some()
33}
34
35pub(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
41pub(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
49pub(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
57pub(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
65pub(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
69pub(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
73pub(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
99fn 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
107fn 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
119fn 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)]
139mod 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};
8use hir::Semantics;
9use ra_syntax::{AstNode, NodeOrToken, SyntaxElement};
8 10
9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { 11pub(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
15pub(crate) fn get_completions(code: &str, kind: CompletionKind) -> Vec<String> {
16 get_completions_with_options(code, kind, &CompletionConfig::default())
17}
18
13pub(crate) fn do_completion_with_options( 19pub(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
32fn 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(); 41pub(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
57pub(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}