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