|
1 | 1 | //! `helix-event` contains systems that allow (often async) communication between
|
2 |
| -//! different editor components without strongly coupling them. Currently this |
3 |
| -//! crate only contains some smaller facilities but the intend is to add more |
4 |
| -//! functionality in the future ( like a generic hook system) |
| 2 | +//! different editor components without strongly coupling them. Specifically |
| 3 | +//! it allows defining synchronous hooks that run when certain editor events |
| 4 | +//! occurs. |
| 5 | +//! |
| 6 | +//! The core of the event system is the [`Hook`] trait. A hook is essentially |
| 7 | +//! just a closure `Fn(event: &mut impl Event) -> Result<()>`. This can currently |
| 8 | +//! not be represented in the rust type system with closures (it requires second |
| 9 | +//! order generics). Instead we use generic associated types to represent that |
| 10 | +//! invariant so a custom type is always required. |
| 11 | +//! |
| 12 | +//! The [`Event`] trait is unsafe because upon dispatch event lifetimes are |
| 13 | +//! essentially erased. To ensure safety all lifetime parameters of the event |
| 14 | +//! must oulife the lifetime Parameter of the event trait. To avoid worrying about |
| 15 | +//! that (and spreading unsafe everywhere) the [`events`] macro is provided which |
| 16 | +//! automatically declares event types. |
| 17 | +//! |
| 18 | +//! Hooks run synchronously which can be advantageous since they can modify the |
| 19 | +//! current editor state right away (for example to immidietly hide the completion |
| 20 | +//! popup). However, they can not contain their own state without locking since |
| 21 | +//! they only receive immutable references. For handler that want to track state, do |
| 22 | +//! expensive background computations or debouncing an [`AsyncHook`] is preferable. |
| 23 | +//! Async hooks are based around a channels that receive events specific to |
| 24 | +//! that `AsyncHook` (usually an enum). These events can be send by synchronous |
| 25 | +//! [`Hook`]s. Due to some limtations around tokio channels the [`send_blocking`] |
| 26 | +//! function exported in this crate should be used instead of the builtin |
| 27 | +//! `blocking_send`. |
| 28 | +//! |
| 29 | +//! In addition to the core event system, this crate contains some message queues |
| 30 | +//! that allow transfer of data back to the main event loop from async hooks and |
| 31 | +//! hooks that may not have access to all application data (for example in helix-view). |
| 32 | +//! This include the ability to control rendering ([`lock_frame`], [`request_redraw`]) and |
| 33 | +//! display status messages ([`status`]). |
| 34 | +//! |
| 35 | +//! Hooks declared in helix-term can furthermore dispatch synchronous jobs to be run on the |
| 36 | +//! main loop (including access to the compositor). Ideally that queue will be moved |
| 37 | +//! to helix-view in the future if we manage to detch the comositor from its rendering backgend. |
5 | 38 |
|
| 39 | +use anyhow::Result; |
| 40 | +pub use cancel::{canceable_future, cancelation, CancelRx, CancelTx}; |
| 41 | +pub use debounce::{send_blocking, AsyncHook}; |
6 | 42 | pub use redraw::{lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard};
|
| 43 | +pub use registry::Event; |
7 | 44 |
|
| 45 | +mod cancel; |
| 46 | +mod debounce; |
| 47 | +mod hook; |
8 | 48 | mod redraw;
|
| 49 | +mod registry; |
| 50 | +#[doc(hidden)] |
| 51 | +pub mod runtime; |
| 52 | +pub mod status; |
| 53 | + |
| 54 | +#[cfg(test)] |
| 55 | +mod test; |
| 56 | + |
| 57 | +/// A hook is a colsure that will be automatically callen whenever |
| 58 | +/// an `Event` of the associated function is [dispatched](crate::dispatch) |
| 59 | +/// is called. The closure must be generic over the lifetime of the event. |
| 60 | +pub trait Hook: Sized + Sync + Send + 'static { |
| 61 | + type Event<'a>: Event<'a>; |
| 62 | + fn run(&self, _event: &mut Self::Event<'_>) -> Result<()>; |
| 63 | +} |
| 64 | + |
| 65 | +pub fn register_event<E: Event<'static>>() { |
| 66 | + registry::with_mut(|registry| registry.register_event::<E>()) |
| 67 | +} |
| 68 | + |
| 69 | +pub fn register_hook(hook: impl Hook) { |
| 70 | + registry::with_mut(|registry| registry.register_hook(hook)) |
| 71 | +} |
| 72 | + |
| 73 | +pub fn register_dynamic_hook<H: Fn() + Sync + Send + 'static>(hook: H, id: &str) -> Result<()> { |
| 74 | + registry::with_mut(|reg| reg.register_dynamic_hook(hook, id)) |
| 75 | +} |
| 76 | + |
| 77 | +pub fn dispatch<'a>(e: impl Event<'a>) { |
| 78 | + registry::with(|registry| registry.dispatch(e)); |
| 79 | +} |
| 80 | + |
| 81 | +/// Macro to delclare events |
| 82 | +/// |
| 83 | +/// # Examples |
| 84 | +/// |
| 85 | +/// ``` no-compile |
| 86 | +/// events! { |
| 87 | +/// FileWrite(&Path) |
| 88 | +/// ViewScrolled{ view: View, new_pos: ViewOffset } |
| 89 | +/// DocumentChanged<'a> { old_doc: &'a Rope, doc: &'a mut Document, changes: &'a ChangSet } |
| 90 | +/// } |
| 91 | +/// |
| 92 | +/// fn init() { |
| 93 | +/// register_event::<FileWrite>(); |
| 94 | +/// register_event::<ViewScrolled>(); |
| 95 | +/// register_event::<InsertChar>(); |
| 96 | +/// register_event::<DocumentChanged>(); |
| 97 | +/// } |
| 98 | +/// |
| 99 | +/// fn save(path: &Path, content: &str){ |
| 100 | +/// std::fs::write(path, content); |
| 101 | +/// dispach(FilWrite(path)); |
| 102 | +/// } |
| 103 | +/// ``` |
| 104 | +#[macro_export] |
| 105 | +macro_rules! events { |
| 106 | + ($name: ident($($data: ty),*) $($rem:tt)*) => { |
| 107 | + pub struct $name($(pub $data),*); |
| 108 | + unsafe impl<'a> $crate::Event<'a> for $name { |
| 109 | + const ID: &'static str = stringify!($name); |
| 110 | + type Static = Self; |
| 111 | + } |
| 112 | + $crate::events!{ $($rem)* } |
| 113 | + }; |
| 114 | + ($name: ident<$lt: lifetime>($(pub $data: ty),*) $($rem:tt)*) => { |
| 115 | + pub struct $name<$lt>($($data),*); |
| 116 | + unsafe impl<$lt> $crate::Event<$lt> for $name<$lt> { |
| 117 | + const ID: &'static str = stringify!($name); |
| 118 | + type Static = $name<'static>; |
| 119 | + } |
| 120 | + $crate::events!{ $($rem)* } |
| 121 | + }; |
| 122 | + ($name: ident<$lt: lifetime> { $($data:ident : $data_ty:ty),* } $($rem:tt)*) => { |
| 123 | + pub struct $name<$lt> { $(pub $data: $data_ty),* } |
| 124 | + unsafe impl<$lt> $crate::Event<$lt> for $name<$lt> { |
| 125 | + const ID: &'static str = stringify!($name); |
| 126 | + type Static = $name<'static>; |
| 127 | + } |
| 128 | + $crate::events!{ $($rem)* } |
| 129 | + }; |
| 130 | + ($name: ident<$lt1: lifetime, $lt2: lifetime> { $($data:ident : $data_ty:ty),* } $($rem:tt)*) => { |
| 131 | + pub struct $name<$lt1, $lt2> { $(pub $data: $data_ty),* } |
| 132 | + unsafe impl<$lt1, $lt2: $lt1> $crate::Event<$lt1> for $name<$lt1, $lt2> { |
| 133 | + const ID: &'static str = stringify!($name); |
| 134 | + type Static = $name<'static, 'static>; |
| 135 | + } |
| 136 | + $crate::events!{ $($rem)* } |
| 137 | + }; |
| 138 | + ($name: ident { $($data:ident : $data_ty:ty),* } $($rem:tt)*) => { |
| 139 | + pub struct $name { $(pub $data: $data_ty),* } |
| 140 | + unsafe impl<'a> $crate::Event<'a> for $name { |
| 141 | + const ID: &'static str = stringify!($name); |
| 142 | + type Static = Self; |
| 143 | + } |
| 144 | + $crate::events!{ $($rem)* } |
| 145 | + }; |
| 146 | + () => {}; |
| 147 | +} |
0 commit comments