use crate::ported::params::{TERMFLAGS };
use std::sync::atomic::Ordering;
use crate::ported::zsh_h::module;
use std::sync::{Mutex, OnceLock};
use crate::options::optlookup;
use crate::zsh_h::{isset, TERM_UNKNOWN};
#[link(name = "ncurses")]
extern "C" {
fn setupterm(
term: *const libc::c_char,
filedes: libc::c_int,
errret: *mut libc::c_int,
) -> libc::c_int;
fn tigetstr(capname: *const libc::c_char) -> *const libc::c_char;
fn tigetnum(capname: *const libc::c_char) -> libc::c_int;
fn tigetflag(capname: *const libc::c_char) -> libc::c_int;
fn putp(s: *const libc::c_char) -> libc::c_int;
fn tparm(
s: *const libc::c_char,
p1: libc::c_long,
p2: libc::c_long,
p3: libc::c_long,
p4: libc::c_long,
p5: libc::c_long,
p6: libc::c_long,
p7: libc::c_long,
p8: libc::c_long,
p9: libc::c_long,
) -> *const libc::c_char;
}
pub fn bin_echoti(
name: &str,
argv: &[String], _ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
const TERM_BAD: i32 = 1 << 1;
if argv.is_empty() {
crate::ported::utils::zwarnnam(name, "missing capability name");
return 1;
}
let s = &argv[0]; let argv_rest = &argv[1..];
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_BAD) != 0 {
return 1; }
let interactive = isset(optlookup("interactive")); if (TERMFLAGS.load(Ordering::Relaxed) & TERM_UNKNOWN) != 0 && interactive {
return 1; }
let cs = match std::ffi::CString::new(s.as_str()) {
Ok(c) => c,
Err(_) => return 1,
};
static ECHOTI_TERM_READY: OnceLock<bool> = OnceLock::new();
let term_ok = *ECHOTI_TERM_READY.get_or_init(|| {
let mut errret: libc::c_int = 0;
unsafe { setupterm(std::ptr::null(), 1, &mut errret) == 0 }
});
if !term_ok {
crate::ported::utils::zwarnnam(
name,
&format!("no such terminfo capability: {}", s),
);
return 1;
}
let num = unsafe { tigetnum(cs.as_ptr()) }; if num != -1 && num != -2 {
println!("{}", num); return 0; }
match unsafe { tigetflag(cs.as_ptr()) } {
-1 => {} 0 => {
println!("no");
return 0;
} _ => {
println!("yes");
return 0;
} }
let t = unsafe { tigetstr(cs.as_ptr()) }; let t_addr = t as isize;
if t.is_null() || t_addr == -1 || unsafe { *t } == 0 {
crate::ported::utils::zwarnnam(
name, &format!("no such terminfo capability: {}", s),
);
return 1; }
if argv_rest.len() > 9 {
crate::ported::utils::zwarnnam(name, "too many arguments"); return 1; }
let strcap = ["pfkey", "pfloc", "pfx", "pln", "pfxl"];
let strarg = strcap.iter().any(|c| s.as_str() == *c);
let mut pars: [libc::c_long; 9] = [0; 9]; let mut keep_alive: Vec<std::ffi::CString> = Vec::new(); for (i, a) in argv_rest.iter().enumerate().take(9) {
if strarg && i > 0 {
let cs = std::ffi::CString::new(a.as_str()).unwrap_or_default();
pars[i] = cs.as_ptr() as libc::c_long; keep_alive.push(cs);
} else {
pars[i] = a.parse::<libc::c_long>().unwrap_or(0); }
}
if argv_rest.is_empty() {
unsafe {
putp(t);
} } else {
let formatted = unsafe {
tparm(
t, pars[0], pars[1], pars[2], pars[3], pars[4], pars[5], pars[6], pars[7], pars[8],
)
};
if !formatted.is_null() {
unsafe {
putp(formatted);
}
}
}
drop(keep_alive);
0 }
pub fn getterminfo(_ht: *mut crate::ported::zsh_h::HashTable, name: &str) -> Option<crate::ported::zsh_h::Param> {
use crate::ported::zsh_h::{hashnode, param, PM_INTEGER, PM_READONLY, PM_SCALAR, PM_UNSET};
const TERM_BAD: i32 = 1 << 1;
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_BAD) != 0 {
return None;
}
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_UNKNOWN) != 0 {
if isset(optlookup("interactive")) {
return None;
}
}
static INITIALIZED: OnceLock<bool> = OnceLock::new();
let ok = *INITIALIZED.get_or_init(|| {
let mut errret: libc::c_int = 0;
unsafe { setupterm(std::ptr::null(), 1, &mut errret) == 0 }
});
if !ok {
return None;
}
let mk_str = |s: String, extra_flags: i32| -> crate::ported::zsh_h::Param {
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: PM_READONLY as i32 | extra_flags,
},
u_data: 0,
u_arr: None,
u_str: Some(s),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
})
};
let mut buf = name.as_bytes().to_vec();
crate::ported::utils::unmetafy(&mut buf);
let nameu = match std::str::from_utf8(&buf) {
Ok(s) => s.to_string(),
Err(_) => return None,
};
let cname = std::ffi::CString::new(nameu).ok()?;
unsafe {
let n = tigetnum(cname.as_ptr());
if n != -1 && n != -2 {
let mut pm = mk_str((n as i64).to_string(), PM_INTEGER as i32);
pm.u_val = n as i64;
return Some(pm);
}
let b = tigetflag(cname.as_ptr());
if b != -1 {
let s = if b != 0 { "yes" } else { "no" }.to_string();
return Some(mk_str(s, PM_SCALAR as i32));
}
let tistr = tigetstr(cname.as_ptr());
let s_addr = tistr as isize;
if !tistr.is_null() && s_addr != -1 {
let raw = std::ffi::CStr::from_ptr(tistr)
.to_string_lossy()
.into_owned();
return Some(mk_str(crate::ported::utils::metafy(&raw), PM_SCALAR as i32));
}
}
Some(mk_str(String::new(), PM_SCALAR as i32 | PM_UNSET as i32))
}
pub fn scanterminfo(
_ht: *mut crate::ported::zsh_h::HashTable,
func: Option<crate::ported::zsh_h::ScanFunc>,
flags: i32,
) {
use crate::ported::zsh_h::{hashnode, param, PM_SCALAR};
let f = match func {
Some(f) => f,
None => return,
};
let emit_cap = |cap_name: &str, val: &str| {
let pm = param {
node: hashnode {
next: None,
nam: cap_name.to_string(),
flags: PM_SCALAR as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(val.to_string()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
let node_box = Box::new(pm.node.clone());
f(&node_box, flags);
};
const TERM_BAD: i32 = 1 << 1;
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_BAD) != 0 {
return;
}
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_UNKNOWN) != 0 {
let interactive =
isset(optlookup("interactive"));
if interactive {
return;
}
}
static INITIALIZED: OnceLock<bool> = OnceLock::new();
let ok = *INITIALIZED.get_or_init(|| {
let mut errret: libc::c_int = 0;
unsafe { setupterm(std::ptr::null(), 1, &mut errret) == 0 }
});
if !ok {
return;
}
let boolnames = [
"bw", "am", "bce", "ccc", "xhp", "xhpa", "cpix", "crxm", "xt", "xenl", "eo", "gn", "hc",
"chts", "km", "daisy", "hs", "hls", "in", "lpix", "da", "db", "mir", "msgr", "nxon", "xsb",
"npc", "ndscr", "nrrmc", "os", "mc5i", "xvpa", "sam", "eslok", "hz", "ul", "xon",
];
let numnames = [
"cols", "it", "lh", "lw", "lines", "lm", "xmc", "ma", "colors", "pairs", "wnum", "ncv",
"nlab", "pb", "vt", "wsl", "bitwin", "bitype", "bufsz", "btns", "spinh", "spinv", "maddr",
"mjump", "mcs", "mls", "npins", "orc", "orhi", "orl", "orvi", "cps", "widcs",
];
let strnames: &[&str] = &[
"acsc", "cbt", "bel", "cr", "cpi", "lpi", "chr", "cvr", "csr", "rmp", "tbc", "mgc",
"clear", "el1", "el", "ed", "hpa", "cmdch", "cwin", "cup", "cud1", "home", "civis", "cub1",
"mrcup", "cnorm", "cuf1", "ll", "cuu1", "cvvis", "defc", "dch1", "dl1", "dial", "dsl",
"dclk", "hd", "enacs", "smacs", "smam", "blink", "bold", "smcup", "smdc", "dim", "swidm",
"sdrfq", "smir", "sitm", "slm", "smicm", "snlq", "snrmq", "prot", "rev", "invis", "sshm",
"smso", "ssubm", "ssupm", "smul", "sum", "smxon", "ech", "rmacs", "rmam", "sgr0", "rmcup",
"rmdc", "rwidm", "rmir", "ritm", "rlm", "rmicm", "rshm", "rmso", "rsubm", "rsupm", "rmul",
"rum", "rmxon", "pause", "hook", "flash", "ff", "fsl", "wingo", "hup", "is1", "is2", "is3",
"if", "iprog", "initc", "initp", "ich1", "il1", "ip", "ka1", "ka3", "kb2", "kbs", "kbeg",
"kcbt", "kc1", "kc3", "kcan", "ktbc", "kclr", "kclo", "kcmd", "kcpy", "kcrt", "kctab",
"kdch1", "kdl1", "kcud1", "krmir", "kend", "kent", "kel", "ked", "kext", "kf0", "kf1",
"kf10", "kf11", "kf12", "kf13", "kf14", "kf15", "kf16", "kf17", "kf18", "kf19", "kf2",
"kf20", "kf21", "kf22", "kf23", "kf24", "kf25", "kf26", "kf27", "kf28", "kf29", "kf3",
"kf30", "kf31", "kf32", "kf33", "kf34", "kf35", "kf36", "kf37", "kf38", "kf39", "kf4",
"kf40", "kf41", "kf42", "kf43", "kf44", "kf45", "kf46", "kf47", "kf48", "kf49", "kf5",
"kf50", "kf51", "kf52", "kf53", "kf54", "kf55", "kf56", "kf57", "kf58", "kf59", "kf6",
"kf60", "kf61", "kf62", "kf63", "kf7", "kf8", "kf9", "kfnd", "khlp", "khome", "kich1",
"kil1", "kcub1", "kll", "kmrk", "kmsg", "kmov", "knxt", "knp", "kopn", "kopt", "kpp",
"kprv", "kprt", "krdo", "kref", "krfr", "krpl", "krst", "kres", "kcuf1", "ksav", "kBEG",
"kCAN", "kCMD", "kCPY", "kCRT", "kDC", "kDL", "kslt", "kEND", "kEOL", "kEXT", "kind",
"kFND", "kHLP", "kHOM", "kIC", "kLFT", "kMSG", "kMOV", "kNXT", "kOPT", "kPRV", "kPRT",
"kri", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "khts", "kUND", "kspd", "kund",
"kcuu1", "rmkx", "smkx", "lf0", "lf1", "lf10", "lf2", "lf3", "lf4", "lf5", "lf6", "lf7",
"lf8", "lf9", "fln", "rmln", "smln", "rmm", "smm", "mhpa", "mcud1", "mcub1", "mcuf1",
"mvpa", "mcuu1", "nel", "porder", "oc", "op", "pad", "dch", "dl", "cud", "mcud", "ich",
"indn", "il", "cub", "mcub", "cuf", "mcuf", "rin", "cuu", "mcuu", "pfkey", "pfloc", "pfx",
"pln", "mc0", "mc5p", "mc4", "mc5", "pulse", "qdial", "rmclk", "rep", "rfi", "rs1", "rs2",
"rs3", "rf", "rc", "vpa", "sc", "ind", "ri", "scs", "sgr", "setb", "smgb", "smgbp", "sclk",
"scp", "setf", "smgl", "smglp", "smgr", "smgrp", "hts", "smgt", "smgtp", "wind", "sbim",
"scsd", "rbim", "rcsd", "subcs", "supcs", "ht", "docr", "tsl", "tone", "uc", "hu", "u0",
"u1", "u2", "u3", "u4", "u5", "u6", "u7", "u8", "u9", "wait", "xoffc", "xonc", "zerom",
"scesa", "bicr", "binel", "birep", "csnm", "csin", "colornm", "defbi", "devt", "dispc",
"endbi", "smpch", "smsc", "rmpch", "rmsc", "getm", "kmous", "minfo", "pctrm", "pfxl",
"reqmp", "scesc", "s0ds", "s1ds", "s2ds", "s3ds", "setab", "setaf", "setcolor", "smglr",
"slines", "smgtb", "ehhlm", "elhlm", "elohlm", "erhlm", "ethlm", "evhlm", "sgr1",
"slength",
];
for cap in &boolnames {
let cn = match std::ffi::CString::new(*cap) {
Ok(c) => c,
Err(_) => continue,
};
let n = unsafe { tigetflag(cn.as_ptr()) }; if n != -1 {
let v = if n != 0 { "yes" } else { "no" }; emit_cap(cap, v); }
}
for cap in &numnames {
let cn = match std::ffi::CString::new(*cap) {
Ok(c) => c,
Err(_) => continue,
};
let n = unsafe { tigetnum(cn.as_ptr()) }; if n != -1 && n != -2 {
emit_cap(cap, &n.to_string()); }
}
for cap in strnames {
let cn = match std::ffi::CString::new(*cap) {
Ok(c) => c,
Err(_) => continue,
};
let raw = unsafe { tigetstr(cn.as_ptr()) }; let s_addr = raw as isize;
if !raw.is_null() && s_addr != -1 {
let bytes = unsafe { std::ffi::CStr::from_ptr(raw) }
.to_string_lossy()
.into_owned();
emit_cap(cap, &crate::ported::utils::metafy(&bytes)); }
}
}
#[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
}
pub const COMMON_STRING_CAPS: &[&str] = &[
"kf1", "kf2", "kf3", "kf4", "kf5", "kf6", "kf7", "kf8", "kf9", "kf10", "kf11", "kf12", "kf13",
"kf14", "kf15", "kf16", "kf17", "kf18", "kf19", "kf20", "kcuu1", "kcud1", "kcuf1", "kcub1", "khome", "kend", "kpp", "knp", "kbs", "kich1", "kdch1", "clear", "ed", "el", "home", "civis", "cnorm", "smso", "rmso", "smul", "rmul", "bold", "rev", "sgr0",
"smkx", "rmkx", "smcup", "rmcup", "setaf", "setab",
"cup", "ich1", "dch1", "il1", "dl1",
];
static MODULE_FEATURES: OnceLock<Mutex<crate::ported::zsh_h::features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>) -> Vec<String> {
vec!["b:echoti".to_string(), "p:terminfo".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<crate::ported::zsh_h::features>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 2]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<crate::ported::zsh_h::features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(crate::ported::zsh_h::features {
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,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn getterminfo_unknown_cap_returns_unset_param() {
let _g = crate::test_util::global_state_lock();
use crate::ported::zsh_h::PM_UNSET;
if let Some(pm) = getterminfo(
std::ptr::null_mut(),
"definitely_not_a_real_cap_name_zshrs",
) {
assert!(
pm.node.flags & PM_UNSET as i32 != 0,
"PM_UNSET flag must be set for unknown cap"
);
assert_eq!(pm.u_str.as_deref(), Some(""), "u_str empty for unknown cap");
}
}
#[test]
fn echoti_with_no_args_is_usage_error() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
assert_eq!(bin_echoti("echoti", &[], &ops, 0), 1);
}
#[test]
fn echoti_unknown_capability_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_echoti("echoti", &["__not_a_terminfo_cap__".to_string()], &ops, 0);
assert_eq!(r, 1, "echoti must reject unknown caps, not emit garbage");
}
#[test]
fn scanterminfo_does_not_panic_for_dumb_term() {
let _g = crate::test_util::global_state_lock();
let old = std::env::var_os("TERM");
unsafe {
std::env::set_var("TERM", "dumb");
}
fn cb(_n: &crate::ported::zsh_h::HashNode, _f: i32) {}
scanterminfo(std::ptr::null_mut(), Some(cb), 0);
match old {
Some(v) => unsafe {
std::env::set_var("TERM", v);
},
None => unsafe {
std::env::remove_var("TERM");
},
}
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let _g = crate::test_util::global_state_lock();
let m = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn features_returns_success() {
let _g = crate::test_util::global_state_lock();
let mut features = Vec::new();
assert_eq!(features_(std::ptr::null(), &mut features), 0);
}
#[test]
fn enables_returns_success_with_none_arg() {
let _g = crate::test_util::global_state_lock();
let mut enables: Option<Vec<i32>> = None;
assert_eq!(enables_(std::ptr::null(), &mut enables), 0);
}
#[test]
fn terminfo_corpus_lifecycle_returns_zero() {
let _g = crate::test_util::global_state_lock();
let m: *const crate::ported::zsh_h::module = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn terminfo_corpus_unknown_cap_returns_unset_or_none() {
let _g = crate::test_util::global_state_lock();
let r = getterminfo(std::ptr::null_mut(), "zzz_not_a_real_cap_xyz");
if let Some(p) = r {
assert!(
(p.node.flags as u32 & crate::ported::zsh_h::PM_UNSET) != 0,
"unknown cap → PM_UNSET",
);
}
}
#[test]
fn terminfo_corpus_features_populates_vec() {
let _g = crate::test_util::global_state_lock();
let mut features = Vec::new();
let r = features_(std::ptr::null(), &mut features);
assert_eq!(r, 0);
}
}