diff options
Diffstat (limited to 'crates/ra_analysis/src/completion/complete_keywords.rs')
-rw-r--r-- | crates/ra_analysis/src/completion/complete_keywords.rs | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/completion/complete_keywords.rs b/crates/ra_analysis/src/completion/complete_keywords.rs new file mode 100644 index 000000000..d0a6ec19e --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_keywords.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | use ra_syntax::{ | ||
2 | algo::visit::{visitor, Visitor}, | ||
3 | AstNode, | ||
4 | ast::{self, LoopBodyOwner}, | ||
5 | SyntaxKind::*, SyntaxNodeRef, | ||
6 | }; | ||
7 | |||
8 | use crate::{ | ||
9 | completion::{SyntaxContext, CompletionItem, Completions, CompletionKind::*}, | ||
10 | }; | ||
11 | |||
12 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &SyntaxContext) { | ||
13 | if !ctx.is_trivial_path { | ||
14 | return; | ||
15 | } | ||
16 | let fn_def = match ctx.enclosing_fn { | ||
17 | Some(it) => it, | ||
18 | None => return, | ||
19 | }; | ||
20 | acc.add(keyword("if", "if $0 {}")); | ||
21 | acc.add(keyword("match", "match $0 {}")); | ||
22 | acc.add(keyword("while", "while $0 {}")); | ||
23 | acc.add(keyword("loop", "loop {$0}")); | ||
24 | |||
25 | if ctx.after_if { | ||
26 | acc.add(keyword("else", "else {$0}")); | ||
27 | acc.add(keyword("else if", "else if $0 {}")); | ||
28 | } | ||
29 | if is_in_loop_body(ctx.leaf) { | ||
30 | acc.add(keyword("continue", "continue")); | ||
31 | acc.add(keyword("break", "break")); | ||
32 | } | ||
33 | acc.add_all(complete_return(fn_def, ctx.is_stmt)); | ||
34 | } | ||
35 | |||
36 | fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool { | ||
37 | for node in leaf.ancestors() { | ||
38 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | ||
39 | break; | ||
40 | } | ||
41 | let loop_body = visitor() | ||
42 | .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body) | ||
43 | .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body) | ||
44 | .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body) | ||
45 | .accept(node); | ||
46 | if let Some(Some(body)) = loop_body { | ||
47 | if leaf.range().is_subrange(&body.syntax().range()) { | ||
48 | return true; | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | false | ||
53 | } | ||
54 | |||
55 | fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> { | ||
56 | let snip = match (is_stmt, fn_def.ret_type().is_some()) { | ||
57 | (true, true) => "return $0;", | ||
58 | (true, false) => "return;", | ||
59 | (false, true) => "return $0", | ||
60 | (false, false) => "return", | ||
61 | }; | ||
62 | Some(keyword("return", snip)) | ||
63 | } | ||
64 | |||
65 | fn keyword(kw: &str, snippet: &str) -> CompletionItem { | ||
66 | CompletionItem::new(kw) | ||
67 | .kind(Keyword) | ||
68 | .snippet(snippet) | ||
69 | .build() | ||
70 | } | ||
71 | |||
72 | #[cfg(test)] | ||
73 | mod tests { | ||
74 | use crate::completion::{CompletionKind, check_completion}; | ||
75 | fn check_keyword_completion(code: &str, expected_completions: &str) { | ||
76 | check_completion(code, expected_completions, CompletionKind::Keyword); | ||
77 | } | ||
78 | |||
79 | #[test] | ||
80 | fn test_completion_kewords() { | ||
81 | check_keyword_completion( | ||
82 | r" | ||
83 | fn quux() { | ||
84 | <|> | ||
85 | } | ||
86 | ", | ||
87 | r#" | ||
88 | if "if $0 {}" | ||
89 | match "match $0 {}" | ||
90 | while "while $0 {}" | ||
91 | loop "loop {$0}" | ||
92 | return "return" | ||
93 | "#, | ||
94 | ); | ||
95 | } | ||
96 | |||
97 | #[test] | ||
98 | fn test_completion_else() { | ||
99 | check_keyword_completion( | ||
100 | r" | ||
101 | fn quux() { | ||
102 | if true { | ||
103 | () | ||
104 | } <|> | ||
105 | } | ||
106 | ", | ||
107 | r#" | ||
108 | if "if $0 {}" | ||
109 | match "match $0 {}" | ||
110 | while "while $0 {}" | ||
111 | loop "loop {$0}" | ||
112 | else "else {$0}" | ||
113 | else if "else if $0 {}" | ||
114 | return "return" | ||
115 | "#, | ||
116 | ); | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn test_completion_return_value() { | ||
121 | check_keyword_completion( | ||
122 | r" | ||
123 | fn quux() -> i32 { | ||
124 | <|> | ||
125 | 92 | ||
126 | } | ||
127 | ", | ||
128 | r#" | ||
129 | if "if $0 {}" | ||
130 | match "match $0 {}" | ||
131 | while "while $0 {}" | ||
132 | loop "loop {$0}" | ||
133 | return "return $0;" | ||
134 | "#, | ||
135 | ); | ||
136 | check_keyword_completion( | ||
137 | r" | ||
138 | fn quux() { | ||
139 | <|> | ||
140 | 92 | ||
141 | } | ||
142 | ", | ||
143 | r#" | ||
144 | if "if $0 {}" | ||
145 | match "match $0 {}" | ||
146 | while "while $0 {}" | ||
147 | loop "loop {$0}" | ||
148 | return "return;" | ||
149 | "#, | ||
150 | ); | ||
151 | } | ||
152 | |||
153 | #[test] | ||
154 | fn test_completion_return_no_stmt() { | ||
155 | check_keyword_completion( | ||
156 | r" | ||
157 | fn quux() -> i32 { | ||
158 | match () { | ||
159 | () => <|> | ||
160 | } | ||
161 | } | ||
162 | ", | ||
163 | r#" | ||
164 | if "if $0 {}" | ||
165 | match "match $0 {}" | ||
166 | while "while $0 {}" | ||
167 | loop "loop {$0}" | ||
168 | return "return $0" | ||
169 | "#, | ||
170 | ); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn test_continue_break_completion() { | ||
175 | check_keyword_completion( | ||
176 | r" | ||
177 | fn quux() -> i32 { | ||
178 | loop { <|> } | ||
179 | } | ||
180 | ", | ||
181 | r#" | ||
182 | if "if $0 {}" | ||
183 | match "match $0 {}" | ||
184 | while "while $0 {}" | ||
185 | loop "loop {$0}" | ||
186 | continue "continue" | ||
187 | break "break" | ||
188 | return "return $0" | ||
189 | "#, | ||
190 | ); | ||
191 | check_keyword_completion( | ||
192 | r" | ||
193 | fn quux() -> i32 { | ||
194 | loop { || { <|> } } | ||
195 | } | ||
196 | ", | ||
197 | r#" | ||
198 | if "if $0 {}" | ||
199 | match "match $0 {}" | ||
200 | while "while $0 {}" | ||
201 | loop "loop {$0}" | ||
202 | return "return $0" | ||
203 | "#, | ||
204 | ); | ||
205 | } | ||
206 | } | ||