1use crate::doc::{Doc, Part};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
22pub struct Cursor {
23 pub block: usize,
24 pub part: Part,
25 pub offset: usize,
26}
27
28impl Cursor {
29 pub fn new(block: usize, part: Part, offset: usize) -> Self {
30 Self {
31 block,
32 part,
33 offset,
34 }
35 }
36
37 pub fn len_in(self, doc: &Doc) -> Option<usize> {
40 doc.blocks
41 .get(self.block)?
42 .text_at(self.part)
43 .map(|text| text.text.len())
44 }
45
46 pub fn clamp(self, doc: &Doc) -> Self {
49 if doc.blocks.is_empty() {
50 return Self::default();
51 }
52 let block = self.block.min(doc.blocks.len() - 1);
53 let part = match doc.blocks[block].text_at(self.part) {
57 Some(_) => self.part,
58 None => match doc.blocks[block].parts().first() {
59 Some(part) => *part,
60 None => return Self::new(block, Part::default(), 0),
61 },
62 };
63 let here = Self::new(block, part, self.offset);
64 let Some(text) = doc.blocks[block].text_at(part) else {
65 return here;
66 };
67 let mut offset = self.offset.min(text.text.len());
68 while offset > 0 && !text.text.is_char_boundary(offset) {
69 offset -= 1;
70 }
71 if let Some(mention) = text.mention_around(offset) {
72 offset = match offset - mention.start <= mention.end - offset {
73 true => mention.start,
74 false => mention.end,
75 };
76 }
77 Self { offset, ..here }
78 }
79
80 fn next_editable(doc: &Doc, from: usize) -> Option<Self> {
82 (from..doc.blocks.len()).find_map(|ix| {
83 doc.blocks[ix]
84 .parts()
85 .first()
86 .map(|part| Self::new(ix, *part, 0))
87 })
88 }
89
90 fn previous_editable(doc: &Doc, from: usize) -> Option<Self> {
93 (0..=from.min(doc.blocks.len().saturating_sub(1)))
94 .rev()
95 .find_map(|ix| {
96 let part = *doc.blocks[ix].parts().last()?;
97 let at = Self::new(ix, part, 0);
98 Some(Self {
99 offset: at.len_in(doc).unwrap_or(0),
100 ..at
101 })
102 })
103 }
104
105 fn step_part(self, doc: &Doc, by: isize) -> Option<Part> {
108 let parts = doc.blocks.get(self.block)?.parts();
109 let ix = parts.iter().position(|part| *part == self.part)?;
110 parts.get(ix.checked_add_signed(by)?).copied()
111 }
112
113 pub fn left(self, doc: &Doc) -> Self {
115 let here = self.clamp(doc);
116 if here.offset > 0
117 && let Some(text) = doc.blocks[here.block].text_at(here.part)
118 {
119 let mut offset = here.offset - 1;
120 while offset > 0 && !text.text.is_char_boundary(offset) {
121 offset -= 1;
122 }
123 let offset = text.mention_around(offset).map_or(offset, |m| m.start);
124 return Self { offset, ..here };
125 }
126 if let Some(part) = here.step_part(doc, -1) {
127 let at = Self { part, ..here };
128 return Self {
129 offset: at.len_in(doc).unwrap_or(0),
130 ..at
131 };
132 }
133 here.block
134 .checked_sub(1)
135 .and_then(|ix| Self::previous_editable(doc, ix))
136 .unwrap_or(here)
137 }
138
139 pub fn right(self, doc: &Doc) -> Self {
141 let here = self.clamp(doc);
142 let len = here.len_in(doc).unwrap_or(0);
143 if here.offset < len
144 && let Some(text) = doc.blocks[here.block].text_at(here.part)
145 {
146 let mut offset = here.offset + 1;
147 while offset < len && !text.text.is_char_boundary(offset) {
148 offset += 1;
149 }
150 let offset = text.mention_around(offset).map_or(offset, |m| m.end);
151 return Self { offset, ..here };
152 }
153 if let Some(part) = here.step_part(doc, 1) {
154 return Self {
155 part,
156 offset: 0,
157 ..here
158 };
159 }
160 Self::next_editable(doc, here.block + 1).unwrap_or(here)
161 }
162
163 pub fn up(self, doc: &Doc) -> Self {
170 let here = self.clamp(doc);
171 if let Part::Cell { row, column } = here.part
172 && row > 0
173 {
174 return Self {
175 part: Part::Cell {
176 row: row - 1,
177 column,
178 },
179 ..here
180 }
181 .clamp(doc);
182 }
183 match here
184 .block
185 .checked_sub(1)
186 .and_then(|ix| Self::previous_editable(doc, ix))
187 {
188 Some(above) => Self {
189 offset: here.offset,
190 ..above
191 }
192 .clamp(doc),
193 None => Self { offset: 0, ..here },
194 }
195 }
196
197 pub fn down(self, doc: &Doc) -> Self {
199 let here = self.clamp(doc);
200 if let Part::Cell { row, column } = here.part {
201 let below = Self {
202 part: Part::Cell {
203 row: row + 1,
204 column,
205 },
206 ..here
207 };
208 if below.len_in(doc).is_some() {
209 return below.clamp(doc);
210 }
211 }
212 match Self::next_editable(doc, here.block + 1) {
213 Some(below) => Self {
214 offset: here.offset,
215 ..below
216 }
217 .clamp(doc),
218 None => Self {
219 offset: here.len_in(doc).unwrap_or(0),
220 ..here
221 },
222 }
223 }
224
225 pub fn home(self) -> Self {
226 Self { offset: 0, ..self }
227 }
228
229 pub fn end(self, doc: &Doc) -> Self {
230 let here = self.clamp(doc);
231 Self {
232 offset: here.len_in(doc).unwrap_or(0),
233 ..here
234 }
235 }
236
237 pub fn word_left(self, doc: &Doc) -> Self {
242 let here = self.clamp(doc);
243 let Some(text) = doc.blocks[here.block].text_at(here.part) else {
244 return here.left(doc);
245 };
246 if here.offset == 0 {
247 return here.left(doc);
248 }
249 let head = &text.text[..here.offset];
250 let trimmed = head.trim_end_matches(|c: char| !c.is_alphanumeric());
251 let offset = trimmed.trim_end_matches(char::is_alphanumeric).len();
252 let offset = text.mention_around(offset).map_or(offset, |m| m.start);
253 Self { offset, ..here }
254 }
255
256 pub fn word_right(self, doc: &Doc) -> Self {
258 let here = self.clamp(doc);
259 let Some(text) = doc.blocks[here.block].text_at(here.part) else {
260 return here.right(doc);
261 };
262 if here.offset >= text.text.len() {
263 return here.right(doc);
264 }
265 let tail = &text.text[here.offset..];
266 let skipped = tail.len()
267 - tail
268 .trim_start_matches(|c: char| !c.is_alphanumeric())
269 .len();
270 let rest = &tail[skipped..];
271 let word = rest.len() - rest.trim_start_matches(char::is_alphanumeric).len();
272 let offset = here.offset + skipped + word;
273 Self {
274 offset: text.mention_around(offset).map_or(offset, |m| m.end),
275 ..here
276 }
277 }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
287pub struct Selection {
288 pub anchor: Cursor,
289 pub head: Cursor,
290}
291
292impl From<Cursor> for Selection {
293 fn from(cursor: Cursor) -> Self {
294 Self::at(cursor)
295 }
296}
297
298impl Selection {
299 pub fn at(cursor: Cursor) -> Self {
301 Self {
302 anchor: cursor,
303 head: cursor,
304 }
305 }
306
307 pub fn new(anchor: Cursor, head: Cursor) -> Self {
308 Self { anchor, head }
309 }
310
311 pub fn is_collapsed(&self) -> bool {
312 self.anchor == self.head
313 }
314
315 pub fn ordered(&self) -> (Cursor, Cursor) {
317 (self.anchor.min(self.head), self.anchor.max(self.head))
318 }
319
320 pub fn extend_to(self, head: Cursor) -> Self {
322 Self { head, ..self }
323 }
324
325 pub fn clamp(self, doc: &Doc) -> Self {
326 Self {
327 anchor: self.anchor.clamp(doc),
328 head: self.head.clamp(doc),
329 }
330 }
331
332 pub fn all(doc: &Doc) -> Self {
334 let start = Cursor::next_editable(doc, 0).unwrap_or_default();
335 let end =
336 Cursor::previous_editable(doc, doc.blocks.len().saturating_sub(1)).unwrap_or(start);
337 Self::new(start, end)
338 }
339}