diff options
Diffstat (limited to 'crates/ra_ide/src')
23 files changed, 915 insertions, 814 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index a721e23c6..e1fcf379d 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -15,6 +15,7 @@ mod complete_unqualified_path; | |||
15 | mod complete_postfix; | 15 | mod complete_postfix; |
16 | mod complete_macro_in_item_position; | 16 | mod complete_macro_in_item_position; |
17 | mod complete_trait_impl; | 17 | mod complete_trait_impl; |
18 | mod patterns; | ||
18 | #[cfg(test)] | 19 | #[cfg(test)] |
19 | mod test_utils; | 20 | mod test_utils; |
20 | 21 | ||
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs index fd95bc410..b2f621a11 100644 --- a/crates/ra_ide/src/completion/complete_keyword.rs +++ b/crates/ra_ide/src/completion/complete_keyword.rs | |||
@@ -1,11 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::ast; |
4 | ast::{self, LoopBodyOwner}, | ||
5 | match_ast, AstNode, | ||
6 | SyntaxKind::*, | ||
7 | SyntaxToken, | ||
8 | }; | ||
9 | 4 | ||
10 | use crate::completion::{ | 5 | use crate::completion::{ |
11 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | 6 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, |
@@ -41,68 +36,122 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC | |||
41 | } | 36 | } |
42 | } | 37 | } |
43 | 38 | ||
44 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { | 39 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { |
45 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) | 40 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; |
46 | .kind(CompletionItemKind::Keyword); | 41 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { |
47 | 42 | add_keyword(ctx, acc, "where", "where "); | |
48 | match ctx.config.snippet_cap { | 43 | return; |
49 | Some(cap) => res.insert_snippet(cap, snippet), | ||
50 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), | ||
51 | } | 44 | } |
52 | .build() | 45 | if ctx.unsafe_is_prev { |
53 | } | 46 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { |
47 | add_keyword(ctx, acc, "fn", "fn $0() {}") | ||
48 | } | ||
49 | |||
50 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
51 | || ctx.block_expr_parent | ||
52 | { | ||
53 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
54 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
55 | } | ||
54 | 56 | ||
55 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
56 | if !ctx.is_trivial_path { | ||
57 | return; | 57 | return; |
58 | } | 58 | } |
59 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { | ||
60 | add_keyword(ctx, acc, "fn", "fn $0() {}"); | ||
61 | } | ||
62 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
63 | || ctx.block_expr_parent | ||
64 | { | ||
65 | add_keyword(ctx, acc, "use", "use "); | ||
66 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
67 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
68 | } | ||
59 | 69 | ||
60 | let fn_def = match &ctx.function_syntax { | 70 | if ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent { |
61 | Some(it) => it, | 71 | add_keyword(ctx, acc, "enum", "enum $0 {}"); |
62 | None => return, | 72 | add_keyword(ctx, acc, "struct", "struct $0 {}"); |
63 | }; | 73 | add_keyword(ctx, acc, "union", "union $0 {}"); |
64 | acc.add(keyword(ctx, "if", "if $0 {}")); | 74 | } |
65 | acc.add(keyword(ctx, "match", "match $0 {}")); | ||
66 | acc.add(keyword(ctx, "while", "while $0 {}")); | ||
67 | acc.add(keyword(ctx, "loop", "loop {$0}")); | ||
68 | 75 | ||
76 | if ctx.block_expr_parent || ctx.is_match_arm { | ||
77 | add_keyword(ctx, acc, "match", "match $0 {}"); | ||
78 | add_keyword(ctx, acc, "loop", "loop {$0}"); | ||
79 | } | ||
80 | if ctx.block_expr_parent { | ||
81 | add_keyword(ctx, acc, "while", "while $0 {}"); | ||
82 | } | ||
83 | if ctx.if_is_prev || ctx.block_expr_parent { | ||
84 | add_keyword(ctx, acc, "let", "let "); | ||
85 | } | ||
86 | if ctx.if_is_prev || ctx.block_expr_parent || ctx.is_match_arm { | ||
87 | add_keyword(ctx, acc, "if", "if "); | ||
88 | add_keyword(ctx, acc, "if let", "if let "); | ||
89 | } | ||
69 | if ctx.after_if { | 90 | if ctx.after_if { |
70 | acc.add(keyword(ctx, "else", "else {$0}")); | 91 | add_keyword(ctx, acc, "else", "else {$0}"); |
71 | acc.add(keyword(ctx, "else if", "else if $0 {}")); | 92 | add_keyword(ctx, acc, "else if", "else if $0 {}"); |
93 | } | ||
94 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
95 | || ctx.block_expr_parent | ||
96 | { | ||
97 | add_keyword(ctx, acc, "mod", "mod $0 {}"); | ||
98 | } | ||
99 | if ctx.bind_pat_parent || ctx.ref_pat_parent { | ||
100 | add_keyword(ctx, acc, "mut", "mut "); | ||
72 | } | 101 | } |
73 | if is_in_loop_body(&ctx.token) { | 102 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { |
103 | add_keyword(ctx, acc, "const", "const "); | ||
104 | add_keyword(ctx, acc, "type", "type "); | ||
105 | } | ||
106 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
107 | || ctx.block_expr_parent | ||
108 | { | ||
109 | add_keyword(ctx, acc, "static", "static "); | ||
110 | }; | ||
111 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
112 | || ctx.block_expr_parent | ||
113 | { | ||
114 | add_keyword(ctx, acc, "extern", "extern "); | ||
115 | } | ||
116 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent || ctx.is_match_arm { | ||
117 | add_keyword(ctx, acc, "unsafe", "unsafe "); | ||
118 | } | ||
119 | if ctx.in_loop_body { | ||
74 | if ctx.can_be_stmt { | 120 | if ctx.can_be_stmt { |
75 | acc.add(keyword(ctx, "continue", "continue;")); | 121 | add_keyword(ctx, acc, "continue", "continue;"); |
76 | acc.add(keyword(ctx, "break", "break;")); | 122 | add_keyword(ctx, acc, "break", "break;"); |
77 | } else { | 123 | } else { |
78 | acc.add(keyword(ctx, "continue", "continue")); | 124 | add_keyword(ctx, acc, "continue", "continue"); |
79 | acc.add(keyword(ctx, "break", "break")); | 125 | add_keyword(ctx, acc, "break", "break"); |
80 | } | 126 | } |
81 | } | 127 | } |
128 | if ctx.has_item_list_or_source_file_parent && !ctx.has_trait_parent { | ||
129 | add_keyword(ctx, acc, "pub", "pub ") | ||
130 | } | ||
131 | |||
132 | if !ctx.is_trivial_path { | ||
133 | return; | ||
134 | } | ||
135 | let fn_def = match &ctx.function_syntax { | ||
136 | Some(it) => it, | ||
137 | None => return, | ||
138 | }; | ||
82 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); | 139 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); |
83 | } | 140 | } |
84 | 141 | ||
85 | fn is_in_loop_body(leaf: &SyntaxToken) -> bool { | 142 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { |
86 | // FIXME move this to CompletionContext and make it handle macros | 143 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) |
87 | for node in leaf.parent().ancestors() { | 144 | .kind(CompletionItemKind::Keyword); |
88 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | 145 | |
89 | break; | 146 | match ctx.config.snippet_cap { |
90 | } | 147 | Some(cap) => res.insert_snippet(cap, snippet), |
91 | let loop_body = match_ast! { | 148 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), |
92 | match node { | ||
93 | ast::ForExpr(it) => it.loop_body(), | ||
94 | ast::WhileExpr(it) => it.loop_body(), | ||
95 | ast::LoopExpr(it) => it.loop_body(), | ||
96 | _ => None, | ||
97 | } | ||
98 | }; | ||
99 | if let Some(body) = loop_body { | ||
100 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
101 | return true; | ||
102 | } | ||
103 | } | ||
104 | } | 149 | } |
105 | false | 150 | .build() |
151 | } | ||
152 | |||
153 | fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { | ||
154 | acc.add(keyword(ctx, kw, snippet)); | ||
106 | } | 155 | } |
107 | 156 | ||
108 | fn complete_return( | 157 | fn complete_return( |
@@ -121,327 +170,156 @@ fn complete_return( | |||
121 | 170 | ||
122 | #[cfg(test)] | 171 | #[cfg(test)] |
123 | mod tests { | 172 | mod tests { |
124 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 173 | use crate::completion::{test_utils::completion_list, CompletionKind}; |
125 | use insta::assert_debug_snapshot; | 174 | use insta::assert_snapshot; |
126 | 175 | ||
127 | fn do_keyword_completion(code: &str) -> Vec<CompletionItem> { | 176 | fn get_keyword_completions(code: &str) -> String { |
128 | do_completion(code, CompletionKind::Keyword) | 177 | completion_list(code, CompletionKind::Keyword) |
129 | } | 178 | } |
130 | 179 | ||
131 | #[test] | 180 | #[test] |
132 | fn completes_keywords_in_use_stmt() { | 181 | fn test_keywords_in_use_stmt() { |
133 | assert_debug_snapshot!( | 182 | assert_snapshot!( |
134 | do_keyword_completion( | 183 | get_keyword_completions(r"use <|>"), |
135 | r" | ||
136 | use <|> | ||
137 | ", | ||
138 | ), | ||
139 | @r###" | 184 | @r###" |
140 | [ | 185 | kw crate |
141 | CompletionItem { | 186 | kw self |
142 | label: "crate", | 187 | kw super |
143 | source_range: 21..21, | ||
144 | delete: 21..21, | ||
145 | insert: "crate::", | ||
146 | kind: Keyword, | ||
147 | }, | ||
148 | CompletionItem { | ||
149 | label: "self", | ||
150 | source_range: 21..21, | ||
151 | delete: 21..21, | ||
152 | insert: "self", | ||
153 | kind: Keyword, | ||
154 | }, | ||
155 | CompletionItem { | ||
156 | label: "super", | ||
157 | source_range: 21..21, | ||
158 | delete: 21..21, | ||
159 | insert: "super::", | ||
160 | kind: Keyword, | ||
161 | }, | ||
162 | ] | ||
163 | "### | 188 | "### |
164 | ); | 189 | ); |
165 | 190 | ||
166 | assert_debug_snapshot!( | 191 | assert_snapshot!( |
167 | do_keyword_completion( | 192 | get_keyword_completions(r"use a::<|>"), |
168 | r" | ||
169 | use a::<|> | ||
170 | ", | ||
171 | ), | ||
172 | @r###" | 193 | @r###" |
173 | [ | 194 | kw self |
174 | CompletionItem { | 195 | kw super |
175 | label: "self", | ||
176 | source_range: 24..24, | ||
177 | delete: 24..24, | ||
178 | insert: "self", | ||
179 | kind: Keyword, | ||
180 | }, | ||
181 | CompletionItem { | ||
182 | label: "super", | ||
183 | source_range: 24..24, | ||
184 | delete: 24..24, | ||
185 | insert: "super::", | ||
186 | kind: Keyword, | ||
187 | }, | ||
188 | ] | ||
189 | "### | 196 | "### |
190 | ); | 197 | ); |
191 | 198 | ||
192 | assert_debug_snapshot!( | 199 | assert_snapshot!( |
193 | do_keyword_completion( | 200 | get_keyword_completions(r"use a::{b, <|>}"), |
194 | r" | ||
195 | use a::{b, <|>} | ||
196 | ", | ||
197 | ), | ||
198 | @r###" | 201 | @r###" |
199 | [ | 202 | kw self |
200 | CompletionItem { | 203 | kw super |
201 | label: "self", | ||
202 | source_range: 28..28, | ||
203 | delete: 28..28, | ||
204 | insert: "self", | ||
205 | kind: Keyword, | ||
206 | }, | ||
207 | CompletionItem { | ||
208 | label: "super", | ||
209 | source_range: 28..28, | ||
210 | delete: 28..28, | ||
211 | insert: "super::", | ||
212 | kind: Keyword, | ||
213 | }, | ||
214 | ] | ||
215 | "### | 204 | "### |
216 | ); | 205 | ); |
217 | } | 206 | } |
218 | 207 | ||
219 | #[test] | 208 | #[test] |
220 | fn completes_various_keywords_in_function() { | 209 | fn test_keywords_at_source_file_level() { |
221 | assert_debug_snapshot!( | 210 | assert_snapshot!( |
222 | do_keyword_completion( | 211 | get_keyword_completions(r"m<|>"), |
223 | r" | ||
224 | fn quux() { | ||
225 | <|> | ||
226 | } | ||
227 | ", | ||
228 | ), | ||
229 | @r###" | 212 | @r###" |
230 | [ | 213 | kw const |
231 | CompletionItem { | 214 | kw enum |
232 | label: "if", | 215 | kw extern |
233 | source_range: 49..49, | 216 | kw fn |
234 | delete: 49..49, | 217 | kw impl |
235 | insert: "if $0 {}", | 218 | kw mod |
236 | kind: Keyword, | 219 | kw pub |
237 | }, | 220 | kw static |
238 | CompletionItem { | 221 | kw struct |
239 | label: "loop", | 222 | kw trait |
240 | source_range: 49..49, | 223 | kw type |
241 | delete: 49..49, | 224 | kw union |
242 | insert: "loop {$0}", | 225 | kw unsafe |
243 | kind: Keyword, | 226 | kw use |
244 | }, | ||
245 | CompletionItem { | ||
246 | label: "match", | ||
247 | source_range: 49..49, | ||
248 | delete: 49..49, | ||
249 | insert: "match $0 {}", | ||
250 | kind: Keyword, | ||
251 | }, | ||
252 | CompletionItem { | ||
253 | label: "return", | ||
254 | source_range: 49..49, | ||
255 | delete: 49..49, | ||
256 | insert: "return;", | ||
257 | kind: Keyword, | ||
258 | }, | ||
259 | CompletionItem { | ||
260 | label: "while", | ||
261 | source_range: 49..49, | ||
262 | delete: 49..49, | ||
263 | insert: "while $0 {}", | ||
264 | kind: Keyword, | ||
265 | }, | ||
266 | ] | ||
267 | "### | 227 | "### |
268 | ); | 228 | ); |
269 | } | 229 | } |
270 | 230 | ||
271 | #[test] | 231 | #[test] |
272 | fn completes_else_after_if() { | 232 | fn test_keywords_in_function() { |
273 | assert_debug_snapshot!( | 233 | assert_snapshot!( |
274 | do_keyword_completion( | 234 | get_keyword_completions(r"fn quux() { <|> }"), |
275 | r" | ||
276 | fn quux() { | ||
277 | if true { | ||
278 | () | ||
279 | } <|> | ||
280 | } | ||
281 | ", | ||
282 | ), | ||
283 | @r###" | 235 | @r###" |
284 | [ | 236 | kw const |
285 | CompletionItem { | 237 | kw extern |
286 | label: "else", | 238 | kw fn |
287 | source_range: 108..108, | 239 | kw if |
288 | delete: 108..108, | 240 | kw if let |
289 | insert: "else {$0}", | 241 | kw impl |
290 | kind: Keyword, | 242 | kw let |
291 | }, | 243 | kw loop |
292 | CompletionItem { | 244 | kw match |
293 | label: "else if", | 245 | kw mod |
294 | source_range: 108..108, | 246 | kw return |
295 | delete: 108..108, | 247 | kw static |
296 | insert: "else if $0 {}", | 248 | kw trait |
297 | kind: Keyword, | 249 | kw type |
298 | }, | 250 | kw unsafe |
299 | CompletionItem { | 251 | kw use |
300 | label: "if", | 252 | kw while |
301 | source_range: 108..108, | ||
302 | delete: 108..108, | ||
303 | insert: "if $0 {}", | ||
304 | kind: Keyword, | ||
305 | }, | ||
306 | CompletionItem { | ||
307 | label: "loop", | ||
308 | source_range: 108..108, | ||
309 | delete: 108..108, | ||
310 | insert: "loop {$0}", | ||
311 | kind: Keyword, | ||
312 | }, | ||
313 | CompletionItem { | ||
314 | label: "match", | ||
315 | source_range: 108..108, | ||
316 | delete: 108..108, | ||
317 | insert: "match $0 {}", | ||
318 | kind: Keyword, | ||
319 | }, | ||
320 | CompletionItem { | ||
321 | label: "return", | ||
322 | source_range: 108..108, | ||
323 | delete: 108..108, | ||
324 | insert: "return;", | ||
325 | kind: Keyword, | ||
326 | }, | ||
327 | CompletionItem { | ||
328 | label: "while", | ||
329 | source_range: 108..108, | ||
330 | delete: 108..108, | ||
331 | insert: "while $0 {}", | ||
332 | kind: Keyword, | ||
333 | }, | ||
334 | ] | ||
335 | "### | 253 | "### |
336 | ); | 254 | ); |
337 | } | 255 | } |
338 | 256 | ||
339 | #[test] | 257 | #[test] |
340 | fn test_completion_return_value() { | 258 | fn test_keywords_inside_block() { |
341 | assert_debug_snapshot!( | 259 | assert_snapshot!( |
342 | do_keyword_completion( | 260 | get_keyword_completions(r"fn quux() { if true { <|> } }"), |
343 | r" | ||
344 | fn quux() -> i32 { | ||
345 | <|> | ||
346 | 92 | ||
347 | } | ||
348 | ", | ||
349 | ), | ||
350 | @r###" | 261 | @r###" |
351 | [ | 262 | kw const |
352 | CompletionItem { | 263 | kw extern |
353 | label: "if", | 264 | kw fn |
354 | source_range: 56..56, | 265 | kw if |
355 | delete: 56..56, | 266 | kw if let |
356 | insert: "if $0 {}", | 267 | kw impl |
357 | kind: Keyword, | 268 | kw let |
358 | }, | 269 | kw loop |
359 | CompletionItem { | 270 | kw match |
360 | label: "loop", | 271 | kw mod |
361 | source_range: 56..56, | 272 | kw return |
362 | delete: 56..56, | 273 | kw static |
363 | insert: "loop {$0}", | 274 | kw trait |
364 | kind: Keyword, | 275 | kw type |
365 | }, | 276 | kw unsafe |
366 | CompletionItem { | 277 | kw use |
367 | label: "match", | 278 | kw while |
368 | source_range: 56..56, | ||
369 | delete: 56..56, | ||
370 | insert: "match $0 {}", | ||
371 | kind: Keyword, | ||
372 | }, | ||
373 | CompletionItem { | ||
374 | label: "return", | ||
375 | source_range: 56..56, | ||
376 | delete: 56..56, | ||
377 | insert: "return $0;", | ||
378 | kind: Keyword, | ||
379 | }, | ||
380 | CompletionItem { | ||
381 | label: "while", | ||
382 | source_range: 56..56, | ||
383 | delete: 56..56, | ||
384 | insert: "while $0 {}", | ||
385 | kind: Keyword, | ||
386 | }, | ||
387 | ] | ||
388 | "### | 279 | "### |
389 | ); | 280 | ); |
390 | assert_debug_snapshot!( | 281 | } |
391 | do_keyword_completion( | 282 | |
283 | #[test] | ||
284 | fn test_keywords_after_if() { | ||
285 | assert_snapshot!( | ||
286 | get_keyword_completions( | ||
392 | r" | 287 | r" |
393 | fn quux() { | 288 | fn quux() { |
394 | <|> | 289 | if true { |
395 | 92 | 290 | () |
291 | } <|> | ||
396 | } | 292 | } |
397 | ", | 293 | ", |
398 | ), | 294 | ), |
399 | @r###" | 295 | @r###" |
400 | [ | 296 | kw const |
401 | CompletionItem { | 297 | kw else |
402 | label: "if", | 298 | kw else if |
403 | source_range: 49..49, | 299 | kw extern |
404 | delete: 49..49, | 300 | kw fn |
405 | insert: "if $0 {}", | 301 | kw if |
406 | kind: Keyword, | 302 | kw if let |
407 | }, | 303 | kw impl |
408 | CompletionItem { | 304 | kw let |
409 | label: "loop", | 305 | kw loop |
410 | source_range: 49..49, | 306 | kw match |
411 | delete: 49..49, | 307 | kw mod |
412 | insert: "loop {$0}", | 308 | kw return |
413 | kind: Keyword, | 309 | kw static |
414 | }, | 310 | kw trait |
415 | CompletionItem { | 311 | kw type |
416 | label: "match", | 312 | kw unsafe |
417 | source_range: 49..49, | 313 | kw use |
418 | delete: 49..49, | 314 | kw while |
419 | insert: "match $0 {}", | ||
420 | kind: Keyword, | ||
421 | }, | ||
422 | CompletionItem { | ||
423 | label: "return", | ||
424 | source_range: 49..49, | ||
425 | delete: 49..49, | ||
426 | insert: "return;", | ||
427 | kind: Keyword, | ||
428 | }, | ||
429 | CompletionItem { | ||
430 | label: "while", | ||
431 | source_range: 49..49, | ||
432 | delete: 49..49, | ||
433 | insert: "while $0 {}", | ||
434 | kind: Keyword, | ||
435 | }, | ||
436 | ] | ||
437 | "### | 315 | "### |
438 | ); | 316 | ); |
439 | } | 317 | } |
440 | 318 | ||
441 | #[test] | 319 | #[test] |
442 | fn dont_add_semi_after_return_if_not_a_statement() { | 320 | fn test_keywords_in_match_arm() { |
443 | assert_debug_snapshot!( | 321 | assert_snapshot!( |
444 | do_keyword_completion( | 322 | get_keyword_completions( |
445 | r" | 323 | r" |
446 | fn quux() -> i32 { | 324 | fn quux() -> i32 { |
447 | match () { | 325 | match () { |
@@ -451,336 +329,130 @@ mod tests { | |||
451 | ", | 329 | ", |
452 | ), | 330 | ), |
453 | @r###" | 331 | @r###" |
454 | [ | 332 | kw if |
455 | CompletionItem { | 333 | kw if let |
456 | label: "if", | 334 | kw loop |
457 | source_range: 97..97, | 335 | kw match |
458 | delete: 97..97, | 336 | kw return |
459 | insert: "if $0 {}", | 337 | kw unsafe |
460 | kind: Keyword, | ||
461 | }, | ||
462 | CompletionItem { | ||
463 | label: "loop", | ||
464 | source_range: 97..97, | ||
465 | delete: 97..97, | ||
466 | insert: "loop {$0}", | ||
467 | kind: Keyword, | ||
468 | }, | ||
469 | CompletionItem { | ||
470 | label: "match", | ||
471 | source_range: 97..97, | ||
472 | delete: 97..97, | ||
473 | insert: "match $0 {}", | ||
474 | kind: Keyword, | ||
475 | }, | ||
476 | CompletionItem { | ||
477 | label: "return", | ||
478 | source_range: 97..97, | ||
479 | delete: 97..97, | ||
480 | insert: "return $0", | ||
481 | kind: Keyword, | ||
482 | }, | ||
483 | CompletionItem { | ||
484 | label: "while", | ||
485 | source_range: 97..97, | ||
486 | delete: 97..97, | ||
487 | insert: "while $0 {}", | ||
488 | kind: Keyword, | ||
489 | }, | ||
490 | ] | ||
491 | "### | 338 | "### |
492 | ); | 339 | ); |
493 | } | 340 | } |
494 | 341 | ||
495 | #[test] | 342 | #[test] |
496 | fn last_return_in_block_has_semi() { | 343 | fn test_keywords_in_trait_def() { |
497 | assert_debug_snapshot!( | 344 | assert_snapshot!( |
498 | do_keyword_completion( | 345 | get_keyword_completions(r"trait My { <|> }"), |
499 | r" | ||
500 | fn quux() -> i32 { | ||
501 | if condition { | ||
502 | <|> | ||
503 | } | ||
504 | } | ||
505 | ", | ||
506 | ), | ||
507 | @r###" | 346 | @r###" |
508 | [ | 347 | kw const |
509 | CompletionItem { | 348 | kw fn |
510 | label: "if", | 349 | kw type |
511 | source_range: 95..95, | 350 | kw unsafe |
512 | delete: 95..95, | ||
513 | insert: "if $0 {}", | ||
514 | kind: Keyword, | ||
515 | }, | ||
516 | CompletionItem { | ||
517 | label: "loop", | ||
518 | source_range: 95..95, | ||
519 | delete: 95..95, | ||
520 | insert: "loop {$0}", | ||
521 | kind: Keyword, | ||
522 | }, | ||
523 | CompletionItem { | ||
524 | label: "match", | ||
525 | source_range: 95..95, | ||
526 | delete: 95..95, | ||
527 | insert: "match $0 {}", | ||
528 | kind: Keyword, | ||
529 | }, | ||
530 | CompletionItem { | ||
531 | label: "return", | ||
532 | source_range: 95..95, | ||
533 | delete: 95..95, | ||
534 | insert: "return $0;", | ||
535 | kind: Keyword, | ||
536 | }, | ||
537 | CompletionItem { | ||
538 | label: "while", | ||
539 | source_range: 95..95, | ||
540 | delete: 95..95, | ||
541 | insert: "while $0 {}", | ||
542 | kind: Keyword, | ||
543 | }, | ||
544 | ] | ||
545 | "### | 351 | "### |
546 | ); | 352 | ); |
547 | assert_debug_snapshot!( | 353 | } |
548 | do_keyword_completion( | 354 | |
549 | r" | 355 | #[test] |
550 | fn quux() -> i32 { | 356 | fn test_keywords_in_impl_def() { |
551 | if condition { | 357 | assert_snapshot!( |
552 | <|> | 358 | get_keyword_completions(r"impl My { <|> }"), |
553 | } | ||
554 | let x = 92; | ||
555 | x | ||
556 | } | ||
557 | ", | ||
558 | ), | ||
559 | @r###" | 359 | @r###" |
560 | [ | 360 | kw const |
561 | CompletionItem { | 361 | kw fn |
562 | label: "if", | 362 | kw pub |
563 | source_range: 95..95, | 363 | kw type |
564 | delete: 95..95, | 364 | kw unsafe |
565 | insert: "if $0 {}", | ||
566 | kind: Keyword, | ||
567 | }, | ||
568 | CompletionItem { | ||
569 | label: "loop", | ||
570 | source_range: 95..95, | ||
571 | delete: 95..95, | ||
572 | insert: "loop {$0}", | ||
573 | kind: Keyword, | ||
574 | }, | ||
575 | CompletionItem { | ||
576 | label: "match", | ||
577 | source_range: 95..95, | ||
578 | delete: 95..95, | ||
579 | insert: "match $0 {}", | ||
580 | kind: Keyword, | ||
581 | }, | ||
582 | CompletionItem { | ||
583 | label: "return", | ||
584 | source_range: 95..95, | ||
585 | delete: 95..95, | ||
586 | insert: "return $0;", | ||
587 | kind: Keyword, | ||
588 | }, | ||
589 | CompletionItem { | ||
590 | label: "while", | ||
591 | source_range: 95..95, | ||
592 | delete: 95..95, | ||
593 | insert: "while $0 {}", | ||
594 | kind: Keyword, | ||
595 | }, | ||
596 | ] | ||
597 | "### | 365 | "### |
598 | ); | 366 | ); |
599 | } | 367 | } |
600 | 368 | ||
601 | #[test] | 369 | #[test] |
602 | fn completes_break_and_continue_in_loops() { | 370 | fn test_keywords_in_loop() { |
603 | assert_debug_snapshot!( | 371 | assert_snapshot!( |
604 | do_keyword_completion( | 372 | get_keyword_completions(r"fn my() { loop { <|> } }"), |
605 | r" | ||
606 | fn quux() -> i32 { | ||
607 | loop { <|> } | ||
608 | } | ||
609 | ", | ||
610 | ), | ||
611 | @r###" | 373 | @r###" |
612 | [ | 374 | kw break |
613 | CompletionItem { | 375 | kw const |
614 | label: "break", | 376 | kw continue |
615 | source_range: 63..63, | 377 | kw extern |
616 | delete: 63..63, | 378 | kw fn |
617 | insert: "break;", | 379 | kw if |
618 | kind: Keyword, | 380 | kw if let |
619 | }, | 381 | kw impl |
620 | CompletionItem { | 382 | kw let |
621 | label: "continue", | 383 | kw loop |
622 | source_range: 63..63, | 384 | kw match |
623 | delete: 63..63, | 385 | kw mod |
624 | insert: "continue;", | 386 | kw return |
625 | kind: Keyword, | 387 | kw static |
626 | }, | 388 | kw trait |
627 | CompletionItem { | 389 | kw type |
628 | label: "if", | 390 | kw unsafe |
629 | source_range: 63..63, | 391 | kw use |
630 | delete: 63..63, | 392 | kw while |
631 | insert: "if $0 {}", | ||
632 | kind: Keyword, | ||
633 | }, | ||
634 | CompletionItem { | ||
635 | label: "loop", | ||
636 | source_range: 63..63, | ||
637 | delete: 63..63, | ||
638 | insert: "loop {$0}", | ||
639 | kind: Keyword, | ||
640 | }, | ||
641 | CompletionItem { | ||
642 | label: "match", | ||
643 | source_range: 63..63, | ||
644 | delete: 63..63, | ||
645 | insert: "match $0 {}", | ||
646 | kind: Keyword, | ||
647 | }, | ||
648 | CompletionItem { | ||
649 | label: "return", | ||
650 | source_range: 63..63, | ||
651 | delete: 63..63, | ||
652 | insert: "return $0;", | ||
653 | kind: Keyword, | ||
654 | }, | ||
655 | CompletionItem { | ||
656 | label: "while", | ||
657 | source_range: 63..63, | ||
658 | delete: 63..63, | ||
659 | insert: "while $0 {}", | ||
660 | kind: Keyword, | ||
661 | }, | ||
662 | ] | ||
663 | "### | 393 | "### |
664 | ); | 394 | ); |
395 | } | ||
665 | 396 | ||
666 | // No completion: lambda isolates control flow | 397 | #[test] |
667 | assert_debug_snapshot!( | 398 | fn test_keywords_after_unsafe_in_item_list() { |
668 | do_keyword_completion( | 399 | assert_snapshot!( |
669 | r" | 400 | get_keyword_completions(r"unsafe <|>"), |
670 | fn quux() -> i32 { | ||
671 | loop { || { <|> } } | ||
672 | } | ||
673 | ", | ||
674 | ), | ||
675 | @r###" | 401 | @r###" |
676 | [ | 402 | kw fn |
677 | CompletionItem { | 403 | kw impl |
678 | label: "if", | 404 | kw trait |
679 | source_range: 68..68, | ||
680 | delete: 68..68, | ||
681 | insert: "if $0 {}", | ||
682 | kind: Keyword, | ||
683 | }, | ||
684 | CompletionItem { | ||
685 | label: "loop", | ||
686 | source_range: 68..68, | ||
687 | delete: 68..68, | ||
688 | insert: "loop {$0}", | ||
689 | kind: Keyword, | ||
690 | }, | ||
691 | CompletionItem { | ||
692 | label: "match", | ||
693 | source_range: 68..68, | ||
694 | delete: 68..68, | ||
695 | insert: "match $0 {}", | ||
696 | kind: Keyword, | ||
697 | }, | ||
698 | CompletionItem { | ||
699 | label: "return", | ||
700 | source_range: 68..68, | ||
701 | delete: 68..68, | ||
702 | insert: "return $0;", | ||
703 | kind: Keyword, | ||
704 | }, | ||
705 | CompletionItem { | ||
706 | label: "while", | ||
707 | source_range: 68..68, | ||
708 | delete: 68..68, | ||
709 | insert: "while $0 {}", | ||
710 | kind: Keyword, | ||
711 | }, | ||
712 | ] | ||
713 | "### | 405 | "### |
714 | ); | 406 | ); |
715 | } | 407 | } |
716 | 408 | ||
717 | #[test] | 409 | #[test] |
718 | fn no_semi_after_break_continue_in_expr() { | 410 | fn test_keywords_after_unsafe_in_block_expr() { |
719 | assert_debug_snapshot!( | 411 | assert_snapshot!( |
720 | do_keyword_completion( | 412 | get_keyword_completions(r"fn my_fn() { unsafe <|> }"), |
721 | r" | ||
722 | fn f() { | ||
723 | loop { | ||
724 | match () { | ||
725 | () => br<|> | ||
726 | } | ||
727 | } | ||
728 | } | ||
729 | ", | ||
730 | ), | ||
731 | @r###" | 413 | @r###" |
732 | [ | 414 | kw fn |
733 | CompletionItem { | 415 | kw impl |
734 | label: "break", | 416 | kw trait |
735 | source_range: 122..124, | ||
736 | delete: 122..124, | ||
737 | insert: "break", | ||
738 | kind: Keyword, | ||
739 | }, | ||
740 | CompletionItem { | ||
741 | label: "continue", | ||
742 | source_range: 122..124, | ||
743 | delete: 122..124, | ||
744 | insert: "continue", | ||
745 | kind: Keyword, | ||
746 | }, | ||
747 | CompletionItem { | ||
748 | label: "if", | ||
749 | source_range: 122..124, | ||
750 | delete: 122..124, | ||
751 | insert: "if $0 {}", | ||
752 | kind: Keyword, | ||
753 | }, | ||
754 | CompletionItem { | ||
755 | label: "loop", | ||
756 | source_range: 122..124, | ||
757 | delete: 122..124, | ||
758 | insert: "loop {$0}", | ||
759 | kind: Keyword, | ||
760 | }, | ||
761 | CompletionItem { | ||
762 | label: "match", | ||
763 | source_range: 122..124, | ||
764 | delete: 122..124, | ||
765 | insert: "match $0 {}", | ||
766 | kind: Keyword, | ||
767 | }, | ||
768 | CompletionItem { | ||
769 | label: "return", | ||
770 | source_range: 122..124, | ||
771 | delete: 122..124, | ||
772 | insert: "return", | ||
773 | kind: Keyword, | ||
774 | }, | ||
775 | CompletionItem { | ||
776 | label: "while", | ||
777 | source_range: 122..124, | ||
778 | delete: 122..124, | ||
779 | insert: "while $0 {}", | ||
780 | kind: Keyword, | ||
781 | }, | ||
782 | ] | ||
783 | "### | 417 | "### |
784 | ) | 418 | ); |
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn test_mut_in_ref_and_in_fn_parameters_list() { | ||
423 | assert_snapshot!( | ||
424 | get_keyword_completions(r"fn my_fn(&<|>) {}"), | ||
425 | @r###" | ||
426 | kw mut | ||
427 | "### | ||
428 | ); | ||
429 | assert_snapshot!( | ||
430 | get_keyword_completions(r"fn my_fn(<|>) {}"), | ||
431 | @r###" | ||
432 | kw mut | ||
433 | "### | ||
434 | ); | ||
435 | assert_snapshot!( | ||
436 | get_keyword_completions(r"fn my_fn() { let &<|> }"), | ||
437 | @r###" | ||
438 | kw mut | ||
439 | "### | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn test_where_keyword() { | ||
445 | assert_snapshot!( | ||
446 | get_keyword_completions(r"trait A <|>"), | ||
447 | @r###" | ||
448 | kw where | ||
449 | "### | ||
450 | ); | ||
451 | assert_snapshot!( | ||
452 | get_keyword_completions(r"impl A <|>"), | ||
453 | @r###" | ||
454 | kw where | ||
455 | "### | ||
456 | ); | ||
785 | } | 457 | } |
786 | } | 458 | } |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 59b58bf98..b878aeb0a 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -91,7 +91,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
91 | &dot_receiver, | 91 | &dot_receiver, |
92 | "if", | 92 | "if", |
93 | "if expr {}", | 93 | "if expr {}", |
94 | &format!("if {} {{$0}}", receiver_text), | 94 | &format!("if {} {{\n $0\n}}", receiver_text), |
95 | ) | 95 | ) |
96 | .add_to(acc); | 96 | .add_to(acc); |
97 | postfix_snippet( | 97 | postfix_snippet( |
@@ -100,7 +100,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
100 | &dot_receiver, | 100 | &dot_receiver, |
101 | "while", | 101 | "while", |
102 | "while expr {}", | 102 | "while expr {}", |
103 | &format!("while {} {{\n$0\n}}", receiver_text), | 103 | &format!("while {} {{\n $0\n}}", receiver_text), |
104 | ) | 104 | ) |
105 | .add_to(acc); | 105 | .add_to(acc); |
106 | } | 106 | } |
@@ -283,7 +283,7 @@ mod tests { | |||
283 | label: "if", | 283 | label: "if", |
284 | source_range: 89..89, | 284 | source_range: 89..89, |
285 | delete: 85..89, | 285 | delete: 85..89, |
286 | insert: "if bar {$0}", | 286 | insert: "if bar {\n $0\n}", |
287 | detail: "if expr {}", | 287 | detail: "if expr {}", |
288 | }, | 288 | }, |
289 | CompletionItem { | 289 | CompletionItem { |
@@ -318,7 +318,7 @@ mod tests { | |||
318 | label: "while", | 318 | label: "while", |
319 | source_range: 89..89, | 319 | source_range: 89..89, |
320 | delete: 85..89, | 320 | delete: 85..89, |
321 | insert: "while bar {\n$0\n}", | 321 | insert: "while bar {\n $0\n}", |
322 | detail: "while expr {}", | 322 | detail: "while expr {}", |
323 | }, | 323 | }, |
324 | ] | 324 | ] |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index c4646b727..560fb19e6 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -5,12 +5,17 @@ use ra_db::SourceDatabase; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
8 | ast, match_ast, AstNode, | 8 | ast, match_ast, AstNode, NodeOrToken, |
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 10 | SyntaxNode, SyntaxToken, TextRange, TextSize, |
11 | }; | 11 | }; |
12 | use ra_text_edit::Indel; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use super::patterns::{ | ||
15 | has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, | ||
16 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, | ||
17 | has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, | ||
18 | }; | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 19 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
15 | use test_utils::mark; | 20 | use test_utils::mark; |
16 | 21 | ||
@@ -60,6 +65,18 @@ pub(crate) struct CompletionContext<'a> { | |||
60 | pub(super) is_path_type: bool, | 65 | pub(super) is_path_type: bool, |
61 | pub(super) has_type_args: bool, | 66 | pub(super) has_type_args: bool, |
62 | pub(super) attribute_under_caret: Option<ast::Attr>, | 67 | pub(super) attribute_under_caret: Option<ast::Attr>, |
68 | pub(super) unsafe_is_prev: bool, | ||
69 | pub(super) if_is_prev: bool, | ||
70 | pub(super) block_expr_parent: bool, | ||
71 | pub(super) bind_pat_parent: bool, | ||
72 | pub(super) ref_pat_parent: bool, | ||
73 | pub(super) in_loop_body: bool, | ||
74 | pub(super) has_trait_parent: bool, | ||
75 | pub(super) has_impl_parent: bool, | ||
76 | pub(super) trait_as_prev_sibling: bool, | ||
77 | pub(super) impl_as_prev_sibling: bool, | ||
78 | pub(super) is_match_arm: bool, | ||
79 | pub(super) has_item_list_or_source_file_parent: bool, | ||
63 | } | 80 | } |
64 | 81 | ||
65 | impl<'a> CompletionContext<'a> { | 82 | impl<'a> CompletionContext<'a> { |
@@ -118,6 +135,18 @@ impl<'a> CompletionContext<'a> { | |||
118 | has_type_args: false, | 135 | has_type_args: false, |
119 | dot_receiver_is_ambiguous_float_literal: false, | 136 | dot_receiver_is_ambiguous_float_literal: false, |
120 | attribute_under_caret: None, | 137 | attribute_under_caret: None, |
138 | unsafe_is_prev: false, | ||
139 | in_loop_body: false, | ||
140 | ref_pat_parent: false, | ||
141 | bind_pat_parent: false, | ||
142 | block_expr_parent: false, | ||
143 | has_trait_parent: false, | ||
144 | has_impl_parent: false, | ||
145 | trait_as_prev_sibling: false, | ||
146 | impl_as_prev_sibling: false, | ||
147 | if_is_prev: false, | ||
148 | is_match_arm: false, | ||
149 | has_item_list_or_source_file_parent: false, | ||
121 | }; | 150 | }; |
122 | 151 | ||
123 | let mut original_file = original_file.syntax().clone(); | 152 | let mut original_file = original_file.syntax().clone(); |
@@ -159,7 +188,7 @@ impl<'a> CompletionContext<'a> { | |||
159 | break; | 188 | break; |
160 | } | 189 | } |
161 | } | 190 | } |
162 | 191 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | |
163 | ctx.fill(&original_file, hypothetical_file, offset); | 192 | ctx.fill(&original_file, hypothetical_file, offset); |
164 | Some(ctx) | 193 | Some(ctx) |
165 | } | 194 | } |
@@ -188,6 +217,24 @@ impl<'a> CompletionContext<'a> { | |||
188 | self.sema.scope_at_offset(&self.token.parent(), self.offset) | 217 | self.sema.scope_at_offset(&self.token.parent(), self.offset) |
189 | } | 218 | } |
190 | 219 | ||
220 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | ||
221 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
222 | let syntax_element = NodeOrToken::Token(fake_ident_token.clone()); | ||
223 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | ||
224 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | ||
225 | self.if_is_prev = if_is_prev(syntax_element.clone()); | ||
226 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | ||
227 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | ||
228 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
229 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | ||
230 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | ||
231 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | ||
232 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | ||
233 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
234 | self.has_item_list_or_source_file_parent = | ||
235 | has_item_list_or_source_file_parent(syntax_element.clone()); | ||
236 | } | ||
237 | |||
191 | fn fill( | 238 | fn fill( |
192 | &mut self, | 239 | &mut self, |
193 | original_file: &SyntaxNode, | 240 | original_file: &SyntaxNode, |
@@ -334,6 +381,7 @@ impl<'a> CompletionContext<'a> { | |||
334 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | 381 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); |
335 | self.has_type_args = segment.type_arg_list().is_some(); | 382 | self.has_type_args = segment.type_arg_list().is_some(); |
336 | 383 | ||
384 | #[allow(deprecated)] | ||
337 | if let Some(path) = hir::Path::from_ast(path.clone()) { | 385 | if let Some(path) = hir::Path::from_ast(path.clone()) { |
338 | if let Some(path_prefix) = path.qualifier() { | 386 | if let Some(path_prefix) = path.qualifier() { |
339 | self.path_prefix = Some(path_prefix); | 387 | self.path_prefix = Some(path_prefix); |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index cfb7c1e38..98348b349 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -125,6 +125,32 @@ pub enum CompletionItemKind { | |||
125 | Attribute, | 125 | Attribute, |
126 | } | 126 | } |
127 | 127 | ||
128 | impl CompletionItemKind { | ||
129 | #[cfg(test)] | ||
130 | pub(crate) fn tag(&self) -> &'static str { | ||
131 | match self { | ||
132 | CompletionItemKind::Snippet => "sn", | ||
133 | CompletionItemKind::Keyword => "kw", | ||
134 | CompletionItemKind::Module => "md", | ||
135 | CompletionItemKind::Function => "fn", | ||
136 | CompletionItemKind::BuiltinType => "bt", | ||
137 | CompletionItemKind::Struct => "st", | ||
138 | CompletionItemKind::Enum => "en", | ||
139 | CompletionItemKind::EnumVariant => "ev", | ||
140 | CompletionItemKind::Binding => "bn", | ||
141 | CompletionItemKind::Field => "fd", | ||
142 | CompletionItemKind::Static => "sc", | ||
143 | CompletionItemKind::Const => "ct", | ||
144 | CompletionItemKind::Trait => "tt", | ||
145 | CompletionItemKind::TypeAlias => "ta", | ||
146 | CompletionItemKind::Method => "me", | ||
147 | CompletionItemKind::TypeParam => "tp", | ||
148 | CompletionItemKind::Macro => "ma", | ||
149 | CompletionItemKind::Attribute => "at", | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
128 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | 154 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] |
129 | pub(crate) enum CompletionKind { | 155 | pub(crate) enum CompletionKind { |
130 | /// Parser-based keyword completion. | 156 | /// Parser-based keyword completion. |
diff --git a/crates/ra_ide/src/completion/patterns.rs b/crates/ra_ide/src/completion/patterns.rs new file mode 100644 index 000000000..b2fe13280 --- /dev/null +++ b/crates/ra_ide/src/completion/patterns.rs | |||
@@ -0,0 +1,194 @@ | |||
1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::non_trivia_sibling, | ||
5 | ast::{self, LoopBodyOwner}, | ||
6 | match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, | ||
7 | SyntaxKind::*, | ||
8 | SyntaxNode, SyntaxToken, | ||
9 | }; | ||
10 | |||
11 | #[cfg(test)] | ||
12 | use crate::completion::test_utils::check_pattern_is_applicable; | ||
13 | |||
14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | ||
15 | not_same_range_ancestor(element) | ||
16 | .filter(|it| it.kind() == ITEM_LIST) | ||
17 | .and_then(|it| it.parent()) | ||
18 | .filter(|it| it.kind() == TRAIT_DEF) | ||
19 | .is_some() | ||
20 | } | ||
21 | #[test] | ||
22 | fn test_has_trait_parent() { | ||
23 | check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent); | ||
24 | } | ||
25 | |||
26 | pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | ||
27 | not_same_range_ancestor(element) | ||
28 | .filter(|it| it.kind() == ITEM_LIST) | ||
29 | .and_then(|it| it.parent()) | ||
30 | .filter(|it| it.kind() == IMPL_DEF) | ||
31 | .is_some() | ||
32 | } | ||
33 | #[test] | ||
34 | fn test_has_impl_parent() { | ||
35 | check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); | ||
36 | } | ||
37 | |||
38 | pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { | ||
39 | not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() | ||
40 | } | ||
41 | #[test] | ||
42 | fn test_has_block_expr_parent() { | ||
43 | check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent); | ||
44 | } | ||
45 | |||
46 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | ||
47 | element.ancestors().find(|it| it.kind() == BIND_PAT).is_some() | ||
48 | } | ||
49 | #[test] | ||
50 | fn test_has_bind_pat_parent() { | ||
51 | check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent); | ||
52 | check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent); | ||
53 | } | ||
54 | |||
55 | pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { | ||
56 | not_same_range_ancestor(element) | ||
57 | .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) | ||
58 | .is_some() | ||
59 | } | ||
60 | #[test] | ||
61 | fn test_has_ref_parent() { | ||
62 | check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent); | ||
63 | check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent); | ||
64 | } | ||
65 | |||
66 | pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { | ||
67 | let ancestor = not_same_range_ancestor(element); | ||
68 | if !ancestor.is_some() { | ||
69 | return true; | ||
70 | } | ||
71 | ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some() | ||
72 | } | ||
73 | #[test] | ||
74 | fn test_has_item_list_or_source_file_parent() { | ||
75 | check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent); | ||
76 | check_pattern_is_applicable(r"impl { f<|> }", has_item_list_or_source_file_parent); | ||
77 | } | ||
78 | |||
79 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | ||
80 | not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() | ||
81 | && previous_sibling_or_ancestor_sibling(element) | ||
82 | .and_then(|it| it.into_token()) | ||
83 | .filter(|it| it.kind() == FAT_ARROW) | ||
84 | .is_some() | ||
85 | } | ||
86 | #[test] | ||
87 | fn test_is_match_arm() { | ||
88 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm); | ||
89 | } | ||
90 | |||
91 | pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { | ||
92 | element | ||
93 | .into_token() | ||
94 | .and_then(|it| previous_non_trivia_token(it)) | ||
95 | .filter(|it| it.kind() == UNSAFE_KW) | ||
96 | .is_some() | ||
97 | } | ||
98 | #[test] | ||
99 | fn test_unsafe_is_prev() { | ||
100 | check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev); | ||
101 | } | ||
102 | |||
103 | pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | ||
104 | element | ||
105 | .into_token() | ||
106 | .and_then(|it| previous_non_trivia_token(it)) | ||
107 | .filter(|it| it.kind() == IF_KW) | ||
108 | .is_some() | ||
109 | } | ||
110 | #[test] | ||
111 | fn test_if_is_prev() { | ||
112 | check_pattern_is_applicable(r"if l<|>", if_is_prev); | ||
113 | } | ||
114 | |||
115 | pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { | ||
116 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).is_some() | ||
117 | } | ||
118 | #[test] | ||
119 | fn test_has_trait_as_prev_sibling() { | ||
120 | check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling); | ||
121 | } | ||
122 | |||
123 | pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { | ||
124 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL_DEF).is_some() | ||
125 | } | ||
126 | #[test] | ||
127 | fn test_has_impl_as_prev_sibling() { | ||
128 | check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling); | ||
129 | } | ||
130 | |||
131 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | ||
132 | let leaf = match element { | ||
133 | NodeOrToken::Node(node) => node, | ||
134 | NodeOrToken::Token(token) => token.parent(), | ||
135 | }; | ||
136 | for node in leaf.ancestors() { | ||
137 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | ||
138 | break; | ||
139 | } | ||
140 | let loop_body = match_ast! { | ||
141 | match node { | ||
142 | ast::ForExpr(it) => it.loop_body(), | ||
143 | ast::WhileExpr(it) => it.loop_body(), | ||
144 | ast::LoopExpr(it) => it.loop_body(), | ||
145 | _ => None, | ||
146 | } | ||
147 | }; | ||
148 | if let Some(body) = loop_body { | ||
149 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
150 | return true; | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | false | ||
155 | } | ||
156 | |||
157 | fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | ||
158 | element | ||
159 | .ancestors() | ||
160 | .take_while(|it| it.text_range() == element.text_range()) | ||
161 | .last() | ||
162 | .and_then(|it| it.parent()) | ||
163 | } | ||
164 | |||
165 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | ||
166 | let mut token = token.prev_token(); | ||
167 | while let Some(inner) = token.clone() { | ||
168 | if !inner.kind().is_trivia() { | ||
169 | return Some(inner); | ||
170 | } else { | ||
171 | token = inner.prev_token(); | ||
172 | } | ||
173 | } | ||
174 | None | ||
175 | } | ||
176 | |||
177 | fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { | ||
178 | let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); | ||
179 | if let Some(sibling) = token_sibling { | ||
180 | Some(sibling) | ||
181 | } else { | ||
182 | // if not trying to find first ancestor which has such a sibling | ||
183 | let node = match element { | ||
184 | NodeOrToken::Node(node) => node, | ||
185 | NodeOrToken::Token(token) => token.parent(), | ||
186 | }; | ||
187 | let range = node.text_range(); | ||
188 | let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; | ||
189 | let prev_sibling_node = top_node.ancestors().find(|it| { | ||
190 | non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() | ||
191 | })?; | ||
192 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | ||
193 | } | ||
194 | } | ||
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index bf22452a2..1e16a43ca 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -5,25 +5,63 @@ use crate::{ | |||
5 | mock_analysis::{analysis_and_position, single_file_with_position}, | 5 | mock_analysis::{analysis_and_position, single_file_with_position}, |
6 | CompletionItem, | 6 | CompletionItem, |
7 | }; | 7 | }; |
8 | use hir::Semantics; | ||
9 | use ra_syntax::{AstNode, NodeOrToken, SyntaxElement}; | ||
8 | 10 | ||
9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | 11 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { |
10 | do_completion_with_options(code, kind, &CompletionConfig::default()) | 12 | do_completion_with_options(code, kind, &CompletionConfig::default()) |
11 | } | 13 | } |
12 | 14 | ||
15 | pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { | ||
16 | completion_list_with_options(code, kind, &CompletionConfig::default()) | ||
17 | } | ||
18 | |||
13 | pub(crate) fn do_completion_with_options( | 19 | pub(crate) fn do_completion_with_options( |
14 | code: &str, | 20 | code: &str, |
15 | kind: CompletionKind, | 21 | kind: CompletionKind, |
16 | options: &CompletionConfig, | 22 | options: &CompletionConfig, |
17 | ) -> Vec<CompletionItem> { | 23 | ) -> Vec<CompletionItem> { |
24 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
25 | .into_iter() | ||
26 | .filter(|c| c.completion_kind == kind) | ||
27 | .collect(); | ||
28 | kind_completions.sort_by(|l, r| l.label().cmp(r.label())); | ||
29 | kind_completions | ||
30 | } | ||
31 | |||
32 | fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec<CompletionItem> { | ||
18 | let (analysis, position) = if code.contains("//-") { | 33 | let (analysis, position) = if code.contains("//-") { |
19 | analysis_and_position(code) | 34 | analysis_and_position(code) |
20 | } else { | 35 | } else { |
21 | single_file_with_position(code) | 36 | single_file_with_position(code) |
22 | }; | 37 | }; |
23 | let completions = analysis.completions(options, position).unwrap().unwrap(); | 38 | analysis.completions(options, position).unwrap().unwrap().into() |
24 | let completion_items: Vec<CompletionItem> = completions.into(); | 39 | } |
25 | let mut kind_completions: Vec<CompletionItem> = | 40 | |
26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | 41 | pub(crate) fn completion_list_with_options( |
42 | code: &str, | ||
43 | kind: CompletionKind, | ||
44 | options: &CompletionConfig, | ||
45 | ) -> String { | ||
46 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
47 | .into_iter() | ||
48 | .filter(|c| c.completion_kind == kind) | ||
49 | .collect(); | ||
27 | kind_completions.sort_by_key(|c| c.label().to_owned()); | 50 | kind_completions.sort_by_key(|c| c.label().to_owned()); |
28 | kind_completions | 51 | kind_completions |
52 | .into_iter() | ||
53 | .map(|it| format!("{} {}\n", it.kind().unwrap().tag(), it.label())) | ||
54 | .collect() | ||
55 | } | ||
56 | |||
57 | pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { | ||
58 | let (analysis, pos) = single_file_with_position(code); | ||
59 | analysis | ||
60 | .with_db(|db| { | ||
61 | let sema = Semantics::new(db); | ||
62 | let original_file = sema.parse(pos.file_id); | ||
63 | let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); | ||
64 | assert!(check(NodeOrToken::Token(token))); | ||
65 | }) | ||
66 | .unwrap(); | ||
29 | } | 67 | } |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index f44feaf69..fd9abb55b 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -11,7 +11,7 @@ use hir::{ | |||
11 | Semantics, | 11 | Semantics, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; | 14 | use ra_db::SourceDatabase; |
15 | use ra_ide_db::RootDatabase; | 15 | use ra_ide_db::RootDatabase; |
16 | use ra_prof::profile; | 16 | use ra_prof::profile; |
17 | use ra_syntax::{ | 17 | use ra_syntax::{ |
@@ -57,14 +57,10 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
57 | }) | 57 | }) |
58 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 58 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
59 | let original_file = d.source().file_id.original_file(db); | 59 | let original_file = d.source().file_id.original_file(db); |
60 | let source_root = db.file_source_root(original_file); | 60 | let fix = Fix::new( |
61 | let path = db | 61 | "Create module", |
62 | .file_relative_path(original_file) | 62 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(), |
63 | .parent() | 63 | ); |
64 | .unwrap_or_else(|| RelativePath::new("")) | ||
65 | .join(&d.candidate); | ||
66 | let fix = | ||
67 | Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into()); | ||
68 | res.borrow_mut().push(Diagnostic { | 64 | res.borrow_mut().push(Diagnostic { |
69 | range: sema.diagnostics_range(d).range, | 65 | range: sema.diagnostics_range(d).range, |
70 | message: d.message(), | 66 | message: d.message(), |
@@ -187,7 +183,8 @@ fn check_struct_shorthand_initialization( | |||
187 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | 183 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { |
188 | let field_name = name_ref.syntax().text().to_string(); | 184 | let field_name = name_ref.syntax().text().to_string(); |
189 | let field_expr = expr.syntax().text().to_string(); | 185 | let field_expr = expr.syntax().text().to_string(); |
190 | if field_name == field_expr { | 186 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); |
187 | if field_name == field_expr && !field_name_is_tup_index { | ||
191 | let mut edit_builder = TextEditBuilder::default(); | 188 | let mut edit_builder = TextEditBuilder::default(); |
192 | edit_builder.delete(record_field.syntax().text_range()); | 189 | edit_builder.delete(record_field.syntax().text_range()); |
193 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | 190 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); |
@@ -611,10 +608,10 @@ mod tests { | |||
611 | source_file_edits: [], | 608 | source_file_edits: [], |
612 | file_system_edits: [ | 609 | file_system_edits: [ |
613 | CreateFile { | 610 | CreateFile { |
614 | source_root: SourceRootId( | 611 | anchor: FileId( |
615 | 0, | 612 | 1, |
616 | ), | 613 | ), |
617 | path: "foo.rs", | 614 | dst: "foo.rs", |
618 | }, | 615 | }, |
619 | ], | 616 | ], |
620 | is_snippet: false, | 617 | is_snippet: false, |
@@ -719,6 +716,18 @@ mod tests { | |||
719 | "#, | 716 | "#, |
720 | check_struct_shorthand_initialization, | 717 | check_struct_shorthand_initialization, |
721 | ); | 718 | ); |
719 | check_not_applicable( | ||
720 | r#" | ||
721 | struct A(usize); | ||
722 | |||
723 | fn main() { | ||
724 | A { | ||
725 | 0: 0 | ||
726 | } | ||
727 | } | ||
728 | "#, | ||
729 | check_struct_shorthand_initialization, | ||
730 | ); | ||
722 | 731 | ||
723 | check_apply( | 732 | check_apply( |
724 | r#" | 733 | r#" |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 28f686767..375da1f45 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -440,12 +440,14 @@ impl Analysis { | |||
440 | 440 | ||
441 | /// Computes syntax highlighting for the given file | 441 | /// Computes syntax highlighting for the given file |
442 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { | 442 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { |
443 | self.with_db(|db| syntax_highlighting::highlight(db, file_id, None)) | 443 | self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) |
444 | } | 444 | } |
445 | 445 | ||
446 | /// Computes syntax highlighting for the given file range. | 446 | /// Computes syntax highlighting for the given file range. |
447 | pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { | 447 | pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { |
448 | self.with_db(|db| syntax_highlighting::highlight(db, frange.file_id, Some(frange.range))) | 448 | self.with_db(|db| { |
449 | syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false) | ||
450 | }) | ||
449 | } | 451 | } |
450 | 452 | ||
451 | /// Computes syntax highlighting for the given file. | 453 | /// Computes syntax highlighting for the given file. |
diff --git a/crates/ra_ide/src/prime_caches.rs b/crates/ra_ide/src/prime_caches.rs index 90bf7d25f..c5ab5a1d8 100644 --- a/crates/ra_ide/src/prime_caches.rs +++ b/crates/ra_ide/src/prime_caches.rs | |||
@@ -7,6 +7,6 @@ use crate::{FileId, RootDatabase}; | |||
7 | 7 | ||
8 | pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { | 8 | pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { |
9 | for file in files { | 9 | for file in files { |
10 | let _ = crate::syntax_highlighting::highlight(db, file, None); | 10 | let _ = crate::syntax_highlighting::highlight(db, file, None, false); |
11 | } | 11 | } |
12 | } | 12 | } |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 546224b50..99c2581b7 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 3 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::{RelativePathBuf, SourceDatabaseExt}; |
5 | use ra_ide_db::{ | 5 | use ra_ide_db::{ |
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, |
7 | RootDatabase, | 7 | RootDatabase, |
@@ -111,23 +111,14 @@ fn rename_mod( | |||
111 | ModuleSource::SourceFile(..) => { | 111 | ModuleSource::SourceFile(..) => { |
112 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); | 112 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); |
113 | // mod is defined in path/to/dir/mod.rs | 113 | // mod is defined in path/to/dir/mod.rs |
114 | let dst_path = if mod_path.file_stem() == Some("mod") { | 114 | let dst = if mod_path.file_stem() == Some("mod") { |
115 | mod_path | 115 | format!("../{}/mod.rs", new_name) |
116 | .parent() | ||
117 | .and_then(|p| p.parent()) | ||
118 | .or_else(|| Some(RelativePath::new(""))) | ||
119 | .map(|p| p.join(new_name).join("mod.rs")) | ||
120 | } else { | 116 | } else { |
121 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | 117 | format!("{}.rs", new_name) |
122 | }; | 118 | }; |
123 | if let Some(path) = dst_path { | 119 | let move_file = |
124 | let move_file = FileSystemEdit::MoveFile { | 120 | FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; |
125 | src: file_id, | 121 | file_system_edits.push(move_file); |
126 | dst_source_root: db.file_source_root(position.file_id), | ||
127 | dst_path: path, | ||
128 | }; | ||
129 | file_system_edits.push(move_file); | ||
130 | } | ||
131 | } | 122 | } |
132 | ModuleSource::Module(..) => {} | 123 | ModuleSource::Module(..) => {} |
133 | } | 124 | } |
@@ -644,16 +635,16 @@ mod tests { | |||
644 | #[test] | 635 | #[test] |
645 | fn test_rename_mod() { | 636 | fn test_rename_mod() { |
646 | let (analysis, position) = analysis_and_position( | 637 | let (analysis, position) = analysis_and_position( |
647 | " | 638 | r#" |
648 | //- /lib.rs | 639 | //- /lib.rs |
649 | mod bar; | 640 | mod bar; |
650 | 641 | ||
651 | //- /bar.rs | 642 | //- /bar.rs |
652 | mod foo<|>; | 643 | mod foo<|>; |
653 | 644 | ||
654 | //- /bar/foo.rs | 645 | //- /bar/foo.rs |
655 | // emtpy | 646 | // emtpy |
656 | ", | 647 | "#, |
657 | ); | 648 | ); |
658 | let new_name = "foo2"; | 649 | let new_name = "foo2"; |
659 | let source_change = analysis.rename(position, new_name).unwrap(); | 650 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -683,10 +674,10 @@ mod tests { | |||
683 | src: FileId( | 674 | src: FileId( |
684 | 3, | 675 | 3, |
685 | ), | 676 | ), |
686 | dst_source_root: SourceRootId( | 677 | anchor: FileId( |
687 | 0, | 678 | 2, |
688 | ), | 679 | ), |
689 | dst_path: "bar/foo2.rs", | 680 | dst: "foo2.rs", |
690 | }, | 681 | }, |
691 | ], | 682 | ], |
692 | is_snippet: false, | 683 | is_snippet: false, |
@@ -699,18 +690,18 @@ mod tests { | |||
699 | #[test] | 690 | #[test] |
700 | fn test_rename_mod_in_use_tree() { | 691 | fn test_rename_mod_in_use_tree() { |
701 | let (analysis, position) = analysis_and_position( | 692 | let (analysis, position) = analysis_and_position( |
702 | " | 693 | r#" |
703 | //- /main.rs | 694 | //- /main.rs |
704 | pub mod foo; | 695 | pub mod foo; |
705 | pub mod bar; | 696 | pub mod bar; |
706 | fn main() {} | 697 | fn main() {} |
707 | 698 | ||
708 | //- /foo.rs | 699 | //- /foo.rs |
709 | pub struct FooContent; | 700 | pub struct FooContent; |
710 | 701 | ||
711 | //- /bar.rs | 702 | //- /bar.rs |
712 | use crate::foo<|>::FooContent; | 703 | use crate::foo<|>::FooContent; |
713 | ", | 704 | "#, |
714 | ); | 705 | ); |
715 | let new_name = "qux"; | 706 | let new_name = "qux"; |
716 | let source_change = analysis.rename(position, new_name).unwrap(); | 707 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -753,10 +744,10 @@ mod tests { | |||
753 | src: FileId( | 744 | src: FileId( |
754 | 2, | 745 | 2, |
755 | ), | 746 | ), |
756 | dst_source_root: SourceRootId( | 747 | anchor: FileId( |
757 | 0, | 748 | 3, |
758 | ), | 749 | ), |
759 | dst_path: "qux.rs", | 750 | dst: "qux.rs", |
760 | }, | 751 | }, |
761 | ], | 752 | ], |
762 | is_snippet: false, | 753 | is_snippet: false, |
@@ -769,12 +760,12 @@ mod tests { | |||
769 | #[test] | 760 | #[test] |
770 | fn test_rename_mod_in_dir() { | 761 | fn test_rename_mod_in_dir() { |
771 | let (analysis, position) = analysis_and_position( | 762 | let (analysis, position) = analysis_and_position( |
772 | " | 763 | r#" |
773 | //- /lib.rs | 764 | //- /lib.rs |
774 | mod fo<|>o; | 765 | mod fo<|>o; |
775 | //- /foo/mod.rs | 766 | //- /foo/mod.rs |
776 | // emtpy | 767 | // emtpy |
777 | ", | 768 | "#, |
778 | ); | 769 | ); |
779 | let new_name = "foo2"; | 770 | let new_name = "foo2"; |
780 | let source_change = analysis.rename(position, new_name).unwrap(); | 771 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -804,10 +795,10 @@ mod tests { | |||
804 | src: FileId( | 795 | src: FileId( |
805 | 2, | 796 | 2, |
806 | ), | 797 | ), |
807 | dst_source_root: SourceRootId( | 798 | anchor: FileId( |
808 | 0, | 799 | 1, |
809 | ), | 800 | ), |
810 | dst_path: "foo2/mod.rs", | 801 | dst: "../foo2/mod.rs", |
811 | }, | 802 | }, |
812 | ], | 803 | ], |
813 | is_snippet: false, | 804 | is_snippet: false, |
@@ -844,19 +835,19 @@ mod tests { | |||
844 | #[test] | 835 | #[test] |
845 | fn test_rename_mod_filename_and_path() { | 836 | fn test_rename_mod_filename_and_path() { |
846 | let (analysis, position) = analysis_and_position( | 837 | let (analysis, position) = analysis_and_position( |
847 | " | 838 | r#" |
848 | //- /lib.rs | 839 | //- /lib.rs |
849 | mod bar; | 840 | mod bar; |
850 | fn f() { | 841 | fn f() { |
851 | bar::foo::fun() | 842 | bar::foo::fun() |
852 | } | 843 | } |
853 | 844 | ||
854 | //- /bar.rs | 845 | //- /bar.rs |
855 | pub mod foo<|>; | 846 | pub mod foo<|>; |
856 | 847 | ||
857 | //- /bar/foo.rs | 848 | //- /bar/foo.rs |
858 | // pub fn fun() {} | 849 | // pub fn fun() {} |
859 | ", | 850 | "#, |
860 | ); | 851 | ); |
861 | let new_name = "foo2"; | 852 | let new_name = "foo2"; |
862 | let source_change = analysis.rename(position, new_name).unwrap(); | 853 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -899,10 +890,10 @@ mod tests { | |||
899 | src: FileId( | 890 | src: FileId( |
900 | 3, | 891 | 3, |
901 | ), | 892 | ), |
902 | dst_source_root: SourceRootId( | 893 | anchor: FileId( |
903 | 0, | 894 | 2, |
904 | ), | 895 | ), |
905 | dst_path: "bar/foo2.rs", | 896 | dst: "foo2.rs", |
906 | }, | 897 | }, |
907 | ], | 898 | ], |
908 | is_snippet: false, | 899 | is_snippet: false, |
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html index 0ae8c7efc..f92a0aba5 100644 --- a/crates/ra_ide/src/snapshots/highlight_doctest.html +++ b/crates/ra_ide/src/snapshots/highlight_doctest.html | |||
@@ -25,22 +25,30 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
31 | .control { font-style: italic; } | 33 | .control { font-style: italic; } |
32 | </style> | 34 | </style> |
33 | <pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> { | 35 | <pre><code><span class="keyword">struct</span> <span class="struct declaration">Foo</span> { |
36 | <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>, | ||
37 | } | ||
38 | |||
39 | <span class="keyword">impl</span> <span class="struct">Foo</span> { | ||
40 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration">bar</span>: <span class="builtin_type">bool</span> = <span class="bool_literal">true</span>; | ||
41 | |||
34 | <span class="comment">/// Constructs a new `Foo`.</span> | 42 | <span class="comment">/// Constructs a new `Foo`.</span> |
35 | <span class="comment">///</span> | 43 | <span class="comment">///</span> |
36 | <span class="comment">/// # Examples</span> | 44 | <span class="comment">/// # Examples</span> |
37 | <span class="comment">///</span> | 45 | <span class="comment">///</span> |
38 | <span class="comment">/// ```</span> | 46 | <span class="comment">/// ```</span> |
39 | <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> | 47 | <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> |
40 | <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | 48 | <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="struct">Foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>(); |
41 | <span class="comment">/// ```</span> | 49 | <span class="comment">/// ```</span> |
42 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="unresolved_reference">Foo</span> { | 50 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="struct">Foo</span> { |
43 | <span class="unresolved_reference">Foo</span> { } | 51 | <span class="struct">Foo</span> { <span class="field">bar</span>: <span class="bool_literal">true</span> } |
44 | } | 52 | } |
45 | 53 | ||
46 | <span class="comment">/// `bar` method on `Foo`.</span> | 54 | <span class="comment">/// `bar` method on `Foo`.</span> |
@@ -48,11 +56,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
48 | <span class="comment">/// # Examples</span> | 56 | <span class="comment">/// # Examples</span> |
49 | <span class="comment">///</span> | 57 | <span class="comment">///</span> |
50 | <span class="comment">/// ```</span> | 58 | <span class="comment">/// ```</span> |
51 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | 59 | <span class="comment">/// </span><span class="keyword">use</span> <span class="module">x</span>::<span class="module">y</span>; |
60 | <span class="comment">///</span> | ||
61 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>(); | ||
52 | <span class="comment">///</span> | 62 | <span class="comment">///</span> |
53 | <span class="comment">/// </span><span class="comment">// calls bar on foo</span> | 63 | <span class="comment">/// </span><span class="comment">// calls bar on foo</span> |
54 | <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); | 64 | <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); |
55 | <span class="comment">///</span> | 65 | <span class="comment">///</span> |
66 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">bar</span> = <span class="variable">foo</span>.<span class="field">bar</span> || <span class="struct">Foo</span>::<span class="constant">bar</span>; | ||
67 | <span class="comment">///</span> | ||
56 | <span class="comment">/// </span><span class="comment">/* multi-line | 68 | <span class="comment">/// </span><span class="comment">/* multi-line |
57 | </span><span class="comment">/// </span><span class="comment"> comment */</span> | 69 | </span><span class="comment">/// </span><span class="comment"> comment */</span> |
58 | <span class="comment">///</span> | 70 | <span class="comment">///</span> |
@@ -62,8 +74,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
62 | <span class="comment">///</span> | 74 | <span class="comment">///</span> |
63 | <span class="comment">/// ```</span> | 75 | <span class="comment">/// ```</span> |
64 | <span class="comment">///</span> | 76 | <span class="comment">///</span> |
77 | <span class="comment">/// ```rust,no_run</span> | ||
78 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="struct">Foo</span>::<span class="function">new</span>().<span class="function">bar</span>(); | ||
65 | <span class="comment">/// ```</span> | 79 | <span class="comment">/// ```</span> |
66 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>(); | 80 | <span class="comment">///</span> |
81 | <span class="comment">/// ```sh</span> | ||
82 | <span class="comment">/// echo 1</span> | ||
67 | <span class="comment">/// ```</span> | 83 | <span class="comment">/// ```</span> |
68 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">bool</span> { | 84 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">bool</span> { |
69 | <span class="bool_literal">true</span> | 85 | <span class="bool_literal">true</span> |
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index dec06eb51..47dbd7bc8 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 849eb3b73..b46fa44c6 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
@@ -82,6 +84,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
82 | 84 | ||
83 | <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); | 85 | <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); |
84 | 86 | ||
85 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>); | 87 | <span class="comment">// escape sequences</span> |
88 | <span class="macro">println!</span>(<span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span>); | ||
89 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span>); | ||
90 | |||
91 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>); | ||
86 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>); | 92 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>); |
87 | }</code></pre> \ No newline at end of file | 93 | }</code></pre> \ No newline at end of file |
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html index bd24e6e38..73438fbb4 100644 --- a/crates/ra_ide/src/snapshots/highlight_unsafe.html +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 33548d43c..0c4f0a018 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
@@ -62,6 +64,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
62 | } | 64 | } |
63 | } | 65 | } |
64 | 66 | ||
67 | <span class="macro">macro_rules!</span> <span class="macro declaration">noop</span> { | ||
68 | ($expr:expr) => { | ||
69 | $expr | ||
70 | } | ||
71 | } | ||
72 | |||
65 | <span class="comment">// comment</span> | 73 | <span class="comment">// comment</span> |
66 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | 74 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { |
67 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); | 75 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); |
@@ -80,6 +88,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
80 | <span class="comment">// Do nothing</span> | 88 | <span class="comment">// Do nothing</span> |
81 | } | 89 | } |
82 | 90 | ||
91 | <span class="macro">noop!</span>(<span class="macro">noop</span><span class="macro">!</span>(<span class="numeric_literal">1</span>)); | ||
92 | |||
83 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; | 93 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; |
84 | <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; | 94 | <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; |
85 | <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>; | 95 | <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>; |
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 1ab06182c..a74a70069 100644 --- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 93e9aee1d..762aab962 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -27,11 +27,11 @@ impl std::error::Error for SsrError {} | |||
27 | // | 27 | // |
28 | // Search and replace with named wildcards that will match any expression. | 28 | // Search and replace with named wildcards that will match any expression. |
29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | 29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. |
30 | // A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | 30 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. |
31 | // Available via the command `rust-analyzer.ssr`. | 31 | // Available via the command `rust-analyzer.ssr`. |
32 | // | 32 | // |
33 | // ```rust | 33 | // ```rust |
34 | // // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | 34 | // // Using structural search replace command [foo($a, $b) ==>> ($a).foo($b)] |
35 | // | 35 | // |
36 | // // BEFORE | 36 | // // BEFORE |
37 | // String::from(foo(y + 5, z)) | 37 | // String::from(foo(y + 5, z)) |
@@ -79,7 +79,7 @@ struct SsrPattern { | |||
79 | vars: Vec<Var>, | 79 | vars: Vec<Var>, |
80 | } | 80 | } |
81 | 81 | ||
82 | /// represents an `$var` in an SSR query | 82 | /// Represents a `$var` in an SSR query. |
83 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 83 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
84 | struct Var(String); | 84 | struct Var(String); |
85 | 85 | ||
@@ -122,8 +122,7 @@ impl FromStr for SsrQuery { | |||
122 | let mut pattern = it.next().expect("something").to_string(); | 122 | let mut pattern = it.next().expect("something").to_string(); |
123 | 123 | ||
124 | for part in it.map(split_by_var) { | 124 | for part in it.map(split_by_var) { |
125 | let (var, var_type, remainder) = part?; | 125 | let (var, remainder) = part?; |
126 | is_expr(var_type)?; | ||
127 | let new_var = create_name(var, &mut vars)?; | 126 | let new_var = create_name(var, &mut vars)?; |
128 | pattern.push_str(new_var); | 127 | pattern.push_str(new_var); |
129 | pattern.push_str(remainder); | 128 | pattern.push_str(remainder); |
@@ -166,15 +165,11 @@ fn traverse(node: &SyntaxNode, go: &mut impl FnMut(&SyntaxNode) -> bool) { | |||
166 | } | 165 | } |
167 | } | 166 | } |
168 | 167 | ||
169 | fn split_by_var(s: &str) -> Result<(&str, &str, &str), SsrError> { | 168 | fn split_by_var(s: &str) -> Result<(&str, &str), SsrError> { |
170 | let end_of_name = s.find(':').ok_or_else(|| SsrError("Use $<name>:expr".into()))?; | 169 | let end_of_name = s.find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len()); |
171 | let name = &s[0..end_of_name]; | 170 | let name = &s[..end_of_name]; |
172 | is_name(name)?; | 171 | is_name(name)?; |
173 | let type_begin = end_of_name + 1; | 172 | Ok((name, &s[end_of_name..])) |
174 | let type_length = | ||
175 | s[type_begin..].find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len()); | ||
176 | let type_name = &s[type_begin..type_begin + type_length]; | ||
177 | Ok((name, type_name, &s[type_begin + type_length..])) | ||
178 | } | 173 | } |
179 | 174 | ||
180 | fn is_name(s: &str) -> Result<(), SsrError> { | 175 | fn is_name(s: &str) -> Result<(), SsrError> { |
@@ -185,14 +180,6 @@ fn is_name(s: &str) -> Result<(), SsrError> { | |||
185 | } | 180 | } |
186 | } | 181 | } |
187 | 182 | ||
188 | fn is_expr(s: &str) -> Result<(), SsrError> { | ||
189 | if s == "expr" { | ||
190 | Ok(()) | ||
191 | } else { | ||
192 | Err(SsrError("Only $<name>:expr is supported".into())) | ||
193 | } | ||
194 | } | ||
195 | |||
196 | fn replace_in_template(template: String, var: &str, new_var: &str) -> String { | 183 | fn replace_in_template(template: String, var: &str, new_var: &str) -> String { |
197 | let name = format!("${}", var); | 184 | let name = format!("${}", var); |
198 | template.replace(&name, new_var) | 185 | template.replace(&name, new_var) |
@@ -450,7 +437,7 @@ mod tests { | |||
450 | 437 | ||
451 | #[test] | 438 | #[test] |
452 | fn parser_happy_case() { | 439 | fn parser_happy_case() { |
453 | let result: SsrQuery = "foo($a:expr, $b:expr) ==>> bar($b, $a)".parse().unwrap(); | 440 | let result: SsrQuery = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap(); |
454 | assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)"); | 441 | assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)"); |
455 | assert_eq!(result.pattern.vars.len(), 2); | 442 | assert_eq!(result.pattern.vars.len(), 2); |
456 | assert_eq!(result.pattern.vars[0].0, "__search_pattern_a"); | 443 | assert_eq!(result.pattern.vars[0].0, "__search_pattern_a"); |
@@ -477,30 +464,9 @@ mod tests { | |||
477 | } | 464 | } |
478 | 465 | ||
479 | #[test] | 466 | #[test] |
480 | fn parser_no_pattern_type() { | ||
481 | assert_eq!(parse_error_text("foo($a) ==>>"), "Parse error: Use $<name>:expr"); | ||
482 | } | ||
483 | |||
484 | #[test] | ||
485 | fn parser_invalid_name() { | ||
486 | assert_eq!( | ||
487 | parse_error_text("foo($a+:expr) ==>>"), | ||
488 | "Parse error: Name can contain only alphanumerics and _" | ||
489 | ); | ||
490 | } | ||
491 | |||
492 | #[test] | ||
493 | fn parser_invalid_type() { | ||
494 | assert_eq!( | ||
495 | parse_error_text("foo($a:ident) ==>>"), | ||
496 | "Parse error: Only $<name>:expr is supported" | ||
497 | ); | ||
498 | } | ||
499 | |||
500 | #[test] | ||
501 | fn parser_repeated_name() { | 467 | fn parser_repeated_name() { |
502 | assert_eq!( | 468 | assert_eq!( |
503 | parse_error_text("foo($a:expr, $a:expr) ==>>"), | 469 | parse_error_text("foo($a, $a) ==>>"), |
504 | "Parse error: Name `a` repeats more than once" | 470 | "Parse error: Name `a` repeats more than once" |
505 | ); | 471 | ); |
506 | } | 472 | } |
@@ -517,7 +483,7 @@ mod tests { | |||
517 | 483 | ||
518 | #[test] | 484 | #[test] |
519 | fn parse_match_replace() { | 485 | fn parse_match_replace() { |
520 | let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap(); | 486 | let query: SsrQuery = "foo($x) ==>> bar($x)".parse().unwrap(); |
521 | let input = "fn main() { foo(1+2); }"; | 487 | let input = "fn main() { foo(1+2); }"; |
522 | 488 | ||
523 | let code = SourceFile::parse(input).tree(); | 489 | let code = SourceFile::parse(input).tree(); |
@@ -549,7 +515,7 @@ mod tests { | |||
549 | #[test] | 515 | #[test] |
550 | fn ssr_function_to_method() { | 516 | fn ssr_function_to_method() { |
551 | assert_ssr_transform( | 517 | assert_ssr_transform( |
552 | "my_function($a:expr, $b:expr) ==>> ($a).my_method($b)", | 518 | "my_function($a, $b) ==>> ($a).my_method($b)", |
553 | "loop { my_function( other_func(x, y), z + w) }", | 519 | "loop { my_function( other_func(x, y), z + w) }", |
554 | "loop { (other_func(x, y)).my_method(z + w) }", | 520 | "loop { (other_func(x, y)).my_method(z + w) }", |
555 | ) | 521 | ) |
@@ -558,7 +524,7 @@ mod tests { | |||
558 | #[test] | 524 | #[test] |
559 | fn ssr_nested_function() { | 525 | fn ssr_nested_function() { |
560 | assert_ssr_transform( | 526 | assert_ssr_transform( |
561 | "foo($a:expr, $b:expr, $c:expr) ==>> bar($c, baz($a, $b))", | 527 | "foo($a, $b, $c) ==>> bar($c, baz($a, $b))", |
562 | "fn main { foo (x + value.method(b), x+y-z, true && false) }", | 528 | "fn main { foo (x + value.method(b), x+y-z, true && false) }", |
563 | "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", | 529 | "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", |
564 | ) | 530 | ) |
@@ -567,7 +533,7 @@ mod tests { | |||
567 | #[test] | 533 | #[test] |
568 | fn ssr_expected_spacing() { | 534 | fn ssr_expected_spacing() { |
569 | assert_ssr_transform( | 535 | assert_ssr_transform( |
570 | "foo($x:expr) + bar() ==>> bar($x)", | 536 | "foo($x) + bar() ==>> bar($x)", |
571 | "fn main() { foo(5) + bar() }", | 537 | "fn main() { foo(5) + bar() }", |
572 | "fn main() { bar(5) }", | 538 | "fn main() { bar(5) }", |
573 | ); | 539 | ); |
@@ -576,7 +542,7 @@ mod tests { | |||
576 | #[test] | 542 | #[test] |
577 | fn ssr_with_extra_space() { | 543 | fn ssr_with_extra_space() { |
578 | assert_ssr_transform( | 544 | assert_ssr_transform( |
579 | "foo($x:expr ) + bar() ==>> bar($x)", | 545 | "foo($x ) + bar() ==>> bar($x)", |
580 | "fn main() { foo( 5 ) +bar( ) }", | 546 | "fn main() { foo( 5 ) +bar( ) }", |
581 | "fn main() { bar(5) }", | 547 | "fn main() { bar(5) }", |
582 | ); | 548 | ); |
@@ -585,7 +551,7 @@ mod tests { | |||
585 | #[test] | 551 | #[test] |
586 | fn ssr_keeps_nested_comment() { | 552 | fn ssr_keeps_nested_comment() { |
587 | assert_ssr_transform( | 553 | assert_ssr_transform( |
588 | "foo($x:expr) ==>> bar($x)", | 554 | "foo($x) ==>> bar($x)", |
589 | "fn main() { foo(other(5 /* using 5 */)) }", | 555 | "fn main() { foo(other(5 /* using 5 */)) }", |
590 | "fn main() { bar(other(5 /* using 5 */)) }", | 556 | "fn main() { bar(other(5 /* using 5 */)) }", |
591 | ) | 557 | ) |
@@ -594,7 +560,7 @@ mod tests { | |||
594 | #[test] | 560 | #[test] |
595 | fn ssr_keeps_comment() { | 561 | fn ssr_keeps_comment() { |
596 | assert_ssr_transform( | 562 | assert_ssr_transform( |
597 | "foo($x:expr) ==>> bar($x)", | 563 | "foo($x) ==>> bar($x)", |
598 | "fn main() { foo(5 /* using 5 */) }", | 564 | "fn main() { foo(5 /* using 5 */) }", |
599 | "fn main() { bar(5)/* using 5 */ }", | 565 | "fn main() { bar(5)/* using 5 */ }", |
600 | ) | 566 | ) |
@@ -603,7 +569,7 @@ mod tests { | |||
603 | #[test] | 569 | #[test] |
604 | fn ssr_struct_lit() { | 570 | fn ssr_struct_lit() { |
605 | assert_ssr_transform( | 571 | assert_ssr_transform( |
606 | "foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)", | 572 | "foo{a: $a, b: $b} ==>> foo::new($a, $b)", |
607 | "fn main() { foo{b:2, a:1} }", | 573 | "fn main() { foo{b:2, a:1} }", |
608 | "fn main() { foo::new(1, 2) }", | 574 | "fn main() { foo::new(1, 2) }", |
609 | ) | 575 | ) |
@@ -612,7 +578,7 @@ mod tests { | |||
612 | #[test] | 578 | #[test] |
613 | fn ssr_call_and_method_call() { | 579 | fn ssr_call_and_method_call() { |
614 | assert_ssr_transform( | 580 | assert_ssr_transform( |
615 | "foo::<'a>($a:expr, $b:expr)) ==>> foo2($a, $b)", | 581 | "foo::<'a>($a, $b)) ==>> foo2($a, $b)", |
616 | "fn main() { get().bar.foo::<'a>(1); }", | 582 | "fn main() { get().bar.foo::<'a>(1); }", |
617 | "fn main() { foo2(get().bar, 1); }", | 583 | "fn main() { foo2(get().bar, 1); }", |
618 | ) | 584 | ) |
@@ -621,7 +587,7 @@ mod tests { | |||
621 | #[test] | 587 | #[test] |
622 | fn ssr_method_call_and_call() { | 588 | fn ssr_method_call_and_call() { |
623 | assert_ssr_transform( | 589 | assert_ssr_transform( |
624 | "$o:expr.foo::<i32>($a:expr)) ==>> $o.foo2($a)", | 590 | "$o.foo::<i32>($a)) ==>> $o.foo2($a)", |
625 | "fn main() { X::foo::<i32>(x, 1); }", | 591 | "fn main() { X::foo::<i32>(x, 1); }", |
626 | "fn main() { x.foo2(1); }", | 592 | "fn main() { x.foo2(1); }", |
627 | ) | 593 | ) |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index ab45c364a..854b6cc6d 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -44,6 +44,7 @@ pub(crate) fn highlight( | |||
44 | db: &RootDatabase, | 44 | db: &RootDatabase, |
45 | file_id: FileId, | 45 | file_id: FileId, |
46 | range_to_highlight: Option<TextRange>, | 46 | range_to_highlight: Option<TextRange>, |
47 | syntactic_name_ref_highlighting: bool, | ||
47 | ) -> Vec<HighlightedRange> { | 48 | ) -> Vec<HighlightedRange> { |
48 | let _p = profile("highlight"); | 49 | let _p = profile("highlight"); |
49 | let sema = Semantics::new(db); | 50 | let sema = Semantics::new(db); |
@@ -104,6 +105,7 @@ pub(crate) fn highlight( | |||
104 | if let Some((highlight, binding_hash)) = highlight_element( | 105 | if let Some((highlight, binding_hash)) = highlight_element( |
105 | &sema, | 106 | &sema, |
106 | &mut bindings_shadow_count, | 107 | &mut bindings_shadow_count, |
108 | syntactic_name_ref_highlighting, | ||
107 | name.syntax().clone().into(), | 109 | name.syntax().clone().into(), |
108 | ) { | 110 | ) { |
109 | stack.add(HighlightedRange { | 111 | stack.add(HighlightedRange { |
@@ -160,23 +162,25 @@ pub(crate) fn highlight( | |||
160 | // Check if macro takes a format string and remember it for highlighting later. | 162 | // Check if macro takes a format string and remember it for highlighting later. |
161 | // The macros that accept a format string expand to a compiler builtin macros | 163 | // The macros that accept a format string expand to a compiler builtin macros |
162 | // `format_args` and `format_args_nl`. | 164 | // `format_args` and `format_args_nl`. |
163 | if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) { | 165 | if let Some(name) = parent |
164 | if let Some(name) = | 166 | .parent() |
165 | fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref()) | 167 | .and_then(ast::MacroCall::cast) |
166 | { | 168 | .and_then(|mc| mc.path()) |
167 | match name.text().as_str() { | 169 | .and_then(|p| p.segment()) |
168 | "format_args" | "format_args_nl" => { | 170 | .and_then(|s| s.name_ref()) |
169 | format_string = parent | 171 | { |
170 | .children_with_tokens() | 172 | match name.text().as_str() { |
171 | .filter(|t| t.kind() != WHITESPACE) | 173 | "format_args" | "format_args_nl" => { |
172 | .nth(1) | 174 | format_string = parent |
173 | .filter(|e| { | 175 | .children_with_tokens() |
174 | ast::String::can_cast(e.kind()) | 176 | .filter(|t| t.kind() != WHITESPACE) |
175 | || ast::RawString::can_cast(e.kind()) | 177 | .nth(1) |
176 | }) | 178 | .filter(|e| { |
177 | } | 179 | ast::String::can_cast(e.kind()) |
178 | _ => {} | 180 | || ast::RawString::can_cast(e.kind()) |
181 | }) | ||
179 | } | 182 | } |
183 | _ => {} | ||
180 | } | 184 | } |
181 | } | 185 | } |
182 | 186 | ||
@@ -198,15 +202,18 @@ pub(crate) fn highlight( | |||
198 | 202 | ||
199 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | 203 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); |
200 | 204 | ||
201 | if let Some((highlight, binding_hash)) = | 205 | if let Some((highlight, binding_hash)) = highlight_element( |
202 | highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) | 206 | &sema, |
203 | { | 207 | &mut bindings_shadow_count, |
208 | syntactic_name_ref_highlighting, | ||
209 | element_to_highlight.clone(), | ||
210 | ) { | ||
204 | stack.add(HighlightedRange { range, highlight, binding_hash }); | 211 | stack.add(HighlightedRange { range, highlight, binding_hash }); |
205 | if let Some(string) = | 212 | if let Some(string) = |
206 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 213 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
207 | { | 214 | { |
208 | stack.push(); | ||
209 | if is_format_string { | 215 | if is_format_string { |
216 | stack.push(); | ||
210 | string.lex_format_specifier(|piece_range, kind| { | 217 | string.lex_format_specifier(|piece_range, kind| { |
211 | if let Some(highlight) = highlight_format_specifier(kind) { | 218 | if let Some(highlight) = highlight_format_specifier(kind) { |
212 | stack.add(HighlightedRange { | 219 | stack.add(HighlightedRange { |
@@ -216,13 +223,27 @@ pub(crate) fn highlight( | |||
216 | }); | 223 | }); |
217 | } | 224 | } |
218 | }); | 225 | }); |
226 | stack.pop(); | ||
227 | } | ||
228 | // Highlight escape sequences | ||
229 | if let Some(char_ranges) = string.char_ranges() { | ||
230 | stack.push(); | ||
231 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { | ||
232 | if string.text()[piece_range.start().into()..].starts_with('\\') { | ||
233 | stack.add(HighlightedRange { | ||
234 | range: piece_range + range.start(), | ||
235 | highlight: HighlightTag::EscapeSequence.into(), | ||
236 | binding_hash: None, | ||
237 | }); | ||
238 | } | ||
239 | } | ||
240 | stack.pop_and_inject(false); | ||
219 | } | 241 | } |
220 | stack.pop(); | ||
221 | } else if let Some(string) = | 242 | } else if let Some(string) = |
222 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | 243 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) |
223 | { | 244 | { |
224 | stack.push(); | ||
225 | if is_format_string { | 245 | if is_format_string { |
246 | stack.push(); | ||
226 | string.lex_format_specifier(|piece_range, kind| { | 247 | string.lex_format_specifier(|piece_range, kind| { |
227 | if let Some(highlight) = highlight_format_specifier(kind) { | 248 | if let Some(highlight) = highlight_format_specifier(kind) { |
228 | stack.add(HighlightedRange { | 249 | stack.add(HighlightedRange { |
@@ -232,8 +253,8 @@ pub(crate) fn highlight( | |||
232 | }); | 253 | }); |
233 | } | 254 | } |
234 | }); | 255 | }); |
256 | stack.pop(); | ||
235 | } | 257 | } |
236 | stack.pop(); | ||
237 | } | 258 | } |
238 | } | 259 | } |
239 | } | 260 | } |
@@ -408,6 +429,7 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | |||
408 | fn highlight_element( | 429 | fn highlight_element( |
409 | sema: &Semantics<RootDatabase>, | 430 | sema: &Semantics<RootDatabase>, |
410 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | 431 | bindings_shadow_count: &mut FxHashMap<Name, u32>, |
432 | syntactic_name_ref_highlighting: bool, | ||
411 | element: SyntaxElement, | 433 | element: SyntaxElement, |
412 | ) -> Option<(Highlight, Option<u64>)> { | 434 | ) -> Option<(Highlight, Option<u64>)> { |
413 | let db = sema.db; | 435 | let db = sema.db; |
@@ -461,6 +483,7 @@ fn highlight_element( | |||
461 | } | 483 | } |
462 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), | 484 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), |
463 | }, | 485 | }, |
486 | None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref), | ||
464 | None => HighlightTag::UnresolvedReference.into(), | 487 | None => HighlightTag::UnresolvedReference.into(), |
465 | } | 488 | } |
466 | } | 489 | } |
@@ -493,6 +516,9 @@ fn highlight_element( | |||
493 | h |= HighlightModifier::Unsafe; | 516 | h |= HighlightModifier::Unsafe; |
494 | h | 517 | h |
495 | } | 518 | } |
519 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | ||
520 | Highlight::new(HighlightTag::Macro) | ||
521 | } | ||
496 | 522 | ||
497 | k if k.is_keyword() => { | 523 | k if k.is_keyword() => { |
498 | let h = Highlight::new(HighlightTag::Keyword); | 524 | let h = Highlight::new(HighlightTag::Keyword); |
@@ -609,3 +635,53 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
609 | 635 | ||
610 | tag.into() | 636 | tag.into() |
611 | } | 637 | } |
638 | |||
639 | fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | ||
640 | let default = HighlightTag::UnresolvedReference; | ||
641 | |||
642 | let parent = match name.syntax().parent() { | ||
643 | Some(it) => it, | ||
644 | _ => return default.into(), | ||
645 | }; | ||
646 | |||
647 | let tag = match parent.kind() { | ||
648 | METHOD_CALL_EXPR => HighlightTag::Function, | ||
649 | FIELD_EXPR => HighlightTag::Field, | ||
650 | PATH_SEGMENT => { | ||
651 | let path = match parent.parent().and_then(ast::Path::cast) { | ||
652 | Some(it) => it, | ||
653 | _ => return default.into(), | ||
654 | }; | ||
655 | let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
656 | Some(it) => it, | ||
657 | _ => { | ||
658 | // within path, decide whether it is module or adt by checking for uppercase name | ||
659 | return if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
660 | HighlightTag::Struct | ||
661 | } else { | ||
662 | HighlightTag::Module | ||
663 | } | ||
664 | .into(); | ||
665 | } | ||
666 | }; | ||
667 | let parent = match expr.syntax().parent() { | ||
668 | Some(it) => it, | ||
669 | None => return default.into(), | ||
670 | }; | ||
671 | |||
672 | match parent.kind() { | ||
673 | CALL_EXPR => HighlightTag::Function, | ||
674 | _ => { | ||
675 | if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
676 | HighlightTag::Struct | ||
677 | } else { | ||
678 | HighlightTag::Constant | ||
679 | } | ||
680 | } | ||
681 | } | ||
682 | } | ||
683 | _ => default, | ||
684 | }; | ||
685 | |||
686 | tag.into() | ||
687 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index 5bada6252..99b6b25ab 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -19,7 +19,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
19 | ) | 19 | ) |
20 | } | 20 | } |
21 | 21 | ||
22 | let ranges = highlight(db, file_id, None); | 22 | let ranges = highlight(db, file_id, None, false); |
23 | let text = parse.tree().syntax().to_string(); | 23 | let text = parse.tree().syntax().to_string(); |
24 | let mut prev_pos = TextSize::from(0); | 24 | let mut prev_pos = TextSize::from(0); |
25 | let mut buf = String::new(); | 25 | let mut buf = String::new(); |
@@ -84,6 +84,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
84 | .variable { color: #DCDCCC; } | 84 | .variable { color: #DCDCCC; } |
85 | .format_specifier { color: #CC696B; } | 85 | .format_specifier { color: #CC696B; } |
86 | .mutable { text-decoration: underline; } | 86 | .mutable { text-decoration: underline; } |
87 | .unresolved_reference { color: #FC5555; } | ||
88 | .escape_sequence { color: #94BFF3; } | ||
87 | 89 | ||
88 | .keyword { color: #F0DFAF; font-weight: bold; } | 90 | .keyword { color: #F0DFAF; font-weight: bold; } |
89 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 91 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs index 3575a0fc6..929a5cc5c 100644 --- a/crates/ra_ide/src/syntax_highlighting/injection.rs +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs | |||
@@ -53,6 +53,10 @@ pub(super) fn highlight_injection( | |||
53 | /// Mapping from extracted documentation code to original code | 53 | /// Mapping from extracted documentation code to original code |
54 | type RangesMap = BTreeMap<TextSize, TextSize>; | 54 | type RangesMap = BTreeMap<TextSize, TextSize>; |
55 | 55 | ||
56 | const RUSTDOC_FENCE: &'static str = "```"; | ||
57 | const RUSTDOC_FENCE_TOKENS: &[&'static str] = | ||
58 | &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"]; | ||
59 | |||
56 | /// Extracts Rust code from documentation comments as well as a mapping from | 60 | /// Extracts Rust code from documentation comments as well as a mapping from |
57 | /// the extracted source code back to the original source ranges. | 61 | /// the extracted source code back to the original source ranges. |
58 | /// Lastly, a vector of new comment highlight ranges (spanning only the | 62 | /// Lastly, a vector of new comment highlight ranges (spanning only the |
@@ -67,6 +71,7 @@ pub(super) fn extract_doc_comments( | |||
67 | // Mapping from extracted documentation code to original code | 71 | // Mapping from extracted documentation code to original code |
68 | let mut range_mapping: RangesMap = BTreeMap::new(); | 72 | let mut range_mapping: RangesMap = BTreeMap::new(); |
69 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); | 73 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); |
74 | let mut is_codeblock = false; | ||
70 | let mut is_doctest = false; | 75 | let mut is_doctest = false; |
71 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | 76 | // Replace the original, line-spanning comment ranges by new, only comment-prefix |
72 | // spanning comment ranges. | 77 | // spanning comment ranges. |
@@ -76,8 +81,13 @@ pub(super) fn extract_doc_comments( | |||
76 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) | 81 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) |
77 | .filter(|comment| comment.kind().doc.is_some()) | 82 | .filter(|comment| comment.kind().doc.is_some()) |
78 | .filter(|comment| { | 83 | .filter(|comment| { |
79 | if comment.text().contains("```") { | 84 | if let Some(idx) = comment.text().find(RUSTDOC_FENCE) { |
80 | is_doctest = !is_doctest; | 85 | is_codeblock = !is_codeblock; |
86 | // Check whether code is rust by inspecting fence guards | ||
87 | let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..]; | ||
88 | let is_rust = | ||
89 | guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); | ||
90 | is_doctest = is_codeblock && is_rust; | ||
81 | false | 91 | false |
82 | } else { | 92 | } else { |
83 | is_doctest | 93 | is_doctest |
@@ -137,7 +147,7 @@ pub(super) fn highlight_doc_comment( | |||
137 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); | 147 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); |
138 | 148 | ||
139 | stack.push(); | 149 | stack.push(); |
140 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | 150 | for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() { |
141 | // Determine start offset and end offset in case of multi-line ranges | 151 | // Determine start offset and end offset in case of multi-line ranges |
142 | let mut start_offset = None; | 152 | let mut start_offset = None; |
143 | let mut end_offset = None; | 153 | let mut end_offset = None; |
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index 94f466966..400d22fb6 100644 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ b/crates/ra_ide/src/syntax_highlighting/tags.rs | |||
@@ -23,6 +23,7 @@ pub enum HighlightTag { | |||
23 | Constant, | 23 | Constant, |
24 | Enum, | 24 | Enum, |
25 | EnumVariant, | 25 | EnumVariant, |
26 | EscapeSequence, | ||
26 | Field, | 27 | Field, |
27 | FormatSpecifier, | 28 | FormatSpecifier, |
28 | Function, | 29 | Function, |
@@ -71,6 +72,7 @@ impl HighlightTag { | |||
71 | HighlightTag::Constant => "constant", | 72 | HighlightTag::Constant => "constant", |
72 | HighlightTag::Enum => "enum", | 73 | HighlightTag::Enum => "enum", |
73 | HighlightTag::EnumVariant => "enum_variant", | 74 | HighlightTag::EnumVariant => "enum_variant", |
75 | HighlightTag::EscapeSequence => "escape_sequence", | ||
74 | HighlightTag::Field => "field", | 76 | HighlightTag::Field => "field", |
75 | HighlightTag::FormatSpecifier => "format_specifier", | 77 | HighlightTag::FormatSpecifier => "format_specifier", |
76 | HighlightTag::Function => "function", | 78 | HighlightTag::Function => "function", |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 949bf59a0..b4d56a7a0 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -43,6 +43,12 @@ def_fn! { | |||
43 | } | 43 | } |
44 | } | 44 | } |
45 | 45 | ||
46 | macro_rules! noop { | ||
47 | ($expr:expr) => { | ||
48 | $expr | ||
49 | } | ||
50 | } | ||
51 | |||
46 | // comment | 52 | // comment |
47 | fn main() { | 53 | fn main() { |
48 | println!("Hello, {}!", 92); | 54 | println!("Hello, {}!", 92); |
@@ -61,6 +67,8 @@ fn main() { | |||
61 | // Do nothing | 67 | // Do nothing |
62 | } | 68 | } |
63 | 69 | ||
70 | noop!(noop!(1)); | ||
71 | |||
64 | let mut x = 42; | 72 | let mut x = 42; |
65 | let y = &mut x; | 73 | let y = &mut x; |
66 | let z = &y; | 74 | let z = &y; |
@@ -238,6 +246,10 @@ fn main() { | |||
238 | 246 | ||
239 | println!(r"Hello, {}!", "world"); | 247 | println!(r"Hello, {}!", "world"); |
240 | 248 | ||
249 | // escape sequences | ||
250 | println!("Hello\nWorld"); | ||
251 | println!("\u{48}\x65\x6C\x6C\x6F World"); | ||
252 | |||
241 | println!("{\x41}", A = 92); | 253 | println!("{\x41}", A = 92); |
242 | println!("{ничоси}", ничоси = 92); | 254 | println!("{ничоси}", ничоси = 92); |
243 | }"# | 255 | }"# |
@@ -279,7 +291,13 @@ fn main() { | |||
279 | fn test_highlight_doctest() { | 291 | fn test_highlight_doctest() { |
280 | check_highlighting( | 292 | check_highlighting( |
281 | r#" | 293 | r#" |
294 | struct Foo { | ||
295 | bar: bool, | ||
296 | } | ||
297 | |||
282 | impl Foo { | 298 | impl Foo { |
299 | pub const bar: bool = true; | ||
300 | |||
283 | /// Constructs a new `Foo`. | 301 | /// Constructs a new `Foo`. |
284 | /// | 302 | /// |
285 | /// # Examples | 303 | /// # Examples |
@@ -289,7 +307,7 @@ impl Foo { | |||
289 | /// let mut foo: Foo = Foo::new(); | 307 | /// let mut foo: Foo = Foo::new(); |
290 | /// ``` | 308 | /// ``` |
291 | pub const fn new() -> Foo { | 309 | pub const fn new() -> Foo { |
292 | Foo { } | 310 | Foo { bar: true } |
293 | } | 311 | } |
294 | 312 | ||
295 | /// `bar` method on `Foo`. | 313 | /// `bar` method on `Foo`. |
@@ -297,11 +315,15 @@ impl Foo { | |||
297 | /// # Examples | 315 | /// # Examples |
298 | /// | 316 | /// |
299 | /// ``` | 317 | /// ``` |
318 | /// use x::y; | ||
319 | /// | ||
300 | /// let foo = Foo::new(); | 320 | /// let foo = Foo::new(); |
301 | /// | 321 | /// |
302 | /// // calls bar on foo | 322 | /// // calls bar on foo |
303 | /// assert!(foo.bar()); | 323 | /// assert!(foo.bar()); |
304 | /// | 324 | /// |
325 | /// let bar = foo.bar || Foo::bar; | ||
326 | /// | ||
305 | /// /* multi-line | 327 | /// /* multi-line |
306 | /// comment */ | 328 | /// comment */ |
307 | /// | 329 | /// |
@@ -311,9 +333,13 @@ impl Foo { | |||
311 | /// | 333 | /// |
312 | /// ``` | 334 | /// ``` |
313 | /// | 335 | /// |
314 | /// ``` | 336 | /// ```rust,no_run |
315 | /// let foobar = Foo::new().bar(); | 337 | /// let foobar = Foo::new().bar(); |
316 | /// ``` | 338 | /// ``` |
339 | /// | ||
340 | /// ```sh | ||
341 | /// echo 1 | ||
342 | /// ``` | ||
317 | pub fn foo(&self) -> bool { | 343 | pub fn foo(&self) -> bool { |
318 | true | 344 | true |
319 | } | 345 | } |
@@ -322,7 +348,7 @@ impl Foo { | |||
322 | .trim(), | 348 | .trim(), |
323 | "crates/ra_ide/src/snapshots/highlight_doctest.html", | 349 | "crates/ra_ide/src/snapshots/highlight_doctest.html", |
324 | false, | 350 | false, |
325 | ) | 351 | ); |
326 | } | 352 | } |
327 | 353 | ||
328 | /// Highlights the code given by the `ra_fixture` argument, renders the | 354 | /// Highlights the code given by the `ra_fixture` argument, renders the |