diff options
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 214 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/format.rs | 78 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_rules.rs | 129 |
3 files changed, 231 insertions, 190 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 6aafd6fd5..527888306 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | mod tags; | 1 | mod format; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | 3 | mod injection; |
4 | mod macro_rules; | ||
5 | mod tags; | ||
4 | #[cfg(test)] | 6 | #[cfg(test)] |
5 | mod tests; | 7 | mod tests; |
6 | 8 | ||
@@ -17,9 +19,11 @@ use syntax::{ | |||
17 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 19 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, |
18 | }; | 20 | }; |
19 | 21 | ||
20 | use crate::FileId; | 22 | use crate::{ |
23 | syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter}, | ||
24 | FileId, | ||
25 | }; | ||
21 | 26 | ||
22 | use ast::FormatSpecifier; | ||
23 | pub(crate) use html::highlight_as_html; | 27 | pub(crate) use html::highlight_as_html; |
24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 28 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
25 | 29 | ||
@@ -68,8 +72,9 @@ pub(crate) fn highlight( | |||
68 | // When we leave a node, the we use it to flatten the highlighted ranges. | 72 | // When we leave a node, the we use it to flatten the highlighted ranges. |
69 | let mut stack = HighlightedRangeStack::new(); | 73 | let mut stack = HighlightedRangeStack::new(); |
70 | 74 | ||
71 | let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; | 75 | let mut current_macro_call: Option<ast::MacroCall> = None; |
72 | let mut format_string: Option<SyntaxElement> = None; | 76 | let mut format_string_highlighter = FormatStringHighlighter::default(); |
77 | let mut macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
73 | 78 | ||
74 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 79 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
75 | // If in macro, expand it first and highlight the expanded code. | 80 | // If in macro, expand it first and highlight the expanded code. |
@@ -99,9 +104,8 @@ pub(crate) fn highlight( | |||
99 | binding_hash: None, | 104 | binding_hash: None, |
100 | }); | 105 | }); |
101 | } | 106 | } |
102 | let mut is_macro_rules = None; | ||
103 | if let Some(name) = mc.is_macro_rules() { | 107 | if let Some(name) = mc.is_macro_rules() { |
104 | is_macro_rules = Some(MacroMatcherParseState::new()); | 108 | macro_rules_highlighter.init(); |
105 | if let Some((highlight, binding_hash)) = highlight_element( | 109 | if let Some((highlight, binding_hash)) = highlight_element( |
106 | &sema, | 110 | &sema, |
107 | &mut bindings_shadow_count, | 111 | &mut bindings_shadow_count, |
@@ -115,13 +119,14 @@ pub(crate) fn highlight( | |||
115 | }); | 119 | }); |
116 | } | 120 | } |
117 | } | 121 | } |
118 | current_macro_call = Some((mc.clone(), is_macro_rules)); | 122 | current_macro_call = Some(mc.clone()); |
119 | continue; | 123 | continue; |
120 | } | 124 | } |
121 | WalkEvent::Leave(Some(mc)) => { | 125 | WalkEvent::Leave(Some(mc)) => { |
122 | assert!(current_macro_call.map(|it| it.0) == Some(mc)); | 126 | assert!(current_macro_call == Some(mc)); |
123 | current_macro_call = None; | 127 | current_macro_call = None; |
124 | format_string = None; | 128 | format_string_highlighter = FormatStringHighlighter::default(); |
129 | macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
125 | } | 130 | } |
126 | _ => (), | 131 | _ => (), |
127 | } | 132 | } |
@@ -148,20 +153,6 @@ pub(crate) fn highlight( | |||
148 | WalkEvent::Leave(_) => continue, | 153 | WalkEvent::Leave(_) => continue, |
149 | }; | 154 | }; |
150 | 155 | ||
151 | // check if in matcher part of a macro_rules rule | ||
152 | if let Some((_, Some(ref mut state))) = current_macro_call { | ||
153 | if let Some(tok) = element.as_token() { | ||
154 | if matches!( | ||
155 | update_macro_rules_state(tok, state), | ||
156 | RuleState::Matcher | RuleState::Expander | ||
157 | ) { | ||
158 | if skip_metavariables(element.clone()) { | ||
159 | continue; | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | |||
165 | let range = element.text_range(); | 156 | let range = element.text_range(); |
166 | 157 | ||
167 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { | 158 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { |
@@ -173,29 +164,9 @@ pub(crate) fn highlight( | |||
173 | let token = sema.descend_into_macros(token.clone()); | 164 | let token = sema.descend_into_macros(token.clone()); |
174 | let parent = token.parent(); | 165 | let parent = token.parent(); |
175 | 166 | ||
176 | // Check if macro takes a format string and remember it for highlighting later. | 167 | format_string_highlighter.check_for_format_string(&parent); |
177 | // The macros that accept a format string expand to a compiler builtin macros | 168 | if let Some(tok) = element.as_token() { |
178 | // `format_args` and `format_args_nl`. | 169 | macro_rules_highlighter.advance(tok); |
179 | if let Some(name) = parent | ||
180 | .parent() | ||
181 | .and_then(ast::MacroCall::cast) | ||
182 | .and_then(|mc| mc.path()) | ||
183 | .and_then(|p| p.segment()) | ||
184 | .and_then(|s| s.name_ref()) | ||
185 | { | ||
186 | match name.text().as_str() { | ||
187 | "format_args" | "format_args_nl" => { | ||
188 | format_string = parent | ||
189 | .children_with_tokens() | ||
190 | .filter(|t| t.kind() != WHITESPACE) | ||
191 | .nth(1) | ||
192 | .filter(|e| { | ||
193 | ast::String::can_cast(e.kind()) | ||
194 | || ast::RawString::can_cast(e.kind()) | ||
195 | }) | ||
196 | } | ||
197 | _ => {} | ||
198 | } | ||
199 | } | 170 | } |
200 | 171 | ||
201 | // We only care Name and Name_ref | 172 | // We only care Name and Name_ref |
@@ -214,31 +185,20 @@ pub(crate) fn highlight( | |||
214 | } | 185 | } |
215 | } | 186 | } |
216 | 187 | ||
217 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | ||
218 | |||
219 | if let Some((highlight, binding_hash)) = highlight_element( | 188 | if let Some((highlight, binding_hash)) = highlight_element( |
220 | &sema, | 189 | &sema, |
221 | &mut bindings_shadow_count, | 190 | &mut bindings_shadow_count, |
222 | syntactic_name_ref_highlighting, | 191 | syntactic_name_ref_highlighting, |
223 | element_to_highlight.clone(), | 192 | element_to_highlight.clone(), |
224 | ) { | 193 | ) { |
225 | stack.add(HighlightedRange { range, highlight, binding_hash }); | 194 | if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() { |
195 | stack.add(HighlightedRange { range, highlight, binding_hash }); | ||
196 | } | ||
197 | |||
226 | if let Some(string) = | 198 | if let Some(string) = |
227 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 199 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
228 | { | 200 | { |
229 | if is_format_string { | 201 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
230 | stack.push(); | ||
231 | string.lex_format_specifier(|piece_range, kind| { | ||
232 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
233 | stack.add(HighlightedRange { | ||
234 | range: piece_range + range.start(), | ||
235 | highlight: highlight.into(), | ||
236 | binding_hash: None, | ||
237 | }); | ||
238 | } | ||
239 | }); | ||
240 | stack.pop(); | ||
241 | } | ||
242 | // Highlight escape sequences | 202 | // Highlight escape sequences |
243 | if let Some(char_ranges) = string.char_ranges() { | 203 | if let Some(char_ranges) = string.char_ranges() { |
244 | stack.push(); | 204 | stack.push(); |
@@ -256,19 +216,7 @@ pub(crate) fn highlight( | |||
256 | } else if let Some(string) = | 216 | } else if let Some(string) = |
257 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | 217 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) |
258 | { | 218 | { |
259 | if is_format_string { | 219 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
260 | stack.push(); | ||
261 | string.lex_format_specifier(|piece_range, kind| { | ||
262 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
263 | stack.add(HighlightedRange { | ||
264 | range: piece_range + range.start(), | ||
265 | highlight: highlight.into(), | ||
266 | binding_hash: None, | ||
267 | }); | ||
268 | } | ||
269 | }); | ||
270 | stack.pop(); | ||
271 | } | ||
272 | } | 220 | } |
273 | } | 221 | } |
274 | } | 222 | } |
@@ -436,24 +384,6 @@ impl HighlightedRangeStack { | |||
436 | } | 384 | } |
437 | } | 385 | } |
438 | 386 | ||
439 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
440 | Some(match kind { | ||
441 | FormatSpecifier::Open | ||
442 | | FormatSpecifier::Close | ||
443 | | FormatSpecifier::Colon | ||
444 | | FormatSpecifier::Fill | ||
445 | | FormatSpecifier::Align | ||
446 | | FormatSpecifier::Sign | ||
447 | | FormatSpecifier::NumberSign | ||
448 | | FormatSpecifier::DollarSign | ||
449 | | FormatSpecifier::Dot | ||
450 | | FormatSpecifier::Asterisk | ||
451 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
452 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
453 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
454 | }) | ||
455 | } | ||
456 | |||
457 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 387 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
458 | let path = macro_call.path()?; | 388 | let path = macro_call.path()?; |
459 | let name_ref = path.segment()?.name_ref()?; | 389 | let name_ref = path.segment()?.name_ref()?; |
@@ -934,99 +864,3 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas | |||
934 | _ => default.into(), | 864 | _ => default.into(), |
935 | } | 865 | } |
936 | } | 866 | } |
937 | |||
938 | struct MacroMatcherParseState { | ||
939 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
940 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
941 | paren_level: usize, | ||
942 | rule_state: RuleState, | ||
943 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
944 | in_invoc_body: bool, | ||
945 | } | ||
946 | |||
947 | impl MacroMatcherParseState { | ||
948 | fn new() -> Self { | ||
949 | MacroMatcherParseState { | ||
950 | paren_ty: None, | ||
951 | paren_level: 0, | ||
952 | in_invoc_body: false, | ||
953 | rule_state: RuleState::None, | ||
954 | } | ||
955 | } | ||
956 | } | ||
957 | |||
958 | #[derive(Copy, Clone, PartialEq)] | ||
959 | enum RuleState { | ||
960 | Matcher, | ||
961 | Expander, | ||
962 | Between, | ||
963 | None, | ||
964 | } | ||
965 | |||
966 | impl RuleState { | ||
967 | fn transition(&mut self) { | ||
968 | *self = match self { | ||
969 | RuleState::Matcher => RuleState::Between, | ||
970 | RuleState::Expander => RuleState::None, | ||
971 | RuleState::Between => RuleState::Expander, | ||
972 | RuleState::None => RuleState::Matcher, | ||
973 | }; | ||
974 | } | ||
975 | } | ||
976 | |||
977 | fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState { | ||
978 | if !state.in_invoc_body { | ||
979 | if tok.kind() == T!['{'] { | ||
980 | state.in_invoc_body = true; | ||
981 | } | ||
982 | return state.rule_state; | ||
983 | } | ||
984 | |||
985 | match state.paren_ty { | ||
986 | Some((open, close)) => { | ||
987 | if tok.kind() == open { | ||
988 | state.paren_level += 1; | ||
989 | } else if tok.kind() == close { | ||
990 | state.paren_level -= 1; | ||
991 | if state.paren_level == 0 { | ||
992 | let res = state.rule_state; | ||
993 | state.rule_state.transition(); | ||
994 | state.paren_ty = None; | ||
995 | return res; | ||
996 | } | ||
997 | } | ||
998 | } | ||
999 | None => { | ||
1000 | match tok.kind() { | ||
1001 | T!['('] => { | ||
1002 | state.paren_ty = Some((T!['('], T![')'])); | ||
1003 | } | ||
1004 | T!['{'] => { | ||
1005 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
1006 | } | ||
1007 | T!['['] => { | ||
1008 | state.paren_ty = Some((T!['['], T![']'])); | ||
1009 | } | ||
1010 | _ => (), | ||
1011 | } | ||
1012 | if state.paren_ty.is_some() { | ||
1013 | state.paren_level = 1; | ||
1014 | state.rule_state.transition(); | ||
1015 | } | ||
1016 | } | ||
1017 | } | ||
1018 | state.rule_state | ||
1019 | } | ||
1020 | |||
1021 | fn skip_metavariables(element: SyntaxElement) -> bool { | ||
1022 | let tok = match element.as_token() { | ||
1023 | Some(tok) => tok, | ||
1024 | None => return false, | ||
1025 | }; | ||
1026 | let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]); | ||
1027 | match tok.kind() { | ||
1028 | IDENT if is_fragment() => true, | ||
1029 | kind if kind.is_keyword() && is_fragment() => true, | ||
1030 | _ => false, | ||
1031 | } | ||
1032 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs new file mode 100644 index 000000000..71bde24f0 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/format.rs | |||
@@ -0,0 +1,78 @@ | |||
1 | //! Syntax highlighting for format macro strings. | ||
2 | use syntax::{ | ||
3 | ast::{self, FormatSpecifier, HasFormatSpecifier}, | ||
4 | AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | ||
5 | }; | ||
6 | |||
7 | use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange}; | ||
8 | |||
9 | #[derive(Default)] | ||
10 | pub(super) struct FormatStringHighlighter { | ||
11 | format_string: Option<SyntaxElement>, | ||
12 | } | ||
13 | |||
14 | impl FormatStringHighlighter { | ||
15 | pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { | ||
16 | // Check if macro takes a format string and remember it for highlighting later. | ||
17 | // The macros that accept a format string expand to a compiler builtin macros | ||
18 | // `format_args` and `format_args_nl`. | ||
19 | if let Some(name) = parent | ||
20 | .parent() | ||
21 | .and_then(ast::MacroCall::cast) | ||
22 | .and_then(|mc| mc.path()) | ||
23 | .and_then(|p| p.segment()) | ||
24 | .and_then(|s| s.name_ref()) | ||
25 | { | ||
26 | match name.text().as_str() { | ||
27 | "format_args" | "format_args_nl" => { | ||
28 | self.format_string = parent | ||
29 | .children_with_tokens() | ||
30 | .filter(|t| t.kind() != SyntaxKind::WHITESPACE) | ||
31 | .nth(1) | ||
32 | .filter(|e| { | ||
33 | ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind()) | ||
34 | }) | ||
35 | } | ||
36 | _ => {} | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | pub(super) fn highlight_format_string( | ||
41 | &self, | ||
42 | range_stack: &mut HighlightedRangeStack, | ||
43 | string: &impl HasFormatSpecifier, | ||
44 | range: TextRange, | ||
45 | ) { | ||
46 | if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { | ||
47 | range_stack.push(); | ||
48 | string.lex_format_specifier(|piece_range, kind| { | ||
49 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
50 | range_stack.add(HighlightedRange { | ||
51 | range: piece_range + range.start(), | ||
52 | highlight: highlight.into(), | ||
53 | binding_hash: None, | ||
54 | }); | ||
55 | } | ||
56 | }); | ||
57 | range_stack.pop(); | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
63 | Some(match kind { | ||
64 | FormatSpecifier::Open | ||
65 | | FormatSpecifier::Close | ||
66 | | FormatSpecifier::Colon | ||
67 | | FormatSpecifier::Fill | ||
68 | | FormatSpecifier::Align | ||
69 | | FormatSpecifier::Sign | ||
70 | | FormatSpecifier::NumberSign | ||
71 | | FormatSpecifier::DollarSign | ||
72 | | FormatSpecifier::Dot | ||
73 | | FormatSpecifier::Asterisk | ||
74 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
75 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
76 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
77 | }) | ||
78 | } | ||
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..4462af47e --- /dev/null +++ b/crates/ide/src/syntax_highlighting/macro_rules.rs | |||
@@ -0,0 +1,129 @@ | |||
1 | //! Syntax highlighting for macro_rules!. | ||
2 | use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T}; | ||
3 | |||
4 | use crate::{HighlightTag, HighlightedRange}; | ||
5 | |||
6 | #[derive(Default)] | ||
7 | pub(super) struct MacroRulesHighlighter { | ||
8 | state: Option<MacroMatcherParseState>, | ||
9 | } | ||
10 | |||
11 | impl MacroRulesHighlighter { | ||
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_rules_state(state, token); | ||
19 | } | ||
20 | } | ||
21 | |||
22 | pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HighlightedRange> { | ||
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(HighlightedRange { | ||
27 | range, | ||
28 | highlight: HighlightTag::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_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) { | ||
78 | if !state.in_invoc_body { | ||
79 | if 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() == SyntaxKind::DOLLAR) { | ||
123 | return Some(tok.text_range()); | ||
124 | } | ||
125 | } | ||
126 | _ => (), | ||
127 | }; | ||
128 | None | ||
129 | } | ||