aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/syntax_highlighting/macro_rules.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/syntax_highlighting/macro_rules.rs')
-rw-r--r--crates/ide/src/syntax_highlighting/macro_rules.rs136
1 files changed, 136 insertions, 0 deletions
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}