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}