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 containing commands to be executed concurrently.
416/// This enables non-blocking batch operations that spawn commands immediately.
417pub struct BatchCmdMsg(pub Vec<crate::Cmd>);
418
419/// A message to signal the terminal to enter the alternate screen buffer.
420#[derive(Debug, Clone)]
421pub struct EnterAltScreenMsg;
422
423/// A message to signal the terminal to exit the alternate screen buffer.
424#[derive(Debug, Clone)]
425pub struct ExitAltScreenMsg;
426
427/// A message to signal the terminal to enable mouse cell motion reporting.
428#[derive(Debug, Clone)]
429pub struct EnableMouseCellMotionMsg;
430
431/// A message to signal the terminal to enable all mouse motion reporting.
432#[derive(Debug, Clone)]
433pub struct EnableMouseAllMotionMsg;
434
435/// A message to signal the terminal to disable mouse reporting.
436#[derive(Debug, Clone)]
437pub struct DisableMouseMsg;
438
439/// A message to signal the terminal to enable bracketed paste mode.
440#[derive(Debug, Clone)]
441pub struct EnableBracketedPasteMsg;
442
443/// A message to signal the terminal to disable bracketed paste mode.
444#[derive(Debug, Clone)]
445pub struct DisableBracketedPasteMsg;
446
447/// A message to signal the terminal to enable focus reporting.
448#[derive(Debug, Clone)]
449pub struct EnableReportFocusMsg;
450
451/// A message to signal the terminal to disable focus reporting.
452#[derive(Debug, Clone)]
453pub struct DisableReportFocusMsg;
454
455/// A message to signal the terminal to show the cursor.
456#[derive(Debug, Clone)]
457pub struct ShowCursorMsg;
458
459/// A message to signal the terminal to hide the cursor.
460#[derive(Debug, Clone)]
461pub struct HideCursorMsg;
462
463/// A message to signal the terminal to clear the screen.
464#[derive(Debug, Clone)]
465pub struct ClearScreenMsg;
466
467/// A message to signal the terminal to request its current window size.
468///
469/// The terminal will respond with a `WindowSizeMsg` containing its dimensions.
470#[derive(Debug, Clone)]
471pub struct RequestWindowSizeMsg;
472
473/// A message to print a line to the terminal.
474///
475/// This message causes the program to print text to the terminal output.
476/// The text will be printed as-is with a newline appended.
477///
478/// # Examples
479///
480/// ```
481/// use bubbletea_rs::event::PrintMsg;
482///
483/// // Print a simple message
484/// let msg = PrintMsg("Hello, Terminal!".to_string());
485/// ```
486///
487/// # Note
488///
489/// This bypasses the normal view rendering and directly outputs to the terminal.
490/// Use sparingly as it can interfere with the TUI display.
491#[derive(Debug, Clone)]
492pub struct PrintMsg(pub String);
493
494/// A message to print formatted text to the terminal.
495///
496/// Similar to `PrintMsg`, but the text is treated as pre-formatted and
497/// printed exactly as provided without adding a newline.
498///
499/// # Examples
500///
501/// ```
502/// use bubbletea_rs::event::PrintfMsg;
503///
504/// // Print formatted text without automatic newline
505/// let msg = PrintfMsg("Progress: 50%\r".to_string());
506/// ```
507///
508/// # Note
509///
510/// This bypasses the normal view rendering and directly outputs to the terminal.
511/// Useful for progress indicators or custom formatting that requires precise
512/// control over newlines and carriage returns.
513#[derive(Debug, Clone)]
514pub struct PrintfMsg(pub String);
515
516/// A message to set the terminal window title.
517///
518/// This message updates the terminal window's title bar with the provided string.
519/// Not all terminals support this feature.
520///
521/// # Examples
522///
523/// ```
524/// use bubbletea_rs::event::SetWindowTitleMsg;
525///
526/// // Set a custom window title
527/// let msg = SetWindowTitleMsg("My App - Document.txt".to_string());
528/// ```
529///
530/// # Platform Support
531///
532/// - **Unix/Linux**: Generally supported in most terminal emulators
533/// - **macOS**: Supported in Terminal.app and iTerm2
534/// - **Windows**: Supported in Windows Terminal and newer console hosts
535#[derive(Debug, Clone)]
536pub struct SetWindowTitleMsg(pub String);
537
538/// An internal message used to start a recurring timer.
539///
540/// This structure is used internally by the framework to manage recurring
541/// timers created with the `every()` command. It contains the timer's
542/// configuration and a cancellation token for stopping the timer.
543///
544/// # Note
545///
546/// This is not exposed as a public API and should not be used directly
547/// by application code. Use the `every()` command function instead.
548pub struct EveryMsgInternal {
549    /// Interval between timer ticks.
550    pub duration: std::time::Duration,
551    /// Function invoked on each tick producing a message.
552    pub func: Box<dyn Fn(std::time::Duration) -> Msg + Send>,
553    /// Token used to cancel the running timer.
554    pub cancellation_token: CancellationToken,
555    /// Unique identifier for this timer instance.
556    pub timer_id: u64,
557}
558
559impl std::fmt::Debug for EveryMsgInternal {
560    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561        f.debug_struct("EveryMsgInternal")
562            .field("duration", &self.duration)
563            .field("timer_id", &self.timer_id)
564            .field("func", &"<closure>")
565            .finish()
566    }
567}
568
569/// A message to cancel a specific timer.
570///
571/// This message stops a running timer identified by its unique ID.
572/// If the timer has already completed or been cancelled, this message
573/// has no effect.
574///
575/// # Fields
576///
577/// * `timer_id` - The unique identifier of the timer to cancel
578///
579/// # Examples
580///
581/// ```
582/// use bubbletea_rs::event::{CancelTimerMsg, next_timer_id};
583///
584/// // Cancel a specific timer
585/// let timer_id = next_timer_id();
586/// let cancel_msg = CancelTimerMsg { timer_id };
587/// ```
588#[derive(Debug, Clone)]
589pub struct CancelTimerMsg {
590    /// The unique identifier of the timer to cancel.
591    pub timer_id: u64,
592}
593
594/// A message to cancel all active timers.
595///
596/// This message stops all currently running timers in the program.
597/// This is useful during cleanup or when transitioning between different
598/// application states.
599///
600/// # Examples
601///
602/// ```
603/// use bubbletea_rs::event::CancelAllTimersMsg;
604///
605/// // Cancel all timers
606/// let cancel_all = CancelAllTimersMsg;
607/// ```
608///
609/// # Use Cases
610///
611/// - Application shutdown
612/// - State transitions that invalidate existing timers
613/// - Error recovery scenarios
614#[derive(Debug, Clone)]
615pub struct CancelAllTimersMsg;