1use crate::buffer::Buffer;
11use std::cmp::min;
12
13mod cur;
14pub(crate) mod find;
15mod range;
16mod text_object;
17
18pub(crate) use cur::Cur;
19pub(crate) use range::{LineRange, Range};
20pub(crate) 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 addr(&self, b: &Buffer) -> String {
120 match self {
121 Self::Cur { c } => c.as_string_addr(b),
122 Self::Range { r } => r.as_string_addr(b),
123 }
124 }
125
126 pub fn content(&self, b: &Buffer) -> String {
128 let len_chars = b.txt.len_chars();
129
130 if len_chars == 0 {
131 return String::new();
132 }
133
134 let (from, to) = self.as_char_indices();
135 b.txt.slice(from, min(to + 1, len_chars)).to_string()
136 }
137
138 #[inline]
140 pub fn active_cur(&self) -> Cur {
141 match self {
142 Self::Cur { c } => *c,
143 Self::Range { r } => r.active_cursor(),
144 }
145 }
146
147 pub fn set_active_cur(&mut self, cur: Cur) {
150 match self {
151 Self::Cur { c } => *c = cur,
152 Self::Range { r } => r.set_active_cursor(cur),
153 }
154 }
155
156 #[inline]
158 pub fn first_cur(&self) -> Cur {
159 match self {
160 Self::Cur { c } => *c,
161 Self::Range { r } => r.start,
162 }
163 }
164
165 #[inline]
167 pub fn last_cur(&self) -> Cur {
168 match self {
169 Self::Cur { c } => *c,
170 Self::Range { r } => r.end,
171 }
172 }
173
174 #[inline]
179 pub fn as_range(&self) -> Range {
180 match self {
181 Self::Cur { c } => Range {
182 start: *c,
183 end: *c,
184 start_active: false,
185 },
186 Self::Range { r } => *r,
187 }
188 }
189
190 #[inline]
192 pub fn collapse_to_first_cur(&self) -> Self {
193 Dot::Cur {
194 c: self.first_cur(),
195 }
196 }
197
198 #[inline]
200 pub fn collapse_to_last_cur(&self) -> Self {
201 Dot::Cur { c: self.last_cur() }
202 }
203
204 #[inline]
206 pub fn flip(&mut self) {
207 if let Dot::Range { r } = self {
208 r.flip();
209 }
210 }
211
212 pub(crate) fn line_range(&self, y: usize, b: &Buffer) -> Option<LineRange> {
213 match self {
214 Dot::Cur { .. } => None,
215 Dot::Range { r } => r.line_range(y, b),
216 }
217 }
218
219 pub(crate) fn collapse_null_range(self) -> Self {
221 match self {
222 Dot::Range {
223 r: Range { start, end, .. },
224 } if start == end => Dot::Cur { c: start },
225 _ => self,
226 }
227 }
228
229 pub(crate) fn clamp_idx(&mut self, max_idx: usize) {
231 match self {
232 Dot::Cur { c } => c.clamp_idx(max_idx),
233 Dot::Range { r } => {
234 r.start.clamp_idx(max_idx);
235 r.end.clamp_idx(max_idx);
236 }
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::{
244 text_object::TextObject::{self, *},
245 *,
246 };
247 use simple_test_case::test_case;
248
249 const EXAMPLE_TEXT: &str = "\
250This is the first line of the file. Followed
251by the second line. Some of the sentences are split
252over multiple lines.
253Others are not.
254
255There is a second paragraph as well. But it
256is quite short when compared to the first.
257
258
259The third paragraph is even shorter.";
260
261 fn cur(y: usize, x: usize) -> Cur {
262 let y = if y == 0 {
263 0
264 } else {
265 EXAMPLE_TEXT
266 .lines()
267 .take(y)
268 .map(|line| line.len() + 1)
269 .sum()
270 };
271
272 Cur { idx: y + x }
273 }
274
275 fn c(y: usize, x: usize) -> Dot {
276 Dot::Cur { c: cur(y, x) }
277 }
278
279 fn r(y: usize, x: usize, y2: usize, x2: usize) -> Dot {
280 Dot::Range {
281 r: Range {
282 start: cur(y, x),
283 end: cur(y2, x2),
284 start_active: false,
285 },
286 }
287 }
288
289 #[test_case(BufferStart, c(0, 0); "buffer start")]
290 #[test_case(BufferEnd, c(9, 36); "buffer end")]
291 #[test_case(Character, c(5, 2); "character")]
292 #[test_case(Line, r(5, 0, 5, 43); "line")]
293 #[test_case(LineEnd, c(5, 43); "line end")]
294 #[test_case(LineStart, c(5, 0); "line start")]
295 #[test]
296 fn set_dot_works(to: TextObject, expected: Dot) {
297 let mut b = Buffer::new_virtual(0, "test".to_string(), EXAMPLE_TEXT.to_string());
298 b.dot = c(5, 1); to.set_dot(&mut b);
300
301 assert_eq!(b.dot, expected);
302 }
303
304 #[test_case(c(0, 0), "T"; "first character")]
305 #[test_case(r(0, 0, 0, 34), "This is the first line of the file."; "first sentence")]
306 #[test_case(
307 r(0, 0, 1, 18),
308 "This is the first line of the file. Followed\nby the second line.";
309 "spanning a newline"
310 )]
311 #[test]
312 fn dot_content_includes_expected_text(dot: Dot, expected: &str) {
313 let mut b = Buffer::new_virtual(0, "test".to_string(), EXAMPLE_TEXT.to_string());
314 b.dot = dot;
315 let content = b.dot_contents();
316
317 assert_eq!(content, expected);
318 }
319}