aboutsummaryrefslogtreecommitdiff
path: root/mingling/src/osc94/state.rs
blob: 1fff7ac8278e14f17fa64c045f9005d5411d3061 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/// `OSC 9;4` protocol message
///
/// Used to send task progress notification messages to the terminal via ANSI escape sequences.
///
/// This protocol follows the [Windows Terminal Progress Bar Sequences](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences) specification.
/// The status codes (0-4) represent: clear progress, normal, error, indeterminate, and warning states, respectively.
///
/// # Examples
///
/// ```rust
/// use mingling::osc94::OSC94State;
///
/// // Set progress to 50%
/// let state = OSC94State::Normal(0.5);
/// assert_eq!(state.state_code(), 1);
/// assert_eq!(state.progress(), 50.0);
///
/// // Generate escape sequence string
/// let seq = state.to_escape_sequence();
/// assert_eq!(seq, "\x1b]9;4;1;50\x07");
///
/// // Convert to string (Display implementation)
/// let s = format!("{state}");
/// assert_eq!(s, "\x1b]9;4;1;50\x07");
///
/// // Convert via From
/// let s2: String = state.into();
/// assert_eq!(s2, "\x1b]9;4;1;50\x07");
///
/// // Error state
/// let err = OSC94State::Error;
/// assert_eq!(err.state_code(), 2);
/// ```
///
/// # Use Cases
///
/// In command-line tools or scripts, the [`OSC94State::send`] method can be used to directly send progress notifications to the terminal.
/// Supported terminals include: `Windows Terminal`, `kitty`, `iTerm2`, `WezTerm`, `foot`, etc.
///
/// ```
/// use mingling::osc94::OSC94State;
///
/// // Send progress 100%
/// OSC94State::Normal(1.0).send();
/// // Send completion (clear) message
/// OSC94State::Clean.send();
/// ```
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OSC94State {
    /// Clear/hide progress
    Clean,
    /// Normal state, corresponding to status code `1`, carries a progress value (0.0 to 1.0)
    Normal(f32),
    /// Error state, corresponding to status code `2`
    Error,
    /// Indeterminate state, corresponding to status code `3`
    Unknown,
    /// Warning state, corresponding to status code `4`
    Warn,
}

impl OSC94State {
    /// Returns the status code for the `OSC 9;4` protocol.
    ///
    /// Status code meanings:
    /// - `0`: Clear progress (`Clean`)
    /// - `1`: Normal state (`Normal`)
    /// - `2`: Error state (`Error`)
    /// - `3`: Indeterminate state (`Unknown`)
    /// - `4`: Warning state (`Warn`)
    ///
    /// # Examples
    ///
    /// ```
    /// use mingling::osc94::OSC94State;
    ///
    /// assert_eq!(OSC94State::Clean.state_code(), 0);
    /// assert_eq!(OSC94State::Normal(0.5).state_code(), 1);
    /// assert_eq!(OSC94State::Error.state_code(), 2);
    /// assert_eq!(OSC94State::Unknown.state_code(), 3);
    /// assert_eq!(OSC94State::Warn.state_code(), 4);
    /// ```
    ///
    /// # Return Value
    ///
    /// Returns the corresponding status code (`u8` type), ranging from `0` to `4`.
    #[must_use]
    pub const fn state_code(&self) -> u8 {
        match self {
            Self::Clean => 0,
            Self::Normal(_) => 1,
            Self::Error => 2,
            Self::Unknown => 3,
            Self::Warn => 4,
        }
    }

    /// Returns the progress value (0-100), used for the `Normal` state, clamped to a valid range.
    ///
    /// This function converts the progress value (between 0.0 and 1.0) stored in the `Normal` variant
    /// into a percentage (0 to 100) and rounds it. For non-`Normal` states (such as `Clean`, `Error`,
    /// `Unknown`, `Warn`), it returns a fixed `0.0`, because only the `Normal` state carries progress information.
    ///
    /// # Examples
    ///
    /// ```
    /// use mingling::osc94::OSC94State;
    ///
    /// // Progress conversion in normal state
    /// assert_eq!(OSC94State::Normal(0.5).progress(), 50.0);
    /// assert_eq!(OSC94State::Normal(1.0).progress(), 100.0);
    /// assert_eq!(OSC94State::Normal(0.0).progress(), 0.0);
    ///
    /// // Out-of-range values are clamped to 0-100
    /// assert_eq!(OSC94State::Normal(1.5).progress(), 100.0);
    /// assert_eq!(OSC94State::Normal(-0.5).progress(), 0.0);
    ///
    /// // Rounding behavior
    /// assert_eq!(OSC94State::Normal(0.335).progress(), 34.0);
    /// assert_eq!(OSC94State::Normal(0.999).progress(), 100.0);
    ///
    /// // Non-Normal states return 0.0
    /// assert_eq!(OSC94State::Clean.progress(), 0.0);
    /// assert_eq!(OSC94State::Error.progress(), 0.0);
    /// assert_eq!(OSC94State::Unknown.progress(), 0.0);
    /// assert_eq!(OSC94State::Warn.progress(), 0.0);
    /// ```
    ///
    /// # Return Value
    ///
    /// Returns an `f32` progress percentage, ranging from `0.0` to `100.0` (inclusive).
    /// For the `Normal` state, returns the rounded result of converting its progress value to a percentage;
    /// for other states, always returns `0.0`.
    #[must_use]
    pub const fn progress(&self) -> f32 {
        match self {
            Self::Normal(progress) => (progress.clamp(0.0, 1.0) * 100.0).round(),
            _ => 0.0,
        }
    }

