duat_utils/
lib.rs

1//! Standard complementary additions to Duat
2//!
3//! This crate essentially consists of the standard bits that pretty
4//! much every `config` crate will want to use, but aren't strictly
5//! speaking necessary for Duat to function. This split is mostly to
6//! improve compile times, but semantically, if a crate doesn't need
7//! all of these extra things, it is nice to separate them out.
8//!
9//! The crate has the following elements:
10//!
11//! - 4 [`widgets`]:
12//!   - [`LineNumbers`] shows the numbers on a [`File`] (for now), and
13//!     you can configure their alignment, relativeness, etc.
14//!   - The [`PromptLine`] lets you run commands and do other things,
15//!     like incremental search
16//!   - The [`StatusLine`] lets you display information that gets
17//!     updated automatically, it can show information from
18//!     [`RwData`]s, mapping functions, static elements, and every bit
19//!     of Duat. It's syntax, in [`status!`] is the same as the
20//!     [`txt!`] macro.
21//!   - [`Notifications`] shows things that have been logged to the
22//!     [`Logs`] of Duat, through the [`error!`], [`warn!`] and
23//!     [`info!`] macros.
24//!
25//! - 2 [`modes`]:
26//!   - [`Regular`] is essentially the standard [`Mode`] that text
27//!     editors use. Sort of like VSCode.
28//!   - [`Prompt`] is a multitool that can serve many purposes,
29//!     through the [`PromptMode`] trait, which allows one to act on
30//!     the [`PromptLine`] while abstracting over less important
31//!     elements of the [`Widget`].
32//!
33//! - For the [`PromptLine`], there are 3 [`PromptMode`]s:
34//!   - [`RunCommands`] will interpret and run Duat commands, with
35//!     syntax highlighting for correctness, defined by the
36//!     [`Parameter`] trait.
37//!   - [`PipeSelections`] will pipe each selection on the current
38//!     [`File`], replacing them with the return value from a shell
39//!     command.
40//!   - [`IncSearch`] is a specialized mode used for incremental
41//!     search, which can abstract over what the search actually does
42//!     with the [`IncSearcher`] trait.
43//!
44//! - For [`IncSearch`], there are 4 [`IncSearcher`]s:
45//!   - [`SearchFwd`] will move each [`Cursor`] to the next match.
46//!   - [`SearchRev`] will move each [`Cursor`] to the previous match.
47//!   - [`ExtendFwd`] will extend each [`Cursor`]'s selections to the
48//!     next match.
49//!   - [`ExtendRev`] will extend each [`Cursor`]'s selections to the
50//!     previous match.
51//!
52//! Note that the [`IncSearcher`] trait can be used for many more
53//! interesting things, like in [`duat-kak`] for example, where its
54//! implementors allow for splitting selections, selecting everything
55//! within a range, and many more such things in the future.
56//!
57//! - There are also two [`hooks`]:
58//!   - [`SearchUpdated`] for when an [`IncSearch`] is updated.
59//!   - [`SearchPerformed`] for when an [`IncSearch`] is finished.
60//!
61//! And finally, there is the [`state`] module, which contains a bunch
62//! of [`StatusLine`] parts for you to customize the [`StatusLine`]
63//! with.
64//!
65//! I would consider this crate essential for all `config`s of Duat
66//! out there, since it defines primitives that are not only hard to
67//! replace, but might also be very extensible by plugins in the
68//! ecosystem.
69//!
70//! [`LineNumbers`]: widgets::LineNumbers
71//! [`File`]: duat_core::file::File
72//! [`PromptLine`]: widgets::PromptLine
73//! [`StatusLine`]: widgets::StatusLine
74//! [`RwData`]: duat_core::data::RwData
75//! [`status!`]: widgets::status
76//! [`txt!`]: duat_core::text::txt
77//! [`Notifications`]: widgets::Notifications
78//! [`Logs`]: duat_core::context::Logs
79//! [`error!`]: duat_core::context::error
80//! [`warn!`]: duat_core::context::warn
81//! [`info!`]: duat_core::context::info
82//! [`Regular`]: modes::Regular
83//! [`Mode`]: duat_core::mode::Mode
84//! [`Prompt`]: modes::Prompt
85//! [`PromptMode`]: modes::PromptMode
86//! [`Widget`]: duat_core::ui::Widget
87//! [`RunCommands`]: modes::RunCommands
88//! [`Parameter`]: duat_core::cmd::Parameter
89//! [`PipeSelections`]: modes::PipeSelections
90//! [`IncSearch`]: modes::IncSearch
91//! [`IncSearcher`]: modes::IncSearcher
92//! [`SearchFwd`]: modes::SearchFwd
93//! [`Cursor`]: duat_core::mode::Cursor
94//! [`SearchRev`]: modes::SearchRev
95//! [`ExtendFwd`]: modes::ExtendFwd
96//! [`ExtendRev`]: modes::ExtendRev
97//! [`duat-kak`]: https://docs.rs/duat-kak/latest/duat_kak
98//! [`SearchUpdated`]: hooks::SearchUpdated
99//! [`SearchPerformed`]: hooks::SearchPerformed
100#![feature(
101    decl_macro,
102    closure_lifetime_binder,
103    default_field_values,
104    associated_type_defaults
105)]
106
107use duat_core::prelude::*;
108use regex_syntax::ast::Ast;
109
110pub mod modes;
111pub mod state;
112pub mod widgets;
113
114pub mod hooks {
115    //! Additional hooks for `duat-utils` specific things
116    //!
117    //! Right now, these are the two [`hook`]s available for use:
118    //!
119    //!   - [`SearchUpdated`] for when an [`IncSearch`] is updated.
120    //!   - [`SearchPerformed`] for when an [`IncSearch`] is finished.
121    //!
122    //! More may come in the future
123    //!
124    //! [`hook`]: duat_core::hook
125    //! [`IncSearch`]: crate::modes::IncSearch
126    use duat_core::hook::Hookable;
127
128    /// [`Hookable`]: Triggers when a [search] is updated
129    ///
130    /// Will not be triggered if the previous and current patterns are
131    /// the same.
132    ///
133    /// # Arguments
134    ///
135    /// - The previous regex pattern
136    /// - The current regex pattern
137    ///
138    /// [search]: crate::modes::IncSearch
139    pub struct SearchUpdated(pub(crate) (String, String));
140
141    impl Hookable for SearchUpdated {
142        type Input<'h> = (&'h str, &'h str);
143
144        fn get_input(&mut self) -> Self::Input<'_> {
145            (&self.0.0, &self.0.1)
146        }
147    }
148
149    /// [`Hookable`]: Triggers when a [search] is performed
150    ///
151    /// Will not be triggered on empty searches.
152    ///
153    /// # Arguments
154    ///
155    /// - The searched regex pattern
156    ///
157    /// [search]: crate::modes::IncSearch
158    pub struct SearchPerformed(pub(crate) String);
159
160    impl Hookable for SearchPerformed {
161        type Input<'h> = &'h str;
162
163        fn get_input(&mut self) -> Self::Input<'_> {
164            &self.0
165        }
166    }
167}
168
169fn tag_from_ast(tagger: Tagger, text: &mut Text, ast: &Ast) {
170    use duat_core::form::FormId;
171    use regex_syntax::ast::{Ast::*, Span};
172
173    let mut insert_form = |id: FormId, span: Span| {
174        text.insert_tag(tagger, span.start.offset..span.end.offset, id.to_tag(0));
175    };
176
177    match ast {
178        Empty(_) => {}
179        Flags(set_flags) => {
180            let id = form::id_of!("Regex.operator.flags");
181            insert_form(id, set_flags.span);
182        }
183        Literal(literal) => {
184            let id = form::id_of!("Regex.literal");
185            insert_form(id, literal.span);
186        }
187        Dot(span) => {
188            let id = form::id_of!("Regex.operator.dot");
189            insert_form(id, **span);
190        }
191        Assertion(assertion) => {
192            let id = form::id_of!("Regex.operator.assertion");
193            insert_form(id, assertion.span);
194        }
195        ClassUnicode(class) => {
196            let id = form::id_of!("Regex.class.unicode");
197            insert_form(id, class.span);
198        }
199        ClassPerl(class) => {
200            let id = form::id_of!("Regex.class.perl");
201            insert_form(id, class.span);
202        }
203        ClassBracketed(class) => {
204            let class_id = form::id_of!("Regex.class.bracketed");
205            let bracket_id = form::id_of!("Regex.bracket.class");
206
207            insert_form(class_id, *class.kind.span());
208
209            let range = class.span.start.offset..class.span.start.offset + 1;
210            text.insert_tag(tagger, range, bracket_id.to_tag(0));
211            let range = class.span.end.offset - 1..class.span.end.offset;
212            text.insert_tag(tagger, range, bracket_id.to_tag(0));
213        }
214        Repetition(repetition) => {
215            let id = form::id_of!("Regex.operator.repetition");
216            insert_form(id, repetition.op.span);
217        }
218        Group(group) => {
219            let group_id = form::id_of!("Regex.group");
220            let bracket_id = form::id_of!("Regex.bracket.group");
221
222            insert_form(group_id, *group.ast.span());
223
224            let range = group.span.start.offset..group.span.start.offset + 1;
225            text.insert_tag(tagger, range, bracket_id.to_tag(0));
226            let range = group.span.end.offset - 1..group.span.end.offset;
227            text.insert_tag(tagger, range, bracket_id.to_tag(0));
228
229            tag_from_ast(tagger, text, &group.ast);
230        }
231        Alternation(alternation) => {
232            let id = form::id_of!("Regex.operator.alternation");
233
234            let mut prev_end = None;
235
236            for ast in alternation.asts.iter() {
237                tag_from_ast(tagger, text, ast);
238
239                if let Some(end) = prev_end {
240                    let range = end..ast.span().start.offset;
241                    text.insert_tag(tagger, range, id.to_tag(0));
242                }
243
244                prev_end = Some(ast.span().end.offset);
245            }
246        }
247        Concat(concat) => {
248            for ast in concat.asts.iter() {
249                tag_from_ast(tagger, text, ast);
250            }
251        }
252    }
253}
254
255mod private_exports {
256    pub use duat_core;
257    pub use format_like::format_like;
258
259    pub macro parse_str($appender_checker:expr, $str:literal) {{
260        use crate::{
261            private_exports::duat_core::{context::FileHandle, data::Pass, text::Builder},
262            widgets::State,
263        };
264
265        let (mut appender, checker) = $appender_checker;
266        let (mut ap, _) = State::from($str).fns();
267
268        let appender = move |pa: &Pass, builder: &mut Builder, reader: &FileHandle<_>| {
269            appender(pa, builder, reader);
270            ap(pa, builder, reader);
271        };
272
273        (appender, checker)
274    }}
275
276    pub macro parse_status_part {
277        ($appender_checker:expr, "", $part:expr) => {{
278            use crate::{
279                private_exports::duat_core::{context::FileHandle, data::Pass, text::Builder},
280                widgets::State,
281            };
282
283			#[allow(unused_mut)]
284    		let (mut appender, checker) = $appender_checker;
285            let (mut ap, ch) = State::from($part).fns();
286
287            let checker = move || checker() || ch();
288
289            let pre_fn = move |pa: &Pass, builder: &mut Builder, handle: &FileHandle<_>| {
290                appender(pa, builder, handle);
291                ap(pa, builder, handle);
292            };
293
294            (pre_fn, checker)
295        }},
296        ($appender_checker:expr, $modif:literal, $part:expr) => {{
297            use crate::{
298                private_exports::duat_core::{context::FileHandle, data::Pass, text::Builder},
299                widgets::State,
300            };
301
302            let (mut appender, checker) = $appender_checker;
303            let (mut ap, ch) =
304                State::from(format!(concat!("{:", $modif, "}"), $part)).fns();
305
306            let checker = move || checker() || ch();
307
308            let appender = move |pa: &Pass, builder: &mut Builder, handle: &FileHandle<_>| {
309                appender(pa, builder, handle);
310                ap(pa, builder, handle);
311            };
312
313            (appender, checker)
314        }}
315    }
316
317    pub macro parse_form {
318        ($appender_checker:expr, "",) => {{
319            use crate::{
320                private_exports::duat_core::{context::FileHandle, data::Pass, form, text::Builder},
321            };
322
323            let (mut appender, checker) = $appender_checker;
324            let appender = move |pa: &Pass, builder: &mut Builder, handle: &FileHandle<_>| {
325                appender(pa, builder, handle);
326                builder.push(form::DEFAULT_ID);
327            };
328
329            (appender, checker)
330        }},
331        ($appender_checker:expr, "", $($form:tt)*) => {{
332            use crate::{
333                private_exports::duat_core::{context::FileHandle, data::Pass, form, text::Builder},
334            };
335
336            let (mut appender, checker) = $appender_checker;
337            let id = form::id_of!(concat!($(stringify!($form)),*));
338
339            let appender = move |pa: &Pass, builder: &mut Builder, handle: &FileHandle<_>| {
340                appender(pa, builder, handle);
341                builder.push(id);
342            };
343
344            (appender, checker)
345        }},
346        ($pre_fn_and_checker:expr, $modif:literal, $($form:ident).*) => {{
347            compile_error!(concat!("at the moment, Forms don't support modifiers like ", $modif))
348        }}
349    }
350}