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.rs65
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(..);