1111 limitations under the License.
1212*/
1313
14+ use crate :: models:: matches:: Point ;
1415use crate :: models:: matches:: Range ;
1516
1617use regex:: Regex ;
@@ -22,10 +23,22 @@ use tree_sitter_traversal::Cursor;
2223use crate :: models:: capture_group_patterns:: ConcreteSyntax ;
2324use crate :: models:: matches:: Match ;
2425
26+ /// Represents the different matching modes for template variables
27+ #[ derive( Clone , Copy , PartialEq , Eq ) ]
28+ enum MatchMode {
29+ /// Match exactly one node :[var]
30+ Single ,
31+ /// Match one or more nodes :[var+]
32+ OnePlus ,
33+ /// Match zero or more nodes :[var*]
34+ ZeroPlus ,
35+ }
36+
2537// Precompile the regex outside the function
2638lazy_static ! {
2739 static ref RE_VAR : Regex = Regex :: new( r"^:\[(?P<var_name>\w+)\]" ) . unwrap( ) ;
2840 static ref RE_VAR_PLUS : Regex = Regex :: new( r"^:\[(?P<var_name>\w+)\+\]" ) . unwrap( ) ;
41+ static ref RE_VAR_ASTERISK : Regex = Regex :: new( r"^:\[(?P<var_name>\w+)\*\]" ) . unwrap( ) ;
2942}
3043
3144// Struct to avoid dealing with lifetimes
@@ -200,10 +213,34 @@ pub(crate) fn get_matches_for_subsequence_of_nodes(
200213
201214 if let Some ( caps) = RE_VAR_PLUS . captures ( match_template) {
202215 // If template starts with a template variable
203- handle_template_variable_matching ( cursor, source_code, top_node, caps, match_template, true )
216+ handle_template_variable_matching (
217+ cursor,
218+ source_code,
219+ top_node,
220+ caps,
221+ match_template,
222+ MatchMode :: OnePlus ,
223+ )
204224 } else if let Some ( caps) = RE_VAR . captures ( match_template) {
205225 // If template starts with a template variable
206- handle_template_variable_matching ( cursor, source_code, top_node, caps, match_template, false )
226+ handle_template_variable_matching (
227+ cursor,
228+ source_code,
229+ top_node,
230+ caps,
231+ match_template,
232+ MatchMode :: Single ,
233+ )
234+ } else if let Some ( caps) = RE_VAR_ASTERISK . captures ( match_template) {
235+ // If template starts with a template variable with asterisk (zero or more)
236+ handle_template_variable_matching (
237+ cursor,
238+ source_code,
239+ top_node,
240+ caps,
241+ match_template,
242+ MatchMode :: ZeroPlus ,
243+ )
207244 } else if node. child_count ( ) == 0 {
208245 // If the current node if a leaf
209246 return handle_leaf_node ( cursor, source_code, match_template, top_node) ;
@@ -214,17 +251,18 @@ pub(crate) fn get_matches_for_subsequence_of_nodes(
214251 }
215252}
216253
217- /// This function does the template variable matching against entire tree nodes.function
218- /// Keep in my mind that it will only attempt to match the template variables against nodes
219- /// at either the current level of the traversal, or it's children. It can also operate on
220- /// single node templates [args], and multiple nodes templates :[args+].
221-
222- /// For successful matches, it returns the assignment of each template varaible against a
223- /// particular range. The Option<usize> indicates whether a match was succesfull, and keeps
254+ /// This function does the template variable matching against entire tree nodes.
255+ /// It handles different matching modes:
256+ /// - Single: Match exactly one node :[var]
257+ /// - OnePlus: Match one or more nodes :[var+]
258+ /// - ZeroPlus: Match zero or more nodes :[var*]
259+ ///
260+ /// For successful matches, it returns the assignment of each template variable against a
261+ /// particular range. The Option<usize> indicates whether a match was successful, and keeps
224262/// track of the last sibling node that was matched (wrt to the match_sequential_siblings function)
225263fn handle_template_variable_matching (
226264 cursor : & mut TreeCursor , source_code : & [ u8 ] , top_node : & Node , caps : regex:: Captures ,
227- match_template : & str , one_plus : bool ,
265+ match_template : & str , mode : MatchMode ,
228266) -> ( HashMap < String , CapturedNode > , Option < usize > ) {
229267 let var_name = & caps[ "var_name" ] ;
230268 let cs_adv_len = caps[ 0 ] . len ( ) ;
@@ -235,6 +273,35 @@ fn handle_template_variable_matching(
235273 . to_string ( ) ,
236274 ) ;
237275
276+ // For zero_plus patterns, first try to match with zero nodes
277+ if mode == MatchMode :: ZeroPlus {
278+ let mut tmp_cursor = cursor. clone ( ) ;
279+ if let ( mut recursive_matches, Some ( last_matched_node_idx) ) =
280+ get_matches_for_subsequence_of_nodes (
281+ & mut tmp_cursor,
282+ source_code,
283+ & cs_advanced,
284+ true , // nodes_left_to_match
285+ top_node,
286+ )
287+ {
288+ // Successfully matched with zero nodes
289+ recursive_matches. insert (
290+ var_name. to_string ( ) ,
291+ CapturedNode {
292+ range : Range {
293+ start_byte : 0 ,
294+ end_byte : 0 ,
295+ start_point : Point { row : 0 , column : 0 } ,
296+ end_point : Point { row : 0 , column : 0 } ,
297+ } ,
298+ text : String :: new ( ) ,
299+ } ,
300+ ) ;
301+ return ( recursive_matches, Some ( last_matched_node_idx) ) ;
302+ }
303+ }
304+
238305 // Matching :[var] against a sequence of nodes [first_node, ... last_node]
239306 loop {
240307 let first_node = cursor. node ( ) ;
@@ -303,7 +370,7 @@ fn handle_template_variable_matching(
303370 should_match = find_next_sibling_or_ancestor_sibling ( & mut next_node_cursor) ;
304371 }
305372
306- if !one_plus {
373+ if mode == MatchMode :: Single {
307374 break ;
308375 }
309376 }
0 commit comments