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