aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src/complete_postfix.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion/src/complete_postfix.rs')
-rw-r--r--crates/completion/src/complete_postfix.rs452
1 files changed, 452 insertions, 0 deletions
diff --git a/crates/completion/src/complete_postfix.rs b/crates/completion/src/complete_postfix.rs
new file mode 100644
index 000000000..700573cf2
--- /dev/null
+++ b/crates/completion/src/complete_postfix.rs
@@ -0,0 +1,452 @@
1//! Postfix completions, like `Ok(10).ifl<|>` => `if let Ok() = Ok(10) { <|> }`.
2
3mod format_like;
4
5use assists::utils::TryEnum;
6use syntax::{
7 ast::{self, AstNode, AstToken},
8 TextRange, TextSize,
9};
10use text_edit::TextEdit;
11
12use self::format_like::add_format_like_completions;
13use crate::{
14 completion_config::SnippetCap,
15 completion_context::CompletionContext,
16 completion_item::{Builder, CompletionKind, Completions},
17 CompletionItem, CompletionItemKind,
18};
19
20pub(super) 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 cap = match ctx.config.snippet_cap {
39 Some(it) => it,
40 None => return,
41 };
42 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty);
43 if let Some(try_enum) = &try_enum {
44 match try_enum {
45 TryEnum::Result => {
46 postfix_snippet(
47 ctx,
48 cap,
49 &dot_receiver,
50 "ifl",
51 "if let Ok {}",
52 &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
53 )
54 .add_to(acc);
55
56 postfix_snippet(
57 ctx,
58 cap,
59 &dot_receiver,
60 "while",
61 "while let Ok {}",
62 &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
63 )
64 .add_to(acc);
65 }
66 TryEnum::Option => {
67 postfix_snippet(
68 ctx,
69 cap,
70 &dot_receiver,
71 "ifl",
72 "if let Some {}",
73 &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
74 )
75 .add_to(acc);
76
77 postfix_snippet(
78 ctx,
79 cap,
80 &dot_receiver,
81 "while",
82 "while let Some {}",
83 &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
84 )
85 .add_to(acc);
86 }
87 }
88 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
89 postfix_snippet(
90 ctx,
91 cap,
92 &dot_receiver,
93 "if",
94 "if expr {}",
95 &format!("if {} {{\n $0\n}}", receiver_text),
96 )
97 .add_to(acc);
98 postfix_snippet(
99 ctx,
100 cap,
101 &dot_receiver,
102 "while",
103 "while expr {}",
104 &format!("while {} {{\n $0\n}}", receiver_text),
105 )
106 .add_to(acc);
107 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
108 .add_to(acc);
109 }
110
111 postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text))
112 .add_to(acc);
113 postfix_snippet(
114 ctx,
115 cap,
116 &dot_receiver,
117 "refm",
118 "&mut expr",
119 &format!("&mut {}", receiver_text),
120 )
121 .add_to(acc);
122
123 // The rest of the postfix completions create an expression that moves an argument,
124 // so it's better to consider references now to avoid breaking the compilation
125 let dot_receiver = include_references(dot_receiver);
126 let receiver_text =
127 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
128
129 match try_enum {
130 Some(try_enum) => match try_enum {
131 TryEnum::Result => {
132 postfix_snippet(
133 ctx,
134 cap,
135 &dot_receiver,
136 "match",
137 "match expr {}",
138 &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
139 )
140 .add_to(acc);
141 }
142 TryEnum::Option => {
143 postfix_snippet(
144 ctx,
145 cap,
146 &dot_receiver,
147 "match",
148 "match expr {}",
149 &format!(
150 "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
151 receiver_text
152 ),
153 )
154 .add_to(acc);
155 }
156 },
157 None => {
158 postfix_snippet(
159 ctx,
160 cap,
161 &dot_receiver,
162 "match",
163 "match expr {}",
164 &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
165 )
166 .add_to(acc);
167 }
168 }
169
170 postfix_snippet(
171 ctx,
172 cap,
173 &dot_receiver,
174 "box",
175 "Box::new(expr)",
176 &format!("Box::new({})", receiver_text),
177 )
178 .add_to(acc);
179
180 postfix_snippet(ctx, cap, &dot_receiver, "ok", "Ok(expr)", &format!("Ok({})", receiver_text))
181 .add_to(acc);
182
183 postfix_snippet(
184 ctx,
185 cap,
186 &dot_receiver,
187 "dbg",
188 "dbg!(expr)",
189 &format!("dbg!({})", receiver_text),
190 )
191 .add_to(acc);
192
193 postfix_snippet(
194 ctx,
195 cap,
196 &dot_receiver,
197 "dbgr",
198 "dbg!(&expr)",
199 &format!("dbg!(&{})", receiver_text),
200 )
201 .add_to(acc);
202
203 postfix_snippet(
204 ctx,
205 cap,
206 &dot_receiver,
207 "call",
208 "function(expr)",
209 &format!("${{1}}({})", receiver_text),
210 )
211 .add_to(acc);
212
213 if let ast::Expr::Literal(literal) = dot_receiver.clone() {
214 if let Some(literal_text) = ast::String::cast(literal.token()) {
215 add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
216 }
217 }
218}
219
220fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
221 if receiver_is_ambiguous_float_literal {
222 let text = receiver.syntax().text();
223 let without_dot = ..text.len() - TextSize::of('.');
224 text.slice(without_dot).to_string()
225 } else {
226 receiver.to_string()
227 }
228}
229
230fn include_references(initial_element: &ast::Expr) -> ast::Expr {
231 let mut resulting_element = initial_element.clone();
232 while let Some(parent_ref_element) =
233 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
234 {
235 resulting_element = ast::Expr::from(parent_ref_element);
236 }
237 resulting_element
238}
239
240fn postfix_snippet(
241 ctx: &CompletionContext,
242 cap: SnippetCap,
243 receiver: &ast::Expr,
244 label: &str,
245 detail: &str,
246 snippet: &str,
247) -> Builder {
248 let edit = {
249 let receiver_syntax = receiver.syntax();
250 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
251 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
252 TextEdit::replace(delete_range, snippet.to_string())
253 };
254 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
255 .detail(detail)
256 .kind(CompletionItemKind::Snippet)
257 .snippet_edit(cap, edit)
258}
259
260#[cfg(test)]
261mod tests {
262 use expect_test::{expect, Expect};
263
264 use crate::{
265 test_utils::{check_edit, completion_list},
266 CompletionKind,
267 };
268
269 fn check(ra_fixture: &str, expect: Expect) {
270 let actual = completion_list(ra_fixture, CompletionKind::Postfix);
271 expect.assert_eq(&actual)
272 }
273
274 #[test]
275 fn postfix_completion_works_for_trivial_path_expression() {
276 check(
277 r#"
278fn main() {
279 let bar = true;
280 bar.<|>
281}
282"#,
283 expect![[r#"
284 sn box Box::new(expr)
285 sn call function(expr)
286 sn dbg dbg!(expr)
287 sn dbgr dbg!(&expr)
288 sn if if expr {}
289 sn match match expr {}
290 sn not !expr
291 sn ok Ok(expr)
292 sn ref &expr
293 sn refm &mut expr
294 sn while while expr {}
295 "#]],
296 );
297 }
298
299 #[test]
300 fn postfix_type_filtering() {
301 check(
302 r#"
303fn main() {
304 let bar: u8 = 12;
305 bar.<|>
306}
307"#,
308 expect![[r#"
309 sn box Box::new(expr)
310 sn call function(expr)
311 sn dbg dbg!(expr)
312 sn dbgr dbg!(&expr)
313 sn match match expr {}
314 sn ok Ok(expr)
315 sn ref &expr
316 sn refm &mut expr
317 "#]],
318 )
319 }
320
321 #[test]
322 fn option_iflet() {
323 check_edit(
324 "ifl",
325 r#"
326enum Option<T> { Some(T), None }
327
328fn main() {
329 let bar = Option::Some(true);
330 bar.<|>
331}
332"#,
333 r#"
334enum Option<T> { Some(T), None }
335
336fn main() {
337 let bar = Option::Some(true);
338 if let Some($1) = bar {
339 $0
340}
341}
342"#,
343 );
344 }
345
346 #[test]
347 fn result_match() {
348 check_edit(
349 "match",
350 r#"
351enum Result<T, E> { Ok(T), Err(E) }
352
353fn main() {
354 let bar = Result::Ok(true);
355 bar.<|>
356}
357"#,
358 r#"
359enum Result<T, E> { Ok(T), Err(E) }
360
361fn main() {
362 let bar = Result::Ok(true);
363 match bar {
364 Ok(${1:_}) => {$2},
365 Err(${3:_}) => {$0},
366}
367}
368"#,
369 );
370 }
371
372 #[test]
373 fn postfix_completion_works_for_ambiguous_float_literal() {
374 check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#)
375 }
376
377 #[test]
378 fn works_in_simple_macro() {
379 check_edit(
380 "dbg",
381 r#"
382macro_rules! m { ($e:expr) => { $e } }
383fn main() {
384 let bar: u8 = 12;
385 m!(bar.d<|>)
386}
387"#,
388 r#"
389macro_rules! m { ($e:expr) => { $e } }
390fn main() {
391 let bar: u8 = 12;
392 m!(dbg!(bar))
393}
394"#,
395 );
396 }
397
398 #[test]
399 fn postfix_completion_for_references() {
400 check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#);
401 check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#);
402 }
403
404 #[test]
405 fn postfix_completion_for_format_like_strings() {
406 check_edit(
407 "fmt",
408 r#"fn main() { "{some_var:?}".<|> }"#,
409 r#"fn main() { format!("{:?}", some_var) }"#,
410 );
411 check_edit(
412 "panic",
413 r#"fn main() { "Panic with {a}".<|> }"#,
414 r#"fn main() { panic!("Panic with {}", a) }"#,
415 );
416 check_edit(
417 "println",
418 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".<|> }"#,
419 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
420 );
421 check_edit(
422 "loge",
423 r#"fn main() { "{2+2}".<|> }"#,
424 r#"fn main() { log::error!("{}", 2+2) }"#,
425 );
426 check_edit(
427 "logt",
428 r#"fn main() { "{2+2}".<|> }"#,
429 r#"fn main() { log::trace!("{}", 2+2) }"#,
430 );
431 check_edit(
432 "logd",
433 r#"fn main() { "{2+2}".<|> }"#,
434 r#"fn main() { log::debug!("{}", 2+2) }"#,
435 );
436 check_edit(
437 "logi",
438 r#"fn main() { "{2+2}".<|> }"#,
439 r#"fn main() { log::info!("{}", 2+2) }"#,
440 );
441 check_edit(
442 "logw",
443 r#"fn main() { "{2+2}".<|> }"#,
444 r#"fn main() { log::warn!("{}", 2+2) }"#,
445 );
446 check_edit(
447 "loge",
448 r#"fn main() { "{2+2}".<|> }"#,
449 r#"fn main() { log::error!("{}", 2+2) }"#,
450 );
451 }
452}