duat_core/
hooks.rs

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