@@ -68,7 +68,7 @@ pub(crate) fn highlight(
68
68
// When we leave a node, the we use it to flatten the highlighted ranges.
69
69
let mut stack = HighlightedRangeStack :: new ( ) ;
70
70
71
- let mut current_macro_call: Option < ast:: MacroCall > = None ;
71
+ let mut current_macro_call: Option < ( ast:: MacroCall , Option < MacroMatcherParseState > ) > = None ;
72
72
let mut format_string: Option < SyntaxElement > = None ;
73
73
74
74
// Walk all nodes, keeping track of whether we are inside a macro or not.
@@ -92,15 +92,16 @@ pub(crate) fn highlight(
92
92
// Track "inside macro" state
93
93
match event. clone ( ) . map ( |it| it. into_node ( ) . and_then ( ast:: MacroCall :: cast) ) {
94
94
WalkEvent :: Enter ( Some ( mc) ) => {
95
- current_macro_call = Some ( mc. clone ( ) ) ;
96
95
if let Some ( range) = macro_call_range ( & mc) {
97
96
stack. add ( HighlightedRange {
98
97
range,
99
98
highlight : HighlightTag :: Macro . into ( ) ,
100
99
binding_hash : None ,
101
100
} ) ;
102
101
}
102
+ let mut is_macro_rules = None ;
103
103
if let Some ( name) = mc. is_macro_rules ( ) {
104
+ is_macro_rules = Some ( MacroMatcherParseState :: new ( ) ) ;
104
105
if let Some ( ( highlight, binding_hash) ) = highlight_element (
105
106
& sema,
106
107
& mut bindings_shadow_count,
@@ -114,10 +115,11 @@ pub(crate) fn highlight(
114
115
} ) ;
115
116
}
116
117
}
118
+ current_macro_call = Some ( ( mc. clone ( ) , is_macro_rules) ) ;
117
119
continue ;
118
120
}
119
121
WalkEvent :: Leave ( Some ( mc) ) => {
120
- assert ! ( current_macro_call == Some ( mc) ) ;
122
+ assert ! ( current_macro_call. map ( |it| it . 0 ) == Some ( mc) ) ;
121
123
current_macro_call = None ;
122
124
format_string = None ;
123
125
}
@@ -146,6 +148,20 @@ pub(crate) fn highlight(
146
148
WalkEvent :: Leave ( _) => continue ,
147
149
} ;
148
150
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
+
149
165
let range = element. text_range ( ) ;
150
166
151
167
let element_to_highlight = if current_macro_call. is_some ( ) && element. kind ( ) != COMMENT {
@@ -918,3 +934,99 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
918
934
_ => default. into ( ) ,
919
935
}
920
936
}
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
+ }
0 commit comments