Skip to content

Commit e61581a

Browse files
committed
feat: add codegen for event type definitions
1 parent c0495db commit e61581a

3 files changed

Lines changed: 429 additions & 0 deletions

File tree

rust/src/bin/codegen.rs

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
//! Code generator for TypeScript event types
2+
//! Generates TypeScript definitions from Rust event_types module
3+
//!
4+
//! Run with: cargo run --bin codegen
5+
6+
use std::fs;
7+
use std::path::Path;
8+
9+
/// Event type definition
10+
struct EventDef {
11+
prop_name: &'static str,
12+
event_type: &'static str,
13+
}
14+
15+
/// All event definitions - single source of truth
16+
const EVENT_DEFINITIONS: &[EventDef] = &[
17+
EventDef { prop_name: "onClick", event_type: "click" },
18+
EventDef { prop_name: "onDoubleClick", event_type: "dblclick" },
19+
EventDef { prop_name: "onMouseDown", event_type: "mousedown" },
20+
EventDef { prop_name: "onMouseUp", event_type: "mouseup" },
21+
EventDef { prop_name: "onMouseMove", event_type: "mousemove" },
22+
EventDef { prop_name: "onMouseEnter", event_type: "mouseenter" },
23+
EventDef { prop_name: "onMouseLeave", event_type: "mouseleave" },
24+
EventDef { prop_name: "onHover", event_type: "hover" },
25+
EventDef { prop_name: "onKeyDown", event_type: "keydown" },
26+
EventDef { prop_name: "onKeyUp", event_type: "keyup" },
27+
EventDef { prop_name: "onKeyPress", event_type: "keypress" },
28+
EventDef { prop_name: "onFocus", event_type: "focus" },
29+
EventDef { prop_name: "onBlur", event_type: "blur" },
30+
EventDef { prop_name: "onScroll", event_type: "scroll" },
31+
EventDef { prop_name: "onWheel", event_type: "wheel" },
32+
];
33+
34+
/// Additional event types that don't have props (internal events)
35+
const INTERNAL_EVENT_TYPES: &[&str] = &["focusin", "focusout"];
36+
37+
fn generate_typescript() -> String {
38+
let mut output = String::new();
39+
40+
// Header
41+
output.push_str("/**\n");
42+
output.push_str(" * Auto-generated event types - DO NOT EDIT\n");
43+
output.push_str(" * Generated by: cargo run --bin codegen\n");
44+
output.push_str(" * Source: rust/src/bin/codegen.rs\n");
45+
output.push_str(" */\n\n");
46+
47+
// Event type union
48+
output.push_str("/** All GPUI event types */\n");
49+
output.push_str("export type GPUIEventType =\n");
50+
for (i, def) in EVENT_DEFINITIONS.iter().enumerate() {
51+
output.push_str(&format!(" | \"{}\"", def.event_type));
52+
if i < EVENT_DEFINITIONS.len() - 1 || !INTERNAL_EVENT_TYPES.is_empty() {
53+
output.push('\n');
54+
}
55+
}
56+
for (i, event_type) in INTERNAL_EVENT_TYPES.iter().enumerate() {
57+
output.push_str(&format!(" | \"{}\"", event_type));
58+
if i < INTERNAL_EVENT_TYPES.len() - 1 {
59+
output.push('\n');
60+
}
61+
}
62+
output.push_str(";\n\n");
63+
64+
// Event prop names union
65+
output.push_str("/** React-style event handler prop names */\n");
66+
output.push_str("export type GPUIEventPropName =\n");
67+
for (i, def) in EVENT_DEFINITIONS.iter().enumerate() {
68+
output.push_str(&format!(" | \"{}\"", def.prop_name));
69+
if i < EVENT_DEFINITIONS.len() - 1 {
70+
output.push('\n');
71+
}
72+
}
73+
output.push_str(";\n\n");
74+
75+
// Prop to event type mapping
76+
output.push_str("/** Maps React prop names to event types */\n");
77+
output.push_str("export const EVENT_PROP_TO_TYPE = {\n");
78+
for def in EVENT_DEFINITIONS {
79+
output.push_str(&format!(" {}: \"{}\",\n", def.prop_name, def.event_type));
80+
}
81+
output.push_str("} as const;\n\n");
82+
83+
// Event type to prop mapping
84+
output.push_str("/** Maps event types to React prop names */\n");
85+
output.push_str("export const EVENT_TYPE_TO_PROP = {\n");
86+
for def in EVENT_DEFINITIONS {
87+
output.push_str(&format!(" {}: \"{}\",\n", def.event_type, def.prop_name));
88+
}
89+
output.push_str("} as const;\n\n");
90+
91+
// Helper function
92+
output.push_str("/** Check if a prop name is an event handler */\n");
93+
output.push_str("export function isEventHandlerProp(prop: string): prop is GPUIEventPropName {\n");
94+
output.push_str(" return prop in EVENT_PROP_TO_TYPE;\n");
95+
output.push_str("}\n\n");
96+
97+
// Event type categories
98+
output.push_str("/** Mouse event types */\n");
99+
output.push_str("export const MOUSE_EVENT_TYPES = [\n");
100+
for def in EVENT_DEFINITIONS.iter().filter(|d| {
101+
d.event_type.starts_with("mouse")
102+
|| d.event_type == "click"
103+
|| d.event_type == "dblclick"
104+
|| d.event_type == "hover"
105+
}) {
106+
output.push_str(&format!(" \"{}\",\n", def.event_type));
107+
}
108+
output.push_str("] as const;\n\n");
109+
110+
output.push_str("/** Keyboard event types */\n");
111+
output.push_str("export const KEYBOARD_EVENT_TYPES = [\n");
112+
for def in EVENT_DEFINITIONS.iter().filter(|d| d.event_type.starts_with("key")) {
113+
output.push_str(&format!(" \"{}\",\n", def.event_type));
114+
}
115+
output.push_str("] as const;\n\n");
116+
117+
output.push_str("/** Focus event types */\n");
118+
output.push_str("export const FOCUS_EVENT_TYPES = [\n");
119+
for def in EVENT_DEFINITIONS.iter().filter(|d| {
120+
d.event_type == "focus" || d.event_type == "blur"
121+
}) {
122+
output.push_str(&format!(" \"{}\",\n", def.event_type));
123+
}
124+
for event_type in INTERNAL_EVENT_TYPES.iter().filter(|t| {
125+
t.starts_with("focus")
126+
}) {
127+
output.push_str(&format!(" \"{}\",\n", event_type));
128+
}
129+
output.push_str("] as const;\n\n");
130+
131+
output.push_str("/** Scroll event types */\n");
132+
output.push_str("export const SCROLL_EVENT_TYPES = [\n");
133+
for def in EVENT_DEFINITIONS.iter().filter(|d| {
134+
d.event_type == "scroll" || d.event_type == "wheel"
135+
}) {
136+
output.push_str(&format!(" \"{}\",\n", def.event_type));
137+
}
138+
output.push_str("] as const;\n");
139+
140+
output
141+
}
142+
143+
fn generate_rust_event_types() -> String {
144+
let mut output = String::new();
145+
146+
// Header
147+
output.push_str("//! Auto-generated event type constants - DO NOT EDIT\n");
148+
output.push_str("//! Generated by: cargo run --bin codegen\n");
149+
output.push_str("//! Source: rust/src/bin/codegen.rs\n\n");
150+
output.push_str("#![allow(dead_code)] // Many constants are defined for completeness and code generation\n\n");
151+
152+
// Props module
153+
output.push_str("/// Maps React-style prop names to standard event type names\n");
154+
output.push_str("/// Used when checking if an element has a handler registered\n");
155+
output.push_str("pub mod props {\n");
156+
for def in EVENT_DEFINITIONS {
157+
let const_name = prop_to_const_name(def.prop_name);
158+
output.push_str(&format!(" pub const {}: &str = \"{}\";\n", const_name, def.prop_name));
159+
}
160+
output.push_str("}\n\n");
161+
162+
// Types module
163+
output.push_str("/// Standard event type names dispatched to JavaScript\n");
164+
output.push_str("/// These match the GPUIEventType in TypeScript\n");
165+
output.push_str("pub mod types {\n");
166+
for def in EVENT_DEFINITIONS {
167+
let const_name = event_type_to_const_name(def.event_type);
168+
output.push_str(&format!(" pub const {}: &str = \"{}\";\n", const_name, def.event_type));
169+
}
170+
for event_type in INTERNAL_EVENT_TYPES {
171+
let const_name = event_type_to_const_name(event_type);
172+
output.push_str(&format!(" pub const {}: &str = \"{}\";\n", const_name, event_type));
173+
}
174+
output.push_str("}\n\n");
175+
176+
// Conversion function
177+
output.push_str("/// Convert prop name to event type\n");
178+
output.push_str("/// Returns None if the prop is not a recognized event handler\n");
179+
output.push_str("pub fn prop_to_event_type(prop: &str) -> Option<&'static str> {\n");
180+
output.push_str(" match prop {\n");
181+
for def in EVENT_DEFINITIONS {
182+
let prop_const = prop_to_const_name(def.prop_name);
183+
let type_const = event_type_to_const_name(def.event_type);
184+
output.push_str(&format!(" props::{} => Some(types::{}),\n", prop_const, type_const));
185+
}
186+
output.push_str(" _ => None,\n");
187+
output.push_str(" }\n");
188+
output.push_str("}\n");
189+
190+
output
191+
}
192+
193+
/// Convert prop name like "onClick" to const name like "ON_CLICK"
194+
fn prop_to_const_name(prop: &str) -> String {
195+
let mut result = String::new();
196+
for (i, c) in prop.chars().enumerate() {
197+
if c.is_uppercase() {
198+
if i > 0 {
199+
result.push('_');
200+
}
201+
result.push(c.to_ascii_uppercase());
202+
} else {
203+
result.push(c.to_ascii_uppercase());
204+
}
205+
}
206+
result
207+
}
208+
209+
/// Convert event type like "mousedown" to const name like "MOUSEDOWN"
210+
fn event_type_to_const_name(event_type: &str) -> String {
211+
event_type.to_uppercase()
212+
}
213+
214+
fn main() {
215+
// Get project root (assumes we're running from rust/ directory or project root)
216+
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
217+
.unwrap_or_else(|_| ".".to_string());
218+
let rust_dir = Path::new(&manifest_dir);
219+
let project_root = rust_dir.parent().unwrap_or(rust_dir);
220+
221+
// Generate TypeScript file
222+
let ts_output = generate_typescript();
223+
let ts_path = project_root.join("src/events/generated.ts");
224+
225+
if let Some(parent) = ts_path.parent() {
226+
fs::create_dir_all(parent).expect("Failed to create events directory");
227+
}
228+
229+
fs::write(&ts_path, &ts_output).expect("Failed to write TypeScript file");
230+
println!("Generated: {}", ts_path.display());
231+
232+
// Generate Rust file
233+
let rust_output = generate_rust_event_types();
234+
let rust_path = rust_dir.join("src/event_types.rs");
235+
236+
fs::write(&rust_path, &rust_output).expect("Failed to write Rust file");
237+
println!("Generated: {}", rust_path.display());
238+
239+
println!("\nDone! Event type definitions are now synchronized.");
240+
}

