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