diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index 4e89361d2..333c94096 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -1,6 +1,7 @@ //! LSP diagnostic utility types. use std::fmt; +pub use helix_stdx::range::Range; use serde::{Deserialize, Serialize}; /// Describes the severity level of a [`Diagnostic`]. @@ -19,19 +20,6 @@ fn default() -> Self { } } -/// A range of `char`s within the text. -#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] -pub struct Range { - pub start: usize, - pub end: usize, -} - -impl Range { - pub fn contains(self, pos: usize) -> bool { - (self.start..self.end).contains(&pos) - } -} - #[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)] pub enum NumberOrString { Number(i32), diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index eff1fcd70..26151350f 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -11,6 +11,7 @@ movement::Direction, Assoc, ChangeSet, RopeGraphemes, RopeSlice, }; +use helix_stdx::range::is_subset; use helix_stdx::rope::{self, RopeSliceExt}; use smallvec::{smallvec, SmallVec}; use std::{borrow::Cow, iter, slice}; @@ -401,6 +402,15 @@ fn from((anchor, head): (usize, usize)) -> Self { } } +impl From for helix_stdx::Range { + fn from(range: Range) -> Self { + Self { + start: range.from(), + end: range.to(), + } + } +} + /// A selection consists of one or more selection ranges. /// invariant: A selection can never be empty (always contains at least primary range). #[derive(Debug, Clone, PartialEq, Eq)] @@ -513,6 +523,10 @@ pub fn line_ranges<'a>(&'a self, text: RopeSlice<'a>) -> LineRangeIter<'a> { } } + pub fn range_bounds(&self) -> impl Iterator + '_ { + self.ranges.iter().map(|&range| range.into()) + } + pub fn primary_index(&self) -> usize { self.primary_index } @@ -683,32 +697,9 @@ pub fn len(&self) -> usize { self.ranges.len() } - // returns true if self ⊇ other + /// returns true if self ⊇ other pub fn contains(&self, other: &Selection) -> bool { - let (mut iter_self, mut iter_other) = (self.iter(), other.iter()); - let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next()); - - loop { - match (ele_self, ele_other) { - (Some(ra), Some(rb)) => { - if !ra.contains_range(rb) { - // `self` doesn't contain next element from `other`, advance `self`, we need to match all from `other` - ele_self = iter_self.next(); - } else { - // matched element from `other`, advance `other` - ele_other = iter_other.next(); - }; - } - (None, Some(_)) => { - // exhausted `self`, we can't match the reminder of `other` - return false; - } - (_, None) => { - // no elements from `other` left to match, `self` contains `other` - return true; - } - } - } + is_subset::(self.range_bounds(), other.range_bounds()) } } diff --git a/helix-stdx/src/lib.rs b/helix-stdx/src/lib.rs index 19602c205..d09df587a 100644 --- a/helix-stdx/src/lib.rs +++ b/helix-stdx/src/lib.rs @@ -1,4 +1,7 @@ pub mod env; pub mod faccess; pub mod path; +pub mod range; pub mod rope; + +pub use range::Range; diff --git a/helix-stdx/src/range.rs b/helix-stdx/src/range.rs new file mode 100644 index 000000000..46ff6386d --- /dev/null +++ b/helix-stdx/src/range.rs @@ -0,0 +1,103 @@ +use std::ops::{self, RangeBounds}; + +/// A range of `char`s within the text. +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] +pub struct Range { + pub start: T, + pub end: T, +} + +impl Range { + pub fn contains(&self, other: Self) -> bool { + self.start <= other.start && other.end <= self.end + } + pub fn is_empty(&self) -> bool { + self.end <= self.start + } +} + +impl RangeBounds for Range { + fn start_bound(&self) -> ops::Bound<&T> { + ops::Bound::Included(&self.start) + } + + fn end_bound(&self) -> ops::Bound<&T> { + ops::Bound::Excluded(&self.end) + } +} + +/// Returns true if all ranges yielded by `sub_set` are contained by +/// `super_set`. This is essentially an optimized implementation of +/// `sub_set.all(|rb| super_set.any(|ra| ra.contains(rb)))` that runs in O(m+n) +/// instead of O(mn) (and in many cases faster). +/// +/// Both iterators must uphold a the follwong invariants: +/// * ranges must not overlap (but they can be adjecent) +/// * ranges must be sorted +pub fn is_subset( + mut super_set: impl Iterator, + mut sub_set: impl Iterator, +) -> bool { + let (mut super_range, mut sub_range) = (super_set.next(), sub_set.next()); + loop { + match (super_range, sub_range) { + // skip over irrelevant ranges + (Some(ra), Some(rb)) + if ra.end <= rb.start && (ra.start != rb.start || !ALLOW_EMPTY) => + { + super_range = super_set.next(); + } + (Some(ra), Some(rb)) => { + if ra.contains(rb) { + sub_range = sub_set.next(); + } else { + return false; + } + } + (None, Some(_)) => { + // exhausted `super_set`, we can't match the reminder of `sub_set` + return false; + } + (_, None) => { + // no elements from `sub_sut` left to match, `super_set` contains `sub_set` + return true; + } + } + } +} + +pub fn is_exact_subset( + mut super_set: impl Iterator, + mut sub_set: impl Iterator, +) -> bool { + let (mut super_range, mut sub_range) = (super_set.next(), sub_set.next()); + let mut super_range_matched = true; + loop { + match (super_range, sub_range) { + // skip over irrelevant ranges + (Some(ra), Some(rb)) if ra.end <= rb.start && ra.start < rb.start => { + if !super_range_matched { + return false; + } + super_range_matched = false; + super_range = super_set.next(); + } + (Some(ra), Some(rb)) => { + if ra.contains(rb) { + super_range_matched = true; + sub_range = sub_set.next(); + } else { + return false; + } + } + (None, Some(_)) => { + // exhausted `super_set`, we can't match the reminder of `sub_set` + return false; + } + (_, None) => { + // no elements from `sub_sut` left to match, `super_set` contains `sub_set` + return super_set.next().is_none(); + } + } + } +}