Skip to main content

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//! - 8 [`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//!   - [`Info`] just shows static information, resizing itself to
36//!     properly show as much as possible of it.
37//!
38//! - 2 [`modes`]:
39//!   - [`Prompt`] is a multitool that can serve many purposes,
40//!     through the [`PromptMode`] trait, which allows one to act on
41//!     the `PromptLine` while abstracting over less important
42//!     elements of the `Widget`.
43//!   - [`Pager`] is a simple, read only `Mode`, designed for
44//!     scrolling and searching through `Widget`s, most commonly the
45//!     `LogBook`.
46//!
47//! - For the [`PromptLine`], there are 4 [`PromptMode`]s:
48//!   - [`RunCommands`] will interpret and run Duat commands, with
49//!     syntax highlighting for correctness, defined by the
50//!     [`Parameter`] trait.
51//!   - [`PipeSelections`] will pipe each selection on the current
52//!     `Buffer`, replacing them with the return value from a shell
53//!     command.
54//!   - [`IncSearch`] is a specialized mode used for incremental
55//!     search, which can abstract over what the search actually does
56//!     with the [`IncSearcher`] trait.
57//!
58//! - For [`IncSearch`], there are 4 `IncSearcher`s:
59//!   - [`SearchFwd`] will move each [`Cursor`] to the next match.
60//!   - [`SearchRev`] will move each `Cursor` to the previous match.
61//!   - [`ExtendFwd`] will extend each `Cursor`'s selections to the
62//!     next match.
63//!   - [`ExtendRev`] will extend each `Cursor`'s selections to the
64//!     previous match.
65//!
66//! Note that the [`IncSearcher`] trait can be used for many more
67//! interesting things, like in [`duat-kak`] for example, where its
68//! implementors allow for splitting selections, selecting everything
69//! within a range, and many more such things in the future.
70//!
71//! - There are also two [`hooks`]:
72//!   - [`SearchUpdated`] for when an `IncSearch` is updated.
73//!   - [`SearchPerformed`] for when an `IncSearch` is finished.
74//!
75//! And finally, there is the [`state`] module, which contains a bunch
76//! of [`StatusLine`] parts for you to customize the `StatusLine`
77//! with.
78//!
79//! I would consider this crate essential for all `config`s of Duat
80//! out there, since it defines primitives that are not only hard to
81//! replace, but might also be very extensible by plugins in the
82//! ecosystem.
83//!
84//! [`LineNumbers`]: widgets::LineNumbers
85//! [`Buffer`]: duat_core::buffer::Buffer
86//! [`PromptLine`]: widgets::PromptLine
87//! [`StatusLine`]: widgets::StatusLine
88//! [`RwData`]: duat_core::data::RwData
89//! [`status!`]: widgets::status
90//! [`txt!`]: duat_core::text::txt
91//! [`Notifications`]: widgets::Notifications
92//! [`Logs`]: duat_core::context::Logs
93//! [`error!`]: duat_core::context::error
94//! [`warn!`]: duat_core::context::warn
95//! [`info!`]: duat_core::context::info
96//! [`Mode`]: duat_core::mode::Mode
97//! [`Prompt`]: modes::Prompt
98//! [`PromptMode`]: modes::PromptMode
99//! [`Widget`]: duat_core::ui::Widget
100//! [`RunCommands`]: modes::RunCommands
101//! [`Parameter`]: duat_core::cmd::Parameter
102//! [`PipeSelections`]: modes::PipeSelections
103//! [`IncSearch`]: modes::IncSearch
104//! [`IncSearcher`]: modes::IncSearcher
105//! [`SearchFwd`]: modes::SearchFwd
106//! [`Cursor`]: duat_core::mode::Cursor
107//! [`SearchRev`]: modes::SearchRev
108//! [`ExtendFwd`]: modes::ExtendFwd
109//! [`ExtendRev`]: modes::ExtendRev
110//! [`duat-kak`]: https://docs.rs/duat-kak/latest/duat_kak
111//! [`SearchUpdated`]: hooks::SearchUpdated
112//! [`SearchPerformed`]: hooks::SearchPerformed
113//! [`Pager`]: modes::Pager
114//! [`LogBook`]: widgets::LogBook
115//! [`Completions`]: widgets::Completions
116//! [`CompletionsProvider`]: widgets::CompletionsProvider
117//! [`WhichKey`]: widgets::WhichKey
118//! [`Info`]: widgets::Info
119use duat_core::{
120    Plugin, cmd,
121    data::Pass,
122    form::{self, Form},
123    mode,
124    text::{Tagger, Text, txt},
125};
126use regex_syntax::ast::Ast;
127
128use crate::{modes::Pager, widgets::LogBook};
129
130mod buffer_parser;
131pub mod modes;
132pub mod state;
133pub mod widgets;
134
135/// The plugin for `duat-base`
136///
137/// This plugin will setup forms, hooks, completions, and add a
138/// default parser for [`BufferOpts`].
139///
140/// [`BufferOpts`]: duat_core::buffer::BufferOpts
141#[derive(Default)]
142pub struct DuatBase;
143
144impl Plugin for DuatBase {
145    fn plug(self, _: &duat_core::Plugins) {
146        widgets::setup_completions();
147        buffer_parser::enable_parser();
148
149        // Setup for the LineNumbers
150        form::set_weak("linenum.main", Form::new().yellow());
151        form::set_weak("linenum.wrapped", Form::new().cyan().italic());
152
153        // Setup for the StatusLine
154        form::set_weak("buffer", Form::new().yellow().italic());
155        form::set_weak("selections", Form::new().dark_blue());
156        form::set_weak("coord", Form::mimic("contant"));
157        form::set_weak("separator", Form::mimic("punctuation.delimiter"));
158        form::set_weak("mode", Form::new().green());
159        form::set_weak("default.StatusLine", Form::new().on_dark_grey());
160
161        // Setup for the LogBook
162        form::set_weak("default.LogBook", Form::new().on_dark_grey());
163        form::set_weak("log_book.error", Form::mimic("default.error"));
164        form::set_weak("log_book.warn", Form::mimic("default.warn"));
165        form::set_weak("log_book.info", Form::mimic("default.info"));
166        form::set_weak("log_book.debug", Form::mimic("default.debug"));
167        form::set_weak("log_book.colon", Form::mimic("prompt.colon"));
168        form::set_weak("log_book.bracket", Form::mimic("punctuation.bracket"));
169        form::set_weak("log_book.target", Form::mimic("module"));
170
171        // Setup for the PromptLine
172        form::set_weak("prompt.preview", Form::mimic("comment"));
173
174        // Setup for Completions
175        form::set_weak("default.Completions", Form::new().on_dark_grey());
176        form::set_weak("selected.Completions", Form::new().black().on_grey());
177
178        // Setup for WhichKey
179        form::set_weak("key", Form::mimic("const"));
180        form::set_weak("key.mod", Form::mimic("punctuation.bracket"));
181        form::set_weak("key.angle", Form::mimic("punctuation.bracket"));
182        form::set_weak("key.special", Form::new().yellow());
183        form::set_weak("remap", Form::new().italic());
184
185        cmd::add("logs", |pa: &mut Pass| {
186            mode::set(pa, Pager::<LogBook>::new());
187            Ok(None)
188        })
189        .doc(
190            txt!("Open the [a]Logs[] and enter [mode]Pager[] mode"),
191            None,
192        );
193    }
194}
195
196pub mod hooks {
197    //! Additional hooks for `duat-base` specific things
198    //!
199    //! Right now, these are the two [`hook`]s available for use:
200    //!
201    //!   - [`SearchUpdated`] for when an [`IncSearch`] is updated.
202    //!   - [`SearchPerformed`] for when an [`IncSearch`] is finished.
203    //!
204    //! More may come in the future
205    //!
206    //! [`hook`]: duat_core::hook
207    //! [`IncSearch`]: crate::modes::IncSearch
208    use duat_core::{data::Pass, hook::Hookable};
209
210    /// [`Hookable`]: Triggers when a [search] is updated
211    ///
212    /// Will not be triggered if the previous and current patterns are
213    /// the same.
214    ///
215    /// # Arguments
216    ///
217    /// - The previous regex pattern
218    /// - The current regex pattern
219    ///
220    /// [search]: crate::modes::IncSearch
221    pub struct SearchUpdated(pub(crate) (String, String));
222
223    impl Hookable for SearchUpdated {
224        type Input<'h> = (&'h str, &'h str);
225
226        fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
227            (&self.0.0, &self.0.1)
228        }
229    }
230
231    /// [`Hookable`]: Triggers when a [search] is performed
232    ///
233    /// Will not be triggered on empty searches.
234    ///
235    /// # Arguments
236    ///
237    /// - The searched regex pattern
238    ///
239    /// [search]: crate::modes::IncSearch
240    pub struct SearchPerformed(pub(crate) String);
241
242    impl Hookable for SearchPerformed {
243        type Input<'h> = &'h str;
244
245        fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
246            &self.0
247        }
248    }
249}
250
251fn tag_from_ast(tagger: Tagger, text: &mut Text, ast: &Ast) {
252    use duat_core::form::FormId;
253    use regex_syntax::ast::{Ast::*, Span};
254
255    let mut insert_form = |id: FormId, span: Span| {
256        text.insert_tag(tagger, span.start.offset..span.end.offset, id.to_tag(0));
257    };
258
259    match ast {
260        Empty(_) => {}
261        Flags(set_flags) => {
262            let id = form::id_of!("regex.operator.flags");
263            insert_form(id, set_flags.span);
264        }
265        Literal(literal) => {
266            let id = form::id_of!("regex.literal");
267            insert_form(id, literal.span);
268        }
269        Dot(span) => {
270            let id = form::id_of!("regex.operator.dot");
271            insert_form(id, **span);
272        }
273        Assertion(assertion) => {
274            let id = form::id_of!("regex.operator.assertion");
275            insert_form(id, assertion.span);
276        }
277        ClassUnicode(class) => {
278            let id = form::id_of!("regex.class.unicode");
279            insert_form(id, class.span);
280        }
281        ClassPerl(class) => {
282            let id = form::id_of!("regex.class.perl");
283            insert_form(id, class.span);
284        }
285        ClassBracketed(class) => {
286            let class_id = form::id_of!("regex.class.bracketed");
287            let bracket_id = form::id_of!("regex.bracket.class");
288
289            insert_form(class_id, *class.kind.span());
290
291            let range = class.span.start.offset..class.span.start.offset + 1;
292            text.insert_tag(tagger, range, bracket_id.to_tag(0));
293            let range = class.span.end.offset - 1..class.span.end.offset;
294            text.insert_tag(tagger, range, bracket_id.to_tag(0));
295        }
296        Repetition(repetition) => {
297            let id = form::id_of!("regex.operator.repetition");
298            insert_form(id, repetition.op.span);
299        }
300        Group(group) => {
301            let group_id = form::id_of!("regex.group");
302            let bracket_id = form::id_of!("regex.bracket.group");
303
304            insert_form(group_id, *group.ast.span());
305
306            let range = group.span.start.offset..group.span.start.offset + 1;
307            text.insert_tag(tagger, range, bracket_id.to_tag(0));
308            let range = group.span.end.offset - 1..group.span.end.offset;
309            text.insert_tag(tagger, range, bracket_id.to_tag(0));
310
311            tag_from_ast(tagger, text, &group.ast);
312        }
313        Alternation(alternation) => {
314            let id = form::id_of!("regex.operator.alternation");
315
316            let mut prev_end = None;
317
318            for ast in alternation.asts.iter() {
319                tag_from_ast(tagger, text, ast);
320
321                if let Some(end) = prev_end {
322                    let range = end..ast.span().start.offset;
323                    text.insert_tag(tagger, range, id.to_tag(0));
324                }
325
326                prev_end = Some(ast.span().end.offset);
327            }
328        }
329        Concat(concat) => {
330            for ast in concat.asts.iter() {
331                tag_from_ast(tagger, text, ast);
332            }
333        }
334    }
335}
336
337#[doc(hidden)]
338pub mod private_exports {
339    pub use duat_core::{context::Handle, data::Pass, form, text::Builder, ui::PushSpecs};
340    pub use format_like::format_like;
341}