diff options
Diffstat (limited to 'mingling/src/osc94')
| -rw-r--r-- | mingling/src/osc94/guard.rs | 203 | ||||
| -rw-r--r-- | mingling/src/osc94/state.rs | 243 |
2 files changed, 446 insertions, 0 deletions
diff --git a/mingling/src/osc94/guard.rs b/mingling/src/osc94/guard.rs new file mode 100644 index 0000000..99c1084 --- /dev/null +++ b/mingling/src/osc94/guard.rs @@ -0,0 +1,203 @@ +use crate::osc94::OSC94State; + +/// A guard for modifying process state. +/// +/// Obtained via [`OSC94::get_mut`]. When the guard is dropped, the process state is +/// automatically restored to `OS94State::Clean`, so no manual cleanup is needed. +/// +/// # Example +/// +/// Create a guard via [`OSC94`], and the state is automatically restored to Clean +/// when the guard is dropped: +/// +/// ``` +/// use mingling::res::OSC94; +/// use mingling::osc94::OSC94Guard; +/// +/// let osc94 = OSC94::default(); +/// { +/// let mut guard: OSC94Guard = osc94.get_mut(); +/// guard.set_progress(0.5); +/// // When leaving this scope, the guard is dropped and the process state is automatically restored to Clean +/// } +/// ``` +pub struct OSC94Guard { + pub(crate) is_support: bool, + pub(crate) msg: OSC94State, +} + +impl OSC94Guard { + /// Set the process state to Clean. + /// + /// Indicates that the process has finished or is in a normal, problem-free state. + /// + /// # Example + /// + /// ``` + /// use mingling::res::OSC94; + /// use mingling::osc94::{OSC94Guard, OSC94State}; + /// + /// let osc94 = OSC94::default(); + /// let mut guard: OSC94Guard = osc94.get_mut(); + /// guard.set_progress(0.5); + /// guard.set_clean_state(); + /// assert_eq!(guard.state(), OSC94State::Clean); + /// ``` + pub fn set_clean_state(&mut self) { + self.msg = OSC94State::Clean; + if self.is_support { + self.msg.send(); + } + } + + /// Set the process state to Error. + /// + /// Indicates that an error occurred during process execution. + /// + /// # Example + /// + /// ``` + /// use mingling::res::OSC94; + /// use mingling::osc94::{OSC94Guard, OSC94State}; + /// + /// let osc94 = OSC94::default(); + /// let mut guard: OSC94Guard = osc94.get_mut(); + /// guard.set_error_state(); + /// assert_eq!(guard.state(), OSC94State::Error); + /// ``` + pub fn set_error_state(&mut self) { + self.msg = OSC94State::Error; + if self.is_support { + self.msg.send(); + } + } + + /// Set the process state to Warn. + /// + /// Indicates that a warning occurred during process execution, but it has not + /// reached the level of an error. + /// + /// # Example + /// + /// ``` + /// use mingling::res::OSC94; + /// use mingling::osc94::{OSC94Guard, OSC94State}; + /// + /// let osc94 = OSC94::default(); + /// let mut guard: OSC94Guard = osc94.get_mut(); + /// guard.set_warn_state(); + /// assert_eq!(guard.state(), OSC94State::Warn); + /// ``` + pub fn set_warn_state(&mut self) { + self.msg = OSC94State::Warn; + if self.is_support { + self.msg.send(); + } + } + + /// Set the process state to Unknown. + /// + /// Indicates that the process state cannot be determined or has not been defined. + /// + /// # Example + /// + /// ``` + /// use mingling::res::OSC94; + /// use mingling::osc94::{OSC94Guard, OSC94State}; + /// + /// let osc94 = OSC94::default(); + /// let mut guard: OSC94Guard = osc94.get_mut(); + /// guard.set_unknown_state(); + /// assert_eq!(guard.state(), OSC94State::Unknown); + /// ``` + pub fn set_unknown_state(&mut self) { + self.msg = OSC94State::Unknown; + if self.is_support { + self.msg.send(); + } + } + + /// Set the progress of the process. + /// + /// The `progress` parameter should be between `0.0` and `1.0`. `0.0` indicates + /// the start of the task, and `1.0` indicates the completion of the task. + /// Values outside this range are not clamped, but it is recommended to keep them + /// within this range. + /// + /// # Parameters + /// + /// * `progress` - The progress value, ranging from `0.0` to `1.0`. + /// + /// # Example + /// + /// ``` + /// use mingling::res::OSC94; + /// use mingling::osc94::OSC94Guard; + /// + /// let osc94 = OSC94::default(); + /// let mut guard: OSC94Guard = osc94.get_mut(); + /// guard.set_progress(0.5); + /// assert_eq!(guard.progress(), 0.5); + /// ``` + pub fn set_progress(&mut self, progress: f32) { + self.msg = OSC94State::Normal(progress); + if self.is_support { + self.msg.send(); + } + } + + /// Get the current process state. + /// + /// # Returns + /// + /// Returns the current [`OSC94State`] value, representing the state of the process. + /// + /// # Example + /// + /// ``` + /// use mingling::res::OSC94; + /// use mingling::osc94::{OSC94Guard, OSC94State}; + /// + /// let osc94 = OSC94::default(); + /// let guard: OSC94Guard = osc94.get_mut(); + /// assert_eq!(guard.state(), OSC94State::Clean); + /// ``` + #[must_use] + pub const fn state(&self) -> OSC94State { + self.msg + } + + /// Get the current progress value. + /// + /// Returns the actual progress value only when the state is [`OSC94State::Normal`]; + /// otherwise returns `0.0`. + /// + /// # Returns + /// + /// Returns an `f32` progress value, ranging from `0.0` to `1.0`. + /// + /// # Example + /// + /// ``` + /// use mingling::res::OSC94; + /// use mingling::osc94::OSC94Guard; + /// + /// let osc94 = OSC94::default(); + /// let mut guard: OSC94Guard = osc94.get_mut(); + /// guard.set_progress(0.25); + /// assert_eq!(guard.progress(), 0.25); + /// ``` + #[must_use] + pub const fn progress(&self) -> f32 { + match self.msg { + OSC94State::Normal(progress) => progress, + _ => 0.0, + } + } +} + +impl Drop for OSC94Guard { + fn drop(&mut self) { + OSC94State::Clean.send(); + } +} diff --git a/mingling/src/osc94/state.rs b/mingling/src/osc94/state.rs new file mode 100644 index 0000000..1fff7ac --- /dev/null +++ b/mingling/src/osc94/state.rs @@ -0,0 +1,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() + } +} |
