diff options
Diffstat (limited to 'crates/ide/src/syntax_highlighting/macro_.rs')
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/crates/ide/src/syntax_highlighting/macro_.rs b/crates/ide/src/syntax_highlighting/macro_.rs new file mode 100644 index 000000000..819704294 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/macro_.rs | |||
@@ -0,0 +1,129 @@ | |||
1 | //! Syntax highlighting for macro_rules!. | ||
2 | use syntax::{SyntaxElement, 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, element: SyntaxElement) -> 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(element) { | ||
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(element: SyntaxElement) -> Option<TextRange> { | ||
119 | let tok = element.as_token()?; | ||
120 | match tok.kind() { | ||
121 | kind if kind == SyntaxKind::IDENT || kind.is_keyword() => { | ||
122 | if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == T![$]) { | ||
123 | return Some(tok.text_range()); | ||
124 | } | ||
125 | } | ||
126 | _ => (), | ||
127 | }; | ||
128 | None | ||
129 | } | ||