1 //! Syntax highlighting for macro_rules!.
2 use syntax::{SyntaxKind, SyntaxToken, TextRange, T};
4 use crate::{HlRange, HlTag};
7 pub(super) struct MacroHighlighter {
8 state: Option<MacroMatcherParseState>,
11 impl MacroHighlighter {
12 pub(super) fn init(&mut self) {
13 self.state = Some(MacroMatcherParseState::default());
16 pub(super) fn advance(&mut self, token: &SyntaxToken) {
17 if let Some(state) = self.state.as_mut() {
18 update_macro_state(state, token);
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) {
28 highlight: HlTag::UnresolvedReference.into(),
38 struct MacroMatcherParseState {
39 /// Opening and corresponding closing bracket of the matcher or expander of the current rule
40 paren_ty: Option<(SyntaxKind, SyntaxKind)>,
42 rule_state: RuleState,
43 /// Whether we are inside the outer `{` `}` macro block that holds the rules
47 impl Default for MacroMatcherParseState {
48 fn default() -> Self {
49 MacroMatcherParseState {
53 rule_state: RuleState::None,
58 #[derive(Copy, Clone, Debug, PartialEq)]
67 fn transition(&mut self) {
69 RuleState::Matcher => RuleState::Between,
70 RuleState::Expander => RuleState::None,
71 RuleState::Between => RuleState::Expander,
72 RuleState::None => RuleState::Matcher,
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;
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;
100 state.paren_ty = Some((T!['('], T![')']));
103 state.paren_ty = Some((T!['{'], T!['}']));
106 state.paren_ty = Some((T!['['], T![']']));
110 if state.paren_ty.is_some() {
111 state.paren_level = 1;
112 state.rule_state.transition();
118 fn is_metavariable(token: &SyntaxToken) -> Option<TextRange> {
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());