]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/keyword.rs
1c686dc0a63efb0d95da2dcf38e8d96356c40a8f
[rust.git] / crates / ide_completion / src / completions / keyword.rs
1 //! Completes keywords, except:
2 //! - `self`, `super` and `crate`, as these are considered part of path completions.
3 //! - `await`, as this is a postfix completion we handle this in the postfix completions.
4
5 use syntax::{SyntaxKind, T};
6
7 use crate::{
8     context::{PathCompletionContext, PathKind},
9     patterns::ImmediateLocation,
10     CompletionContext, CompletionItem, CompletionItemKind, Completions,
11 };
12
13 pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
14     if ctx.token.kind() == SyntaxKind::COMMENT {
15         cov_mark::hit!(no_keyword_completion_in_comments);
16         return;
17     }
18     if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) {
19         cov_mark::hit!(no_keyword_completion_in_record_lit);
20         return;
21     }
22     if ctx.attribute_under_caret.is_some() {
23         cov_mark::hit!(no_keyword_completion_in_attr_of_expr);
24         return;
25     }
26     if ctx.is_non_trivial_path() {
27         cov_mark::hit!(no_keyword_completion_in_non_trivial_path);
28         return;
29     }
30
31     let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet);
32
33     let expects_assoc_item = ctx.expects_assoc_item();
34     let has_block_expr_parent = ctx.has_block_expr_parent();
35     let expects_item = ctx.expects_item();
36
37     if let Some(PathKind::Vis { has_in_token }) = ctx.path_kind() {
38         if !has_in_token {
39             cov_mark::hit!(kw_completion_in);
40             add_keyword("in", "in");
41         }
42         return;
43     }
44     if ctx.has_impl_or_trait_prev_sibling() {
45         add_keyword("where", "where");
46         if ctx.has_impl_prev_sibling() {
47             add_keyword("for", "for");
48         }
49         return;
50     }
51     if ctx.previous_token_is(T![unsafe]) {
52         if expects_item || expects_assoc_item || has_block_expr_parent {
53             add_keyword("fn", "fn $1($2) {\n    $0\n}")
54         }
55
56         if expects_item || has_block_expr_parent {
57             add_keyword("trait", "trait $1 {\n    $0\n}");
58             add_keyword("impl", "impl $1 {\n    $0\n}");
59         }
60
61         return;
62     }
63
64     if !ctx.has_visibility_prev_sibling()
65         && (expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_field())
66     {
67         add_keyword("pub(crate)", "pub(crate)");
68         add_keyword("pub(super)", "pub(super)");
69         add_keyword("pub", "pub");
70     }
71
72     if expects_item || expects_assoc_item || has_block_expr_parent {
73         add_keyword("unsafe", "unsafe");
74         add_keyword("fn", "fn $1($2) {\n    $0\n}");
75         add_keyword("const", "const $0");
76         add_keyword("type", "type $0");
77     }
78
79     if expects_item || has_block_expr_parent {
80         if !ctx.has_visibility_prev_sibling() {
81             add_keyword("impl", "impl $1 {\n    $0\n}");
82             add_keyword("extern", "extern $0");
83         }
84         add_keyword("use", "use $0");
85         add_keyword("trait", "trait $1 {\n    $0\n}");
86         add_keyword("static", "static $0");
87         add_keyword("mod", "mod $0");
88     }
89
90     if expects_item {
91         add_keyword("enum", "enum $1 {\n    $0\n}");
92         add_keyword("struct", "struct $0");
93         add_keyword("union", "union $1 {\n    $0\n}");
94     }
95
96     if ctx.expects_type() {
97         return;
98     }
99
100     if ctx.expects_expression() {
101         if !has_block_expr_parent {
102             add_keyword("unsafe", "unsafe {\n    $0\n}");
103         }
104         add_keyword("match", "match $1 {\n    $0\n}");
105         add_keyword("while", "while $1 {\n    $0\n}");
106         add_keyword("while let", "while let $1 = $2 {\n    $0\n}");
107         add_keyword("loop", "loop {\n    $0\n}");
108         add_keyword("if", "if $1 {\n    $0\n}");
109         add_keyword("if let", "if let $1 = $2 {\n    $0\n}");
110         add_keyword("for", "for $1 in $2 {\n    $0\n}");
111         add_keyword("true", "true");
112         add_keyword("false", "false");
113     }
114
115     if ctx.previous_token_is(T![if]) || ctx.previous_token_is(T![while]) || has_block_expr_parent {
116         add_keyword("let", "let");
117     }
118
119     if ctx.after_if() {
120         add_keyword("else", "else {\n    $0\n}");
121         add_keyword("else if", "else if $1 {\n    $0\n}");
122     }
123
124     if ctx.expects_ident_pat_or_ref_expr() {
125         add_keyword("mut", "mut ");
126     }
127
128     let (can_be_stmt, in_loop_body) = match ctx.path_context {
129         Some(PathCompletionContext {
130             is_trivial_path: true, can_be_stmt, in_loop_body, ..
131         }) => (can_be_stmt, in_loop_body),
132         _ => return,
133     };
134
135     if in_loop_body {
136         if can_be_stmt {
137             add_keyword("continue", "continue;");
138             add_keyword("break", "break;");
139         } else {
140             add_keyword("continue", "continue");
141             add_keyword("break", "break");
142         }
143     }
144
145     let fn_def = match &ctx.function_def {
146         Some(it) => it,
147         None => return,
148     };
149
150     add_keyword(
151         "return",
152         match (can_be_stmt, fn_def.ret_type().is_some()) {
153             (true, true) => "return $0;",
154             (true, false) => "return;",
155             (false, true) => "return $0",
156             (false, false) => "return",
157         },
158     )
159 }
160
161 fn add_keyword(acc: &mut Completions, ctx: &CompletionContext, kw: &str, snippet: &str) {
162     let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
163
164     match ctx.config.snippet_cap {
165         Some(cap) => {
166             if snippet.ends_with('}') && ctx.incomplete_let {
167                 cov_mark::hit!(let_semi);
168                 item.insert_snippet(cap, format!("{};", snippet));
169             } else {
170                 item.insert_snippet(cap, snippet);
171             }
172         }
173         None => {
174             item.insert_text(if snippet.contains('$') { kw } else { snippet });
175         }
176     };
177     item.add_to(acc);
178 }
179
180 #[cfg(test)]
181 mod tests {
182     use expect_test::{expect, Expect};
183
184     use crate::tests::{check_edit, completion_list};
185
186     fn check(ra_fixture: &str, expect: Expect) {
187         let actual = completion_list(ra_fixture);
188         expect.assert_eq(&actual)
189     }
190
191     #[test]
192     fn test_else_edit_after_if() {
193         check_edit(
194             "else",
195             r#"fn quux() { if true { () } $0 }"#,
196             r#"fn quux() { if true { () } else {
197     $0
198 } }"#,
199         );
200     }
201
202     #[test]
203     fn test_keywords_after_unsafe_in_block_expr() {
204         check(
205             r"fn my_fn() { unsafe $0 }",
206             expect![[r#"
207                 kw fn
208                 kw trait
209                 kw impl
210                 sn pd
211                 sn ppd
212             "#]],
213         );
214     }
215
216     #[test]
217     fn test_completion_await_impls_future() {
218         check(
219             r#"
220 //- minicore: future
221 use core::future::*;
222 struct A {}
223 impl Future for A {}
224 fn foo(a: A) { a.$0 }
225 "#,
226             expect![[r#"
227                 kw await expr.await
228                 sn ref   &expr
229                 sn refm  &mut expr
230                 sn match match expr {}
231                 sn box   Box::new(expr)
232                 sn ok    Ok(expr)
233                 sn err   Err(expr)
234                 sn some  Some(expr)
235                 sn dbg   dbg!(expr)
236                 sn dbgr  dbg!(&expr)
237                 sn call  function(expr)
238                 sn let   let
239                 sn letm  let mut
240             "#]],
241         );
242
243         check(
244             r#"
245 //- minicore: future
246 use std::future::*;
247 fn foo() {
248     let a = async {};
249     a.$0
250 }
251 "#,
252             expect![[r#"
253                 kw await expr.await
254                 sn ref   &expr
255                 sn refm  &mut expr
256                 sn match match expr {}
257                 sn box   Box::new(expr)
258                 sn ok    Ok(expr)
259                 sn err   Err(expr)
260                 sn some  Some(expr)
261                 sn dbg   dbg!(expr)
262                 sn dbgr  dbg!(&expr)
263                 sn call  function(expr)
264                 sn let   let
265                 sn letm  let mut
266             "#]],
267         )
268     }
269
270     #[test]
271     fn let_semi() {
272         cov_mark::check!(let_semi);
273         check_edit(
274             "match",
275             r#"
276 fn main() { let x = $0 }
277 "#,
278             r#"
279 fn main() { let x = match $1 {
280     $0
281 }; }
282 "#,
283         );
284
285         check_edit(
286             "if",
287             r#"
288 fn main() {
289     let x = $0
290     let y = 92;
291 }
292 "#,
293             r#"
294 fn main() {
295     let x = if $1 {
296     $0
297 };
298     let y = 92;
299 }
300 "#,
301         );
302
303         check_edit(
304             "loop",
305             r#"
306 fn main() {
307     let x = $0
308     bar();
309 }
310 "#,
311             r#"
312 fn main() {
313     let x = loop {
314     $0
315 };
316     bar();
317 }
318 "#,
319         );
320     }
321 }