    /// Converts the message to the corresponding `OSC 9;4` escape sequence string.
    ///
    /// This method generates an ANSI escape sequence conforming to the
    /// [Windows Terminal Progress Bar Sequences](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences)
    /// protocol based on the current state, in the format `\x1b]9;4;{status_code};{progress}\x07`.
    ///
    /// Escape sequence format description:
    /// - `\x1b]`: ESC character followed by `]`, marking the start of an OSC (Operating System Command) sequence.
    /// - `9;4`: Indicates the `OSC 9;4` protocol (task progress notification).
    /// - `{status_code}`: Task status, ranging from `0` (clear), `1` (normal), `2` (error), `3` (indeterminate), to `4` (warning).
    /// - `{progress}`: Task progress percentage (0-100), only meaningful for the `Normal` state.
    /// - `\x07`: BEL character, marking the end of the OSC sequence.
    ///
    /// # Examples
    ///
    /// ```
    /// use mingling::osc94::OSC94State;
    ///
    /// // Clear progress
    /// let clean = OSC94State::Clean;
    /// assert_eq!(clean.to_escape_sequence(), "\x1b]9;4;0;0\x07");
    ///
    /// // Normal state, progress 50%
    /// let normal = OSC94State::Normal(0.5);
    /// assert_eq!(normal.to_escape_sequence(), "\x1b]9;4;1;50\x07");
    ///
    /// // Normal state, progress 100%
    /// let complete = OSC94State::Normal(1.0);
    /// assert_eq!(complete.to_escape_sequence(), "\x1b]9;4;1;100\x07");
    ///
    /// // Error state
    /// let error = OSC94State::Error;
    /// assert_eq!(error.to_escape_sequence(), "\x1b]9;4;2;0\x07");
    ///
    /// // Indeterminate state
    /// let unknown = OSC94State::Unknown;
    /// assert_eq!(unknown.to_escape_sequence(), "\x1b]9;4;3;0\x07");
    ///
    /// // Warning state
    /// let warn = OSC94State::Warn;
    /// assert_eq!(warn.to_escape_sequence(), "\x1b]9;4;4;0\x07");
    /// ```
    ///
    /// # Return Value
    ///
    /// Returns a `String` containing an ANSI escape sequence conforming to the `OSC 9;4` protocol standard.
    /// This string can be directly output to a terminal that supports this protocol (such as `Windows Terminal`,
    /// `kitty`, `iTerm2`, `WezTerm`, `foot`, etc.) to display a task progress notification.
    #[must_use]
    pub fn to_escape_sequence(&self) -> String {
        format!("\x1b]9;4;{};{}\x07", self.state_code(), self.progress())
    }

    /// Sends the OSC 9;4 message to the terminal via stdout.
    ///
    /// This method outputs the escape sequence of the current state to standard output and flushes the buffer,
    /// allowing terminals that support the
    /// [Windows Terminal Progress Bar Sequences](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences)
    /// protocol to display the corresponding task progress notification.
    ///
    /// # Examples
    ///
    /// ```
    /// use mingling::osc94::OSC94State;
    ///
    /// // Send normal state, progress 50%
    /// OSC94State::Normal(0.5).send();
    ///
    /// // Send error state
    /// OSC94State::Error.send();
    ///
    /// // Send clear progress message
    /// OSC94State::Clean.send();
    /// ```
    ///
    /// # Panics
    ///
    /// Panics if the stdout stream cannot be flushed.
    pub fn send(&self) {
        use std::io::Write;
        print!("{}", self.to_escape_sequence());
        std::io::stdout().flush().unwrap();
    }
}

impl std::fmt::Display for OSC94State {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_escape_sequence())
    }
}

impl From<OSC94State> for String {
    fn from(msg: OSC94State) -> Self {
        msg.to_escape_sequence()
    }
}

impl From<&OSC94State> for String {
    fn from(msg: &OSC94State) -> Self {
        msg.to_escape_sequence()
    }
}