aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/annotations.rs937
-rw-r--r--crates/ide/src/lib.rs14
-rw-r--r--crates/rust-analyzer/src/from_proto.rs40
-rw-r--r--crates/rust-analyzer/src/handlers.rs228
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/to_proto.rs145
-rw-r--r--docs/dev/lsp-extensions.md2
7 files changed, 1172 insertions, 202 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
new file mode 100644
index 000000000..414a60bed
--- /dev/null
+++ b/crates/ide/src/annotations.rs
@@ -0,0 +1,937 @@
1use hir::Semantics;
2use ide_db::{
3 base_db::{FileId, FilePosition, FileRange, SourceDatabase},
4 RootDatabase, SymbolKind,
5};
6use syntax::TextRange;
7
8use 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)]
22pub struct Annotation {
23 pub range: TextRange,
24 pub kind: AnnotationKind,
25}
26
27#[derive(Debug)]
28pub 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
34pub 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
44pub(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 action.debugee && config.debug {
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: true, runnable: runnable.clone() },
66 });
67 }
68
69 if config.run {
70 annotations.push(Annotation {
71 range,
72 kind: AnnotationKind::Runnable { debug: false, 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
125pub(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
148fn 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)]
156mod 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#"
189const DEMO: i32 = 123;
190
191const UNUSED: i32 = 123;
192
193fn main() {
194 let hello = DEMO;
195}
196 "#,
197 expect![[r#"
198 [
199 Annotation {
200 range: 50..85,
201 kind: Runnable {
202 debug: true,
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: false,
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#"
295struct Test;
296
297fn main() {
298 let test = Test;
299}
300 "#,
301 expect![[r#"
302 [
303 Annotation {
304 range: 14..48,
305 kind: Runnable {
306 debug: true,
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: false,
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#"
399struct Test;
400
401trait MyCoolTrait {}
402
403impl MyCoolTrait for Test {}
404
405fn main() {
406 let test = Test;
407}
408 "#,
409 expect![[r#"
410 [
411 Annotation {
412 range: 66..100,
413 kind: Runnable {
414 debug: true,
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: false,
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#"
568fn main() {}
569 "#,
570 expect![[r#"
571 [
572 Annotation {
573 range: 0..12,
574 kind: Runnable {
575 debug: true,
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: false,
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#"
633struct Test;
634
635impl Test {
636 fn self_by_ref(&self) {}
637}
638
639fn main() {
640 Test.self_by_ref();
641}
642 "#,
643 expect![[r#"
644 [
645 Annotation {
646 range: 58..95,
647 kind: Runnable {
648 debug: true,
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: false,
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#"
778fn main() {}
779
780mod 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: true,
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: false,
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: true,
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: false,
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: true,
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: false,
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/lib.rs b/crates/ide/src/lib.rs
index 592b12925..89e7bef7d 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -22,6 +22,7 @@ mod markup;
22mod prime_caches; 22mod prime_caches;
23mod display; 23mod display;
24 24
25mod annotations;
25mod call_hierarchy; 26mod call_hierarchy;
26mod diagnostics; 27mod diagnostics;
27mod expand_macro; 28mod expand_macro;
@@ -63,6 +64,7 @@ use syntax::SourceFile;
63use crate::display::ToNav; 64use crate::display::ToNav;
64 65
65pub use crate::{ 66pub 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, 70 display::navigation_target::NavigationTarget,
@@ -555,6 +557,18 @@ impl Analysis {
555 }) 557 })
556 } 558 }
557 559
560 pub fn annotations(
561 &self,
562 file_id: FileId,
563 config: AnnotationConfig,
564 ) -> Cancelable<Vec<Annotation>> {
565 self.with_db(|db| annotations::annotations(db, file_id, config))
566 }
567
568 pub fn resolve_annotation(&self, annotation: Annotation) -> Cancelable<Annotation> {
569 self.with_db(|db| annotations::resolve_annotation(db, annotation))
570 }
571
558 /// Performs an operation on that may be Canceled. 572 /// Performs an operation on that may be Canceled.
559 fn with_db<F, T>(&self, f: F) -> Cancelable<T> 573 fn with_db<F, T>(&self, f: F) -> Cancelable<T>
560 where 574 where
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs
index aa6b808d6..6676eebf4 100644
--- a/crates/rust-analyzer/src/from_proto.rs
+++ b/crates/rust-analyzer/src/from_proto.rs
@@ -1,12 +1,12 @@
1//! Conversion lsp_types types to rust-analyzer specific ones. 1//! Conversion lsp_types types to rust-analyzer specific ones.
2use std::convert::TryFrom; 2use std::convert::TryFrom;
3 3
4use ide::{AssistKind, LineCol, LineIndex}; 4use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineIndex};
5use ide_db::base_db::{FileId, FilePosition, FileRange}; 5use ide_db::base_db::{FileId, FilePosition, FileRange};
6use syntax::{TextRange, TextSize}; 6use syntax::{TextRange, TextSize};
7use vfs::AbsPathBuf; 7use vfs::AbsPathBuf;
8 8
9use crate::{global_state::GlobalStateSnapshot, Result}; 9use crate::{from_json, global_state::GlobalStateSnapshot, lsp_ext, Result};
10 10
11pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> { 11pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> {
12 let path = url.to_file_path().map_err(|()| "url is not a file")?; 12 let path = url.to_file_path().map_err(|()| "url is not a file")?;
@@ -66,3 +66,39 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind>
66 66
67 Some(assist_kind) 67 Some(assist_kind)
68} 68}
69
70pub(crate) fn annotation(
71 world: &GlobalStateSnapshot,
72 code_lens: lsp_types::CodeLens,
73) -> Result<Annotation> {
74 let data = code_lens.data.unwrap();
75 let resolve = from_json::<lsp_ext::CodeLensResolveData>("CodeLensResolveData", data)?;
76
77 match resolve {
78 lsp_ext::CodeLensResolveData::Impls(params) => {
79 let file_id =
80 world.url_to_file_id(&params.text_document_position_params.text_document.uri)?;
81 let line_index = world.analysis.file_line_index(file_id)?;
82
83 Ok(Annotation {
84 range: text_range(&line_index, code_lens.range),
85 kind: AnnotationKind::HasImpls {
86 position: file_position(world, params.text_document_position_params)?,
87 data: None,
88 },
89 })
90 }
91 lsp_ext::CodeLensResolveData::References(params) => {
92 let file_id = world.url_to_file_id(&params.text_document.uri)?;
93 let line_index = world.analysis.file_line_index(file_id)?;
94
95 Ok(Annotation {
96 range: text_range(&line_index, code_lens.range),
97 kind: AnnotationKind::HasReferences {
98 position: file_position(world, params)?,
99 data: None,
100 },
101 })
102 }
103 }
104}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 8898c12e3..b051c8f6c 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -9,8 +9,9 @@ use std::{
9}; 9};
10 10
11use ide::{ 11use ide::{
12 FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, NavigationTarget, 12 AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex,
13 Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit, 13 NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange,
14 TextEdit,
14}; 15};
15use ide_db::SymbolKind; 16use ide_db::SymbolKind;
16use itertools::Itertools; 17use itertools::Itertools;
@@ -35,7 +36,7 @@ use crate::{
35 cargo_target_spec::CargoTargetSpec, 36 cargo_target_spec::CargoTargetSpec,
36 config::RustfmtConfig, 37 config::RustfmtConfig,
37 diff::diff, 38 diff::diff,
38 from_json, from_proto, 39 from_proto,
39 global_state::{GlobalState, GlobalStateSnapshot}, 40 global_state::{GlobalState, GlobalStateSnapshot},
40 line_endings::LineEndings, 41 line_endings::LineEndings,
41 lsp_ext::{self, InlayHint, InlayHintsParams}, 42 lsp_ext::{self, InlayHint, InlayHintsParams},
@@ -1078,177 +1079,51 @@ pub(crate) fn handle_code_lens(
1078 params: lsp_types::CodeLensParams, 1079 params: lsp_types::CodeLensParams,
1079) -> Result<Option<Vec<CodeLens>>> { 1080) -> Result<Option<Vec<CodeLens>>> {
1080 let _p = profile::span("handle_code_lens"); 1081 let _p = profile::span("handle_code_lens");
1081 let mut lenses: Vec<CodeLens> = Default::default();
1082 1082
1083 let lens_config = snap.config.lens(); 1083 let lens_config = snap.config.lens();
1084 if lens_config.none() { 1084 if lens_config.none() {
1085 // early return before any db query! 1085 // early return before any db query!
1086 return Ok(Some(lenses)); 1086 return Ok(Some(Vec::default()));
1087 } 1087 }
1088 1088
1089 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 1089 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1090 let line_index = snap.analysis.file_line_index(file_id)?; 1090 let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?;
1091 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
1092
1093 if lens_config.runnable() {
1094 // Gather runnables
1095 for runnable in snap.analysis.runnables(file_id)? {
1096 if should_skip_target(&runnable, cargo_spec.as_ref()) {
1097 continue;
1098 }
1099
1100 let action = runnable.action();
1101 let range = to_proto::range(&line_index, runnable.nav.full_range);
1102 let r = to_proto::runnable(&snap, file_id, runnable)?;
1103 if lens_config.run {
1104 let lens = CodeLens {
1105 range,
1106 command: Some(run_single_command(&r, action.run_title)),
1107 data: None,
1108 };
1109 lenses.push(lens);
1110 }
1111 1091
1112 if action.debugee && lens_config.debug { 1092 let lenses = snap
1113 let debug_lens = 1093 .analysis
1114 CodeLens { range, command: Some(debug_single_command(&r)), data: None }; 1094 .annotations(
1115 lenses.push(debug_lens); 1095 file_id,
1116 } 1096 AnnotationConfig {
1117 } 1097 binary_target: cargo_target_spec
1118 } 1098 .map(|spec| {
1119 1099 matches!(
1120 if lens_config.implementations || lens_config.refs { 1100 spec.target_kind,
1121 snap.analysis 1101 TargetKind::Bin | TargetKind::Example | TargetKind::Test
1122 .file_structure(file_id)? 1102 )
1123 .into_iter()
1124 .filter(|it| {
1125 matches!(
1126 it.kind,
1127 SymbolKind::Trait | SymbolKind::Struct | SymbolKind::Enum | SymbolKind::Union
1128 )
1129 })
1130 .for_each(|it| {
1131 let range = to_proto::range(&line_index, it.node_range);
1132 let position = to_proto::position(&line_index, it.navigation_range.start());
1133 let doc_pos = lsp_types::TextDocumentPositionParams::new(
1134 params.text_document.clone(),
1135 position,
1136 );
1137 let goto_params = lsp_types::request::GotoImplementationParams {
1138 text_document_position_params: doc_pos.clone(),
1139 work_done_progress_params: Default::default(),
1140 partial_result_params: Default::default(),
1141 };
1142
1143 if lens_config.implementations {
1144 lenses.push(CodeLens {
1145 range,
1146 command: None,
1147 data: Some(to_value(CodeLensResolveData::Impls(goto_params)).unwrap()),
1148 })
1149 }
1150
1151 if lens_config.refs {
1152 lenses.push(CodeLens {
1153 range,
1154 command: None,
1155 data: Some(to_value(CodeLensResolveData::References(doc_pos)).unwrap()),
1156 }) 1103 })
1157 } 1104 .unwrap_or(false),
1158 }); 1105 annotate_runnables: lens_config.runnable(),
1159 } 1106 annotate_impls: lens_config.implementations,
1160 1107 annotate_references: lens_config.refs,
1161 if lens_config.method_refs { 1108 annotate_method_references: lens_config.method_refs,
1162 lenses.extend(snap.analysis.find_all_methods(file_id)?.into_iter().map(|it| { 1109 run: lens_config.run,
1163 let range = to_proto::range(&line_index, it.range); 1110 debug: lens_config.debug,
1164 let position = to_proto::position(&line_index, it.range.start()); 1111 },
1165 let lens_params = 1112 )?
1166 lsp_types::TextDocumentPositionParams::new(params.text_document.clone(), position); 1113 .into_iter()
1167 1114 .map(|annotation| to_proto::code_lens(&snap, annotation).unwrap())
1168 CodeLens { 1115 .collect();
1169 range,
1170 command: None,
1171 data: Some(to_value(CodeLensResolveData::References(lens_params)).unwrap()),
1172 }
1173 }));
1174 }
1175 1116
1176 Ok(Some(lenses)) 1117 Ok(Some(lenses))
1177} 1118}
1178 1119
1179#[derive(Debug, Serialize, Deserialize)]
1180#[serde(rename_all = "camelCase")]
1181enum CodeLensResolveData {
1182 Impls(lsp_types::request::GotoImplementationParams),
1183 References(lsp_types::TextDocumentPositionParams),
1184}
1185
1186pub(crate) fn handle_code_lens_resolve( 1120pub(crate) fn handle_code_lens_resolve(
1187 snap: GlobalStateSnapshot, 1121 snap: GlobalStateSnapshot,
1188 code_lens: CodeLens, 1122 code_lens: CodeLens,
1189) -> Result<CodeLens> { 1123) -> Result<CodeLens> {
1190 let _p = profile::span("handle_code_lens_resolve"); 1124 let annotation = from_proto::annotation(&snap, code_lens)?;
1191 let data = code_lens.data.unwrap();
1192 let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
1193 match resolve {
1194 Some(CodeLensResolveData::Impls(lens_params)) => {
1195 let locations: Vec<Location> =
1196 match handle_goto_implementation(snap, lens_params.clone())? {
1197 Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
1198 Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
1199 Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
1200 .into_iter()
1201 .map(|link| Location::new(link.target_uri, link.target_selection_range))
1202 .collect(),
1203 _ => vec![],
1204 };
1205
1206 let title = implementation_title(locations.len());
1207 let cmd = show_references_command(
1208 title,
1209 &lens_params.text_document_position_params.text_document.uri,
1210 code_lens.range.start,
1211 locations,
1212 );
1213 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
1214 }
1215 Some(CodeLensResolveData::References(doc_position)) => {
1216 let position = from_proto::file_position(&snap, doc_position.clone())?;
1217 let locations = snap
1218 .analysis
1219 .find_all_refs(position, None)
1220 .unwrap_or(None)
1221 .map(|r| {
1222 r.references
1223 .into_iter()
1224 .flat_map(|(file_id, ranges)| {
1225 ranges.into_iter().map(move |(range, _)| FileRange { file_id, range })
1226 })
1227 .filter_map(|frange| to_proto::location(&snap, frange).ok())
1228 .collect_vec()
1229 })
1230 .unwrap_or_default();
1231
1232 let title = reference_title(locations.len());
1233 let cmd = if locations.is_empty() {
1234 Command { title, command: "".into(), arguments: None }
1235 } else {
1236 show_references_command(
1237 title,
1238 &doc_position.text_document.uri,
1239 code_lens.range.start,
1240 locations,
1241 )
1242 };
1243 1125
1244 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) 1126 Ok(to_proto::code_lens(&snap, snap.analysis.resolve_annotation(annotation)?)?)
1245 }
1246 None => Ok(CodeLens {
1247 range: code_lens.range,
1248 command: Some(Command { title: "Error".into(), ..Default::default() }),
1249 data: None,
1250 }),
1251 }
1252} 1127}
1253 1128
1254pub(crate) fn handle_document_highlight( 1129pub(crate) fn handle_document_highlight(
@@ -1547,43 +1422,6 @@ pub(crate) fn handle_open_cargo_toml(
1547 Ok(Some(res)) 1422 Ok(Some(res))
1548} 1423}
1549 1424
1550fn implementation_title(count: usize) -> String {
1551 if count == 1 {
1552 "1 implementation".into()
1553 } else {
1554 format!("{} implementations", count)
1555 }
1556}
1557
1558fn reference_title(count: usize) -> String {
1559 if count == 1 {
1560 "1 reference".into()
1561 } else {
1562 format!("{} references", count)
1563 }
1564}
1565
1566fn show_references_command(
1567 title: String,
1568 uri: &lsp_types::Url,
1569 position: lsp_types::Position,
1570 locations: Vec<lsp_types::Location>,
1571) -> Command {
1572 // We cannot use the 'editor.action.showReferences' command directly
1573 // because that command requires vscode types which we convert in the handler
1574 // on the client side.
1575
1576 Command {
1577 title,
1578 command: "rust-analyzer.showReferences".into(),
1579 arguments: Some(vec![
1580 to_value(uri).unwrap(),
1581 to_value(position).unwrap(),
1582 to_value(locations).unwrap(),
1583 ]),
1584 }
1585}
1586
1587fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command { 1425fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command {
1588 Command { 1426 Command {
1589 title: title.to_string(), 1427 title: title.to_string(),
@@ -1635,8 +1473,8 @@ fn show_impl_command_link(
1635 .into_iter() 1473 .into_iter()
1636 .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok()) 1474 .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok())
1637 .collect(); 1475 .collect();
1638 let title = implementation_title(locations.len()); 1476 let title = to_proto::implementation_title(locations.len());
1639 let command = show_references_command(title, &uri, position, locations); 1477 let command = to_proto::show_references_command(title, &uri, position, locations);
1640 1478
1641 return Some(lsp_ext::CommandLinkGroup { 1479 return Some(lsp_ext::CommandLinkGroup {
1642 commands: vec![to_command_link(command, "Go to implementations".into())], 1480 commands: vec![to_command_link(command, "Go to implementations".into())],
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index ce5a0e822..a1ad855c3 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -377,3 +377,11 @@ impl Request for OpenCargoToml {
377pub struct OpenCargoTomlParams { 377pub struct OpenCargoTomlParams {
378 pub text_document: TextDocumentIdentifier, 378 pub text_document: TextDocumentIdentifier,
379} 379}
380
381/// Information about CodeLens, that is to be resolved.
382#[derive(Debug, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub(crate) enum CodeLensResolveData {
385 Impls(lsp_types::request::GotoImplementationParams),
386 References(lsp_types::TextDocumentPositionParams),
387}
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index be10ac1ae..29fac96fb 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -5,13 +5,15 @@ use std::{
5}; 5};
6 6
7use ide::{ 7use ide::{
8 Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, FileId, 8 Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind,
9 FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, 9 Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct,
10 InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, ReferenceAccess, 10 HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup,
11 RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize, 11 NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit,
12 TextRange, TextSize,
12}; 13};
13use ide_db::SymbolKind; 14use ide_db::SymbolKind;
14use itertools::Itertools; 15use itertools::Itertools;
16use serde_json::to_value;
15 17
16use crate::{ 18use crate::{
17 cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, 19 cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot,
@@ -863,6 +865,141 @@ pub(crate) fn runnable(
863 }) 865 })
864} 866}
865 867
868pub(crate) fn code_lens(
869 snap: &GlobalStateSnapshot,
870 annotation: Annotation,
871) -> Result<lsp_types::CodeLens> {
872 match annotation.kind {
873 AnnotationKind::Runnable { debug, runnable: run } => {
874 let line_index = snap.analysis.file_line_index(run.nav.file_id)?;
875 let annotation_range = range(&line_index, annotation.range);
876
877 let action = run.action();
878 let r = runnable(&snap, run.nav.file_id, run)?;
879
880 let command = if debug {
881 lsp_types::Command {
882 title: action.run_title.to_string(),
883 command: "rust-analyzer.runSingle".into(),
884 arguments: Some(vec![to_value(r).unwrap()]),
885 }
886 } else {
887 lsp_types::Command {
888 title: "Debug".into(),
889 command: "rust-analyzer.debugSingle".into(),
890 arguments: Some(vec![to_value(r).unwrap()]),
891 }
892 };
893
894 Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None })
895 }
896 AnnotationKind::HasImpls { position: file_position, data } => {
897 let line_index = snap.analysis.file_line_index(file_position.file_id)?;
898 let annotation_range = range(&line_index, annotation.range);
899 let url = url(snap, file_position.file_id);
900
901 let position = position(&line_index, file_position.offset);
902
903 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
904
905 let doc_pos = lsp_types::TextDocumentPositionParams::new(id.clone(), position);
906
907 let goto_params = lsp_types::request::GotoImplementationParams {
908 text_document_position_params: doc_pos.clone(),
909 work_done_progress_params: Default::default(),
910 partial_result_params: Default::default(),
911 };
912
913 let command = data.map(|ranges| {
914 let locations: Vec<lsp_types::Location> = ranges
915 .into_iter()
916 .filter_map(|target| {
917 location(
918 snap,
919 FileRange { file_id: target.file_id, range: target.full_range },
920 )
921 .ok()
922 })
923 .collect();
924
925 show_references_command(
926 implementation_title(locations.len()),
927 &url,
928 position,
929 locations,
930 )
931 });
932
933 Ok(lsp_types::CodeLens {
934 range: annotation_range,
935 command,
936 data: Some(to_value(lsp_ext::CodeLensResolveData::Impls(goto_params)).unwrap()),
937 })
938 }
939 AnnotationKind::HasReferences { position: file_position, data } => {
940 let line_index = snap.analysis.file_line_index(file_position.file_id)?;
941 let annotation_range = range(&line_index, annotation.range);
942 let url = url(snap, file_position.file_id);
943
944 let position = position(&line_index, file_position.offset);
945
946 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
947
948 let doc_pos = lsp_types::TextDocumentPositionParams::new(id, position);
949
950 let command = data.map(|ranges| {
951 let locations: Vec<lsp_types::Location> =
952 ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
953
954 show_references_command(reference_title(locations.len()), &url, position, locations)
955 });
956
957 Ok(lsp_types::CodeLens {
958 range: annotation_range,
959 command,
960 data: Some(to_value(lsp_ext::CodeLensResolveData::References(doc_pos)).unwrap()),
961 })
962 }
963 }
964}
965
966pub(crate) fn show_references_command(
967 title: String,
968 uri: &lsp_types::Url,
969 position: lsp_types::Position,
970 locations: Vec<lsp_types::Location>,
971) -> lsp_types::Command {
972 // We cannot use the 'editor.action.showReferences' command directly
973 // because that command requires vscode types which we convert in the handler
974 // on the client side.
975
976 lsp_types::Command {
977 title,
978 command: "rust-analyzer.showReferences".into(),
979 arguments: Some(vec![
980 to_value(uri).unwrap(),
981 to_value(position).unwrap(),
982 to_value(locations).unwrap(),
983 ]),
984 }
985}
986
987pub(crate) fn implementation_title(count: usize) -> String {
988 if count == 1 {
989 "1 implementation".into()
990 } else {
991 format!("{} implementations", count)
992 }
993}
994
995pub(crate) fn reference_title(count: usize) -> String {
996 if count == 1 {
997 "1 reference".into()
998 } else {
999 format!("{} references", count)
1000 }
1001}
1002
866pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { 1003pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
867 let value = crate::markdown::format_docs(markup.as_str()); 1004 let value = crate::markdown::format_docs(markup.as_str());
868 lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } 1005 lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 2a966a96d..a4e108724 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
1<!--- 1<!---
2lsp_ext.rs hash: 8f1ae8530f69e3a3 2lsp_ext.rs hash: 34aec6bfeaeb97a
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue: