duat_utils/modes/
inc_search.rs

1//! Utilities for incremental search in Duat
2//!
3//! This specific feature of Duat is kind of split across this crate
4//! and [`duat-core`], since some of the low level features (like
5//! spawning a bazillion [`Cursor`]s) were only possible with access
6//! to private things.
7//!
8//! [`duat-core`]: duat_core
9//! [`Cursor`]: duat_core::mode::Cursor
10use duat_core::{prelude::*, text::Searcher};
11
12/// An abstraction trait used to handle incremental search
13///
14/// This trait can be used for various ways of interpreting what
15/// incremental search should do, right now, these are the
16/// implementations of [`IncSearcher`]:
17///
18/// - [`SearchFwd`]: In each cursor, searches forward for the match
19/// - [`SearchRev`]: In each cursor, searches backwards for the match
20/// - [`ExtendFwd`]: In each cursor, extends forward for the match
21/// - [`ExtendRev`]: In each cursor, extends backwards for the match
22///
23/// Here is how you can implement this trait yourself:
24///
25/// ```rust
26/// use duat_core::{prelude::*, text::Searcher};
27/// use duat_utils::modes::IncSearcher;
28///
29/// #[derive(Clone, Copy)]
30/// struct SearchAround;
31///
32/// impl<U: Ui> IncSearcher<U> for SearchAround {
33///     fn search(&mut self, pa: &mut Pass, handle: Handle<File<U>, U, Searcher>) {
34///         handle.edit_all(pa, |mut e| {
35///             e.set_caret_on_end();
36///             let Some([_, p1]) = e.search_inc_fwd(None).next() else {
37///                 return;
38///             };
39///
40///             e.set_caret_on_start();
41///             let Some([p0, _]) = e.search_inc_rev(None).next() else {
42///                 return;
43///             };
44///
45///             e.move_to(p0);
46///             e.set_anchor();
47///             e.move_to(p1);
48///         });
49///     }
50///
51///     fn prompt(&self) -> Text {
52///         txt!("[prompt]search around").build()
53///     }
54/// }
55/// ```
56///
57/// There are more advanced implementations in the [`duat-kak`] crate
58///
59/// [`duat-kak`]: https://docs.rs/duat-kak
60pub trait IncSearcher<U: Ui>: Clone + Send + 'static {
61    /// Performs the incremental search
62    fn search(&mut self, pa: &mut Pass, handle: Handle<File<U>, U, Searcher>);
63
64    /// What prompt to show in the [`PromptLine`]
65    ///
66    /// [`PromptLine`]: crate::widgets::PromptLine
67    fn prompt(&self) -> Text;
68}
69
70/// Searches forward on each [`Cursor`]
71///
72/// [`Cursor`]: duat_core::mode::Cursor
73#[derive(Clone, Copy)]
74pub struct SearchFwd;
75
76impl<U: Ui> IncSearcher<U> for SearchFwd {
77    fn search(&mut self, pa: &mut Pass, handle: Handle<File<U>, U, Searcher>) {
78        handle.edit_all(pa, |mut e| {
79            let caret = e.caret();
80            let next = e.search_inc_fwd(None).find(|[p, _]| *p != caret);
81            if let Some([p0, p1]) = next {
82                e.move_to(p0);
83                if p1 > p0 {
84                    e.set_anchor();
85                    e.move_to(p1);
86                    e.move_hor(-1);
87                }
88            }
89        });
90    }
91
92    fn prompt(&self) -> Text {
93        txt!("[prompt]search").build()
94    }
95}
96
97/// Searches backwards on each [`Cursor`]
98///
99/// [`Cursor`]: duat_core::mode::Cursor
100#[derive(Clone, Copy)]
101pub struct SearchRev;
102
103impl<U: Ui> IncSearcher<U> for SearchRev {
104    fn search(&mut self, pa: &mut Pass, handle: Handle<File<U>, U, Searcher>) {
105        handle.edit_all(pa, |mut e| {
106            let caret = e.caret();
107            let next = e.search_inc_rev(None).find(|[_, p]| *p != caret);
108            if let Some([p0, p1]) = next {
109                e.move_to(p0);
110                if p1 > p0 {
111                    e.set_anchor();
112                    e.move_to(p1);
113                    e.move_hor(-1);
114                }
115            }
116        });
117    }
118
119    fn prompt(&self) -> Text {
120        txt!("[prompt]rev search").build()
121    }
122}
123
124/// Extends forward on each [`Cursor`]
125///
126/// [`Cursor`]: duat_core::mode::Cursor
127#[derive(Clone, Copy)]
128pub struct ExtendFwd;
129
130impl<U: Ui> IncSearcher<U> for ExtendFwd {
131    fn search(&mut self, pa: &mut Pass, handle: Handle<File<U>, U, Searcher>) {
132        handle.edit_all(pa, |mut e| {
133            let caret = e.caret();
134            let next = e.search_inc_fwd(None).find(|[p, _]| *p != caret);
135            if let Some([_, p1]) = next {
136                if e.anchor().is_none() {
137                    e.set_anchor();
138                }
139                e.move_to(p1);
140            }
141        });
142    }
143
144    fn prompt(&self) -> Text {
145        txt!("[prompt]search (extend)").build()
146    }
147}
148
149/// Extends backwards on each [`Cursor`]
150///
151/// [`Cursor`]: duat_core::mode::Cursor
152#[derive(Clone, Copy)]
153pub struct ExtendRev;
154
155impl<U: Ui> IncSearcher<U> for ExtendRev {
156    fn search(&mut self, pa: &mut Pass, handle: Handle<File<U>, U, Searcher>) {
157        handle.edit_all(pa, |mut e| {
158            let caret = e.caret();
159            let next = e.search_inc_rev(None).find(|[_, p]| *p != caret);
160            if let Some([p0, _]) = next {
161                if e.anchor().is_none() {
162                    e.set_anchor();
163                }
164                e.move_to(p0);
165            }
166        });
167    }
168
169    fn prompt(&self) -> Text {
170        txt!("[prompt]rev search (extend)").build()
171    }
172}