|
1 | 1 | use std::cmp::Reverse; |
2 | | -use std::iter::{self, Peekable}; |
| 2 | +use std::iter::{self, from_fn, Peekable}; |
3 | 3 | use std::mem::take; |
4 | 4 | use std::sync::Arc; |
5 | 5 |
|
@@ -450,19 +450,19 @@ impl Syntax { |
450 | 450 | } |
451 | 451 |
|
452 | 452 | 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 | + )); |
460 | 460 | injections.push(Injection { |
461 | 461 | range, |
462 | 462 | layer, |
463 | 463 | matched_node_range: matched_node_range.clone(), |
464 | 464 | }); |
465 | | - }); |
| 465 | + } |
466 | 466 | if old_len != insert_position { |
467 | 467 | let inserted = injections.len() - old_len; |
468 | 468 | injections[insert_position..].rotate_right(inserted); |
@@ -637,79 +637,270 @@ impl Syntax { |
637 | 637 | } |
638 | 638 | } |
639 | 639 |
|
640 | | -fn intersect_ranges( |
| 640 | +fn intersect_ranges<'tree, 'p>( |
641 | 641 | 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> { |
646 | 645 | let range = node.byte_range(); |
647 | 646 | let i = parent_ranges.partition_point(|parent_range| parent_range.end_byte <= range.start); |
648 | 647 | let parent_ranges = parent_ranges[i..] |
649 | 648 | .iter() |
650 | 649 | .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) = ¤t_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) = ¤t_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); |
660 | 716 | } |
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 | + }) |
670 | 718 | } |
671 | 719 |
|
| 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. |
672 | 725 | fn intersect_ranges_impl( |
673 | | - range: Range, |
| 726 | + mut range: Range, |
674 | 727 | excluded_ranges: impl Iterator<Item = Range>, |
675 | 728 | 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(); |
688 | 742 | } |
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; |
691 | 754 | } |
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; |
697 | 759 | } |
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); |
700 | 788 | } |
701 | | - let Some(next_parent_range) = parent_ranges.peek() else { |
702 | | - return; |
703 | | - }; |
704 | | - start = next_parent_range.start; |
705 | 789 | } |
706 | | - } |
707 | | - if start != range.end { |
708 | | - push_range(start..range.end) |
709 | | - } |
| 790 | + }) |
710 | 791 | } |
711 | 792 |
|
712 | 793 | fn ranges_intersect(a: &Range, b: &Range) -> bool { |
713 | 794 | // Adapted from <https://github.com/helix-editor/helix/blob/8df58b2e1779dcf0046fb51ae1893c1eebf01e7c/helix-core/src/selection.rs#L156-L163> |
714 | 795 | a.start == b.start || (a.end > b.start && b.end > a.start) |
715 | 796 | } |
| 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 | +} |
0 commit comments