Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/selection.rs')
| -rw-r--r-- | helix-core/src/selection.rs | 65 |
1 files changed, 64 insertions, 1 deletions
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 48eaf289..8995da8f 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -13,7 +13,7 @@ use crate::{ }; use helix_stdx::rope::{self, RopeSliceExt}; use smallvec::{smallvec, SmallVec}; -use std::borrow::Cow; +use std::{borrow::Cow, iter, slice}; use tree_sitter::Node; /// A single selection range. @@ -503,6 +503,16 @@ impl Selection { &self.ranges } + /// Returns an iterator over the line ranges of each range in the selection. + /// + /// Adjacent and overlapping line ranges of the [Range]s in the selection are merged. + pub fn line_ranges<'a>(&'a self, text: RopeSlice<'a>) -> LineRangeIter<'a> { + LineRangeIter { + ranges: self.ranges.iter().peekable(), + text, + } + } + pub fn primary_index(&self) -> usize { self.primary_index } @@ -727,6 +737,33 @@ impl From<Range> for Selection { } } +pub struct LineRangeIter<'a> { + ranges: iter::Peekable<slice::Iter<'a, Range>>, + text: RopeSlice<'a>, +} + +impl<'a> Iterator for LineRangeIter<'a> { + type Item = (usize, usize); + + fn next(&mut self) -> Option<Self::Item> { + let (start, mut end) = self.ranges.next()?.line_range(self.text); + while let Some((next_start, next_end)) = + self.ranges.peek().map(|range| range.line_range(self.text)) + { + // Merge overlapping and adjacent ranges. + // This subtraction cannot underflow because the ranges are sorted. + if next_start - end <= 1 { + end = next_end; + self.ranges.next(); + } else { + break; + } + } + + Some((start, end)) + } +} + // TODO: checkSelection -> check if valid for doc length && sorted pub fn keep_or_remove_matches( @@ -1166,6 +1203,32 @@ mod test { } #[test] + fn selection_line_ranges() { + let (text, selection) = crate::test::print( + r#" L0 + #[|these]# line #(|ranges)# are #(|merged)# L1 + L2 + single one-line #(|range)# L3 + L4 + single #(|multiline L5 + range)# L6 + L7 + these #(|multiline L8 + ranges)# are #(|also L9 + merged)# L10 + L11 + adjacent #(|ranges)# L12 + are merged #(|the same way)# L13 + "#, + ); + let rope = Rope::from_str(&text); + assert_eq!( + vec![(1, 1), (3, 3), (5, 6), (8, 10), (12, 13)], + selection.line_ranges(rope.slice(..)).collect::<Vec<_>>(), + ); + } + + #[test] fn test_cursor() { let r = Rope::from_str("\r\nHi\r\nthere!"); let s = r.slice(..); |