aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/completion/complete_postfix.rs
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-24 10:19:53 +0100
committerZac Pullar-Strecker <[email protected]>2020-08-24 10:20:13 +0100
commit7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch)
treebdb47765991cb973b2cd5481a088fac636bd326c /crates/ide/src/completion/complete_postfix.rs
parentca464650eeaca6195891199a93f4f76cf3e7e697 (diff)
parente65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/ide/src/completion/complete_postfix.rs')
-rw-r--r--crates/ide/src/completion/complete_postfix.rs378
1 files changed, 378 insertions, 0 deletions
diff --git a/crates/ide/src/completion/complete_postfix.rs b/crates/ide/src/completion/complete_postfix.rs
new file mode 100644
index 000000000..84c4e129d
--- /dev/null
+++ b/crates/ide/src/completion/complete_postfix.rs
@@ -0,0 +1,378 @@
1//! FIXME: write short doc here
2use assists::utils::TryEnum;
3use syntax::{
4 ast::{self, AstNode},
5 TextRange, TextSize,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 completion::{
11 completion_config::SnippetCap,
12 completion_context::CompletionContext,
13 completion_item::{Builder, CompletionKind, Completions},
14 },
15 CompletionItem, CompletionItemKind,
16};
17
18pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
19 if !ctx.config.enable_postfix_completions {
20 return;
21 }
22
23 let dot_receiver = match &ctx.dot_receiver {
24 Some(it) => it,
25 None => return,
26 };
27
28 let receiver_text =
29 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
30
31 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
32 Some(it) => it,
33 None => return,
34 };
35
36 let cap = match ctx.config.snippet_cap {
37 Some(it) => it,
38 None => return,
39 };
40 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty);
41 if let Some(try_enum) = &try_enum {
42 match try_enum {
43 TryEnum::Result => {
44 postfix_snippet(
45 ctx,
46 cap,
47 &dot_receiver,
48 "ifl",
49 "if let Ok {}",
50 &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
51 )
52 .add_to(acc);
53
54 postfix_snippet(
55 ctx,
56 cap,
57 &dot_receiver,
58 "while",
59 "while let Ok {}",
60 &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
61 )
62 .add_to(acc);
63 }
64 TryEnum::Option => {
65 postfix_snippet(
66 ctx,
67 cap,
68 &dot_receiver,
69 "ifl",
70 "if let Some {}",
71 &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
72 )
73 .add_to(acc);
74
75 postfix_snippet(
76 ctx,
77 cap,
78 &dot_receiver,
79 "while",
80 "while let Some {}",
81 &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
82 )
83 .add_to(acc);
84 }
85 }
86 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
87 postfix_snippet(
88 ctx,
89 cap,
90 &dot_receiver,
91 "if",
92 "if expr {}",
93 &format!("if {} {{\n $0\n}}", receiver_text),
94 )
95 .add_to(acc);
96 postfix_snippet(
97 ctx,
98 cap,
99 &dot_receiver,
100 "while",
101 "while expr {}",
102 &format!("while {} {{\n $0\n}}", receiver_text),
103 )
104 .add_to(acc);
105 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
106 .add_to(acc);
107 }
108
109 postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text))
110 .add_to(acc);
111 postfix_snippet(
112 ctx,
113 cap,
114 &dot_receiver,
115 "refm",
116 "&mut expr",
117 &format!("&mut {}", receiver_text),
118 )
119 .add_to(acc);
120
121 // The rest of the postfix completions create an expression that moves an argument,
122 // so it's better to consider references now to avoid breaking the compilation
123 let dot_receiver = include_references(dot_receiver);
124 let receiver_text =
125 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
126
127 match try_enum {
128 Some(try_enum) => match try_enum {
129 TryEnum::Result => {
130 postfix_snippet(
131 ctx,
132 cap,
133 &dot_receiver,
134 "match",
135 "match expr {}",
136 &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
137 )
138 .add_to(acc);
139 }
140 TryEnum::Option => {
141 postfix_snippet(
142 ctx,
143 cap,
144 &dot_receiver,
145 "match",
146 "match expr {}",
147 &format!(
148 "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
149 receiver_text
150 ),
151 )
152 .add_to(acc);
153 }
154 },
155 None => {
156 postfix_snippet(
157 ctx,
158 cap,
159 &dot_receiver,
160 "match",
161 "match expr {}",
162 &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
163 )
164 .add_to(acc);
165 }
166 }
167
168 postfix_snippet(
169 ctx,
170 cap,
171 &dot_receiver,
172 "box",
173 "Box::new(expr)",
174 &format!("Box::new({})", receiver_text),
175 )
176 .add_to(acc);
177
178 postfix_snippet(
179 ctx,
180 cap,
181 &dot_receiver,
182 "dbg",
183 "dbg!(expr)",
184 &format!("dbg!({})", receiver_text),
185 )
186 .add_to(acc);
187
188 postfix_snippet(
189 ctx,
190 cap,
191 &dot_receiver,
192 "call",
193 "function(expr)",
194 &format!("${{1}}({})", receiver_text),
195 )
196 .add_to(acc);
197}
198
199fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
200 if receiver_is_ambiguous_float_literal {
201 let text = receiver.syntax().text();
202 let without_dot = ..text.len() - TextSize::of('.');
203 text.slice(without_dot).to_string()
204 } else {
205 receiver.to_string()
206 }
207}
208
209fn include_references(initial_element: &ast::Expr) -> ast::Expr {
210 let mut resulting_element = initial_element.clone();
211 while let Some(parent_ref_element) =
212 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
213 {
214 resulting_element = ast::Expr::from(parent_ref_element);
215 }
216 resulting_element
217}
218
219fn postfix_snippet(
220 ctx: &CompletionContext,
221 cap: SnippetCap,
222 receiver: &ast::Expr,
223 label: &str,
224 detail: &str,
225 snippet: &str,
226) -> Builder {
227 let edit = {
228 let receiver_syntax = receiver.syntax();
229 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
230 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
231 TextEdit::replace(delete_range, snippet.to_string())
232 };
233 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
234 .detail(detail)
235 .kind(CompletionItemKind::Snippet)
236 .snippet_edit(cap, edit)
237}
238
239#[cfg(test)]
240mod tests {
241 use expect_test::{expect, Expect};
242
243 use crate::completion::{
244 test_utils::{check_edit, completion_list},
245 CompletionKind,
246 };
247
248 fn check(ra_fixture: &str, expect: Expect) {
249 let actual = completion_list(ra_fixture, CompletionKind::Postfix);
250 expect.assert_eq(&actual)
251 }
252
253 #[test]
254 fn postfix_completion_works_for_trivial_path_expression() {
255 check(
256 r#"
257fn main() {
258 let bar = true;
259 bar.<|>
260}
261"#,
262 expect![[r#"
263 sn box Box::new(expr)
264 sn call function(expr)
265 sn dbg dbg!(expr)
266 sn if if expr {}
267 sn match match expr {}
268 sn not !expr
269 sn ref &expr
270 sn refm &mut expr
271 sn while while expr {}
272 "#]],
273 );
274 }
275
276 #[test]
277 fn postfix_type_filtering() {
278 check(
279 r#"
280fn main() {
281 let bar: u8 = 12;
282 bar.<|>
283}
284"#,
285 expect![[r#"
286 sn box Box::new(expr)
287 sn call function(expr)
288 sn dbg dbg!(expr)
289 sn match match expr {}
290 sn ref &expr
291 sn refm &mut expr
292 "#]],
293 )
294 }
295
296 #[test]
297 fn option_iflet() {
298 check_edit(
299 "ifl",
300 r#"
301enum Option<T> { Some(T), None }
302
303fn main() {
304 let bar = Option::Some(true);
305 bar.<|>
306}
307"#,
308 r#"
309enum Option<T> { Some(T), None }
310
311fn main() {
312 let bar = Option::Some(true);
313 if let Some($1) = bar {
314 $0
315}
316}
317"#,
318 );
319 }
320
321 #[test]
322 fn result_match() {
323 check_edit(
324 "match",
325 r#"
326enum Result<T, E> { Ok(T), Err(E) }
327
328fn main() {
329 let bar = Result::Ok(true);
330 bar.<|>
331}
332"#,
333 r#"
334enum Result<T, E> { Ok(T), Err(E) }
335
336fn main() {
337 let bar = Result::Ok(true);
338 match bar {
339 Ok(${1:_}) => {$2},
340 Err(${3:_}) => {$0},
341}
342}
343"#,
344 );
345 }
346
347 #[test]
348 fn postfix_completion_works_for_ambiguous_float_literal() {
349 check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#)
350 }
351
352 #[test]
353 fn works_in_simple_macro() {
354 check_edit(
355 "dbg",
356 r#"
357macro_rules! m { ($e:expr) => { $e } }
358fn main() {
359 let bar: u8 = 12;
360 m!(bar.d<|>)
361}
362"#,
363 r#"
364macro_rules! m { ($e:expr) => { $e } }
365fn main() {
366 let bar: u8 = 12;
367 m!(dbg!(bar))
368}
369"#,
370 );
371 }
372
373 #[test]
374 fn postfix_completion_for_references() {
375 check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#);
376 check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#);
377 }
378}