use std::sync::OnceLock;
const GREEN: &str = "32";
const RED: &str = "31";
const YELLOW: &str = "33";
pub fn colors_enabled() -> bool {
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| {
let no_color = std::env::var_os("NO_COLOR")
.map(|v| !v.is_empty())
.unwrap_or(false);
let dumb = std::env::var("TERM").map(|t| t == "dumb").unwrap_or(false);
!no_color && !dumb
})
}
fn paint(text: &str, sgr: &str) -> String {
if colors_enabled() {
format!("\u{1b}[{sgr}m{text}\u{1b}[0m")
} else {
text.to_string()
}
}
fn sign(glyph: &str, sgr: &str) -> String {
format!(" {}", paint(glyph, sgr))
}
pub fn valid_sign() -> &'static str {
static S: OnceLock<String> = OnceLock::new();
S.get_or_init(|| sign("✓", GREEN))
}
pub fn error_sign() -> &'static str {
static S: OnceLock<String> = OnceLock::new();
S.get_or_init(|| sign("✗", RED))
}
pub fn warning_sign() -> &'static str {
static S: OnceLock<String> = OnceLock::new();
S.get_or_init(|| sign("!", YELLOW))
}
pub fn highlight(text: &str) -> String {
paint(&sanitize(text), YELLOW)
}
pub fn sanitize(text: &str) -> String {
let mut out = String::with_capacity(text.len());
for c in text.chars() {
match c {
'\t' => out.push(' '),
c if (c as u32) < 0x20 || c == '\u{7f}' => {
out.push_str(&format!("\\x{:02x}", c as u32));
}
c if ('\u{80}'..='\u{9f}').contains(&c) => {
out.push_str(&format!("\\x{:02x}", c as u32));
}
'\u{200e}' | '\u{200f}' | '\u{202a}'..='\u{202e}' | '\u{2066}'..='\u{2069}' => {
out.push_str(&format!("\\u{{{:04x}}}", c as u32));
}
c => out.push(c),
}
}
out
}
pub fn sanitize_path(p: &std::path::Path) -> String {
sanitize(&p.display().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_escapes_what_a_terminal_would_obey() {
assert_eq!(sanitize("a\u{1b}[8mb"), "a\\x1b[8mb");
assert_eq!(sanitize("bell\u{7}"), "bell\\x07");
assert_eq!(sanitize("cr\r"), "cr\\x0d");
assert_eq!(sanitize("del\u{7f}"), "del\\x7f");
assert_eq!(sanitize("csi\u{9b}"), "csi\\x9b");
assert_eq!(sanitize("rtl\u{202e}"), "rtl\\u{202e}");
assert_eq!(sanitize("iso\u{2066}"), "iso\\u{2066}");
assert_eq!(sanitize("a\tb"), "a b");
}
#[test]
fn sanitize_leaves_ordinary_text_alone() {
for s in ["plain ascii", "café", "日本語", "✓ ✗ !", "a/b-c_d.e", "🐛"] {
assert_eq!(sanitize(s), s, "{s:?} should pass through");
}
}
#[test]
fn highlight_emits_only_its_own_escapes() {
let out = highlight("a\u{1b}[0m\u{1b}[8mb");
let escapes = out.matches('\u{1b}').count();
assert!(
escapes == 0 || escapes == 2,
"{out:?} has {escapes} escapes"
);
assert!(
!out.contains("\u{1b}[8m"),
"a conceal sequence survived: {out:?}"
);
assert!(
!out.contains("\u{1b}[0m\u{1b}[8m"),
"an injected reset survived: {out:?}"
);
}
#[test]
fn no_sign_uses_the_fixed_256_colour_cube() {
for s in [valid_sign(), error_sign(), warning_sign()] {
assert!(
!s.contains("38;5;"),
"a 256-colour code overrides the user's theme: {s:?}"
);
}
}
#[test]
fn signs_carry_a_distinct_glyph_not_only_a_colour() {
assert!(valid_sign().contains('✓'));
assert!(error_sign().contains('✗'));
assert!(warning_sign().contains('!'));
}
#[test]
fn no_source_file_emits_a_256_colour_escape() {
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/src");
let mut offenders = Vec::new();
fn walk(d: &std::path::Path, out: &mut Vec<String>) {
for e in std::fs::read_dir(d).expect("src").flatten() {
let p = e.path();
if p.is_dir() {
walk(&p, out);
} else if p.extension().is_some_and(|x| x == "rs") {
for (i, line) in std::fs::read_to_string(&p)
.unwrap_or_default()
.lines()
.enumerate()
{
let mentions_the_pattern =
line.contains("offenders") || line.trim_start().starts_with("//");
if mentions_the_pattern {
continue;
}
if line.contains(concat!("[38", ";5;")) {
out.push(format!("{}:{}", p.display(), i + 1));
}
}
}
}
}
walk(std::path::Path::new(dir), &mut offenders);
assert!(
offenders.is_empty(),
"a fixed 256-colour code overrides the user's terminal theme: {offenders:?}"
);
}
#[test]
fn glyphs_are_not_routed_through_highlight() {
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/src");
let mut offenders = Vec::new();
fn walk(d: &std::path::Path, out: &mut Vec<String>) {
for e in std::fs::read_dir(d).expect("src").flatten() {
let p = e.path();
if p.is_dir() {
walk(&p, out);
} else if p.extension().is_some_and(|x| x == "rs") {
let body = std::fs::read_to_string(&p).unwrap_or_default();
for (i, line) in body.lines().enumerate() {
let is_test_or_doc =
line.trim_start().starts_with("//") || line.contains("offenders");
if is_test_or_doc {
continue;
}
for g in ["\\u{2713}", "\\u{2717}", "✓", "✗"] {
if line.contains("highlight(") && line.contains(g) {
out.push(format!("{}:{}", p.display(), i + 1));
}
}
}
}
}
}
walk(std::path::Path::new(dir), &mut offenders);
assert!(
offenders.is_empty(),
"a status glyph is being painted as emphasis: {offenders:?}"
);
}
#[test]
fn no_color_is_honoured_per_the_standard() {
fn decide(no_color: Option<&str>, term: &str) -> bool {
let nc = no_color.map(|v| !v.is_empty()).unwrap_or(false);
!nc && term != "dumb"
}
assert!(decide(None, "xterm-256color"), "colour by default");
assert!(!decide(Some("1"), "xterm-256color"), "any value disables");
assert!(!decide(Some("0"), "xterm-256color"), "even \"0\" disables");
assert!(
decide(Some(""), "xterm-256color"),
"an EMPTY value does NOT disable — no-color.org is explicit"
);
assert!(!decide(None, "dumb"), "a dumb terminal cannot render SGR");
}
}