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