diff options
author | Lukas Wirth <[email protected]> | 2020-10-14 18:23:59 +0100 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2020-10-14 18:23:59 +0100 |
commit | 8c6dc5f28a5550acffbbb063335833304dac266d (patch) | |
tree | 6b5966bf5bff6436a47741ce25ace87812794eed /crates/ide/src/syntax_highlighting | |
parent | df87be88d8500c8955f882d71467e01a7d4db9ab (diff) |
Factor macro_rules! highlighting out
Diffstat (limited to 'crates/ide/src/syntax_highlighting')
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_rules.rs | 136 |
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!. | ||
2 | use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T}; | ||
3 | |||
4 | use crate::{HighlightTag, HighlightedRange}; | ||
5 | |||
6 | pub(super) struct MacroRulesHighlighter { | ||
7 | state: Option<MacroMatcherParseState>, | ||
8 | } | ||
9 | |||
10 | impl 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 | |||
45 | struct 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 | |||
54 | impl 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)] | ||
66 | enum RuleState { | ||
67 | Matcher, | ||
68 | Expander, | ||
69 | Between, | ||
70 | None, | ||
71 | } | ||
72 | |||
73 | impl 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 | |||
84 | fn 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 | |||
125 | fn 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 | } | ||