aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/completion.rs1
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs940
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs8
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs52
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs26
-rw-r--r--crates/ra_ide/src/completion/patterns.rs194
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs46
-rw-r--r--crates/ra_ide/src/diagnostics.rs20
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs23
-rw-r--r--crates/ra_ide/src/hover.rs1142
-rw-r--r--crates/ra_ide/src/lib.rs10
-rw-r--r--crates/ra_ide/src/prime_caches.rs2
-rw-r--r--crates/ra_ide/src/references/rename.rs91
-rw-r--r--crates/ra_ide/src/runnables.rs18
-rw-r--r--crates/ra_ide/src/snapshots/highlight_doctest.html82
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html8
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html10
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html2
-rw-r--r--crates/ra_ide/src/ssr.rs74
-rw-r--r--crates/ra_ide/src/status.rs15
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs131
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs4
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs24
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs5
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs32
27 files changed, 2097 insertions, 867 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;
15mod complete_postfix; 15mod complete_postfix;
16mod complete_macro_in_item_position; 16mod complete_macro_in_item_position;
17mod complete_trait_impl; 17mod complete_trait_impl;
18mod patterns;
18#[cfg(test)] 19#[cfg(test)]
19mod test_utils; 20mod 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
3use ra_syntax::{ 3use ra_syntax::ast;
4 ast::{self, LoopBodyOwner},
5 match_ast, AstNode,
6 SyntaxKind::*,
7 SyntaxToken,
8};
9 4
10use crate::completion::{ 5use 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
44fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { 39pub(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
55pub(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
85fn is_in_loop_body(leaf: &SyntaxToken) -> bool { 142fn 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
153fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
154 acc.add(keyword(ctx, kw, snippet));
106} 155}
107 156
108fn complete_return( 157fn complete_return(
@@ -121,327 +170,156 @@ fn complete_return(
121 170
122#[cfg(test)] 171#[cfg(test)]
123mod tests { 172mod 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;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use 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};
12use ra_text_edit::Indel; 12use ra_text_edit::Indel;
13 13
14use 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};
14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; 19use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15use test_utils::mark; 20use 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
65impl<'a> CompletionContext<'a> { 82impl<'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
128impl 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)]
129pub(crate) enum CompletionKind { 155pub(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
3use 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)]
12use crate::completion::test_utils::check_pattern_is_applicable;
13
14pub(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]
22fn test_has_trait_parent() {
23 check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent);
24}
25
26pub(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]
34fn test_has_impl_parent() {
35 check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent);
36}
37
38pub(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]
42fn test_has_block_expr_parent() {
43 check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent);
44}
45
46pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
47 element.ancestors().find(|it| it.kind() == BIND_PAT).is_some()
48}
49#[test]
50fn 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
55pub(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]
61fn 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
66pub(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]
74fn 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
79pub(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]
87fn test_is_match_arm() {
88 check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm);
89}
90
91pub(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]
99fn test_unsafe_is_prev() {
100 check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev);
101}
102
103pub(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]
111fn test_if_is_prev() {
112 check_pattern_is_applicable(r"if l<|>", if_is_prev);
113}
114
115pub(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]
119fn test_has_trait_as_prev_sibling() {
120 check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling);
121}
122
123pub(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]
127fn test_has_impl_as_prev_sibling() {
128 check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling);
129}
130
131pub(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
157fn 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
165fn 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
177fn 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};
8use hir::Semantics;
9use ra_syntax::{AstNode, NodeOrToken, SyntaxElement};
8 10
9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { 11pub(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
15pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
16 completion_list_with_options(code, kind, &CompletionConfig::default())
17}
18
13pub(crate) fn do_completion_with_options( 19pub(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
32fn 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(); 41pub(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
57pub(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 365d52168..a88a978d7 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -11,7 +11,7 @@ use hir::{
11 HasSource, HirDisplay, Semantics, VariantDef, 11 HasSource, HirDisplay, Semantics, VariantDef,
12}; 12};
13use itertools::Itertools; 13use itertools::Itertools;
14use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; 14use ra_db::SourceDatabase;
15use ra_ide_db::RootDatabase; 15use ra_ide_db::RootDatabase;
16use ra_prof::profile; 16use ra_prof::profile;
17use ra_syntax::{ 17use 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(),
@@ -683,10 +679,10 @@ mod tests {
683 source_file_edits: [], 679 source_file_edits: [],
684 file_system_edits: [ 680 file_system_edits: [
685 CreateFile { 681 CreateFile {
686 source_root: SourceRootId( 682 anchor: FileId(
687 0, 683 1,
688 ), 684 ),
689 path: "foo.rs", 685 dst: "foo.rs",
690 }, 686 },
691 ], 687 ],
692 is_snippet: false, 688 is_snippet: false,
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index c7bb1e69f..0b52b01ab 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -135,8 +135,8 @@ impl NavigationTarget {
135 db: &RootDatabase, 135 db: &RootDatabase,
136 node: InFile<&dyn ast::NameOwner>, 136 node: InFile<&dyn ast::NameOwner>,
137 ) -> NavigationTarget { 137 ) -> NavigationTarget {
138 //FIXME: use `_` instead of empty string 138 let name =
139 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); 139 node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
140 let focus_range = 140 let focus_range =
141 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); 141 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range);
142 let frange = original_range(db, node.map(|it| it.syntax())); 142 let frange = original_range(db, node.map(|it| it.syntax()));
@@ -150,6 +150,25 @@ impl NavigationTarget {
150 ) 150 )
151 } 151 }
152 152
153 /// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner`
154 pub(crate) fn from_doc_commented(
155 db: &RootDatabase,
156 named: InFile<&dyn ast::NameOwner>,
157 node: InFile<&dyn ast::DocCommentsOwner>,
158 ) -> NavigationTarget {
159 let name =
160 named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
161 let frange = original_range(db, node.map(|it| it.syntax()));
162
163 NavigationTarget::from_syntax(
164 frange.file_id,
165 name,
166 None,
167 frange.range,
168 node.value.syntax().kind(),
169 )
170 }
171
153 fn from_syntax( 172 fn from_syntax(
154 file_id: FileId, 173 file_id: FileId,
155 name: SmolStr, 174 name: SmolStr,
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index ad78b7671..d870e4cbc 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -2,7 +2,7 @@ use std::iter::once;
2 2
3use hir::{ 3use hir::{
4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, 4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
5 ModuleDef, ModuleSource, Semantics, 5 Module, ModuleDef, ModuleSource, Semantics,
6}; 6};
7use itertools::Itertools; 7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
@@ -13,7 +13,9 @@ use ra_ide_db::{
13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, 16 display::{
17 macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav, TryToNav,
18 },
17 runnables::runnable, 19 runnables::runnable,
18 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, 20 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
19}; 21};
@@ -24,19 +26,21 @@ pub struct HoverConfig {
24 pub implementations: bool, 26 pub implementations: bool,
25 pub run: bool, 27 pub run: bool,
26 pub debug: bool, 28 pub debug: bool,
29 pub goto_type_def: bool,
27} 30}
28 31
29impl Default for HoverConfig { 32impl Default for HoverConfig {
30 fn default() -> Self { 33 fn default() -> Self {
31 Self { implementations: true, run: true, debug: true } 34 Self { implementations: true, run: true, debug: true, goto_type_def: true }
32 } 35 }
33} 36}
34 37
35impl HoverConfig { 38impl HoverConfig {
36 pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; 39 pub const NO_ACTIONS: Self =
40 Self { implementations: false, run: false, debug: false, goto_type_def: false };
37 41
38 pub fn any(&self) -> bool { 42 pub fn any(&self) -> bool {
39 self.implementations || self.runnable() 43 self.implementations || self.runnable() || self.goto_type_def
40 } 44 }
41 45
42 pub fn none(&self) -> bool { 46 pub fn none(&self) -> bool {
@@ -52,6 +56,13 @@ impl HoverConfig {
52pub enum HoverAction { 56pub enum HoverAction {
53 Runnable(Runnable), 57 Runnable(Runnable),
54 Implementaion(FilePosition), 58 Implementaion(FilePosition),
59 GoToType(Vec<HoverGotoTypeData>),
60}
61
62#[derive(Debug, Clone, Eq, PartialEq)]
63pub struct HoverGotoTypeData {
64 pub mod_path: String,
65 pub nav: NavigationTarget,
55} 66}
56 67
57/// Contains the results when hovering over an item 68/// Contains the results when hovering over an item
@@ -138,6 +149,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
138 res.push_action(action); 149 res.push_action(action);
139 } 150 }
140 151
152 if let Some(action) = goto_type_action(db, name_kind) {
153 res.push_action(action);
154 }
155
141 return Some(RangeInfo::new(range, res)); 156 return Some(RangeInfo::new(range, res));
142 } 157 }
143 } 158 }
@@ -218,6 +233,44 @@ fn runnable_action(
218 } 233 }
219} 234}
220 235
236fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
237 match def {
238 Definition::Local(it) => {
239 let mut targets: Vec<ModuleDef> = Vec::new();
240 let mut push_new_def = |item: ModuleDef| {
241 if !targets.contains(&item) {
242 targets.push(item);
243 }
244 };
245
246 it.ty(db).walk(db, |t| {
247 if let Some(adt) = t.as_adt() {
248 push_new_def(adt.into());
249 } else if let Some(trait_) = t.as_dyn_trait() {
250 push_new_def(trait_.into());
251 } else if let Some(traits) = t.as_impl_traits(db) {
252 traits.into_iter().for_each(|it| push_new_def(it.into()));
253 } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
254 push_new_def(trait_.into());
255 }
256 });
257
258 let targets = targets
259 .into_iter()
260 .filter_map(|it| {
261 Some(HoverGotoTypeData {
262 mod_path: mod_path(db, &it)?,
263 nav: it.try_to_nav(db)?,
264 })
265 })
266 .collect();
267
268 Some(HoverAction::GoToType(targets))
269 }
270 _ => None,
271 }
272}
273
221fn hover_text( 274fn hover_text(
222 docs: Option<String>, 275 docs: Option<String>,
223 desc: Option<String>, 276 desc: Option<String>,
@@ -248,25 +301,31 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
248 .map(|name| name.to_string()) 301 .map(|name| name.to_string())
249} 302}
250 303
251fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { 304fn determine_mod_path(db: &RootDatabase, module: Module, name: Option<String>) -> String {
252 let mod_path = def.module(db).map(|module| { 305 once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string))
253 once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) 306 .chain(
254 .chain( 307 module
255 module 308 .path_to_root(db)
256 .path_to_root(db) 309 .into_iter()
257 .into_iter() 310 .rev()
258 .rev() 311 .map(|it| it.name(db).map(|name| name.to_string())),
259 .map(|it| it.name(db).map(|name| name.to_string())), 312 )
260 ) 313 .chain(once(name))
261 .chain(once(definition_owner_name(db, def))) 314 .flatten()
262 .flatten() 315 .join("::")
263 .join("::") 316}
264 }); 317
265 mod_path 318// returns None only for ModuleDef::BuiltinType
319fn mod_path(db: &RootDatabase, item: &ModuleDef) -> Option<String> {
320 Some(determine_mod_path(db, item.module(db)?, item.name(db).map(|name| name.to_string())))
321}
322
323fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
324 def.module(db).map(|module| determine_mod_path(db, module, definition_owner_name(db, def)))
266} 325}
267 326
268fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { 327fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> {
269 let mod_path = determine_mod_path(db, &def); 328 let mod_path = definition_mod_path(db, &def);
270 return match def { 329 return match def {
271 Definition::Macro(it) => { 330 Definition::Macro(it) => {
272 let src = it.source(db); 331 let src = it.source(db);
@@ -1310,4 +1369,1045 @@ fn func(foo: i32) { if true { <|>foo; }; }
1310 ] 1369 ]
1311 "###); 1370 "###);
1312 } 1371 }
1372
1373 #[test]
1374 fn test_hover_struct_has_goto_type_action() {
1375 let (_, actions) = check_hover_result(
1376 "
1377 //- /main.rs
1378 struct S{ f1: u32 }
1379
1380 fn main() {
1381 let s<|>t = S{ f1:0 };
1382 }
1383 ",
1384 &["S"],
1385 );
1386 assert_debug_snapshot!(actions,
1387 @r###"
1388 [
1389 GoToType(
1390 [
1391 HoverGotoTypeData {
1392 mod_path: "S",
1393 nav: NavigationTarget {
1394 file_id: FileId(
1395 1,
1396 ),
1397 full_range: 0..19,
1398 name: "S",
1399 kind: STRUCT_DEF,
1400 focus_range: Some(
1401 7..8,
1402 ),
1403 container_name: None,
1404 description: Some(
1405 "struct S",
1406 ),
1407 docs: None,
1408 },
1409 },
1410 ],
1411 ),
1412 ]
1413 "###);
1414 }
1415
1416 #[test]
1417 fn test_hover_generic_struct_has_goto_type_actions() {
1418 let (_, actions) = check_hover_result(
1419 "
1420 //- /main.rs
1421 struct Arg(u32);
1422 struct S<T>{ f1: T }
1423
1424 fn main() {
1425 let s<|>t = S{ f1:Arg(0) };
1426 }
1427 ",
1428 &["S<Arg>"],
1429 );
1430 assert_debug_snapshot!(actions,
1431 @r###"
1432 [
1433 GoToType(
1434 [
1435 HoverGotoTypeData {
1436 mod_path: "S",
1437 nav: NavigationTarget {
1438 file_id: FileId(
1439 1,
1440 ),
1441 full_range: 17..37,
1442 name: "S",
1443 kind: STRUCT_DEF,
1444 focus_range: Some(
1445 24..25,
1446 ),
1447 container_name: None,
1448 description: Some(
1449 "struct S",
1450 ),
1451 docs: None,
1452 },
1453 },
1454 HoverGotoTypeData {
1455 mod_path: "Arg",
1456 nav: NavigationTarget {
1457 file_id: FileId(
1458 1,
1459 ),
1460 full_range: 0..16,
1461 name: "Arg",
1462 kind: STRUCT_DEF,
1463 focus_range: Some(
1464 7..10,
1465 ),
1466 container_name: None,
1467 description: Some(
1468 "struct Arg",
1469 ),
1470 docs: None,
1471 },
1472 },
1473 ],
1474 ),
1475 ]
1476 "###);
1477 }
1478
1479 #[test]
1480 fn test_hover_generic_struct_has_flattened_goto_type_actions() {
1481 let (_, actions) = check_hover_result(
1482 "
1483 //- /main.rs
1484 struct Arg(u32);
1485 struct S<T>{ f1: T }
1486
1487 fn main() {
1488 let s<|>t = S{ f1: S{ f1: Arg(0) } };
1489 }
1490 ",
1491 &["S<S<Arg>>"],
1492 );
1493 assert_debug_snapshot!(actions,
1494 @r###"
1495 [
1496 GoToType(
1497 [
1498 HoverGotoTypeData {
1499 mod_path: "S",
1500 nav: NavigationTarget {
1501 file_id: FileId(
1502 1,
1503 ),
1504 full_range: 17..37,
1505 name: "S",
1506 kind: STRUCT_DEF,
1507 focus_range: Some(
1508 24..25,
1509 ),
1510 container_name: None,
1511 description: Some(
1512 "struct S",
1513 ),
1514 docs: None,
1515 },
1516 },
1517 HoverGotoTypeData {
1518 mod_path: "Arg",
1519 nav: NavigationTarget {
1520 file_id: FileId(
1521 1,
1522 ),
1523 full_range: 0..16,
1524 name: "Arg",
1525 kind: STRUCT_DEF,
1526 focus_range: Some(
1527 7..10,
1528 ),
1529 container_name: None,
1530 description: Some(
1531 "struct Arg",
1532 ),
1533 docs: None,
1534 },
1535 },
1536 ],
1537 ),
1538 ]
1539 "###);
1540 }
1541
1542 #[test]
1543 fn test_hover_tuple_has_goto_type_actions() {
1544 let (_, actions) = check_hover_result(
1545 "
1546 //- /main.rs
1547 struct A(u32);
1548 struct B(u32);
1549 mod M {
1550 pub struct C(u32);
1551 }
1552
1553 fn main() {
1554 let s<|>t = (A(1), B(2), M::C(3) );
1555 }
1556 ",
1557 &["(A, B, C)"],
1558 );
1559 assert_debug_snapshot!(actions,
1560 @r###"
1561 [
1562 GoToType(
1563 [
1564 HoverGotoTypeData {
1565 mod_path: "A",
1566 nav: NavigationTarget {
1567 file_id: FileId(
1568 1,
1569 ),
1570 full_range: 0..14,
1571 name: "A",
1572 kind: STRUCT_DEF,
1573 focus_range: Some(
1574 7..8,
1575 ),
1576 container_name: None,
1577 description: Some(
1578 "struct A",
1579 ),
1580 docs: None,
1581 },
1582 },
1583 HoverGotoTypeData {
1584 mod_path: "B",
1585 nav: NavigationTarget {
1586 file_id: FileId(
1587 1,
1588 ),
1589 full_range: 15..29,
1590 name: "B",
1591 kind: STRUCT_DEF,
1592 focus_range: Some(
1593 22..23,
1594 ),
1595 container_name: None,
1596 description: Some(
1597 "struct B",
1598 ),
1599 docs: None,
1600 },
1601 },
1602 HoverGotoTypeData {
1603 mod_path: "M::C",
1604 nav: NavigationTarget {
1605 file_id: FileId(
1606 1,
1607 ),
1608 full_range: 42..60,
1609 name: "C",
1610 kind: STRUCT_DEF,
1611 focus_range: Some(
1612 53..54,
1613 ),
1614 container_name: None,
1615 description: Some(
1616 "pub struct C",
1617 ),
1618 docs: None,
1619 },
1620 },
1621 ],
1622 ),
1623 ]
1624 "###);
1625 }
1626
1627 #[test]
1628 fn test_hover_return_impl_trait_has_goto_type_action() {
1629 let (_, actions) = check_hover_result(
1630 "
1631 //- /main.rs
1632 trait Foo {}
1633
1634 fn foo() -> impl Foo {}
1635
1636 fn main() {
1637 let s<|>t = foo();
1638 }
1639 ",
1640 &["impl Foo"],
1641 );
1642 assert_debug_snapshot!(actions,
1643 @r###"
1644 [
1645 GoToType(
1646 [
1647 HoverGotoTypeData {
1648 mod_path: "Foo",
1649 nav: NavigationTarget {
1650 file_id: FileId(
1651 1,
1652 ),
1653 full_range: 0..12,
1654 name: "Foo",
1655 kind: TRAIT_DEF,
1656 focus_range: Some(
1657 6..9,
1658 ),
1659 container_name: None,
1660 description: Some(
1661 "trait Foo",
1662 ),
1663 docs: None,
1664 },
1665 },
1666 ],
1667 ),
1668 ]
1669 "###);
1670 }
1671
1672 #[test]
1673 fn test_hover_generic_return_impl_trait_has_goto_type_action() {
1674 let (_, actions) = check_hover_result(
1675 "
1676 //- /main.rs
1677 trait Foo<T> {}
1678 struct S;
1679
1680 fn foo() -> impl Foo<S> {}
1681
1682 fn main() {
1683 let s<|>t = foo();
1684 }
1685 ",
1686 &["impl Foo<S>"],
1687 );
1688 assert_debug_snapshot!(actions,
1689 @r###"
1690 [
1691 GoToType(
1692 [
1693 HoverGotoTypeData {
1694 mod_path: "Foo",
1695 nav: NavigationTarget {
1696 file_id: FileId(
1697 1,
1698 ),
1699 full_range: 0..15,
1700 name: "Foo",
1701 kind: TRAIT_DEF,
1702 focus_range: Some(
1703 6..9,
1704 ),
1705 container_name: None,
1706 description: Some(
1707 "trait Foo",
1708 ),
1709 docs: None,
1710 },
1711 },
1712 HoverGotoTypeData {
1713 mod_path: "S",
1714 nav: NavigationTarget {
1715 file_id: FileId(
1716 1,
1717 ),
1718 full_range: 16..25,
1719 name: "S",
1720 kind: STRUCT_DEF,
1721 focus_range: Some(
1722 23..24,
1723 ),
1724 container_name: None,
1725 description: Some(
1726 "struct S",
1727 ),
1728 docs: None,
1729 },
1730 },
1731 ],
1732 ),
1733 ]
1734 "###);
1735 }
1736
1737 #[test]
1738 fn test_hover_return_impl_traits_has_goto_type_action() {
1739 let (_, actions) = check_hover_result(
1740 "
1741 //- /main.rs
1742 trait Foo {}
1743 trait Bar {}
1744
1745 fn foo() -> impl Foo + Bar {}
1746
1747 fn main() {
1748 let s<|>t = foo();
1749 }
1750 ",
1751 &["impl Foo + Bar"],
1752 );
1753 assert_debug_snapshot!(actions,
1754 @r###"
1755 [
1756 GoToType(
1757 [
1758 HoverGotoTypeData {
1759 mod_path: "Foo",
1760 nav: NavigationTarget {
1761 file_id: FileId(
1762 1,
1763 ),
1764 full_range: 0..12,
1765 name: "Foo",
1766 kind: TRAIT_DEF,
1767 focus_range: Some(
1768 6..9,
1769 ),
1770 container_name: None,
1771 description: Some(
1772 "trait Foo",
1773 ),
1774 docs: None,
1775 },
1776 },
1777 HoverGotoTypeData {
1778 mod_path: "Bar",
1779 nav: NavigationTarget {
1780 file_id: FileId(
1781 1,
1782 ),
1783 full_range: 13..25,
1784 name: "Bar",
1785 kind: TRAIT_DEF,
1786 focus_range: Some(
1787 19..22,
1788 ),
1789 container_name: None,
1790 description: Some(
1791 "trait Bar",
1792 ),
1793 docs: None,
1794 },
1795 },
1796 ],
1797 ),
1798 ]
1799 "###);
1800 }
1801
1802 #[test]
1803 fn test_hover_generic_return_impl_traits_has_goto_type_action() {
1804 let (_, actions) = check_hover_result(
1805 "
1806 //- /main.rs
1807 trait Foo<T> {}
1808 trait Bar<T> {}
1809 struct S1 {}
1810 struct S2 {}
1811
1812 fn foo() -> impl Foo<S1> + Bar<S2> {}
1813
1814 fn main() {
1815 let s<|>t = foo();
1816 }
1817 ",
1818 &["impl Foo<S1> + Bar<S2>"],
1819 );
1820 assert_debug_snapshot!(actions,
1821 @r###"
1822 [
1823 GoToType(
1824 [
1825 HoverGotoTypeData {
1826 mod_path: "Foo",
1827 nav: NavigationTarget {
1828 file_id: FileId(
1829 1,
1830 ),
1831 full_range: 0..15,
1832 name: "Foo",
1833 kind: TRAIT_DEF,
1834 focus_range: Some(
1835 6..9,
1836 ),
1837 container_name: None,
1838 description: Some(
1839 "trait Foo",
1840 ),
1841 docs: None,
1842 },
1843 },
1844 HoverGotoTypeData {
1845 mod_path: "Bar",
1846 nav: NavigationTarget {
1847 file_id: FileId(
1848 1,
1849 ),
1850 full_range: 16..31,
1851 name: "Bar",
1852 kind: TRAIT_DEF,
1853 focus_range: Some(
1854 22..25,
1855 ),
1856 container_name: None,
1857 description: Some(
1858 "trait Bar",
1859 ),
1860 docs: None,
1861 },
1862 },
1863 HoverGotoTypeData {
1864 mod_path: "S1",
1865 nav: NavigationTarget {
1866 file_id: FileId(
1867 1,
1868 ),
1869 full_range: 32..44,
1870 name: "S1",
1871 kind: STRUCT_DEF,
1872 focus_range: Some(
1873 39..41,
1874 ),
1875 container_name: None,
1876 description: Some(
1877 "struct S1",
1878 ),
1879 docs: None,
1880 },
1881 },
1882 HoverGotoTypeData {
1883 mod_path: "S2",
1884 nav: NavigationTarget {
1885 file_id: FileId(
1886 1,
1887 ),
1888 full_range: 45..57,
1889 name: "S2",
1890 kind: STRUCT_DEF,
1891 focus_range: Some(
1892 52..54,
1893 ),
1894 container_name: None,
1895 description: Some(
1896 "struct S2",
1897 ),
1898 docs: None,
1899 },
1900 },
1901 ],
1902 ),
1903 ]
1904 "###);
1905 }
1906
1907 #[test]
1908 fn test_hover_arg_impl_trait_has_goto_type_action() {
1909 let (_, actions) = check_hover_result(
1910 "
1911 //- /lib.rs
1912 trait Foo {}
1913 fn foo(ar<|>g: &impl Foo) {}
1914 ",
1915 &["&impl Foo"],
1916 );
1917 assert_debug_snapshot!(actions,
1918 @r###"
1919 [
1920 GoToType(
1921 [
1922 HoverGotoTypeData {
1923 mod_path: "Foo",
1924 nav: NavigationTarget {
1925 file_id: FileId(
1926 1,
1927 ),
1928 full_range: 0..12,
1929 name: "Foo",
1930 kind: TRAIT_DEF,
1931 focus_range: Some(
1932 6..9,
1933 ),
1934 container_name: None,
1935 description: Some(
1936 "trait Foo",
1937 ),
1938 docs: None,
1939 },
1940 },
1941 ],
1942 ),
1943 ]
1944 "###);
1945 }
1946
1947 #[test]
1948 fn test_hover_arg_impl_traits_has_goto_type_action() {
1949 let (_, actions) = check_hover_result(
1950 "
1951 //- /lib.rs
1952 trait Foo {}
1953 trait Bar<T> {}
1954 struct S{}
1955
1956 fn foo(ar<|>g: &impl Foo + Bar<S>) {}
1957 ",
1958 &["&impl Foo + Bar<S>"],
1959 );
1960 assert_debug_snapshot!(actions,
1961 @r###"
1962 [
1963 GoToType(
1964 [
1965 HoverGotoTypeData {
1966 mod_path: "Foo",
1967 nav: NavigationTarget {
1968 file_id: FileId(
1969 1,
1970 ),
1971 full_range: 0..12,
1972 name: "Foo",
1973 kind: TRAIT_DEF,
1974 focus_range: Some(
1975 6..9,
1976 ),
1977 container_name: None,
1978 description: Some(
1979 "trait Foo",
1980 ),
1981 docs: None,
1982 },
1983 },
1984 HoverGotoTypeData {
1985 mod_path: "Bar",
1986 nav: NavigationTarget {
1987 file_id: FileId(
1988 1,
1989 ),
1990 full_range: 13..28,
1991 name: "Bar",
1992 kind: TRAIT_DEF,
1993 focus_range: Some(
1994 19..22,
1995 ),
1996 container_name: None,
1997 description: Some(
1998 "trait Bar",
1999 ),
2000 docs: None,
2001 },
2002 },
2003 HoverGotoTypeData {
2004 mod_path: "S",
2005 nav: NavigationTarget {
2006 file_id: FileId(
2007 1,
2008 ),
2009 full_range: 29..39,
2010 name: "S",
2011 kind: STRUCT_DEF,
2012 focus_range: Some(
2013 36..37,
2014 ),
2015 container_name: None,
2016 description: Some(
2017 "struct S",
2018 ),
2019 docs: None,
2020 },
2021 },
2022 ],
2023 ),
2024 ]
2025 "###);
2026 }
2027
2028 #[test]
2029 fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
2030 let (_, actions) = check_hover_result(
2031 "
2032 //- /lib.rs
2033 trait Foo<T> {}
2034 struct S {}
2035 fn foo(ar<|>g: &impl Foo<S>) {}
2036 ",
2037 &["&impl Foo<S>"],
2038 );
2039 assert_debug_snapshot!(actions,
2040 @r###"
2041 [
2042 GoToType(
2043 [
2044 HoverGotoTypeData {
2045 mod_path: "Foo",
2046 nav: NavigationTarget {
2047 file_id: FileId(
2048 1,
2049 ),
2050 full_range: 0..15,
2051 name: "Foo",
2052 kind: TRAIT_DEF,
2053 focus_range: Some(
2054 6..9,
2055 ),
2056 container_name: None,
2057 description: Some(
2058 "trait Foo",
2059 ),
2060 docs: None,
2061 },
2062 },
2063 HoverGotoTypeData {
2064 mod_path: "S",
2065 nav: NavigationTarget {
2066 file_id: FileId(
2067 1,
2068 ),
2069 full_range: 16..27,
2070 name: "S",
2071 kind: STRUCT_DEF,
2072 focus_range: Some(
2073 23..24,
2074 ),
2075 container_name: None,
2076 description: Some(
2077 "struct S",
2078 ),
2079 docs: None,
2080 },
2081 },
2082 ],
2083 ),
2084 ]
2085 "###);
2086 }
2087
2088 #[test]
2089 fn test_hover_dyn_return_has_goto_type_action() {
2090 let (_, actions) = check_hover_result(
2091 "
2092 //- /main.rs
2093 trait Foo {}
2094 struct S;
2095 impl Foo for S {}
2096
2097 struct B<T>{}
2098
2099 fn foo() -> B<dyn Foo> {}
2100
2101 fn main() {
2102 let s<|>t = foo();
2103 }
2104 ",
2105 &["B<dyn Foo>"],
2106 );
2107 assert_debug_snapshot!(actions,
2108 @r###"
2109 [
2110 GoToType(
2111 [
2112 HoverGotoTypeData {
2113 mod_path: "B",
2114 nav: NavigationTarget {
2115 file_id: FileId(
2116 1,
2117 ),
2118 full_range: 41..54,
2119 name: "B",
2120 kind: STRUCT_DEF,
2121 focus_range: Some(
2122 48..49,
2123 ),
2124 container_name: None,
2125 description: Some(
2126 "struct B",
2127 ),
2128 docs: None,
2129 },
2130 },
2131 HoverGotoTypeData {
2132 mod_path: "Foo",
2133 nav: NavigationTarget {
2134 file_id: FileId(
2135 1,
2136 ),
2137 full_range: 0..12,
2138 name: "Foo",
2139 kind: TRAIT_DEF,
2140 focus_range: Some(
2141 6..9,
2142 ),
2143 container_name: None,
2144 description: Some(
2145 "trait Foo",
2146 ),
2147 docs: None,
2148 },
2149 },
2150 ],
2151 ),
2152 ]
2153 "###);
2154 }
2155
2156 #[test]
2157 fn test_hover_dyn_arg_has_goto_type_action() {
2158 let (_, actions) = check_hover_result(
2159 "
2160 //- /lib.rs
2161 trait Foo {}
2162 fn foo(ar<|>g: &dyn Foo) {}
2163 ",
2164 &["&dyn Foo"],
2165 );
2166 assert_debug_snapshot!(actions,
2167 @r###"
2168 [
2169 GoToType(
2170 [
2171 HoverGotoTypeData {
2172 mod_path: "Foo",
2173 nav: NavigationTarget {
2174 file_id: FileId(
2175 1,
2176 ),
2177 full_range: 0..12,
2178 name: "Foo",
2179 kind: TRAIT_DEF,
2180 focus_range: Some(
2181 6..9,
2182 ),
2183 container_name: None,
2184 description: Some(
2185 "trait Foo",
2186 ),
2187 docs: None,
2188 },
2189 },
2190 ],
2191 ),
2192 ]
2193 "###);
2194 }
2195
2196 #[test]
2197 fn test_hover_generic_dyn_arg_has_goto_type_action() {
2198 let (_, actions) = check_hover_result(
2199 "
2200 //- /lib.rs
2201 trait Foo<T> {}
2202 struct S {}
2203 fn foo(ar<|>g: &dyn Foo<S>) {}
2204 ",
2205 &["&dyn Foo<S>"],
2206 );
2207 assert_debug_snapshot!(actions,
2208 @r###"
2209 [
2210 GoToType(
2211 [
2212 HoverGotoTypeData {
2213 mod_path: "Foo",
2214 nav: NavigationTarget {
2215 file_id: FileId(
2216 1,
2217 ),
2218 full_range: 0..15,
2219 name: "Foo",
2220 kind: TRAIT_DEF,
2221 focus_range: Some(
2222 6..9,
2223 ),
2224 container_name: None,
2225 description: Some(
2226 "trait Foo",
2227 ),
2228 docs: None,
2229 },
2230 },
2231 HoverGotoTypeData {
2232 mod_path: "S",
2233 nav: NavigationTarget {
2234 file_id: FileId(
2235 1,
2236 ),
2237 full_range: 16..27,
2238 name: "S",
2239 kind: STRUCT_DEF,
2240 focus_range: Some(
2241 23..24,
2242 ),
2243 container_name: None,
2244 description: Some(
2245 "struct S",
2246 ),
2247 docs: None,
2248 },
2249 },
2250 ],
2251 ),
2252 ]
2253 "###);
2254 }
2255
2256 #[test]
2257 fn test_hover_goto_type_action_links_order() {
2258 let (_, actions) = check_hover_result(
2259 "
2260 //- /lib.rs
2261 trait ImplTrait<T> {}
2262 trait DynTrait<T> {}
2263 struct B<T> {}
2264 struct S {}
2265
2266 fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
2267 ",
2268 &["&impl ImplTrait<B<dyn DynTrait<B<S>>>>"],
2269 );
2270 assert_debug_snapshot!(actions,
2271 @r###"
2272 [
2273 GoToType(
2274 [
2275 HoverGotoTypeData {
2276 mod_path: "ImplTrait",
2277 nav: NavigationTarget {
2278 file_id: FileId(
2279 1,
2280 ),
2281 full_range: 0..21,
2282 name: "ImplTrait",
2283 kind: TRAIT_DEF,
2284 focus_range: Some(
2285 6..15,
2286 ),
2287 container_name: None,
2288 description: Some(
2289 "trait ImplTrait",
2290 ),
2291 docs: None,
2292 },
2293 },
2294 HoverGotoTypeData {
2295 mod_path: "B",
2296 nav: NavigationTarget {
2297 file_id: FileId(
2298 1,
2299 ),
2300 full_range: 43..57,
2301 name: "B",
2302 kind: STRUCT_DEF,
2303 focus_range: Some(
2304 50..51,
2305 ),
2306 container_name: None,
2307 description: Some(
2308 "struct B",
2309 ),
2310 docs: None,
2311 },
2312 },
2313 HoverGotoTypeData {
2314 mod_path: "DynTrait",
2315 nav: NavigationTarget {
2316 file_id: FileId(
2317 1,
2318 ),
2319 full_range: 22..42,
2320 name: "DynTrait",
2321 kind: TRAIT_DEF,
2322 focus_range: Some(
2323 28..36,
2324 ),
2325 container_name: None,
2326 description: Some(
2327 "trait DynTrait",
2328 ),
2329 docs: None,
2330 },
2331 },
2332 HoverGotoTypeData {
2333 mod_path: "S",
2334 nav: NavigationTarget {
2335 file_id: FileId(
2336 1,
2337 ),
2338 full_range: 58..69,
2339 name: "S",
2340 kind: STRUCT_DEF,
2341 focus_range: Some(
2342 65..66,
2343 ),
2344 container_name: None,
2345 description: Some(
2346 "struct S",
2347 ),
2348 docs: None,
2349 },
2350 },
2351 ],
2352 ),
2353 ]
2354 "###);
2355 }
2356
2357 #[test]
2358 fn test_hover_associated_type_has_goto_type_action() {
2359 let (_, actions) = check_hover_result(
2360 "
2361 //- /main.rs
2362 trait Foo {
2363 type Item;
2364 fn get(self) -> Self::Item {}
2365 }
2366
2367 struct Bar{}
2368 struct S{}
2369
2370 impl Foo for S{
2371 type Item = Bar;
2372 }
2373
2374 fn test() -> impl Foo {
2375 S{}
2376 }
2377
2378 fn main() {
2379 let s<|>t = test().get();
2380 }
2381 ",
2382 &["Foo::Item<impl Foo>"],
2383 );
2384 assert_debug_snapshot!(actions,
2385 @r###"
2386 [
2387 GoToType(
2388 [
2389 HoverGotoTypeData {
2390 mod_path: "Foo",
2391 nav: NavigationTarget {
2392 file_id: FileId(
2393 1,
2394 ),
2395 full_range: 0..62,
2396 name: "Foo",
2397 kind: TRAIT_DEF,
2398 focus_range: Some(
2399 6..9,
2400 ),
2401 container_name: None,
2402 description: Some(
2403 "trait Foo",
2404 ),
2405 docs: None,
2406 },
2407 },
2408 ],
2409 ),
2410 ]
2411 "###);
2412 }
1313} 2413}
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 28f686767..be9ab62c0 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -66,7 +66,7 @@ pub use crate::{
66 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 66 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
67 expand_macro::ExpandedMacro, 67 expand_macro::ExpandedMacro,
68 folding_ranges::{Fold, FoldKind}, 68 folding_ranges::{Fold, FoldKind},
69 hover::{HoverAction, HoverConfig, HoverResult}, 69 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
72 runnables::{Runnable, RunnableKind, TestId}, 72 runnables::{Runnable, RunnableKind, TestId},
@@ -82,7 +82,7 @@ pub use ra_db::{
82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
83}; 83};
84pub use ra_ide_db::{ 84pub use ra_ide_db::{
85 change::{AnalysisChange, LibraryData}, 85 change::AnalysisChange,
86 line_index::{LineCol, LineIndex}, 86 line_index::{LineCol, LineIndex},
87 search::SearchScope, 87 search::SearchScope,
88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
@@ -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
8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { 8pub(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 915d4f4d3..c4f07f905 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
3use hir::{ModuleSource, Semantics}; 3use hir::{ModuleSource, Semantics};
4use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; 4use ra_db::{RelativePathBuf, SourceDatabaseExt};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, 7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind,
@@ -92,23 +92,14 @@ fn rename_mod(
92 ModuleSource::SourceFile(..) => { 92 ModuleSource::SourceFile(..) => {
93 let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); 93 let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id);
94 // mod is defined in path/to/dir/mod.rs 94 // mod is defined in path/to/dir/mod.rs
95 let dst_path = if mod_path.file_stem() == Some("mod") { 95 let dst = if mod_path.file_stem() == Some("mod") {
96 mod_path 96 format!("../{}/mod.rs", new_name)
97 .parent()
98 .and_then(|p| p.parent())
99 .or_else(|| Some(RelativePath::new("")))
100 .map(|p| p.join(new_name).join("mod.rs"))
101 } else { 97 } else {
102 Some(mod_path.with_file_name(new_name).with_extension("rs")) 98 format!("{}.rs", new_name)
103 }; 99 };
104 if let Some(path) = dst_path { 100 let move_file =
105 let move_file = FileSystemEdit::MoveFile { 101 FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst };
106 src: file_id, 102 file_system_edits.push(move_file);
107 dst_source_root: sema.db.file_source_root(position.file_id),
108 dst_path: path,
109 };
110 file_system_edits.push(move_file);
111 }
112 } 103 }
113 ModuleSource::Module(..) => {} 104 ModuleSource::Module(..) => {}
114 } 105 }
@@ -623,16 +614,16 @@ mod tests {
623 #[test] 614 #[test]
624 fn test_rename_mod() { 615 fn test_rename_mod() {
625 let (analysis, position) = analysis_and_position( 616 let (analysis, position) = analysis_and_position(
626 " 617 r#"
627 //- /lib.rs 618//- /lib.rs
628 mod bar; 619mod bar;
629 620
630 //- /bar.rs 621//- /bar.rs
631 mod foo<|>; 622mod foo<|>;
632 623
633 //- /bar/foo.rs 624//- /bar/foo.rs
634 // emtpy 625// emtpy
635 ", 626 "#,
636 ); 627 );
637 let new_name = "foo2"; 628 let new_name = "foo2";
638 let source_change = analysis.rename(position, new_name).unwrap(); 629 let source_change = analysis.rename(position, new_name).unwrap();
@@ -662,10 +653,10 @@ mod tests {
662 src: FileId( 653 src: FileId(
663 3, 654 3,
664 ), 655 ),
665 dst_source_root: SourceRootId( 656 anchor: FileId(
666 0, 657 2,
667 ), 658 ),
668 dst_path: "bar/foo2.rs", 659 dst: "foo2.rs",
669 }, 660 },
670 ], 661 ],
671 is_snippet: false, 662 is_snippet: false,
@@ -678,12 +669,12 @@ mod tests {
678 #[test] 669 #[test]
679 fn test_rename_mod_in_dir() { 670 fn test_rename_mod_in_dir() {
680 let (analysis, position) = analysis_and_position( 671 let (analysis, position) = analysis_and_position(
681 " 672 r#"
682 //- /lib.rs 673//- /lib.rs
683 mod fo<|>o; 674mod fo<|>o;
684 //- /foo/mod.rs 675//- /foo/mod.rs
685 // emtpy 676// emtpy
686 ", 677 "#,
687 ); 678 );
688 let new_name = "foo2"; 679 let new_name = "foo2";
689 let source_change = analysis.rename(position, new_name).unwrap(); 680 let source_change = analysis.rename(position, new_name).unwrap();
@@ -713,10 +704,10 @@ mod tests {
713 src: FileId( 704 src: FileId(
714 2, 705 2,
715 ), 706 ),
716 dst_source_root: SourceRootId( 707 anchor: FileId(
717 0, 708 1,
718 ), 709 ),
719 dst_path: "foo2/mod.rs", 710 dst: "../foo2/mod.rs",
720 }, 711 },
721 ], 712 ],
722 is_snippet: false, 713 is_snippet: false,
@@ -753,19 +744,19 @@ mod tests {
753 #[test] 744 #[test]
754 fn test_rename_mod_filename_and_path() { 745 fn test_rename_mod_filename_and_path() {
755 let (analysis, position) = analysis_and_position( 746 let (analysis, position) = analysis_and_position(
756 " 747 r#"
757 //- /lib.rs 748//- /lib.rs
758 mod bar; 749mod bar;
759 fn f() { 750fn f() {
760 bar::foo::fun() 751 bar::foo::fun()
761 } 752}
762 753
763 //- /bar.rs 754//- /bar.rs
764 pub mod foo<|>; 755pub mod foo<|>;
765 756
766 //- /bar/foo.rs 757//- /bar/foo.rs
767 // pub fn fun() {} 758// pub fn fun() {}
768 ", 759 "#,
769 ); 760 );
770 let new_name = "foo2"; 761 let new_name = "foo2";
771 let source_change = analysis.rename(position, new_name).unwrap(); 762 let source_change = analysis.rename(position, new_name).unwrap();
@@ -808,10 +799,10 @@ mod tests {
808 src: FileId( 799 src: FileId(
809 3, 800 3,
810 ), 801 ),
811 dst_source_root: SourceRootId( 802 anchor: FileId(
812 0, 803 2,
813 ), 804 ),
814 dst_path: "bar/foo2.rs", 805 dst: "foo2.rs",
815 }, 806 },
816 ], 807 ],
817 is_snippet: false, 808 is_snippet: false,
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index fc57dc33d..8105ef373 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -171,7 +171,15 @@ fn runnable_fn(
171 let cfg_exprs = 171 let cfg_exprs =
172 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); 172 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
173 173
174 let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); 174 let nav = if let RunnableKind::DocTest { .. } = kind {
175 NavigationTarget::from_doc_commented(
176 sema.db,
177 InFile::new(file_id.into(), &fn_def),
178 InFile::new(file_id.into(), &fn_def),
179 )
180 } else {
181 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def))
182 };
175 Some(Runnable { nav, kind, cfg_exprs }) 183 Some(Runnable { nav, kind, cfg_exprs })
176} 184}
177 185
@@ -419,9 +427,7 @@ mod tests {
419 full_range: 22..64, 427 full_range: 22..64,
420 name: "foo", 428 name: "foo",
421 kind: FN_DEF, 429 kind: FN_DEF,
422 focus_range: Some( 430 focus_range: None,
423 56..59,
424 ),
425 container_name: None, 431 container_name: None,
426 description: None, 432 description: None,
427 docs: None, 433 docs: None,
@@ -486,9 +492,7 @@ mod tests {
486 full_range: 51..105, 492 full_range: 51..105,
487 name: "foo", 493 name: "foo",
488 kind: FN_DEF, 494 kind: FN_DEF,
489 focus_range: Some( 495 focus_range: None,
490 97..100,
491 ),
492 container_name: None, 496 container_name: None,
493 description: None, 497 description: None,
494 docs: None, 498 docs: None,
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html
index 0ae8c7efc..f61c0daa5 100644
--- a/crates/ra_ide/src/snapshots/highlight_doctest.html
+++ b/crates/ra_ide/src/snapshots/highlight_doctest.html
@@ -25,46 +25,62 @@ 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> {
34 <span class="comment">/// Constructs a new `Foo`.</span> 36 <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>,
35 <span class="comment">///</span> 37}
36 <span class="comment">/// # Examples</span> 38
37 <span class="comment">///</span> 39<span class="keyword">impl</span> <span class="struct">Foo</span> {
38 <span class="comment">/// ```</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>;
39 <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> 41
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>(); 42 <span class="comment documentation">/// Constructs a new `Foo`.</span>
41 <span class="comment">/// ```</span> 43 <span class="comment documentation">///</span>
42 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="unresolved_reference">Foo</span> { 44 <span class="comment documentation">/// # Examples</span>
43 <span class="unresolved_reference">Foo</span> { } 45 <span class="comment documentation">///</span>
46 <span class="comment documentation">/// ```</span>
47 <span class="comment documentation">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span>
48 <span class="comment documentation">/// </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>();
49 <span class="comment documentation">/// ```</span>
50 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="struct">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 documentation">/// `bar` method on `Foo`.</span>
47 <span class="comment">///</span> 55 <span class="comment documentation">///</span>
48 <span class="comment">/// # Examples</span> 56 <span class="comment documentation">/// # Examples</span>
49 <span class="comment">///</span> 57 <span class="comment documentation">///</span>
50 <span class="comment">/// ```</span> 58 <span class="comment documentation">/// ```</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 documentation">/// </span><span class="keyword">use</span> <span class="module">x</span>::<span class="module">y</span>;
52 <span class="comment">///</span> 60 <span class="comment documentation">///</span>
53 <span class="comment">/// </span><span class="comment">// calls bar on foo</span> 61 <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>();
54 <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); 62 <span class="comment documentation">///</span>
55 <span class="comment">///</span> 63 <span class="comment documentation">/// </span><span class="comment">// calls bar on foo</span>
56 <span class="comment">/// </span><span class="comment">/* multi-line 64 <span class="comment documentation">/// </span><span class="macro">assert!</span>(foo.bar());
57 </span><span class="comment">/// </span><span class="comment"> comment */</span> 65 <span class="comment documentation">///</span>
58 <span class="comment">///</span> 66 <span class="comment documentation">/// </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>;
59 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo 67 <span class="comment documentation">///</span>
60 </span><span class="comment">/// </span><span class="string_literal"> bar 68 <span class="comment documentation">/// </span><span class="comment">/* multi-line
61 </span><span class="comment">/// </span><span class="string_literal"> "</span>; 69 </span><span class="comment documentation">/// </span><span class="comment"> comment */</span>
62 <span class="comment">///</span> 70 <span class="comment documentation">///</span>
63 <span class="comment">/// ```</span> 71 <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo
64 <span class="comment">///</span> 72 </span><span class="comment documentation">/// </span><span class="string_literal"> bar
65 <span class="comment">/// ```</span> 73 </span><span class="comment documentation">/// </span><span class="string_literal"> "</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>(); 74 <span class="comment documentation">///</span>
67 <span class="comment">/// ```</span> 75 <span class="comment documentation">/// ```</span>
76 <span class="comment documentation">///</span>
77 <span class="comment documentation">/// ```rust,no_run</span>
78 <span class="comment documentation">/// </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>();
79 <span class="comment documentation">/// ```</span>
80 <span class="comment documentation">///</span>
81 <span class="comment documentation">/// ```sh</span>
82 <span class="comment documentation">/// echo 1</span>
83 <span class="comment documentation">/// ```</span>
68 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -&gt; <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>) -&gt; <span class="builtin_type">bool</span> {
69 <span class="bool_literal">true</span> 85 <span class="bool_literal">true</span>
70 } 86 }
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) =&gt; {
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)]
84struct Var(String); 84struct 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
169fn split_by_var(s: &str) -> Result<(&str, &str, &str), SsrError> { 168fn 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
180fn is_name(s: &str) -> Result<(), SsrError> { 175fn is_name(s: &str) -> Result<(), SsrError> {
@@ -185,14 +180,6 @@ fn is_name(s: &str) -> Result<(), SsrError> {
185 } 180 }
186} 181}
187 182
188fn 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
196fn replace_in_template(template: String, var: &str, new_var: &str) -> String { 183fn 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/status.rs b/crates/ra_ide/src/status.rs
index 5b7992920..45411b357 100644
--- a/crates/ra_ide/src/status.rs
+++ b/crates/ra_ide/src/status.rs
@@ -16,6 +16,7 @@ use ra_prof::{memory_usage, Bytes};
16use ra_syntax::{ast, Parse, SyntaxNode}; 16use ra_syntax::{ast, Parse, SyntaxNode};
17 17
18use crate::FileId; 18use crate::FileId;
19use rustc_hash::FxHashMap;
19 20
20fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { 21fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
21 db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>() 22 db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>()
@@ -123,20 +124,24 @@ struct LibrarySymbolsStats {
123 124
124impl fmt::Display for LibrarySymbolsStats { 125impl fmt::Display for LibrarySymbolsStats {
125 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 126 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
126 write!(fmt, "{} ({}) symbols", self.total, self.size,) 127 write!(fmt, "{} ({}) symbols", self.total, self.size)
127 } 128 }
128} 129}
129 130
130impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats { 131impl FromIterator<TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>
132 for LibrarySymbolsStats
133{
131 fn from_iter<T>(iter: T) -> LibrarySymbolsStats 134 fn from_iter<T>(iter: T) -> LibrarySymbolsStats
132 where 135 where
133 T: IntoIterator<Item = TableEntry<SourceRootId, Arc<SymbolIndex>>>, 136 T: IntoIterator<Item = TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>,
134 { 137 {
135 let mut res = LibrarySymbolsStats::default(); 138 let mut res = LibrarySymbolsStats::default();
136 for entry in iter { 139 for entry in iter {
137 let value = entry.value.unwrap(); 140 let value = entry.value.unwrap();
138 res.total += value.len(); 141 for symbols in value.values() {
139 res.size += value.memory_size(); 142 res.total += symbols.len();
143 res.size += symbols.memory_size();
144 }
140 } 145 }
141 res 146 res
142 } 147 }
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index ab45c364a..f8f790e59 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> {
408fn highlight_element( 429fn 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,12 +483,20 @@ 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 }
467 490
468 // Simple token-based highlighting 491 // Simple token-based highlighting
469 COMMENT => HighlightTag::Comment.into(), 492 COMMENT => {
493 let comment = element.into_token().and_then(ast::Comment::cast)?;
494 let h = HighlightTag::Comment;
495 match comment.kind().doc {
496 Some(_) => h | HighlightModifier::Documentation,
497 None => h.into(),
498 }
499 }
470 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), 500 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
471 ATTR => HighlightTag::Attribute.into(), 501 ATTR => HighlightTag::Attribute.into(),
472 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), 502 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
@@ -493,6 +523,9 @@ fn highlight_element(
493 h |= HighlightModifier::Unsafe; 523 h |= HighlightModifier::Unsafe;
494 h 524 h
495 } 525 }
526 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
527 Highlight::new(HighlightTag::Macro)
528 }
496 529
497 k if k.is_keyword() => { 530 k if k.is_keyword() => {
498 let h = Highlight::new(HighlightTag::Keyword); 531 let h = Highlight::new(HighlightTag::Keyword);
@@ -609,3 +642,53 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
609 642
610 tag.into() 643 tag.into()
611} 644}
645
646fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
647 let default = HighlightTag::UnresolvedReference;
648
649 let parent = match name.syntax().parent() {
650 Some(it) => it,
651 _ => return default.into(),
652 };
653
654 let tag = match parent.kind() {
655 METHOD_CALL_EXPR => HighlightTag::Function,
656 FIELD_EXPR => HighlightTag::Field,
657 PATH_SEGMENT => {
658 let path = match parent.parent().and_then(ast::Path::cast) {
659 Some(it) => it,
660 _ => return default.into(),
661 };
662 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
663 Some(it) => it,
664 _ => {
665 // within path, decide whether it is module or adt by checking for uppercase name
666 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
667 HighlightTag::Struct
668 } else {
669 HighlightTag::Module
670 }
671 .into();
672 }
673 };
674 let parent = match expr.syntax().parent() {
675 Some(it) => it,
676 None => return default.into(),
677 };
678
679 match parent.kind() {
680 CALL_EXPR => HighlightTag::Function,
681 _ => {
682 if name.text().chars().next().unwrap_or_default().is_uppercase() {
683 HighlightTag::Struct
684 } else {
685 HighlightTag::Constant
686 }
687 }
688 }
689 }
690 _ => default,
691 };
692
693 tag.into()
694}
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..415f24a6d 100644
--- a/crates/ra_ide/src/syntax_highlighting/injection.rs
+++ b/crates/ra_ide/src/syntax_highlighting/injection.rs
@@ -7,7 +7,10 @@ use hir::Semantics;
7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; 7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use stdx::SepBy; 8use stdx::SepBy;
9 9
10use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; 10use crate::{
11 call_info::ActiveParameter, Analysis, HighlightModifier, HighlightTag, HighlightedRange,
12 RootDatabase,
13};
11 14
12use super::HighlightedRangeStack; 15use super::HighlightedRangeStack;
13 16
@@ -53,6 +56,10 @@ pub(super) fn highlight_injection(
53/// Mapping from extracted documentation code to original code 56/// Mapping from extracted documentation code to original code
54type RangesMap = BTreeMap<TextSize, TextSize>; 57type RangesMap = BTreeMap<TextSize, TextSize>;
55 58
59const RUSTDOC_FENCE: &'static str = "```";
60const RUSTDOC_FENCE_TOKENS: &[&'static str] =
61 &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"];
62
56/// Extracts Rust code from documentation comments as well as a mapping from 63/// Extracts Rust code from documentation comments as well as a mapping from
57/// the extracted source code back to the original source ranges. 64/// the extracted source code back to the original source ranges.
58/// Lastly, a vector of new comment highlight ranges (spanning only the 65/// Lastly, a vector of new comment highlight ranges (spanning only the
@@ -67,6 +74,7 @@ pub(super) fn extract_doc_comments(
67 // Mapping from extracted documentation code to original code 74 // Mapping from extracted documentation code to original code
68 let mut range_mapping: RangesMap = BTreeMap::new(); 75 let mut range_mapping: RangesMap = BTreeMap::new();
69 let mut line_start = TextSize::try_from(prefix.len()).unwrap(); 76 let mut line_start = TextSize::try_from(prefix.len()).unwrap();
77 let mut is_codeblock = false;
70 let mut is_doctest = false; 78 let mut is_doctest = false;
71 // Replace the original, line-spanning comment ranges by new, only comment-prefix 79 // Replace the original, line-spanning comment ranges by new, only comment-prefix
72 // spanning comment ranges. 80 // spanning comment ranges.
@@ -76,8 +84,13 @@ pub(super) fn extract_doc_comments(
76 .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) 84 .filter_map(|el| el.into_token().and_then(ast::Comment::cast))
77 .filter(|comment| comment.kind().doc.is_some()) 85 .filter(|comment| comment.kind().doc.is_some())
78 .filter(|comment| { 86 .filter(|comment| {
79 if comment.text().contains("```") { 87 if let Some(idx) = comment.text().find(RUSTDOC_FENCE) {
80 is_doctest = !is_doctest; 88 is_codeblock = !is_codeblock;
89 // Check whether code is rust by inspecting fence guards
90 let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..];
91 let is_rust =
92 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
93 is_doctest = is_codeblock && is_rust;
81 false 94 false
82 } else { 95 } else {
83 is_doctest 96 is_doctest
@@ -108,7 +121,7 @@ pub(super) fn extract_doc_comments(
108 range.start(), 121 range.start(),
109 range.start() + TextSize::try_from(pos).unwrap(), 122 range.start() + TextSize::try_from(pos).unwrap(),
110 ), 123 ),
111 highlight: HighlightTag::Comment.into(), 124 highlight: HighlightTag::Comment | HighlightModifier::Documentation,
112 binding_hash: None, 125 binding_hash: None,
113 }); 126 });
114 line_start += range.len() - TextSize::try_from(pos).unwrap(); 127 line_start += range.len() - TextSize::try_from(pos).unwrap();
@@ -137,7 +150,7 @@ pub(super) fn highlight_doc_comment(
137 let (analysis, tmp_file_id) = Analysis::from_single_file(text); 150 let (analysis, tmp_file_id) = Analysis::from_single_file(text);
138 151
139 stack.push(); 152 stack.push();
140 for mut h in analysis.highlight(tmp_file_id).unwrap() { 153 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 154 // Determine start offset and end offset in case of multi-line ranges
142 let mut start_offset = None; 155 let mut start_offset = None;
143 let mut end_offset = None; 156 let mut end_offset = None;
@@ -154,6 +167,7 @@ pub(super) fn highlight_doc_comment(
154 h.range.start() + start_offset, 167 h.range.start() + start_offset,
155 h.range.end() + end_offset.unwrap_or(start_offset), 168 h.range.end() + end_offset.unwrap_or(start_offset),
156 ); 169 );
170
157 stack.add(h); 171 stack.add(h);
158 } 172 }
159 } 173 }
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index 94f466966..93bbb4b4d 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,
@@ -55,6 +56,7 @@ pub enum HighlightModifier {
55 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 56 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
56 /// not. 57 /// not.
57 Definition, 58 Definition,
59 Documentation,
58 Mutable, 60 Mutable,
59 Unsafe, 61 Unsafe,
60} 62}
@@ -71,6 +73,7 @@ impl HighlightTag {
71 HighlightTag::Constant => "constant", 73 HighlightTag::Constant => "constant",
72 HighlightTag::Enum => "enum", 74 HighlightTag::Enum => "enum",
73 HighlightTag::EnumVariant => "enum_variant", 75 HighlightTag::EnumVariant => "enum_variant",
76 HighlightTag::EscapeSequence => "escape_sequence",
74 HighlightTag::Field => "field", 77 HighlightTag::Field => "field",
75 HighlightTag::FormatSpecifier => "format_specifier", 78 HighlightTag::FormatSpecifier => "format_specifier",
76 HighlightTag::Function => "function", 79 HighlightTag::Function => "function",
@@ -106,6 +109,7 @@ impl HighlightModifier {
106 HighlightModifier::Attribute, 109 HighlightModifier::Attribute,
107 HighlightModifier::ControlFlow, 110 HighlightModifier::ControlFlow,
108 HighlightModifier::Definition, 111 HighlightModifier::Definition,
112 HighlightModifier::Documentation,
109 HighlightModifier::Mutable, 113 HighlightModifier::Mutable,
110 HighlightModifier::Unsafe, 114 HighlightModifier::Unsafe,
111 ]; 115 ];
@@ -115,6 +119,7 @@ impl HighlightModifier {
115 HighlightModifier::Attribute => "attribute", 119 HighlightModifier::Attribute => "attribute",
116 HighlightModifier::ControlFlow => "control", 120 HighlightModifier::ControlFlow => "control",
117 HighlightModifier::Definition => "declaration", 121 HighlightModifier::Definition => "declaration",
122 HighlightModifier::Documentation => "documentation",
118 HighlightModifier::Mutable => "mutable", 123 HighlightModifier::Mutable => "mutable",
119 HighlightModifier::Unsafe => "unsafe", 124 HighlightModifier::Unsafe => "unsafe",
120 } 125 }
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
46macro_rules! noop {
47 ($expr:expr) => {
48 $expr
49 }
50}
51
46// comment 52// comment
47fn main() { 53fn 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() {
279fn test_highlight_doctest() { 291fn test_highlight_doctest() {
280 check_highlighting( 292 check_highlighting(
281 r#" 293 r#"
294struct Foo {
295 bar: bool,
296}
297
282impl Foo { 298impl 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