in crates/concrete-syntax/src/models/concrete_syntax/interpreter.rs [359:435]
fn try_match_node_range(
ctx: &mut MatchingContext<'_>, var_name: &str, constraints: &[CsConstraint],
remaining_pattern: &[ResolvedCsElement], allow_horizontal_expansion: bool,
) -> PatternMatchResult {
// 1. Initial anchors
let range_start = ctx.cursor.node();
let mut range_end = range_start.clone();
let mut next_cursor = ctx.cursor.clone();
// 'should_match' is used to check whether there are siblings or ancestor siblings we need to match
// after we assign the current range to the capture node
let mut should_match = CursorNavigator::find_next_sibling_or_ancestor_sibling(&mut next_cursor);
let mut is_last = false;
// Helper to slice out the captured text
let make_capture = |end: &Node| {
let node_type = if range_start.range() == end.range() {
range_start.kind().to_string() // Single node - use actual type
} else {
"multi_node".to_string() // Multiple nodes - use special type
};
CapturedNode {
range: Range::span_ranges(range_start.range(), end.range()),
text: CursorNavigator::get_text_from_range(
range_start.range().start_byte,
end.range().end_byte,
ctx.source_code,
),
node_type,
}
};
loop {
// --- 1) try current [start...end] slice ---
let captured = make_capture(&range_end);
let constraint_context = ConstraintContext {
captured_node: &captured,
source_code: ctx.source_code,
ast_root: &ctx.cursor.node(),
};
if satisfies_constraints(&captured, constraints, Some(&constraint_context)) {
let mut sub_ctx = MatchingContext {
cursor: next_cursor.clone(),
source_code: ctx.source_code,
top_node: ctx.top_node,
};
if let PatternMatchResult::Success {
mut captures,
consumed_nodes,
range: _,
} = match_cs_pattern(&mut sub_ctx, remaining_pattern, should_match)
{
// conflict check
if let Some(prev) = captures.get(var_name) {
if prev.text.trim() != captured.text.trim() {
return PatternMatchResult::failed();
}
}
captures.insert(var_name.to_owned(), captured);
return PatternMatchResult::success(captures, consumed_nodes);
}
}
// --- 2) need to expand slice? ---
if !allow_horizontal_expansion || is_last {
return PatternMatchResult::failed();
}
// grow the range to include the node under next_cursor
range_end = next_cursor.node();
is_last = !next_cursor.goto_next_sibling();
if is_last {
// once we're at the end of the range, check if there is any ancestor left to match
should_match = CursorNavigator::find_next_sibling_or_ancestor_sibling(&mut next_cursor);
}
}
}