prettytty/
opt.rs

1//! Helper module with the options for connecting to terminals.
2//!
3//! This module provides the options for a terminal connection and the
4//! corresponding builder.
5//!
6//!
7//! # Example
8//!
9//! ```
10//! # use prettytty::opt::Options;
11//! let options = Options::builder()
12//!     .timeout(50)
13//!     .build();
14//!
15//! assert_eq!(options.timeout(), 50);
16//! ```
17
18/// The diagnostic logging volume.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum Volume {
21    Silent,
22    Regular,
23    Detailed,
24}
25
26/// A terminal mode.
27///
28/// Currently four terminal modes are supported:
29///
30///   * __Charred mode__ considers the terminal configuration as too hot to
31///     touch and makes no changes.
32///
33///   * __Cooked mode__ is the usual mode of operation on Unix and includes
34///     several features that go beyond character-based I/O, including editing
35///     the input line by line, turning key presses such as control-c into
36///     signals, and translating line endings.
37///
38///     On Windows, this mode optimizes for interoperability, enables the UTF-8
39///     code page for input and output, while also activating
40///     `ENABLE_VIRTUAL_TERMINAL_INPUT`, `ENABLE_PROCESSED_OUTPUT`, and
41///     `ENABLE_VIRTUAL_TERMINAL_PROCESSING`.
42///
43///   * __Rare mode__, also called cbreak mode, disables the line editor but
44///     leaves other terminal convenience features such as processing control-c
45///     enabled. This is the default mode for prettytty.
46///
47///   * __Raw mode__ disables all features beyond character-based I/O and ANSI
48///     escape sequences. It maximizes the application's control over input and
49///     output, but it also places the burden of implementing features at least as
50///     good as line editing on the application developer.
51///
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub enum Mode {
54    /// Charred mode doesn't dare to touch the terminal configuration; it's too
55    /// hot.
56    Charred,
57    /// Cooked mode means turning control-c/d into signals, fiddling with
58    /// line-endings in the output, and always editing the input line by line.
59    /// Still, it allows for ANSI escape sequences.
60    Cooked,
61    /// Rare or cbreak mode.
62    #[default]
63    Rare,
64    /// Raw mode.
65    Raw,
66}
67
68#[derive(Clone, Debug)]
69struct OptionData {
70    volume: Volume,
71    mode: Mode,
72    timeout: u8,
73    pathological_size: usize,
74    read_buffer_size: usize,
75    write_buffer_size: usize,
76}
77
78impl OptionData {
79    pub const fn new() -> Self {
80        Self {
81            volume: Volume::Silent,
82            mode: Mode::Rare,
83            timeout: 10,
84            pathological_size: 512,
85            read_buffer_size: 256,
86            write_buffer_size: 1_024,
87        }
88    }
89}
90
91/// A builder of options objects.
92#[derive(Debug)]
93pub struct OptionBuilder(OptionData);
94
95impl OptionBuilder {
96    /// Set the volume.
97    pub fn volume(&mut self, volume: Volume) -> &mut Self {
98        self.0.volume = volume;
99        self
100    }
101
102    /// Set rare or raw mode.
103    pub fn mode(&mut self, mode: Mode) -> &mut Self {
104        self.0.mode = mode;
105        self
106    }
107
108    /// Set the timeout in deciseconds (0.1s).
109    pub fn timeout(&mut self, timeout: u8) -> &mut Self {
110        self.0.timeout = timeout;
111        self
112    }
113
114    /// Set the minimum length for pathological ANSI escape sequences.
115    ///
116    /// This method ensures that the given size is at least double the read
117    /// buffer size, updating it if necessary.
118    pub fn pathological_size(&mut self, size: usize) -> &mut Self {
119        self.0.pathological_size = size.max(
120            self.0
121                .read_buffer_size
122                .saturating_add(self.0.read_buffer_size),
123        );
124        self
125    }
126
127    /// Set the read buffer size.
128    ///
129    /// This method also updates the pathological size to twice the given size.
130    ///
131    /// The read buffer must be large enough to hold the entire escape sequence
132    /// being recognized. When querying colors, that is 27 bytes: A response for
133    /// the 16th ANSI color *bright white* starts with `‹OSC›4;15;rgb:` followed
134    /// by three four-digit hexadecimal numbers separated by forward slashes,
135    /// such as `ffff/ffff/ffff`, and then the terminating `‹ST›`. Both OSC and
136    /// ST require at most two bytes, resulting in a maximum sequence length of
137    /// 27 bytes.
138    ///
139    /// This method ensures that the pathological size is at least double the
140    /// given size, updating it if necessary.
141    pub fn read_buffer_size(&mut self, size: usize) -> &mut Self {
142        self.0.read_buffer_size = size;
143        self.0.pathological_size = self.0.pathological_size.max(size.saturating_add(size));
144        self
145    }
146
147    /// Set the write buffer size.
148    pub fn write_buffer_size(&mut self, size: usize) -> &mut Self {
149        self.0.write_buffer_size = size;
150        self
151    }
152
153    /// Instantiate the options.
154    #[must_use = "the only reason to invoke method is to access the returned value"]
155    pub fn build(&self) -> Options {
156        Options(self.0.clone())
157    }
158}
159
160/// An options object.
161#[derive(Debug)]
162pub struct Options(OptionData);
163
164impl Default for Options {
165    fn default() -> Self {
166        Self(OptionData::new())
167    }
168}
169
170impl Options {
171    /// Create a new builder with the default option values.
172    pub fn builder() -> OptionBuilder {
173        OptionBuilder(OptionData::new())
174    }
175
176    /// Instantiate the default options but with regular debugging output
177    /// enabled.
178    pub fn with_log() -> Self {
179        Self::builder().volume(Volume::Regular).build()
180    }
181
182    /// Instantiate the default options but with detailed debugging output
183    /// enabled.
184    pub fn with_detailed_log() -> Self {
185        Self::builder().volume(Volume::Detailed).build()
186    }
187
188    /// Get the volume.
189    pub fn volume(&self) -> Volume {
190        self.0.volume
191    }
192
193    /// Get the terminal mode.
194    pub fn mode(&self) -> Mode {
195        self.0.mode
196    }
197
198    /// Get the timeout in 0.1s increments for blocking read operations.
199    pub fn timeout(&self) -> u8 {
200        self.0.timeout
201    }
202
203    /// Get the pathological size.
204    pub fn pathological_size(&self) -> usize {
205        self.0.pathological_size
206    }
207
208    /// Get the size of the read buffer.
209    pub fn read_buffer_size(&self) -> usize {
210        self.0.read_buffer_size
211    }
212
213    /// Get the size of the write buffer.
214    pub fn write_buffer_size(&self) -> usize {
215        self.0.write_buffer_size
216    }
217}