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//! - [`ModeSetTo`] lets you act on a [`Mode`] after switching.
36//! - [`SearchPerformed`] triggers after a search is performed.
37//! - [`SearchUpdated`] triggers after a search updates.
38//! - [`FileWritten`] triggers after the [`File`] is written.
39//!
40//! # A note on execution
41//!
42//! In order to prevent [deadlocks], Duat executes hooks and
43//! [commands] asynchronously in the order that they are sent. For
44//! hooks, this means that if you [`trigger`] a [`Hookable`], close
45//! actions following said triggering will probably happen before the
46//! [`trigger`] is over:
47//!
48//! ```rust
49//! # use duat_core::hooks::{self, *};
50//! struct CustomHook;
51//! impl Hookable for CustomHook {
52//!     type Args<'a> = usize;
53//!     type PreArgs = usize;
54//!
55//!     fn trigger_hooks<'b>(
56//!         pre_args: Self::PreArgs,
57//!         hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
58//!     ) {
59//!         for hook in hooks {
60//!             hook(pre_args)
61//!         }
62//!     }
63//! }
64//!
65//! let arg = 42;
66//! hooks::trigger::<CustomHook>(arg);
67//! println!("This will probably print before the trigger is over.");
68//! ```
69//!
70//! The above example ilustrates how hooks are implemented in Duat,
71//! that is, you are responsible for actually calling the functions.
72//! This is roughly how hooks should be defined:
73//!
74//! ```rust
75//! # fn args_from_pre_args(usize: &usize) -> usize { *usize }
76//! # use duat_core::hooks::{self, *};
77//! struct NewHook;
78//! impl Hookable for NewHook {
79//!     // Some type derived from Self::PreArgs
80//!     type Args<'a> = usize;
81//!     // Some Send + 'static type
82//!     type PreArgs = usize;
83//!
84//!     fn trigger_hooks<'b>(
85//!         pre_args: Self::PreArgs,
86//!         hooks: impl Iterator<Item = &'b mut HookFn<Self>>,
87//!     ) {
88//!         // Preprocessing before creating Self::Args
89//!         // ...
90//!         let args = args_from_pre_args(&pre_args);
91//!         // Preprocessing before triggering hook functions
92//!         // ...
93//!         for hook in hooks {
94//!             hook(args)
95//!         }
96//!         // Postprocessing
97//!         // ...
98//!     }
99//! }
100//! ```
101//!
102//! One example of where this ends up being very useful is in
103//! [`Widget`] related hooks, since it allows Duat to pass just [`&mut
104//! Widget`] instead of [`RwData<Widget>`] as a parameter, and it also
105//! lets Duat immediately print the widget as soon as the hooks are
106//! done altering it.
107//!
108//! [`File`]: crate::widgets::File
109//! [`LineNumbers`]: crate::widgets::LineNumbers
110//! [widget]: Widget
111//! [dyn Widget]: Widget
112//! [key]: KeyEvent
113//! [deadlocks]: https://en.wikipedia.org/wiki/Deadlock_(computer_science)
114//! [commands]: crate::cmd
115//! [`Mode`]: crate::mode::Mode
116//! [`&mut Widget`]: Widget
117use std::{
118    any::TypeId,
119    collections::HashMap,
120    marker::PhantomData,
121    sync::{Arc, LazyLock},
122};
123
124use parking_lot::{Mutex, RwLock};
125
126pub use self::global::*;
127use crate::{
128    data::RwData,
129    form::{Form, FormId},
130    mode::{Cursors, KeyEvent, Mode},
131    ui::{Area, FileBuilder, Ui, WindowBuilder},
132    widgets::Widget,
133};
134
135/// Hook functions
136mod global {
137    use super::{Hookable, Hooks};
138
139    static HOOKS: Hooks = Hooks::new();
140
141    /// Adds a [hook]
142    ///
143    /// This hook is ungrouped, that is, it cannot be removed. If you
144    /// want a hook that is removable, see [`hooks::add_grouped`].
145    ///
146    /// [hook]: Hookable
147    /// [`hooks::add_grouped`]: add_grouped
148    #[inline(never)]
149    pub fn add<H: Hookable>(f: impl for<'a> FnMut(H::Args<'a>) -> H::Return + Send + 'static) {
150        HOOKS.add::<H>("", f);
151    }
152
153    /// Adds a grouped [hook]
154    ///
155    /// A grouped hook is one that, along with others on the same
156    /// group, can be removed by [`hooks::remove`]. If you do
157    /// not need/want this feature, take a look at [`hooks::add`]
158    ///
159    /// [hook]: Hookable
160    /// [`hooks::remove`]: remove
161    /// [`hooks::add`]: add
162    #[inline(never)]
163    pub fn add_grouped<H: Hookable>(
164        group: &'static str,
165        f: impl for<'a> FnMut(H::Args<'a>) -> H::Return + Send + 'static,
166    ) {
167        HOOKS.add::<H>(group, f);
168    }
169
170    /// Removes a [hook] group
171    ///
172    /// By removing the group, this function will remove all hooks
173    /// added via [`hooks::add_grouped`] with the same group.
174    ///
175    /// [hook]: Hookable
176    /// [`hooks::add_grouped`]: add_grouped
177    pub fn remove(group: &'static str) {
178        HOOKS.remove(group);
179    }
180
181    /// Triggers a hooks for a [`Hookable`] struct
182    ///
183    /// When you trigger a hook, all hooks added via [`hooks::add`] or
184    /// [`hooks::add_grouped`] for said [`Hookable`] struct will
185    /// be called.
186    ///
187    /// [hook]: Hookable
188    /// [`hooks::add`]: add
189    /// [`hooks::add_grouped`]: add_grouped
190    #[inline(never)]
191    pub fn trigger<H: Hookable>(args: H::PreArgs) {
192        crate::thread::spawn(move || {
193            HOOKS.trigger::<H>(args);
194        });
195    }
196
197    #[inline(never)]
198    pub(crate) fn trigger_now<H: Hookable>(args: H::PreArgs) -> H::Return {
199        HOOKS.trigger_now::<H>(args)
200    }
201
202    /// Checks if a give group exists
203    ///
204    /// Returns `true` if said group was added via
205    /// [`hooks::add_grouped`], and no [`hooks::remove`]
206    /// followed these additions
207    ///
208    /// [`hooks::add_grouped`]: add_grouped
209    /// [`hooks::remove`]: remove
210    pub fn group_exists(group: &'static str) -> bool {
211        HOOKS.group_exists(group)
212    }
213}
214
215/// [`Hookable`]: Triggers when Duat opens or reloads
216///
217/// This trigger will also happen after a few other initial setups of
218/// Duat.
219///
220/// There are no arguments
221pub struct ConfigLoaded;
222
223impl Hookable for ConfigLoaded {
224    type Args<'a> = ();
225    type PreArgs = ();
226
227    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
228        for hook in hooks {
229            hook(pre_args)
230        }
231    }
232}
233
234/// [`Hookable`]: Triggers when Duat closes or has to reload
235///
236/// There are no arguments
237pub struct ConfigUnloaded;
238
239impl Hookable for ConfigUnloaded {
240    type Args<'a> = ();
241    type PreArgs = ();
242
243    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
244        for hook in hooks {
245            hook(pre_args)
246        }
247    }
248}
249
250/// [`Hookable`]: Triggers when Duat closes
251///
252/// There are no arguments
253pub struct ExitedDuat;
254
255impl Hookable for ExitedDuat {
256    type Args<'a> = ();
257    type PreArgs = ();
258
259    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
260        for hook in hooks {
261            hook(pre_args)
262        }
263    }
264}
265
266/// [`Hookable`]: Triggers when a [`File`] is opened
267///
268/// # Arguments
269///
270/// - The file [builder], which can be used to push widgets to the
271///   file, and to eachother.
272///
273/// [`File`]: crate::widgets::File
274/// [builder]: crate::ui::FileBuilder
275pub struct OnFileOpen<U: Ui>(PhantomData<U>);
276
277impl<U: Ui> Hookable for OnFileOpen<U> {
278    type Args<'a> = &'a mut FileBuilder<U>;
279    type PreArgs = FileBuilder<U>;
280
281    fn trigger<'b>(_: Self::PreArgs, _: impl Iterator<Item = Hook<'b, Self>>) {
282        unreachable!("This hook is not meant to be triggered asynchronously")
283    }
284
285    fn trigger_now<'b>(mut pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
286        for hook in hooks {
287            hook(&mut pre_args)
288        }
289    }
290}
291
292/// [`Hookable`]: Triggers when a new window is opened
293///
294/// # Arguments
295///
296/// - The window [builder], which can be used to push widgets to the
297///   edges of the window, surrounding the inner file region.
298///
299/// [builder]: crate::ui::WindowBuilder
300pub struct OnWindowOpen<U: Ui>(PhantomData<U>);
301
302impl<U: Ui> Hookable for OnWindowOpen<U> {
303    type Args<'a> = &'a mut WindowBuilder<U>;
304    type PreArgs = WindowBuilder<U>;
305
306    fn trigger<'b>(_: Self::PreArgs, _: impl Iterator<Item = Hook<'b, Self>>) {
307        unreachable!("This hook is not meant to be triggered asynchronously")
308    }
309
310    fn trigger_now<'b>(mut pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
311        for hook in hooks {
312            hook(&mut pre_args)
313        }
314    }
315}
316
317/// [`Hookable`]: Triggers when the [`Widget`] is focused
318///
319/// # Arguments
320///
321/// - The widget itself.
322/// - Its [area].
323///
324/// [`Widget`]: crate::widgets::Widget
325/// [area]: crate::ui::Area
326pub struct FocusedOn<W: Widget<U>, U: Ui>(PhantomData<(W, U)>);
327
328impl<W: Widget<U>, U: Ui> Hookable for FocusedOn<W, U> {
329    type Args<'a> = (&'a mut W, &'a U::Area);
330    type PreArgs = (RwData<W>, U::Area);
331
332    fn trigger<'b>((widget, area): Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
333        let mut widget = widget.write();
334        let cfg = widget.print_cfg();
335        widget.text_mut().remove_cursors(&area, cfg);
336        widget.on_focus(&area);
337
338        for hook in hooks {
339            hook((&mut *widget, &area));
340        }
341
342        widget.text_mut().add_cursors(&area, cfg);
343        if let Some(main) = widget.cursors().and_then(Cursors::get_main) {
344            area.scroll_around_point(widget.text(), main.caret(), widget.print_cfg());
345        }
346
347        widget.update(&area);
348        widget.print(&area);
349    }
350}
351
352/// [`Hookable`]: Triggers when the [`Widget`] is unfocused
353///
354/// # Arguments
355///
356/// - The widget itself.
357/// - Its [area].
358///
359/// [`Widget`]: crate::widgets::Widget
360/// [area]: crate::ui::Area
361pub struct UnfocusedFrom<W: Widget<U>, U: Ui>(PhantomData<(W, U)>);
362
363impl<W: Widget<U>, U: Ui> Hookable for UnfocusedFrom<W, U> {
364    type Args<'a> = (&'a mut W, &'a U::Area);
365    type PreArgs = (RwData<W>, U::Area);
366
367    fn trigger<'b>((widget, area): Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
368        let mut widget = widget.write();
369        let cfg = widget.print_cfg();
370        widget.text_mut().remove_cursors(&area, cfg);
371        widget.on_unfocus(&area);
372
373        for hook in hooks {
374            hook((&mut *widget, &area));
375        }
376
377        widget.text_mut().add_cursors(&area, cfg);
378        if let Some(main) = widget.cursors().and_then(Cursors::get_main) {
379            area.scroll_around_point(widget.text(), main.caret(), widget.print_cfg());
380        }
381
382        widget.update(&area);
383        widget.print(&area);
384    }
385}
386
387/// [`Hookable`]: Triggers when the [`Mode`] is changed
388///
389/// # Arguments
390///
391/// - The previous mode.
392/// - The current mode.
393///
394/// # Note
395///
396/// You should try to avoid more than one [`Mode`] with the same name.
397/// This can happen if you're using two structs with the same name,
398/// but from different crates.
399///
400/// [`Mode`]: crate::mode::Mode
401pub struct ModeSwitched;
402
403impl Hookable for ModeSwitched {
404    type Args<'a> = (&'a str, &'a str);
405    type PreArgs = (&'static str, &'static str);
406
407    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
408        for hook in hooks {
409            hook(pre_args)
410        }
411    }
412}
413
414/// [`Hookable`]: Lets you modify a [`Mode`] as it is set
415///
416/// # Arguments
417///
418/// - The new mode.
419/// - Its widget.
420///
421/// This hook is very useful if you want to, for example, set
422/// different options upon switching to modes, depending on things
423/// like the language of a [`File`].
424///
425/// [`File`]: crate::widgets::File
426pub struct ModeSetTo<M: Mode<U>, U: Ui>(PhantomData<(M, U)>);
427
428impl<M: Mode<U>, U: Ui> Hookable for ModeSetTo<M, U> {
429    type Args<'a> = (M, &'a M::Widget, &'a U::Area);
430    type PreArgs = (M, RwData<M::Widget>, U::Area);
431    type Return = M;
432
433    fn trigger<'b>(_: Self::PreArgs, _: impl Iterator<Item = Hook<'b, Self>>) {
434        unreachable!("This hook is not meant to be triggered asynchronously")
435    }
436
437    fn trigger_now<'b>(
438        (mut mode, widget, area): Self::PreArgs,
439        hooks: impl Iterator<Item = Hook<'b, Self>>,
440    ) -> Self::Return {
441        let widget = widget.read();
442
443        for hook in hooks {
444            mode = hook((mode, &widget, &area));
445        }
446
447        mode
448    }
449}
450
451/// [`Hookable`]: Triggers whenever a [key] is sent
452///
453/// # Arguments
454///
455/// - The [key] sent.
456///
457/// [key]: KeyEvent
458pub struct KeySent;
459
460impl Hookable for KeySent {
461    type Args<'a> = KeyEvent;
462    type PreArgs = KeyEvent;
463
464    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
465        for hook in hooks {
466            hook(pre_args);
467        }
468    }
469}
470
471/// [`Hookable`]: Triggers whenever a [key] is sent to the [`Widget`]
472///
473/// # Arguments
474///
475/// - The [key] sent.
476/// - An [`RwData<W>`] for the widget.
477///
478/// [key]: KeyEvent
479pub struct KeySentTo<W: Widget<U>, U: Ui>(PhantomData<(&'static W, U)>);
480
481impl<W: Widget<U>, U: Ui> Hookable for KeySentTo<W, U> {
482    type Args<'b> = (KeyEvent, &'b mut W, &'b U::Area);
483    type PreArgs = (KeyEvent, RwData<W>, U::Area);
484
485    fn trigger<'b>(_: Self::PreArgs, _: impl Iterator<Item = Hook<'b, Self>>) {
486        unreachable!("This hook is not meant to be triggered asynchronously")
487    }
488
489    fn trigger_now<'b>(
490        (key, widget, area): Self::PreArgs,
491        hooks: impl Iterator<Item = Hook<'b, Self>>,
492    ) {
493        let mut widget = widget.write();
494        for hook in hooks {
495            hook((key, &mut *widget, &area));
496        }
497    }
498}
499
500/// [`Hookable`]: Triggers whenever a [`Form`] is set
501///
502/// This can be a creation or alteration of a [`Form`].
503/// If the [`Form`] is a reference to another, the reference's
504/// [`Form`] will be returned instead.
505///
506/// # Arguments
507///
508/// - The [`Form`]'s name.
509/// - Its [`FormId`].
510/// - Its new value.
511pub struct FormSet;
512
513impl Hookable for FormSet {
514    type Args<'b> = Self::PreArgs;
515    type PreArgs = (&'static str, FormId, Form);
516
517    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
518        for hook in hooks {
519            hook(pre_args)
520        }
521    }
522}
523
524/// [`Hookable`]: Triggers when a [`ColorScheme`] is set
525///
526/// Since [`Form`]s are set asynchronously, this may happen before the
527/// [`ColorScheme`] is done with its changes.
528///
529/// # Arguments
530///
531/// - The name of the [`ColorScheme`]
532///
533/// [`ColorScheme`]: crate::form::ColorScheme
534pub struct ColorSchemeSet;
535
536impl Hookable for ColorSchemeSet {
537    type Args<'b> = Self::PreArgs;
538    type PreArgs = &'static str;
539
540    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
541        for hook in hooks {
542            hook(pre_args)
543        }
544    }
545}
546
547/// [`Hookable`]: Triggers when a [search] is performed
548///
549/// Will not be triggered on empty searches.
550///
551/// # Arguments
552///
553/// - The searched regex pattern
554///
555/// [search]: crate::mode::IncSearch
556pub struct SearchPerformed;
557
558impl Hookable for SearchPerformed {
559    type Args<'a> = &'a str;
560    type PreArgs = String;
561    type Return = ();
562
563    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
564        for hook in hooks {
565            hook(&pre_args)
566        }
567    }
568}
569
570/// [`Hookable`]: Triggers when a [search] is updated
571///
572/// Will not be triggered if the previous and current patterns are the
573/// same.
574///
575/// # Arguments
576///
577/// - The previous regex pattern
578/// - The current regex pattern
579///
580/// [search]: crate::mode::IncSearch
581pub struct SearchUpdated;
582
583impl Hookable for SearchUpdated {
584    type Args<'a> = (&'a str, &'a str);
585    type PreArgs = (String, String);
586    type Return = ();
587
588    fn trigger<'b>((prev, cur): Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
589        for hook in hooks {
590            hook((&prev, &cur))
591        }
592    }
593}
594
595/// [`Hookable`]: Triggers after [`File::write`] or [`File::write_to`]
596///
597/// Only triggers if the file was actually updated.
598///
599/// # Arguments
600///
601/// - The path of the file
602/// - The number of bytes written to said file
603///
604/// [`File::write`]: crate::widgets::File::write
605/// [`File::write_to`]: crate::widgets::File::write_to
606pub struct FileWritten;
607
608impl Hookable for FileWritten {
609    type Args<'a> = (&'a str, usize);
610    type PreArgs = (String, usize);
611    type Return = ();
612
613    fn trigger<'b>((file, bytes): Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>) {
614        for hook in hooks {
615            hook((&file, bytes));
616        }
617    }
618}
619
620/// A hookable struct, for hooks taking [`Hookable::Args`]
621///
622/// Through this trait, Duat allows for custom hookable structs. With
623/// these structs, plugin creators can create their own custom hooks,
624/// and trigger them via [`hooks::trigger`].
625///
626/// This further empowers an end user to customize the behaviour of
627/// Duat in the configuration crate.
628///
629/// [`hooks::trigger`]: trigger
630pub trait Hookable: Sized + 'static {
631    type Args<'a>;
632    type PreArgs: Send;
633    /// This type is useful if, for example, you want to pass a value
634    /// by moving it to Args, while returning and reusing said value.
635    type Return = ();
636
637    fn trigger<'b>(pre_args: Self::PreArgs, hooks: impl Iterator<Item = Hook<'b, Self>>);
638
639    /// This function is only for internal use, it will not be used if
640    /// you implement it.
641    #[allow(unused_variables)]
642    #[doc(hidden)]
643    fn trigger_now<'b>(
644        pre_args: Self::PreArgs,
645        hooks: impl Iterator<Item = Hook<'b, Self>>,
646    ) -> Self::Return {
647        unreachable!("This Hookable is not meant to be called immediately");
648    }
649}
650
651/// Where all hooks of Duat are stored
652struct Hooks {
653    types: LazyLock<RwLock<HashMap<TypeId, Arc<dyn HookHolder>>>>,
654    groups: LazyLock<RwLock<Vec<&'static str>>>,
655}
656
657impl Hooks {
658    /// Returns a new instance of [`Hooks`]
659    const fn new() -> Self {
660        Hooks {
661            types: LazyLock::new(RwLock::default),
662            groups: LazyLock::new(RwLock::default),
663        }
664    }
665
666    /// Adds a hook for a [`Hookable`]
667    fn add<H: Hookable>(
668        &self,
669        group: &'static str,
670        f: impl for<'a> FnMut(H::Args<'a>) -> H::Return + Send + 'static,
671    ) {
672        let mut map = self.types.write();
673
674        if !group.is_empty() {
675            let mut groups = self.groups.write();
676            if !groups.contains(&group) {
677                groups.push(group)
678            }
679        }
680
681        if let Some(holder) = map.get(&TypeId::of::<H>()) {
682            let hooks_of = unsafe {
683                let ptr = (&**holder as *const dyn HookHolder).cast::<HooksOf<H>>();
684                ptr.as_ref().unwrap()
685            };
686
687            let mut hooks = hooks_of.0.lock();
688            hooks.push((group, Box::leak(Box::new(Mutex::new(f)))));
689        } else {
690            let hooks_of = HooksOf::<H>(Mutex::new(vec![(
691                group,
692                Box::leak(Box::new(Mutex::new(f))),
693            )]));
694
695            map.insert(TypeId::of::<H>(), Arc::new(hooks_of));
696        }
697    }
698
699    /// Removes hooks with said group
700    fn remove(&'static self, group: &'static str) {
701        self.groups.write().retain(|g| *g != group);
702        let map = self.types.read();
703        for holder in map.iter() {
704            holder.1.remove(group)
705        }
706    }
707
708    /// Triggers hooks with args of the [`Hookable`]
709    fn trigger<H: Hookable>(&self, pre_args: H::PreArgs) {
710        let map = self.types.read();
711
712        if let Some(holder) = map.get(&TypeId::of::<H>()) {
713            let holder = holder.clone();
714            drop(map);
715            // SAFETY: HooksOf<H> is the only type that this HookHolder could be.
716            let hooks_of = unsafe {
717                let ptr = (&*holder as *const dyn HookHolder).cast::<HooksOf<H>>();
718                ptr.as_ref().unwrap()
719            };
720
721            let mut hooks = hooks_of.0.lock().clone();
722            H::trigger(pre_args, hooks.iter_mut().map(|(_, f)| Hook(f)));
723        } else {
724            drop(map);
725            H::trigger(pre_args, std::iter::empty());
726        }
727    }
728
729    /// Triggers hooks, returning [`H::Return`]
730    ///
731    /// [`H::Return`]: Hookable::Return
732    fn trigger_now<H: Hookable>(&self, pre_args: H::PreArgs) -> H::Return {
733        let map = self.types.read();
734
735        if let Some(holder) = map.get(&TypeId::of::<H>()) {
736            let holder = holder.clone();
737            drop(map);
738            // SAFETY: HooksOf<H> is the only type that this HookHolder could be.
739            let hooks_of = unsafe {
740                let ptr = (&*holder as *const dyn HookHolder).cast::<HooksOf<H>>();
741                ptr.as_ref().unwrap()
742            };
743
744            let mut hooks = hooks_of.0.lock();
745            H::trigger_now(pre_args, hooks.iter_mut().map(|(_, f)| Hook(f)))
746        } else {
747            drop(map);
748            H::trigger_now(pre_args, std::iter::empty())
749        }
750    }
751
752    /// Checks if a hook group exists
753    fn group_exists(&self, group: &str) -> bool {
754        self.groups.read().contains(&group)
755    }
756}
757
758/// An intermediary trait, meant for group removal
759trait HookHolder: Send + Sync {
760    /// Remove the given group from hooks of this holder
761    fn remove(&self, group: &str);
762}
763
764/// An intermediary struct, meant to hold the hooks of a [`Hookable`]
765struct HooksOf<H: Hookable>(Mutex<Vec<(&'static str, InnerHookFn<H>)>>);
766
767impl<H: Hookable> HookHolder for HooksOf<H> {
768    fn remove(&self, group: &str) {
769        let mut hooks = self.0.lock();
770        hooks.retain(|(g, _)| *g != group);
771    }
772}
773
774/// A function to be called when a [`Hookable`] is triggered
775///
776/// This function implements [`FnOnce`], as it is only meant to be
777/// called once per [`trigger`] call.
778struct Hook<'b, H: Hookable>(&'b mut InnerHookFn<H>);
779
780impl<'b, H: Hookable> std::ops::FnOnce<(H::Args<'_>,)> for Hook<'b, H> {
781    type Output = H::Return;
782
783    extern "rust-call" fn call_once(self, (args,): (H::Args<'_>,)) -> Self::Output {
784        (self.0.lock())(args)
785    }
786}
787
788pub type InnerHookFn<H> = &'static Mutex<
789    (dyn for<'a> FnMut(<H as Hookable>::Args<'a>) -> <H as Hookable>::Return + Send + 'static),
790>;