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 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 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 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 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 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 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 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}