duat_core/
hook.rs

1//! Utilities for hooks in Duat
2//!
3//! In Duat, hooks are handled through the [`Hookable`] trait. This
4//! trait contains the [`Hookable::Input`] associated type, which is
5//! what should be passed to hooks on the specific [`Hookable`]. By
6//! implementing this trait, you allow an end user to hook executions
7//! whenever said [`Hookable`] is triggered:
8//!
9//! ```rust
10//! # use duat_core::doc_duat as duat;
11//! setup_duat!(setup);
12//! use duat::prelude::*;
13//!
14//! fn setup() {
15//!     hook::add::<File>(|pa: &mut Pass, (cfg, builder)| {
16//!         // `LineNumbers` comes from duat-utils
17//!         builder.push(LineNumbers::cfg());
18//!
19//!         if let Some("lisp") = cfg.filetype() {
20//!             cfg.dont_wrap()
21//!         } else {
22//!             cfg
23//!         }
24//!     });
25//! }
26//! ```
27//!
28//! The hook above is triggered whenever a [`File`] widget is opened.
29//! Like every other hook, it gives you access to the global state via
30//! the [`Pass`], and this one also gets you a [`UiBuilder`] and a
31//! [`Widget::Cfg`] argument. The [`UiBuilder`] lets you push new
32//! [`Widget`]s around the [`File`], while the [`Widget::Cfg`]
33//! argument lets you modify a [`Widget`] before it gets added in. You
34//! can call this hook with any [`Widget`], not just the [`File`].
35//!
36//! This is just one of many built-in [`Hookable`]s. Currently, these
37//! are the existing hooks in `duat-core`, but you can also make your
38//! own:
39//!
40//! - [`ConfigLoaded`] triggers after loading the config crate.
41//! - [`ConfigUnloaded`] triggers after unloading the config crate.
42//! - [`ExitedDuat`] triggers after Duat has exited.
43//! - [`FocusedOnDuat`] triggers when Duat gains focus.
44//! - [`UnfocusedFromDuat`] triggers when Duat loses focus.
45//! - [`WidgetCreated`] triggers when a [`Widget`]'s [cfg] is created,
46//!   letting you change it, [`Widget`] can be used as its [alias]
47//! - [`WindowCreated`], which lets you push widgets around the
48//!   window.
49//! - [`OnFileClose`] triggers on every file upon closing Duat.
50//! - [`OnFileReload`] triggers on every file upon reloading Duat.
51//! - [`FocusedOn`] lets you act on a [widget] when focused.
52//! - [`UnfocusedFrom`] lets you act on a [widget] when unfocused.
53//! - [`KeysSent`] lets you act on a [dyn Widget], given a [key].
54//! - [`KeysSentTo`] lets you act on a given [widget], given a [key].
55//! - [`FormSet`] triggers whenever a [`Form`] is added/altered.
56//! - [`ModeSwitched`] triggers when you change [`Mode`].
57//! - [`ModeCreated`] lets you act on a [`Mode`] after switching.
58//! - [`FileWritten`] triggers after the [`File`] is written.
59//! - [`SearchPerformed`] (from `duat-utils`) triggers after a search
60//!   is performed.
61//! - [`SearchUpdated`] (from `duat-utils`) triggers after a search
62//!   updates.
63//!
64//! # Basic makeout
65//!
66//! When a hook is added, it can take arguments
67//!
68//!
69//! ```rust
70//! use duat_core::{hook::Hookable, prelude::*};
71//! struct CustomHook(usize);
72//! impl Hookable for CustomHook {
73//!     type Input<'h> = usize;
74//!
75//!     fn get_input(&mut self) -> Self::Input<'_> {
76//!         self.0
77//!     }
78//! }
79//!
80//! fn runtime_function_that_triggers_hook(pa: &mut Pass) {
81//!     let arg = 42;
82//!     hook::trigger(pa, CustomHook(arg));
83//! }
84//! ```
85//!
86//! The above example ilustrates how hooks are implemented in Duat.
87//! You essentially pass a struct wich will hold the arguments that
88//! will be passed as input to the hooks. The [`Hookable::Input`]
89//! argument makes it so you can have more convenient parameters for
90//! hooks, like `(usize, &'h str)`, for example.
91//!
92//! [`Hookable`]s also have the [`Output`] type, which is set to `()`
93//! by default, because it is mostly unnecessary. But it can be used
94//! to, for example, make the builder pattern work through hooks:
95//!
96//! ```rust
97//! use duat_core::{hook::Hookable, prelude::*};
98//! pub struct MyBuilder(bool, usize);
99//!
100//! impl MyBuilder {
101//!     pub fn set_true(mut self) -> Self {
102//!         self.0 = true;
103//!         self
104//!     }
105//!
106//!     pub fn set_num(mut self, num: usize) -> Self {
107//!         self.1 = num;
108//!         self
109//!     }
110//!
111//!     pub fn consume(self) {
112//!         todo!();
113//!     }
114//! }
115//!
116//! struct MyBuilderCreated(Option<MyBuilder>);
117//! impl Hookable for MyBuilderCreated {
118//!     type Input<'h> = MyBuilder;
119//!     type Output = MyBuilder;
120//!
121//!     fn get_input(&mut self) -> Self::Input<'_> {
122//!         self.0.take().unwrap()
123//!     }
124//!
125//!     fn take_output_back(&mut self, output: Self::Output) {
126//!         self.0 = Some(output)
127//!     }
128//! }
129//!
130//! fn runtime_function_that_triggers_hook(pa: &mut Pass) {
131//!     let builder = MyBuilder(false, 0);
132//!
133//!     let mut hookable = hook::trigger(pa, MyBuilderCreated(Some(builder)));
134//!
135//!     let builder = hookable.0.take().unwrap();
136//!     builder.consume();
137//! }
138//! ```
139//!
140//! This is, for example, the pattern that [`ModeCreated`] follows.
141//!
142//! [alias]: HookAlias
143//! [cfg]: crate::ui::Widget::Cfg
144//! [`LineNumbers`]: https://docs.rs/duat-utils/latest/duat_utils/widgets/struct.LineNumbers.html
145//! [widget]: Widget
146//! [dyn Widget]: Widget
147//! [key]: KeyEvent
148//! [deadlocks]: https://en.wikipedia.org/wiki/Deadlock_(computer_science)
149//! [commands]: crate::cmd
150//! [`Mode`]: crate::mode::Mode
151//! [`&mut Widget`]: Widget
152//! [`Output`]: Hookable::Output
153//! [`SearchPerformed`]: https://docs.rs/duat-utils/latest/duat_utils/hooks/struct.SearchPerformed.html
154//! [`SearchUpdated`]: https://docs.rs/duat-utils/latest/duat_utils/hooks/struct.SearchUpdated.html
155use std::{any::TypeId, cell::RefCell, collections::HashMap, marker::PhantomData, sync::Mutex};
156
157pub use self::global::*;
158use crate::{
159    context::{Cache, Handle},
160    data::Pass,
161    file::File,
162    form::{Form, FormId},
163    mode::{KeyEvent, Mode},
164    ui::{Ui, UiBuilder, Widget},
165};
166
167/// Hook functions
168mod global {
169    use std::sync::LazyLock;
170
171    use super::{HookAlias, Hookable, InnerHooks};
172    use crate::{
173        data::Pass,
174        hook::HookDummy,
175        ui::{DuatEvent, Ui},
176    };
177
178    static HOOKS: LazyLock<InnerHooks> = LazyLock::new(InnerHooks::default);
179
180    /// Adds a [hook]
181    ///
182    /// This hook is ungrouped, that is, it cannot be removed. If you
183    /// want a hook that is removable, see [`hook::add_grouped`]. If
184    /// you don't have access to a [`Ui`] argument for some reason,
185    /// see [`hook::add_no_alias`].
186    ///
187    /// [hook]: Hookable
188    /// [`hook::add_grouped`]: add_grouped
189    /// [`hook::add_no_alias`]: add_no_alias
190    #[inline(never)]
191    pub fn add<H: HookAlias<U, impl HookDummy>, U: Ui>(
192        f: impl FnMut(&mut Pass, H::Input<'_>) -> H::Output + Send + 'static,
193    ) {
194        HOOKS.add::<H::Hookable>("", Box::new(f));
195    }
196
197    /// Adds a [hook], without accepting aliases
198    ///
199    /// Use this if you want to add a hook, but have no access to
200    /// [`Ui`] parameter.
201    ///
202    /// [hook]: Hookable
203    #[doc(hidden)]
204    pub fn add_no_alias<H: Hookable>(
205        f: impl FnMut(&mut Pass, H::Input<'_>) -> H::Output + Send + 'static,
206    ) {
207        HOOKS.add::<H>("", Box::new(f));
208    }
209
210    /// Adds a grouped [hook]
211    ///
212    /// A grouped hook is one that, along with others on the same
213    /// group, can be removed by [`hook::remove`]. If you do
214    /// not need/want this feature, take a look at [`hook::add`]. If
215    /// you don't have access to a [`Ui`] argument for some reason,
216    /// see [`hook::add_grouped_no_alias`].
217    ///
218    /// [hook]: Hookable
219    /// [`hook::remove`]: remove
220    /// [`hook::add`]: add
221    /// [`hook::add_grouped_no_alias`]: add_grouped_no_alias
222    #[inline(never)]
223    pub fn add_grouped<H: HookAlias<U, impl HookDummy>, U: Ui>(
224        group: &'static str,
225        f: impl FnMut(&mut Pass, H::Input<'_>) -> H::Output + Send + 'static,
226    ) {
227        HOOKS.add::<H::Hookable>(group, Box::new(f));
228    }
229
230    /// Adds a grouped [hook], without accepting aliases
231    ///
232    /// Use this if you want to add a hook, but have no access to
233    /// [`Ui`] parameter.
234    ///
235    /// [hook]: Hookable
236    #[doc(hidden)]
237    pub fn add_grouped_no_alias<H: Hookable>(
238        f: impl FnMut(&mut Pass, H::Input<'_>) -> H::Output + Send + 'static,
239    ) {
240        HOOKS.add::<H>("", Box::new(f));
241    }
242
243    /// Removes a [hook] group
244    ///
245    /// By removing the group, this function will remove all hooks
246    /// added via [`hook::add_grouped`] with the same group.
247    ///
248    /// [hook]: Hookable
249    /// [`hook::add_grouped`]: add_grouped
250    pub fn remove(group: &'static str) {
251        HOOKS.remove(group);
252    }
253
254    /// Triggers a hooks for a [`Hookable`] struct
255    ///
256    /// When you trigger a hook, all hooks added via [`hook::add`] or
257    /// [`hook::add_grouped`] for said [`Hookable`] struct will
258    /// be called.
259    ///
260    /// [hook]: Hookable
261    /// [`hook::add`]: add
262    /// [`hook::add_grouped`]: add_grouped
263    pub fn trigger<H: Hookable>(pa: &mut Pass, hookable: H) -> H {
264        HOOKS.trigger(pa, hookable)
265    }
266
267    /// Queues a [`Hookable`]'s execution
268    ///
269    /// You should use this if you are not on the main thread of
270    /// execution, and are thus unable to call [`trigger`].
271    /// The notable difference between this function and [`trigger`]
272    /// is that it doesn't return the [`Hookable`], since the
273    /// triggering of the hooks will happen outside of the calling
274    /// function.
275    ///
276    /// Most of the time, this doesn't really matter, as in only a few
277    /// cases do you actually need to recover the [`Hookable`], so you
278    /// should be able to call this from pretty much anywhere.
279    pub fn queue(hookable: impl Hookable + Send) {
280        let sender = crate::context::sender();
281        sender
282            .send(DuatEvent::QueuedFunction(Box::new(move |pa| {
283                trigger(pa, hookable);
284            })))
285            .unwrap();
286    }
287
288    /// Checks if a give group exists
289    ///
290    /// Returns `true` if said group was added via
291    /// [`hook::add_grouped`], and no [`hook::remove`]
292    /// followed these additions
293    ///
294    /// [`hook::add_grouped`]: add_grouped
295    /// [`hook::remove`]: remove
296    pub fn group_exists(group: &'static str) -> bool {
297        HOOKS.group_exists(group)
298    }
299}
300
301/// [`Hookable`]: Triggers when Duat opens or reloads
302///
303/// This trigger will also happen after a few other initial setups of
304/// Duat.
305///
306/// There are no arguments
307pub struct ConfigLoaded(pub(crate) ());
308
309impl Hookable for ConfigLoaded {
310    type Input<'h> = ();
311
312    fn get_input(&mut self) -> Self::Input<'_> {}
313}
314
315/// [`Hookable`]: Triggers when Duat closes or has to reload
316///
317/// There are no arguments
318pub struct ConfigUnloaded(pub(crate) ());
319
320impl Hookable for ConfigUnloaded {
321    type Input<'h> = ();
322
323    fn get_input(&mut self) -> Self::Input<'_> {}
324}
325
326/// [`Hookable`]: Triggers when Duat closes
327///
328/// There are no arguments
329pub struct ExitedDuat(pub(crate) ());
330
331impl Hookable for ExitedDuat {
332    type Input<'h> = ();
333
334    fn get_input(&mut self) -> Self::Input<'_> {}
335}
336
337/// [`Hookable`]: Triggers when Duat is refocused
338///
339/// # Arguments
340///
341/// There are no arguments
342pub struct FocusedOnDuat(pub(crate) ());
343
344impl Hookable for FocusedOnDuat {
345    type Input<'h> = ();
346
347    fn get_input(&mut self) -> Self::Input<'_> {}
348}
349
350/// [`Hookable`]: Triggers when Duat is unfocused
351///
352/// # Arguments
353///
354/// There are no arguments
355pub struct UnfocusedFromDuat(pub(crate) ());
356
357impl Hookable for UnfocusedFromDuat {
358    type Input<'h> = ();
359
360    fn get_input(&mut self) -> Self::Input<'_> {}
361}
362
363/// [`Hookable`]: Triggers when a [`Widget`]'s [cfg] is created
364///
365/// # Arguments
366///
367/// - The [`WidgetCfg`] in question.
368/// - A [`UiBuilder`], which lets you push [`Widget`]s around this
369///   one.
370///
371/// # Aliases
372///
373/// Since every [`Widget`] implements the [`HookAlias`] trait, instead
374/// of writing this in the config crate:
375///
376/// ```rust
377/// # use duat_core::doc_duat as duat;
378/// setup_duat!(setup);
379/// use duat::prelude::*;
380///
381/// fn setup() {
382///     hook::add::<WidgetCreated<LineNumbers<Ui>>>(|pa, (ln, _)| ln.rel_abs());
383/// }
384/// ```
385///
386/// You can just write this:
387///
388/// ```rust
389/// # use duat_core::doc_duat as duat;
390/// setup_duat!(setup);
391/// use duat::prelude::*;
392///
393/// fn setup() {
394///     hook::add::<LineNumbers<Ui>>(|_, (ln, _)| ln.rel_abs());
395/// }
396/// ```
397///
398/// # Changing the layout
399///
400/// Assuming you are using `duat-term`, you could make it so every
401/// [`LineNumbers`] comes with a [`VertRule`] on the right, like this:
402///
403/// ```rust
404/// # use duat_core::prelude::{BuildInfo, PushSpecs};
405/// # pub struct VertRule(Text);
406/// # impl Widget<Ui> for VertRule {
407/// #     type Cfg = VertRuleCfg;
408/// #     fn update(_: &mut Pass, _: &Handle<Self>) {}
409/// #     fn needs_update(&self, _: &Pass) -> bool { false }
410/// #     fn cfg() -> Self::Cfg { VertRuleCfg }
411/// #     fn text(&self) -> &Text { &self.0 }
412/// #     fn text_mut(&mut self) -> &mut Text { &mut self.0 }
413/// #     fn once() -> Result<(), Text> { Ok(()) }
414/// # }
415/// # pub struct VertRuleCfg;
416/// # impl VertRuleCfg {
417/// #     pub fn on_the_right(self) -> Self { self }
418/// # }
419/// # impl WidgetCfg<Ui> for VertRuleCfg {
420/// #     type Widget = VertRule;
421/// #     fn build(self, _: &mut Pass, _: BuildInfo<Ui>) -> (Self::Widget, PushSpecs) {
422/// #         (VertRule(Text::new()), PushSpecs::left())
423/// #     }
424/// # }
425/// # use duat_core::doc_duat as duat;
426/// setup_duat!(setup);
427/// use duat::prelude::*;
428///
429/// fn setup() {
430///     hook::add::<LineNumbers<Ui>>(|_, (ln, builder)| {
431///         builder.push(VertRule::cfg().on_the_right());
432///         ln
433///     });
434/// }
435/// ```
436///
437/// Now, every time a [`LineNumbers`]s [`Widget`] is inserted in Duat,
438/// a [`VertRule`] will be pushed on the right of it. You could even
439/// further add a [hook] on [`VertRule`], that would push further
440/// [`Widget`]s if you wanted to.
441///
442/// [cfg]: crate::ui::Widget::Cfg
443/// [`WidgetCfg`]: crate::ui::WidgetCfg
444/// [hook]: self
445/// [direction]: crate::ui::PushSpecs
446/// [`LineNumbers`]: https://docs.rs/duat_utils/latest/duat-utils/widgets/struct.LineNumbers.html
447/// [`VertRule`]: https://docs.rs/duat_term/latest/duat-term/struct.VertRule.html
448pub struct WidgetCreated<W: Widget<U>, U: Ui>(pub(crate) (Option<W::Cfg>, UiBuilder<U>));
449
450impl<W: Widget<U>, U: Ui> Hookable for WidgetCreated<W, U> {
451    type Input<'h> = (W::Cfg, &'h mut UiBuilder<U>);
452    type Output = W::Cfg;
453
454    fn get_input(&mut self) -> Self::Input<'_> {
455        (self.0.0.take().unwrap(), &mut self.0.1)
456    }
457
458    fn take_output_back(&mut self, output: Self::Output) {
459        self.0.0 = Some(output)
460    }
461}
462
463/// [`Hookable`]: Triggers when a new window is opened
464///
465/// # Arguments
466///
467/// - The window [builder], which can be used to push widgets to the
468///   edges of the window, surrounding the inner file region.
469///
470/// This is a rather "advanced" [hook], since it lets you change the
471/// layout of [`Widget`]s around the screen. If you don't need all
472/// that power, you can check out [`WidgetCreated`], which is a more
473/// straightforward form of changing [`Widget`]s, and doesn't
474/// interfere with the default hooks of `"FileWidgets"` and
475/// `"WindowWidgets"`, preset by Duat.
476///
477/// [builder]: crate::ui::UiBuilder
478/// [hook]: self
479pub struct WindowCreated<U: Ui>(pub(crate) UiBuilder<U>);
480
481impl<U: Ui> Hookable for WindowCreated<U> {
482    type Input<'h> = &'h mut UiBuilder<U>;
483
484    fn get_input(&mut self) -> Self::Input<'_> {
485        &mut self.0
486    }
487}
488
489/// [`Hookable`]: Triggers before closing a [`File`]
490///
491/// # Arguments
492///
493/// - The [`File`]'s [`Handle`].
494/// - A [`Cache`]. This can be used in order to decide wether or not
495///   some things will be reloaded on the next opening of Duat.
496///
497/// This will not trigger upon reloading Duat. For that, see
498/// [`OnFileClose`].
499pub struct OnFileClose<U: Ui>(pub(crate) (Handle<File<U>, U>, Cache));
500
501impl<U: Ui> Hookable for OnFileClose<U> {
502    type Input<'h> = &'h (Handle<File<U>, U>, Cache);
503
504    fn get_input(&mut self) -> Self::Input<'_> {
505        &self.0
506    }
507}
508
509/// [`Hookable`]: Triggers before reloading a [`File`]
510///
511/// # Arguments
512///
513/// - The [`File`]'s [`Handle`].
514/// - A [`Cache`]. This can be used in order to decide wether or not
515///   some things will be reloaded on the next opening of Duat.
516///
517/// This will not trigger upon closing Duat. For that, see
518/// [`OnFileClose`].
519pub struct OnFileReload<U: Ui>(pub(crate) (Handle<File<U>, U>, Cache));
520
521impl<U: Ui> Hookable for OnFileReload<U> {
522    type Input<'h> = &'h (Handle<File<U>, U>, Cache);
523
524    fn get_input(&mut self) -> Self::Input<'_> {
525        &self.0
526    }
527}
528
529/// [`Hookable`]: Triggers when the [`Widget`] is focused
530///
531/// # Arguments
532///
533/// - A [`Handle<dyn Widget>`] for newly focused [`Widget`]
534/// - A [`Handle<W>`] for the unfocused [`Widget`]
535pub struct FocusedOn<W: Widget<U>, U: Ui>(pub(crate) (Handle<dyn Widget<U>, U>, Handle<W, U>));
536
537impl<W: Widget<U>, U: Ui> Hookable for FocusedOn<W, U> {
538    type Input<'h> = &'h (Handle<dyn Widget<U>, U>, Handle<W, U>);
539
540    fn get_input(&mut self) -> Self::Input<'_> {
541        &self.0
542    }
543}
544
545/// [`Hookable`]: Triggers when the [`Widget`] is unfocused
546///
547/// # Arguments
548///
549/// - A [`Handle<W>`] for newly focused [`Widget`]
550/// - A [`Handle<dyn Widget>`] for the unfocused [`Widget`]
551pub struct UnfocusedFrom<W: Widget<U>, U: Ui>(pub(crate) (Handle<W, U>, Handle<dyn Widget<U>, U>));
552
553impl<W: Widget<U>, U: Ui> Hookable for UnfocusedFrom<W, U> {
554    type Input<'h> = &'h (Handle<W, U>, Handle<dyn Widget<U>, U>);
555
556    fn get_input(&mut self) -> Self::Input<'_> {
557        &self.0
558    }
559}
560
561/// [`Hookable`]: Triggers when the [`Mode`] is changed
562///
563/// # Arguments
564///
565/// - The previous mode.
566/// - The current mode.
567///
568/// # Aliases
569///
570/// Since every [`Mode`] implements the [`HookAlias`] trait, given a
571/// `duat_kak` plugin imported as `kak`, instead of writing this:
572///
573/// ```rust
574/// # mod kak {
575/// #     use duat_core::prelude::*;
576/// #     #[derive(Clone)]
577/// #     pub struct Normal;
578/// #     impl<U: Ui> Mode<U> for Normal {
579/// #         type Widget = File<U>;
580/// #         fn send_key(&mut self, _: &mut Pass, _: KeyEvent, _: Handle<Self::Widget, U>) {}
581/// #     }
582/// # }
583/// # use duat_core::doc_duat as duat;
584/// setup_duat!(setup);
585/// use duat::prelude::*;
586/// use kak::Normal;
587///
588/// fn setup() {
589///     hook::add::<ModeCreated<Normal>>(|pa, (normal, handle)| normal);
590/// }
591/// ```
592///
593/// You can just write this:
594///
595/// ```rust
596/// # mod kak {
597/// #     use duat_core::prelude::*;
598/// #     #[derive(Clone)]
599/// #     pub struct Normal;
600/// #     impl<U: Ui> Mode<U> for Normal {
601/// #         type Widget = File<U>;
602/// #         fn send_key(&mut self, _: &mut Pass, _: KeyEvent, _: Handle<Self::Widget, U>) {}
603/// #     }
604/// # }
605/// # use duat_core::doc_duat as duat;
606/// setup_duat!(setup);
607/// use duat::prelude::*;
608/// use kak::Normal;
609///
610/// fn setup() {
611///     hook::add::<Normal>(|pa, (normal, handle)| normal);
612/// }
613/// ```
614///
615/// # Note
616///
617/// You should try to avoid more than one [`Mode`] with the same name.
618/// This can happen if you're using two structs with the same name,
619/// but from different crates.
620///
621/// [`Mode`]: crate::mode::Mode
622pub struct ModeSwitched(pub(crate) (&'static str, &'static str));
623
624impl Hookable for ModeSwitched {
625    type Input<'h> = (&'static str, &'static str);
626
627    fn get_input(&mut self) -> Self::Input<'_> {
628        self.0
629    }
630}
631
632/// [`Hookable`]: Lets you modify a [`Mode`] as it is set
633///
634/// # Arguments
635///
636/// - The new mode.
637/// - Its widget.
638///
639/// This hook is very useful if you want to, for example, set
640/// different options upon switching to modes, depending on things
641/// like the language of a [`File`].
642pub struct ModeCreated<M: Mode<U>, U: Ui>(pub(crate) (Option<M>, Handle<M::Widget, U>));
643
644impl<M: Mode<U>, U: Ui> Hookable for ModeCreated<M, U> {
645    type Input<'h> = (M, &'h Handle<M::Widget, U>);
646    type Output = M;
647
648    fn get_input(&mut self) -> Self::Input<'_> {
649        (self.0.0.take().unwrap(), &self.0.1)
650    }
651
652    fn take_output_back(&mut self, output: Self::Output) {
653        self.0.0 = Some(output)
654    }
655}
656
657/// [`Hookable`]: Triggers whenever a [key] is sent
658///
659/// # Arguments
660///
661/// - The [key] sent.
662///
663/// [key]: KeyEvent
664pub struct KeysSent(pub(crate) Vec<KeyEvent>);
665
666impl Hookable for KeysSent {
667    type Input<'h> = &'h [KeyEvent];
668
669    fn get_input(&mut self) -> Self::Input<'_> {
670        &self.0
671    }
672}
673
674/// [`Hookable`]: Triggers whenever a [key] is sent to the [`Widget`]
675///
676/// # Arguments
677///
678/// - The [key] sent.
679/// - An [`Handle<W>`] for the widget.
680///
681/// [key]: KeyEvent
682pub struct KeysSentTo<W: Widget<U>, U: Ui>(pub(crate) (Vec<KeyEvent>, Handle<W, U>));
683
684impl<W: Widget<U>, U: Ui> Hookable for KeysSentTo<W, U> {
685    type Input<'h> = (&'h [KeyEvent], &'h Handle<W, U>);
686
687    fn get_input(&mut self) -> Self::Input<'_> {
688        (&self.0.0, &self.0.1)
689    }
690}
691
692/// [`Hookable`]: Triggers whenever a [`Form`] is set
693///
694/// This can be a creation or alteration of a [`Form`].
695/// If the [`Form`] is a reference to another, the reference's
696/// [`Form`] will be returned instead.
697///
698/// # Arguments
699///
700/// - The [`Form`]'s name.
701/// - Its [`FormId`].
702/// - Its new value.
703pub struct FormSet(pub(crate) (&'static str, FormId, Form));
704
705impl Hookable for FormSet {
706    type Input<'h> = (&'static str, FormId, Form);
707
708    fn get_input(&mut self) -> Self::Input<'_> {
709        (self.0.0, self.0.1, self.0.2)
710    }
711}
712
713/// [`Hookable`]: Triggers when a [`ColorScheme`] is set
714///
715/// Since [`Form`]s are set asynchronously, this may happen before the
716/// [`ColorScheme`] is done with its changes.
717///
718/// # Arguments
719///
720/// - The name of the [`ColorScheme`]
721///
722/// [`ColorScheme`]: crate::form::ColorScheme
723pub struct ColorSchemeSet(pub(crate) &'static str);
724
725impl Hookable for ColorSchemeSet {
726    type Input<'h> = &'static str;
727
728    fn get_input(&mut self) -> Self::Input<'_> {
729        self.0
730    }
731}
732
733/// [`Hookable`]: Triggers after [`File::save`] or [`File::save_to`]
734///
735/// Only triggers if the file was actually updated.
736///
737/// # Arguments
738///
739/// - The path of the file
740/// - The number of bytes written to said file
741/// - Wether Duat is in the process of quitting (happens when calling
742///   the `wq` or `waq` commands)
743///
744/// [`File::save`]: crate::file::File::save
745/// [`File::save_to`]: crate::file::File::save_to
746pub struct FileWritten(pub(crate) (String, usize, bool));
747
748impl Hookable for FileWritten {
749    type Input<'h> = (&'h str, usize, bool);
750
751    fn get_input(&mut self) -> Self::Input<'_> {
752        (&self.0.0, self.0.1, self.0.2)
753    }
754}
755
756/// A hookable struct, for hooks taking [`Hookable::Input`]
757///
758/// Through this trait, Duat allows for custom hookable structs. With
759/// these structs, plugin creators can create their own custom hooks,
760/// and trigger them via [`hook::trigger`].
761///
762/// This further empowers an end user to customize the behaviour of
763/// Duat in the configuration crate.
764///
765/// [`hook::trigger`]: trigger
766pub trait Hookable<_H: HookDummy = NormalHook>: Sized + 'static {
767    /// The arguments that are passed to each hook.
768    type Input<'h>;
769    /// The output of triggering hooks. Mostly never used
770    ///
771    /// This value is never returned when calling [`hook::trigger`],
772    /// instead, through the [`Hookable::take_output_back`] function,
773    /// you are supposed to store it in [`Self`], and then you can
774    /// access it after the [`hook::trigger`] call, if it supposed
775    /// to be something like the builder pattern.
776    ///
777    /// [`hook::trigger`]: global::trigger
778    /// [`Self`]: Hookable
779    type Output = ();
780
781    /// How to get the arguments from the [`Hookable`]
782    fn get_input(&mut self) -> Self::Input<'_>;
783
784    /// When a [`Hookable`] has an [`Output`], you can define how it
785    /// takes it back
786    ///
787    /// One example of how this can be useful is if your [`Hookable`]
788    /// is using a builder pattern definition for the [`Output`], like
789    /// the [`ModeCreated`] [`Hookable`].
790    ///
791    /// [`Output`]: Hookable::Output
792    #[allow(unused_variables)]
793    fn take_output_back(&mut self, output: Self::Output) {}
794}
795
796/// Where all hooks of Duat are stored
797#[derive(Clone, Copy)]
798struct InnerHooks {
799    types: &'static Mutex<HashMap<TypeId, Box<dyn HookHolder>>>,
800    groups: &'static Mutex<Vec<&'static str>>,
801}
802
803impl InnerHooks {
804    /// Adds a hook for a [`Hookable`]
805    fn add<H: Hookable>(
806        &self,
807        group: &'static str,
808        f: Box<dyn FnMut(&mut Pass, H::Input<'_>) -> H::Output + 'static>,
809    ) {
810        let mut map = self.types.lock().unwrap();
811
812        if !group.is_empty() {
813            let mut groups = self.groups.lock().unwrap();
814            if !groups.contains(&group) {
815                groups.push(group)
816            }
817        }
818
819        if let Some(holder) = map.get(&TypeId::of::<H>()) {
820            let hooks_of = unsafe {
821                let ptr = (&**holder as *const dyn HookHolder).cast::<HooksOf<H>>();
822                ptr.as_ref().unwrap()
823            };
824
825            let mut hooks = hooks_of.0.borrow_mut();
826            hooks.push((group, Box::leak(Box::new(RefCell::new(f)))));
827        } else {
828            let hooks_of = HooksOf::<H>(RefCell::new(vec![(
829                group,
830                Box::leak(Box::new(RefCell::new(f))),
831            )]));
832
833            map.insert(TypeId::of::<H>(), Box::new(hooks_of));
834        }
835    }
836
837    /// Removes hooks with said group
838    fn remove(&self, group: &'static str) {
839        self.groups.lock().unwrap().retain(|g| *g != group);
840        let map = self.types.lock().unwrap();
841        for holder in map.iter() {
842            holder.1.remove(group)
843        }
844    }
845
846    /// Triggers hooks with args of the [`Hookable`]
847    fn trigger<H: Hookable>(&self, pa: &mut Pass, mut hookable: H) -> H {
848        let holder = self.types.lock().unwrap().remove(&TypeId::of::<H>());
849
850        let Some(holder) = holder else {
851            return hookable;
852        };
853
854        // SAFETY: HooksOf<H> is the only type that this HookHolder could be.
855        let hooks_of = unsafe {
856            let ptr = Box::into_raw(holder) as *mut HooksOf<H>;
857            Box::from_raw(ptr)
858        };
859
860        for (_, hook) in hooks_of.0.borrow_mut().iter() {
861            let input = hookable.get_input();
862            let output = hook.borrow_mut()(pa, input);
863            hookable.take_output_back(output);
864        }
865
866        self.types
867            .lock()
868            .unwrap()
869            .insert(TypeId::of::<H>(), unsafe {
870                Box::from_raw(Box::into_raw(hooks_of) as *mut dyn HookHolder)
871            });
872
873        hookable
874    }
875
876    /// Checks if a hook group exists
877    fn group_exists(&self, group: &str) -> bool {
878        self.groups.lock().unwrap().contains(&group)
879    }
880}
881
882impl Default for InnerHooks {
883    fn default() -> Self {
884        Self {
885            types: Box::leak(Box::default()),
886            groups: Box::leak(Box::default()),
887        }
888    }
889}
890
891unsafe impl Send for InnerHooks {}
892unsafe impl Sync for InnerHooks {}
893
894/// An intermediary trait, meant for group removal
895trait HookHolder {
896    /// Remove the given group from hooks of this holder
897    fn remove(&self, group: &str);
898}
899
900/// An intermediary struct, meant to hold the hooks of a [`Hookable`]
901struct HooksOf<H: Hookable>(RefCell<Vec<(&'static str, InnerHookFn<H>)>>);
902
903impl<H: Hookable> HookHolder for HooksOf<H> {
904    fn remove(&self, group: &str) {
905        let mut hooks = self.0.borrow_mut();
906        hooks.retain(|(g, _)| *g != group);
907    }
908}
909
910type InnerHookFn<H> =
911    &'static RefCell<dyn FnMut(&mut Pass, <H as Hookable>::Input<'_>) -> <H as Hookable>::Output>;
912
913/// An alias for a [`Hookable`]
914///
915/// This trait is not normally meant to be implemented manually,
916/// instead, it is automatically derived for every [`Hookable`].
917///
918/// You can use this if you want to use something that is not
919/// [`Hookable`] as a [`Hookable`] alias that gets translated to an
920/// actually [`Hookable`] type via [`HookAlias::Hookable`]. An example
921/// of where this is used is with [`Widget`]s and [`Mode`]s:
922///
923/// ```rust
924/// use duat_core::{
925///     hook::{HookAlias, HookDummy, Hookable},
926///     prelude::*,
927/// };
928///
929/// pub struct CreatedStruct<T: 'static>(Option<T>);
930///
931/// impl<T: 'static> Hookable for CreatedStruct<T> {
932///     type Input<'h> = T;
933///     type Output = T;
934///
935///     fn get_input(&mut self) -> Self::Input<'_> {
936///         self.0.take().unwrap()
937///     }
938///
939///     fn take_output_back(&mut self, output: Self::Output) {
940///         self.0 = Some(output);
941///     }
942/// }
943///
944/// struct MyStructWithAVeryLongName;
945///
946/// // In the user's config crate:
947/// # {
948/// # use duat_core::doc_duat as duat;
949/// setup_duat!(setup);
950/// use duat::prelude::*;
951///
952/// fn setup() {
953///     // This is way too long
954///     hook::add::<CreatedStruct<MyStructWithAVeryLongName>>(|pa, arg| arg);
955/// }
956/// # }
957/// ```
958///
959/// In this case, you can do this instead:
960///
961/// ```rust
962/// use duat_core::{
963///     hook::{HookAlias, HookDummy, Hookable},
964///     prelude::*,
965/// };
966///
967/// pub struct CreatedStruct<T: 'static>(Option<T>);
968///
969/// impl<T: 'static> Hookable for CreatedStruct<T> {
970///     type Input<'h> = T;
971///     type Output = T;
972///
973///     fn get_input(&mut self) -> Self::Input<'_> {
974///         self.0.take().unwrap()
975///     }
976///
977///     fn take_output_back(&mut self, output: Self::Output) {
978///         self.0 = Some(output);
979///     }
980/// }
981///
982/// struct MyStructWithAVeryLongName;
983///
984/// struct MyDummy;
985///
986/// #[doc(hidden)]
987/// impl HookDummy for MyDummy {}
988///
989/// impl<U: Ui> HookAlias<U, MyDummy> for MyStructWithAVeryLongName {
990///     type Hookable = CreatedStruct<MyStructWithAVeryLongName>;
991///     type Input<'h> = <Self::Hookable as Hookable>::Input<'h>;
992///     type Output = <Self::Hookable as Hookable>::Output;
993/// }
994///
995/// // In the user's config crate:
996/// # {
997/// # use duat_core::doc_duat as duat;
998/// setup_duat!(setup);
999/// use duat::prelude::*;
1000///
1001/// fn setup() {
1002///     // Much better
1003///     hook::add::<MyStructWithAVeryLongName>(|pa, arg| arg);
1004/// }
1005/// # }
1006/// ```
1007pub trait HookAlias<U: Ui, D: HookDummy = NormalHook> {
1008    /// Just a shorthand for less boilerplate in the function
1009    /// definition
1010    type Input<'h>;
1011    /// Just a shorthand for less boilerplate in the function
1012    /// definition
1013    type Output;
1014
1015    /// The actual [`Hookable`] that this [`HookAlias`] is supposed to
1016    /// map to
1017    type Hookable: for<'h> Hookable<Input<'h> = Self::Input<'h>, Output = Self::Output>;
1018}
1019
1020impl<H: Hookable, U: Ui> HookAlias<U> for H {
1021    type Hookable = Self;
1022    type Input<'h> = H::Input<'h>;
1023    type Output = H::Output;
1024}
1025
1026impl<W: Widget<U>, U: Ui> HookAlias<U, WidgetCreatedDummy<U>> for W {
1027    type Hookable = WidgetCreated<W, U>;
1028    type Input<'h> = <WidgetCreated<W, U> as Hookable>::Input<'h>;
1029    type Output = <WidgetCreated<W, U> as Hookable>::Output;
1030}
1031
1032impl<M: Mode<U>, U: Ui> HookAlias<U, ModeCreatedDummy<U>> for M {
1033    type Hookable = ModeCreated<M, U>;
1034    type Input<'h> = <ModeCreated<M, U> as Hookable>::Input<'h>;
1035    type Output = <ModeCreated<M, U> as Hookable>::Output;
1036}
1037
1038/// Use this trait if you want to make specialized hooks
1039///
1040/// This trait in particular doesn't really serve any purposes other
1041/// than allowing for specialization resolution. It is recommended
1042/// that you use `#[doc(hidden)]` for any type implementing this
1043/// trait.
1044///
1045/// ```rust
1046/// use duat_core::{
1047///     hook::{HookAlias, HookDummy, Hookable},
1048///     prelude::*,
1049/// };
1050///
1051/// pub struct CreatedStruct<T: 'static>(Option<T>);
1052///
1053/// impl<T: 'static> Hookable for CreatedStruct<T> {
1054///     type Input<'h> = T;
1055///     type Output = T;
1056///
1057///     fn get_input(&mut self) -> Self::Input<'_> {
1058///         self.0.take().unwrap()
1059///     }
1060///
1061///     fn take_output_back(&mut self, output: Self::Output) {
1062///         self.0 = Some(output);
1063///     }
1064/// }
1065///
1066/// struct MyStructWithAVeryLongName;
1067///
1068/// // In the user's config crate:
1069/// # {
1070/// # use duat_core::doc_duat as duat;
1071/// setup_duat!(setup);
1072/// use duat::prelude::*;
1073///
1074/// fn setup() {
1075///     // This is way too long
1076///     hook::add::<CreatedStruct<MyStructWithAVeryLongName>>(|pa, arg| arg);
1077/// }
1078/// # }
1079/// ```
1080///
1081/// In this case, you can do this instead:
1082///
1083/// ```rust
1084/// use duat_core::{
1085///     hook::{HookAlias, HookDummy, Hookable},
1086///     prelude::*,
1087/// };
1088///
1089/// pub struct CreatedStruct<T: 'static>(Option<T>);
1090///
1091/// impl<T: 'static> Hookable for CreatedStruct<T> {
1092///     type Input<'h> = T;
1093///     type Output = T;
1094///
1095///     fn get_input(&mut self) -> Self::Input<'_> {
1096///         self.0.take().unwrap()
1097///     }
1098///
1099///     fn take_output_back(&mut self, output: Self::Output) {
1100///         self.0 = Some(output);
1101///     }
1102/// }
1103///
1104/// struct MyStructWithAVeryLongName;
1105///
1106/// struct MyDummy;
1107///
1108/// #[doc(hidden)]
1109/// impl HookDummy for MyDummy {}
1110///
1111/// impl<U: Ui> HookAlias<U, MyDummy> for MyStructWithAVeryLongName {
1112///     type Hookable = CreatedStruct<MyStructWithAVeryLongName>;
1113///     type Input<'h> = <Self::Hookable as Hookable>::Input<'h>;
1114///     type Output = <Self::Hookable as Hookable>::Output;
1115/// }
1116///
1117/// // In the user's config crate:
1118/// # {
1119/// # use duat_core::doc_duat as duat;
1120/// setup_duat!(setup);
1121/// use duat::prelude::*;
1122///
1123/// fn setup() {
1124///     // Much better
1125///     hook::add::<MyStructWithAVeryLongName>(|pa, arg| arg);
1126/// }
1127/// # }
1128/// ```
1129pub trait HookDummy {}
1130
1131/// For specialization purposes
1132#[doc(hidden)]
1133pub struct NormalHook;
1134
1135impl HookDummy for NormalHook {}
1136
1137/// For specialization purposes
1138#[doc(hidden)]
1139pub struct WidgetCreatedDummy<U>(PhantomData<U>);
1140
1141impl<U> HookDummy for WidgetCreatedDummy<U> {}
1142
1143/// For specialization purposes
1144#[doc(hidden)]
1145pub struct ModeCreatedDummy<U>(PhantomData<U>);
1146
1147impl<U> HookDummy for ModeCreatedDummy<U> {}