#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
use crate::ported::builtin::findcmd;
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::utils::errflag;
use crate::ported::zle::comp_h::{Aminfo, Cmlist};
use crate::ported::zle::compctl_h::{
Compcond, CompcondData, Compctl, CCT_CURPAT, CCT_CURPRE, CCT_CURSTR, CCT_CURSUB, CCT_CURSUBC,
CCT_CURSUF, CCT_NUMWORDS, CCT_POS, CCT_QUOTE, CCT_RANGEPAT, CCT_RANGESTR, CCT_WORDPAT,
CCT_WORDSTR, CC_ALGLOB, CC_ALREG, CC_ARRAYS, CC_BINDINGS, CC_BUILTINS, CC_CCCONT, CC_COMMPATH,
CC_DEFCONT, CC_DELETE, CC_DIRS, CC_DISCMDS, CC_ENVVARS, CC_EXCMDS, CC_EXPANDEXPL, CC_EXTCMDS,
CC_FILES, CC_INTVARS, CC_JOBS, CC_NAMED, CC_NOSORT, CC_OPTIONS, CC_PARAMS, CC_PATCONT,
CC_QUOTEFLAG, CC_READONLYS, CC_REMOVE, CC_RESWDS, CC_RUNNING, CC_SCALARS, CC_SHFUNCS,
CC_SPECIALS, CC_STOPPED, CC_UNIQALL, CC_UNIQCON, CC_USERS, CC_VARS, CC_XORCONT,
};
use crate::ported::zle::complete::parse_cmatcher;
use crate::ported::zle::complete::{COMPCURRENT, COMPQSTACK, COMPWORDS};
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
zle_params::*, zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
};
use crate::ported::zsh_h::PAT_HEAPDUP;
use crate::ported::zsh_h::{
IN_NOTHING, QT_BACKSLASH, QT_BACKTICK, QT_DOLLARS, QT_DOUBLE, QT_NONE, QT_SINGLE,
};
use std::collections::HashMap;
use std::os::unix::fs::PermissionsExt;
use std::sync::{Arc, Mutex};
#[allow(unused_imports)]
#[allow(unused_imports)]
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(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>) {
let (cc_default_ref, cc_first_ref, cc_compos_ref) = {
match COMPCTL_TAB.read().ok().and_then(|g| g.clone()) {
Some(map) => (
map.get("__cc_default").cloned(),
map.get("__cc_first").cloned(),
map.get("__cc_compos").cloned(),
),
None => (None, None, None),
}
};
let is_sentinel = cc_default_ref.as_ref().is_some_and(|s| Arc::ptr_eq(s, &cc))
|| cc_first_ref.as_ref().is_some_and(|s| Arc::ptr_eq(s, &cc))
|| cc_compos_ref.as_ref().is_some_and(|s| Arc::ptr_eq(s, &cc));
if is_sentinel || Arc::strong_count(&cc) > 1 {
return; }
let _ = &cc.keyvar; let _ = &cc.glob; let _ = &cc.str; let _ = &cc.func; let _ = &cc.explain; let _ = &cc.ylist; let _ = &cc.prefix; let _ = &cc.suffix; let _ = &cc.hpat; let _ = &cc.gname; let _ = &cc.subcmd; let _ = &cc.substr;
if let Some(cond) = cc.cond.as_deref() {
freecompcond((*cond).clone()); }
if cc.ext.is_some() {
let mut n: Option<Arc<Compctl>> = cc.ext.clone(); while let Some(node) = n.take() {
let m = node.next.clone(); freecompctl(node); n = m; }
}
if let Some(xor) = cc.xor.clone() {
let xor_is_default = cc_default_ref
.as_ref()
.is_some_and(|s| Arc::ptr_eq(s, &xor));
if !xor_is_default {
freecompctl(xor); }
}
let _ = &cc.matcher;
let _ = &cc.mstr;
drop(cc); }
pub(crate) fn freecompcond(cc: Compcond) {
let mut or_cur: Option<Box<Compcond>> = Some(Box::new(cc)); while let Some(mut or_node) = or_cur {
let next_or = or_node.or.take(); let mut and_cur: Option<Box<Compcond>> = Some(or_node); while let Some(mut and_node) = and_cur {
let next_and = and_node.and.take(); let _ = and_node.u; and_cur = next_and; }
or_cur = next_or; }
}
pub(crate) fn cpcmlist(
mut l: Option<&Cmlist>,
) -> Option<Box<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 }
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 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 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) {
let guard = CMATCHER.read().ok();
let head = match guard.as_ref().and_then(|g| g.as_deref()) {
Some(h) => h,
None => return, };
let prefix = if ac != 0 { "compctl -M" } else { "MATCH" }; print!("{}", prefix);
let mut cur: Option<&Cmlist> = Some(head);
while let Some(p) = cur {
print!(" '{}'", p.str); cur = p.next.as_deref();
}
println!(); }
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 {
if parse_cmatcher(name, &s).is_none() {
eprintln!("{}: bad matcher specification `{}'", name, s);
return 1;
}
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 patcomps = PATCOMPS.write().unwrap();
for i in 0..patcomps.len() {
if patcomps[i].0 == n {
patcomps.remove(i);
break;
}
}
}
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 fn bin_compctl(
name: &str,
argv: &[String],
_ops: &crate::ported::zsh_h::options,
_func: i32,
) -> 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 const CFN_FIRST: i32 = 1; pub const CFN_DEFAULT: i32 = 2;
pub fn bin_compcall(
name: &str,
argv: &[String],
_ops: &crate::ported::zsh_h::options,
_func: i32,
) -> 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
}
pub(crate) fn ccmakehookfn(_dat: ()) -> i32 {
use std::sync::atomic::Ordering;
if let Ok(g) = CMATCHER.read() {
let mut cur: Option<&Cmlist> = g.as_deref();
if let Ok(mut mlist) = crate::ported::zle::compcore::matchers
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
{
mlist.clear();
while let Some(p) = cur {
mlist.push(p.matcher.clone()); cur = p.next.as_deref();
}
}
}
if let Ok(mut g) = crate::ported::zle::compcore::bmatchers
.get_or_init(|| std::sync::Mutex::new(None))
.lock()
{
*g = None;
}
if let Ok(mut g) = crate::ported::zle::compcore::ainfo
.get_or_init(|| std::sync::Mutex::new(None))
.lock()
{
*g = Some(Aminfo::default());
}
if let Ok(mut g) = crate::ported::zle::compcore::fainfo
.get_or_init(|| std::sync::Mutex::new(None))
.lock()
{
*g = Some(Aminfo::default());
}
crate::ported::zle::zle_tricky::LASTAMBIG.store(0, Ordering::Relaxed);
if let Ok(mut g) = crate::ported::zle::compcore::amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
{
g.clear(); }
crate::ported::zle::zle_tricky::MENUCMP.store(0, Ordering::Relaxed);
crate::ported::zle::compcore::menuacc.store(0, Ordering::Relaxed); crate::ported::zle::compcore::onlyexpl.store(0, Ordering::Relaxed);
let _ccused: Vec<String> = Vec::new(); let _ccstack: Vec<String> = Vec::new();
0
}
pub(crate) fn cccleanuphookfn(_dat: ()) -> i32 {
0
}
pub(crate) fn maketildelist() {
crate::ported::hashnameddir::fillnameddirtable();
let only_users = ADDWHAT.with(|c| c.get()) != -1;
let uname_bit = crate::ported::zsh_h::ND_USERNAME as i32;
let entries: Vec<String> = crate::ported::hashnameddir::nameddirtab()
.lock()
.ok()
.map(|t| {
t.iter()
.filter(|(_, nd)| !only_users || (nd.node.flags & uname_bit) != 0)
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
for name in entries {
addmatch(&name, None);
}
}
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 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);
}
}
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()) }; }
thread_local! {
static LPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static RPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static RSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static PPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static PSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LPPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LPSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static FPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static FSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QFPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QFSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QRPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QRSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QLPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QLSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static LSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static RPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static RSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static FPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static FSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static LPPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static LPSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static NOREAL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static IC: std::cell::Cell<char> = const { std::cell::Cell::new('\0') };
static PATCOMP: std::cell::RefCell<Option<crate::ported::pattern::Patprog>> =
const { std::cell::RefCell::new(None) };
static FILECOMP: std::cell::RefCell<Option<crate::ported::pattern::Patprog>> =
const { std::cell::RefCell::new(None) };
static CURCC: std::cell::RefCell<Option<Arc<Compctl>>> =
const { std::cell::RefCell::new(None) };
}
pub(crate) fn addmatch(s: &str, t: Option<&str>) {
let _ = t;
use crate::ported::zle::comp_h::{Cline, CMF_FILE};
use crate::ported::zle::compcore::{add_match_data, multiquote, tildequote};
use crate::ported::zle::compmatch::comp_match;
use std::sync::atomic::Ordering;
let aw = ADDWHAT.with(|c| c.get());
let mut isfile: i32 = 0; let mut isalt: i32 = 0; let mut isexact: i32 = 0; let mut lc: Option<Box<Cline>> = None; let ms: Option<String>;
let ppre = PPRE.with(|r| r.borrow().clone());
let psuf = PSUF.with(|r| r.borrow().clone());
if aw == -1 || aw == -5 || aw == -6 || aw == CC_FILES as i32 || aw == -7 || aw == -8 {
let ppl = ppre.len() as i32; let psl = psuf.len() as i32;
if (aw == CC_FILES as i32 || aw == -5) && psuf.is_empty() {
let sl = s.len();
if let Some(fign) = crate::ported::params::getaparam("fignore") {
for pt in &fign {
let filell = pt.len();
if filell < sl && s.ends_with(pt.as_str()) {
isalt = 1; break;
}
}
}
}
if aw == CC_FILES as i32 || aw == -6 || aw == -5 || aw == -8 {
let pfx = tildequote(&QFPRE.with(|r| r.borrow().clone()), 1);
let sfx = multiquote(&QFSUF.with(|r| r.borrow().clone()), 1);
let qu = if !ppre.is_empty() { 1 } else { 2 }; ms = FILECOMP.with(|fc| {
let fb = fc.borrow();
comp_match(
&pfx,
&sfx,
s,
fb.as_ref(),
Some(&mut lc),
qu,
None,
ppl,
None,
psl,
&mut isexact,
)
});
} else {
let pfx = multiquote(&FPRE.with(|r| r.borrow().clone()), 1);
let sfx = multiquote(&FSUF.with(|r| r.borrow().clone()), 1);
ms = FILECOMP.with(|fc| {
let fb = fc.borrow();
comp_match(
&pfx,
&sfx,
s,
fb.as_ref(),
Some(&mut lc),
0,
None,
ppl,
None,
psl,
&mut isexact,
)
});
}
if ms.is_none() {
return; }
if aw == -7 && findcmd(s, 0, 0).is_none() {
return;
}
isfile = CMF_FILE; } else if aw == CC_QUOTEFLAG as i32 || aw == -2 || aw == -3 || aw == -4 || aw == -9 || aw > 0 {
let (p1s, s1s, p2s, s2s) = if aw == CC_QUOTEFLAG as i32 {
(
QRPRE.with(|r| r.borrow().clone()),
QRSUF.with(|r| r.borrow().clone()),
RPRE.with(|r| r.borrow().clone()),
RSUF.with(|r| r.borrow().clone()),
)
} else {
(
QLPRE.with(|r| r.borrow().clone()),
QLSUF.with(|r| r.borrow().clone()),
LPRE.with(|r| r.borrow().clone()),
LSUF.with(|r| r.borrow().clone()),
)
};
let p1 = multiquote(&p1s, 1); let s1 = multiquote(&s1s, 1); let p2 = multiquote(&p2s, 1); let s2 = multiquote(&s2s, 1); let qflag = (aw == CC_QUOTEFLAG as i32) as i32; let p1l = p1.len() as i32;
let s1l = s1.len() as i32;
let first = PATCOMP.with(|pc| {
let pb = pc.borrow();
comp_match(
&p1,
&s1,
s,
pb.as_ref(),
Some(&mut lc),
qflag,
None,
p1l,
None,
s1l,
&mut isexact,
)
});
if first.is_some() {
ms = first;
} else {
let p2l = p2.len() as i32;
let s2l = s2.len() as i32;
ms = comp_match(
&p2,
&s2,
s,
None,
Some(&mut lc),
qflag,
None,
p2l,
None,
s2l,
&mut isexact,
);
if ms.is_none() {
return; }
}
} else {
return; }
let ms = match ms {
Some(m) => m,
None => return, };
let ipre_v = crate::ported::zle::compcore::ipre
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let ripre_v = crate::ported::zle::compcore::ripre
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let isuf_v = ISUF.lock().map(|g| g.clone()).unwrap_or_default();
let prpre_v = PRPRE.with(|r| r.borrow().clone()).unwrap_or_default();
let (pre_v, suf_v) = CURCC.with(|r| {
r.borrow()
.as_ref()
.map(|cc| {
(
cc.prefix.clone().unwrap_or_default(),
cc.suffix.clone().unwrap_or_default(),
)
})
.unwrap_or_default()
});
let lppre_v = if isfile != 0 {
LPPRE.with(|r| r.borrow().clone())
} else {
String::new()
};
let lpsuf_v = if isfile != 0 {
LPSUF.with(|r| r.borrow().clone())
} else {
String::new()
};
let mflags_v = crate::ported::zle::compcore::mflags.load(Ordering::Relaxed);
add_match_data(
isalt, &ms, s, lc, &ipre_v, &ripre_v, &isuf_v, &pre_v, &prpre_v, &lppre_v, None, &lpsuf_v, None, &suf_v, mflags_v | isfile, isexact,
);
MATCH_LIST.with(|r| r.borrow_mut().push(s.to_string()));
}
pub(crate) fn addhnmatch(name: &str, _flags: i32) {
addmatch(name, None);
}
pub(crate) fn getreal(str_in: &str) -> String {
let mut ne_guard = NOERRS.lock().expect("NOERRS poisoned");
let ne = *ne_guard;
*ne_guard = 1;
drop(ne_guard);
let s = crate::ported::subst::singsub(str_in);
*NOERRS.lock().expect("NOERRS poisoned") = ne;
if errflag.load(std::sync::atomic::Ordering::Relaxed) == 0 && !s.is_empty() {
return s;
}
errflag.fetch_and(
!crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
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,
};
let dot_prefix = FPRE.with(|r| r.borrow().starts_with('.'));
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 && !dot_prefix && name.starts_with('.') {
continue;
}
let meta = match entry.metadata() {
Ok(m) => m,
Err(_) => continue,
};
let is_dir = meta.is_dir();
#[cfg(unix)]
let is_exec = !is_dir && (meta.permissions().mode() & 0o111 != 0);
#[cfg(not(unix))]
let is_exec = false;
let keep = match (dirs, execs) {
(false, false) => true,
(true, false) => is_dir,
(false, true) => is_exec,
(true, true) => is_dir || is_exec,
};
if !keep {
continue;
}
addmatch(&name, None);
}
}
pub(crate) fn makecomplistglobal(os: &str, incmd: bool, _lst: i32, flags: i32) -> i32 {
use std::sync::atomic::Ordering;
CCONT.with(|c| c.set(CC_CCCONT));
if let Some(d) = CC_DUMMY.lock().unwrap().as_mut() {
if let Some(inner) = std::sync::Arc::get_mut(d) {
inner.suffix = None;
}
}
const CFN_FIRST: i32 = 1;
const CFN_DEFAULT: i32 = 2;
if !incmd {
let cmd_word = crate::ported::zle::complete::COMPWORDS
.get()
.and_then(|m| m.lock().ok())
.and_then(|g| g.first().cloned());
CMDSTR.with(|r| *r.borrow_mut() = cmd_word);
}
let lw = crate::ported::zle::compcore::linwhat.load(Ordering::Relaxed);
let in_env = lw == crate::ported::zle::compcore::IN_ENV_LW; let in_math = lw == crate::ported::zle::compcore::IN_MATH_LW; let in_cond = lw == crate::ported::zle::compcore::IN_COND_LW;
let mut cc: Option<Arc<Compctl>> = None;
if in_env {
if (flags & CFN_DEFAULT) == 0 {
cc = CC_DEFAULT.lock().unwrap().clone(); }
} else if in_math {
if (flags & CFN_DEFAULT) == 0 {
let mut dummy_inner = Compctl::default();
dummy_inner.mask = CC_PARAMS; dummy_inner.refc = 10000; cc = Some(Arc::new(dummy_inner));
}
} else if in_cond {
if (flags & CFN_DEFAULT) == 0 {
let lwpos = *CLWPOS.lock().unwrap() as usize;
let words = CLWORDS.lock().unwrap().clone();
let prev = if lwpos > 0 {
words.get(lwpos - 1).cloned()
} else {
None
};
let prev_s = prev.as_deref().unwrap_or("");
let mask = if prev_s == "-o" {
CC_OPTIONS
} else if (prev_s.starts_with('-') && prev_s.len() == 2)
|| prev_s == "-nt"
|| prev_s == "-ot"
|| prev_s == "-ef"
{
CC_FILES
} else {
CC_FILES | CC_PARAMS };
let mut dummy_inner = Compctl::default();
dummy_inner.mask = mask;
dummy_inner.refc = 10000;
cc = Some(Arc::new(dummy_inner));
}
} else {
return makecomplistcmd(os, incmd, flags);
}
if let Some(cc) = cc {
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;
}
}
}
makecomplistcc(&cc, os, incmd); return 1; }
0 }
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
}
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 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 (str_in, lip, lp) = crate::ported::zle::compcore::comp_str(false);
let os = CMDSTR.with(|r| r.borrow().clone());
let ow = CLWORDS.lock().unwrap().clone();
let on = *CLWNUM.lock().unwrap();
let op = *CLWPOS.lock().unwrap();
let ois = *INSTRING.lock().unwrap();
let oib = *INBACKT.lock().unwrap();
let oisuf = ISUF.lock().unwrap().clone();
let oqp = QIPRE.lock().unwrap().clone();
let oqs = QISUF.lock().unwrap().clone();
let oaq = AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let ooffs = *OFFS.lock().unwrap();
let compquote = crate::ported::zle::zle_tricky::COMPQUOTE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
match compquote.chars().next() {
Some('`') => {
*INSTRING.lock().unwrap() = QT_NONE;
*INBACKT.lock().unwrap() = 0;
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = String::new();
}
Some(c) if c == '\'' || c == '"' || c == '$' => {
*INSTRING.lock().unwrap() = match c {
'\'' => QT_SINGLE,
'"' => QT_DOUBLE,
_ => QT_DOLLARS,
};
*INBACKT.lock().unwrap() = 0;
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = if c == '$' {
compquote[1..].to_string()
} else {
compquote.clone()
};
}
_ => {
*INSTRING.lock().unwrap() = QT_NONE;
*INBACKT.lock().unwrap() = 0;
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = String::new();
}
}
*QIPRE.lock().unwrap() = COMPQIPREFIX.lock().unwrap().clone();
*QISUF.lock().unwrap() = COMPQISUFFIX.lock().unwrap().clone();
let mut isuf_v = crate::ported::zle::compcore::ctokenize(&COMPISUFFIX.lock().unwrap().clone());
crate::ported::glob::remnulargs(&mut isuf_v);
*ISUF.lock().unwrap() = isuf_v;
let compwords = COMPWORDS
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clone();
*CLWNUM.lock().unwrap() = compwords.len() as i32; *CLWPOS.lock().unwrap() = COMPCURRENT.load(std::sync::atomic::Ordering::Relaxed) - 1; CMDSTR.with(|r| *r.borrow_mut() = compwords.first().cloned()); let clw: Vec<String> = compwords
.iter()
.map(|w| {
let mut t = w.clone(); crate::ported::glob::tokenize(&mut t); crate::ported::glob::remnulargs(&mut t); t
})
.collect();
*CLWORDS.lock().unwrap() = clw;
*OFFS.lock().unwrap() = lip + lp;
INCOMPFUNC.with(|c| c.set(2));
let incmd = *CLWPOS.lock().unwrap() == 0; let ret = makecomplistglobal(
&str_in,
incmd,
crate::ported::zle::zle_h::COMP_COMPLETE,
flags,
); INCOMPFUNC.with(|c| c.set(1));
*ISUF.lock().unwrap() = oisuf;
*QIPRE.lock().unwrap() = oqp;
*QISUF.lock().unwrap() = oqs;
*INSTRING.lock().unwrap() = ois;
*INBACKT.lock().unwrap() = oib;
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = oaq;
*OFFS.lock().unwrap() = ooffs;
CMDSTR.with(|r| *r.borrow_mut() = os);
*CLWORDS.lock().unwrap() = ow;
*CLWNUM.lock().unwrap() = on;
*CLWPOS.lock().unwrap() = op;
CDEPTH.with(|c| c.set(c.get() - 1)); ret }
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.to_string(), incmd, compadd);
}
}
pub(crate) fn makecomplistext(occ: &Arc<Compctl>, os: &str, incmd: bool) {
use crate::ported::glob::tokenize;
use crate::ported::lex::untokenize;
use crate::ported::zle::compcore::rembslash;
let ins = {
let is = *INSTRING.lock().unwrap();
if is != QT_NONE {
is
} else if *INBACKT.lock().unwrap() != 0 {
QT_BACKTICK
} else {
0
}
};
let complete_in_word =
crate::ported::options::opt_state_get("COMPLETEINWORD").unwrap_or(false);
let mut m = 0; let mut d = 0;
let mut compc_opt = occ.ext.clone();
while let Some(compc) = compc_opt {
let mut compadd = 0i32; let mut t;
t = 0;
let mut cc_opt: Option<&Compcond> = compc.cond.as_deref();
while let Some(cc0) = cc_opt {
if t != 0 {
break; }
let or = cc0.or.as_deref();
t = 1;
let mut and_opt: Option<&Compcond> = Some(cc0);
while let Some(cc) = and_opt {
if t == 0 {
break;
}
let clwnum = *CLWNUM.lock().unwrap();
let clwpos = *CLWPOS.lock().unwrap();
t = 0;
let mut i = 0i32;
while i < cc.n && t == 0 {
*BRANGE.lock().unwrap() = 0;
*ERANGE.lock().unwrap() = clwnum - 1;
let iu = i as usize;
match cc.typ {
x if x == CCT_QUOTE => {
if let CompcondData::S { s, .. } = &cc.u {
let c0 = s.get(iu).and_then(|x| x.chars().next());
t = match c0 {
Some('s') if ins == QT_SINGLE => 1,
Some('d') if ins == QT_DOUBLE => 1,
Some('b') if ins == QT_BACKTICK => 1,
_ => 0,
};
}
}
x if x == CCT_POS || x == CCT_NUMWORDS => {
let tt = if x == CCT_POS { clwpos } else { clwnum }; if let CompcondData::R { a, b } = &cc.u {
let mut av = *a.get(iu).unwrap_or(&0); if av < 0 {
av += clwnum;
}
let mut bv = *b.get(iu).unwrap_or(&0); if bv < 0 {
bv += clwnum;
}
if x == CCT_POS {
*BRANGE.lock().unwrap() = av;
*ERANGE.lock().unwrap() = bv;
}
t = if tt >= av && tt <= bv { 1 } else { 0 }; }
}
x if x == CCT_CURSUF || x == CCT_CURPRE => {
if let CompcondData::S { s, .. } = &cc.u {
let mut sv = if clwpos < clwnum {
os.to_string()
} else {
String::new()
};
sv = untokenize(&sv); if complete_in_word {
let off = (*OFFS.lock().unwrap()).max(0) as usize;
if off <= sv.len() && sv.is_char_boundary(off) {
sv.truncate(off);
}
}
let sc =
rembslash(s.get(iu).map(|x| x.as_str()).unwrap_or("")); let a = sc.len(); if sv.len() >= a && sv.starts_with(&sc) {
compadd = if x == CCT_CURSUF { a as i32 } else { 0 }; t = 1;
}
}
}
x if x == CCT_CURSUB || x == CCT_CURSUBC => {
if clwpos < 0 || clwpos >= clwnum {
t = 0; } else if let CompcondData::S { p, s } = &cc.u {
let mut sv = untokenize(os); if complete_in_word {
let off = (*OFFS.lock().unwrap()).max(0) as usize; if off <= sv.len() && sv.is_char_boundary(off) {
sv.truncate(off);
}
}
let a = getcpat(
&sv,
*p.get(iu).unwrap_or(&0),
s.get(iu).map(|x| x.as_str()).unwrap_or(""),
if x == CCT_CURSUBC { 1 } else { 0 },
); if a != -1 {
compadd = a; t = 1;
}
}
}
x if x == CCT_CURPAT
|| x == CCT_CURSTR
|| x == CCT_WORDPAT
|| x == CCT_WORDSTR =>
{
let tt = if x == CCT_CURPAT || x == CCT_CURSTR {
clwpos
} else {
0
};
if let CompcondData::S { p, s } = &cc.u {
let mut a = tt + *p.get(iu).unwrap_or(&0); if a < 0 {
a += clwnum;
}
let sv0 = if a < 0 || a >= clwnum {
String::new()
} else {
CLWORDS
.lock()
.unwrap()
.get(a as usize)
.cloned()
.unwrap_or_default()
}; let sv = untokenize(&sv0); let patstr = s.get(iu).map(|x| x.as_str()).unwrap_or("");
if x == CCT_CURPAT || x == CCT_WORDPAT {
let mut ss = patstr.to_string();
tokenize(&mut ss);
t = patcompile(&ss, PAT_HEAPDUP as i32, None)
.map_or(false, |pp| pattry(&pp, &sv))
as i32;
} else {
t = (sv == rembslash(patstr)) as i32;
}
}
}
x if x == CCT_RANGESTR || x == CCT_RANGEPAT => {
let is_pat = x == CCT_RANGEPAT;
if let CompcondData::L { a, b } = &cc.u {
let astr = a.get(iu).cloned().unwrap_or_default();
let bstr = b.get(iu).cloned().unwrap_or_default();
let words = CLWORDS.lock().unwrap().clone();
let sc_a = if is_pat {
let mut z = astr.clone();
tokenize(&mut z);
z
} else {
rembslash(&astr) };
let mut j = clwpos - 1;
while j > 0 {
let sv = untokenize(
words.get(j as usize).map(|x| x.as_str()).unwrap_or(""),
); let matched = if is_pat {
patcompile(&sc_a, PAT_HEAPDUP as i32, None)
.map_or(false, |pp| pattry(&pp, &sv))
} else {
sv.len() >= sc_a.len() && sv.starts_with(&sc_a)
}; if matched {
*BRANGE.lock().unwrap() = j + 1; t = 1;
break;
}
j -= 1;
}
if t != 0 && !bstr.is_empty() {
let sc_b = if is_pat {
let mut z = bstr.clone();
tokenize(&mut z);
z
} else {
rembslash(&bstr)
};
let mut k = j + 1; while k < clwnum {
let sv = untokenize(
words
.get(k as usize)
.map(|x| x.as_str())
.unwrap_or(""),
);
let matched = if is_pat {
patcompile(&sc_b, PAT_HEAPDUP as i32, None)
.map_or(false, |pp| pattry(&pp, &sv))
} else {
sv.len() >= sc_b.len() && sv.starts_with(&sc_b)
};
if matched {
*ERANGE.lock().unwrap() = k - 1; t = if clwpos <= k - 1 { 1 } else { 0 }; break;
}
k += 1;
}
}
}
}
_ => {}
}
i += 1;
}
and_opt = cc.and.as_deref();
}
cc_opt = or;
}
if t != 0 {
m = 1;
CCONT.with(|c| c.set(c.get() & !(CC_PATCONT | CC_DEFCONT))); makecomplistor(&compc, os, incmd, compadd, 1); if d == 0 && (CCONT.with(|c| c.get()) & CC_DEFCONT) != 0 {
d = 1;
*BRANGE.lock().unwrap() = 0; *ERANGE.lock().unwrap() = *CLWNUM.lock().unwrap() - 1; makecomplistflags(occ, os.to_string(), incmd, 0); }
if (CCONT.with(|c| c.get()) & CC_PATCONT) == 0 {
break; }
}
compc_opt = compc.next.clone();
}
if m == 0 {
*BRANGE.lock().unwrap() = 0; *ERANGE.lock().unwrap() = *CLWNUM.lock().unwrap() - 1; makecomplistflags(occ, os.to_string(), incmd, 0); }
}
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::hashtable::shfunctab_lock()
.read()
.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 {
findcmd(&cmdstr, 1, 0) };
let pats = PATCOMPS.read().unwrap().clone();
for (pat, cc) in &pats {
let matches = patcompile(
&{
let mut __pat_tok = (pat).to_string();
crate::ported::glob::tokenize(&mut __pat_tok);
__pat_tok
},
PAT_HEAPDUP as i32,
None,
)
.map_or(false, |prog| {
pattry(&prog, &cmdstr) || s_resolved.as_deref()
.map(|sr| pattry(&prog, 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 sep_comp_string(ss: &str, s: &str, noffs: i32) -> i32 {
use crate::ported::zle::compcore::{ZLEMETACS as CS_G, ZLEMETALINE as LINE_G};
use std::sync::atomic::Ordering;
let owe = crate::ported::zle::compcore::WE.load(std::sync::atomic::Ordering::Relaxed);
let owb = crate::ported::zle::compcore::WB.load(std::sync::atomic::Ordering::Relaxed);
let ocs = CS_G.load(Ordering::Relaxed);
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 = LINE_G
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap()
.clone();
let oaq = AUTOQ.get_or_init(|| Mutex::new(String::new())).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;
CS_G.store(scs_initial, Ordering::Relaxed);
let mut scs = scs_initial;
tmp.push('x');
tmp.push_str(&s_post);
let tl = tmp.len() as i32;
let qstack_head = COMPQSTACK
.get_or_init(|| Mutex::new(String::new()))
.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;
}
*LINE_G.get_or_init(|| Mutex::new(String::new())).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;
crate::ported::zle::compcore::WB.store(owb, std::sync::atomic::Ordering::Relaxed);
crate::ported::zle::compcore::WE.store(owe, std::sync::atomic::Ordering::Relaxed);
CS_G.store(ocs, Ordering::Relaxed);
*LINE_G.get_or_init(|| Mutex::new(String::new())).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.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
if qstack.len() >= 2 {
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = String::new();
} else {
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = ts.clone();
}
} else {
*INSTRING.lock().unwrap() = QT_NONE;
*AUTOQ.get_or_init(|| Mutex::new(String::new())).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.get_or_init(|| Mutex::new(String::new())).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.get_or_init(|| Mutex::new(String::new())).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.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = oqst;
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = oaq;
*INSTRING.lock().unwrap() = ois;
*INBACKT.lock().unwrap() = oib;
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 makecomplistflags(cc: &Arc<Compctl>, mut s: String, _incmd: bool, compadd: i32) {
use crate::ported::string::dupstrpfx;
use crate::ported::utils::quotestring;
use crate::ported::zle::comp_h::{Cexpl, CGF_NOSORT, CGF_UNIQALL, CGF_UNIQCON, CMF_REMOVE};
use crate::ported::zle::compcore::{begcmgroup, check_param, endcmgroup, rembslash};
use crate::ported::zsh_h::{
ALIAS_GLOBAL, DISABLED, PM_ARRAY, PM_EXPORTED, PM_INTEGER, PM_READONLY, PM_SCALAR,
PM_SPECIAL, PM_UNSET,
};
use crate::ported::zsh_h::{Equals, Stringg, Tick, Tilde};
use std::sync::atomic::Ordering;
let incompfunc = INCOMPFUNC.with(|c| c.get());
let instr = *INSTRING.lock().unwrap_or_else(|e| e.into_inner());
let quotename = |x: &str| -> String {
if x.is_empty() {
String::new()
} else {
quotestring(x, if instr == QT_NONE { QT_BACKSLASH } else { instr })
}
};
let mut delit = false; PATCOMP.with(|r| *r.borrow_mut() = None);
FILECOMP.with(|r| *r.borrow_mut() = None);
LPRE.with(|r| r.borrow_mut().clear());
LSUF.with(|r| r.borrow_mut().clear());
RPRE.with(|r| r.borrow_mut().clear());
RSUF.with(|r| r.borrow_mut().clear());
PPRE.with(|r| r.borrow_mut().clear());
PSUF.with(|r| r.borrow_mut().clear());
LPPRE.with(|r| r.borrow_mut().clear());
LPSUF.with(|r| r.borrow_mut().clear());
FPRE.with(|r| r.borrow_mut().clear());
FSUF.with(|r| r.borrow_mut().clear());
QFPRE.with(|r| r.borrow_mut().clear());
QFSUF.with(|r| r.borrow_mut().clear());
QRPRE.with(|r| r.borrow_mut().clear());
QRSUF.with(|r| r.borrow_mut().clear());
QLPRE.with(|r| r.borrow_mut().clear());
QLSUF.with(|r| r.borrow_mut().clear());
if let Some(m) = crate::ported::zle::compcore::ipre.get() {
if let Ok(mut g) = m.lock() {
g.clear();
}
}
if let Some(m) = crate::ported::zle::compcore::ripre.get() {
if let Ok(mut g) = m.lock() {
g.clear();
}
}
CURCC.with(|r| *r.borrow_mut() = Some(cc.clone()));
crate::ported::zle::compcore::mflags.store(0, Ordering::Relaxed);
let gflags = (if (cc.mask2 & CC_NOSORT) != 0 { CGF_NOSORT } else { 0 })
| (if (cc.mask2 & CC_UNIQALL) != 0 {
CGF_UNIQALL
} else {
0
})
| (if (cc.mask2 & CC_UNIQCON) != 0 {
CGF_UNIQCON
} else {
0
});
if cc.gname.is_some() {
endcmgroup(None);
begcmgroup(cc.gname.as_deref(), gflags);
}
if cc.ylist.is_some() {
endcmgroup(None);
begcmgroup(None, gflags);
}
if (cc.mask & CC_REMOVE) != 0 {
crate::ported::zle::compcore::mflags.fetch_or(CMF_REMOVE, Ordering::Relaxed);
}
let cell = crate::ported::zle::compcore::curexpl.get_or_init(|| Mutex::new(None));
if let Ok(mut g) = cell.lock() {
*g = if cc.explain.is_some() {
Some(Cexpl::default())
} else {
None
};
}
let mut offs = crate::ported::zle::compcore::OFFS.load(Ordering::Relaxed);
if compadd > 0 {
let mut ca = (compadd as usize).min(s.len());
while ca > 0 && !s.is_char_boundary(ca) {
ca -= 1;
}
let ip = crate::ported::lex::untokenize(&s[..ca]);
let cell = crate::ported::zle::compcore::ipre.get_or_init(|| Mutex::new(String::new()));
if let Ok(mut g) = cell.lock() {
*g = ip;
}
crate::ported::zle::compcore::WB
.fetch_add(compadd, std::sync::atomic::Ordering::Relaxed);
s = s[ca..].to_string();
offs -= compadd;
if offs < 0 {
return;
}
}
if let Some(prefix) = cc.prefix.as_ref() {
if !s.is_empty() {
let dp = rembslash(prefix);
let sd = crate::ported::lex::untokenize(&s);
let mut pl = crate::ported::zle::zle_tricky::pfxlen(&dp, &sd).min(s.len());
while pl > 0 && !s.is_char_boundary(pl) {
pl -= 1;
}
s = s[pl..].to_string();
offs -= pl as i32;
}
}
if let Some(suffix) = cc.suffix.as_ref() {
let mut sdup = rembslash(suffix);
while sdup.ends_with(' ') {
sdup.pop();
}
let sd = crate::ported::lex::untokenize(&s);
let sl = sdup.len();
let suffixll = sd.len();
if !sd.is_empty()
&& suffixll >= sl
&& (offs as usize) <= suffixll - sl
&& sd.ends_with(sdup.as_str())
{
let cut = suffixll - sl;
if cut <= s.len() && s.is_char_boundary(cut) {
s.truncate(cut);
}
}
}
let ic_char = s.chars().next().unwrap_or('\0');
let mut ic = if incompfunc != 0 {
'\0'
} else if ic_char == Tilde || ic_char == '~' {
Tilde
} else if ic_char == Equals || ic_char == '=' {
Equals
} else {
'\0'
};
let param_cc: Option<Arc<Compctl>> = if incompfunc == 0 {
let saved_wb = crate::ported::zle::compcore::WB.load(Ordering::Relaxed);
crate::ported::zle::compcore::OFFS.store(offs, Ordering::Relaxed);
if let Some(p) = check_param(&s, true, false) {
let mut p = p.min(s.len());
while p > 0 && !s.is_char_boundary(p) {
p -= 1;
}
s = s[p..].to_string(); offs = crate::ported::zle::compcore::OFFS.load(Ordering::Relaxed);
crate::ported::zle::compcore::WB.store(saved_wb, Ordering::Relaxed);
Some(Arc::new(Compctl {
refc: 10000,
mask: CC_PARAMS | CC_ENVVARS,
..Default::default()
}))
} else {
None
}
} else {
None
};
let cc: &Arc<Compctl> = param_cc.as_ref().unwrap_or(cc);
if (cc.mask & CC_DELETE) != 0 {
delit = true;
s.clear();
offs = 0;
}
crate::ported::zle::compcore::OFFS.store(offs, Ordering::Relaxed);
let cut = {
let mut c = (offs.max(0) as usize).min(s.len());
while c > 0 && !s.is_char_boundary(c) {
c -= 1;
}
c
};
let lpre_s = s[..cut].to_string();
let lsuf_s = s[cut..].to_string();
LPL.with(|c| c.set(lpre_s.len() as i32));
LSL.with(|c| c.set(lsuf_s.len() as i32));
let qlpre_s = quotename(&lpre_s);
let qlsuf_s = quotename(&lsuf_s);
if ic == Tilde && lpre_s.contains('/') {
ic = '\0';
}
IC.with(|c| c.set(ic));
let ispar = crate::ported::zle::compcore::ispar.load(Ordering::Relaxed);
let mut noreal = if delit { 0 } else { 1 };
let lpre_has_tok = lpre_s.chars().any(|c| c == Stringg || c == Tick);
let tt: &str = if ic != '\0' && ispar == 0 && !lpre_s.is_empty() {
&lpre_s[lpre_s.chars().next().map(|c| c.len_utf8()).unwrap_or(0)..]
} else {
&lpre_s
};
let first_lpre = lpre_s.chars().next();
let mut rpre_s = if lpre_has_tok || first_lpre == Some(Tilde) || first_lpre == Some(Equals) {
noreal = 0;
getreal(tt)
} else if (first_lpre == Some('~') && lpre_s.contains('/')) || first_lpre == Some('=') {
noreal = 0;
let mut tok = tt.to_string();
crate::ported::glob::tokenize(&mut tok);
getreal(&tok)
} else {
tt.to_string()
};
let qrpre_s = quotename(&rpre_s);
let lsuf_has_tok = lsuf_s.chars().any(|c| c == Stringg || c == Tick);
let mut rsuf_s = if lsuf_has_tok {
noreal = 0;
getreal(&lsuf_s)
} else {
lsuf_s.clone()
};
let qrsuf_s = quotename(&rsuf_s);
NOREAL.with(|c| c.set(noreal));
crate::ported::zle::compcore::ispattern.store(0, Ordering::Relaxed);
rpre_s = crate::ported::lex::untokenize(&rpre_s);
rsuf_s = crate::ported::lex::untokenize(&rsuf_s);
let rpl = rpre_s.len();
let rsl = rsuf_s.len();
RPL.with(|c| c.set(rpl as i32));
RSL.with(|c| c.set(rsl as i32));
let lpre_s = crate::ported::lex::untokenize(&lpre_s);
let lsuf_s = crate::ported::lex::untokenize(&lsuf_s);
LPRE.with(|r| *r.borrow_mut() = lpre_s);
LSUF.with(|r| *r.borrow_mut() = lsuf_s);
RPRE.with(|r| *r.borrow_mut() = rpre_s.clone());
RSUF.with(|r| *r.borrow_mut() = rsuf_s.clone());
QLPRE.with(|r| *r.borrow_mut() = qlpre_s);
QLSUF.with(|r| *r.borrow_mut() = qlsuf_s);
QRPRE.with(|r| *r.borrow_mut() = qrpre_s);
QRSUF.with(|r| *r.borrow_mut() = qrsuf_s);
if (cc.mask & CC_DELETE) == 0 {
crate::ported::zle::compcore::hasmatched.store(1, Ordering::Relaxed);
}
if (cc.mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) != 0 || cc.glob.is_some() {
let s1 = rpre_s.rfind('/');
let s2 = rsuf_s.find('/');
let ppre_s = match s1 {
Some(idx) => rpre_s[..idx + 1].to_string(),
None => String::new(),
};
let psuf_s = match s2 {
Some(idx) => rsuf_s[idx..].to_string(),
None => String::new(),
};
let s_first = s.as_bytes().first().copied();
let cs_eq_wb = crate::ported::zle::compcore::ZLEMETACS
.load(std::sync::atomic::Ordering::Relaxed)
== crate::ported::zle::compcore::WB.load(std::sync::atomic::Ordering::Relaxed);
let s1_is_start = s1.is_none() || s1 == Some(0);
let start_cond = s1_is_start || ic != '\0';
let mut fpre_s = if start_cond && (s_first != Some(b'/') || cs_eq_wb) {
match s1 {
Some(idx) => rpre_s[idx..].to_string(),
None => rpre_s.clone(),
}
} else {
match s1 {
Some(idx) => rpre_s[idx + 1..].to_string(),
None => {
let skip = rpre_s.chars().next().map(|c| c.len_utf8()).unwrap_or(0);
rpre_s[skip..].to_string()
}
}
};
let mut fsuf_s = match s2 {
Some(idx) => dupstrpfx(&rsuf_s, idx),
None => rsuf_s.clone(),
};
let qfpre_s = quotename(&fpre_s);
let qfsuf_s = quotename(&fsuf_s);
fpre_s = crate::ported::lex::untokenize(&fpre_s);
fsuf_s = crate::ported::lex::untokenize(&fsuf_s);
FPL.with(|c| c.set(fpre_s.len() as i32));
FSL.with(|c| c.set(fsuf_s.len() as i32));
let meta = crate::ported::zle::compcore::ZLEMETALINE
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let wb = crate::ported::zle::compcore::WB.load(std::sync::atomic::Ordering::Relaxed) as usize;
let we = crate::ported::zle::compcore::WE.load(std::sync::atomic::Ordering::Relaxed) as usize;
let cs = crate::ported::zle::compcore::ZLEMETACS
.load(std::sync::atomic::Ordering::Relaxed) as usize;
let sf1 = !ppre_s.is_empty();
let lppre_s = if cs != wb && wb <= cs && cs <= meta.len() {
let word = &meta[wb..cs];
match word.rfind('/') {
Some(p) => word[..=p].to_string(), None if !sf1 => String::new(), None => ppre_s.clone(), }
} else {
String::new()
};
let lpsuf_s = if cs != we && cs <= we && we <= meta.len() {
let tail = &meta[cs..we];
match tail.find('/') {
Some(p) => tail[p..].to_string(),
None if s2.is_some() => psuf_s.clone(), None => String::new(),
}
} else {
String::new()
};
PPRE.with(|r| *r.borrow_mut() = ppre_s);
PSUF.with(|r| *r.borrow_mut() = psuf_s);
FPRE.with(|r| *r.borrow_mut() = fpre_s);
FSUF.with(|r| *r.borrow_mut() = fsuf_s);
QFPRE.with(|r| *r.borrow_mut() = qfpre_s);
QFSUF.with(|r| *r.borrow_mut() = qfsuf_s);
LPPRE.with(|r| *r.borrow_mut() = lppre_s);
LPSUF.with(|r| *r.borrow_mut() = lpsuf_s);
}
let s: &str = &s;
let ppre = PPRE.with(|r| r.borrow().clone());
let psuf = PSUF.with(|r| r.borrow().clone());
CCONT.with(|c| c.set(c.get() | (cc.mask2 & (CC_CCCONT | CC_DEFCONT | CC_PATCONT))));
let saved_prpre = PRPRE.with(|r| r.borrow().clone());
if !ppre.is_empty() {
PRPRE.with(|r| *r.borrow_mut() = Some(ppre.clone()));
}
let has_file_mask =
(cc.mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) != 0 || cc.glob.is_some();
if ic == Tilde && has_file_mask {
ADDWHAT.with(|c| c.set(-1)); let oi = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = format!("~{}", oi); }
maketildelist();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = oi; }
} else if ic == Equals && has_file_mask {
ADDWHAT.with(|c| c.set(-7));
let oi = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = format!("={}", oi); }
if crate::ported::zsh_h::isset(crate::ported::zsh_h::HASHLISTALL) {
let path = crate::ported::params::getaparam("path").unwrap_or_default();
crate::ported::hashtable::fillcmdnamtable(&path);
}
let cmds: Vec<String> = crate::ported::hashtable::cmdnamtab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(cmds, -7);
let aliases: Vec<String> = crate::ported::hashtable::aliastab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(aliases, -2);
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = oi; }
} else {
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_COMMPATH) != 0 {
ADDWHAT.with(|c| c.set(-5));
let sf1 = !ppre.is_empty(); if sf1 {
let save = PRPRE.with(|r| r.borrow().clone());
PRPRE.with(|r| *r.borrow_mut() = Some(ppre.clone()));
gen_matches_files(true, true, false);
PRPRE.with(|r| *r.borrow_mut() = save);
} else {
let path = crate::ported::params::getaparam("path").unwrap_or_default();
let cwd_in_path = path.iter().any(|p| p.is_empty() || p == ".");
if cwd_in_path {
let save = PRPRE.with(|r| r.borrow().clone());
PRPRE.with(|r| *r.borrow_mut() = Some("./".to_string()));
gen_matches_files(true, true, false); PRPRE.with(|r| *r.borrow_mut() = save);
}
}
}
} PRPRE.with(|r| *r.borrow_mut() = saved_prpre);
if (cc.mask & CC_NAMED) != 0 {
ADDWHAT.with(|c| c.set(-1));
let oi = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = format!("~{}", oi);
}
maketildelist();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = oi;
}
}
if ic == '\0' && (cc.mask & CC_COMMPATH) != 0 && ppre.is_empty() && psuf.is_empty() {
let aliases: Vec<String> = crate::ported::hashtable::aliastab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(aliases, -3);
let reswds: Vec<String> = crate::ported::hashtable::reswdtab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(reswds, -3);
let funcs: Vec<String> = crate::ported::hashtable::shfunctab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(funcs, -3);
let builtins: Vec<String> = crate::ported::builtin::BUILTINS
.iter()
.map(|b| b.node.nam.clone())
.collect();
dumphashtable(builtins, -3);
if crate::ported::zsh_h::isset(crate::ported::zsh_h::HASHLISTALL) {
let path = crate::ported::params::getaparam("path").unwrap_or_default();
crate::ported::hashtable::fillcmdnamtable(&path);
}
let cmds: Vec<String> = crate::ported::hashtable::cmdnamtab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(cmds, -3);
}
let oaw: i32 = if (cc.mask & CC_QUOTEFLAG) != 0 {
-2
} else {
CC_QUOTEFLAG as i32
};
ADDWHAT.with(|c| c.set(oaw));
if (cc.mask & CC_OPTIONS) != 0 {
let names: Vec<String> = crate::ported::options::OPTIONTAB
.iter()
.map(|o| o.to_string())
.collect();
dumphashtable(names, oaw);
}
if (cc.mask & CC_VARS) != 0 {
let names: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| (pm.node.flags & PM_UNSET as i32) == 0 && pm.level == 0)
.map(|(n, _)| n.clone())
.collect()
};
dumphashtable(names, -9);
ADDWHAT.with(|c| c.set(oaw)); }
if (cc.mask & CC_BINDINGS) != 0 {
let names: Vec<String> = crate::ported::zle::zle_thingy::thingytab()
.lock()
.map(|t| t.keys().cloned().collect())
.unwrap_or_default();
dumphashtable(names, CC_BINDINGS as i32);
ADDWHAT.with(|c| c.set(oaw)); }
if let Some(kv) = cc.keyvar.as_ref() {
if let Some(usr) = crate::ported::zle::compcore::get_user_var(Some(kv.as_str())) {
ADDWHAT.with(|c| c.set(oaw));
for u in usr {
addmatch(&u, None);
}
}
}
if (cc.mask & CC_USERS) != 0 {
maketildelist();
ADDWHAT.with(|c| c.set(oaw)); }
if let Some(func_name) = cc.func.as_ref() {
if let Some(mut shfunc) = crate::ported::utils::getshfunc(func_name) {
let largs: Vec<String> = vec![func_name.clone(), s.to_string()];
let in_compfunc = INCOMPFUNC.with(|c| c.get());
if in_compfunc != 1 {
INCOMPCTLFUNC.with(|c| c.set(true));
}
let osc = crate::ported::builtin::SFCONTEXT.swap(
crate::ported::zsh_h::SFC_COMPLETE,
std::sync::atomic::Ordering::Relaxed,
);
let name_for_body = func_name.clone();
let body_args: Vec<String> = vec![s.to_string()];
let body_runner = move || -> i32 {
crate::ported::exec::run_function_body(&name_for_body, &body_args).unwrap_or(0)
};
let _ = crate::ported::exec::doshfunc(&mut shfunc, largs, true, body_runner);
crate::ported::builtin::SFCONTEXT.store(osc, std::sync::atomic::Ordering::Relaxed);
INCOMPCTLFUNC.with(|c| c.set(false));
if let Some(reply) = crate::ported::params::getaparam("reply") {
ADDWHAT.with(|c| c.set(oaw));
for m in reply {
addmatch(&m, None);
}
}
}
}
if let Some(ylist) = cc.ylist.as_ref() {
let is_var_ref = ylist.starts_with('$') || ylist.starts_with('(');
if !is_var_ref {
if let Some(mut shfunc) = crate::ported::utils::getshfunc(ylist) {
let largs: Vec<String> = vec![ylist.clone()];
let in_compfunc = INCOMPFUNC.with(|c| c.get());
if in_compfunc != 1 {
INCOMPCTLFUNC.with(|c| c.set(true));
}
let osc = crate::ported::builtin::SFCONTEXT.swap(
crate::ported::zsh_h::SFC_COMPLETE,
std::sync::atomic::Ordering::Relaxed,
);
let name_for_body = ylist.clone();
let body_runner = move || -> i32 {
crate::ported::exec::run_function_body(&name_for_body, &[]).unwrap_or(0)
};
let _ = crate::ported::exec::doshfunc(&mut shfunc, largs, true, body_runner);
crate::ported::builtin::SFCONTEXT.store(osc, std::sync::atomic::Ordering::Relaxed);
INCOMPCTLFUNC.with(|c| c.set(false));
}
}
}
if let Some(s) = &cc.str {
let expanded = getreal(s);
ADDWHAT.with(|c| c.set(-6));
addmatch(&expanded, None);
}
if let Some(hpat) = cc.hpat.as_ref() {
use crate::ported::zsh_h::{GETHIST_UPWARD, HIST_FOREIGN};
let pprog = if !hpat.is_empty() {
let mut thpat = hpat.clone();
crate::ported::glob::tokenize(&mut thpat);
patcompile(&thpat, PAT_HEAPDUP as i32, None)
} else {
None
};
let mut n = cc.hnum;
if n == 0 {
n = -1;
}
let cur = crate::ported::hist::curhist.load(std::sync::atomic::Ordering::Relaxed);
let start = crate::ported::hist::addhistnum(cur, -1, HIST_FOREIGN as i32); let mut he_ev = crate::ported::hist::gethistent(start, GETHIST_UPWARD); ADDWHAT.with(|c| c.set(oaw));
while n != 0 {
let ev = match he_ev {
Some(e) => e,
None => break,
};
n -= 1;
if let Some(he) = crate::ported::hist::gethist(ev) {
let nam = &he.node.nam;
for iw in (0..he.nwords).rev() {
let bi = (iw * 2) as usize;
if bi + 1 >= he.words.len() {
continue;
}
let hstart = he.words[bi] as usize; let hend = he.words[bi + 1] as usize; if hstart > hend
|| hend > nam.len()
|| !nam.is_char_boundary(hstart)
|| !nam.is_char_boundary(hend)
{
continue;
}
let word = &nam[hstart..hend];
let c0 = word.chars().next();
if !matches!(c0, Some('\'') | Some('"') | Some('`') | Some('$'))
&& pprog.as_ref().map_or(true, |pp| pattry(pp, word))
{
addmatch(word, None); }
}
}
he_ev = crate::ported::hist::up_histent(ev); }
}
{
let t = cc.mask
& (CC_ARRAYS
| CC_INTVARS
| CC_ENVVARS
| CC_SCALARS
| CC_READONLYS
| CC_SPECIALS
| CC_PARAMS);
if t != 0 {
let names: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| {
let f = pm.node.flags as u32;
if (f & PM_UNSET) != 0 || pm.level != 0 {
return false;
}
((t & CC_ARRAYS) != 0 && (f & PM_ARRAY) != 0)
|| ((t & CC_INTVARS) != 0 && (f & PM_INTEGER) != 0)
|| ((t & CC_ENVVARS) != 0 && (f & PM_EXPORTED) != 0)
|| ((t & CC_SCALARS) != 0 && (f & PM_SCALAR) != 0)
|| ((t & CC_READONLYS) != 0 && (f & PM_READONLY) != 0)
|| ((t & CC_SPECIALS) != 0 && (f & PM_SPECIAL) != 0)
|| ((t & CC_PARAMS) != 0 && (f & PM_EXPORTED) == 0)
})
.map(|(n, _)| n.clone())
.collect()
};
dumphashtable(names, t as i32);
}
}
let want_dis = (cc.mask & CC_DISCMDS) != 0;
let want_ex = (cc.mask & CC_EXCMDS) != 0;
let en_ok = |disabled: bool| (want_dis && disabled) || (want_ex && !disabled);
if (cc.mask & CC_SHFUNCS) != 0 {
let names: Vec<String> = crate::ported::hashtable::shfunctab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, f)| en_ok((f.node.flags & DISABLED) != 0))
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & CC_SHFUNCS) as i32);
}
if (cc.mask & CC_BUILTINS) != 0 {
let names: Vec<String> = crate::ported::builtin::BUILTINS
.iter()
.filter(|b| en_ok((b.node.flags & DISABLED) != 0))
.map(|b| b.node.nam.clone())
.collect();
dumphashtable(names, (cc.mask & CC_BUILTINS) as i32);
}
if (cc.mask & CC_EXTCMDS) != 0 {
let names: Vec<String> = crate::ported::hashtable::cmdnamtab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, c)| en_ok((c.node.flags & DISABLED) != 0))
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & CC_EXTCMDS) as i32);
}
if (cc.mask & CC_RESWDS) != 0 {
let names: Vec<String> = crate::ported::hashtable::reswdtab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, r)| en_ok((r.node.flags & DISABLED) != 0))
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & CC_RESWDS) as i32);
}
if (cc.mask & (CC_ALREG | CC_ALGLOB)) != 0 {
let want_reg = (cc.mask & CC_ALREG) != 0;
let want_glob = (cc.mask & CC_ALGLOB) != 0;
let names: Vec<String> = crate::ported::hashtable::aliastab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, a)| {
let g = (a.node.flags & ALIAS_GLOBAL) != 0;
let type_ok = (want_reg && !g) || (want_glob && g);
type_ok && en_ok((a.node.flags & DISABLED) != 0)
})
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & (CC_ALREG | CC_ALGLOB)) as i32);
}
}
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
}
static ZLEMETALL: Mutex<i32> = Mutex::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
}
pub(crate) static CMATCHER: std::sync::RwLock<Option<Box<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());
static NOERRS: Mutex<i32> = Mutex::new(0);
static NOALIASES: Mutex<i32> = Mutex::new(0);
static INSTRING: Mutex<i32> = Mutex::new(QT_NONE);
static INBACKT: Mutex<i32> = Mutex::new(0);
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 ISUF: Mutex<String> = Mutex::new(String::new());
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(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}';
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);
fn inull(c: char) -> bool {
matches!(c, Snull | Dnull | Bnull | Stringg | QSTRING_TOK)
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn createcompctltable_initializes_table() {
let _g = crate::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(COMP_SPECIAL, COMP_COMMAND | COMP_DEFAULT | COMP_FIRST);
}
#[test]
fn cc_flags2_constants_match_c_compctlh() {
let _g = crate::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compctl("compctl", &["-f".to_string(), "mycmd".to_string()], &ops, 0);
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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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 delpatcomp_removes_only_first_match_when_duplicates() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
{
let mut p = PATCOMPS.write().unwrap();
p.clear();
for tag in ["a", "b", "c"] {
p.push((
"dup*".to_string(),
Arc::new(Compctl {
keyvar: Some(tag.to_string()),
..Compctl::default()
}),
));
}
}
delpatcomp("dup*");
let p = PATCOMPS.read().unwrap();
assert_eq!(
p.len(),
2,
"c:1308 — only ONE entry removed; got len={}",
p.len()
);
assert_eq!(
p[0].1.keyvar.as_deref(),
Some("b"),
"remaining first entry must be the second-inserted (b)"
);
assert_eq!(
p[1].1.keyvar.as_deref(),
Some("c"),
"remaining second entry must be the third-inserted (c)"
);
}
#[test]
fn cc_assign_with_reass_command_target_uses_special_key() {
let _g = crate::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = zle_test_setup();
let e = enables_();
assert_eq!(e, vec![1, 1]);
}
#[test]
fn bin_compcall_outside_compfunc_errors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPFUNC.with(|c| c.set(0));
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compcall("compcall", &[], &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn bin_compcall_inside_compfunc_succeeds() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPFUNC.with(|c| c.set(1));
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compcall("compcall", &["-T".to_string()], &ops, 0);
assert_eq!(r, 0);
INCOMPFUNC.with(|c| c.set(0));
}
#[test]
fn compctlread_outside_compctl_func_errors() {
let _g = crate::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(cccleanuphookfn(()), 0);
}
fn mnum_now() -> i32 {
crate::ported::zle::compcore::mnum.load(std::sync::atomic::Ordering::Relaxed)
}
fn clear_matches() {
crate::ported::zle::compcore::matches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clear();
}
fn last_match_orig() -> Option<String> {
crate::ported::zle::compcore::matches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.last()
.and_then(|cm| cm.orig.clone())
}
fn reset_compctl_statics() {
PATCOMP.with(|r| *r.borrow_mut() = None);
FILECOMP.with(|r| *r.borrow_mut() = None);
for st in [
&LPRE, &LSUF, &RPRE, &RSUF, &PPRE, &PSUF, &LPPRE, &LPSUF, &FPRE, &FSUF, &QFPRE, &QFSUF,
&QRPRE, &QRSUF, &QLPRE, &QLSUF,
] {
st.with(|r| r.borrow_mut().clear());
}
crate::ported::zle::compcore::mflags.store(0, std::sync::atomic::Ordering::Relaxed);
}
#[test]
fn addmatch_rejects_unset_addwhat() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(0));
addmatch("dropped", None);
assert!(
MATCH_LIST.with(|r| r.borrow().is_empty()),
"addwhat=0 should drop the match"
);
assert_eq!(mnum_now(), before, "no Cmatch may be registered");
assert_eq!(
crate::ported::zle::compcore::matches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.len(),
0,
"match registry stays empty"
);
}
#[test]
fn addmatch_accepts_files_kind() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(-5));
addmatch("foo.txt", None);
addmatch("bar.txt", None);
assert_eq!(mnum_now(), before + 2, "two Cmatches registered");
let reg = crate::ported::zle::compcore::matches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clone();
assert_eq!(reg.len(), 2);
assert_eq!(reg[0].orig.as_deref(), Some("foo.txt"));
assert_eq!(reg[1].orig.as_deref(), Some("bar.txt"));
use crate::ported::zle::comp_h::CMF_FILE;
assert_ne!(reg[0].flags & CMF_FILE, 0, "file matches flagged CMF_FILE");
}
#[test]
fn addmatch_accepts_param_kind() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(-9));
addmatch("HOME", None);
assert_eq!(mnum_now(), before + 1, "one Cmatch registered");
assert_eq!(last_match_orig().as_deref(), Some("HOME"));
use crate::ported::zle::comp_h::CMF_FILE;
let reg = crate::ported::zle::compcore::matches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clone();
assert_eq!(reg[0].flags & CMF_FILE, 0, "param match is not a file");
}
#[test]
fn addmatch_accepts_cc_files_positive_mask() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(CC_FILES as i32));
addmatch("foo", None);
assert_eq!(mnum_now(), before + 1);
assert_eq!(last_match_orig().as_deref(), Some("foo"));
}
#[test]
fn getcpat_finds_first_substring() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("abcabc", 1, "bc", 0);
assert_eq!(r, 3);
}
#[test]
fn getcpat_finds_second_substring() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("abcabc", 2, "bc", 0);
assert_eq!(r, 6);
}
#[test]
fn getcpat_negative_index_searches_backward() {
let _g = crate::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("hello", 1, "xyz", 0);
assert_eq!(r, -1);
}
#[test]
fn getcpat_strips_backslashes_in_pattern() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("foo$bar", 1, "\\$", 0);
assert_eq!(r, 4); }
#[test]
fn dumphashtable_calls_addmatch_per_entry() {
let _g = crate::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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, String::new(), 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::test_util::global_state_lock();
let _g = 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, String::new(), 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
use crate::ported::zle::compcore::{WB, WE, ZLEMETACS as CS_G, ZLEMETALINE as LINE_G};
use std::sync::atomic::Ordering;
WE.store(42, Ordering::Relaxed);
WB.store(7, Ordering::Relaxed);
CS_G.store(11, Ordering::Relaxed);
*ZLEMETALL.lock().unwrap() = 99;
*INSTRING.lock().unwrap() = QT_DOUBLE;
*INBACKT.lock().unwrap() = 1;
*NOALIASES.lock().unwrap() = 1;
*NOERRS.lock().unwrap() = 0;
*LINE_G.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "hello".to_string();
*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "Q".to_string();
let _ = sep_comp_string("", "x", 0);
assert_eq!(WE.load(Ordering::Relaxed), 42);
assert_eq!(WB.load(Ordering::Relaxed), 7);
assert_eq!(CS_G.load(Ordering::Relaxed), 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!(*LINE_G.get_or_init(|| Mutex::new(String::new())).lock().unwrap(), "hello");
assert_eq!(*AUTOQ.get_or_init(|| Mutex::new(String::new())).lock().unwrap(), "Q");
}
#[test]
fn inull_recognises_marker_chars() {
let _g = crate::test_util::global_state_lock();
let _g = 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::test_util::global_state_lock();
let _g = 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);
}
#[test]
fn compctl_corpus_inull_recognises_null_tokens() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(inull(Snull));
assert!(inull(Dnull));
assert!(inull(Bnull));
assert!(inull(Stringg));
assert!(inull(QSTRING_TOK));
}
#[test]
fn compctl_corpus_inull_rejects_printables() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
for c in ['a', 'Z', '0', ' ', '!', '~'] {
assert!(!inull(c), "{c:?} should NOT be inull");
}
}
#[test]
fn compctl_corpus_qt_pairwise_distinct() {
let qs = [
QT_NONE,
QT_BACKSLASH,
QT_SINGLE,
QT_DOUBLE,
QT_DOLLARS,
QT_BACKTICK,
];
for (i, a) in qs.iter().enumerate() {
for b in &qs[i + 1..] {
assert_ne!(a, b);
}
}
}
#[test]
fn compctl_corpus_qt_all_within_range() {
for q in [
QT_NONE,
QT_BACKSLASH,
QT_SINGLE,
QT_DOUBLE,
QT_DOLLARS,
QT_BACKTICK,
] {
assert!((0..=5).contains(&q), "QT_* value {q} out of [0,5]");
}
}
#[test]
fn createcompctltable_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
createcompctltable();
}
}
#[test]
fn freecompctlp_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
freecompctlp("");
}
#[test]
fn freecompctlp_unknown_name_no_panic() {
let _g = crate::test_util::global_state_lock();
freecompctlp("__never_a_real_compctl_xyz__");
}
#[test]
fn compctl_name_pat_returns_tuple_type() {
let _: (bool, String) = compctl_name_pat("");
}
#[test]
fn compctl_name_pat_is_pure() {
for s in ["", "name", "*pat*", "with[brackets]"] {
let first = compctl_name_pat(s);
for _ in 0..3 {
assert_eq!(
compctl_name_pat(s),
first,
"compctl_name_pat({:?}) must be pure",
s
);
}
}
}
#[test]
fn delpatcomp_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
delpatcomp("");
}
#[test]
fn ccmakehookfn_returns_i32_type() {
let _: i32 = ccmakehookfn(());
}
#[test]
fn cccleanuphookfn_returns_i32_type() {
let _: i32 = cccleanuphookfn(());
}
#[test]
fn ccmakehookfn_idempotent() {
for _ in 0..5 {
let _ = ccmakehookfn(());
}
}
#[test]
fn cccleanuphookfn_idempotent() {
for _ in 0..5 {
let _ = cccleanuphookfn(());
}
}
#[test]
fn set_gmatcher_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = set_gmatcher("", &[]);
}
#[test]
fn get_gmatcher_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = get_gmatcher("", &[]);
}
#[test]
fn print_gmatcher_safe_for_zero() {
let _g = crate::test_util::global_state_lock();
print_gmatcher(0);
}
#[test]
fn maketildelist_safe_no_panic() {
let _g = crate::test_util::global_state_lock();
maketildelist();
}
#[test]
fn maketildelist_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
maketildelist();
}
}
#[test]
fn set_gmatcher_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = set_gmatcher("test", &[]);
for _ in 0..3 {
assert_eq!(
set_gmatcher("test", &[]),
first,
"set_gmatcher must be deterministic"
);
}
}
#[test]
fn get_gmatcher_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = get_gmatcher("test", &[]);
for _ in 0..3 {
assert_eq!(
get_gmatcher("test", &[]),
first,
"get_gmatcher must be deterministic"
);
}
}
#[test]
fn bin_compctl_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let _: i32 = bin_compctl("compctl", &[], &ops, 0);
}
#[test]
fn bin_compcall_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let _: i32 = bin_compcall("compcall", &[], &ops, 0);
}
#[test]
fn ccmakehookfn_is_deterministic() {
let first = ccmakehookfn(());
for _ in 0..3 {
assert_eq!(
ccmakehookfn(()),
first,
"ccmakehookfn must be deterministic"
);
}
}
#[test]
fn cccleanuphookfn_is_deterministic() {
let first = cccleanuphookfn(());
for _ in 0..3 {
assert_eq!(
cccleanuphookfn(()),
first,
"cccleanuphookfn must be deterministic"
);
}
}
#[test]
fn print_gmatcher_full_index_range_safe() {
let _g = crate::test_util::global_state_lock();
for ac in [-1i32, 0, 1, 10, 100, i32::MAX, i32::MIN] {
print_gmatcher(ac);
}
}
#[test]
fn compctl_name_pat_empty_returns_false_empty() {
let (is_pat, name) = compctl_name_pat("");
assert!(!is_pat, "empty input not a pattern");
assert_eq!(name, "", "empty input → empty name");
}
#[test]
fn createcompctltable_idempotent_10_call_alt() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
createcompctltable();
}
}
#[test]
fn freecompctlp_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
freecompctlp("");
}
#[test]
fn freecompctlp_unknown_name_no_panic_alt() {
let _g = crate::test_util::global_state_lock();
freecompctlp("__never_real_compctl_xyz__");
}
#[test]
fn set_gmatcher_returns_i32_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: i32 = set_gmatcher("test", &[]);
}
#[test]
fn get_gmatcher_returns_i32_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: i32 = get_gmatcher("test", &[]);
}
#[test]
fn compctl_name_pat_returns_bool_string_tuple_type() {
let _: (bool, String) = compctl_name_pat("");
}
#[test]
fn compctl_name_pat_deterministic() {
for s in ["", "abc", "*", "(pattern)", "name"] {
let a = compctl_name_pat(s);
let b = compctl_name_pat(s);
assert_eq!(a, b, "compctl_name_pat({:?}) must be pure", s);
}
}
#[test]
fn delpatcomp_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
delpatcomp("");
}
#[test]
fn delpatcomp_unknown_no_panic() {
let _g = crate::test_util::global_state_lock();
delpatcomp("__never_real_pattern_xyz__");
}
#[test]
fn bin_compctl_exit_code_non_negative() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compctl("compctl", &[], &ops, 0);
assert!(r >= 0, "bin_compctl exit code must be ≥ 0, got {}", r);
}
#[test]
fn bin_compcall_exit_code_non_negative() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compcall("compcall", &[], &ops, 0);
assert!(r >= 0, "bin_compcall exit code must be ≥ 0, got {}", r);
}
#[test]
fn ccmakehookfn_returns_i32_type_alt() {
let _: i32 = ccmakehookfn(());
}
}