aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/completions/postfix.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-02-17 14:53:31 +0000
committerAleksey Kladov <[email protected]>2021-02-17 14:53:31 +0000
commit3db64a400c78bbd2708e67ddc07df1001fff3f29 (patch)
tree5386aab9c452981be09bc3e4362643a34e6e3617 /crates/ide_completion/src/completions/postfix.rs
parent6334ce866ab095215381c4b72692b20a84d26e96 (diff)
rename completion -> ide_completion
We don't have completion-related PRs in flight, so lets do it
Diffstat (limited to 'crates/ide_completion/src/completions/postfix.rs')
-rw-r--r--crates/ide_completion/src/completions/postfix.rs565
1 files changed, 565 insertions, 0 deletions
diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs
new file mode 100644
index 000000000..9c34ed0b6
--- /dev/null
+++ b/crates/ide_completion/src/completions/postfix.rs
@@ -0,0 +1,565 @@
1//! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
2
3mod format_like;
4
5use ide_db::{helpers::SnippetCap, ty_filter::TryEnum};
6use syntax::{
7 ast::{self, AstNode, AstToken},
8 SyntaxKind::{BLOCK_EXPR, EXPR_STMT},
9 TextRange, TextSize,
10};
11use text_edit::TextEdit;
12
13use crate::{
14 completions::postfix::format_like::add_format_like_completions,
15 context::CompletionContext,
16 item::{Builder, CompletionKind},
17 CompletionItem, CompletionItemKind, Completions,
18};
19
20pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
21 if !ctx.config.enable_postfix_completions {
22 return;
23 }
24
25 let dot_receiver = match &ctx.dot_receiver {
26 Some(it) => it,
27 None => return,
28 };
29
30 let receiver_text =
31 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
32
33 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
34 Some(it) => it,
35 None => return,
36 };
37
38 let ref_removed_ty =
39 std::iter::successors(Some(receiver_ty.clone()), |ty| ty.remove_ref()).last().unwrap();
40
41 let cap = match ctx.config.snippet_cap {
42 Some(it) => it,
43 None => return,
44 };
45 let try_enum = TryEnum::from_ty(&ctx.sema, &ref_removed_ty);
46 if let Some(try_enum) = &try_enum {
47 match try_enum {
48 TryEnum::Result => {
49 postfix_snippet(
50 ctx,
51 cap,
52 &dot_receiver,
53 "ifl",
54 "if let Ok {}",
55 &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
56 )
57 .add_to(acc);
58
59 postfix_snippet(
60 ctx,
61 cap,
62 &dot_receiver,
63 "while",
64 "while let Ok {}",
65 &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
66 )
67 .add_to(acc);
68 }
69 TryEnum::Option => {
70 postfix_snippet(
71 ctx,
72 cap,
73 &dot_receiver,
74 "ifl",
75 "if let Some {}",
76 &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
77 )
78 .add_to(acc);
79
80 postfix_snippet(
81 ctx,
82 cap,
83 &dot_receiver,
84 "while",
85 "while let Some {}",
86 &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
87 )
88 .add_to(acc);
89 }
90 }
91 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
92 postfix_snippet(
93 ctx,
94 cap,
95 &dot_receiver,
96 "if",
97 "if expr {}",
98 &format!("if {} {{\n $0\n}}", receiver_text),
99 )
100 .add_to(acc);
101 postfix_snippet(
102 ctx,
103 cap,
104 &dot_receiver,
105 "while",
106 "while expr {}",
107 &format!("while {} {{\n $0\n}}", receiver_text),
108 )
109 .add_to(acc);
110 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
111 .add_to(acc);
112 }
113
114 postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text))
115 .add_to(acc);
116 postfix_snippet(
117 ctx,
118 cap,
119 &dot_receiver,
120 "refm",
121 "&mut expr",
122 &format!("&mut {}", receiver_text),
123 )
124 .add_to(acc);
125
126 // The rest of the postfix completions create an expression that moves an argument,
127 // so it's better to consider references now to avoid breaking the compilation
128 let dot_receiver = include_references(dot_receiver);
129 let receiver_text =
130 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
131
132 match try_enum {
133 Some(try_enum) => match try_enum {
134 TryEnum::Result => {
135 postfix_snippet(
136 ctx,
137 cap,
138 &dot_receiver,
139 "match",
140 "match expr {}",
141 &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
142 )
143 .add_to(acc);
144 }
145 TryEnum::Option => {
146 postfix_snippet(
147 ctx,
148 cap,
149 &dot_receiver,
150 "match",
151 "match expr {}",
152 &format!(
153 "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
154 receiver_text
155 ),
156 )
157 .add_to(acc);
158 }
159 },
160 None => {
161 postfix_snippet(
162 ctx,
163 cap,
164 &dot_receiver,
165 "match",
166 "match expr {}",
167 &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
168 )
169 .add_to(acc);
170 }
171 }
172
173 postfix_snippet(
174 ctx,
175 cap,
176 &dot_receiver,
177 "box",
178 "Box::new(expr)",
179 &format!("Box::new({})", receiver_text),
180 )
181 .add_to(acc);
182
183 postfix_snippet(ctx, cap, &dot_receiver, "ok", "Ok(expr)", &format!("Ok({})", receiver_text))
184 .add_to(acc);
185
186 postfix_snippet(
187 ctx,
188 cap,
189 &dot_receiver,
190 "some",
191 "Some(expr)",
192 &format!("Some({})", receiver_text),
193 )
194 .add_to(acc);
195
196 postfix_snippet(
197 ctx,
198 cap,
199 &dot_receiver,
200 "dbg",
201 "dbg!(expr)",
202 &format!("dbg!({})", receiver_text),
203 )
204 .add_to(acc);
205
206 postfix_snippet(
207 ctx,
208 cap,
209 &dot_receiver,
210 "dbgr",
211 "dbg!(&expr)",
212 &format!("dbg!(&{})", receiver_text),
213 )
214 .add_to(acc);
215
216 postfix_snippet(
217 ctx,
218 cap,
219 &dot_receiver,
220 "call",
221 "function(expr)",
222 &format!("${{1}}({})", receiver_text),
223 )
224 .add_to(acc);
225
226 if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
227 if matches!(parent.kind(), BLOCK_EXPR | EXPR_STMT) {
228 postfix_snippet(
229 ctx,
230 cap,
231 &dot_receiver,
232 "let",
233 "let",
234 &format!("let $0 = {};", receiver_text),
235 )
236 .add_to(acc);
237 postfix_snippet(
238 ctx,
239 cap,
240 &dot_receiver,
241 "letm",
242 "let mut",
243 &format!("let mut $0 = {};", receiver_text),
244 )
245 .add_to(acc);
246 }
247 }
248
249 if let ast::Expr::Literal(literal) = dot_receiver.clone() {
250 if let Some(literal_text) = ast::String::cast(literal.token()) {
251 add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
252 }
253 }
254}
255
256fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
257 if receiver_is_ambiguous_float_literal {
258 let text = receiver.syntax().text();
259 let without_dot = ..text.len() - TextSize::of('.');
260 text.slice(without_dot).to_string()
261 } else {
262 receiver.to_string()
263 }
264}
265
266fn include_references(initial_element: &ast::Expr) -> ast::Expr {
267 let mut resulting_element = initial_element.clone();
268 while let Some(parent_ref_element) =
269 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
270 {
271 resulting_element = ast::Expr::from(parent_ref_element);
272 }
273 resulting_element
274}
275
276fn postfix_snippet(
277 ctx: &CompletionContext,
278 cap: SnippetCap,
279 receiver: &ast::Expr,
280 label: &str,
281 detail: &str,
282 snippet: &str,
283) -> Builder {
284 let edit = {
285 let receiver_syntax = receiver.syntax();
286 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
287 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
288 TextEdit::replace(delete_range, snippet.to_string())
289 };
290 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
291 .detail(detail)
292 .kind(CompletionItemKind::Snippet)
293 .snippet_edit(cap, edit)
294}
295
296#[cfg(test)]
297mod tests {
298 use expect_test::{expect, Expect};
299
300 use crate::{
301 test_utils::{check_edit, completion_list},
302 CompletionKind,
303 };
304
305 fn check(ra_fixture: &str, expect: Expect) {
306 let actual = completion_list(ra_fixture, CompletionKind::Postfix);
307 expect.assert_eq(&actual)
308 }
309
310 #[test]
311 fn postfix_completion_works_for_trivial_path_expression() {
312 check(
313 r#"
314fn main() {
315 let bar = true;
316 bar.$0
317}
318"#,
319 expect![[r#"
320 sn if if expr {}
321 sn while while expr {}
322 sn not !expr
323 sn ref &expr
324 sn refm &mut expr
325 sn match match expr {}
326 sn box Box::new(expr)
327 sn ok Ok(expr)
328 sn some Some(expr)
329 sn dbg dbg!(expr)
330 sn dbgr dbg!(&expr)
331 sn call function(expr)
332 sn let let
333 sn letm let mut
334 "#]],
335 );
336 }
337
338 #[test]
339 fn postfix_completion_works_for_function_calln() {
340 check(
341 r#"
342fn foo(elt: bool) -> bool {
343 !elt
344}
345
346fn main() {
347 let bar = true;
348 foo(bar.$0)
349}
350"#,
351 expect![[r#"
352 sn if if expr {}
353 sn while while expr {}
354 sn not !expr
355 sn ref &expr
356 sn refm &mut expr
357 sn match match expr {}
358 sn box Box::new(expr)
359 sn ok Ok(expr)
360 sn some Some(expr)
361 sn dbg dbg!(expr)
362 sn dbgr dbg!(&expr)
363 sn call function(expr)
364 "#]],
365 );
366 }
367
368 #[test]
369 fn postfix_type_filtering() {
370 check(
371 r#"
372fn main() {
373 let bar: u8 = 12;
374 bar.$0
375}
376"#,
377 expect![[r#"
378 sn ref &expr
379 sn refm &mut expr
380 sn match match expr {}
381 sn box Box::new(expr)
382 sn ok Ok(expr)
383 sn some Some(expr)
384 sn dbg dbg!(expr)
385 sn dbgr dbg!(&expr)
386 sn call function(expr)
387 sn let let
388 sn letm let mut
389 "#]],
390 )
391 }
392
393 #[test]
394 fn let_middle_block() {
395 check(
396 r#"
397fn main() {
398 baz.l$0
399 res
400}
401"#,
402 expect![[r#"
403 sn if if expr {}
404 sn while while expr {}
405 sn not !expr
406 sn ref &expr
407 sn refm &mut expr
408 sn match match expr {}
409 sn box Box::new(expr)
410 sn ok Ok(expr)
411 sn some Some(expr)
412 sn dbg dbg!(expr)
413 sn dbgr dbg!(&expr)
414 sn call function(expr)
415 sn let let
416 sn letm let mut
417 "#]],
418 );
419 }
420
421 #[test]
422 fn option_iflet() {
423 check_edit(
424 "ifl",
425 r#"
426enum Option<T> { Some(T), None }
427
428fn main() {
429 let bar = Option::Some(true);
430 bar.$0
431}
432"#,
433 r#"
434enum Option<T> { Some(T), None }
435
436fn main() {
437 let bar = Option::Some(true);
438 if let Some($1) = bar {
439 $0
440}
441}
442"#,
443 );
444 }
445
446 #[test]
447 fn result_match() {
448 check_edit(
449 "match",
450 r#"
451enum Result<T, E> { Ok(T), Err(E) }
452
453fn main() {
454 let bar = Result::Ok(true);
455 bar.$0
456}
457"#,
458 r#"
459enum Result<T, E> { Ok(T), Err(E) }
460
461fn main() {
462 let bar = Result::Ok(true);
463 match bar {
464 Ok(${1:_}) => {$2},
465 Err(${3:_}) => {$0},
466}
467}
468"#,
469 );
470 }
471
472 #[test]
473 fn postfix_completion_works_for_ambiguous_float_literal() {
474 check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
475 }
476
477 #[test]
478 fn works_in_simple_macro() {
479 check_edit(
480 "dbg",
481 r#"
482macro_rules! m { ($e:expr) => { $e } }
483fn main() {
484 let bar: u8 = 12;
485 m!(bar.d$0)
486}
487"#,
488 r#"
489macro_rules! m { ($e:expr) => { $e } }
490fn main() {
491 let bar: u8 = 12;
492 m!(dbg!(bar))
493}
494"#,
495 );
496 }
497
498 #[test]
499 fn postfix_completion_for_references() {
500 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
501 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
502 check_edit(
503 "ifl",
504 r#"
505enum Option<T> { Some(T), None }
506
507fn main() {
508 let bar = &Option::Some(true);
509 bar.$0
510}
511"#,
512 r#"
513enum Option<T> { Some(T), None }
514
515fn main() {
516 let bar = &Option::Some(true);
517 if let Some($1) = bar {
518 $0
519}
520}
521"#,
522 )
523 }
524
525 #[test]
526 fn postfix_completion_for_format_like_strings() {
527 check_edit(
528 "format",
529 r#"fn main() { "{some_var:?}".$0 }"#,
530 r#"fn main() { format!("{:?}", some_var) }"#,
531 );
532 check_edit(
533 "panic",
534 r#"fn main() { "Panic with {a}".$0 }"#,
535 r#"fn main() { panic!("Panic with {}", a) }"#,
536 );
537 check_edit(
538 "println",
539 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
540 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
541 );
542 check_edit(
543 "loge",
544 r#"fn main() { "{2+2}".$0 }"#,
545 r#"fn main() { log::error!("{}", 2+2) }"#,
546 );
547 check_edit(
548 "logt",
549 r#"fn main() { "{2+2}".$0 }"#,
550 r#"fn main() { log::trace!("{}", 2+2) }"#,
551 );
552 check_edit(
553 "logd",
554 r#"fn main() { "{2+2}".$0 }"#,
555 r#"fn main() { log::debug!("{}", 2+2) }"#,
556 );
557 check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
558 check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
559 check_edit(
560 "loge",
561 r#"fn main() { "{2+2}".$0 }"#,
562 r#"fn main() { log::error!("{}", 2+2) }"#,
563 );
564 }
565}