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//! - 7 [`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 an
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//!   - [`WhichKey`] shows what each key will do. It shows up
33//!     automatically as you are typing and multi key sequences are
34//!     expected (e.g. Vim's `c`, `d`, `f` and others).
35//!
36//! - 3 [`modes`]:
37//!   - [`Prompt`] is a multitool that can serve many purposes,
38//!     through the [`PromptMode`] trait, which allows one to act on
39//!     the `PromptLine` while abstracting over less important
40//!     elements of the `Widget`.
41//!   - [`Pager`] is a simple, read only `Mode`, designed for
42//!     scrolling and searching through `Widget`s, most commonly the
43//!     `LogBook`.
44//!
45//! - For the [`PromptLine`], there are 4 [`PromptMode`]s:
46//!   - [`RunCommands`] will interpret and run Duat commands, with
47//!     syntax highlighting for correctness, defined by the
48//!     [`Parameter`] trait.
49//!   - [`PipeSelections`] will pipe each selection on the current
50//!     `Buffer`, replacing them with the return value from a shell
51//!     command.
52//!   - [`IncSearch`] is a specialized mode used for incremental
53//!     search, which can abstract over what the search actually does
54//!     with the [`IncSearcher`] trait.
55//!
56//! - For [`IncSearch`], there are 4 `IncSearcher`s:
57//!   - [`SearchFwd`] will move each [`Cursor`] to the next match.
58//!   - [`SearchRev`] will move each `Cursor` to the previous match.
59//!   - [`ExtendFwd`] will extend each `Cursor`'s selections to the
60//!     next match.
61//!   - [`ExtendRev`] will extend each `Cursor`'s selections to the
62//!     previous match.
63//!
64//! Note that the [`IncSearcher`] trait can be used for many more
65//! interesting things, like in [`duat-kak`] for example, where its
66//! implementors allow for splitting selections, selecting everything
67//! within a range, and many more such things in the future.
68//!
69//! - There are also two [`hooks`]:
70//!   - [`SearchUpdated`] for when an `IncSearch` is updated.
71//!   - [`SearchPerformed`] for when an `IncSearch` is finished.
72//!
73//! And finally, there is the [`state`] module, which contains a bunch
74//! of [`StatusLine`] parts for you to customize the `StatusLine`
75//! with.
76//!
77//! I would consider this crate essential for all `config`s of Duat
78//! out there, since it defines primitives that are not only hard to
79//! replace, but might also be very extensible by plugins in the
80//! ecosystem.
81//!
82//! [`LineNumbers`]: widgets::LineNumbers
83//! [`Buffer`]: duat_core::buffer::Buffer
84//! [`PromptLine`]: widgets::PromptLine
85//! [`StatusLine`]: widgets::StatusLine
86//! [`RwData`]: duat_core::data::RwData
87//! [`status!`]: widgets::status
88//! [`txt!`]: duat_core::text::txt
89//! [`Notifications`]: widgets::Notifications
90//! [`Logs`]: duat_core::context::Logs
91//! [`error!`]: duat_core::context::error
92//! [`warn!`]: duat_core::context::warn
93//! [`info!`]: duat_core::context::info
94//! [`Mode`]: duat_core::mode::Mode
95//! [`Prompt`]: modes::Prompt
96//! [`PromptMode`]: modes::PromptMode
97//! [`Widget`]: duat_core::ui::Widget
98//! [`RunCommands`]: modes::RunCommands
99//! [`Parameter`]: duat_core::cmd::Parameter
100//! [`PipeSelections`]: modes::PipeSelections
101//! [`IncSearch`]: modes::IncSearch
102//! [`IncSearcher`]: modes::IncSearcher
103//! [`SearchFwd`]: modes::SearchFwd
104//! [`Cursor`]: duat_core::mode::Cursor
105//! [`SearchRev`]: modes::SearchRev
106//! [`ExtendFwd`]: modes::ExtendFwd
107//! [`ExtendRev`]: modes::ExtendRev
108//! [`duat-kak`]: https://docs.rs/duat-kak/latest/duat_kak
109//! [`SearchUpdated`]: hooks::SearchUpdated
110//! [`SearchPerformed`]: hooks::SearchPerformed
111//! [`Pager`]: modes::Pager
112//! [`LogBook`]: widgets::LogBook
113//! [`Completions`]: widgets::Completions
114//! [`CompletionsProvider`]: widgets::CompletionsProvider
115//! [`WhichKey`]: widgets::WhichKey
116use duat_core::{
117    form,
118    text::{Tagger, Text},
119};
120use regex_syntax::ast::Ast;
121
122pub mod modes;
123pub mod state;
124pub mod widgets;
125
126pub mod hooks {
127    //! Additional hooks for `duat-base` specific things
128    //!
129    //! Right now, these are the two [`hook`]s available for use:
130    //!
131    //!   - [`SearchUpdated`] for when an [`IncSearch`] is updated.
132    //!   - [`SearchPerformed`] for when an [`IncSearch`] is finished.
133    //!
134    //! More may come in the future
135    //!
136    //! [`hook`]: duat_core::hook
137    //! [`IncSearch`]: crate::modes::IncSearch
138    use duat_core::hook::Hookable;
139
140    /// [`Hookable`]: Triggers when a [search] is updated
141    ///
142    /// Will not be triggered if the previous and current patterns are
143    /// the same.
144    ///
145    /// # Arguments
146    ///
147    /// - The previous regex pattern
148    /// - The current regex pattern
149    ///
150    /// [search]: crate::modes::IncSearch
151    pub struct SearchUpdated(pub(crate) (String, String));
152
153    impl Hookable for SearchUpdated {
154        type Input<'h> = (&'h str, &'h str);
155
156        fn get_input(&mut self) -> Self::Input<'_> {
157            (&self.0.0, &self.0.1)
158        }
159    }
160
161    /// [`Hookable`]: Triggers when a [search] is performed
162    ///
163    /// Will not be triggered on empty searches.
164    ///
165    /// # Arguments
166    ///
167    /// - The searched regex pattern
168    ///
169    /// [search]: crate::modes::IncSearch
170    pub struct SearchPerformed(pub(crate) String);
171
172    impl Hookable for SearchPerformed {
173        type Input<'h> = &'h str;
174
175        fn get_input(&mut self) -> Self::Input<'_> {
176            &self.0
177        }
178    }
179}
180
181fn tag_from_ast(tagger: Tagger, text: &mut Text, ast: &Ast) {
182    use duat_core::form::FormId;
183    use regex_syntax::ast::{Ast::*, Span};
184
185    let mut insert_form = |id: FormId, span: Span| {
186        text.insert_tag(tagger, span.start.offset..span.end.offset, id.to_tag(0));
187    };
188
189    match ast {
190        Empty(_) => {}
191        Flags(set_flags) => {
192            let id = form::id_of!("regex.operator.flags");
193            insert_form(id, set_flags.span);
194        }
195        Literal(literal) => {
196            let id = form::id_of!("regex.literal");
197            insert_form(id, literal.span);
198        }
199        Dot(span) => {
200            let id = form::id_of!("regex.operator.dot");
201            insert_form(id, **span);
202        }
203        Assertion(assertion) => {
204            let id = form::id_of!("regex.operator.assertion");
205            insert_form(id, assertion.span);
206        }
207        ClassUnicode(class) => {
208            let id = form::id_of!("regex.class.unicode");
209            insert_form(id, class.span);
210        }
211        ClassPerl(class) => {
212            let id = form::id_of!("regex.class.perl");
213            insert_form(id, class.span);
214        }
215        ClassBracketed(class) => {
216            let class_id = form::id_of!("regex.class.bracketed");
217            let bracket_id = form::id_of!("regex.bracket.class");
218
219            insert_form(class_id, *class.kind.span());
220
221            let range = class.span.start.offset..class.span.start.offset + 1;
222            text.insert_tag(tagger, range, bracket_id.to_tag(0));
223            let range = class.span.end.offset - 1..class.span.end.offset;
224            text.insert_tag(tagger, range, bracket_id.to_tag(0));
225        }
226        Repetition(repetition) => {
227            let id = form::id_of!("regex.operator.repetition");
228            insert_form(id, repetition.op.span);
229        }
230        Group(group) => {
231            let group_id = form::id_of!("regex.group");
232            let bracket_id = form::id_of!("regex.bracket.group");
233
234            insert_form(group_id, *group.ast.span());
235
236            let range = group.span.start.offset..group.span.start.offset + 1;
237            text.insert_tag(tagger, range, bracket_id.to_tag(0));
238            let range = group.span.end.offset - 1..group.span.end.offset;
239            text.insert_tag(tagger, range, bracket_id.to_tag(0));
240
241            tag_from_ast(tagger, text, &group.ast);
242        }
243        Alternation(alternation) => {
244            let id = form::id_of!("regex.operator.alternation");
245
246            let mut prev_end = None;
247
248            for ast in alternation.asts.iter() {
249                tag_from_ast(tagger, text, ast);
250
251                if let Some(end) = prev_end {
252                    let range = end..ast.span().start.offset;
253                    text.insert_tag(tagger, range, id.to_tag(0));
254                }
255
256                prev_end = Some(ast.span().end.offset);
257            }
258        }
259        Concat(concat) => {
260            for ast in concat.asts.iter() {
261                tag_from_ast(tagger, text, ast);
262            }
263        }
264    }
265}
266
267#[doc(hidden)]
268pub mod private_exports {
269    pub use duat_core::{context::Handle, data::Pass, form, text::Builder, ui::PushSpecs};
270    pub use format_like::format_like;
271}