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}