#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
pub struct TextRange {
pub start: usize,
pub end: usize,
}
impl TextRange {
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub const fn cursor(position: usize) -> Self {
Self {
start: position,
end: position,
}
}
pub const fn zero() -> Self {
Self { start: 0, end: 0 }
}
pub const fn collapsed(&self) -> bool {
self.start == self.end
}
pub fn length(&self) -> usize {
self.end.abs_diff(self.start)
}
pub fn min(&self) -> usize {
self.start.min(self.end)
}
pub fn max(&self) -> usize {
self.start.max(self.end)
}
pub fn contains(&self, index: usize) -> bool {
index >= self.min() && index < self.max()
}
pub fn coerce_in(&self, max: usize) -> Self {
Self {
start: self.start.min(max),
end: self.end.min(max),
}
}
pub const fn all(length: usize) -> Self {
Self {
start: 0,
end: length,
}
}
pub fn safe_slice<'a>(&self, text: &'a str) -> &'a str {
if text.is_empty() {
return "";
}
let start = self.min().min(text.len());
let end = self.max().min(text.len());
let start = if text.is_char_boundary(start) {
start
} else {
(0..start)
.rev()
.find(|&i| text.is_char_boundary(i))
.unwrap_or(0)
};
let end = if text.is_char_boundary(end) {
end
} else {
(end..=text.len())
.find(|&i| text.is_char_boundary(i))
.unwrap_or(text.len())
};
if start <= end { &text[start..end] } else { "" }
}
}
#[cfg(test)]
#[path = "tests/range_tests.rs"]
mod tests;