use std::sync::atomic::{AtomicBool, Ordering};
const OSC8_PREFIX: &str = "\x1b]8;;";
const OSC8_TERMINATOR: &str = "\x1b\\";
static ENABLED: AtomicBool = AtomicBool::new(true);
pub fn set_enabled(enabled: bool) {
ENABLED.store(enabled, Ordering::Relaxed);
}
#[must_use]
pub fn enabled() -> bool {
ENABLED.load(Ordering::Relaxed)
}
#[must_use]
pub fn wrap_link(target: &str, label: &str) -> String {
let mut out = String::with_capacity(target.len() + label.len() + 12);
out.push_str(OSC8_PREFIX);
out.push_str(target);
out.push_str(OSC8_TERMINATOR);
out.push_str(label);
out.push_str(OSC8_PREFIX);
out.push_str(OSC8_TERMINATOR);
out
}
pub fn strip_ansi_into(s: &str, out: &mut String) {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == 0x1b && i + 1 < bytes.len() {
let next = bytes[i + 1];
match next {
b'[' => {
let mut j = i + 2;
while j < bytes.len() {
let b = bytes[j];
if (0x40..=0x7e).contains(&b) {
j += 1;
break;
}
j += 1;
}
i = j;
continue;
}
b']' | b'P' | b'X' | b'^' | b'_' => {
let mut j = i + 2;
while j < bytes.len() {
if bytes[j] == 0x07 {
j += 1;
break;
}
if bytes[j] == 0x1b && j + 1 < bytes.len() && bytes[j + 1] == b'\\' {
j += 2;
break;
}
j += 1;
}
i = j;
continue;
}
_ => {
i += 2;
continue;
}
}
}
let b = bytes[i];
if b < 0x20 && b != b'\n' && b != b'\r' && b != b'\t' {
i += 1;
continue;
}
out.push(b as char);
i += 1;
}
}
pub fn strip_into(s: &str, out: &mut String) {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if i + 4 <= bytes.len()
&& bytes[i] == 0x1b
&& bytes[i + 1] == b']'
&& bytes[i + 2] == b'8'
&& bytes[i + 3] == b';'
{
let mut j = i + 4;
while j < bytes.len() {
if bytes[j] == 0x07 {
j += 1;
break;
}
if bytes[j] == 0x1b && j + 1 < bytes.len() && bytes[j + 1] == b'\\' {
j += 2;
break;
}
j += 1;
}
i = j;
continue;
}
out.push(bytes[i] as char);
i += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static FLAG_GUARD: Mutex<()> = Mutex::new(());
fn strip(s: &str) -> String {
let mut out = String::with_capacity(s.len());
strip_into(s, &mut out);
out
}
#[test]
fn wrap_link_shape_is_osc_8_compliant() {
let wrapped = wrap_link("https://example.com", "click me");
assert_eq!(
wrapped,
"\x1b]8;;https://example.com\x1b\\click me\x1b]8;;\x1b\\"
);
}
#[test]
fn strip_removes_wrapper_keeps_label() {
let wrapped = wrap_link("https://example.com", "click me");
assert_eq!(strip(&wrapped), "click me");
}
#[test]
fn strip_handles_bel_terminator() {
let wrapped = "\x1b]8;;https://example.com\x07click me\x1b]8;;\x07";
assert_eq!(strip(wrapped), "click me");
}
#[test]
fn strip_passes_through_text_with_no_escapes() {
let plain = "no escapes here";
assert_eq!(strip(plain), plain);
}
#[test]
fn strip_preserves_non_osc_8_escapes() {
let mixed = format!(
"\x1b[31mred\x1b[0m {wrapped}",
wrapped = wrap_link("https://example.com", "click")
);
assert_eq!(strip(&mixed), "\x1b[31mred\x1b[0m click");
}
fn strip_ansi(s: &str) -> String {
let mut out = String::with_capacity(s.len());
strip_ansi_into(s, &mut out);
out
}
#[test]
fn strip_ansi_removes_csi_sgr_and_keeps_text() {
let coloured = "526 \x1b[1;32mOPEN\x1b[0m bug fix";
assert_eq!(strip_ansi(coloured), "526 OPEN bug fix");
}
#[test]
fn strip_ansi_removes_osc_8_wrapper() {
let wrapped = wrap_link("https://example.com", "click");
assert_eq!(strip_ansi(&wrapped), "click");
}
#[test]
fn strip_ansi_preserves_newlines_tabs_and_cr() {
let s = "a\nb\tc\rd";
assert_eq!(strip_ansi(s), "a\nb\tc\rd");
}
#[test]
fn strip_ansi_drops_lone_control_bytes() {
let s = "a\x07b\x01c";
assert_eq!(strip_ansi(s), "abc");
}
#[test]
fn enabled_is_true_by_default_when_untouched() {
let _g = FLAG_GUARD.lock().unwrap_or_else(|e| e.into_inner());
assert!(enabled());
}
#[test]
fn set_enabled_round_trips() {
let _g = FLAG_GUARD.lock().unwrap_or_else(|e| e.into_inner());
let prior = enabled();
set_enabled(false);
assert!(!enabled());
set_enabled(true);
assert!(enabled());
set_enabled(prior);
}
}