duat_core/mode/cursor/mod.rs
1//! A helper struct for [`Mode`]s with [`Selections`]
2//!
3//! This struct can edit [`Text`] in a declarative way, freeing the
4//! [`Mode`]s from worrying about synchronization of the
5//! selections and dealing with editing the text directly.
6//!
7//! [`Mode`]: super::Mode
8use std::{
9 cell::Cell,
10 ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
11 rc::Rc,
12};
13
14use lender::{Lender, Lending};
15
16pub use self::selections::{Selection, Selections, VPoint};
17use crate::{
18 buffer::{Buffer, BufferId, Change},
19 opts::PrintOpts,
20 text::{Lines, Matches, Point, RegexHaystack, RegexPattern, Strs, Text, TextIndex, TextRange},
21 ui::{Area, Widget},
22};
23
24/// The [`Selection`] and [`Selections`] structs
25mod selections;
26
27macro_rules! sel {
28 ($cursor:expr) => {
29 $cursor.selections[$cursor.sels_i]
30 .as_ref()
31 .unwrap()
32 .selection
33 };
34}
35
36macro_rules! sel_mut {
37 ($cursor:expr) => {{
38 let mod_sel = $cursor.selections[$cursor.sels_i].as_mut().unwrap();
39 mod_sel.has_changed = true;
40 &mut mod_sel.selection
41 }};
42}
43
44/// A selection that can edit [`Text`], but can't alter selections
45///
46/// This struct will be used only inside functions passed to the
47/// [`edit_*`] family of methods from the [`Handle`].
48///
49/// To make edits, you can use three different functions. You can,
50/// those being [`replace`], [`insert`], and [`append`]. [`replace`]
51/// will completely replace the [`Selection`]'s selection. [`insert`]
52/// will place text behind the `caret`, and [`append`] will place it
53/// after the `caret`.
54///
55/// You can also move the [`Selection`]'s selection in many different
56/// ways, which are described below, in the `impl` section for this
57/// struct.
58///
59/// ```rust
60/// # duat_core::doc_duat!(duat);
61/// # use duat::prelude::*;
62/// # fn test(mut pa: Pass, handle: &mut Handle) {
63/// let sel = handle.edit_main(&mut pa, |mut c| {
64/// c.set_anchor();
65/// c.set_caret_on_end();
66/// c.replace("my replacement");
67/// c.append(" and my edit");
68///
69/// c.swap_ends();
70/// c.insert("This is ");
71/// c.swap_ends();
72///
73/// c.move_hor(" and my edit".chars().count() as i32);
74/// c.set_anchor();
75/// c.move_hor(-("This is my replacement and my edit".chars().count() as i32));
76/// c.selection().to_string()
77/// });
78///
79/// assert_eq!(&sel, "This is my replacement and my edit");
80/// # }
81/// ```
82///
83/// [`edit_*`]: crate::context::Handle::edit_nth
84/// [`Handle`]: crate::context::Handle
85/// [`replace`]: Cursor::replace
86/// [`insert`]: Cursor::insert
87/// [`append`]: Cursor::append
88pub struct Cursor<'c, W: Widget + ?Sized = crate::buffer::Buffer> {
89 selections: &'c mut Vec<Option<ModSelection>>,
90 sels_i: usize,
91 initial: Selection,
92 widget: &'c mut W,
93 area: &'c Area,
94 next_i: Option<Rc<Cell<usize>>>,
95}
96
97impl<'c, W: Widget + ?Sized> Cursor<'c, W> {
98 /// Returns a new instance of [`Cursor`]
99 pub(crate) fn new(
100 selections: &'c mut Vec<Option<ModSelection>>,
101 sels_i: usize,
102 (widget, area): (&'c mut W, &'c Area),
103 next_i: Option<Rc<Cell<usize>>>,
104 ) -> Self {
105 let initial = selections[sels_i].as_ref().unwrap().selection.clone();
106 Self {
107 selections,
108 sels_i,
109 initial,
110 widget,
111 area,
112 next_i,
113 }
114 }
115
116 ////////// Text editing
117
118 /// Replaces the entire selection with new text
119 ///
120 /// If there is a selection, then it is treated as _inclusive_,
121 /// therefore, a selection where `caret == anchor` will remove the
122 /// character where the caret is. If there is no selection, then
123 /// this has the same effect as [`insert`]. If you wish to
124 /// append to the `caret` instead, see [`append`].
125 ///
126 /// After replacing the sele tion, if the `caret` is behind the
127 /// `anchor` (or in the same spot), it will be placed on the start
128 /// of the selection, while the `anchor` will be placed on the
129 /// new end. If it is ahead, it will be placed ahead.
130 ///
131 /// [`insert`]: Self::insert
132 /// [`append`]: Self::append
133 pub fn replace(&mut self, edit: impl ToString) {
134 let change = {
135 let edit = edit.to_string();
136 let range = sel!(self).point_range(self.widget.text());
137 let (p0, p1) = (range.start, range.end);
138 let p1 = if self.anchor().is_some() { p1 } else { p0 };
139 Change::new(edit, p0..p1, self.widget.text())
140 };
141
142 // Disconsider null changes.
143 if change.added_str().len() < 10 && change.added_str() == change.taken_str() {
144 return;
145 }
146
147 let (start, end) = (change.start(), change.added_end());
148
149 self.edit(change.clone());
150
151 let anchor_was_on_start = self.anchor_is_start();
152 self.move_to(start..end);
153 if !anchor_was_on_start {
154 self.set_caret_on_start();
155 }
156 }
157
158 /// Inserts new text directly behind the `caret`
159 ///
160 /// If the `anchor` is ahead of the `caret`, it will move forwards
161 /// by the number of chars in the new text.
162 ///
163 /// If you wish to replace the selected text, see [`replace`], if
164 /// you want to append after the `caret` instead, see [`append`]
165 ///
166 /// [`replace`]: Self::replace
167 /// [`append`]: Self::append
168 pub fn insert(&mut self, edit: impl ToString) {
169 let caret_point = sel!(self).caret();
170 let range = caret_point..caret_point;
171 let change = Change::new(edit.to_string(), range, self.widget.text());
172 let (added, taken) = (change.added_end(), change.taken_end());
173
174 self.edit(change);
175
176 if let Some(anchor) = sel!(self).anchor()
177 && anchor > sel!(self).caret()
178 {
179 let new_anchor = anchor + added - taken;
180 sel_mut!(self).swap_ends();
181 sel_mut!(self).move_to(new_anchor, self.widget.text());
182 sel_mut!(self).swap_ends();
183 }
184 }
185
186 /// Appends new text directly after the `caret`
187 ///
188 /// If the `anchor` is ahead of the `caret`, it will move forwards
189 /// by the number of chars in the new text.
190 ///
191 /// If you wish to replace the selected text, see [`replace`], if
192 /// you want to insert before the `caret` instead, see [`insert`]
193 ///
194 /// [`replace`]: Self::replace
195 /// [`insert`]: Self::insert
196 pub fn append(&mut self, edit: impl ToString) {
197 let caret = sel!(self).caret();
198 let after = caret.fwd(self.widget.text().char_at(caret).unwrap());
199 let change = Change::new(edit.to_string(), after..after, self.widget.text());
200 let (added, taken) = (change.added_end(), change.taken_end());
201
202 self.edit(change);
203
204 if let Some(anchor) = sel!(self).anchor()
205 && anchor > after
206 {
207 let new_anchor = anchor + added - taken;
208 sel_mut!(self).swap_ends();
209 sel_mut!(self).move_to(new_anchor, self.widget.text());
210 sel_mut!(self).swap_ends();
211 }
212 }
213
214 /// Edits the buffer with a [`Change`]
215 fn edit(&mut self, change: Change<'static, String>) {
216 let mut text = self.widget.text_mut();
217 let (change_i, selections_taken) =
218 text.apply_change(sel!(self).change_i.map(|i| i as usize), change);
219 sel_mut!(self).change_i = change_i.map(|i| i as u32);
220
221 // The Change may have happened before the index of the next curossr,
222 // so we need to account for that.
223 if let Some(change_i) = change_i
224 && let Some(next_i) = self.next_i.as_ref()
225 && change_i <= next_i.get()
226 {
227 next_i.set(next_i.get().saturating_sub(selections_taken));
228 }
229 }
230
231 ////////// Movement functions
232
233 /// Moves the selection horizontally. May cause vertical movement
234 ///
235 /// Returns the distance traveled, in character indices
236 #[track_caller]
237 pub fn move_hor(&mut self, by: i32) -> i32 {
238 if by == 0 {
239 return 0;
240 }
241 let moved = sel_mut!(self).move_hor(by, self.widget.text());
242 if moved != 0 {
243 self.widget.text_mut().add_record_for(sel!(self).caret());
244 }
245 moved
246 }
247
248 /// Moves the selection vertically. May cause horizontal movement
249 ///
250 /// Returns the distance moved in lines.
251 #[track_caller]
252 pub fn move_ver(&mut self, by: i32) -> i32 {
253 if by == 0 {
254 return 0;
255 }
256 let line_count = sel_mut!(self).move_ver(
257 by,
258 self.widget.text(),
259 self.area,
260 self.widget.get_print_opts(),
261 );
262 if line_count != 0 {
263 self.widget.text_mut().add_record_for(sel!(self).caret());
264 }
265 line_count
266 }
267
268 /// Moves the selection vertically a number of wrapped lines. May
269 /// cause horizontal movement
270 ///
271 /// Returns the distance moved in wrapped lines.
272 #[track_caller]
273 pub fn move_ver_wrapped(&mut self, count: i32) {
274 if count == 0 {
275 return;
276 }
277 sel_mut!(self).move_ver_wrapped(
278 count,
279 self.widget.text(),
280 self.area,
281 self.widget.get_print_opts(),
282 );
283 }
284
285 /// Moves the selection to a [`Point`] or a [range] of [`Point`]s
286 ///
287 /// If you give it just a [`Point`], it will move the caret,
288 /// without affecting the anchor. If you give it a [range] of
289 /// [`Point`]s, the anchor will be placed at the start, while the
290 /// caret will be placed at the end of said [range]. You can flip
291 /// those positions with a function like [`swap_ends`].
292 ///
293 /// If a [`Point`] is not valid, it will be corrected and clamped
294 /// to the lenght of the [`Text`].
295 ///
296 /// [range]: std::ops::RangeBounds
297 /// [`swap_ends`]: Self::swap_ends
298 #[track_caller]
299 pub fn move_to(&mut self, point_or_points: impl CaretOrRange) {
300 point_or_points.move_to(self);
301 for point in [Some(sel!(self).caret()), sel!(self).anchor()]
302 .into_iter()
303 .flatten()
304 {
305 self.widget.text_mut().add_record_for(point);
306 }
307 }
308
309 /// Moves the selection to [`Point::default`], i.c., the start of
310 /// the [`Text`]
311 #[track_caller]
312 pub fn move_to_start(&mut self) {
313 sel_mut!(self).move_to(Point::default(), self.widget.text());
314 }
315
316 /// Moves the selection to a `line` and a `column`
317 ///
318 /// - If the coords isn't valid, it will move to the "maximum"
319 /// position allowed.
320 #[track_caller]
321 pub fn move_to_coords(&mut self, line: usize, col: usize) {
322 let range = self
323 .text()
324 .line_range(line.min(self.text().last_point().line()));
325 let byte = self
326 .text()
327 .chars_fwd(range.clone())
328 .unwrap()
329 .map(|(byte, _)| byte)
330 .take(col + 1)
331 .last();
332 self.move_to(byte.unwrap_or(range.end.byte() - 1));
333 }
334
335 /// Moves to a column on the current line
336 #[track_caller]
337 pub fn move_to_col(&mut self, col: usize) {
338 let line = self.text().point_at_line(self.caret().line()).line();
339 self.move_to_coords(line, col);
340 }
341
342 /// Returns and takes the anchor of the [`Selection`], if there
343 /// was one
344 pub fn unset_anchor(&mut self) -> Option<Point> {
345 sel_mut!(self).unset_anchor()
346 }
347
348 /// Sets the `anchor` to the current `caret`
349 pub fn set_anchor(&mut self) {
350 sel_mut!(self).set_anchor()
351 }
352
353 /// Sets the `anchor` if it was not already set
354 ///
355 /// Returns `true` if the anchor was set by this command.
356 pub fn set_anchor_if_needed(&mut self) -> bool {
357 if self.anchor().is_none() {
358 sel_mut!(self).set_anchor();
359 true
360 } else {
361 false
362 }
363 }
364
365 /// Swaps the position of the `caret` and `anchor`
366 pub fn swap_ends(&mut self) {
367 sel_mut!(self).swap_ends();
368 }
369
370 /// Sets the caret of the [`Selection`] on the start of the
371 /// selection
372 ///
373 /// Returns `true` if a swap occurred
374 pub fn set_caret_on_start(&mut self) -> bool {
375 if let Some(anchor) = self.anchor()
376 && anchor < self.caret()
377 {
378 self.swap_ends();
379 true
380 } else {
381 false
382 }
383 }
384
385 /// Sets the caret of the [`Selection`] on the end of the
386 /// selection
387 ///
388 /// Returns `true` if a swap occurred
389 pub fn set_caret_on_end(&mut self) -> bool {
390 if let Some(anchor) = self.anchor()
391 && anchor > self.caret()
392 {
393 self.swap_ends();
394 true
395 } else {
396 false
397 }
398 }
399
400 ////////// Selection meta manipulation
401
402 /// Resets the [`Selection`] to how it was before being modified
403 pub fn reset(&mut self) {
404 *sel_mut!(self) = self.initial.clone();
405 }
406
407 /// Copies the current [`Selection`] in place
408 ///
409 /// This will leave an additional [`Selection`] with the current
410 /// selection. Do note that normal intersection rules apply, so if
411 /// at the end of the movement, this selection intersects with any
412 /// other, they will be merged into one.
413 ///
414 /// When this [`Cursor`] is dropped, like with normal [`Cursor`]s,
415 /// its [`Selection`] will be added to the [`Selections`], unless
416 /// you [destroy] it.
417 ///
418 /// [destroy]: Self::destroy
419 pub fn copy(&mut self) -> Cursor<'_, W> {
420 let copy = self.selections[self.sels_i].clone().unwrap();
421 self.selections
422 .push(Some(ModSelection { was_main: false, ..copy }));
423
424 let sels_i = self.selections.len() - 1;
425 Cursor::new(
426 self.selections,
427 sels_i,
428 (self.widget, self.area),
429 self.next_i.clone(),
430 )
431 }
432
433 /// Destroys the current [`Selection`]
434 ///
435 /// Will not destroy it if it is the last [`Selection`] left
436 ///
437 /// If this was the main selection, the main selection will now be
438 /// the selection immediately behind it.
439 pub fn destroy(self) {
440 // If there are other Selections in the list, or other copies still
441 // lying around, the Cursor Selection can be destroyed.
442 if self.widget.text().selections().is_empty()
443 && self.selections.iter().flatten().count() <= 1
444 {
445 return;
446 }
447
448 if self.selections[self.sels_i].as_ref().unwrap().was_main {
449 self.widget.text_mut().selections_mut().rotate_main(-1);
450 }
451
452 self.selections[self.sels_i] = None;
453 }
454
455 /// Sets the "desired visual column"
456 ///
457 /// The desired visual column determines at what point in a line
458 /// the caret will be placed when moving [up and down] through
459 /// lines of varying lengths.
460 ///
461 /// Will also set the "desired wrapped visual column", which is
462 /// the same thing but used when moving vertically in a [wrapped]
463 /// fashion.
464 ///
465 /// [up and down]: Cursor::move_ver
466 /// [wrapped]: Cursor::move_ver_wrapped
467 pub fn set_desired_vcol(&mut self, x: usize) {
468 sel_mut!(self).set_desired_cols(x, x);
469 }
470
471 ////////// Iteration functions
472
473 /// Iterates over the [`char`]s
474 ///
475 /// Each [`char`] will be accompanied by a byte index, which is
476 /// the position where said character starts, e.g. `0` for the
477 /// first character
478 pub fn chars_fwd(&self) -> impl Iterator<Item = (usize, char)> + '_ {
479 self.widget.text().chars_fwd(self.caret()..).unwrap()
480 }
481
482 /// Iterates over the [`char`]s, in reverse
483 ///
484 /// Each [`char`] will be accompanied by a byte index, which is
485 /// the position where said character starts, e.g. `0` for the
486 /// first character
487 pub fn chars_rev(&self) -> impl Iterator<Item = (usize, char)> {
488 self.widget.text().chars_rev(..self.caret()).unwrap()
489 }
490
491 /// Wether the current selection matches a regex pattern
492 #[track_caller]
493 pub fn matches_pat<R: RegexPattern>(&self, pat: R) -> bool {
494 let range = sel!(self).byte_range(self.widget.text());
495 match self.widget.text().strs(range).unwrap().matches_pat(pat) {
496 Ok(result) => result,
497 Err(err) => panic!("{err}"),
498 }
499 }
500
501 /// Returns an [`Iterator`] over the matches of a [`RegexPattern`]
502 ///
503 /// This `Iterator` normally covers the entire range of the
504 /// [`Text`], however, there are methods that you can use to
505 /// narrow it down to ranges relative to the `Cursor`'s [`caret`].
506 ///
507 /// For example, [`CursorMatches::from_caret`] will narrow the
508 /// searched range from the beginning of the caret's `char` all
509 /// the way until the end of the [`Text`].
510 ///
511 /// This `Iterator` also implements [`DoubleEndedIterator`], which
512 /// means you can search in reverse as well.
513 ///
514 /// [`caret`]: Self::caret
515 #[track_caller]
516 pub fn search<R: RegexPattern>(&self, pat: R) -> CursorMatches<'_, R> {
517 let text = self.widget.text();
518 let caret = self.caret();
519 CursorMatches {
520 text_byte_len: text.len().byte(),
521 caret_range: caret.byte()..caret.fwd(self.char()).byte(),
522 matches: text.search(pat),
523 }
524 }
525
526 ////////// Text queries
527
528 /// Returns the [`char`] in the `caret`
529 pub fn char(&self) -> char {
530 self.text().char_at(sel!(self).caret()).unwrap()
531 }
532
533 /// Returns the [`char`] at a given [`Point`]
534 pub fn char_at(&self, i: impl TextIndex) -> Option<char> {
535 self.text().char_at(i)
536 }
537
538 /// Returns the [`Selection`]'s selection
539 ///
540 /// The reason why this return value is `IntoIter<&str, 2>` is
541 /// because the [`Text`] utilizes an underlying [`GapBuffer`]
542 /// to store the characters. This means that the text is
543 /// always separated into two distinct chunks.
544 ///
545 /// If this [`Selection`]'s selection happens to be entirely
546 /// within one of these chunks, the other `&str` will just be
547 /// empty.
548 ///
549 /// [`GapBuffer`]: gapbuf::GapBuffer
550 pub fn selection(&self) -> Strs<'_> {
551 let range = sel!(self).byte_range(self.text());
552 self.text().strs(range).unwrap()
553 }
554
555 /// Returns the [`Strs`] for the given [`TextRange`]
556 ///
557 /// [`GapBuffer`]: gapbuf::GapBuffer
558 pub fn strs(&self, range: impl TextRange) -> Option<Strs<'_>> {
559 self.widget.text().strs(range)
560 }
561
562 /// Returns the length of the [`Text`], in [`Point`]
563 pub fn len(&self) -> Point {
564 self.text().len()
565 }
566
567 /// Returns the position of the last [`char`] if there is one
568 pub fn last_point(&self) -> Point {
569 self.text().last_point()
570 }
571
572 /// An [`Iterator`] over the lines the `Cursor`'s range
573 pub fn lines(&self) -> Lines<'_> {
574 self.widget.text().lines(self.range())
575 }
576
577 /// An [`Iterator`] over the lines in a given [range]
578 ///
579 /// [range]: TextRange
580 pub fn lines_on(&self, range: impl TextRange) -> Lines<'_> {
581 self.widget.text().lines(range)
582 }
583
584 /// Gets the current level of indentation
585 pub fn indent(&self) -> usize {
586 self.widget
587 .text()
588 .indent(self.caret(), self.area, self.opts())
589 }
590
591 /// Gets the indentation level on the given [`Point`]
592 pub fn indent_on(&self, p: Point) -> usize {
593 self.widget.text().indent(p, self.area, self.opts())
594 }
595
596 ////////// Selection queries
597
598 /// Returns the `caret`
599 pub fn caret(&self) -> Point {
600 sel!(self).caret()
601 }
602
603 /// Returns the `anchor`
604 pub fn anchor(&self) -> Option<Point> {
605 sel!(self).anchor()
606 }
607
608 /// The [`Point`] range of the [`Selection`]
609 ///
610 /// This is an _inclusive_ range (not Rust's [`RangeInclusive`]
611 /// however), this means that, even if there is no anchor, the
612 /// lenght of this range will always be at least 1.
613 ///
614 /// If you want an exclusive range, see [`Cursor::range_excl`]
615 ///
616 /// [`RangeInclusive`]: std::ops::RangeInclusive
617 pub fn range(&self) -> Range<Point> {
618 sel!(self).point_range(self.text())
619 }
620
621 /// An exclusive [`Point`] range of the [`Selection`]
622 ///
623 /// If you wish for an inclusive range, whose length is always
624 /// greater than or equal to 1, see [`RangeInclusive`].
625 pub fn range_excl(&self) -> Range<Point> {
626 sel!(self).point_range_excl()
627 }
628
629 /// The [`VPoint`] range of the [`Selection`]
630 ///
631 /// Use only if you need the things that the [`VPoint`] provides,
632 /// in order to preven extraneous calculations
633 pub fn v_caret(&self) -> VPoint {
634 sel!(self).v_caret(self.widget.text(), self.area, self.widget.get_print_opts())
635 }
636
637 /// The [`VPoint`] of the anchor, if it exists
638 ///
639 /// Use only if you need the things that the [`VPoint`] provides,
640 /// in order to preven extraneous calculations
641 pub fn v_anchor(&self) -> Option<VPoint> {
642 sel!(self).v_anchor(self.widget.text(), self.area, self.widget.get_print_opts())
643 }
644
645 /// Returns `true` if the `anchor` exists before the `caret`
646 pub fn anchor_is_start(&self) -> bool {
647 self.anchor().is_none_or(|anchor| anchor <= self.caret())
648 }
649
650 /// Whether or not this is the main [`Selection`]
651 pub fn is_main(&self) -> bool {
652 self.selections[self.sels_i].as_ref().unwrap().was_main
653 }
654
655 /// The [`Text`] of the [`Widget`]
656 pub fn text(&self) -> &Text {
657 self.widget.text()
658 }
659
660 /// The [`PrintOpts`] in use
661 pub fn opts(&self) -> PrintOpts {
662 self.widget.get_print_opts()
663 }
664}
665
666impl Cursor<'_, Buffer> {
667 /// A unique identifier for this [`Buffer`]
668 ///
669 /// This is more robust than identifying it by its path or name,
670 /// or even [`PathKind`], since those could change, but this
671 /// cannot.
672 ///
673 /// [`PathKind`]: crate::buffer::PathKind
674 pub fn buffer_id(&self) -> BufferId {
675 self.widget.buffer_id()
676 }
677}
678
679impl<'a, W: Widget + ?Sized> std::fmt::Debug for Cursor<'a, W> {
680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681 f.debug_struct("Cursor")
682 .field("selection", &sel!(self))
683 .finish_non_exhaustive()
684 }
685}
686
687/// An [`Iterator`] over the matches of a [`RegexPattern`]
688///
689/// This `Iterator` comes from searching from a [`Cursor`]. Because of
690/// that, it has methods for added convenience of search. The methods
691/// [`to_caret`], [`to_caret_incl`], [`from_caret`] and
692/// [`from_caret_excl`] will change the [`Range`] of searching to one
693/// starting or ending on the `Cursor`'s [`caret`].
694///
695/// [`to_caret`]: CursorMatches::to_caret
696/// [`to_caret_incl`]: CursorMatches::to_caret_incl
697/// [`from_caret`]: CursorMatches::from_caret
698/// [`from_caret_excl`]: CursorMatches::from_caret_excl
699/// [`caret`]: Cursor::caret
700pub struct CursorMatches<'c, R: RegexPattern> {
701 text_byte_len: usize,
702 caret_range: Range<usize>,
703 matches: Matches<'c, R>,
704}
705
706impl<'c, R: RegexPattern> CursorMatches<'c, R> {
707 /// Changes the [`TextRange`] to search on
708 ///
709 /// This _will_ reset the [`Iterator`], if it was returning
710 /// [`None`] before, it might start returning [`Some`] again if
711 /// the pattern exists in the specified [`Range`]
712 pub fn range(self, range: impl TextRange) -> Self {
713 Self {
714 matches: self.matches.range(range),
715 ..self
716 }
717 }
718
719 /// Searches over a range from the start of the caret to the end
720 /// of the [`Text`]
721 ///
722 /// ```rust
723 /// # duat_core::doc_duat!(duat);
724 /// # use duat::prelude::*;
725 /// fn search_nth(pa: &mut Pass, handle: &Handle, n: usize, pat: &str) {
726 /// handle.edit_all(pa, |mut c| {
727 /// let mut nth = c.search(pat).from_caret().nth(n);
728 /// if let Some(range) = nth {
729 /// c.move_to(range);
730 /// }
731 /// })
732 /// }
733 /// ```
734 #[allow(clippy::wrong_self_convention)]
735 pub fn from_caret(self) -> Self {
736 Self {
737 matches: self
738 .matches
739 .range(self.caret_range.start..self.text_byte_len),
740 ..self
741 }
742 }
743
744 /// Searches over a range from the end of the caret to the end
745 /// of the [`Text`]
746 ///
747 /// ```rust
748 /// # duat_core::doc_duat!(duat);
749 /// # use duat::prelude::*;
750 /// fn next_paren_match(pa: &mut Pass, handle: &Handle) {
751 /// handle.edit_all(pa, |mut c| {
752 /// let mut start_count = 0;
753 /// let mut start_bound = None;
754 /// let end_bound = c
755 /// .search([r"\(", r"\)"])
756 /// .from_caret_excl()
757 /// .find(|(id, range)| {
758 /// start_count += ((*id == 0) as u32).saturating_sub((*id == 1) as u32);
759 /// start_bound = (*id == 0 && start_count == 0).then_some(range.clone());
760 /// start_count == 0 && *id == 1
761 /// });
762 ///
763 /// if let (Some(start), Some((_, end))) = (start_bound, end_bound) {
764 /// c.move_to(start.start..end.end);
765 /// }
766 /// })
767 /// }
768 /// ```
769 #[allow(clippy::wrong_self_convention)]
770 pub fn from_caret_excl(self) -> Self {
771 Self {
772 matches: self.matches.range(self.caret_range.end..self.text_byte_len),
773 ..self
774 }
775 }
776
777 /// Searches over a range from the start of the [`Text`] to the
778 /// start of the caret's char
779 ///
780 /// ```rust
781 /// # duat_core::doc_duat!(duat);
782 /// # use duat::prelude::*;
783 /// fn remove_prefix(pa: &mut Pass, handle: &Handle) {
784 /// let prefix_pat = format!(r"{}*\z", handle.opts(pa).word_chars_regex());
785 /// handle.edit_all(pa, |mut c| {
786 /// let prefix_range = c.search(&prefix_pat).to_caret().rev().next();
787 /// if let Some(range) = prefix_range {
788 /// c.move_to(range);
789 /// c.replace("");
790 /// }
791 /// })
792 /// }
793 /// ```
794 #[allow(clippy::wrong_self_convention)]
795 pub fn to_caret(self) -> Self {
796 Self {
797 matches: self.matches.range(0..self.caret_range.start),
798 ..self
799 }
800 }
801
802 /// Searches over a range from the start of the [`Text`] to the
803 /// end of the caret's char
804 ///
805 /// ```rust
806 /// # duat_core::doc_duat!(duat);
807 /// # use duat::prelude::*;
808 /// fn last_word_in_selection(pa: &mut Pass, handle: &Handle) {
809 /// let word_pat = format!(r"{}+", handle.opts(pa).word_chars_regex());
810 /// handle.edit_all(pa, |mut c| {
811 /// c.set_caret_on_end();
812 /// let mut nth = c.search(&word_pat).to_caret_incl().rev().next();
813 /// if let Some(range) = nth {
814 /// c.move_to(range)
815 /// } else {
816 /// c.reset()
817 /// }
818 /// })
819 /// }
820 /// ```
821 #[allow(clippy::wrong_self_convention)]
822 pub fn to_caret_incl(self) -> Self {
823 Self {
824 matches: self.matches.range(0..self.caret_range.end),
825 ..self
826 }
827 }
828}
829
830impl<'c, R: RegexPattern> Iterator for CursorMatches<'c, R> {
831 type Item = R::Match;
832
833 fn next(&mut self) -> Option<Self::Item> {
834 self.matches.next()
835 }
836}
837
838impl<'c, R: RegexPattern> DoubleEndedIterator for CursorMatches<'c, R> {
839 fn next_back(&mut self) -> Option<Self::Item> {
840 self.matches.next_back()
841 }
842}
843
844/// An [`Iterator`] overf all [`Cursor`]s
845pub struct Cursors<'c, W: Widget + ?Sized> {
846 current: Vec<Option<ModSelection>>,
847 next_i: Rc<Cell<usize>>,
848 widget: &'c mut W,
849 area: &'c Area,
850}
851
852impl<'c, W: Widget + ?Sized> Cursors<'c, W> {
853 /// Creates a new [`Cursors`]
854 pub(crate) fn new(next_i: usize, widget: &'c mut W, area: &'c Area) -> Self {
855 Self {
856 current: Vec::new(),
857 next_i: Rc::new(Cell::new(next_i)),
858 widget,
859 area,
860 }
861 }
862}
863
864impl<'a, 'lend, W: Widget + ?Sized> Lending<'lend> for Cursors<'a, W> {
865 type Lend = Cursor<'lend, W>;
866}
867
868impl<'a, W: Widget + ?Sized> Lender for Cursors<'a, W> {
869 fn next<'lend>(&'lend mut self) -> Option<<Self as Lending<'lend>>::Lend> {
870 reinsert_selections(
871 self.current.drain(..).flatten(),
872 self.widget,
873 Some(&self.next_i),
874 );
875
876 let current_i = self.next_i.get();
877 let (selection, was_main) = self.widget.text_mut().selections_mut().remove(current_i)?;
878 self.current
879 .push(Some(ModSelection::new(selection, current_i, was_main)));
880
881 Some(Cursor::new(
882 &mut self.current,
883 0,
884 (self.widget, self.area),
885 Some(self.next_i.clone()),
886 ))
887 }
888}
889
890impl<'a, W: Widget + ?Sized> Drop for Cursors<'a, W> {
891 fn drop(&mut self) {
892 reinsert_selections(
893 self.current.drain(..).flatten(),
894 self.widget,
895 Some(&self.next_i),
896 );
897 }
898}
899
900/// Reinsert edited [`Selections`]
901#[inline]
902pub(crate) fn reinsert_selections(
903 selections: impl Iterator<Item = ModSelection>,
904 widget: &mut (impl Widget + ?Sized),
905 next_i: Option<&Cell<usize>>,
906) {
907 for mod_sel in selections {
908 let ([inserted_i, selections_taken], last_selection_overhangs) = widget
909 .text_mut()
910 .selections_mut()
911 .insert(mod_sel.index, mod_sel.selection, mod_sel.was_main);
912
913 if let Some(next_i) = next_i
914 && inserted_i <= next_i.get()
915 {
916 let go_to_next = !last_selection_overhangs as usize;
917 next_i.set(
918 next_i
919 .get()
920 .saturating_sub(selections_taken)
921 .max(inserted_i)
922 + go_to_next,
923 )
924 }
925 }
926}
927
928/// A struct representing the temporary state of a [`Selection`] in a
929/// [`Cursor`]
930#[derive(Clone, Debug)]
931pub(crate) struct ModSelection {
932 selection: Selection,
933 index: usize,
934 was_main: bool,
935 has_changed: bool,
936}
937
938impl ModSelection {
939 /// Returns a new `ModSelection`
940 pub(crate) fn new(selection: Selection, index: usize, was_main: bool) -> Self {
941 Self {
942 selection,
943 index,
944 was_main,
945 has_changed: false,
946 }
947 }
948}
949
950/// A position that a [`Cursor`] can move to
951///
952/// This will come either in the form of [`Point`]s or byte indices
953/// (in the form of `usize`). It can be a single value, like
954/// `Point::default()` or `3`, in which case only the [caret] will
955/// move, not affecting the [anchor].
956///
957/// Or it could be a [range], like `p1..p2` or `..=1000`, in which
958/// case the caret will be placed at the end, while the anchor will be
959/// placed at the start.
960///
961/// [caret]: Cursor::caret
962/// [anchor]: Cursor::anchor
963/// [range]: std::ops::RangeBounds
964pub trait CaretOrRange {
965 /// Internal movement function for monomorphization
966 #[doc(hidden)]
967 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>);
968}
969
970impl CaretOrRange for Point {
971 #[track_caller]
972 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
973 sel_mut!(cursor).move_to(self, cursor.widget.text());
974 }
975}
976
977impl CaretOrRange for usize {
978 #[track_caller]
979 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
980 sel_mut!(cursor).move_to(
981 cursor.widget.text().point_at_byte(self),
982 cursor.widget.text(),
983 )
984 }
985}
986
987impl<Idx: TextIndex> CaretOrRange for Range<Idx> {
988 #[track_caller]
989 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
990 let range = self.start.to_byte_index()..self.end.to_byte_index();
991 assert!(
992 range.start <= range.end,
993 "slice index start is larger than end"
994 );
995
996 sel_mut!(cursor).move_to(range.start, cursor.widget.text());
997 if range.start < range.end {
998 cursor.set_anchor();
999 sel_mut!(cursor).move_to(range.end, cursor.widget.text());
1000 if range.end < cursor.widget.text().len().byte() {
1001 cursor.move_hor(-1);
1002 }
1003 } else {
1004 cursor.unset_anchor();
1005 }
1006 }
1007}
1008
1009impl<Idx: TextIndex> CaretOrRange for RangeInclusive<Idx> {
1010 #[track_caller]
1011 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
1012 let range = self.start().to_byte_index()..=self.end().to_byte_index();
1013 assert!(
1014 range.start() <= range.end(),
1015 "slice index start is larger than end"
1016 );
1017
1018 sel_mut!(cursor).move_to(*range.start(), cursor.widget.text());
1019 cursor.set_anchor();
1020 sel_mut!(cursor).move_to(*range.end(), cursor.widget.text());
1021 }
1022}
1023
1024impl<Idx: TextIndex> CaretOrRange for RangeFrom<Idx> {
1025 #[track_caller]
1026 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
1027 let start = self.start.to_byte_index();
1028 sel_mut!(cursor).move_to(start, cursor.widget.text());
1029 if start < cursor.text().len().byte() {
1030 cursor.set_anchor();
1031 sel_mut!(cursor).move_to(cursor.widget.text().len(), cursor.widget.text());
1032 cursor.move_hor(-1);
1033 } else {
1034 cursor.unset_anchor();
1035 }
1036 }
1037}
1038
1039impl<Idx: TextIndex> CaretOrRange for RangeTo<Idx> {
1040 #[track_caller]
1041 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
1042 let end = self
1043 .end
1044 .to_byte_index()
1045 .min(cursor.text().last_point().byte());
1046 cursor.move_to_start();
1047 if 0 < end {
1048 cursor.set_anchor();
1049 sel_mut!(cursor).move_to(end, cursor.widget.text());
1050 cursor.move_hor(-1);
1051 } else {
1052 cursor.unset_anchor();
1053 }
1054 }
1055}
1056
1057impl<Idx: TextIndex> CaretOrRange for RangeToInclusive<Idx> {
1058 #[track_caller]
1059 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
1060 cursor.move_to_start();
1061 cursor.set_anchor();
1062 sel_mut!(cursor).move_to(self.end, cursor.widget.text());
1063 }
1064}
1065
1066impl CaretOrRange for RangeFull {
1067 #[track_caller]
1068 fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
1069 cursor.move_to_start();
1070 if cursor.text().len() > Point::default() {
1071 cursor.set_anchor();
1072 sel_mut!(cursor).move_to(cursor.widget.text().len(), cursor.widget.text());
1073 }
1074 }
1075}