duat_base/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::{
11 buffer::Buffer,
12 context::Handle,
13 data::Pass,
14 text::{Searcher, Text, txt},
15};
16
17/// An abstraction trait used to handle incremental search
18///
19/// This trait can be used for various ways of interpreting what
20/// incremental search should do, right now, these are the
21/// implementations of [`IncSearcher`]:
22///
23/// - [`SearchFwd`]: In each cursor, searches forward for the match
24/// - [`SearchRev`]: In each cursor, searches backwards for the match
25/// - [`ExtendFwd`]: In each cursor, extends forward for the match
26/// - [`ExtendRev`]: In each cursor, extends backwards for the match
27///
28/// Here is how you can implement this trait yourself:
29///
30/// ```rust
31/// # duat_core::doc_duat!(duat);
32/// # use duat_base::modes::IncSearcher;
33/// use duat::prelude::*;
34///
35/// #[derive(Clone, Copy)]
36/// struct SearchAround;
37///
38/// impl IncSearcher for SearchAround {
39/// fn search(&mut self, pa: &mut Pass, handle: Handle<Buffer, Searcher>) {
40/// handle.edit_all(pa, |mut c| {
41/// c.set_caret_on_end();
42/// let Some(e_range) = c.search_inc_fwd(None).next() else {
43/// return;
44/// };
45///
46/// c.set_caret_on_start();
47/// let Some(s_range) = c.search_inc_rev(None).next() else {
48/// return;
49/// };
50///
51/// c.move_to(s_range.start..e_range.end)
52/// });
53/// }
54///
55/// fn prompt(&self) -> Text {
56/// txt!("[prompt]search around")
57/// }
58/// }
59/// ```
60///
61/// There are more advanced implementations in the [`duat-kak`] crate
62///
63/// [`duat-kak`]: https://docs.rs/duat-kak
64pub trait IncSearcher: Clone + Send + 'static {
65 /// Performs the incremental search
66 fn search(&mut self, pa: &mut Pass, handle: Handle<Buffer, Searcher>);
67
68 /// What prompt to show in the [`PromptLine`]
69 ///
70 /// [`PromptLine`]: crate::widgets::PromptLine
71 fn prompt(&self) -> Text;
72}
73
74/// Searches forward on each [`Cursor`]
75///
76/// [`Cursor`]: duat_core::mode::Cursor
77#[derive(Clone, Copy)]
78pub struct SearchFwd;
79
80impl IncSearcher for SearchFwd {
81 fn search(&mut self, pa: &mut Pass, handle: Handle<Buffer, Searcher>) {
82 handle.edit_all(pa, |mut c| {
83 let caret = c.caret();
84 if let Some(range) = { c.search_inc_fwd(None).find(|r| r.start != caret.byte()) } {
85 c.move_to(range)
86 }
87 });
88 }
89
90 fn prompt(&self) -> Text {
91 txt!("[prompt]search")
92 }
93}
94
95/// Searches backwards on each [`Cursor`]
96///
97/// [`Cursor`]: duat_core::mode::Cursor
98#[derive(Clone, Copy)]
99pub struct SearchRev;
100
101impl IncSearcher for SearchRev {
102 fn search(&mut self, pa: &mut Pass, handle: Handle<Buffer, Searcher>) {
103 handle.edit_all(pa, |mut c| {
104 let caret = c.caret();
105 if let Some(range) = { c.search_inc_rev(None).find(|r| r.end != caret.byte()) } {
106 c.move_to(range)
107 }
108 });
109 }
110
111 fn prompt(&self) -> Text {
112 txt!("[prompt]rev search")
113 }
114}
115
116/// Extends forward on each [`Cursor`]
117///
118/// [`Cursor`]: duat_core::mode::Cursor
119#[derive(Clone, Copy)]
120pub struct ExtendFwd;
121
122impl IncSearcher for ExtendFwd {
123 fn search(&mut self, pa: &mut Pass, handle: Handle<Buffer, Searcher>) {
124 handle.edit_all(pa, |mut c| {
125 let caret = c.caret();
126 if let Some(range) = { c.search_inc_fwd(None).find(|r| r.start != caret.byte()) } {
127 c.set_anchor_if_needed();
128 c.move_to(range.end);
129 }
130 });
131 }
132
133 fn prompt(&self) -> Text {
134 txt!("[prompt]search (extend)")
135 }
136}
137
138/// Extends backwards on each [`Cursor`]
139///
140/// [`Cursor`]: duat_core::mode::Cursor
141#[derive(Clone, Copy)]
142pub struct ExtendRev;
143
144impl IncSearcher for ExtendRev {
145 fn search(&mut self, pa: &mut Pass, handle: Handle<Buffer, Searcher>) {
146 handle.edit_all(pa, |mut c| {
147 let caret = c.caret();
148 if let Some(range) = { c.search_inc_rev(None).find(|r| r.end != caret.byte()) } {
149 c.set_anchor_if_needed();
150 c.move_to(range.start);
151 }
152 });
153 }
154
155 fn prompt(&self) -> Text {
156 txt!("[prompt]rev search (extend)")
157 }
158}