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}