aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/syntax_highlighting.rs135
-rw-r--r--crates/ide/src/syntax_highlighting/macro_rules.rs136
2 files changed, 154 insertions, 117 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index f430006d7..8ecaff204 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -1,6 +1,7 @@
1mod format; 1mod format;
2mod html; 2mod html;
3mod injection; 3mod injection;
4mod macro_rules;
4mod tags; 5mod tags;
5#[cfg(test)] 6#[cfg(test)]
6mod tests; 7mod tests;
@@ -18,7 +19,10 @@ use syntax::{
18 SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, 19 SyntaxNode, SyntaxToken, TextRange, WalkEvent, T,
19}; 20};
20 21
21use crate::{syntax_highlighting::format::FormatStringHighlighter, FileId}; 22use crate::{
23 syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter},
24 FileId,
25};
22 26
23pub(crate) use html::highlight_as_html; 27pub(crate) use html::highlight_as_html;
24pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; 28pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
@@ -68,8 +72,9 @@ pub(crate) fn highlight(
68 // When we leave a node, the we use it to flatten the highlighted ranges. 72 // When we leave a node, the we use it to flatten the highlighted ranges.
69 let mut stack = HighlightedRangeStack::new(); 73 let mut stack = HighlightedRangeStack::new();
70 74
71 let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; 75 let mut current_macro_call: Option<ast::MacroCall> = None;
72 let mut format_string_highlighter = FormatStringHighlighter::default(); 76 let mut format_string_highlighter = FormatStringHighlighter::default();
77 let mut macro_rules_highlighter = MacroRulesHighlighter::new();
73 78
74 // Walk all nodes, keeping track of whether we are inside a macro or not. 79 // Walk all nodes, keeping track of whether we are inside a macro or not.
75 // If in macro, expand it first and highlight the expanded code. 80 // If in macro, expand it first and highlight the expanded code.
@@ -99,9 +104,8 @@ pub(crate) fn highlight(
99 binding_hash: None, 104 binding_hash: None,
100 }); 105 });
101 } 106 }
102 let mut is_macro_rules = None;
103 if let Some(name) = mc.is_macro_rules() { 107 if let Some(name) = mc.is_macro_rules() {
104 is_macro_rules = Some(MacroMatcherParseState::new()); 108 macro_rules_highlighter.init();
105 if let Some((highlight, binding_hash)) = highlight_element( 109 if let Some((highlight, binding_hash)) = highlight_element(
106 &sema, 110 &sema,
107 &mut bindings_shadow_count, 111 &mut bindings_shadow_count,
@@ -115,13 +119,14 @@ pub(crate) fn highlight(
115 }); 119 });
116 } 120 }
117 } 121 }
118 current_macro_call = Some((mc.clone(), is_macro_rules)); 122 current_macro_call = Some(mc.clone());
119 continue; 123 continue;
120 } 124 }
121 WalkEvent::Leave(Some(mc)) => { 125 WalkEvent::Leave(Some(mc)) => {
122 assert!(current_macro_call.map(|it| it.0) == Some(mc)); 126 assert!(current_macro_call == Some(mc));
123 current_macro_call = None; 127 current_macro_call = None;
124 format_string_highlighter.reset(); 128 format_string_highlighter.reset();
129 macro_rules_highlighter.reset();
125 } 130 }
126 _ => (), 131 _ => (),
127 } 132 }
@@ -148,20 +153,6 @@ pub(crate) fn highlight(
148 WalkEvent::Leave(_) => continue, 153 WalkEvent::Leave(_) => continue,
149 }; 154 };
150 155
151 // check if in matcher part of a macro_rules rule
152 if let Some((_, Some(ref mut state))) = current_macro_call {
153 if let Some(tok) = element.as_token() {
154 if matches!(
155 update_macro_rules_state(tok, state),
156 RuleState::Matcher | RuleState::Expander
157 ) {
158 if skip_metavariables(element.clone()) {
159 continue;
160 }
161 }
162 }
163 }
164
165 let range = element.text_range(); 156 let range = element.text_range();
166 157
167 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { 158 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
@@ -174,6 +165,9 @@ pub(crate) fn highlight(
174 let parent = token.parent(); 165 let parent = token.parent();
175 166
176 format_string_highlighter.check_for_format_string(&parent); 167 format_string_highlighter.check_for_format_string(&parent);
168 if let Some(tok) = element.as_token() {
169 macro_rules_highlighter.advance(tok);
170 }
177 171
178 // We only care Name and Name_ref 172 // We only care Name and Name_ref
179 match (token.kind(), parent.kind()) { 173 match (token.kind(), parent.kind()) {
@@ -197,7 +191,10 @@ pub(crate) fn highlight(
197 syntactic_name_ref_highlighting, 191 syntactic_name_ref_highlighting,
198 element_to_highlight.clone(), 192 element_to_highlight.clone(),
199 ) { 193 ) {
200 stack.add(HighlightedRange { range, highlight, binding_hash }); 194 if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() {
195 stack.add(HighlightedRange { range, highlight, binding_hash });
196 }
197
201 if let Some(string) = 198 if let Some(string) =
202 element_to_highlight.as_token().cloned().and_then(ast::String::cast) 199 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
203 { 200 {
@@ -867,99 +864,3 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
867 _ => default.into(), 864 _ => default.into(),
868 } 865 }
869} 866}
870
871struct MacroMatcherParseState {
872 /// Opening and corresponding closing bracket of the matcher or expander of the current rule
873 paren_ty: Option<(SyntaxKind, SyntaxKind)>,
874 paren_level: usize,
875 rule_state: RuleState,
876 /// Whether we are inside the outer `{` `}` macro block that holds the rules
877 in_invoc_body: bool,
878}
879
880impl MacroMatcherParseState {
881 fn new() -> Self {
882 MacroMatcherParseState {
883 paren_ty: None,
884 paren_level: 0,
885 in_invoc_body: false,
886 rule_state: RuleState::None,
887 }
888 }
889}
890
891#[derive(Copy, Clone, PartialEq)]
892enum RuleState {
893 Matcher,
894 Expander,
895 Between,
896 None,
897}
898
899impl RuleState {
900 fn transition(&mut self) {
901 *self = match self {
902 RuleState::Matcher => RuleState::Between,
903 RuleState::Expander => RuleState::None,
904 RuleState::Between => RuleState::Expander,
905 RuleState::None => RuleState::Matcher,
906 };
907 }
908}
909
910fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState {
911 if !state.in_invoc_body {
912 if tok.kind() == T!['{'] {
913 state.in_invoc_body = true;
914 }
915 return state.rule_state;
916 }
917
918 match state.paren_ty {
919 Some((open, close)) => {
920 if tok.kind() == open {
921 state.paren_level += 1;
922 } else if tok.kind() == close {
923 state.paren_level -= 1;
924 if state.paren_level == 0 {
925 let res = state.rule_state;
926 state.rule_state.transition();
927 state.paren_ty = None;
928 return res;
929 }
930 }
931 }
932 None => {
933 match tok.kind() {
934 T!['('] => {
935 state.paren_ty = Some((T!['('], T![')']));
936 }
937 T!['{'] => {
938 state.paren_ty = Some((T!['{'], T!['}']));
939 }
940 T!['['] => {
941 state.paren_ty = Some((T!['['], T![']']));
942 }
943 _ => (),
944 }
945 if state.paren_ty.is_some() {
946 state.paren_level = 1;
947 state.rule_state.transition();
948 }
949 }
950 }
951 state.rule_state
952}
953
954fn skip_metavariables(element: SyntaxElement) -> bool {
955 let tok = match element.as_token() {
956 Some(tok) => tok,
957 None => return false,
958 };
959 let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]);
960 match tok.kind() {
961 IDENT if is_fragment() => true,
962 kind if kind.is_keyword() && is_fragment() => true,
963 _ => false,
964 }
965}
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_rules.rs
new file mode 100644
index 000000000..0676e0972
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/macro_rules.rs
@@ -0,0 +1,136 @@
1//! Syntax highlighting for macro_rules!.
2use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T};
3
4use crate::{HighlightTag, HighlightedRange};
5
6pub(super) struct MacroRulesHighlighter {
7 state: Option<MacroMatcherParseState>,
8}
9
10impl MacroRulesHighlighter {
11 pub(super) fn new() -> Self {
12 MacroRulesHighlighter { state: None }
13 }
14
15 pub(super) fn init(&mut self) {
16 self.state = Some(MacroMatcherParseState::new());
17 }
18
19 pub(super) fn reset(&mut self) {
20 self.state = None;
21 }
22
23 pub(super) fn advance(&mut self, token: &SyntaxToken) {
24 if let Some(state) = self.state.as_mut() {
25 update_macro_rules_state(state, token);
26 }
27 }
28
29 pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HighlightedRange> {
30 if let Some(state) = self.state.as_ref() {
31 if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) {
32 if let Some(range) = is_metavariable(element) {
33 return Some(HighlightedRange {
34 range,
35 highlight: HighlightTag::UnresolvedReference.into(),
36 binding_hash: None,
37 });
38 }
39 }
40 }
41 None
42 }
43}
44
45struct MacroMatcherParseState {
46 /// Opening and corresponding closing bracket of the matcher or expander of the current rule
47 paren_ty: Option<(SyntaxKind, SyntaxKind)>,
48 paren_level: usize,
49 rule_state: RuleState,
50 /// Whether we are inside the outer `{` `}` macro block that holds the rules
51 in_invoc_body: bool,
52}
53
54impl MacroMatcherParseState {
55 fn new() -> Self {
56 MacroMatcherParseState {
57 paren_ty: None,
58 paren_level: 0,
59 in_invoc_body: false,
60 rule_state: RuleState::None,
61 }
62 }
63}
64
65#[derive(Copy, Clone, Debug, PartialEq)]
66enum RuleState {
67 Matcher,
68 Expander,
69 Between,
70 None,
71}
72
73impl RuleState {
74 fn transition(&mut self) {
75 *self = match self {
76 RuleState::Matcher => RuleState::Between,
77 RuleState::Expander => RuleState::None,
78 RuleState::Between => RuleState::Expander,
79 RuleState::None => RuleState::Matcher,
80 };
81 }
82}
83
84fn update_macro_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
85 if !state.in_invoc_body {
86 if tok.kind() == T!['{'] {
87 state.in_invoc_body = true;
88 }
89 return;
90 }
91
92 match state.paren_ty {
93 Some((open, close)) => {
94 if tok.kind() == open {
95 state.paren_level += 1;
96 } else if tok.kind() == close {
97 state.paren_level -= 1;
98 if state.paren_level == 0 {
99 state.rule_state.transition();
100 state.paren_ty = None;
101 }
102 }
103 }
104 None => {
105 match tok.kind() {
106 T!['('] => {
107 state.paren_ty = Some((T!['('], T![')']));
108 }
109 T!['{'] => {
110 state.paren_ty = Some((T!['{'], T!['}']));
111 }
112 T!['['] => {
113 state.paren_ty = Some((T!['['], T![']']));
114 }
115 _ => (),
116 }
117 if state.paren_ty.is_some() {
118 state.paren_level = 1;
119 state.rule_state.transition();
120 }
121 }
122 }
123}
124
125fn is_metavariable(element: SyntaxElement) -> Option<TextRange> {
126 let tok = element.as_token()?;
127 match tok.kind() {
128 kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {
129 if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == SyntaxKind::DOLLAR) {
130 return Some(tok.text_range());
131 }
132 }
133 _ => (),
134 };
135 None
136}