1use crate::buffer::Buffer;
11use std::cmp::min;
12
13mod cur;
14pub(crate) mod find;
15mod range;
16mod text_object;
17
18pub use cur::Cur;
19pub use range::Range;
20pub use text_object::TextObject;
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
28pub enum Dot {
29 Cur {
31 c: Cur,
33 },
34 Range {
36 r: Range,
38 },
39}
40
41impl Default for Dot {
42 fn default() -> Self {
43 Self::Cur { c: Cur::default() }
44 }
45}
46
47impl From<Cur> for Dot {
48 fn from(c: Cur) -> Self {
49 Self::Cur { c }
50 }
51}
52
53impl From<Range> for Dot {
54 fn from(r: Range) -> Self {
55 Self::Range { r }
56 }
57}
58
59impl Dot {
60 pub fn from_char_indices(from: usize, to: usize) -> Self {
64 Self::Range {
65 r: Range::from_cursors(Cur { idx: from }, Cur { idx: to }, false),
66 }
67 }
68
69 pub fn as_char_indices(&self) -> (usize, usize) {
74 match *self {
75 Self::Cur { c: Cur { idx } } => (idx, idx),
76 Self::Range {
77 r:
78 Range {
79 start: Cur { idx: from },
80 end: Cur { idx: to },
81 ..
82 },
83 } => (from, to),
84 }
85 }
86
87 pub fn n_chars(&self) -> usize {
89 let (from, to) = self.as_char_indices();
90
91 to - from + 1
92 }
93
94 pub fn is_cur(&self) -> bool {
96 matches!(self, Dot::Cur { .. })
97 }
98
99 pub fn is_range(&self) -> bool {
101 matches!(self, Dot::Range { .. })
102 }
103
104 pub fn contains(&self, cur: &Cur) -> bool {
106 match self {
107 Dot::Cur { c } => cur == c,
108 Dot::Range { r } => r.contains(cur),
109 }
110 }
111
112 pub fn contains_range(&self, rng: &Range) -> bool {
114 self.contains(&rng.start) && self.contains(&rng.end)
115 }
116
117 pub fn with_offset_saturating(mut self, offset: isize) -> Self {
122 match &mut self {
123 Dot::Cur { c } if offset >= 0 => c.idx += offset as usize,
124 Dot::Cur { c } => c.idx = c.idx.saturating_sub(-offset as usize),
125 Dot::Range { r } if offset >= 0 => {
126 r.start.idx += offset as usize;
127 r.end.idx += offset as usize;
128 }
129 Dot::Range { r } => {
130 r.start.idx = r.start.idx.saturating_sub(-offset as usize);
131 r.end.idx = r.end.idx.saturating_sub(-offset as usize);
132 }
133 }
134
135 self
136 }
137
138 pub fn addr(&self, b: &Buffer) -> String {
141 match self {
142 Self::Cur { c } => c.as_string_addr(b),
143 Self::Range { r } => r.as_string_addr(b),
144 }
145 }
146
147 pub fn content(&self, b: &Buffer) -> String {
149 let len_chars = b.txt.len_chars();
150
151 if len_chars == 0 {
152 return String::new();
153 }
154
155 let (from, to) = self.as_char_indices();
156 b.txt.slice(from, min(to + 1, len_chars)).to_string()
157 }
158
159 #[inline]
161 pub fn active_cur(&self) -> Cur {
162 match self {
163 Self::Cur { c } => *c,
164 Self::Range { r } => r.active_cursor(),
165 }
166 }
167
168 pub fn set_active_cur(&mut self, cur: Cur) {
171 match self {
172 Self::Cur { c } => *c = cur,
173 Self::Range { r } => r.set_active_cursor(cur),
174 }
175 }
176
177 #[inline]
179 pub fn first_cur(&self) -> Cur {
180 match self {
181 Self::Cur { c } => *c,
182 Self::Range { r } => r.start,
183 }
184 }
185
186 #[inline]
188 pub fn last_cur(&self) -> Cur {
189 match self {
190 Self::Cur { c } => *c,
191 Self::Range { r } => r.end,
192 }
193 }
194
195 #[inline]
200 pub fn as_range(&self) -> Range {
201 match self {
202 Self::Cur { c } => Range {
203 start: *c,
204 end: *c,
205 start_active: false,
206 },
207 Self::Range { r } => *r,
208 }
209 }
210
211 #[inline]
213 pub fn collapse_to_first_cur(&self) -> Self {
214 Dot::Cur {
215 c: self.first_cur(),
216 }
217 }
218
219 #[inline]
221 pub fn collapse_to_last_cur(&self) -> Self {
222 Dot::Cur { c: self.last_cur() }
223 }
224
225 #[inline]
227 pub fn collapse_to_active_cur(&self) -> Self {
228 Dot::Cur {
229 c: self.active_cur(),
230 }
231 }
232
233 #[inline]
235 pub fn flip(&mut self) {
236 if let Dot::Range { r } = self {
237 r.flip();
238 }
239 }
240
241 pub fn collapse_null_range(self) -> Self {
243 match self {
244 Dot::Range {
245 r: Range { start, end, .. },
246 } if start == end => Dot::Cur { c: start },
247 _ => self,
248 }
249 }
250
251 pub fn clamp_idx(&mut self, max_idx: usize) {
253 match self {
254 Dot::Cur { c } => c.clamp_idx(max_idx),
255 Dot::Range { r } => {
256 r.start.clamp_idx(max_idx);
257 r.end.clamp_idx(max_idx);
258 }
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::{
266 text_object::TextObject::{self, *},
267 *,
268 };
269 use simple_test_case::test_case;
270
271 const EXAMPLE_TEXT: &str = "\
272This is the first line of the file. Followed
273by the second line. Some of the sentences are split
274over multiple lines.
275Others are not.
276
277There is a second paragraph as well. But it
278is quite short when compared to the first.
279
280
281The third paragraph is even shorter.";
282
283 fn cur(y: usize, x: usize) -> Cur {
284 let y = if y == 0 {
285 0
286 } else {
287 EXAMPLE_TEXT
288 .lines()
289 .take(y)
290 .map(|line| line.len() + 1)
291 .sum()
292 };
293
294 Cur { idx: y + x }
295 }
296
297 fn c(y: usize, x: usize) -> Dot {
298 Dot::Cur { c: cur(y, x) }
299 }
300
301 fn r(y: usize, x: usize, y2: usize, x2: usize) -> Dot {
302 Dot::Range {
303 r: Range {
304 start: cur(y, x),
305 end: cur(y2, x2),
306 start_active: false,
307 },
308 }
309 }
310
311 #[test_case(BufferStart, c(0, 0); "buffer start")]
312 #[test_case(BufferEnd, c(9, 36); "buffer end")]
313 #[test_case(Character, c(5, 2); "character")]
314 #[test_case(Line, r(5, 0, 5, 43); "line")]
315 #[test_case(LineEnd, c(5, 43); "line end")]
316 #[test_case(LineStart, c(5, 0); "line start")]
317 #[test]
318 fn set_dot_works(to: TextObject, expected: Dot) {
319 let mut b = Buffer::new_virtual(0, "test", EXAMPLE_TEXT, Default::default());
320 b.dot = c(5, 1); to.set_dot(&mut b);
322
323 assert_eq!(b.dot, expected);
324 }
325
326 #[test_case(c(0, 0), "T"; "first character")]
327 #[test_case(r(0, 0, 0, 34), "This is the first line of the file."; "first sentence")]
328 #[test_case(
329 r(0, 0, 1, 18),
330 "This is the first line of the file. Followed\nby the second line.";
331 "spanning a newline"
332 )]
333 #[test]
334 fn dot_content_includes_expected_text(dot: Dot, expected: &str) {
335 let mut b = Buffer::new_virtual(0, "test", EXAMPLE_TEXT, Default::default());
336 b.dot = dot;
337 let content = b.dot_contents();
338
339 assert_eq!(content, expected);
340 }
341}