Skip to content

Commit 2302fd2

Browse files
Merge pull request #36 from sruggier/pr/fix-parse-error
Fix parse error described in #35
2 parents 735923a + 6441b77 commit 2302fd2

4 files changed

Lines changed: 306 additions & 68 deletions

File tree

bindings/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ pub struct Range {
4545
pub end_byte: u32,
4646
}
4747

48+
impl Range {
49+
pub fn new(start_point: Point, end_point: Point, start_byte: u32, end_byte: u32) -> Range {
50+
debug_assert!(start_point <= end_point);
51+
debug_assert!(start_byte <= end_byte);
52+
Range {
53+
start_point,
54+
end_point,
55+
start_byte,
56+
end_byte,
57+
}
58+
}
59+
}
60+
4861
pub trait Input {
4962
type Cursor: regex_cursor::Cursor;
5063
fn cursor_at(&mut self, offset: u32) -> &mut Self::Cursor;

highlighter/src/injections_query.rs

Lines changed: 253 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::cmp::Reverse;
2-
use std::iter::{self, Peekable};
2+
use std::iter::{self, from_fn, Peekable};
33
use std::mem::take;
44
use std::sync::Arc;
55

@@ -450,19 +450,19 @@ impl Syntax {
450450
}
451451

452452
let old_len = injections.len();
453-
intersect_ranges(mat.include_children, mat.node, &parent_ranges, |range| {
454-
layer_data.ranges.push(tree_sitter::Range {
455-
start_point: tree_sitter::Point::ZERO,
456-
end_point: tree_sitter::Point::ZERO,
457-
start_byte: range.start,
458-
end_byte: range.end,
459-
});
453+
for range in intersect_ranges(mat.include_children, mat.node, &parent_ranges) {
454+
layer_data.ranges.push(tree_sitter::Range::new(
455+
tree_sitter::Point::ZERO,
456+
tree_sitter::Point::ZERO,
457+
range.start,
458+
range.end,
459+
));
460460
injections.push(Injection {
461461
range,
462462
layer,
463463
matched_node_range: matched_node_range.clone(),
464464
});
465-
});
465+
}
466466
if old_len != insert_position {
467467
let inserted = injections.len() - old_len;
468468
injections[insert_position..].rotate_right(inserted);
@@ -637,79 +637,270 @@ impl Syntax {
637637
}
638638
}
639639

640-
fn intersect_ranges(
640+
fn intersect_ranges<'tree, 'p>(
641641
include_children: IncludedChildren,
642-
node: Node,
643-
parent_ranges: &[tree_sitter::Range],
644-
push_range: impl FnMut(Range),
645-
) {
642+
node: Node<'tree>,
643+
parent_ranges: &'p [tree_sitter::Range],
644+
) -> impl Iterator<Item = Range> + use<'tree, 'p> {
646645
let range = node.byte_range();
647646
let i = parent_ranges.partition_point(|parent_range| parent_range.end_byte <= range.start);
648647
let parent_ranges = parent_ranges[i..]
649648
.iter()
650649
.map(|range| range.start_byte..range.end_byte);
651-
match include_children {
652-
IncludedChildren::None => intersect_ranges_impl(
653-
range,
654-
node.children().map(|node| node.byte_range()),
655-
parent_ranges,
656-
push_range,
657-
),
658-
IncludedChildren::All => {
659-
intersect_ranges_impl(range, [].into_iter(), parent_ranges, push_range)
650+
let excluded_ranges = node
651+
.children()
652+
.filter(move |node| match include_children {
653+
IncludedChildren::None => true,
654+
IncludedChildren::All => false,
655+
IncludedChildren::Unnamed => node.is_named(),
656+
})
657+
.map(|n| n.byte_range());
658+
659+
intersect_ranges_impl(range, excluded_ranges, parent_ranges)
660+
}
661+
662+
/// Creates an iterator over the subtraction of one set of ranges from another.
663+
///
664+
/// Given two iterators that yield a sorted series of ranges, this function
665+
/// returns a new iterator that yields a series of ranges, equivalent to the
666+
/// ranges from the first iterator, but excluding any ranges returned by the
667+
/// second.
668+
///
669+
/// To reiterate, both iterators are assumed to return the ranges in sorted order.
670+
fn exclude_ranges(
671+
mut ranges: impl Iterator<Item = Range>,
672+
excluded_ranges: impl Iterator<Item = Range>,
673+
) -> impl Iterator<Item = Range> {
674+
let mut excluded_ranges = excluded_ranges.filter(|range| !range.is_empty());
675+
let mut next_range = ranges.next();
676+
let mut current_excluded_range = excluded_ranges.next();
677+
from_fn(move || {
678+
loop {
679+
let range = next_range.take()?;
680+
681+
// Consume any ranges that precede the rest of the input ranges
682+
while let Some(excluded_range) = &current_excluded_range {
683+
if excluded_range.end <= range.start {
684+
current_excluded_range = excluded_ranges.next()
685+
} else {
686+
break;
687+
}
688+
}
689+
// Handle the next exclusion, if applicable
690+
if let Some(excluded_range) = &current_excluded_range {
691+
if ranges_intersect(&range, excluded_range) {
692+
let preceding_range = range.start..excluded_range.start;
693+
let remaining_range = excluded_range.end..range.end;
694+
695+
// Handle the rest of the range on the next iteration, if
696+
// applicable, or take the next one.
697+
if !remaining_range.is_empty() {
698+
next_range = Some(remaining_range);
699+
// This would also be handled by the loop above
700+
current_excluded_range = excluded_ranges.next()
701+
} else {
702+
next_range = ranges.next()
703+
}
704+
705+
// Return any part of the range that preceded the exclusion
706+
if !preceding_range.is_empty() {
707+
return Some(preceding_range);
708+
} else {
709+
continue;
710+
}
711+
}
712+
}
713+
// Return any ranges that precede the next excluded range
714+
next_range = ranges.next();
715+
return Some(range);
660716
}
661-
IncludedChildren::Unnamed => intersect_ranges_impl(
662-
range,
663-
node.children()
664-
.filter(|node| node.is_named())
665-
.map(|node| node.byte_range()),
666-
parent_ranges,
667-
push_range,
668-
),
669-
}
717+
})
670718
}
671719

720+
/// Return an iterator over the ranges resulting from intersection calculation.
721+
///
722+
/// Given a range, a set of ranges to exclude, and a set of parent ranges,
723+
/// this function calculates the intersection of range with the parent ranges,
724+
/// minus the excluded ranges.
672725
fn intersect_ranges_impl(
673-
range: Range,
726+
mut range: Range,
674727
excluded_ranges: impl Iterator<Item = Range>,
675728
parent_ranges: impl Iterator<Item = Range>,
676-
mut push_range: impl FnMut(Range),
677-
) {
678-
let mut start = range.start;
679-
let mut excluded_ranges = excluded_ranges.filter(|range| !range.is_empty()).peekable();
680-
let mut parent_ranges = parent_ranges.peekable();
681-
loop {
682-
let parent_range = parent_ranges.peek().unwrap().clone();
683-
if let Some(excluded_range) =
684-
excluded_ranges.next_if(|range| range.start <= parent_range.end)
685-
{
686-
if excluded_range.start >= range.end {
687-
break;
729+
) -> impl Iterator<Item = Range> {
730+
// Skip ranges that end before the start of range
731+
let parent_ranges = parent_ranges.skip_while(move |r| r.end <= range.start);
732+
// filter by the excluded ranges
733+
let mut parent_ranges = exclude_ranges(parent_ranges, excluded_ranges).peekable();
734+
let mut next_parent_range = parent_ranges.next();
735+
// Pre-merge any adjacent parent ranges before the first iteration so that the very first
736+
// intersection is computed against the full merged span rather than just the first piece.
737+
while let Some(ref mut current) = next_parent_range {
738+
match parent_ranges.peek() {
739+
Some(next) if next.start == current.end => {
740+
current.end = next.end;
741+
parent_ranges.next();
688742
}
689-
if start != excluded_range.start {
690-
push_range(start..excluded_range.start)
743+
_ => break,
744+
}
745+
}
746+
from_fn(move || {
747+
loop {
748+
// If either range is exhausted, so is this iterator
749+
let Some(parent_range) = &next_parent_range else {
750+
return None;
751+
};
752+
if range.is_empty() {
753+
return None;
691754
}
692-
start = excluded_range.end;
693-
} else {
694-
parent_ranges.next();
695-
if parent_range.end >= range.end {
696-
break;
755+
756+
// Discard non-intersecting part of range
757+
if parent_range.start > range.start {
758+
range.start = parent_range.start;
697759
}
698-
if start != parent_range.end {
699-
push_range(start..parent_range.end)
760+
761+
// Consume some or all of the range
762+
let intersection = range.start..std::cmp::min(range.end, parent_range.end);
763+
range.start = intersection.end;
764+
765+
// Discard any parents that fully precede the new range, merging any adjacent ones
766+
// into the replacement so the next intersection is computed against the full span.
767+
while let Some(parent_range) = &next_parent_range {
768+
if parent_range.end <= range.start {
769+
let mut next = parent_ranges.next();
770+
while let Some(ref mut current) = next {
771+
match parent_ranges.peek() {
772+
Some(n) if n.start == current.end => {
773+
current.end = n.end;
774+
parent_ranges.next();
775+
}
776+
_ => break,
777+
}
778+
}
779+
next_parent_range = next;
780+
} else {
781+
break;
782+
}
783+
}
784+
785+
// Return the part of the range that was consumed
786+
if !intersection.is_empty() {
787+
return Some(intersection);
700788
}
701-
let Some(next_parent_range) = parent_ranges.peek() else {
702-
return;
703-
};
704-
start = next_parent_range.start;
705789
}
706-
}
707-
if start != range.end {
708-
push_range(start..range.end)
709-
}
790+
})
710791
}
711792

712793
fn ranges_intersect(a: &Range, b: &Range) -> bool {
713794
// Adapted from <https://github.com/helix-editor/helix/blob/8df58b2e1779dcf0046fb51ae1893c1eebf01e7c/helix-core/src/selection.rs#L156-L163>
714795
a.start == b.start || (a.end > b.start && b.end > a.start)
715796
}
797+
798+
#[cfg(test)]
799+
#[allow(clippy::single_range_in_vec_init)]
800+
mod tests {
801+
use super::*;
802+
803+
#[test]
804+
fn exclude_ranges_no_exclusions() {
805+
let result: Vec<Range> =
806+
exclude_ranges([0..10, 20..30].into_iter(), [].into_iter()).collect();
807+
assert_eq!(result, vec![0..10, 20..30]);
808+
}
809+
810+
#[test]
811+
fn exclude_ranges_excludes_prefix() {
812+
let result: Vec<Range> = exclude_ranges([0..10].into_iter(), [0..5].into_iter()).collect();
813+
assert_eq!(result, vec![5..10]);
814+
}
815+
816+
#[test]
817+
fn exclude_ranges_excludes_suffix() {
818+
let result: Vec<Range> = exclude_ranges([0..10].into_iter(), [5..10].into_iter()).collect();
819+
assert_eq!(result, vec![0..5]);
820+
}
821+
822+
#[test]
823+
fn exclude_ranges_excludes_middle() {
824+
let result: Vec<Range> = exclude_ranges([0..10].into_iter(), [3..7].into_iter()).collect();
825+
assert_eq!(result, vec![0..3, 7..10]);
826+
}
827+
828+
#[test]
829+
fn exclude_ranges_full_exclusion() {
830+
let result: Vec<Range> = exclude_ranges([0..10].into_iter(), [0..10].into_iter()).collect();
831+
assert_eq!(result, vec![]);
832+
}
833+
834+
#[test]
835+
fn exclude_ranges_multiple_exclusions() {
836+
let result: Vec<Range> =
837+
exclude_ranges([0..100].into_iter(), [10..20, 40..50, 70..80].into_iter()).collect();
838+
assert_eq!(result, vec![0..10, 20..40, 50..70, 80..100]);
839+
}
840+
841+
#[test]
842+
fn exclude_ranges_spans_multiple_input_ranges() {
843+
let result: Vec<Range> =
844+
exclude_ranges([0..20, 30..50].into_iter(), [15..35].into_iter()).collect();
845+
assert_eq!(result, vec![0..15, 35..50]);
846+
}
847+
848+
#[test]
849+
fn intersect_ranges_impl_basic() {
850+
let result: Vec<Range> =
851+
intersect_ranges_impl(10..50, [].into_iter(), [0..100].into_iter()).collect();
852+
assert_eq!(result, vec![10..50]);
853+
}
854+
855+
#[test]
856+
fn intersect_ranges_impl_with_excluded_child() {
857+
let result: Vec<Range> =
858+
intersect_ranges_impl(0..100, [30..70].into_iter(), [0..100].into_iter()).collect();
859+
assert_eq!(result, vec![0..30, 70..100]);
860+
}
861+
862+
#[test]
863+
fn intersect_ranges_impl_non_adjacent_parent_ranges() {
864+
let result: Vec<Range> =
865+
intersect_ranges_impl(0..100, [].into_iter(), [10..40, 60..90].into_iter()).collect();
866+
assert_eq!(result, vec![10..40, 60..90]);
867+
}
868+
869+
#[test]
870+
fn intersect_ranges_impl_adjacent_parent_ranges() {
871+
// Two adjacent parent ranges must be merged into one to avoid emitting adjacent
872+
// injection ranges. The first range's adjacency to the second must be detected before
873+
// computing the first intersection, not after yielding it.
874+
let result: Vec<Range> =
875+
intersect_ranges_impl(0..100, [].into_iter(), [10..50, 50..80].into_iter()).collect();
876+
assert_eq!(result, vec![10..80]);
877+
}
878+
879+
#[test]
880+
fn intersect_ranges_impl_three_adjacent_parent_ranges() {
881+
// All three adjacent ranges must be collapsed into one.
882+
let result: Vec<Range> =
883+
intersect_ranges_impl(0..100, [].into_iter(), [10..30, 30..50, 50..80].into_iter())
884+
.collect();
885+
assert_eq!(result, vec![10..80]);
886+
}
887+
888+
#[test]
889+
fn intersect_ranges_impl_adjacent_then_gap() {
890+
// First two adjacent, third separated: merge only the adjacent pair.
891+
let result: Vec<Range> =
892+
intersect_ranges_impl(0..100, [].into_iter(), [10..30, 30..50, 60..80].into_iter())
893+
.collect();
894+
assert_eq!(result, vec![10..50, 60..80]);
895+
}
896+
897+
#[test]
898+
fn intersect_ranges_impl_gap_then_adjacent() {
899+
// Gap then adjacent: the post-discard merge in the existing code already handles this,
900+
// but it should also be correct after the pre-merge fix.
901+
let result: Vec<Range> =
902+
intersect_ranges_impl(0..100, [].into_iter(), [10..30, 40..60, 60..80].into_iter())
903+
.collect();
904+
assert_eq!(result, vec![10..30, 40..80]);
905+
}
906+
}

highlighter/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ impl Syntax {
9898
parse_tree: None,
9999
language,
100100
flags: LayerUpdateFlags::default(),
101-
ranges: vec![tree_sitter::Range {
102-
start_byte: 0,
103-
end_byte: u32::MAX,
104-
start_point: tree_sitter::Point::ZERO,
105-
end_point: tree_sitter::Point::MAX,
106-
}],
101+
ranges: vec![tree_sitter::Range::new(
102+
tree_sitter::Point::ZERO,
103+
tree_sitter::Point::MAX,
104+
0,
105+
u32::MAX,
106+
)],
107107
injections: Vec::new(),
108108
parent: None,
109109
locals: Locals::default(),

0 commit comments

Comments
 (0)