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, RefMut},
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, Parser},
19 opts::PrintOpts,
20 text::{Change, Lines, Point, RegexPattern, Searcher, Strs, Text, TextIndex, TextRange},
21 ui::{Area, Widget},
22};
23
24/// The [`Selection`] and [`Selections`] structs
25mod selections;
26
27/// A selection that can edit [`Text`], but can't alter selections
28///
29/// This struct will be used only inside functions passed to the
30/// [`edit_*`] family of methods from the [`Handle`].
31///
32/// To make edits, you can use three different functions. You can,
33/// those being [`replace`], [`insert`], and [`append`]. [`replace`]
34/// will completely replace the [`Selection`]'s selection. [`insert`]
35/// will place text behind the `caret`, and [`append`] will place it
36/// after the `caret`.
37///
38/// You can also move the [`Selection`]'s selection in many different
39/// ways, which are described below, in the `impl` section for this
40/// struct.
41///
42/// ```rust
43/// # duat_core::doc_duat!(duat);
44/// # use duat::prelude::*;
45/// # fn test(mut pa: Pass, handle: &mut Handle) {
46/// let sel: String = handle.edit_main(&mut pa, |mut c| {
47/// c.set_anchor();
48/// c.set_caret_on_end();
49/// c.replace("my replacement");
50/// c.append(" and my edit");
51///
52/// c.swap_ends();
53/// c.insert("This is ");
54/// c.swap_ends();
55///
56/// c.move_hor(" and my edit".chars().count() as i32);
57/// c.set_anchor();
58/// c.move_hor(-("This is my replacement and my edit".chars().count() as i32));
59/// c.selection().into_iter().collect()
60/// });
61///
62/// assert_eq!(&sel, "This is my replacement and my edit");
63/// # }
64/// ```
65///
66/// [`edit_*`]: crate::context::Handle::edit_nth
67/// [`Handle`]: crate::context::Handle
68/// [`replace`]: Cursor::replace
69/// [`insert`]: Cursor::insert
70/// [`append`]: Cursor::append
71pub struct Cursor<'a, W: Widget + ?Sized = crate::buffer::Buffer, S = ()> {
72 initial: Selection,
73 selection: Selection,
74 n: usize,
75 was_main: bool,
76 widget: &'a mut W,
77 area: &'a Area,
78 next_i: Option<Rc<Cell<usize>>>,
79 inc_searcher: &'a mut S,
80 is_copy: bool,
81}
82
83impl<'a, W: Widget + ?Sized, S> Cursor<'a, W, S> {
84 /// Returns a new instance of [`Cursor`]
85 pub(crate) fn new(
86 (selection, n, was_main): (Selection, usize, bool),
87 (widget, area): (&'a mut W, &'a Area),
88 next_i: Option<Rc<Cell<usize>>>,
89 searcher: &'a mut S,
90 is_copy: bool,
91 ) -> Self {
92 Self {
93 initial: selection.clone(),
94 selection,
95 n,
96 was_main,
97 widget,
98 area,
99 next_i,
100 inc_searcher: searcher,
101 is_copy,
102 }
103 }
104
105 ////////// Text editing
106
107 /// Replaces the entire selection with new text
108 ///
109 /// If there is a selection, then it is treated as _inclusive_,
110 /// therefore, a selection where `caret == anchor` will remove the
111 /// character where the caret is. If there is no selection, then
112 /// this has the same effect as [`insert`]. If you wish to
113 /// append to the `caret` instead, see [`append`].
114 ///
115 /// After replacing the sele tion, if the `caret` is behind the
116 /// `anchor` (or in the same spot), it will be placed on the start
117 /// of the selection, while the `anchor` will be placed on the
118 /// new end. If it is ahead, it will be placed ahead.
119 ///
120 /// [`insert`]: Self::insert
121 /// [`append`]: Self::append
122 pub fn replace(&mut self, edit: impl ToString) {
123 let change = {
124 let edit = edit.to_string();
125 let range = self.selection.point_range(self.widget.text());
126 let (p0, p1) = (range.start, range.end);
127 let p1 = if self.anchor().is_some() { p1 } else { p0 };
128 Change::new(edit, p0..p1, self.widget.text())
129 };
130
131 // Disconsider null changes.
132 if change.added_str().len() < 10 && change.added_str() == change.taken_str() {
133 return;
134 }
135
136 let (start, end) = (change.start(), change.added_end());
137
138 self.edit(change.clone());
139
140 let anchor_was_on_start = self.anchor_is_start();
141 self.move_to(start..end);
142 if !anchor_was_on_start {
143 self.set_caret_on_start();
144 }
145 }
146
147 /// Inserts new text directly behind the `caret`
148 ///
149 /// If the `anchor` is ahead of the `caret`, it will move forwards
150 /// by the number of chars in the new text.
151 ///
152 /// If you wish to replace the selected text, see [`replace`], if
153 /// you want to append after the `caret` instead, see [`append`]
154 ///
155 /// [`replace`]: Self::replace
156 /// [`append`]: Self::append
157 pub fn insert(&mut self, edit: impl ToString) {
158 let range = self.selection.caret()..self.selection.caret();
159 let change = Change::new(edit.to_string(), range, self.widget.text());
160 let (added, taken) = (change.added_end(), change.taken_end());
161
162 self.edit(change);
163
164 if let Some(anchor) = self.selection.anchor()
165 && anchor > self.selection.caret()
166 {
167 let new_anchor = anchor + added - taken;
168 self.selection.swap_ends();
169 self.selection.move_to(new_anchor, self.widget.text());
170 self.selection.swap_ends();
171 }
172 }
173
174 /// Appends new text directly after the `caret`
175 ///
176 /// If the `anchor` is ahead of the `caret`, it will move forwards
177 /// by the number of chars in the new text.
178 ///
179 /// If you wish to replace the selected text, see [`replace`], if
180 /// you want to insert before the `caret` instead, see [`insert`]
181 ///
182 /// [`replace`]: Self::replace
183 /// [`insert`]: Self::insert
184 pub fn append(&mut self, edit: impl ToString) {
185 let caret = self.selection.caret();
186 let p = caret.fwd(self.widget.text().char_at(caret).unwrap());
187 let change = Change::new(edit.to_string(), p..p, self.widget.text());
188 let (added, taken) = (change.added_end(), change.taken_end());
189
190 self.edit(change);
191
192 if let Some(anchor) = self.selection.anchor()
193 && anchor > p
194 {
195 let new_anchor = anchor + added - taken;
196 self.selection.swap_ends();
197 self.selection.move_to(new_anchor, self.widget.text());
198 self.selection.swap_ends();
199 }
200 }
201
202 /// Edits the buffer with a [`Change`]
203 fn edit(&mut self, change: Change<'static, String>) {
204 let text = self.widget.text_mut();
205 let (change_i, selections_taken) =
206 text.apply_change(self.selection.change_i.map(|i| i as usize), change);
207 self.selection.change_i = change_i.map(|i| i as u32);
208
209 // The Change may have happened before the index of the next curossr,
210 // so we need to account for that.
211 if let Some(change_i) = change_i
212 && let Some(next_i) = self.next_i.as_ref()
213 && change_i <= next_i.get()
214 {
215 next_i.set(next_i.get().saturating_sub(selections_taken));
216 }
217 }
218
219 ////////// Movement functions
220
221 /// Moves the selection horizontally. May cause vertical movement
222 ///
223 /// Returns the distance moved in chars.
224 #[track_caller]
225 pub fn move_hor(&mut self, count: i32) -> i32 {
226 self.selection.move_hor(count, self.widget.text())
227 }
228
229 /// Moves the selection vertically. May cause horizontal movement
230 ///
231 /// Returns the distance moved in lines.
232 #[track_caller]
233 pub fn move_ver(&mut self, count: i32) -> i32 {
234 self.selection.move_ver(
235 count,
236 self.widget.text(),
237 self.area,
238 self.widget.get_print_opts(),
239 )
240 }
241
242 /// Moves the selection vertically a number of wrapped lines. May
243 /// cause horizontal movement
244 ///
245 /// Returns the distance moved in wrapped lines.
246 #[track_caller]
247 pub fn move_ver_wrapped(&mut self, count: i32) {
248 self.selection.move_ver_wrapped(
249 count,
250 self.widget.text(),
251 self.area,
252 self.widget.get_print_opts(),
253 );
254 }
255
256 /// Moves the selection to a [`Point`] or a [range] of [`Point`]s
257 ///
258 /// If you give it just a [`Point`], it will move the caret,
259 /// without affecting the anchor. If you give it a [range] of
260 /// [`Point`]s, the anchor will be placed at the start, while the
261 /// caret will be placed at the end of said [range]. You can flip
262 /// those positions with a function like [`swap_ends`].
263 ///
264 /// If a [`Point`] is not valid, it will be corrected and clamped
265 /// to the lenght of the [`Text`].
266 ///
267 /// [range]: std::ops::RangeBounds
268 /// [`swap_ends`]: Self::swap_ends
269 #[track_caller]
270 pub fn move_to(&mut self, point_or_points: impl CaretOrRange) {
271 point_or_points.move_to(self);
272 }
273
274 /// Moves the selection to [`Point::default`], i.c., the start of
275 /// the [`Text`]
276 #[track_caller]
277 pub fn move_to_start(&mut self) {
278 self.selection.move_to(Point::default(), self.widget.text());
279 }
280
281 /// Moves the selection to a `line` and a `column`
282 ///
283 /// - If the coords isn't valid, it will move to the "maximum"
284 /// position allowed.
285 #[track_caller]
286 pub fn move_to_coords(&mut self, line: usize, col: usize) {
287 let range = self
288 .text()
289 .line_range(line.min(self.text().last_point().line()));
290 let (p, _) = self
291 .text()
292 .chars_fwd(range.clone())
293 .unwrap()
294 .take(col + 1)
295 .last()
296 .unzip();
297 self.move_to(p.unwrap_or(range.end));
298 }
299
300 /// Moves to a column on the current line
301 #[track_caller]
302 pub fn move_to_col(&mut self, col: usize) {
303 let line = self.text().point_at_line(self.caret().line()).line();
304 self.move_to_coords(line, col);
305 }
306
307 /// Returns and takes the anchor of the [`Selection`].
308 pub fn unset_anchor(&mut self) -> Option<Point> {
309 self.selection.unset_anchor()
310 }
311
312 /// Sets the `anchor` to the current `caret`
313 pub fn set_anchor(&mut self) {
314 self.selection.set_anchor()
315 }
316
317 /// Sets the `anchor` if it was not already set
318 ///
319 /// Returns `true` if the anchor was set by this command.
320 pub fn set_anchor_if_needed(&mut self) -> bool {
321 if self.anchor().is_none() {
322 self.selection.set_anchor();
323 true
324 } else {
325 false
326 }
327 }
328
329 /// Swaps the position of the `caret` and `anchor`
330 pub fn swap_ends(&mut self) {
331 self.selection.swap_ends();
332 }
333
334 /// Sets the caret of the [`Selection`] on the start of the
335 /// selection
336 ///
337 /// Returns `true` if a swap occurred
338 pub fn set_caret_on_start(&mut self) -> bool {
339 if let Some(anchor) = self.anchor()
340 && anchor < self.caret()
341 {
342 self.swap_ends();
343 true
344 } else {
345 false
346 }
347 }
348
349 /// Sets the caret of the [`Selection`] on the end of the
350 /// selection
351 ///
352 /// Returns `true` if a swap occurred
353 pub fn set_caret_on_end(&mut self) -> bool {
354 if let Some(anchor) = self.anchor()
355 && anchor > self.caret()
356 {
357 self.swap_ends();
358 true
359 } else {
360 false
361 }
362 }
363
364 ////////// Selection meta manipulation
365
366 /// Resets the [`Selection`] to how it was before being modified
367 pub fn reset(&mut self) {
368 self.selection = self.initial.clone();
369 }
370
371 /// Copies the current [`Selection`] in place
372 ///
373 /// This will leave an additional [`Selection`] with the current
374 /// selection. Do note that normal intersection rules apply, so if
375 /// at the end of the movement, this selection intersects with any
376 /// other, they will be merged into one.
377 ///
378 /// When this [`Cursor`] is dropped, like with normal [`Cursor`]s,
379 /// its [`Selection`] will be added to the [`Selections`], unless
380 /// you [destroy] it.
381 ///
382 /// [destroy]: Self::destroy
383 pub fn copy(&mut self) -> Cursor<'_, W, S> {
384 Cursor::new(
385 (self.selection.clone(), self.n, false),
386 (self.widget, self.area),
387 self.next_i.clone(),
388 self.inc_searcher,
389 true,
390 )
391 }
392
393 /// Destroys the current [`Selection`]
394 ///
395 /// Will not destroy it if it is the last [`Selection`] left
396 ///
397 /// If this was the main selection, the main selection will now be
398 /// the selection immediately behind it.
399 pub fn destroy(mut self) {
400 // If it is 1, it is actually 2, because this Selection is also part
401 // of that list.
402 if !self.widget.text().selections().is_empty() || self.is_copy {
403 // Rc<Cell> needs to be manually dropped to reduce its counter.
404 self.next_i.take();
405 if self.was_main {
406 self.widget.text_mut().selections_mut().rotate_main(-1);
407 }
408 // The destructor is what inserts the Selection back into the list, so
409 // don't run it.
410 std::mem::forget(self);
411 } else {
412 // Just to be explicit.
413 drop(self);
414 }
415 }
416
417 /// Sets the "desired visual column"
418 ///
419 /// The desired visual column determines at what point in a line
420 /// the caret will be placed when moving [up and down] through
421 /// lines of varying lengths.
422 ///
423 /// Will also set the "desired wrapped visual column", which is
424 /// the same thing but used when moving vertically in a [wrapped]
425 /// fashion.
426 ///
427 /// [up and down]: Cursor::move_ver
428 /// [wrapped]: Cursor::move_ver_wrapped
429 pub fn set_desired_vcol(&mut self, x: usize) {
430 self.selection.set_desired_cols(x, x);
431 }
432
433 ////////// Iteration functions
434
435 /// Iterates over the [`char`]s
436 ///
437 /// This iteration will begin on the `caret`. It will also include
438 /// the [`Point`] of each `char`
439 pub fn chars_fwd(&self) -> impl Iterator<Item = (Point, char)> + '_ {
440 self.widget.text().chars_fwd(self.caret()..).unwrap()
441 }
442
443 /// Iterates over the [`char`]s, in reverse
444 ///
445 /// This iteration will begin on the `caret`. It will also include
446 /// the [`Point`] of each `char`
447 pub fn chars_rev(&self) -> impl Iterator<Item = (Point, char)> {
448 self.widget.text().chars_rev(..self.caret()).unwrap()
449 }
450
451 /// Wether the current selection matches a regex pattern
452 #[track_caller]
453 pub fn matches<R: RegexPattern>(&self, pat: R) -> bool {
454 let range = self.selection.byte_range(self.widget.text());
455 match self.widget.text().matches(pat, range) {
456 Ok(result) => result,
457 Err(err) => panic!("{err}"),
458 }
459 }
460
461 /// Searches the [`Text`] for a regex
462 ///
463 /// The search will begin on the `caret` and returns the
464 /// [`Range<usize>`] for the match, where the bounding `usize`s
465 /// represents a byte index, and can be directly used in, for
466 /// example, [`Cursor::move_to`].
467 ///
468 /// ```rust
469 /// # duat_core::doc_duat!(duat);
470 /// # use duat::prelude::*;
471 /// fn search_nth(pa: &mut Pass, handle: &Handle, n: usize, pat: &str) {
472 /// handle.edit_all(pa, |mut c| {
473 /// let mut nth = c.search_fwd(pat).nth(n);
474 /// if let Some(range) = nth {
475 /// c.move_to(range);
476 /// }
477 /// })
478 /// }
479 /// ```
480 ///
481 /// # Panics
482 ///
483 /// If the regex is not valid, this method will panic.
484 #[track_caller]
485 pub fn search_fwd<R: RegexPattern>(&self, pat: R) -> impl Iterator<Item = R::Match> + '_ {
486 let start = self.selection.caret();
487 let text = self.widget.text();
488 match text.search_fwd(pat, start..text.len()) {
489 Ok(iter) => iter,
490 Err(err) => panic!("{err}"),
491 }
492 }
493
494 /// Searches the [`Text`] for a regex, with an upper limit
495 ///
496 /// The search will begin on the `caret` and returns the
497 /// [`Range<usize>`] for the match, where the bounding `usize`s
498 /// represents a byte index, and can be directly used in, for
499 /// example, [`Cursor::move_to`].
500 ///
501 /// ```rust
502 /// # duat_core::doc_duat!(duat);
503 /// # use duat::prelude::*;
504 /// fn find_within(pa: &mut Pass, handle: &Handle, pat: &str) {
505 /// handle.edit_all(pa, |mut c| {
506 /// c.set_caret_on_start();
507 /// let mut range = c.search_fwd_until(pat, c.range().end).next();
508 /// if let Some(range) = range {
509 /// c.move_to(range);
510 /// }
511 /// })
512 /// }
513 /// ```
514 ///
515 /// # Panics
516 ///
517 /// If the regex is not valid, this method will panic.
518 #[track_caller]
519 pub fn search_fwd_until<R: RegexPattern>(
520 &self,
521 pat: R,
522 until: impl TextIndex,
523 ) -> impl Iterator<Item = R::Match> + '_ {
524 let start = self.selection.caret();
525 let text = self.widget.text();
526 match text.search_fwd(pat, start.byte()..until.to_byte_index()) {
527 Ok(iter) => iter,
528 Err(err) => panic!("{err}"),
529 }
530 }
531
532 /// Searches the [`Text`] for a regex, skipping the caret's
533 /// character
534 ///
535 /// The search will begin one character afer the `caret` and
536 /// returns the [`Range<usize>`] for the match, where the
537 /// bounding `usize`s represents a byte index, and can be
538 /// directly used in, for example, [`Cursor::move_to`].
539 ///
540 /// ```rust
541 /// # duat_core::doc_duat!(duat);
542 /// # use duat::prelude::*;
543 /// fn next_paren_match(pa: &mut Pass, handle: &Handle) {
544 /// handle.edit_all(pa, |mut c| {
545 /// let mut start_count = 0;
546 /// let mut start_bound = None;
547 /// let end_bound = c.search_fwd_excl([r"\(", r"\)"]).find(|(id, range)| {
548 /// start_count += ((*id == 0) as u32).saturating_sub((*id == 1) as u32);
549 /// start_bound = (*id == 0 && start_count == 0).then_some(range.clone());
550 /// start_count == 0 && *id == 1
551 /// });
552 ///
553 /// if let (Some(start), Some((_, end))) = (start_bound, end_bound) {
554 /// c.move_to(start.start..end.end);
555 /// }
556 /// })
557 /// }
558 /// ```
559 ///
560 /// # Panics
561 ///
562 /// If the regex is not valid, this method will panic.
563 #[track_caller]
564 pub fn search_fwd_excl<R: RegexPattern>(&self, pat: R) -> impl Iterator<Item = R::Match> + '_ {
565 let start = self.selection.caret();
566 let text = self.widget.text();
567 match text.search_fwd(
568 pat,
569 start.byte() + self.char().len_utf8()..text.len().byte(),
570 ) {
571 Ok(iter) => iter,
572 Err(err) => panic!("{err}"),
573 }
574 }
575
576 /// Searches the [`Text`] for a regex, with an upper limit and
577 /// skipping the caret's character
578 ///
579 /// The search will begin one character afer the `caret` and
580 /// returns the [`Range<usize>`] for the match, where the
581 /// bounding `usize`s represents a byte index, and can be
582 /// directly used in, for example, [`Cursor::move_to`].
583 ///
584 /// ```rust
585 /// # duat_core::doc_duat!(duat);
586 /// # use duat::prelude::*;
587 /// # #[derive(Clone, Copy)]
588 /// # struct Insert;
589 /// # impl Mode for Insert {
590 /// # type Widget = Buffer;
591 /// # fn send_key(&mut self, _: &mut Pass, _: KeyEvent, _: Handle<Self::Widget>) {}
592 /// # }
593 /// #[derive(Clone, Copy)]
594 /// enum Action {
595 /// Move,
596 /// Select,
597 /// Delete,
598 /// Change,
599 /// }
600 ///
601 /// fn f_key_from_vim(pa: &mut Pass, handle: &Handle, char: char, n: usize, action: Action) {
602 /// handle.edit_all(pa, |mut c| {
603 /// let line_end = c.search_fwd('\n').next().unwrap();
604 /// let mut nth = c.search_fwd_excl_until(char, line_end.start).nth(n);
605 ///
606 /// match (nth, action) {
607 /// (Some(range), Action::Move) => {
608 /// c.unset_anchor();
609 /// c.move_to(range.start);
610 /// }
611 /// (Some(range), Action::Select) => {
612 /// c.set_anchor_if_needed();
613 /// c.move_to(range.start);
614 /// }
615 /// (Some(range), Action::Delete | Action::Change) => {
616 /// c.set_anchor_if_needed();
617 /// c.move_to(range.start);
618 /// c.replace("");
619 /// }
620 /// _ => {}
621 /// }
622 /// });
623 ///
624 /// if let Action::Change = action {
625 /// mode::set(Insert);
626 /// }
627 /// }
628 /// ```
629 ///
630 /// # Panics
631 ///
632 /// If the regex is not valid, this method will panic.
633 #[track_caller]
634 pub fn search_fwd_excl_until<R: RegexPattern>(
635 &self,
636 pat: R,
637 until: impl TextIndex,
638 ) -> impl Iterator<Item = R::Match> + '_ {
639 let start = self.selection.caret();
640 let text = self.widget.text();
641 match text.search_fwd(
642 pat,
643 start.byte() + self.char().len_utf8()..until.to_byte_index(),
644 ) {
645 Ok(iter) => iter,
646 Err(err) => panic!("{err}"),
647 }
648 }
649
650 /// Searches the [`Text`] for a regex, in reverse
651 ///
652 /// The search will begin on the `caret` and returns the
653 /// [`Range<usize>`] for the match, where the bounding `usize`s
654 /// represents a byte index, and can be directly used in, for
655 /// example, [`Cursor::move_to`].
656 ///
657 /// ```rust
658 /// # duat_core::doc_duat!(duat);
659 /// # use duat::prelude::*;
660 /// fn remove_prefix(pa: &mut Pass, handle: &Handle) {
661 /// let prefix_pat = format!(r"{}*\z", handle.opts(pa).word_chars_regex());
662 /// handle.edit_all(pa, |mut c| {
663 /// let prefix_range = c.search_rev(&prefix_pat).next();
664 /// if let Some(range) = prefix_range {
665 /// c.move_to(range);
666 /// c.replace("");
667 /// }
668 /// })
669 /// }
670 /// ```
671 ///
672 /// # Panics
673 ///
674 /// If the regex is not valid, this method will panic.
675 #[track_caller]
676 pub fn search_rev<R: RegexPattern>(&self, pat: R) -> impl Iterator<Item = R::Match> + '_ {
677 let end = self.selection.caret();
678 let text = self.widget.text();
679 match text.search_rev(pat, Point::default()..end) {
680 Ok(iter) => iter,
681 Err(err) => panic!("{err}"),
682 }
683 }
684
685 /// Searches the [`Text`] for a regex, in reverse, until a given
686 /// point
687 ///
688 /// The search will begin on the `caret` and returns the
689 /// [`Range<usize>`] for the match, where the bounding `usize`s
690 /// represents a byte index, and can be directly used in, for
691 /// example, [`Cursor::move_to`].
692 ///
693 /// ```rust
694 /// # duat_core::doc_duat!(duat);
695 /// # use duat::prelude::*;
696 /// fn search_before_but_after_prev(pa: &mut Pass, handle: &Handle, pat: &str) {
697 /// let mut last_end = 0;
698 /// handle.edit_all(pa, |mut c| {
699 /// let mut nth = c.search_rev_until(pat, last_end).next();
700 /// if let Some(range) = nth {
701 /// c.move_to(range)
702 /// }
703 /// last_end = c.range().end.byte();
704 /// })
705 /// }
706 /// ```
707 ///
708 /// # Panics
709 ///
710 /// If the regex is not valid, this method will panic.
711 #[track_caller]
712 pub fn search_rev_until<R: RegexPattern>(
713 &self,
714 pat: R,
715 until: impl TextIndex,
716 ) -> impl Iterator<Item = R::Match> + '_ {
717 let end = self.selection.caret();
718 let start = until.to_byte_index();
719 let text = self.widget.text();
720 match text.search_rev(pat, start..end.byte()) {
721 Ok(iter) => iter,
722 Err(err) => panic!("{err}"),
723 }
724 }
725
726 /// Searches the [`Text`] for a regex, in reverse, including the
727 /// caret's character
728 ///
729 /// The search will begin on the character right after the `caret`
730 /// and returns the [`Range<usize>`] for the match, where the
731 /// bounding `usize`s represents a byte index, and can be
732 /// directly used in, for example, [`Cursor::move_to`].
733 ///
734 /// ```rust
735 /// # duat_core::doc_duat!(duat);
736 /// # use duat::prelude::*;
737 /// fn last_word_in_selection(pa: &mut Pass, handle: &Handle) {
738 /// let word_pat = format!(r"{}+", handle.opts(pa).word_chars_regex());
739 /// handle.edit_all(pa, |mut c| {
740 /// c.set_caret_on_end();
741 /// let mut nth = c.search_rev_incl(&word_pat).next();
742 /// if let Some(range) = nth {
743 /// c.move_to(range)
744 /// } else {
745 /// c.reset()
746 /// }
747 /// })
748 /// }
749 /// ```
750 ///
751 /// # Panics
752 ///
753 /// If the regex is not valid, this method will panic.
754 #[track_caller]
755 pub fn search_rev_incl<R: RegexPattern>(&self, pat: R) -> impl Iterator<Item = R::Match> + '_ {
756 let end = self.selection.caret();
757 let text = self.widget.text();
758 match text.search_rev(pat, ..end.byte() + self.char().len_utf8()) {
759 Ok(iter) => iter,
760 Err(err) => panic!("{err}"),
761 }
762 }
763
764 /// Searches the [`Text`] for a regex, in reverse, including the
765 /// caret's character, until a given point
766 ///
767 /// The search will begin on the character right after the `caret`
768 /// and returns the [`Range<usize>`] for the match, where the
769 /// bounding `usize`s represents a byte index, and can be
770 /// directly used in, for example, [`Cursor::move_to`].
771 ///
772 /// ```rust
773 /// # duat_core::doc_duat!(duat);
774 /// # use duat::prelude::*;
775 /// fn last_word_limited_to_selection(pa: &mut Pass, handle: &Handle) {
776 /// let word_pat = format!(r"{}+", handle.opts(pa).word_chars_regex());
777 /// handle.edit_all(pa, |mut c| {
778 /// c.set_caret_on_end();
779 /// let start = c.range().start;
780 /// let mut nth = c.search_rev_incl_until(&word_pat, start).next();
781 /// if let Some(range) = nth {
782 /// c.move_to(range)
783 /// } else {
784 /// c.reset()
785 /// }
786 /// })
787 /// }
788 /// ```
789 ///
790 /// # Panics
791 ///
792 /// If the regex is not valid, this method will panic.
793 #[track_caller]
794 pub fn search_rev_incl_until<R: RegexPattern>(
795 &self,
796 pat: R,
797 until: impl TextIndex,
798 ) -> impl Iterator<Item = R::Match> + '_ {
799 let end = self.selection.caret();
800 let start = until.to_byte_index();
801 let text = self.widget.text();
802 match text.search_rev(pat, start..end.byte() + self.char().len_utf8()) {
803 Ok(iter) => iter,
804 Err(err) => panic!("{err}"),
805 }
806 }
807
808 ////////// Text queries
809
810 /// Returns the [`char`] in the `caret`
811 pub fn char(&self) -> char {
812 self.text()
813 .char_at(self.selection.caret())
814 .unwrap_or_else(|| panic!("{:#?}\n{:#?}", self.selection.caret(), self.text()))
815 }
816
817 /// Returns the [`char`] at a given [`Point`]
818 pub fn char_at(&self, i: impl TextIndex) -> Option<char> {
819 self.text().char_at(i)
820 }
821
822 /// Returns the [`Selection`]'s selection
823 ///
824 /// The reason why this return value is `IntoIter<&str, 2>` is
825 /// because the [`Text`] utilizes an underlying [`GapBuffer`]
826 /// to store the characters. This means that the text is
827 /// always separated into two distinct chunks.
828 ///
829 /// If this [`Selection`]'s selection happens to be entirely
830 /// within one of these chunks, the other `&str` will just be
831 /// empty.
832 ///
833 /// [`GapBuffer`]: gapbuf::GapBuffer
834 pub fn selection(&self) -> Strs<'_> {
835 let range = self.selection.byte_range(self.text());
836 self.text().strs(range).unwrap()
837 }
838
839 /// Returns the [`Strs`] for the given [`TextRange`]
840 ///
841 /// [`GapBuffer`]: gapbuf::GapBuffer
842 pub fn strs(&self, range: impl TextRange) -> Option<Strs<'_>> {
843 self.widget.text().strs(range)
844 }
845
846 /// Returns the length of the [`Text`], in [`Point`]
847 pub fn len(&self) -> Point {
848 self.text().len()
849 }
850
851 /// Returns the position of the last [`char`] if there is one
852 pub fn last_point(&self) -> Point {
853 self.text().last_point()
854 }
855
856 /// An [`Iterator`] over the lines the `Cursor`'s range
857 pub fn lines(&self) -> Lines<'_> {
858 self.widget.text().lines(self.range())
859 }
860
861 /// An [`Iterator`] over the lines in a given [range]
862 ///
863 /// [range]: TextRange
864 pub fn lines_on(&self, range: impl TextRange) -> Lines<'_> {
865 self.widget.text().lines(range)
866 }
867
868 /// Gets the current level of indentation
869 pub fn indent(&self) -> usize {
870 self.widget
871 .text()
872 .indent(self.caret(), self.area, self.opts())
873 }
874
875 /// Gets the indentation level on the given [`Point`]
876 pub fn indent_on(&self, p: Point) -> usize {
877 self.widget.text().indent(p, self.area, self.opts())
878 }
879
880 ////////// Selection queries
881
882 /// Returns the `caret`
883 pub fn caret(&self) -> Point {
884 self.selection.caret()
885 }
886
887 /// Returns the `anchor`
888 pub fn anchor(&self) -> Option<Point> {
889 self.selection.anchor()
890 }
891
892 /// The [`Point`] range of the [`Selection`]
893 ///
894 /// This is an _inclusive_ range (not Rust's [`RangeInclusive`]
895 /// however), this means that, even if there is no anchor, the
896 /// lenght of this range will always be at least 1.
897 ///
898 /// If you want an exclusive range, see [`Cursor::range_excl`]
899 ///
900 /// [`RangeInclusive`]: std::ops::RangeInclusive
901 pub fn range(&self) -> Range<Point> {
902 self.selection.point_range(self.text())
903 }
904
905 /// An exclusive [`Point`] range of the [`Selection`]
906 ///
907 /// If you wish for an inclusive range, whose length is always
908 /// greater than or equal to 1, see [`RangeInclusive`].
909 pub fn range_excl(&self) -> Range<Point> {
910 self.selection.point_range_excl()
911 }
912
913 /// The [`VPoint`] range of the [`Selection`]
914 ///
915 /// Use only if you need the things that the [`VPoint`] provides,
916 /// in order to preven extraneous calculations
917 pub fn v_caret(&self) -> VPoint {
918 self.selection
919 .v_caret(self.widget.text(), self.area, self.widget.get_print_opts())
920 }
921
922 /// The [`VPoint`] of the anchor, if it exists
923 ///
924 /// Use only if you need the things that the [`VPoint`] provides,
925 /// in order to preven extraneous calculations
926 pub fn v_anchor(&self) -> Option<VPoint> {
927 self.selection
928 .v_anchor(self.widget.text(), self.area, self.widget.get_print_opts())
929 }
930
931 /// Returns `true` if the `anchor` exists before the `caret`
932 pub fn anchor_is_start(&self) -> bool {
933 self.anchor().is_none_or(|anchor| anchor <= self.caret())
934 }
935
936 /// Whether or not this is the main [`Selection`]
937 pub fn is_main(&self) -> bool {
938 self.was_main
939 }
940
941 /// The [`Text`] of the [`Widget`]
942 pub fn text(&self) -> &Text {
943 self.widget.text()
944 }
945
946 /// The [`PrintOpts`] in use
947 pub fn opts(&self) -> PrintOpts {
948 self.widget.get_print_opts()
949 }
950}
951
952impl<S> Cursor<'_, Buffer, S> {
953 /// Reads the [`Bytes`] and a [`Parser`]
954 ///
955 /// [`Bytes`]: crate::text::Bytes
956 pub fn read_parser<Rd: Parser, Ret>(&self, read: impl FnOnce(&Rd) -> Ret) -> Option<Ret> {
957 self.widget.read_parser(read)
958 }
959}
960
961/// Incremental search functions, only available on [`IncSearcher`]s
962///
963/// [`IncSearcher`]: https://docs.rs/duat/latest/duat/modes/struct.IncSearcher.html
964impl<W: Widget + ?Sized> Cursor<'_, W, Searcher> {
965 /// Search incrementally from an [`IncSearch`] request
966 ///
967 /// This will match the Regex pattern from the current position of
968 /// the caret. if `end` is [`Some`], the search will end at the
969 /// requested [`Point`].
970 ///
971 /// [`IncSearch`]: https://docs.rs/duat/latest/duat/modes/struct.IncSearch.html
972 pub fn search_inc_fwd(
973 &mut self,
974 end: Option<Point>,
975 ) -> impl Iterator<Item = Range<usize>> + '_ {
976 let range = if let Some(end) = end {
977 (self.selection.caret()..end).to_range(self.text().len().byte())
978 } else {
979 (self.selection.caret()..).to_range(self.text().len().byte())
980 };
981 self.inc_searcher.search_fwd(self.widget.text(), range)
982 }
983
984 /// Search incrementally from an [`IncSearch`] request in reverse
985 ///
986 /// This will match the Regex pattern from the current position of
987 /// the caret in reverse. if `start` is [`Some`], the search will
988 /// end at the requested [`Point`].
989 ///
990 /// [`IncSearch`]: https://docs.rs/duat/latest/duat/modes/struct.IncSearch.html
991 pub fn search_inc_rev(
992 &mut self,
993 start: Option<Point>,
994 ) -> impl Iterator<Item = Range<usize>> + '_ {
995 let range = if let Some(start) = start {
996 (start..self.selection.caret()).to_range(self.text().len().byte())
997 } else {
998 (..self.selection.caret()).to_range(self.text().len().byte())
999 };
1000 self.inc_searcher.search_rev(self.widget.text(), range)
1001 }
1002
1003 /// Whether the [`Selection`]'s selection matches the
1004 /// [`IncSearch`] request
1005 ///
1006 /// [`IncSearch`]: https://docs.rs/duat/latest/duat/modes/struct.IncSearch.html
1007 pub fn matches_inc(&mut self) -> bool {
1008 let range = self.selection.byte_range(self.widget.text());
1009 self.inc_searcher
1010 .matches(self.widget.text().strs(range).unwrap().to_string().as_str())
1011 }
1012}
1013
1014// SAFETY: In theory, it should be impossible to maintain a reference
1015// to W after it has dropped, since the Handle would be mutably
1016// borrowing from said W, and you can only get a Cursor from Handles.
1017// Thus, the only thing which may have been dropped is the Selections
1018// within, which are accounted for.
1019unsafe impl<#[may_dangle] 'a, W: Widget + ?Sized + 'a, S: 'a> Drop for Cursor<'a, W, S> {
1020 fn drop(&mut self) {
1021 let selection = std::mem::take(&mut self.selection);
1022 let ([inserted_i, selections_taken], last_selection_overhangs) = self
1023 .widget
1024 .text_mut()
1025 .selections_mut()
1026 .insert(self.n, selection, self.was_main);
1027
1028 if let Some(next_i) = self.next_i.as_ref()
1029 && inserted_i <= next_i.get()
1030 {
1031 let go_to_next = !last_selection_overhangs as usize;
1032 next_i.set(
1033 next_i
1034 .get()
1035 .saturating_sub(selections_taken)
1036 .max(inserted_i)
1037 + go_to_next,
1038 )
1039 }
1040 }
1041}
1042
1043impl<'a, W: Widget + ?Sized, S> std::fmt::Debug for Cursor<'a, W, S> {
1044 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1045 f.debug_struct("Cursor")
1046 .field("selection", &self.selection)
1047 .finish_non_exhaustive()
1048 }
1049}
1050
1051/// An [`Iterator`] overf all [`Cursor`]s
1052pub struct Cursors<'a, W: Widget + ?Sized, S> {
1053 next_i: Rc<Cell<usize>>,
1054 widget: &'a mut W,
1055 area: &'a Area,
1056 inc_searcher: RefMut<'a, S>,
1057}
1058
1059impl<'a, W: Widget + ?Sized, S> Cursors<'a, W, S> {
1060 /// Creates a new [`Cursors`]
1061 pub(crate) fn new(
1062 next_i: usize,
1063 widget: &'a mut W,
1064 area: &'a Area,
1065 inc_searcher: RefMut<'a, S>,
1066 ) -> Self {
1067 Self {
1068 next_i: Rc::new(Cell::new(next_i)),
1069 widget,
1070 area,
1071 inc_searcher,
1072 }
1073 }
1074}
1075
1076impl<'a, 'lend, W: Widget + ?Sized, S> Lending<'lend> for Cursors<'a, W, S> {
1077 type Lend = Cursor<'lend, W, S>;
1078}
1079
1080impl<'a, W: Widget + ?Sized, S> Lender for Cursors<'a, W, S> {
1081 fn next<'lend>(&'lend mut self) -> Option<<Self as Lending<'lend>>::Lend> {
1082 let current_i = self.next_i.get();
1083 let (selection, was_main) = self.widget.text_mut().selections_mut().remove(current_i)?;
1084
1085 Some(Cursor::new(
1086 (selection, current_i, was_main),
1087 (self.widget, self.area),
1088 Some(self.next_i.clone()),
1089 &mut self.inc_searcher,
1090 false,
1091 ))
1092 }
1093}
1094
1095/// A position that a [`Cursor`] can move to
1096///
1097/// This will come either in the form of [`Point`]s or byte indices
1098/// (in the form of `usize`). It can be a single value, like
1099/// `Point::default()` or `3`, in which case only the [caret] will
1100/// move, not affecting the [anchor].
1101///
1102/// Or it could be a [range], like `p1..p2` or `..=1000`, in which
1103/// case the caret will be placed at the end, while the anchor will be
1104/// placed at the start.
1105///
1106/// [caret]: Cursor::caret
1107/// [anchor]: Cursor::anchor
1108/// [range]: std::ops::RangeBounds
1109pub trait CaretOrRange {
1110 /// Internal movement function for monomorphization
1111 #[doc(hidden)]
1112 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>);
1113}
1114
1115impl CaretOrRange for Point {
1116 #[track_caller]
1117 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1118 cursor.selection.move_to(self, cursor.widget.text());
1119 }
1120}
1121
1122impl CaretOrRange for Range<Point> {
1123 #[track_caller]
1124 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1125 assert!(
1126 self.start <= self.end,
1127 "slice index start is larger than end"
1128 );
1129
1130 cursor.selection.move_to(self.start, cursor.widget.text());
1131 if self.start < self.end {
1132 cursor.set_anchor();
1133 cursor.selection.move_to(self.end, cursor.widget.text());
1134 if self.end < cursor.widget.text().len() {
1135 cursor.move_hor(-1);
1136 }
1137 } else {
1138 cursor.unset_anchor();
1139 }
1140 }
1141}
1142
1143impl CaretOrRange for RangeInclusive<Point> {
1144 #[track_caller]
1145 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1146 assert!(
1147 self.start() <= self.end(),
1148 "slice index start is larger than end"
1149 );
1150
1151 cursor
1152 .selection
1153 .move_to(*self.start(), cursor.widget.text());
1154 cursor.set_anchor();
1155 cursor.selection.move_to(*self.end(), cursor.widget.text());
1156 }
1157}
1158
1159impl CaretOrRange for RangeFrom<Point> {
1160 #[track_caller]
1161 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1162 cursor.selection.move_to(self.start, cursor.widget.text());
1163 if self.start < cursor.text().len() {
1164 cursor.set_anchor();
1165 cursor
1166 .selection
1167 .move_to(cursor.widget.text().len(), cursor.widget.text());
1168 cursor.move_hor(-1);
1169 } else {
1170 cursor.unset_anchor();
1171 }
1172 }
1173}
1174
1175impl CaretOrRange for RangeTo<Point> {
1176 #[track_caller]
1177 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1178 cursor.move_to_start();
1179 if Point::default() < self.end {
1180 cursor.set_anchor();
1181 cursor.selection.move_to(self.end, cursor.widget.text());
1182 cursor.move_hor(-1);
1183 } else {
1184 cursor.unset_anchor();
1185 }
1186 }
1187}
1188
1189impl CaretOrRange for RangeToInclusive<Point> {
1190 #[track_caller]
1191 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1192 cursor.move_to_start();
1193 cursor.set_anchor();
1194 cursor.selection.move_to(self.end, cursor.widget.text());
1195 }
1196}
1197
1198impl CaretOrRange for usize {
1199 #[track_caller]
1200 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1201 cursor.selection.move_to(
1202 cursor.widget.text().point_at_byte(self),
1203 cursor.widget.text(),
1204 )
1205 }
1206}
1207
1208impl CaretOrRange for Range<usize> {
1209 #[track_caller]
1210 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1211 assert!(
1212 self.start <= self.end,
1213 "slice index start is larger than end"
1214 );
1215
1216 cursor.selection.move_to(
1217 cursor.widget.text().point_at_byte(self.start),
1218 cursor.widget.text(),
1219 );
1220 if self.start < self.end {
1221 cursor.set_anchor();
1222 cursor.selection.move_to(
1223 cursor.widget.text().point_at_byte(self.end),
1224 cursor.widget.text(),
1225 );
1226 if self.end < cursor.widget.text().len().byte() {
1227 cursor.move_hor(-1);
1228 }
1229 } else {
1230 cursor.unset_anchor();
1231 }
1232 }
1233}
1234
1235impl CaretOrRange for RangeInclusive<usize> {
1236 #[track_caller]
1237 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1238 assert!(
1239 self.start() <= self.end(),
1240 "slice index start is larger than end"
1241 );
1242
1243 cursor.selection.move_to(
1244 cursor.widget.text().point_at_byte(*self.start()),
1245 cursor.widget.text(),
1246 );
1247 cursor.set_anchor();
1248 cursor.selection.move_to(
1249 cursor.widget.text().point_at_byte(*self.end()),
1250 cursor.widget.text(),
1251 );
1252 }
1253}
1254
1255impl CaretOrRange for RangeFrom<usize> {
1256 #[track_caller]
1257 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1258 cursor.selection.move_to(
1259 cursor.widget.text().point_at_byte(self.start),
1260 cursor.widget.text(),
1261 );
1262 if self.start < cursor.text().len().byte() {
1263 cursor.set_anchor();
1264 cursor
1265 .selection
1266 .move_to(cursor.widget.text().len(), cursor.widget.text());
1267 cursor.move_hor(-1);
1268 } else {
1269 cursor.unset_anchor();
1270 }
1271 }
1272}
1273
1274impl CaretOrRange for RangeTo<usize> {
1275 #[track_caller]
1276 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1277 cursor.move_to_start();
1278 if self.end > 0 {
1279 cursor.set_anchor();
1280 cursor.selection.move_to(
1281 cursor.widget.text().point_at_byte(self.end),
1282 cursor.widget.text(),
1283 );
1284 cursor.move_hor(-1);
1285 } else {
1286 cursor.unset_anchor();
1287 }
1288 }
1289}
1290
1291impl CaretOrRange for RangeToInclusive<usize> {
1292 #[track_caller]
1293 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1294 cursor.move_to_start();
1295 cursor.set_anchor();
1296 cursor.selection.move_to(
1297 cursor.widget.text().point_at_byte(self.end),
1298 cursor.widget.text(),
1299 );
1300 }
1301}
1302
1303impl CaretOrRange for RangeFull {
1304 #[track_caller]
1305 fn move_to<W: Widget + ?Sized, S>(self, cursor: &mut Cursor<'_, W, S>) {
1306 cursor.move_to_start();
1307 if cursor.text().len() > Point::default() {
1308 cursor.set_anchor();
1309 cursor
1310 .selection
1311 .move_to(cursor.widget.text().len(), cursor.widget.text());
1312 }
1313 }
1314}