1use std::ops::Range;
16
17use ui::input::Granularity;
18
19use crate::doc::{Doc, Part};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
26pub struct Cursor {
27 pub block: usize,
28 pub part: Part,
29 pub offset: usize,
30}
31
32impl Cursor {
33 pub fn new(block: usize, part: Part, offset: usize) -> Self {
34 Self {
35 block,
36 part,
37 offset,
38 }
39 }
40
41 pub fn len_in(self, doc: &Doc) -> Option<usize> {
44 doc.blocks
45 .get(self.block)?
46 .text_at(self.part)
47 .map(|text| text.text.len())
48 }
49
50 pub fn clamp(self, doc: &Doc) -> Self {
53 if doc.blocks.is_empty() {
54 return Self::default();
55 }
56 let block = self.block.min(doc.blocks.len() - 1);
57 let part = match doc.blocks[block].text_at(self.part) {
61 Some(_) => self.part,
62 None => match doc.blocks[block].parts().first() {
63 Some(part) => *part,
64 None => return Self::new(block, Part::default(), 0),
65 },
66 };
67 let here = Self::new(block, part, self.offset);
68 let Some(text) = doc.blocks[block].text_at(part) else {
69 return here;
70 };
71 let mut offset = self.offset.min(text.text.len());
72 while offset > 0 && !text.text.is_char_boundary(offset) {
73 offset -= 1;
74 }
75 if let Some(mention) = text.mention_around(offset) {
76 offset = match offset - mention.start <= mention.end - offset {
77 true => mention.start,
78 false => mention.end,
79 };
80 }
81 Self { offset, ..here }
82 }
83
84 fn next_editable(doc: &Doc, from: usize) -> Option<Self> {
86 (from..doc.blocks.len()).find_map(|ix| {
87 doc.blocks[ix]
88 .parts()
89 .first()
90 .map(|part| Self::new(ix, *part, 0))
91 })
92 }
93
94 fn previous_editable(doc: &Doc, from: usize) -> Option<Self> {
97 (0..=from.min(doc.blocks.len().saturating_sub(1)))
98 .rev()
99 .find_map(|ix| {
100 let part = *doc.blocks[ix].parts().last()?;
101 let at = Self::new(ix, part, 0);
102 Some(Self {
103 offset: at.len_in(doc).unwrap_or(0),
104 ..at
105 })
106 })
107 }
108
109 fn step_part(self, doc: &Doc, by: isize) -> Option<Part> {
112 let parts = doc.blocks.get(self.block)?.parts();
113 let ix = parts.iter().position(|part| *part == self.part)?;
114 parts.get(ix.checked_add_signed(by)?).copied()
115 }
116
117 pub fn left(self, doc: &Doc) -> Self {
119 let here = self.clamp(doc);
120 if here.offset > 0
121 && let Some(text) = doc.blocks[here.block].text_at(here.part)
122 {
123 let mut offset = here.offset - 1;
124 while offset > 0 && !text.text.is_char_boundary(offset) {
125 offset -= 1;
126 }
127 let offset = text.mention_around(offset).map_or(offset, |m| m.start);
128 return Self { offset, ..here };
129 }
130 if let Some(part) = here.step_part(doc, -1) {
131 let at = Self { part, ..here };
132 return Self {
133 offset: at.len_in(doc).unwrap_or(0),
134 ..at
135 };
136 }
137 here.block
138 .checked_sub(1)
139 .and_then(|ix| Self::previous_editable(doc, ix))
140 .unwrap_or(here)
141 }
142
143 pub fn right(self, doc: &Doc) -> Self {
145 let here = self.clamp(doc);
146 let len = here.len_in(doc).unwrap_or(0);
147 if here.offset < len
148 && let Some(text) = doc.blocks[here.block].text_at(here.part)
149 {
150 let mut offset = here.offset + 1;
151 while offset < len && !text.text.is_char_boundary(offset) {
152 offset += 1;
153 }
154 let offset = text.mention_around(offset).map_or(offset, |m| m.end);
155 return Self { offset, ..here };
156 }
157 if let Some(part) = here.step_part(doc, 1) {
158 return Self {
159 part,
160 offset: 0,
161 ..here
162 };
163 }
164 Self::next_editable(doc, here.block + 1).unwrap_or(here)
165 }
166
167 pub fn up(self, doc: &Doc) -> Self {
174 let here = self.clamp(doc);
175 if let Part::Cell { row, column } = here.part
176 && row > 0
177 {
178 return Self {
179 part: Part::Cell {
180 row: row - 1,
181 column,
182 },
183 ..here
184 }
185 .clamp(doc);
186 }
187 match here
188 .block
189 .checked_sub(1)
190 .and_then(|ix| Self::previous_editable(doc, ix))
191 {
192 Some(above) => Self {
193 offset: here.offset,
194 ..above
195 }
196 .clamp(doc),
197 None => Self { offset: 0, ..here },
198 }
199 }
200
201 pub fn down(self, doc: &Doc) -> Self {
203 let here = self.clamp(doc);
204 if let Part::Cell { row, column } = here.part {
205 let below = Self {
206 part: Part::Cell {
207 row: row + 1,
208 column,
209 },
210 ..here
211 };
212 if below.len_in(doc).is_some() {
213 return below.clamp(doc);
214 }
215 }
216 match Self::next_editable(doc, here.block + 1) {
217 Some(below) => Self {
218 offset: here.offset,
219 ..below
220 }
221 .clamp(doc),
222 None => Self {
223 offset: here.len_in(doc).unwrap_or(0),
224 ..here
225 },
226 }
227 }
228
229 pub fn home(self) -> Self {
230 Self { offset: 0, ..self }
231 }
232
233 pub fn end(self, doc: &Doc) -> Self {
234 let here = self.clamp(doc);
235 Self {
236 offset: here.len_in(doc).unwrap_or(0),
237 ..here
238 }
239 }
240
241 pub fn word_left(self, doc: &Doc) -> Self {
246 let here = self.clamp(doc);
247 let Some(text) = doc.blocks[here.block].text_at(here.part) else {
248 return here.left(doc);
249 };
250 if here.offset == 0 {
251 return here.left(doc);
252 }
253 let head = &text.text[..here.offset];
254 let trimmed = head.trim_end_matches(|c: char| !c.is_alphanumeric());
255 let offset = trimmed.trim_end_matches(char::is_alphanumeric).len();
256 let offset = text.mention_around(offset).map_or(offset, |m| m.start);
257 Self { offset, ..here }
258 }
259
260 pub fn word_right(self, doc: &Doc) -> Self {
262 let here = self.clamp(doc);
263 let Some(text) = doc.blocks[here.block].text_at(here.part) else {
264 return here.right(doc);
265 };
266 if here.offset >= text.text.len() {
267 return here.right(doc);
268 }
269 let tail = &text.text[here.offset..];
270 let skipped = tail.len()
271 - tail
272 .trim_start_matches(|c: char| !c.is_alphanumeric())
273 .len();
274 let rest = &tail[skipped..];
275 let word = rest.len() - rest.trim_start_matches(char::is_alphanumeric).len();
276 let offset = here.offset + skipped + word;
277 Self {
278 offset: text.mention_around(offset).map_or(offset, |m| m.end),
279 ..here
280 }
281 }
282
283 pub fn span(self, unit: Granularity, doc: &Doc) -> Range<Self> {
287 let here = self.clamp(doc);
288 let Some(text) = doc
289 .blocks
290 .get(here.block)
291 .and_then(|b| b.text_at(here.part))
292 else {
293 return here..here;
294 };
295 let (start, end) = match unit {
296 Granularity::Char => return here..here,
297 Granularity::Line => (
298 ui::input::line_start(&text.text, here.offset),
299 ui::input::line_end(&text.text, here.offset),
300 ),
301 Granularity::Word => {
302 let (head, tail) = text.text.split_at(here.offset);
303 let word = head.chars().next_back().is_some_and(char::is_alphanumeric)
304 || tail.chars().next().is_some_and(char::is_alphanumeric);
305 let same = |c: &char| c.is_alphanumeric() == word;
306 let back: usize = head
307 .chars()
308 .rev()
309 .take_while(same)
310 .map(char::len_utf8)
311 .sum();
312 let ahead: usize = tail.chars().take_while(same).map(char::len_utf8).sum();
313 (here.offset - back, here.offset + ahead)
314 }
315 };
316 let start = text.mention_around(start).map_or(start, |m| m.start);
317 let end = text.mention_around(end).map_or(end, |m| m.end);
318 Self {
319 offset: start,
320 ..here
321 }..Self {
322 offset: end,
323 ..here
324 }
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
332pub enum Affinity {
333 #[default]
335 Downstream,
336 Upstream,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
347pub struct Selection {
348 pub anchor: Cursor,
349 pub head: Cursor,
350 pub affinity: Affinity,
352}
353
354impl From<Cursor> for Selection {
355 fn from(cursor: Cursor) -> Self {
356 Self::at(cursor)
357 }
358}
359
360impl Selection {
361 pub fn at(cursor: Cursor) -> Self {
363 Self::new(cursor, cursor)
364 }
365
366 pub fn new(anchor: Cursor, head: Cursor) -> Self {
367 Self {
368 anchor,
369 head,
370 affinity: Affinity::Downstream,
371 }
372 }
373
374 pub fn with_affinity(self, affinity: Affinity) -> Self {
375 Self { affinity, ..self }
376 }
377
378 pub fn is_collapsed(&self) -> bool {
379 self.anchor == self.head
380 }
381
382 pub fn ordered(&self) -> (Cursor, Cursor) {
384 (self.anchor.min(self.head), self.anchor.max(self.head))
385 }
386
387 pub fn extend_to(self, head: Cursor) -> Self {
389 Self::new(self.anchor, head)
390 }
391
392 pub fn clamp(self, doc: &Doc) -> Self {
393 Self {
394 anchor: self.anchor.clamp(doc),
395 head: self.head.clamp(doc),
396 ..self
397 }
398 }
399
400 pub fn all(doc: &Doc) -> Self {
402 let start = Cursor::next_editable(doc, 0).unwrap_or_default();
403 let end =
404 Cursor::previous_editable(doc, doc.blocks.len().saturating_sub(1)).unwrap_or(start);
405 Self::new(start, end)
406 }
407}