Skip to content
//! ANSI escape removal for `once` output.
//!
//! `once` renders through lipgloss, so its output carries SGR colour codes and
//! cursor control even when piped. Clients want plain text.

/// Strips ANSI escape sequences and carriage returns from a line of output.
pub(crate) fn strip(raw: &str) -> String {
    let mut out = String::with_capacity(raw.len());
    let mut chars = raw.chars();
    while let Some(c) = chars.next() {
        match c {
            '\u{1b}' => skip_escape(&mut chars),
            '\r' => {}
            other => out.push(other),
        }
    }
    out.trim_end().to_owned()
}

/// Consumes the remainder of an escape sequence that has already had its
/// leading `ESC` taken off the iterator.
fn skip_escape(chars: &mut std::str::Chars<'_>) {
    match chars.next() {
        // CSI: parameters and intermediates, terminated by a final byte.
        Some('[') => {
            for c in chars.by_ref() {
                if c.is_ascii_alphabetic() || c == '~' {
                    break;
                }
            }
        }
        // OSC: terminated by BEL or ST.
        Some(']') => {
            while let Some(c) = chars.next() {
                if c == '\u{7}' {
                    break;
                }
                if c == '\u{1b}' && chars.next() == Some('\\') {
                    break;
                }
            }
        }
        // Two-character sequences consume exactly the byte already taken.
        Some(_) | None => {}
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn plain_text_survives_untouched() {
        assert_eq!(strip("Update complete."), "Update complete.");
    }

    #[test]
    fn sgr_colour_codes_are_removed() {
        assert_eq!(strip("\u{1b}[1;32mDeployed\u{1b}[0m"), "Deployed");
    }

    #[test]
    fn cursor_control_and_carriage_returns_are_removed() {
        assert_eq!(strip("\u{1b}[2K\rPulling image"), "Pulling image");
    }

    #[test]
    fn osc_hyperlinks_are_removed() {
        assert_eq!(strip("\u{1b}]8;;https://example.com\u{7}link"), "link");
    }

    #[test]
    fn trailing_whitespace_is_trimmed() {
        assert_eq!(strip("done   "), "done");
    }
}