#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use crate::ported::zle::compctl_h::{
Compctl, Compcond, CompcondData, Patcomp, Compctlp,
CC_FILES, CC_COMMPATH, CC_REMOVE, CC_OPTIONS, CC_VARS, CC_BINDINGS,
CC_ARRAYS, CC_INTVARS, CC_SHFUNCS, CC_PARAMS, CC_ENVVARS, CC_JOBS,
CC_RUNNING, CC_STOPPED, CC_BUILTINS, CC_ALREG, CC_ALGLOB, CC_USERS,
CC_DISCMDS, CC_EXCMDS, CC_SCALARS, CC_READONLYS, CC_SPECIALS,
CC_DELETE, CC_NAMED, CC_QUOTEFLAG, CC_EXTCMDS, CC_RESWDS, CC_DIRS,
CC_EXPANDEXPL, CC_RESERVED,
CC_NOSORT, CC_XORCONT, CC_CCCONT, CC_PATCONT, CC_DEFCONT, CC_UNIQCON, CC_UNIQALL,
CCT_UNUSED, CCT_POS, CCT_CURSTR, CCT_CURPAT, CCT_WORDSTR, CCT_WORDPAT,
CCT_CURSUF, CCT_CURPRE, CCT_CURSUB, CCT_CURSUBC, CCT_NUMWORDS,
CCT_RANGESTR, CCT_RANGEPAT, CCT_QUOTE,
};
use crate::ported::zle::comp_h::Cmlist;
use std::os::unix::fs::PermissionsExt;
#[allow(unused_imports)]
#[allow(unused_imports)]
use crate::ported::zle::zle_main::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_misc::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_hist::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_move::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_word::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_params::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_vi::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_utils::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_refresh::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_tricky::*;
#[allow(unused_imports)]
use crate::ported::zle::textobjects::*;
#[allow(unused_imports)]
use crate::ported::zle::deltochar::*;
pub const COMP_LIST: i32 = 1 << 0; pub const COMP_COMMAND: i32 = 1 << 1; pub const COMP_DEFAULT: i32 = 1 << 2; pub const COMP_FIRST: i32 = 1 << 3; pub const COMP_REMOVE: i32 = 1 << 4; pub const COMP_LISTMATCH: i32 = 1 << 5;
pub const COMP_SPECIAL: i32 = COMP_COMMAND | COMP_DEFAULT | COMP_FIRST;
pub const CFN_FIRST: i32 = 1; pub const CFN_DEFAULT: i32 = 2;
pub(crate) static CMATCHER:
std::sync::RwLock<Option<Box<crate::ported::zle::comp_h::Cmlist>>> =
std::sync::RwLock::new(None);
static COMPCTL_TAB: std::sync::RwLock<Option<HashMap<String, Arc<Compctl>>>>
= std::sync::RwLock::new(None);
static PATCOMPS: std::sync::RwLock<Vec<(String, Arc<Compctl>)>>
= std::sync::RwLock::new(Vec::new());
thread_local! {
static CCLIST: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
thread_local! {
static SHOWMASK: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
pub(crate) fn createcompctltable() {
let mut g = COMPCTL_TAB.write().unwrap();
*g = Some(HashMap::new());
let mut p = PATCOMPS.write().unwrap();
p.clear();
}
pub(crate) fn freecompctlp(name: &str) {
let mut g = COMPCTL_TAB.write().unwrap();
if let Some(map) = g.as_mut() {
map.remove(name);
}
}
pub(crate) fn freecompctl(_cc: Arc<Compctl>) {
}
pub(crate) fn freecompcond(_cc: Compcond) {
}
pub(crate) fn cpcmlist( mut l: Option<&crate::ported::zle::comp_h::Cmlist>,
) -> Option<Box<crate::ported::zle::comp_h::Cmlist>> {
let mut head: Option<Box<Cmlist>> = None; let mut tail_ref: *mut Option<Box<Cmlist>> = &mut head;
while let Some(src) = l { let matcher_chain = crate::ported::zle::complete::cpcmatcher( Some(&*src.matcher),
).expect("cpcmatcher returned None for non-null source");
let n = Box::new(Cmlist { next: None, matcher: matcher_chain, str: src.str.clone(), });
unsafe {
*tail_ref = Some(n);
if let Some(ref mut newnode) = *tail_ref { tail_ref = &mut newnode.next as *mut _;
}
}
l = src.next.as_deref(); }
head }
pub(crate) fn set_gmatcher(name: &str, argv: &[String]) -> i32 { let mut head: Option<Box<Cmlist>> = None; let mut tail_ref: *mut Option<Box<Cmlist>> = &mut head;
for word in argv { let m = match crate::ported::zle::complete::parse_cmatcher(name, word) {
Some(m) => m, None => return 1, };
let n = Box::new(Cmlist { next: None, matcher: m, str: word.clone(), });
unsafe {
*tail_ref = Some(n);
if let Some(ref mut newnode) = *tail_ref { tail_ref = &mut newnode.next as *mut _;
}
}
}
let new_list = cpcmlist(head.as_deref()); if let Ok(mut guard) = CMATCHER.write() {
*guard = new_list;
}
1 }
pub(crate) fn get_gmatcher(name: &str, argv: &[String]) -> i32 { if argv.first().map(|s| s.as_str()) != Some("-M") { return 0; }
let rest = &argv[1..]; for w in rest { if w.starts_with('-') { return 0; }
}
if set_gmatcher(name, rest) != 0 { return 2; }
1 }
pub(crate) fn print_gmatcher(_ac: i32) {}
pub(crate) fn get_compctl(
name: &str,
av: &mut Vec<String>,
cc: &mut Compctl,
first: bool,
mut isdef: bool,
cl: i32,
) -> i32 {
let mut i: usize = 0;
let hx = false;
let mut cclist_local = CCLIST.with(|c| c.get());
cc.mask2 = CC_CCCONT;
if first
&& i < av.len()
&& av[i] == "+"
&& !(i + 1 < av.len() && av[i + 1].starts_with('-') && av[i + 1].len() > 1)
{
i += 1;
if i < av.len() && av[i].starts_with('-') {
i += 1;
}
av.drain(0..i);
if cl != 0 {
return 1;
} else {
CCLIST.with(|c| c.set(COMP_REMOVE));
return 0;
}
}
let mut ready = false;
while !ready
&& i < av.len()
&& av[i].starts_with('-')
&& (av[i].len() > 1 || !first)
{
if av[i].len() == 1 {
av[i] = "-+".to_string();
}
let arg = av[i].clone();
let chars: Vec<char> = arg.chars().skip(1).collect();
let mut consumed = false;
for c in chars {
if ready { break; }
match c {
'f' => cc.mask |= CC_FILES, 'c' => cc.mask |= CC_COMMPATH, 'm' => cc.mask |= CC_EXTCMDS, 'w' => cc.mask |= CC_RESWDS, 'o' => cc.mask |= CC_OPTIONS, 'v' => cc.mask |= CC_VARS, 'b' => cc.mask |= CC_BINDINGS, 'A' => cc.mask |= CC_ARRAYS, 'I' => cc.mask |= CC_INTVARS, 'F' => cc.mask |= CC_SHFUNCS, 'p' => cc.mask |= CC_PARAMS, 'E' => cc.mask |= CC_ENVVARS, 'j' => cc.mask |= CC_JOBS, 'r' => cc.mask |= CC_RUNNING, 'z' => cc.mask |= CC_STOPPED, 'B' => cc.mask |= CC_BUILTINS, 'a' => cc.mask |= CC_ALREG | CC_ALGLOB, 'R' => cc.mask |= CC_ALREG, 'G' => cc.mask |= CC_ALGLOB, 'u' => cc.mask |= CC_USERS, 'd' => cc.mask |= CC_DISCMDS, 'e' => cc.mask |= CC_EXCMDS, 'N' => cc.mask |= CC_SCALARS, 'O' => cc.mask |= CC_READONLYS, 'Z' => cc.mask |= CC_SPECIALS, 'q' => cc.mask |= CC_REMOVE, 'U' => cc.mask |= CC_DELETE, 'n' => cc.mask |= CC_NAMED, 'Q' => cc.mask |= CC_QUOTEFLAG, '/' => cc.mask |= CC_DIRS, '1' => { cc.mask2 |= CC_UNIQALL;
cc.mask2 &= !CC_UNIQCON;
}
'2' => { cc.mask2 |= CC_UNIQCON;
cc.mask2 &= !CC_UNIQALL;
}
'C' => { if cl != 0 {
eprintln!("{}: illegal option -{}", name, c);
return 1;
}
if first && !hx {
cclist_local |= COMP_COMMAND;
} else {
eprintln!("{}: misplaced command completion (-C) flag", name);
return 1;
}
}
'D' => { if cl != 0 {
eprintln!("{}: illegal option -{}", name, c);
return 1;
}
if first && !hx {
isdef = true;
cclist_local |= COMP_DEFAULT;
} else {
eprintln!("{}: misplaced default completion (-D) flag", name);
return 1;
}
}
'T' => { if cl != 0 {
eprintln!("{}: illegal option -{}", name, c);
return 1;
}
if first && !hx {
cclist_local |= COMP_FIRST;
} else {
eprintln!("{}: misplaced first completion (-T) flag", name);
return 1;
}
}
'L' => { if cl != 0 {
eprintln!("{}: illegal option -{}", name, c);
return 1;
}
if !first || hx {
eprintln!("{}: illegal use of -L flag", name);
return 1;
}
cclist_local |= COMP_LIST;
}
'+' => { ready = true;
consumed = true;
break;
}
_ => {
let (has_inline, inline_val) = (
arg.len() > 2 && arg.chars().nth(1) == Some(c),
if arg.len() > 2 { arg[2..].to_string() } else { String::new() },
);
let mut val: Option<String> = None;
if has_inline {
val = Some(inline_val);
} else if i + 1 < av.len() {
val = Some(av[i + 1].clone());
i += 1;
}
match c {
'k' => cc.keyvar = val, 'K' => cc.func = val, 'Y' => { cc.mask |= CC_EXPANDEXPL;
cc.explain = val;
}
'X' => { cc.mask &= !CC_EXPANDEXPL;
cc.explain = val;
}
'y' => cc.ylist = val, 'P' => cc.prefix = val, 'S' => cc.suffix = val, 'g' => cc.glob = val, 's' => cc.str = val, 'l' => cc.subcmd = val, 'h' => cc.substr = val, 'W' => cc.withd = val, 'J' => cc.gname = val, 'V' => { cc.gname = val;
cc.mask2 |= CC_NOSORT;
}
'M' => { if let Some(s) = val {
cc.mstr = Some(s);
}
}
'H' => { if let Some(s) = val {
cc.hnum = s.parse::<i32>().unwrap_or(0).max(0);
}
if i + 1 < av.len() {
cc.hpat = Some(av[i + 1].clone());
if cc.hpat.as_deref() == Some("*") {
cc.hpat = Some(String::new());
}
i += 1;
}
}
't' => { if let Some(s) = val {
let bit = match s.as_str() {
"+" => CC_XORCONT,
"n" => 0,
"-" => CC_PATCONT,
"x" => CC_DEFCONT,
_ => {
eprintln!("{}: invalid retry specification character `{}`", name, s);
return 1;
}
};
cc.mask2 = bit;
}
}
_ => {
eprintln!("{}: unknown compctl flag `-{}`", name, c);
return 1;
}
}
consumed = true;
break;
}
}
}
i += 1;
if !consumed {
}
}
av.drain(0..i);
let _ = isdef;
CCLIST.with(|c| c.set(cclist_local));
0
}
pub(crate) fn get_xcompctl(
name: &str,
av: &mut Vec<String>,
cc: &mut Compctl,
isdef: bool,
) -> i32 {
let mut ready = false;
let mut next_chain: Vec<Arc<Compctl>> = Vec::new();
while !ready {
let mut head: Compcond = Compcond::default();
let mut current_or = &mut head as *mut Compcond;
if av.is_empty() {
eprintln!("{}: missing command names", name);
return 1;
}
let arg = av[0].clone();
let bytes: Vec<char> = arg.chars().collect();
let mut t = 0_usize;
let mut current_and: Option<*mut Compcond> = None;
while t < bytes.len() {
while t < bytes.len() && bytes[t] == ' ' {
t += 1;
}
if t >= bytes.len() { break; }
let typ = match bytes[t] {
'q' => CCT_QUOTE, 's' => CCT_CURSUF, 'S' => CCT_CURPRE, 'p' => CCT_POS, 'c' => CCT_CURSTR, 'C' => CCT_CURPAT, 'w' => CCT_WORDSTR, 'W' => CCT_WORDPAT, 'n' => CCT_CURSUB, 'N' => CCT_CURSUBC, 'm' => CCT_NUMWORDS, 'r' => CCT_RANGESTR, 'R' => CCT_RANGEPAT, _ => {
eprintln!("{}: unknown condition code: {}", name, bytes[t]);
return 1;
}
};
if t + 1 >= bytes.len() || bytes[t + 1] != '[' {
eprintln!("{}: expected condition after condition code: {}", name, bytes[t]);
return 1;
}
t += 1;
let mut bodies: Vec<String> = Vec::new();
while t < bytes.len() && bytes[t] == '[' {
t += 1; while t < bytes.len() && bytes[t] == ' ' { t += 1; }
let body_start = t;
let mut depth = 1_i32;
while t < bytes.len() && depth > 0 {
if bytes[t] == '\\' && t + 1 < bytes.len() {
t += 2;
continue;
}
if bytes[t] == '[' { depth += 1; }
else if bytes[t] == ']' { depth -= 1; if depth == 0 { break; } }
t += 1;
}
if t >= bytes.len() {
eprintln!("{}: error after condition code", name);
return 1;
}
let body: String = bytes[body_start..t].iter().collect();
bodies.push(body);
t += 1; }
let n = bodies.len() as i32;
let data = match typ {
t if t == CCT_POS || t == CCT_NUMWORDS => {
let mut a: Vec<i32> = Vec::with_capacity(n as usize);
let mut b: Vec<i32> = Vec::with_capacity(n as usize);
for body in &bodies {
let parts: Vec<&str> = body.splitn(2, ',').collect();
let av_n: i32 = parts[0].trim().parse().unwrap_or(0);
let bv_n: i32 = if parts.len() == 2 {
parts[1].trim().parse().unwrap_or(0)
} else {
av_n };
a.push(av_n);
b.push(bv_n);
}
CompcondData::R { a, b }
}
t if t == CCT_CURSUF || t == CCT_CURPRE || t == CCT_QUOTE => {
let s: Vec<String> = bodies.iter().cloned().collect();
let p: Vec<i32> = vec![0; s.len()];
CompcondData::S { p, s }
}
t if t == CCT_RANGESTR || t == CCT_RANGEPAT => {
let mut a: Vec<String> = Vec::with_capacity(n as usize);
let mut b: Vec<String> = Vec::with_capacity(n as usize);
for body in &bodies {
let parts: Vec<&str> = body.splitn(2, ',').collect();
a.push(parts[0].to_string());
b.push(parts.get(1).map(|s| s.to_string()).unwrap_or_default());
}
CompcondData::L { a, b }
}
_ => {
let mut p: Vec<i32> = Vec::with_capacity(n as usize);
let mut s: Vec<String> = Vec::with_capacity(n as usize);
for body in &bodies {
let parts: Vec<&str> = body.splitn(2, ',').collect();
if parts.len() != 2 {
eprintln!("{}: error in condition", name);
return 1;
}
p.push(parts[0].trim().parse().unwrap_or(0));
s.push(parts[1].to_string());
}
CompcondData::S { p, s }
}
};
unsafe {
let cur = match current_and {
Some(p) => p,
None => current_or,
};
(*cur).typ = typ;
(*cur).n = n;
(*cur).u = data;
}
while t < bytes.len() && bytes[t] == ' ' { t += 1; }
if t < bytes.len() && bytes[t] == ',' {
let new_node = Box::new(Compcond::default());
let new_ptr = Box::into_raw(new_node);
unsafe {
let cur = current_and.unwrap_or(current_or);
(*cur).or = Some(Box::from_raw(new_ptr));
current_or = (*cur).or.as_mut().unwrap().as_mut() as *mut Compcond;
}
current_and = None;
t += 1;
} else if t < bytes.len() {
let new_node = Box::new(Compcond::default());
let new_ptr = Box::into_raw(new_node);
unsafe {
let cur = current_and.unwrap_or(current_or);
(*cur).and = Some(Box::from_raw(new_ptr));
current_and = Some((*cur).and.as_mut().unwrap().as_mut() as *mut Compcond);
}
}
}
let mut next_cc = Compctl::default();
next_cc.cond = Some(Box::new(head));
av.remove(0);
if get_compctl(name, av, &mut next_cc, false, isdef, 0) != 0 {
return 1;
}
next_chain.push(Arc::new(next_cc));
let cclist = CCLIST.with(|c| c.get());
if (av.is_empty()) && (cclist & COMP_SPECIAL) != 0 {
ready = true;
continue;
}
if av.is_empty()
|| !av[0].starts_with('-')
|| (av[0].len() == 1 && av.len() < 2)
{
eprintln!("{}: missing command names", name);
return 1;
}
if av[0] == "--" {
ready = true;
} else if av[0] == "-+" && av.len() >= 2 && av[1] == "--" {
ready = true;
av.remove(0);
}
av.remove(0);
}
if let Some(first) = next_chain.into_iter().next() {
cc.ext = Some(first);
}
0
}
pub(crate) fn cc_assign(name: &str, cct: Arc<Compctl>, reass: bool) {
let cclist = CCLIST.with(|c| c.get());
if reass && (cclist & COMP_LIST) == 0 {
let conflicts = cclist == (COMP_COMMAND | COMP_DEFAULT)
|| cclist == (COMP_COMMAND | COMP_FIRST)
|| cclist == (COMP_DEFAULT | COMP_FIRST)
|| cclist == COMP_SPECIAL;
if conflicts {
eprintln!("{}: can't set -D, -T, and -C simultaneously", name);
return;
}
if (cclist & COMP_COMMAND) != 0 {
let _ = cc_reassign(cct.clone());
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() { *g = Some(HashMap::new()); }
if let Some(map) = g.as_mut() {
map.insert("__cc_compos".to_string(), cct);
}
return;
}
if (cclist & COMP_DEFAULT) != 0 {
let _ = cc_reassign(cct.clone());
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() { *g = Some(HashMap::new()); }
if let Some(map) = g.as_mut() {
map.insert("__cc_default".to_string(), cct);
}
return;
}
if (cclist & COMP_FIRST) != 0 {
let _ = cc_reassign(cct.clone());
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() { *g = Some(HashMap::new()); }
if let Some(map) = g.as_mut() {
map.insert("__cc_first".to_string(), cct);
}
return;
}
}
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() { *g = Some(HashMap::new()); }
if let Some(map) = g.as_mut() {
map.insert(name.to_string(), cct);
}
}
pub(crate) fn cc_reassign(_cc: Arc<Compctl>) -> Arc<Compctl> {
Arc::new(Compctl::default())
}
pub(crate) fn compctl_name_pat(p: &str) -> (bool, String) {
let has_glob = p.chars().any(|c| matches!(c, '*' | '?' | '['));
if has_glob {
(true, p.to_string())
} else {
let mut out = String::with_capacity(p.len());
let mut chars = p.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&nx) = chars.peek() {
out.push(nx);
chars.next();
continue;
}
}
out.push(c);
}
(false, out)
}
}
pub(crate) fn delpatcomp(n: &str) {
let mut p = PATCOMPS.write().unwrap();
p.retain(|(pat, _)| pat != n);
}
pub(crate) fn compctl_process_cc(s: &[String], cc: Arc<Compctl>) -> i32 {
let cclist = CCLIST.with(|c| c.get());
if (cclist & COMP_REMOVE) != 0 {
for n in s {
let mut p = PATCOMPS.write().unwrap();
let len_before = p.len();
p.retain(|(pat, _)| pat != n);
let pat_removed = p.len() != len_before;
drop(p);
if !pat_removed {
if let Some(map) = COMPCTL_TAB.write().unwrap().as_mut() {
map.remove(n);
}
}
}
} else {
for n in s {
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() {
*g = Some(HashMap::new());
}
if let Some(map) = g.as_mut() {
map.insert(n.clone(), cc.clone());
}
}
}
0
}
pub(crate) fn printcompctl(
s: &str,
cc: &Compctl,
printflags: i32,
ispat: bool,
) {
const CSS: &str = "fcqovbAIFpEjrzBRGudeNOZUnQmw/";
const MSS: &str = " pcCwWsSnNmrRq";
let mut flags = cc.mask;
let flags2 = cc.mask2;
const PRINT_LIST: i32 = 1 << 0;
const PRINT_TYPE: i32 = 1 << 1;
let mut cclist = CCLIST.with(|c| c.get());
if (printflags & PRINT_LIST) != 0 {
cclist |= COMP_LIST;
} else if (printflags & PRINT_TYPE) != 0 {
cclist &= !COMP_LIST;
}
if (flags & CC_EXCMDS) != 0 && (flags & CC_DISCMDS) == 0 {
flags &= !CC_EXCMDS;
}
let showmask = SHOWMASK.with(|c| c.get());
if showmask != 0 && (flags & showmask) == 0 {
return;
}
let oldshowmask = showmask;
SHOWMASK.with(|c| c.set(0));
if (cclist & COMP_LIST) != 0 {
print!("compctl");
} else if !s.is_empty() {
print!("compctl");
}
for (i, ch) in CSS.chars().enumerate() {
if ch == ' ' { continue; }
if (flags & (1u64 << i)) != 0 {
print!(" -{}", ch);
}
}
let _ = MSS;
if let Some(s) = &cc.keyvar { print!(" -k '{}'", s); }
if let Some(s) = &cc.glob { print!(" -g '{}'", s); }
if let Some(s) = &cc.str { print!(" -s '{}'", s); }
if let Some(s) = &cc.func { print!(" -K '{}'", s); }
if let Some(s) = &cc.explain {
if (cc.mask & CC_EXPANDEXPL) != 0 { print!(" -Y '{}'", s); }
else { print!(" -X '{}'", s); }
}
if let Some(s) = &cc.ylist { print!(" -y '{}'", s); }
if let Some(s) = &cc.prefix { print!(" -P '{}'", s); }
if let Some(s) = &cc.suffix { print!(" -S '{}'", s); }
if let Some(s) = &cc.subcmd { print!(" -l '{}'", s); }
if let Some(s) = &cc.substr { print!(" -h '{}'", s); }
if let Some(s) = &cc.withd { print!(" -W '{}'", s); }
if let Some(s) = &cc.gname {
if (flags2 & CC_NOSORT) != 0 { print!(" -V '{}'", s); }
else { print!(" -J '{}'", s); }
}
if let Some(s) = &cc.mstr { print!(" -M '{}'", s); }
if cc.hnum > 0 {
if let Some(p) = &cc.hpat {
print!(" -H {} '{}'", cc.hnum, if p.is_empty() { "*" } else { p });
}
}
if cc.xor.is_some() {
print!(" +");
}
if !s.is_empty() && (cclist & COMP_LIST) != 0 {
if ispat {
print!(" -p '{}'", s);
} else {
print!(" '{}'", s);
}
} else if !s.is_empty() {
print!(" '{}'", s);
}
println!();
SHOWMASK.with(|c| c.set(oldshowmask));
}
pub(crate) fn printcompctlp(name: &str, hn: &Compctl, printflags: i32) {
printcompctl(name, hn, printflags, false);
}
pub(crate) fn bin_compctl(name: &str, argv: &[String]) -> i32 {
let mut argv: Vec<String> = argv.to_vec();
let mut ret: i32 = 0;
CCLIST.with(|c| c.set(0));
SHOWMASK.with(|c| c.set(0));
if !argv.is_empty() {
let gret = get_gmatcher(name, &argv);
if gret != 0 {
return gret - 1;
}
let mut cc = Compctl::default();
if get_compctl(name, &mut argv, &mut cc, true, false, 0) != 0 {
return 1;
}
let mut showmask = cc.mask;
if (showmask & CC_EXCMDS) != 0 && (showmask & CC_DISCMDS) == 0 {
showmask &= !CC_EXCMDS;
}
SHOWMASK.with(|c| c.set(showmask));
let cclist = CCLIST.with(|c| c.get());
if argv.is_empty() || (cclist & COMP_LIST) != 0 {
} else {
if (cclist & COMP_SPECIAL) != 0 {
eprintln!("{}: extraneous commands ignored", name);
} else {
let cc_arc = Arc::new(cc);
ret = compctl_process_cc(&argv, cc_arc);
}
return ret;
}
}
let cclist = CCLIST.with(|c| c.get());
if argv.is_empty() && (cclist & (COMP_SPECIAL | COMP_LISTMATCH)) == 0 {
let pats = PATCOMPS.read().unwrap().clone();
for (pat, cc) in &pats {
printcompctl(pat, cc, 0, true);
}
if let Some(map) = COMPCTL_TAB.read().unwrap().as_ref() {
let mut names: Vec<&String> = map.keys().collect();
names.sort();
for n in names {
if let Some(cc) = map.get(n) {
printcompctlp(n, cc, 0);
}
}
}
print_gmatcher((cclist & COMP_LIST) as i32);
return ret;
}
if (cclist & COMP_LIST) != 0 {
SHOWMASK.with(|c| c.set(0));
for n in &argv {
let mut found = false;
let pats = PATCOMPS.read().unwrap().clone();
for (pat, cc) in &pats {
if pat == n {
printcompctl(pat, cc, 0, true);
found = true;
break;
}
}
if !found {
if let Some(map) = COMPCTL_TAB.read().unwrap().as_ref() {
if let Some(cc) = map.get(n) {
printcompctlp(n, cc, 0);
found = true;
}
}
}
if !found {
eprintln!("{}: no compctl defined for {}", name, n);
ret = 1;
}
}
if (cclist & COMP_LISTMATCH) != 0 {
print_gmatcher(COMP_LIST as i32);
}
}
ret
}
pub(crate) fn bin_compcall(name: &str, argv: &[String]) -> i32 {
let incompfunc = INCOMPFUNC.with(|c| c.get());
if incompfunc != 1 {
eprintln!("{}: can only be called from completion function", name);
return 1;
}
let mut flags = 0_i32;
let mut t_set = false;
let mut d_set = false;
for a in argv {
if a == "-T" { t_set = true; }
else if a == "-D" { d_set = true; }
}
const CFN_FIRST: i32 = 1;
const CFN_DEFAULT: i32 = 2;
if !t_set { flags |= CFN_FIRST; }
if !d_set { flags |= CFN_DEFAULT; }
makecomplistctl(flags);
0
}
thread_local! { static INCOMPFUNC: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
pub(crate) fn compctlread(name: &str, args: &[String]) -> i32 {
let incompctlfunc = INCOMPCTLFUNC.with(|c| c.get());
if !incompctlfunc {
eprintln!("{}: option valid only in functions called via compctl", name);
return 1;
}
let mut opt_l = false;
let mut opt_n = false;
let mut opt_c = false;
let mut opt_e = false;
let mut opt_e_upper = false;
let mut reply: Option<&String> = None;
for a in args {
if let Some(rest) = a.strip_prefix('-') {
for ch in rest.chars() {
match ch {
'l' => opt_l = true,
'n' => opt_n = true,
'c' => opt_c = true,
'e' => opt_e = true,
'E' => opt_e_upper = true,
_ => {}
}
}
} else {
reply = Some(a);
}
}
if opt_l && opt_n {
let idx = 1 + crate::ported::zle::compcore::ZLEMETACS .load(std::sync::atomic::Ordering::Relaxed);
if opt_e || opt_e_upper {
println!("{}", idx);
}
if !opt_e {
if let Some(r) = reply { let idx_str = idx.to_string();
let _ = crate::ported::params::assignsparam(
&r, &idx_str, 0,
);
}
}
return 0;
}
if opt_l && opt_c {
let cnt = 0;
if opt_e || opt_e_upper { println!("{}", cnt); }
return 0;
}
let _ = reply;
0
}
thread_local! {
pub(crate) static INCOMPCTLFUNC: std::cell::Cell<bool> =
const { std::cell::Cell::new(false) };
}
pub(crate) fn ccmakehookfn(_dat: ()) -> i32 {
let _guard = CMATCHER.read();
drop(_guard);
0
}
pub(crate) fn cccleanuphookfn(_dat: ()) -> i32 {
0
}
thread_local! { static ADDWHAT: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
thread_local! { static MATCH_LIST: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) }; }
pub(crate) fn addmatch(s: &str, _t: Option<&str>) {
let aw = ADDWHAT.with(|c| c.get());
let file_thread = matches!(aw, -1 | -5 | -6 | -7 | -8)
|| (aw > 0 && (aw as u64 & CC_FILES) != 0);
if file_thread {
MATCH_LIST.with(|r| r.borrow_mut().push(s.to_string()));
return;
}
if matches!(aw, -2 | -3 | -4 | -9) {
MATCH_LIST.with(|r| r.borrow_mut().push(s.to_string()));
return;
}
if aw > 0 {
MATCH_LIST.with(|r| r.borrow_mut().push(s.to_string()));
}
}
pub(crate) fn maketildelist() {
}
pub(crate) fn getcpat(str: &str, cpatindex: i32, cpat: &str, class: i32) -> i32 {
if str.is_empty() {
return -1;
}
let cpat_clean: String = {
let mut out = String::with_capacity(cpat.len());
let mut chars = cpat.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&nx) = chars.peek() {
out.push(nx);
chars.next();
continue;
}
}
out.push(c);
}
out
};
let (mut idx, backward) = if cpatindex == 0 {
(1_i32, false)
} else if cpatindex < 0 {
(-cpatindex, true)
} else {
(cpatindex, false)
};
let str_chars: Vec<char> = str.chars().collect();
let cpat_chars: Vec<char> = cpat_clean.chars().collect();
let n = str_chars.len();
let positions: Vec<usize> = if backward {
(0..n).rev().collect()
} else {
(0..n).collect()
};
for s_start in positions {
if class != 0 {
let sc = str_chars[s_start];
if cpat_chars.iter().any(|&p| p == sc) {
idx -= 1;
if idx == 0 {
return (s_start + 1) as i32;
}
}
} else {
let mut t = s_start;
let mut p = 0;
while t < n && p < cpat_chars.len() && str_chars[t] == cpat_chars[p] {
t += 1;
p += 1;
}
if p == cpat_chars.len() {
idx -= 1;
if idx == 0 {
return t as i32;
}
}
}
}
-1
}
pub(crate) fn dumphashtable<I: IntoIterator<Item = String>>(names: I, what: i32) {
ADDWHAT.with(|c| c.set(what));
for nam in names {
addmatch(&nam, None);
}
}
pub(crate) fn addhnmatch(name: &str, _flags: i32) {
addmatch(name, None);
}
pub(crate) fn getreal(str_in: &str) -> String {
let s = crate::ported::subst::singsub(str_in);
if !s.is_empty() { s } else { str_in.to_string() }
}
pub(crate) fn gen_matches_files(dirs: bool, execs: bool, all: bool) {
let prpre = PRPRE.with(|r| r.borrow().clone()).unwrap_or_else(|| ".".to_string());
let entries = match std::fs::read_dir(&prpre) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.flatten() {
let name = match entry.file_name().into_string() {
Ok(n) => n,
Err(_) => continue,
};
if !all && (name == "." || name == "..") {
continue;
}
if !all && name.starts_with('.') {
continue;
}
let meta = match entry.metadata() {
Ok(m) => m,
Err(_) => continue,
};
if dirs && !meta.is_dir() {
continue;
}
if execs {
#[cfg(unix)]
{
let mode = meta.permissions().mode();
if mode & 0o111 == 0 || meta.is_dir() {
continue;
}
}
#[cfg(not(unix))]
{ continue; }
}
addmatch(&name, None);
}
}
thread_local! { static PRPRE: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) }; }
pub(crate) fn findnode<T: PartialEq>(list: &[T], dat: &T) -> Option<usize> {
list.iter().position(|x| x == dat)
}
thread_local! { static CDEPTH: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
pub const MAX_CDEPTH: i32 = 16;
thread_local! { static CCONT: std::cell::Cell<u64> = const { std::cell::Cell::new(0) }; }
pub(crate) fn makecomplistctl(flags: i32) -> i32 {
let cdepth = CDEPTH.with(|c| c.get());
if cdepth == MAX_CDEPTH { return 0;
}
CDEPTH.with(|c| c.set(cdepth + 1));
let saved_incomp = INCOMPFUNC.with(|c| c.get());
INCOMPFUNC.with(|c| c.set(2));
let str_in = ""; let ret = makecomplistglobal(str_in, false, COMP_LIST as i32, flags);
INCOMPFUNC.with(|c| c.set(saved_incomp));
CDEPTH.with(|c| c.set(c.get() - 1));
ret
}
pub(crate) fn makecomplistglobal(os: &str, incmd: bool, _lst: i32, flags: i32) -> i32 {
CCONT.with(|c| c.set(CC_CCCONT));
if let Some(d) = CC_DUMMY.lock().unwrap().as_mut() {
let _ = d;
}
let _ = flags;
makecomplistcmd(os, incmd, flags)
}
pub(crate) fn makecomplistcmd(os: &str, incmd: bool, flags: i32) -> i32 {
const CFN_FIRST: i32 = 1;
const CFN_DEFAULT: i32 = 2;
let mut ret: i32 = 0;
if (flags & CFN_FIRST) == 0 {
if let Some(cc_first) = CC_FIRST.lock().unwrap().clone() {
makecomplistcc(&cc_first, os, incmd);
if (CCONT.with(|c| c.get()) & CC_CCCONT) == 0 {
return 0;
}
}
}
let cmdstr = CMDSTR.with(|r| r.borrow().clone());
if cmdstr.is_some() {
ret |= makecomplistpc(os, incmd);
if (CCONT.with(|c| c.get()) & CC_CCCONT) == 0 {
return ret;
}
}
let cc = if incmd {
CC_COMPOS.lock().unwrap().clone()
} else {
let name = match &cmdstr {
Some(s) => s.clone(),
None => return ret,
};
let table = COMPCTL_TAB.read().unwrap();
let from_table = table.as_ref().and_then(|m| m.get(&name).cloned());
drop(table);
match from_table {
Some(c) => Some(c),
None => {
if (flags & CFN_DEFAULT) != 0 {
return ret;
}
ret |= 1;
CC_DEFAULT.lock().unwrap().clone()
}
}
};
if let Some(c) = cc {
makecomplistcc(&c, os, incmd);
}
ret
}
thread_local! { static CMDSTR: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) }; }
pub(crate) fn makecomplistpc(os: &str, incmd: bool) -> i32 { let mut ret: i32 = 0; let cmdstr = match CMDSTR.with(|r| r.borrow().clone()) { Some(s) => s,
None => return 0,
};
let is_function = crate::ported::builtin::shfunctab_table().lock()
.map(|t| t.contains_key(&cmdstr)).unwrap_or(false);
let is_builtin = crate::ported::builtin::BUILTINS.iter()
.any(|b| b.node.nam == cmdstr);
let s_resolved: Option<String> = if is_function || is_builtin { None } else {
crate::ported::builtin::findcmd(&cmdstr, 1, 0) };
let pats = PATCOMPS.read().unwrap().clone();
for (pat, cc) in &pats { let matches = crate::ported::pattern::patmatch(pat, &cmdstr) || s_resolved.as_deref()
.map(|sr| crate::ported::pattern::patmatch(pat, sr)) .unwrap_or(false);
if matches {
makecomplistcc(cc, os, incmd); ret |= 2; if (CCONT.with(|c| c.get()) & CC_CCCONT) == 0 { return ret; }
}
}
ret }
pub(crate) fn makecomplistcc(cc: &Arc<Compctl>, s: &str, incmd: bool) {
let _ = cc.clone();
CCUSED.with(|r| r.borrow_mut().push(cc.clone()));
CCONT.with(|c| c.set(0));
makecomplistor(cc, s, incmd, 0, 0);
}
thread_local! { static CCUSED: std::cell::RefCell<Vec<Arc<Compctl>>> = const { std::cell::RefCell::new(Vec::new()) }; }
pub(crate) fn makecomplistor(cc: &Arc<Compctl>, s: &str, incmd: bool, compadd: i32, sub: i32) {
let mut current = cc.clone();
loop {
makecomplistlist(¤t, s, incmd, compadd);
match ¤t.xor {
Some(next) => current = next.clone(),
None => break,
}
let _ = sub;
}
}
pub(crate) fn makecomplistlist(cc: &Arc<Compctl>, s: &str, incmd: bool, compadd: i32) {
if cc.ext.is_some() {
makecomplistext(cc, s, incmd);
} else {
makecomplistflags(cc, s, incmd, compadd);
}
}
pub(crate) fn makecomplistext(occ: &Arc<Compctl>, os: &str, incmd: bool) {
let mut current = occ.ext.clone();
while let Some(cc) = current {
let accept = if let Some(ref cond) = cc.cond {
let cs = crate::ported::zle::compcore::ZLECS
.load(std::sync::atomic::Ordering::Relaxed);
let total = crate::ported::params::getiparam("CURRENT") as i32;
let mut accepted = false;
let mut or_cur: Option<&Compcond> = Some(cond);
while let Some(o) = or_cur {
let mut and_cur = Some(o);
let mut all_match = true;
while let Some(c) = and_cur {
let one = match (c.typ, &c.u) {
(x, CompcondData::R { a, b }) if x == CCT_POS =>
a.iter().zip(b.iter())
.any(|(lo, hi)| *lo <= cs && cs <= *hi),
(x, CompcondData::R { a, b }) if x == CCT_NUMWORDS =>
a.iter().zip(b.iter())
.any(|(lo, hi)| *lo <= total && total <= *hi),
_ => true,
};
if !one { all_match = false; break; }
and_cur = c.and.as_deref();
}
if all_match { accepted = true; break; }
or_cur = o.or.as_deref();
}
accepted
} else {
true
};
if accept {
makecomplistflags(&cc, os, incmd, 0);
}
current = cc.next.clone();
}
}
thread_local! { static WE: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
thread_local! { static WB: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
thread_local! { static ZLEMETACS: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
static ZLEMETALL: Mutex<i32> = Mutex::new(0);
static ZLEMETALINE: Mutex<String> = Mutex::new(String::new());
static NOERRS: Mutex<i32> = Mutex::new(0);
static NOALIASES: Mutex<i32> = Mutex::new(0);
use crate::ported::zsh_h::{QT_NONE, QT_BACKSLASH, QT_SINGLE, QT_DOUBLE, QT_DOLLARS, QT_BACKTICK};
static INSTRING: Mutex<i32> = Mutex::new(QT_NONE);
static INBACKT: Mutex<i32> = Mutex::new(0);
static AUTOQ: Mutex<String> = Mutex::new(String::new());
static COMPQSTACK: Mutex<String> = Mutex::new(String::new());
static QIPRE: Mutex<String> = Mutex::new(String::new());
static QISUF: Mutex<String> = Mutex::new(String::new());
static COMPQIPREFIX: Mutex<String> = Mutex::new(String::new());
static COMPQISUFFIX: Mutex<String> = Mutex::new(String::new());
static COMPISUFFIX: Mutex<String> = Mutex::new(String::new());
static COMPWORDS: Mutex<Vec<String>> = Mutex::new(Vec::new());
static COMPCURRENT: Mutex<i32> = Mutex::new(0);
static CLWORDS: Mutex<Vec<String>> = Mutex::new(Vec::new());
static CLWSIZE: Mutex<i32> = Mutex::new(0);
static CLWNUM: Mutex<i32> = Mutex::new(0);
static CLWPOS: Mutex<i32> = Mutex::new(0);
static OFFS: Mutex<i32> = Mutex::new(0);
static ADDEDX: Mutex<i32> = Mutex::new(0);
static LEXFLAGS: Mutex<i32> = Mutex::new(0);
const LEXFLAGS_ZLE: i32 = 1 << 0;
static BRANGE: Mutex<i32> = Mutex::new(0);
static ERANGE: Mutex<i32> = Mutex::new(0);
static LINWHAT: Mutex<i32> = Mutex::new(crate::ported::zsh_h::IN_NOTHING);
static LINREDIR: Mutex<i32> = Mutex::new(0);
static INSUBSCR: Mutex<i32> = Mutex::new(0);
pub const Snull: char = '\u{9d}'; pub const Dnull: char = '\u{9e}'; pub const Bnull: char = '\u{9f}'; pub const Stringg: char = '\u{85}'; pub const QSTRING_TOK: char = '\u{84}';
fn inull(c: char) -> bool { matches!(c, Snull | Dnull | Bnull | Stringg | QSTRING_TOK)
}
pub(crate) fn sep_comp_string(ss: &str, s: &str, noffs: i32) -> i32 {
let owe = WE.with(|c| c.get());
let owb = WB.with(|c| c.get());
let ocs = ZLEMETACS.with(|c| c.get());
let oll = *ZLEMETALL.lock().unwrap();
let ois = *INSTRING.lock().unwrap();
let oib = *INBACKT.lock().unwrap();
let ona = *NOALIASES.lock().unwrap();
let ne = *NOERRS.lock().unwrap();
let ol = ZLEMETALINE.lock().unwrap().clone();
let oaq = AUTOQ.lock().unwrap().clone();
let sl = ss.len() as i32;
let mut got = false;
let mut i = 0_i32;
let mut cur: i32 = -1;
let mut swb = 0_i32;
let mut swe = 0_i32;
let mut soffs = 0_i32;
let mut ns: String = String::new();
let mut foo: Vec<String> = Vec::new();
*ADDEDX.lock().unwrap() = 1;
*NOERRS.lock().unwrap() = 1;
*LEXFLAGS.lock().unwrap() = LEXFLAGS_ZLE;
let mut tmp = String::with_capacity(ss.len() + 3 + s.len());
tmp.push_str(ss);
tmp.push(' ');
let s_chars: Vec<char> = s.chars().collect();
let noffs_u = (noffs as usize).min(s_chars.len());
let s_pre: String = s_chars[..noffs_u].iter().collect();
let s_post: String = s_chars[noffs_u..].iter().collect();
tmp.push_str(&s_pre);
let scs_initial = sl + 1 + noffs;
ZLEMETACS.with(|c| c.set(scs_initial));
let mut scs = scs_initial;
tmp.push('x');
tmp.push_str(&s_post);
let tl = tmp.len() as i32;
let qstack_head = COMPQSTACK.lock().unwrap().chars().next().unwrap_or(QT_NONE as u8 as char);
let remq = qstack_head as i32 == QT_BACKSLASH;
if remq {
let mut stripped = String::with_capacity(tmp.len());
let mut chars = tmp.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&_nx) = chars.peek() {
continue;
}
}
stripped.push(c);
}
tmp = stripped;
}
*ZLEMETALINE.lock().unwrap() = tmp.clone();
*ZLEMETALL.lock().unwrap() = tl - 1;
*NOALIASES.lock().unwrap() = 1;
{
let chars: Vec<char> = tmp.chars().collect();
let mut t_start = 0_usize;
let mut idx = 0_usize;
let mut word_idx = 0_i32;
while idx <= chars.len() {
let at_end = idx == chars.len();
let is_sep = !at_end && chars[idx] == ' ';
if at_end || is_sep {
if idx > t_start {
let token: String = chars[t_start..idx].iter().collect();
let abs_start = t_start as i32;
let abs_end = idx as i32;
foo.push(token.clone());
if !got && scs >= abs_start && scs <= abs_end {
got = true;
cur = word_idx;
swb = abs_start;
swe = abs_end;
soffs = scs - swb;
let mut t = token.clone();
if (soffs as usize) < t.len() {
t.remove(soffs as usize);
}
ns = t;
}
word_idx += 1;
}
t_start = idx + 1;
}
if at_end { break; }
idx += 1;
}
i = word_idx;
}
*NOALIASES.lock().unwrap() = ona;
*NOERRS.lock().unwrap() = ne;
WB.with(|c| c.set(owb));
WE.with(|c| c.set(owe));
ZLEMETACS.with(|c| c.set(ocs));
*ZLEMETALINE.lock().unwrap() = ol;
*ZLEMETALL.lock().unwrap() = oll;
if cur < 0 || i < 1 {
return 1;
}
let ts = ns.clone();
let _ = ts.clone();
let first_char = ns.chars().next();
let is_quoted_open = matches!(
first_char,
Some(Snull) | Some(Dnull)
) || (matches!(first_char, Some(Stringg) | Some(QSTRING_TOK))
&& ns.chars().nth(1) == Some(Snull));
if is_quoted_open {
let new_instring = match first_char {
Some(Snull) => QT_SINGLE,
Some(Dnull) => QT_DOUBLE,
_ => QT_DOLLARS,
};
*INSTRING.lock().unwrap() = new_instring;
*INBACKT.lock().unwrap() = 0;
swb += 1;
if let (Some(first), Some(last)) = (ns.chars().next(), ns.chars().last()) {
if first == last && ns.len() >= 2 {
swe -= 1;
}
}
let qstack = COMPQSTACK.lock().unwrap().clone();
if qstack.len() >= 2 {
*AUTOQ.lock().unwrap() = String::new();
} else {
*AUTOQ.lock().unwrap() = ts.clone();
}
} else {
*INSTRING.lock().unwrap() = QT_NONE;
*AUTOQ.lock().unwrap() = String::new();
}
let mut ns_chars: Vec<char> = ns.chars().collect();
let mut p_idx = 0_usize;
let mut walk_i = swb;
while p_idx < ns_chars.len() {
let c = ns_chars[p_idx];
if inull(c) {
if walk_i < scs {
soffs -= 1;
if remq && c == Bnull && p_idx + 1 < ns_chars.len() {
swb -= 2;
}
}
let next = ns_chars.get(p_idx + 1).copied();
if next.is_some() || c != Bnull {
if c == Bnull {
if scs == walk_i + 1 {
scs += 1;
soffs += 1;
}
} else if scs > walk_i {
scs -= 1;
walk_i -= 1; }
} else if scs == swe {
scs -= 1;
}
ns_chars.remove(p_idx);
walk_i -= 1;
} else {
p_idx += 1;
walk_i += 1;
}
}
ns = ns_chars.iter().collect();
let qipre_val = QIPRE.lock().unwrap().clone();
let qisuf_val = QISUF.lock().unwrap().clone();
let qp = format!("{}{}", qipre_val, &s[..((swb - sl - 1).max(0) as usize).min(s.len())]);
if swe < swb {
swe = swb;
}
swe -= sl + 1;
let s_len = s.len() as i32;
if swe > s_len {
swe = s_len;
if (ns.len() as i32) > swe - swb + 1 {
ns.truncate((swe - swb + 1) as usize);
}
}
let qs_start = (swe.max(0) as usize).min(s.len());
let qs = format!("{}{}", &s[qs_start..], qisuf_val);
let s_chars_len = ns.len() as i32;
if soffs > s_chars_len {
soffs = s_chars_len;
}
let ow = CLWORDS.lock().unwrap().clone();
let os = CMDSTR.with(|r| r.borrow().clone());
let oqp = QIPRE.lock().unwrap().clone();
let oqs = QISUF.lock().unwrap().clone();
let oqst = COMPQSTACK.lock().unwrap().clone();
let olws = *CLWSIZE.lock().unwrap();
let olwn = *CLWNUM.lock().unwrap();
let olwp = *CLWPOS.lock().unwrap();
let obr = *BRANGE.lock().unwrap();
let oer = *ERANGE.lock().unwrap();
let oof = *OFFS.lock().unwrap();
let occ = CCONT.with(|c| c.get());
let new_quote_char = if *INSTRING.lock().unwrap() != QT_NONE {
char::from_u32(*INSTRING.lock().unwrap() as u32).unwrap_or('\\')
} else {
char::from_u32(QT_BACKSLASH as u32).unwrap_or('\\')
};
let mut new_compqstack = String::new();
new_compqstack.push(new_quote_char);
new_compqstack.push_str(&oqst);
*COMPQSTACK.lock().unwrap() = new_compqstack;
*CLWSIZE.lock().unwrap() = foo.len() as i32;
*CLWNUM.lock().unwrap() = foo.len() as i32;
*CLWORDS.lock().unwrap() = foo.clone();
*CLWPOS.lock().unwrap() = cur;
CMDSTR.with(|r| *r.borrow_mut() = foo.first().cloned());
*BRANGE.lock().unwrap() = 0;
*ERANGE.lock().unwrap() = (foo.len() as i32) - 1;
*QIPRE.lock().unwrap() = qp;
*QISUF.lock().unwrap() = qs;
*OFFS.lock().unwrap() = soffs;
CCONT.with(|c| c.set(CC_CCCONT));
const CFN_FIRST: i32 = 1;
let _ = makecomplistcmd(&ns, cur == 0, CFN_FIRST);
CCONT.with(|c| c.set(occ));
*OFFS.lock().unwrap() = oof;
CMDSTR.with(|r| *r.borrow_mut() = os);
*CLWORDS.lock().unwrap() = ow;
*CLWSIZE.lock().unwrap() = olws;
*CLWNUM.lock().unwrap() = olwn;
*CLWPOS.lock().unwrap() = olwp;
*BRANGE.lock().unwrap() = obr;
*ERANGE.lock().unwrap() = oer;
*QIPRE.lock().unwrap() = oqp;
*QISUF.lock().unwrap() = oqs;
*COMPQSTACK.lock().unwrap() = oqst;
*AUTOQ.lock().unwrap() = oaq;
*INSTRING.lock().unwrap() = ois;
*INBACKT.lock().unwrap() = oib;
0
}
pub(crate) fn makecomplistflags(cc: &Arc<Compctl>, s: &str, _incmd: bool, _compadd: i32) {
let _ = (cc, s);
CCONT.with(|c| c.set(cc.mask2));
if (cc.mask & CC_FILES) != 0 {
ADDWHAT.with(|c| c.set(-5));
gen_matches_files(false, false, false);
}
if (cc.mask & CC_DIRS) != 0 {
ADDWHAT.with(|c| c.set(-5));
gen_matches_files(true, false, false);
}
if (cc.mask & CC_NAMED) != 0 {
ADDWHAT.with(|c| c.set(-1));
maketildelist();
}
if let Some(s) = &cc.str {
let expanded = getreal(s);
ADDWHAT.with(|c| c.set(-6));
addmatch(&expanded, None);
}
}
pub(crate) static CC_COMPOS: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
pub(crate) static CC_DEFAULT: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
pub(crate) static CC_FIRST: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
pub(crate) static CC_DUMMY: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
static LASTCCUSED: Mutex<Vec<Arc<Compctl>>> = Mutex::new(Vec::new());
static COMPCTLREAD_INSTALLED: Mutex<bool> = Mutex::new(false);
pub(crate) fn setup_() -> i32 {
*COMPCTLREAD_INSTALLED.lock().unwrap() = true;
createcompctltable();
*CC_COMPOS.lock().unwrap() = Some(Arc::new(Compctl {
mask: CC_COMMPATH, ..Default::default()
}));
*CC_DEFAULT.lock().unwrap() = Some(Arc::new(Compctl {
refc: 10000, mask: CC_FILES, ..Default::default()
}));
*CC_FIRST.lock().unwrap() = Some(Arc::new(Compctl {
refc: 10000, mask2: CC_CCCONT, ..Default::default()
}));
*LASTCCUSED.lock().unwrap() = Vec::new(); 0
}
pub(crate) fn features_() -> Vec<String> {
vec!["b:compctl".to_string(), "b:compcall".to_string()]
}
pub(crate) fn enables_() -> Vec<i32> {
vec![1, 1]
}
pub(crate) fn boot_() -> i32 {
0
}
pub(crate) fn cleanup_() -> i32 {
0
}
pub(crate) fn finish_() -> i32 {
*COMPCTL_TAB.write().unwrap() = None; LASTCCUSED.lock().unwrap().clear(); *COMPCTLREAD_INSTALLED.lock().unwrap() = false; 0
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn createcompctltable_initializes_table() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
let g = COMPCTL_TAB.read().unwrap();
assert!(g.is_some());
assert_eq!(g.as_ref().unwrap().len(), 0);
}
#[test]
fn cc_assign_inserts_into_table() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
let cc = Arc::new(Compctl {
mask: CC_FILES,
..Default::default()
});
cc_assign("ls", cc, false);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("ls"));
}
#[test]
fn freecompctlp_removes_entry() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
cc_assign("rm", Arc::new(Compctl::default()), false);
freecompctlp("rm");
let g = COMPCTL_TAB.read().unwrap();
assert!(!g.as_ref().unwrap().contains_key("rm"));
}
#[test]
fn cc_flags_bit_layout_matches_c_compctlh() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CC_FILES, 1);
assert_eq!(CC_COMMPATH, 2);
assert_eq!(CC_OPTIONS, 8);
assert_eq!(CC_JOBS, 1 << 11);
}
#[test]
fn cct_constants_match_c_compctlh() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CCT_POS, 1);
assert_eq!(CCT_CURPAT, 3);
assert_eq!(CCT_QUOTE, 13);
}
#[test]
fn comp_op_special_combines_command_default_first() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(
COMP_SPECIAL,
COMP_COMMAND | COMP_DEFAULT | COMP_FIRST
);
}
#[test]
fn cc_flags2_constants_match_c_compctlh() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CC_NOSORT, 1);
assert_eq!(CC_CCCONT, 4);
assert_eq!(CC_UNIQALL, 1 << 6);
}
#[test]
fn get_compctl_simple_flag_chars_set_mask() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut argv = vec!["-fcv".to_string(), "ls".to_string()];
let mut cc = Compctl::default();
let r = get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(r, 0);
assert_ne!(cc.mask & CC_FILES, 0);
assert_ne!(cc.mask & CC_COMMPATH, 0);
assert_ne!(cc.mask & CC_VARS, 0);
assert_eq!(argv, vec!["ls".to_string()]);
}
#[test]
fn get_compctl_combined_a_sets_alreg_and_alglob() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut argv = vec!["-a".to_string(), "ls".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_ne!(cc.mask & CC_ALREG, 0);
assert_ne!(cc.mask & CC_ALGLOB, 0);
}
#[test]
fn get_compctl_arg_taking_K_captures_function_name() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut argv = vec!["-K".to_string(), "_my_completer".to_string(), "myfunc".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.func.as_deref(), Some("_my_completer"));
assert_eq!(argv, vec!["myfunc".to_string()]);
}
#[test]
fn get_compctl_inline_arg_K_captures_function_name() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut argv = vec!["-K_my_func".to_string(), "myfunc".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.func.as_deref(), Some("_my_func"));
}
#[test]
fn get_compctl_P_S_capture_prefix_suffix() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut argv = vec![
"-P".to_string(), "before-".to_string(),
"-S".to_string(), "-after".to_string(),
"cmd".to_string()
];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.prefix.as_deref(), Some("before-"));
assert_eq!(cc.suffix.as_deref(), Some("-after"));
}
#[test]
fn get_compctl_1_2_set_uniq_flags() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut argv = vec!["-1".to_string(), "ls".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_ne!(cc.mask2 & CC_UNIQALL, 0);
assert_eq!(cc.mask2 & CC_UNIQCON, 0);
}
#[test]
fn get_compctl_V_implies_NOSORT() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut argv = vec!["-V".to_string(), "mygroup".to_string(), "cmd".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.gname.as_deref(), Some("mygroup"));
assert_ne!(cc.mask2 & CC_NOSORT, 0);
}
#[test]
fn bin_compctl_install_then_lookup_via_table() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
let r = bin_compctl("compctl", &["-f".to_string(), "mycmd".to_string()]);
assert_eq!(r, 0);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("mycmd"));
let cc = g.as_ref().unwrap().get("mycmd").unwrap();
assert_ne!(cc.mask & CC_FILES, 0);
}
#[test]
fn compctl_name_pat_detects_glob_wildcards() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (is_pat, _) = compctl_name_pat("ls*");
assert!(is_pat);
let (is_pat, _) = compctl_name_pat("foo?bar");
assert!(is_pat);
let (is_pat, _) = compctl_name_pat("[abc]");
assert!(is_pat);
}
#[test]
fn compctl_name_pat_strips_backslashes_from_literal() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (is_pat, out) = compctl_name_pat("\\$home");
assert!(!is_pat);
assert_eq!(out, "$home");
}
#[test]
fn delpatcomp_removes_matching_pattern() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut p = PATCOMPS.write().unwrap();
p.push(("foo*".to_string(), Arc::new(Compctl::default())));
p.push(("bar*".to_string(), Arc::new(Compctl::default())));
drop(p);
delpatcomp("foo*");
let p = PATCOMPS.read().unwrap();
assert_eq!(p.len(), 1);
assert_eq!(p[0].0, "bar*");
}
#[test]
fn cc_assign_with_reass_command_target_uses_special_key() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
CCLIST.with(|c| c.set(COMP_COMMAND));
cc_assign("compctl", Arc::new(Compctl {
mask: CC_FILES,
..Default::default()
}), true);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("__cc_compos"));
drop(g);
CCLIST.with(|c| c.set(0));
}
#[test]
fn cc_assign_with_reass_default_target_uses_special_key() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
CCLIST.with(|c| c.set(COMP_DEFAULT));
cc_assign("compctl", Arc::new(Compctl::default()), true);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("__cc_default"));
drop(g);
CCLIST.with(|c| c.set(0));
}
#[test]
fn setup_initializes_special_targets_and_table() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
setup_();
let cc_compos = CC_COMPOS.lock().unwrap().clone();
assert!(cc_compos.is_some());
assert_eq!(cc_compos.unwrap().mask, CC_COMMPATH);
let cc_default = CC_DEFAULT.lock().unwrap().clone();
assert!(cc_default.is_some());
let cc_default = cc_default.unwrap();
assert_eq!(cc_default.mask, CC_FILES);
assert_eq!(cc_default.refc, 10000);
let cc_first = CC_FIRST.lock().unwrap().clone();
assert!(cc_first.is_some());
assert_eq!(cc_first.unwrap().mask2, CC_CCCONT);
assert!(COMPCTL_TAB.read().unwrap().is_some());
assert!(*COMPCTLREAD_INSTALLED.lock().unwrap());
}
#[test]
fn finish_tears_down_state() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
setup_();
finish_();
assert!(COMPCTL_TAB.read().unwrap().is_none());
assert!(!*COMPCTLREAD_INSTALLED.lock().unwrap());
assert_eq!(LASTCCUSED.lock().unwrap().len(), 0);
}
#[test]
fn features_returns_two_builtins() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let f = features_();
assert_eq!(f, vec!["b:compctl".to_string(), "b:compcall".to_string()]);
}
#[test]
fn enables_returns_two_enabled_bits() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let e = enables_();
assert_eq!(e, vec![1, 1]);
}
#[test]
fn bin_compcall_outside_compfunc_errors() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPFUNC.with(|c| c.set(0));
let r = bin_compcall("compcall", &[]);
assert_eq!(r, 1);
}
#[test]
fn bin_compcall_inside_compfunc_succeeds() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPFUNC.with(|c| c.set(1));
let r = bin_compcall("compcall", &["-T".to_string()]);
assert_eq!(r, 0);
INCOMPFUNC.with(|c| c.set(0));
}
#[test]
fn compctlread_outside_compctl_func_errors() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPCTLFUNC.with(|c| c.set(false));
let r = compctlread("compctlread", &[]);
assert_eq!(r, 1);
}
#[test]
fn cccleanuphookfn_returns_zero() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(cccleanuphookfn(()), 0);
}
#[test]
fn addmatch_rejects_unset_addwhat() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
ADDWHAT.with(|c| c.set(0));
addmatch("dropped", None);
let captured = MATCH_LIST.with(|r| r.borrow().clone());
assert!(captured.is_empty(), "addwhat=0 should drop matches");
}
#[test]
fn addmatch_accepts_files_kind() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
ADDWHAT.with(|c| c.set(-5));
addmatch("foo.txt", None);
addmatch("bar.txt", None);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 2);
assert_eq!(m[0], "foo.txt");
}
#[test]
fn addmatch_accepts_param_kind() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
ADDWHAT.with(|c| c.set(-9));
addmatch("HOME", None);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 1);
assert_eq!(m[0], "HOME");
}
#[test]
fn addmatch_accepts_cc_files_positive_mask() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
ADDWHAT.with(|c| c.set(CC_FILES as i32));
addmatch("foo", None);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 1);
}
#[test]
fn getcpat_finds_first_substring() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = getcpat("abcabc", 1, "bc", 0);
assert_eq!(r, 3);
}
#[test]
fn getcpat_finds_second_substring() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = getcpat("abcabc", 2, "bc", 0);
assert_eq!(r, 6);
}
#[test]
fn getcpat_negative_index_searches_backward() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = getcpat("abcabc", -1, "bc", 0);
assert!(r >= 0, "should find match (got {})", r);
}
#[test]
fn getcpat_class_mode_matches_any_char_in_set() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = getcpat("abcdef", 1, "bdf", 1);
assert_eq!(r, 2); }
#[test]
fn getcpat_not_found_returns_negative_one() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = getcpat("hello", 1, "xyz", 0);
assert_eq!(r, -1);
}
#[test]
fn getcpat_strips_backslashes_in_pattern() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = getcpat("foo$bar", 1, "\\$", 0);
assert_eq!(r, 4); }
#[test]
fn dumphashtable_calls_addmatch_per_entry() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
let entries = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
dumphashtable(entries, -5);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 3);
}
#[test]
fn addhnmatch_forwards_to_addmatch() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
ADDWHAT.with(|c| c.set(-5));
addhnmatch("xyz", 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 1);
assert_eq!(m[0], "xyz");
}
#[test]
fn makecomplistctl_recursion_guard() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
CDEPTH.with(|c| c.set(MAX_CDEPTH));
let r = makecomplistctl(0);
assert_eq!(r, 0);
CDEPTH.with(|c| c.set(0));
}
#[test]
fn makecomplistflags_cc_files_invokes_gen_matches() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
PRPRE.with(|r| *r.borrow_mut() = Some(".".to_string()));
let cc = Arc::new(Compctl {
mask: CC_FILES,
..Default::default()
});
makecomplistflags(&cc, "", false, 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert!(!m.is_empty(), "expected file matches in pwd");
}
#[test]
fn makecomplistflags_cc_str_expansion_emits_one_match() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
let cc = Arc::new(Compctl {
str: Some("hardcoded".to_string()),
..Default::default()
});
makecomplistflags(&cc, "", false, 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 1);
assert_eq!(m[0], "hardcoded");
}
#[test]
fn makecomplistor_walks_xor_chain() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
let cc2 = Arc::new(Compctl {
str: Some("second".to_string()),
..Default::default()
});
let cc1 = Arc::new(Compctl {
str: Some("first".to_string()),
xor: Some(cc2),
..Default::default()
});
makecomplistor(&cc1, "", false, 0, 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 2);
assert_eq!(m[0], "first");
assert_eq!(m[1], "second");
}
#[test]
fn makecomplistcc_pushes_to_ccused() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
CCUSED.with(|r| r.borrow_mut().clear());
let cc = Arc::new(Compctl::default());
makecomplistcc(&cc, "", false);
let used = CCUSED.with(|r| r.borrow().clone());
assert_eq!(used.len(), 1);
}
#[test]
fn makecomplistpc_iterates_patcomps() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
CMDSTR.with(|r| *r.borrow_mut() = None);
let r = makecomplistpc("", false);
assert_eq!(r, 0);
}
#[test]
fn findnode_returns_index_of_match() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let list = vec!["a".to_string(), "b".to_string(), "c".to_string()];
assert_eq!(findnode(&list, &"b".to_string()), Some(1));
assert_eq!(findnode(&list, &"z".to_string()), None);
}
#[test]
fn cc_assign_rejects_conflicting_special_targets() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
CCLIST.with(|c| c.set(COMP_COMMAND | COMP_DEFAULT));
cc_assign("compctl", Arc::new(Compctl::default()), true);
let g = COMPCTL_TAB.read().unwrap();
assert!(!g.as_ref().unwrap().contains_key("__cc_compos"));
assert!(!g.as_ref().unwrap().contains_key("__cc_default"));
drop(g);
CCLIST.with(|c| c.set(0));
}
#[test]
fn compctl_process_cc_remove_deletes_named_entries() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
cc_assign("foo", Arc::new(Compctl::default()), false);
cc_assign("bar", Arc::new(Compctl::default()), false);
CCLIST.with(|c| c.set(COMP_REMOVE));
compctl_process_cc(&["foo".to_string()], Arc::new(Compctl::default()));
let g = COMPCTL_TAB.read().unwrap();
let map = g.as_ref().unwrap();
assert!(!map.contains_key("foo"));
assert!(map.contains_key("bar"));
CCLIST.with(|c| c.set(0));
}
#[test]
fn sep_comp_string_returns_zero_or_one() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let r = sep_comp_string("", "", 0);
assert!(r == 0 || r == 1, "expected 0 or 1, got {}", r);
}
#[test]
fn sep_comp_string_round_trips_zle_state() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
WE.with(|c| c.set(42));
WB.with(|c| c.set(7));
ZLEMETACS.with(|c| c.set(11));
*ZLEMETALL.lock().unwrap() = 99;
*INSTRING.lock().unwrap() = QT_DOUBLE;
*INBACKT.lock().unwrap() = 1;
*NOALIASES.lock().unwrap() = 1;
*NOERRS.lock().unwrap() = 0;
*ZLEMETALINE.lock().unwrap() = "hello".to_string();
*AUTOQ.lock().unwrap() = "Q".to_string();
let _ = sep_comp_string("", "x", 0);
assert_eq!(WE.with(|c| c.get()), 42);
assert_eq!(WB.with(|c| c.get()), 7);
assert_eq!(ZLEMETACS.with(|c| c.get()), 11);
assert_eq!(*ZLEMETALL.lock().unwrap(), 99);
assert_eq!(*INSTRING.lock().unwrap(), QT_DOUBLE);
assert_eq!(*INBACKT.lock().unwrap(), 1);
assert_eq!(*NOALIASES.lock().unwrap(), 1);
assert_eq!(*NOERRS.lock().unwrap(), 0);
assert_eq!(*ZLEMETALINE.lock().unwrap(), "hello");
assert_eq!(*AUTOQ.lock().unwrap(), "Q");
}
#[test]
fn inull_recognises_marker_chars() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert!(inull(Snull));
assert!(inull(Dnull));
assert!(inull(Bnull));
assert!(inull(Stringg));
assert!(inull(QSTRING_TOK));
assert!(!inull('a'));
assert!(!inull(' '));
}
#[test]
fn qt_constants_match_c_zsh_h() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(QT_NONE, 0);
assert_eq!(QT_BACKSLASH, 1);
assert_eq!(QT_SINGLE, 2);
assert_eq!(QT_DOUBLE, 3);
assert_eq!(QT_DOLLARS, 4);
assert_eq!(QT_BACKTICK, 5);
}
}