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//! # duat_core::doc_duat!(duat);
11//! setup_duat!(setup);
12//! use duat::prelude::*;
13//!
14//! fn setup() {
15//!     hook::add::<BufferOpened>(|pa: &mut Pass, handle: &Handle| {
16//!         let buffer = handle.write(pa);
17//!         if let Some("lisp") = buffer.filetype() {
18//!             buffer.opts.wrap_lines = true;
19//!         }
20//!     });
21//! }
22//! ```
23//!
24//! The hook above is triggered whenever a [`Buffer`] widget is
25//! opened. Like every other hook, it gives you access to the global
26//! state via the [`Pass`]. Additionally, like most hooks, it gives
27//! you a relevant argument, in this case, a [`Handle<Buffer>`], which
28//! you can modify to your liking.
29//!
30//! This is just one of many built-in [`Hookable`]s. Currently, these
31//! are the existing hooks in `duat-core`, but you can also make your
32//! own:
33//!
34//! - [`BufferOpened`] is an alias for [`WidgetOpened<Buffer>`].
35//! - [`BufferSaved`] triggers after the [`Buffer`] is written.
36//! - [`BufferClosed`] triggers on every buffer upon closing Duat.
37//! - [`BufferReloaded`] triggers on every buffer upon reloading Duat.
38//! - [`BufferUpdated`] triggers whenever a buffer changesg.
39//! - [`BufferSwitched`] triggers when switching the active buffer.
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//! - [`WidgetOpened`] triggers when a [`Widget`] is opened.
46//! - [`WindowOpened`] triggers when a [`Window`] is created.
47//! - [`FocusedOn`] triggers when a [widget] is focused.
48//! - [`UnfocusedFrom`] triggers when a [widget] is unfocused.
49//! - [`FocusChanged`] is like [`FocusedOn`], but on [dyn `Widget`]s.
50//! - [`KeySent`] triggers when a keys are sent.
51//! - [`KeySentTo`] same, but on a specific [widget].
52//! - [`KeyTyped`] triggers when keys are _typed_, not _sent_.
53//! - [`OnMouseEvent`] triggers with mouse events.
54//! - [`FormSet`] triggers whenever a [`Form`] is added/altered.
55//! - [`ModeSwitched`] triggers when you change [`Mode`].
56//!
57//! # Basic makeout
58//!
59//! When a hook is added, it can take arguments
60//!
61//! ```rust
62//! # duat_core::doc_duat!(duat);
63//! use duat::prelude::*;
64//!
65//! struct CustomHook(usize);
66//! impl Hookable for CustomHook {
67//!     type Input<'h> = usize;
68//!
69//!     fn get_input<'h>(&'h mut self, pa: &mut Pass) -> Self::Input<'h> {
70//!         self.0
71//!     }
72//! }
73//!
74//! fn runtime_function_that_triggers_hook(pa: &mut Pass) {
75//!     let arg = 42;
76//!     hook::trigger(pa, CustomHook(arg));
77//! }
78//! ```
79//!
80//! The above example ilustrates how hooks are implemented in Duat.
81//! You essentially pass a struct wich will hold the arguments that
82//! will be passed as input to the hooks. The [`Hookable::Input`]
83//! argument makes it so you can have more convenient parameters for
84//! hooks, like `(usize, &'h str)`, for example.
85//!
86//! Sometimes, you may need to trigger hooks "remotely", that is, in a
87//! place wher you don't have acces to a [`Pass`] (due to not being on
88//! the main thread or for some other reason), you can make use of
89//! [`context::queue`] in order to queue the hook to be executed on
90//! the main thread:
91//!
92//! ```rust
93//! # duat_core::doc_duat!(duat);
94//! # use duat::prelude::*;
95//! # struct CustomHook(usize);
96//! # impl Hookable for CustomHook {
97//! #     type Input<'h> = usize;
98//! #     fn get_input<'h>(&'h mut self, pa: &mut Pass) -> Self::Input<'h> { self.0 }
99//! # }
100//! fn on_a_thread_far_far_away() {
101//!     let arg = 42;
102//!     context::queue(move |pa| _ = hook::trigger(pa, CustomHook(arg)));
103//! }
104//! ```
105//!
106//! The main difference (apart from the asynchronous execution) is
107//! that [`hook::trigger`] _returns_ the hook to you, so you can
108//! retrieve its internal values. This can be useful if, for example,
109//! you wish to create a hook for configuring things:
110//!
111//! ```rust
112//! # duat_core::doc_duat!(duat);
113//! use duat::prelude::*;
114//!
115//! #[derive(Default)]
116//! struct MyConfig {
117//!     pub value_1: usize,
118//!     pub value_2: Option<f32>,
119//! }
120//!
121//! struct MyConfigCreated(MyConfig);
122//!
123//! impl Hookable for MyConfigCreated {
124//!     type Input<'h> = &'h mut MyConfig;
125//!
126//!     fn get_input<'h>(&'h mut self, pa: &mut Pass) -> Self::Input<'h> {
127//!         &mut self.0
128//!     }
129//! }
130//!
131//! fn create_my_config(pa: &mut Pass) -> MyConfig {
132//!     let my_config = MyConfig::default();
133//!     let MyConfigCreated(my_config) = hook::trigger(pa, MyConfigCreated(my_config));
134//!     my_config
135//! }
136//! ```
137//!
138//! This way, the user can configure `MyConfig` by calling
139//! [`hook::add`]:
140//!
141//! ```rust
142//! # duat_core::doc_duat!(duat);
143//! # #[derive(Default)]
144//! # struct MyConfig {
145//! #     pub value_1: usize,
146//! #     pub value_2: Option<f32>,
147//! # }
148//! # struct MyConfigCreated(MyConfig);
149//! # impl Hookable for MyConfigCreated {
150//! #     type Input<'h> = &'h mut MyConfig;
151//! #     fn get_input<'h>(&'h mut self, pa: &mut Pass) -> Self::Input<'h> { &mut self.0 }
152//! # }
153//! use duat::prelude::*;
154//! setup_duat!(setup);
155//!
156//! fn setup() {
157//!     hook::add::<MyConfigCreated>(|pa, my_config| my_config.value_1 = 3);
158//! }
159//! ```
160//!
161//! [`Buffer`]: crate::buffer::Buffer
162//! [`LineNumbers`]: https://docs.rs/duat/latest/duat/widgets/struct.LineNumbers.html
163//! [widget]: Widget
164//! [dyn `Widget`]: Widget
165//! [key]: KeyEvent
166//! [deadlocks]: https://en.wikipedia.org/wiki/Deadlock_(computer_science)
167//! [commands]: crate::cmd
168//! [`Mode`]: crate::mode::Mode
169//! [`&mut Widget`]: Widget
170//! [`context::queue`]: crate::context::queue
171//! [`hook::trigger`]: trigger
172//! [`hook::add`]: add
173//! [`SearchPerformed`]: https://docs.rs/duat/latest/duat/hooks/struct.SearchPerformed.html
174//! [`SearchUpdated`]: https://docs.rs/duat/latest/duat/hooks/struct.SearchUpdated.html
175use std::{any::TypeId, cell::RefCell, collections::HashMap, sync::Mutex};
176
177use crossterm::event::MouseEventKind;
178
179pub use self::global::*;
180use crate::{
181    buffer::Buffer,
182    context::Handle,
183    data::Pass,
184    form::{Form, FormId},
185    mode::{KeyEvent, Mode, MouseEvent},
186    ui::{Widget, Window},
187    utils::catch_panic,
188};
189
190/// Hook functions
191mod global {
192    use std::sync::{
193        LazyLock,
194        atomic::{AtomicUsize, Ordering},
195    };
196
197    use super::{Hookable, InnerGroupId, InnerHooks};
198    use crate::data::Pass;
199
200    static HOOKS: LazyLock<InnerHooks> = LazyLock::new(InnerHooks::default);
201
202    /// A [`GroupId`] that can be used in order to remove hooks
203    ///
204    /// When [adding grouped hooks], you can either use strings or a
205    /// [`GroupId`]. You should use strings for publicly removable
206    /// hooks, and [`GroupId`]s for privately removable hooks:
207    ///
208    /// [adding grouped hooks]: HookBuilder::grouped
209    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
210    pub struct GroupId(usize);
211
212    impl GroupId {
213        /// Returns a new [`GroupId`]
214        #[allow(clippy::new_without_default)]
215        pub fn new() -> Self {
216            static HOOK_GROUPS: AtomicUsize = AtomicUsize::new(0);
217            Self(HOOK_GROUPS.fetch_add(1, Ordering::Relaxed))
218        }
219
220        /// Remove all hooks that belong to this [`GroupId`]
221        ///
222        /// This can be used in order to have hooks remove themselves,
223        /// for example.
224        pub fn remove(self) {
225            remove(self)
226        }
227
228		/// Returns `true` if this `GroupId` has any hooks
229        pub fn has_hooks(self) -> bool {
230            group_exists(self)
231        }
232    }
233
234    /// A struct used in order to specify more options for [hook]s
235    ///
236    /// You can set three options currently:
237    ///
238    /// - [`HookBuilder::grouped`]: Groups this hook with others,
239    ///   allowing them to all be [removed] at once.
240    /// - [`HookBuilder::filter`]: Filters when this hook should be
241    ///   called, by giving a struct for which  `H` implements
242    ///   [`PartialEq`]. An example is the [`FocusedOn`] hook, which
243    ///   accepts [`Handle`]s and [`Handle`] pairs.
244    /// - [`HookBuilder::once`]: Calls the hook only once.
245    ///
246    /// [hook]: crate::hook
247    /// [`FocusedOn`]: super::FocusedOn
248    /// [`Handle`]: crate::context::Handle
249    /// [removed]: remove
250    pub struct HookBuilder<H: Hookable> {
251        callback: Option<Box<dyn FnMut(&mut Pass, H::Input<'_>) + 'static>>,
252        group: Option<InnerGroupId>,
253        filter: Option<Box<dyn Fn(&H) -> bool + Send>>,
254        once: bool,
255    }
256
257    impl<H: Hookable> HookBuilder<H> {
258        /// Add a group to this hook
259        ///
260        /// This makes it so you can call [`hook::remove`] in order to
261        /// remove this hook as well as every other hook added to the
262        /// same group.
263        ///
264        /// There are two types of group, a private [`GroupId`] and
265        /// [`impl ToString`] types, which can be removed by an end
266        /// user.
267        ///
268        /// [`impl ToString`]: ToString
269        /// [`hook::remove`]: super::remove
270        pub fn grouped(mut self, group: impl Into<InnerGroupId>) -> Self {
271            self.group = Some(group.into());
272            self
273        }
274
275        /// Calls this hook only once
276        ///
277        /// This makes it so the hook will only be called once. Note
278        /// that, if the hook is removed via [`hook::remove`] before
279        /// being called, then it never will be.
280        ///
281        /// [`hook::remove`]: super::remove
282        pub fn once(mut self) -> Self {
283            self.once = true;
284            self
285        }
286
287        /// Filter when this hook will be called
288        ///
289        /// This is mostly for convenience's sake, since you _could_
290        /// just add a check inside of the callback itself.
291        ///
292        /// This is useful if for example, you want to trigger a hook
293        /// on only some specific [`Handle`], or some [`Buffer`],
294        /// things of the sort.
295        ///
296        /// [`Handle`]: crate::context::Handle
297        /// [`Buffer`]: crate::buffer::Buffer
298        pub fn filter<T: Send + 'static>(mut self, filter: T) -> Self
299        where
300            H: PartialEq<T>,
301        {
302            self.filter = Some(Box::new(move |hookable| *hookable == filter));
303            self
304        }
305    }
306
307    impl<H: Hookable> Drop for HookBuilder<H> {
308        fn drop(&mut self) {
309            HOOKS.add::<H>(
310                self.callback.take().unwrap(),
311                self.group.take(),
312                self.filter.take(),
313                self.once,
314            )
315        }
316    }
317
318    /// Adds a hook, which will be called whenever the [`Hookable`] is
319    /// triggered
320    ///
321    /// [`hook::add`] will return a [`HookBuilder`], which is a struct
322    /// that can be used to further modify the behaviour of the hook,
323    /// and will add said hook when [`Drop`]ped.
324    ///
325    /// For example, [`HookBuilder::grouped`] will group this hook
326    /// with others of the same group, while [`HookBuilder::once`]
327    /// will make it so the hook is only called one time.
328    ///
329    /// [`hook::add`]: add
330    #[inline(never)]
331    pub fn add<H: Hookable>(
332        f: impl FnMut(&mut Pass, H::Input<'_>) + Send + 'static,
333    ) -> HookBuilder<H> {
334        HookBuilder {
335            callback: Some(Box::new(f)),
336            group: None,
337            once: false,
338            filter: None,
339        }
340    }
341
342    /// Removes a [hook] group
343    ///
344    /// The hook can either be a string type, or a [`GroupId`].
345    ///
346    /// [hook]: Hookable
347    pub fn remove(group: impl Into<InnerGroupId>) {
348        HOOKS.remove(group.into());
349    }
350
351    /// Triggers a hooks for a [`Hookable`] struct
352    pub fn trigger<H: Hookable>(pa: &mut Pass, hookable: H) -> H {
353        HOOKS.trigger(pa, hookable)
354    }
355
356    /// Checks if a give group exists
357    ///
358    /// The hook can either be a string type, or a [`GroupId`].
359    ///
360    /// Returns `true` if said group was added via
361    /// [`HookBuilder::grouped`], and no [`hook::remove`]
362    /// followed these additions
363    ///
364    /// [`hook::remove`]: remove
365    pub fn group_exists(group: impl Into<InnerGroupId>) -> bool {
366        HOOKS.group_exists(group.into())
367    }
368}
369
370/// A group can either be created through a name or through a
371/// [`GroupId`]
372#[derive(Clone, Debug, PartialEq, Eq)]
373#[doc(hidden)]
374pub enum InnerGroupId {
375    Numbered(GroupId),
376    Named(String),
377}
378
379impl From<GroupId> for InnerGroupId {
380    fn from(value: GroupId) -> Self {
381        Self::Numbered(value)
382    }
383}
384
385impl<S: ToString> From<S> for InnerGroupId {
386    fn from(value: S) -> Self {
387        Self::Named(value.to_string())
388    }
389}
390
391/// [`Hookable`]: Triggers when Duat opens or reloads
392///
393/// This trigger will also happen after a few other initial setups of
394/// Duat.
395///
396/// There are no arguments
397pub struct ConfigLoaded(pub(crate) ());
398
399impl Hookable for ConfigLoaded {
400    type Input<'h> = ();
401
402    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {}
403}
404
405/// [`Hookable`]: Triggers when Duat closes or has to reload
406///
407/// There are no arguments
408pub struct ConfigUnloaded(pub(crate) ());
409
410impl Hookable for ConfigUnloaded {
411    type Input<'h> = ();
412
413    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {}
414}
415
416/// [`Hookable`]: Triggers when Duat closes
417///
418/// There are no arguments
419pub struct ExitedDuat(pub(crate) ());
420
421impl Hookable for ExitedDuat {
422    type Input<'h> = ();
423
424    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {}
425}
426
427/// [`Hookable`]: Triggers when Duat is refocused
428///
429/// # Arguments
430///
431/// There are no arguments
432pub struct FocusedOnDuat(pub(crate) ());
433
434impl Hookable for FocusedOnDuat {
435    type Input<'h> = ();
436
437    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {}
438}
439
440/// [`Hookable`]: Triggers when Duat is unfocused
441///
442/// # Arguments
443///
444/// There are no arguments
445pub struct UnfocusedFromDuat(pub(crate) ());
446
447impl Hookable for UnfocusedFromDuat {
448    type Input<'h> = ();
449
450    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {}
451}
452
453/// [`Hookable`]: Triggers when a [`Widget`] is created
454///
455/// # Arguments
456///
457/// - The [`Handle<W>`] of said `Widget`.
458///
459/// # Aliases
460///
461/// Since every `Widget` implements the `HookAlias` trait, instead
462/// of writing this in the config crate:
463///
464/// ```rust
465/// # duat_core::doc_duat!(duat);
466/// setup_duat!(setup);
467/// use duat::prelude::*;
468///
469/// fn setup() {
470///     hook::add::<WidgetOpened<LineNumbers>>(|pa, ln| ln.write(pa).relative = true);
471/// }
472/// ```
473///
474/// You can just write this:
475///
476/// ```rust
477/// # duat_core::doc_duat!(duat);
478/// setup_duat!(setup);
479/// use duat::prelude::*;
480///
481/// fn setup() {
482///     hook::add::<WidgetOpened<LineNumbers>>(|pa, ln| ln.write(pa).relative = true);
483/// }
484/// ```
485///
486/// # Changing the layout
487///
488/// Assuming you are using `duat-term`, you could make it so every
489/// [`LineNumbers`] comes with a [`VertRule`] on the right, like this:
490///
491/// ```rust
492/// # duat_core::doc_duat!(duat);
493/// setup_duat!(setup);
494/// use duat::prelude::*;
495///
496/// fn setup() {
497///     hook::add::<WidgetOpened<LineNumbers>>(|pa, handle| {
498///         VertRule::builder().on_the_right().push_on(pa, handle);
499///     });
500/// }
501/// ```
502///
503/// Now, every time a [`LineNumbers`]s `Widget` is inserted in Duat,
504/// a [`VertRule`] will be pushed on the right of it. You could even
505/// further add a [hook] on `VertRule`, that would push further
506/// `Widget`s if you wanted to.
507///
508/// [hook]: self
509/// [direction]: crate::ui::PushSpecs
510/// [`LineNumbers`]: https://docs.rs/duat/latest/duat/widgets/struct.LineNumbers.html
511/// [`VertRule`]: https://docs.rs/duat_term/latest/duat-term/struct.VertRule.html
512pub struct WidgetOpened<W: Widget>(pub(crate) Handle<W>);
513
514impl<W: Widget> Hookable for WidgetOpened<W> {
515    type Input<'h> = &'h Handle<W>;
516
517    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
518        &self.0
519    }
520}
521
522/// An alias for [`WidgetOpened<Buffer>`]
523pub type BufferOpened = WidgetOpened<Buffer>;
524
525/// [`Hookable`]: Triggers when a new window is opened
526///
527/// # Arguments
528///
529/// - The [`Window`] that was created
530///
531/// One of the main reasons to use this [hook] is to push new
532/// [`Widget`]s to a `Window`. That is, you can push [outer
533/// `Widget`s] or [inner `Widget`s], just like with
534/// [`Handle`]s.
535///
536/// Here's how that works: `Window`s are divided into two main
537/// regions, the inner "[`Buffer`] region", and the outer "master
538/// region". This means that, on every `Window`, you'll have a
539/// collection of `Buffer`s in the middle, with their satellite
540/// `Widget`s, and various `Widget`s on the outer rims of the
541/// `Window`, not necessarily associated with any single `Buffer`.
542///
543/// As an example, this is how the default layout of Duat is layed
544/// out:
545///
546/// ```text
547/// ╭┄┄┬┄┄┄┄┄┄┄┄┬┄┄┬┄┄┄┄┄┄┄┄┬───────╮
548/// ┊  │        │  │        ┊       │
549/// ┊LN│        │LN│        ┊       │
550/// ┊  │ Buffer │  │ Buffer ┊       │
551/// ┊VR│        │VR│        ┊LogBook│
552/// ┊  │        │  │        ┊       │
553/// ├┄┄┴┄┄┄┄┄┄┄┄┴┄┄┴┄┄┄┄┄┄┄┄┤       │
554/// │     FooterWidgets     │       │
555/// ╰───────────────────────┴───────╯
556/// ```
557///
558/// In this configuration, you can see the delineation between the
559/// "`Buffer` region" (surrounded by dotted lines) and the "master
560/// region", where:
561///
562/// - For each [`Buffer`], we are adding a [`LineNumbers`] (LN) and a
563///   [`VertRule`] (VR) `Widget`s. Each of these is related to a
564///   specific `Buffer`, and if that `Buffer` moves around, they will
565///   follow.
566///
567/// - On the outer edges, we have a [`FooterWidgets`], which includes
568///   a [`StatusLine`], [`PromptLine`] and [`Notifications`], as well
569///   as a [`LogBook`] on the side, which is hidden by default. These
570///   [`Widget`]s are not related to any `Buffer`, and will not move
571///   around or be removed, unless directly.
572///
573/// So the distinction here is that, if you call
574/// [`Window::push_inner`], you will be pushing [`Widget`]s _around_
575/// the "`Buffer` region", but _not_ within it. If you want to push to
576/// specific [`Buffer`]s, you should look at
577/// [`Handle::push_inner_widget`] and [`Handle::push_outer_widget`].
578///
579/// On the other hand, by calling [`Window::push_outer`], you will be
580/// pushing [`Widget`]s around the "master region", so they will go on
581/// the edges of the screen.
582///
583/// [hook]: self
584/// [outer `Widget`s]: Window::push_outer
585/// [inner `Widget`s]: Window::push_inner
586/// [`LineNumbers`]: https://docs.rs/duat/duat/latest/widgets/struct.LineNumbers.html
587/// [`VertRule`]: https://docs.rs/duat/duat/latest/widgets/struct.VertRule.html
588/// [`FooterWidgets`]: https://docs.rs/duat/duat/latest/widgets/struct.FooterWidgets.html
589/// [`StatusLine`]: https://docs.rs/duat/duat/latest/widgets/struct.StatusLine.html
590/// [`PromptLine`]: https://docs.rs/duat/duat/latest/widgets/struct.PromptLine.html
591/// [`Notifications`]: https://docs.rs/duat/duat/latest/widgets/struct.Notifications.html
592/// [`LogBook`]: https://docs.rs/duat/duat/latest/widgets/struct.LogBook.html
593pub struct WindowOpened(pub(crate) Window);
594
595impl Hookable for WindowOpened {
596    type Input<'h> = &'h mut Window;
597
598    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
599        &mut self.0
600    }
601}
602
603/// [`Hookable`]: Triggers before closing a [`Buffer`]
604///
605/// # Arguments
606///
607/// - The [`Buffer`]'s [`Handle`].
608pub struct BufferClosed(pub(crate) Handle);
609
610impl Hookable for BufferClosed {
611    type Input<'h> = &'h Handle;
612
613    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
614        &self.0
615    }
616}
617
618impl PartialEq<Handle> for BufferClosed {
619    fn eq(&self, other: &Handle) -> bool {
620        self.0 == *other
621    }
622}
623
624/// [`Hookable`]: Triggers before reloading a [`Buffer`]
625///
626/// # Arguments
627///
628/// - The [`Buffer`]'s [`Handle`].
629///
630/// This will not trigger upon closing Duat. For that, see
631/// [`BufferClosed`].
632pub struct BufferReloaded(pub(crate) Handle);
633
634impl Hookable for BufferReloaded {
635    type Input<'h> = &'h Handle;
636
637    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
638        &self.0
639    }
640}
641
642impl PartialEq<Handle> for BufferReloaded {
643    fn eq(&self, other: &Handle) -> bool {
644        self.0 == *other
645    }
646}
647
648/// [`Hookable`]: Triggers when a [`Buffer`] updates
649///
650/// This is triggered after a batch of writing calls to the `Buffer`,
651/// once per frame. This can happen after typing a key, calling a
652/// command, triggering hooks, or any other action with access to a
653/// [`Pass`], which could be used to write to the `Buffer`.
654///
655/// Think of this is as a "last pass" on the `Buffer`, right before
656/// printing, where it can be adjusted given the modifications to it,
657/// like [`Change`]s and such.
658///
659/// As an example, here's a hook that will highlight every non ascii
660/// character:
661///
662/// ```rust
663/// # duat_core::doc_duat!(duat);
664/// use duat::{
665///     prelude::*,
666///     text::{Bytes, Tags},
667/// };
668///
669/// static TRACKER: BufferTracker = BufferTracker::new();
670///
671/// fn setup() {
672///     let tagger = Tagger::new();
673///     let tag = form::id_of!("non_ascii_char").to_tag(50);
674///
675///     let highlight_non_ascii = move |tags: &mut Tags, bytes: &Bytes, range| {
676///         for (b, char) in bytes.strs(range).unwrap().char_indices() {
677///             if !char.is_ascii() {
678///                 tags.insert(tagger, b..b + char.len_utf8(), tag);
679///             }
680///         }
681///     };
682///
683///     hook::add::<BufferOpened>(move |pa, handle| {
684///         TRACKER.register_buffer(handle.write(pa));
685///
686///         let mut parts = handle.text_parts(pa);
687///         let range = Point::default()..parts.bytes.len();
688///         highlight_non_ascii(&mut parts.tags, parts.bytes, range);
689///     });
690///
691///     hook::add::<BufferUpdated>(move |pa, handle| {
692///         let mut parts = TRACKER.parts(handle.write(pa)).unwrap();
693///
694///         for change in parts.changes {
695///             parts.tags.remove(tagger, change.added_range());
696///             highlight_non_ascii(&mut parts.tags, parts.bytes, change.added_range())
697///         }
698///     });
699/// }
700/// ```
701///
702/// The [`BufferTracker`] will keep track of each registered
703/// [`Buffer`], telling you about every new [`Change`] that took place
704/// since the last call to [`BufferTracker::parts`]. The
705/// `BufferTracker::parts` function works much like [`Text::parts`],
706/// by separating the [`Bytes`], [`Tags`] and [`Selections`], letting
707/// you modify the tags, without permitting further edits to the
708/// `Text`.
709///
710/// This is a nice way to automatically keep track of the changes, and
711/// it will work even if the function isn't called frequently.
712///
713/// # Arguments
714///
715/// - The [`Buffer`]'s [`Handle`]
716///
717/// [`Area`]: crate::ui::Area
718/// [`Buffer`]: crate::buffer::Buffer
719/// [`PrintOpts`]: crate::opts::PrintOpts
720/// [`Change`]: crate::buffer::Change
721/// [`Cursor`]: crate::mode::Cursor
722/// [`Tag`]: crate::text::Tag
723/// [`Bytes`]: crate::text::Bytes
724/// [`Tags`]: crate::text::Tags
725/// [`Selections`]: crate::mode::Selections
726/// [`Text`]: crate::text::Text
727/// [`Text::parts`]: crate::text::Text::parts
728/// [`Text::replace_range`]: crate::text::Text::replace_range
729/// [`BufferTracker`]: crate::buffer::BufferTracker
730/// [`BufferTracker::parts`]: crate::buffer::BufferTracker::parts
731pub struct BufferUpdated(pub(crate) Handle);
732
733impl Hookable for BufferUpdated {
734    type Input<'h> = &'h Handle;
735
736    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
737        &self.0
738    }
739}
740
741impl PartialEq<Handle> for BufferUpdated {
742    fn eq(&self, other: &Handle) -> bool {
743        self.0 == *other
744    }
745}
746
747/// [`Hookable`]: Triggers whenever the active [`Buffer`] changes
748///
749/// # Arguments
750///
751/// - The former `Buffer`'s [`Handle`]
752/// - The current `Buffer`'s [`Handle`]
753///
754/// [`Buffer`]: crate::buffer::Buffer
755pub struct BufferSwitched(pub(crate) (Handle, Handle));
756
757impl Hookable for BufferSwitched {
758    type Input<'h> = (&'h Handle, &'h Handle);
759
760    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
761        (&self.0.0, &self.0.1)
762    }
763}
764
765/// [`Hookable`]: Triggers when the [`Widget`] is focused
766///
767/// # Arguments
768///
769/// - The [`Handle<dyn Widget>`] for the unfocused `Widget`.
770/// - The [`Handle<W>`] for the newly focused `Widget`.
771///
772/// # Filters
773///
774/// This `Hookable` can be filtered in two ways
775///
776/// - By a focused [`Handle<_>`].
777/// - By a `(Handle<_>, Handle<_>)` pair.
778pub struct FocusedOn<W: Widget>(pub(crate) (Handle<dyn Widget>, Handle<W>));
779
780impl<W: Widget> Hookable for FocusedOn<W> {
781    type Input<'h> = &'h (Handle<dyn Widget>, Handle<W>);
782
783    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
784        &self.0
785    }
786}
787
788impl<W1: Widget, W2: Widget + ?Sized> PartialEq<Handle<W2>> for FocusedOn<W1> {
789    fn eq(&self, other: &Handle<W2>) -> bool {
790        self.0.1 == *other
791    }
792}
793
794impl<W1: Widget, W2: Widget + ?Sized, W3: Widget + ?Sized> PartialEq<(Handle<W2>, Handle<W3>)>
795    for FocusedOn<W1>
796{
797    fn eq(&self, other: &(Handle<W2>, Handle<W3>)) -> bool {
798        self.0.0 == other.0 && self.0.1 == other.1
799    }
800}
801
802/// [`Hookable`]: Triggers when the [`Widget`] is unfocused
803///
804/// # Arguments
805///
806/// - The [`Handle<W>`] for the unfocused `Widget`
807/// - The [`Handle<dyn Widget>`] for the newly focused `Widget`
808pub struct UnfocusedFrom<W: Widget>(pub(crate) (Handle<W>, Handle<dyn Widget>));
809
810impl<W: Widget> Hookable for UnfocusedFrom<W> {
811    type Input<'h> = &'h (Handle<W>, Handle<dyn Widget>);
812
813    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
814        &self.0
815    }
816}
817
818impl<W1: Widget, W2: Widget + ?Sized> PartialEq<Handle<W2>> for UnfocusedFrom<W1> {
819    fn eq(&self, other: &Handle<W2>) -> bool {
820        self.0.0 == *other
821    }
822}
823
824impl<W1: Widget, W2: Widget + ?Sized, W3: Widget + ?Sized> PartialEq<(Handle<W2>, Handle<W3>)>
825    for UnfocusedFrom<W1>
826{
827    fn eq(&self, other: &(Handle<W2>, Handle<W3>)) -> bool {
828        self.0.0 == other.0 && self.0.1 == other.1
829    }
830}
831
832/// [`Hookable`]: Triggers when focus changes between two [`Widget`]s
833///
834/// # Arguments
835///
836/// - The [`Handle<dyn Widget>`] for the unfocused `Widget`
837/// - The [`Handle<dyn Widget>`] for the newly focused `Widget`
838///
839/// This `Hookable` is triggered _before_ [`FocusedOn`] and
840/// [`UnfocusedFrom`] are triggered.
841pub struct FocusChanged(pub(crate) (Handle<dyn Widget>, Handle<dyn Widget>));
842
843impl Hookable for FocusChanged {
844    type Input<'h> = &'h (Handle<dyn Widget>, Handle<dyn Widget>);
845
846    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
847        &self.0
848    }
849}
850
851/// [`Hookable`]: Triggers when the [`Mode`] is changed
852///
853/// # Arguments
854///
855/// - The previous mode.
856/// - The current mode.
857pub struct ModeSwitched(pub(crate) (&'static str, &'static str));
858
859impl Hookable for ModeSwitched {
860    type Input<'h> = (&'static str, &'static str);
861
862    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
863        self.0
864    }
865}
866
867/// [`Hookable`]: Triggers whenever a [key] is sent
868///
869/// [`KeyEvent`]s are "sent" when you type [unmapped] keys _or_ with
870/// the keys that were mapped, this is in contrast with [`KeyTyped`],
871/// which triggers when you type or when calling [`mode::type_keys`].
872/// For example, if `jk` is mapped to `<Esc>`, [`KeyTyped`] will
873/// trigger once for `j` and once for `k`, while [`KeySent`] will
874/// trigger once for `<Esc>`.
875///
876/// # Arguments
877///
878/// - The sent [key].
879///
880/// [key]: KeyEvent
881/// [unmapped]: crate::mode::map
882/// [`mode::type_keys`]: crate::mode::type_keys
883pub struct KeySent(pub(crate) KeyEvent);
884
885impl Hookable for KeySent {
886    type Input<'h> = KeyEvent;
887
888    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
889        self.0
890    }
891}
892
893/// [`Hookable`]: Triggers whenever a [key] is sent to the [`Widget`]
894///
895/// # Arguments
896///
897/// - The sent [key].
898/// - An [`Handle<W>`] for the widget.
899///
900/// [key]: KeyEvent
901pub struct KeySentTo<M: Mode>(pub(crate) (KeyEvent, Handle<M::Widget>));
902
903impl<M: Mode> Hookable for KeySentTo<M> {
904    type Input<'h> = (KeyEvent, &'h Handle<M::Widget>);
905
906    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
907        (self.0.0, &self.0.1)
908    }
909}
910
911/// [`Hookable`]: Triggers whenever a [key] is typed
912///
913/// [`KeyEvent`]s are "typed" when typing keys _or_ when calling the
914/// [`mode::type_keys`] function, this is in contrast with
915/// [`KeySent`], which triggers when you type [unmapped] keys or with
916/// the remapped keys. For example, if `jk` is mapped to `<Esc>`,
917/// [`KeyTyped`] will trigger once for `j` and once for `k`, while
918/// [`KeySent`] will trigger once for `<Esc>`.
919///
920/// # Arguments
921///
922/// - The typed [key].
923///
924/// [key]: KeyEvent
925/// [unmapped]: crate::mode::map
926/// [`mode::type_keys`]: crate::mode::type_keys
927pub struct KeyTyped(pub(crate) KeyEvent);
928
929impl Hookable for KeyTyped {
930    type Input<'h> = KeyEvent;
931
932    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
933        self.0
934    }
935}
936
937/// [`Hookable`]: Triggers on every [`MouseEvent`]
938///
939/// # Arguments
940///
941/// - The [`Handle<dyn Widget>`] under the mouse.
942/// - The [`MouseEvent`] itself.
943pub struct OnMouseEvent(pub(crate) (Handle<dyn Widget>, MouseEvent));
944
945impl Hookable for OnMouseEvent {
946    type Input<'h> = (&'h Handle<dyn Widget>, MouseEvent);
947
948    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
949        (&self.0.0, self.0.1)
950    }
951}
952
953impl PartialEq<MouseEvent> for OnMouseEvent {
954    fn eq(&self, other: &MouseEvent) -> bool {
955        self.0.1 == *other
956    }
957}
958
959impl PartialEq<MouseEventKind> for OnMouseEvent {
960    fn eq(&self, other: &MouseEventKind) -> bool {
961        self.0.1.kind == *other
962    }
963}
964
965impl<W: Widget> PartialEq<Handle<W>> for OnMouseEvent {
966    fn eq(&self, other: &Handle<W>) -> bool {
967        self.0.0.ptr_eq(other.widget())
968    }
969}
970
971/// [`Hookable`]: Triggers whenever a [`Form`] is set
972///
973/// This can be a creation or alteration of a `Form`.
974/// If the `Form` is a reference to another, the reference's
975/// `Form` will be returned instead.
976///
977/// # Arguments
978///
979/// - The `Form`'s name.
980/// - Its [`FormId`].
981/// - Its new value.
982pub struct FormSet(pub(crate) (&'static str, FormId, Form));
983
984impl Hookable for FormSet {
985    type Input<'h> = (&'static str, FormId, Form);
986
987    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
988        (self.0.0, self.0.1, self.0.2)
989    }
990}
991
992/// [`Hookable`]: Triggers when a [`ColorScheme`] is set
993///
994/// Since [`Form`]s are set asynchronously, this may happen before the
995/// `ColorScheme` is done with its changes.
996///
997/// # Arguments
998///
999/// - The name of the `ColorScheme`
1000///
1001/// [`ColorScheme`]: crate::form::ColorScheme
1002pub struct ColorSchemeSet(pub(crate) &'static str);
1003
1004impl Hookable for ColorSchemeSet {
1005    type Input<'h> = &'static str;
1006
1007    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
1008        self.0
1009    }
1010}
1011
1012/// [`Hookable`]: Triggers after [`Handle::save`] or [`Handle::save_to`]
1013///
1014/// Only triggers if the buffer was actually updated.
1015///
1016/// # Arguments
1017///
1018/// - The [`Handle`] of said [`Buffer`]
1019/// - Wether the `Buffer` will be closed (happens when calling the
1020///   `wq` or `waq` commands)
1021pub struct BufferSaved(pub(crate) (Handle, bool));
1022
1023impl Hookable for BufferSaved {
1024    type Input<'h> = (&'h Handle, bool);
1025
1026    fn get_input<'h>(&'h mut self, _: &mut Pass) -> Self::Input<'h> {
1027        (&self.0.0, self.0.1)
1028    }
1029}
1030
1031/// A hookable struct, for hooks taking [`Hookable::Input`]
1032///
1033/// Through this trait, Duat allows for custom hookable structs. With
1034/// these structs, plugin creators can create their own custom hooks,
1035/// and trigger them via [`hook::trigger`].
1036///
1037/// This further empowers an end user to customize the behaviour of
1038/// Duat in the configuration crate.
1039///
1040/// [`hook::trigger`]: trigger
1041pub trait Hookable: Sized + 'static {
1042    /// The arguments that are passed to each hook.
1043    type Input<'h>;
1044    /// How to get the arguments from the [`Hookable`]
1045    ///
1046    /// This function is triggered once on every call that was added
1047    /// via [`hook::add`]. So if three hooks were added to
1048    /// [`BufferSaved`], for example, [`BufferSaved::get_input`]
1049    /// will be called three times, once before each hook.
1050    ///
1051    /// The vast majority of the time, this function is just a
1052    /// "getter", as it should take a copy, clone, or reference to the
1053    /// input type, which should be owned by the `Hookable`. For
1054    /// example, here's the definition of the [`KeyTyped`] hook:
1055    ///
1056    /// ```rust
1057    /// # duat_core::doc_duat!(duat);
1058    /// use duat::{hook::Hookable, mode::KeyEvent, prelude::*};
1059    ///
1060    /// struct KeyTyped(pub(crate) KeyEvent);
1061    ///
1062    /// impl Hookable for KeyTyped {
1063    ///     type Input<'h> = KeyEvent;
1064    ///
1065    ///     fn get_input<'h>(&'h mut self, pa: &mut Pass) -> Self::Input<'h> {
1066    ///         self.0
1067    ///     }
1068    /// }
1069    /// ```
1070    ///
1071    /// However, given the `&mut self` and `&mut Pass`, you can also
1072    /// do "inter hook mutations", in order to prepare for future hook
1073    /// calls. An example of this is on [`BufferUpdated`].
1074    ///
1075    /// Note that the [`Pass`] here is purely for internal use, you
1076    /// are not allowed to return something that borrows from it, as
1077    /// the borrow checker will prevent you.
1078    ///
1079    /// [`hook::add`]: add
1080    fn get_input<'h>(&'h mut self, pa: &mut Pass) -> Self::Input<'h>;
1081}
1082
1083/// Where all hooks of Duat are stored
1084#[derive(Clone, Copy)]
1085struct InnerHooks {
1086    types: &'static Mutex<HashMap<TypeId, Box<dyn HookHolder>>>,
1087    groups: &'static Mutex<Vec<InnerGroupId>>,
1088}
1089
1090impl InnerHooks {
1091    /// Adds a hook for a [`Hookable`]
1092    fn add<H: Hookable>(
1093        &self,
1094        callback: Box<dyn FnMut(&mut Pass, H::Input<'_>) + 'static>,
1095        group: Option<InnerGroupId>,
1096        filter: Option<Box<dyn Fn(&H) -> bool + Send + 'static>>,
1097        once: bool,
1098    ) {
1099        let mut map = self.types.lock().unwrap();
1100
1101        if let Some(group_id) = group.clone() {
1102            let mut groups = self.groups.lock().unwrap();
1103            if !groups.contains(&group_id) {
1104                groups.push(group_id)
1105            }
1106        }
1107
1108        if let Some(holder) = map.get(&TypeId::of::<H>()) {
1109            let hooks_of = unsafe {
1110                let ptr = (&**holder as *const dyn HookHolder).cast::<HooksOf<H>>();
1111                ptr.as_ref().unwrap()
1112            };
1113
1114            let mut hooks = hooks_of.0.borrow_mut();
1115            hooks.push(Hook {
1116                callback: Box::leak(Box::new(RefCell::new(callback))),
1117                group,
1118                filter,
1119                once,
1120            });
1121        } else {
1122            let hooks_of = HooksOf::<H>(RefCell::new(vec![Hook {
1123                callback: Box::leak(Box::new(RefCell::new(callback))),
1124                group,
1125                filter,
1126                once,
1127            }]));
1128
1129            map.insert(TypeId::of::<H>(), Box::new(hooks_of));
1130        }
1131    }
1132
1133    /// Removes hooks with said group
1134    fn remove(&self, group_id: InnerGroupId) {
1135        self.groups.lock().unwrap().retain(|g| *g != group_id);
1136        let map = self.types.lock().unwrap();
1137        for holder in map.iter() {
1138            holder.1.remove(&group_id)
1139        }
1140    }
1141
1142    /// Triggers hooks with args of the [`Hookable`]
1143    fn trigger<H: Hookable>(&self, pa: &mut Pass, mut hookable: H) -> H {
1144        let holder = self.types.lock().unwrap().remove(&TypeId::of::<H>());
1145
1146        let Some(holder) = holder else {
1147            return hookable;
1148        };
1149
1150        // SAFETY: HooksOf<H> is the only type that this HookHolder could be.
1151        let hooks_of = unsafe {
1152            let ptr = Box::into_raw(holder) as *mut HooksOf<H>;
1153            Box::from_raw(ptr)
1154        };
1155
1156        hooks_of.0.borrow_mut().retain_mut(|hook| {
1157            if let Some(filter) = hook.filter.as_ref()
1158                && !filter(&hookable)
1159            {
1160                return true;
1161            }
1162
1163            let input = hookable.get_input(pa);
1164            catch_panic(|| hook.callback.borrow_mut()(pa, input));
1165
1166            !hook.once
1167        });
1168
1169        let mut types = self.types.lock().unwrap();
1170        if let Some(new_holder) = types.remove(&TypeId::of::<H>()) {
1171            let new_hooks_of = unsafe {
1172                let ptr = Box::into_raw(new_holder) as *mut HooksOf<H>;
1173                Box::from_raw(ptr)
1174            };
1175
1176            hooks_of
1177                .0
1178                .borrow_mut()
1179                .extend(new_hooks_of.0.borrow_mut().drain(..));
1180
1181            types.insert(TypeId::of::<H>(), unsafe {
1182                Box::from_raw(Box::into_raw(hooks_of) as *mut dyn HookHolder)
1183            });
1184        } else {
1185            types.insert(TypeId::of::<H>(), unsafe {
1186                Box::from_raw(Box::into_raw(hooks_of) as *mut dyn HookHolder)
1187            });
1188        }
1189
1190        hookable
1191    }
1192
1193    /// Checks if a hook group exists
1194    fn group_exists(&self, group: InnerGroupId) -> bool {
1195        self.groups.lock().unwrap().contains(&group)
1196    }
1197}
1198
1199impl Default for InnerHooks {
1200    fn default() -> Self {
1201        Self {
1202            types: Box::leak(Box::default()),
1203            groups: Box::leak(Box::default()),
1204        }
1205    }
1206}
1207
1208unsafe impl Send for InnerHooks {}
1209unsafe impl Sync for InnerHooks {}
1210
1211/// An intermediary trait, meant for group removal
1212trait HookHolder {
1213    /// Remove the given group from hooks of this holder
1214    fn remove(&self, group_id: &InnerGroupId);
1215}
1216
1217/// An intermediary struct, meant to hold the hooks of a [`Hookable`]
1218struct HooksOf<H: Hookable>(RefCell<Vec<Hook<H>>>);
1219
1220impl<H: Hookable> HookHolder for HooksOf<H> {
1221    fn remove(&self, group_id: &InnerGroupId) {
1222        let mut hooks = self.0.borrow_mut();
1223        hooks.retain(|hook| hook.group.as_ref().is_none_or(|g| g != group_id));
1224    }
1225}
1226
1227struct Hook<H: Hookable> {
1228    callback: &'static RefCell<dyn FnMut(&mut Pass, <H as Hookable>::Input<'_>)>,
1229    group: Option<InnerGroupId>,
1230    filter: Option<Box<dyn Fn(&H) -> bool + Send + 'static>>,
1231    once: bool,
1232}