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 | 688 |
1 files changed, 118 insertions, 570 deletions
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 5bde08e3..18af4d08 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -7,15 +7,10 @@ use crate::{ ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary, prev_grapheme_boundary, }, - line_ending::get_line_ending, - movement::Direction, - tree_sitter::Node, Assoc, ChangeSet, RopeSlice, }; -use helix_stdx::range::is_subset; -use helix_stdx::rope::{self, RopeSliceExt}; use smallvec::{smallvec, SmallVec}; -use std::{borrow::Cow, iter, slice}; +use std::borrow::Cow; /// A single selection range. /// @@ -25,14 +20,14 @@ use std::{borrow::Cow, iter, slice}; /// can be in any order, or even share the same position. /// /// The anchor and head positions use gap indexing, meaning -/// that their indices represent the gaps *between* `char`s +/// that their indices represent the the gaps *between* `char`s /// rather than the `char`s themselves. For example, 1 /// represents the position between the first and second `char`. /// -/// Below are some examples of `Range` configurations. -/// The anchor and head indices are shown as "(anchor, head)" -/// tuples, followed by example text with "[" and "]" symbols -/// representing the anchor and head positions: +/// Below are some example `Range` configurations to better +/// illustrate. The anchor and head indices are show as +/// "(anchor, head)", followed by example text with "[" and "]" +/// inserted to represent the anchor and head positions: /// /// - (0, 3): `[Som]e text`. /// - (3, 0): `]Som[e text`. @@ -42,7 +37,7 @@ use std::{borrow::Cow, iter, slice}; /// Ranges are considered to be inclusive on the left and /// exclusive on the right, regardless of anchor-head ordering. /// This means, for example, that non-zero-width ranges that -/// are directly adjacent, sharing an edge, do not overlap. +/// are directly adjecent, sharing an edge, do not overlap. /// However, a zero-width range will overlap with the shared /// left-edge of another range. /// @@ -57,9 +52,7 @@ pub struct Range { pub anchor: usize, /// The head of the range, moved when extending. pub head: usize, - /// The previous visual offset (softwrapped lines and columns) from - /// the start of the line - pub old_visual_position: Option<(u32, u32)>, + pub horiz: Option<u32>, } impl Range { @@ -67,7 +60,7 @@ impl Range { Self { anchor, head, - old_visual_position: None, + horiz: None, } } @@ -75,12 +68,6 @@ impl Range { Self::new(head, head) } - pub fn from_node(node: Node, text: RopeSlice, direction: Direction) -> Self { - let from = text.byte_to_char(node.start_byte() as usize); - let to = text.byte_to_char(node.end_byte() as usize); - Range::new(from, to).with_direction(direction) - } - /// Start of the range. #[inline] #[must_use] @@ -95,13 +82,6 @@ impl Range { std::cmp::max(self.anchor, self.head) } - /// Total length of the range. - #[inline] - #[must_use] - pub fn len(&self) -> usize { - self.to() - self.from() - } - /// The (inclusive) range of lines that the range overlaps. #[inline] #[must_use] @@ -122,37 +102,6 @@ impl Range { self.anchor == self.head } - /// `Direction::Backward` when head < anchor. - /// `Direction::Forward` otherwise. - #[inline] - #[must_use] - pub fn direction(&self) -> Direction { - if self.head < self.anchor { - Direction::Backward - } else { - Direction::Forward - } - } - - /// Flips the direction of the selection - pub fn flip(&self) -> Self { - Self { - anchor: self.head, - head: self.anchor, - old_visual_position: self.old_visual_position, - } - } - - /// Returns the selection if it goes in the direction of `direction`, - /// flipping the selection otherwise. - pub fn with_direction(self, direction: Direction) -> Self { - if self.direction() == direction { - self - } else { - self.flip() - } - } - /// Check two ranges for overlap. #[must_use] pub fn overlaps(&self, other: &Self) -> bool { @@ -162,44 +111,38 @@ impl Range { self.from() == other.from() || (self.to() > other.from() && other.to() > self.from()) } - #[inline] - pub fn contains_range(&self, other: &Self) -> bool { - self.from() <= other.from() && self.to() >= other.to() - } - pub fn contains(&self, pos: usize) -> bool { self.from() <= pos && pos < self.to() } - /// Map a range through a set of changes. Returns a new range representing - /// the same position after the changes are applied. Note that this - /// function runs in O(N) (N is number of changes) and can therefore - /// cause performance problems if run for a large number of ranges as the - /// complexity is then O(MN) (for multicuror M=N usually). Instead use - /// [Selection::map] or [ChangeSet::update_positions]. - pub fn map(mut self, changes: &ChangeSet) -> Self { + /// Map a range through a set of changes. Returns a new range representing the same position + /// after the changes are applied. + pub fn map(self, changes: &ChangeSet) -> Self { use std::cmp::Ordering; - if changes.is_empty() { - return self; - } - - let positions_to_map = match self.anchor.cmp(&self.head) { - Ordering::Equal => [ - (&mut self.anchor, Assoc::AfterSticky), - (&mut self.head, Assoc::AfterSticky), - ], - Ordering::Less => [ - (&mut self.anchor, Assoc::AfterSticky), - (&mut self.head, Assoc::BeforeSticky), - ], - Ordering::Greater => [ - (&mut self.head, Assoc::AfterSticky), - (&mut self.anchor, Assoc::BeforeSticky), - ], + let (anchor, head) = match self.anchor.cmp(&self.head) { + Ordering::Equal => ( + changes.map_pos(self.anchor, Assoc::After), + changes.map_pos(self.head, Assoc::After), + ), + Ordering::Less => ( + changes.map_pos(self.anchor, Assoc::After), + changes.map_pos(self.head, Assoc::Before), + ), + Ordering::Greater => ( + changes.map_pos(self.anchor, Assoc::Before), + changes.map_pos(self.head, Assoc::After), + ), }; - changes.update_positions(positions_to_map.into_iter()); - self.old_visual_position = None; - self + + // We want to return a new `Range` with `horiz == None` every time, + // even if the anchor and head haven't changed, because we don't + // know if the *visual* position hasn't changed due to + // character-width or grapheme changes earlier in the text. + Self { + anchor, + head, + horiz: None, + } } /// Extend the range to cover at least `from` `to`. @@ -211,13 +154,13 @@ impl Range { Self { anchor: self.anchor.min(from), head: self.head.max(to), - old_visual_position: None, + horiz: None, } } else { Self { anchor: self.anchor.max(to), head: self.head.min(from), - old_visual_position: None, + horiz: None, } } } @@ -232,36 +175,22 @@ impl Range { Range { anchor: self.anchor.max(other.anchor), head: self.head.min(other.head), - old_visual_position: None, + horiz: None, } } else { Range { anchor: self.from().min(other.from()), head: self.to().max(other.to()), - old_visual_position: None, + horiz: None, } } } // groupAt - /// Returns the text inside this range given the text of the whole buffer. - /// - /// The returned `Cow` is a reference if the range of text is inside a single - /// chunk of the rope. Otherwise a copy of the text is returned. Consider - /// using `slice` instead if you do not need a `Cow` or `String` to avoid copying. #[inline] pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> { - self.slice(text).into() - } - - /// Returns the text inside this range given the text of the whole buffer. - /// - /// The returned value is a reference to the passed slice. This method never - /// copies any contents. - #[inline] - pub fn slice<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> RopeSlice<'b> { - text.slice(self.from()..self.to()) + text.slice(self.from()..self.to()).into() } //-------------------------------- @@ -292,8 +221,8 @@ impl Range { Range { anchor: new_anchor, head: new_head, - old_visual_position: if new_anchor == self.anchor { - self.old_visual_position + horiz: if new_anchor == self.anchor { + self.horiz } else { None }, @@ -319,7 +248,7 @@ impl Range { Range { anchor: self.anchor, head: next_grapheme_boundary(slice, self.head), - old_visual_position: self.old_visual_position, + horiz: self.horiz, } } else { *self @@ -376,37 +305,14 @@ impl Range { pub fn cursor_line(&self, text: RopeSlice) -> usize { text.char_to_line(self.cursor(text)) } - - /// Returns true if this Range covers a single grapheme in the given text - pub fn is_single_grapheme(&self, doc: RopeSlice) -> bool { - let mut graphemes = doc.slice(self.from()..self.to()).graphemes(); - let first = graphemes.next(); - let second = graphemes.next(); - first.is_some() && second.is_none() - } - - /// Converts this char range into an in order byte range, discarding - /// direction. - pub fn into_byte_range(&self, text: RopeSlice) -> (usize, usize) { - (text.char_to_byte(self.from()), text.char_to_byte(self.to())) - } } impl From<(usize, usize)> for Range { - fn from((anchor, head): (usize, usize)) -> Self { - Self { - anchor, - head, - old_visual_position: None, - } - } -} - -impl From<Range> for helix_stdx::Range { - fn from(range: Range) -> Self { + fn from(tuple: (usize, usize)) -> Self { Self { - start: range.from(), - end: range.to(), + anchor: tuple.0, + head: tuple.1, + horiz: None, } } } @@ -454,13 +360,8 @@ impl Selection { self.normalize() } - /// Removes a range from the selection. + /// Adds a new range to the selection and makes it the primary range. pub fn remove(mut self, index: usize) -> Self { - assert!( - self.ranges.len() > 1, - "can't remove the last range from a selection!" - ); - self.ranges.remove(index); if index < self.primary_index || self.primary_index == self.ranges.len() { self.primary_index -= 1; @@ -468,65 +369,26 @@ impl Selection { self } - /// Replace a range in the selection with a new range. - pub fn replace(mut self, index: usize, range: Range) -> Self { - self.ranges[index] = range; - self.normalize() - } - /// Map selections over a set of changes. Useful for adjusting the selection position after /// applying changes to a document. pub fn map(self, changes: &ChangeSet) -> Self { - self.map_no_normalize(changes).normalize() - } - - /// Map selections over a set of changes. Useful for adjusting the selection position after - /// applying changes to a document. Doesn't normalize the selection - pub fn map_no_normalize(mut self, changes: &ChangeSet) -> Self { if changes.is_empty() { return self; } - let positions_to_map = self.ranges.iter_mut().flat_map(|range| { - use std::cmp::Ordering; - range.old_visual_position = None; - match range.anchor.cmp(&range.head) { - Ordering::Equal => [ - (&mut range.anchor, Assoc::AfterSticky), - (&mut range.head, Assoc::AfterSticky), - ], - Ordering::Less => [ - (&mut range.anchor, Assoc::AfterSticky), - (&mut range.head, Assoc::BeforeSticky), - ], - Ordering::Greater => [ - (&mut range.head, Assoc::AfterSticky), - (&mut range.anchor, Assoc::BeforeSticky), - ], - } - }); - changes.update_positions(positions_to_map); - self + Self::new( + self.ranges + .into_iter() + .map(|range| range.map(changes)) + .collect(), + self.primary_index, + ) } pub fn ranges(&self) -> &[Range] { &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 range_bounds(&self) -> impl Iterator<Item = helix_stdx::Range> + '_ { - self.ranges.iter().map(|&range| range.into()) - } - pub fn primary_index(&self) -> usize { self.primary_index } @@ -543,7 +405,7 @@ impl Selection { ranges: smallvec![Range { anchor, head, - old_visual_position: None + horiz: None }], primary_index: 0, } @@ -555,87 +417,56 @@ impl Selection { } /// Normalizes a `Selection`. - /// - /// Ranges are sorted by [Range::from], with overlapping ranges merged. fn normalize(mut self) -> Self { - if self.len() < 2 { - return self; - } - let mut primary = self.ranges[self.primary_index]; + let primary = self.ranges[self.primary_index]; self.ranges.sort_unstable_by_key(Range::from); - - self.ranges.dedup_by(|curr_range, prev_range| { - if prev_range.overlaps(curr_range) { - let new_range = curr_range.merge(*prev_range); - if prev_range == &primary || curr_range == &primary { - primary = new_range; - } - *prev_range = new_range; - true - } else { - false - } - }); - self.primary_index = self .ranges .iter() .position(|&range| range == primary) .unwrap(); - self - } - - /// Replaces ranges with one spanning from first to last range. - pub fn merge_ranges(self) -> Self { - let first = self.ranges.first().unwrap(); - let last = self.ranges.last().unwrap(); - Selection::new(smallvec![first.merge(*last)], 0) - } - - /// Merges all ranges that are consecutive. - pub fn merge_consecutive_ranges(mut self) -> Self { - let mut primary = self.ranges[self.primary_index]; - - self.ranges.dedup_by(|curr_range, prev_range| { - if prev_range.to() == curr_range.from() { - let new_range = curr_range.merge(*prev_range); - if prev_range == &primary || curr_range == &primary { - primary = new_range; - } - *prev_range = new_range; - true + let mut prev_i = 0; + for i in 1..self.ranges.len() { + if self.ranges[prev_i].overlaps(&self.ranges[i]) { + self.ranges[prev_i] = self.ranges[prev_i].merge(self.ranges[i]); } else { - false + prev_i += 1; + self.ranges[prev_i] = self.ranges[i]; } - }); + if i == self.primary_index { + self.primary_index = prev_i; + } + } - self.primary_index = self - .ranges - .iter() - .position(|&range| range == primary) - .unwrap(); + self.ranges.truncate(prev_i + 1); self } + // TODO: consume an iterator or a vec to reduce allocations? #[must_use] pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self { assert!(!ranges.is_empty()); debug_assert!(primary_index < ranges.len()); - let selection = Self { + let mut selection = Self { ranges, primary_index, }; - selection.normalize() + if selection.ranges.len() > 1 { + // TODO: only normalize if needed (any ranges out of order) + selection = selection.normalize(); + } + + selection } /// Takes a closure and maps each `Range` over the closure. - pub fn transform<F>(mut self, mut f: F) -> Self + pub fn transform<F>(mut self, f: F) -> Self where - F: FnMut(Range) -> Range, + F: Fn(Range) -> Range, { for range in self.ranges.iter_mut() { *range = f(*range) @@ -643,16 +474,6 @@ impl Selection { self.normalize() } - /// Takes a closure and maps each `Range` over the closure to multiple `Range`s. - pub fn transform_iter<F, I>(mut self, f: F) -> Self - where - F: FnMut(Range) -> I, - I: Iterator<Item = Range>, - { - self.ranges = self.ranges.into_iter().flat_map(f).collect(); - self.normalize() - } - // Ensures the selection adheres to the following invariants: // 1. All ranges are grapheme aligned. // 2. All ranges are at least 1 character wide, unless at the @@ -670,22 +491,10 @@ impl Selection { self.transform(|range| Range::point(range.cursor(text))) } - pub fn fragments<'a>( - &'a self, - text: RopeSlice<'a>, - ) -> impl DoubleEndedIterator<Item = Cow<'a, str>> + ExactSizeIterator<Item = Cow<'a, str>> - { + pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a { self.ranges.iter().map(move |range| range.fragment(text)) } - pub fn slices<'a>( - &'a self, - text: RopeSlice<'a>, - ) -> impl DoubleEndedIterator<Item = RopeSlice<'a>> + ExactSizeIterator<Item = RopeSlice<'a>> + 'a - { - self.ranges.iter().map(move |range| range.slice(text)) - } - #[inline(always)] pub fn iter(&self) -> std::slice::Iter<'_, Range> { self.ranges.iter() @@ -695,11 +504,6 @@ impl Selection { pub fn len(&self) -> usize { self.ranges.len() } - - /// returns true if self ⊇ other - pub fn contains(&self, other: &Selection) -> bool { - is_subset::<true>(self.range_bounds(), other.range_bounds()) - } } impl<'a> IntoIterator for &'a Selection { @@ -711,68 +515,16 @@ impl<'a> IntoIterator for &'a Selection { } } -impl IntoIterator for Selection { - type Item = Range; - type IntoIter = smallvec::IntoIter<[Range; 1]>; - - fn into_iter(self) -> smallvec::IntoIter<[Range; 1]> { - self.ranges.into_iter() - } -} - -impl FromIterator<Range> for Selection { - fn from_iter<T: IntoIterator<Item = Range>>(ranges: T) -> Self { - Self::new(ranges.into_iter().collect(), 0) - } -} - -impl From<Range> for Selection { - fn from(range: Range) -> Self { - Self { - ranges: smallvec![range], - primary_index: 0, - } - } -} - -pub struct LineRangeIter<'a> { - ranges: iter::Peekable<slice::Iter<'a, Range>>, - text: RopeSlice<'a>, -} - -impl Iterator for LineRangeIter<'_> { - 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( +pub fn keep_matches( text: RopeSlice, selection: &Selection, - regex: &rope::Regex, - remove: bool, + regex: &crate::regex::Regex, ) -> Option<Selection> { let result: SmallVec<_> = selection .iter() - .filter(|range| regex.is_match(text.regex_input_at(range.from()..range.to())) ^ remove) + .filter(|range| regex.is_match(&range.fragment(text))) .copied() .collect(); @@ -783,27 +535,26 @@ pub fn keep_or_remove_matches( None } -// TODO: support to split on capture #N instead of whole match pub fn select_on_matches( text: RopeSlice, selection: &Selection, - regex: &rope::Regex, + regex: &crate::regex::Regex, ) -> Option<Selection> { let mut result = SmallVec::with_capacity(selection.len()); for sel in selection { - for mat in regex.find_iter(text.regex_input_at(sel.from()..sel.to())) { - // TODO: retain range direction + // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet + let fragment = sel.fragment(text); - let start = text.byte_to_char(mat.start()); - let end = text.byte_to_char(mat.end()); + let sel_start = sel.from(); + let start_byte = text.char_to_byte(sel_start); - let range = Range::new(start, end); - // Make sure the match is not right outside of the selection. - // These invalid matches can come from using RegEx anchors like `^`, `$` - if range != Range::point(sel.to()) { - result.push(range); - } + for mat in regex.find_iter(&fragment) { + // TODO: retain range direction + + let start = text.byte_to_char(start_byte + mat.start()); + let end = text.byte_to_char(start_byte + mat.end()); + result.push(Range::new(start, end)); } } @@ -815,7 +566,12 @@ pub fn select_on_matches( None } -pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection { +// TODO: support to split on capture #N instead of whole match +pub fn split_on_matches( + text: RopeSlice, + selection: &Selection, + regex: &crate::regex::Regex, +) -> Selection { let mut result = SmallVec::with_capacity(selection.len()); for sel in selection { @@ -825,49 +581,21 @@ pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection { continue; } + // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet + let fragment = sel.fragment(text); + let sel_start = sel.from(); let sel_end = sel.to(); - let mut start = sel_start; + let start_byte = text.char_to_byte(sel_start); - for line in sel.slice(text).lines() { - let Some(line_ending) = get_line_ending(&line) else { - break; - }; - let line_end = start + line.len_chars(); - // TODO: retain range direction - result.push(Range::new(start, line_end - line_ending.len_chars())); - start = line_end; - } - - if start < sel_end { - result.push(Range::new(start, sel_end)); - } - } - - // TODO: figure out a new primary index - Selection::new(result, 0) -} - -pub fn split_on_matches(text: RopeSlice, selection: &Selection, regex: &rope::Regex) -> Selection { - let mut result = SmallVec::with_capacity(selection.len()); - - for sel in selection { - // Special case: zero-width selection. - if sel.from() == sel.to() { - result.push(*sel); - continue; - } - - let sel_start = sel.from(); - let sel_end = sel.to(); let mut start = sel_start; - for mat in regex.find_iter(text.regex_input_at(sel_start..sel_end)) { + for mat in regex.find_iter(&fragment) { // TODO: retain range direction - let end = text.byte_to_char(mat.start()); + let end = text.byte_to_char(start_byte + mat.start()); result.push(Range::new(start, end)); - start = text.byte_to_char(mat.end()); + start = text.byte_to_char(start_byte + mat.end()); } if start < sel_end { @@ -959,16 +687,16 @@ mod test { fn test_contains() { let range = Range::new(10, 12); - assert!(!range.contains(9)); - assert!(range.contains(10)); - assert!(range.contains(11)); - assert!(!range.contains(12)); - assert!(!range.contains(13)); + assert_eq!(range.contains(9), false); + assert_eq!(range.contains(10), true); + assert_eq!(range.contains(11), true); + assert_eq!(range.contains(12), false); + assert_eq!(range.contains(13), false); let range = Range::new(9, 6); - assert!(!range.contains(9)); - assert!(range.contains(7)); - assert!(range.contains(6)); + assert_eq!(range.contains(9), false); + assert_eq!(range.contains(7), true); + assert_eq!(range.contains(6), true); } #[test] @@ -1023,7 +751,7 @@ mod test { } #[test] - fn test_grapheme_aligned() { + fn test_graphem_aligned() { let r = Rope::from_str("\r\nHi\r\n"); let s = r.slice(..); @@ -1097,80 +825,6 @@ mod test { } #[test] - fn test_select_on_matches() { - let r = Rope::from_str("Nobody expects the Spanish inquisition"); - let s = r.slice(..); - - let selection = Selection::single(0, r.len_chars()); - assert_eq!( - select_on_matches(s, &selection, &rope::Regex::new(r"[A-Z][a-z]*").unwrap()), - Some(Selection::new( - smallvec![Range::new(0, 6), Range::new(19, 26)], - 0 - )) - ); - - let r = Rope::from_str("This\nString\n\ncontains multiple\nlines"); - let s = r.slice(..); - - let start_of_line = rope::RegexBuilder::new() - .syntax(rope::Config::new().multi_line(true)) - .build(r"^") - .unwrap(); - let end_of_line = rope::RegexBuilder::new() - .syntax(rope::Config::new().multi_line(true)) - .build(r"$") - .unwrap(); - - // line without ending - assert_eq!( - select_on_matches(s, &Selection::single(0, 4), &start_of_line), - Some(Selection::single(0, 0)) - ); - assert_eq!( - select_on_matches(s, &Selection::single(0, 4), &end_of_line), - None - ); - // line with ending - assert_eq!( - select_on_matches(s, &Selection::single(0, 5), &start_of_line), - Some(Selection::single(0, 0)) - ); - assert_eq!( - select_on_matches(s, &Selection::single(0, 5), &end_of_line), - Some(Selection::single(4, 4)) - ); - // line with start of next line - assert_eq!( - select_on_matches(s, &Selection::single(0, 6), &start_of_line), - Some(Selection::new( - smallvec![Range::point(0), Range::point(5)], - 0 - )) - ); - assert_eq!( - select_on_matches(s, &Selection::single(0, 6), &end_of_line), - Some(Selection::single(4, 4)) - ); - - // multiple lines - assert_eq!( - select_on_matches( - s, - &Selection::single(0, s.len_chars()), - &rope::RegexBuilder::new() - .syntax(rope::Config::new().multi_line(true)) - .build(r"^[a-z ]*$") - .unwrap() - ), - Some(Selection::new( - smallvec![Range::point(12), Range::new(13, 30), Range::new(31, 36)], - 0 - )) - ); - } - - #[test] fn test_line_range() { let r = Rope::from_str("\r\nHi\r\nthere!"); let s = r.slice(..); @@ -1201,32 +855,6 @@ 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(..); @@ -1278,15 +906,13 @@ mod test { #[test] fn test_split_on_matches() { + use crate::regex::Regex; + let text = Rope::from(" abcd efg wrs xyz 123 456"); let selection = Selection::new(smallvec![Range::new(0, 9), Range::new(11, 20),], 0); - let result = split_on_matches( - text.slice(..), - &selection, - &rope::Regex::new(r"\s+").unwrap(), - ); + let result = split_on_matches(text.slice(..), &selection, &Regex::new(r"\s+").unwrap()); assert_eq!( result.ranges(), @@ -1315,82 +941,4 @@ mod test { &["", "abcd", "efg", "rs", "xyz"] ); } - - #[test] - fn test_merge_consecutive_ranges() { - let selection = Selection::new( - smallvec![ - Range::new(0, 1), - Range::new(1, 10), - Range::new(15, 20), - Range::new(25, 26), - Range::new(26, 30) - ], - 4, - ); - - let result = selection.merge_consecutive_ranges(); - - assert_eq!( - result.ranges(), - &[Range::new(0, 10), Range::new(15, 20), Range::new(25, 30)] - ); - assert_eq!(result.primary_index, 2); - - let selection = Selection::new(smallvec![Range::new(0, 1)], 0); - let result = selection.merge_consecutive_ranges(); - - assert_eq!(result.ranges(), &[Range::new(0, 1)]); - assert_eq!(result.primary_index, 0); - - let selection = Selection::new( - smallvec![ - Range::new(0, 1), - Range::new(1, 5), - Range::new(5, 8), - Range::new(8, 10), - Range::new(10, 15), - Range::new(18, 25) - ], - 3, - ); - - let result = selection.merge_consecutive_ranges(); - - assert_eq!(result.ranges(), &[Range::new(0, 15), Range::new(18, 25)]); - assert_eq!(result.primary_index, 0); - } - - #[test] - fn test_selection_contains() { - fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool { - let sela = Selection::new(a.iter().map(|a| Range::new(a.0, a.1)).collect(), 0); - let selb = Selection::new(b.iter().map(|b| Range::new(b.0, b.1)).collect(), 0); - sela.contains(&selb) - } - - // exact match - assert!(contains(vec!((1, 1)), vec!((1, 1)))); - - // larger set contains smaller - assert!(contains(vec!((1, 1), (2, 2), (3, 3)), vec!((2, 2)))); - - // multiple matches - assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2)))); - - // smaller set can't contain bigger - assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2)))); - - assert!(contains( - vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)), - vec!((3, 4), (7, 9)) - )); - assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6)))); - - // multiple ranges of other are all contained in some ranges of self, - assert!(contains( - vec!((1, 4), (7, 10)), - vec!((1, 2), (3, 4), (7, 9)) - )); - } } |