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