use crate::ported::utils::zwarnnam;
use std::sync::Mutex;
use std::sync::atomic::{AtomicI32, Ordering};
use crate::ported::params::{TERMFLAGS, TERM_UNKNOWN};
static TERMCAP_LOCK: Mutex<()> = Mutex::new(());
static BOOLCODES: &[&str] = &[
"am", "bs", "bw", "da", "db", "eo", "es", "gn", "hc", "hs",
"in", "km", "mi", "ms", "nc", "ns", "os", "ul", "ut", "xb",
"xn", "xo", "xs", "xt",
];
static NUMCODES: &[&str] = &[
"co", "it", "lh", "lm", "lw", "li", "ma", "MW", "Nl", "pa",
"Nco", "sg", "tw", "ug", "vt", "ws",
];
static STRCODES: &[&str] = &[
"ae", "al", "AL", "ac", "as", "bc", "bl", "bt", "cb", "cd",
"ce", "cm", "cr", "cs", "ct", "cl", "cv", "DC", "DL", "DO",
"do", "ds", "ec", "ed", "ei", "fs", "ho", "hd", "hu", "i1",
"i3", "i2", "ic", "IC", "if", "im", "ip", "is", "kA", "kb",
"kB", "kC", "kd", "kD", "kE", "kF", "ke", "kh", "kH", "kI",
"kL", "kl", "kM", "km", "kN", "kP", "kr", "kR", "kS", "ks",
"kT", "kt", "ku", "l0", "l1", "l2", "l3", "l4", "l5", "l6",
"l7", "l8", "l9", "le", "ll", "ma", "mb", "MC", "md", "me",
"mh", "mk", "mm", "mo", "mp", "mr", "nd", "nl", "nw", "pc",
"pf", "pk", "pl", "pn", "po", "pO", "ps", "px", "rc", "rf",
"RI", "rp", "rs", "sa", "sc", "se", "SF", "sf", "so", "SR",
"sr", "st", "ta", "te", "ti", "ts", "uc", "ue", "up", "UP",
"us", "vb", "ve", "vi", "vs", "wi",
];
unsafe extern "C" {
fn tgetent(bp: *mut libc::c_char, name: *const libc::c_char) -> libc::c_int;
fn tgetflag(id: *const libc::c_char) -> libc::c_int;
fn tgetnum(id: *const libc::c_char) -> libc::c_int;
fn tgetstr(id: *const libc::c_char, area: *mut *mut libc::c_char) -> *mut libc::c_char;
}
fn ensure_termcap_loaded() -> bool {
static STATE: AtomicI32 = AtomicI32::new(0);
match STATE.load(Ordering::Relaxed) {
1 => true,
-1 => false,
_ => {
let term = std::env::var("TERM").unwrap_or_else(|_| "dumb".into());
let term_c = match std::ffi::CString::new(term) { Ok(c) => c, Err(_) => return false };
let r = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
unsafe { tgetent(std::ptr::null_mut(), term_c.as_ptr()) }
};
let ok = r > 0;
STATE.store(if ok { 1 } else { -1 }, Ordering::Relaxed);
ok
}
}
}
pub fn ztgetflag(s: &str) -> i32 { if !ensure_termcap_loaded() {
return -1; }
let s_c = match std::ffi::CString::new(s) { Ok(c) => c, Err(_) => return -1 };
let flag = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
unsafe { tgetflag(s_c.as_ptr()) }
};
match flag { 1 => 1, _ => {
for b in BOOLCODES { if *b == s { return 0; }
}
-1 }
}
}
pub fn bin_echotc(name: &str, argv: &[&str], _ops: &[bool; 256]) -> i32 { const TERM_BAD: i32 = 1 << 1;
if argv.is_empty() { zwarnnam(name, "missing argument");
return 1;
}
let s = argv[0];
let argv_rest: Vec<&str> = argv[1..].to_vec();
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_BAD) != 0 { return 1; }
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_UNKNOWN) != 0 { let interactive = crate::ported::zsh_h::isset(crate::ported::options::optlookup("interactive"));
if interactive || !ensure_termcap_loaded() { return 1; }
}
if !ensure_termcap_loaded() {
return 1;
}
let s_c = match std::ffi::CString::new(s) { Ok(c) => c, Err(_) => return 1 };
let num = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
unsafe { tgetnum(s_c.as_ptr()) }
}; if num != -1 { println!("{}", num); return 0; }
match ztgetflag(s) { -1 => {} 0 => { println!("no"); return 0; }
_ => { println!("yes"); return 0; }
}
let mut buf: [libc::c_char; 2048] = [0; 2048]; let mut area = buf.as_mut_ptr();
let value = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let t = unsafe { tgetstr(s_c.as_ptr(), &mut area) }; if t.is_null() || (t as isize) == -1 || unsafe { *t } == 0 { drop(_g);
zwarnnam(name, &format!("no such capability: {}", s)); return 1; }
unsafe { std::ffi::CStr::from_ptr(t) }.to_string_lossy().into_owned()
};
let mut argct = 0usize; let bytes = value.as_bytes();
let mut i = 0;
while i < bytes.len() { if bytes[i] == b'%' { i += 1;
if i < bytes.len() { match bytes[i] { b'd' | b'2' | b'3' | b'.' | b'+' => argct += 1, _ => {}
}
}
}
i += 1;
}
if argv_rest.len() != argct { let msg = if argv_rest.len() < argct { "not enough arguments" } else { "too many arguments" }; zwarnnam(name, msg); return 1; }
if argct == 0 { print!("{}", value); } else {
let mut out = value;
for arg in &argv_rest {
out = out.replacen("%d", arg, 1);
out = out.replacen("%2", arg, 1);
out = out.replacen("%3", arg, 1);
}
print!("{}", out); }
0 }
pub fn gettermcap(name: &str) -> Option<String> { if !ensure_termcap_loaded() { return None; }
let n_c = std::ffi::CString::new(name).ok()?;
let mut buf: [libc::c_char; 1024] = [0; 1024];
let mut area = buf.as_mut_ptr();
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let raw = unsafe { tgetstr(n_c.as_ptr(), &mut area) }; if !raw.is_null() {
return Some(unsafe { std::ffi::CStr::from_ptr(raw) }.to_string_lossy().into_owned());
}
let n = unsafe { tgetnum(n_c.as_ptr()) }; if n != -1 {
return Some(n.to_string());
}
match unsafe { tgetflag(n_c.as_ptr()) } { 1 => Some("yes".to_string()),
0 => {
if BOOLCODES.iter().any(|b| *b == name) {
Some(String::new())
} else {
None
}
}
_ => None,
}
}
pub fn scantermcap() -> Vec<(String, String)> { let mut out = Vec::new();
if !ensure_termcap_loaded() { return out; }
for &name in BOOLCODES.iter().chain(NUMCODES.iter()).chain(STRCODES.iter()) {
if let Some(v) = gettermcap(name) {
out.push((name.to_string(), v));
}
}
out
}
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 { let _ = crate::ported::utils::zsetupterm(); 0
}
pub fn cleanup_(m: *const module) -> i32 { setfeatureenables(m, module_features(), None)
}
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ztgetflag_known_on_returns_one() {
assert_eq!(ztgetflag("am"), 1);
}
#[test]
fn ztgetflag_unknown_returns_minus_one() {
assert_eq!(ztgetflag("zz"), -1);
}
#[test]
fn gettermcap_co_returns_columns() {
let v = gettermcap("co");
assert!(v.is_some());
let n: i32 = v.unwrap().parse().unwrap_or(0);
assert!(n > 0);
}
#[test]
fn gettermcap_unknown_returns_none() {
assert!(gettermcap("zz_nonexistent").is_none());
}
#[test]
fn scantermcap_emits_bool_caps() {
let v = scantermcap();
assert!(v.iter().any(|(k, _)| k == "am"));
}
}
use crate::ported::zsh_h::features as features_t;
use std::sync::OnceLock;
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 1,
n_abstract: 0,
}))
}
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:echotc".to_string(), "p:termcap".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
}