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> {}