]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/syntax_highlighting/macro_.rs
Code blocks with tilde also works like code block
[rust.git] / crates / ide / src / syntax_highlighting / macro_.rs
1 //! Syntax highlighting for macro_rules!.
2 use syntax::{SyntaxKind, SyntaxToken, TextRange, T};
3
4 use crate::{HlRange, HlTag};
5
6 #[derive(Default)]
7 pub(super) struct MacroHighlighter {
8     state: Option<MacroMatcherParseState>,
9 }
10
11 impl MacroHighlighter {
12     pub(super) fn init(&mut self) {
13         self.state = Some(MacroMatcherParseState::default());
14     }
15
16     pub(super) fn advance(&mut self, token: &SyntaxToken) {
17         if let Some(state) = self.state.as_mut() {
18             update_macro_state(state, token);
19         }
20     }
21
22     pub(super) fn highlight(&self, token: &SyntaxToken) -> Option<HlRange> {
23         if let Some(state) = self.state.as_ref() {
24             if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) {
25                 if let Some(range) = is_metavariable(token) {
26                     return Some(HlRange {
27                         range,
28                         highlight: HlTag::UnresolvedReference.into(),
29                         binding_hash: None,
30                     });
31                 }
32             }
33         }
34         None
35     }
36 }
37
38 struct MacroMatcherParseState {
39     /// Opening and corresponding closing bracket of the matcher or expander of the current rule
40     paren_ty: Option<(SyntaxKind, SyntaxKind)>,
41     paren_level: usize,
42     rule_state: RuleState,
43     /// Whether we are inside the outer `{` `}` macro block that holds the rules
44     in_invoc_body: bool,
45 }
46
47 impl Default for MacroMatcherParseState {
48     fn default() -> Self {
49         MacroMatcherParseState {
50             paren_ty: None,
51             paren_level: 0,
52             in_invoc_body: false,
53             rule_state: RuleState::None,
54         }
55     }
56 }
57
58 #[derive(Copy, Clone, Debug, PartialEq)]
59 enum RuleState {
60     Matcher,
61     Expander,
62     Between,
63     None,
64 }
65
66 impl RuleState {
67     fn transition(&mut self) {
68         *self = match self {
69             RuleState::Matcher => RuleState::Between,
70             RuleState::Expander => RuleState::None,
71             RuleState::Between => RuleState::Expander,
72             RuleState::None => RuleState::Matcher,
73         };
74     }
75 }
76
77 fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
78     if !state.in_invoc_body {
79         if tok.kind() == T!['{'] || tok.kind() == T!['('] {
80             state.in_invoc_body = true;
81         }
82         return;
83     }
84
85     match state.paren_ty {
86         Some((open, close)) => {
87             if tok.kind() == open {
88                 state.paren_level += 1;
89             } else if tok.kind() == close {
90                 state.paren_level -= 1;
91                 if state.paren_level == 0 {
92                     state.rule_state.transition();
93                     state.paren_ty = None;
94                 }
95             }
96         }
97         None => {
98             match tok.kind() {
99                 T!['('] => {
100                     state.paren_ty = Some((T!['('], T![')']));
101                 }
102                 T!['{'] => {
103                     state.paren_ty = Some((T!['{'], T!['}']));
104                 }
105                 T!['['] => {
106                     state.paren_ty = Some((T!['['], T![']']));
107                 }
108                 _ => (),
109             }
110             if state.paren_ty.is_some() {
111                 state.paren_level = 1;
112                 state.rule_state.transition();
113             }
114         }
115     }
116 }
117
118 fn is_metavariable(token: &SyntaxToken) -> Option<TextRange> {
119     match token.kind() {
120         kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {
121             if let Some(_dollar) = token.prev_token().filter(|t| t.kind() == T![$]) {
122                 return Some(token.text_range());
123             }
124         }
125         _ => (),
126     };
127     None
128 }