use std::collections::HashSet;
use std::sync::atomic::AtomicI32;
use std::sync::LazyLock;
use crate::ported::init::SHTTY;
use crate::ported::jobs::{acquire_pgrp, ORIGPGRP};
use crate::ported::params::{keyboardhacksetfn, paramtab};
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::utils::zwarnnam;
use crate::ported::zsh_h::{
interact, isset, opt_name, options, APPENDHISTORY, BANGHIST, CHASELINKS, EMACSMODE,
EMULATE_CSH, EMULATE_FULLY, EMULATE_KSH, EMULATE_SH, EMULATE_UNUSED, EMULATE_ZSH, EXECOPT,
GLOBDOTS, HASHCMDS, HISTNOFUNCTIONS, IGNOREBRACES, INTERACTIVE, LOGINSHELL, MAILWARNING, Meta,
MONITOR, MULTIBYTE, OPT_INVALID, OPT_SIZE, PAT_HEAPDUP, PROMPTSUBST, SHINSTDIN, SINGLECOMMAND,
SUNKEYBOARDHACK, USEZLE, VIMODE,
};
use crate::utils::inittyptab;
const OPT_CSH: u8 = EMULATE_CSH as u8; const OPT_KSH: u8 = EMULATE_KSH as u8; const OPT_SH: u8 = EMULATE_SH as u8; const OPT_ZSH: u8 = EMULATE_ZSH as u8; const OPT_ALL: u8 = OPT_CSH | OPT_KSH | OPT_SH | OPT_ZSH; const OPT_BOURNE: u8 = OPT_KSH | OPT_SH; const OPT_BSHELL: u8 = OPT_KSH | OPT_SH | OPT_ZSH; const OPT_NONBOURNE: u8 = OPT_ALL & !OPT_BOURNE; const OPT_NONZSH: u8 = OPT_ALL & !OPT_ZSH;
const OPT_EMULATE: u16 = EMULATE_UNUSED as u16; const OPT_SPECIAL: u16 = (EMULATE_UNUSED << 1) as u16; const OPT_ALIAS: u16 = (EMULATE_UNUSED << 2) as u16;
pub static zshletters: &[(char, &str, bool)] = &[
('0', "correct", false),
('1', "printexitvalue", false),
('2', "badpattern", true),
('3', "nomatch", true),
('4', "globdots", false),
('5', "notify", false),
('6', "bgnice", false),
('7', "ignoreeof", false),
('8', "markdirs", false),
('9', "autolist", false),
('B', "beep", true),
('C', "clobber", true),
('D', "pushdtohome", false),
('E', "pushdsilent", false),
('F', "glob", true),
('G', "nullglob", false),
('H', "rmstarsilent", false),
('I', "ignorebraces", false),
('J', "autocd", false),
('K', "banghist", true),
('L', "sunkeyboardhack", false),
('M', "singlelinezle", false),
('N', "autopushd", false),
('O', "correctall", false),
('P', "rcexpandparam", false),
('Q', "pathdirs", false),
('R', "longlistjobs", false),
('S', "recexact", false),
('T', "cdablevars", false),
('U', "mailwarning", false),
('V', "promptcr", true),
('W', "autoresume", false),
('X', "listtypes", false),
('Y', "menucomplete", false),
('Z', "zle", false),
('a', "allexport", false),
('d', "globalrcs", true),
('e', "errexit", false),
('f', "rcs", true),
('g', "histignorespace", false),
('h', "histignoredups", false),
('i', "interactive", false),
('k', "interactivecomments", false),
('l', "login", false),
('m', "monitor", false),
('n', "exec", true),
('p', "privileged", false),
('s', "shinstdin", false),
('t', "singlecommand", false),
('u', "unset", true),
('v', "verbose", false),
('w', "chaselinks", false),
('x', "xtrace", false),
('y', "shwordsplit", false),
];
pub fn printoptionnode(hn: &str, set: bool) {
let on = opt_state_get(hn).unwrap_or(false); let default_on = default_on_options().contains(&hn); let kshprint = opt_state_get("kshoptionprint").unwrap_or(false); if kshprint {
if default_on {
println!("no{:<19} {}", hn, if on { "off" } else { "on" }); } else {
println!("{:<21} {}", hn, if on { "on" } else { "off" }); }
} else if set == (on ^ default_on) {
if set ^ on {
print!("no"); }
println!("{}", hn); }
}
pub fn createoptiontable() {
let zsh_emu = EMULATE_ZSH;
for name in ZSH_OPTIONS_SET.iter() {
opt_state_set(name, defset(name, zsh_emu));
}
}
pub fn setemulate(name: &str, fully: i32) {
let flags = optns_flags(name); let is_alias = (flags & OPT_ALIAS) != 0;
let is_special = (flags & OPT_SPECIAL) != 0;
let is_emulate = (flags & OPT_EMULATE) != 0;
if is_alias {
return;
}
if !((fully != 0 && !is_special) || is_emulate) {
return;
}
let target = SETEMULATE_EMULATION.load(std::sync::atomic::Ordering::Relaxed);
let on_by_default = defset(name, target);
if let Ok(mut tab) = setemulate_opts_lock().lock() {
tab.insert(name.to_string(), on_by_default);
}
}
pub fn installemulation(
new_emulation: i32,
new_opts: &mut std::collections::HashMap<String, bool>,
) {
SETEMULATE_EMULATION.store(new_emulation, std::sync::atomic::Ordering::Relaxed); if let Ok(mut tab) = setemulate_opts_lock().lock() {
tab.clear();
}
let fully = if (new_emulation & EMULATE_FULLY) != 0 {
1
} else {
0
}; for name in ZSH_OPTIONS_SET.iter() {
setemulate(name, fully); }
if let Ok(tab) = setemulate_opts_lock().lock() {
for (k, v) in tab.iter() {
new_opts.insert(k.clone(), *v);
}
}
}
pub fn emulate(mode: &str, fully: bool) {
let ch = mode.chars().next().unwrap_or('z');
let ch = if ch == 'r' {
mode.chars().nth(1).unwrap_or('z')
} else {
ch
};
let new_emu = match ch {
'c' => EMULATE_CSH,
'k' => EMULATE_KSH,
's' | 'b' => EMULATE_SH,
_ => EMULATE_ZSH,
};
EMULATION.store(new_emu, std::sync::atomic::Ordering::Relaxed);
FULLY_EMULATING.store(fully, std::sync::atomic::Ordering::Relaxed);
let mut emu = new_emu;
if fully {
emu |= EMULATE_FULLY; }
let mut new_opts: std::collections::HashMap<String, bool> = std::collections::HashMap::new();
installemulation(emu, &mut new_opts); for (k, v) in &new_opts {
if (optns_flags(k) & OPT_SPECIAL) == 0 {
opt_state_set(k, *v);
}
}
if new_emu == EMULATE_ZSH {
for name in ZSH_OPTIONS_SET.iter() {
opt_state_set(name, defset(name, EMULATE_ZSH));
}
}
}
pub fn setoption(hn: &str, value: i32) -> i32 {
opt_state_set(hn, value != 0); 0
}
pub fn bin_setopt(
nam: &str,
args: &[String], _ops: &options,
isun: i32,
) -> i32 {
let mut retval = 0i32;
let mut match_glob = false; let mut idx = 0usize;
if args.is_empty() {
let want_set = isun == 0;
let mut names: Vec<String> = ZSH_OPTIONS_SET.iter().map(|s| s.to_string()).collect();
names.sort();
for n in names {
let on = opt_state_get(&n).unwrap_or(false);
if on == want_set {
printoptionnode(&n, want_set); }
}
return 0; }
'outer: while idx < args.len() && (args[idx].starts_with('-') || args[idx].starts_with('+')) {
let leading = args[idx].as_bytes()[0]; let action: i32 = ((leading == b'-') as i32) ^ isun; if args[idx].len() == 1 {
idx += 1;
break 'outer;
}
let body_bytes = args[idx].as_bytes()[1..].to_vec(); let mut k = 0usize;
while k < body_bytes.len() {
let mut c = body_bytes[k];
if c == Meta {
k += 1;
if k < body_bytes.len() {
c = body_bytes[k] ^ 32;
}
else {
break;
}
}
if c == b'-' {
idx += 1; break 'outer; } else if c == b'o' {
let oarg: String = if k + 1 < body_bytes.len() {
String::from_utf8_lossy(&body_bytes[k + 1..]).into_owned()
} else {
idx += 1; if idx >= args.len() {
zwarnnam(nam, "string expected after -o"); return 1; }
args[idx].clone()
};
let optno = optlookup(&oarg); if optno == 0 {
zwarnnam(
nam, &format!("no such option: {}", oarg),
);
retval |= 1;
} else if dosetopt(optno, action, 0) != 0 {
zwarnnam(
nam, &format!("can't change option: {}", oarg),
);
retval |= 1;
}
break; } else if c == b'm' {
match_glob = true; } else {
let optno = optlookupc(c as char); if optno == 0 {
zwarnnam(nam, &format!("bad option: -{}", c as char)); retval |= 1;
} else if dosetopt(optno, action, 0) != 0 {
zwarnnam(
nam, &format!("can't change option: -{}", c as char),
);
retval |= 1;
}
}
k += 1;
}
idx += 1; }
if !match_glob {
while idx < args.len() {
let oname = args[idx].clone();
idx += 1;
let optno = optlookup(&oname); if optno == 0 {
zwarnnam(
nam, &format!("no such option: {}", oname),
);
retval |= 1;
} else {
let v = (isun == 0) as i32; if dosetopt(optno, v, 0) != 0 {
zwarnnam(
nam, &format!("can't change option: {}", oname),
);
retval |= 1;
}
}
}
} else {
while idx < args.len() {
let raw = args[idx].clone();
idx += 1;
let normalized: String = raw
.chars()
.filter(|&c| c != '_')
.map(|c| c.to_ascii_lowercase())
.collect();
let prog = patcompile(
&normalized,
PAT_HEAPDUP,
None,
);
if prog.is_none() {
zwarnnam(nam, &format!("bad pattern: {}", raw)); retval |= 1;
break; }
let v = (isun == 0) as i32;
if let Some(prog) = patcompile(&normalized, PAT_HEAPDUP as i32, None) {
for opt_name in ZSH_OPTIONS_SET.iter() {
if pattry(&prog, opt_name) {
let _ = setoption(opt_name, v); }
}
}
}
}
inittyptab(); retval }
pub fn optlookup(name: &str) -> i32 {
let s: String = name
.chars() .filter(|&c| c != '_') .map(|c| {
if ('A'..='Z').contains(&c) {
((c as u8 - b'A') + b'a') as char } else {
c
}
})
.collect();
let alias_optno: Option<i32> = match s.as_str() {
"braceexpand" => Some(-IGNOREBRACES), "dotglob" => Some(GLOBDOTS), "hashall" => Some(HASHCMDS), "histappend" => Some(APPENDHISTORY), "histexpand" => Some(BANGHIST), "log" => Some(-HISTNOFUNCTIONS), "mailwarn" => Some(MAILWARNING), "onecmd" => Some(SINGLECOMMAND), "physical" => Some(CHASELINKS), "promptvars" => Some(PROMPTSUBST), "stdin" => Some(SHINSTDIN), "trackall" => Some(HASHCMDS), "login" => Some(LOGINSHELL), _ => None,
};
if let Some(optno) = alias_optno {
return optno;
}
if let Some(stripped) = s.strip_prefix("no") {
if let Some(optno) = optno_by_name(stripped) {
return -optno; }
}
match optno_by_name(&s) {
Some(optno) => optno, None => OPT_INVALID, }
}
pub fn optlookupc(c: char) -> i32 {
let letters = if isset(optlookup("shoptionletters")) {
KSH_LETTERS
} else {
zshletters
};
for (ch, name, negated) in letters {
if *ch == c {
let optno = optno_by_name(name).unwrap_or(0); return if *negated { -optno } else { optno };
}
}
0
}
pub fn dosetopt(optno: i32, mut value: i32, force: i32) -> i32 {
if optno == 0 {
return -1;
}
let mut idx = optno;
if idx < 0 {
idx = -idx;
value = if value != 0 { 0 } else { 1 }; }
if force == 0 {
if idx == EXECOPT && value == 0 && interact() {
return -1;
}
if idx == INTERACTIVE || idx == SHINSTDIN || idx == SINGLECOMMAND {
let cur_name = ZSH_OPTIONS_SET.iter().find(|n| optlookup(n) == idx);
if let Some(name) = cur_name {
let cur = opt_state_get(name).unwrap_or(false);
if cur as i32 == value {
return 0; }
}
return -1; }
if idx == USEZLE && value != 0 && !interact() {
return -1;
}
if idx == MONITOR && value != 0 {
let cur_name = ZSH_OPTIONS_SET.iter().find(|n| optlookup(n) == idx);
if let Some(name) = cur_name {
let cur = opt_state_get(name).unwrap_or(false);
if cur as i32 == value {
return 0;
}
}
let shtty = SHTTY.load(std::sync::atomic::Ordering::SeqCst);
if shtty == -1 {
return -1;
}
let origpgrp = ORIGPGRP.get_or_init(|| std::sync::Mutex::new(0));
let mut og = origpgrp.lock().expect("origpgrp poisoned");
if *og == 0 {
*og = unsafe { libc::getpgrp() }; drop(og);
let _ = acquire_pgrp(); }
}
}
{
if (idx == EMACSMODE || idx == VIMODE) && value != 0 {
let other = idx ^ EMACSMODE ^ VIMODE;
let other_name = opt_name(other);
if !other_name.is_empty() {
opt_state_set(other_name, false);
}
}
}
{
if idx == SUNKEYBOARDHACK {
let new_val = if value != 0 {
"`".to_string()
} else {
String::new()
};
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut("KEYBOARD_HACK") {
keyboardhacksetfn(pm, new_val);
}
}
}
}
let name = ZSH_OPTIONS_SET.iter().find(|n| optlookup(n) == idx);
let ret = match name {
Some(n) => {
opt_state_set(n, value != 0);
0
} None => -1, };
if ret == 0 && (idx == MULTIBYTE || idx == BANGHIST || idx == SHINSTDIN)
{
inittyptab(); }
ret
}
pub fn dashgetfn() -> String {
const FIRST_OPT: u8 = b'0'; const LAST_OPT: u8 = b'y'; let letters = if isset(optlookup("shoptionletters")) {
KSH_LETTERS
} else {
zshletters
};
let mut out = String::new();
for c in (FIRST_OPT..=LAST_OPT).map(|b| b as char) {
for (ch, name, negated) in letters {
if *ch == c {
let value = opt_state_get(name).unwrap_or(false); let effective = if *negated { !value } else { value };
if effective {
out.push(c);
}
break;
}
}
}
out
}
pub fn printoptionstates(hadplus: bool) {
let mut names: Vec<&'static str> = ZSH_OPTIONS_SET.iter().copied().collect();
names.sort();
for n in names {
let value = opt_state_get(n).unwrap_or(false);
printoptionnodestate(n, value, hadplus); }
}
pub fn printoptionnodestate(name: &str, value: bool, hadplus: bool) {
let default_on = default_on_options().contains(&name); if hadplus {
let sign = if default_on != value { '-' } else { '+' }; let no_prefix = if default_on { "no" } else { "" }; println!("set {}o {}{}", sign, no_prefix, name); } else {
if default_on {
println!(
"no{:<19} {}",
name, if value { "off" } else { "on" }
);
} else {
println!(
"{:<21} {}",
name, if value { "on" } else { "off" }
);
}
}
}
pub fn printoptionlist() {
println!();
println!("Named options:"); let mut names: Vec<&'static str> = ZSH_OPTIONS_SET.iter().copied().collect();
names.sort();
for n in &names {
printoptionlist_printoption(n, 0); }
println!();
println!("Option aliases:"); println!();
println!("Option letters:"); let letters = if isset(optlookup("shoptionletters")) {
KSH_LETTERS
} else {
zshletters
};
for c in (b'A'..=b'z').map(|b| b as char) {
for (ch, aname, _negated) in letters {
if *ch == c {
print!(" -{} ", c); printoptionlist_printequiv(optlookup(aname)); break;
}
}
}
}
pub fn printoptionlist_printoption(name: &str, _ignored: i32) {
println!(" --{}", name); }
pub fn printoptionlist_printequiv(optno: i32) {
let isneg = optno < 0; let abs_optno = if isneg { -optno } else { optno }; let prefix = if isneg { "no-" } else { "" }; let name = ZSH_OPTIONS_SET
.iter()
.find(|n| optlookup(n) == abs_optno)
.copied()
.unwrap_or("?"); println!(" equivalent to --{}{}", prefix, name); }
pub fn print_emulate_option(name: &str, value: bool, _fully: bool) {
if !value {
print!("no"); }
println!("{}", name); }
#[allow(non_upper_case_globals)]
pub static emulation: AtomicI32 = AtomicI32::new(0);
pub static KSH_LETTERS: &[(char, &str, bool)] = &[
('C', "clobber", true),
('T', "trapsasync", false),
('X', "markdirs", false),
('a', "allexport", false),
('b', "notify", false),
('e', "errexit", false),
('f', "glob", true),
('i', "interactive", false),
('l', "login", false),
('m', "monitor", false),
('n', "exec", true),
('p', "privileged", false),
('s', "shinstdin", false),
('t', "singlecommand", false),
('u', "unset", true),
('v', "verbose", false),
('x', "xtrace", false),
];
pub static EMULATION: AtomicI32 =
AtomicI32::new(EMULATE_ZSH);
pub static FULLY_EMULATING: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub static ZSH_OPTIONS_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
[
"aliases",
"aliasfuncdef",
"allexport",
"alwayslastprompt",
"alwaystoend",
"appendcreate",
"appendhistory",
"autocd",
"autocontinue",
"autolist",
"automenu",
"autonamedirs",
"autoparamkeys",
"autoparamslash",
"autopushd",
"autoremoveslash",
"autoresume",
"badpattern",
"banghist",
"bareglobqual",
"bashautolist",
"bashrematch",
"beep",
"bgnice",
"braceccl",
"bsdecho",
"caseglob",
"casematch",
"casepaths",
"cbases",
"cdablevars",
"cdsilent",
"chasedots",
"chaselinks",
"checkjobs",
"checkrunningjobs",
"clobber",
"clobberempty",
"combiningchars",
"completealiases",
"completeinword",
"continueonerror",
"correct",
"correctall",
"cprecedences",
"cshjunkiehistory",
"cshjunkieloops",
"cshjunkiequotes",
"cshnullcmd",
"cshnullglob",
"debugbeforecmd",
"dotglob",
"dvorak",
"emacs",
"equals",
"errexit",
"errreturn",
"evallineno",
"exec",
"extendedglob",
"extendedhistory",
"flowcontrol",
"forcefloat",
"functionargzero",
"glob",
"globassign",
"globcomplete",
"globdots",
"globstarshort",
"globsubst",
"globalexport",
"globalrcs",
"hashall",
"hashcmds",
"hashdirs",
"hashexecutablesonly",
"hashlistall",
"histallowclobber",
"histappend",
"histbeep",
"histexpand",
"histexpiredupsfirst",
"histfcntllock",
"histfindnodups",
"histignorealldups",
"histignoredups",
"histignorespace",
"histlexwords",
"histnofunctions",
"histnostore",
"histreduceblanks",
"histsavebycopy",
"histsavenodups",
"histsubstpattern",
"histverify",
"hup",
"ignorebraces",
"ignoreclosebraces",
"ignoreeof",
"incappendhistory",
"incappendhistorytime",
"interactive",
"interactivecomments",
"ksharrays",
"kshautoload",
"kshglob",
"kshoptionprint",
"kshtypeset",
"kshzerosubscript",
"listambiguous",
"listbeep",
"listpacked",
"listrowsfirst",
"listtypes",
"localloops",
"localoptions",
"localpatterns",
"localtraps",
"log",
"login",
"longlistjobs",
"magicequalsubst",
"mailwarn",
"mailwarning",
"markdirs",
"menucomplete",
"monitor",
"multibyte",
"multifuncdef",
"multios",
"nomatch",
"notify",
"nullglob",
"numericglobsort",
"octalzeroes",
"onecmd",
"overstrike",
"pathdirs",
"pathscript",
"physical",
"pipefail",
"posixaliases",
"posixargzero",
"posixbuiltins",
"posixcd",
"posixidentifiers",
"posixjobs",
"posixstrings",
"posixtraps",
"printeightbit",
"printexitvalue",
"privileged",
"promptbang",
"promptcr",
"promptpercent",
"promptsp",
"promptsubst",
"promptvars",
"pushdignoredups",
"pushdminus",
"pushdsilent",
"pushdtohome",
"rcexpandparam",
"rcquotes",
"rcs",
"recexact",
"rematchpcre",
"restricted",
"rmstarsilent",
"rmstarwait",
"sharehistory",
"shfileexpansion",
"shglob",
"shinstdin",
"shnullcmd",
"shoptionletters",
"shortloops",
"shortrepeat",
"shwordsplit",
"singlecommand",
"singlelinezle",
"sourcetrace",
"stdin",
"sunkeyboardhack",
"trackall",
"transientrprompt",
"trapsasync",
"typesetsilent",
"typesettounset",
"unset",
"verbose",
"vi",
"warncreateglobal",
"warnnestedvar",
"xtrace",
"zle",
"braceexpand", "dotglob", "hashall", "histappend", "histexpand", "log", "mailwarn", "onecmd", "physical", "promptvars", ]
.into_iter()
.collect()
});
static SETEMULATE_EMULATION: AtomicI32 = AtomicI32::new(0);
static SETEMULATE_OPTS: std::sync::OnceLock<
std::sync::Mutex<std::collections::HashMap<String, bool>>,
> = std::sync::OnceLock::new();
static OPTS_LIVE: std::sync::OnceLock<std::sync::RwLock<std::collections::HashMap<String, bool>>> =
std::sync::OnceLock::new();
#[inline]
pub fn defset(X: &str, my_emulation: i32) -> bool {
let flags = optns_flags(X);
(flags & (my_emulation as u16)) != 0
}
fn optns_flags(name: &str) -> u16 {
match name.to_lowercase().as_str() {
"aliases" => OPT_EMULATE | (OPT_ALL as u16), "aliasfuncdef" => OPT_EMULATE | (OPT_BOURNE as u16), "allexport" => OPT_EMULATE, "alwayslastprompt" => OPT_ALL as u16, "alwaystoend" => 0, "appendcreate" => OPT_EMULATE | (OPT_BOURNE as u16), "appendhistory" => OPT_ALL as u16, "autocd" => OPT_EMULATE, "autocontinue" => 0, "autolist" => OPT_ALL as u16, "automenu" => OPT_ALL as u16, "autonamedirs" => 0, "autoparamkeys" => OPT_ALL as u16, "autoparamslash" => OPT_ALL as u16, "autopushd" => 0, "autoremoveslash" => OPT_ALL as u16, "autoresume" => 0, "badpattern" => OPT_EMULATE | (OPT_NONBOURNE as u16), "banghist" => OPT_NONBOURNE as u16, "bareglobqual" => OPT_EMULATE | (OPT_ZSH as u16), "bashautolist" => 0, "bashrematch" => OPT_EMULATE, "beep" => OPT_ALL as u16, "bgnice" => OPT_EMULATE | (OPT_NONBOURNE as u16), "braceccl" => 0, "bsdecho" => OPT_EMULATE | (OPT_SH as u16), "caseglob" => OPT_ALL as u16, "casematch" => OPT_ALL as u16, "casepaths" => 0, "cbases" => 0, "cdablevars" => OPT_EMULATE, "cdsilent" => 0, "chasedots" => 0, "chaselinks" => 0, "checkjobs" => OPT_EMULATE | (OPT_ZSH as u16), "checkrunningjobs" => OPT_EMULATE | (OPT_ZSH as u16), "clobber" => OPT_EMULATE | (OPT_ALL as u16), "clobberempty" => 0, "combiningchars" => 0, "completealiases" => 0, "completeinword" => 0, "correct" => 0, "correctall" => 0, "cprecedences" => OPT_EMULATE | (OPT_NONZSH as u16), "cshjunkiehistory" => OPT_EMULATE | (OPT_CSH as u16), "cshjunkieloops" => OPT_EMULATE | (OPT_CSH as u16), "cshjunkiequotes" => OPT_EMULATE | (OPT_CSH as u16), "cshnullcmd" => OPT_EMULATE | (OPT_CSH as u16), "cshnullglob" => OPT_EMULATE | (OPT_CSH as u16), "debugbeforecmd" => OPT_ALL as u16, "emacs" => 0, "equals" => OPT_EMULATE | (OPT_NONBOURNE as u16), "errexit" => OPT_EMULATE, "errreturn" => OPT_EMULATE, "exec" => OPT_ALL as u16, "extendedglob" => OPT_EMULATE, "extendedhistory" => OPT_CSH as u16, "evallineno" => OPT_EMULATE | (OPT_ZSH as u16), "flowcontrol" => OPT_ALL as u16, "forcefloat" => 0, "functionargzero" => OPT_EMULATE | (OPT_NONBOURNE as u16), "glob" => OPT_EMULATE | (OPT_ALL as u16), "globalexport" => OPT_EMULATE | (OPT_ZSH as u16), "globalrcs" => OPT_ALL as u16, "globassign" => OPT_EMULATE | (OPT_CSH as u16), "globcomplete" => 0, "globdots" => OPT_EMULATE, "globstarshort" => OPT_EMULATE, "globsubst" => OPT_EMULATE | (OPT_NONZSH as u16), "hashcmds" => OPT_ALL as u16, "hashdirs" => OPT_ALL as u16, "hashexecutablesonly" => 0, "hashlistall" => OPT_ALL as u16, "histallowclobber" => 0, "histbeep" => OPT_ALL as u16, "histexpiredupsfirst" => 0, "histfcntllock" => 0, "histfindnodups" => 0, "histignorealldups" => 0, "histignoredups" => 0, "histignorespace" => 0, "histlexwords" => 0, "histnofunctions" => 0, "histnostore" => 0, "histreduceblanks" => 0, "histsavebycopy" => OPT_ALL as u16, "histsavenodups" => 0, "histsubstpattern" => OPT_EMULATE, "histverify" => 0, "hup" => OPT_EMULATE | (OPT_ZSH as u16), "ignorebraces" => OPT_EMULATE | (OPT_SH as u16), "ignoreclosebraces" => 0, "ignoreeof" => 0, "incappendhistory" => 0, "incappendhistorytime" => 0, "interactive" => OPT_SPECIAL as u16, "interactivecomments" => OPT_EMULATE | (OPT_BOURNE as u16), "ksharrays" => OPT_EMULATE | (OPT_BOURNE as u16), "kshautoload" => OPT_EMULATE | (OPT_BOURNE as u16), "kshglob" => OPT_EMULATE | (OPT_KSH as u16), "kshoptionprint" => OPT_EMULATE | (OPT_KSH as u16), "kshtypeset" => OPT_EMULATE | (OPT_BOURNE as u16), "kshzerosubscript" => OPT_EMULATE | (OPT_BOURNE as u16), "listambiguous" => OPT_ALL as u16, "listbeep" => OPT_ALL as u16, "listpacked" => 0, "listrowsfirst" => 0, "listtypes" => OPT_ALL as u16, "localoptions" => OPT_EMULATE | (OPT_KSH as u16), "localloops" => 0, "localpatterns" => 0, "localtraps" => OPT_EMULATE | (OPT_KSH as u16), "loginshell" => OPT_SPECIAL as u16, "longlistjobs" => 0, "magicequalsubst" => OPT_EMULATE, "mailwarning" => 0, "markdirs" => 0, "menucomplete" => 0, "monitor" => OPT_SPECIAL as u16, "multibyte" => OPT_ALL as u16, "multifuncdef" => OPT_EMULATE | (OPT_ZSH as u16), "multios" => OPT_EMULATE | (OPT_ZSH as u16), "nomatch" => OPT_EMULATE | (OPT_NONBOURNE as u16), "notify" => OPT_EMULATE | (OPT_ZSH as u16), "nullglob" => OPT_EMULATE, "numericglobsort" => 0, "octalzeroes" => OPT_EMULATE | (OPT_SH as u16), "overstrike" => 0, "pathdirs" => 0, "pathscript" => OPT_EMULATE | (OPT_BOURNE as u16), "pipefail" => OPT_EMULATE, "posixaliases" => OPT_EMULATE | (OPT_BOURNE as u16), "posixargzero" => OPT_EMULATE | (OPT_BOURNE as u16), "posixbuiltins" => OPT_EMULATE | (OPT_BOURNE as u16), "posixcd" => OPT_EMULATE | (OPT_BOURNE as u16), "posixidentifiers" => OPT_EMULATE | (OPT_BOURNE as u16), "posixjobs" => OPT_EMULATE | (OPT_BOURNE as u16), "posixstrings" => OPT_EMULATE | (OPT_BOURNE as u16), "posixtraps" => OPT_EMULATE | (OPT_BOURNE as u16), "printeightbit" => 0, "printexitvalue" => 0, "privileged" => OPT_SPECIAL as u16, "promptbang" => OPT_EMULATE | (OPT_KSH as u16), "promptcr" => OPT_ALL as u16, "promptpercent" => OPT_EMULATE | (OPT_NONBOURNE as u16), "promptsp" => OPT_ALL as u16, "promptsubst" => OPT_EMULATE | (OPT_BOURNE as u16), "pushdignoredups" => 0, "pushdminus" => 0, "pushdsilent" => 0, "pushdtohome" => 0, "rcexpandparam" => OPT_EMULATE, "rcquotes" => 0, "rcs" => OPT_ALL as u16, "recexact" => 0, "rematchpcre" => 0, "restricted" => OPT_SPECIAL as u16, "rmstarsilent" => OPT_EMULATE | (OPT_BOURNE as u16), "rmstarwait" => 0, "sharehistory" => 0, "shfileexpansion" => OPT_EMULATE | (OPT_BOURNE as u16), "shglob" => OPT_EMULATE | (OPT_BOURNE as u16), "shinstdin" => OPT_SPECIAL as u16, "shnullcmd" => OPT_EMULATE | (OPT_BOURNE as u16), "shoptionletters" => OPT_EMULATE | (OPT_BOURNE as u16), "shortloops" => OPT_EMULATE | (OPT_NONBOURNE as u16), "shortrepeat" => OPT_EMULATE, "shwordsplit" => OPT_EMULATE | (OPT_BOURNE as u16), "singlecommand" => OPT_SPECIAL as u16, "singlelinezle" => 0, "sourcetrace" => 0, "sunkeyboardhack" => 0, "transientrprompt" => 0, "trapsasync" => 0, "typesetsilent" => OPT_EMULATE | (OPT_BOURNE as u16), "unset" => OPT_EMULATE | (OPT_BSHELL as u16), "verbose" => OPT_EMULATE, "vi" => 0, "warncreateglobal" => 0, "warnnestedvar" => 0, "xtrace" => OPT_EMULATE, "zle" => OPT_SPECIAL as u16, "dvorak" => 0, _ => 0,
}
}
pub(crate) fn default_on_options() -> HashSet<&'static str> {
let zsh_emu = EMULATE_ZSH as u16;
let mut set = HashSet::new();
for name in ZSH_OPTIONS_SET.iter() {
let flags = optns_flags(name);
if (flags & zsh_emu) != 0 && (flags & OPT_SPECIAL) == 0 {
set.insert(*name);
}
}
set
}
fn setemulate_opts_lock() -> &'static std::sync::Mutex<std::collections::HashMap<String, bool>> {
SETEMULATE_OPTS.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
fn optno_by_name(name: &str) -> Option<i32> {
for idx in 1..OPT_SIZE {
let n = opt_name(idx);
if !n.is_empty() && n == name {
return Some(idx);
}
}
None
}
pub fn opt_state_get(name: &str) -> Option<bool> {
let m = OPTS_LIVE.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new()));
let optno = optlookup(name);
if optno != OPT_INVALID {
let (target_optno, negate) = if optno < 0 { (-optno, true) } else { (optno, false) };
let target_name = opt_name(target_optno);
if !target_name.is_empty() && target_name != name {
if let Ok(g) = m.read() {
if let Some(&v) = g.get(target_name) {
return Some(if negate { !v } else { v });
}
}
}
}
m.read().ok().and_then(|g| g.get(name).copied())
}
pub fn opt_state_set(name: &str, value: bool) {
let m = OPTS_LIVE.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new()));
if let Ok(mut g) = m.write() {
g.insert(name.to_string(), value);
}
}
pub fn opt_state_unset(name: &str) {
let m = OPTS_LIVE.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new()));
if let Ok(mut g) = m.write() {
g.remove(name);
}
}
pub fn opt_state_snapshot() -> std::collections::HashMap<String, bool> {
let m = OPTS_LIVE.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new()));
m.read().map(|g| g.clone()).unwrap_or_default()
}
pub fn opt_state_restore(snap: std::collections::HashMap<String, bool>) {
let m = OPTS_LIVE.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new()));
if let Ok(mut g) = m.write() {
*g = snap;
}
}
pub fn opt_state_set_via_alias(name: &str, on: bool) -> bool {
let optno = optlookup(name);
if optno == OPT_INVALID {
if on {
opt_state_set(name, true);
} else {
opt_state_unset(name);
}
return false;
}
let (target_optno, negate) = if optno < 0 { (-optno, true) } else { (optno, false) };
let target_name = opt_name(target_optno);
if target_name.is_empty() {
if on {
opt_state_set(name, true);
} else {
opt_state_unset(name);
}
return false;
}
let effective = if negate { !on } else { on };
opt_state_set(target_name, effective);
true
}
pub fn opt_state_len() -> usize {
let m = OPTS_LIVE.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new()));
m.read().map(|g| g.len()).unwrap_or(0)
}
pub fn list_emulate_options(cmdopts: &std::collections::HashMap<String, bool>, fully: bool) {
let mut names: Vec<&'static str> = ZSH_OPTIONS_SET.iter().copied().collect();
names.sort();
for n in names {
let value = cmdopts.get(n).copied().unwrap_or(false);
print_emulate_option(n, value, fully); }
}
#[cfg(test)]
mod tests {
use crate::ported::zsh_h::ALIASESOPT;
use super::*;
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn test_default_options() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert!(isset(optlookup("glob")));
assert!(!isset(optlookup("xtrace")));
assert!(!isset(optlookup("zle")));
}
#[test]
fn test_set_option() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
dosetopt(optlookup("xtrace"), if true { 1 } else { 0 }, 0);
assert!(isset(optlookup("xtrace")));
dosetopt(optlookup("xtrace"), if false { 1 } else { 0 }, 0);
assert!(!isset(optlookup("xtrace")));
}
#[test]
fn dosetopt_emacs_vi_mutual_exclusion() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
opt_state_set("emacs", true);
opt_state_set("vi", true);
dosetopt(EMACSMODE, 1, 1);
assert!(isset(EMACSMODE), "EMACSMODE must be set");
assert!(!isset(VIMODE), "c:870 — setopt emacs must clear VIMODE");
dosetopt(VIMODE, 1, 1);
assert!(isset(VIMODE), "VIMODE must be set");
assert!(!isset(EMACSMODE), "c:870 — setopt vi must clear EMACSMODE");
opt_state_set("emacs", false);
opt_state_set("vi", false);
}
#[test]
fn test_no_prefix() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
dosetopt(optlookup("noglob"), if true { 1 } else { 0 }, 0);
assert!(!isset(optlookup("glob")));
let n = optlookup("noglob");
assert!(n < 0, "noglob should resolve to negative optno");
assert!(!isset(-n), "after `setopt noglob`, glob must be unset");
}
#[test]
fn test_case_insensitive() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert_eq!(optlookup("GLOB"), optlookup("glob"));
assert_eq!(optlookup("GlOb"), optlookup("glob"));
}
#[test]
fn test_underscore_ignored() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert_eq!(optlookup("auto_list"), optlookup("autolist"));
assert_eq!(optlookup("AUTO_LIST"), optlookup("autolist"));
}
#[test]
fn test_option_alias() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
dosetopt(optlookup("braceexpand"), if true { 1 } else { 0 }, 0);
assert!(!isset(optlookup("ignorebraces")));
}
#[test]
fn test_single_letter() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
dosetopt(optlookupc('x'), if true { 1 } else { 0 }, 0);
assert!(isset(optlookup("xtrace")));
dosetopt(optlookupc('n'), if true { 1 } else { 0 }, 0);
assert!(!isset(optlookup("exec")));
}
#[test]
fn test_emulation() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
emulate("sh", true);
assert_eq!(
EMULATION.load(std::sync::atomic::Ordering::Relaxed),
EMULATE_SH
);
assert!(isset(optlookup("shwordsplit")));
emulate("zsh", true);
assert_eq!(
EMULATION.load(std::sync::atomic::Ordering::Relaxed),
EMULATE_ZSH
);
}
#[test]
fn test_dash_string() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
opt_state_set("interactive", true);
opt_state_set("monitor", true);
let dash = dashgetfn();
assert!(dash.contains('i'));
assert!(dash.contains('m'));
}
#[test]
fn test_lookup_canonicalises_underscores_and_case() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert_eq!(optlookup("AUTO_LIST"), optlookup("autolist"));
assert_eq!(optlookup("AutoList"), optlookup("autolist"));
assert_eq!(optlookup("auto__list"), optlookup("autolist"));
}
#[test]
fn optlookup_lowercase_folding_is_ascii_only() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let glob = optlookup("glob");
assert!(glob > 0, "GLOB must be a valid option");
assert_eq!(
optlookup("GLOB"),
glob,
"c:702 — ASCII 'G' must fold to 'g'"
);
assert_eq!(
optlookup("Glob"),
glob,
"c:702 — ASCII 'G' must fold to 'g' (mixed case)"
);
let glob_with_high_byte = std::str::from_utf8(b"gl\xc3\xb6b").unwrap();
assert_eq!(
optlookup(glob_with_high_byte),
OPT_INVALID,
"c:702 — non-ASCII chars NOT folded; lookup fails"
);
assert_eq!(
optlookup("G_L_O_B"),
glob,
"c:693 — underscores stripped regardless of case"
);
}
#[test]
fn optlookup_unknown_names_return_opt_invalid() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert_eq!(optlookup(""), OPT_INVALID, "c:714 — empty name");
assert_eq!(optlookup("definitely_not_an_option"), OPT_INVALID);
assert_eq!(optlookup("no_such_option_either"), OPT_INVALID);
assert_eq!(optlookup("nodefinitelynot"), OPT_INVALID);
}
#[test]
fn optlookup_no_prefix_only_fires_when_suffix_resolves() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let n = optlookup("noglob");
assert!(n < 0, "c:710 — `noglob` returns negative optno");
assert_eq!(-n, optlookup("glob"));
let n2 = optlookup("notify");
assert!(
n2 > 0,
"c:711 — `notify` is a real option, must be positive"
);
}
#[test]
fn opt_invalid_matches_c_enum_value_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
OPT_INVALID, 0,
"Src/zsh.h:2363 — OPT_INVALID is enum slot 0"
);
assert_eq!(
ALIASESOPT,
1,
"Src/zsh.h:2364 — ALIASESOPT immediately follows OPT_INVALID"
);
}
#[test]
fn optlookupc_rejects_letters_outside_range() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert_eq!(optlookupc(' '), 0, "space below FIRST_OPT");
assert_eq!(optlookupc('\0'), 0, "NUL is invalid");
assert_eq!(optlookupc('~'), 0, "tilde above LAST_OPT");
assert_eq!(optlookupc('字'), 0);
}
#[test]
fn dosetopt_rejects_locked_options_without_force() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let saved_interactive = opt_state_get("interactive").unwrap_or(false);
let saved_shinstdin = opt_state_get("shinstdin").unwrap_or(false);
let saved_single = opt_state_get("singlecommand").unwrap_or(false);
opt_state_set("interactive", false);
opt_state_set("shinstdin", false);
opt_state_set("singlecommand", false);
assert_eq!(
dosetopt(INTERACTIVE, 1, 0),
-1,
"c:746 — dosetopt INTERACTIVE on without force must reject"
);
assert_eq!(
dosetopt(SHINSTDIN, 1, 0),
-1,
"c:746 — dosetopt SHINSTDIN on without force must reject"
);
assert_eq!(
dosetopt(SINGLECOMMAND, 1, 0),
-1,
"c:746 — dosetopt SINGLECOMMAND on without force must reject"
);
assert_eq!(
dosetopt(INTERACTIVE, 0, 0),
0,
"c:749 — same value is a no-op success"
);
assert_eq!(
dosetopt(INTERACTIVE, 1, 1),
0,
"c:743 — force=1 bypasses the lock"
);
assert!(
opt_state_get("interactive").unwrap_or(false),
"force=1 must actually flip the option"
);
opt_state_set("interactive", saved_interactive);
opt_state_set("shinstdin", saved_shinstdin);
opt_state_set("singlecommand", saved_single);
}
#[test]
fn dashgetfn_iterates_c_canonical_range_first_opt_to_last_opt() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let saved_i = opt_state_get("interactive").unwrap_or(false);
let saved_m = opt_state_get("monitor").unwrap_or(false);
opt_state_set("interactive", true);
opt_state_set("monitor", true);
let dash = dashgetfn();
assert!(
dash.contains('i'),
"c:891 — interactive set → 'i' appears in $-"
);
assert!(
dash.contains('m'),
"c:891 — monitor set → 'm' appears in $-"
);
for b in dash.bytes() {
assert!((b'0'..=b'y').contains(&b),
"c:289-290 — every emitted char must be in [FIRST_OPT..=LAST_OPT] = '0'..='y', got {}", b as char);
}
opt_state_set("interactive", saved_i);
opt_state_set("monitor", saved_m);
}
#[test]
fn dosetopt_negative_optno_flips_value() {
let _g = crate::test_util::global_state_lock();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let auto_menu = optlookup("automenu");
assert!(auto_menu > 0, "automenu must look up to a valid optno");
let saved = opt_state_get("automenu").unwrap_or(false);
let _ = dosetopt(auto_menu, 0, 0);
assert!(
!opt_state_get("automenu").unwrap_or(true),
"automenu = 0 → unset"
);
let _ = dosetopt(-auto_menu, 0, 0);
assert!(
opt_state_get("automenu").unwrap_or(false),
"c:741 — negative optno flips value (0 → 1)"
);
opt_state_set("automenu", saved);
}
#[test]
fn opt_state_roundtrip_set_then_get_true() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let saved = opt_state_get("automenu").unwrap_or(false);
opt_state_set("automenu", true);
assert_eq!(opt_state_get("automenu"), Some(true));
opt_state_set("automenu", saved);
}
#[test]
fn opt_state_roundtrip_set_then_get_false() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let saved = opt_state_get("automenu").unwrap_or(false);
opt_state_set("automenu", false);
assert_eq!(opt_state_get("automenu"), Some(false));
opt_state_set("automenu", saved);
}
#[test]
fn optlookup_kshglob_underscore_equiv_to_no_underscore() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let a = optlookup("KSH_GLOB");
let b = optlookup("kshglob");
assert_eq!(a, b, "KSH_GLOB and kshglob must resolve to same optno");
assert!(a > 0, "must be a valid option");
}
#[test]
fn optlookup_mixed_case_variants_resolve_to_same_optno() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let canon = optlookup("kshglob");
for variant in &["KSHGLOB", "KshGlob", "kSHglob", "kShGlOb"] {
assert_eq!(
optlookup(variant),
canon,
"{variant} should resolve to same optno as kshglob"
);
}
}
#[test]
fn optlookup_multiple_underscores_collapse() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let canon = optlookup("kshglob");
assert_eq!(optlookup("ksh__glob"), canon);
assert_eq!(optlookup("ksh___glob"), canon);
}
#[test]
fn optlookup_unknown_name_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert_eq!(optlookup("totally_not_an_option_xyz"), 0);
}
#[test]
fn optlookup_empty_string_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
assert_eq!(optlookup(""), 0);
}
#[test]
fn optlookup_resolves_common_options() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
for name in &[
"interactive",
"monitor",
"shwordsplit",
"kshglob",
"extendedglob",
"globdots",
"clobber",
"automenu",
"histignoredups",
"verbose",
] {
assert!(
optlookup(name) > 0,
"common option {name} must be in the table"
);
}
}
#[test]
fn optlookup_no_prefix_resolves_to_canonical_option() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let canon = optlookup("clobber");
let negated = optlookup("noclobber");
assert!(canon > 0, "clobber must resolve");
assert_ne!(negated, 0, "noclobber must resolve (either as alias or distinct optno)");
}
#[test]
fn isset_returns_false_for_unset_option() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let saved = opt_state_get("automenu").unwrap_or(false);
opt_state_set("automenu", false);
assert!(!isset(optlookup("automenu")));
opt_state_set("automenu", saved);
}
#[test]
fn isset_returns_true_for_set_option() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createoptiontable();
let saved = opt_state_get("automenu").unwrap_or(false);
opt_state_set("automenu", true);
assert!(isset(optlookup("automenu")));
opt_state_set("automenu", saved);
}
#[test]
fn opt_state_unset_clears_value() {
let _g = crate::test_util::global_state_lock();
let _g2 = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let saved = opt_state_get("automenu").unwrap_or(false);
opt_state_set("automenu", true);
opt_state_unset("automenu");
let v = opt_state_get("automenu");
assert!(v.is_none() || v == Some(false),
"after unset, got {v:?} — should be None or Some(false)");
opt_state_set("automenu", saved);
}
#[test]
fn options_corpus_extendedglob_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("extendedglob");
opt_state_set("extendedglob", true);
assert_eq!(opt_state_get("extendedglob"), Some(true));
opt_state_set("extendedglob", false);
assert_eq!(opt_state_get("extendedglob"), Some(false));
if let Some(s) = saved { opt_state_set("extendedglob", s); }
}
#[test]
fn options_corpus_nounset_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("nounset");
opt_state_set("nounset", true);
assert_eq!(opt_state_get("nounset"), Some(true));
opt_state_set("nounset", false);
assert_eq!(opt_state_get("nounset"), Some(false));
if let Some(s) = saved { opt_state_set("nounset", s); }
}
#[test]
fn options_corpus_errexit_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("errexit");
opt_state_set("errexit", true);
assert_eq!(opt_state_get("errexit"), Some(true));
opt_state_set("errexit", false);
assert_eq!(opt_state_get("errexit"), Some(false));
if let Some(s) = saved { opt_state_set("errexit", s); }
}
#[test]
fn options_corpus_xtrace_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("xtrace");
opt_state_set("xtrace", true);
assert_eq!(opt_state_get("xtrace"), Some(true));
opt_state_set("xtrace", false);
assert_eq!(opt_state_get("xtrace"), Some(false));
if let Some(s) = saved { opt_state_set("xtrace", s); }
}
#[test]
fn options_corpus_kshglob_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("kshglob");
opt_state_set("kshglob", true);
assert_eq!(opt_state_get("kshglob"), Some(true));
opt_state_set("kshglob", false);
assert_eq!(opt_state_get("kshglob"), Some(false));
if let Some(s) = saved { opt_state_set("kshglob", s); }
}
#[test]
fn options_corpus_nullglob_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("nullglob");
opt_state_set("nullglob", true);
assert_eq!(opt_state_get("nullglob"), Some(true));
opt_state_set("nullglob", false);
assert_eq!(opt_state_get("nullglob"), Some(false));
if let Some(s) = saved { opt_state_set("nullglob", s); }
}
#[test]
fn options_corpus_kshzerosubscript_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("kshzerosubscript");
opt_state_set("kshzerosubscript", true);
assert_eq!(opt_state_get("kshzerosubscript"), Some(true));
opt_state_set("kshzerosubscript", false);
assert_eq!(opt_state_get("kshzerosubscript"), Some(false));
if let Some(s) = saved { opt_state_set("kshzerosubscript", s); }
}
#[test]
fn options_corpus_kshtypeset_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("kshtypeset");
opt_state_set("kshtypeset", true);
assert_eq!(opt_state_get("kshtypeset"), Some(true));
opt_state_set("kshtypeset", false);
assert_eq!(opt_state_get("kshtypeset"), Some(false));
if let Some(s) = saved { opt_state_set("kshtypeset", s); }
}
}
pub static sticky: std::sync::Mutex<Option<crate::ported::zsh_h::Emulation_options>> = std::sync::Mutex::new(None);