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