rust/src/event_types.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Auto-generated event type constants - DO NOT EDIT
2+
//! Generated by: cargo run --bin codegen
3+
//! Source: rust/src/bin/codegen.rs
4+
5+
#![allow(dead_code)] // Many constants are defined for completeness and code generation
6+
7+
/// Maps React-style prop names to standard event type names
8+
/// Used when checking if an element has a handler registered
9+
pub mod props {
10+
pub const ON_CLICK: &str = "onClick";
11+
pub const ON_DOUBLE_CLICK: &str = "onDoubleClick";
12+
pub const ON_MOUSE_DOWN: &str = "onMouseDown";
13+
pub const ON_MOUSE_UP: &str = "onMouseUp";
14+
pub const ON_MOUSE_MOVE: &str = "onMouseMove";
15+
pub const ON_MOUSE_ENTER: &str = "onMouseEnter";
16+
pub const ON_MOUSE_LEAVE: &str = "onMouseLeave";
17+
pub const ON_HOVER: &str = "onHover";
18+
pub const ON_KEY_DOWN: &str = "onKeyDown";
19+
pub const ON_KEY_UP: &str = "onKeyUp";
20+
pub const ON_KEY_PRESS: &str = "onKeyPress";
21+
pub const ON_FOCUS: &str = "onFocus";
22+
pub const ON_BLUR: &str = "onBlur";
23+
pub const ON_SCROLL: &str = "onScroll";
24+
pub const ON_WHEEL: &str = "onWheel";
25+
}
26+
27+
/// Standard event type names dispatched to JavaScript
28+
/// These match the GPUIEventType in TypeScript
29+
pub mod types {
30+
pub const CLICK: &str = "click";
31+
pub const DBLCLICK: &str = "dblclick";
32+
pub const MOUSEDOWN: &str = "mousedown";
33+
pub const MOUSEUP: &str = "mouseup";
34+
pub const MOUSEMOVE: &str = "mousemove";
35+
pub const MOUSEENTER: &str = "mouseenter";
36+
pub const MOUSELEAVE: &str = "mouseleave";
37+
pub const HOVER: &str = "hover";
38+
pub const KEYDOWN: &str = "keydown";
39+
pub const KEYUP: &str = "keyup";
40+
pub const KEYPRESS: &str = "keypress";
41+
pub const FOCUS: &str = "focus";
42+
pub const BLUR: &str = "blur";
43+
pub const SCROLL: &str = "scroll";
44+
pub const WHEEL: &str = "wheel";
45+
pub const FOCUSIN: &str = "focusin";
46+
pub const FOCUSOUT: &str = "focusout";
47+
}
48+
49+
/// Convert prop name to event type
50+
/// Returns None if the prop is not a recognized event handler
51+
pub fn prop_to_event_type(prop: &str) -> Option<&'static str> {
52+
match prop {
53+
props::ON_CLICK => Some(types::CLICK),
54+
props::ON_DOUBLE_CLICK => Some(types::DBLCLICK),
55+
props::ON_MOUSE_DOWN => Some(types::MOUSEDOWN),
56+
props::ON_MOUSE_UP => Some(types::MOUSEUP),
57+
props::ON_MOUSE_MOVE => Some(types::MOUSEMOVE),
58+
props::ON_MOUSE_ENTER => Some(types::MOUSEENTER),
59+
props::ON_MOUSE_LEAVE => Some(types::MOUSELEAVE),
60+
props::ON_HOVER => Some(types::HOVER),
61+
props::ON_KEY_DOWN => Some(types::KEYDOWN),
62+
props::ON_KEY_UP => Some(types::KEYUP),
63+
props::ON_KEY_PRESS => Some(types::KEYPRESS),
64+
props::ON_FOCUS => Some(types::FOCUS),
65+
props::ON_BLUR => Some(types::BLUR),
66+
props::ON_SCROLL => Some(types::SCROLL),
67+
props::ON_WHEEL => Some(types::WHEEL),
68+
_ => None,
69+
}
70+
}

0 commit comments

Comments
 (0)