aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/replacing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/replacing.rs')
-rw-r--r--crates/ra_ssr/src/replacing.rs106
1 files changed, 70 insertions, 36 deletions
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs
index 81f8634ba..f1c5bdf14 100644
--- a/crates/ra_ssr/src/replacing.rs
+++ b/crates/ra_ssr/src/replacing.rs
@@ -1,70 +1,104 @@
1//! Code for applying replacement templates for matches that have previously been found. 1//! Code for applying replacement templates for matches that have previously been found.
2 2
3use crate::matching::Var; 3use crate::matching::Var;
4use crate::parsing::PatternElement; 4use crate::{parsing::ParsedRule, Match, SsrMatches};
5use crate::{Match, SsrMatches};
6use ra_syntax::ast::AstToken; 5use ra_syntax::ast::AstToken;
7use ra_syntax::TextSize; 6use ra_syntax::{SyntaxElement, SyntaxNode, SyntaxToken, TextSize};
8use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
9 8
10/// Returns a text edit that will replace each match in `matches` with its corresponding replacement 9/// Returns a text edit that will replace each match in `matches` with its corresponding replacement
11/// template. Placeholders in the template will have been substituted with whatever they matched to 10/// template. Placeholders in the template will have been substituted with whatever they matched to
12/// in the original code. 11/// in the original code.
13pub(crate) fn matches_to_edit(matches: &SsrMatches, file_src: &str) -> TextEdit { 12pub(crate) fn matches_to_edit(
14 matches_to_edit_at_offset(matches, file_src, 0.into()) 13 matches: &SsrMatches,
14 file_src: &str,
15 rules: &[ParsedRule],
16) -> TextEdit {
17 matches_to_edit_at_offset(matches, file_src, 0.into(), rules)
15} 18}
16 19
17fn matches_to_edit_at_offset( 20fn matches_to_edit_at_offset(
18 matches: &SsrMatches, 21 matches: &SsrMatches,
19 file_src: &str, 22 file_src: &str,
20 relative_start: TextSize, 23 relative_start: TextSize,
24 rules: &[ParsedRule],
21) -> TextEdit { 25) -> TextEdit {
22 let mut edit_builder = ra_text_edit::TextEditBuilder::default(); 26 let mut edit_builder = ra_text_edit::TextEditBuilder::default();
23 for m in &matches.matches { 27 for m in &matches.matches {
24 edit_builder.replace( 28 edit_builder.replace(
25 m.range.range.checked_sub(relative_start).unwrap(), 29 m.range.range.checked_sub(relative_start).unwrap(),
26 render_replace(m, file_src), 30 render_replace(m, file_src, rules),
27 ); 31 );
28 } 32 }
29 edit_builder.finish() 33 edit_builder.finish()
30} 34}
31 35
32fn render_replace(match_info: &Match, file_src: &str) -> String { 36struct ReplacementRenderer<'a> {
37 match_info: &'a Match,
38 file_src: &'a str,
39 rules: &'a [ParsedRule],
40 rule: &'a ParsedRule,
41}
42
43fn render_replace(match_info: &Match, file_src: &str, rules: &[ParsedRule]) -> String {
33 let mut out = String::new(); 44 let mut out = String::new();
34 let template = match_info 45 let rule = &rules[match_info.rule_index];
46 let template = rule
35 .template 47 .template
36 .as_ref() 48 .as_ref()
37 .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern"); 49 .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern");
38 for r in &template.tokens { 50 let renderer = ReplacementRenderer { match_info, file_src, rules, rule };
39 match r { 51 renderer.render_node_children(&template, &mut out);
40 PatternElement::Token(t) => out.push_str(t.text.as_str()),
41 PatternElement::Placeholder(p) => {
42 if let Some(placeholder_value) =
43 match_info.placeholder_values.get(&Var(p.ident.to_string()))
44 {
45 let range = &placeholder_value.range.range;
46 let mut matched_text =
47 file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
48 let edit = matches_to_edit_at_offset(
49 &placeholder_value.inner_matches,
50 file_src,
51 range.start(),
52 );
53 edit.apply(&mut matched_text);
54 out.push_str(&matched_text);
55 } else {
56 // We validated that all placeholder references were valid before we
57 // started, so this shouldn't happen.
58 panic!(
59 "Internal error: replacement referenced unknown placeholder {}",
60 p.ident
61 );
62 }
63 }
64 }
65 }
66 for comment in &match_info.ignored_comments { 52 for comment in &match_info.ignored_comments {
67 out.push_str(&comment.syntax().to_string()); 53 out.push_str(&comment.syntax().to_string());
68 } 54 }
69 out 55 out
70} 56}
57
58impl ReplacementRenderer<'_> {
59 fn render_node_children(&self, node: &SyntaxNode, out: &mut String) {
60 for node_or_token in node.children_with_tokens() {
61 self.render_node_or_token(&node_or_token, out);
62 }
63 }
64
65 fn render_node_or_token(&self, node_or_token: &SyntaxElement, out: &mut String) {
66 match node_or_token {
67 SyntaxElement::Token(token) => {
68 self.render_token(&token, out);
69 }
70 SyntaxElement::Node(child_node) => {
71 self.render_node_children(&child_node, out);
72 }
73 }
74 }
75
76 fn render_token(&self, token: &SyntaxToken, out: &mut String) {
77 if let Some(placeholder) = self.rule.get_placeholder(&token) {
78 if let Some(placeholder_value) =
79 self.match_info.placeholder_values.get(&Var(placeholder.ident.to_string()))
80 {
81 let range = &placeholder_value.range.range;
82 let mut matched_text =
83 self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
84 let edit = matches_to_edit_at_offset(
85 &placeholder_value.inner_matches,
86 self.file_src,
87 range.start(),
88 self.rules,
89 );
90 edit.apply(&mut matched_text);
91 out.push_str(&matched_text);
92 } else {
93 // We validated that all placeholder references were valid before we
94 // started, so this shouldn't happen.
95 panic!(
96 "Internal error: replacement referenced unknown placeholder {}",
97 placeholder.ident
98 );
99 }
100 } else {
101 out.push_str(token.text().as_str());
102 }
103 }
104}