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::{cell::Cell, rc::Rc};
9
10use lender::{Lender, Lending};
11
12pub use self::cursors::{Cursor, Cursors, VPoint};
13use crate::{
14 cfg::PrintCfg,
15 text::{Change, Point, Reader, RegexPattern, Searcher, Strs, Text, TextRange},
16 ui::Area,
17 widgets::{File, Widget},
18};
19
20/// The [`Cursor`] and [`Cursors`] structs
21mod cursors;
22
23/// A struct used by [`Mode`]s to edit [`Text`]
24///
25/// You will want to use this struct when editing [`Widget`]s
26/// with [`Cursors`]. For example, let's say you want to create an
27/// mode for the [`File`] widget:
28///
29/// ```rust
30/// # use duat_core::{mode::{EditHelper, Mode, KeyEvent, Cursors}, ui::Ui, widgets::File};
31/// /// A very basic example Mode.
32/// #[derive(Clone)]
33/// struct PlacesCharactersAndMoves;
34///
35/// impl<U: Ui> Mode<U> for PlacesCharactersAndMoves {
36/// type Widget = File;
37/// /* ... */
38/// # fn send_key(&mut self, key: KeyEvent, widget: &mut File, area: &U::Area) {
39/// # todo!();
40/// # }
41/// # }
42/// ```
43///
44/// In order to modify the widget, you must implement the
45/// [`Mode::send_key`] method. In it, you receive the following:
46///
47/// - The [key].
48/// - A [`&mut Self::Widget`].
49/// - An [`Area`], which you can resize and modify it in other ways.
50/// - The current [`Cursors`] of the widget, these should be modified
51/// by the [`EditHelper`].
52///
53/// In a [`Mode`] without cursors, you'd probably want to run
54/// [`Cursors::clear`], in order to make sure there are no cursors.
55///
56/// ```rust
57/// # use duat_core::{
58/// # mode::{key, Cursors, EditHelper, Mode, KeyCode, KeyEvent}, ui::Ui, widgets::File,
59/// # };
60/// # #[derive(Clone)]
61/// # struct PlacesCharactersAndMoves;
62/// impl<U: Ui> Mode<U> for PlacesCharactersAndMoves {
63/// # type Widget = File;
64/// /* ... */
65/// fn send_key(&mut self, key: KeyEvent, widget: &mut File, area: &U::Area) {
66/// match key {
67/// // actions based on the key pressed
68/// key!(KeyCode::Char('c')) => {
69/// /* Do something when the character 'c' is typed. */
70/// }
71/// /* Matching the rest of the keys */
72/// # _ => todo!()
73/// }
74/// }
75/// # }
76/// ```
77///
78/// (You can use the [`key!`] macro in order to match [`KeyEvent`]s).
79///
80/// With the `EditHelper`, you can modify [`Text`] in a simplified
81/// way. This is done by two actions, [editing] and [moving]. You
82/// can only do one of these on any number of cursors at the same
83/// time.
84///
85/// ```rust
86/// # use duat_core::{
87/// # mode::{ key, Cursors, EditHelper, Mode, KeyCode, KeyEvent, KeyMod}, ui::Ui, widgets::File,
88/// # };
89/// # #[derive(Clone)]
90/// # struct PlacesCharactersAndMoves;
91/// impl<U: Ui> Mode<U> for PlacesCharactersAndMoves {
92/// # type Widget = File;
93/// /* ... */
94/// fn send_key(&mut self, key: KeyEvent, file: &mut File, area: &U::Area) {
95/// let mut helper = EditHelper::new(file, area);
96///
97/// match key {
98/// key!(KeyCode::Char(c)) => {
99/// helper.edit_many(.., |e| e.insert('c'));
100/// helper.move_many(.., |mut m| { m.move_hor(1); });
101/// },
102/// key!(KeyCode::Right, KeyMod::SHIFT) => {
103/// helper.move_many(.., |mut m| {
104/// if m.anchor().is_none() {
105/// m.set_anchor()
106/// }
107/// m.move_hor(1);
108/// })
109/// }
110/// key!(KeyCode::Right) => {
111/// helper.move_many(.., |mut m| {
112/// m.unset_anchor();
113/// m.move_hor(1);
114/// })
115/// }
116/// /* Predictable remaining implementations */
117/// # _ => todo!()
118/// }
119/// }
120/// # }
121/// ```
122///
123/// [`Mode`]: super::Mode
124/// [`Text`]: crate::text::Text
125/// [`PromptLine`]: crate::widgets::PromptLine
126/// [`&mut Self::Widget`]: super::Mode::Widget
127/// [`Mode::send_key`]: super::Mode::send_key
128/// [key]: super::KeyEvent
129/// [`Self::Widget`]: super::Mode::Widget
130/// [`Some(cursors)`]: Some
131/// [`Ui::Area`]: crate::ui::Ui::Area
132/// [commands]: crate::cmd
133/// [`key!`]: super::key
134/// [`KeyEvent`]: super::KeyEvent
135/// [editing]: Editor
136/// [moving]: Mover
137pub struct EditHelper<'a, W: Widget<A::Ui>, A: Area, S> {
138 widget: &'a mut W,
139 area: &'a A,
140 inc_searcher: S,
141}
142
143impl<'a, W: Widget<A::Ui>, A: Area> EditHelper<'a, W, A, ()> {
144 /// Returns a new instance of [`EditHelper`]
145 pub fn new(widget: &'a mut W, area: &'a A) -> Self {
146 widget.text_mut().enable_cursors();
147 widget.cursors_mut().unwrap().populate();
148 EditHelper { widget, area, inc_searcher: () }
149 }
150}
151
152impl<W: Widget<A::Ui>, A: Area, S> EditHelper<'_, W, A, S> {
153 ////////// Editing functions
154
155 /// Edits the nth [`Cursor`] in the [`Text`]
156 ///
157 /// Once dropped, the [`Cursor`] in this [`Editor`] will be added
158 /// back to the list of [`Cursor`]s, unless it is [destroyed]
159 ///
160 /// If you want to edit on the main cursor, see [`edit_main`], if
161 /// you want to edit on many [`Cursor`]s, see [`edit_iter`].
162 ///
163 /// Just like all other `edit` methods, this one will populate the
164 /// [`Cursors`], so if there are no [`Cursor`]s, it will create
165 /// one at [`Point::default`].
166 ///
167 /// [destroyed]: Editor::destroy
168 /// [`edit_main`]: Self::edit_main
169 /// [`edit_iter`]: Self::edit_iter
170 pub fn edit_nth(&mut self, n: usize) -> Editor<W, A, S> {
171 let cursors = self.widget.cursors_mut().unwrap();
172 cursors.populate();
173 let Some((cursor, was_main)) = cursors.remove(n) else {
174 panic!("Cursor index {n} out of bounds");
175 };
176
177 Editor::new(
178 cursor,
179 n,
180 was_main,
181 self.widget,
182 self.area,
183 None,
184 &mut self.inc_searcher,
185 )
186 }
187
188 /// Edits the main [`Cursor`] in the [`Text`]
189 ///
190 /// Once dropped, the [`Cursor`] in this [`Editor`] will be added
191 /// back to the list of [`Cursor`]s, unless it is [destroyed]
192 ///
193 /// If you want to edit on the `nth` cursor, see [`edit_nth`],
194 /// same for [`edit_last`], if you want to edit on many
195 /// [`Cursor`]s, see [`edit_iter`].
196 ///
197 /// Just like all other `edit` methods, this one will populate the
198 /// [`Cursors`], so if there are no [`Cursor`]s, it will create
199 /// one at [`Point::default`].
200 ///
201 /// [destroyed]: Editor::destroy
202 /// [`edit_nth`]: Self::edit_nth
203 /// [`edit_last`]: Self::edit_last
204 /// [`edit_iter`]: Self::edit_iter
205 pub fn edit_main(&mut self) -> Editor<W, A, S> {
206 self.edit_nth(self.cursors().main_index())
207 }
208
209 /// Edits the last [`Cursor`] in the [`Text`]
210 ///
211 /// Once dropped, the [`Cursor`] in this [`Editor`] will be added
212 /// back to the list of [`Cursor`]s, unless it is [destroyed]
213 ///
214 /// If you want to edit on the `nth` cursor, see [`edit_nth`],
215 /// same for [`edit_main`], if you want to edit on many
216 /// [`Cursor`]s, see [`edit_iter`].
217 ///
218 /// Just like all other `edit` methods, this one will populate the
219 /// [`Cursors`], so if there are no [`Cursor`]s, it will create
220 /// one at [`Point::default`].
221 ///
222 /// [destroyed]: Editor::destroy
223 /// [`edit_nth`]: Self::edit_nth
224 /// [`edit_main`]: Self::edit_main
225 /// [`edit_iter`]: Self::edit_iter
226 pub fn edit_last(&mut self) -> Editor<W, A, S> {
227 self.edit_nth(self.cursors().len().saturating_sub(1))
228 }
229
230 /// A [`Lender`] over all [`Editor`]s of the [`Text`]
231 ///
232 /// This lets you easily iterate over all [`Cursor`]s, without
233 /// having to worry about insertion affecting the order at which
234 /// they are edited (like what repeated calls to [`edit_nth`]
235 /// would do)
236 ///
237 /// Note however that you can't use a [`Lender`] (also known as a
238 /// lending iterator) in a `for` loop, but you should be able
239 /// to just `while let Some(e) = editors.next() {}` or
240 /// `helper.edit_iter().for_each(|_| {})` instead.
241 ///
242 /// Just like all other `edit` methods, this one will populate the
243 /// [`Cursors`], so if there are no [`Cursor`]s, it will create
244 /// one at [`Point::default`].
245 ///
246 /// [`edit_nth`]: Self::edit_nth
247 pub fn edit_iter<'b>(&'b mut self) -> EditIter<'b, W, A, S> {
248 self.cursors_mut().populate();
249 EditIter {
250 next_i: Rc::new(Cell::new(0)),
251 widget: self.widget,
252 area: self.area,
253 inc_searcher: &mut self.inc_searcher,
254 }
255 }
256
257 ////////// Getter functions
258
259 /// A shared reference to the [`Widget`]
260 pub fn widget(&self) -> &W {
261 self.widget
262 }
263
264 /// A mutable reference to the [`Widget`]
265 pub fn widget_mut(&mut self) -> &mut W {
266 self.widget
267 }
268
269 /// A shared reference to the [`Widget`]
270 pub fn text(&self) -> &Text {
271 self.widget.text()
272 }
273
274 /// A mutable reference to the [`Widget`]
275 pub fn text_mut(&mut self) -> &mut Text {
276 self.widget.text_mut()
277 }
278
279 /// A shared reference to the [`Widget`]
280 pub fn cursors(&self) -> &Cursors {
281 self.widget.text().cursors().unwrap()
282 }
283
284 /// A mutable reference to the [`Widget`]
285 pub fn cursors_mut(&mut self) -> &mut Cursors {
286 self.widget.text_mut().cursors_mut().unwrap()
287 }
288
289 /// Undoes the last moment in the history, if there is one
290 pub fn undo(&mut self) {
291 self.widget.text_mut().undo();
292 }
293
294 /// Redoes the last moment in the history, if there is one
295 pub fn redo(&mut self) {
296 self.widget.text_mut().redo();
297 }
298
299 /// Finishes the current moment and adds a new one to the history
300 pub fn new_moment(&mut self) {
301 self.widget.text_mut().new_moment();
302 }
303
304 /// The [`PrintCfg`] in use
305 pub fn cfg(&self) -> PrintCfg {
306 self.widget.print_cfg()
307 }
308}
309
310impl<'a, A> EditHelper<'a, File, A, Searcher>
311where
312 A: Area,
313{
314 /// Returns a new instance of [`EditHelper`]
315 pub fn new_inc(widget: &'a mut File, area: &'a A, searcher: Searcher) -> Self {
316 let cfg = widget.print_cfg();
317 if let Some(c) = widget.cursors_mut() {
318 c.populate()
319 }
320 widget.text_mut().remove_cursors(area, cfg);
321
322 EditHelper { widget, area, inc_searcher: searcher }
323 }
324}
325
326/// A cursor that can edit [`Text`], but can't alter selections
327///
328/// This struct will be used only inside functions passed to the
329/// [`edit_*`] family of methods from the [`EditHelper`].
330///
331/// To make edits, you can use two different functions. You can either
332/// [`replace`] or you can [`insert`]. The former will completely
333/// replace the [`Cursor`]'s selection, while the latter will only
334/// place the edit before the position of the `caret`, which could be
335/// either in the start or the end of the selection.
336///
337/// ```rust
338/// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
339/// # fn test<S>(helper: &mut EditHelper<File, impl Area, S>) {
340/// helper.edit_main(|e| {
341/// e.replace("my replacement");
342/// e.insert(" and my edit");
343/// });
344/// helper.move_main(|mut m| {
345/// m.move_hor(" and my edit".chars().count() as i32);
346/// m.set_anchor();
347/// m.move_hor(-("my replacement and my edit".chars().count() as i32));
348/// let sel: String = m.selection().into_iter().collect();
349/// assert_eq!(sel, "my replacement and my edit".to_string());
350/// });
351/// # }
352/// ```
353///
354/// [`edit_*`]: EditHelper::edit_nth
355/// [`replace`]: Editor::replace
356/// [`insert`]: Editor::insert
357pub struct Editor<'a, W: Widget<A::Ui>, A: Area, S> {
358 initial: Cursor,
359 cursor: Cursor,
360 n: usize,
361 was_main: bool,
362 widget: &'a mut W,
363 area: &'a A,
364 next_i: Option<Rc<Cell<usize>>>,
365 inc_searcher: &'a mut S,
366}
367
368impl<'a, W: Widget<A::Ui>, A: Area, S> Editor<'a, W, A, S> {
369 /// Returns a new instance of [`Editor`]
370 fn new(
371 cursor: Cursor,
372 n: usize,
373 was_main: bool,
374 widget: &'a mut W,
375 area: &'a A,
376 next_i: Option<Rc<Cell<usize>>>,
377 searcher: &'a mut S,
378 ) -> Self {
379 Self {
380 initial: cursor.clone(),
381 cursor,
382 n,
383 was_main,
384 widget,
385 area,
386 next_i,
387 inc_searcher: searcher,
388 }
389 }
390
391 ////////// Text editing
392
393 /// Replaces the entire selection with new text
394 ///
395 /// If the `caret` is behind the `anchor` (or in the same spot),
396 /// after replacing the selection, the `caret` will be placed on
397 /// the start of the selection, while the `anchor` will be placed
398 /// on the new end. If it is ahead, it will be placed ahead.
399 ///
400 /// If there is no selection, then this has the same effect as
401 /// [`insert`]. If you wish to append to the `caret` instead, see
402 /// [`append`].
403 ///
404 /// [`insert`]: Self::insert
405 /// [`append`]: Self::append
406 pub fn replace(&mut self, edit: impl ToString) {
407 let change = {
408 let edit = edit.to_string();
409 let [p0, p1] = self.cursor.point_range(self.widget.text());
410 Change::new(edit, [p0, p1], self.widget.text())
411 };
412
413 let added_len = change.added_text().len();
414 let end = change.added_end();
415
416 self.edit(change);
417
418 let caret_was_on_start = self.set_caret_on_end();
419
420 self.move_to(end);
421 if added_len > 0 {
422 self.move_hor(-1);
423 }
424 if caret_was_on_start {
425 self.set_caret_on_start();
426 }
427 }
428
429 /// Inserts new text directly behind the `caret`
430 ///
431 /// If the `anchor` is ahead of the `caret`, it will move forwards
432 /// by the number of chars in the new text.
433 ///
434 /// If you wish to replace the selected text, see [`replace`], if
435 /// you want to append after the `caret` instead, see [`append`]
436 ///
437 /// [`replace`]: Self::replace
438 /// [`append`]: Self::append
439 pub fn insert(&mut self, edit: impl ToString) {
440 let range = [self.cursor.caret(), self.cursor.caret()];
441 let change = Change::new(edit.to_string(), range, self.widget.text());
442 let (added, taken) = (change.added_end(), change.taken_end());
443
444 self.edit(change);
445
446 if let Some(anchor) = self.cursor.anchor()
447 && anchor > self.cursor.caret()
448 {
449 let new_anchor = anchor + added - taken;
450 self.cursor.swap_ends();
451 self.cursor.move_to(new_anchor, self.widget.text());
452 self.cursor.swap_ends();
453 }
454 }
455
456 /// Appends new text directly after the `caret`
457 ///
458 /// If the `anchor` is ahead of the `caret`, it will move forwards
459 /// by the number of chars in the new text.
460 ///
461 /// If you wish to replace the selected text, see [`replace`], if
462 /// you want to insert before the `caret` instead, see [`insert`]
463 ///
464 /// [`replace`]: Self::replace
465 /// [`insert`]: Self::insert
466 pub fn append(&mut self, edit: impl ToString) {
467 let caret = self.cursor.caret();
468 let p = caret.fwd(self.widget.text().char_at(caret).unwrap());
469 let change = Change::new(edit.to_string(), [p, p], self.widget.text());
470 let (added, taken) = (change.added_end(), change.taken_end());
471
472 self.edit(change);
473
474 if let Some(anchor) = self.cursor.anchor()
475 && anchor > p
476 {
477 let new_anchor = anchor + added - taken;
478 self.cursor.swap_ends();
479 self.cursor.move_to(new_anchor, self.widget.text());
480 self.cursor.swap_ends();
481 }
482 }
483
484 /// Edits the file with a [`Change`]
485 fn edit(&mut self, change: Change<String>) {
486 let text = self.widget.text_mut();
487 let (change_i, cursors_taken) =
488 text.apply_change(self.cursor.change_i.map(|i| i as usize), change);
489 self.cursor.change_i = change_i.map(|i| i as u32);
490
491 // The Change may have happened before the index of the next curossr,
492 // so we need to account for that.
493 if let Some((change_i, cursors_taken)) = change_i.zip(cursors_taken)
494 && let Some(next_i) = self.next_i.as_ref()
495 && change_i <= next_i.get()
496 {
497 next_i.set(next_i.get().saturating_sub(cursors_taken));
498 }
499 }
500
501 ////////// Movement functions
502
503 /// Moves the cursor horizontally. May cause vertical movement
504 ///
505 /// Returns the distance moved in chars.
506 pub fn move_hor(&mut self, count: i32) -> i32 {
507 self.cursor.move_hor(count, self.widget.text())
508 }
509
510 /// Moves the cursor vertically. May cause horizontal movement
511 ///
512 /// Returns the distance moved in lines.
513 pub fn move_ver(&mut self, count: i32) -> i32 {
514 self.cursor.move_ver(
515 count,
516 self.widget.text(),
517 self.area,
518 self.widget.print_cfg(),
519 )
520 }
521
522 /// Moves the cursor vertically. May cause horizontal movement
523 ///
524 /// Returns the distance moved in wrapped lines.
525 pub fn move_ver_wrapped(&mut self, count: i32) {
526 self.cursor.move_ver_wrapped(
527 count,
528 self.widget.text(),
529 self.area,
530 self.widget.print_cfg(),
531 );
532 }
533
534 /// Moves the cursor to a [`Point`]
535 ///
536 /// - If the position isn't valid, it will move to the "maximum"
537 /// position allowed.
538 pub fn move_to(&mut self, point: Point) {
539 self.cursor.move_to(point, self.widget.text());
540 }
541
542 /// Moves the cursor to a `line` and a `column`
543 ///
544 /// - If the coords isn't valid, it will move to the "maximum"
545 /// position allowed.
546 pub fn move_to_coords(&mut self, line: usize, col: usize) {
547 let at = self
548 .text()
549 .point_at_line(line.min(self.text().len().line()));
550 let (point, _) = self.text().chars_fwd(at).take(col + 1).last().unwrap();
551 self.move_to(point);
552 }
553
554 /// Returns and takes the anchor of the [`Cursor`].
555 pub fn unset_anchor(&mut self) -> Option<Point> {
556 self.cursor.unset_anchor()
557 }
558
559 /// Sets the `anchor` to the current `caret`
560 pub fn set_anchor(&mut self) {
561 self.cursor.set_anchor()
562 }
563
564 /// Swaps the position of the `caret` and `anchor`
565 pub fn swap_ends(&mut self) {
566 self.cursor.swap_ends();
567 }
568
569 /// Sets the caret of the [`Cursor`] on the start of the
570 /// selection
571 ///
572 /// Returns `true` if a swap occurred
573 pub fn set_caret_on_start(&mut self) -> bool {
574 if let Some(anchor) = self.anchor()
575 && anchor < self.caret()
576 {
577 self.swap_ends();
578 true
579 } else {
580 false
581 }
582 }
583
584 /// Sets the caret of the [`Cursor`] on the end of the
585 /// selection
586 ///
587 /// Returns `true` if a swap occurred
588 pub fn set_caret_on_end(&mut self) -> bool {
589 if let Some(anchor) = self.anchor()
590 && anchor > self.caret()
591 {
592 self.swap_ends();
593 true
594 } else {
595 false
596 }
597 }
598
599 ////////// Cursor meta manipulation
600
601 /// Resets the [`Cursor`] to how it was before being modified
602 pub fn reset(&mut self) {
603 self.cursor = self.initial.clone();
604 }
605
606 /// Copies the current [`Cursor`] in place
607 ///
608 /// This will leave an additional [`Cursor`] with the current
609 /// selection. Do note that normal intersection rules apply, so if
610 /// at the end of the movement, this cursor intersects with any
611 /// other, they will be merged into one.
612 ///
613 /// When this [`Editor`] is dropped, like with normal [`Editor`]s,
614 /// its [`Cursor`] will be added to the [`Cursors`], unless you
615 /// [destroy] it.
616 ///
617 /// [destroy]: Self::destroy
618 pub fn copy(&mut self) -> Editor<W, A, S> {
619 Editor::new(
620 self.cursor.clone(),
621 self.n,
622 false,
623 self.widget,
624 self.area,
625 self.next_i.clone(),
626 self.inc_searcher,
627 )
628 }
629
630 /// Destroys the current [`Cursor`]
631 ///
632 /// Will not destroy it if it is the last [`Cursor`] left
633 ///
634 /// If this was the main cursor, the main cursor will now be the
635 /// cursor immediately behind it.
636 pub fn destroy(mut self) {
637 // If it is 1, it is actually 2, because this Cursor is also part of
638 // that list.
639 if !self.widget.cursors().unwrap().is_empty() {
640 // Rc<Cell> needs to be manually dropped to reduce its counter.
641 self.next_i.take();
642 if self.was_main {
643 self.widget.cursors_mut().unwrap().rotate_main(-1);
644 }
645 // The destructor is what inserts the Cursor back into the list, so
646 // don't run it.
647 std::mem::forget(self);
648 } else {
649 // Just to be explicit.
650 drop(self);
651 }
652 }
653
654 /// Sets the "desired visual column"
655 ///
656 /// The desired visual column determines at what point in a line
657 /// the caret will be placed when moving [up and down] through
658 /// lines of varying lengths.
659 ///
660 /// Will also set the "desired wrapped visual column", which is
661 /// the same thing but used when moving vertically in a [wrapped]
662 /// fashion.
663 ///
664 /// [up and down]: Mover::move_ver
665 /// [wrapped]: Mover::move_ver_wrapped
666 pub fn set_desired_vcol(&mut self, x: usize) {
667 self.cursor.set_desired_cols(x, x);
668 }
669
670 ////////// Iteration functions
671
672 /// Iterates over the [`char`]s
673 ///
674 /// This iteration will begin on the `caret`. It will also include
675 /// the [`Point`] of each `char`
676 pub fn chars_fwd(&self) -> impl Iterator<Item = (Point, char)> + '_ {
677 self.widget.text().chars_fwd(self.caret())
678 }
679
680 /// Iterates over the [`char`]s, in reverse
681 ///
682 /// This iteration will begin on the `caret`. It will also include
683 /// the [`Point`] of each `char`
684 pub fn chars_rev(&self) -> impl Iterator<Item = (Point, char)> + '_ {
685 self.widget.text().chars_rev(self.caret())
686 }
687
688 /// Searches the [`Text`] for a regex
689 ///
690 /// The search will begin on the `caret`, and returns the bounding
691 /// [`Point`]s, alongside the match. If an `end` is provided,
692 /// the search will stop at the given [`Point`].
693 ///
694 /// # Panics
695 ///
696 /// If the regex is not valid, this method will panic.
697 ///
698 /// ```rust
699 /// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
700 /// fn search_nth_paren<S>(
701 /// helper: &mut EditHelper<File, impl Area, S>,
702 /// n: usize,
703 /// ) {
704 /// helper.move_many(.., |mut m| {
705 /// let mut nth = m.search_fwd('(', None).nth(n);
706 /// if let Some([p0, p1]) = nth {
707 /// m.move_to(p0);
708 /// m.set_anchor();
709 /// m.move_to(p1);
710 /// }
711 /// })
712 /// }
713 /// ```
714 pub fn search_fwd<R: RegexPattern>(
715 &mut self,
716 pat: R,
717 end: Option<Point>,
718 ) -> impl Iterator<Item = R::Match> + '_ {
719 let start = self.cursor.caret().byte();
720 let text = self.widget.text_mut();
721 match end {
722 Some(end) => text.search_fwd(pat, start..end.byte()).unwrap(),
723 None => {
724 let end = text.len().byte();
725 text.search_fwd(pat, start..end).unwrap()
726 }
727 }
728 }
729
730 /// Searches the [`Text`] for a regex, in reverse
731 ///
732 /// The search will begin on the `caret`, and returns the bounding
733 /// [`Point`]s, alongside the match. If a `start` is provided,
734 /// the search will stop at the given [`Point`].
735 ///
736 /// # Panics
737 ///
738 /// If the regex is not valid, this method will panic.
739 ///
740 /// ```rust
741 /// # use duat_core::{mode::EditHelper, ui::Area, widgets::File};
742 /// fn search_nth_rev<S>(
743 /// helper: &mut EditHelper<File, impl Area, S>,
744 /// n: usize,
745 /// s: &str,
746 /// ) {
747 /// helper.move_many(.., |mut m| {
748 /// let mut nth = m.search_rev(s, None).nth(n);
749 /// if let Some([p0, p1]) = nth {
750 /// m.move_to(p0);
751 /// m.set_anchor();
752 /// m.move_to(p1);
753 /// }
754 /// })
755 /// }
756 /// ```
757 pub fn search_rev<R: RegexPattern>(
758 &mut self,
759 pat: R,
760 start: Option<Point>,
761 ) -> impl Iterator<Item = R::Match> + '_ {
762 let end = self.cursor.caret().byte();
763 let start = start.unwrap_or_default();
764 let text = self.widget.text_mut();
765 text.search_rev(pat, start.byte()..end).unwrap()
766 }
767
768 /// Wether the current selection matches a regex pattern
769 pub fn matches<R: RegexPattern>(&mut self, pat: R) -> bool {
770 let range = self.cursor.range(self.widget.text());
771 self.widget.text_mut().matches(pat, range).unwrap()
772 }
773
774 ////////// Text queries
775
776 /// Returns the [`char`] in the `caret`
777 pub fn char(&self) -> char {
778 self.text().char_at(self.cursor.caret()).unwrap()
779 }
780
781 /// Returns the [`char`] at a given [`Point`]
782 pub fn char_at(&self, p: Point) -> Option<char> {
783 self.text().char_at(p)
784 }
785
786 /// Returns the [`Cursor`]'s selection
787 ///
788 /// The reason why this return value is `IntoIter<&str, 2>` is
789 /// because the [`Text`] utilizes an underlying [`GapBuffer`]
790 /// to store the characters. This means that the text is
791 /// always separated into two distinct chunks.
792 ///
793 /// If this [`Cursor`]'s selection happens to be entirely
794 /// within one of these chunks, the other `&str` will just be
795 /// empty.
796 ///
797 /// [`GapBuffer`]: gapbuf::GapBuffer
798 pub fn selection(&self) -> Strs {
799 let range = self.cursor.range(self.text());
800 self.text().strs(range)
801 }
802
803 pub fn contiguous_in(&mut self, range: impl TextRange) -> &str {
804 self.widget.text_mut().contiguous(range)
805 }
806
807 /// Returns the length of the [`Text`], in [`Point`]
808 pub fn len(&self) -> Point {
809 self.text().len()
810 }
811
812 /// Returns the position of the last [`char`] if there is one
813 pub fn last_point(&self) -> Option<Point> {
814 self.text().last_point()
815 }
816
817 /// An [`Iterator`] over the lines in a given [range]
818 ///
819 /// [range]: TextRange
820 pub fn lines_on(
821 &mut self,
822 range: impl TextRange,
823 ) -> impl DoubleEndedIterator<Item = (usize, &'_ str)> + '_ {
824 self.widget.text_mut().lines(range)
825 }
826
827 /// Gets the current level of indentation
828 pub fn indent(&self) -> usize {
829 self.widget
830 .text()
831 .indent(self.caret(), self.area, self.cfg())
832 }
833
834 /// Gets the indentation level on the given [`Point`]
835 pub fn indent_on(&self, p: Point) -> usize {
836 self.widget.text().indent(p, self.area, self.cfg())
837 }
838
839 /// Gets a [`Reader`]'s [public facing API], if it exists
840 ///
841 /// [public facing API]: Reader::PublicReader
842 pub fn get_reader<R: Reader>(&mut self) -> Option<R::PublicReader<'_>> {
843 self.widget.text_mut().get_reader::<R>()
844 }
845
846 ////////// Cursor queries
847
848 /// Returns the `caret`
849 pub fn caret(&self) -> Point {
850 self.cursor.caret()
851 }
852
853 /// Returns the `anchor`
854 pub fn anchor(&self) -> Option<Point> {
855 self.cursor.anchor()
856 }
857
858 pub fn range(&self) -> [Point; 2] {
859 self.cursor.point_range(self.text())
860 }
861
862 pub fn v_caret(&self) -> VPoint {
863 self.cursor
864 .v_caret(self.widget.text(), self.area, self.widget.print_cfg())
865 }
866
867 pub fn v_anchor(&self) -> Option<VPoint> {
868 self.cursor
869 .v_anchor(self.widget.text(), self.area, self.widget.print_cfg())
870 }
871
872 /// Returns `true` if the `anchor` exists before the `caret`
873 pub fn anchor_is_start(&self) -> bool {
874 self.anchor().is_none_or(|anchor| anchor < self.caret())
875 }
876
877 /// Whether or not this is the main [`Cursor`]
878 pub fn is_main(&self) -> bool {
879 self.was_main
880 }
881
882 pub fn text(&self) -> &Text {
883 self.widget.text()
884 }
885
886 /// The [`PrintCfg`] in use
887 pub fn cfg(&self) -> PrintCfg {
888 self.widget.print_cfg()
889 }
890}
891
892/// Incremental search functions, only available on [`IncSearcher`]s
893///
894/// [`IncSearcher`]: crate::mode::IncSearcher
895impl<W: Widget<A::Ui>, A: Area> Editor<'_, W, A, Searcher> {
896 /// Search incrementally from an [`IncSearch`] request
897 ///
898 /// This will match the Regex pattern from the current position of
899 /// the caret. if `end` is [`Some`], the search will end at the
900 /// requested [`Point`].
901 ///
902 /// [`IncSearch`]: crate::mode::IncSearch
903 pub fn search_inc_fwd(&mut self, end: Option<Point>) -> impl Iterator<Item = [Point; 2]> + '_ {
904 self.inc_searcher
905 .search_fwd(self.widget.text_mut(), (self.cursor.caret(), end))
906 }
907
908 /// Search incrementally from an [`IncSearch`] request in reverse
909 ///
910 /// This will match the Regex pattern from the current position of
911 /// the caret in reverse. if `start` is [`Some`], the search will
912 /// end at the requested [`Point`].
913 ///
914 /// [`IncSearch`]: crate::mode::IncSearch
915 pub fn search_inc_rev(
916 &mut self,
917 start: Option<Point>,
918 ) -> impl Iterator<Item = [Point; 2]> + '_ {
919 self.inc_searcher
920 .search_rev(self.widget.text_mut(), (start, self.cursor.caret()))
921 }
922
923 /// Whether the [`Cursor`]'s selection matches the [`IncSearch`]
924 /// request
925 ///
926 /// [`IncSearch`]: crate::mode::IncSearch
927 pub fn matches_inc(&mut self) -> bool {
928 let range = self.cursor.range(self.widget.text());
929 self.inc_searcher
930 .matches(self.widget.text_mut().contiguous(range))
931 }
932}
933
934// SAFETY: In theory, it should be impossible to maintain a reference
935// to W after it has dropped, since EditHelper would be mutably
936// borrowing from said W, and you can only get an Editor from
937// EditHelper. Thus. the only thing which may have been dropped is the
938// Cursors within, which are accounted for.
939// Also, since this is created by an EditHelper, it is created after
940// it, and thus is dropped before it, making its &mut Cursor reference
941// valid.
942unsafe impl<#[may_dangle] 'a, W: Widget<A::Ui> + 'a, A: Area + 'a, S: 'a> Drop
943 for Editor<'a, W, A, S>
944{
945 fn drop(&mut self) {
946 let Some(cursors) = self.widget.cursors_mut() else {
947 return;
948 };
949 let cursor = std::mem::take(&mut self.cursor);
950 let ([inserted_i, cursors_taken], last_cursor_overhangs) =
951 cursors.insert(self.n, cursor, self.was_main);
952
953 if let Some(next_i) = self.next_i.as_ref()
954 && inserted_i <= next_i.get()
955 {
956 let go_to_next = !last_cursor_overhangs as usize;
957 next_i.set(next_i.get().saturating_sub(cursors_taken).max(inserted_i) + go_to_next)
958 }
959 }
960}
961
962pub struct EditIter<'a, W: Widget<A::Ui>, A: Area, S> {
963 next_i: Rc<Cell<usize>>,
964 widget: &'a mut W,
965 area: &'a A,
966 inc_searcher: &'a mut S,
967}
968
969impl<'a, 'lend, W: Widget<A::Ui>, A: Area, S> Lending<'lend> for EditIter<'a, W, A, S> {
970 type Lend = Editor<'lend, W, A, S>;
971}
972
973impl<'a, W: Widget<A::Ui>, A: Area, S> Lender for EditIter<'a, W, A, S> {
974 fn next<'lend>(&'lend mut self) -> Option<<Self as Lending<'lend>>::Lend> {
975 let current_i = self.next_i.get();
976 let (cursor, was_main) = self.widget.cursors_mut().unwrap().remove(current_i)?;
977
978 Some(Editor::new(
979 cursor,
980 current_i,
981 was_main,
982 self.widget,
983 self.area,
984 Some(self.next_i.clone()),
985 self.inc_searcher,
986 ))
987 }
988}