use std::fmt::Write;
pub const GROUPVAR: &str = ".zle.hlgroups";
pub fn convertattr(attrstr: &str, sgr: bool) -> String { let mut esc_stream = String::new();
for part in attrstr.split(',') {
let part = part.trim();
let attr_n: Option<i32> = match part {
"" | "none" | "reset" => Some(0),
"bold" => Some(1),
"dim" | "faint" => Some(2),
"italic" => Some(3),
"underline" => Some(4),
"blink" => Some(5),
"reverse" | "inverse" => Some(7),
"hidden" | "invisible" => Some(8),
"strikethrough" => Some(9),
_ => None,
};
if let Some(n) = attr_n {
let _ = write!(esc_stream, "\x1b[{}m", n);
continue;
}
let (is_fg, rest) = if let Some(r) = part.strip_prefix("fg=") {
(true, r)
} else if let Some(r) = part.strip_prefix("bg=") {
(false, r)
} else {
continue;
};
let base = if is_fg { 30 } else { 40 };
let bright_base = if is_fg { 90 } else { 100 };
let prefix = if is_fg { 38 } else { 48 };
let named: Option<i32> = match rest {
"black" => Some(base),
"red" => Some(base + 1),
"green" => Some(base + 2),
"yellow" => Some(base + 3),
"blue" => Some(base + 4),
"magenta" => Some(base + 5),
"cyan" => Some(base + 6),
"white" => Some(base + 7),
"default" => Some(base + 9),
_ => None,
};
if let Some(n) = named {
let _ = write!(esc_stream, "\x1b[{}m", n);
continue;
}
if let Some(inner) = rest.strip_prefix("bright-")
.or_else(|| rest.strip_prefix("light-"))
{
let bn: Option<i32> = match inner {
"black" => Some(bright_base),
"red" => Some(bright_base + 1),
"green" => Some(bright_base + 2),
"yellow" => Some(bright_base + 3),
"blue" => Some(bright_base + 4),
"magenta" => Some(bright_base + 5),
"cyan" => Some(bright_base + 6),
"white" => Some(bright_base + 7),
_ => None,
};
if let Some(n) = bn {
let _ = write!(esc_stream, "\x1b[{}m", n);
continue;
}
}
if let Ok(n) = rest.parse::<u8>() {
let _ = write!(esc_stream, "\x1b[{};5;{}m", prefix, n);
continue;
}
if let Some(hex) = rest.strip_prefix('#') {
if hex.len() == 6 {
let r = u8::from_str_radix(&hex[0..2], 16);
let g = u8::from_str_radix(&hex[2..4], 16);
let b = u8::from_str_radix(&hex[4..6], 16);
if let (Ok(r), Ok(g), Ok(b)) = (r, g, b) {
let _ = write!(esc_stream, "\x1b[{};2;{};{};{}m",
prefix, r, g, b);
}
}
}
}
if sgr {
let bytes = esc_stream.as_bytes();
let mut out = String::new();
let mut i = 0;
while i + 1 < bytes.len() && bytes[i] == 0x1b && bytes[i + 1] == b'[' {
i += 2; while i < bytes.len() {
let b = bytes[i];
if b.is_ascii_digit() { out.push(b as char); i += 1;
} else if b == b';' || b == b':' { out.push(';'); i += 1;
} else {
break; }
}
if i >= bytes.len() || bytes[i] != b'm' {
break; }
out.push(';'); i += 1; }
while out.ends_with(';') {
out.pop();
}
if out.is_empty() {
out.push('0');
}
out
} else {
esc_stream }
}
pub fn getgroup(_name: &str, _sgr: bool) -> Option<String> { None }
pub fn scangroup(_sgr: bool) -> Vec<(String, String)> { Vec::new() }
pub fn getpmesc(name: &str) -> Option<String> { getgroup(name, false) }
pub fn scanpmesc() -> Vec<(String, String)> { scangroup(false) }
pub fn getpmsgr(name: &str) -> Option<String> { getgroup(name, true) }
pub fn scanpmsgr() -> Vec<(String, String)> { scangroup(true) }
use crate::ported::zsh_h::module;
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 { 0 }
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 { *features = featuresarray(m, module_features());
0 }
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 { handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 { 0 }
pub fn cleanup_(m: *const module) -> i32 { setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0 }
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["p:.zle.esc".to_string(), "p:.zle.sgr".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 2]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 0,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 2,
n_abstract: 0,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn convertattr_bold_escape() {
assert_eq!(convertattr("bold", false), "\x1b[1m");
}
#[test]
fn convertattr_chained_escape() {
let s = convertattr("bold,underline", false);
assert!(s.contains("\x1b[1m"));
assert!(s.contains("\x1b[4m"));
}
#[test]
fn convertattr_fg_red_escape() {
let s = convertattr("fg=red", false);
assert!(s.contains("\x1b[31m"));
}
#[test]
fn convertattr_sgr_bold() {
assert_eq!(convertattr("bold", true), "1");
}
#[test]
fn convertattr_sgr_chain() {
let s = convertattr("bold,underline", true);
assert!(s.contains('1'));
assert!(s.contains('4'));
}
#[test]
fn convertattr_sgr_empty_returns_zero() {
assert_eq!(convertattr("", true), "0");
}
#[test]
fn convertattr_256_color() {
let s = convertattr("fg=196", false);
assert!(s.contains("\x1b[38;5;196m"));
}
#[test]
fn convertattr_truecolor() {
let s = convertattr("fg=#ff0000", false);
assert!(s.contains("\x1b[38;2;255;0;0m"));
}
#[test]
fn convertattr_sgr_256_color() {
let s = convertattr("fg=196", true);
assert!(s.contains("38;5;196"));
}
#[test]
fn convertattr_sgr_truecolor() {
let s = convertattr("fg=#00ff00", true);
assert!(s.contains("38;2;0;255;0"));
}
#[test]
fn getgroup_returns_none_until_paramtable_wired() {
assert_eq!(getgroup("any", false), None);
assert_eq!(getgroup("any", true), None);
}
#[test]
fn scangroup_returns_empty_until_paramtable_wired() {
assert!(scangroup(false).is_empty());
assert!(scangroup(true).is_empty());
}
#[test]
fn convertattr_empty_input_is_safe() {
let _ = convertattr("", false);
let _ = convertattr("", true);
}
#[test]
fn convertattr_bold_emits_sgr_bold() {
let s = convertattr("bold", false);
assert!(s.contains("\x1b[1m") || s.contains("\x1b[1;"),
"bold attr must emit SGR 1, got {:?}", s);
}
#[test]
fn convertattr_unknown_attr_is_safe() {
let _ = convertattr("definitely_not_a_real_attr", false);
}
#[test]
fn convertattr_truecolor_max_rgb() {
let s = convertattr("fg=#ffffff", false);
assert!(s.contains("38;2;255;255;255"),
"white truecolor must encode as 255;255;255, got {:?}", s);
}
#[test]
fn convertattr_256_color_upper_boundary() {
let s = convertattr("fg=255", false);
assert!(s.contains("38;5;255"),
"256-color upper boundary 255 must encode correctly, got {:?}", s);
}
#[test]
fn getpmesc_empty_or_unknown_returns_none() {
assert!(getpmesc("").is_none());
assert!(getpmesc("definitely_not_in_table_xyzzy").is_none());
}
#[test]
fn getpmsgr_empty_or_unknown_returns_none() {
assert!(getpmsgr("").is_none());
assert!(getpmsgr("definitely_not_in_table_xyzzy").is_none());
}
#[test]
fn scanpmesc_and_scanpmsgr_are_empty_until_wired() {
assert!(scanpmesc().is_empty());
assert!(scanpmsgr().is_empty());
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let m: *const module = std::ptr::null();
assert_eq!(setup_(m), 0);
let mut features = Vec::new();
assert_eq!(features_(m, &mut features), 0);
let mut enables: Option<Vec<i32>> = None;
assert_eq!(enables_(m, &mut enables), 0);
}
#[test]
fn convertattr_bg_color_uses_40_base() {
let s = convertattr("bg=blue", false);
assert!(s.contains("\x1b[44m"),
"c:40 — bg=blue → base 40 + 4 = 44 (got {:?})", s);
let s = convertattr("bg=red", false);
assert!(s.contains("\x1b[41m"));
let s = convertattr("bg=default", false);
assert!(s.contains("\x1b[49m"));
}
#[test]
fn convertattr_bright_prefix_uses_high_intensity_base() {
let s = convertattr("fg=bright-red", false);
assert!(s.contains("\x1b[91m"),
"fg=bright-red → 90+1=91 (got {:?})", s);
let s = convertattr("bg=bright-cyan", false);
assert!(s.contains("\x1b[106m"),
"bg=bright-cyan → 100+6=106 (got {:?})", s);
}
#[test]
fn convertattr_light_prefix_is_alias_for_bright() {
let bright = convertattr("fg=bright-green", false);
let light = convertattr("fg=light-green", false);
assert_eq!(bright, light,
"c:40 — light- and bright- prefixes must produce identical SGR codes");
}
#[test]
fn convertattr_sgr_bg_color() {
assert_eq!(convertattr("bg=blue", true), "44",
"SGR mode strips ESC[/m wrapper → bare digit string");
assert_eq!(convertattr("bg=red", true), "41");
}
#[test]
fn convertattr_unknown_color_drops_silently() {
let s = convertattr("fg=not_a_real_color", false);
assert_eq!(s, "",
"unknown color → no escape emitted");
let s = convertattr("fg=not_a_real_color", true);
assert_eq!(s, "0");
}
#[test]
fn convertattr_short_hex_dropped() {
let s = convertattr("fg=#abc", false);
assert_eq!(s, "",
"3-digit hex must be rejected per c:40 6-digit check");
let s = convertattr("fg=#abcdef00", false);
assert_eq!(s, "");
}
#[test]
fn convertattr_dim_and_faint_are_aliases() {
let dim = convertattr("dim", false);
let faint = convertattr("faint", false);
assert_eq!(dim, faint,
"dim and faint must produce identical SGR 2");
assert!(dim.contains("\x1b[2m"));
}
#[test]
fn convertattr_reverse_and_inverse_are_aliases() {
let rev = convertattr("reverse", false);
let inv = convertattr("inverse", false);
assert_eq!(rev, inv);
assert!(rev.contains("\x1b[7m"));
}
#[test]
fn convertattr_hidden_and_invisible_are_aliases() {
let h = convertattr("hidden", false);
let i = convertattr("invisible", false);
assert_eq!(h, i);
assert!(h.contains("\x1b[8m"));
}
}