duat_core/hooks.rs
1//! Utilities for hooks in Duat
2//!
3//! In Duat, hooks are handled through the [`Hookable`] trait. This
4//! trait contains the [`Hookable::Args`] associated type. By
5//! implementing this trait, you allow an end user to hook executions
6//! whenever said [`Hookable`] is triggered:
7//!
8//! ```rust
9//! # use duat_core::{hooks::{self, *}, ui::Ui, widgets::{File, LineNumbers, Widget}};
10//! # fn test<U: Ui>() {
11//! hooks::add::<OnFileOpen<U>>(|builder| {
12//! builder.push(LineNumbers::cfg());
13//! });
14//! # }
15//! ```
16//!
17//! The hook above is an example of a specialized use. [`OnFileOpen`]
18//! lets you push widgets around a [`File`] whenever one is opened. In
19//! the case above, I've pushed the [`LineNumbers`] widget to the
20//! [`File`].
21//!
22//! Currently, these are the existing hooks in `duat-core`:
23//!
24//! - [`ConfigLoaded`] triggers after loading the config crate.
25//! - [`ConfigUnloaded`] triggers after unloading the config crate.
26//! - [`ExitedDuat`] triggers after Duat has exited.
27//! - [`OnFileOpen`], which lets you push widgets around a [`File`].
28//! - [`OnWindowOpen`], which lets you push widgets around the window.
29//! - [`FocusedOn`] lets you act on a [widget] when focused.
30//! - [`UnfocusedFrom`] lets you act on a [widget] when unfocused.
31//! - [`KeySent`] lets you act on a [dyn Widget], given a[key].
32//! - [`KeySentTo`] lets you act on a given [widget], given a [key].
33//! - [`FormSet`] triggers whenever a [`Form`] is added/altered.
34//! - [`ModeSwitched`] triggers when you change [`Mode`].
35//!
36//! # A note on execution
37//!
38//! In order to prevent [deadlocks], Duat executes hooks and
39//! [commands] asynchronously in the order that they are sent. For
40//! hooks, this means that if you [`trigger`] a [`Hookable`], close
41//! actions following said triggering will probably happen before the
42//! [`trigger`] is over:
43//!
44//! ```rust
45//! # use duat_core::hooks::{self, *};
46//! struct CustomHook;
47//! impl Hookable for CustomHook {
48//! type Args<'a> = usize;
49//! type PreArgs = usize;
50//!
51//! fn trigger_hooks<'b>(
52//! pre_args: Self::PreArgs,
53//! hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
54//! ) {
55//! for hook in hooks {
56//! hook(pre_args)
57//! }
58//! }
59//! }
60//!
61//! let arg = 42;
62//! hooks::trigger::<CustomHook>(arg);
63//! println!("This will probably print before the trigger is over.");
64//! ```
65//!
66//! The above example ilustrates how hooks are implemented in Duat,
67//! that is, you are responsible for actually calling the functions.
68//! This is roughly how hooks should be defined:
69//!
70//! ```rust
71//! # fn args_from_pre_args(usize: &usize) -> usize { *usize }
72//! # use duat_core::hooks::{self, *};
73//! struct NewHook;
74//! impl Hookable for NewHook {
75//! // Some type derived from Self::PreArgs
76//! type Args<'a> = usize;
77//! // Some Send + 'static type
78//! type PreArgs = usize;
79//!
80//! fn trigger_hooks<'b>(
81//! pre_args: Self::PreArgs,
82//! hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
83//! ) {
84//! // Preprocessing before creating Self::Args
85//! // ...
86//! let args = args_from_pre_args(&pre_args);
87//! // Preprocessing before triggering hook functions
88//! // ...
89//! for hook in hooks {
90//! hook(args)
91//! }
92//! // Postprocessing
93//! // ...
94//! }
95//! }
96//! ```
97//!
98//! One example of where this ends up being very useful is in
99//! [`Widget`] related hooks, since it allows Duat to pass just [`&mut
100//! Widget`] instead of [`RwData<Widget>`] as a parameter, and it also
101//! lets Duat immediately print the widget as soon as the hooks are
102//! done altering it.
103//!
104//! [`File`]: crate::widgets::File
105//! [`LineNumbers`]: crate::widgets::LineNumbers
106//! [widget]: Widget
107//! [dyn Widget]: Widget
108//! [key]: KeyEvent
109//! [deadlocks]: https://en.wikipedia.org/wiki/Deadlock_(computer_science)
110//! [commands]: crate::cmd
111//! [`Mode`]: crate::mode::Mode
112//! [`&mut Widget`]: Widget
113use std::{any::TypeId, collections::HashMap, marker::PhantomData, sync::LazyLock};
114
115use parking_lot::{Mutex, RwLock};
116
117pub use self::global::*;
118use crate::{
119 data::RwData,
120 form::{Form, FormId},
121 mode::{Cursors, KeyEvent},
122 ui::{Area, FileBuilder, Ui, WindowBuilder},
123 widgets::Widget,
124};
125
126/// [`Hookable`]: Triggers when Duat opens or reloads
127///
128/// This trigger will also happen after a few other initial setups of
129/// Duat.
130///
131/// There are no arguments
132pub struct ConfigLoaded;
133
134impl Hookable for ConfigLoaded {
135 type Args<'a> = ();
136 type PreArgs = ();
137
138 fn trigger_hooks<'b>(
139 pre_args: Self::PreArgs,
140 hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
141 ) {
142 for hook in hooks {
143 hook(pre_args)
144 }
145 }
146}
147
148/// [`Hookable`]: Triggers when Duat closes or has to reload
149///
150/// There are no arguments
151pub struct ConfigUnloaded;
152
153impl Hookable for ConfigUnloaded {
154 type Args<'a> = ();
155 type PreArgs = ();
156
157 fn trigger_hooks<'b>(
158 pre_args: Self::PreArgs,
159 hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
160 ) {
161 for hook in hooks {
162 hook(pre_args)
163 }
164 }
165}
166
167/// [`Hookable`]: Triggers when Duat closes
168///
169/// There are no arguments
170pub struct ExitedDuat;
171
172impl Hookable for ExitedDuat {
173 type Args<'a> = ();
174 type PreArgs = ();
175
176 fn trigger_hooks<'b>(
177 pre_args: Self::PreArgs,
178 hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
179 ) {
180 for hook in hooks {
181 hook(pre_args)
182 }
183 }
184}
185
186/// [`Hookable`]: Triggers when a [`File`] is opened
187///
188/// # Arguments
189///
190/// - The file [builder], which can be used to push widgets to the
191/// file, and to eachother.
192///
193/// [`File`]: crate::widgets::File
194/// [builder]: crate::ui::FileBuilder
195pub struct OnFileOpen<U: Ui>(PhantomData<U>);
196
197impl<U: Ui> Hookable for OnFileOpen<U> {
198 type Args<'a> = &'a mut FileBuilder<U>;
199 type PreArgs = FileBuilder<U>;
200
201 fn trigger_hooks<'b>(
202 mut pre_args: Self::PreArgs,
203 hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
204 ) {
205 for hook in hooks {
206 hook(&mut pre_args)
207 }
208 }
209}
210
211/// [`Hookable`]: Triggers when a new window is opened
212///
213/// # Arguments
214///
215/// - The window [builder], which can be used to push widgets to the
216/// edges of the window, surrounding the inner file region.
217///
218/// [builder]: crate::ui::WindowBuilder
219pub struct OnWindowOpen<U: Ui>(PhantomData<U>);
220
221impl<U: Ui> Hookable for OnWindowOpen<U> {
222 type Args<'a> = &'a mut WindowBuilder<U>;
223 type PreArgs = WindowBuilder<U>;
224
225 fn trigger_hooks<'b>(
226 mut pre_args: Self::PreArgs,
227 hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
228 ) {
229 for hook in hooks {
230 hook(&mut pre_args)
231 }
232 }
233}
234
235/// [`Hookable`]: Triggers when the [`Widget`] is focused
236///
237/// # Arguments
238///
239/// - The widget itself.
240/// - Its [area].
241///
242/// [`Widget`]: crate::widgets::Widget
243/// [area]: crate::ui::Area
244pub struct FocusedOn<W: Widget<U>, U: Ui>(PhantomData<(W, U)>);
245
246impl<W: Widget<U>, U: Ui> Hookable for FocusedOn<W, U> {
247 type Args<'a> = (&'a mut W, &'a U::Area);
248 type PreArgs = (RwData<W>, U::Area);
249
250 fn trigger_hooks<'b>(
251 (widget, area): Self::PreArgs,
252 hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
253 ) {
254 let mut widget = widget.write();
255 let cfg = widget.print_cfg();
256 widget.text_mut().remove_cursors(&area, cfg);
257 widget.on_focus(&area);
258
259 for hook in hooks {
260 hook((&mut *widget, &area));
261 }
262
263 widget.text_mut().add_cursors(&area, cfg);
264 if let Some(main) = widget.cursors().and_then(Cursors::get_main) {
265 area.scroll_around_point(widget.text(), main.caret(), widget.print_cfg());
266 }
267
268 widget.update(&area);
269 widget.print(&area);
270 }
271}
272
273/// [`Hookable`]: Triggers when the [`Widget`] is unfocused
274///
275/// # Arguments
276///
277/// - The widget itself.
278/// - Its [area].
279///
280/// [`Widget`]: crate::widgets::Widget
281/// [area]: crate::ui::Area
282pub struct UnfocusedFrom<W: Widget<U>, U: Ui>(PhantomData<(W, U)>);
283
284impl<W: Widget<U>, U: Ui> Hookable for UnfocusedFrom<W, U> {
285 type Args<'a> = (&'a mut W, &'a U::Area);
286 type PreArgs = (RwData<W>, U::Area);
287
288 fn trigger_hooks<'b>(
289 (widget, area): Self::PreArgs,
290 hooks: impl Iterator<Item = &'b mut Box<dyn for<'a> FnMut(Self::Args<'a>) + Send>>,
291 ) {
292 let mut widget = widget.write();
293 let cfg = widget.print_cfg();
294 widget.text_mut().remove_cursors(&area, cfg);
295 widget.on_unfocus(&area);
296
297 for hook in hooks {
298 hook((&mut *widget, &area));
299 }
300
301 widget.text_mut().add_cursors(&area, cfg);
302 if let Some(main) = widget.cursors().and_then(Cursors::get_main) {
303 area.scroll_around_point(widget.text(), main.caret(), widget.print_cfg());
304 }
305
306 widget.update(&area);
307 widget.print(&area);
308 }
309}
310
311/// [`Hookable`]: Triggers when the [`Mode`] is changed
312///
313/// # Arguments
314///
315/// - The previous mode.
316/// - The current mode.
317///
318/// # Note
319///
320/// You should try to avoid more than one [`Mode`] with the same name.
321/// This can happen if you're using two structs with the same name,
322/// but from different crates.
323///
324/// [`Mode`]: crate::mode::Mode
325pub struct ModeSwitched;
326
327impl Hookable for ModeSwitched {
328 type Args<'a> = (&'a str, &'a str);
329 type PreArgs = (&'static str, &'static str);
330
331 fn trigger_hooks<'b>(
332 pre_args: Self::PreArgs,
333 hooks: impl Iterator<Item = &'b mut Box<dyn for<'a> FnMut(Self::Args<'a>) + Send>>,
334 ) {
335 for hook in hooks {
336 hook(pre_args)
337 }
338 }
339}
340
341/// [`Hookable`]: Triggers whenever a [key] is sent
342///
343/// # Arguments
344///
345/// - The [key] sent.
346/// - An [`RwData<dyn Widget<U>>`] for the widget.
347///
348/// [key]: KeyEvent
349pub struct KeySent<U: Ui>(PhantomData<U>);
350
351impl<U: Ui> Hookable for KeySent<U> {
352 type Args<'a> = KeyEvent;
353 type PreArgs = KeyEvent;
354
355 fn trigger_hooks<'b>(
356 pre_args: Self::PreArgs,
357 hooks: impl Iterator<Item = &'b mut Box<dyn for<'a> FnMut(Self::Args<'a>) + Send>>,
358 ) {
359 for hook in hooks {
360 hook(pre_args);
361 }
362 }
363}
364
365/// [`Hookable`]: Triggers whenever a [key] is sent to the [`Widget`]
366///
367/// # Arguments
368///
369/// - The [key] sent.
370/// - An [`RwData<W>`] for the widget.
371///
372/// [key]: KeyEvent
373pub struct KeySentTo<W: Widget<U>, U: Ui>(PhantomData<(&'static W, U)>);
374
375impl<W: Widget<U>, U: Ui> Hookable for KeySentTo<W, U> {
376 type Args<'b> = (KeyEvent, &'b mut W, &'b U::Area);
377 type PreArgs = (KeyEvent, RwData<W>, U::Area);
378
379 fn trigger_hooks<'b>(
380 (key, widget, area): Self::PreArgs,
381 hooks: impl Iterator<Item = &'b mut Box<dyn for<'a> FnMut(Self::Args<'a>) + Send>>,
382 ) {
383 let mut widget = widget.write();
384 for hook in hooks {
385 hook((key, &mut *widget, &area));
386 }
387 }
388}
389
390/// [`Hookable`]: Triggers whenever a [`Form`] is set
391///
392/// This can be a creation or alteration of a [`Form`].
393/// If the [`Form`] is a reference to another, the reference's
394/// [`Form`] will be returned instead.
395///
396/// # Arguments
397///
398/// - The [`Form`]'s name.
399/// - Its [`FormId`].
400/// - Its new value.
401pub struct FormSet;
402
403impl Hookable for FormSet {
404 type Args<'b> = Self::PreArgs;
405 type PreArgs = (&'static str, FormId, Form);
406
407 fn trigger_hooks<'b>(
408 pre_args: Self::PreArgs,
409 hooks: impl Iterator<Item = &'b mut Box<dyn for<'a> FnMut(Self::Args<'a>) + Send>>,
410 ) {
411 for hook in hooks {
412 hook(pre_args)
413 }
414 }
415}
416
417/// [`Hookable`]: Triggers whenever a [`ColorScheme`] is set
418///
419/// Since [`Form`]s are set asynchronously, this may happen before the
420/// [`ColorScheme`] is done with its changes.
421///
422/// # Arguments
423///
424/// - The name of the [`ColorScheme`]
425///
426/// [`ColorScheme`]: crate::form::ColorScheme
427pub struct ColorSchemeSet;
428
429impl Hookable for ColorSchemeSet {
430 type Args<'b> = Self::PreArgs;
431 type PreArgs = &'static str;
432
433 fn trigger_hooks<'b>(
434 pre_args: Self::PreArgs,
435 hooks: impl Iterator<Item = &'b mut Box<dyn for<'a> FnMut(Self::Args<'a>) + Send>>,
436 ) {
437 for hook in hooks {
438 hook(pre_args)
439 }
440 }
441}
442
443/// Hook functions
444mod global {
445 use super::{Hookable, Hooks};
446
447 static HOOKS: Hooks = Hooks::new();
448
449 /// Adds a [hook]
450 ///
451 /// This hook is ungrouped, that is, it cannot be removed. If you
452 /// want a hook that is removable, see [`hooks::add_grouped`].
453 ///
454 /// [hook]: Hookable
455 /// [`hooks::add_grouped`]: add_grouped
456 pub fn add<H: Hookable>(f: impl for<'a> FnMut(H::Args<'a>) + Send + 'static) {
457 crate::thread::queue(move || HOOKS.add::<H>("", f))
458 }
459
460 /// Adds a grouped [hook]
461 ///
462 /// A grouped hook is one that, along with others on the same
463 /// group, can be removed by [`hooks::remove`]. If you do
464 /// not need/want this feature, take a look at [`hooks::add`]
465 ///
466 /// [hook]: Hookable
467 /// [`hooks::remove`]: remove
468 /// [`hooks::add`]: add
469 pub fn add_grouped<H: Hookable>(
470 group: &'static str,
471 f: impl for<'a> FnMut(H::Args<'a>) + Send + 'static,
472 ) {
473 crate::thread::queue(move || HOOKS.add::<H>(group, f))
474 }
475
476 /// Removes a [hook] group
477 ///
478 /// By removing the group, this function will remove all hooks
479 /// added via [`hooks::add_grouped`] with the same group.
480 ///
481 /// [hook]: Hookable
482 /// [`hooks::add_grouped`]: add_grouped
483 pub fn remove(group: &'static str) {
484 crate::thread::queue(move || HOOKS.remove(group));
485 }
486
487 /// Triggers a hooks for a [`Hookable`] struct
488 ///
489 /// When you trigger a hook, all hooks added via [`hooks::add`] or
490 /// [`hooks::add_grouped`] for said [`Hookable`] struct will
491 /// be called.
492 ///
493 /// [hook]: Hookable
494 /// [`hooks::add`]: add
495 /// [`hooks::add_grouped`]: add_grouped
496 pub fn trigger<H: Hookable>(args: H::PreArgs) {
497 crate::thread::queue(move || HOOKS.trigger::<H>(args));
498 }
499
500 pub(crate) fn trigger_now<H: Hookable>(args: H::PreArgs) {
501 HOOKS.trigger::<H>(args)
502 }
503
504 /// Checks if a give group exists
505 ///
506 /// Returns `true` if said group was added via
507 /// [`hooks::add_grouped`], and no [`hooks::remove`]
508 /// followed these additions
509 ///
510 /// [`hooks::add_grouped`]: add_grouped
511 /// [`hooks::remove`]: remove
512 pub fn group_exists(group: &'static str) -> bool {
513 HOOKS.group_exists(group)
514 }
515}
516
517/// A hookable struct, for hooks taking [`Hookable::Args`]
518///
519/// Through this trait, Duat allows for custom hookable structs. With
520/// these structs, plugin creators can create their own custom hooks,
521/// and trigger them via [`hooks::trigger`].
522///
523/// This further empowers an end user to customize the behaviour of
524/// Duat in the configuration crate.
525///
526/// [`hooks::trigger`]: trigger
527pub trait Hookable: Sized + 'static {
528 type PreArgs: Send;
529 type Args<'a>;
530
531 fn trigger_hooks<'b>(
532 pre_args: Self::PreArgs,
533 hooks: impl Iterator<Item = &'b mut Box<dyn for<'a> FnMut(Self::Args<'a>) + Send>>,
534 );
535}
536
537/// An intermediary trait, meant for group removal
538trait HookHolder: Send + Sync {
539 /// Remove the given group from hooks of this holder
540 fn remove(&mut self, group: &str);
541}
542
543/// An intermediary struct, meant to hold the hooks of a [`Hookable`]
544struct HooksOf<H: Hookable>(Mutex<Vec<(&'static str, HookFn<H>)>>);
545
546impl<H: Hookable> HookHolder for HooksOf<H> {
547 fn remove(&mut self, group: &str) {
548 let mut hooks = None;
549 while hooks.is_none() {
550 hooks = self.0.try_lock();
551 }
552 hooks.unwrap().retain(|(g, _)| *g != group);
553 }
554}
555
556/// Where all hooks of Duat are stored
557struct Hooks {
558 types: LazyLock<RwLock<HashMap<TypeId, Box<dyn HookHolder>>>>,
559 groups: LazyLock<RwLock<Vec<&'static str>>>,
560}
561
562impl Hooks {
563 /// Returns a new instance of [`Hooks`]
564 const fn new() -> Self {
565 Hooks {
566 types: LazyLock::new(RwLock::default),
567 groups: LazyLock::new(RwLock::default),
568 }
569 }
570
571 /// Adds a hook for a [`Hookable`]
572 fn add<H: Hookable>(
573 &self,
574 group: &'static str,
575 f: impl for<'a> FnMut(H::Args<'a>) + Send + 'static,
576 ) {
577 let mut map = self.types.write();
578
579 if !group.is_empty() {
580 let mut groups = self.groups.write();
581 if !groups.contains(&group) {
582 groups.push(group)
583 }
584 }
585
586 if let Some(holder) = map.get_mut(&TypeId::of::<H>()) {
587 let hooks_of = unsafe {
588 let ptr = (&mut **holder as *mut dyn HookHolder).cast::<HooksOf<H>>();
589 ptr.as_mut().unwrap()
590 };
591
592 hooks_of.0.lock().push((group, Box::new(f)))
593 } else {
594 let hooks_of = HooksOf::<H>(Mutex::new(vec![(group, Box::new(f))]));
595
596 map.insert(TypeId::of::<H>(), Box::new(hooks_of));
597 }
598 }
599
600 /// Removes hooks with said group
601 fn remove(&'static self, group: &'static str) {
602 self.groups.write().retain(|g| *g != group);
603 let mut map = self.types.write();
604 for holder in map.iter_mut() {
605 holder.1.remove(group)
606 }
607 }
608
609 /// Triggers hooks with args of the [`Hookable`]
610 fn trigger<H: Hookable>(&self, pre_args: H::PreArgs) {
611 let map = self.types.read();
612
613 if let Some(holder) = map.get(&TypeId::of::<H>()) {
614 let hooks_of = unsafe {
615 let ptr = (&**holder as *const dyn HookHolder).cast::<HooksOf<H>>();
616 ptr.as_ref().unwrap()
617 };
618
619 let mut hooks = hooks_of.0.lock();
620 H::trigger_hooks(pre_args, hooks.iter_mut().map(|(_, f)| f));
621 } else {
622 H::trigger_hooks(pre_args, std::iter::empty())
623 }
624 }
625
626 /// Checks if a hook group exists
627 fn group_exists(&self, group: &str) -> bool {
628 self.groups.read().contains(&group)
629 }
630}
631
632pub type HookFn<H> = Box<dyn for<'a> FnMut(<H as Hookable>::Args<'a>) + Send + 'static>;