aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml1
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs278
-rw-r--r--crates/ra_ide/src/lib.rs2
-rw-r--r--crates/ra_ide/src/references/rename.rs187
-rw-r--r--crates/ra_ide/src/ssr.rs563
5 files changed, 356 insertions, 675 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 05c940605..bbc6a5c9b 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -29,6 +29,7 @@ ra_fmt = { path = "../ra_fmt" }
29ra_prof = { path = "../ra_prof" } 29ra_prof = { path = "../ra_prof" }
30test_utils = { path = "../test_utils" } 30test_utils = { path = "../test_utils" }
31ra_assists = { path = "../ra_assists" } 31ra_assists = { path = "../ra_assists" }
32ra_ssr = { path = "../ra_ssr" }
32 33
33# ra_ide should depend only on the top-level `hir` package. if you need 34# ra_ide should depend only on the top-level `hir` package. if you need
34# something from some `hir_xxx` subpackage, reexport the API via `hir`. 35# something from some `hir_xxx` subpackage, reexport the API via `hir`.
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs
index fb3f0b743..ade17a1ff 100644
--- a/crates/ra_ide/src/completion/complete_attribute.rs
+++ b/crates/ra_ide/src/completion/complete_attribute.rs
@@ -20,6 +20,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
20 { 20 {
21 complete_derive(acc, ctx, token_tree) 21 complete_derive(acc, ctx, token_tree)
22 } 22 }
23 (_, Some(ast::AttrInput::TokenTree(_token_tree))) => {}
23 _ => complete_attribute_start(acc, ctx, attribute), 24 _ => complete_attribute_start(acc, ctx, attribute),
24 } 25 }
25 Some(()) 26 Some(())
@@ -34,6 +35,10 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
34 ) 35 )
35 .kind(CompletionItemKind::Attribute); 36 .kind(CompletionItemKind::Attribute);
36 37
38 if let Some(lookup) = attr_completion.lookup {
39 item = item.lookup_by(lookup);
40 }
41
37 match (attr_completion.snippet, ctx.config.snippet_cap) { 42 match (attr_completion.snippet, ctx.config.snippet_cap) {
38 (Some(snippet), Some(cap)) => { 43 (Some(snippet), Some(cap)) => {
39 item = item.insert_snippet(cap, snippet); 44 item = item.insert_snippet(cap, snippet);
@@ -49,84 +54,160 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
49 54
50struct AttrCompletion { 55struct AttrCompletion {
51 label: &'static str, 56 label: &'static str,
57 lookup: Option<&'static str>,
52 snippet: Option<&'static str>, 58 snippet: Option<&'static str>,
53 should_be_inner: bool, 59 should_be_inner: bool,
54} 60}
55 61
56const ATTRIBUTES: &[AttrCompletion] = &[ 62const ATTRIBUTES: &[AttrCompletion] = &[
57 AttrCompletion { label: "allow", snippet: Some("allow(${0:lint})"), should_be_inner: false },
58 AttrCompletion { 63 AttrCompletion {
59 label: "cfg_attr", 64 label: "allow(…)",
65 snippet: Some("allow(${0:lint})"),
66 should_be_inner: false,
67 lookup: Some("allow"),
68 },
69 AttrCompletion {
70 label: "cfg_attr(…)",
60 snippet: Some("cfg_attr(${1:predicate}, ${0:attr})"), 71 snippet: Some("cfg_attr(${1:predicate}, ${0:attr})"),
61 should_be_inner: false, 72 should_be_inner: false,
73 lookup: Some("cfg_attr"),
62 }, 74 },
63 AttrCompletion { label: "cfg", snippet: Some("cfg(${0:predicate})"), should_be_inner: false },
64 AttrCompletion { label: "deny", snippet: Some("deny(${0:lint})"), should_be_inner: false },
65 AttrCompletion { 75 AttrCompletion {
66 label: "deprecated", 76 label: "cfg(…)",
77 snippet: Some("cfg(${0:predicate})"),
78 should_be_inner: false,
79 lookup: Some("cfg"),
80 },
81 AttrCompletion {
82 label: "deny(…)",
83 snippet: Some("deny(${0:lint})"),
84 should_be_inner: false,
85 lookup: Some("deny"),
86 },
87 AttrCompletion {
88 label: r#"deprecated = "…""#,
67 snippet: Some(r#"deprecated = "${0:reason}""#), 89 snippet: Some(r#"deprecated = "${0:reason}""#),
68 should_be_inner: false, 90 should_be_inner: false,
91 lookup: Some("deprecated"),
69 }, 92 },
70 AttrCompletion { 93 AttrCompletion {
71 label: "derive", 94 label: "derive(…)",
72 snippet: Some(r#"derive(${0:Debug})"#), 95 snippet: Some(r#"derive(${0:Debug})"#),
73 should_be_inner: false, 96 should_be_inner: false,
97 lookup: Some("derive"),
98 },
99 AttrCompletion {
100 label: r#"doc = "…""#,
101 snippet: Some(r#"doc = "${0:docs}""#),
102 should_be_inner: false,
103 lookup: Some("doc"),
104 },
105 AttrCompletion {
106 label: "feature(…)",
107 snippet: Some("feature(${0:flag})"),
108 should_be_inner: true,
109 lookup: Some("feature"),
110 },
111 AttrCompletion {
112 label: "forbid(…)",
113 snippet: Some("forbid(${0:lint})"),
114 should_be_inner: false,
115 lookup: Some("forbid"),
74 }, 116 },
75 AttrCompletion { label: "doc", snippet: Some(r#"doc = "${0:docs}""#), should_be_inner: false },
76 AttrCompletion { label: "feature", snippet: Some("feature(${0:flag})"), should_be_inner: true },
77 AttrCompletion { label: "forbid", snippet: Some("forbid(${0:lint})"), should_be_inner: false },
78 // FIXME: resolve through macro resolution? 117 // FIXME: resolve through macro resolution?
79 AttrCompletion { label: "global_allocator", snippet: None, should_be_inner: true },
80 AttrCompletion { label: "ignore", snippet: Some("ignore(${0:lint})"), should_be_inner: false },
81 AttrCompletion { label: "inline", snippet: Some("inline(${0:lint})"), should_be_inner: false },
82 AttrCompletion { 118 AttrCompletion {
83 label: "link_name", 119 label: "global_allocator",
120 snippet: None,
121 should_be_inner: true,
122 lookup: None,
123 },
124 AttrCompletion {
125 label: "ignore(…)",
126 snippet: Some("ignore(${0:lint})"),
127 should_be_inner: false,
128 lookup: Some("ignore"),
129 },
130 AttrCompletion {
131 label: "inline(…)",
132 snippet: Some("inline(${0:lint})"),
133 should_be_inner: false,
134 lookup: Some("inline"),
135 },
136 AttrCompletion {
137 label: r#"link_name = "…""#,
84 snippet: Some(r#"link_name = "${0:symbol_name}""#), 138 snippet: Some(r#"link_name = "${0:symbol_name}""#),
85 should_be_inner: false, 139 should_be_inner: false,
140 lookup: Some("link_name"),
86 }, 141 },
87 AttrCompletion { label: "link", snippet: None, should_be_inner: false }, 142 AttrCompletion { label: "link", snippet: None, should_be_inner: false, lookup: None },
88 AttrCompletion { label: "macro_export", snippet: None, should_be_inner: false }, 143 AttrCompletion { label: "macro_export", snippet: None, should_be_inner: false, lookup: None },
89 AttrCompletion { label: "macro_use", snippet: None, should_be_inner: false }, 144 AttrCompletion { label: "macro_use", snippet: None, should_be_inner: false, lookup: None },
90 AttrCompletion { 145 AttrCompletion {
91 label: "must_use", 146 label: r#"must_use = "…""#,
92 snippet: Some(r#"must_use = "${0:reason}""#), 147 snippet: Some(r#"must_use = "${0:reason}""#),
93 should_be_inner: false, 148 should_be_inner: false,
149 lookup: Some("must_use"),
94 }, 150 },
95 AttrCompletion { label: "no_mangle", snippet: None, should_be_inner: false }, 151 AttrCompletion { label: "no_mangle", snippet: None, should_be_inner: false, lookup: None },
96 AttrCompletion { label: "no_std", snippet: None, should_be_inner: true }, 152 AttrCompletion { label: "no_std", snippet: None, should_be_inner: true, lookup: None },
97 AttrCompletion { label: "non_exhaustive", snippet: None, should_be_inner: false }, 153 AttrCompletion { label: "non_exhaustive", snippet: None, should_be_inner: false, lookup: None },
98 AttrCompletion { label: "panic_handler", snippet: None, should_be_inner: true }, 154 AttrCompletion { label: "panic_handler", snippet: None, should_be_inner: true, lookup: None },
99 AttrCompletion { label: "path", snippet: Some("path =\"${0:path}\""), should_be_inner: false },
100 AttrCompletion { label: "proc_macro", snippet: None, should_be_inner: false },
101 AttrCompletion { label: "proc_macro_attribute", snippet: None, should_be_inner: false },
102 AttrCompletion { 155 AttrCompletion {
103 label: "proc_macro_derive", 156 label: "path = \"…\"",
157 snippet: Some("path =\"${0:path}\""),
158 should_be_inner: false,
159 lookup: Some("path"),
160 },
161 AttrCompletion { label: "proc_macro", snippet: None, should_be_inner: false, lookup: None },
162 AttrCompletion {
163 label: "proc_macro_attribute",
164 snippet: None,
165 should_be_inner: false,
166 lookup: None,
167 },
168 AttrCompletion {
169 label: "proc_macro_derive(…)",
104 snippet: Some("proc_macro_derive(${0:Trait})"), 170 snippet: Some("proc_macro_derive(${0:Trait})"),
105 should_be_inner: false, 171 should_be_inner: false,
172 lookup: Some("proc_macro_derive"),
106 }, 173 },
107 AttrCompletion { 174 AttrCompletion {
108 label: "recursion_limit", 175 label: "recursion_limit = …",
109 snippet: Some("recursion_limit = ${0:128}"), 176 snippet: Some("recursion_limit = ${0:128}"),
110 should_be_inner: true, 177 should_be_inner: true,
178 lookup: Some("recursion_limit"),
179 },
180 AttrCompletion {
181 label: "repr(…)",
182 snippet: Some("repr(${0:C})"),
183 should_be_inner: false,
184 lookup: Some("repr"),
111 }, 185 },
112 AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false },
113 AttrCompletion { 186 AttrCompletion {
114 label: "should_panic", 187 label: "should_panic(…)",
115 snippet: Some(r#"should_panic(expected = "${0:reason}")"#), 188 snippet: Some(r#"should_panic(expected = "${0:reason}")"#),
116 should_be_inner: false, 189 should_be_inner: false,
190 lookup: Some("should_panic"),
117 }, 191 },
118 AttrCompletion { 192 AttrCompletion {
119 label: "target_feature", 193 label: r#"target_feature = "…""#,
120 snippet: Some("target_feature = \"${0:feature}\""), 194 snippet: Some("target_feature = \"${0:feature}\""),
121 should_be_inner: false, 195 should_be_inner: false,
196 lookup: Some("target_feature"),
197 },
198 AttrCompletion { label: "test", snippet: None, should_be_inner: false, lookup: None },
199 AttrCompletion { label: "used", snippet: None, should_be_inner: false, lookup: None },
200 AttrCompletion {
201 label: "warn(…)",
202 snippet: Some("warn(${0:lint})"),
203 should_be_inner: false,
204 lookup: Some("warn"),
122 }, 205 },
123 AttrCompletion { label: "test", snippet: None, should_be_inner: false },
124 AttrCompletion { label: "used", snippet: None, should_be_inner: false },
125 AttrCompletion { label: "warn", snippet: Some("warn(${0:lint})"), should_be_inner: false },
126 AttrCompletion { 206 AttrCompletion {
127 label: "windows_subsystem", 207 label: r#"windows_subsystem = "…""#,
128 snippet: Some(r#"windows_subsystem = "${0:subsystem}""#), 208 snippet: Some(r#"windows_subsystem = "${0:subsystem}""#),
129 should_be_inner: true, 209 should_be_inner: true,
210 lookup: Some("windows_subsystem"),
130 }, 211 },
131]; 212];
132 213
@@ -414,74 +495,84 @@ mod tests {
414 @r###" 495 @r###"
415 [ 496 [
416 CompletionItem { 497 CompletionItem {
417 label: "allow", 498 label: "allow(…)",
418 source_range: 19..19, 499 source_range: 19..19,
419 delete: 19..19, 500 delete: 19..19,
420 insert: "allow(${0:lint})", 501 insert: "allow(${0:lint})",
421 kind: Attribute, 502 kind: Attribute,
503 lookup: "allow",
422 }, 504 },
423 CompletionItem { 505 CompletionItem {
424 label: "cfg", 506 label: "cfg(…)",
425 source_range: 19..19, 507 source_range: 19..19,
426 delete: 19..19, 508 delete: 19..19,
427 insert: "cfg(${0:predicate})", 509 insert: "cfg(${0:predicate})",
428 kind: Attribute, 510 kind: Attribute,
511 lookup: "cfg",
429 }, 512 },
430 CompletionItem { 513 CompletionItem {
431 label: "cfg_attr", 514 label: "cfg_attr(…)",
432 source_range: 19..19, 515 source_range: 19..19,
433 delete: 19..19, 516 delete: 19..19,
434 insert: "cfg_attr(${1:predicate}, ${0:attr})", 517 insert: "cfg_attr(${1:predicate}, ${0:attr})",
435 kind: Attribute, 518 kind: Attribute,
519 lookup: "cfg_attr",
436 }, 520 },
437 CompletionItem { 521 CompletionItem {
438 label: "deny", 522 label: "deny(…)",
439 source_range: 19..19, 523 source_range: 19..19,
440 delete: 19..19, 524 delete: 19..19,
441 insert: "deny(${0:lint})", 525 insert: "deny(${0:lint})",
442 kind: Attribute, 526 kind: Attribute,
527 lookup: "deny",
443 }, 528 },
444 CompletionItem { 529 CompletionItem {
445 label: "deprecated", 530 label: "deprecated = \"…\"",
446 source_range: 19..19, 531 source_range: 19..19,
447 delete: 19..19, 532 delete: 19..19,
448 insert: "deprecated = \"${0:reason}\"", 533 insert: "deprecated = \"${0:reason}\"",
449 kind: Attribute, 534 kind: Attribute,
535 lookup: "deprecated",
450 }, 536 },
451 CompletionItem { 537 CompletionItem {
452 label: "derive", 538 label: "derive(…)",
453 source_range: 19..19, 539 source_range: 19..19,
454 delete: 19..19, 540 delete: 19..19,
455 insert: "derive(${0:Debug})", 541 insert: "derive(${0:Debug})",
456 kind: Attribute, 542 kind: Attribute,
543 lookup: "derive",
457 }, 544 },
458 CompletionItem { 545 CompletionItem {
459 label: "doc", 546 label: "doc = \"…\"",
460 source_range: 19..19, 547 source_range: 19..19,
461 delete: 19..19, 548 delete: 19..19,
462 insert: "doc = \"${0:docs}\"", 549 insert: "doc = \"${0:docs}\"",
463 kind: Attribute, 550 kind: Attribute,
551 lookup: "doc",
464 }, 552 },
465 CompletionItem { 553 CompletionItem {
466 label: "forbid", 554 label: "forbid(…)",
467 source_range: 19..19, 555 source_range: 19..19,
468 delete: 19..19, 556 delete: 19..19,
469 insert: "forbid(${0:lint})", 557 insert: "forbid(${0:lint})",
470 kind: Attribute, 558 kind: Attribute,
559 lookup: "forbid",
471 }, 560 },
472 CompletionItem { 561 CompletionItem {
473 label: "ignore", 562 label: "ignore(…)",
474 source_range: 19..19, 563 source_range: 19..19,
475 delete: 19..19, 564 delete: 19..19,
476 insert: "ignore(${0:lint})", 565 insert: "ignore(${0:lint})",
477 kind: Attribute, 566 kind: Attribute,
567 lookup: "ignore",
478 }, 568 },
479 CompletionItem { 569 CompletionItem {
480 label: "inline", 570 label: "inline(…)",
481 source_range: 19..19, 571 source_range: 19..19,
482 delete: 19..19, 572 delete: 19..19,
483 insert: "inline(${0:lint})", 573 insert: "inline(${0:lint})",
484 kind: Attribute, 574 kind: Attribute,
575 lookup: "inline",
485 }, 576 },
486 CompletionItem { 577 CompletionItem {
487 label: "link", 578 label: "link",
@@ -491,11 +582,12 @@ mod tests {
491 kind: Attribute, 582 kind: Attribute,
492 }, 583 },
493 CompletionItem { 584 CompletionItem {
494 label: "link_name", 585 label: "link_name = \"…\"",
495 source_range: 19..19, 586 source_range: 19..19,
496 delete: 19..19, 587 delete: 19..19,
497 insert: "link_name = \"${0:symbol_name}\"", 588 insert: "link_name = \"${0:symbol_name}\"",
498 kind: Attribute, 589 kind: Attribute,
590 lookup: "link_name",
499 }, 591 },
500 CompletionItem { 592 CompletionItem {
501 label: "macro_export", 593 label: "macro_export",
@@ -512,11 +604,12 @@ mod tests {
512 kind: Attribute, 604 kind: Attribute,
513 }, 605 },
514 CompletionItem { 606 CompletionItem {
515 label: "must_use", 607 label: "must_use = \"…\"",
516 source_range: 19..19, 608 source_range: 19..19,
517 delete: 19..19, 609 delete: 19..19,
518 insert: "must_use = \"${0:reason}\"", 610 insert: "must_use = \"${0:reason}\"",
519 kind: Attribute, 611 kind: Attribute,
612 lookup: "must_use",
520 }, 613 },
521 CompletionItem { 614 CompletionItem {
522 label: "no_mangle", 615 label: "no_mangle",
@@ -533,11 +626,12 @@ mod tests {
533 kind: Attribute, 626 kind: Attribute,
534 }, 627 },
535 CompletionItem { 628 CompletionItem {
536 label: "path", 629 label: "path = \"…\"",
537 source_range: 19..19, 630 source_range: 19..19,
538 delete: 19..19, 631 delete: 19..19,
539 insert: "path =\"${0:path}\"", 632 insert: "path =\"${0:path}\"",
540 kind: Attribute, 633 kind: Attribute,
634 lookup: "path",
541 }, 635 },
542 CompletionItem { 636 CompletionItem {
543 label: "proc_macro", 637 label: "proc_macro",
@@ -554,32 +648,36 @@ mod tests {
554 kind: Attribute, 648 kind: Attribute,
555 }, 649 },
556 CompletionItem { 650 CompletionItem {
557 label: "proc_macro_derive", 651 label: "proc_macro_derive(…)",
558 source_range: 19..19, 652 source_range: 19..19,
559 delete: 19..19, 653 delete: 19..19,
560 insert: "proc_macro_derive(${0:Trait})", 654 insert: "proc_macro_derive(${0:Trait})",
561 kind: Attribute, 655 kind: Attribute,
656 lookup: "proc_macro_derive",
562 }, 657 },
563 CompletionItem { 658 CompletionItem {
564 label: "repr", 659 label: "repr(…)",
565 source_range: 19..19, 660 source_range: 19..19,
566 delete: 19..19, 661 delete: 19..19,
567 insert: "repr(${0:C})", 662 insert: "repr(${0:C})",
568 kind: Attribute, 663 kind: Attribute,
664 lookup: "repr",
569 }, 665 },
570 CompletionItem { 666 CompletionItem {
571 label: "should_panic", 667 label: "should_panic(…)",
572 source_range: 19..19, 668 source_range: 19..19,
573 delete: 19..19, 669 delete: 19..19,
574 insert: "should_panic(expected = \"${0:reason}\")", 670 insert: "should_panic(expected = \"${0:reason}\")",
575 kind: Attribute, 671 kind: Attribute,
672 lookup: "should_panic",
576 }, 673 },
577 CompletionItem { 674 CompletionItem {
578 label: "target_feature", 675 label: "target_feature = \"…\"",
579 source_range: 19..19, 676 source_range: 19..19,
580 delete: 19..19, 677 delete: 19..19,
581 insert: "target_feature = \"${0:feature}\"", 678 insert: "target_feature = \"${0:feature}\"",
582 kind: Attribute, 679 kind: Attribute,
680 lookup: "target_feature",
583 }, 681 },
584 CompletionItem { 682 CompletionItem {
585 label: "test", 683 label: "test",
@@ -596,11 +694,12 @@ mod tests {
596 kind: Attribute, 694 kind: Attribute,
597 }, 695 },
598 CompletionItem { 696 CompletionItem {
599 label: "warn", 697 label: "warn(…)",
600 source_range: 19..19, 698 source_range: 19..19,
601 delete: 19..19, 699 delete: 19..19,
602 insert: "warn(${0:lint})", 700 insert: "warn(${0:lint})",
603 kind: Attribute, 701 kind: Attribute,
702 lookup: "warn",
604 }, 703 },
605 ] 704 ]
606 "### 705 "###
@@ -608,6 +707,20 @@ mod tests {
608 } 707 }
609 708
610 #[test] 709 #[test]
710 fn test_attribute_completion_inside_nested_attr() {
711 assert_debug_snapshot!(
712 do_attr_completion(
713 r"
714 #[allow(<|>)]
715 ",
716 ),
717 @r###"
718 []
719 "###
720 );
721 }
722
723 #[test]
611 fn test_inner_attribute_completion() { 724 fn test_inner_attribute_completion() {
612 assert_debug_snapshot!( 725 assert_debug_snapshot!(
613 do_attr_completion( 726 do_attr_completion(
@@ -618,67 +731,76 @@ mod tests {
618 @r###" 731 @r###"
619 [ 732 [
620 CompletionItem { 733 CompletionItem {
621 label: "allow", 734 label: "allow(…)",
622 source_range: 20..20, 735 source_range: 20..20,
623 delete: 20..20, 736 delete: 20..20,
624 insert: "allow(${0:lint})", 737 insert: "allow(${0:lint})",
625 kind: Attribute, 738 kind: Attribute,
739 lookup: "allow",
626 }, 740 },
627 CompletionItem { 741 CompletionItem {
628 label: "cfg", 742 label: "cfg(…)",
629 source_range: 20..20, 743 source_range: 20..20,
630 delete: 20..20, 744 delete: 20..20,
631 insert: "cfg(${0:predicate})", 745 insert: "cfg(${0:predicate})",
632 kind: Attribute, 746 kind: Attribute,
747 lookup: "cfg",
633 }, 748 },
634 CompletionItem { 749 CompletionItem {
635 label: "cfg_attr", 750 label: "cfg_attr(…)",
636 source_range: 20..20, 751 source_range: 20..20,
637 delete: 20..20, 752 delete: 20..20,
638 insert: "cfg_attr(${1:predicate}, ${0:attr})", 753 insert: "cfg_attr(${1:predicate}, ${0:attr})",
639 kind: Attribute, 754 kind: Attribute,
755 lookup: "cfg_attr",
640 }, 756 },
641 CompletionItem { 757 CompletionItem {
642 label: "deny", 758 label: "deny(…)",
643 source_range: 20..20, 759 source_range: 20..20,
644 delete: 20..20, 760 delete: 20..20,
645 insert: "deny(${0:lint})", 761 insert: "deny(${0:lint})",
646 kind: Attribute, 762 kind: Attribute,
763 lookup: "deny",
647 }, 764 },
648 CompletionItem { 765 CompletionItem {
649 label: "deprecated", 766 label: "deprecated = \"…\"",
650 source_range: 20..20, 767 source_range: 20..20,
651 delete: 20..20, 768 delete: 20..20,
652 insert: "deprecated = \"${0:reason}\"", 769 insert: "deprecated = \"${0:reason}\"",
653 kind: Attribute, 770 kind: Attribute,
771 lookup: "deprecated",
654 }, 772 },
655 CompletionItem { 773 CompletionItem {
656 label: "derive", 774 label: "derive(…)",
657 source_range: 20..20, 775 source_range: 20..20,
658 delete: 20..20, 776 delete: 20..20,
659 insert: "derive(${0:Debug})", 777 insert: "derive(${0:Debug})",
660 kind: Attribute, 778 kind: Attribute,
779 lookup: "derive",
661 }, 780 },
662 CompletionItem { 781 CompletionItem {
663 label: "doc", 782 label: "doc = \"…\"",
664 source_range: 20..20, 783 source_range: 20..20,
665 delete: 20..20, 784 delete: 20..20,
666 insert: "doc = \"${0:docs}\"", 785 insert: "doc = \"${0:docs}\"",
667 kind: Attribute, 786 kind: Attribute,
787 lookup: "doc",
668 }, 788 },
669 CompletionItem { 789 CompletionItem {
670 label: "feature", 790 label: "feature(…)",
671 source_range: 20..20, 791 source_range: 20..20,
672 delete: 20..20, 792 delete: 20..20,
673 insert: "feature(${0:flag})", 793 insert: "feature(${0:flag})",
674 kind: Attribute, 794 kind: Attribute,
795 lookup: "feature",
675 }, 796 },
676 CompletionItem { 797 CompletionItem {
677 label: "forbid", 798 label: "forbid(…)",
678 source_range: 20..20, 799 source_range: 20..20,
679 delete: 20..20, 800 delete: 20..20,
680 insert: "forbid(${0:lint})", 801 insert: "forbid(${0:lint})",
681 kind: Attribute, 802 kind: Attribute,
803 lookup: "forbid",
682 }, 804 },
683 CompletionItem { 805 CompletionItem {
684 label: "global_allocator", 806 label: "global_allocator",
@@ -688,18 +810,20 @@ mod tests {
688 kind: Attribute, 810 kind: Attribute,
689 }, 811 },
690 CompletionItem { 812 CompletionItem {
691 label: "ignore", 813 label: "ignore(…)",
692 source_range: 20..20, 814 source_range: 20..20,
693 delete: 20..20, 815 delete: 20..20,
694 insert: "ignore(${0:lint})", 816 insert: "ignore(${0:lint})",
695 kind: Attribute, 817 kind: Attribute,
818 lookup: "ignore",
696 }, 819 },
697 CompletionItem { 820 CompletionItem {
698 label: "inline", 821 label: "inline(…)",
699 source_range: 20..20, 822 source_range: 20..20,
700 delete: 20..20, 823 delete: 20..20,
701 insert: "inline(${0:lint})", 824 insert: "inline(${0:lint})",
702 kind: Attribute, 825 kind: Attribute,
826 lookup: "inline",
703 }, 827 },
704 CompletionItem { 828 CompletionItem {
705 label: "link", 829 label: "link",
@@ -709,11 +833,12 @@ mod tests {
709 kind: Attribute, 833 kind: Attribute,
710 }, 834 },
711 CompletionItem { 835 CompletionItem {
712 label: "link_name", 836 label: "link_name = \"…\"",
713 source_range: 20..20, 837 source_range: 20..20,
714 delete: 20..20, 838 delete: 20..20,
715 insert: "link_name = \"${0:symbol_name}\"", 839 insert: "link_name = \"${0:symbol_name}\"",
716 kind: Attribute, 840 kind: Attribute,
841 lookup: "link_name",
717 }, 842 },
718 CompletionItem { 843 CompletionItem {
719 label: "macro_export", 844 label: "macro_export",
@@ -730,11 +855,12 @@ mod tests {
730 kind: Attribute, 855 kind: Attribute,
731 }, 856 },
732 CompletionItem { 857 CompletionItem {
733 label: "must_use", 858 label: "must_use = \"…\"",
734 source_range: 20..20, 859 source_range: 20..20,
735 delete: 20..20, 860 delete: 20..20,
736 insert: "must_use = \"${0:reason}\"", 861 insert: "must_use = \"${0:reason}\"",
737 kind: Attribute, 862 kind: Attribute,
863 lookup: "must_use",
738 }, 864 },
739 CompletionItem { 865 CompletionItem {
740 label: "no_mangle", 866 label: "no_mangle",
@@ -765,11 +891,12 @@ mod tests {
765 kind: Attribute, 891 kind: Attribute,
766 }, 892 },
767 CompletionItem { 893 CompletionItem {
768 label: "path", 894 label: "path = \"…\"",
769 source_range: 20..20, 895 source_range: 20..20,
770 delete: 20..20, 896 delete: 20..20,
771 insert: "path =\"${0:path}\"", 897 insert: "path =\"${0:path}\"",
772 kind: Attribute, 898 kind: Attribute,
899 lookup: "path",
773 }, 900 },
774 CompletionItem { 901 CompletionItem {
775 label: "proc_macro", 902 label: "proc_macro",
@@ -786,39 +913,44 @@ mod tests {
786 kind: Attribute, 913 kind: Attribute,
787 }, 914 },
788 CompletionItem { 915 CompletionItem {
789 label: "proc_macro_derive", 916 label: "proc_macro_derive(…)",
790 source_range: 20..20, 917 source_range: 20..20,
791 delete: 20..20, 918 delete: 20..20,
792 insert: "proc_macro_derive(${0:Trait})", 919 insert: "proc_macro_derive(${0:Trait})",
793 kind: Attribute, 920 kind: Attribute,
921 lookup: "proc_macro_derive",
794 }, 922 },
795 CompletionItem { 923 CompletionItem {
796 label: "recursion_limit", 924 label: "recursion_limit = …",
797 source_range: 20..20, 925 source_range: 20..20,
798 delete: 20..20, 926 delete: 20..20,
799 insert: "recursion_limit = ${0:128}", 927 insert: "recursion_limit = ${0:128}",
800 kind: Attribute, 928 kind: Attribute,
929 lookup: "recursion_limit",
801 }, 930 },
802 CompletionItem { 931 CompletionItem {
803 label: "repr", 932 label: "repr(…)",
804 source_range: 20..20, 933 source_range: 20..20,
805 delete: 20..20, 934 delete: 20..20,
806 insert: "repr(${0:C})", 935 insert: "repr(${0:C})",
807 kind: Attribute, 936 kind: Attribute,
937 lookup: "repr",
808 }, 938 },
809 CompletionItem { 939 CompletionItem {
810 label: "should_panic", 940 label: "should_panic(…)",
811 source_range: 20..20, 941 source_range: 20..20,
812 delete: 20..20, 942 delete: 20..20,
813 insert: "should_panic(expected = \"${0:reason}\")", 943 insert: "should_panic(expected = \"${0:reason}\")",
814 kind: Attribute, 944 kind: Attribute,
945 lookup: "should_panic",
815 }, 946 },
816 CompletionItem { 947 CompletionItem {
817 label: "target_feature", 948 label: "target_feature = \"…\"",
818 source_range: 20..20, 949 source_range: 20..20,
819 delete: 20..20, 950 delete: 20..20,
820 insert: "target_feature = \"${0:feature}\"", 951 insert: "target_feature = \"${0:feature}\"",
821 kind: Attribute, 952 kind: Attribute,
953 lookup: "target_feature",
822 }, 954 },
823 CompletionItem { 955 CompletionItem {
824 label: "test", 956 label: "test",
@@ -835,18 +967,20 @@ mod tests {
835 kind: Attribute, 967 kind: Attribute,
836 }, 968 },
837 CompletionItem { 969 CompletionItem {
838 label: "warn", 970 label: "warn(…)",
839 source_range: 20..20, 971 source_range: 20..20,
840 delete: 20..20, 972 delete: 20..20,
841 insert: "warn(${0:lint})", 973 insert: "warn(${0:lint})",
842 kind: Attribute, 974 kind: Attribute,
975 lookup: "warn",
843 }, 976 },
844 CompletionItem { 977 CompletionItem {
845 label: "windows_subsystem", 978 label: "windows_subsystem = \"…\"",
846 source_range: 20..20, 979 source_range: 20..20,
847 delete: 20..20, 980 delete: 20..20,
848 insert: "windows_subsystem = \"${0:subsystem}\"", 981 insert: "windows_subsystem = \"${0:subsystem}\"",
849 kind: Attribute, 982 kind: Attribute,
983 lookup: "windows_subsystem",
850 }, 984 },
851 ] 985 ]
852 "### 986 "###
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index be9ab62c0..47823718f 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -70,7 +70,6 @@ pub use crate::{
70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
72 runnables::{Runnable, RunnableKind, TestId}, 72 runnables::{Runnable, RunnableKind, TestId},
73 ssr::SsrError,
74 syntax_highlighting::{ 73 syntax_highlighting::{
75 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 74 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
76 }, 75 },
@@ -89,6 +88,7 @@ pub use ra_ide_db::{
89 symbol_index::Query, 88 symbol_index::Query,
90 RootDatabase, 89 RootDatabase,
91}; 90};
91pub use ra_ssr::SsrError;
92pub use ra_text_edit::{Indel, TextEdit}; 92pub use ra_text_edit::{Indel, TextEdit};
93 93
94pub type Cancelable<T> = Result<T, Canceled>; 94pub type Cancelable<T> = Result<T, Canceled>;
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index c4f07f905..99c2581b7 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -1,11 +1,14 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{ModuleSource, Semantics}; 3use hir::{Module, ModuleDef, ModuleSource, Semantics};
4use ra_db::{RelativePathBuf, SourceDatabaseExt}; 4use ra_db::{RelativePathBuf, SourceDatabaseExt};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase,
8};
6use ra_syntax::{ 9use ra_syntax::{
7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, 10 algo::find_node_at_offset, ast, ast::NameOwner, ast::TypeAscriptionOwner,
8 AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 11 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
9}; 12};
10use ra_text_edit::TextEdit; 13use ra_text_edit::TextEdit;
11use std::convert::TryInto; 14use std::convert::TryInto;
@@ -30,10 +33,8 @@ pub(crate) fn rename(
30 let sema = Semantics::new(db); 33 let sema = Semantics::new(db);
31 let source_file = sema.parse(position.file_id); 34 let source_file = sema.parse(position.file_id);
32 let syntax = source_file.syntax(); 35 let syntax = source_file.syntax();
33 if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { 36 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
34 let range = ast_name.syntax().text_range(); 37 rename_mod(db, position, module, new_name)
35 rename_mod(&sema, &ast_name, &ast_module, position, new_name)
36 .map(|info| RangeInfo::new(range, info))
37 } else if let Some(self_token) = 38 } else if let Some(self_token) =
38 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 39 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
39 { 40 {
@@ -43,13 +44,32 @@ pub(crate) fn rename(
43 } 44 }
44} 45}
45 46
46fn find_name_and_module_at_offset( 47fn find_module_at_offset(
47 syntax: &SyntaxNode, 48 sema: &Semantics<RootDatabase>,
48 position: FilePosition, 49 position: FilePosition,
49) -> Option<(ast::Name, ast::Module)> { 50 syntax: &SyntaxNode,
50 let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; 51) -> Option<Module> {
51 let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; 52 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?;
52 Some((ast_name, ast_module)) 53
54 let module = match_ast! {
55 match (ident.parent()) {
56 ast::NameRef(name_ref) => {
57 match classify_name_ref(sema, &name_ref)? {
58 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
59 _ => return None,
60 }
61 },
62 ast::Name(name) => {
63 match classify_name(&sema, &name)? {
64 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
65 _ => return None,
66 }
67 },
68 _ => return None,
69 }
70 };
71
72 Some(module)
53} 73}
54 74
55fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { 75fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit {
@@ -77,49 +97,50 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
77} 97}
78 98
79fn rename_mod( 99fn rename_mod(
80 sema: &Semantics<RootDatabase>, 100 db: &RootDatabase,
81 ast_name: &ast::Name,
82 ast_module: &ast::Module,
83 position: FilePosition, 101 position: FilePosition,
102 module: Module,
84 new_name: &str, 103 new_name: &str,
85) -> Option<SourceChange> { 104) -> Option<RangeInfo<SourceChange>> {
86 let mut source_file_edits = Vec::new(); 105 let mut source_file_edits = Vec::new();
87 let mut file_system_edits = Vec::new(); 106 let mut file_system_edits = Vec::new();
88 if let Some(module) = sema.to_def(ast_module) { 107
89 let src = module.definition_source(sema.db); 108 let src = module.definition_source(db);
90 let file_id = src.file_id.original_file(sema.db); 109 let file_id = src.file_id.original_file(db);
91 match src.value { 110 match src.value {
92 ModuleSource::SourceFile(..) => { 111 ModuleSource::SourceFile(..) => {
93 let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); 112 let mod_path: RelativePathBuf = db.file_relative_path(file_id);
94 // mod is defined in path/to/dir/mod.rs 113 // mod is defined in path/to/dir/mod.rs
95 let dst = if mod_path.file_stem() == Some("mod") { 114 let dst = if mod_path.file_stem() == Some("mod") {
96 format!("../{}/mod.rs", new_name) 115 format!("../{}/mod.rs", new_name)
97 } else { 116 } else {
98 format!("{}.rs", new_name) 117 format!("{}.rs", new_name)
99 }; 118 };
100 let move_file = 119 let move_file =
101 FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; 120 FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst };
102 file_system_edits.push(move_file); 121 file_system_edits.push(move_file);
103 }
104 ModuleSource::Module(..) => {}
105 } 122 }
123 ModuleSource::Module(..) => {}
106 } 124 }
107 125
108 let edit = SourceFileEdit { 126 if let Some(src) = module.declaration_source(db) {
109 file_id: position.file_id, 127 let file_id = src.file_id.original_file(db);
110 edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), 128 let name = src.value.name()?;
111 }; 129 let edit = SourceFileEdit {
112 source_file_edits.push(edit); 130 file_id: file_id,
113 131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
114 if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) { 132 };
115 let ref_edits = refs 133 source_file_edits.push(edit);
116 .references
117 .into_iter()
118 .map(|reference| source_edit_from_reference(reference, new_name));
119 source_file_edits.extend(ref_edits);
120 } 134 }
121 135
122 Some(SourceChange::from_edits(source_file_edits, file_system_edits)) 136 let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
137 let ref_edits = refs
138 .references
139 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits);
142
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
123} 144}
124 145
125fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { 146fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
@@ -667,6 +688,76 @@ mod foo<|>;
667 } 688 }
668 689
669 #[test] 690 #[test]
691 fn test_rename_mod_in_use_tree() {
692 let (analysis, position) = analysis_and_position(
693 r#"
694//- /main.rs
695pub mod foo;
696pub mod bar;
697fn main() {}
698
699//- /foo.rs
700pub struct FooContent;
701
702//- /bar.rs
703use crate::foo<|>::FooContent;
704 "#,
705 );
706 let new_name = "qux";
707 let source_change = analysis.rename(position, new_name).unwrap();
708 assert_debug_snapshot!(&source_change,
709@r###"
710 Some(
711 RangeInfo {
712 range: 11..14,
713 info: SourceChange {
714 source_file_edits: [
715 SourceFileEdit {
716 file_id: FileId(
717 1,
718 ),
719 edit: TextEdit {
720 indels: [
721 Indel {
722 insert: "qux",
723 delete: 8..11,
724 },
725 ],
726 },
727 },
728 SourceFileEdit {
729 file_id: FileId(
730 3,
731 ),
732 edit: TextEdit {
733 indels: [
734 Indel {
735 insert: "qux",
736 delete: 11..14,
737 },
738 ],
739 },
740 },
741 ],
742 file_system_edits: [
743 MoveFile {
744 src: FileId(
745 2,
746 ),
747 anchor: FileId(
748 3,
749 ),
750 dst: "qux.rs",
751 },
752 ],
753 is_snippet: false,
754 },
755 },
756 )
757 "###);
758 }
759
760 #[test]
670 fn test_rename_mod_in_dir() { 761 fn test_rename_mod_in_dir() {
671 let (analysis, position) = analysis_and_position( 762 let (analysis, position) = analysis_and_position(
672 r#" 763 r#"
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 762aab962..59c230f6c 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,31 +1,12 @@
1use std::{collections::HashMap, iter::once, str::FromStr}; 1use ra_db::SourceDatabaseExt;
2
3use ra_db::{SourceDatabase, SourceDatabaseExt};
4use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 2use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
5use ra_syntax::ast::{
6 make::try_expr_from_text, ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr,
7 RecordField, RecordLit,
8};
9use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode};
10use ra_text_edit::{TextEdit, TextEditBuilder};
11use rustc_hash::FxHashMap;
12 3
13use crate::SourceFileEdit; 4use crate::SourceFileEdit;
14 5use ra_ssr::{MatchFinder, SsrError, SsrRule};
15#[derive(Debug, PartialEq)]
16pub struct SsrError(String);
17
18impl std::fmt::Display for SsrError {
19 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20 write!(f, "Parse error: {}", self.0)
21 }
22}
23
24impl std::error::Error for SsrError {}
25 6
26// Feature: Structural Seach and Replace 7// Feature: Structural Seach and Replace
27// 8//
28// Search and replace with named wildcards that will match any expression. 9// Search and replace with named wildcards that will match any expression, type, path, pattern or item.
29// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. 10// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
30// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. 11// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
31// Available via the command `rust-analyzer.ssr`. 12// Available via the command `rust-analyzer.ssr`.
@@ -46,550 +27,24 @@ impl std::error::Error for SsrError {}
46// | VS Code | **Rust Analyzer: Structural Search Replace** 27// | VS Code | **Rust Analyzer: Structural Search Replace**
47// |=== 28// |===
48pub fn parse_search_replace( 29pub fn parse_search_replace(
49 query: &str, 30 rule: &str,
50 parse_only: bool, 31 parse_only: bool,
51 db: &RootDatabase, 32 db: &RootDatabase,
52) -> Result<Vec<SourceFileEdit>, SsrError> { 33) -> Result<Vec<SourceFileEdit>, SsrError> {
53 let mut edits = vec![]; 34 let mut edits = vec![];
54 let query: SsrQuery = query.parse()?; 35 let rule: SsrRule = rule.parse()?;
55 if parse_only { 36 if parse_only {
56 return Ok(edits); 37 return Ok(edits);
57 } 38 }
39 let mut match_finder = MatchFinder::new(db);
40 match_finder.add_rule(rule);
58 for &root in db.local_roots().iter() { 41 for &root in db.local_roots().iter() {
59 let sr = db.source_root(root); 42 let sr = db.source_root(root);
60 for file_id in sr.walk() { 43 for file_id in sr.walk() {
61 let matches = find(&query.pattern, db.parse(file_id).tree().syntax()); 44 if let Some(edit) = match_finder.edits_for_file(file_id) {
62 if !matches.matches.is_empty() { 45 edits.push(SourceFileEdit { file_id, edit });
63 edits.push(SourceFileEdit { file_id, edit: replace(&matches, &query.template) });
64 } 46 }
65 } 47 }
66 } 48 }
67 Ok(edits) 49 Ok(edits)
68} 50}
69
70#[derive(Debug)]
71struct SsrQuery {
72 pattern: SsrPattern,
73 template: SsrTemplate,
74}
75
76#[derive(Debug)]
77struct SsrPattern {
78 pattern: SyntaxNode,
79 vars: Vec<Var>,
80}
81
82/// Represents a `$var` in an SSR query.
83#[derive(Debug, Clone, PartialEq, Eq, Hash)]
84struct Var(String);
85
86#[derive(Debug)]
87struct SsrTemplate {
88 template: SyntaxNode,
89 placeholders: FxHashMap<SyntaxNode, Var>,
90}
91
92type Binding = HashMap<Var, SyntaxNode>;
93
94#[derive(Debug)]
95struct Match {
96 place: SyntaxNode,
97 binding: Binding,
98 ignored_comments: Vec<Comment>,
99}
100
101#[derive(Debug)]
102struct SsrMatches {
103 matches: Vec<Match>,
104}
105
106impl FromStr for SsrQuery {
107 type Err = SsrError;
108
109 fn from_str(query: &str) -> Result<SsrQuery, SsrError> {
110 let mut it = query.split("==>>");
111 let pattern = it.next().expect("at least empty string").trim();
112 let mut template = it
113 .next()
114 .ok_or_else(|| SsrError("Cannot find delemiter `==>>`".into()))?
115 .trim()
116 .to_string();
117 if it.next().is_some() {
118 return Err(SsrError("More than one delimiter found".into()));
119 }
120 let mut vars = vec![];
121 let mut it = pattern.split('$');
122 let mut pattern = it.next().expect("something").to_string();
123
124 for part in it.map(split_by_var) {
125 let (var, remainder) = part?;
126 let new_var = create_name(var, &mut vars)?;
127 pattern.push_str(new_var);
128 pattern.push_str(remainder);
129 template = replace_in_template(template, var, new_var);
130 }
131
132 let template = try_expr_from_text(&template)
133 .ok_or(SsrError("Template is not an expression".into()))?
134 .syntax()
135 .clone();
136 let mut placeholders = FxHashMap::default();
137
138 traverse(&template, &mut |n| {
139 if let Some(v) = vars.iter().find(|v| v.0.as_str() == n.text()) {
140 placeholders.insert(n.clone(), v.clone());
141 false
142 } else {
143 true
144 }
145 });
146
147 let pattern = SsrPattern {
148 pattern: try_expr_from_text(&pattern)
149 .ok_or(SsrError("Pattern is not an expression".into()))?
150 .syntax()
151 .clone(),
152 vars,
153 };
154 let template = SsrTemplate { template, placeholders };
155 Ok(SsrQuery { pattern, template })
156 }
157}
158
159fn traverse(node: &SyntaxNode, go: &mut impl FnMut(&SyntaxNode) -> bool) {
160 if !go(node) {
161 return;
162 }
163 for ref child in node.children() {
164 traverse(child, go);
165 }
166}
167
168fn split_by_var(s: &str) -> Result<(&str, &str), SsrError> {
169 let end_of_name = s.find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len());
170 let name = &s[..end_of_name];
171 is_name(name)?;
172 Ok((name, &s[end_of_name..]))
173}
174
175fn is_name(s: &str) -> Result<(), SsrError> {
176 if s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
177 Ok(())
178 } else {
179 Err(SsrError("Name can contain only alphanumerics and _".into()))
180 }
181}
182
183fn replace_in_template(template: String, var: &str, new_var: &str) -> String {
184 let name = format!("${}", var);
185 template.replace(&name, new_var)
186}
187
188fn create_name<'a>(name: &str, vars: &'a mut Vec<Var>) -> Result<&'a str, SsrError> {
189 let sanitized_name = format!("__search_pattern_{}", name);
190 if vars.iter().any(|a| a.0 == sanitized_name) {
191 return Err(SsrError(format!("Name `{}` repeats more than once", name)));
192 }
193 vars.push(Var(sanitized_name));
194 Ok(&vars.last().unwrap().0)
195}
196
197fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
198 fn check_record_lit(
199 pattern: RecordLit,
200 code: RecordLit,
201 placeholders: &[Var],
202 match_: Match,
203 ) -> Option<Match> {
204 let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?;
205
206 let mut pattern_fields: Vec<RecordField> =
207 pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
208 let mut code_fields: Vec<RecordField> =
209 code.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
210
211 if pattern_fields.len() != code_fields.len() {
212 return None;
213 }
214
215 let by_name = |a: &RecordField, b: &RecordField| {
216 a.name_ref()
217 .map(|x| x.syntax().text().to_string())
218 .cmp(&b.name_ref().map(|x| x.syntax().text().to_string()))
219 };
220 pattern_fields.sort_by(by_name);
221 code_fields.sort_by(by_name);
222
223 pattern_fields.into_iter().zip(code_fields.into_iter()).fold(
224 Some(match_),
225 |accum, (a, b)| {
226 accum.and_then(|match_| check_opt_nodes(Some(a), Some(b), placeholders, match_))
227 },
228 )
229 }
230
231 fn check_call_and_method_call(
232 pattern: CallExpr,
233 code: MethodCallExpr,
234 placeholders: &[Var],
235 match_: Match,
236 ) -> Option<Match> {
237 let (pattern_name, pattern_type_args) = if let Some(Expr::PathExpr(path_exr)) =
238 pattern.expr()
239 {
240 let segment = path_exr.path().and_then(|p| p.segment());
241 (segment.as_ref().and_then(|s| s.name_ref()), segment.and_then(|s| s.type_arg_list()))
242 } else {
243 (None, None)
244 };
245 let match_ = check_opt_nodes(pattern_name, code.name_ref(), placeholders, match_)?;
246 let match_ =
247 check_opt_nodes(pattern_type_args, code.type_arg_list(), placeholders, match_)?;
248 let pattern_args = pattern.syntax().children().find_map(ArgList::cast)?.args();
249 let code_args = code.syntax().children().find_map(ArgList::cast)?.args();
250 let code_args = once(code.expr()?).chain(code_args);
251 check_iter(pattern_args, code_args, placeholders, match_)
252 }
253
254 fn check_method_call_and_call(
255 pattern: MethodCallExpr,
256 code: CallExpr,
257 placeholders: &[Var],
258 match_: Match,
259 ) -> Option<Match> {
260 let (code_name, code_type_args) = if let Some(Expr::PathExpr(path_exr)) = code.expr() {
261 let segment = path_exr.path().and_then(|p| p.segment());
262 (segment.as_ref().and_then(|s| s.name_ref()), segment.and_then(|s| s.type_arg_list()))
263 } else {
264 (None, None)
265 };
266 let match_ = check_opt_nodes(pattern.name_ref(), code_name, placeholders, match_)?;
267 let match_ =
268 check_opt_nodes(pattern.type_arg_list(), code_type_args, placeholders, match_)?;
269 let code_args = code.syntax().children().find_map(ArgList::cast)?.args();
270 let pattern_args = pattern.syntax().children().find_map(ArgList::cast)?.args();
271 let pattern_args = once(pattern.expr()?).chain(pattern_args);
272 check_iter(pattern_args, code_args, placeholders, match_)
273 }
274
275 fn check_opt_nodes(
276 pattern: Option<impl AstNode>,
277 code: Option<impl AstNode>,
278 placeholders: &[Var],
279 match_: Match,
280 ) -> Option<Match> {
281 match (pattern, code) {
282 (Some(pattern), Some(code)) => check(
283 &pattern.syntax().clone().into(),
284 &code.syntax().clone().into(),
285 placeholders,
286 match_,
287 ),
288 (None, None) => Some(match_),
289 _ => None,
290 }
291 }
292
293 fn check_iter<T, I1, I2>(
294 mut pattern: I1,
295 mut code: I2,
296 placeholders: &[Var],
297 match_: Match,
298 ) -> Option<Match>
299 where
300 T: AstNode,
301 I1: Iterator<Item = T>,
302 I2: Iterator<Item = T>,
303 {
304 pattern
305 .by_ref()
306 .zip(code.by_ref())
307 .fold(Some(match_), |accum, (a, b)| {
308 accum.and_then(|match_| {
309 check(
310 &a.syntax().clone().into(),
311 &b.syntax().clone().into(),
312 placeholders,
313 match_,
314 )
315 })
316 })
317 .filter(|_| pattern.next().is_none() && code.next().is_none())
318 }
319
320 fn check(
321 pattern: &SyntaxElement,
322 code: &SyntaxElement,
323 placeholders: &[Var],
324 mut match_: Match,
325 ) -> Option<Match> {
326 match (&pattern, &code) {
327 (SyntaxElement::Token(pattern), SyntaxElement::Token(code)) => {
328 if pattern.text() == code.text() {
329 Some(match_)
330 } else {
331 None
332 }
333 }
334 (SyntaxElement::Node(pattern), SyntaxElement::Node(code)) => {
335 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
336 match_.binding.insert(Var(pattern.text().to_string()), code.clone());
337 Some(match_)
338 } else {
339 if let (Some(pattern), Some(code)) =
340 (RecordLit::cast(pattern.clone()), RecordLit::cast(code.clone()))
341 {
342 check_record_lit(pattern, code, placeholders, match_)
343 } else if let (Some(pattern), Some(code)) =
344 (CallExpr::cast(pattern.clone()), MethodCallExpr::cast(code.clone()))
345 {
346 check_call_and_method_call(pattern, code, placeholders, match_)
347 } else if let (Some(pattern), Some(code)) =
348 (MethodCallExpr::cast(pattern.clone()), CallExpr::cast(code.clone()))
349 {
350 check_method_call_and_call(pattern, code, placeholders, match_)
351 } else {
352 let mut pattern_children = pattern
353 .children_with_tokens()
354 .filter(|element| !element.kind().is_trivia());
355 let mut code_children = code
356 .children_with_tokens()
357 .filter(|element| !element.kind().is_trivia());
358 let new_ignored_comments =
359 code.children_with_tokens().filter_map(|element| {
360 element.as_token().and_then(|token| Comment::cast(token.clone()))
361 });
362 match_.ignored_comments.extend(new_ignored_comments);
363 pattern_children
364 .by_ref()
365 .zip(code_children.by_ref())
366 .fold(Some(match_), |accum, (a, b)| {
367 accum.and_then(|match_| check(&a, &b, placeholders, match_))
368 })
369 .filter(|_| {
370 pattern_children.next().is_none() && code_children.next().is_none()
371 })
372 }
373 }
374 }
375 _ => None,
376 }
377 }
378 let kind = pattern.pattern.kind();
379 let matches = code
380 .descendants()
381 .filter(|n| {
382 n.kind() == kind
383 || (kind == SyntaxKind::CALL_EXPR && n.kind() == SyntaxKind::METHOD_CALL_EXPR)
384 || (kind == SyntaxKind::METHOD_CALL_EXPR && n.kind() == SyntaxKind::CALL_EXPR)
385 })
386 .filter_map(|code| {
387 let match_ =
388 Match { place: code.clone(), binding: HashMap::new(), ignored_comments: vec![] };
389 check(&pattern.pattern.clone().into(), &code.into(), &pattern.vars, match_)
390 })
391 .collect();
392 SsrMatches { matches }
393}
394
395fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit {
396 let mut builder = TextEditBuilder::default();
397 for match_ in &matches.matches {
398 builder.replace(
399 match_.place.text_range(),
400 render_replace(&match_.binding, &match_.ignored_comments, template),
401 );
402 }
403 builder.finish()
404}
405
406fn render_replace(
407 binding: &Binding,
408 ignored_comments: &Vec<Comment>,
409 template: &SsrTemplate,
410) -> String {
411 let edit = {
412 let mut builder = TextEditBuilder::default();
413 for element in template.template.descendants() {
414 if let Some(var) = template.placeholders.get(&element) {
415 builder.replace(element.text_range(), binding[var].to_string())
416 }
417 }
418 for comment in ignored_comments {
419 builder.insert(template.template.text_range().end(), comment.syntax().to_string())
420 }
421 builder.finish()
422 };
423
424 let mut text = template.template.text().to_string();
425 edit.apply(&mut text);
426 text
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432 use ra_syntax::SourceFile;
433
434 fn parse_error_text(query: &str) -> String {
435 format!("{}", query.parse::<SsrQuery>().unwrap_err())
436 }
437
438 #[test]
439 fn parser_happy_case() {
440 let result: SsrQuery = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap();
441 assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)");
442 assert_eq!(result.pattern.vars.len(), 2);
443 assert_eq!(result.pattern.vars[0].0, "__search_pattern_a");
444 assert_eq!(result.pattern.vars[1].0, "__search_pattern_b");
445 assert_eq!(&result.template.template.text(), "bar(__search_pattern_b, __search_pattern_a)");
446 }
447
448 #[test]
449 fn parser_empty_query() {
450 assert_eq!(parse_error_text(""), "Parse error: Cannot find delemiter `==>>`");
451 }
452
453 #[test]
454 fn parser_no_delimiter() {
455 assert_eq!(parse_error_text("foo()"), "Parse error: Cannot find delemiter `==>>`");
456 }
457
458 #[test]
459 fn parser_two_delimiters() {
460 assert_eq!(
461 parse_error_text("foo() ==>> a ==>> b "),
462 "Parse error: More than one delimiter found"
463 );
464 }
465
466 #[test]
467 fn parser_repeated_name() {
468 assert_eq!(
469 parse_error_text("foo($a, $a) ==>>"),
470 "Parse error: Name `a` repeats more than once"
471 );
472 }
473
474 #[test]
475 fn parser_invlid_pattern() {
476 assert_eq!(parse_error_text(" ==>> ()"), "Parse error: Pattern is not an expression");
477 }
478
479 #[test]
480 fn parser_invlid_template() {
481 assert_eq!(parse_error_text("() ==>> )"), "Parse error: Template is not an expression");
482 }
483
484 #[test]
485 fn parse_match_replace() {
486 let query: SsrQuery = "foo($x) ==>> bar($x)".parse().unwrap();
487 let input = "fn main() { foo(1+2); }";
488
489 let code = SourceFile::parse(input).tree();
490 let matches = find(&query.pattern, code.syntax());
491 assert_eq!(matches.matches.len(), 1);
492 assert_eq!(matches.matches[0].place.text(), "foo(1+2)");
493 assert_eq!(matches.matches[0].binding.len(), 1);
494 assert_eq!(
495 matches.matches[0].binding[&Var("__search_pattern_x".to_string())].text(),
496 "1+2"
497 );
498
499 let edit = replace(&matches, &query.template);
500 let mut after = input.to_string();
501 edit.apply(&mut after);
502 assert_eq!(after, "fn main() { bar(1+2); }");
503 }
504
505 fn assert_ssr_transform(query: &str, input: &str, result: &str) {
506 let query: SsrQuery = query.parse().unwrap();
507 let code = SourceFile::parse(input).tree();
508 let matches = find(&query.pattern, code.syntax());
509 let edit = replace(&matches, &query.template);
510 let mut after = input.to_string();
511 edit.apply(&mut after);
512 assert_eq!(after, result);
513 }
514
515 #[test]
516 fn ssr_function_to_method() {
517 assert_ssr_transform(
518 "my_function($a, $b) ==>> ($a).my_method($b)",
519 "loop { my_function( other_func(x, y), z + w) }",
520 "loop { (other_func(x, y)).my_method(z + w) }",
521 )
522 }
523
524 #[test]
525 fn ssr_nested_function() {
526 assert_ssr_transform(
527 "foo($a, $b, $c) ==>> bar($c, baz($a, $b))",
528 "fn main { foo (x + value.method(b), x+y-z, true && false) }",
529 "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
530 )
531 }
532
533 #[test]
534 fn ssr_expected_spacing() {
535 assert_ssr_transform(
536 "foo($x) + bar() ==>> bar($x)",
537 "fn main() { foo(5) + bar() }",
538 "fn main() { bar(5) }",
539 );
540 }
541
542 #[test]
543 fn ssr_with_extra_space() {
544 assert_ssr_transform(
545 "foo($x ) + bar() ==>> bar($x)",
546 "fn main() { foo( 5 ) +bar( ) }",
547 "fn main() { bar(5) }",
548 );
549 }
550
551 #[test]
552 fn ssr_keeps_nested_comment() {
553 assert_ssr_transform(
554 "foo($x) ==>> bar($x)",
555 "fn main() { foo(other(5 /* using 5 */)) }",
556 "fn main() { bar(other(5 /* using 5 */)) }",
557 )
558 }
559
560 #[test]
561 fn ssr_keeps_comment() {
562 assert_ssr_transform(
563 "foo($x) ==>> bar($x)",
564 "fn main() { foo(5 /* using 5 */) }",
565 "fn main() { bar(5)/* using 5 */ }",
566 )
567 }
568
569 #[test]
570 fn ssr_struct_lit() {
571 assert_ssr_transform(
572 "foo{a: $a, b: $b} ==>> foo::new($a, $b)",
573 "fn main() { foo{b:2, a:1} }",
574 "fn main() { foo::new(1, 2) }",
575 )
576 }
577
578 #[test]
579 fn ssr_call_and_method_call() {
580 assert_ssr_transform(
581 "foo::<'a>($a, $b)) ==>> foo2($a, $b)",
582 "fn main() { get().bar.foo::<'a>(1); }",
583 "fn main() { foo2(get().bar, 1); }",
584 )
585 }
586
587 #[test]
588 fn ssr_method_call_and_call() {
589 assert_ssr_transform(
590 "$o.foo::<i32>($a)) ==>> $o.foo2($a)",
591 "fn main() { X::foo::<i32>(x, 1); }",
592 "fn main() { x.foo2(1); }",
593 )
594 }
595}