harper_core/
span.rs

1use std::ops::Range;
2
3use serde::{Deserialize, Serialize};
4
5use crate::CharStringExt;
6
7/// A window in a [`char`] sequence.
8///
9/// Although specific to `harper.js`, [this page may clear up any questions you have](https://writewithharper.com/docs/harperjs/spans).
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
11pub struct Span {
12    pub start: usize,
13    pub end: usize,
14}
15
16impl Span {
17    pub fn new(start: usize, end: usize) -> Self {
18        if start > end {
19            panic!("{start} > {end}");
20        }
21        Self { start, end }
22    }
23
24    pub fn new_with_len(start: usize, len: usize) -> Self {
25        Self {
26            start,
27            end: start + len,
28        }
29    }
30
31    pub fn len(&self) -> usize {
32        self.end - self.start
33    }
34
35    pub fn is_empty(&self) -> bool {
36        self.len() == 0
37    }
38
39    pub fn contains(&self, idx: usize) -> bool {
40        assert!(self.start <= self.end);
41
42        self.start <= idx && idx < self.end
43    }
44
45    pub fn overlaps_with(&self, other: Self) -> bool {
46        (self.start < other.end) && (other.start < self.end)
47    }
48
49    /// Get the associated content. Will return [`None`] if any aspect is
50    /// invalid.
51    pub fn try_get_content<'a>(&self, source: &'a [char]) -> Option<&'a [char]> {
52        if (self.start > self.end) || (self.start >= source.len()) || (self.end > source.len()) {
53            if self.is_empty() {
54                return Some(&source[0..0]);
55            }
56            return None;
57        }
58
59        Some(&source[self.start..self.end])
60    }
61
62    /// Expand the span by either modifying [`Self::start`] or [`Self::end`] to include the target
63    /// index.
64    ///
65    /// Does nothing if the span already includes the target.
66    pub fn expand_to_include(&mut self, target: usize) {
67        if target < self.start {
68            self.start = target;
69        } else if target >= self.end {
70            self.end = target + 1;
71        }
72    }
73
74    /// Get the associated content. Will panic if any aspect is invalid.
75    pub fn get_content<'a>(&self, source: &'a [char]) -> &'a [char] {
76        match self.try_get_content(source) {
77            Some(v) => v,
78            None => panic!(
79                "Could not get position {:?} within \"{}\"",
80                self,
81                source.to_string()
82            ),
83        }
84    }
85
86    pub fn get_content_string(&self, source: &[char]) -> String {
87        String::from_iter(self.get_content(source))
88    }
89
90    pub fn set_len(&mut self, length: usize) {
91        self.end = self.start + length;
92    }
93
94    pub fn with_len(&self, length: usize) -> Self {
95        let mut cloned = *self;
96        cloned.set_len(length);
97        cloned
98    }
99
100    // Add an amount to both [`Self::start`] and [`Self::end`]
101    pub fn push_by(&mut self, by: usize) {
102        self.start += by;
103        self.end += by;
104    }
105
106    // Subtract an amount to both [`Self::start`] and [`Self::end`]
107    pub fn pull_by(&mut self, by: usize) {
108        self.start -= by;
109        self.end -= by;
110    }
111
112    // Add an amount to a copy of both [`Self::start`] and [`Self::end`]
113    pub fn pushed_by(&self, by: usize) -> Self {
114        let mut clone = *self;
115        clone.start += by;
116        clone.end += by;
117        clone
118    }
119
120    // Subtract an amount to a copy of both [`Self::start`] and [`Self::end`]
121    pub fn pulled_by(&self, by: usize) -> Option<Self> {
122        if by > self.start {
123            return None;
124        }
125
126        let mut clone = *self;
127        clone.start -= by;
128        clone.end -= by;
129        Some(clone)
130    }
131
132    // Add an amount a copy of both [`Self::start`] and [`Self::end`]
133    pub fn with_offset(&self, by: usize) -> Self {
134        let mut clone = *self;
135        clone.push_by(by);
136        clone
137    }
138}
139
140impl From<Range<usize>> for Span {
141    fn from(value: Range<usize>) -> Self {
142        Self::new(value.start, value.end)
143    }
144}
145
146impl From<Span> for Range<usize> {
147    fn from(value: Span) -> Self {
148        value.start..value.end
149    }
150}
151
152impl IntoIterator for Span {
153    type Item = usize;
154
155    type IntoIter = Range<usize>;
156
157    fn into_iter(self) -> Self::IntoIter {
158        self.start..self.end
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use crate::Span;
165
166    #[test]
167    fn overlaps() {
168        assert!(Span::new(0, 5).overlaps_with(Span::new(3, 6)));
169        assert!(Span::new(0, 5).overlaps_with(Span::new(2, 3)));
170        assert!(Span::new(0, 5).overlaps_with(Span::new(4, 5)));
171        assert!(Span::new(0, 5).overlaps_with(Span::new(4, 4)));
172
173        assert!(!Span::new(0, 3).overlaps_with(Span::new(3, 5)));
174    }
175
176    #[test]
177    fn expands_properly() {
178        let mut span = Span::new(2, 2);
179
180        span.expand_to_include(1);
181        assert_eq!(span, Span::new(1, 2));
182
183        span.expand_to_include(2);
184        assert_eq!(span, Span::new(1, 3));
185    }
186}