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 Self { offset, ..here }
72 }
73
74 fn next_editable(doc: &Doc, from: usize) -> Option<Self> {
76 (from..doc.blocks.len()).find_map(|ix| {
77 doc.blocks[ix]
78 .parts()
79 .first()
80 .map(|part| Self::new(ix, *part, 0))
81 })
82 }
83
84 fn previous_editable(doc: &Doc, from: usize) -> Option<Self> {
87 (0..=from.min(doc.blocks.len().saturating_sub(1)))
88 .rev()
89 .find_map(|ix| {
90 let part = *doc.blocks[ix].parts().last()?;
91 let at = Self::new(ix, part, 0);
92 Some(Self {
93 offset: at.len_in(doc).unwrap_or(0),
94 ..at
95 })
96 })
97 }
98
99 fn step_part(self, doc: &Doc, by: isize) -> Option<Part> {
102 let parts = doc.blocks.get(self.block)?.parts();
103 let ix = parts.iter().position(|part| *part == self.part)?;
104 parts.get(ix.checked_add_signed(by)?).copied()
105 }
106
107 pub fn left(self, doc: &Doc) -> Self {
109 let here = self.clamp(doc);
110 if here.offset > 0
111 && let Some(text) = doc.blocks[here.block].text_at(here.part)
112 {
113 let mut offset = here.offset - 1;
114 while offset > 0 && !text.text.is_char_boundary(offset) {
115 offset -= 1;
116 }
117 return Self { offset, ..here };
118 }
119 if let Some(part) = here.step_part(doc, -1) {
120 let at = Self { part, ..here };
121 return Self {
122 offset: at.len_in(doc).unwrap_or(0),
123 ..at
124 };
125 }
126 here.block
127 .checked_sub(1)
128 .and_then(|ix| Self::previous_editable(doc, ix))
129 .unwrap_or(here)
130 }
131
132 pub fn right(self, doc: &Doc) -> Self {
134 let here = self.clamp(doc);
135 let len = here.len_in(doc).unwrap_or(0);
136 if here.offset < len
137 && let Some(text) = doc.blocks[here.block].text_at(here.part)
138 {
139 let mut offset = here.offset + 1;
140 while offset < len && !text.text.is_char_boundary(offset) {
141 offset += 1;
142 }
143 return Self { offset, ..here };
144 }
145 if let Some(part) = here.step_part(doc, 1) {
146 return Self {
147 part,
148 offset: 0,
149 ..here
150 };
151 }
152 Self::next_editable(doc, here.block + 1).unwrap_or(here)
153 }
154
155 pub fn up(self, doc: &Doc) -> Self {
162 let here = self.clamp(doc);
163 if let Part::Cell { row, column } = here.part
164 && row > 0
165 {
166 return Self {
167 part: Part::Cell {
168 row: row - 1,
169 column,
170 },
171 ..here
172 }
173 .clamp(doc);
174 }
175 match here
176 .block
177 .checked_sub(1)
178 .and_then(|ix| Self::previous_editable(doc, ix))
179 {
180 Some(above) => Self {
181 offset: here.offset,
182 ..above
183 }
184 .clamp(doc),
185 None => Self { offset: 0, ..here },
186 }
187 }
188
189 pub fn down(self, doc: &Doc) -> Self {
191 let here = self.clamp(doc);
192 if let Part::Cell { row, column } = here.part {
193 let below = Self {
194 part: Part::Cell {
195 row: row + 1,
196 column,
197 },
198 ..here
199 };
200 if below.len_in(doc).is_some() {
201 return below.clamp(doc);
202 }
203 }
204 match Self::next_editable(doc, here.block + 1) {
205 Some(below) => Self {
206 offset: here.offset,
207 ..below
208 }
209 .clamp(doc),
210 None => Self {
211 offset: here.len_in(doc).unwrap_or(0),
212 ..here
213 },
214 }
215 }
216
217 pub fn home(self) -> Self {
218 Self { offset: 0, ..self }
219 }
220
221 pub fn end(self, doc: &Doc) -> Self {
222 let here = self.clamp(doc);
223 Self {
224 offset: here.len_in(doc).unwrap_or(0),
225 ..here
226 }
227 }
228
229 pub fn word_left(self, doc: &Doc) -> Self {
234 let here = self.clamp(doc);
235 let Some(text) = doc.blocks[here.block].text_at(here.part) else {
236 return here.left(doc);
237 };
238 if here.offset == 0 {
239 return here.left(doc);
240 }
241 let head = &text.text[..here.offset];
242 let trimmed = head.trim_end_matches(|c: char| !c.is_alphanumeric());
243 let offset = trimmed.trim_end_matches(char::is_alphanumeric).len();
244 Self { offset, ..here }
245 }
246
247 pub fn word_right(self, doc: &Doc) -> Self {
249 let here = self.clamp(doc);
250 let Some(text) = doc.blocks[here.block].text_at(here.part) else {
251 return here.right(doc);
252 };
253 if here.offset >= text.text.len() {
254 return here.right(doc);
255 }
256 let tail = &text.text[here.offset..];
257 let skipped = tail.len()
258 - tail
259 .trim_start_matches(|c: char| !c.is_alphanumeric())
260 .len();
261 let rest = &tail[skipped..];
262 let word = rest.len() - rest.trim_start_matches(char::is_alphanumeric).len();
263 Self {
264 offset: here.offset + skipped + word,
265 ..here
266 }
267 }
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
277pub struct Selection {
278 pub anchor: Cursor,
279 pub head: Cursor,
280}
281
282impl Selection {
283 pub fn at(cursor: Cursor) -> Self {
285 Self {
286 anchor: cursor,
287 head: cursor,
288 }
289 }
290
291 pub fn new(anchor: Cursor, head: Cursor) -> Self {
292 Self { anchor, head }
293 }
294
295 pub fn is_collapsed(&self) -> bool {
296 self.anchor == self.head
297 }
298
299 pub fn ordered(&self) -> (Cursor, Cursor) {
301 (self.anchor.min(self.head), self.anchor.max(self.head))
302 }
303
304 pub fn extend_to(self, head: Cursor) -> Self {
306 Self { head, ..self }
307 }
308
309 pub fn clamp(self, doc: &Doc) -> Self {
310 Self {
311 anchor: self.anchor.clamp(doc),
312 head: self.head.clamp(doc),
313 }
314 }
315
316 pub fn all(doc: &Doc) -> Self {
318 let start = Cursor::next_editable(doc, 0).unwrap_or_default();
319 let end =
320 Cursor::previous_editable(doc, doc.blocks.len().saturating_sub(1)).unwrap_or(start);
321 Self::new(start, end)
322 }
323}