Skip to main content

gpui_rhai/
native_handler.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::fmt;
4use std::rc::Rc;
5
6use gpui::{App, Window};
7use rhai::{CustomType, ImmutableString, TypeBuilder};
8use thiserror::Error;
9
10use crate::{EventResponse, UiRuntimeState, UiValue, ValueSchema};
11
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub struct NativeHandlerId(String);
14
15impl NativeHandlerId {
16    /// Parse a namespaced `snake_case` handler identifier.
17    ///
18    /// # Errors
19    ///
20    /// Returns [`NativeHandlerError::InvalidId`] for unsafe identifiers.
21    pub fn parse(value: impl Into<String>) -> Result<Self, NativeHandlerError> {
22        let value = value.into();
23        let valid = value
24            .split_once('.')
25            .is_some_and(|(namespace, name)| valid_segment(namespace) && valid_segment(name));
26        valid
27            .then_some(Self(value.clone()))
28            .ok_or(NativeHandlerError::InvalidId(value))
29    }
30
31    #[must_use]
32    pub fn as_str(&self) -> &str {
33        &self.0
34    }
35}
36
37impl fmt::Display for NativeHandlerId {
38    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
39        self.0.fmt(formatter)
40    }
41}
42
43fn valid_segment(value: &str) -> bool {
44    !value.is_empty()
45        && !value.starts_with('_')
46        && !value.ends_with('_')
47        && !value.contains("__")
48        && value.chars().all(|character| {
49            character.is_ascii_lowercase() || character.is_ascii_digit() || character == '_'
50        })
51}
52
53#[derive(Clone, Debug, PartialEq)]
54pub struct NativeHandlerDescriptor {
55    pub id: NativeHandlerId,
56    pub events: BTreeMap<String, ValueSchema>,
57}
58
59impl NativeHandlerDescriptor {
60    /// Construct a schema-checked native handler descriptor.
61    ///
62    /// # Errors
63    ///
64    /// Returns for empty event sets, unsafe names, or invalid payload schemas.
65    pub fn new(
66        id: NativeHandlerId,
67        events: BTreeMap<String, ValueSchema>,
68    ) -> Result<Self, NativeHandlerError> {
69        if events.is_empty() {
70            return Err(NativeHandlerError::MissingEvents(id));
71        }
72        for (event, schema) in &events {
73            if !valid_segment(event) {
74                return Err(NativeHandlerError::InvalidEvent(event.clone()));
75            }
76            schema
77                .validate_definition()
78                .map_err(|source| NativeHandlerError::InvalidSchema {
79                    event: event.clone(),
80                    source,
81                })?;
82        }
83        Ok(Self { id, events })
84    }
85}
86
87#[derive(Clone, Debug, PartialEq)]
88pub struct NativeHandlerRef {
89    descriptor: NativeHandlerDescriptor,
90}
91
92impl NativeHandlerRef {
93    #[must_use]
94    pub const fn descriptor(&self) -> &NativeHandlerDescriptor {
95        &self.descriptor
96    }
97
98    /// Validate that this ref may receive the named normalized event.
99    ///
100    /// # Errors
101    ///
102    /// Returns [`NativeHandlerError::UnsupportedEvent`] when not declared.
103    pub fn validate_event(&self, event: &str) -> Result<(), NativeHandlerError> {
104        self.descriptor
105            .events
106            .contains_key(event)
107            .then_some(())
108            .ok_or_else(|| NativeHandlerError::UnsupportedEvent {
109                handler: self.descriptor.id.clone(),
110                event: event.to_owned(),
111            })
112    }
113}
114
115impl CustomType for NativeHandlerRef {
116    fn build(mut builder: TypeBuilder<Self>) {
117        builder
118            .with_name("NativeHandlerRef")
119            .with_get("id", |reference: &mut Self| {
120                ImmutableString::from(reference.descriptor.id.as_str())
121            });
122    }
123}
124
125#[derive(Clone, Debug, PartialEq)]
126pub struct NativeEvent {
127    pub name: String,
128    pub payload: UiValue,
129    /// Event-time visual bounds of the retained node owning the handler.
130    pub target: Option<crate::GeometryBounds>,
131}
132
133type NativeHandlerFn = dyn FnMut(
134    NativeEvent,
135    &mut UiRuntimeState,
136    &mut Window,
137    &mut App,
138) -> Result<EventResponse, String>;
139
140#[derive(Clone)]
141struct RegisteredNativeHandler {
142    descriptor: NativeHandlerDescriptor,
143    handler: Rc<RefCell<NativeHandlerFn>>,
144}
145
146impl fmt::Debug for RegisteredNativeHandler {
147    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148        formatter
149            .debug_struct("RegisteredNativeHandler")
150            .field("descriptor", &self.descriptor)
151            .finish_non_exhaustive()
152    }
153}
154
155#[derive(Clone, Debug, Default)]
156pub struct NativeHandlerRegistry {
157    handlers: Rc<RefCell<BTreeMap<NativeHandlerId, RegisteredNativeHandler>>>,
158}
159
160impl NativeHandlerRegistry {
161    #[must_use]
162    pub fn new() -> Self {
163        Self::default()
164    }
165
166    /// Register one trusted foreground Rust handler.
167    ///
168    /// # Errors
169    ///
170    /// Returns [`NativeHandlerError::Duplicate`] for an existing ID.
171    pub fn register(
172        &self,
173        descriptor: NativeHandlerDescriptor,
174        handler: impl FnMut(
175            NativeEvent,
176            &mut UiRuntimeState,
177            &mut Window,
178            &mut App,
179        ) -> Result<EventResponse, String>
180        + 'static,
181    ) -> Result<(), NativeHandlerError> {
182        let id = descriptor.id.clone();
183        let mut handlers = self
184            .handlers
185            .try_borrow_mut()
186            .map_err(|_| NativeHandlerError::Borrowed)?;
187        if handlers.contains_key(&id) {
188            return Err(NativeHandlerError::Duplicate(id));
189        }
190        handlers.insert(
191            id,
192            RegisteredNativeHandler {
193                descriptor,
194                handler: Rc::new(RefCell::new(handler)),
195            },
196        );
197        Ok(())
198    }
199
200    /// Resolve a Rhai-attachable, non-owning reference.
201    ///
202    /// # Errors
203    ///
204    /// Returns [`NativeHandlerError::Missing`] for unregistered IDs.
205    pub fn resolve(&self, id: &NativeHandlerId) -> Result<NativeHandlerRef, NativeHandlerError> {
206        self.handlers
207            .try_borrow()
208            .map_err(|_| NativeHandlerError::Borrowed)?
209            .get(id)
210            .map(|registered| NativeHandlerRef {
211                descriptor: registered.descriptor.clone(),
212            })
213            .ok_or_else(|| NativeHandlerError::Missing(id.clone()))
214    }
215
216    /// Invoke one handler after validating its event payload schema.
217    ///
218    /// # Errors
219    ///
220    /// Returns schema, stale-reference, borrow, or handler errors.
221    pub fn invoke(
222        &self,
223        reference: &NativeHandlerRef,
224        event: NativeEvent,
225        runtime: &mut UiRuntimeState,
226        window: &mut Window,
227        app: &mut App,
228    ) -> Result<EventResponse, NativeHandlerError> {
229        let registered = self
230            .handlers
231            .try_borrow()
232            .map_err(|_| NativeHandlerError::Borrowed)?
233            .get(&reference.descriptor.id)
234            .cloned()
235            .ok_or_else(|| NativeHandlerError::Missing(reference.descriptor.id.clone()))?;
236        if registered.descriptor != reference.descriptor {
237            return Err(NativeHandlerError::Stale(reference.descriptor.id.clone()));
238        }
239        let schema = registered
240            .descriptor
241            .events
242            .get(&event.name)
243            .ok_or_else(|| NativeHandlerError::UnsupportedEvent {
244                handler: registered.descriptor.id.clone(),
245                event: event.name.clone(),
246            })?;
247        schema.validate_ui_value(&event.payload).map_err(|source| {
248            NativeHandlerError::InvalidPayload {
249                event: event.name.clone(),
250                source,
251            }
252        })?;
253        registered
254            .handler
255            .try_borrow_mut()
256            .map_err(|_| NativeHandlerError::Borrowed)?(event, runtime, window, app)
257        .map_err(NativeHandlerError::Handler)
258    }
259}
260
261#[derive(Debug, Error)]
262pub enum NativeHandlerError {
263    #[error("native handler ID `{0}` must be namespaced snake_case")]
264    InvalidId(String),
265    #[error("native handler `{0}` must declare at least one event")]
266    MissingEvents(NativeHandlerId),
267    #[error("native handler event `{0}` must be snake_case")]
268    InvalidEvent(String),
269    #[error("native handler schema for `{event}` is invalid: {source}")]
270    InvalidSchema {
271        event: String,
272        source: crate::SchemaDefinitionError,
273    },
274    #[error("native handler `{handler}` does not accept event `{event}`")]
275    UnsupportedEvent {
276        handler: NativeHandlerId,
277        event: String,
278    },
279    #[error("native handler `{0}` is already registered")]
280    Duplicate(NativeHandlerId),
281    #[error("native handler `{0}` is not registered")]
282    Missing(NativeHandlerId),
283    #[error("native handler ref `{0}` no longer matches its registration")]
284    Stale(NativeHandlerId),
285    #[error("native handler payload for `{event}` is invalid: {source}")]
286    InvalidPayload {
287        event: String,
288        source: crate::SchemaValidationError,
289    },
290    #[error("native handler registry or handler is already borrowed")]
291    Borrowed,
292    #[error("native handler failed: {0}")]
293    Handler(String),
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn descriptor_rejects_unsafe_ids_and_events() {
302        assert!(NativeHandlerId::parse("drag").is_err());
303        let id = NativeHandlerId::parse("timeline.drag").unwrap();
304        assert!(NativeHandlerDescriptor::new(id, BTreeMap::new()).is_err());
305    }
306}