Skip to main content

rusty_bubbletea/
options.rs

1//! Cleanroom Rust port of upstream Go source file: `options.go`
2//! Upstream Target Tag / Version: `v2.0.8`
3//!
4//! <public-docs>
5//! # Program Options
6//!
7//! Program options (`with_fps`, `without_renderer`, `with_filter`, `with_window_size`,
8//! `with_context`, `with_output`, `with_input`, `with_environment`,
9//! `without_signal_handler`, `without_catch_panics`, `without_signals`, `with_color_profile`).
10//! </public-docs>
11
12use std::io::{Read, Write};
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::Arc;
15
16use crate::model::{Model, Msg};
17use crate::profile::ColorProfile;
18
19/// A cancellation context mirroring `context.Context` for the program lifecycle.
20#[derive(Debug, Clone, Default)]
21pub struct Context {
22    cancelled: Arc<AtomicBool>,
23}
24
25impl Context {
26    /// Returns a new cancellable context.
27    pub fn new() -> Context {
28        Context {
29            cancelled: Arc::new(AtomicBool::new(false)),
30        }
31    }
32
33    /// Done returns whether the context has been cancelled.
34    pub fn done(&self) -> bool {
35        self.cancelled.load(Ordering::SeqCst)
36    }
37
38    /// Cancel cancels the context.
39    pub fn cancel(&self) {
40        self.cancelled.store(true, Ordering::SeqCst);
41    }
42}
43
44/// An event filter invoked before the program processes a message, mirroring
45/// the function type of `WithFilter`.
46pub type EventFilter<M> = Box<dyn Fn(&M, Box<dyn Msg>) -> Option<Box<dyn Msg>> + Send + Sync>;
47
48/// Program configuration options.
49pub struct ProgramOptions<M: Model> {
50    /// Target FPS framerate limit.
51    pub fps: u32,
52    /// Disable renderer (for daemon or headless usage).
53    pub disable_renderer: bool,
54    /// Disable OS signal handling.
55    pub disable_signals: bool,
56    /// Disable the signal handler that Bubble Tea sets up for programs.
57    pub disable_signal_handler: bool,
58    /// Disable the panic catching that Bubble Tea does by default.
59    pub disable_catch_panics: bool,
60    /// Initial terminal width override.
61    pub width: usize,
62    /// Initial terminal height override.
63    pub height: usize,
64    /// Optional event filter.
65    pub filter: Option<EventFilter<M>>,
66    /// Input reader override; None means stdin.
67    pub input: Option<Box<dyn Read + Send + Sync>>,
68    /// Output writer override; None means stdout.
69    pub output: Option<Box<dyn Write + Send + Sync>>,
70    /// Environment variables used by the program.
71    pub environ: Option<Vec<(String, String)>>,
72    /// Forced color profile.
73    pub color_profile: Option<ColorProfile>,
74    /// External cancellation context.
75    pub context: Option<Context>,
76}
77
78impl<M: Model> Default for ProgramOptions<M> {
79    fn default() -> Self {
80        Self {
81            fps: 60,
82            disable_renderer: false,
83            disable_signals: false,
84            disable_signal_handler: false,
85            disable_catch_panics: false,
86            width: 0,
87            height: 0,
88            filter: None,
89            input: None,
90            output: None,
91            environ: None,
92            color_profile: None,
93            context: None,
94        }
95    }
96}
97
98impl<M: Model> ProgramOptions<M> {
99    /// <upstream-comment>WithContext lets you specify a context in which to run the Program. This is
100    /// useful if you want to cancel the execution from outside. When a Program gets
101    /// cancelled it will exit with an error ErrProgramKilled.</upstream-comment>
102    pub fn with_context(mut self, ctx: Context) -> Self {
103        self.context = Some(ctx);
104        self
105    }
106
107    /// <upstream-comment>WithOutput sets the output which, by default, is stdout. In most cases you
108    /// won't need to use this.</upstream-comment>
109    pub fn with_output(mut self, output: Box<dyn Write + Send + Sync>) -> Self {
110        self.output = Some(output);
111        self
112    }
113
114    /// <upstream-comment>WithInput sets the input which, by default, is stdin. In most cases you
115    /// won't need to use this. To disable input entirely pass None.</upstream-comment>
116    pub fn with_input(mut self, input: Option<Box<dyn Read + Send + Sync>>) -> Self {
117        self.input = input;
118        self
119    }
120
121    /// <upstream-comment>WithEnvironment sets the environment variables that the program will use.
122    /// This is useful when the program is running in a remote session (e.g. SSH) and
123    /// you want to pass the environment variables from the remote session to the
124    /// program.</upstream-comment>
125    pub fn with_environment(mut self, env: Vec<(String, String)>) -> Self {
126        self.environ = Some(env);
127        self
128    }
129
130    /// <upstream-comment>WithoutSignalHandler disables the signal handler that Bubble Tea sets up for
131    /// Programs. This is useful if you want to handle signals yourself.</upstream-comment>
132    pub fn without_signal_handler(mut self) -> Self {
133        self.disable_signal_handler = true;
134        self
135    }
136
137    /// <upstream-comment>WithoutCatchPanics disables the panic catching that Bubble Tea does by
138    /// default. If panic catching is disabled the terminal will be in a fairly
139    /// unusable state after a panic because Bubble Tea will not perform its usual
140    /// cleanup on exit.</upstream-comment>
141    pub fn without_catch_panics(mut self) -> Self {
142        self.disable_catch_panics = true;
143        self
144    }
145
146    /// <upstream-comment>WithoutSignals will ignore OS signals.
147    /// This is mainly useful for testing.</upstream-comment>
148    pub fn without_signals(mut self) -> Self {
149        self.disable_signals = true;
150        self
151    }
152
153    /// <upstream-comment>WithoutRenderer disables the renderer. When this is set output and log
154    /// statements will be plainly sent to stdout (or another output if one is set)
155    /// without any rendering and redrawing logic.</upstream-comment>
156    pub fn without_renderer(mut self) -> Self {
157        self.disable_renderer = true;
158        self
159    }
160
161    /// <upstream-comment>WithFilter supplies an event filter that will be invoked before Bubble Tea
162    /// processes a tea.Msg. The event filter can return any tea.Msg which will then
163    /// get handled by Bubble Tea instead of the original event. If the event filter
164    /// returns None, the event will be ignored and Bubble Tea will not process it.</upstream-comment>
165    pub fn with_filter(mut self, filter: EventFilter<M>) -> Self {
166        self.filter = Some(filter);
167        self
168    }
169
170    /// <upstream-comment>WithFPS sets a custom maximum FPS at which the renderer should run. If
171    /// less than 1, the default value of 60 will be used. If over 120, the FPS
172    /// will be capped at 120.</upstream-comment>
173    pub fn with_fps(mut self, fps: u32) -> Self {
174        self.fps = fps;
175        self
176    }
177
178    /// <upstream-comment>WithColorProfile sets the color profile that the program will use.</upstream-comment>
179    pub fn with_color_profile(mut self, profile: ColorProfile) -> Self {
180        self.color_profile = Some(profile);
181        self
182    }
183
184    /// <upstream-comment>WithWindowSize sets the initial size of the terminal window. This is useful
185    /// when you need to set the initial size of the terminal window, for example
186    /// during testing or when you want to run your program in a non-interactive
187    /// environment.</upstream-comment>
188    pub fn with_window_size(mut self, width: usize, height: usize) -> Self {
189        self.width = width;
190        self.height = height;
191        self
192    }
193}