diff options
Diffstat (limited to 'crates/ide/src')
31 files changed, 2749 insertions, 1208 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs new file mode 100644 index 000000000..2e8e82b70 --- /dev/null +++ b/crates/ide/src/annotations.rs | |||
@@ -0,0 +1,937 @@ | |||
1 | use hir::Semantics; | ||
2 | use ide_db::{ | ||
3 | base_db::{FileId, FilePosition, FileRange, SourceDatabase}, | ||
4 | RootDatabase, SymbolKind, | ||
5 | }; | ||
6 | use syntax::TextRange; | ||
7 | |||
8 | use crate::{ | ||
9 | file_structure::file_structure, | ||
10 | fn_references::find_all_methods, | ||
11 | goto_implementation::goto_implementation, | ||
12 | references::find_all_refs, | ||
13 | runnables::{runnables, Runnable}, | ||
14 | NavigationTarget, RunnableKind, | ||
15 | }; | ||
16 | |||
17 | // Feature: Annotations | ||
18 | // | ||
19 | // Provides user with annotations above items for looking up references or impl blocks | ||
20 | // and running/debugging binaries. | ||
21 | #[derive(Debug)] | ||
22 | pub struct Annotation { | ||
23 | pub range: TextRange, | ||
24 | pub kind: AnnotationKind, | ||
25 | } | ||
26 | |||
27 | #[derive(Debug)] | ||
28 | pub enum AnnotationKind { | ||
29 | Runnable { debug: bool, runnable: Runnable }, | ||
30 | HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> }, | ||
31 | HasReferences { position: FilePosition, data: Option<Vec<FileRange>> }, | ||
32 | } | ||
33 | |||
34 | pub struct AnnotationConfig { | ||
35 | pub binary_target: bool, | ||
36 | pub annotate_runnables: bool, | ||
37 | pub annotate_impls: bool, | ||
38 | pub annotate_references: bool, | ||
39 | pub annotate_method_references: bool, | ||
40 | pub run: bool, | ||
41 | pub debug: bool, | ||
42 | } | ||
43 | |||
44 | pub(crate) fn annotations( | ||
45 | db: &RootDatabase, | ||
46 | file_id: FileId, | ||
47 | config: AnnotationConfig, | ||
48 | ) -> Vec<Annotation> { | ||
49 | let mut annotations = Vec::default(); | ||
50 | |||
51 | if config.annotate_runnables { | ||
52 | for runnable in runnables(db, file_id) { | ||
53 | if should_skip_runnable(&runnable.kind, config.binary_target) { | ||
54 | continue; | ||
55 | } | ||
56 | |||
57 | let action = runnable.action(); | ||
58 | let range = runnable.nav.full_range; | ||
59 | |||
60 | if config.run { | ||
61 | annotations.push(Annotation { | ||
62 | range, | ||
63 | |||
64 | // FIXME: This one allocates without reason if run is enabled, but debug is disabled | ||
65 | kind: AnnotationKind::Runnable { debug: false, runnable: runnable.clone() }, | ||
66 | }); | ||
67 | } | ||
68 | |||
69 | if action.debugee && config.debug { | ||
70 | annotations.push(Annotation { | ||
71 | range, | ||
72 | kind: AnnotationKind::Runnable { debug: true, runnable }, | ||
73 | }); | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | file_structure(&db.parse(file_id).tree()) | ||
79 | .into_iter() | ||
80 | .filter(|node| { | ||
81 | matches!( | ||
82 | node.kind, | ||
83 | SymbolKind::Trait | ||
84 | | SymbolKind::Struct | ||
85 | | SymbolKind::Enum | ||
86 | | SymbolKind::Union | ||
87 | | SymbolKind::Const | ||
88 | ) | ||
89 | }) | ||
90 | .for_each(|node| { | ||
91 | if config.annotate_impls && node.kind != SymbolKind::Const { | ||
92 | annotations.push(Annotation { | ||
93 | range: node.node_range, | ||
94 | kind: AnnotationKind::HasImpls { | ||
95 | position: FilePosition { file_id, offset: node.navigation_range.start() }, | ||
96 | data: None, | ||
97 | }, | ||
98 | }); | ||
99 | } | ||
100 | |||
101 | if config.annotate_references { | ||
102 | annotations.push(Annotation { | ||
103 | range: node.node_range, | ||
104 | kind: AnnotationKind::HasReferences { | ||
105 | position: FilePosition { file_id, offset: node.navigation_range.start() }, | ||
106 | data: None, | ||
107 | }, | ||
108 | }); | ||
109 | } | ||
110 | }); | ||
111 | |||
112 | if config.annotate_method_references { | ||
113 | annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation { | ||
114 | range: method.range, | ||
115 | kind: AnnotationKind::HasReferences { | ||
116 | position: FilePosition { file_id, offset: method.range.start() }, | ||
117 | data: None, | ||
118 | }, | ||
119 | })); | ||
120 | } | ||
121 | |||
122 | annotations | ||
123 | } | ||
124 | |||
125 | pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation { | ||
126 | match annotation.kind { | ||
127 | AnnotationKind::HasImpls { position, ref mut data } => { | ||
128 | *data = goto_implementation(db, position).map(|range| range.info); | ||
129 | } | ||
130 | AnnotationKind::HasReferences { position, ref mut data } => { | ||
131 | *data = find_all_refs(&Semantics::new(db), position, None).map(|result| { | ||
132 | result | ||
133 | .references | ||
134 | .into_iter() | ||
135 | .map(|(file_id, access)| { | ||
136 | access.into_iter().map(move |(range, _)| FileRange { file_id, range }) | ||
137 | }) | ||
138 | .flatten() | ||
139 | .collect() | ||
140 | }); | ||
141 | } | ||
142 | _ => {} | ||
143 | }; | ||
144 | |||
145 | annotation | ||
146 | } | ||
147 | |||
148 | fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool { | ||
149 | match kind { | ||
150 | RunnableKind::Bin => !binary_target, | ||
151 | _ => false, | ||
152 | } | ||
153 | } | ||
154 | |||
155 | #[cfg(test)] | ||
156 | mod tests { | ||
157 | use expect_test::{expect, Expect}; | ||
158 | |||
159 | use crate::{fixture, Annotation, AnnotationConfig}; | ||
160 | |||
161 | fn check(ra_fixture: &str, expect: Expect) { | ||
162 | let (analysis, file_id) = fixture::file(ra_fixture); | ||
163 | |||
164 | let annotations: Vec<Annotation> = analysis | ||
165 | .annotations( | ||
166 | file_id, | ||
167 | AnnotationConfig { | ||
168 | binary_target: true, | ||
169 | annotate_runnables: true, | ||
170 | annotate_impls: true, | ||
171 | annotate_references: true, | ||
172 | annotate_method_references: true, | ||
173 | run: true, | ||
174 | debug: true, | ||
175 | }, | ||
176 | ) | ||
177 | .unwrap() | ||
178 | .into_iter() | ||
179 | .map(|annotation| analysis.resolve_annotation(annotation).unwrap()) | ||
180 | .collect(); | ||
181 | |||
182 | expect.assert_debug_eq(&annotations); | ||
183 | } | ||
184 | |||
185 | #[test] | ||
186 | fn const_annotations() { | ||
187 | check( | ||
188 | r#" | ||
189 | const DEMO: i32 = 123; | ||
190 | |||
191 | const UNUSED: i32 = 123; | ||
192 | |||
193 | fn main() { | ||
194 | let hello = DEMO; | ||
195 | } | ||
196 | "#, | ||
197 | expect![[r#" | ||
198 | [ | ||
199 | Annotation { | ||
200 | range: 50..85, | ||
201 | kind: Runnable { | ||
202 | debug: false, | ||
203 | runnable: Runnable { | ||
204 | nav: NavigationTarget { | ||
205 | file_id: FileId( | ||
206 | 0, | ||
207 | ), | ||
208 | full_range: 50..85, | ||
209 | focus_range: 53..57, | ||
210 | name: "main", | ||
211 | kind: Function, | ||
212 | }, | ||
213 | kind: Bin, | ||
214 | cfg: None, | ||
215 | }, | ||
216 | }, | ||
217 | }, | ||
218 | Annotation { | ||
219 | range: 50..85, | ||
220 | kind: Runnable { | ||
221 | debug: true, | ||
222 | runnable: Runnable { | ||
223 | nav: NavigationTarget { | ||
224 | file_id: FileId( | ||
225 | 0, | ||
226 | ), | ||
227 | full_range: 50..85, | ||
228 | focus_range: 53..57, | ||
229 | name: "main", | ||
230 | kind: Function, | ||
231 | }, | ||
232 | kind: Bin, | ||
233 | cfg: None, | ||
234 | }, | ||
235 | }, | ||
236 | }, | ||
237 | Annotation { | ||
238 | range: 0..22, | ||
239 | kind: HasReferences { | ||
240 | position: FilePosition { | ||
241 | file_id: FileId( | ||
242 | 0, | ||
243 | ), | ||
244 | offset: 6, | ||
245 | }, | ||
246 | data: Some( | ||
247 | [ | ||
248 | FileRange { | ||
249 | file_id: FileId( | ||
250 | 0, | ||
251 | ), | ||
252 | range: 78..82, | ||
253 | }, | ||
254 | ], | ||
255 | ), | ||
256 | }, | ||
257 | }, | ||
258 | Annotation { | ||
259 | range: 24..48, | ||
260 | kind: HasReferences { | ||
261 | position: FilePosition { | ||
262 | file_id: FileId( | ||
263 | 0, | ||
264 | ), | ||
265 | offset: 30, | ||
266 | }, | ||
267 | data: Some( | ||
268 | [], | ||
269 | ), | ||
270 | }, | ||
271 | }, | ||
272 | Annotation { | ||
273 | range: 53..57, | ||
274 | kind: HasReferences { | ||
275 | position: FilePosition { | ||
276 | file_id: FileId( | ||
277 | 0, | ||
278 | ), | ||
279 | offset: 53, | ||
280 | }, | ||
281 | data: Some( | ||
282 | [], | ||
283 | ), | ||
284 | }, | ||
285 | }, | ||
286 | ] | ||
287 | "#]], | ||
288 | ); | ||
289 | } | ||
290 | |||
291 | #[test] | ||
292 | fn struct_references_annotations() { | ||
293 | check( | ||
294 | r#" | ||
295 | struct Test; | ||
296 | |||
297 | fn main() { | ||
298 | let test = Test; | ||
299 | } | ||
300 | "#, | ||
301 | expect![[r#" | ||
302 | [ | ||
303 | Annotation { | ||
304 | range: 14..48, | ||
305 | kind: Runnable { | ||
306 | debug: false, | ||
307 | runnable: Runnable { | ||
308 | nav: NavigationTarget { | ||
309 | file_id: FileId( | ||
310 | 0, | ||
311 | ), | ||
312 | full_range: 14..48, | ||
313 | focus_range: 17..21, | ||
314 | name: "main", | ||
315 | kind: Function, | ||
316 | }, | ||
317 | kind: Bin, | ||
318 | cfg: None, | ||
319 | }, | ||
320 | }, | ||
321 | }, | ||
322 | Annotation { | ||
323 | range: 14..48, | ||
324 | kind: Runnable { | ||
325 | debug: true, | ||
326 | runnable: Runnable { | ||
327 | nav: NavigationTarget { | ||
328 | file_id: FileId( | ||
329 | 0, | ||
330 | ), | ||
331 | full_range: 14..48, | ||
332 | focus_range: 17..21, | ||
333 | name: "main", | ||
334 | kind: Function, | ||
335 | }, | ||
336 | kind: Bin, | ||
337 | cfg: None, | ||
338 | }, | ||
339 | }, | ||
340 | }, | ||
341 | Annotation { | ||
342 | range: 0..12, | ||
343 | kind: HasImpls { | ||
344 | position: FilePosition { | ||
345 | file_id: FileId( | ||
346 | 0, | ||
347 | ), | ||
348 | offset: 7, | ||
349 | }, | ||
350 | data: Some( | ||
351 | [], | ||
352 | ), | ||
353 | }, | ||
354 | }, | ||
355 | Annotation { | ||
356 | range: 0..12, | ||
357 | kind: HasReferences { | ||
358 | position: FilePosition { | ||
359 | file_id: FileId( | ||
360 | 0, | ||
361 | ), | ||
362 | offset: 7, | ||
363 | }, | ||
364 | data: Some( | ||
365 | [ | ||
366 | FileRange { | ||
367 | file_id: FileId( | ||
368 | 0, | ||
369 | ), | ||
370 | range: 41..45, | ||
371 | }, | ||
372 | ], | ||
373 | ), | ||
374 | }, | ||
375 | }, | ||
376 | Annotation { | ||
377 | range: 17..21, | ||
378 | kind: HasReferences { | ||
379 | position: FilePosition { | ||
380 | file_id: FileId( | ||
381 | 0, | ||
382 | ), | ||
383 | offset: 17, | ||
384 | }, | ||
385 | data: Some( | ||
386 | [], | ||
387 | ), | ||
388 | }, | ||
389 | }, | ||
390 | ] | ||
391 | "#]], | ||
392 | ); | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn struct_and_trait_impls_annotations() { | ||
397 | check( | ||
398 | r#" | ||
399 | struct Test; | ||
400 | |||
401 | trait MyCoolTrait {} | ||
402 | |||
403 | impl MyCoolTrait for Test {} | ||
404 | |||
405 | fn main() { | ||
406 | let test = Test; | ||
407 | } | ||
408 | "#, | ||
409 | expect![[r#" | ||
410 | [ | ||
411 | Annotation { | ||
412 | range: 66..100, | ||
413 | kind: Runnable { | ||
414 | debug: false, | ||
415 | runnable: Runnable { | ||
416 | nav: NavigationTarget { | ||
417 | file_id: FileId( | ||
418 | 0, | ||
419 | ), | ||
420 | full_range: 66..100, | ||
421 | focus_range: 69..73, | ||
422 | name: "main", | ||
423 | kind: Function, | ||
424 | }, | ||
425 | kind: Bin, | ||
426 | cfg: None, | ||
427 | }, | ||
428 | }, | ||
429 | }, | ||
430 | Annotation { | ||
431 | range: 66..100, | ||
432 | kind: Runnable { | ||
433 | debug: true, | ||
434 | runnable: Runnable { | ||
435 | nav: NavigationTarget { | ||
436 | file_id: FileId( | ||
437 | 0, | ||
438 | ), | ||
439 | full_range: 66..100, | ||
440 | focus_range: 69..73, | ||
441 | name: "main", | ||
442 | kind: Function, | ||
443 | }, | ||
444 | kind: Bin, | ||
445 | cfg: None, | ||
446 | }, | ||
447 | }, | ||
448 | }, | ||
449 | Annotation { | ||
450 | range: 0..12, | ||
451 | kind: HasImpls { | ||
452 | position: FilePosition { | ||
453 | file_id: FileId( | ||
454 | 0, | ||
455 | ), | ||
456 | offset: 7, | ||
457 | }, | ||
458 | data: Some( | ||
459 | [ | ||
460 | NavigationTarget { | ||
461 | file_id: FileId( | ||
462 | 0, | ||
463 | ), | ||
464 | full_range: 36..64, | ||
465 | focus_range: 57..61, | ||
466 | name: "impl", | ||
467 | kind: Impl, | ||
468 | }, | ||
469 | ], | ||
470 | ), | ||
471 | }, | ||
472 | }, | ||
473 | Annotation { | ||
474 | range: 0..12, | ||
475 | kind: HasReferences { | ||
476 | position: FilePosition { | ||
477 | file_id: FileId( | ||
478 | 0, | ||
479 | ), | ||
480 | offset: 7, | ||
481 | }, | ||
482 | data: Some( | ||
483 | [ | ||
484 | FileRange { | ||
485 | file_id: FileId( | ||
486 | 0, | ||
487 | ), | ||
488 | range: 57..61, | ||
489 | }, | ||
490 | FileRange { | ||
491 | file_id: FileId( | ||
492 | 0, | ||
493 | ), | ||
494 | range: 93..97, | ||
495 | }, | ||
496 | ], | ||
497 | ), | ||
498 | }, | ||
499 | }, | ||
500 | Annotation { | ||
501 | range: 14..34, | ||
502 | kind: HasImpls { | ||
503 | position: FilePosition { | ||
504 | file_id: FileId( | ||
505 | 0, | ||
506 | ), | ||
507 | offset: 20, | ||
508 | }, | ||
509 | data: Some( | ||
510 | [ | ||
511 | NavigationTarget { | ||
512 | file_id: FileId( | ||
513 | 0, | ||
514 | ), | ||
515 | full_range: 36..64, | ||
516 | focus_range: 57..61, | ||
517 | name: "impl", | ||
518 | kind: Impl, | ||
519 | }, | ||
520 | ], | ||
521 | ), | ||
522 | }, | ||
523 | }, | ||
524 | Annotation { | ||
525 | range: 14..34, | ||
526 | kind: HasReferences { | ||
527 | position: FilePosition { | ||
528 | file_id: FileId( | ||
529 | 0, | ||
530 | ), | ||
531 | offset: 20, | ||
532 | }, | ||
533 | data: Some( | ||
534 | [ | ||
535 | FileRange { | ||
536 | file_id: FileId( | ||
537 | 0, | ||
538 | ), | ||
539 | range: 41..52, | ||
540 | }, | ||
541 | ], | ||
542 | ), | ||
543 | }, | ||
544 | }, | ||
545 | Annotation { | ||
546 | range: 69..73, | ||
547 | kind: HasReferences { | ||
548 | position: FilePosition { | ||
549 | file_id: FileId( | ||
550 | 0, | ||
551 | ), | ||
552 | offset: 69, | ||
553 | }, | ||
554 | data: Some( | ||
555 | [], | ||
556 | ), | ||
557 | }, | ||
558 | }, | ||
559 | ] | ||
560 | "#]], | ||
561 | ); | ||
562 | } | ||
563 | |||
564 | #[test] | ||
565 | fn runnable_annotation() { | ||
566 | check( | ||
567 | r#" | ||
568 | fn main() {} | ||
569 | "#, | ||
570 | expect![[r#" | ||
571 | [ | ||
572 | Annotation { | ||
573 | range: 0..12, | ||
574 | kind: Runnable { | ||
575 | debug: false, | ||
576 | runnable: Runnable { | ||
577 | nav: NavigationTarget { | ||
578 | file_id: FileId( | ||
579 | 0, | ||
580 | ), | ||
581 | full_range: 0..12, | ||
582 | focus_range: 3..7, | ||
583 | name: "main", | ||
584 | kind: Function, | ||
585 | }, | ||
586 | kind: Bin, | ||
587 | cfg: None, | ||
588 | }, | ||
589 | }, | ||
590 | }, | ||
591 | Annotation { | ||
592 | range: 0..12, | ||
593 | kind: Runnable { | ||
594 | debug: true, | ||
595 | runnable: Runnable { | ||
596 | nav: NavigationTarget { | ||
597 | file_id: FileId( | ||
598 | 0, | ||
599 | ), | ||
600 | full_range: 0..12, | ||
601 | focus_range: 3..7, | ||
602 | name: "main", | ||
603 | kind: Function, | ||
604 | }, | ||
605 | kind: Bin, | ||
606 | cfg: None, | ||
607 | }, | ||
608 | }, | ||
609 | }, | ||
610 | Annotation { | ||
611 | range: 3..7, | ||
612 | kind: HasReferences { | ||
613 | position: FilePosition { | ||
614 | file_id: FileId( | ||
615 | 0, | ||
616 | ), | ||
617 | offset: 3, | ||
618 | }, | ||
619 | data: Some( | ||
620 | [], | ||
621 | ), | ||
622 | }, | ||
623 | }, | ||
624 | ] | ||
625 | "#]], | ||
626 | ); | ||
627 | } | ||
628 | |||
629 | #[test] | ||
630 | fn method_annotations() { | ||
631 | check( | ||
632 | r#" | ||
633 | struct Test; | ||
634 | |||
635 | impl Test { | ||
636 | fn self_by_ref(&self) {} | ||
637 | } | ||
638 | |||
639 | fn main() { | ||
640 | Test.self_by_ref(); | ||
641 | } | ||
642 | "#, | ||
643 | expect![[r#" | ||
644 | [ | ||
645 | Annotation { | ||
646 | range: 58..95, | ||
647 | kind: Runnable { | ||
648 | debug: false, | ||
649 | runnable: Runnable { | ||
650 | nav: NavigationTarget { | ||
651 | file_id: FileId( | ||
652 | 0, | ||
653 | ), | ||
654 | full_range: 58..95, | ||
655 | focus_range: 61..65, | ||
656 | name: "main", | ||
657 | kind: Function, | ||
658 | }, | ||
659 | kind: Bin, | ||
660 | cfg: None, | ||
661 | }, | ||
662 | }, | ||
663 | }, | ||
664 | Annotation { | ||
665 | range: 58..95, | ||
666 | kind: Runnable { | ||
667 | debug: true, | ||
668 | runnable: Runnable { | ||
669 | nav: NavigationTarget { | ||
670 | file_id: FileId( | ||
671 | 0, | ||
672 | ), | ||
673 | full_range: 58..95, | ||
674 | focus_range: 61..65, | ||
675 | name: "main", | ||
676 | kind: Function, | ||
677 | }, | ||
678 | kind: Bin, | ||
679 | cfg: None, | ||
680 | }, | ||
681 | }, | ||
682 | }, | ||
683 | Annotation { | ||
684 | range: 0..12, | ||
685 | kind: HasImpls { | ||
686 | position: FilePosition { | ||
687 | file_id: FileId( | ||
688 | 0, | ||
689 | ), | ||
690 | offset: 7, | ||
691 | }, | ||
692 | data: Some( | ||
693 | [ | ||
694 | NavigationTarget { | ||
695 | file_id: FileId( | ||
696 | 0, | ||
697 | ), | ||
698 | full_range: 14..56, | ||
699 | focus_range: 19..23, | ||
700 | name: "impl", | ||
701 | kind: Impl, | ||
702 | }, | ||
703 | ], | ||
704 | ), | ||
705 | }, | ||
706 | }, | ||
707 | Annotation { | ||
708 | range: 0..12, | ||
709 | kind: HasReferences { | ||
710 | position: FilePosition { | ||
711 | file_id: FileId( | ||
712 | 0, | ||
713 | ), | ||
714 | offset: 7, | ||
715 | }, | ||
716 | data: Some( | ||
717 | [ | ||
718 | FileRange { | ||
719 | file_id: FileId( | ||
720 | 0, | ||
721 | ), | ||
722 | range: 19..23, | ||
723 | }, | ||
724 | FileRange { | ||
725 | file_id: FileId( | ||
726 | 0, | ||
727 | ), | ||
728 | range: 74..78, | ||
729 | }, | ||
730 | ], | ||
731 | ), | ||
732 | }, | ||
733 | }, | ||
734 | Annotation { | ||
735 | range: 33..44, | ||
736 | kind: HasReferences { | ||
737 | position: FilePosition { | ||
738 | file_id: FileId( | ||
739 | 0, | ||
740 | ), | ||
741 | offset: 33, | ||
742 | }, | ||
743 | data: Some( | ||
744 | [ | ||
745 | FileRange { | ||
746 | file_id: FileId( | ||
747 | 0, | ||
748 | ), | ||
749 | range: 79..90, | ||
750 | }, | ||
751 | ], | ||
752 | ), | ||
753 | }, | ||
754 | }, | ||
755 | Annotation { | ||
756 | range: 61..65, | ||
757 | kind: HasReferences { | ||
758 | position: FilePosition { | ||
759 | file_id: FileId( | ||
760 | 0, | ||
761 | ), | ||
762 | offset: 61, | ||
763 | }, | ||
764 | data: Some( | ||
765 | [], | ||
766 | ), | ||
767 | }, | ||
768 | }, | ||
769 | ] | ||
770 | "#]], | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn test_annotations() { | ||
776 | check( | ||
777 | r#" | ||
778 | fn main() {} | ||
779 | |||
780 | mod tests { | ||
781 | #[test] | ||
782 | fn my_cool_test() {} | ||
783 | } | ||
784 | "#, | ||
785 | expect![[r#" | ||
786 | [ | ||
787 | Annotation { | ||
788 | range: 0..12, | ||
789 | kind: Runnable { | ||
790 | debug: false, | ||
791 | runnable: Runnable { | ||
792 | nav: NavigationTarget { | ||
793 | file_id: FileId( | ||
794 | 0, | ||
795 | ), | ||
796 | full_range: 0..12, | ||
797 | focus_range: 3..7, | ||
798 | name: "main", | ||
799 | kind: Function, | ||
800 | }, | ||
801 | kind: Bin, | ||
802 | cfg: None, | ||
803 | }, | ||
804 | }, | ||
805 | }, | ||
806 | Annotation { | ||
807 | range: 0..12, | ||
808 | kind: Runnable { | ||
809 | debug: true, | ||
810 | runnable: Runnable { | ||
811 | nav: NavigationTarget { | ||
812 | file_id: FileId( | ||
813 | 0, | ||
814 | ), | ||
815 | full_range: 0..12, | ||
816 | focus_range: 3..7, | ||
817 | name: "main", | ||
818 | kind: Function, | ||
819 | }, | ||
820 | kind: Bin, | ||
821 | cfg: None, | ||
822 | }, | ||
823 | }, | ||
824 | }, | ||
825 | Annotation { | ||
826 | range: 14..64, | ||
827 | kind: Runnable { | ||
828 | debug: false, | ||
829 | runnable: Runnable { | ||
830 | nav: NavigationTarget { | ||
831 | file_id: FileId( | ||
832 | 0, | ||
833 | ), | ||
834 | full_range: 14..64, | ||
835 | focus_range: 18..23, | ||
836 | name: "tests", | ||
837 | kind: Module, | ||
838 | }, | ||
839 | kind: TestMod { | ||
840 | path: "tests", | ||
841 | }, | ||
842 | cfg: None, | ||
843 | }, | ||
844 | }, | ||
845 | }, | ||
846 | Annotation { | ||
847 | range: 14..64, | ||
848 | kind: Runnable { | ||
849 | debug: true, | ||
850 | runnable: Runnable { | ||
851 | nav: NavigationTarget { | ||
852 | file_id: FileId( | ||
853 | 0, | ||
854 | ), | ||
855 | full_range: 14..64, | ||
856 | focus_range: 18..23, | ||
857 | name: "tests", | ||
858 | kind: Module, | ||
859 | }, | ||
860 | kind: TestMod { | ||
861 | path: "tests", | ||
862 | }, | ||
863 | cfg: None, | ||
864 | }, | ||
865 | }, | ||
866 | }, | ||
867 | Annotation { | ||
868 | range: 30..62, | ||
869 | kind: Runnable { | ||
870 | debug: false, | ||
871 | runnable: Runnable { | ||
872 | nav: NavigationTarget { | ||
873 | file_id: FileId( | ||
874 | 0, | ||
875 | ), | ||
876 | full_range: 30..62, | ||
877 | focus_range: 45..57, | ||
878 | name: "my_cool_test", | ||
879 | kind: Function, | ||
880 | }, | ||
881 | kind: Test { | ||
882 | test_id: Path( | ||
883 | "tests::my_cool_test", | ||
884 | ), | ||
885 | attr: TestAttr { | ||
886 | ignore: false, | ||
887 | }, | ||
888 | }, | ||
889 | cfg: None, | ||
890 | }, | ||
891 | }, | ||
892 | }, | ||
893 | Annotation { | ||
894 | range: 30..62, | ||
895 | kind: Runnable { | ||
896 | debug: true, | ||
897 | runnable: Runnable { | ||
898 | nav: NavigationTarget { | ||
899 | file_id: FileId( | ||
900 | 0, | ||
901 | ), | ||
902 | full_range: 30..62, | ||
903 | focus_range: 45..57, | ||
904 | name: "my_cool_test", | ||
905 | kind: Function, | ||
906 | }, | ||
907 | kind: Test { | ||
908 | test_id: Path( | ||
909 | "tests::my_cool_test", | ||
910 | ), | ||
911 | attr: TestAttr { | ||
912 | ignore: false, | ||
913 | }, | ||
914 | }, | ||
915 | cfg: None, | ||
916 | }, | ||
917 | }, | ||
918 | }, | ||
919 | Annotation { | ||
920 | range: 3..7, | ||
921 | kind: HasReferences { | ||
922 | position: FilePosition { | ||
923 | file_id: FileId( | ||
924 | 0, | ||
925 | ), | ||
926 | offset: 3, | ||
927 | }, | ||
928 | data: Some( | ||
929 | [], | ||
930 | ), | ||
931 | }, | ||
932 | }, | ||
933 | ] | ||
934 | "#]], | ||
935 | ); | ||
936 | } | ||
937 | } | ||
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index e8999a7f3..b848945d7 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs | |||
@@ -47,11 +47,11 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
47 | 47 | ||
48 | let mut calls = CallLocations::default(); | 48 | let mut calls = CallLocations::default(); |
49 | 49 | ||
50 | for (&file_id, references) in refs.info.references().iter() { | 50 | for (file_id, references) in refs.references { |
51 | let file = sema.parse(file_id); | 51 | let file = sema.parse(file_id); |
52 | let file = file.syntax(); | 52 | let file = file.syntax(); |
53 | for reference in references { | 53 | for (r_range, _) in references { |
54 | let token = file.token_at_offset(reference.range.start()).next()?; | 54 | let token = file.token_at_offset(r_range.start()).next()?; |
55 | let token = sema.descend_into_macros(token); | 55 | let token = sema.descend_into_macros(token); |
56 | let syntax = token.parent(); | 56 | let syntax = token.parent(); |
57 | 57 | ||
@@ -61,7 +61,7 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
61 | let def = sema.to_def(&fn_)?; | 61 | let def = sema.to_def(&fn_)?; |
62 | def.try_to_nav(sema.db) | 62 | def.try_to_nav(sema.db) |
63 | }) { | 63 | }) { |
64 | let relative_range = reference.range; | 64 | let relative_range = r_range; |
65 | calls.add(&nav, relative_range); | 65 | calls.add(&nav, relative_range); |
66 | } | 66 | } |
67 | } | 67 | } |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b35bc2bae..fe32f39b6 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -10,15 +10,16 @@ mod field_shorthand; | |||
10 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
11 | 11 | ||
12 | use hir::{ | 12 | use hir::{ |
13 | db::AstDatabase, | ||
13 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, | 14 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, |
14 | Semantics, | 15 | InFile, Semantics, |
15 | }; | 16 | }; |
16 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | 17 | use ide_db::{base_db::SourceDatabase, RootDatabase}; |
17 | use itertools::Itertools; | 18 | use itertools::Itertools; |
18 | use rustc_hash::FxHashSet; | 19 | use rustc_hash::FxHashSet; |
19 | use syntax::{ | 20 | use syntax::{ |
20 | ast::{self, AstNode}, | 21 | ast::{self, AstNode}, |
21 | SyntaxNode, TextRange, | 22 | SyntaxNode, SyntaxNodePtr, TextRange, |
22 | }; | 23 | }; |
23 | use text_edit::TextEdit; | 24 | use text_edit::TextEdit; |
24 | 25 | ||
@@ -136,6 +137,9 @@ pub(crate) fn diagnostics( | |||
136 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | 137 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { |
137 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 138 | res.borrow_mut().push(warning_with_fix(d, &sema)); |
138 | }) | 139 | }) |
140 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { | ||
141 | res.borrow_mut().push(warning_with_fix(d, &sema)); | ||
142 | }) | ||
139 | .on::<hir::diagnostics::InactiveCode, _>(|d| { | 143 | .on::<hir::diagnostics::InactiveCode, _>(|d| { |
140 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. | 144 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. |
141 | if d.display_source().file_id.expansion_info(db).is_some() { | 145 | if d.display_source().file_id.expansion_info(db).is_some() { |
@@ -144,20 +148,38 @@ pub(crate) fn diagnostics( | |||
144 | 148 | ||
145 | // Override severity and mark as unused. | 149 | // Override severity and mark as unused. |
146 | res.borrow_mut().push( | 150 | res.borrow_mut().push( |
147 | Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) | 151 | Diagnostic::hint( |
148 | .with_unused(true) | 152 | sema.diagnostics_display_range(d.display_source()).range, |
149 | .with_code(Some(d.code())), | 153 | d.message(), |
154 | ) | ||
155 | .with_unused(true) | ||
156 | .with_code(Some(d.code())), | ||
150 | ); | 157 | ); |
151 | }) | 158 | }) |
152 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { | 159 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { |
153 | // Use more accurate position if available. | 160 | // Use more accurate position if available. |
154 | let display_range = | 161 | let display_range = d |
155 | d.precise_location.unwrap_or_else(|| sema.diagnostics_display_range(d).range); | 162 | .precise_location |
163 | .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range); | ||
156 | 164 | ||
157 | // FIXME: it would be nice to tell the user whether proc macros are currently disabled | 165 | // FIXME: it would be nice to tell the user whether proc macros are currently disabled |
158 | res.borrow_mut() | 166 | res.borrow_mut() |
159 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); | 167 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); |
160 | }) | 168 | }) |
169 | .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| { | ||
170 | let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| { | ||
171 | d.node | ||
172 | .to_node(&root) | ||
173 | .path() | ||
174 | .and_then(|it| it.segment()) | ||
175 | .and_then(|it| it.name_ref()) | ||
176 | .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax()))) | ||
177 | }); | ||
178 | let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source()); | ||
179 | let display_range = sema.diagnostics_display_range(diagnostics).range; | ||
180 | res.borrow_mut() | ||
181 | .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code()))); | ||
182 | }) | ||
161 | // Only collect experimental diagnostics when they're enabled. | 183 | // Only collect experimental diagnostics when they're enabled. |
162 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | 184 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) |
163 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | 185 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); |
@@ -167,8 +189,11 @@ pub(crate) fn diagnostics( | |||
167 | // Diagnostics not handled above get no fix and default treatment. | 189 | // Diagnostics not handled above get no fix and default treatment. |
168 | .build(|d| { | 190 | .build(|d| { |
169 | res.borrow_mut().push( | 191 | res.borrow_mut().push( |
170 | Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()) | 192 | Diagnostic::error( |
171 | .with_code(Some(d.code())), | 193 | sema.diagnostics_display_range(d.display_source()).range, |
194 | d.message(), | ||
195 | ) | ||
196 | .with_code(Some(d.code())), | ||
172 | ); | 197 | ); |
173 | }); | 198 | }); |
174 | 199 | ||
@@ -180,13 +205,13 @@ pub(crate) fn diagnostics( | |||
180 | } | 205 | } |
181 | 206 | ||
182 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 207 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
183 | Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()) | 208 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
184 | .with_fix(d.fix(&sema)) | 209 | .with_fix(d.fix(&sema)) |
185 | .with_code(Some(d.code())) | 210 | .with_code(Some(d.code())) |
186 | } | 211 | } |
187 | 212 | ||
188 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 213 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
189 | Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) | 214 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
190 | .with_fix(d.fix(&sema)) | 215 | .with_fix(d.fix(&sema)) |
191 | .with_code(Some(d.code())) | 216 | .with_code(Some(d.code())) |
192 | } | 217 | } |
@@ -643,6 +668,29 @@ fn test_fn() { | |||
643 | } | 668 | } |
644 | 669 | ||
645 | #[test] | 670 | #[test] |
671 | fn test_unresolved_macro_range() { | ||
672 | check_expect( | ||
673 | r#"foo::bar!(92);"#, | ||
674 | expect![[r#" | ||
675 | [ | ||
676 | Diagnostic { | ||
677 | message: "unresolved macro call", | ||
678 | range: 5..8, | ||
679 | severity: Error, | ||
680 | fix: None, | ||
681 | unused: false, | ||
682 | code: Some( | ||
683 | DiagnosticCode( | ||
684 | "unresolved-macro-call", | ||
685 | ), | ||
686 | ), | ||
687 | }, | ||
688 | ] | ||
689 | "#]], | ||
690 | ); | ||
691 | } | ||
692 | |||
693 | #[test] | ||
646 | fn range_mapping_out_of_macros() { | 694 | fn range_mapping_out_of_macros() { |
647 | // FIXME: this is very wrong, but somewhat tricky to fix. | 695 | // FIXME: this is very wrong, but somewhat tricky to fix. |
648 | check_fix( | 696 | check_fix( |
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index e4335119b..cbfc66ab3 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -4,7 +4,7 @@ use hir::{ | |||
4 | db::AstDatabase, | 4 | db::AstDatabase, |
5 | diagnostics::{ | 5 | diagnostics::{ |
6 | Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField, | 6 | Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField, |
7 | RemoveThisSemicolon, UnresolvedModule, | 7 | RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnresolvedModule, |
8 | }, | 8 | }, |
9 | HasSource, HirDisplay, InFile, Semantics, VariantDef, | 9 | HasSource, HirDisplay, InFile, Semantics, VariantDef, |
10 | }; | 10 | }; |
@@ -15,8 +15,8 @@ use ide_db::{ | |||
15 | }; | 15 | }; |
16 | use syntax::{ | 16 | use syntax::{ |
17 | algo, | 17 | algo, |
18 | ast::{self, edit::IndentLevel, make}, | 18 | ast::{self, edit::IndentLevel, make, ArgListOwner}, |
19 | AstNode, | 19 | AstNode, TextRange, |
20 | }; | 20 | }; |
21 | use text_edit::TextEdit; | 21 | use text_edit::TextEdit; |
22 | 22 | ||
@@ -140,7 +140,34 @@ impl DiagnosticWithFix for IncorrectCase { | |||
140 | rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; | 140 | rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; |
141 | 141 | ||
142 | let label = format!("Rename to {}", self.suggested_text); | 142 | let label = format!("Rename to {}", self.suggested_text); |
143 | Some(Fix::new(&label, rename_changes.info, rename_changes.range)) | 143 | Some(Fix::new(&label, rename_changes, frange.range)) |
144 | } | ||
145 | } | ||
146 | |||
147 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { | ||
148 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
149 | let root = sema.db.parse_or_expand(self.file)?; | ||
150 | let next_expr = self.next_expr.to_node(&root); | ||
151 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; | ||
152 | |||
153 | let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?; | ||
154 | let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range(); | ||
155 | let filter_map_args = filter_map_call.arg_list()?; | ||
156 | |||
157 | let range_to_replace = | ||
158 | TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end()); | ||
159 | let replacement = format!("find_map{}", filter_map_args.syntax().text()); | ||
160 | let trigger_range = next_expr.syntax().text_range(); | ||
161 | |||
162 | let edit = TextEdit::replace(range_to_replace, replacement); | ||
163 | |||
164 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | ||
165 | |||
166 | Some(Fix::new( | ||
167 | "Replace filter_map(..).next() with find_map()", | ||
168 | source_change, | ||
169 | trigger_range, | ||
170 | )) | ||
144 | } | 171 | } |
145 | } | 172 | } |
146 | 173 | ||
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs index 00e601244..198243466 100644 --- a/crates/ide/src/display/navigation_target.rs +++ b/crates/ide/src/display/navigation_target.rs | |||
@@ -7,6 +7,7 @@ use hir::{AssocItem, Documentation, FieldSource, HasAttrs, HasSource, InFile, Mo | |||
7 | use ide_db::{ | 7 | use ide_db::{ |
8 | base_db::{FileId, FileRange, SourceDatabase}, | 8 | base_db::{FileId, FileRange, SourceDatabase}, |
9 | symbol_index::FileSymbolKind, | 9 | symbol_index::FileSymbolKind, |
10 | SymbolKind, | ||
10 | }; | 11 | }; |
11 | use ide_db::{defs::Definition, RootDatabase}; | 12 | use ide_db::{defs::Definition, RootDatabase}; |
12 | use syntax::{ | 13 | use syntax::{ |
@@ -18,30 +19,6 @@ use crate::FileSymbol; | |||
18 | 19 | ||
19 | use super::short_label::ShortLabel; | 20 | use super::short_label::ShortLabel; |
20 | 21 | ||
21 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
22 | pub enum SymbolKind { | ||
23 | Module, | ||
24 | Impl, | ||
25 | Field, | ||
26 | TypeParam, | ||
27 | ConstParam, | ||
28 | LifetimeParam, | ||
29 | ValueParam, | ||
30 | SelfParam, | ||
31 | Local, | ||
32 | Label, | ||
33 | Function, | ||
34 | Const, | ||
35 | Static, | ||
36 | Struct, | ||
37 | Enum, | ||
38 | Variant, | ||
39 | Union, | ||
40 | TypeAlias, | ||
41 | Trait, | ||
42 | Macro, | ||
43 | } | ||
44 | |||
45 | /// `NavigationTarget` represents and element in the editor's UI which you can | 22 | /// `NavigationTarget` represents and element in the editor's UI which you can |
46 | /// click on to navigate to a particular piece of code. | 23 | /// click on to navigate to a particular piece of code. |
47 | /// | 24 | /// |
@@ -108,12 +85,16 @@ impl NavigationTarget { | |||
108 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | 85 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); |
109 | if let Some(src) = module.declaration_source(db) { | 86 | if let Some(src) = module.declaration_source(db) { |
110 | let node = src.as_ref().map(|it| it.syntax()); | 87 | let node = src.as_ref().map(|it| it.syntax()); |
111 | let frange = node.original_file_range(db); | 88 | let full_range = node.original_file_range(db); |
89 | let focus_range = src | ||
90 | .value | ||
91 | .name() | ||
92 | .map(|name| src.with_value(name.syntax()).original_file_range(db).range); | ||
112 | let mut res = NavigationTarget::from_syntax( | 93 | let mut res = NavigationTarget::from_syntax( |
113 | frange.file_id, | 94 | full_range.file_id, |
114 | name, | 95 | name, |
115 | None, | 96 | focus_range, |
116 | frange.range, | 97 | full_range.range, |
117 | SymbolKind::Module, | 98 | SymbolKind::Module, |
118 | ); | 99 | ); |
119 | res.docs = module.attrs(db).docs(); | 100 | res.docs = module.attrs(db).docs(); |
@@ -153,8 +134,7 @@ impl NavigationTarget { | |||
153 | node: InFile<&dyn ast::NameOwner>, | 134 | node: InFile<&dyn ast::NameOwner>, |
154 | kind: SymbolKind, | 135 | kind: SymbolKind, |
155 | ) -> NavigationTarget { | 136 | ) -> NavigationTarget { |
156 | let name = | 137 | let name = node.value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into()); |
157 | node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_")); | ||
158 | let focus_range = | 138 | let focus_range = |
159 | node.value.name().map(|it| node.with_value(it.syntax()).original_file_range(db).range); | 139 | node.value.name().map(|it| node.with_value(it.syntax()).original_file_range(db).range); |
160 | let frange = node.map(|it| it.syntax()).original_file_range(db); | 140 | let frange = node.map(|it| it.syntax()).original_file_range(db); |
@@ -197,6 +177,7 @@ impl ToNav for FileSymbol { | |||
197 | FileSymbolKind::Const => SymbolKind::Const, | 177 | FileSymbolKind::Const => SymbolKind::Const, |
198 | FileSymbolKind::Static => SymbolKind::Static, | 178 | FileSymbolKind::Static => SymbolKind::Static, |
199 | FileSymbolKind::Macro => SymbolKind::Macro, | 179 | FileSymbolKind::Macro => SymbolKind::Macro, |
180 | FileSymbolKind::Union => SymbolKind::Union, | ||
200 | }), | 181 | }), |
201 | full_range: self.range, | 182 | full_range: self.range, |
202 | focus_range: self.name_range, | 183 | focus_range: self.name_range, |
@@ -295,6 +276,7 @@ impl ToNav for hir::Module { | |||
295 | ModuleSource::Module(node) => { | 276 | ModuleSource::Module(node) => { |
296 | (node.syntax(), node.name().map(|it| it.syntax().text_range())) | 277 | (node.syntax(), node.name().map(|it| it.syntax().text_range())) |
297 | } | 278 | } |
279 | ModuleSource::BlockExpr(node) => (node.syntax(), None), | ||
298 | }; | 280 | }; |
299 | let frange = src.with_value(syntax).original_file_range(db); | 281 | let frange = src.with_value(syntax).original_file_range(db); |
300 | NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, SymbolKind::Module) | 282 | NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, SymbolKind::Module) |
@@ -457,13 +439,16 @@ impl TryToNav for hir::TypeParam { | |||
457 | fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { | 439 | fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { |
458 | let src = self.source(db)?; | 440 | let src = self.source(db)?; |
459 | let full_range = match &src.value { | 441 | let full_range = match &src.value { |
460 | Either::Left(it) => it.syntax().text_range(), | 442 | Either::Left(it) => it |
443 | .name() | ||
444 | .map_or_else(|| it.syntax().text_range(), |name| name.syntax().text_range()), | ||
461 | Either::Right(it) => it.syntax().text_range(), | 445 | Either::Right(it) => it.syntax().text_range(), |
462 | }; | 446 | }; |
463 | let focus_range = match &src.value { | 447 | let focus_range = match &src.value { |
464 | Either::Left(_) => None, | 448 | Either::Left(it) => it.name(), |
465 | Either::Right(it) => it.name().map(|it| it.syntax().text_range()), | 449 | Either::Right(it) => it.name(), |
466 | }; | 450 | } |
451 | .map(|it| it.syntax().text_range()); | ||
467 | Some(NavigationTarget { | 452 | Some(NavigationTarget { |
468 | file_id: src.file_id.original_file(db), | 453 | file_id: src.file_id.original_file(db), |
469 | name: self.name(db).to_string().into(), | 454 | name: self.name(db).to_string().into(), |
diff --git a/crates/ide/src/display/short_label.rs b/crates/ide/src/display/short_label.rs index 990f740b8..84b8883de 100644 --- a/crates/ide/src/display/short_label.rs +++ b/crates/ide/src/display/short_label.rs | |||
@@ -53,9 +53,19 @@ impl ShortLabel for ast::SourceFile { | |||
53 | } | 53 | } |
54 | } | 54 | } |
55 | 55 | ||
56 | impl ShortLabel for ast::BlockExpr { | ||
57 | fn short_label(&self) -> Option<String> { | ||
58 | None | ||
59 | } | ||
60 | } | ||
61 | |||
56 | impl ShortLabel for ast::TypeAlias { | 62 | impl ShortLabel for ast::TypeAlias { |
57 | fn short_label(&self) -> Option<String> { | 63 | fn short_label(&self) -> Option<String> { |
58 | short_label_from_node(self, "type ") | 64 | let mut buf = short_label_from_node(self, "type ")?; |
65 | if let Some(type_ref) = self.ty() { | ||
66 | format_to!(buf, " = {}", type_ref.syntax()); | ||
67 | } | ||
68 | Some(buf) | ||
59 | } | 69 | } |
60 | } | 70 | } |
61 | 71 | ||
@@ -90,7 +100,7 @@ impl ShortLabel for ast::Variant { | |||
90 | impl ShortLabel for ast::ConstParam { | 100 | impl ShortLabel for ast::ConstParam { |
91 | fn short_label(&self) -> Option<String> { | 101 | fn short_label(&self) -> Option<String> { |
92 | let mut buf = "const ".to_owned(); | 102 | let mut buf = "const ".to_owned(); |
93 | buf.push_str(self.name()?.text().as_str()); | 103 | buf.push_str(self.name()?.text()); |
94 | if let Some(type_ref) = self.ty() { | 104 | if let Some(type_ref) = self.ty() { |
95 | format_to!(buf, ": {}", type_ref.syntax()); | 105 | format_to!(buf, ": {}", type_ref.syntax()); |
96 | } | 106 | } |
@@ -117,6 +127,6 @@ where | |||
117 | { | 127 | { |
118 | let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default(); | 128 | let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default(); |
119 | buf.push_str(label); | 129 | buf.push_str(label); |
120 | buf.push_str(node.name()?.text().as_str()); | 130 | buf.push_str(node.name()?.text()); |
121 | Some(buf) | 131 | Some(buf) |
122 | } | 132 | } |
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index de10406bc..7bdd3cca3 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -9,8 +9,7 @@ use url::Url; | |||
9 | 9 | ||
10 | use hir::{ | 10 | use hir::{ |
11 | db::{DefDatabase, HirDatabase}, | 11 | db::{DefDatabase, HirDatabase}, |
12 | Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs, | 12 | Adt, AsAssocItem, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs, ModuleDef, |
13 | ModuleDef, | ||
14 | }; | 13 | }; |
15 | use ide_db::{ | 14 | use ide_db::{ |
16 | defs::{Definition, NameClass, NameRefClass}, | 15 | defs::{Definition, NameClass, NameRefClass}, |
@@ -221,14 +220,31 @@ fn rewrite_intra_doc_link( | |||
221 | }?; | 220 | }?; |
222 | let krate = resolved.module(db)?.krate(); | 221 | let krate = resolved.module(db)?.krate(); |
223 | let canonical_path = resolved.canonical_path(db)?; | 222 | let canonical_path = resolved.canonical_path(db)?; |
224 | let new_target = get_doc_url(db, &krate)? | 223 | let mut new_url = get_doc_url(db, &krate)? |
225 | .join(&format!("{}/", krate.display_name(db)?)) | 224 | .join(&format!("{}/", krate.display_name(db)?)) |
226 | .ok()? | 225 | .ok()? |
227 | .join(&canonical_path.replace("::", "/")) | 226 | .join(&canonical_path.replace("::", "/")) |
228 | .ok()? | 227 | .ok()? |
229 | .join(&get_symbol_filename(db, &resolved)?) | 228 | .join(&get_symbol_filename(db, &resolved)?) |
230 | .ok()? | 229 | .ok()?; |
231 | .into_string(); | 230 | |
231 | if let ModuleDef::Trait(t) = resolved { | ||
232 | let items = t.items(db); | ||
233 | if let Some(field_or_assoc_item) = items.iter().find_map(|assoc_item| { | ||
234 | if let Some(name) = assoc_item.name(db) { | ||
235 | if *link == format!("{}::{}", canonical_path, name) { | ||
236 | return Some(FieldOrAssocItem::AssocItem(*assoc_item)); | ||
237 | } | ||
238 | } | ||
239 | None | ||
240 | }) { | ||
241 | if let Some(fragment) = get_symbol_fragment(db, &field_or_assoc_item) { | ||
242 | new_url = new_url.join(&fragment).ok()?; | ||
243 | } | ||
244 | }; | ||
245 | } | ||
246 | |||
247 | let new_target = new_url.into_string(); | ||
232 | let new_title = strip_prefixes_suffixes(title); | 248 | let new_title = strip_prefixes_suffixes(title); |
233 | Some((new_target, new_title.to_string())) | 249 | Some((new_target, new_title.to_string())) |
234 | } | 250 | } |
@@ -412,7 +428,7 @@ fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<S | |||
412 | ModuleDef::Module(_) => "index.html".to_string(), | 428 | ModuleDef::Module(_) => "index.html".to_string(), |
413 | ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), | 429 | ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), |
414 | ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), | 430 | ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), |
415 | ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), | 431 | ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.name()), |
416 | ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), | 432 | ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), |
417 | ModuleDef::Variant(ev) => { | 433 | ModuleDef::Variant(ev) => { |
418 | format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) | 434 | format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) |
@@ -438,10 +454,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) | |||
438 | FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), | 454 | FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), |
439 | FieldOrAssocItem::AssocItem(assoc) => match assoc { | 455 | FieldOrAssocItem::AssocItem(assoc) => match assoc { |
440 | AssocItem::Function(function) => { | 456 | AssocItem::Function(function) => { |
441 | let is_trait_method = matches!( | 457 | let is_trait_method = function |
442 | function.as_assoc_item(db).map(|assoc| assoc.container(db)), | 458 | .as_assoc_item(db) |
443 | Some(AssocItemContainer::Trait(..)) | 459 | .and_then(|assoc| assoc.containing_trait(db)) |
444 | ); | 460 | .is_some(); |
445 | // This distinction may get more complicated when specialization is available. | 461 | // This distinction may get more complicated when specialization is available. |
446 | // Rustdoc makes this decision based on whether a method 'has defaultness'. | 462 | // Rustdoc makes this decision based on whether a method 'has defaultness'. |
447 | // Currently this is only the case for provided trait methods. | 463 | // Currently this is only the case for provided trait methods. |
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs index 17a540972..b540d04fe 100644 --- a/crates/ide/src/extend_selection.rs +++ b/crates/ide/src/extend_selection.rs | |||
@@ -12,15 +12,17 @@ use syntax::{ | |||
12 | 12 | ||
13 | use crate::FileRange; | 13 | use crate::FileRange; |
14 | 14 | ||
15 | // Feature: Extend Selection | 15 | // Feature: Expand and Shrink Selection |
16 | // | 16 | // |
17 | // Extends the current selection to the encompassing syntactic construct | 17 | // Extends or shrinks the current selection to the encompassing syntactic construct |
18 | // (expression, statement, item, module, etc). It works with multiple cursors. | 18 | // (expression, statement, item, module, etc). It works with multiple cursors. |
19 | // | 19 | // |
20 | // This is a standard LSP feature and not a protocol extension. | ||
21 | // | ||
20 | // |=== | 22 | // |=== |
21 | // | Editor | Shortcut | 23 | // | Editor | Shortcut |
22 | // | 24 | // |
23 | // | VS Code | kbd:[Ctrl+Shift+→] | 25 | // | VS Code | kbd:[Alt+Shift+→], kbd:[Alt+Shift+←] |
24 | // |=== | 26 | // |=== |
25 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 27 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
26 | let sema = Semantics::new(db); | 28 | let sema = Semantics::new(db); |
@@ -213,8 +215,8 @@ fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextSize) -> TextRange | |||
213 | let ws_text = ws.text(); | 215 | let ws_text = ws.text(); |
214 | let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start(); | 216 | let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start(); |
215 | let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start(); | 217 | let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start(); |
216 | let ws_suffix = &ws_text.as_str()[suffix]; | 218 | let ws_suffix = &ws_text[suffix]; |
217 | let ws_prefix = &ws_text.as_str()[prefix]; | 219 | let ws_prefix = &ws_text[prefix]; |
218 | if ws_text.contains('\n') && !ws_suffix.contains('\n') { | 220 | if ws_text.contains('\n') && !ws_suffix.contains('\n') { |
219 | if let Some(node) = ws.next_sibling_or_token() { | 221 | if let Some(node) = ws.next_sibling_or_token() { |
220 | let start = match ws_prefix.rfind('\n') { | 222 | let start = match ws_prefix.rfind('\n') { |
diff --git a/crates/ide/src/file_structure.rs b/crates/ide/src/file_structure.rs index 32556dad3..26793bdb4 100644 --- a/crates/ide/src/file_structure.rs +++ b/crates/ide/src/file_structure.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | use ide_db::SymbolKind; | ||
1 | use syntax::{ | 2 | use syntax::{ |
2 | ast::{self, AttrsOwner, GenericParamsOwner, NameOwner}, | 3 | ast::{self, AttrsOwner, GenericParamsOwner, NameOwner}, |
3 | match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent, | 4 | match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent, |
4 | }; | 5 | }; |
5 | 6 | ||
6 | use crate::SymbolKind; | ||
7 | |||
8 | #[derive(Debug, Clone)] | 7 | #[derive(Debug, Clone)] |
9 | pub struct StructureNode { | 8 | pub struct StructureNode { |
10 | pub parent: Option<usize>, | 9 | pub parent: Option<usize>, |
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs index cc8218885..cc6641ba1 100644 --- a/crates/ide/src/fixture.rs +++ b/crates/ide/src/fixture.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | //! Utilities for creating `Analysis` instances for tests. | 1 | //! Utilities for creating `Analysis` instances for tests. |
2 | use ide_db::base_db::fixture::ChangeFixture; | 2 | use ide_db::base_db::fixture::ChangeFixture; |
3 | use syntax::{TextRange, TextSize}; | ||
3 | use test_utils::{extract_annotations, RangeOrOffset}; | 4 | use test_utils::{extract_annotations, RangeOrOffset}; |
4 | 5 | ||
5 | use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; | 6 | use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; |
@@ -68,3 +69,18 @@ pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(Fil | |||
68 | .collect(); | 69 | .collect(); |
69 | (host.analysis(), FilePosition { file_id, offset }, annotations) | 70 | (host.analysis(), FilePosition { file_id, offset }, annotations) |
70 | } | 71 | } |
72 | |||
73 | pub(crate) fn nav_target_annotation(ra_fixture: &str) -> (Analysis, FilePosition, FileRange) { | ||
74 | let (analysis, position, mut annotations) = annotations(ra_fixture); | ||
75 | let (mut expected, data) = annotations.pop().unwrap(); | ||
76 | assert!(annotations.is_empty()); | ||
77 | match data.as_str() { | ||
78 | "" => (), | ||
79 | "file" => { | ||
80 | expected.range = | ||
81 | TextRange::up_to(TextSize::of(&*analysis.file_text(expected.file_id).unwrap())) | ||
82 | } | ||
83 | data => panic!("bad data: {}", data), | ||
84 | } | ||
85 | (analysis, position, expected) | ||
86 | } | ||
diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs index f6e5a522b..1a99a1f37 100644 --- a/crates/ide/src/fn_references.rs +++ b/crates/ide/src/fn_references.rs | |||
@@ -1,8 +1,8 @@ | |||
1 | //! This module implements a methods and free functions search in the specified file. | 1 | //! This module implements a methods and free functions search in the specified file. |
2 | //! We have to skip tests, so cannot reuse file_structure module. | 2 | //! We have to skip tests, so cannot reuse file_structure module. |
3 | 3 | ||
4 | use assists::utils::test_related_attribute; | ||
5 | use hir::Semantics; | 4 | use hir::Semantics; |
5 | use ide_assists::utils::test_related_attribute; | ||
6 | use ide_db::RootDatabase; | 6 | use ide_db::RootDatabase; |
7 | use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode}; | 7 | use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode}; |
8 | 8 | ||
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index a1d2bce1d..abed1969e 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -2,16 +2,14 @@ use either::Either; | |||
2 | use hir::{HasAttrs, ModuleDef, Semantics}; | 2 | use hir::{HasAttrs, ModuleDef, Semantics}; |
3 | use ide_db::{ | 3 | use ide_db::{ |
4 | defs::{Definition, NameClass, NameRefClass}, | 4 | defs::{Definition, NameClass, NameRefClass}, |
5 | symbol_index, RootDatabase, | 5 | RootDatabase, |
6 | }; | 6 | }; |
7 | use syntax::{ | 7 | use syntax::{ |
8 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T, | 8 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T, |
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{ | 11 | use crate::{ |
12 | display::{ToNav, TryToNav}, | 12 | display::TryToNav, doc_links::extract_definitions_from_markdown, runnables::doc_owner_to_def, |
13 | doc_links::extract_definitions_from_markdown, | ||
14 | runnables::doc_owner_to_def, | ||
15 | FilePosition, NavigationTarget, RangeInfo, | 13 | FilePosition, NavigationTarget, RangeInfo, |
16 | }; | 14 | }; |
17 | 15 | ||
@@ -33,33 +31,31 @@ pub(crate) fn goto_definition( | |||
33 | let original_token = pick_best(file.token_at_offset(position.offset))?; | 31 | let original_token = pick_best(file.token_at_offset(position.offset))?; |
34 | let token = sema.descend_into_macros(original_token.clone()); | 32 | let token = sema.descend_into_macros(original_token.clone()); |
35 | let parent = token.parent(); | 33 | let parent = token.parent(); |
36 | if let Some(comment) = ast::Comment::cast(token.clone()) { | 34 | if let Some(comment) = ast::Comment::cast(token) { |
37 | let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?; | 35 | let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?; |
38 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); | 36 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); |
39 | } | 37 | } |
40 | 38 | ||
41 | let nav_targets = match_ast! { | 39 | let nav = match_ast! { |
42 | match parent { | 40 | match parent { |
43 | ast::NameRef(name_ref) => { | 41 | ast::NameRef(name_ref) => { |
44 | reference_definition(&sema, Either::Right(&name_ref)).to_vec() | 42 | reference_definition(&sema, Either::Right(&name_ref)) |
45 | }, | 43 | }, |
46 | ast::Name(name) => { | 44 | ast::Name(name) => { |
47 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); | 45 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); |
48 | let nav = def.try_to_nav(sema.db)?; | 46 | def.try_to_nav(sema.db) |
49 | vec![nav] | ||
50 | }, | 47 | }, |
51 | ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) { | 48 | ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) { |
52 | let def = name_class.referenced_or_defined(sema.db); | 49 | let def = name_class.referenced_or_defined(sema.db); |
53 | let nav = def.try_to_nav(sema.db)?; | 50 | def.try_to_nav(sema.db) |
54 | vec![nav] | ||
55 | } else { | 51 | } else { |
56 | reference_definition(&sema, Either::Left(<)).to_vec() | 52 | reference_definition(&sema, Either::Left(<)) |
57 | }, | 53 | }, |
58 | _ => return None, | 54 | _ => return None, |
59 | } | 55 | } |
60 | }; | 56 | }; |
61 | 57 | ||
62 | Some(RangeInfo::new(original_token.text_range(), nav_targets)) | 58 | Some(RangeInfo::new(original_token.text_range(), nav.into_iter().collect())) |
63 | } | 59 | } |
64 | 60 | ||
65 | fn def_for_doc_comment( | 61 | fn def_for_doc_comment( |
@@ -120,63 +116,26 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
120 | } | 116 | } |
121 | } | 117 | } |
122 | 118 | ||
123 | #[derive(Debug)] | ||
124 | pub(crate) enum ReferenceResult { | ||
125 | Exact(NavigationTarget), | ||
126 | Approximate(Vec<NavigationTarget>), | ||
127 | } | ||
128 | |||
129 | impl ReferenceResult { | ||
130 | fn to_vec(self) -> Vec<NavigationTarget> { | ||
131 | match self { | ||
132 | ReferenceResult::Exact(target) => vec![target], | ||
133 | ReferenceResult::Approximate(vec) => vec, | ||
134 | } | ||
135 | } | ||
136 | } | ||
137 | |||
138 | pub(crate) fn reference_definition( | 119 | pub(crate) fn reference_definition( |
139 | sema: &Semantics<RootDatabase>, | 120 | sema: &Semantics<RootDatabase>, |
140 | name_ref: Either<&ast::Lifetime, &ast::NameRef>, | 121 | name_ref: Either<&ast::Lifetime, &ast::NameRef>, |
141 | ) -> ReferenceResult { | 122 | ) -> Option<NavigationTarget> { |
142 | let name_kind = name_ref.either( | 123 | let name_kind = name_ref.either( |
143 | |lifetime| NameRefClass::classify_lifetime(sema, lifetime), | 124 | |lifetime| NameRefClass::classify_lifetime(sema, lifetime), |
144 | |name_ref| NameRefClass::classify(sema, name_ref), | 125 | |name_ref| NameRefClass::classify(sema, name_ref), |
145 | ); | 126 | )?; |
146 | if let Some(def) = name_kind { | 127 | let def = name_kind.referenced(sema.db); |
147 | let def = def.referenced(sema.db); | 128 | def.try_to_nav(sema.db) |
148 | return match def.try_to_nav(sema.db) { | ||
149 | Some(nav) => ReferenceResult::Exact(nav), | ||
150 | None => ReferenceResult::Approximate(Vec::new()), | ||
151 | }; | ||
152 | } | ||
153 | |||
154 | // Fallback index based approach: | ||
155 | let name = name_ref.either(ast::Lifetime::text, ast::NameRef::text); | ||
156 | let navs = | ||
157 | symbol_index::index_resolve(sema.db, name).into_iter().map(|s| s.to_nav(sema.db)).collect(); | ||
158 | ReferenceResult::Approximate(navs) | ||
159 | } | 129 | } |
160 | 130 | ||
161 | #[cfg(test)] | 131 | #[cfg(test)] |
162 | mod tests { | 132 | mod tests { |
163 | use ide_db::base_db::FileRange; | 133 | use ide_db::base_db::FileRange; |
164 | use syntax::{TextRange, TextSize}; | ||
165 | 134 | ||
166 | use crate::fixture; | 135 | use crate::fixture; |
167 | 136 | ||
168 | fn check(ra_fixture: &str) { | 137 | fn check(ra_fixture: &str) { |
169 | let (analysis, position, mut annotations) = fixture::annotations(ra_fixture); | 138 | let (analysis, position, expected) = fixture::nav_target_annotation(ra_fixture); |
170 | let (mut expected, data) = annotations.pop().unwrap(); | ||
171 | match data.as_str() { | ||
172 | "" => (), | ||
173 | "file" => { | ||
174 | expected.range = | ||
175 | TextRange::up_to(TextSize::of(&*analysis.file_text(expected.file_id).unwrap())) | ||
176 | } | ||
177 | data => panic!("bad data: {}", data), | ||
178 | } | ||
179 | |||
180 | let mut navs = | 139 | let mut navs = |
181 | analysis.goto_definition(position).unwrap().expect("no definition found").info; | 140 | analysis.goto_definition(position).unwrap().expect("no definition found").info; |
182 | if navs.len() == 0 { | 141 | if navs.len() == 0 { |
@@ -192,12 +151,12 @@ mod tests { | |||
192 | fn goto_def_for_extern_crate() { | 151 | fn goto_def_for_extern_crate() { |
193 | check( | 152 | check( |
194 | r#" | 153 | r#" |
195 | //- /main.rs crate:main deps:std | 154 | //- /main.rs crate:main deps:std |
196 | extern crate std$0; | 155 | extern crate std$0; |
197 | //- /std/lib.rs crate:std | 156 | //- /std/lib.rs crate:std |
198 | // empty | 157 | // empty |
199 | //^ file | 158 | //^ file |
200 | "#, | 159 | "#, |
201 | ) | 160 | ) |
202 | } | 161 | } |
203 | 162 | ||
@@ -205,12 +164,12 @@ mod tests { | |||
205 | fn goto_def_for_renamed_extern_crate() { | 164 | fn goto_def_for_renamed_extern_crate() { |
206 | check( | 165 | check( |
207 | r#" | 166 | r#" |
208 | //- /main.rs crate:main deps:std | 167 | //- /main.rs crate:main deps:std |
209 | extern crate std as abc$0; | 168 | extern crate std as abc$0; |
210 | //- /std/lib.rs crate:std | 169 | //- /std/lib.rs crate:std |
211 | // empty | 170 | // empty |
212 | //^ file | 171 | //^ file |
213 | "#, | 172 | "#, |
214 | ) | 173 | ) |
215 | } | 174 | } |
216 | 175 | ||
@@ -297,13 +256,13 @@ fn bar() { | |||
297 | fn goto_def_for_macros_from_other_crates() { | 256 | fn goto_def_for_macros_from_other_crates() { |
298 | check( | 257 | check( |
299 | r#" | 258 | r#" |
300 | //- /lib.rs | 259 | //- /lib.rs crate:main deps:foo |
301 | use foo::foo; | 260 | use foo::foo; |
302 | fn bar() { | 261 | fn bar() { |
303 | $0foo!(); | 262 | $0foo!(); |
304 | } | 263 | } |
305 | 264 | ||
306 | //- /foo/lib.rs | 265 | //- /foo/lib.rs crate:foo |
307 | #[macro_export] | 266 | #[macro_export] |
308 | macro_rules! foo { () => { () } } | 267 | macro_rules! foo { () => { () } } |
309 | //^^^ | 268 | //^^^ |
@@ -315,10 +274,10 @@ macro_rules! foo { () => { () } } | |||
315 | fn goto_def_for_macros_in_use_tree() { | 274 | fn goto_def_for_macros_in_use_tree() { |
316 | check( | 275 | check( |
317 | r#" | 276 | r#" |
318 | //- /lib.rs | 277 | //- /lib.rs crate:main deps:foo |
319 | use foo::foo$0; | 278 | use foo::foo$0; |
320 | 279 | ||
321 | //- /foo/lib.rs | 280 | //- /foo/lib.rs crate:foo |
322 | #[macro_export] | 281 | #[macro_export] |
323 | macro_rules! foo { () => { () } } | 282 | macro_rules! foo { () => { () } } |
324 | //^^^ | 283 | //^^^ |
@@ -976,10 +935,10 @@ type Alias<T> = T$0; | |||
976 | fn goto_def_for_macro_container() { | 935 | fn goto_def_for_macro_container() { |
977 | check( | 936 | check( |
978 | r#" | 937 | r#" |
979 | //- /lib.rs | 938 | //- /lib.rs crate:main deps:foo |
980 | foo::module$0::mac!(); | 939 | foo::module$0::mac!(); |
981 | 940 | ||
982 | //- /foo/lib.rs | 941 | //- /foo/lib.rs crate:foo |
983 | pub mod module { | 942 | pub mod module { |
984 | //^^^^^^ | 943 | //^^^^^^ |
985 | #[macro_export] | 944 | #[macro_export] |
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index 761a98b2c..3990305fc 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs | |||
@@ -23,7 +23,7 @@ pub(crate) fn goto_implementation( | |||
23 | 23 | ||
24 | let krate = sema.to_module_def(position.file_id)?.krate(); | 24 | let krate = sema.to_module_def(position.file_id)?.krate(); |
25 | 25 | ||
26 | if let Some(nominal_def) = find_node_at_offset::<ast::AdtDef>(&syntax, position.offset) { | 26 | if let Some(nominal_def) = find_node_at_offset::<ast::Adt>(&syntax, position.offset) { |
27 | return Some(RangeInfo::new( | 27 | return Some(RangeInfo::new( |
28 | nominal_def.syntax().text_range(), | 28 | nominal_def.syntax().text_range(), |
29 | impls_for_def(&sema, &nominal_def, krate)?, | 29 | impls_for_def(&sema, &nominal_def, krate)?, |
@@ -40,13 +40,13 @@ pub(crate) fn goto_implementation( | |||
40 | 40 | ||
41 | fn impls_for_def( | 41 | fn impls_for_def( |
42 | sema: &Semantics<RootDatabase>, | 42 | sema: &Semantics<RootDatabase>, |
43 | node: &ast::AdtDef, | 43 | node: &ast::Adt, |
44 | krate: Crate, | 44 | krate: Crate, |
45 | ) -> Option<Vec<NavigationTarget>> { | 45 | ) -> Option<Vec<NavigationTarget>> { |
46 | let ty = match node { | 46 | let ty = match node { |
47 | ast::AdtDef::Struct(def) => sema.to_def(def)?.ty(sema.db), | 47 | ast::Adt::Struct(def) => sema.to_def(def)?.ty(sema.db), |
48 | ast::AdtDef::Enum(def) => sema.to_def(def)?.ty(sema.db), | 48 | ast::Adt::Enum(def) => sema.to_def(def)?.ty(sema.db), |
49 | ast::AdtDef::Union(def) => sema.to_def(def)?.ty(sema.db), | 49 | ast::Adt::Union(def) => sema.to_def(def)?.ty(sema.db), |
50 | }; | 50 | }; |
51 | 51 | ||
52 | let impls = Impl::all_in_crate(sema.db, krate); | 52 | let impls = Impl::all_in_crate(sema.db, krate); |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 2024acd94..20b799490 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -2,8 +2,8 @@ use hir::{ | |||
2 | Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource, | 2 | Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource, |
3 | HirDisplay, Module, ModuleDef, ModuleSource, Semantics, | 3 | HirDisplay, Module, ModuleDef, ModuleSource, Semantics, |
4 | }; | 4 | }; |
5 | use ide_db::base_db::SourceDatabase; | ||
6 | use ide_db::{ | 5 | use ide_db::{ |
6 | base_db::SourceDatabase, | ||
7 | defs::{Definition, NameClass, NameRefClass}, | 7 | defs::{Definition, NameClass, NameRefClass}, |
8 | RootDatabase, | 8 | RootDatabase, |
9 | }; | 9 | }; |
@@ -94,7 +94,12 @@ pub(crate) fn hover( | |||
94 | let node = token.parent(); | 94 | let node = token.parent(); |
95 | let definition = match_ast! { | 95 | let definition = match_ast! { |
96 | match node { | 96 | match node { |
97 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)), | 97 | // we don't use NameClass::referenced_or_defined here as we do not want to resolve |
98 | // field pattern shorthands to their definition | ||
99 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class { | ||
100 | NameClass::ConstReference(def) => Some(def), | ||
101 | def => def.defined(sema.db), | ||
102 | }), | ||
98 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), | 103 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
99 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) | 104 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) |
100 | .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), | 105 | .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), |
@@ -182,12 +187,7 @@ fn runnable_action( | |||
182 | ) -> Option<HoverAction> { | 187 | ) -> Option<HoverAction> { |
183 | match def { | 188 | match def { |
184 | Definition::ModuleDef(it) => match it { | 189 | Definition::ModuleDef(it) => match it { |
185 | ModuleDef::Module(it) => match it.definition_source(sema.db).value { | 190 | ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)), |
186 | ModuleSource::Module(it) => { | ||
187 | runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)) | ||
188 | } | ||
189 | _ => None, | ||
190 | }, | ||
191 | ModuleDef::Function(func) => { | 191 | ModuleDef::Function(func) => { |
192 | let src = func.source(sema.db)?; | 192 | let src = func.source(sema.db)?; |
193 | if src.file_id != file_id.into() { | 193 | if src.file_id != file_id.into() { |
@@ -326,6 +326,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
326 | match it.definition_source(db).value { | 326 | match it.definition_source(db).value { |
327 | ModuleSource::Module(it) => it.short_label(), | 327 | ModuleSource::Module(it) => it.short_label(), |
328 | ModuleSource::SourceFile(it) => it.short_label(), | 328 | ModuleSource::SourceFile(it) => it.short_label(), |
329 | ModuleSource::BlockExpr(it) => it.short_label(), | ||
329 | }, | 330 | }, |
330 | mod_path, | 331 | mod_path, |
331 | ), | 332 | ), |
@@ -338,7 +339,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
338 | ModuleDef::Static(it) => from_def_source(db, it, mod_path), | 339 | ModuleDef::Static(it) => from_def_source(db, it, mod_path), |
339 | ModuleDef::Trait(it) => from_def_source(db, it, mod_path), | 340 | ModuleDef::Trait(it) => from_def_source(db, it, mod_path), |
340 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), | 341 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), |
341 | ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it)), | 342 | ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it.name())), |
342 | }, | 343 | }, |
343 | Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))), | 344 | Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))), |
344 | Definition::SelfType(impl_def) => { | 345 | Definition::SelfType(impl_def) => { |
@@ -1830,6 +1831,35 @@ pub struct B$0ar | |||
1830 | "#]], | 1831 | "#]], |
1831 | ); | 1832 | ); |
1832 | } | 1833 | } |
1834 | #[test] | ||
1835 | fn test_hover_intra_link_reference_to_trait_method() { | ||
1836 | check( | ||
1837 | r#" | ||
1838 | pub trait Foo { | ||
1839 | fn buzz() -> usize; | ||
1840 | } | ||
1841 | /// [Foo][buzz] | ||
1842 | /// | ||
1843 | /// [buzz]: Foo::buzz | ||
1844 | pub struct B$0ar | ||
1845 | "#, | ||
1846 | expect![[r#" | ||
1847 | *Bar* | ||
1848 | |||
1849 | ```rust | ||
1850 | test | ||
1851 | ``` | ||
1852 | |||
1853 | ```rust | ||
1854 | pub struct Bar | ||
1855 | ``` | ||
1856 | |||
1857 | --- | ||
1858 | |||
1859 | [Foo](https://docs.rs/test/*/test/trait.Foo.html#tymethod.buzz) | ||
1860 | "#]], | ||
1861 | ); | ||
1862 | } | ||
1833 | 1863 | ||
1834 | #[test] | 1864 | #[test] |
1835 | fn test_hover_external_url() { | 1865 | fn test_hover_external_url() { |
@@ -3387,7 +3417,7 @@ impl<T> Foo<T$0> {} | |||
3387 | ``` | 3417 | ``` |
3388 | "#]], | 3418 | "#]], |
3389 | ); | 3419 | ); |
3390 | // lifetimes aren't being substituted yet | 3420 | // lifetimes bounds arent being tracked yet |
3391 | check( | 3421 | check( |
3392 | r#" | 3422 | r#" |
3393 | struct Foo<T>(T); | 3423 | struct Foo<T>(T); |
@@ -3397,7 +3427,7 @@ impl<T: 'static> Foo<T$0> {} | |||
3397 | *T* | 3427 | *T* |
3398 | 3428 | ||
3399 | ```rust | 3429 | ```rust |
3400 | T: {error} | 3430 | T |
3401 | ``` | 3431 | ``` |
3402 | "#]], | 3432 | "#]], |
3403 | ); | 3433 | ); |
@@ -3419,4 +3449,80 @@ impl<const LEN: usize> Foo<LEN$0> {} | |||
3419 | "#]], | 3449 | "#]], |
3420 | ); | 3450 | ); |
3421 | } | 3451 | } |
3452 | |||
3453 | #[test] | ||
3454 | fn hover_const_pat() { | ||
3455 | check( | ||
3456 | r#" | ||
3457 | /// This is a doc | ||
3458 | const FOO: usize = 3; | ||
3459 | fn foo() { | ||
3460 | match 5 { | ||
3461 | FOO$0 => (), | ||
3462 | _ => () | ||
3463 | } | ||
3464 | } | ||
3465 | "#, | ||
3466 | expect![[r#" | ||
3467 | *FOO* | ||
3468 | |||
3469 | ```rust | ||
3470 | test | ||
3471 | ``` | ||
3472 | |||
3473 | ```rust | ||
3474 | const FOO: usize = 3 | ||
3475 | ``` | ||
3476 | |||
3477 | --- | ||
3478 | |||
3479 | This is a doc | ||
3480 | "#]], | ||
3481 | ); | ||
3482 | } | ||
3483 | |||
3484 | #[test] | ||
3485 | fn hover_mod_def() { | ||
3486 | check( | ||
3487 | r#" | ||
3488 | //- /main.rs | ||
3489 | mod foo$0; | ||
3490 | //- /foo.rs | ||
3491 | //! For the horde! | ||
3492 | "#, | ||
3493 | expect![[r#" | ||
3494 | *foo* | ||
3495 | For the horde! | ||
3496 | "#]], | ||
3497 | ); | ||
3498 | } | ||
3499 | |||
3500 | #[test] | ||
3501 | fn hover_self_in_use() { | ||
3502 | check( | ||
3503 | r#" | ||
3504 | //! This should not appear | ||
3505 | mod foo { | ||
3506 | /// But this should appear | ||
3507 | pub mod bar {} | ||
3508 | } | ||
3509 | use foo::bar::{self$0}; | ||
3510 | "#, | ||
3511 | expect![[r#" | ||
3512 | *self* | ||
3513 | |||
3514 | ```rust | ||
3515 | test::foo | ||
3516 | ``` | ||
3517 | |||
3518 | ```rust | ||
3519 | pub mod bar | ||
3520 | ``` | ||
3521 | |||
3522 | --- | ||
3523 | |||
3524 | But this should appear | ||
3525 | "#]], | ||
3526 | ); | ||
3527 | } | ||
3422 | } | 3528 | } |
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index a2039fcc7..4ceb20742 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs | |||
@@ -109,26 +109,31 @@ fn get_chaining_hints( | |||
109 | // Chaining can be defined as an expression whose next sibling tokens are newline and dot | 109 | // Chaining can be defined as an expression whose next sibling tokens are newline and dot |
110 | // Ignoring extra whitespace and comments | 110 | // Ignoring extra whitespace and comments |
111 | let next = tokens.next()?.kind(); | 111 | let next = tokens.next()?.kind(); |
112 | let next_next = tokens.next()?.kind(); | 112 | if next == SyntaxKind::WHITESPACE { |
113 | if next == SyntaxKind::WHITESPACE && next_next == T![.] { | 113 | let mut next_next = tokens.next()?.kind(); |
114 | let ty = sema.type_of_expr(&expr)?; | 114 | while next_next == SyntaxKind::WHITESPACE { |
115 | if ty.is_unknown() { | 115 | next_next = tokens.next()?.kind(); |
116 | return None; | ||
117 | } | 116 | } |
118 | if matches!(expr, ast::Expr::PathExpr(_)) { | 117 | if next_next == T![.] { |
119 | if let Some(hir::Adt::Struct(st)) = ty.as_adt() { | 118 | let ty = sema.type_of_expr(&expr)?; |
120 | if st.fields(sema.db).is_empty() { | 119 | if ty.is_unknown() { |
121 | return None; | 120 | return None; |
121 | } | ||
122 | if matches!(expr, ast::Expr::PathExpr(_)) { | ||
123 | if let Some(hir::Adt::Struct(st)) = ty.as_adt() { | ||
124 | if st.fields(sema.db).is_empty() { | ||
125 | return None; | ||
126 | } | ||
122 | } | 127 | } |
123 | } | 128 | } |
129 | acc.push(InlayHint { | ||
130 | range: expr.syntax().text_range(), | ||
131 | kind: InlayKind::ChainingHint, | ||
132 | label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| { | ||
133 | ty.display_truncated(sema.db, config.max_length).to_string().into() | ||
134 | }), | ||
135 | }); | ||
124 | } | 136 | } |
125 | acc.push(InlayHint { | ||
126 | range: expr.syntax().text_range(), | ||
127 | kind: InlayKind::ChainingHint, | ||
128 | label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| { | ||
129 | ty.display_truncated(sema.db, config.max_length).to_string().into() | ||
130 | }), | ||
131 | }); | ||
132 | } | 137 | } |
133 | Some(()) | 138 | Some(()) |
134 | } | 139 | } |
@@ -411,13 +416,16 @@ fn get_string_representation(expr: &ast::Expr) -> Option<String> { | |||
411 | match expr { | 416 | match expr { |
412 | ast::Expr::MethodCallExpr(method_call_expr) => { | 417 | ast::Expr::MethodCallExpr(method_call_expr) => { |
413 | let name_ref = method_call_expr.name_ref()?; | 418 | let name_ref = method_call_expr.name_ref()?; |
414 | match name_ref.text().as_str() { | 419 | match name_ref.text() { |
415 | "clone" => method_call_expr.receiver().map(|rec| rec.to_string()), | 420 | "clone" => method_call_expr.receiver().map(|rec| rec.to_string()), |
416 | name_ref => Some(name_ref.to_owned()), | 421 | name_ref => Some(name_ref.to_owned()), |
417 | } | 422 | } |
418 | } | 423 | } |
424 | ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()), | ||
425 | ast::Expr::PathExpr(path_expr) => Some(path_expr.to_string()), | ||
426 | ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?), | ||
419 | ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), | 427 | ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), |
420 | _ => Some(expr.to_string()), | 428 | _ => None, |
421 | } | 429 | } |
422 | } | 430 | } |
423 | 431 | ||
@@ -651,6 +659,7 @@ fn main() { | |||
651 | let test = "test"; | 659 | let test = "test"; |
652 | //^^^^ &str | 660 | //^^^^ &str |
653 | let test = InnerStruct {}; | 661 | let test = InnerStruct {}; |
662 | //^^^^ InnerStruct | ||
654 | 663 | ||
655 | let test = unresolved(); | 664 | let test = unresolved(); |
656 | 665 | ||
@@ -979,6 +988,7 @@ struct C; | |||
979 | fn main() { | 988 | fn main() { |
980 | let c = A(B(C)) | 989 | let c = A(B(C)) |
981 | .into_b() // This is a comment | 990 | .into_b() // This is a comment |
991 | // This is another comment | ||
982 | .into_c(); | 992 | .into_c(); |
983 | } | 993 | } |
984 | "#, | 994 | "#, |
@@ -1438,4 +1448,19 @@ fn main() { | |||
1438 | "#, | 1448 | "#, |
1439 | ) | 1449 | ) |
1440 | } | 1450 | } |
1451 | |||
1452 | #[test] | ||
1453 | fn param_name_hints_show_for_literals() { | ||
1454 | check( | ||
1455 | r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] } | ||
1456 | fn main() { | ||
1457 | test( | ||
1458 | 0x0fab272b, | ||
1459 | //^^^^^^^^^^ a | ||
1460 | 0x0fab272b | ||
1461 | //^^^^^^^^^^ b | ||
1462 | ); | ||
1463 | }"#, | ||
1464 | ) | ||
1465 | } | ||
1441 | } | 1466 | } |
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs index 981467c8d..2c077ed1f 100644 --- a/crates/ide/src/join_lines.rs +++ b/crates/ide/src/join_lines.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use assists::utils::extract_trivial_expression; | 1 | use ide_assists::utils::extract_trivial_expression; |
2 | use itertools::Itertools; | 2 | use itertools::Itertools; |
3 | use syntax::{ | 3 | use syntax::{ |
4 | algo::non_trivia_sibling, | 4 | algo::non_trivia_sibling, |
@@ -59,7 +59,7 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS | |||
59 | // The node is either the first or the last in the file | 59 | // The node is either the first or the last in the file |
60 | let suff = &token.text()[TextRange::new( | 60 | let suff = &token.text()[TextRange::new( |
61 | offset - token.text_range().start() + TextSize::of('\n'), | 61 | offset - token.text_range().start() + TextSize::of('\n'), |
62 | TextSize::of(token.text().as_str()), | 62 | TextSize::of(token.text()), |
63 | )]; | 63 | )]; |
64 | let spaces = suff.bytes().take_while(|&b| b == b' ').count(); | 64 | let spaces = suff.bytes().take_while(|&b| b == b' ').count(); |
65 | 65 | ||
@@ -270,27 +270,28 @@ fn foo() { | |||
270 | 270 | ||
271 | #[test] | 271 | #[test] |
272 | fn test_join_lines_diverging_block() { | 272 | fn test_join_lines_diverging_block() { |
273 | let before = r" | 273 | check_join_lines( |
274 | fn foo() { | 274 | r" |
275 | loop { | 275 | fn foo() { |
276 | match x { | 276 | loop { |
277 | 92 => $0{ | 277 | match x { |
278 | continue; | 278 | 92 => $0{ |
279 | } | 279 | continue; |
280 | } | ||
281 | } | ||
282 | } | ||
283 | "; | ||
284 | let after = r" | ||
285 | fn foo() { | ||
286 | loop { | ||
287 | match x { | ||
288 | 92 => $0continue, | ||
289 | } | ||
290 | } | ||
291 | } | 280 | } |
292 | "; | 281 | } |
293 | check_join_lines(before, after); | 282 | } |
283 | } | ||
284 | ", | ||
285 | r" | ||
286 | fn foo() { | ||
287 | loop { | ||
288 | match x { | ||
289 | 92 => $0continue, | ||
290 | } | ||
291 | } | ||
292 | } | ||
293 | ", | ||
294 | ); | ||
294 | } | 295 | } |
295 | 296 | ||
296 | #[test] | 297 | #[test] |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 6c94c26b5..b600178ee 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -22,6 +22,7 @@ mod markup; | |||
22 | mod prime_caches; | 22 | mod prime_caches; |
23 | mod display; | 23 | mod display; |
24 | 24 | ||
25 | mod annotations; | ||
25 | mod call_hierarchy; | 26 | mod call_hierarchy; |
26 | mod diagnostics; | 27 | mod diagnostics; |
27 | mod expand_macro; | 28 | mod expand_macro; |
@@ -63,9 +64,10 @@ use syntax::SourceFile; | |||
63 | use crate::display::ToNav; | 64 | use crate::display::ToNav; |
64 | 65 | ||
65 | pub use crate::{ | 66 | pub use crate::{ |
67 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, | ||
66 | call_hierarchy::CallItem, | 68 | call_hierarchy::CallItem, |
67 | diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, | 69 | diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, |
68 | display::navigation_target::{NavigationTarget, SymbolKind}, | 70 | display::navigation_target::NavigationTarget, |
69 | expand_macro::ExpandedMacro, | 71 | expand_macro::ExpandedMacro, |
70 | file_structure::StructureNode, | 72 | file_structure::StructureNode, |
71 | folding_ranges::{Fold, FoldKind}, | 73 | folding_ranges::{Fold, FoldKind}, |
@@ -73,19 +75,19 @@ pub use crate::{ | |||
73 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 75 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
74 | markup::Markup, | 76 | markup::Markup, |
75 | prime_caches::PrimeCachesProgress, | 77 | prime_caches::PrimeCachesProgress, |
76 | references::{rename::RenameError, Declaration, ReferenceSearchResult}, | 78 | references::{rename::RenameError, ReferenceSearchResult}, |
77 | runnables::{Runnable, RunnableKind, TestId}, | 79 | runnables::{Runnable, RunnableKind, TestId}, |
78 | syntax_highlighting::{ | 80 | syntax_highlighting::{ |
79 | tags::{Highlight, HlMod, HlMods, HlPunct, HlTag}, | 81 | tags::{Highlight, HlMod, HlMods, HlPunct, HlTag}, |
80 | HlRange, | 82 | HlRange, |
81 | }, | 83 | }, |
82 | }; | 84 | }; |
83 | pub use assists::{Assist, AssistConfig, AssistId, AssistKind}; | 85 | pub use hir::{Documentation, Semantics}; |
84 | pub use completion::{ | 86 | pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; |
87 | pub use ide_completion::{ | ||
85 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, | 88 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, |
86 | InsertTextFormat, | 89 | InsertTextFormat, |
87 | }; | 90 | }; |
88 | pub use hir::{Documentation, Semantics}; | ||
89 | pub use ide_db::{ | 91 | pub use ide_db::{ |
90 | base_db::{ | 92 | base_db::{ |
91 | Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, | 93 | Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, |
@@ -93,13 +95,13 @@ pub use ide_db::{ | |||
93 | }, | 95 | }, |
94 | call_info::CallInfo, | 96 | call_info::CallInfo, |
95 | label::Label, | 97 | label::Label, |
96 | line_index::{LineCol, LineIndex}, | 98 | line_index::{LineCol, LineColUtf16, LineIndex}, |
97 | search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope}, | 99 | search::{ReferenceAccess, SearchScope}, |
98 | source_change::{FileSystemEdit, SourceChange}, | 100 | source_change::{FileSystemEdit, SourceChange}, |
99 | symbol_index::Query, | 101 | symbol_index::Query, |
100 | RootDatabase, | 102 | RootDatabase, |
101 | }; | 103 | }; |
102 | pub use ssr::SsrError; | 104 | pub use ide_ssr::SsrError; |
103 | pub use syntax::{TextRange, TextSize}; | 105 | pub use syntax::{TextRange, TextSize}; |
104 | pub use text_edit::{Indel, TextEdit}; | 106 | pub use text_edit::{Indel, TextEdit}; |
105 | 107 | ||
@@ -369,9 +371,7 @@ impl Analysis { | |||
369 | position: FilePosition, | 371 | position: FilePosition, |
370 | search_scope: Option<SearchScope>, | 372 | search_scope: Option<SearchScope>, |
371 | ) -> Cancelable<Option<ReferenceSearchResult>> { | 373 | ) -> Cancelable<Option<ReferenceSearchResult>> { |
372 | self.with_db(|db| { | 374 | self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope)) |
373 | references::find_all_refs(&Semantics::new(db), position, search_scope).map(|it| it.info) | ||
374 | }) | ||
375 | } | 375 | } |
376 | 376 | ||
377 | /// Finds all methods and free functions for the file. Does not return tests! | 377 | /// Finds all methods and free functions for the file. Does not return tests! |
@@ -468,7 +468,7 @@ impl Analysis { | |||
468 | config: &CompletionConfig, | 468 | config: &CompletionConfig, |
469 | position: FilePosition, | 469 | position: FilePosition, |
470 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | 470 | ) -> Cancelable<Option<Vec<CompletionItem>>> { |
471 | self.with_db(|db| completion::completions(db, config, position).map(Into::into)) | 471 | self.with_db(|db| ide_completion::completions(db, config, position).map(Into::into)) |
472 | } | 472 | } |
473 | 473 | ||
474 | /// Resolves additional completion data at the position given. | 474 | /// Resolves additional completion data at the position given. |
@@ -478,15 +478,17 @@ impl Analysis { | |||
478 | position: FilePosition, | 478 | position: FilePosition, |
479 | full_import_path: &str, | 479 | full_import_path: &str, |
480 | imported_name: String, | 480 | imported_name: String, |
481 | import_for_trait_assoc_item: bool, | ||
481 | ) -> Cancelable<Vec<TextEdit>> { | 482 | ) -> Cancelable<Vec<TextEdit>> { |
482 | Ok(self | 483 | Ok(self |
483 | .with_db(|db| { | 484 | .with_db(|db| { |
484 | completion::resolve_completion_edits( | 485 | ide_completion::resolve_completion_edits( |
485 | db, | 486 | db, |
486 | config, | 487 | config, |
487 | position, | 488 | position, |
488 | full_import_path, | 489 | full_import_path, |
489 | imported_name, | 490 | imported_name, |
491 | import_for_trait_assoc_item, | ||
490 | ) | 492 | ) |
491 | })? | 493 | })? |
492 | .unwrap_or_default()) | 494 | .unwrap_or_default()) |
@@ -520,7 +522,7 @@ impl Analysis { | |||
520 | &self, | 522 | &self, |
521 | position: FilePosition, | 523 | position: FilePosition, |
522 | new_name: &str, | 524 | new_name: &str, |
523 | ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> { | 525 | ) -> Cancelable<Result<SourceChange, RenameError>> { |
524 | self.with_db(|db| references::rename::rename(db, position, new_name)) | 526 | self.with_db(|db| references::rename::rename(db, position, new_name)) |
525 | } | 527 | } |
526 | 528 | ||
@@ -547,14 +549,27 @@ impl Analysis { | |||
547 | selections: Vec<FileRange>, | 549 | selections: Vec<FileRange>, |
548 | ) -> Cancelable<Result<SourceChange, SsrError>> { | 550 | ) -> Cancelable<Result<SourceChange, SsrError>> { |
549 | self.with_db(|db| { | 551 | self.with_db(|db| { |
550 | let rule: ssr::SsrRule = query.parse()?; | 552 | let rule: ide_ssr::SsrRule = query.parse()?; |
551 | let mut match_finder = ssr::MatchFinder::in_context(db, resolve_context, selections); | 553 | let mut match_finder = |
554 | ide_ssr::MatchFinder::in_context(db, resolve_context, selections); | ||
552 | match_finder.add_rule(rule)?; | 555 | match_finder.add_rule(rule)?; |
553 | let edits = if parse_only { Default::default() } else { match_finder.edits() }; | 556 | let edits = if parse_only { Default::default() } else { match_finder.edits() }; |
554 | Ok(SourceChange::from(edits)) | 557 | Ok(SourceChange::from(edits)) |
555 | }) | 558 | }) |
556 | } | 559 | } |
557 | 560 | ||
561 | pub fn annotations( | ||
562 | &self, | ||
563 | file_id: FileId, | ||
564 | config: AnnotationConfig, | ||
565 | ) -> Cancelable<Vec<Annotation>> { | ||
566 | self.with_db(|db| annotations::annotations(db, file_id, config)) | ||
567 | } | ||
568 | |||
569 | pub fn resolve_annotation(&self, annotation: Annotation) -> Cancelable<Annotation> { | ||
570 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) | ||
571 | } | ||
572 | |||
558 | /// Performs an operation on that may be Canceled. | 573 | /// Performs an operation on that may be Canceled. |
559 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> | 574 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> |
560 | where | 575 | where |
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs index d343638fb..ddbaf22b7 100644 --- a/crates/ide/src/parent_module.rs +++ b/crates/ide/src/parent_module.rs | |||
@@ -63,57 +63,62 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> { | |||
63 | 63 | ||
64 | #[cfg(test)] | 64 | #[cfg(test)] |
65 | mod tests { | 65 | mod tests { |
66 | use ide_db::base_db::FileRange; | ||
66 | use test_utils::mark; | 67 | use test_utils::mark; |
67 | 68 | ||
68 | use crate::fixture::{self}; | 69 | use crate::fixture; |
70 | |||
71 | fn check(ra_fixture: &str) { | ||
72 | let (analysis, position, expected) = fixture::nav_target_annotation(ra_fixture); | ||
73 | let mut navs = analysis.parent_module(position).unwrap(); | ||
74 | assert_eq!(navs.len(), 1); | ||
75 | let nav = navs.pop().unwrap(); | ||
76 | assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); | ||
77 | } | ||
69 | 78 | ||
70 | #[test] | 79 | #[test] |
71 | fn test_resolve_parent_module() { | 80 | fn test_resolve_parent_module() { |
72 | let (analysis, pos) = fixture::position( | 81 | check( |
73 | " | 82 | r#" |
74 | //- /lib.rs | 83 | //- /lib.rs |
75 | mod foo; | 84 | mod foo; |
76 | //- /foo.rs | 85 | //^^^ |
77 | $0// empty | 86 | |
78 | ", | 87 | //- /foo.rs |
88 | $0// empty | ||
89 | "#, | ||
79 | ); | 90 | ); |
80 | let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); | ||
81 | nav.assert_match("foo Module FileId(0) 0..8"); | ||
82 | } | 91 | } |
83 | 92 | ||
84 | #[test] | 93 | #[test] |
85 | fn test_resolve_parent_module_on_module_decl() { | 94 | fn test_resolve_parent_module_on_module_decl() { |
86 | mark::check!(test_resolve_parent_module_on_module_decl); | 95 | mark::check!(test_resolve_parent_module_on_module_decl); |
87 | let (analysis, pos) = fixture::position( | 96 | check( |
88 | " | 97 | r#" |
89 | //- /lib.rs | 98 | //- /lib.rs |
90 | mod foo; | 99 | mod foo; |
91 | 100 | //^^^ | |
92 | //- /foo.rs | 101 | //- /foo.rs |
93 | mod $0bar; | 102 | mod $0bar; |
94 | 103 | ||
95 | //- /foo/bar.rs | 104 | //- /foo/bar.rs |
96 | // empty | 105 | // empty |
97 | ", | 106 | "#, |
98 | ); | 107 | ); |
99 | let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); | ||
100 | nav.assert_match("foo Module FileId(0) 0..8"); | ||
101 | } | 108 | } |
102 | 109 | ||
103 | #[test] | 110 | #[test] |
104 | fn test_resolve_parent_module_for_inline() { | 111 | fn test_resolve_parent_module_for_inline() { |
105 | let (analysis, pos) = fixture::position( | 112 | check( |
106 | " | 113 | r#" |
107 | //- /lib.rs | 114 | //- /lib.rs |
108 | mod foo { | 115 | mod foo { |
109 | mod bar { | 116 | mod bar { |
110 | mod baz { $0 } | 117 | mod baz { $0 } |
111 | } | 118 | } //^^^ |
112 | } | 119 | } |
113 | ", | 120 | "#, |
114 | ); | 121 | ); |
115 | let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); | ||
116 | nav.assert_match("baz Module FileId(0) 32..44"); | ||
117 | } | 122 | } |
118 | 123 | ||
119 | #[test] | 124 | #[test] |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index df9c31aef..fef70533d 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -11,171 +11,120 @@ | |||
11 | 11 | ||
12 | pub(crate) mod rename; | 12 | pub(crate) mod rename; |
13 | 13 | ||
14 | use either::Either; | 14 | use hir::{PathResolution, Semantics}; |
15 | use hir::Semantics; | ||
16 | use ide_db::{ | 15 | use ide_db::{ |
17 | base_db::FileId, | 16 | base_db::FileId, |
18 | defs::{Definition, NameClass, NameRefClass}, | 17 | defs::{Definition, NameClass, NameRefClass}, |
19 | search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope, UsageSearchResult}, | 18 | search::{ReferenceAccess, SearchScope}, |
20 | RootDatabase, | 19 | RootDatabase, |
21 | }; | 20 | }; |
21 | use rustc_hash::FxHashMap; | ||
22 | use syntax::{ | 22 | use syntax::{ |
23 | algo::find_node_at_offset, | 23 | algo::find_node_at_offset, |
24 | ast::{self, NameOwner}, | 24 | ast::{self, NameOwner}, |
25 | AstNode, SyntaxNode, TextRange, TokenAtOffset, T, | 25 | match_ast, AstNode, SyntaxNode, TextRange, T, |
26 | }; | 26 | }; |
27 | 27 | ||
28 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; | 28 | use crate::{display::TryToNav, FilePosition, NavigationTarget}; |
29 | 29 | ||
30 | #[derive(Debug, Clone)] | 30 | #[derive(Debug, Clone)] |
31 | pub struct ReferenceSearchResult { | 31 | pub struct ReferenceSearchResult { |
32 | declaration: Declaration, | 32 | pub declaration: Declaration, |
33 | references: UsageSearchResult, | 33 | pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceAccess>)>>, |
34 | } | 34 | } |
35 | 35 | ||
36 | #[derive(Debug, Clone)] | 36 | #[derive(Debug, Clone)] |
37 | pub struct Declaration { | 37 | pub struct Declaration { |
38 | pub nav: NavigationTarget, | 38 | pub nav: NavigationTarget, |
39 | pub kind: ReferenceKind, | ||
40 | pub access: Option<ReferenceAccess>, | 39 | pub access: Option<ReferenceAccess>, |
41 | } | 40 | } |
42 | 41 | ||
43 | impl ReferenceSearchResult { | 42 | // Feature: Find All References |
44 | pub fn declaration(&self) -> &Declaration { | 43 | // |
45 | &self.declaration | 44 | // Shows all references of the item at the cursor location |
46 | } | 45 | // |
47 | 46 | // |=== | |
48 | pub fn decl_target(&self) -> &NavigationTarget { | 47 | // | Editor | Shortcut |
49 | &self.declaration.nav | 48 | // |
50 | } | 49 | // | VS Code | kbd:[Shift+Alt+F12] |
51 | 50 | // |=== | |
52 | pub fn references(&self) -> &UsageSearchResult { | ||
53 | &self.references | ||
54 | } | ||
55 | |||
56 | pub fn references_with_declaration(mut self) -> UsageSearchResult { | ||
57 | let decl_ref = FileReference { | ||
58 | range: self.declaration.nav.focus_or_full_range(), | ||
59 | kind: self.declaration.kind, | ||
60 | access: self.declaration.access, | ||
61 | }; | ||
62 | let file_id = self.declaration.nav.file_id; | ||
63 | self.references.references.entry(file_id).or_default().push(decl_ref); | ||
64 | self.references | ||
65 | } | ||
66 | |||
67 | /// Total number of references | ||
68 | /// At least 1 since all valid references should | ||
69 | /// Have a declaration | ||
70 | pub fn len(&self) -> usize { | ||
71 | self.references.len() + 1 | ||
72 | } | ||
73 | } | ||
74 | |||
75 | // allow turning ReferenceSearchResult into an iterator | ||
76 | // over References | ||
77 | impl IntoIterator for ReferenceSearchResult { | ||
78 | type Item = (FileId, Vec<FileReference>); | ||
79 | type IntoIter = std::collections::hash_map::IntoIter<FileId, Vec<FileReference>>; | ||
80 | |||
81 | fn into_iter(self) -> Self::IntoIter { | ||
82 | self.references_with_declaration().into_iter() | ||
83 | } | ||
84 | } | ||
85 | |||
86 | pub(crate) fn find_all_refs( | 51 | pub(crate) fn find_all_refs( |
87 | sema: &Semantics<RootDatabase>, | 52 | sema: &Semantics<RootDatabase>, |
88 | position: FilePosition, | 53 | position: FilePosition, |
89 | search_scope: Option<SearchScope>, | 54 | search_scope: Option<SearchScope>, |
90 | ) -> Option<RangeInfo<ReferenceSearchResult>> { | 55 | ) -> Option<ReferenceSearchResult> { |
91 | let _p = profile::span("find_all_refs"); | 56 | let _p = profile::span("find_all_refs"); |
92 | let syntax = sema.parse(position.file_id).syntax().clone(); | 57 | let syntax = sema.parse(position.file_id).syntax().clone(); |
93 | 58 | ||
94 | let (opt_name, search_kind) = if let Some(name) = | 59 | let (def, is_literal_search) = |
95 | get_struct_def_name_for_struct_literal_search(&sema, &syntax, position) | 60 | if let Some(name) = get_name_of_item_declaration(&syntax, position) { |
96 | { | 61 | (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true) |
97 | (Some(name), ReferenceKind::StructLiteral) | 62 | } else { |
98 | } else if let Some(name) = get_enum_def_name_for_struct_literal_search(&sema, &syntax, position) | 63 | (find_def(&sema, &syntax, position)?, false) |
99 | { | 64 | }; |
100 | (Some(name), ReferenceKind::EnumLiteral) | ||
101 | } else { | ||
102 | ( | ||
103 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset), | ||
104 | ReferenceKind::Other, | ||
105 | ) | ||
106 | }; | ||
107 | |||
108 | let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; | ||
109 | 65 | ||
110 | let mut usages = def.usages(sema).set_scope(search_scope).all(); | 66 | let mut usages = def.usages(sema).set_scope(search_scope).all(); |
111 | usages | 67 | if is_literal_search { |
112 | .references | 68 | // filter for constructor-literals |
113 | .values_mut() | 69 | let refs = usages.references.values_mut(); |
114 | .for_each(|it| it.retain(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)); | 70 | match def { |
115 | usages.references.retain(|_, it| !it.is_empty()); | 71 | Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => { |
116 | 72 | refs.for_each(|it| { | |
117 | let nav = def.try_to_nav(sema.db)?; | 73 | it.retain(|reference| { |
118 | let decl_range = nav.focus_or_full_range(); | 74 | reference |
119 | 75 | .name | |
120 | let mut kind = ReferenceKind::Other; | 76 | .as_name_ref() |
121 | if let Definition::Local(local) = def { | 77 | .map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref)) |
122 | match local.source(sema.db).value { | 78 | }) |
123 | Either::Left(pat) => { | 79 | }); |
124 | if matches!( | 80 | usages.references.retain(|_, it| !it.is_empty()); |
125 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
126 | Some(pat_field) if pat_field.name_ref().is_none() | ||
127 | ) { | ||
128 | kind = ReferenceKind::FieldShorthandForLocal; | ||
129 | } | ||
130 | } | 81 | } |
131 | Either::Right(_) => kind = ReferenceKind::SelfParam, | 82 | Definition::ModuleDef(hir::ModuleDef::Adt(_)) |
83 | | Definition::ModuleDef(hir::ModuleDef::Variant(_)) => { | ||
84 | refs.for_each(|it| { | ||
85 | it.retain(|reference| { | ||
86 | reference.name.as_name_ref().map_or(false, is_lit_name_ref) | ||
87 | }) | ||
88 | }); | ||
89 | usages.references.retain(|_, it| !it.is_empty()); | ||
90 | } | ||
91 | _ => {} | ||
132 | } | 92 | } |
133 | } else if matches!( | 93 | } |
134 | def, | 94 | let nav = def.try_to_nav(sema.db)?; |
135 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | 95 | let decl_range = nav.focus_or_full_range(); |
136 | ) { | ||
137 | kind = ReferenceKind::Lifetime; | ||
138 | }; | ||
139 | 96 | ||
140 | let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; | 97 | let declaration = Declaration { nav, access: decl_access(&def, &syntax, decl_range) }; |
98 | let references = usages | ||
99 | .into_iter() | ||
100 | .map(|(file_id, refs)| { | ||
101 | (file_id, refs.into_iter().map(|file_ref| (file_ref.range, file_ref.access)).collect()) | ||
102 | }) | ||
103 | .collect(); | ||
141 | 104 | ||
142 | Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references: usages })) | 105 | Some(ReferenceSearchResult { declaration, references }) |
143 | } | 106 | } |
144 | 107 | ||
145 | fn find_name( | 108 | fn find_def( |
146 | sema: &Semantics<RootDatabase>, | 109 | sema: &Semantics<RootDatabase>, |
147 | syntax: &SyntaxNode, | 110 | syntax: &SyntaxNode, |
148 | position: FilePosition, | 111 | position: FilePosition, |
149 | opt_name: Option<ast::Name>, | 112 | ) -> Option<Definition> { |
150 | ) -> Option<RangeInfo<Definition>> { | 113 | let def = match sema.find_node_at_offset_with_descend(syntax, position.offset)? { |
151 | if let Some(name) = opt_name { | 114 | ast::NameLike::NameRef(name_ref) => { |
152 | let def = NameClass::classify(sema, &name)?.referenced_or_defined(sema.db); | 115 | NameRefClass::classify(sema, &name_ref)?.referenced(sema.db) |
153 | let FileRange { range, .. } = sema.original_range(name.syntax()); | ||
154 | return Some(RangeInfo::new(range, def)); | ||
155 | } | ||
156 | |||
157 | let (FileRange { range, .. }, def) = if let Some(lifetime) = | ||
158 | sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset) | ||
159 | { | ||
160 | if let Some(def) = NameRefClass::classify_lifetime(sema, &lifetime) | ||
161 | .map(|class| NameRefClass::referenced(class, sema.db)) | ||
162 | { | ||
163 | (sema.original_range(lifetime.syntax()), def) | ||
164 | } else { | ||
165 | ( | ||
166 | sema.original_range(lifetime.syntax()), | ||
167 | NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db), | ||
168 | ) | ||
169 | } | 116 | } |
170 | } else { | 117 | ast::NameLike::Name(name) => { |
171 | let name_ref = | 118 | NameClass::classify(sema, &name)?.referenced_or_defined(sema.db) |
172 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | 119 | } |
173 | ( | 120 | ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) |
174 | sema.original_range(name_ref.syntax()), | 121 | .map(|class| class.referenced(sema.db)) |
175 | NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), | 122 | .or_else(|| { |
176 | ) | 123 | NameClass::classify_lifetime(sema, &lifetime) |
124 | .map(|class| class.referenced_or_defined(sema.db)) | ||
125 | })?, | ||
177 | }; | 126 | }; |
178 | Some(RangeInfo::new(range, def)) | 127 | Some(def) |
179 | } | 128 | } |
180 | 129 | ||
181 | fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { | 130 | fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { |
@@ -197,58 +146,85 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio | |||
197 | None | 146 | None |
198 | } | 147 | } |
199 | 148 | ||
200 | fn get_struct_def_name_for_struct_literal_search( | 149 | fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> { |
201 | sema: &Semantics<RootDatabase>, | 150 | let token = syntax.token_at_offset(position.offset).right_biased()?; |
202 | syntax: &SyntaxNode, | 151 | let kind = token.kind(); |
203 | position: FilePosition, | 152 | if kind == T![;] { |
204 | ) -> Option<ast::Name> { | 153 | ast::Struct::cast(token.parent()) |
205 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 154 | .filter(|struct_| struct_.field_list().is_none()) |
206 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 155 | .and_then(|struct_| struct_.name()) |
207 | return None; | 156 | } else if kind == T!['{'] { |
208 | } | 157 | match_ast! { |
209 | if let Some(name) = | 158 | match (token.parent()) { |
210 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 159 | ast::RecordFieldList(rfl) => match_ast! { |
211 | { | 160 | match (rfl.syntax().parent()?) { |
212 | return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 161 | ast::Variant(it) => it.name(), |
162 | ast::Struct(it) => it.name(), | ||
163 | ast::Union(it) => it.name(), | ||
164 | _ => None, | ||
165 | } | ||
166 | }, | ||
167 | ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(), | ||
168 | _ => None, | ||
169 | } | ||
213 | } | 170 | } |
214 | if sema | 171 | } else if kind == T!['('] { |
215 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | 172 | let tfl = ast::TupleFieldList::cast(token.parent())?; |
216 | &syntax, | 173 | match_ast! { |
217 | left.text_range().start(), | 174 | match (tfl.syntax().parent()?) { |
218 | ) | 175 | ast::Variant(it) => it.name(), |
219 | .is_some() | 176 | ast::Struct(it) => it.name(), |
220 | { | 177 | _ => None, |
221 | return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 178 | } |
222 | } | 179 | } |
180 | } else { | ||
181 | None | ||
223 | } | 182 | } |
224 | None | ||
225 | } | 183 | } |
226 | 184 | ||
227 | fn get_enum_def_name_for_struct_literal_search( | 185 | fn is_enum_lit_name_ref( |
228 | sema: &Semantics<RootDatabase>, | 186 | sema: &Semantics<RootDatabase>, |
229 | syntax: &SyntaxNode, | 187 | enum_: hir::Enum, |
230 | position: FilePosition, | 188 | name_ref: &ast::NameRef, |
231 | ) -> Option<ast::Name> { | 189 | ) -> bool { |
232 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 190 | let path_is_variant_of_enum = |path: ast::Path| { |
233 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 191 | matches!( |
234 | return None; | 192 | sema.resolve_path(&path), |
235 | } | 193 | Some(PathResolution::Def(hir::ModuleDef::Variant(variant))) |
236 | if let Some(name) = | 194 | if variant.parent_enum(sema.db) == enum_ |
237 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 195 | ) |
238 | { | 196 | }; |
239 | return name.syntax().ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | 197 | name_ref |
240 | } | 198 | .syntax() |
241 | if sema | 199 | .ancestors() |
242 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | 200 | .find_map(|ancestor| { |
243 | &syntax, | 201 | match_ast! { |
244 | left.text_range().start(), | 202 | match ancestor { |
245 | ) | 203 | ast::PathExpr(path_expr) => path_expr.path().map(path_is_variant_of_enum), |
246 | .is_some() | 204 | ast::RecordExpr(record_expr) => record_expr.path().map(path_is_variant_of_enum), |
247 | { | 205 | _ => None, |
248 | return left.ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | 206 | } |
207 | } | ||
208 | }) | ||
209 | .unwrap_or(false) | ||
210 | } | ||
211 | |||
212 | fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool { | ||
213 | path.and_then(|path| path.segment()) | ||
214 | .and_then(|segment| segment.name_ref()) | ||
215 | .map_or(false, |segment| segment == *name_ref) | ||
216 | } | ||
217 | |||
218 | fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool { | ||
219 | name_ref.syntax().ancestors().find_map(|ancestor| { | ||
220 | match_ast! { | ||
221 | match ancestor { | ||
222 | ast::PathExpr(path_expr) => Some(path_ends_with(path_expr.path(), name_ref)), | ||
223 | ast::RecordExpr(record_expr) => Some(path_ends_with(record_expr.path(), name_ref)), | ||
224 | _ => None, | ||
225 | } | ||
249 | } | 226 | } |
250 | } | 227 | }).unwrap_or(false) |
251 | None | ||
252 | } | 228 | } |
253 | 229 | ||
254 | #[cfg(test)] | 230 | #[cfg(test)] |
@@ -275,9 +251,9 @@ fn main() { | |||
275 | } | 251 | } |
276 | "#, | 252 | "#, |
277 | expect![[r#" | 253 | expect![[r#" |
278 | Foo Struct FileId(0) 0..26 7..10 Other | 254 | Foo Struct FileId(0) 0..26 7..10 |
279 | 255 | ||
280 | FileId(0) 101..104 StructLiteral | 256 | FileId(0) 101..104 |
281 | "#]], | 257 | "#]], |
282 | ); | 258 | ); |
283 | } | 259 | } |
@@ -293,10 +269,10 @@ struct Foo$0 {} | |||
293 | } | 269 | } |
294 | "#, | 270 | "#, |
295 | expect![[r#" | 271 | expect![[r#" |
296 | Foo Struct FileId(0) 0..13 7..10 Other | 272 | Foo Struct FileId(0) 0..13 7..10 |
297 | 273 | ||
298 | FileId(0) 41..44 Other | 274 | FileId(0) 41..44 |
299 | FileId(0) 54..57 StructLiteral | 275 | FileId(0) 54..57 |
300 | "#]], | 276 | "#]], |
301 | ); | 277 | ); |
302 | } | 278 | } |
@@ -312,9 +288,9 @@ struct Foo<T> $0{} | |||
312 | } | 288 | } |
313 | "#, | 289 | "#, |
314 | expect![[r#" | 290 | expect![[r#" |
315 | Foo Struct FileId(0) 0..16 7..10 Other | 291 | Foo Struct FileId(0) 0..16 7..10 |
316 | 292 | ||
317 | FileId(0) 64..67 StructLiteral | 293 | FileId(0) 64..67 |
318 | "#]], | 294 | "#]], |
319 | ); | 295 | ); |
320 | } | 296 | } |
@@ -331,9 +307,30 @@ fn main() { | |||
331 | } | 307 | } |
332 | "#, | 308 | "#, |
333 | expect![[r#" | 309 | expect![[r#" |
334 | Foo Struct FileId(0) 0..16 7..10 Other | 310 | Foo Struct FileId(0) 0..16 7..10 |
311 | |||
312 | FileId(0) 54..57 | ||
313 | "#]], | ||
314 | ); | ||
315 | } | ||
316 | |||
317 | #[test] | ||
318 | fn test_struct_literal_for_union() { | ||
319 | check( | ||
320 | r#" | ||
321 | union Foo $0{ | ||
322 | x: u32 | ||
323 | } | ||
324 | |||
325 | fn main() { | ||
326 | let f: Foo; | ||
327 | f = Foo { x: 1 }; | ||
328 | } | ||
329 | "#, | ||
330 | expect![[r#" | ||
331 | Foo Union FileId(0) 0..24 6..9 | ||
335 | 332 | ||
336 | FileId(0) 54..57 StructLiteral | 333 | FileId(0) 62..65 |
337 | "#]], | 334 | "#]], |
338 | ); | 335 | ); |
339 | } | 336 | } |
@@ -344,17 +341,65 @@ fn main() { | |||
344 | r#" | 341 | r#" |
345 | enum Foo $0{ | 342 | enum Foo $0{ |
346 | A, | 343 | A, |
347 | B, | 344 | B(), |
345 | C{}, | ||
348 | } | 346 | } |
349 | fn main() { | 347 | fn main() { |
350 | let f: Foo; | 348 | let f: Foo; |
351 | f = Foo::A; | 349 | f = Foo::A; |
350 | f = Foo::B(); | ||
351 | f = Foo::C{}; | ||
352 | } | ||
353 | "#, | ||
354 | expect![[r#" | ||
355 | Foo Enum FileId(0) 0..37 5..8 | ||
356 | |||
357 | FileId(0) 74..77 | ||
358 | FileId(0) 90..93 | ||
359 | FileId(0) 108..111 | ||
360 | "#]], | ||
361 | ); | ||
362 | } | ||
363 | |||
364 | #[test] | ||
365 | fn test_variant_record_after_space() { | ||
366 | check( | ||
367 | r#" | ||
368 | enum Foo { | ||
369 | A $0{ n: i32 }, | ||
370 | B, | ||
371 | } | ||
372 | fn main() { | ||
373 | let f: Foo; | ||
374 | f = Foo::B; | ||
375 | f = Foo::A { n: 92 }; | ||
352 | } | 376 | } |
353 | "#, | 377 | "#, |
354 | expect![[r#" | 378 | expect![[r#" |
355 | Foo Enum FileId(0) 0..26 5..8 Other | 379 | A Variant FileId(0) 15..27 15..16 |
356 | 380 | ||
357 | FileId(0) 63..66 EnumLiteral | 381 | FileId(0) 95..96 |
382 | "#]], | ||
383 | ); | ||
384 | } | ||
385 | #[test] | ||
386 | fn test_variant_tuple_before_paren() { | ||
387 | check( | ||
388 | r#" | ||
389 | enum Foo { | ||
390 | A$0(i32), | ||
391 | B, | ||
392 | } | ||
393 | fn main() { | ||
394 | let f: Foo; | ||
395 | f = Foo::B; | ||
396 | f = Foo::A(92); | ||
397 | } | ||
398 | "#, | ||
399 | expect![[r#" | ||
400 | A Variant FileId(0) 15..21 15..16 | ||
401 | |||
402 | FileId(0) 89..90 | ||
358 | "#]], | 403 | "#]], |
359 | ); | 404 | ); |
360 | } | 405 | } |
@@ -373,10 +418,10 @@ fn main() { | |||
373 | } | 418 | } |
374 | "#, | 419 | "#, |
375 | expect![[r#" | 420 | expect![[r#" |
376 | Foo Enum FileId(0) 0..26 5..8 Other | 421 | Foo Enum FileId(0) 0..26 5..8 |
377 | 422 | ||
378 | FileId(0) 50..53 Other | 423 | FileId(0) 50..53 |
379 | FileId(0) 63..66 EnumLiteral | 424 | FileId(0) 63..66 |
380 | "#]], | 425 | "#]], |
381 | ); | 426 | ); |
382 | } | 427 | } |
@@ -395,9 +440,9 @@ fn main() { | |||
395 | } | 440 | } |
396 | "#, | 441 | "#, |
397 | expect![[r#" | 442 | expect![[r#" |
398 | Foo Enum FileId(0) 0..32 5..8 Other | 443 | Foo Enum FileId(0) 0..32 5..8 |
399 | 444 | ||
400 | FileId(0) 73..76 EnumLiteral | 445 | FileId(0) 73..76 |
401 | "#]], | 446 | "#]], |
402 | ); | 447 | ); |
403 | } | 448 | } |
@@ -416,9 +461,9 @@ fn main() { | |||
416 | } | 461 | } |
417 | "#, | 462 | "#, |
418 | expect![[r#" | 463 | expect![[r#" |
419 | Foo Enum FileId(0) 0..33 5..8 Other | 464 | Foo Enum FileId(0) 0..33 5..8 |
420 | 465 | ||
421 | FileId(0) 70..73 EnumLiteral | 466 | FileId(0) 70..73 |
422 | "#]], | 467 | "#]], |
423 | ); | 468 | ); |
424 | } | 469 | } |
@@ -439,12 +484,12 @@ fn main() { | |||
439 | i = 5; | 484 | i = 5; |
440 | }"#, | 485 | }"#, |
441 | expect![[r#" | 486 | expect![[r#" |
442 | i Local FileId(0) 20..25 24..25 Other Write | 487 | i Local FileId(0) 20..25 24..25 Write |
443 | 488 | ||
444 | FileId(0) 50..51 Other Write | 489 | FileId(0) 50..51 Write |
445 | FileId(0) 54..55 Other Read | 490 | FileId(0) 54..55 Read |
446 | FileId(0) 76..77 Other Write | 491 | FileId(0) 76..77 Write |
447 | FileId(0) 94..95 Other Write | 492 | FileId(0) 94..95 Write |
448 | "#]], | 493 | "#]], |
449 | ); | 494 | ); |
450 | } | 495 | } |
@@ -463,10 +508,10 @@ fn bar() { | |||
463 | } | 508 | } |
464 | "#, | 509 | "#, |
465 | expect![[r#" | 510 | expect![[r#" |
466 | spam Local FileId(0) 19..23 19..23 Other | 511 | spam Local FileId(0) 19..23 19..23 |
467 | 512 | ||
468 | FileId(0) 34..38 Other Read | 513 | FileId(0) 34..38 Read |
469 | FileId(0) 41..45 Other Read | 514 | FileId(0) 41..45 Read |
470 | "#]], | 515 | "#]], |
471 | ); | 516 | ); |
472 | } | 517 | } |
@@ -478,9 +523,9 @@ fn bar() { | |||
478 | fn foo(i : u32) -> u32 { i$0 } | 523 | fn foo(i : u32) -> u32 { i$0 } |
479 | "#, | 524 | "#, |
480 | expect![[r#" | 525 | expect![[r#" |
481 | i ValueParam FileId(0) 7..8 7..8 Other | 526 | i ValueParam FileId(0) 7..8 7..8 |
482 | 527 | ||
483 | FileId(0) 25..26 Other Read | 528 | FileId(0) 25..26 Read |
484 | "#]], | 529 | "#]], |
485 | ); | 530 | ); |
486 | } | 531 | } |
@@ -492,9 +537,9 @@ fn foo(i : u32) -> u32 { i$0 } | |||
492 | fn foo(i$0 : u32) -> u32 { i } | 537 | fn foo(i$0 : u32) -> u32 { i } |
493 | "#, | 538 | "#, |
494 | expect![[r#" | 539 | expect![[r#" |
495 | i ValueParam FileId(0) 7..8 7..8 Other | 540 | i ValueParam FileId(0) 7..8 7..8 |
496 | 541 | ||
497 | FileId(0) 25..26 Other Read | 542 | FileId(0) 25..26 Read |
498 | "#]], | 543 | "#]], |
499 | ); | 544 | ); |
500 | } | 545 | } |
@@ -513,9 +558,9 @@ fn main(s: Foo) { | |||
513 | } | 558 | } |
514 | "#, | 559 | "#, |
515 | expect![[r#" | 560 | expect![[r#" |
516 | spam Field FileId(0) 17..30 21..25 Other | 561 | spam Field FileId(0) 17..30 21..25 |
517 | 562 | ||
518 | FileId(0) 67..71 Other Read | 563 | FileId(0) 67..71 Read |
519 | "#]], | 564 | "#]], |
520 | ); | 565 | ); |
521 | } | 566 | } |
@@ -530,7 +575,7 @@ impl Foo { | |||
530 | } | 575 | } |
531 | "#, | 576 | "#, |
532 | expect![[r#" | 577 | expect![[r#" |
533 | f Function FileId(0) 27..43 30..31 Other | 578 | f Function FileId(0) 27..43 30..31 |
534 | 579 | ||
535 | "#]], | 580 | "#]], |
536 | ); | 581 | ); |
@@ -547,7 +592,7 @@ enum Foo { | |||
547 | } | 592 | } |
548 | "#, | 593 | "#, |
549 | expect![[r#" | 594 | expect![[r#" |
550 | B Variant FileId(0) 22..23 22..23 Other | 595 | B Variant FileId(0) 22..23 22..23 |
551 | 596 | ||
552 | "#]], | 597 | "#]], |
553 | ); | 598 | ); |
@@ -564,7 +609,7 @@ enum Foo { | |||
564 | } | 609 | } |
565 | "#, | 610 | "#, |
566 | expect![[r#" | 611 | expect![[r#" |
567 | field Field FileId(0) 26..35 26..31 Other | 612 | field Field FileId(0) 26..35 26..31 |
568 | 613 | ||
569 | "#]], | 614 | "#]], |
570 | ); | 615 | ); |
@@ -605,10 +650,10 @@ fn f() { | |||
605 | } | 650 | } |
606 | "#, | 651 | "#, |
607 | expect![[r#" | 652 | expect![[r#" |
608 | Foo Struct FileId(1) 17..51 28..31 Other | 653 | Foo Struct FileId(1) 17..51 28..31 |
609 | 654 | ||
610 | FileId(0) 53..56 StructLiteral | 655 | FileId(0) 53..56 |
611 | FileId(2) 79..82 StructLiteral | 656 | FileId(2) 79..82 |
612 | "#]], | 657 | "#]], |
613 | ); | 658 | ); |
614 | } | 659 | } |
@@ -635,9 +680,9 @@ pub struct Foo { | |||
635 | } | 680 | } |
636 | "#, | 681 | "#, |
637 | expect![[r#" | 682 | expect![[r#" |
638 | foo Module FileId(1) 0..35 Other | 683 | foo Module FileId(1) 0..35 |
639 | 684 | ||
640 | FileId(0) 14..17 Other | 685 | FileId(0) 14..17 |
641 | "#]], | 686 | "#]], |
642 | ); | 687 | ); |
643 | } | 688 | } |
@@ -663,10 +708,10 @@ pub(super) struct Foo$0 { | |||
663 | } | 708 | } |
664 | "#, | 709 | "#, |
665 | expect![[r#" | 710 | expect![[r#" |
666 | Foo Struct FileId(2) 0..41 18..21 Other | 711 | Foo Struct FileId(2) 0..41 18..21 |
667 | 712 | ||
668 | FileId(1) 20..23 Other | 713 | FileId(1) 20..23 |
669 | FileId(1) 47..50 StructLiteral | 714 | FileId(1) 47..50 |
670 | "#]], | 715 | "#]], |
671 | ); | 716 | ); |
672 | } | 717 | } |
@@ -691,10 +736,10 @@ pub(super) struct Foo$0 { | |||
691 | code, | 736 | code, |
692 | None, | 737 | None, |
693 | expect![[r#" | 738 | expect![[r#" |
694 | quux Function FileId(0) 19..35 26..30 Other | 739 | quux Function FileId(0) 19..35 26..30 |
695 | 740 | ||
696 | FileId(1) 16..20 StructLiteral | 741 | FileId(1) 16..20 |
697 | FileId(2) 16..20 StructLiteral | 742 | FileId(2) 16..20 |
698 | "#]], | 743 | "#]], |
699 | ); | 744 | ); |
700 | 745 | ||
@@ -702,9 +747,9 @@ pub(super) struct Foo$0 { | |||
702 | code, | 747 | code, |
703 | Some(SearchScope::single_file(FileId(2))), | 748 | Some(SearchScope::single_file(FileId(2))), |
704 | expect![[r#" | 749 | expect![[r#" |
705 | quux Function FileId(0) 19..35 26..30 Other | 750 | quux Function FileId(0) 19..35 26..30 |
706 | 751 | ||
707 | FileId(2) 16..20 StructLiteral | 752 | FileId(2) 16..20 |
708 | "#]], | 753 | "#]], |
709 | ); | 754 | ); |
710 | } | 755 | } |
@@ -722,10 +767,10 @@ fn foo() { | |||
722 | } | 767 | } |
723 | "#, | 768 | "#, |
724 | expect![[r#" | 769 | expect![[r#" |
725 | m1 Macro FileId(0) 0..46 29..31 Other | 770 | m1 Macro FileId(0) 0..46 29..31 |
726 | 771 | ||
727 | FileId(0) 63..65 StructLiteral | 772 | FileId(0) 63..65 |
728 | FileId(0) 73..75 StructLiteral | 773 | FileId(0) 73..75 |
729 | "#]], | 774 | "#]], |
730 | ); | 775 | ); |
731 | } | 776 | } |
@@ -740,10 +785,10 @@ fn foo() { | |||
740 | } | 785 | } |
741 | "#, | 786 | "#, |
742 | expect![[r#" | 787 | expect![[r#" |
743 | i Local FileId(0) 19..24 23..24 Other Write | 788 | i Local FileId(0) 19..24 23..24 Write |
744 | 789 | ||
745 | FileId(0) 34..35 Other Write | 790 | FileId(0) 34..35 Write |
746 | FileId(0) 38..39 Other Read | 791 | FileId(0) 38..39 Read |
747 | "#]], | 792 | "#]], |
748 | ); | 793 | ); |
749 | } | 794 | } |
@@ -762,10 +807,10 @@ fn foo() { | |||
762 | } | 807 | } |
763 | "#, | 808 | "#, |
764 | expect![[r#" | 809 | expect![[r#" |
765 | f Field FileId(0) 15..21 15..16 Other | 810 | f Field FileId(0) 15..21 15..16 |
766 | 811 | ||
767 | FileId(0) 55..56 RecordFieldExprOrPat Read | 812 | FileId(0) 55..56 Read |
768 | FileId(0) 68..69 Other Write | 813 | FileId(0) 68..69 Write |
769 | "#]], | 814 | "#]], |
770 | ); | 815 | ); |
771 | } | 816 | } |
@@ -780,9 +825,9 @@ fn foo() { | |||
780 | } | 825 | } |
781 | "#, | 826 | "#, |
782 | expect![[r#" | 827 | expect![[r#" |
783 | i Local FileId(0) 19..20 19..20 Other | 828 | i Local FileId(0) 19..20 19..20 |
784 | 829 | ||
785 | FileId(0) 26..27 Other Write | 830 | FileId(0) 26..27 Write |
786 | "#]], | 831 | "#]], |
787 | ); | 832 | ); |
788 | } | 833 | } |
@@ -804,9 +849,9 @@ fn main() { | |||
804 | } | 849 | } |
805 | "#, | 850 | "#, |
806 | expect![[r#" | 851 | expect![[r#" |
807 | new Function FileId(0) 54..81 61..64 Other | 852 | new Function FileId(0) 54..81 61..64 |
808 | 853 | ||
809 | FileId(0) 126..129 StructLiteral | 854 | FileId(0) 126..129 |
810 | "#]], | 855 | "#]], |
811 | ); | 856 | ); |
812 | } | 857 | } |
@@ -826,10 +871,10 @@ use crate::f; | |||
826 | fn g() { f(); } | 871 | fn g() { f(); } |
827 | "#, | 872 | "#, |
828 | expect![[r#" | 873 | expect![[r#" |
829 | f Function FileId(0) 22..31 25..26 Other | 874 | f Function FileId(0) 22..31 25..26 |
830 | 875 | ||
831 | FileId(1) 11..12 Other | 876 | FileId(1) 11..12 |
832 | FileId(1) 24..25 StructLiteral | 877 | FileId(1) 24..25 |
833 | "#]], | 878 | "#]], |
834 | ); | 879 | ); |
835 | } | 880 | } |
@@ -849,9 +894,9 @@ fn f(s: S) { | |||
849 | } | 894 | } |
850 | "#, | 895 | "#, |
851 | expect![[r#" | 896 | expect![[r#" |
852 | field Field FileId(0) 15..24 15..20 Other | 897 | field Field FileId(0) 15..24 15..20 |
853 | 898 | ||
854 | FileId(0) 68..73 FieldShorthandForField Read | 899 | FileId(0) 68..73 Read |
855 | "#]], | 900 | "#]], |
856 | ); | 901 | ); |
857 | } | 902 | } |
@@ -873,9 +918,9 @@ fn f(e: En) { | |||
873 | } | 918 | } |
874 | "#, | 919 | "#, |
875 | expect![[r#" | 920 | expect![[r#" |
876 | field Field FileId(0) 32..41 32..37 Other | 921 | field Field FileId(0) 32..41 32..37 |
877 | 922 | ||
878 | FileId(0) 102..107 FieldShorthandForField Read | 923 | FileId(0) 102..107 Read |
879 | "#]], | 924 | "#]], |
880 | ); | 925 | ); |
881 | } | 926 | } |
@@ -897,9 +942,9 @@ fn f() -> m::En { | |||
897 | } | 942 | } |
898 | "#, | 943 | "#, |
899 | expect![[r#" | 944 | expect![[r#" |
900 | field Field FileId(0) 56..65 56..61 Other | 945 | field Field FileId(0) 56..65 56..61 |
901 | 946 | ||
902 | FileId(0) 125..130 RecordFieldExprOrPat Read | 947 | FileId(0) 125..130 Read |
903 | "#]], | 948 | "#]], |
904 | ); | 949 | ); |
905 | } | 950 | } |
@@ -922,10 +967,10 @@ impl Foo { | |||
922 | } | 967 | } |
923 | "#, | 968 | "#, |
924 | expect![[r#" | 969 | expect![[r#" |
925 | self SelfParam FileId(0) 47..51 47..51 SelfParam | 970 | self SelfParam FileId(0) 47..51 47..51 |
926 | 971 | ||
927 | FileId(0) 71..75 Other Read | 972 | FileId(0) 71..75 Read |
928 | FileId(0) 152..156 Other Read | 973 | FileId(0) 152..156 Read |
929 | "#]], | 974 | "#]], |
930 | ); | 975 | ); |
931 | } | 976 | } |
@@ -943,9 +988,9 @@ impl Foo { | |||
943 | } | 988 | } |
944 | "#, | 989 | "#, |
945 | expect![[r#" | 990 | expect![[r#" |
946 | self SelfParam FileId(0) 47..51 47..51 SelfParam | 991 | self SelfParam FileId(0) 47..51 47..51 |
947 | 992 | ||
948 | FileId(0) 63..67 Other Read | 993 | FileId(0) 63..67 Read |
949 | "#]], | 994 | "#]], |
950 | ); | 995 | ); |
951 | } | 996 | } |
@@ -961,7 +1006,7 @@ impl Foo { | |||
961 | let mut actual = String::new(); | 1006 | let mut actual = String::new(); |
962 | { | 1007 | { |
963 | let decl = refs.declaration; | 1008 | let decl = refs.declaration; |
964 | format_to!(actual, "{} {:?}", decl.nav.debug_render(), decl.kind); | 1009 | format_to!(actual, "{}", decl.nav.debug_render()); |
965 | if let Some(access) = decl.access { | 1010 | if let Some(access) = decl.access { |
966 | format_to!(actual, " {:?}", access) | 1011 | format_to!(actual, " {:?}", access) |
967 | } | 1012 | } |
@@ -969,9 +1014,9 @@ impl Foo { | |||
969 | } | 1014 | } |
970 | 1015 | ||
971 | for (file_id, references) in refs.references { | 1016 | for (file_id, references) in refs.references { |
972 | for r in references { | 1017 | for (range, access) in references { |
973 | format_to!(actual, "{:?} {:?} {:?}", file_id, r.range, r.kind); | 1018 | format_to!(actual, "{:?} {:?}", file_id, range); |
974 | if let Some(access) = r.access { | 1019 | if let Some(access) = access { |
975 | format_to!(actual, " {:?}", access); | 1020 | format_to!(actual, " {:?}", access); |
976 | } | 1021 | } |
977 | actual += "\n"; | 1022 | actual += "\n"; |
@@ -992,13 +1037,13 @@ fn foo<'a, 'b: 'a>(x: &'a$0 ()) -> &'a () where &'a (): Foo<'a> { | |||
992 | } | 1037 | } |
993 | "#, | 1038 | "#, |
994 | expect![[r#" | 1039 | expect![[r#" |
995 | 'a LifetimeParam FileId(0) 55..57 55..57 Lifetime | 1040 | 'a LifetimeParam FileId(0) 55..57 55..57 |
996 | 1041 | ||
997 | FileId(0) 63..65 Lifetime | 1042 | FileId(0) 63..65 |
998 | FileId(0) 71..73 Lifetime | 1043 | FileId(0) 71..73 |
999 | FileId(0) 82..84 Lifetime | 1044 | FileId(0) 82..84 |
1000 | FileId(0) 95..97 Lifetime | 1045 | FileId(0) 95..97 |
1001 | FileId(0) 106..108 Lifetime | 1046 | FileId(0) 106..108 |
1002 | "#]], | 1047 | "#]], |
1003 | ); | 1048 | ); |
1004 | } | 1049 | } |
@@ -1010,10 +1055,10 @@ fn foo<'a, 'b: 'a>(x: &'a$0 ()) -> &'a () where &'a (): Foo<'a> { | |||
1010 | type Foo<'a, T> where T: 'a$0 = &'a T; | 1055 | type Foo<'a, T> where T: 'a$0 = &'a T; |
1011 | "#, | 1056 | "#, |
1012 | expect![[r#" | 1057 | expect![[r#" |
1013 | 'a LifetimeParam FileId(0) 9..11 9..11 Lifetime | 1058 | 'a LifetimeParam FileId(0) 9..11 9..11 |
1014 | 1059 | ||
1015 | FileId(0) 25..27 Lifetime | 1060 | FileId(0) 25..27 |
1016 | FileId(0) 31..33 Lifetime | 1061 | FileId(0) 31..33 |
1017 | "#]], | 1062 | "#]], |
1018 | ); | 1063 | ); |
1019 | } | 1064 | } |
@@ -1032,11 +1077,11 @@ impl<'a> Foo<'a> for &'a () { | |||
1032 | } | 1077 | } |
1033 | "#, | 1078 | "#, |
1034 | expect![[r#" | 1079 | expect![[r#" |
1035 | 'a LifetimeParam FileId(0) 47..49 47..49 Lifetime | 1080 | 'a LifetimeParam FileId(0) 47..49 47..49 |
1036 | 1081 | ||
1037 | FileId(0) 55..57 Lifetime | 1082 | FileId(0) 55..57 |
1038 | FileId(0) 64..66 Lifetime | 1083 | FileId(0) 64..66 |
1039 | FileId(0) 89..91 Lifetime | 1084 | FileId(0) 89..91 |
1040 | "#]], | 1085 | "#]], |
1041 | ); | 1086 | ); |
1042 | } | 1087 | } |
@@ -1052,9 +1097,9 @@ fn main() { | |||
1052 | } | 1097 | } |
1053 | "#, | 1098 | "#, |
1054 | expect![[r#" | 1099 | expect![[r#" |
1055 | a Local FileId(0) 59..60 59..60 Other | 1100 | a Local FileId(0) 59..60 59..60 |
1056 | 1101 | ||
1057 | FileId(0) 80..81 Other Read | 1102 | FileId(0) 80..81 Read |
1058 | "#]], | 1103 | "#]], |
1059 | ); | 1104 | ); |
1060 | } | 1105 | } |
@@ -1070,9 +1115,9 @@ fn main() { | |||
1070 | } | 1115 | } |
1071 | "#, | 1116 | "#, |
1072 | expect![[r#" | 1117 | expect![[r#" |
1073 | a Local FileId(0) 59..60 59..60 Other | 1118 | a Local FileId(0) 59..60 59..60 |
1074 | 1119 | ||
1075 | FileId(0) 80..81 Other Read | 1120 | FileId(0) 80..81 Read |
1076 | "#]], | 1121 | "#]], |
1077 | ); | 1122 | ); |
1078 | } | 1123 | } |
@@ -1091,10 +1136,10 @@ fn foo<'a>() -> &'a () { | |||
1091 | } | 1136 | } |
1092 | "#, | 1137 | "#, |
1093 | expect![[r#" | 1138 | expect![[r#" |
1094 | 'a Label FileId(0) 29..32 29..31 Lifetime | 1139 | 'a Label FileId(0) 29..32 29..31 |
1095 | 1140 | ||
1096 | FileId(0) 80..82 Lifetime | 1141 | FileId(0) 80..82 |
1097 | FileId(0) 108..110 Lifetime | 1142 | FileId(0) 108..110 |
1098 | "#]], | 1143 | "#]], |
1099 | ); | 1144 | ); |
1100 | } | 1145 | } |
@@ -1108,9 +1153,108 @@ fn foo<const FOO$0: usize>() -> usize { | |||
1108 | } | 1153 | } |
1109 | "#, | 1154 | "#, |
1110 | expect![[r#" | 1155 | expect![[r#" |
1111 | FOO ConstParam FileId(0) 7..23 13..16 Other | 1156 | FOO ConstParam FileId(0) 7..23 13..16 |
1157 | |||
1158 | FileId(0) 42..45 | ||
1159 | "#]], | ||
1160 | ); | ||
1161 | } | ||
1162 | |||
1163 | #[test] | ||
1164 | fn test_find_self_ty_in_trait_def() { | ||
1165 | check( | ||
1166 | r#" | ||
1167 | trait Foo { | ||
1168 | fn f() -> Self$0; | ||
1169 | } | ||
1170 | "#, | ||
1171 | expect![[r#" | ||
1172 | Self TypeParam FileId(0) 6..9 6..9 | ||
1173 | |||
1174 | FileId(0) 26..30 | ||
1175 | "#]], | ||
1176 | ); | ||
1177 | } | ||
1178 | |||
1179 | #[test] | ||
1180 | fn test_self_variant_with_payload() { | ||
1181 | check( | ||
1182 | r#" | ||
1183 | enum Foo { Bar() } | ||
1184 | |||
1185 | impl Foo { | ||
1186 | fn foo(self) { | ||
1187 | match self { | ||
1188 | Self::Bar$0() => (), | ||
1189 | } | ||
1190 | } | ||
1191 | } | ||
1192 | |||
1193 | "#, | ||
1194 | expect![[r#" | ||
1195 | Bar Variant FileId(0) 11..16 11..14 | ||
1196 | |||
1197 | FileId(0) 89..92 | ||
1198 | "#]], | ||
1199 | ); | ||
1200 | } | ||
1201 | |||
1202 | #[test] | ||
1203 | fn test_attr_differs_from_fn_with_same_name() { | ||
1204 | check( | ||
1205 | r#" | ||
1206 | #[test] | ||
1207 | fn test$0() { | ||
1208 | test(); | ||
1209 | } | ||
1210 | "#, | ||
1211 | expect![[r#" | ||
1212 | test Function FileId(0) 0..33 11..15 | ||
1213 | |||
1214 | FileId(0) 24..28 | ||
1215 | "#]], | ||
1216 | ); | ||
1217 | } | ||
1218 | |||
1219 | #[test] | ||
1220 | fn test_attr_matches_proc_macro_fn() { | ||
1221 | check( | ||
1222 | r#" | ||
1223 | #[proc_macro_attribute] | ||
1224 | fn my_proc_macro() {} | ||
1225 | |||
1226 | #[my_proc_macro$0] | ||
1227 | fn test() {} | ||
1228 | "#, | ||
1229 | expect![[r#" | ||
1230 | my_proc_macro Function FileId(0) 0..45 27..40 | ||
1231 | |||
1232 | FileId(0) 49..62 | ||
1233 | "#]], | ||
1234 | ); | ||
1235 | } | ||
1236 | |||
1237 | #[test] | ||
1238 | fn test_const_in_pattern() { | ||
1239 | check( | ||
1240 | r#" | ||
1241 | const A$0: i32 = 42; | ||
1242 | |||
1243 | fn main() { | ||
1244 | match A { | ||
1245 | A => (), | ||
1246 | _ => (), | ||
1247 | } | ||
1248 | if let A = A {} | ||
1249 | } | ||
1250 | "#, | ||
1251 | expect![[r#" | ||
1252 | A Const FileId(0) 0..18 6..7 | ||
1112 | 1253 | ||
1113 | FileId(0) 42..45 Other | 1254 | FileId(0) 42..43 |
1255 | FileId(0) 54..55 | ||
1256 | FileId(0) 97..98 | ||
1257 | FileId(0) 101..102 | ||
1114 | "#]], | 1258 | "#]], |
1115 | ); | 1259 | ); |
1116 | } | 1260 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 4df189c98..22ddeeae3 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -1,25 +1,23 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | use std::fmt::{self, Display}; | 2 | use std::fmt::{self, Display}; |
3 | 3 | ||
4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 4 | use either::Either; |
5 | use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; | ||
5 | use ide_db::{ | 6 | use ide_db::{ |
6 | base_db::{AnchoredPathBuf, FileId, FileRange}, | 7 | base_db::{AnchoredPathBuf, FileId}, |
7 | defs::{Definition, NameClass, NameRefClass}, | 8 | defs::{Definition, NameClass, NameRefClass}, |
8 | search::FileReference, | 9 | search::FileReference, |
9 | RootDatabase, | 10 | RootDatabase, |
10 | }; | 11 | }; |
12 | use stdx::never; | ||
11 | use syntax::{ | 13 | use syntax::{ |
12 | algo::find_node_at_offset, | ||
13 | ast::{self, NameOwner}, | 14 | ast::{self, NameOwner}, |
14 | lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T, | 15 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, |
15 | }; | 16 | }; |
16 | use test_utils::mark; | 17 | use test_utils::mark; |
17 | use text_edit::TextEdit; | 18 | use text_edit::TextEdit; |
18 | 19 | ||
19 | use crate::{ | 20 | use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; |
20 | FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, | ||
21 | TextRange, | ||
22 | }; | ||
23 | 21 | ||
24 | type RenameResult<T> = Result<T, RenameError>; | 22 | type RenameResult<T> = Result<T, RenameError>; |
25 | #[derive(Debug)] | 23 | #[derive(Debug)] |
@@ -40,6 +38,8 @@ macro_rules! bail { | |||
40 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | 38 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} |
41 | } | 39 | } |
42 | 40 | ||
41 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is | ||
42 | /// being targeted for a rename. | ||
43 | pub(crate) fn prepare_rename( | 43 | pub(crate) fn prepare_rename( |
44 | db: &RootDatabase, | 44 | db: &RootDatabase, |
45 | position: FilePosition, | 45 | position: FilePosition, |
@@ -47,20 +47,32 @@ pub(crate) fn prepare_rename( | |||
47 | let sema = Semantics::new(db); | 47 | let sema = Semantics::new(db); |
48 | let source_file = sema.parse(position.file_id); | 48 | let source_file = sema.parse(position.file_id); |
49 | let syntax = source_file.syntax(); | 49 | let syntax = source_file.syntax(); |
50 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 50 | let range = match &sema |
51 | rename_mod(&sema, position, module, "dummy") | 51 | .find_node_at_offset_with_descend(&syntax, position.offset) |
52 | } else { | 52 | .ok_or_else(|| format_err!("No references found at position"))? |
53 | let RangeInfo { range, .. } = find_all_refs(&sema, position)?; | 53 | { |
54 | Ok(RangeInfo::new(range, SourceChange::default())) | 54 | ast::NameLike::Name(it) => it.syntax(), |
55 | } | 55 | ast::NameLike::NameRef(it) => it.syntax(), |
56 | .map(|info| RangeInfo::new(info.range, ())) | 56 | ast::NameLike::Lifetime(it) => it.syntax(), |
57 | } | 57 | } |
58 | 58 | .text_range(); | |
59 | Ok(RangeInfo::new(range, ())) | ||
60 | } | ||
61 | |||
62 | // Feature: Rename | ||
63 | // | ||
64 | // Renames the item below the cursor and all of its references | ||
65 | // | ||
66 | // |=== | ||
67 | // | Editor | Shortcut | ||
68 | // | ||
69 | // | VS Code | kbd:[F2] | ||
70 | // |=== | ||
59 | pub(crate) fn rename( | 71 | pub(crate) fn rename( |
60 | db: &RootDatabase, | 72 | db: &RootDatabase, |
61 | position: FilePosition, | 73 | position: FilePosition, |
62 | new_name: &str, | 74 | new_name: &str, |
63 | ) -> RenameResult<RangeInfo<SourceChange>> { | 75 | ) -> RenameResult<SourceChange> { |
64 | let sema = Semantics::new(db); | 76 | let sema = Semantics::new(db); |
65 | rename_with_semantics(&sema, position, new_name) | 77 | rename_with_semantics(&sema, position, new_name) |
66 | } | 78 | } |
@@ -69,14 +81,14 @@ pub(crate) fn rename_with_semantics( | |||
69 | sema: &Semantics<RootDatabase>, | 81 | sema: &Semantics<RootDatabase>, |
70 | position: FilePosition, | 82 | position: FilePosition, |
71 | new_name: &str, | 83 | new_name: &str, |
72 | ) -> RenameResult<RangeInfo<SourceChange>> { | 84 | ) -> RenameResult<SourceChange> { |
73 | let source_file = sema.parse(position.file_id); | 85 | let source_file = sema.parse(position.file_id); |
74 | let syntax = source_file.syntax(); | 86 | let syntax = source_file.syntax(); |
75 | 87 | ||
76 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 88 | let def = find_definition(sema, syntax, position)?; |
77 | rename_mod(&sema, position, module, new_name) | 89 | match def { |
78 | } else { | 90 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), |
79 | rename_reference(&sema, position, new_name) | 91 | def => rename_reference(sema, def, new_name), |
80 | } | 92 | } |
81 | } | 93 | } |
82 | 94 | ||
@@ -87,12 +99,7 @@ pub(crate) fn will_rename_file( | |||
87 | ) -> Option<SourceChange> { | 99 | ) -> Option<SourceChange> { |
88 | let sema = Semantics::new(db); | 100 | let sema = Semantics::new(db); |
89 | let module = sema.to_module_def(file_id)?; | 101 | let module = sema.to_module_def(file_id)?; |
90 | 102 | let mut change = rename_mod(&sema, module, new_name_stem).ok()?; | |
91 | let decl = module.declaration_source(db)?; | ||
92 | let range = decl.value.name()?.syntax().text_range(); | ||
93 | |||
94 | let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() }; | ||
95 | let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info; | ||
96 | change.file_system_edits.clear(); | 103 | change.file_system_edits.clear(); |
97 | Some(change) | 104 | Some(change) |
98 | } | 105 | } |
@@ -124,179 +131,232 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | |||
124 | } | 131 | } |
125 | } | 132 | } |
126 | 133 | ||
127 | fn find_module_at_offset( | 134 | fn find_definition( |
128 | sema: &Semantics<RootDatabase>, | 135 | sema: &Semantics<RootDatabase>, |
129 | position: FilePosition, | ||
130 | syntax: &SyntaxNode, | 136 | syntax: &SyntaxNode, |
131 | ) -> Option<Module> { | ||
132 | let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; | ||
133 | |||
134 | let module = match_ast! { | ||
135 | match (ident.parent()) { | ||
136 | ast::NameRef(name_ref) => { | ||
137 | match NameRefClass::classify(sema, &name_ref)? { | ||
138 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
139 | _ => return None, | ||
140 | } | ||
141 | }, | ||
142 | ast::Name(name) => { | ||
143 | match NameClass::classify(&sema, &name)? { | ||
144 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
145 | _ => return None, | ||
146 | } | ||
147 | }, | ||
148 | _ => return None, | ||
149 | } | ||
150 | }; | ||
151 | |||
152 | Some(module) | ||
153 | } | ||
154 | |||
155 | fn find_all_refs( | ||
156 | sema: &Semantics<RootDatabase>, | ||
157 | position: FilePosition, | 137 | position: FilePosition, |
158 | ) -> RenameResult<RangeInfo<ReferenceSearchResult>> { | 138 | ) -> RenameResult<Definition> { |
159 | crate::references::find_all_refs(sema, position, None) | 139 | match sema |
160 | .ok_or_else(|| format_err!("No references found at position")) | 140 | .find_node_at_offset_with_descend(syntax, position.offset) |
141 | .ok_or_else(|| format_err!("No references found at position"))? | ||
142 | { | ||
143 | // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet | ||
144 | ast::NameLike::Name(name) | ||
145 | if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) => | ||
146 | { | ||
147 | bail!("Renaming aliases is currently unsupported") | ||
148 | } | ||
149 | ast::NameLike::Name(name) => { | ||
150 | NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db)) | ||
151 | } | ||
152 | ast::NameLike::NameRef(name_ref) => { | ||
153 | NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db)) | ||
154 | } | ||
155 | ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) | ||
156 | .map(|class| NameRefClass::referenced(class, sema.db)) | ||
157 | .or_else(|| { | ||
158 | NameClass::classify_lifetime(sema, &lifetime) | ||
159 | .map(|it| it.referenced_or_defined(sema.db)) | ||
160 | }), | ||
161 | } | ||
162 | .ok_or_else(|| format_err!("No references found at position")) | ||
161 | } | 163 | } |
162 | 164 | ||
163 | fn source_edit_from_references( | 165 | fn source_edit_from_references( |
164 | sema: &Semantics<RootDatabase>, | 166 | _sema: &Semantics<RootDatabase>, |
165 | file_id: FileId, | 167 | file_id: FileId, |
166 | references: &[FileReference], | 168 | references: &[FileReference], |
169 | def: Definition, | ||
167 | new_name: &str, | 170 | new_name: &str, |
168 | ) -> (FileId, TextEdit) { | 171 | ) -> (FileId, TextEdit) { |
169 | let mut edit = TextEdit::builder(); | 172 | let mut edit = TextEdit::builder(); |
170 | for reference in references { | 173 | for reference in references { |
171 | let mut replacement_text = String::new(); | 174 | let (range, replacement) = match &reference.name { |
172 | let range = match reference.kind { | 175 | // if the ranges differ then the node is inside a macro call, we can't really attempt |
173 | ReferenceKind::FieldShorthandForField => { | 176 | // to make special rewrites like shorthand syntax and such, so just rename the node in |
174 | mark::hit!(test_rename_struct_field_for_shorthand); | 177 | // the macro input |
175 | replacement_text.push_str(new_name); | 178 | ast::NameLike::NameRef(name_ref) |
176 | replacement_text.push_str(": "); | 179 | if name_ref.syntax().text_range() == reference.range => |
177 | TextRange::new(reference.range.start(), reference.range.start()) | 180 | { |
178 | } | 181 | source_edit_from_name_ref(name_ref, new_name, def) |
179 | ReferenceKind::FieldShorthandForLocal => { | ||
180 | mark::hit!(test_rename_local_for_field_shorthand); | ||
181 | replacement_text.push_str(": "); | ||
182 | replacement_text.push_str(new_name); | ||
183 | TextRange::new(reference.range.end(), reference.range.end()) | ||
184 | } | 182 | } |
185 | ReferenceKind::RecordFieldExprOrPat => { | 183 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { |
186 | mark::hit!(test_rename_field_expr_pat); | 184 | source_edit_from_name(name, new_name) |
187 | replacement_text.push_str(new_name); | ||
188 | edit_text_range_for_record_field_expr_or_pat( | ||
189 | sema, | ||
190 | FileRange { file_id, range: reference.range }, | ||
191 | new_name, | ||
192 | ) | ||
193 | } | 185 | } |
194 | _ => { | 186 | _ => None, |
195 | replacement_text.push_str(new_name); | 187 | } |
196 | reference.range | 188 | .unwrap_or_else(|| (reference.range, new_name.to_string())); |
197 | } | 189 | edit.replace(range, replacement); |
198 | }; | ||
199 | edit.replace(range, replacement_text); | ||
200 | } | 190 | } |
201 | (file_id, edit.finish()) | 191 | (file_id, edit.finish()) |
202 | } | 192 | } |
203 | 193 | ||
204 | fn edit_text_range_for_record_field_expr_or_pat( | 194 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { |
205 | sema: &Semantics<RootDatabase>, | 195 | if let Some(_) = ast::RecordPatField::for_field_name(name) { |
206 | file_range: FileRange, | 196 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { |
197 | return Some(( | ||
198 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
199 | format!("{}: ", new_name), | ||
200 | )); | ||
201 | } | ||
202 | } | ||
203 | None | ||
204 | } | ||
205 | |||
206 | fn source_edit_from_name_ref( | ||
207 | name_ref: &ast::NameRef, | ||
207 | new_name: &str, | 208 | new_name: &str, |
208 | ) -> TextRange { | 209 | def: Definition, |
209 | let source_file = sema.parse(file_range.file_id); | 210 | ) -> Option<(TextRange, String)> { |
210 | let file_syntax = source_file.syntax(); | 211 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { |
211 | let original_range = file_range.range; | 212 | let rcf_name_ref = record_field.name_ref(); |
212 | 213 | let rcf_expr = record_field.expr(); | |
213 | syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range) | 214 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { |
214 | .and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) { | 215 | // field: init-expr, check if we can use a field init shorthand |
215 | Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()), | 216 | (Some(field_name), Some(init)) => { |
216 | _ => None, | 217 | if field_name == *name_ref { |
217 | }) | 218 | if init.text() == new_name { |
218 | .or_else(|| { | 219 | mark::hit!(test_rename_field_put_init_shorthand); |
219 | syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range) | 220 | // same names, we can use a shorthand here instead. |
220 | .and_then(|field_pat| match field_pat.pat() { | 221 | // we do not want to erase attributes hence this range start |
221 | Some(ast::Pat::IdentPat(pat)) | 222 | let s = field_name.syntax().text_range().start(); |
222 | if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) => | 223 | let e = record_field.syntax().text_range().end(); |
223 | { | 224 | return Some((TextRange::new(s, e), new_name.to_owned())); |
224 | Some(field_pat.syntax().text_range()) | ||
225 | } | 225 | } |
226 | _ => None, | 226 | } else if init == *name_ref { |
227 | }) | 227 | if field_name.text() == new_name { |
228 | }) | 228 | mark::hit!(test_rename_local_put_init_shorthand); |
229 | .unwrap_or(original_range) | 229 | // same names, we can use a shorthand here instead. |
230 | // we do not want to erase attributes hence this range start | ||
231 | let s = field_name.syntax().text_range().start(); | ||
232 | let e = record_field.syntax().text_range().end(); | ||
233 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
234 | } | ||
235 | } | ||
236 | None | ||
237 | } | ||
238 | // init shorthand | ||
239 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
240 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
241 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
242 | mark::hit!(test_rename_field_in_field_shorthand); | ||
243 | let s = name_ref.syntax().text_range().start(); | ||
244 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
245 | } | ||
246 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
247 | mark::hit!(test_rename_local_in_field_shorthand); | ||
248 | let s = name_ref.syntax().text_range().end(); | ||
249 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
250 | } | ||
251 | _ => None, | ||
252 | } | ||
253 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
254 | let rcf_name_ref = record_field.name_ref(); | ||
255 | let rcf_pat = record_field.pat(); | ||
256 | match (rcf_name_ref, rcf_pat) { | ||
257 | // field: rename | ||
258 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
259 | // field name is being renamed | ||
260 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
261 | mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
262 | // same names, we can use a shorthand here instead/ | ||
263 | // we do not want to erase attributes hence this range start | ||
264 | let s = field_name.syntax().text_range().start(); | ||
265 | let e = record_field.syntax().text_range().end(); | ||
266 | Some((TextRange::new(s, e), pat.to_string())) | ||
267 | } else { | ||
268 | None | ||
269 | } | ||
270 | } | ||
271 | _ => None, | ||
272 | } | ||
273 | } else { | ||
274 | None | ||
275 | } | ||
230 | } | 276 | } |
231 | 277 | ||
232 | fn rename_mod( | 278 | fn rename_mod( |
233 | sema: &Semantics<RootDatabase>, | 279 | sema: &Semantics<RootDatabase>, |
234 | position: FilePosition, | ||
235 | module: Module, | 280 | module: Module, |
236 | new_name: &str, | 281 | new_name: &str, |
237 | ) -> RenameResult<RangeInfo<SourceChange>> { | 282 | ) -> RenameResult<SourceChange> { |
238 | if IdentifierKind::Ident != check_identifier(new_name)? { | 283 | if IdentifierKind::Ident != check_identifier(new_name)? { |
239 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | 284 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); |
240 | } | 285 | } |
241 | 286 | ||
242 | let mut source_change = SourceChange::default(); | 287 | let mut source_change = SourceChange::default(); |
243 | 288 | ||
244 | let src = module.definition_source(sema.db); | 289 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); |
245 | let file_id = src.file_id.original_file(sema.db); | 290 | let file_id = file_id.original_file(sema.db); |
246 | match src.value { | 291 | if let ModuleSource::SourceFile(..) = def_source { |
247 | ModuleSource::SourceFile(..) => { | 292 | // mod is defined in path/to/dir/mod.rs |
248 | // mod is defined in path/to/dir/mod.rs | 293 | let path = if module.is_mod_rs(sema.db) { |
249 | let path = if module.is_mod_rs(sema.db) { | 294 | format!("../{}/mod.rs", new_name) |
250 | format!("../{}/mod.rs", new_name) | 295 | } else { |
251 | } else { | 296 | format!("{}.rs", new_name) |
252 | format!("{}.rs", new_name) | 297 | }; |
253 | }; | 298 | let dst = AnchoredPathBuf { anchor: file_id, path }; |
254 | let dst = AnchoredPathBuf { anchor: file_id, path }; | 299 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; |
255 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | 300 | source_change.push_file_system_edit(move_file); |
256 | source_change.push_file_system_edit(move_file); | 301 | } |
257 | } | 302 | |
258 | ModuleSource::Module(..) => {} | 303 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { |
259 | } | 304 | let file_id = file_id.original_file(sema.db); |
260 | 305 | match decl_source.name() { | |
261 | if let Some(src) = module.declaration_source(sema.db) { | 306 | Some(name) => source_change.insert_source_edit( |
262 | let file_id = src.file_id.original_file(sema.db); | 307 | file_id, |
263 | let name = src.value.name().unwrap(); | 308 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), |
264 | source_change.insert_source_edit( | 309 | ), |
265 | file_id, | 310 | _ => unreachable!(), |
266 | TextEdit::replace(name.syntax().text_range(), new_name.into()), | 311 | }; |
267 | ); | ||
268 | } | 312 | } |
269 | 313 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | |
270 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 314 | let usages = def.usages(sema).all(); |
271 | let ref_edits = refs.references().iter().map(|(&file_id, references)| { | 315 | let ref_edits = usages.iter().map(|(&file_id, references)| { |
272 | source_edit_from_references(sema, file_id, references, new_name) | 316 | source_edit_from_references(sema, file_id, references, def, new_name) |
273 | }); | 317 | }); |
274 | source_change.extend(ref_edits); | 318 | source_change.extend(ref_edits); |
275 | 319 | ||
276 | Ok(RangeInfo::new(range, source_change)) | 320 | Ok(source_change) |
277 | } | 321 | } |
278 | 322 | ||
279 | fn rename_to_self( | 323 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
280 | sema: &Semantics<RootDatabase>, | 324 | if never!(local.is_self(sema.db)) { |
281 | position: FilePosition, | 325 | bail!("rename_to_self invoked on self"); |
282 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 326 | } |
283 | let source_file = sema.parse(position.file_id); | 327 | |
284 | let syn = source_file.syntax(); | 328 | let fn_def = match local.parent(sema.db) { |
329 | hir::DefWithBody::Function(func) => func, | ||
330 | _ => bail!("Cannot rename non-param local to self"), | ||
331 | }; | ||
285 | 332 | ||
286 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) | 333 | // FIXME: reimplement this on the hir instead |
287 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) | 334 | // as of the time of this writing params in hir don't keep their names |
288 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; | 335 | let fn_ast = fn_def |
289 | let param_range = fn_ast | 336 | .source(sema.db) |
337 | .ok_or_else(|| format_err!("Cannot rename non-param local to self"))? | ||
338 | .value; | ||
339 | |||
340 | let first_param_range = fn_ast | ||
290 | .param_list() | 341 | .param_list() |
291 | .and_then(|p| p.params().next()) | 342 | .and_then(|p| p.params().next()) |
292 | .ok_or_else(|| format_err!("Method has no parameters"))? | 343 | .ok_or_else(|| format_err!("Method has no parameters"))? |
293 | .syntax() | 344 | .syntax() |
294 | .text_range(); | 345 | .text_range(); |
295 | if !param_range.contains(position.offset) { | 346 | let InFile { file_id, value: local_source } = local.source(sema.db); |
296 | bail!("Only the first parameter can be self"); | 347 | match local_source { |
348 | either::Either::Left(pat) | ||
349 | if !first_param_range.contains_range(pat.syntax().text_range()) => | ||
350 | { | ||
351 | bail!("Only the first parameter can be self"); | ||
352 | } | ||
353 | _ => (), | ||
297 | } | 354 | } |
298 | 355 | ||
299 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) | 356 | let impl_block = fn_ast |
357 | .syntax() | ||
358 | .ancestors() | ||
359 | .find_map(|node| ast::Impl::cast(node)) | ||
300 | .and_then(|def| sema.to_def(&def)) | 360 | .and_then(|def| sema.to_def(&def)) |
301 | .ok_or_else(|| format_err!("No impl block found for function"))?; | 361 | .ok_or_else(|| format_err!("No impl block found for function"))?; |
302 | if fn_def.self_param(sema.db).is_some() { | 362 | if fn_def.self_param(sema.db).is_some() { |
@@ -320,25 +380,21 @@ fn rename_to_self( | |||
320 | bail!("Parameter type differs from impl block type"); | 380 | bail!("Parameter type differs from impl block type"); |
321 | } | 381 | } |
322 | 382 | ||
323 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 383 | let def = Definition::Local(local); |
324 | 384 | let usages = def.usages(sema).all(); | |
325 | let mut source_change = SourceChange::default(); | 385 | let mut source_change = SourceChange::default(); |
326 | source_change.extend(refs.references().iter().map(|(&file_id, references)| { | 386 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
327 | source_edit_from_references(sema, file_id, references, "self") | 387 | source_edit_from_references(sema, file_id, references, def, "self") |
328 | })); | 388 | })); |
329 | source_change.insert_source_edit( | 389 | source_change.insert_source_edit( |
330 | position.file_id, | 390 | file_id.original_file(sema.db), |
331 | TextEdit::replace(param_range, String::from(self_param)), | 391 | TextEdit::replace(first_param_range, String::from(self_param)), |
332 | ); | 392 | ); |
333 | 393 | ||
334 | Ok(RangeInfo::new(range, source_change)) | 394 | Ok(source_change) |
335 | } | 395 | } |
336 | 396 | ||
337 | fn text_edit_from_self_param( | 397 | fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { |
338 | syn: &SyntaxNode, | ||
339 | self_param: &ast::SelfParam, | ||
340 | new_name: &str, | ||
341 | ) -> Option<TextEdit> { | ||
342 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { | 398 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { |
343 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | 399 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { |
344 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | 400 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); |
@@ -346,7 +402,7 @@ fn text_edit_from_self_param( | |||
346 | None | 402 | None |
347 | } | 403 | } |
348 | 404 | ||
349 | let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; | 405 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; |
350 | let type_name = target_type_name(&impl_def)?; | 406 | let type_name = target_type_name(&impl_def)?; |
351 | 407 | ||
352 | let mut replacement_text = String::from(new_name); | 408 | let mut replacement_text = String::from(new_name); |
@@ -363,94 +419,119 @@ fn text_edit_from_self_param( | |||
363 | 419 | ||
364 | fn rename_self_to_param( | 420 | fn rename_self_to_param( |
365 | sema: &Semantics<RootDatabase>, | 421 | sema: &Semantics<RootDatabase>, |
366 | position: FilePosition, | 422 | local: hir::Local, |
367 | new_name: &str, | 423 | new_name: &str, |
368 | ident_kind: IdentifierKind, | 424 | identifier_kind: IdentifierKind, |
369 | range: TextRange, | 425 | ) -> RenameResult<SourceChange> { |
370 | refs: ReferenceSearchResult, | 426 | let (file_id, self_param) = match local.source(sema.db) { |
371 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 427 | InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param), |
372 | match ident_kind { | 428 | _ => { |
373 | IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), | 429 | never!(true, "rename_self_to_param invoked on a non-self local"); |
374 | IdentifierKind::ToSelf => { | 430 | bail!("rename_self_to_param invoked on a non-self local"); |
375 | // no-op | ||
376 | mark::hit!(rename_self_to_self); | ||
377 | return Ok(RangeInfo::new(range, SourceChange::default())); | ||
378 | } | 431 | } |
379 | _ => (), | 432 | }; |
380 | } | ||
381 | let source_file = sema.parse(position.file_id); | ||
382 | let syn = source_file.syntax(); | ||
383 | |||
384 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) | ||
385 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; | ||
386 | |||
387 | let mut source_change = SourceChange::default(); | ||
388 | if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) { | ||
389 | if self_param | ||
390 | .syntax() | ||
391 | .text_range() | ||
392 | .contains_range(refs.declaration().nav.focus_or_full_range()) | ||
393 | { | ||
394 | let edit = text_edit_from_self_param(syn, &self_param, new_name) | ||
395 | .ok_or_else(|| format_err!("No target type found"))?; | ||
396 | source_change.insert_source_edit(position.file_id, edit); | ||
397 | |||
398 | source_change.extend(refs.references().iter().map(|(&file_id, references)| { | ||
399 | source_edit_from_references(sema, file_id, &references, new_name) | ||
400 | })); | ||
401 | |||
402 | if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore | ||
403 | { | ||
404 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
405 | } | ||
406 | 433 | ||
407 | return Ok(RangeInfo::new(range, source_change)); | 434 | let def = Definition::Local(local); |
408 | } | 435 | let usages = def.usages(sema).all(); |
436 | let edit = text_edit_from_self_param(&self_param, new_name) | ||
437 | .ok_or_else(|| format_err!("No target type found"))?; | ||
438 | if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { | ||
439 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
409 | } | 440 | } |
410 | Err(format_err!("Method has no self param")) | 441 | let mut source_change = SourceChange::default(); |
442 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); | ||
443 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
444 | source_edit_from_references(sema, file_id, &references, def, new_name) | ||
445 | })); | ||
446 | Ok(source_change) | ||
411 | } | 447 | } |
412 | 448 | ||
413 | fn rename_reference( | 449 | fn rename_reference( |
414 | sema: &Semantics<RootDatabase>, | 450 | sema: &Semantics<RootDatabase>, |
415 | position: FilePosition, | 451 | def: Definition, |
416 | new_name: &str, | 452 | new_name: &str, |
417 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 453 | ) -> RenameResult<SourceChange> { |
418 | let ident_kind = check_identifier(new_name)?; | 454 | let ident_kind = check_identifier(new_name)?; |
419 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | ||
420 | 455 | ||
421 | match (ident_kind, &refs.declaration.kind) { | 456 | let def_is_lbl_or_lt = matches!( |
422 | (IdentifierKind::ToSelf, ReferenceKind::Lifetime) | 457 | def, |
423 | | (IdentifierKind::Underscore, ReferenceKind::Lifetime) | 458 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) |
424 | | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { | 459 | ); |
460 | match (ident_kind, def) { | ||
461 | (IdentifierKind::ToSelf, _) | ||
462 | | (IdentifierKind::Underscore, _) | ||
463 | | (IdentifierKind::Ident, _) | ||
464 | if def_is_lbl_or_lt => | ||
465 | { | ||
425 | mark::hit!(rename_not_a_lifetime_ident_ref); | 466 | mark::hit!(rename_not_a_lifetime_ident_ref); |
426 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | 467 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
427 | } | 468 | } |
428 | (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), | 469 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime), |
429 | (IdentifierKind::Lifetime, _) => { | 470 | (IdentifierKind::Lifetime, _) => { |
430 | mark::hit!(rename_not_an_ident_ref); | 471 | mark::hit!(rename_not_an_ident_ref); |
431 | bail!("Invalid name `{}`: not an identifier", new_name) | 472 | bail!("Invalid name `{}`: not an identifier", new_name) |
432 | } | 473 | } |
433 | (_, ReferenceKind::SelfParam) => { | 474 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { |
475 | // no-op | ||
476 | mark::hit!(rename_self_to_self); | ||
477 | return Ok(SourceChange::default()); | ||
478 | } | ||
479 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
434 | mark::hit!(rename_self_to_param); | 480 | mark::hit!(rename_self_to_param); |
435 | return rename_self_to_param(sema, position, new_name, ident_kind, range, refs); | 481 | return rename_self_to_param(sema, local, new_name, ident_kind); |
436 | } | 482 | } |
437 | (IdentifierKind::ToSelf, _) => { | 483 | (IdentifierKind::ToSelf, Definition::Local(local)) => { |
438 | mark::hit!(rename_to_self); | 484 | mark::hit!(rename_to_self); |
439 | return rename_to_self(sema, position); | 485 | return rename_to_self(sema, local); |
440 | } | ||
441 | (IdentifierKind::Underscore, _) if !refs.references.is_empty() => { | ||
442 | mark::hit!(rename_underscore_multiple); | ||
443 | bail!("Cannot rename reference to `_` as it is being referenced multiple times") | ||
444 | } | 486 | } |
487 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
445 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | 488 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), |
446 | } | 489 | } |
447 | 490 | ||
491 | let usages = def.usages(sema).all(); | ||
492 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
493 | mark::hit!(rename_underscore_multiple); | ||
494 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
495 | } | ||
448 | let mut source_change = SourceChange::default(); | 496 | let mut source_change = SourceChange::default(); |
449 | source_change.extend(refs.into_iter().map(|(file_id, references)| { | 497 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
450 | source_edit_from_references(sema, file_id, &references, new_name) | 498 | source_edit_from_references(sema, file_id, &references, def, new_name) |
451 | })); | 499 | })); |
452 | 500 | ||
453 | Ok(RangeInfo::new(range, source_change)) | 501 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; |
502 | source_change.insert_source_edit(file_id, edit); | ||
503 | Ok(source_change) | ||
504 | } | ||
505 | |||
506 | fn source_edit_from_def( | ||
507 | sema: &Semantics<RootDatabase>, | ||
508 | def: Definition, | ||
509 | new_name: &str, | ||
510 | ) -> RenameResult<(FileId, TextEdit)> { | ||
511 | let nav = def.try_to_nav(sema.db).unwrap(); | ||
512 | |||
513 | let mut replacement_text = String::new(); | ||
514 | let mut repl_range = nav.focus_or_full_range(); | ||
515 | if let Definition::Local(local) = def { | ||
516 | if let Either::Left(pat) = local.source(sema.db).value { | ||
517 | if matches!( | ||
518 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
519 | Some(pat_field) if pat_field.name_ref().is_none() | ||
520 | ) { | ||
521 | replacement_text.push_str(": "); | ||
522 | replacement_text.push_str(new_name); | ||
523 | repl_range = TextRange::new( | ||
524 | pat.syntax().text_range().end(), | ||
525 | pat.syntax().text_range().end(), | ||
526 | ); | ||
527 | } | ||
528 | } | ||
529 | } | ||
530 | if replacement_text.is_empty() { | ||
531 | replacement_text.push_str(new_name); | ||
532 | } | ||
533 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
534 | Ok((nav.file_id, edit)) | ||
454 | } | 535 | } |
455 | 536 | ||
456 | #[cfg(test)] | 537 | #[cfg(test)] |
@@ -472,7 +553,7 @@ mod tests { | |||
472 | Ok(source_change) => { | 553 | Ok(source_change) => { |
473 | let mut text_edit_builder = TextEdit::builder(); | 554 | let mut text_edit_builder = TextEdit::builder(); |
474 | let mut file_id: Option<FileId> = None; | 555 | let mut file_id: Option<FileId> = None; |
475 | for edit in source_change.info.source_file_edits { | 556 | for edit in source_change.source_file_edits { |
476 | file_id = Some(edit.0); | 557 | file_id = Some(edit.0); |
477 | for indel in edit.1.into_iter() { | 558 | for indel in edit.1.into_iter() { |
478 | text_edit_builder.replace(indel.delete, indel.insert); | 559 | text_edit_builder.replace(indel.delete, indel.insert); |
@@ -502,10 +583,8 @@ mod tests { | |||
502 | 583 | ||
503 | fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { | 584 | fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { |
504 | let (analysis, position) = fixture::position(ra_fixture); | 585 | let (analysis, position) = fixture::position(ra_fixture); |
505 | let source_change = analysis | 586 | let source_change = |
506 | .rename(position, new_name) | 587 | analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); |
507 | .unwrap() | ||
508 | .expect("Expect returned RangeInfo to be Some, but was None"); | ||
509 | expect.assert_debug_eq(&source_change) | 588 | expect.assert_debug_eq(&source_change) |
510 | } | 589 | } |
511 | 590 | ||
@@ -749,8 +828,8 @@ impl Foo { | |||
749 | } | 828 | } |
750 | 829 | ||
751 | #[test] | 830 | #[test] |
752 | fn test_rename_struct_field_for_shorthand() { | 831 | fn test_rename_field_in_field_shorthand() { |
753 | mark::check!(test_rename_struct_field_for_shorthand); | 832 | mark::check!(test_rename_field_in_field_shorthand); |
754 | check( | 833 | check( |
755 | "j", | 834 | "j", |
756 | r#" | 835 | r#" |
@@ -775,8 +854,8 @@ impl Foo { | |||
775 | } | 854 | } |
776 | 855 | ||
777 | #[test] | 856 | #[test] |
778 | fn test_rename_local_for_field_shorthand() { | 857 | fn test_rename_local_in_field_shorthand() { |
779 | mark::check!(test_rename_local_for_field_shorthand); | 858 | mark::check!(test_rename_local_in_field_shorthand); |
780 | check( | 859 | check( |
781 | "j", | 860 | "j", |
782 | r#" | 861 | r#" |
@@ -871,36 +950,33 @@ mod foo$0; | |||
871 | // empty | 950 | // empty |
872 | "#, | 951 | "#, |
873 | expect![[r#" | 952 | expect![[r#" |
874 | RangeInfo { | 953 | SourceChange { |
875 | range: 4..7, | 954 | source_file_edits: { |
876 | info: SourceChange { | 955 | FileId( |
877 | source_file_edits: { | 956 | 1, |
878 | FileId( | 957 | ): TextEdit { |
879 | 1, | 958 | indels: [ |
880 | ): TextEdit { | 959 | Indel { |
881 | indels: [ | 960 | insert: "foo2", |
882 | Indel { | 961 | delete: 4..7, |
883 | insert: "foo2", | 962 | }, |
884 | delete: 4..7, | 963 | ], |
885 | }, | ||
886 | ], | ||
887 | }, | ||
888 | }, | 964 | }, |
889 | file_system_edits: [ | 965 | }, |
890 | MoveFile { | 966 | file_system_edits: [ |
891 | src: FileId( | 967 | MoveFile { |
968 | src: FileId( | ||
969 | 2, | ||
970 | ), | ||
971 | dst: AnchoredPathBuf { | ||
972 | anchor: FileId( | ||
892 | 2, | 973 | 2, |
893 | ), | 974 | ), |
894 | dst: AnchoredPathBuf { | 975 | path: "foo2.rs", |
895 | anchor: FileId( | ||
896 | 2, | ||
897 | ), | ||
898 | path: "foo2.rs", | ||
899 | }, | ||
900 | }, | 976 | }, |
901 | ], | 977 | }, |
902 | is_snippet: false, | 978 | ], |
903 | }, | 979 | is_snippet: false, |
904 | } | 980 | } |
905 | "#]], | 981 | "#]], |
906 | ); | 982 | ); |
@@ -923,46 +999,43 @@ pub struct FooContent; | |||
923 | use crate::foo$0::FooContent; | 999 | use crate::foo$0::FooContent; |
924 | "#, | 1000 | "#, |
925 | expect![[r#" | 1001 | expect![[r#" |
926 | RangeInfo { | 1002 | SourceChange { |
927 | range: 11..14, | 1003 | source_file_edits: { |
928 | info: SourceChange { | 1004 | FileId( |
929 | source_file_edits: { | 1005 | 0, |
930 | FileId( | 1006 | ): TextEdit { |
931 | 0, | 1007 | indels: [ |
932 | ): TextEdit { | 1008 | Indel { |
933 | indels: [ | 1009 | insert: "quux", |
934 | Indel { | 1010 | delete: 8..11, |
935 | insert: "quux", | 1011 | }, |
936 | delete: 8..11, | 1012 | ], |
937 | }, | ||
938 | ], | ||
939 | }, | ||
940 | FileId( | ||
941 | 2, | ||
942 | ): TextEdit { | ||
943 | indels: [ | ||
944 | Indel { | ||
945 | insert: "quux", | ||
946 | delete: 11..14, | ||
947 | }, | ||
948 | ], | ||
949 | }, | ||
950 | }, | 1013 | }, |
951 | file_system_edits: [ | 1014 | FileId( |
952 | MoveFile { | 1015 | 2, |
953 | src: FileId( | 1016 | ): TextEdit { |
1017 | indels: [ | ||
1018 | Indel { | ||
1019 | insert: "quux", | ||
1020 | delete: 11..14, | ||
1021 | }, | ||
1022 | ], | ||
1023 | }, | ||
1024 | }, | ||
1025 | file_system_edits: [ | ||
1026 | MoveFile { | ||
1027 | src: FileId( | ||
1028 | 1, | ||
1029 | ), | ||
1030 | dst: AnchoredPathBuf { | ||
1031 | anchor: FileId( | ||
954 | 1, | 1032 | 1, |
955 | ), | 1033 | ), |
956 | dst: AnchoredPathBuf { | 1034 | path: "quux.rs", |
957 | anchor: FileId( | ||
958 | 1, | ||
959 | ), | ||
960 | path: "quux.rs", | ||
961 | }, | ||
962 | }, | 1035 | }, |
963 | ], | 1036 | }, |
964 | is_snippet: false, | 1037 | ], |
965 | }, | 1038 | is_snippet: false, |
966 | } | 1039 | } |
967 | "#]], | 1040 | "#]], |
968 | ); | 1041 | ); |
@@ -979,36 +1052,33 @@ mod fo$0o; | |||
979 | // empty | 1052 | // empty |
980 | "#, | 1053 | "#, |
981 | expect![[r#" | 1054 | expect![[r#" |
982 | RangeInfo { | 1055 | SourceChange { |
983 | range: 4..7, | 1056 | source_file_edits: { |
984 | info: SourceChange { | 1057 | FileId( |
985 | source_file_edits: { | 1058 | 0, |
986 | FileId( | 1059 | ): TextEdit { |
987 | 0, | 1060 | indels: [ |
988 | ): TextEdit { | 1061 | Indel { |
989 | indels: [ | 1062 | insert: "foo2", |
990 | Indel { | 1063 | delete: 4..7, |
991 | insert: "foo2", | 1064 | }, |
992 | delete: 4..7, | 1065 | ], |
993 | }, | ||
994 | ], | ||
995 | }, | ||
996 | }, | 1066 | }, |
997 | file_system_edits: [ | 1067 | }, |
998 | MoveFile { | 1068 | file_system_edits: [ |
999 | src: FileId( | 1069 | MoveFile { |
1070 | src: FileId( | ||
1071 | 1, | ||
1072 | ), | ||
1073 | dst: AnchoredPathBuf { | ||
1074 | anchor: FileId( | ||
1000 | 1, | 1075 | 1, |
1001 | ), | 1076 | ), |
1002 | dst: AnchoredPathBuf { | 1077 | path: "../foo2/mod.rs", |
1003 | anchor: FileId( | ||
1004 | 1, | ||
1005 | ), | ||
1006 | path: "../foo2/mod.rs", | ||
1007 | }, | ||
1008 | }, | 1078 | }, |
1009 | ], | 1079 | }, |
1010 | is_snippet: false, | 1080 | ], |
1011 | }, | 1081 | is_snippet: false, |
1012 | } | 1082 | } |
1013 | "#]], | 1083 | "#]], |
1014 | ); | 1084 | ); |
@@ -1026,36 +1096,33 @@ mod outer { mod fo$0o; } | |||
1026 | // empty | 1096 | // empty |
1027 | "#, | 1097 | "#, |
1028 | expect![[r#" | 1098 | expect![[r#" |
1029 | RangeInfo { | 1099 | SourceChange { |
1030 | range: 16..19, | 1100 | source_file_edits: { |
1031 | info: SourceChange { | 1101 | FileId( |
1032 | source_file_edits: { | 1102 | 0, |
1033 | FileId( | 1103 | ): TextEdit { |
1034 | 0, | 1104 | indels: [ |
1035 | ): TextEdit { | 1105 | Indel { |
1036 | indels: [ | 1106 | insert: "bar", |
1037 | Indel { | 1107 | delete: 16..19, |
1038 | insert: "bar", | 1108 | }, |
1039 | delete: 16..19, | 1109 | ], |
1040 | }, | ||
1041 | ], | ||
1042 | }, | ||
1043 | }, | 1110 | }, |
1044 | file_system_edits: [ | 1111 | }, |
1045 | MoveFile { | 1112 | file_system_edits: [ |
1046 | src: FileId( | 1113 | MoveFile { |
1114 | src: FileId( | ||
1115 | 1, | ||
1116 | ), | ||
1117 | dst: AnchoredPathBuf { | ||
1118 | anchor: FileId( | ||
1047 | 1, | 1119 | 1, |
1048 | ), | 1120 | ), |
1049 | dst: AnchoredPathBuf { | 1121 | path: "bar.rs", |
1050 | anchor: FileId( | ||
1051 | 1, | ||
1052 | ), | ||
1053 | path: "bar.rs", | ||
1054 | }, | ||
1055 | }, | 1122 | }, |
1056 | ], | 1123 | }, |
1057 | is_snippet: false, | 1124 | ], |
1058 | }, | 1125 | is_snippet: false, |
1059 | } | 1126 | } |
1060 | "#]], | 1127 | "#]], |
1061 | ); | 1128 | ); |
@@ -1096,46 +1163,43 @@ pub mod foo$0; | |||
1096 | // pub fn fun() {} | 1163 | // pub fn fun() {} |
1097 | "#, | 1164 | "#, |
1098 | expect![[r#" | 1165 | expect![[r#" |
1099 | RangeInfo { | 1166 | SourceChange { |
1100 | range: 8..11, | 1167 | source_file_edits: { |
1101 | info: SourceChange { | 1168 | FileId( |
1102 | source_file_edits: { | 1169 | 0, |
1103 | FileId( | 1170 | ): TextEdit { |
1104 | 0, | 1171 | indels: [ |
1105 | ): TextEdit { | 1172 | Indel { |
1106 | indels: [ | 1173 | insert: "foo2", |
1107 | Indel { | 1174 | delete: 27..30, |
1108 | insert: "foo2", | 1175 | }, |
1109 | delete: 27..30, | 1176 | ], |
1110 | }, | 1177 | }, |
1111 | ], | 1178 | FileId( |
1112 | }, | 1179 | 1, |
1113 | FileId( | 1180 | ): TextEdit { |
1114 | 1, | 1181 | indels: [ |
1115 | ): TextEdit { | 1182 | Indel { |
1116 | indels: [ | 1183 | insert: "foo2", |
1117 | Indel { | 1184 | delete: 8..11, |
1118 | insert: "foo2", | 1185 | }, |
1119 | delete: 8..11, | 1186 | ], |
1120 | }, | ||
1121 | ], | ||
1122 | }, | ||
1123 | }, | 1187 | }, |
1124 | file_system_edits: [ | 1188 | }, |
1125 | MoveFile { | 1189 | file_system_edits: [ |
1126 | src: FileId( | 1190 | MoveFile { |
1191 | src: FileId( | ||
1192 | 2, | ||
1193 | ), | ||
1194 | dst: AnchoredPathBuf { | ||
1195 | anchor: FileId( | ||
1127 | 2, | 1196 | 2, |
1128 | ), | 1197 | ), |
1129 | dst: AnchoredPathBuf { | 1198 | path: "foo2.rs", |
1130 | anchor: FileId( | ||
1131 | 2, | ||
1132 | ), | ||
1133 | path: "foo2.rs", | ||
1134 | }, | ||
1135 | }, | 1199 | }, |
1136 | ], | 1200 | }, |
1137 | is_snippet: false, | 1201 | ], |
1138 | }, | 1202 | is_snippet: false, |
1139 | } | 1203 | } |
1140 | "#]], | 1204 | "#]], |
1141 | ); | 1205 | ); |
@@ -1389,8 +1453,8 @@ impl Foo { | |||
1389 | } | 1453 | } |
1390 | 1454 | ||
1391 | #[test] | 1455 | #[test] |
1392 | fn test_initializer_use_field_init_shorthand() { | 1456 | fn test_rename_field_put_init_shorthand() { |
1393 | mark::check!(test_rename_field_expr_pat); | 1457 | mark::check!(test_rename_field_put_init_shorthand); |
1394 | check( | 1458 | check( |
1395 | "bar", | 1459 | "bar", |
1396 | r#" | 1460 | r#" |
@@ -1411,23 +1475,46 @@ fn foo(bar: i32) -> Foo { | |||
1411 | } | 1475 | } |
1412 | 1476 | ||
1413 | #[test] | 1477 | #[test] |
1414 | fn test_struct_field_destructure_into_shorthand() { | 1478 | fn test_rename_local_put_init_shorthand() { |
1479 | mark::check!(test_rename_local_put_init_shorthand); | ||
1480 | check( | ||
1481 | "i", | ||
1482 | r#" | ||
1483 | struct Foo { i: i32 } | ||
1484 | |||
1485 | fn foo(bar$0: i32) -> Foo { | ||
1486 | Foo { i: bar } | ||
1487 | } | ||
1488 | "#, | ||
1489 | r#" | ||
1490 | struct Foo { i: i32 } | ||
1491 | |||
1492 | fn foo(i: i32) -> Foo { | ||
1493 | Foo { i } | ||
1494 | } | ||
1495 | "#, | ||
1496 | ); | ||
1497 | } | ||
1498 | |||
1499 | #[test] | ||
1500 | fn test_struct_field_pat_into_shorthand() { | ||
1501 | mark::check!(test_rename_field_put_init_shorthand_pat); | ||
1415 | check( | 1502 | check( |
1416 | "baz", | 1503 | "baz", |
1417 | r#" | 1504 | r#" |
1418 | struct Foo { i$0: i32 } | 1505 | struct Foo { i$0: i32 } |
1419 | 1506 | ||
1420 | fn foo(foo: Foo) { | 1507 | fn foo(foo: Foo) { |
1421 | let Foo { i: baz } = foo; | 1508 | let Foo { i: ref baz @ qux } = foo; |
1422 | let _ = baz; | 1509 | let _ = qux; |
1423 | } | 1510 | } |
1424 | "#, | 1511 | "#, |
1425 | r#" | 1512 | r#" |
1426 | struct Foo { baz: i32 } | 1513 | struct Foo { baz: i32 } |
1427 | 1514 | ||
1428 | fn foo(foo: Foo) { | 1515 | fn foo(foo: Foo) { |
1429 | let Foo { baz } = foo; | 1516 | let Foo { ref baz @ qux } = foo; |
1430 | let _ = baz; | 1517 | let _ = qux; |
1431 | } | 1518 | } |
1432 | "#, | 1519 | "#, |
1433 | ); | 1520 | ); |
@@ -1501,6 +1588,27 @@ fn foo(Foo { i: bar }: foo) -> i32 { | |||
1501 | } | 1588 | } |
1502 | 1589 | ||
1503 | #[test] | 1590 | #[test] |
1591 | fn test_struct_field_complex_ident_pat() { | ||
1592 | check( | ||
1593 | "baz", | ||
1594 | r#" | ||
1595 | struct Foo { i$0: i32 } | ||
1596 | |||
1597 | fn foo(foo: Foo) { | ||
1598 | let Foo { ref i } = foo; | ||
1599 | } | ||
1600 | "#, | ||
1601 | r#" | ||
1602 | struct Foo { baz: i32 } | ||
1603 | |||
1604 | fn foo(foo: Foo) { | ||
1605 | let Foo { baz: ref i } = foo; | ||
1606 | } | ||
1607 | "#, | ||
1608 | ); | ||
1609 | } | ||
1610 | |||
1611 | #[test] | ||
1504 | fn test_rename_lifetimes() { | 1612 | fn test_rename_lifetimes() { |
1505 | mark::check!(rename_lifetime); | 1613 | mark::check!(rename_lifetime); |
1506 | check( | 1614 | check( |
@@ -1607,4 +1715,38 @@ impl Foo { | |||
1607 | "#, | 1715 | "#, |
1608 | ) | 1716 | ) |
1609 | } | 1717 | } |
1718 | |||
1719 | #[test] | ||
1720 | fn test_rename_field_in_pat_in_macro_doesnt_shorthand() { | ||
1721 | // ideally we would be able to make this emit a short hand, but I doubt this is easily possible | ||
1722 | check( | ||
1723 | "baz", | ||
1724 | r#" | ||
1725 | macro_rules! foo { | ||
1726 | ($pattern:pat) => { | ||
1727 | let $pattern = loop {}; | ||
1728 | }; | ||
1729 | } | ||
1730 | struct Foo { | ||
1731 | bar$0: u32, | ||
1732 | } | ||
1733 | fn foo() { | ||
1734 | foo!(Foo { bar: baz }); | ||
1735 | } | ||
1736 | "#, | ||
1737 | r#" | ||
1738 | macro_rules! foo { | ||
1739 | ($pattern:pat) => { | ||
1740 | let $pattern = loop {}; | ||
1741 | }; | ||
1742 | } | ||
1743 | struct Foo { | ||
1744 | baz: u32, | ||
1745 | } | ||
1746 | fn foo() { | ||
1747 | foo!(Foo { baz: baz }); | ||
1748 | } | ||
1749 | "#, | ||
1750 | ) | ||
1751 | } | ||
1610 | } | 1752 | } |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index f5ee7de86..1e7baed20 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -1,18 +1,19 @@ | |||
1 | use std::fmt; | 1 | use std::fmt; |
2 | 2 | ||
3 | use assists::utils::test_related_attribute; | ||
4 | use cfg::CfgExpr; | 3 | use cfg::CfgExpr; |
5 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; | 4 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; |
6 | use ide_db::{defs::Definition, RootDatabase}; | 5 | use ide_assists::utils::test_related_attribute; |
6 | use ide_db::{defs::Definition, RootDatabase, SymbolKind}; | ||
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ | 8 | use syntax::{ |
9 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, | 9 | ast::{self, AstNode, AttrsOwner}, |
10 | match_ast, SyntaxNode, | 10 | match_ast, SyntaxNode, |
11 | }; | 11 | }; |
12 | use test_utils::mark; | ||
12 | 13 | ||
13 | use crate::{ | 14 | use crate::{ |
14 | display::{ToNav, TryToNav}, | 15 | display::{ToNav, TryToNav}, |
15 | FileId, NavigationTarget, SymbolKind, | 16 | FileId, NavigationTarget, |
16 | }; | 17 | }; |
17 | 18 | ||
18 | #[derive(Debug, Clone)] | 19 | #[derive(Debug, Clone)] |
@@ -95,27 +96,45 @@ impl Runnable { | |||
95 | // |=== | 96 | // |=== |
96 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 97 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { |
97 | let sema = Semantics::new(db); | 98 | let sema = Semantics::new(db); |
98 | let source_file = sema.parse(file_id); | 99 | let module = match sema.to_module_def(file_id) { |
99 | source_file | 100 | None => return Vec::new(), |
100 | .syntax() | 101 | Some(it) => it, |
101 | .descendants() | 102 | }; |
102 | .filter_map(|item| { | 103 | |
103 | let runnable = match_ast! { | 104 | let mut res = Vec::new(); |
104 | match item { | 105 | runnables_mod(&sema, &mut res, module); |
105 | ast::Fn(func) => { | 106 | res |
106 | let def = sema.to_def(&func)?; | 107 | } |
107 | runnable_fn(&sema, def) | 108 | |
108 | }, | 109 | fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) { |
109 | ast::Module(it) => runnable_mod(&sema, it), | 110 | acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| { |
110 | _ => None, | 111 | let runnable = match def { |
111 | } | 112 | hir::ModuleDef::Module(it) => runnable_mod(&sema, it), |
112 | }; | 113 | hir::ModuleDef::Function(it) => runnable_fn(&sema, it), |
113 | runnable.or_else(|| match doc_owner_to_def(&sema, item)? { | 114 | _ => None, |
114 | Definition::ModuleDef(def) => module_def_doctest(&sema, def), | 115 | }; |
115 | _ => None, | 116 | runnable.or_else(|| module_def_doctest(&sema, def)) |
116 | }) | 117 | })); |
117 | }) | 118 | |
118 | .collect() | 119 | acc.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map( |
120 | |def| match def { | ||
121 | hir::AssocItem::Function(it) => { | ||
122 | runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into())) | ||
123 | } | ||
124 | hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()), | ||
125 | hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()), | ||
126 | }, | ||
127 | )); | ||
128 | |||
129 | for def in module.declarations(sema.db) { | ||
130 | if let hir::ModuleDef::Module(submodule) = def { | ||
131 | match submodule.definition_source(sema.db).value { | ||
132 | hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule), | ||
133 | hir::ModuleSource::SourceFile(_) => mark::hit!(dont_recurse_in_outline_submodules), | ||
134 | hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable | ||
135 | } | ||
136 | } | ||
137 | } | ||
119 | } | 138 | } |
120 | 139 | ||
121 | pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { | 140 | pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { |
@@ -150,26 +169,16 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> | |||
150 | Some(Runnable { nav, kind, cfg }) | 169 | Some(Runnable { nav, kind, cfg }) |
151 | } | 170 | } |
152 | 171 | ||
153 | pub(crate) fn runnable_mod( | 172 | pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> { |
154 | sema: &Semantics<RootDatabase>, | 173 | if !has_test_function_or_multiple_test_submodules(sema, &def) { |
155 | module: ast::Module, | ||
156 | ) -> Option<Runnable> { | ||
157 | if !has_test_function_or_multiple_test_submodules(&module) { | ||
158 | return None; | 174 | return None; |
159 | } | 175 | } |
160 | let module_def = sema.to_def(&module)?; | 176 | let path = |
177 | def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); | ||
161 | 178 | ||
162 | let path = module_def | ||
163 | .path_to_root(sema.db) | ||
164 | .into_iter() | ||
165 | .rev() | ||
166 | .filter_map(|it| it.name(sema.db)) | ||
167 | .join("::"); | ||
168 | |||
169 | let def = sema.to_def(&module)?; | ||
170 | let attrs = def.attrs(sema.db); | 179 | let attrs = def.attrs(sema.db); |
171 | let cfg = attrs.cfg(); | 180 | let cfg = attrs.cfg(); |
172 | let nav = module_def.to_nav(sema.db); | 181 | let nav = def.to_nav(sema.db); |
173 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) | 182 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) |
174 | } | 183 | } |
175 | 184 | ||
@@ -289,35 +298,37 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool { | |||
289 | 298 | ||
290 | // We could create runnables for modules with number_of_test_submodules > 0, | 299 | // We could create runnables for modules with number_of_test_submodules > 0, |
291 | // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already | 300 | // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already |
292 | fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { | 301 | fn has_test_function_or_multiple_test_submodules( |
293 | if let Some(item_list) = module.item_list() { | 302 | sema: &Semantics<RootDatabase>, |
294 | let mut number_of_test_submodules = 0; | 303 | module: &hir::Module, |
295 | 304 | ) -> bool { | |
296 | for item in item_list.items() { | 305 | let mut number_of_test_submodules = 0; |
297 | match item { | 306 | |
298 | ast::Item::Fn(f) => { | 307 | for item in module.declarations(sema.db) { |
299 | if test_related_attribute(&f).is_some() { | 308 | match item { |
309 | hir::ModuleDef::Function(f) => { | ||
310 | if let Some(it) = f.source(sema.db) { | ||
311 | if test_related_attribute(&it.value).is_some() { | ||
300 | return true; | 312 | return true; |
301 | } | 313 | } |
302 | } | 314 | } |
303 | ast::Item::Module(submodule) => { | 315 | } |
304 | if has_test_function_or_multiple_test_submodules(&submodule) { | 316 | hir::ModuleDef::Module(submodule) => { |
305 | number_of_test_submodules += 1; | 317 | if has_test_function_or_multiple_test_submodules(sema, &submodule) { |
306 | } | 318 | number_of_test_submodules += 1; |
307 | } | 319 | } |
308 | _ => (), | ||
309 | } | 320 | } |
321 | _ => (), | ||
310 | } | 322 | } |
311 | |||
312 | number_of_test_submodules > 1 | ||
313 | } else { | ||
314 | false | ||
315 | } | 323 | } |
324 | |||
325 | number_of_test_submodules > 1 | ||
316 | } | 326 | } |
317 | 327 | ||
318 | #[cfg(test)] | 328 | #[cfg(test)] |
319 | mod tests { | 329 | mod tests { |
320 | use expect_test::{expect, Expect}; | 330 | use expect_test::{expect, Expect}; |
331 | use test_utils::mark; | ||
321 | 332 | ||
322 | use crate::fixture; | 333 | use crate::fixture; |
323 | 334 | ||
@@ -753,6 +764,21 @@ mod root_tests { | |||
753 | file_id: FileId( | 764 | file_id: FileId( |
754 | 0, | 765 | 0, |
755 | ), | 766 | ), |
767 | full_range: 202..286, | ||
768 | focus_range: 206..220, | ||
769 | name: "nested_tests_2", | ||
770 | kind: Module, | ||
771 | }, | ||
772 | kind: TestMod { | ||
773 | path: "root_tests::nested_tests_0::nested_tests_2", | ||
774 | }, | ||
775 | cfg: None, | ||
776 | }, | ||
777 | Runnable { | ||
778 | nav: NavigationTarget { | ||
779 | file_id: FileId( | ||
780 | 0, | ||
781 | ), | ||
756 | full_range: 84..126, | 782 | full_range: 84..126, |
757 | focus_range: 107..121, | 783 | focus_range: 107..121, |
758 | name: "nested_test_11", | 784 | name: "nested_test_11", |
@@ -793,21 +819,6 @@ mod root_tests { | |||
793 | file_id: FileId( | 819 | file_id: FileId( |
794 | 0, | 820 | 0, |
795 | ), | 821 | ), |
796 | full_range: 202..286, | ||
797 | focus_range: 206..220, | ||
798 | name: "nested_tests_2", | ||
799 | kind: Module, | ||
800 | }, | ||
801 | kind: TestMod { | ||
802 | path: "root_tests::nested_tests_0::nested_tests_2", | ||
803 | }, | ||
804 | cfg: None, | ||
805 | }, | ||
806 | Runnable { | ||
807 | nav: NavigationTarget { | ||
808 | file_id: FileId( | ||
809 | 0, | ||
810 | ), | ||
811 | full_range: 235..276, | 822 | full_range: 235..276, |
812 | focus_range: 258..271, | 823 | focus_range: 258..271, |
813 | name: "nested_test_2", | 824 | name: "nested_test_2", |
@@ -982,4 +993,85 @@ impl Foo { | |||
982 | "#]], | 993 | "#]], |
983 | ); | 994 | ); |
984 | } | 995 | } |
996 | |||
997 | #[test] | ||
998 | fn test_runnables_in_macro() { | ||
999 | check( | ||
1000 | r#" | ||
1001 | //- /lib.rs | ||
1002 | $0 | ||
1003 | macro_rules! gen { | ||
1004 | () => { | ||
1005 | #[test] | ||
1006 | fn foo_test() { | ||
1007 | } | ||
1008 | } | ||
1009 | } | ||
1010 | mod tests { | ||
1011 | gen!(); | ||
1012 | } | ||
1013 | "#, | ||
1014 | &[&TEST, &TEST], | ||
1015 | expect![[r#" | ||
1016 | [ | ||
1017 | Runnable { | ||
1018 | nav: NavigationTarget { | ||
1019 | file_id: FileId( | ||
1020 | 0, | ||
1021 | ), | ||
1022 | full_range: 90..115, | ||
1023 | focus_range: 94..99, | ||
1024 | name: "tests", | ||
1025 | kind: Module, | ||
1026 | }, | ||
1027 | kind: TestMod { | ||
1028 | path: "tests", | ||
1029 | }, | ||
1030 | cfg: None, | ||
1031 | }, | ||
1032 | Runnable { | ||
1033 | nav: NavigationTarget { | ||
1034 | file_id: FileId( | ||
1035 | 0, | ||
1036 | ), | ||
1037 | full_range: 106..113, | ||
1038 | focus_range: 106..113, | ||
1039 | name: "foo_test", | ||
1040 | kind: Function, | ||
1041 | }, | ||
1042 | kind: Test { | ||
1043 | test_id: Path( | ||
1044 | "tests::foo_test", | ||
1045 | ), | ||
1046 | attr: TestAttr { | ||
1047 | ignore: false, | ||
1048 | }, | ||
1049 | }, | ||
1050 | cfg: None, | ||
1051 | }, | ||
1052 | ] | ||
1053 | "#]], | ||
1054 | ); | ||
1055 | } | ||
1056 | |||
1057 | #[test] | ||
1058 | fn dont_recurse_in_outline_submodules() { | ||
1059 | mark::check!(dont_recurse_in_outline_submodules); | ||
1060 | check( | ||
1061 | r#" | ||
1062 | //- /lib.rs | ||
1063 | $0 | ||
1064 | mod m; | ||
1065 | //- /m.rs | ||
1066 | mod tests { | ||
1067 | #[test] | ||
1068 | fn t() {} | ||
1069 | } | ||
1070 | "#, | ||
1071 | &[], | ||
1072 | expect![[r#" | ||
1073 | [] | ||
1074 | "#]], | ||
1075 | ); | ||
1076 | } | ||
985 | } | 1077 | } |
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs index e10d7c3a4..137c38c0d 100644 --- a/crates/ide/src/status.rs +++ b/crates/ide/src/status.rs | |||
@@ -38,6 +38,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { | |||
38 | format_to!(buf, "{}\n", syntax_tree_stats(db)); | 38 | format_to!(buf, "{}\n", syntax_tree_stats(db)); |
39 | format_to!(buf, "{} (macros)\n", macro_syntax_tree_stats(db)); | 39 | format_to!(buf, "{} (macros)\n", macro_syntax_tree_stats(db)); |
40 | format_to!(buf, "{} total\n", memory_usage()); | 40 | format_to!(buf, "{} total\n", memory_usage()); |
41 | format_to!(buf, "\ncounts:\n{}", profile::countme::get_all()); | ||
41 | 42 | ||
42 | if let Some(file_id) = file_id { | 43 | if let Some(file_id) = file_id { |
43 | format_to!(buf, "\nfile info:\n"); | 44 | format_to!(buf, "\nfile info:\n"); |
@@ -60,6 +61,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { | |||
60 | None => format_to!(buf, "does not belong to any crate"), | 61 | None => format_to!(buf, "does not belong to any crate"), |
61 | } | 62 | } |
62 | } | 63 | } |
64 | |||
63 | buf | 65 | buf |
64 | } | 66 | } |
65 | 67 | ||
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index f2d4da78d..9bed329d8 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -13,7 +13,7 @@ mod html; | |||
13 | mod tests; | 13 | mod tests; |
14 | 14 | ||
15 | use hir::{Name, Semantics}; | 15 | use hir::{Name, Semantics}; |
16 | use ide_db::RootDatabase; | 16 | use ide_db::{RootDatabase, SymbolKind}; |
17 | use rustc_hash::FxHashMap; | 17 | use rustc_hash::FxHashMap; |
18 | use syntax::{ | 18 | use syntax::{ |
19 | ast::{self, HasFormatSpecifier}, | 19 | ast::{self, HasFormatSpecifier}, |
@@ -27,7 +27,7 @@ use crate::{ | |||
27 | format::highlight_format_string, highlights::Highlights, | 27 | format::highlight_format_string, highlights::Highlights, |
28 | macro_rules::MacroRulesHighlighter, tags::Highlight, | 28 | macro_rules::MacroRulesHighlighter, tags::Highlight, |
29 | }, | 29 | }, |
30 | FileId, HlMod, HlTag, SymbolKind, | 30 | FileId, HlMod, HlTag, |
31 | }; | 31 | }; |
32 | 32 | ||
33 | pub(crate) use html::highlight_as_html; | 33 | pub(crate) use html::highlight_as_html; |
@@ -72,7 +72,7 @@ pub(crate) fn highlight( | |||
72 | } | 72 | } |
73 | }; | 73 | }; |
74 | 74 | ||
75 | let mut hl = highlights::Highlights::new(range_to_highlight); | 75 | let mut hl = highlights::Highlights::new(root.text_range()); |
76 | traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting); | 76 | traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting); |
77 | hl.to_vec() | 77 | hl.to_vec() |
78 | } | 78 | } |
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs index a74ca844b..8c67a0863 100644 --- a/crates/ide/src/syntax_highlighting/format.rs +++ b/crates/ide/src/syntax_highlighting/format.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | //! Syntax highlighting for format macro strings. | 1 | //! Syntax highlighting for format macro strings. |
2 | use ide_db::SymbolKind; | ||
2 | use syntax::{ | 3 | use syntax::{ |
3 | ast::{self, FormatSpecifier, HasFormatSpecifier}, | 4 | ast::{self, FormatSpecifier, HasFormatSpecifier}, |
4 | AstNode, AstToken, TextRange, | 5 | AstNode, AstToken, TextRange, |
5 | }; | 6 | }; |
6 | 7 | ||
7 | use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag, SymbolKind}; | 8 | use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag}; |
8 | 9 | ||
9 | pub(super) fn highlight_format_string( | 10 | pub(super) fn highlight_format_string( |
10 | stack: &mut Highlights, | 11 | stack: &mut Highlights, |
@@ -30,7 +31,7 @@ fn is_format_string(string: &ast::String) -> Option<()> { | |||
30 | let parent = string.syntax().parent(); | 31 | let parent = string.syntax().parent(); |
31 | 32 | ||
32 | let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?; | 33 | let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?; |
33 | if !matches!(name.text().as_str(), "format_args" | "format_args_nl") { | 34 | if !matches!(name.text(), "format_args" | "format_args_nl") { |
34 | return None; | 35 | return None; |
35 | } | 36 | } |
36 | 37 | ||
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 8625ef5df..24fcbb584 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use hir::{AsAssocItem, Semantics, VariantDef}; | 3 | use hir::{AsAssocItem, Semantics, VariantDef}; |
4 | use ide_db::{ | 4 | use ide_db::{ |
5 | defs::{Definition, NameClass, NameRefClass}, | 5 | defs::{Definition, NameClass, NameRefClass}, |
6 | RootDatabase, | 6 | RootDatabase, SymbolKind, |
7 | }; | 7 | }; |
8 | use rustc_hash::FxHashMap; | 8 | use rustc_hash::FxHashMap; |
9 | use syntax::{ | 9 | use syntax::{ |
@@ -12,7 +12,7 @@ use syntax::{ | |||
12 | SyntaxNode, SyntaxToken, T, | 12 | SyntaxNode, SyntaxToken, T, |
13 | }; | 13 | }; |
14 | 14 | ||
15 | use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag, SymbolKind}; | 15 | use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag}; |
16 | 16 | ||
17 | pub(super) fn element( | 17 | pub(super) fn element( |
18 | sema: &Semantics<RootDatabase>, | 18 | sema: &Semantics<RootDatabase>, |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 281461493..8cdc3688f 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -116,7 +116,7 @@ pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) { | |||
116 | None => (), | 116 | None => (), |
117 | } | 117 | } |
118 | 118 | ||
119 | let line: &str = comment.text().as_str(); | 119 | let line: &str = comment.text(); |
120 | let range = comment.syntax().text_range(); | 120 | let range = comment.syntax().text_range(); |
121 | 121 | ||
122 | let mut pos = TextSize::of(comment.prefix()); | 122 | let mut pos = TextSize::of(comment.prefix()); |
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 8dd05ac52..3c02fdb11 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | 3 | ||
4 | use std::{fmt, ops}; | 4 | use std::{fmt, ops}; |
5 | 5 | ||
6 | use crate::SymbolKind; | 6 | use ide_db::SymbolKind; |
7 | 7 | ||
8 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | 8 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] |
9 | pub struct Highlight { | 9 | pub struct Highlight { |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index 237149566..2f983c0b8 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html | |||
@@ -108,6 +108,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
108 | <span class="brace">}</span> | 108 | <span class="brace">}</span> |
109 | <span class="brace">}</span> | 109 | <span class="brace">}</span> |
110 | 110 | ||
111 | <span class="keyword">fn</span> <span class="function declaration">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> | ||
112 | <span class="function">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> | ||
113 | <span class="brace">}</span> | ||
114 | |||
111 | <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">STATIC_MUT</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span> | 115 | <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">STATIC_MUT</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span> |
112 | 116 | ||
113 | <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="angle"><</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="type_param declaration">T</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="type_param">T</span> <span class="brace">{</span> | 117 | <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="angle"><</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="type_param declaration">T</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="type_param">T</span> <span class="brace">{</span> |
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index a62704c39..9d0cd1af5 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -1,9 +1,8 @@ | |||
1 | use std::fs; | ||
2 | |||
3 | use expect_test::{expect_file, ExpectFile}; | 1 | use expect_test::{expect_file, ExpectFile}; |
4 | use test_utils::project_dir; | 2 | use ide_db::SymbolKind; |
3 | use test_utils::{bench, bench_fixture, skip_slow_tests}; | ||
5 | 4 | ||
6 | use crate::{fixture, FileRange, TextRange}; | 5 | use crate::{fixture, FileRange, HlTag, TextRange}; |
7 | 6 | ||
8 | #[test] | 7 | #[test] |
9 | fn test_highlighting() { | 8 | fn test_highlighting() { |
@@ -81,6 +80,10 @@ impl FooCopy { | |||
81 | } | 80 | } |
82 | } | 81 | } |
83 | 82 | ||
83 | fn str() { | ||
84 | str(); | ||
85 | } | ||
86 | |||
84 | static mut STATIC_MUT: i32 = 0; | 87 | static mut STATIC_MUT: i32 = 0; |
85 | 88 | ||
86 | fn foo<'a, T>() -> T { | 89 | fn foo<'a, T>() -> T { |
@@ -224,15 +227,45 @@ fn bar() { | |||
224 | } | 227 | } |
225 | 228 | ||
226 | #[test] | 229 | #[test] |
227 | fn accidentally_quadratic() { | 230 | fn benchmark_syntax_highlighting_long_struct() { |
228 | let file = project_dir().join("crates/syntax/test_data/accidentally_quadratic"); | 231 | if skip_slow_tests() { |
229 | let src = fs::read_to_string(file).unwrap(); | 232 | return; |
233 | } | ||
230 | 234 | ||
231 | let (analysis, file_id) = fixture::file(&src); | 235 | let fixture = bench_fixture::big_struct(); |
236 | let (analysis, file_id) = fixture::file(&fixture); | ||
232 | 237 | ||
233 | // let t = std::time::Instant::now(); | 238 | let hash = { |
234 | let _ = analysis.highlight(file_id).unwrap(); | 239 | let _pt = bench("syntax highlighting long struct"); |
235 | // eprintln!("elapsed: {:?}", t.elapsed()); | 240 | analysis |
241 | .highlight(file_id) | ||
242 | .unwrap() | ||
243 | .iter() | ||
244 | .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct)) | ||
245 | .count() | ||
246 | }; | ||
247 | assert_eq!(hash, 2001); | ||
248 | } | ||
249 | |||
250 | #[test] | ||
251 | fn benchmark_syntax_highlighting_parser() { | ||
252 | if skip_slow_tests() { | ||
253 | return; | ||
254 | } | ||
255 | |||
256 | let fixture = bench_fixture::glorious_old_parser(); | ||
257 | let (analysis, file_id) = fixture::file(&fixture); | ||
258 | |||
259 | let hash = { | ||
260 | let _pt = bench("syntax highlighting parser"); | ||
261 | analysis | ||
262 | .highlight(file_id) | ||
263 | .unwrap() | ||
264 | .iter() | ||
265 | .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function)) | ||
266 | .count() | ||
267 | }; | ||
268 | assert_eq!(hash, 1629); | ||
236 | } | 269 | } |
237 | 270 | ||
238 | #[test] | 271 | #[test] |
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs index 4c63d3023..f979ba434 100644 --- a/crates/ide/src/syntax_tree.rs +++ b/crates/ide/src/syntax_tree.rs | |||
@@ -100,147 +100,137 @@ fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<St | |||
100 | 100 | ||
101 | #[cfg(test)] | 101 | #[cfg(test)] |
102 | mod tests { | 102 | mod tests { |
103 | use test_utils::assert_eq_text; | 103 | use expect_test::expect; |
104 | 104 | ||
105 | use crate::fixture; | 105 | use crate::fixture; |
106 | 106 | ||
107 | fn check(ra_fixture: &str, expect: expect_test::Expect) { | ||
108 | let (analysis, file_id) = fixture::file(ra_fixture); | ||
109 | let syn = analysis.syntax_tree(file_id, None).unwrap(); | ||
110 | expect.assert_eq(&syn) | ||
111 | } | ||
112 | fn check_range(ra_fixture: &str, expect: expect_test::Expect) { | ||
113 | let (analysis, frange) = fixture::range(ra_fixture); | ||
114 | let syn = analysis.syntax_tree(frange.file_id, Some(frange.range)).unwrap(); | ||
115 | expect.assert_eq(&syn) | ||
116 | } | ||
117 | |||
107 | #[test] | 118 | #[test] |
108 | fn test_syntax_tree_without_range() { | 119 | fn test_syntax_tree_without_range() { |
109 | // Basic syntax | 120 | // Basic syntax |
110 | let (analysis, file_id) = fixture::file(r#"fn foo() {}"#); | 121 | check( |
111 | let syn = analysis.syntax_tree(file_id, None).unwrap(); | 122 | r#"fn foo() {}"#, |
112 | 123 | expect![[r#" | |
113 | assert_eq_text!( | 124 | [email protected] |
114 | r#" | 125 | [email protected] |
115 | [email protected] | 126 | [email protected] "fn" |
116 | [email protected] | 127 | [email protected] " " |
117 | [email protected] "fn" | 128 | [email protected] |
118 | [email protected] " " | 129 | [email protected] "foo" |
119 | [email protected] | 130 | [email protected] |
120 | [email protected] "foo" | 131 | [email protected] "(" |
121 | [email protected] | 132 | [email protected] ")" |
122 | [email protected] "(" | 133 | [email protected] " " |
123 | [email protected] ")" | 134 | [email protected] |
124 | [email protected] " " | 135 | [email protected] "{" |
125 | [email protected] | 136 | [email protected] "}" |
126 | [email protected] "{" | 137 | "#]], |
127 | [email protected] "}" | ||
128 | "# | ||
129 | .trim(), | ||
130 | syn.trim() | ||
131 | ); | 138 | ); |
132 | 139 | ||
133 | let (analysis, file_id) = fixture::file( | 140 | check( |
134 | r#" | 141 | r#" |
135 | fn test() { | 142 | fn test() { |
136 | assert!(" | 143 | assert!(" |
137 | fn foo() { | 144 | fn foo() { |
138 | } | 145 | } |
139 | ", ""); | 146 | ", ""); |
140 | }"# | 147 | }"#, |
141 | .trim(), | 148 | expect![[r#" |
142 | ); | 149 | [email protected] |
143 | let syn = analysis.syntax_tree(file_id, None).unwrap(); | 150 | [email protected] |
144 | 151 | [email protected] "fn" | |
145 | assert_eq_text!( | 152 | [email protected] " " |
146 | r#" | 153 | [email protected] |
147 | [email protected] | 154 | [email protected] "test" |
148 | [email protected] | 155 | [email protected] |
149 | [email protected] "fn" | 156 | [email protected] "(" |
150 | [email protected] " " | 157 | [email protected] ")" |
151 | [email protected] | 158 | [email protected] " " |
152 | [email protected] "test" | 159 | [email protected] |
153 | [email protected] | 160 | [email protected] "{" |
154 | [email protected] "(" | 161 | [email protected] "\n " |
155 | [email protected] ")" | 162 | [email protected] |
156 | [email protected] " " | 163 | [email protected] |
157 | [email protected] | 164 | [email protected] |
158 | [email protected] "{" | 165 | [email protected] |
159 | [email protected] "\n " | 166 | [email protected] |
160 | [email protected] | 167 | [email protected] "assert" |
161 | [email protected] | 168 | [email protected] "!" |
162 | [email protected] | 169 | [email protected] |
163 | [email protected] | 170 | [email protected] "(" |
164 | [email protected] | 171 | [email protected] "\"\n fn foo() {\n ..." |
165 | [email protected] "assert" | 172 | [email protected] "," |
166 | [email protected] "!" | 173 | [email protected] " " |
167 | [email protected] | 174 | [email protected] "\"\"" |
168 | [email protected] "(" | 175 | [email protected] ")" |
169 | [email protected] "\"\n fn foo() {\n ..." | 176 | [email protected] ";" |
170 | [email protected] "," | 177 | [email protected] "\n" |
171 | [email protected] " " | 178 | [email protected] "}" |
172 | [email protected] "\"\"" | 179 | "#]], |
173 | [email protected] ")" | 180 | ) |
174 | [email protected] ";" | ||
175 | [email protected] "\n" | ||
176 | [email protected] "}" | ||
177 | "# | ||
178 | .trim(), | ||
179 | syn.trim() | ||
180 | ); | ||
181 | } | 181 | } |
182 | 182 | ||
183 | #[test] | 183 | #[test] |
184 | fn test_syntax_tree_with_range() { | 184 | fn test_syntax_tree_with_range() { |
185 | let (analysis, range) = fixture::range(r#"$0fn foo() {}$0"#.trim()); | 185 | check_range( |
186 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 186 | r#"$0fn foo() {}$0"#, |
187 | 187 | expect![[r#" | |
188 | assert_eq_text!( | 188 | [email protected] |
189 | r#" | 189 | [email protected] "fn" |
190 | [email protected] | 190 | [email protected] " " |
191 | [email protected] "fn" | 191 | [email protected] |
192 | [email protected] " " | 192 | [email protected] "foo" |
193 | [email protected] | 193 | [email protected] |
194 | [email protected] "foo" | 194 | [email protected] "(" |
195 | [email protected] | 195 | [email protected] ")" |
196 | [email protected] "(" | 196 | [email protected] " " |
197 | [email protected] ")" | 197 | [email protected] |
198 | [email protected] " " | 198 | [email protected] "{" |
199 | [email protected] | 199 | [email protected] "}" |
200 | [email protected] "{" | 200 | "#]], |
201 | [email protected] "}" | ||
202 | "# | ||
203 | .trim(), | ||
204 | syn.trim() | ||
205 | ); | 201 | ); |
206 | 202 | ||
207 | let (analysis, range) = fixture::range( | 203 | check_range( |
208 | r#"fn test() { | 204 | r#" |
205 | fn test() { | ||
209 | $0assert!(" | 206 | $0assert!(" |
210 | fn foo() { | 207 | fn foo() { |
211 | } | 208 | } |
212 | ", "");$0 | 209 | ", "");$0 |
213 | }"# | 210 | }"#, |
214 | .trim(), | 211 | expect![[r#" |
215 | ); | 212 | [email protected] |
216 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 213 | [email protected] |
217 | 214 | [email protected] | |
218 | assert_eq_text!( | 215 | [email protected] |
219 | r#" | 216 | [email protected] |
220 | [email protected] | 217 | [email protected] "assert" |
221 | [email protected] | 218 | [email protected] "!" |
222 | [email protected] | 219 | [email protected] |
223 | [email protected] | 220 | [email protected] "(" |
224 | [email protected] | 221 | [email protected] "\"\n fn foo() {\n ..." |
225 | [email protected] "assert" | 222 | [email protected] "," |
226 | [email protected] "!" | 223 | [email protected] " " |
227 | [email protected] | 224 | [email protected] "\"\"" |
228 | [email protected] "(" | 225 | [email protected] ")" |
229 | [email protected] "\"\n fn foo() {\n ..." | 226 | [email protected] ";" |
230 | [email protected] "," | 227 | "#]], |
231 | [email protected] " " | ||
232 | [email protected] "\"\"" | ||
233 | [email protected] ")" | ||
234 | [email protected] ";" | ||
235 | "# | ||
236 | .trim(), | ||
237 | syn.trim() | ||
238 | ); | 228 | ); |
239 | } | 229 | } |
240 | 230 | ||
241 | #[test] | 231 | #[test] |
242 | fn test_syntax_tree_inside_string() { | 232 | fn test_syntax_tree_inside_string() { |
243 | let (analysis, range) = fixture::range( | 233 | check_range( |
244 | r#"fn test() { | 234 | r#"fn test() { |
245 | assert!(" | 235 | assert!(" |
246 | $0fn foo() { | 236 | $0fn foo() { |
@@ -248,33 +238,27 @@ $0fn foo() { | |||
248 | fn bar() { | 238 | fn bar() { |
249 | } | 239 | } |
250 | ", ""); | 240 | ", ""); |
251 | }"# | 241 | }"#, |
252 | .trim(), | 242 | expect![[r#" |
253 | ); | 243 | [email protected] |
254 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 244 | [email protected] |
255 | assert_eq_text!( | 245 | [email protected] "fn" |
256 | r#" | 246 | [email protected] " " |
257 | [email protected] | 247 | [email protected] |
258 | [email protected] | 248 | [email protected] "foo" |
259 | [email protected] "fn" | 249 | [email protected] |
260 | [email protected] " " | 250 | [email protected] "(" |
261 | [email protected] | 251 | [email protected] ")" |
262 | [email protected] "foo" | 252 | [email protected] " " |
263 | [email protected] | 253 | [email protected] |
264 | [email protected] "(" | 254 | [email protected] "{" |
265 | [email protected] ")" | 255 | [email protected] "\n" |
266 | [email protected] " " | 256 | [email protected] "}" |
267 | [email protected] | 257 | "#]], |
268 | [email protected] "{" | ||
269 | [email protected] "\n" | ||
270 | [email protected] "}" | ||
271 | "# | ||
272 | .trim(), | ||
273 | syn.trim() | ||
274 | ); | 258 | ); |
275 | 259 | ||
276 | // With a raw string | 260 | // With a raw string |
277 | let (analysis, range) = fixture::range( | 261 | check_range( |
278 | r###"fn test() { | 262 | r###"fn test() { |
279 | assert!(r#" | 263 | assert!(r#" |
280 | $0fn foo() { | 264 | $0fn foo() { |
@@ -282,76 +266,64 @@ $0fn foo() { | |||
282 | fn bar() { | 266 | fn bar() { |
283 | } | 267 | } |
284 | "#, ""); | 268 | "#, ""); |
285 | }"### | 269 | }"###, |
286 | .trim(), | 270 | expect![[r#" |
287 | ); | 271 | [email protected] |
288 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 272 | [email protected] |
289 | assert_eq_text!( | 273 | [email protected] "fn" |
290 | r#" | 274 | [email protected] " " |
291 | [email protected] | 275 | [email protected] |
292 | [email protected] | 276 | [email protected] "foo" |
293 | [email protected] "fn" | 277 | [email protected] |
294 | [email protected] " " | 278 | [email protected] "(" |
295 | [email protected] | 279 | [email protected] ")" |
296 | [email protected] "foo" | 280 | [email protected] " " |
297 | [email protected] | 281 | [email protected] |
298 | [email protected] "(" | 282 | [email protected] "{" |
299 | [email protected] ")" | 283 | [email protected] "\n" |
300 | [email protected] " " | 284 | [email protected] "}" |
301 | [email protected] | 285 | "#]], |
302 | [email protected] "{" | ||
303 | [email protected] "\n" | ||
304 | [email protected] "}" | ||
305 | "# | ||
306 | .trim(), | ||
307 | syn.trim() | ||
308 | ); | 286 | ); |
309 | 287 | ||
310 | // With a raw string | 288 | // With a raw string |
311 | let (analysis, range) = fixture::range( | 289 | check_range( |
312 | r###"fn test() { | 290 | r###"fn test() { |
313 | assert!(r$0#" | 291 | assert!(r$0#" |
314 | fn foo() { | 292 | fn foo() { |
315 | } | 293 | } |
316 | fn bar() { | 294 | fn bar() { |
317 | }"$0#, ""); | 295 | }"$0#, ""); |
318 | }"### | 296 | }"###, |
319 | .trim(), | 297 | expect![[r#" |
320 | ); | 298 | [email protected] |
321 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 299 | [email protected] |
322 | assert_eq_text!( | 300 | [email protected] "fn" |
323 | r#" | 301 | [email protected] " " |
324 | [email protected] | 302 | [email protected] |
325 | [email protected] | 303 | [email protected] "foo" |
326 | [email protected] "fn" | 304 | [email protected] |
327 | [email protected] " " | 305 | [email protected] "(" |
328 | [email protected] | 306 | [email protected] ")" |
329 | [email protected] "foo" | 307 | [email protected] " " |
330 | [email protected] | 308 | [email protected] |
331 | [email protected] "(" | 309 | [email protected] "{" |
332 | [email protected] ")" | 310 | [email protected] "\n" |
333 | [email protected] " " | 311 | [email protected] "}" |
334 | [email protected] | 312 | [email protected] "\n" |
335 | [email protected] "{" | 313 | [email protected] |
336 | [email protected] "\n" | 314 | [email protected] "fn" |
337 | [email protected] "}" | 315 | [email protected] " " |
338 | [email protected] "\n" | 316 | [email protected] |
339 | [email protected] | 317 | [email protected] "bar" |
340 | [email protected] "fn" | 318 | [email protected] |
341 | [email protected] " " | 319 | [email protected] "(" |
342 | [email protected] | 320 | [email protected] ")" |
343 | [email protected] "bar" | 321 | [email protected] " " |
344 | [email protected] | 322 | [email protected] |
345 | [email protected] "(" | 323 | [email protected] "{" |
346 | [email protected] ")" | 324 | [email protected] "\n" |
347 | [email protected] " " | 325 | [email protected] "}" |
348 | [email protected] | 326 | "#]], |
349 | [email protected] "{" | ||
350 | [email protected] "\n" | ||
351 | [email protected] "}" | ||
352 | "# | ||
353 | .trim(), | ||
354 | syn.trim() | ||
355 | ); | 327 | ); |
356 | } | 328 | } |
357 | } | 329 | } |
diff --git a/crates/ide/src/view_hir.rs b/crates/ide/src/view_hir.rs index cfcfb7cfb..f8f3fae3d 100644 --- a/crates/ide/src/view_hir.rs +++ b/crates/ide/src/view_hir.rs | |||
@@ -11,7 +11,7 @@ use syntax::{algo::find_node_at_offset, ast, AstNode}; | |||
11 | // | VS Code | **Rust Analyzer: View Hir** | 11 | // | VS Code | **Rust Analyzer: View Hir** |
12 | // |=== | 12 | // |=== |
13 | pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String { | 13 | pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String { |
14 | body_hir(db, position).unwrap_or("Not inside a function body".to_string()) | 14 | body_hir(db, position).unwrap_or_else(|| "Not inside a function body".to_string()) |
15 | } | 15 | } |
16 | 16 | ||
17 | fn body_hir(db: &RootDatabase, position: FilePosition) -> Option<String> { | 17 | fn body_hir(db: &RootDatabase, position: FilePosition) -> Option<String> { |