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>;