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
112use duat_core::{
113 form,
114 text::{Tagger, Text},
115};
116use regex_syntax::ast::Ast;
117
118pub mod modes;
119pub mod state;
120pub mod widgets;
121
122pub mod hooks {
123 //! Additional hooks for `duat-base` specific things
124 //!
125 //! Right now, these are the two [`hook`]s available for use:
126 //!
127 //! - [`SearchUpdated`] for when an [`IncSearch`] is updated.
128 //! - [`SearchPerformed`] for when an [`IncSearch`] is finished.
129 //!
130 //! More may come in the future
131 //!
132 //! [`hook`]: duat_core::hook
133 //! [`IncSearch`]: crate::modes::IncSearch
134 use duat_core::hook::Hookable;
135
136 /// [`Hookable`]: Triggers when a [search] is updated
137 ///
138 /// Will not be triggered if the previous and current patterns are
139 /// the same.
140 ///
141 /// # Arguments
142 ///
143 /// - The previous regex pattern
144 /// - The current regex pattern
145 ///
146 /// [search]: crate::modes::IncSearch
147 pub struct SearchUpdated(pub(crate) (String, String));
148
149 impl Hookable for SearchUpdated {
150 type Input<'h> = (&'h str, &'h str);
151
152 fn get_input(&mut self) -> Self::Input<'_> {
153 (&self.0.0, &self.0.1)
154 }
155 }
156
157 /// [`Hookable`]: Triggers when a [search] is performed
158 ///
159 /// Will not be triggered on empty searches.
160 ///
161 /// # Arguments
162 ///
163 /// - The searched regex pattern
164 ///
165 /// [search]: crate::modes::IncSearch
166 pub struct SearchPerformed(pub(crate) String);
167
168 impl Hookable for SearchPerformed {
169 type Input<'h> = &'h str;
170
171 fn get_input(&mut self) -> Self::Input<'_> {
172 &self.0
173 }
174 }
175}
176
177fn tag_from_ast(tagger: Tagger, text: &mut Text, ast: &Ast) {
178 use duat_core::form::FormId;
179 use regex_syntax::ast::{Ast::*, Span};
180
181 let mut insert_form = |id: FormId, span: Span| {
182 text.insert_tag(tagger, span.start.offset..span.end.offset, id.to_tag(0));
183 };
184
185 match ast {
186 Empty(_) => {}
187 Flags(set_flags) => {
188 let id = form::id_of!("regex.operator.flags");
189 insert_form(id, set_flags.span);
190 }
191 Literal(literal) => {
192 let id = form::id_of!("regex.literal");
193 insert_form(id, literal.span);
194 }
195 Dot(span) => {
196 let id = form::id_of!("regex.operator.dot");
197 insert_form(id, **span);
198 }
199 Assertion(assertion) => {
200 let id = form::id_of!("regex.operator.assertion");
201 insert_form(id, assertion.span);
202 }
203 ClassUnicode(class) => {
204 let id = form::id_of!("regex.class.unicode");
205 insert_form(id, class.span);
206 }
207 ClassPerl(class) => {
208 let id = form::id_of!("regex.class.perl");
209 insert_form(id, class.span);
210 }
211 ClassBracketed(class) => {
212 let class_id = form::id_of!("regex.class.bracketed");
213 let bracket_id = form::id_of!("regex.bracket.class");
214
215 insert_form(class_id, *class.kind.span());
216
217 let range = class.span.start.offset..class.span.start.offset + 1;
218 text.insert_tag(tagger, range, bracket_id.to_tag(0));
219 let range = class.span.end.offset - 1..class.span.end.offset;
220 text.insert_tag(tagger, range, bracket_id.to_tag(0));
221 }
222 Repetition(repetition) => {
223 let id = form::id_of!("regex.operator.repetition");
224 insert_form(id, repetition.op.span);
225 }
226 Group(group) => {
227 let group_id = form::id_of!("regex.group");
228 let bracket_id = form::id_of!("regex.bracket.group");
229
230 insert_form(group_id, *group.ast.span());
231
232 let range = group.span.start.offset..group.span.start.offset + 1;
233 text.insert_tag(tagger, range, bracket_id.to_tag(0));
234 let range = group.span.end.offset - 1..group.span.end.offset;
235 text.insert_tag(tagger, range, bracket_id.to_tag(0));
236
237 tag_from_ast(tagger, text, &group.ast);
238 }
239 Alternation(alternation) => {
240 let id = form::id_of!("regex.operator.alternation");
241
242 let mut prev_end = None;
243
244 for ast in alternation.asts.iter() {
245 tag_from_ast(tagger, text, ast);
246
247 if let Some(end) = prev_end {
248 let range = end..ast.span().start.offset;
249 text.insert_tag(tagger, range, id.to_tag(0));
250 }
251
252 prev_end = Some(ast.span().end.offset);
253 }
254 }
255 Concat(concat) => {
256 for ast in concat.asts.iter() {
257 tag_from_ast(tagger, text, ast);
258 }
259 }
260 }
261}
262
263#[doc(hidden)]
264pub mod private_exports {
265 pub use duat_core::{context::Handle, data::Pass, form, text::Builder, ui::PushSpecs};
266 pub use format_like::format_like;
267}