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