bubbletea_rs/event.rs
1//! This module defines the various message types used in `bubbletea-rs`.
2//! Messages are events that trigger updates in your application's model.
3//! They are typically sent by commands or the input handler.
4
5use std::any::Any;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::OnceLock;
8use tokio::sync::mpsc;
9use tokio_util::sync::CancellationToken;
10
11/// A message represents any event that can trigger a model update.
12///
13/// `Msg` is a type alias for `Box<dyn Any + Send>`, allowing it to hold
14/// any type that implements `Any` and `Send`. This provides flexibility
15/// in defining custom message types for your application.
16pub type Msg = Box<dyn Any + Send>;
17
18/// Event sender abstraction that can be either bounded or unbounded.
19///
20/// `EventSender` provides a unified interface for sending messages through
21/// either bounded or unbounded channels. This abstraction allows the framework
22/// to switch between different channel types without changing the API.
23///
24/// # Examples
25///
26/// ```
27/// use bubbletea_rs::event::{EventSender, Msg};
28/// use tokio::sync::mpsc;
29///
30/// // Create from unbounded channel
31/// let (tx, _rx) = mpsc::unbounded_channel::<Msg>();
32/// let sender = EventSender::from_unbounded(tx);
33///
34/// // Send a message
35/// let msg: Msg = Box::new("Hello");
36/// sender.send(msg).unwrap();
37/// ```
38#[derive(Clone)]
39pub enum EventSender {
40 /// Unbounded channel sender used for unlimited-capacity message delivery.
41 Unbounded(mpsc::UnboundedSender<Msg>),
42 /// Bounded channel sender that applies backpressure when full.
43 Bounded(mpsc::Sender<Msg>),
44}
45
46impl EventSender {
47 /// Send a message through the channel.
48 ///
49 /// Attempts to send a message through the underlying channel. For unbounded
50 /// channels, this will only fail if the receiver has been dropped. For bounded
51 /// channels, this may also fail due to backpressure (channel full).
52 ///
53 /// # Arguments
54 ///
55 /// * `msg` - The message to send
56 ///
57 /// # Returns
58 ///
59 /// Returns `Ok(())` if the message was sent successfully, or an error if:
60 /// - The channel is closed (`Error::ChannelClosed`)
61 /// - The channel is full (`Error::ChannelFull`) for bounded channels
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// use bubbletea_rs::event::{EventSender, Msg};
67 /// use tokio::sync::mpsc;
68 ///
69 /// let (tx, _rx) = mpsc::unbounded_channel::<Msg>();
70 /// let sender = EventSender::from_unbounded(tx);
71 ///
72 /// let msg: Msg = Box::new(42);
73 /// match sender.send(msg) {
74 /// Ok(()) => println!("Message sent!"),
75 /// Err(e) => eprintln!("Failed to send: {}", e),
76 /// }
77 /// ```
78 pub fn send(&self, msg: Msg) -> Result<(), crate::Error> {
79 match self {
80 // Unbounded send fails only when the receiver is closed.
81 EventSender::Unbounded(tx) => tx.send(msg).map_err(|_| crate::Error::ChannelClosed),
82 // Bounded send can fail due to Full (backpressure) or Closed.
83 EventSender::Bounded(tx) => tx.try_send(msg).map_err(Into::into),
84 }
85 }
86
87 /// Check if the sender is closed.
88 ///
89 /// Returns `true` if the receiver side of the channel has been dropped,
90 /// meaning that any future send operations will fail.
91 ///
92 /// # Returns
93 ///
94 /// `true` if the channel is closed, `false` otherwise
95 ///
96 /// # Examples
97 ///
98 /// ```
99 /// use bubbletea_rs::event::{EventSender, Msg};
100 /// use tokio::sync::mpsc;
101 ///
102 /// let (tx, rx) = mpsc::unbounded_channel::<Msg>();
103 /// let sender = EventSender::from_unbounded(tx);
104 ///
105 /// assert!(!sender.is_closed());
106 /// drop(rx); // Drop the receiver
107 /// assert!(sender.is_closed());
108 /// ```
109 pub fn is_closed(&self) -> bool {
110 match self {
111 EventSender::Unbounded(tx) => tx.is_closed(),
112 EventSender::Bounded(tx) => tx.is_closed(),
113 }
114 }
115
116 /// Create an EventSender from an UnboundedSender (for backward compatibility).
117 ///
118 /// This method creates an `EventSender` wrapping an unbounded channel sender.
119 /// Unbounded channels have unlimited capacity and never apply backpressure.
120 ///
121 /// # Arguments
122 ///
123 /// * `tx` - The unbounded sender to wrap
124 ///
125 /// # Returns
126 ///
127 /// An `EventSender` that uses the provided unbounded channel
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use bubbletea_rs::event::{EventSender, Msg};
133 /// use tokio::sync::mpsc;
134 ///
135 /// let (tx, _rx) = mpsc::unbounded_channel::<Msg>();
136 /// let sender = EventSender::from_unbounded(tx);
137 /// ```
138 pub fn from_unbounded(tx: mpsc::UnboundedSender<Msg>) -> Self {
139 EventSender::Unbounded(tx)
140 }
141
142 /// Create an EventSender from a bounded Sender (for testing).
143 ///
144 /// This method creates an `EventSender` wrapping a bounded channel sender.
145 /// Bounded channels have limited capacity and will apply backpressure when full.
146 /// This is primarily used in testing scenarios to verify behavior under
147 /// backpressure conditions.
148 ///
149 /// # Arguments
150 ///
151 /// * `tx` - The bounded sender to wrap
152 ///
153 /// # Returns
154 ///
155 /// An `EventSender` that uses the provided bounded channel
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// use bubbletea_rs::event::{EventSender, Msg};
161 /// use tokio::sync::mpsc;
162 ///
163 /// let (tx, _rx) = mpsc::channel::<Msg>(10); // Capacity of 10
164 /// let sender = EventSender::from_bounded(tx);
165 /// ```
166 pub fn from_bounded(tx: mpsc::Sender<Msg>) -> Self {
167 EventSender::Bounded(tx)
168 }
169}
170
171impl From<mpsc::UnboundedSender<Msg>> for EventSender {
172 fn from(tx: mpsc::UnboundedSender<Msg>) -> Self {
173 EventSender::Unbounded(tx)
174 }
175}
176
177impl From<mpsc::Sender<Msg>> for EventSender {
178 fn from(tx: mpsc::Sender<Msg>) -> Self {
179 EventSender::Bounded(tx)
180 }
181}
182
183/// Event receiver abstraction that can be either bounded or unbounded.
184///
185/// `EventReceiver` provides a unified interface for receiving messages from
186/// either bounded or unbounded channels. This abstraction allows the framework
187/// to switch between different channel types without changing the API.
188///
189/// # Examples
190///
191/// ```no_run
192/// use bubbletea_rs::event::{EventReceiver, EventSender, Msg};
193/// use tokio::sync::mpsc;
194///
195/// async fn example() {
196/// let (tx, rx) = mpsc::unbounded_channel::<Msg>();
197/// let mut receiver = EventReceiver::Unbounded(rx);
198/// let sender = EventSender::from_unbounded(tx);
199///
200/// // Send and receive a message
201/// sender.send(Box::new(42)).unwrap();
202/// if let Some(msg) = receiver.recv().await {
203/// // Process the message
204/// }
205/// }
206/// ```
207pub enum EventReceiver {
208 /// Unbounded channel receiver counterpart for unlimited-capacity channels.
209 Unbounded(mpsc::UnboundedReceiver<Msg>),
210 /// Bounded channel receiver that may yield `None` when closed and drained.
211 Bounded(mpsc::Receiver<Msg>),
212}
213
214impl EventReceiver {
215 /// Receive the next message from the channel.
216 ///
217 /// Asynchronously waits for the next message from the channel. Returns `None`
218 /// when the sender side has been dropped and all messages have been received.
219 ///
220 /// # Returns
221 ///
222 /// - `Some(Msg)` if a message was received
223 /// - `None` if the channel is closed and empty
224 ///
225 /// # Examples
226 ///
227 /// ```no_run
228 /// use bubbletea_rs::event::{EventReceiver, Msg};
229 /// use tokio::sync::mpsc;
230 ///
231 /// async fn example() {
232 /// let (tx, rx) = mpsc::unbounded_channel::<Msg>();
233 /// let mut receiver = EventReceiver::Unbounded(rx);
234 ///
235 /// // Send a message
236 /// tx.send(Box::new("Hello")).unwrap();
237 ///
238 /// // Receive the message
239 /// match receiver.recv().await {
240 /// Some(msg) => {
241 /// if let Some(text) = msg.downcast_ref::<&str>() {
242 /// println!("Received: {}", text);
243 /// }
244 /// }
245 /// None => println!("Channel closed"),
246 /// }
247 /// }
248 /// ```
249 pub async fn recv(&mut self) -> Option<Msg> {
250 match self {
251 EventReceiver::Unbounded(rx) => rx.recv().await,
252 EventReceiver::Bounded(rx) => rx.recv().await,
253 }
254 }
255}
256
257/// Global event sender set by Program on startup so commands can emit messages
258/// back into the event loop from background tasks.
259///
260/// This global static is initialized once when a `Program` starts running and
261/// provides a way for background tasks and commands to send messages back to
262/// the main event loop. It uses `OnceLock` to ensure thread-safe one-time
263/// initialization.
264///
265/// # Examples
266///
267/// ```
268/// use bubbletea_rs::event::{EVENT_SENDER, Msg};
269///
270/// // In a background task or command
271/// if let Some(sender) = EVENT_SENDER.get() {
272/// let msg: Msg = Box::new("Task completed");
273/// sender.send(msg).unwrap();
274/// }
275/// ```
276///
277/// # Note
278///
279/// This is automatically initialized by the framework. User code should only
280/// read from it, never write to it.
281pub static EVENT_SENDER: OnceLock<EventSender> = OnceLock::new();
282
283/// Global timer ID generator for unique timer identification.
284///
285/// This atomic counter ensures that each timer created in the application
286/// receives a unique identifier. The counter starts at 1 and increments
287/// atomically to avoid race conditions in multi-threaded environments.
288static TIMER_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
289
290/// Generates a unique timer ID.
291///
292/// This function atomically increments and returns a unique identifier
293/// for timers. Each call is guaranteed to return a different value,
294/// even when called from multiple threads simultaneously.
295///
296/// # Returns
297///
298/// A unique `u64` identifier for a timer
299///
300/// # Examples
301///
302/// ```
303/// use bubbletea_rs::event::next_timer_id;
304///
305/// let id1 = next_timer_id();
306/// let id2 = next_timer_id();
307/// assert_ne!(id1, id2);
308/// ```
309pub fn next_timer_id() -> u64 {
310 TIMER_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
311}
312
313/// A message indicating a keyboard input event.
314#[derive(Debug, Clone)]
315pub struct KeyMsg {
316 /// The `crossterm::event::KeyCode` representing the key pressed.
317 pub key: crossterm::event::KeyCode,
318 /// The `crossterm::event::KeyModifiers` active during the key press.
319 pub modifiers: crossterm::event::KeyModifiers,
320}
321
322/// A message indicating a mouse input event.
323#[derive(Debug, Clone)]
324pub struct MouseMsg {
325 /// The column coordinate of the mouse event.
326 pub x: u16,
327 /// The row coordinate of the mouse event.
328 pub y: u16,
329 /// The `crossterm::event::MouseEventKind` representing the type of mouse event.
330 pub button: crossterm::event::MouseEventKind,
331 /// The `crossterm::event::KeyModifiers` active during the mouse event.
332 pub modifiers: crossterm::event::KeyModifiers,
333}
334
335/// A message indicating that text was pasted into the terminal (bracketed paste).
336///
337/// This message is generated when bracketed paste mode is enabled and the user
338/// pastes text into the terminal. The pasted content is captured as a single
339/// string, preserving newlines and special characters.
340///
341/// # Examples
342///
343/// ```
344/// use bubbletea_rs::event::PasteMsg;
345///
346/// // Handling a paste event in your model's update method
347/// let paste_msg = PasteMsg("Hello\nWorld".to_string());
348/// // The text contains the exact pasted content
349/// assert_eq!(paste_msg.0, "Hello\nWorld");
350/// ```
351///
352/// # Note
353///
354/// Bracketed paste mode must be enabled with `EnableBracketedPasteMsg` for
355/// these messages to be generated.
356#[derive(Debug, Clone)]
357pub struct PasteMsg(pub String);
358
359/// A message indicating a change in the terminal window size.
360#[derive(Debug, Clone)]
361pub struct WindowSizeMsg {
362 /// The new width of the terminal window.
363 pub width: u16,
364 /// The new height of the terminal window.
365 pub height: u16,
366}
367
368/// A message to signal the application to quit.
369///
370/// Sending this message to the `Program` will initiate a graceful shutdown.
371#[derive(Debug, Clone)]
372pub struct QuitMsg;
373
374/// A message to forcefully kill the application immediately.
375///
376/// Sending this message to the `Program` will cause it to terminate as soon as
377/// possible. The event loop will stop without invoking the model's `update` and
378/// will return an `Error::ProgramKilled`.
379#[derive(Debug, Clone)]
380pub struct KillMsg;
381
382/// A message to signal an application interruption.
383///
384/// This is typically sent when an interrupt signal (e.g., Ctrl+C) is received.
385#[derive(Debug, Clone)]
386pub struct InterruptMsg;
387
388/// A message to signal the application to suspend.
389///
390/// This can be used to temporarily pause the application, for example, when
391/// another process needs control of the terminal.
392#[derive(Debug, Clone)]
393pub struct SuspendMsg;
394
395/// A message to signal the application to resume after suspension.
396#[derive(Debug, Clone)]
397pub struct ResumeMsg;
398
399/// A message indicating that the terminal gained focus.
400#[derive(Debug, Clone)]
401pub struct FocusMsg;
402
403/// A message indicating that the terminal lost focus.
404#[derive(Debug, Clone)]
405pub struct BlurMsg;
406
407/// An internal message type used to batch multiple messages together.
408/// This is not exposed as a public API.
409#[derive(Debug)]
410pub struct BatchMsgInternal {
411 /// Aggregated messages to dispatch as a single batch.
412 pub messages: Vec<Msg>,
413}
414
415/// A message to signal the terminal to enter the alternate screen buffer.
416#[derive(Debug, Clone)]
417pub struct EnterAltScreenMsg;
418
419/// A message to signal the terminal to exit the alternate screen buffer.
420#[derive(Debug, Clone)]
421pub struct ExitAltScreenMsg;
422
423/// A message to signal the terminal to enable mouse cell motion reporting.
424#[derive(Debug, Clone)]
425pub struct EnableMouseCellMotionMsg;
426
427/// A message to signal the terminal to enable all mouse motion reporting.
428#[derive(Debug, Clone)]
429pub struct EnableMouseAllMotionMsg;
430
431/// A message to signal the terminal to disable mouse reporting.
432#[derive(Debug, Clone)]
433pub struct DisableMouseMsg;
434
435/// A message to signal the terminal to enable bracketed paste mode.
436#[derive(Debug, Clone)]
437pub struct EnableBracketedPasteMsg;
438
439/// A message to signal the terminal to disable bracketed paste mode.
440#[derive(Debug, Clone)]
441pub struct DisableBracketedPasteMsg;
442
443/// A message to signal the terminal to enable focus reporting.
444#[derive(Debug, Clone)]
445pub struct EnableReportFocusMsg;
446
447/// A message to signal the terminal to disable focus reporting.
448#[derive(Debug, Clone)]
449pub struct DisableReportFocusMsg;
450
451/// A message to signal the terminal to show the cursor.
452#[derive(Debug, Clone)]
453pub struct ShowCursorMsg;
454
455/// A message to signal the terminal to hide the cursor.
456#[derive(Debug, Clone)]
457pub struct HideCursorMsg;
458
459/// A message to signal the terminal to clear the screen.
460#[derive(Debug, Clone)]
461pub struct ClearScreenMsg;
462
463/// A message to signal the terminal to request its current window size.
464///
465/// The terminal will respond with a `WindowSizeMsg` containing its dimensions.
466#[derive(Debug, Clone)]
467pub struct RequestWindowSizeMsg;
468
469/// A message to print a line to the terminal.
470///
471/// This message causes the program to print text to the terminal output.
472/// The text will be printed as-is with a newline appended.
473///
474/// # Examples
475///
476/// ```
477/// use bubbletea_rs::event::PrintMsg;
478///
479/// // Print a simple message
480/// let msg = PrintMsg("Hello, Terminal!".to_string());
481/// ```
482///
483/// # Note
484///
485/// This bypasses the normal view rendering and directly outputs to the terminal.
486/// Use sparingly as it can interfere with the TUI display.
487#[derive(Debug, Clone)]
488pub struct PrintMsg(pub String);
489
490/// A message to print formatted text to the terminal.
491///
492/// Similar to `PrintMsg`, but the text is treated as pre-formatted and
493/// printed exactly as provided without adding a newline.
494///
495/// # Examples
496///
497/// ```
498/// use bubbletea_rs::event::PrintfMsg;
499///
500/// // Print formatted text without automatic newline
501/// let msg = PrintfMsg("Progress: 50%\r".to_string());
502/// ```
503///
504/// # Note
505///
506/// This bypasses the normal view rendering and directly outputs to the terminal.
507/// Useful for progress indicators or custom formatting that requires precise
508/// control over newlines and carriage returns.
509#[derive(Debug, Clone)]
510pub struct PrintfMsg(pub String);
511
512/// A message to set the terminal window title.
513///
514/// This message updates the terminal window's title bar with the provided string.
515/// Not all terminals support this feature.
516///
517/// # Examples
518///
519/// ```
520/// use bubbletea_rs::event::SetWindowTitleMsg;
521///
522/// // Set a custom window title
523/// let msg = SetWindowTitleMsg("My App - Document.txt".to_string());
524/// ```
525///
526/// # Platform Support
527///
528/// - **Unix/Linux**: Generally supported in most terminal emulators
529/// - **macOS**: Supported in Terminal.app and iTerm2
530/// - **Windows**: Supported in Windows Terminal and newer console hosts
531#[derive(Debug, Clone)]
532pub struct SetWindowTitleMsg(pub String);
533
534/// An internal message used to start a recurring timer.
535///
536/// This structure is used internally by the framework to manage recurring
537/// timers created with the `every()` command. It contains the timer's
538/// configuration and a cancellation token for stopping the timer.
539///
540/// # Note
541///
542/// This is not exposed as a public API and should not be used directly
543/// by application code. Use the `every()` command function instead.
544pub struct EveryMsgInternal {
545 /// Interval between timer ticks.
546 pub duration: std::time::Duration,
547 /// Function invoked on each tick producing a message.
548 pub func: Box<dyn Fn(std::time::Duration) -> Msg + Send>,
549 /// Token used to cancel the running timer.
550 pub cancellation_token: CancellationToken,
551 /// Unique identifier for this timer instance.
552 pub timer_id: u64,
553}
554
555impl std::fmt::Debug for EveryMsgInternal {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 f.debug_struct("EveryMsgInternal")
558 .field("duration", &self.duration)
559 .field("timer_id", &self.timer_id)
560 .field("func", &"<closure>")
561 .finish()
562 }
563}
564
565/// A message to cancel a specific timer.
566///
567/// This message stops a running timer identified by its unique ID.
568/// If the timer has already completed or been cancelled, this message
569/// has no effect.
570///
571/// # Fields
572///
573/// * `timer_id` - The unique identifier of the timer to cancel
574///
575/// # Examples
576///
577/// ```
578/// use bubbletea_rs::event::{CancelTimerMsg, next_timer_id};
579///
580/// // Cancel a specific timer
581/// let timer_id = next_timer_id();
582/// let cancel_msg = CancelTimerMsg { timer_id };
583/// ```
584#[derive(Debug, Clone)]
585pub struct CancelTimerMsg {
586 /// The unique identifier of the timer to cancel.
587 pub timer_id: u64,
588}
589
590/// A message to cancel all active timers.
591///
592/// This message stops all currently running timers in the program.
593/// This is useful during cleanup or when transitioning between different
594/// application states.
595///
596/// # Examples
597///
598/// ```
599/// use bubbletea_rs::event::CancelAllTimersMsg;
600///
601/// // Cancel all timers
602/// let cancel_all = CancelAllTimersMsg;
603/// ```
604///
605/// # Use Cases
606///
607/// - Application shutdown
608/// - State transitions that invalidate existing timers
609/// - Error recovery scenarios
610#[derive(Debug, Clone)]
611pub struct CancelAllTimersMsg;