duat_core/mode/helper/
mod.rs

1//! A helper struct for [`Mode`]s with [`Cursors`]
2//!
3//! This struct can edit [`Text`] in a declarative way, freeing the
4//! [`Mode`]s from worrying about synchronization of the
5//! cursors and dealing with editing the text directly.
6//!
7//! [`Mode`]: super::Mode
8use std::{array::IntoIter, ops::RangeBounds};
9
10pub use self::cursors::{Cursor, Cursors};
11use crate::{
12    cfg::{IterCfg, PrintCfg},
13    text::{Change, Point, RegexPattern, Searcher, Text},
14    ui::Area,
15    widgets::{File, Widget},
16};
17
18/// The [`Cursor`] and [`Cursors`] structs
19mod cursors;
20
21/// A struct used by [`Mode`]s to edit [`Text`]
22///
23/// You will want to use this struct when editing [`Widget`]s
24/// with [`Cursors`]. For example, let's say you want to create an
25/// mode for the [`File`] widget:
26///
27/// ```rust
28/// # use duat_core::{mode::{EditHelper, Mode, KeyEvent, Cursors}, ui::Ui, widgets::File};
29/// /// A very basic example Mode.
30/// #[derive(Clone)]
31/// struct PlacesCharactersAndMoves;
32///
33/// impl<U: Ui> Mode<U> for PlacesCharactersAndMoves {
34///     type Widget = File;
35///     /* ... */
36/// #   fn send_key(&mut self, key: KeyEvent, widget: &mut File, area: &U::Area) {
37/// #       todo!();
38/// #   }
39/// # }
40/// ```
41///
42/// In order to modify the widget, you must implement the
43/// [`Mode::send_key`] method. In it, you receive the following:
44///
45/// - The [key].
46/// - A [`&mut Self::Widget`].
47/// - An [`Area`], which you can resize and modify it in other ways.
48/// - The current [`Cursors`] of the widget, these should be modified
49///   by the [`EditHelper`].
50///
51/// In a [`Mode`] without cursors, you'd probably want to run
52/// [`Cursors::clear`], in order to make sure there are no cursors.
53///
54/// ```rust
55/// # use duat_core::{
56/// #     mode::{key, Cursors, EditHelper, Mode, KeyCode, KeyEvent}, ui::Ui, widgets::File,
57/// # };
58/// # #[derive(Clone)]
59/// # struct PlacesCharactersAndMoves;
60/// impl<U: Ui> Mode<U> for PlacesCharactersAndMoves {
61/// #   type Widget = File;
62///     /* ... */
63///     fn send_key(&mut self, key: KeyEvent, widget: &mut File, area: &U::Area) {
64///         match key {
65///             // actions based on the key pressed
66///             key!(KeyCode::Char('c')) => {
67///                 /* Do something when the character 'c' is typed. */
68///             }
69///             /* Matching the rest of the keys */
70/// #           _ => todo!()
71///         }
72///     }
73/// # }
74/// ```
75///
76/// (You can use the [`key!`] macro in order to match [`KeyEvent`]s).
77///
78/// With the `EditHelper`, you can modify [`Text`] in a simplified
79/// way. This is done by two actions, [editing] and [moving]. You
80/// can only do one of these on any number of cursors at the same
81/// time.
82///
83/// ```rust
84/// # use duat_core::{
85/// #     mode::{ key, Cursors, EditHelper, Mode, KeyCode, KeyEvent, KeyMod}, ui::Ui, widgets::File,
86/// # };
87/// # #[derive(Clone)]
88/// # struct PlacesCharactersAndMoves;
89/// impl<U: Ui> Mode<U> for PlacesCharactersAndMoves {
90/// #   type Widget = File;
91///     /* ... */
92///     fn send_key(&mut self, key: KeyEvent, file: &mut File, area: &U::Area) {
93///         let mut helper = EditHelper::new(file, area);
94///         helper.cursors_mut().make_excl();
95///         
96///         match key {
97///             key!(KeyCode::Char(c)) => {
98///                 helper.edit_many(.., |e| e.insert('c'));
99///                 helper.move_many(.., |mut m| m.move_hor(1));
100///             },
101///             key!(KeyCode::Right, KeyMod::SHIFT) => {
102///                 helper.move_many(.., |mut m| {
103///                     if m.anchor().is_none() {
104///                         m.set_anchor()
105///                     }
106///                     m.move_hor(1)
107///                 })
108///             }
109///             key!(KeyCode::Right) => {
110///                 helper.move_many(.., |mut m| {
111///                     m.unset_anchor();
112///                     m.move_hor(1)
113///                 })
114///             }
115///             /* Predictable remaining implementations */
116/// #           _ => todo!()
117///         }
118///     }
119/// # }
120/// ```
121///
122/// Notice the [`Cursors::make_excl`]. In Duat, there are two types of
123/// [`Cursors`], inclusive and exclusive. The only difference between
124/// them is that in inclusive cursors, the selection acts like a Rust
125/// inclusive selection (`..=`), while in exclusive cursors, it acts
126/// like an exclusive selection (`..`).
127///
128/// [`Mode`]: super::Mode
129/// [`Text`]: crate::text::Text
130/// [`CmdLine`]: crate::widgets::CmdLine
131/// [`&mut Self::Widget`]: super::Mode::Widget
132/// [`Mode::send_key`]: super::Mode::send_key
133/// [key]: super::KeyEvent
134/// [`Self::Widget`]: super::Mode::Widget
135/// [`Some(cursors)`]: Some
136/// [`Ui::Area`]: crate::ui::Ui::Area
137/// [commands]: crate::cmd
138/// [`key!`]: super::key
139/// [`KeyEvent`]: super::KeyEvent
140/// [editing]: Editor
141/// [moving]: Mover
142pub struct EditHelper<'a, W, A, S>
143where
144    W: Widget<A::Ui> + 'static,
145    A: Area,
146{
147    widget: &'a mut W,
148    area: &'a A,
149    searcher: S,
150}
151
152impl<'a, W, A> EditHelper<'a, W, A, ()>
153where
154    W: Widget<A::Ui> + 'static,
155    A: Area,
156{
157    /// Returns a new instance of [`EditHelper`]
158    pub fn new(widget: &'a mut W, area: &'a A) -> Self {
159        widget.text_mut().enable_cursors();
160        widget.cursors_mut().unwrap().populate();
161        EditHelper { widget, area, searcher: () }
162    }
163}
164
165impl<W, A, S> EditHelper<'_, W, A, S>
166where
167    W: Widget<A::Ui> + 'static,
168    A: Area,
169{
170    ////////// Editing functions
171
172    /// Edits on the `nth` [`Cursor`]'s selection
173    ///
174    /// Since the editing function takes [`Editor`] as an argument,
175    /// you cannot change the selection of the [`Cursor`].
176    ///
177    /// If you want to move the `nth` cursor, see [`move_nth`],
178    /// if you want to edit on the main cursor, see [`edit_main`],
179    /// if you want to edit each cursor, see [`edit_many`].
180    ///
181    /// [`move_nth`]: Self::move_nth
182    /// [`edit_main`]: Self::edit_main
183    /// [`edit_many`]: Self::edit_many
184    pub fn edit_nth(&mut self, n: usize, edit: impl FnOnce(&mut Editor<A, W>)) {
185        let cursors = self.widget.cursors_mut().unwrap();
186        let is_incl = cursors.is_incl();
187        let Some((mut cursor, was_main)) = cursors.remove(n) else {
188            panic!("Cursor index {n} out of bounds");
189        };
190
191        let mut shift = (0, 0, 0);
192
193        edit(&mut Editor::<A, W>::new(
194            &mut cursor,
195            self.widget,
196            self.area,
197            &mut shift,
198            was_main,
199            is_incl,
200        ));
201
202        let cursors = self.widget.cursors_mut().unwrap();
203        cursors.insert(n, was_main, cursor);
204
205        let cfg = self.cfg();
206        self.widget
207            .text_mut()
208            .shift_cursors(n + 1, shift, self.area, cfg);
209    }
210
211    /// Edits on the main [`Cursor`]'s selection
212    ///
213    /// Since the editing function takes [`Editor`] as an argument,
214    /// you cannot change the selection of the [`Cursor`].
215    ///
216    /// If you want to move the main cursor, see [`move_main`],
217    /// if you want to edit on the `nth` cursor, see [`edit_nth`],
218    /// if you want to edit each cursor, see [`edit_many`].
219    ///
220    /// [`move_main`]: Self::move_main
221    /// [`edit_nth`]: Self::edit_nth
222    /// [`edit_many`]: Self::edit_many
223    pub fn edit_main(&mut self, edit: impl FnOnce(&mut Editor<A, W>)) {
224        let n = self.widget.cursors().unwrap().main_index();
225        self.edit_nth(n, edit);
226    }
227
228    /// Edits on a range of [`Cursor`]s
229    ///
230    /// Since the editing function takes [`Editor`] as an argument,
231    /// you cannot change the selection of the [`Cursor`].
232    ///
233    /// If you want to move many cursors, see [`move_many`],
234    /// if you want to edit on a specific cursor, see [`edit_nth`]
235    /// or [`edit_main`].
236    ///
237    /// [`move_many`]: Self::move_many
238    /// [`edit_nth`]: Self::edit_nth
239    /// [`edit_main`]: Self::edit_main
240    pub fn edit_many(
241        &mut self,
242        range: impl RangeBounds<usize> + Clone,
243        mut f: impl FnMut(&mut Editor<A, W>),
244    ) {
245        let cfg = self.widget.print_cfg();
246        let cursors = self.widget.cursors_mut().unwrap();
247        let is_incl = cursors.is_incl();
248        let (start, end) = crate::get_ends(range, cursors.len());
249        assert!(end <= cursors.len(), "Cursor index {end} out of bounds");
250        let mut removed: Vec<(Cursor, bool)> = cursors.drain(start..).collect();
251
252        let mut shift = (0, 0, 0);
253
254        for (i, (mut cursor, was_main)) in removed.splice(..(end - start), []).enumerate() {
255            let guess_i = i + start;
256            cursor.shift_by(shift, self.widget.text(), self.area, cfg);
257
258            let mut editor = Editor::new(
259                &mut cursor,
260                self.widget,
261                self.area,
262                &mut shift,
263                was_main,
264                is_incl,
265            );
266            f(&mut editor);
267
268            self.widget
269                .cursors_mut()
270                .unwrap()
271                .insert(guess_i, was_main, cursor);
272        }
273
274        for (i, (mut cursor, was_main)) in removed.into_iter().enumerate() {
275            let guess_i = i + end;
276            cursor.shift_by(shift, self.widget.text_mut(), self.area, cfg);
277            self.widget
278                .cursors_mut()
279                .unwrap()
280                .insert(guess_i, was_main, cursor);
281        }
282    }
283
284    ////////// Moving functions
285
286    /// Moves the nth [`Cursor`]'s selection
287    ///
288    /// Since the moving function takes [`Mover`] as an argument, this
289    /// method cannot be used to change the [`Text`] in any way.
290    ///
291    /// At the end of the movement, if the cursor intersects any
292    /// other, they will be merged into one.
293    ///
294    /// If you want to edit on the `nth` cursor, see [`edit_nth`],
295    /// if you want to move the main cursor, see [`move_main`], if you
296    /// want to move each cursor, see [`move_many`].
297    ///
298    /// [`edit_nth`]: Self::edit_nth
299    /// [`move_main`]: Self::move_main
300    /// [`move_many`]: Self::move_many
301    pub fn move_nth<_T>(&mut self, n: usize, mov: impl FnOnce(Mover<A, S>) -> _T) {
302        let cfg = self.cfg();
303        let text = self.widget.text_mut();
304        let Some((cursor, is_main)) = text.cursors_mut().unwrap().remove(n) else {
305            panic!("Cursor index {n} out of bounds");
306        };
307
308        let mut cursor = Some(cursor);
309        mov(Mover::new(
310            &mut cursor,
311            is_main,
312            text,
313            self.area,
314            cfg,
315            &mut self.searcher,
316        ));
317
318        if let Some(cursor) = cursor {
319            text.cursors_mut().unwrap().insert(n, is_main, cursor);
320        }
321    }
322
323    /// Moves the main [`Cursor`]'s selection
324    ///
325    /// Since the moving function takes [`Mover`] as an argument, this
326    /// method cannot be used to change the [`Text`] in any way.
327    ///
328    /// At the end of the movement, if the cursor intersects any
329    /// other, they will be merged into one.
330    ///
331    /// If you want to move the main cursor, see [`edit_main`],
332    /// if you want to move the main cursor, see [`move_main`], if you
333    /// want to move each cursor, see [`move_many`].
334    ///
335    /// [`edit_main`]: Self::edit_main
336    /// [`move_main`]: Self::move_main
337    /// [`move_many`]: Self::move_many
338    pub fn move_main<_T>(&mut self, mov: impl FnOnce(Mover<A, S>) -> _T) {
339        let n = self.widget.cursors().unwrap().main_index();
340        self.move_nth(n, mov);
341    }
342
343    /// Moves a range of [`Cursor`]'s selections
344    ///
345    /// Since the moving function takes [`Mover`] as an argument, this
346    /// method cannot be used to change the [`Text`] in any way.
347    ///
348    /// At the end of the movement, if any of the cursors intersect
349    /// with each other, they will be merged into one.
350    ///
351    /// If you want to edit on many cursors, see [`edit_many`],
352    /// if you want to move a specific cursor, see [`move_nth`]
353    /// or [`move_main`].
354    ///
355    /// [`edit_many`]: Self::edit_many
356    /// [`move_nth`]: Self::move_nth
357    /// [`move_main`]: Self::move_main
358    pub fn move_many<_T>(
359        &mut self,
360        range: impl RangeBounds<usize> + Clone,
361        mut mov: impl FnMut(Mover<A, S>) -> _T,
362    ) {
363        let cfg = self.cfg();
364        let text = self.widget.text_mut();
365        let cursors = text.cursors_mut().unwrap();
366        let (start, end) = crate::get_ends(range.clone(), cursors.len());
367        assert!(end <= cursors.len(), "Cursor index {end} out of bounds");
368        let removed_cursors: Vec<(Cursor, bool)> = cursors.drain(range).collect();
369
370        for (i, (cursor, is_main)) in removed_cursors.into_iter().enumerate() {
371            let guess_i = i + start;
372            let mut cursor = Some(cursor);
373            mov(Mover::new(
374                &mut cursor,
375                is_main,
376                text,
377                self.area,
378                cfg,
379                &mut self.searcher,
380            ));
381
382            if let Some(cursor) = cursor {
383                text.cursors_mut().unwrap().insert(guess_i, is_main, cursor);
384            }
385        }
386    }
387
388    ////////// Getter functions
389
390    /// A shared reference to the [`Widget`]
391    pub fn widget(&self) -> &W {
392        self.widget
393    }
394
395    /// A mutable reference to the [`Widget`]
396    pub fn widget_mut(&mut self) -> &mut W {
397        self.widget
398    }
399
400    /// A shared reference to the [`Widget`]
401    pub fn text(&self) -> &Text {
402        self.widget.text()
403    }
404
405    /// A mutable reference to the [`Widget`]
406    pub fn text_mut(&mut self) -> &mut Text {
407        self.widget.text_mut()
408    }
409
410    /// A shared reference to the [`Widget`]
411    pub fn cursors(&self) -> &Cursors {
412        self.widget.text().cursors().unwrap()
413    }
414
415    /// A mutable reference to the [`Widget`]
416    pub fn cursors_mut(&mut self) -> &mut Cursors {
417        self.widget.text_mut().cursors_mut().unwrap()
418    }
419
420    /// Undoes the last moment in the history, if there is one
421    pub fn undo(&mut self) {
422        let cfg = self.widget.print_cfg();
423        self.widget.text_mut().undo(self.area, cfg);
424    }
425
426    /// Redoes the last moment in the history, if there is one
427    pub fn redo(&mut self) {
428        let cfg = self.widget.print_cfg();
429        self.widget.text_mut().redo(self.area, cfg);
430    }
431
432    /// Finishes the current moment and adds a new one to the history
433    pub fn new_moment(&mut self) {
434        self.widget.text_mut().new_moment();
435    }
436
437    /// The [`PrintCfg`] in use
438    pub fn cfg(&self) -> PrintCfg {
439        self.widget.print_cfg()
440    }
441}
442
443impl<'a, A> EditHelper<'a, File, A, Searcher>
444where
445    A: Area,
446{
447    /// Returns a new instance of [`EditHelper`]
448    pub fn new_inc(widget: &'a mut File, area: &'a A, searcher: Searcher) -> Self {
449        let cfg = widget.print_cfg();
450        if let Some(c) = widget.cursors_mut() {
451            c.populate()
452        }
453        widget.text_mut().remove_cursors(area, cfg);
454
455        EditHelper { widget, area, searcher }
456    }
457}
458
459/// A cursor that can edit [`Text`], but can't alter selections
460///
461/// This struct will be used only inside functions passed to the
462/// [`edit_*`] family of methods from the [`EditHelper`].
463///
464/// To make edits, you can use two different functions. You can either
465/// [`replace`] or you can [`insert`]. The former will completely
466/// replace the [`Cursor`]'s selection, while the latter will only
467/// place the edit before the position of the `caret`, which could be
468/// either in the start or the end of the selection.
469///
470/// ```rust
471/// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
472/// # fn test<S>(helper: &mut EditHelper<File, impl Area, S>) {
473/// helper.edit_main(|e| {
474///     e.replace("my replacement");
475///     e.insert(" and my edit");
476/// });
477/// helper.move_main(|mut m| {
478///     m.move_hor(" and my edit".chars().count() as i32);
479///     m.set_anchor();
480///     m.move_hor(-("my replacement and my edit".chars().count() as i32));
481///     let sel: String = m.selection().into_iter().collect();
482///     assert_eq!(sel, "my replacement and my edit".to_string());
483/// });
484/// # }
485/// ```
486///
487/// [`edit_*`]: EditHelper::edit_nth
488/// [`replace`]: Editor::replace
489/// [`insert`]: Editor::insert
490pub struct Editor<'a, 'b, A, W>
491where
492    A: Area,
493    W: Widget<A::Ui>,
494{
495    cursor: &'a mut Cursor,
496    widget: &'b mut W,
497    area: &'b A,
498    shift: &'a mut (i32, i32, i32),
499    is_main: bool,
500    is_incl: bool,
501}
502
503impl<'a, 'b, A, W> Editor<'a, 'b, A, W>
504where
505    A: Area,
506    W: Widget<A::Ui>,
507{
508    /// Returns a new instance of [`Editor`]
509    #[allow(clippy::too_many_arguments)]
510    fn new(
511        cursor: &'a mut Cursor,
512        widget: &'b mut W,
513        area: &'b A,
514        shift: &'a mut (i32, i32, i32),
515        is_main: bool,
516        is_incl: bool,
517    ) -> Self {
518        Self {
519            cursor,
520            widget,
521            area,
522            shift,
523            is_main,
524            is_incl,
525        }
526    }
527
528    /// Replaces the entire selection with new text
529    ///
530    /// If the `caret` is behind the `anchor` (or in the same spot),
531    /// after replacing the selection, the `caret` will be placed on
532    /// the start of the selection, while the `anchor` will be placed
533    /// on the new end. If it is ahead, it will be placed ahead.
534    ///
535    /// If there is no selection, then this has the same effect as
536    /// [`insert`].
537    ///
538    /// [`insert`]: Self::insert
539    pub fn replace(&mut self, edit: impl ToString) {
540        let (p0, p1) = self.cursor.point_range(self.is_incl, self.widget.text());
541        let change = Change::new(edit.to_string(), (p0, p1), self.widget.text());
542        let edit_len = change.added_text().len();
543        let end = change.added_end();
544
545        self.edit(change);
546
547        let text = self.widget.text();
548
549        let cfg = self.cfg();
550        if let Some(anchor) = self.cursor.anchor()
551            && anchor >= self.cursor.caret()
552            && edit_len > 0
553        {
554            self.cursor.swap_ends();
555            self.cursor.move_to(end, text, self.area, cfg);
556            self.cursor.swap_ends();
557        } else {
558            self.cursor.unset_anchor();
559            self.cursor.move_to(end, text, self.area, cfg);
560        }
561    }
562
563    /// Inserts new text directly behind the `caret`
564    ///
565    /// The selection remains unaltered, if the `anchor` is ahead of
566    /// the `caret`, it will move forwards by `edit.chars().count()`.
567    ///
568    /// If you wish to replace the selected text, see [`replace`]
569    ///
570    /// [`replace`]: Self::replace
571    pub fn insert(&mut self, edit: impl ToString) {
572        let range = (self.cursor.caret(), self.cursor.caret());
573        let change = Change::new(edit.to_string(), range, self.widget.text());
574        let (added, taken) = (change.added_end(), change.taken_end());
575
576        self.edit(change);
577
578        if let Some(anchor) = self.cursor.anchor()
579            && anchor >= self.cursor.caret()
580        {
581            let new_anchor = anchor + added - taken;
582            self.cursor.swap_ends();
583            self.cursor
584                .move_to(new_anchor, self.widget.text(), self.area, self.cfg());
585            self.cursor.swap_ends();
586        }
587    }
588
589    /// If there is a selection, acts like [`replace`], otherwise acts
590    /// like [`insert`]
591    ///
592    /// This only makes a difference if your selections are
593    /// [inclusive], since a [`replace`] when the anchor is [`None`]
594    /// would still include one character.
595    ///
596    /// [`replace`]: Editor::replace
597    /// [`insert`]: Editor::insert
598    /// [inclusive]: Cursors::is_incl
599    pub fn insert_or_replace(&mut self, edit: impl ToString) {
600        if self.anchor().is_some() {
601            self.replace(edit)
602        } else {
603            self.insert(edit)
604        }
605    }
606
607    /// Edits the file with a [`Change`]
608    fn edit(&mut self, change: Change<String>) {
609        self.shift.0 += change.added_end().byte() as i32 - change.taken_end().byte() as i32;
610        self.shift.1 += change.added_end().char() as i32 - change.taken_end().char() as i32;
611        self.shift.2 += change.added_end().line() as i32 - change.taken_end().line() as i32;
612
613        let text = self.widget.text_mut();
614        self.cursor.change_i = text.apply_change(self.cursor.change_i, change)
615    }
616
617    ////////// Iteration functions
618
619    /// Iterates over the [`char`]s
620    ///
621    /// This iteration will begin on the `caret`. It will also include
622    /// the [`Point`] of each `char`
623    pub fn iter(&self) -> impl Iterator<Item = (Point, char)> + '_ {
624        self.widget.text().chars_fwd(self.caret())
625    }
626
627    /// Iterates over the [`char`]s, in reverse
628    ///
629    /// This iteration will begin on the `caret`. It will also include
630    /// the [`Point`] of each `char`
631    pub fn iter_rev(&self) -> impl Iterator<Item = (Point, char)> + '_ {
632        self.widget.text().chars_rev(self.caret())
633    }
634
635    /// Searches the [`Text`] for a regex
636    ///
637    /// The search will begin on the `caret`, and returns the bounding
638    /// [`Point`]s, alongside the match. If an `end` is provided,
639    /// the search will stop at the given [`Point`].
640    ///
641    /// # Panics
642    ///
643    /// If the regex is not valid, this method will panic.
644    ///
645    /// ```rust
646    /// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
647    /// fn search_nth_paren<S>(
648    ///     helper: &mut EditHelper<File, impl Area, S>,
649    ///     n: usize,
650    /// ) {
651    ///     helper.move_many(.., |mut m| {
652    ///         let mut nth = m.search_fwd('(', None).nth(n);
653    ///         if let Some((start, end)) = nth {
654    ///             m.move_to(start);
655    ///             m.set_anchor();
656    ///             m.move_to(end);
657    ///         }
658    ///     })
659    /// }
660    /// ```
661    pub fn search_fwd<R: RegexPattern>(
662        &mut self,
663        pat: R,
664        end: Option<Point>,
665    ) -> impl Iterator<Item = R::Match> + '_ {
666        let start = self.cursor.caret().byte();
667        let text = self.widget.text_mut();
668        match end {
669            Some(end) => text.search_fwd(pat, start..end.byte()).unwrap(),
670            None => {
671                let end = text.len().byte();
672                text.search_fwd(pat, start..end).unwrap()
673            }
674        }
675    }
676
677    /// Searches the [`Text`] for a regex, in reverse
678    ///
679    /// The search will begin on the `caret`, and returns the bounding
680    /// [`Point`]s, alongside the match. If a `start` is provided,
681    /// the search will stop at the given [`Point`].
682    ///
683    /// # Panics
684    ///
685    /// If the regex is not valid, this method will panic.
686    ///
687    /// ```rust
688    /// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
689    /// fn search_nth_rev<S>(
690    ///     helper: &mut EditHelper<File, impl Area, S>,
691    ///     n: usize,
692    ///     s: &str,
693    /// ) {
694    ///     helper.move_many(.., |mut m| {
695    ///         let mut nth = m.search_rev(s, None).nth(n);
696    ///         if let Some((start, end)) = nth {
697    ///             m.move_to(start);
698    ///             m.set_anchor();
699    ///             m.move_to(end);
700    ///         }
701    ///     })
702    /// }
703    /// ```
704    pub fn search_rev<R: RegexPattern>(
705        &mut self,
706        pat: R,
707        start: Option<Point>,
708    ) -> impl Iterator<Item = R::Match> + '_ {
709        let end = self.cursor.caret().byte();
710        let start = start.unwrap_or_default();
711        let text = self.widget.text_mut();
712        text.search_rev(pat, start.byte()..end).unwrap()
713    }
714
715    ////////// Anchor modification
716
717    /// Returns and takes the anchor of the [`Cursor`].
718    pub fn unset_anchor(&mut self) -> Option<Point> {
719        self.cursor.unset_anchor()
720    }
721
722    /// Sets the `anchor` to the current `caret`
723    pub fn set_anchor(&mut self) {
724        self.cursor.set_anchor()
725    }
726
727    /// Swaps the position of the `caret` and `anchor`
728    pub fn swap_ends(&mut self) {
729        self.cursor.swap_ends();
730    }
731
732    /// Sets the caret of the [`Cursor`] on the start of the selection
733    pub fn set_caret_on_start(&mut self) {
734        if let Some(anchor) = self.anchor()
735            && anchor < self.caret()
736        {
737            self.swap_ends();
738        }
739    }
740
741    /// Sets the caret of the [`Cursor`] on the end of the selection
742    pub fn set_caret_on_end(&mut self) {
743        if let Some(anchor) = self.anchor()
744            && anchor > self.caret()
745        {
746            self.swap_ends();
747        }
748    }
749
750    ////////// Queries
751
752    /// Returns the [`Cursor`]'s selection
753    ///
754    /// The reason why this return value is `IntoIter<&str, 2>` is
755    /// because the [`Text`] utilizes an underlying [`GapBuffer`]
756    /// to store the characters. This means that the text is
757    /// always separated into two distinct chunks.
758    ///
759    /// If this [`Cursor`]'s selection happens to be entirely within
760    /// one of these chunks, the other `&str` will just be empty.
761    ///
762    /// [`GapBuffer`]: gapbuf::GapBuffer
763    pub fn selection(&self) -> IntoIter<&str, 2> {
764        let anchor = self.anchor().unwrap_or(self.caret());
765        let (start, end) = if anchor < self.caret() {
766            (anchor, self.caret())
767        } else {
768            (self.caret(), anchor)
769        };
770        self.text()
771            .strs_in(start.byte()..end.byte() + self.is_incl() as usize)
772    }
773
774    /// Returns the needed level of indentation in the line of the
775    /// [`Point`]
776    ///
777    /// If the tree-sitter can figure out the indentation level, it
778    /// will return that. Otherwise, it will copy the level of
779    /// indentation of the last non empty line.
780    pub fn indent_on(&mut self, point: Point) -> usize {
781        let cfg = self.cfg();
782        let text = self.widget.text_mut();
783        if let Some(indent) = text.ts_indent_on(point, cfg) {
784            indent
785        } else {
786            let t_iter = text.iter_rev(point).no_ghosts().no_conceals();
787            let iter = self
788                .area
789                .rev_print_iter(t_iter, IterCfg::new(cfg))
790                // This should skip the current line and all empty lines before.
791                .skip_while(|(_, item)| {
792                    item.part.as_char().is_none_or(char::is_whitespace)
793                        || item.real.line() == point.line()
794                })
795                // And this should make sure we only capture one line.
796                .take_while(|(_, item)| item.part.as_char().is_none_or(|c| c != '\n'));
797            iter.fold(0, |mut indent, (caret, item)| {
798                indent = indent.max((caret.x + caret.len) as usize);
799                indent * item.part.as_char().is_none_or(char::is_whitespace) as usize
800            })
801        }
802    }
803
804    /// Returns the `caret`
805    pub fn caret(&self) -> Point {
806        self.cursor.caret()
807    }
808
809    /// How many characterss the caret is from the start of the line
810    pub fn caret_col(&self) -> usize {
811        self.iter_rev().take_while(|(_, c)| *c != '\n').count()
812    }
813
814    /// The visual distance between the caret and the start of the
815    /// [`Area`]
816    pub fn caret_vcol(&self) -> usize {
817        self.cursor.vcol()
818    }
819
820    /// The desired visual distance between the caret and the start of
821    /// the [`Area`]
822    pub fn desired_caret_vcol(&self) -> usize {
823        self.cursor.desired_vcol()
824    }
825
826    /// Returns the `anchor`
827    pub fn anchor(&self) -> Option<Point> {
828        self.cursor.anchor()
829    }
830
831    /// How many characterss the anchor is from the start of the line
832    pub fn anchor_col(&self) -> Option<usize> {
833        self.anchor().map(|a| {
834            self.text()
835                .chars_rev(a)
836                .take_while(|(_, c)| *c != '\n')
837                .count()
838        })
839    }
840
841    /// The visual distance between the anchor and the start of the
842    /// [`Area`]
843    pub fn anchor_vcol(&self) -> Option<usize> {
844        self.cursor.anchor_vcol()
845    }
846
847    /// The desired visual distance between the anchor and the start
848    /// of the [`Area`]
849    pub fn desired_anchor_vcol(&self) -> Option<usize> {
850        self.cursor.desired_anchor_vcol()
851    }
852
853    /// Returns `true` if the `anchor` exists before the `caret`
854    pub fn anchor_is_start(&self) -> bool {
855        self.anchor().is_none_or(|anchor| anchor < self.caret())
856    }
857
858    /// Whether or not this is the main [`Cursor`]
859    pub fn is_main(&self) -> bool {
860        self.is_main
861    }
862
863    /// Whether or not this cursor's selections are inclusive
864    pub fn is_incl(&self) -> bool {
865        self.text().cursors().unwrap().is_incl()
866    }
867
868    pub fn text(&self) -> &Text {
869        self.widget.text()
870    }
871
872    /// The [`PrintCfg`] in use
873    pub fn cfg(&self) -> PrintCfg {
874        self.widget.print_cfg()
875    }
876}
877
878/// A cursor that can alter the selection, but can't edit
879pub struct Mover<'a, A, S>
880where
881    A: Area,
882{
883    cursor: &'a mut Option<Cursor>,
884    is_main: bool,
885    text: &'a mut Text,
886    area: &'a A,
887    cfg: PrintCfg,
888    inc_searcher: &'a mut S,
889}
890
891impl<'a, A, S> Mover<'a, A, S>
892where
893    A: Area,
894{
895    /// Returns a new instance of `Mover`
896    fn new(
897        cursor: &'a mut Option<Cursor>,
898        is_main: bool,
899        text: &'a mut Text,
900        area: &'a A,
901        cfg: PrintCfg,
902        inc_searcher: &'a mut S,
903    ) -> Self {
904        Self {
905            cursor,
906            is_main,
907            text,
908            area,
909            cfg,
910            inc_searcher,
911        }
912    }
913
914    ////////// Movement functions
915
916    /// Moves the cursor horizontally. May cause vertical movement
917    pub fn move_hor(&mut self, count: i32) {
918        let cursor = self.cursor.as_mut().unwrap();
919        cursor.move_hor(count, self.text, self.area, self.cfg);
920    }
921
922    /// Moves the cursor vertically. May cause horizontal movement
923    pub fn move_ver(&mut self, count: i32) {
924        let cursor = self.cursor.as_mut().unwrap();
925        cursor.move_ver(count, self.text, self.area, self.cfg);
926    }
927
928    /// Moves the cursor vertically. May cause horizontal movement
929    pub fn move_ver_wrapped(&mut self, count: i32) {
930        let cursor = self.cursor.as_mut().unwrap();
931        cursor.move_ver_wrapped(count, self.text, self.area, self.cfg);
932    }
933
934    /// Moves the cursor to a [`Point`]
935    ///
936    /// - If the position isn't valid, it will move to the "maximum"
937    ///   position allowed.
938    pub fn move_to(&mut self, point: Point) {
939        let cursor = self.cursor.as_mut().unwrap();
940        cursor.move_to(point, self.text, self.area, self.cfg);
941    }
942
943    /// Moves the cursor to a `line` and a `column`
944    ///
945    /// - If the coords isn't valid, it will move to the "maximum"
946    ///   position allowed.
947    pub fn move_to_coords(&mut self, line: usize, col: usize) {
948        let at = self.text.point_at_line(line.min(self.text.len().line()));
949        let (point, _) = self.text.chars_fwd(at).take(col + 1).last().unwrap();
950        self.move_to(point);
951    }
952
953    ////////// Cursor addition and removal
954
955    /// Copies the current [`Cursor`] in place
956    ///
957    /// This will leave an additional [`Cursor`] with the current
958    /// selection. Do note that normal intersection rules apply, so,
959    /// if at the end of the movement, this cursor intersects with any
960    /// other, one of them will be deleted.
961    ///
962    /// Returns the index of the new [`Cursor`], note that this might
963    /// change throughout the movement function, as new cursors might
964    /// be added before it, moving it ahead.
965    pub fn copy(&mut self) -> usize {
966        let cursors = self.text.cursors_mut().unwrap();
967        cursors.insert(0, false, self.cursor.unwrap())
968    }
969
970    /// Destroys the current [`Cursor`]
971    ///
972    /// Will not destroy it if it is the last [`Cursor`] left
973    ///
974    /// If this was the main cursor, the main cursor will now be the
975    /// cursor immediately behind it.
976    pub fn destroy(self) {
977        if self.text.cursors().unwrap().len() > 1 {
978            *self.cursor = None;
979        }
980    }
981
982    ////////// Anchor Manipulation
983
984    /// Returns and takes the anchor of the [`Cursor`].
985    pub fn unset_anchor(&mut self) -> Option<Point> {
986        let cursor = self.cursor.as_mut().unwrap();
987        cursor.unset_anchor()
988    }
989
990    /// Sets the `anchor` to the current `caret`
991    pub fn set_anchor(&mut self) {
992        let cursor = self.cursor.as_mut().unwrap();
993        cursor.set_anchor()
994    }
995
996    /// Swaps the position of the `caret` and `anchor`
997    pub fn swap_ends(&mut self) {
998        let cursor = self.cursor.as_mut().unwrap();
999        cursor.swap_ends();
1000    }
1001
1002    /// Sets the caret of the [`Cursor`] on the start of the selection
1003    pub fn set_caret_on_start(&mut self) {
1004        if let Some(anchor) = self.anchor()
1005            && anchor < self.caret()
1006        {
1007            self.swap_ends();
1008        }
1009    }
1010
1011    /// Sets the caret of the [`Cursor`] on the end of the selection
1012    pub fn set_caret_on_end(&mut self) {
1013        if let Some(anchor) = self.anchor()
1014            && anchor > self.caret()
1015        {
1016            self.swap_ends();
1017        }
1018    }
1019
1020    ////////// Text queries
1021
1022    /// Returns the [`char`] in the `caret`
1023    pub fn char(&self) -> char {
1024        self.text.char_at(self.cursor.unwrap().caret()).unwrap()
1025    }
1026
1027    /// Returns the [`Cursor`]'s selection
1028    ///
1029    /// The reason why this return value is `IntoIter<&str, 2>` is
1030    /// because the [`Text`] utilizes an underlying [`GapBuffer`]
1031    /// to store the characters. This means that the text is
1032    /// always separated into two distinct chunks.
1033    ///
1034    /// If this [`Cursor`]'s selection happens to be entirely within
1035    /// one of these chunks, the other `&str` will just be empty.
1036    ///
1037    /// [`GapBuffer`]: gapbuf::GapBuffer
1038    pub fn selection(&self) -> IntoIter<&str, 2> {
1039        let anchor = self.anchor().unwrap_or(self.caret());
1040        let (start, end) = if anchor < self.caret() {
1041            (anchor, self.caret())
1042        } else {
1043            (self.caret(), anchor)
1044        };
1045        self.text
1046            .strs_in(start.byte()..end.byte() + self.is_incl() as usize)
1047    }
1048
1049    /// Returns the length of the [`Text`], in [`Point`]
1050    pub fn len(&self) -> Point {
1051        self.text.len()
1052    }
1053
1054    /// Returns the position of the last [`char`] if there is one
1055    pub fn last_point(&self) -> Option<Point> {
1056        self.text.last_point()
1057    }
1058
1059    ////////// Iteration functions
1060
1061    /// Iterates over the [`char`]s
1062    ///
1063    /// This iteration will begin on the `caret`. It will also include
1064    /// the [`Point`] of each `char`
1065    pub fn fwd(&self) -> impl Iterator<Item = (Point, char)> + '_ {
1066        self.text.chars_fwd(self.caret())
1067    }
1068
1069    /// Iterates over the [`char`]s, in reverse
1070    ///
1071    /// This iteration will begin on the `caret`. It will also include
1072    /// the [`Point`] of each `char`
1073    pub fn rev(&self) -> impl Iterator<Item = (Point, char)> + '_ {
1074        self.text.chars_rev(self.caret())
1075    }
1076
1077    /// Searches the [`Text`] for a regex
1078    ///
1079    /// The search will begin on the `caret`, and returns the bounding
1080    /// [`Point`]s, alongside the match. If an `end` is provided,
1081    /// the search will stop at the given [`Point`].
1082    ///
1083    /// # Panics
1084    ///
1085    /// If the regex is not valid, this method will panic.
1086    ///
1087    /// ```rust
1088    /// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
1089    /// fn search_nth_paren<S>(
1090    ///     helper: &mut EditHelper<File, impl Area, S>,
1091    ///     n: usize,
1092    /// ) {
1093    ///     helper.move_many(.., |mut m| {
1094    ///         let mut nth = m.search_fwd('(', None).nth(n);
1095    ///         if let Some((start, end)) = nth {
1096    ///             m.move_to(start);
1097    ///             m.set_anchor();
1098    ///             m.move_to(end);
1099    ///         }
1100    ///     })
1101    /// }
1102    /// ```
1103    pub fn search_fwd<R: RegexPattern>(
1104        &mut self,
1105        pat: R,
1106        end: Option<Point>,
1107    ) -> impl Iterator<Item = R::Match> + '_ {
1108        let start = self.cursor.unwrap().caret();
1109        self.text.search_fwd(pat, (start, end)).unwrap()
1110    }
1111
1112    /// Searches the [`Text`] for a regex, in reverse
1113    ///
1114    /// The search will begin on the `caret`, and returns the bounding
1115    /// [`Point`]s, alongside the match. If a `start` is provided,
1116    /// the search will stop at the given [`Point`].
1117    ///
1118    /// # Panics
1119    ///
1120    /// If the regex is not valid, this method will panic.
1121    ///
1122    /// ```rust
1123    /// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
1124    /// fn search_nth_rev<S>(
1125    ///     helper: &mut EditHelper<File, impl Area, S>,
1126    ///     n: usize,
1127    ///     s: &str,
1128    /// ) {
1129    ///     helper.move_many(.., |mut m| {
1130    ///         let mut nth = m.search_rev(s, None).nth(n);
1131    ///         if let Some((start, end)) = nth {
1132    ///             m.move_to(start);
1133    ///             m.set_anchor();
1134    ///             m.move_to(end);
1135    ///         }
1136    ///     })
1137    /// }
1138    /// ```
1139    pub fn search_rev<R: RegexPattern>(
1140        &mut self,
1141        pat: R,
1142        start: Option<Point>,
1143    ) -> impl Iterator<Item = R::Match> + '_ {
1144        let end = self.cursor.unwrap().caret();
1145        self.text.search_rev(pat, (start, end)).unwrap()
1146    }
1147
1148    ////////// Behavior changes
1149
1150    /// Sets the "desired visual column"
1151    ///
1152    /// The desired visual column determines at what point in a line
1153    /// the caret will be placed when moving [up and down] through
1154    /// lines of varying lengths.
1155    ///
1156    /// Will also set the "desired wrapped visual column", which is
1157    /// the same thing but used when moving vertically in a [wrapped]
1158    /// fashion.
1159    ///
1160    /// [up and down]: Mover::move_ver
1161    /// [wrapped]: Mover::move_ver_wrapped
1162    pub fn set_desired_v_col(&mut self, x: usize) {
1163        let cursor = self.cursor.as_mut().unwrap();
1164        cursor.set_desired_v_col(x);
1165        cursor.set_desired_wrapped_v_col(x);
1166    }
1167
1168    ////////// Queries
1169
1170    /// Returns the `caret`
1171    pub fn caret(&self) -> Point {
1172        self.cursor.unwrap().caret()
1173    }
1174
1175    /// How many characterss the caret is from the start of the line
1176    pub fn caret_col(&self) -> usize {
1177        self.rev().take_while(|(_, c)| *c != '\n').count()
1178    }
1179
1180    /// The visual distance between the caret and the start of the
1181    /// [`Area`]
1182    pub fn caret_vcol(&self) -> usize {
1183        self.cursor.unwrap().vcol()
1184    }
1185
1186    /// The desired visual distance between the caret and the start of
1187    /// the [`Area`]
1188    pub fn desired_caret_vcol(&self) -> usize {
1189        self.cursor.unwrap().desired_vcol()
1190    }
1191
1192    /// Returns the `anchor`
1193    pub fn anchor(&self) -> Option<Point> {
1194        self.cursor.unwrap().anchor()
1195    }
1196
1197    /// How many characterss the anchor is from the start of the line
1198    pub fn anchor_col(&self) -> Option<usize> {
1199        self.anchor().map(|a| {
1200            self.text
1201                .chars_rev(a)
1202                .take_while(|(_, c)| *c != '\n')
1203                .count()
1204        })
1205    }
1206
1207    /// The visual distance between the anchor and the start of the
1208    /// [`Area`]
1209    pub fn anchor_vcol(&self) -> Option<usize> {
1210        self.cursor.unwrap().anchor_vcol()
1211    }
1212
1213    /// The desired visual distance between the anchor and the start
1214    /// of the [`Area`]
1215    pub fn desired_anchor_vcol(&self) -> Option<usize> {
1216        self.cursor.unwrap().desired_anchor_vcol()
1217    }
1218
1219    /// Returns `true` if the `anchor` exists before the `caret`
1220    pub fn anchor_is_start(&self) -> bool {
1221        self.anchor().is_none_or(|anchor| anchor < self.caret())
1222    }
1223
1224    /// Whether or not this is the main [`Cursor`]
1225    pub fn is_main(&self) -> bool {
1226        self.is_main
1227    }
1228
1229    /// Whether or not this cursor's selections are inclusive
1230    pub fn is_incl(&self) -> bool {
1231        self.text.cursors().unwrap().is_incl()
1232    }
1233
1234    pub fn text(&self) -> &Text {
1235        self.text
1236    }
1237
1238    /// The [`PrintCfg`] in use
1239    pub fn cfg(&self) -> PrintCfg {
1240        self.cfg
1241    }
1242}
1243
1244/// Incremental search functions, only available on [`IncSearcher`]s
1245///
1246/// [`IncSearcher`]: crate::mode::IncSearcher
1247impl<A> Mover<'_, A, Searcher>
1248where
1249    A: Area,
1250{
1251    /// Search incrementally from an [`IncSearch`] request
1252    ///
1253    /// This will match the Regex pattern from the current position of
1254    /// the caret. if `end` is [`Some`], the search will end at the
1255    /// requested [`Point`].
1256    ///
1257    /// [`IncSearch`]: crate::widgets::IncSearch
1258    pub fn search_inc_fwd(
1259        &mut self,
1260        end: Option<Point>,
1261    ) -> impl Iterator<Item = (Point, Point)> + '_ {
1262        self.inc_searcher.search_fwd(self.text, (self.caret(), end))
1263    }
1264
1265    /// Search incrementally from an [`IncSearch`] request in reverse
1266    ///
1267    /// This will match the Regex pattern from the current position of
1268    /// the caret in reverse. if `start` is [`Some`], the search will
1269    /// end at the requested [`Point`].
1270    ///
1271    /// [`IncSearch`]: crate::widgets::IncSearch
1272    pub fn search_inc_rev(
1273        &mut self,
1274        start: Option<Point>,
1275    ) -> impl Iterator<Item = (Point, Point)> + '_ {
1276        self.inc_searcher
1277            .search_rev(self.text, (start, self.caret()))
1278    }
1279
1280    /// Whether the [`Cursor`]'s selection matches the [`IncSearch`]
1281    /// request
1282    ///
1283    /// [`IncSearch`]: crate::widgets::IncSearch
1284    pub fn matches_inc(&mut self) -> bool {
1285        let is_incl = self.text.cursors().unwrap().is_incl();
1286        let range = self.cursor.unwrap().range(is_incl, self.text);
1287        self.text.make_contiguous_in(range.clone());
1288        let str = unsafe { self.text.continuous_in_unchecked(range) };
1289
1290        self.inc_searcher.matches(str)
1291    }
1292}