summaryrefslogtreecommitdiff
path: root/helix-event/src/lib.rs
blob: 894de5e8d6287d80fca6db76c515db64b29fed6b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! `helix-event` contains systems that allow (often async) communication between
//! different editor components without strongly coupling them. Specifically
//! it allows defining synchronous hooks that run when certain editor events
//! occur.
//!
//! The core of the event system are hook callbacks and the [`Event`] trait. A
//! hook is essentially just a closure `Fn(event: &mut impl Event) -> Result<()>`
//! that gets called every time an appropriate event is dispatched. The implementation
//! details of the [`Event`] trait are considered private. The [`events`] macro is
//! provided which automatically declares event types. Similarly the `register_hook`
//! macro should be used to (safely) declare event hooks.
//!
//! Hooks run synchronously which can be advantageous since they can modify the
//! current editor state right away (for example to immediately hide the completion
//! popup). However, they can not contain their own state without locking since
//! they only receive immutable references. For handler that want to track state, do
//! expensive background computations or debouncing an [`AsyncHook`] is preferable.
//! Async hooks are based around a channels that receive events specific to
//! that `AsyncHook` (usually an enum). These events can be sent by synchronous
//! hooks. Due to some limitations around tokio channels the [`send_blocking`]
//! function exported in this crate should be used instead of the builtin
//! `blocking_send`.
//!
//! In addition to the core event system, this crate contains some message queues
//! that allow transfer of data back to the main event loop from async hooks and
//! hooks that may not have access to all application data (for example in helix-view).
//! This include the ability to control rendering ([`lock_frame`], [`request_redraw`]) and
//! display status messages ([`status`]).
//!
//! Hooks declared in helix-term can furthermore dispatch synchronous jobs to be run on the
//! main loop (including access to the compositor). Ideally that queue will be moved
//! to helix-view in the future if we manage to detach the compositor from its rendering backend.

use anyhow::Result;
pub use cancel::{cancelable_future, cancelation, CancelRx, CancelTx};
pub use debounce::{send_blocking, AsyncHook};
pub use redraw::{lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard};
pub use registry::Event;

mod cancel;
mod debounce;
mod hook;
mod redraw;
mod registry;
#[doc(hidden)]
pub mod runtime;
pub mod status;

#[cfg(test)]
mod test;

pub fn register_event<E: Event + 'static>() {
    registry::with_mut(|registry| registry.register_event::<E>())
}

/// Registers a hook that will be called when an event of type `E` is dispatched.
/// This function should usually not be used directly, use the [`register_hook`]
/// macro instead.
///
///
/// # Safety
///
/// `hook` must be totally generic over all lifetime parameters of `E`. For
/// example if `E` was a known type `Foo<'a, 'b>`, then the correct trait bound
/// would be `F: for<'a, 'b, 'c> Fn(&'a mut Foo<'b, 'c>)`, but there is no way to
/// express that kind of constraint for a generic type with the Rust type system
/// as of this writing.
pub unsafe fn register_hook_raw<E: Event>(
    hook: impl Fn(&mut E) -> Result<()> + 'static + Send + Sync,
) {
    registry::with_mut(|registry| registry.register_hook(hook))
}

/// Register a hook solely by event name
pub fn register_dynamic_hook(
    hook: impl Fn() -> Result<()> + 'static + Send + Sync,
    id: &str,
) -> Result<()> {
    registry::with_mut(|reg| reg.register_dynamic_hook(hook, id))
}

pub fn dispatch(e: impl Event) {
    registry::with(|registry| registry.dispatch(e));
}

/// Macro to declare events
///
/// # Examples
///
/// ``` no-compile
/// events! {
///     FileWrite(&Path)
///     ViewScrolled{ view: View, new_pos: ViewOffset }
///     DocumentChanged<'a> { old_doc: &'a Rope, doc: &'a mut Document, changes: &'a ChangeSet  }
/// }
///
/// fn init() {
///    register_event::<FileWrite>();
///    register_event::<ViewScrolled>();
///    register_event::<DocumentChanged>();
/// }
///
/// fn save(path: &Path, content: &str){
///     std::fs::write(path, content);
///     dispatch(FileWrite(path));
/// }
/// ```
#[macro_export]
macro_rules! events {
    ($name: ident<$($lt: lifetime),*> { $($data:ident : $data_ty:ty),* } $($rem:tt)*) => {
        pub struct $name<$($lt),*> { $(pub $data: $data_ty),* }
        unsafe impl<$($lt),*> $crate::Event for $name<$($lt),*> {
            const ID: &'static str = stringify!($name);
            const LIFETIMES: usize = $crate::events!(@sum $(1, $lt),*);
            type Static = $crate::events!(@replace_lt $name, $('static, $lt),*);
        }
        $crate::events!{ $($rem)* }
    };
    ($name: ident { $($data:ident : $data_ty:ty),* } $($rem:tt)*) => {
        pub struct $name { $(pub $data: $data_ty),* }
        unsafe impl $crate::Event for $name {
            const ID: &'static str = stringify!($name);
            const LIFETIMES: usize = 0;
            type Static = Self;
        }
        $crate::events!{ $($rem)* }
    };
    () => {};
    (@replace_lt $name: ident, $($lt1: lifetime, $lt2: lifetime),* ) => {$name<$($lt1),*>};
    (@sum $($val: expr, $lt1: lifetime),* ) => {0 $(+ $val)*};
}

/// Safely register statically typed event hooks
#[macro_export]
macro_rules! register_hook {
    // Safety: this is safe because we fully control the type of the event here and
    // ensure all lifetime arguments are fully generic and the correct number of lifetime arguments
    // is present
    (move |$event:ident: &mut $event_ty: ident<$($lt: lifetime),*>| $body: expr) => {
        let val = move |$event: &mut $event_ty<$($lt),*>| $body;
        unsafe {
            // Lifetimes are a bit of a pain. We want to allow events being
            // non-static. Lifetimes don't actually exist at runtime so its
            // fine to essentially transmute the lifetimes as long as we can
            // prove soundness. The hook must therefore accept any combination
            // of lifetimes. In other words fn(&'_ mut Event<'_, '_>) is ok
            // but examples like fn(&'_ mut Event<'_, 'static>) or fn<'a>(&'a
            // mut Event<'a, 'a>) are not. To make this safe we use a macro to
            // forbid the user from specifying lifetimes manually (all lifetimes
            // specified are always function generics and passed to the event so
            // lifetimes can't be used multiple times and using 'static causes a
            // syntax error).
            //
            // There is one soundness hole tough: Type Aliases allow
            // "accidentally" creating these problems. For example:
            //
            // type Event2  = Event<'static>.
            // type Event2<'a>  = Event<'a, a>.
            //
            // These cases can be caught by counting the number of lifetimes
            // parameters at the parameter declaration site and then at the hook
            // declaration site. By asserting the number of lifetime parameters
            // are equal we can catch all bad type aliases under one assumption:
            // There are no unused lifetime parameters. Introducing a static
            // would reduce the number of arguments of the alias by one in the
            // above example Event2 has zero lifetime arguments while the original
            // event has one lifetime argument. Similar logic applies to using
            // a lifetime argument multiple times. The ASSERT below performs a
            // a compile time assertion to ensure exactly this property.
            //
            // With unused lifetime arguments it is still one way to cause unsound code:
            //
            // type Event2<'a, 'b> = Event<'a, 'a>;
            //
            // However, this case will always emit a compiler warning/cause CI
            // failures so a user would have to introduce #[allow(unused)] which
            // is easily caught in review (and a very theoretical case anyway).
            // If we want to be pedantic we can simply compile helix with
            // forbid(unused). All of this is just a safety net to prevent
            // very theoretical misuse. This won't come up in real code (and is
            // easily caught in review).
            #[allow(unused)]
            const ASSERT: () = {
                if <$event_ty as $crate::Event>::LIFETIMES != 0 + $crate::events!(@sum $(1, $lt),*){
                    panic!("invalid type alias");
                }
            };
            $crate::register_hook_raw::<$crate::events!(@replace_lt $event_ty, $('static, $lt),*)>(val);
        }
    };
    (move |$event:ident: &mut $event_ty: ident| $body: expr) => {
        let val = move |$event: &mut $event_ty| $body;
        unsafe {
            #[allow(unused)]
            const ASSERT: () = {
                if <$event_ty as $crate::Event>::LIFETIMES != 0{
                    panic!("invalid type alias");
                }
            };
            $crate::register_hook_raw::<$event_ty>(val);
        }
    };
}