use once_cell::sync::Lazy;
use std::sync::atomic::{AtomicI32, AtomicI64, Ordering};
use std::sync::Mutex;
use crate::ported::glob::{remnulargs, tokenize};
use crate::ported::params::{
arrvargetfn, arrvarsetfn, createparam, deleteparamtable, getintvalue, getstrvalue,
intvargetfn, intvarsetfn, newparamtable, nullintsetfn, nullstrsetfn, paramtab, strvargetfn,
strvarsetfn,
};
use crate::ported::pattern::{patcompile, pattry, range_type};
use crate::ported::utils::{zerr, zwarnnam};
use crate::ported::zle::comp_h::{
Cmatcher, Cpattern, CAF_ALL, CAF_ARRAYS, CAF_KEYS, CAF_MATCH, CAF_MATSORT, CAF_NOSORT,
CAF_QUOTE, CAF_UNIQALL, CAF_UNIQCON, CLF_LINE, CLF_SUF, CMF_DISPLINE, CMF_FILE, CMF_HIDE,
CMF_INTER, CMF_ISPAR, CMF_LEFT, CMF_LINE, CMF_NOLIST, CMF_REMOVE, CMF_RIGHT, CPAT_ANY,
CPAT_CCLASS, CPAT_CHAR, CPAT_EQUIV, CPAT_NCLASS,
};
use crate::ported::zle::{
compcore, compresult, 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::zle::comp_h::{CPN_COMPSTATE, CP_KEYPARAMS, CP_REALPARAMS};
use crate::ported::zsh_h::{
gsu_array, gsu_hash, gsu_integer, gsu_scalar, value, HashTable, Param, PM_HASHELEM,
eprog, funcwrap, module, options, param, PAT_HEAPDUP, PM_ARRAY, PM_HASHED, PM_INTEGER,
PM_LOCAL, PM_READONLY, PM_REMOVABLE, PM_SCALAR, PM_SINGLE, PM_SPECIAL, PM_TYPE, PM_UNSET,
PP_RANGE, PP_UNKWN,
};
#[allow(unused_imports)]
#[allow(unused_imports)]
#[allow(unused_imports)]
pub fn freecmlist(l: Option<std::sync::Arc<crate::ported::zle::comp_h::Cmlist>>) {
let mut cur = l;
while let Some(node) = cur {
cur = node.next.clone();
}
}
pub fn freecmatcher(m: Option<Box<Cmatcher>>) {
let mut cur = m;
while let Some(node) = cur {
cur = node.next; }
}
pub fn freecpattern(p: Option<Box<Cpattern>>) {
let mut cur = p;
while let Some(node) = cur {
cur = node.next; }
}
pub fn cpcmatcher(m: Option<&Cmatcher>) -> Option<Box<Cmatcher>> {
let mut head: Option<Box<Cmatcher>> = None; let mut tail_ref: *mut Option<Box<Cmatcher>> = &mut head;
let mut cur = m;
while let Some(src) = cur {
let n = Box::new(Cmatcher {
refc: 1, next: None, flags: src.flags, line: cpcpattern(src.line.as_deref()), llen: src.llen, word: cpcpattern(src.word.as_deref()), wlen: src.wlen, left: cpcpattern(src.left.as_deref()), lalen: src.lalen, right: cpcpattern(src.right.as_deref()), ralen: src.ralen, });
unsafe {
*tail_ref = Some(n);
if let Some(ref mut new_node) = *tail_ref {
tail_ref = &mut new_node.next as *mut _;
}
}
cur = src.next.as_deref(); }
head }
pub fn cp_cpattern_element(o: &Cpattern) -> Box<Cpattern> {
let mut n = Cpattern::default(); n.next = None; n.tp = o.tp; match o.tp {
CPAT_CCLASS | CPAT_NCLASS | CPAT_EQUIV => {
n.str = o.str.clone(); }
CPAT_CHAR => {
n.chr = o.chr; }
_ => {} }
Box::new(n) }
pub fn cpcpattern(o: Option<&Cpattern>) -> Option<Box<Cpattern>> {
let mut head: Option<Box<Cpattern>> = None; let mut tail_ref: *mut Option<Box<Cpattern>> = &mut head;
let mut cur = o;
while let Some(src) = cur {
unsafe {
*tail_ref = Some(cp_cpattern_element(src)); if let Some(ref mut new_node) = *tail_ref {
tail_ref = &mut new_node.next as *mut _;
}
}
cur = src.next.as_deref(); }
head }
pub use crate::ported::utils::INCOMPFUNC;
pub static COMPCURRENT: AtomicI64 = AtomicI64::new(0);
pub static COMPLISTMAX: AtomicI64 = AtomicI64::new(0);
pub static COMPLISTLINES: AtomicI64 = AtomicI64::new(0);
pub static COMPIGNORED: AtomicI64 = AtomicI64::new(0);
macro_rules! comp_string_global {
($vis:vis $name:ident, $cname:literal, $cline:literal) => {
#[doc = concat!("Port of `char *", $cname, "` from complete.c:", stringify!($cline), ".")]
$vis static $name: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
};
}
comp_string_global!(pub COMPPREFIX, "compprefix", 47);
comp_string_global!(pub COMPSUFFIX, "compsuffix", 48);
comp_string_global!(pub COMPLASTPREFIX,"complastprefix",49);
comp_string_global!(pub COMPLASTSUFFIX,"complastsuffix",50);
comp_string_global!(pub COMPIPREFIX, "compiprefix", 58);
comp_string_global!(pub COMPISUFFIX, "compisuffix", 51);
comp_string_global!(pub COMPQIPREFIX, "compqiprefix", 52);
comp_string_global!(pub COMPQISUFFIX, "compqisuffix", 53);
comp_string_global!(pub COMPQUOTE, "compquote", 54);
comp_string_global!(pub COMPQUOTING, "compquoting", 55);
comp_string_global!(pub COMPQSTACK, "compqstack", 55);
comp_string_global!(pub COMPLIST, "complist", 65);
comp_string_global!(pub COMPCONTEXT, "compcontext", 59);
comp_string_global!(pub COMPPARAMETER, "compparameter", 60);
comp_string_global!(pub COMPREDIRECT, "compredirect", 61);
comp_string_global!(pub COMPPATINSERT, "comppatinsert", 69);
comp_string_global!(pub COMPLASTPROMPT, "complastprompt", 57);
comp_string_global!(pub COMPVARED, "compvared", 73);
comp_string_global!(pub COMPRESTORE, "comprestore", 64);
comp_string_global!(pub COMPINSERT, "compinsert", 66);
comp_string_global!(pub COMPEXACT, "compexact", 67);
comp_string_global!(pub COMPEXACTSTR, "compexactstr", 68);
comp_string_global!(pub COMPTOEND, "comptoend", 70);
comp_string_global!(pub COMPOLDLIST, "compoldlist", 71);
comp_string_global!(pub COMPOLDINS, "compoldins", 72);
pub static COMPREDIRS: std::sync::OnceLock<Mutex<Vec<String>>> = std::sync::OnceLock::new();
pub static COMPWORDS: std::sync::OnceLock<Mutex<Vec<String>>> = std::sync::OnceLock::new();
thread_local! {
pub static PCM_ERR: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn parse_cmatcher(name: &str, s: &str) -> Option<Box<Cmatcher>> {
PCM_ERR.with(|f| f.set(false));
if s.is_empty() {
return None;
}
let mut ret: Option<Box<Cmatcher>> = None;
let mut tail_ptr: *mut Option<Box<Cmatcher>> = &mut ret;
let mut rest = s;
while !rest.is_empty() {
rest = rest.trim_start_matches(|c: char| c == ' ' || c == '\t');
if rest.is_empty() {
break;
}
let c = rest.chars().next().unwrap();
let (fl, fl2) = match c {
'b' => (CMF_LEFT, CMF_INTER), 'l' => (CMF_LEFT, 0), 'e' => (CMF_RIGHT, CMF_INTER), 'r' => (CMF_RIGHT, 0), 'm' => (0, 0), 'B' => (CMF_LEFT | CMF_LINE, CMF_INTER), 'L' => (CMF_LEFT | CMF_LINE, 0), 'E' => (CMF_RIGHT | CMF_LINE, CMF_INTER), 'R' => (CMF_RIGHT | CMF_LINE, 0), 'M' => (CMF_LINE, 0), 'x' => (0, 0), _ => {
if !name.is_empty() {
zwarnnam(
name,
&format!("unknown match specification character `{}'", c),
);
}
{
PCM_ERR.with(|f| f.set(true));
return None;
} }
};
let mut chars = rest.chars();
chars.next();
if chars.clone().next() != Some(':') {
if !name.is_empty() {
zwarnnam(name, "missing `:'");
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
chars.next();
if c == 'x' {
if let Some(next) = chars.clone().next() {
if next != ' ' && next != '\t' {
if !name.is_empty() {
zwarnnam(name, "unexpected pattern following x: specification");
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
}
return ret; }
rest = chars.as_str();
if rest.is_empty() {
if !name.is_empty() {
zwarnnam(name, "missing patterns");
}
PCM_ERR.with(|f| f.set(true));
return None;
}
let mut left: Option<Box<Cpattern>> = None;
let mut lal: i32 = 0;
let mut both: bool = false;
if (fl & CMF_LEFT) != 0 && fl2 == 0 {
let (lt, r2, l, err) = parse_pattern(name, rest, '|'); if err {
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
left = lt;
lal = l;
rest = r2;
let mut peek = rest.chars();
peek.next();
if peek.clone().next() == Some('|') {
both = true;
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
if rest.len() <= 1 {
if !name.is_empty() {
zwarnnam(
name,
if both {
"missing right anchor"
} else {
"missing line pattern"
},
);
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
let line_end = if (fl & CMF_RIGHT) != 0 && fl2 == 0 {
'|'
} else {
'='
};
let (mut line_pat, r2, mut ll, err) = parse_pattern(name, rest, line_end);
if err {
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
rest = r2;
let (mut right, mut ral) = (None, 0i32);
if both {
right = line_pat;
ral = ll;
line_pat = None;
ll = 0;
}
if (fl & CMF_RIGHT) != 0 && fl2 == 0 {
if rest.len() <= 1 {
if !name.is_empty() {
zwarnnam(name, "missing right anchor");
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
let mut adv = rest.chars(); adv.next();
rest = adv.as_str();
} else {
if rest.is_empty() {
if !name.is_empty() {
zwarnnam(name, "missing word pattern");
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
let mut adv = rest.chars(); adv.next();
rest = adv.as_str();
}
if (fl & CMF_RIGHT) != 0 && fl2 == 0 {
if rest.chars().next() == Some('|') {
left = line_pat.take();
lal = ll;
ll = 0;
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
let (rt, r3, r_len, err) = parse_pattern(name, rest, '=');
if err {
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
right = rt;
ral = r_len;
rest = r3;
if rest.is_empty() {
if !name.is_empty() {
zwarnnam(name, "missing word pattern");
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
let (word_pat, wl): (Option<Box<Cpattern>>, i32);
if rest.chars().next() == Some('*') {
if (fl & (CMF_LEFT | CMF_RIGHT)) == 0 {
if !name.is_empty() {
zwarnnam(name, "need anchor for `*'");
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
if rest.chars().next() == Some('*') {
let mut adv2 = rest.chars();
adv2.next();
rest = adv2.as_str();
word_pat = None;
wl = -2;
} else {
word_pat = None;
wl = -1;
}
} else {
let (w, r4, w_len, err) = parse_pattern(name, rest, '\0');
if err {
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
if w.is_none() && line_pat.is_none() {
if !name.is_empty() {
zwarnnam(name, "need non-empty word or line pattern");
}
{
PCM_ERR.with(|f| f.set(true));
return None;
}
}
word_pat = w;
wl = w_len;
rest = r4;
}
let node = Box::new(Cmatcher {
refc: 0,
next: None,
flags: fl | fl2,
line: line_pat,
llen: ll,
word: word_pat,
wlen: wl,
left,
lalen: lal,
right,
ralen: ral,
});
unsafe {
*tail_ptr = Some(node);
if let Some(boxed) = (*tail_ptr).as_mut() {
tail_ptr = &mut boxed.next as *mut _;
}
}
}
ret }
pub fn parse_pattern<'a>(
name: &str,
s: &'a str,
end: char,
) -> (Option<Box<Cpattern>>, &'a str, i32, bool) {
let mut ret: Option<Box<Cpattern>> = None;
let mut tail_ptr: *mut Option<Box<Cpattern>> = &mut ret;
let mut rest = s;
let mut len = 0i32;
loop {
let next_ch = match rest.chars().next() {
Some(c) => c,
None => break,
};
if end != '\0' {
if next_ch == end {
break;
}
} else if next_ch == ' ' || next_ch == '\t' {
break;
}
let mut node = Box::new(Cpattern::default());
if next_ch == '[' || next_ch == '{' {
rest = parse_class(&mut node, rest);
if rest.is_empty() {
if !name.is_empty() {
zwarnnam(name, "unterminated character class");
}
return (None, rest, 0, true);
}
rest = &rest[1..];
} else if next_ch == '?' {
node.tp = CPAT_ANY;
let mut it = rest.chars();
it.next();
rest = it.as_str();
} else if matches!(next_ch, '*' | '(' | ')' | '=') {
if !name.is_empty() {
zwarnnam(name, &format!("invalid pattern character `{}'", next_ch));
}
return (None, rest, 0, true);
} else {
if next_ch == '\\' {
let mut it = rest.chars();
it.next();
if it.clone().next().is_some() {
rest = it.as_str();
}
}
let ch = rest.chars().next().unwrap();
node.tp = CPAT_CHAR;
node.chr = ch as u32;
let mut it = rest.chars();
it.next();
rest = it.as_str();
}
unsafe {
*tail_ptr = Some(node);
if let Some(boxed) = (*tail_ptr).as_mut() {
tail_ptr = &mut boxed.next as *mut _;
}
}
len += 1;
}
(ret, rest, len, false)
}
pub fn parse_class<'a>(
p: &mut Cpattern, iptr: &'a str,
) -> &'a str {
let bytes = iptr.as_bytes();
if bytes.is_empty() {
return iptr;
}
let opener = bytes[0];
let endchar: u8;
let mut i = 1;
if opener == b'[' {
endchar = b']';
if i < bytes.len()
&& (bytes[i] == b'!' || bytes[i] == b'^')
&& i + 1 < bytes.len()
&& bytes[i + 1] != b']'
{
p.tp = CPAT_NCLASS;
i += 1;
} else {
p.tp = CPAT_CCLASS;
}
} else {
endchar = 0x7d; p.tp = CPAT_EQUIV;
}
let start = i;
let mut optr_idx = i;
while optr_idx < bytes.len() && (optr_idx == start || bytes[optr_idx] != endchar) {
optr_idx += 1;
}
if optr_idx >= bytes.len() {
return &iptr[bytes.len()..];
}
let mut out: Vec<u8> = Vec::with_capacity(optr_idx - i + 1);
let mut firsttime = true;
while firsttime || (i < bytes.len() && bytes[i] != endchar) {
if bytes[i] == b'[' && i + 1 < bytes.len() && bytes[i + 1] == b':' {
if let Some(nptr) = bytes[i + 2..].iter().position(|&b| b == b':') {
let nptr = i + 2 + nptr;
if nptr + 1 < bytes.len() && bytes[nptr + 1] == b']' {
let name = std::str::from_utf8(&bytes[i + 2..nptr]).unwrap_or("");
let ch = range_type(name).unwrap_or(PP_UNKWN as usize);
i = nptr + 2;
if ch != PP_UNKWN as usize {
out.push(0x80u8.wrapping_add(ch as u8));
}
firsttime = false;
continue;
}
}
}
let ptr1 = i;
if bytes[i] == 0x83 {
i += 1;
}
if i >= bytes.len() {
break;
}
i += 1;
if i < bytes.len() && bytes[i] == b'-' && i + 1 < bytes.len() && bytes[i + 1] != endchar {
i += 1; out.push(0x80u8.wrapping_add(PP_RANGE as u8));
if bytes[ptr1] == 0x83 && ptr1 + 1 < bytes.len() {
out.push(0x83);
out.push(bytes[ptr1 + 1] ^ 32);
} else {
out.push(bytes[ptr1]);
}
if i < bytes.len() && bytes[i] == 0x83 && i + 1 < bytes.len() {
out.push(bytes[i]);
out.push(bytes[i + 1]);
i += 2;
} else if i < bytes.len() {
out.push(bytes[i]);
i += 1;
}
} else {
if bytes[ptr1] == 0x83 && ptr1 + 1 < bytes.len() {
out.push(0x83);
out.push(bytes[ptr1 + 1] ^ 32);
} else {
out.push(bytes[ptr1]);
}
}
firsttime = false;
}
p.str = Some(out);
&iptr[i.min(bytes.len())..]
}
pub fn parse_ordering(arg: &str, flags: &mut Option<i32>) -> i32 {
let mut fl = 0i32; for opt_token in arg.split(',') {
let mut found = false; for o in ORDEROPTS.iter().rev() {
if opt_token.len() >= o.abbrev && o.name.starts_with(opt_token)
{
fl |= o.oflag; found = true;
break;
}
}
if !found {
if let Some(ref mut f) = flags {
*f = CAF_MATSORT; }
return -1; }
}
if let Some(ref mut f) = flags {
*f |= fl; }
0 }
thread_local! {
pub static FORCED_BUILTIN_COMPADD: std::cell::Cell<bool> =
const { std::cell::Cell::new(false) };
}
pub struct ForcedBuiltinGuard(bool);
impl ForcedBuiltinGuard {
pub fn enter() -> Self {
Self(FORCED_BUILTIN_COMPADD.with(|f| f.replace(true)))
}
}
impl Drop for ForcedBuiltinGuard {
fn drop(&mut self) {
FORCED_BUILTIN_COMPADD.with(|f| f.set(self.0));
}
}
pub fn bin_compadd(
name: &str,
argv: &[String], _ops: &options,
_func: i32,
) -> i32 {
if INCOMPFUNC.load(Ordering::Relaxed) != 1 {
zwarnnam(name, "can only be called from completion function"); return 1; }
thread_local! {
static IN_COMPADD_OVERRIDE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
if !FORCED_BUILTIN_COMPADD.with(|f| f.get())
&& !IN_COMPADD_OVERRIDE.with(|g| g.get())
&& crate::ported::utils::getshfunc("compadd").is_some()
{
IN_COMPADD_OVERRIDE.with(|g| g.set(true));
let rc = crate::ported::exec::dispatch_function_call("compadd", argv).unwrap_or(1);
IN_COMPADD_OVERRIDE.with(|g| g.set(false));
return rc;
}
let shadow_argv: Vec<String>;
let argv: &[String] = {
let hook = *COMPADD_ARGV_SHADOW.lock().unwrap();
match hook {
Some(f) => match f(argv) {
None => return 0,
Some(v) => {
shadow_argv = v;
&shadow_argv
}
},
None => argv,
}
};
let saved_prefix_for_inject: Option<String> = {
let lock = COMPADD_PREFIX_INJECTOR.lock().unwrap();
if let Some(inj) = lock.as_ref() {
let cur_prefix = crate::ported::params::getsparam("PREFIX").unwrap_or_default();
let p_idx = argv.iter().position(|a| a == "-p");
let tilde_in_p = p_idx
.and_then(|i| argv.get(i + 1))
.map(|v| v.starts_with('~'))
.unwrap_or(false);
let new_prefix = if cur_prefix.starts_with('~') && !tilde_in_p {
format!("~{}{}", inj, &cur_prefix[1..])
} else {
format!("{}{}", inj, cur_prefix)
};
let _ = crate::ported::params::setsparam("PREFIX", &new_prefix);
Some(cur_prefix)
} else {
None
}
};
let ret = bin_compadd_body(name, argv, _ops, _func);
if let Some(saved) = saved_prefix_for_inject {
let _ = crate::ported::params::setsparam("PREFIX", &saved);
}
if COMPADD_TRACE_ACTIVE.load(Ordering::Relaxed) {
let mut buf = crate::ported::params::getaparam("_complete_help_funcs").unwrap_or_default();
buf.push(argv.join(" "));
crate::ported::params::setaparam("_complete_help_funcs", buf);
return 1;
}
ret
}
pub static COMPADD_PREFIX_INJECTOR: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub static COMPADD_TRACE_ACTIVE: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub fn set_compadd_prefix_injector(s: impl Into<String>) -> Option<String> {
COMPADD_PREFIX_INJECTOR.lock().unwrap().replace(s.into())
}
pub fn clear_compadd_prefix_injector() {
*COMPADD_PREFIX_INJECTOR.lock().unwrap() = None;
}
pub type CompaddArgvShadow = fn(&[String]) -> Option<Vec<String>>;
pub static COMPADD_ARGV_SHADOW: std::sync::Mutex<Option<CompaddArgvShadow>> =
std::sync::Mutex::new(None);
pub fn set_compadd_trace(active: bool) {
COMPADD_TRACE_ACTIVE.store(active, std::sync::atomic::Ordering::Relaxed);
}
pub fn bin_compadd_body(name: &str, argv: &[String], _ops: &options, _func: i32) -> i32 {
if INCOMPFUNC.load(Ordering::Relaxed) != 1 {
zwarnnam(name, "can only be called from completion function"); return 1; }
let mut dat = crate::ported::zle::comp_h::Cadata::default();
dat.aflags = CAF_MATCH;
dat.dummies = -1;
let mut mstr: Option<String> = None;
let mut oarg: Option<String> = None;
let c_atoi = |s: &str| -> i32 {
let b = s.as_bytes();
let mut i = 0;
while i < b.len() && (b[i] == b' ' || b[i] == b'\t') {
i += 1;
}
let neg = i < b.len() && b[i] == b'-';
if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
i += 1;
}
let mut v: i64 = 0;
while i < b.len() && b[i].is_ascii_digit() {
v = (v * 10 + (b[i] - b'0') as i64).min(i32::MAX as i64);
i += 1;
}
if neg {
-v as i32
} else {
v as i32
}
};
let mut idx = 0usize;
'outer: while idx < argv.len() {
let arg = argv[idx].clone();
if !arg.starts_with('-') {
break; }
if arg.len() < 2 {
idx += 1;
break;
}
if arg == "--" {
idx += 1;
break;
} idx += 1;
let bytes = arg.clone().into_bytes();
let mut p = 1usize; while p < bytes.len() {
let c = bytes[p] as char;
let mut take = |p: &mut usize, idx: &mut usize| -> Option<String> {
if *p + 1 < bytes.len() {
let v = String::from_utf8_lossy(&bytes[*p + 1..]).into_owned();
*p = bytes.len(); Some(v)
} else if *idx < argv.len() {
let v = argv[*idx].clone();
*idx += 1;
Some(v)
} else {
None }
};
macro_rules! opt_arg {
($slot:expr, $emsg:expr) => {{
match take(&mut p, &mut idx) {
Some(v) => {
if $slot.is_none() {
$slot = Some(v);
}
}
None => {
zwarnnam(name, &format!(concat!($emsg, " -{}"), c));
return 1;
}
}
}};
}
match c {
'a' => dat.aflags |= CAF_ARRAYS, 'k' => dat.aflags |= CAF_ARRAYS | CAF_KEYS, 'l' => dat.flags |= CMF_DISPLINE as i32, 'o' => {
let order: i32 = if oarg.is_some() { -1 } else { 1 }; let mut run_ordering = |oarg: &Option<String>, aflags: &mut i32| -> i32 {
let mut fl: Option<i32> = if order == 1 { Some(*aflags) } else { None };
let rc = parse_ordering(oarg.as_deref().unwrap_or(""), &mut fl);
if let Some(f) = fl {
*aflags = f;
}
rc
};
if p + 1 < bytes.len() {
if oarg.is_none() {
oarg = Some(String::from_utf8_lossy(&bytes[p + 1..]).into_owned());
}
if run_ordering(&oarg, &mut dat.aflags) == 0 {
p = bytes.len(); }
} else if idx < argv.len() {
let cand = argv[idx].clone();
idx += 1; if oarg.is_none() {
oarg = Some(cand); }
if run_ordering(&oarg, &mut dat.aflags) != 0 {
idx -= 1; }
}
}
'Q' => dat.aflags |= CAF_QUOTE, '1' => {
if (dat.aflags & CAF_UNIQCON) == 0 {
dat.aflags |= CAF_UNIQALL; }
}
'2' => {
if (dat.aflags & CAF_UNIQALL) == 0 {
dat.aflags |= CAF_UNIQCON; }
}
'C' => dat.aflags |= CAF_ALL, 'F' => opt_arg!(dat.ign, "string expected after"), 'f' => dat.flags |= CMF_FILE as i32, 'P' => opt_arg!(dat.pre, "string expected after"), 'S' => opt_arg!(dat.suf, "string expected after"), 'p' => opt_arg!(dat.ppre, "string expected after"), 's' => opt_arg!(dat.psuf, "string expected after"), 'W' => opt_arg!(dat.prpre, "string expected after"), 'i' => opt_arg!(dat.ipre, "string expected after"), 'I' => opt_arg!(dat.isuf, "string expected after"), 'J' => opt_arg!(dat.group, "group name expected after"),
'V' => {
if dat.group.is_none() {
dat.aflags |= CAF_NOSORT; }
opt_arg!(dat.group, "group name expected after"); }
'X' => opt_arg!(dat.exp, "string expected after"),
'x' => opt_arg!(dat.mesg, "string expected after"), 'd' => opt_arg!(dat.disp, "parameter name expected after"), 'O' => opt_arg!(dat.opar, "parameter name expected after"), 'A' => opt_arg!(dat.apar, "parameter name expected after"), 'D' => {
match take(&mut p, &mut idx) {
Some(s) => dat.dpar.push(s),
None => {
zwarnnam(name, &format!("parameter name expected after -{}", c));
return 1; }
}
}
'E' => {
let v = if p + 1 < bytes.len() {
let v = String::from_utf8_lossy(&bytes[p + 1..]).into_owned();
p = bytes.len();
Some(v)
} else if idx < argv.len() {
let v = argv[idx].clone();
idx += 1;
Some(v)
} else {
None
};
match v {
Some(s) => {
dat.dummies = c_atoi(&s); if dat.dummies < 0 {
zwarnnam(name, &format!("invalid number: {}", dat.dummies));
return 1;
}
}
None => {
zwarnnam(name, &format!("number expected after -{}", c));
return 1;
}
}
}
'M' => {
match take(&mut p, &mut idx) {
Some(s) => match mstr {
Some(ref mut acc) => {
acc.push(' ');
acc.push_str(&s);
}
None => mstr = Some(s),
},
None => {
zwarnnam(
name,
&format!("matching specification expected after -{}", c),
);
return 1;
}
}
}
'q' => dat.flags |= CMF_REMOVE as i32, 'r' => {
dat.flags |= CMF_REMOVE as i32; opt_arg!(dat.rems, "string expected after"); }
'R' => {
dat.flags |= CMF_REMOVE as i32; opt_arg!(dat.remf, "function name expected after"); }
'n' => dat.flags |= CMF_NOLIST as i32, 'U' => dat.aflags &= !CAF_MATCH, 'e' => dat.flags |= CMF_ISPAR as i32, 'Y' => dat.flags |= CMF_ISPAR as i32, '-' => {
break 'outer;
}
_ => {
zwarnnam(name, &format!("bad option: -{}", c));
return 1;
}
}
p += 1; }
}
let mut matcher: Option<Box<Cmatcher>> = None; if let Some(spec) = mstr {
if (dat.aflags & CAF_MATCH) != 0 {
matcher = parse_cmatcher(name, &spec); if PCM_ERR.with(|f| f.get()) {
return 1; }
}
}
let matches = &argv[idx..];
if matches.is_empty()
&& dat.group.is_none()
&& dat.mesg.is_none()
&& (dat.aflags & (CAF_NOSORT | CAF_UNIQALL | CAF_UNIQCON | CAF_ALL)) == 0
{
return 1;
}
dat.match_ = matcher; if crate::compsys::in_editor::try_capture_compadd(&dat, matches) {
return 0; }
compcore::addmatches(&mut dat, matches) }
pub fn ignore_prefix(l: i32) {
if l > 0 {
{
let mut prefix = lock_str(&COMPPREFIX).lock().unwrap();
let pl = prefix.len() as i32; let take = l.min(pl) as usize; let head: String = prefix[..take].to_string(); let tail: String = prefix[take..].to_string(); let mut iprefix = lock_str(&COMPIPREFIX).lock().unwrap();
iprefix.push_str(&head); *prefix = tail; }
}
}
pub fn ignore_suffix(l: i32) {
if l > 0 {
{
let mut suffix = lock_str(&COMPSUFFIX).lock().unwrap();
let sl = suffix.len() as i32; let mut split = sl - l; if split < 0 {
split = 0;
} let split = split as usize;
let head: String = suffix[..split].to_string(); let tail: String = suffix[split..].to_string(); let mut isuffix = lock_str(&COMPISUFFIX).lock().unwrap();
let mut new_isuffix = tail; new_isuffix.push_str(&isuffix);
*isuffix = new_isuffix;
*suffix = head; }
}
}
pub fn restrict_range(b: i32, e: i32) {
let mut words = lock_vec(&COMPWORDS).lock().unwrap();
let wl = words.len() as i32 - 1; if wl > 0 && b >= 0 && e >= 0 && (b > 0 || e < wl) {
let mut e = e;
if e > wl {
e = wl;
} let count = (e - b + 1) as usize; let new_words: Vec<String> = words
.iter() .skip(b as usize)
.take(count)
.cloned()
.collect();
*words = new_words; let cur = (COMPCURRENT.load(Ordering::Relaxed) as i32);
COMPCURRENT.store((cur - b) as i64, Ordering::Relaxed);
}
}
pub fn do_comp_vars(
test: i32,
mut na: i32,
sa: &str, mut nb: i32,
sb: &str,
mod_: i32,
) -> i32 {
match test {
CVT_RANGENUM => {
let words = COMPWORDS
.get()
.map(|m| m.lock().map(|g| g.clone()).unwrap_or_default())
.unwrap_or_default();
let l = words.len() as i32; if na < 0 {
na += l;
} else {
na -= 1;
} if nb < 0 {
nb += l;
} else {
nb -= 1;
} let cur = (COMPCURRENT.load(Ordering::Relaxed) as i32);
if cur - 1 < na || cur - 1 > nb {
return 0;
} if mod_ != 0 {
restrict_range(na, nb);
} 1 }
CVT_RANGEPAT => {
let words = COMPWORDS
.get()
.map(|m| m.lock().map(|g| g.clone()).unwrap_or_default())
.unwrap_or_default();
let l = words.len() as i32;
let mut t = 0i32; let mut b = 0i32;
let mut e = l - 1;
let mut i = (COMPCURRENT.load(Ordering::Relaxed) as i32) - 1; if i < 0 || i >= l {
return 0;
} let pp = patcompile(sa, PAT_HEAPDUP, None); i -= 1; while i >= 0 {
if let Some(ref prog) = pp {
if pattry(prog, &words[i as usize]) {
b = i + 1; t = 1; break;
}
}
i -= 1;
}
if t != 0 && !sb.is_empty() {
let mut tt = 0i32;
let pp2 = patcompile(sb, PAT_HEAPDUP, None); i += 1; while i < l {
if let Some(ref prog) = pp2 {
if pattry(prog, &words[i as usize]) {
e = i - 1; tt = 1;
break;
}
}
i += 1;
}
if tt != 0 && i < (COMPCURRENT.load(Ordering::Relaxed) as i32) {
t = 0; }
}
if e < b {
t = 0;
} if t != 0 && mod_ != 0 {
restrict_range(b, e);
} t }
CVT_PRENUM | CVT_SUFNUM => {
if na < 0 {
return 0;
} if na > 0 && mod_ != 0 {
let target_str = if test == CVT_PRENUM {
lock_str(&COMPPREFIX)
.lock()
.map(|s| s.clone())
.unwrap_or_default()
} else {
lock_str(&COMPSUFFIX)
.lock()
.map(|s| s.clone())
.unwrap_or_default()
};
let cnt = target_str.chars().count() as i32;
if cnt < na {
return 0;
}
let na_bytes = if test == CVT_PRENUM {
target_str
.char_indices()
.nth(na as usize)
.map(|(o, _)| o)
.unwrap_or(target_str.len()) as i32
} else {
let off = target_str
.char_indices()
.nth((cnt - na) as usize)
.map(|(o, _)| o)
.unwrap_or(target_str.len());
(target_str.len() - off) as i32
};
if test == CVT_PRENUM {
ignore_prefix(na_bytes); } else {
ignore_suffix(na_bytes); }
}
1 }
CVT_PREPAT | CVT_SUFPAT => {
if na == 0 {
return 0;
} let pp = match patcompile(sa, PAT_HEAPDUP, None) {
Some(p) => p,
None => return 0,
};
if test == CVT_PREPAT {
let prefix = lock_str(&COMPPREFIX)
.lock()
.map(|s| s.clone())
.unwrap_or_default();
let l = prefix.chars().count() as i32;
if l == 0 {
let hit = (na == 1 || na == -1) && pattry(&pp, &prefix);
return if hit { 1 } else { 0 };
}
let chars: Vec<char> = prefix.chars().collect();
let (mut p, add): (i32, i32) = if na < 0 {
(l, -1) } else {
(1, 1) };
if na < 0 {
na = -na;
}
loop {
let p_uz = p.max(0).min(l) as usize;
let head: String = chars[..p_uz].iter().collect(); let hit = pattry(&pp, &head); if hit {
na -= 1;
if na == 0 {
break;
} }
p += add; if add > 0 && p > l {
return 0;
} if add < 0 && p < 0 {
return 0;
} }
if mod_ != 0 {
let p_bytes: i32 = chars[..p.max(0).min(l) as usize]
.iter()
.map(|ch| ch.len_utf8() as i32)
.sum();
ignore_prefix(p_bytes);
}
} else {
let suffix = lock_str(&COMPSUFFIX)
.lock()
.map(|s| s.clone())
.unwrap_or_default();
let l = suffix.chars().count() as i32;
if l == 0 {
let hit = (na == 1 || na == -1) && pattry(&pp, &suffix);
return if hit { 1 } else { 0 };
}
let chars: Vec<char> = suffix.chars().collect();
let (mut p, add): (i32, i32) = if na < 0 {
(0, 1)
} else {
(l - 1, -1)
};
if na < 0 {
na = -na;
}
loop {
let p_uz = p.max(0).min(l) as usize;
let tail: String = chars[p_uz..].iter().collect();
let hit = pattry(&pp, &tail); if hit {
na -= 1;
if na == 0 {
break;
}
}
p += add; if add > 0 && p > l {
return 0;
}
if add < 0 && p < 0 {
return 0;
}
}
if mod_ != 0 {
let n_bytes: i32 = chars[p.max(0).min(l) as usize..]
.iter()
.map(|ch| ch.len_utf8() as i32)
.sum();
ignore_suffix(n_bytes);
}
}
1 }
_ => 0, }
}
fn atoi(s: &str) -> i32 {
let b = s.as_bytes();
let mut i = 0usize;
while i < b.len() && (b[i] as char).is_whitespace() {
i += 1;
}
let neg = match b.get(i) {
Some(b'-') => {
i += 1;
true
}
Some(b'+') => {
i += 1;
false
}
_ => false,
};
let mut n: i64 = 0;
while i < b.len() && b[i].is_ascii_digit() {
n = n.saturating_mul(10).saturating_add((b[i] - b'0') as i64);
i += 1;
}
let n = if neg { -n } else { n };
n.clamp(i32::MIN as i64, i32::MAX as i64) as i32
}
pub fn bin_compset(
name: &str,
argv: &[String], _ops: &options,
_func: i32,
) -> i32 {
let mut test = 0i32; let mut na = 0i32;
let mut nb;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 {
zwarnnam(name, "can only be called from completion function"); return 1; }
if argv.is_empty() || !argv[0].starts_with('-') {
zwarnnam(name, "missing option"); return 1; }
let arg0 = &argv[0];
let opt = arg0.as_bytes().get(1).copied().unwrap_or(0); match opt {
b'n' => test = CVT_RANGENUM, b'N' => test = CVT_RANGEPAT, b'p' => test = CVT_PRENUM, b'P' => test = CVT_PREPAT, b's' => test = CVT_SUFNUM, b'S' => test = CVT_SUFPAT, b'q' => return compcore::set_comp_sep() as i32, _ => {
zwarnnam(name, &format!("bad option -{}", opt as char)); return 1; }
}
let (sa, sb, na_consumed): (Option<String>, Option<String>, usize);
if arg0.len() > 2 {
sa = Some(arg0[2..].to_string()); sb = argv.get(1).cloned(); na_consumed = 2; } else {
let Some(s1) = argv.get(1).cloned() else {
zwarnnam(name, &format!("missing string for option -{}", opt as char)); return 1; };
sa = Some(s1);
sb = argv.get(2).cloned();
na_consumed = 3; }
let too_many = if test == CVT_PRENUM || test == CVT_SUFNUM {
sb.is_some()
} else {
sb.is_some() && argv.len() > na_consumed
};
if too_many {
zwarnnam(name, "too many arguments"); return 1; }
let mut sa = sa;
let mut sb = sb;
match test {
CVT_RANGENUM => {
na = atoi(sa.as_deref().unwrap_or("")); nb = sb.as_deref().map(atoi).unwrap_or(-1); }
CVT_RANGEPAT => {
if let Some(s) = sa.as_mut() {
tokenize(s); remnulargs(s); }
if let Some(s) = sb.as_mut() {
tokenize(s); remnulargs(s); }
nb = 0;
}
CVT_PRENUM | CVT_SUFNUM => {
na = atoi(sa.as_deref().unwrap_or("")); nb = 0;
}
CVT_PREPAT | CVT_SUFPAT => {
if sb.is_some() {
na = atoi(sa.as_deref().unwrap_or("")); sa = sb.clone(); } else {
na = -1; }
if let Some(s) = sa.as_mut() {
tokenize(s); remnulargs(s); *s = crate::pattern_data_escape::escape_tokenized_data_backslashes(s);
}
nb = 0;
}
_ => {
nb = 0;
}
}
let _ = (na, nb);
let pat = sa.as_deref().unwrap_or("");
let sb_ref = sb.as_deref();
if do_comp_vars(test, na, pat, nb, sb_ref.unwrap_or(""), 1) != 0 {
0
} else {
1
}
}
#[allow(non_camel_case_types)]
pub struct compparam {
pub name: &'static str, pub r#type: i32, pub var: compvar, pub gsu: compgsu, }
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub enum compvar {
NULL,
STR(&'static std::sync::OnceLock<Mutex<String>>),
ARR(&'static std::sync::OnceLock<Mutex<Vec<String>>>),
INT(&'static AtomicI64),
}
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub enum compgsu {
NULL,
S(&'static gsu_scalar),
I(&'static gsu_integer),
}
#[allow(non_upper_case_globals)]
static compvarscalar_gsu: gsu_scalar = gsu_scalar {
getfn: strvargetfn,
setfn: |pm, x| strvarsetfn(pm, Some(x)),
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static complist_gsu: gsu_scalar = gsu_scalar {
getfn: get_complist,
setfn: set_complist,
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static unambig_gsu: gsu_scalar = gsu_scalar {
getfn: get_unambig,
setfn: nullstrsetfn,
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static unambig_pos_gsu: gsu_scalar = gsu_scalar {
getfn: get_unambig_pos,
setfn: nullstrsetfn,
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static insert_pos_gsu: gsu_scalar = gsu_scalar {
getfn: get_insert_pos,
setfn: nullstrsetfn,
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static compqstack_gsu: gsu_scalar = gsu_scalar {
getfn: get_compqstack,
setfn: nullstrsetfn,
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static compvarinteger_gsu: gsu_integer = gsu_integer {
getfn: intvargetfn,
setfn: intvarsetfn,
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static nmatches_gsu: gsu_integer = gsu_integer {
getfn: get_nmatches,
setfn: nullintsetfn, unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static unambig_curs_gsu: gsu_integer = gsu_integer {
getfn: get_unambig_curs,
setfn: nullintsetfn, unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static listlines_gsu: gsu_integer = gsu_integer {
getfn: get_listlines,
setfn: nullintsetfn, unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static compvararray_gsu: gsu_array = gsu_array {
getfn: arrvargetfn,
setfn: |pm, x| arrvarsetfn(pm, Some(x)),
unsetfn: compunsetfn,
}; #[allow(non_upper_case_globals)]
static compstate_gsu: gsu_hash = gsu_hash {
getfn: get_compstate,
setfn: set_compstate,
unsetfn: compunsetfn,
};
#[allow(non_upper_case_globals)]
pub static comprpms: Mutex<Option<Vec<Option<String>>>> = Mutex::new(None);
#[allow(non_upper_case_globals)]
pub static compkpms: Mutex<Option<Vec<Option<String>>>> = Mutex::new(None);
pub fn addcompparams(cp: &[compparam], pp: &mut [Option<String>], mut tab: Option<&mut HashTable>) {
for (i, c) in cp.iter().enumerate() {
let flags = c.r#type | (PM_SPECIAL | PM_REMOVABLE | PM_LOCAL) as i32;
let level = crate::ported::params::locallevel.load(Ordering::Relaxed) + 1;
let mut node: Param = match tab.as_deref_mut() {
None => {
let _ = createparam(c.name, flags); match paramtab().read().ok().and_then(|t| t.get(c.name).cloned()) {
Some(pm) => pm, None => continue,
}
}
Some(_) => {
let mut pm: Param = Box::new(param::default());
pm.node.nam = c.name.to_string();
pm.node.flags = (flags & !(PM_LOCAL as i32)) | PM_HASHELEM as i32;
pm
}
};
pp[i] = Some(c.name.to_string()); node.level = level; match c.var {
compvar::NULL => {
match c.gsu {
compgsu::S(g) => node.gsu_s = Some(Box::new(g.clone())),
compgsu::I(g) => node.gsu_i = Some(Box::new(g.clone())),
compgsu::NULL => {}
}
}
var => {
node.u_data = match var {
compvar::STR(r) => r as *const _ as usize,
compvar::ARR(r) => r as *const _ as usize,
compvar::INT(r) => r as *const _ as usize,
compvar::NULL => 0,
};
match PM_TYPE(c.r#type as u32) {
PM_SCALAR => node.gsu_s = Some(Box::new(compvarscalar_gsu.clone())),
PM_INTEGER => {
node.gsu_i = Some(Box::new(compvarinteger_gsu.clone()));
node.base = 10;
}
PM_ARRAY => node.gsu_a = Some(Box::new(compvararray_gsu.clone())),
_ => {}
}
}
}
match tab.as_deref_mut() {
None => {
if let Ok(mut t) = paramtab().write() {
if let Some(live) = t.get_mut(c.name) {
live.level = node.level;
live.u_data = node.u_data;
live.base = node.base;
live.gsu_s = node.gsu_s;
live.gsu_i = node.gsu_i;
live.gsu_a = node.gsu_a;
}
}
}
Some(t) => {
t.parnodes.insert(c.name.to_string(), node);
}
}
}
}
pub fn makecompparams() {
let mut rp = comprpms.lock().unwrap();
let rp = rp.get_or_insert_with(|| vec![None; CP_REALPARAMS as usize]);
addcompparams(COMPRPARAMS, rp, None);
let _ = createparam(
COMPSTATENAME,
(PM_SPECIAL | PM_REMOVABLE | PM_SINGLE | PM_LOCAL | PM_HASHED) as i32,
);
rp[CPN_COMPSTATE as usize] = Some(COMPSTATENAME.to_string());
let mut tht = newparamtable(31, COMPSTATENAME).expect("newparamtable"); let mut kp = compkpms.lock().unwrap();
let kp = kp.get_or_insert_with(|| vec![None; CP_KEYPARAMS as usize]);
addcompparams(COMPKPARAMS, kp, Some(&mut tht)); if let Ok(mut t) = paramtab().write() {
if let Some(cpm) = t.get_mut(COMPSTATENAME) {
cpm.level = crate::ported::params::locallevel.load(Ordering::Relaxed) + 1; cpm.gsu_h = Some(Box::new(compstate_gsu.clone())); cpm.u_hash = Some(tht); }
}
}
pub fn get_compstate(pm: ¶m) -> Option<&HashTable> {
pm.u_hash.as_ref() }
pub fn set_compstate(pm: &mut param, ht: HashTable) {
for (nam, hn) in ht.parnodes.iter() {
for cp in COMPKPARAMS {
if nam != cp.name {
continue;
}
let mut v = value {
pm: Some(hn.clone()),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
match cp.var {
compvar::INT(r) if cp.r#type == PM_INTEGER as i32 => {
r.store(getintvalue(Some(&mut v)), Ordering::Relaxed)
}
compvar::STR(r) => {
let str = getstrvalue(Some(&mut v));
if let Ok(mut g) = r.get_or_init(|| Mutex::new(String::new())).lock() {
*g = str;
}
}
_ => {
let str = getstrvalue(Some(&mut v));
if let Some(mut elem) = pm
.u_hash
.as_ref()
.and_then(|h| h.parnodes.get(cp.name).cloned())
{
if PM_TYPE(elem.node.flags as u32) == PM_INTEGER {
if let Some(sf) = elem.gsu_i.as_ref().map(|g| g.setfn) {
sf(&mut elem, crate::ported::math::mathevali(&str).unwrap_or(0));
}
} else if let Some(sf) = elem.gsu_s.as_ref().map(|g| g.setfn) {
sf(&mut elem, str);
}
}
}
}
if let Some(elem) = pm.u_hash.as_mut().and_then(|h| h.parnodes.get_mut(cp.name)) {
elem.node.flags &= !(PM_UNSET as i32);
}
break; }
}
}
#[allow(unused_variables)]
pub fn get_nmatches(_pm: ¶m) -> i64 {
if compcore::permmatches(0) != 0 {
return 0;
}
compcore::nmatches.load(Ordering::Relaxed) as i64 }
#[allow(unused_variables)]
pub fn get_listlines(_pm: ¶m) -> i64 {
compresult::list_lines() }
#[allow(unused_variables)]
pub fn set_complist(_pm: &mut param, v: String) {
compresult::comp_list(Some(&v)); }
#[allow(unused_variables)]
pub fn get_complist(_pm: ¶m) -> String {
lock_str(&COMPLIST)
.lock()
.map(|s| s.clone())
.unwrap_or_default() }
pub const COMPSTATENAME: &str = "compstate"; pub const CVT_RANGENUM: i32 = 0; pub const CVT_RANGEPAT: i32 = 1; pub const CVT_PRENUM: i32 = 2; pub const CVT_PREPAT: i32 = 3; pub const CVT_SUFNUM: i32 = 4; pub const CVT_SUFPAT: i32 = 5;
#[allow(non_snake_case)]
struct OrderOpt {
name: &'static str,
abbrev: usize,
oflag: i32,
}
static ORDEROPTS: &[OrderOpt] = &[
OrderOpt {
name: "nosort",
abbrev: 2,
oflag: CAF_NOSORT,
}, OrderOpt {
name: "match",
abbrev: 3,
oflag: CAF_MATSORT,
}, OrderOpt {
name: "numeric",
abbrev: 3,
oflag: crate::ported::zle::comp_h::CAF_NUMSORT,
}, OrderOpt {
name: "reverse",
abbrev: 3,
oflag: crate::ported::zle::comp_h::CAF_REVSORT,
}, ];
#[allow(unused_variables)]
pub fn get_unambig(_pm: ¶m) -> String {
if let Some(s) = compcore::ainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().and_then(|a| a.line.clone()))
.map(|l| compresult::cline_str(Some(l), 0, None, None).unwrap_or_default())
.filter(|s| !s.is_empty())
{
return s;
}
let strs: Vec<String> = compcore::amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| {
g.iter()
.flat_map(|gr| gr.matches.iter())
.filter(|m| (m.flags & CMF_HIDE) == 0)
.filter_map(|m| m.str.clone())
.collect()
})
.unwrap_or_default();
compresult::unambig_data(&strs)
}
#[allow(unused_variables)]
pub fn get_unambig_curs(_pm: ¶m) -> i64 {
let line = {
let a = compcore::ainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().map(|a| (a.count, a.line.clone())));
match a {
Some((count, l)) if count != 0 => l,
_ => compcore::fainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().and_then(|f| f.line.clone())),
}
};
if line.is_some() {
let mut ccache = 0i32;
let s = compresult::cline_str(line, 0, Some(&mut ccache), None).unwrap_or_default();
if !s.is_empty() {
return ccache as i64 + 1; }
}
let prefix = get_unambig(¶m::default());
prefix.chars().count() as i64 + 1
}
pub static COMPRPARAMS: &[compparam] = &[
compparam { name: "words", r#type: PM_ARRAY as i32, var: compvar::ARR(&COMPWORDS), gsu: compgsu::NULL }, compparam { name: "redirections", r#type: PM_ARRAY as i32, var: compvar::ARR(&COMPREDIRS), gsu: compgsu::NULL }, compparam { name: "CURRENT", r#type: PM_INTEGER as i32, var: compvar::INT(&COMPCURRENT), gsu: compgsu::NULL }, compparam { name: "PREFIX", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPPREFIX), gsu: compgsu::NULL }, compparam { name: "SUFFIX", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPSUFFIX), gsu: compgsu::NULL }, compparam { name: "IPREFIX", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPIPREFIX), gsu: compgsu::NULL }, compparam { name: "ISUFFIX", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPISUFFIX), gsu: compgsu::NULL }, compparam { name: "QIPREFIX", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::STR(&COMPQIPREFIX), gsu: compgsu::NULL }, compparam { name: "QISUFFIX", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::STR(&COMPQISUFFIX), gsu: compgsu::NULL }, ];
pub static COMPKPARAMS: &[compparam] = &[
compparam { name: "nmatches", r#type: (PM_INTEGER | PM_READONLY) as i32, var: compvar::NULL, gsu: compgsu::I(&nmatches_gsu) }, compparam { name: "context", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPCONTEXT), gsu: compgsu::NULL }, compparam { name: "parameter", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPPARAMETER), gsu: compgsu::NULL }, compparam { name: "redirect", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPREDIRECT), gsu: compgsu::NULL }, compparam { name: "quote", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::STR(&COMPQUOTE), gsu: compgsu::NULL }, compparam { name: "quoting", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::STR(&COMPQUOTING), gsu: compgsu::NULL }, compparam { name: "restore", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPRESTORE), gsu: compgsu::NULL }, compparam { name: "list", r#type: PM_SCALAR as i32, var: compvar::NULL, gsu: compgsu::S(&complist_gsu) }, compparam { name: "insert", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPINSERT), gsu: compgsu::NULL }, compparam { name: "exact", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPEXACT), gsu: compgsu::NULL }, compparam { name: "exact_string", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPEXACTSTR), gsu: compgsu::NULL }, compparam { name: "pattern_match", r#type: PM_SCALAR as i32, var: compvar::STR(&compcore::comppatmatch), gsu: compgsu::NULL }, compparam { name: "pattern_insert", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPPATINSERT), gsu: compgsu::NULL }, compparam { name: "unambiguous", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::NULL, gsu: compgsu::S(&unambig_gsu) }, compparam { name: "unambiguous_cursor", r#type: (PM_INTEGER | PM_READONLY) as i32, var: compvar::NULL, gsu: compgsu::I(&unambig_curs_gsu) }, compparam { name: "unambiguous_positions", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::NULL, gsu: compgsu::S(&unambig_pos_gsu) }, compparam { name: "insert_positions", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::NULL, gsu: compgsu::S(&insert_pos_gsu) }, compparam { name: "list_max", r#type: PM_INTEGER as i32, var: compvar::INT(&COMPLISTMAX), gsu: compgsu::NULL }, compparam { name: "last_prompt", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPLASTPROMPT), gsu: compgsu::NULL }, compparam { name: "to_end", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPTOEND), gsu: compgsu::NULL }, compparam { name: "old_list", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPOLDLIST), gsu: compgsu::NULL }, compparam { name: "old_insert", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPOLDINS), gsu: compgsu::NULL }, compparam { name: "vared", r#type: PM_SCALAR as i32, var: compvar::STR(&COMPVARED), gsu: compgsu::NULL }, compparam { name: "list_lines", r#type: (PM_INTEGER | PM_READONLY) as i32, var: compvar::NULL, gsu: compgsu::I(&listlines_gsu) }, compparam { name: "all_quotes", r#type: (PM_SCALAR | PM_READONLY) as i32, var: compvar::NULL, gsu: compgsu::S(&compqstack_gsu) }, compparam { name: "ignored", r#type: (PM_INTEGER | PM_READONLY) as i32, var: compvar::INT(&COMPIGNORED), gsu: compgsu::NULL }, ];
#[allow(unused_variables)]
pub fn get_unambig_pos(_pm: ¶m) -> String {
let line = {
let a = compcore::ainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().map(|a| (a.count, a.line.clone())));
match a {
Some((count, l)) if count != 0 => l,
_ => compcore::fainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().and_then(|f| f.line.clone())),
}
};
if line.is_some() {
let mut list: Vec<i32> = Vec::new();
let s = compresult::cline_str(line, 0, None, Some(&mut list)).unwrap_or_default();
if !s.is_empty() {
return if list.is_empty() {
String::new()
} else {
compresult::build_pos_string(&list)
};
}
}
let strs: Vec<String> = compcore::amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| {
g.iter()
.flat_map(|gr| gr.matches.iter())
.filter(|m| (m.flags & CMF_HIDE) == 0)
.filter_map(|m| m.str.clone())
.collect()
})
.unwrap_or_default();
if strs.len() < 2 {
return String::new();
}
let lcp_len = compresult::unambig_data(&strs).chars().count();
if strs.iter().any(|s| s.chars().count() > lcp_len) {
format!("{}", lcp_len)
} else {
String::new()
}
}
#[allow(unused_variables)]
pub fn get_insert_pos(_pm: ¶m) -> String {
let line = {
let a = compcore::ainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().map(|a| (a.count, a.line.clone())));
match a {
Some((count, l)) if count != 0 => l,
_ => compcore::fainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().and_then(|f| f.line.clone())),
}
};
if line.is_some() {
let mut list: Vec<i32> = Vec::new();
let s = compresult::cline_str(line, 2, None, Some(&mut list));
if s.is_some() {
return if list.is_empty() {
String::new()
} else {
compresult::build_pos_string(&list)
};
}
}
let wb = compcore::WB.load(Ordering::Relaxed).max(0) as i64;
get_unambig_pos(¶m::default())
.split(':') .filter(|s| !s.is_empty())
.filter_map(|s| s.parse::<i64>().ok())
.map(|p| (p + wb).to_string())
.collect::<Vec<_>>()
.join(":")
}
#[allow(unused_variables)]
pub fn get_compqstack(_pm: ¶m) -> String {
let stack = lock_str(&COMPQSTACK)
.lock()
.map(|s| s.clone())
.unwrap_or_default();
if stack.is_empty() {
return String::new();
}
let mut out = String::with_capacity(stack.len());
for cqp in stack.chars() {
let cqp_byte = cqp as i32;
let s = compcore::comp_quoting_string(cqp_byte);
if let Some(first) = s.chars().next() {
out.push(first);
}
}
out
}
pub fn compunsetfn(pm: &mut param, exp: i32) {
if exp != 0 {
match PM_TYPE(pm.node.flags as u32) {
PM_SCALAR if pm.u_data != 0 => {
let q = unsafe { &*(pm.u_data as *const std::sync::OnceLock<Mutex<String>>) };
if let Ok(mut g) = q.get_or_init(|| Mutex::new(String::new())).lock() {
g.clear(); }
}
PM_ARRAY if pm.u_data != 0 => {
let q =
unsafe { &*(pm.u_data as *const std::sync::OnceLock<Mutex<Vec<String>>>) };
if let Ok(mut g) = q.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear(); }
}
PM_HASHED if pm.u_hash.is_some() => {
deleteparamtable(pm.u_hash.take()); }
_ => {}
}
} else if PM_TYPE(pm.node.flags as u32) == PM_HASHED {
pm.u_hash = None; if let Ok(mut g) = compkpms.lock() {
if let Some(kp) = g.as_mut() {
for p in kp.iter_mut() {
*p = None; }
}
}
}
if exp == 0 {
if let Ok(mut g) = comprpms.lock() {
if let Some(rp) = g.as_mut() {
for p in rp.iter_mut() {
if p.as_deref() == Some(pm.node.nam.as_str()) {
*p = None;
break;
}
}
}
}
}
}
pub fn comp_setunset(mut rset: i32, mut runset: i32, mut kset: i32, mut kunset: i32) {
if rset >= 0 || runset >= 0 {
let rp = comprpms.lock().unwrap().clone();
if let Some(rp) = rp {
let mut i = 0usize;
while (rset != 0 || runset != 0) && i < rp.len() {
if let Some(nam) = rp[i].as_deref() {
if let Ok(mut t) = paramtab().write() {
if let Some(pm) = t.get_mut(nam) {
if rset & 1 != 0 {
pm.node.flags &= !(PM_UNSET as i32); }
if runset & 1 != 0 {
pm.node.flags |= PM_UNSET as i32; }
}
}
}
rset >>= 1;
runset >>= 1;
i += 1;
}
}
}
if kset >= 0 || kunset >= 0 {
let kp = compkpms.lock().unwrap().clone();
if let Some(kp) = kp {
let mut i = 0usize;
while (kset != 0 || kunset != 0) && i < kp.len() {
if let Some(nam) = kp[i].as_deref() {
if let Ok(mut t) = paramtab().write() {
if let Some(elem) = t
.get_mut(COMPSTATENAME)
.and_then(|p| p.u_hash.as_mut())
.and_then(|h| h.parnodes.get_mut(nam))
{
if kset & 1 != 0 {
elem.node.flags &= !(PM_UNSET as i32); }
if kunset & 1 != 0 {
elem.node.flags |= PM_UNSET as i32; }
}
}
}
kset >>= 1;
kunset >>= 1;
i += 1;
}
}
}
}
pub fn comp_wrapper(
_prog: *const eprog, _w: *const funcwrap,
name: &str,
runshfunc: impl FnOnce(),
) -> i32 {
use crate::ported::zle::comp_h::{
CP_ALLKEYS, CP_ALLREALS, CP_COMPSTATE, CP_CURRENT, CP_IPREFIX, CP_ISUFFIX, CP_PREFIX,
CP_QIPREFIX, CP_QISUFFIX, CP_REDIRS, CP_RESTORE, CP_SUFFIX, CP_WORDS,
};
if INCOMPFUNC.load(Ordering::Relaxed) != 1 {
return 1; }
let _ = name; let snap = |g: &'static std::sync::OnceLock<Mutex<String>>| -> String {
g.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|s| s.clone())
.unwrap_or_default()
};
let restore = |g: &'static std::sync::OnceLock<Mutex<String>>, v: String| {
if let Ok(mut s) = g.get_or_init(|| Mutex::new(String::new())).lock() {
*s = v;
}
};
let mut runset: u32 = 0; let mut kunset: u32 = 0; {
let mut m = CP_WORDS
| CP_REDIRS
| CP_CURRENT
| CP_PREFIX
| CP_SUFFIX
| CP_IPREFIX
| CP_ISUFFIX
| CP_QIPREFIX
| CP_QISUFFIX; let mut sm: u32 = 1;
let rp = comprpms.lock().unwrap().clone().unwrap_or_default();
if let Ok(tab) = paramtab().read() {
for pp in rp.iter() {
if m == 0 {
break;
}
if (m & 1) != 0 {
let is_unset = pp
.as_deref()
.and_then(|nam| tab.get(nam))
.map(|p| (p.node.flags as u32 & PM_UNSET) != 0)
.unwrap_or(false);
if is_unset {
runset |= sm; }
}
m >>= 1;
sm <<= 1;
}
}
let kp = compkpms.lock().unwrap().clone().unwrap_or_default();
let restore_unset = kp
.get(crate::ported::zle::comp_h::CPN_RESTORE as usize)
.and_then(|n| n.as_deref())
.and_then(|nam| {
paramtab()
.read()
.ok()
.and_then(|t| {
t.get(COMPSTATENAME)
.and_then(|p| p.u_hash.as_ref())
.and_then(|h| h.parnodes.get(nam))
.map(|e| e.node.flags)
})
})
.map(|f| (f as u32 & PM_UNSET) != 0)
.unwrap_or(false);
if restore_unset {
kunset = CP_RESTORE;
}
}
let orest = snap(&COMPRESTORE);
restore(&COMPRESTORE, "auto".to_string());
let ocur = COMPCURRENT.load(Ordering::Relaxed); let opre = snap(&COMPPREFIX); let osuf = snap(&COMPSUFFIX); let oipre = snap(&COMPIPREFIX); let oisuf = snap(&COMPISUFFIX); let oqipre = snap(&COMPQIPREFIX); let oqisuf = snap(&COMPQISUFFIX); let oq = snap(&COMPQUOTE); let oqi = snap(&COMPQUOTING); let oqs = snap(&COMPQSTACK); let oaq = snap(&AUTOQ); let owords = lock_vec(&COMPWORDS).lock().map(|v| v.clone()).unwrap_or_default(); let oredirs = lock_vec(&COMPREDIRS).lock().map(|v| v.clone()).unwrap_or_default();
runshfunc();
if snap(&COMPRESTORE) == "auto" {
COMPCURRENT.store(ocur, Ordering::Relaxed); restore(&COMPPREFIX, opre); restore(&COMPSUFFIX, osuf); restore(&COMPIPREFIX, oipre); restore(&COMPISUFFIX, oisuf); restore(&COMPQIPREFIX, oqipre); restore(&COMPQISUFFIX, oqisuf); restore(&COMPQUOTE, oq); restore(&COMPQUOTING, oqi); restore(&COMPQSTACK, oqs); restore(&AUTOQ, oaq); if let Ok(mut g) = lock_vec(&COMPWORDS).lock() {
*g = owords; }
if let Ok(mut g) = lock_vec(&COMPREDIRS).lock() {
*g = oredirs; }
let realmask = CP_WORDS
| CP_REDIRS
| CP_CURRENT
| CP_PREFIX
| CP_SUFFIX
| CP_IPREFIX
| CP_ISUFFIX
| CP_QIPREFIX
| CP_QISUFFIX; comp_setunset(
(CP_COMPSTATE | (!runset & realmask)) as i32, (runset & CP_ALLREALS) as i32, (!kunset & CP_RESTORE) as i32, (kunset & CP_ALLKEYS) as i32, );
} else {
comp_setunset(
CP_COMPSTATE as i32, 0, (!kunset & CP_RESTORE) as i32, (kunset & CP_RESTORE) as i32, );
}
restore(&COMPRESTORE, orest);
0 }
pub fn comp_check() -> i32 {
if INCOMPFUNC.load(Ordering::Relaxed) != 1 {
zerr(
"condition can only be used in completion function",
);
return 0; }
1 }
#[allow(unused_variables)]
pub fn cond_psfix(a: &[String], id: i32) -> i32 {
if comp_check() != 0 {
if a.len() >= 2 {
let na = crate::ported::cond::cond_val(a, 0) as i32;
let sa = crate::ported::cond::cond_str(a, 1, true);
return do_comp_vars(id, na, &sa, 0, "", 0);
} else {
let sa = crate::ported::cond::cond_str(a, 0, true);
return do_comp_vars(id, -1, &sa, 0, "", 0);
}
}
0 }
pub fn cond_range(a: &[String], id: i32) -> i32 {
let sa = crate::ported::cond::cond_str(a, 0, true); let sb = if id != 0 {
crate::ported::cond::cond_str(a, 1, true) } else {
String::new()
};
do_comp_vars(CVT_RANGEPAT, 0, &sa, 0, &sb, 0) }
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
compcore::hasperm.store(0, Ordering::Relaxed); let clear = |g: &'static std::sync::OnceLock<Mutex<String>>| {
if let Ok(mut s) = g.get_or_init(|| Mutex::new(String::new())).lock() {
s.clear();
}
};
clear(&COMPPREFIX);
clear(&COMPSUFFIX); clear(&COMPIPREFIX);
clear(&COMPISUFFIX);
clear(&COMPQIPREFIX);
clear(&COMPQISUFFIX);
clear(&COMPCONTEXT);
clear(&COMPPARAMETER);
clear(&COMPREDIRECT);
clear(&COMPQUOTE);
clear(&COMPQUOTING);
clear(&COMPRESTORE);
clear(&COMPLIST);
clear(&COMPINSERT);
clear(&COMPEXACT);
clear(&COMPEXACTSTR);
clear(&COMPPATINSERT);
clear(&COMPLASTPROMPT);
clear(&COMPTOEND);
clear(&COMPOLDLIST);
clear(&COMPOLDINS);
clear(&COMPVARED);
clear(&COMPQSTACK);
if let Ok(mut g) = compcore::comppatmatch
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
g.clear(); }
if let Ok(mut g) = comprpms.lock() {
*g = None;
}
if let Ok(mut g) = compkpms.lock() {
*g = None;
}
if let Ok(mut g) = COMPREDIRS.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
if let Ok(mut g) = COMPWORDS.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
if let Ok(mut s) = COMPLASTPREFIX
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
s.clear();
}
if let Ok(mut s) = COMPLASTSUFFIX
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
s.clear();
}
COMPLISTMAX.store(0, Ordering::Relaxed); HASCOMPMOD.store(true, Ordering::SeqCst); 0 }
#[allow(unused_variables)]
pub fn features_(m: *const module) -> i32 {
0 }
static COTAB: Lazy<Mutex<Vec<crate::ported::zsh_h::conddef>>> = Lazy::new(|| {
Mutex::new(vec![
crate::ported::zsh_h::CONDDEF("after", 0, cond_range, 1, 1, 0), crate::ported::zsh_h::CONDDEF("between", 0, cond_range, 2, 2, 1), crate::ported::zsh_h::CONDDEF("prefix", 0, cond_psfix, 1, 2, CVT_PREPAT), crate::ported::zsh_h::CONDDEF("suffix", 0, cond_psfix, 1, 2, CVT_SUFPAT), ])
});
#[allow(unused_variables)]
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
const BN_SIZE: usize = 2; let mut cotab = match COTAB.lock() {
Ok(t) => t,
Err(_) => return 1,
};
match enables.as_ref() {
None => {
let mut bn_bits: Option<Vec<i32>> = None;
crate::ported::module::handlefeatures(
"zsh/complete",
&[String::from("b:compadd"), String::from("b:compset")],
&mut bn_bits,
);
let mut bits = bn_bits.unwrap_or_else(|| vec![0i32; BN_SIZE]);
for c in cotab.iter() {
bits.push(i32::from(
(c.flags & crate::ported::module::CONDF_ADDED) != 0,
));
}
*enables = Some(bits);
0
}
Some(e) => {
let mut bn_set = Some(e.iter().take(BN_SIZE).copied().collect::<Vec<i32>>());
crate::ported::module::handlefeatures(
"zsh/complete",
&[String::from("b:compadd"), String::from("b:compset")],
&mut bn_set,
);
let cd_bits: Vec<i32> = e.iter().skip(BN_SIZE).copied().collect(); crate::ported::module::setconddefs("zsh/complete", &mut cotab, Some(&cd_bits))
}
}
}
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
let _ = crate::ported::module::addhookfunc("complete", complete_hook);
let _ = crate::ported::module::addhookfunc("before_complete", before_complete_hook);
let _ = crate::ported::module::addhookfunc("after_complete", after_complete_hook);
let accept_last_as_hook: crate::ported::zsh_h::Hookfn =
unsafe { std::mem::transmute(crate::ported::zle::compresult::accept_last as fn() -> i32) };
let _ = crate::ported::module::addhookfunc("accept_completion", accept_last_as_hook);
let _ = crate::ported::module::addhookfunc("list_matches", list_matches_hook);
let _ = crate::ported::module::addhookfunc("invalidate_list", invalidate_list_hook);
0 }
fn complete_hook(_h: *mut crate::ported::zsh_h::hookdef, d: *mut std::ffi::c_void) -> i32 {
if d.is_null() {
return crate::ported::zle::compcore::do_completion("", 0, 0);
}
let dat = unsafe { &*(d as *const crate::ported::zle::zle_h::compldat) };
crate::ported::zle::compcore::do_completion(&dat.s, dat.incmd, dat.lst)
}
fn before_complete_hook(_h: *mut crate::ported::zsh_h::hookdef, d: *mut std::ffi::c_void) -> i32 {
if d.is_null() {
let mut lst = 0i32;
return crate::ported::zle::compcore::before_complete(&mut lst);
}
let lst_ptr = d as *mut i32;
crate::ported::zle::compcore::before_complete(unsafe { &mut *lst_ptr })
}
fn after_complete_hook(_h: *mut crate::ported::zsh_h::hookdef, d: *mut std::ffi::c_void) -> i32 {
if d.is_null() {
let mut dat = [0i32; 2];
return crate::ported::zle::compcore::after_complete(&mut dat);
}
let dat_ptr = d as *mut i32;
let dat_slice = unsafe { std::slice::from_raw_parts_mut(dat_ptr, 2) };
crate::ported::zle::compcore::after_complete(dat_slice)
}
fn list_matches_hook(_h: *mut crate::ported::zsh_h::hookdef, _d: *mut std::ffi::c_void) -> i32 {
crate::ported::zle::compresult::list_matches()
}
fn invalidate_list_hook(_h: *mut crate::ported::zsh_h::hookdef, _d: *mut std::ffi::c_void) -> i32 {
crate::ported::zle::compresult::invalidate_list()
}
#[allow(unused_variables)]
pub fn cleanup_(m: *const module) -> i32 {
let _ = crate::ported::module::deletehookfunc("list_matches", list_matches_hook);
let _ = crate::ported::module::deletehookfunc("invalidate_list", invalidate_list_hook);
let accept_last_as_hook: crate::ported::zsh_h::Hookfn =
unsafe { std::mem::transmute(crate::ported::zle::compresult::accept_last as fn() -> i32) };
let _ = crate::ported::module::deletehookfunc("accept_completion", accept_last_as_hook);
0 }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
let clear = |g: &'static std::sync::OnceLock<Mutex<String>>| {
if let Ok(mut s) = g.get_or_init(|| Mutex::new(String::new())).lock() {
s.clear();
}
};
clear(&COMPPREFIX);
clear(&COMPSUFFIX); clear(&COMPLASTPREFIX);
clear(&COMPLASTSUFFIX); clear(&COMPIPREFIX);
clear(&COMPISUFFIX); clear(&COMPQIPREFIX);
clear(&COMPQISUFFIX); clear(&COMPCONTEXT);
clear(&COMPPARAMETER);
clear(&COMPREDIRECT); clear(&COMPQUOTE);
clear(&COMPQSTACK);
clear(&COMPQUOTING); clear(&COMPRESTORE); clear(&COMPLIST); clear(&COMPINSERT);
clear(&COMPEXACT);
clear(&COMPEXACTSTR);
clear(&COMPPATINSERT);
clear(&COMPLASTPROMPT);
clear(&COMPTOEND);
clear(&COMPOLDLIST);
clear(&COMPOLDINS);
clear(&COMPVARED); if let Ok(mut g) = compcore::comppatmatch
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
g.clear(); }
if let Ok(mut g) = COMPWORDS.get_or_init(|| Mutex::new(Vec::new())).lock()
{
g.clear();
}
if let Ok(mut g) = COMPREDIRS.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
HASCOMPMOD.store(false, Ordering::SeqCst); 0
}
fn lock_str(g: &'static std::sync::OnceLock<Mutex<String>>) -> &'static Mutex<String> {
g.get_or_init(|| Mutex::new(String::new()))
}
fn lock_vec(g: &'static std::sync::OnceLock<Mutex<Vec<String>>>) -> &'static Mutex<Vec<String>> {
g.get_or_init(|| Mutex::new(Vec::new()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn boot_registers_accept_last_on_the_accept_completion_hook() {
let _g = crate::test_util::global_state_lock();
let _ = crate::ported::zle::zle_main::boot_(std::ptr::null());
let _ = boot_(std::ptr::null());
let h = crate::ported::module::gethookdef("accept_completion");
assert!(
!h.is_null(),
"c:zle_main.c:2229 — the ACCEPTCOMPHOOK hookdef is named accept_completion"
);
let want = crate::ported::zle::compresult::accept_last as fn() -> i32 as usize;
let mut found = false;
unsafe {
let funcs = (*h).funcs;
assert!(!funcs.is_null(), "c:1773 — boot_ must attach a Hookfn");
let mut node = (*funcs).first.as_ref();
while let Some(n) = node {
if n.dat == want {
found = true;
}
node = n.next.as_ref();
}
}
assert!(
found,
"c:1773 — accept_last must be the accept_completion hook function"
);
}
#[test]
fn get_compqstack_translates_each_quoting_level() {
use crate::ported::zsh_h::{QT_BACKSLASH, QT_BACKTICK, QT_DOLLARS, QT_DOUBLE, QT_SINGLE};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let put = |s: &str| {
*COMPQSTACK
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = s.to_string();
};
let q = |c: i32| char::from_u32(c as u32).unwrap();
put("");
assert_eq!(get_compqstack(¶m::default()), "");
put(&q(QT_BACKSLASH).to_string());
assert_eq!(get_compqstack(¶m::default()), "\\");
put(&q(QT_DOUBLE).to_string());
assert_eq!(get_compqstack(¶m::default()), "\"");
put(&q(QT_SINGLE).to_string());
assert_eq!(get_compqstack(¶m::default()), "'");
put(&q(QT_DOLLARS).to_string());
assert_eq!(get_compqstack(¶m::default()), "$");
put(&q(QT_BACKTICK).to_string());
assert_eq!(get_compqstack(¶m::default()), "\\");
put(&format!("{}{}", q(QT_SINGLE), q(QT_DOUBLE)));
assert_eq!(get_compqstack(¶m::default()), "'\"");
put("");
}
#[test]
fn classes_basic_cclass() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut p = Cpattern::default();
let rest = parse_class(&mut p, "[abc]rest");
assert_eq!(p.tp, CPAT_CCLASS);
assert_eq!(p.str.as_deref(), Some(b"abc".as_slice()));
assert_eq!(rest, "]rest");
}
#[test]
fn classes_negated_cclass_via_bang() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut p = Cpattern::default();
let _ = parse_class(&mut p, "[!abc]");
assert_eq!(p.tp, CPAT_NCLASS);
}
#[test]
fn classes_negated_cclass_via_caret() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut p = Cpattern::default();
let _ = parse_class(&mut p, "[^abc]");
assert_eq!(p.tp, CPAT_NCLASS);
}
#[test]
fn classes_equiv_braces() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut p = Cpattern::default();
let _ = parse_class(&mut p, "{abc}");
assert_eq!(p.tp, CPAT_EQUIV);
}
#[test]
fn classes_range_consumes_input() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut p = Cpattern::default();
let rest = parse_class(&mut p, "[a-z]rest");
assert_eq!(p.tp, CPAT_CCLASS);
assert_eq!(rest, "]rest");
assert!(p.str.is_some());
}
#[test]
fn cmatcher_empty_input_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(parse_cmatcher("", "").is_none());
}
#[test]
fn cmatcher_x_early_return() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(parse_cmatcher("", "x:").is_none());
}
#[test]
fn cmatcher_unknown_letter_errors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(parse_cmatcher("", "q:abc").is_none());
}
#[test]
fn cmatcher_missing_colon_errors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(parse_cmatcher("", "rabc").is_none());
}
#[test]
fn cmatcher_x_with_trailing_pattern_errors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(parse_cmatcher("", "x:foo").is_none());
}
#[test]
fn cmatcher_valid_letters_dont_panic() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
for c in ['b', 'l', 'e', 'r', 'm', 'B', 'L', 'E', 'R', 'M'] {
let spec = format!("{}:body", c);
let _ = parse_cmatcher("", &spec);
}
}
#[test]
fn cmatcher_m_rule_emits_cmatcher() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = parse_cmatcher("", "m:foo=bar");
assert!(r.is_some(), "m: rule should produce a Cmatcher");
let cm = r.unwrap();
assert_eq!(cm.flags, 0); assert_eq!(cm.llen, 3); assert_eq!(cm.wlen, 3); assert!(cm.line.is_some());
assert!(cm.word.is_some());
assert!(cm.left.is_none());
assert!(cm.right.is_none());
}
#[test]
fn cmatcher_r_rule_emits_anchored_cmatcher() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = parse_cmatcher("", "r:abc|xy=def");
assert!(r.is_some(), "r: rule should produce a Cmatcher");
let cm = r.unwrap();
assert_eq!(cm.flags, CMF_RIGHT);
assert_eq!(cm.lalen, 0, "single `|`: no left anchor"); assert!(cm.left.is_none());
assert_eq!(cm.llen, 3, "`abc` is the line/word pattern"); assert!(cm.line.is_some());
assert_eq!(cm.ralen, 2, "`xy` is the right anchor"); assert!(cm.right.is_some());
assert_eq!(cm.wlen, 3); }
#[test]
fn cmatcher_r_rule_double_bar_promotes_left_anchor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let cm = parse_cmatcher("", "r:abc||xy=def").expect("r: two-anchor rule parses");
assert_eq!(cm.flags, CMF_RIGHT);
assert_eq!(cm.lalen, 3, "`abc` promoted to the left coanchor"); assert!(cm.left.is_some());
assert_eq!(cm.llen, 0, "line pattern cleared by the promotion"); assert!(cm.line.is_none());
assert_eq!(cm.ralen, 2, "`xy` is the right anchor, no stray `|`"); assert_eq!(cm.wlen, 3);
let cm = parse_cmatcher("", "r:[^A-Z0-9]||[A-Z0-9]=**").expect("documented idiom parses");
assert_eq!(cm.lalen, 1);
assert_eq!(cm.ralen, 1, "a leaked `|` would make this 2"); assert_eq!(cm.wlen, -2, "`**` sentinel"); }
#[test]
fn cmatcher_l_rule_emits_left_anchor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = parse_cmatcher("", "l:ab|cd=ef");
assert!(r.is_some(), "l: rule should produce a Cmatcher");
let cm = r.unwrap();
assert_eq!(cm.flags, CMF_LEFT);
assert!(cm.left.is_some());
assert_eq!(cm.lalen, 2);
assert_eq!(cm.llen, 2);
assert_eq!(cm.wlen, 2);
}
#[test]
fn cmatcher_star_word_with_anchor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = parse_cmatcher("", "r:|=*");
assert!(r.is_some(), "r:|=* should produce a Cmatcher");
let cm = r.unwrap();
assert_eq!(cm.wlen, -1); assert!(cm.word.is_none());
}
#[test]
fn cmatcher_double_star_word() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = parse_cmatcher("", "r:|=**");
assert!(r.is_some());
let cm = r.unwrap();
assert_eq!(cm.wlen, -2); }
#[test]
fn cmatcher_star_without_anchor_errors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = parse_cmatcher("", "m:=*");
assert!(r.is_none(), "*-without-anchor should error");
}
#[test]
fn cmatcher_chain_multiple_rules() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = parse_cmatcher("", "m:foo=bar m:baz=qux");
assert!(r.is_some());
let head = r.unwrap();
assert!(head.next.is_some(), "second rule should be linked");
}
#[test]
fn pattern_single_char_emits_cpat_char() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (chain, rest, len, err) = parse_pattern("", "abc", '\0');
assert!(!err);
assert_eq!(len, 3);
assert_eq!(rest, ""); let mut count = 0;
let mut cur = chain.as_deref();
while let Some(n) = cur {
assert_eq!(n.tp, CPAT_CHAR);
count += 1;
cur = n.next.as_deref();
}
assert_eq!(count, 3);
}
#[test]
fn pattern_question_mark_is_cpat_any() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (chain, _, len, err) = parse_pattern("", "?", '\0');
assert!(!err);
assert_eq!(len, 1);
assert_eq!(chain.as_ref().unwrap().tp, CPAT_ANY);
}
#[test]
fn pattern_invalid_chars_error() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
for c in ['*', '(', ')', '='] {
let s = format!("{}", c);
let (chain, _, _, err) = parse_pattern("", &s, '\0');
assert!(err, "char {} should error", c);
assert!(chain.is_none());
}
}
#[test]
fn pattern_backslash_escapes_next() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (chain, _, len, err) = parse_pattern("", r"\*", '\0');
assert!(!err);
assert_eq!(len, 1);
let n = chain.as_ref().unwrap();
assert_eq!(n.tp, CPAT_CHAR);
assert_eq!(n.chr, '*' as u32);
}
#[test]
fn pattern_stops_at_end_char() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (_, rest, len, err) = parse_pattern("", "ab=cd", '=');
assert!(!err);
assert_eq!(len, 2);
assert_eq!(rest, "=cd");
}
#[test]
fn pattern_stops_at_whitespace_when_no_end_char() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (_, rest, len, err) = parse_pattern("", "ab cd", '\0');
assert!(!err);
assert_eq!(len, 2);
assert_eq!(rest, " cd");
}
#[test]
fn pattern_bracket_class_routes_to_parse_class() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (chain, rest, len, err) = parse_pattern("", "[abc]xy=q", '=');
assert!(!err);
assert_eq!(len, 3);
assert_eq!(rest, "=q");
assert_eq!(chain.as_ref().unwrap().tp, CPAT_CCLASS);
}
#[test]
fn classes_unterminated_returns_eos() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut p = Cpattern::default();
let rest = parse_class(&mut p, "[abc");
assert_eq!(rest, "");
}
#[test]
fn compadd_trace_records_and_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
INCOMPFUNC.store(1, Ordering::Relaxed);
crate::ported::params::setaparam("_complete_help_funcs", Vec::new());
set_compadd_trace(true);
let argv = vec!["-X".to_string(), "files".to_string(), "alpha".to_string()];
let r = bin_compadd("compadd", &argv, &make_test_ops(), 0);
set_compadd_trace(false);
INCOMPFUNC.store(0, Ordering::Relaxed);
assert_eq!(r, 1, "trace mode short-circuits to 1");
let buf = crate::ported::params::getaparam("_complete_help_funcs").unwrap_or_default();
assert_eq!(buf, vec!["-X files alpha".to_string()]);
}
#[test]
fn compadd_prefix_injector_mutates_prefix_then_restores() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
INCOMPFUNC.store(1, Ordering::Relaxed);
crate::ported::params::setsparam("PREFIX", "abc").unwrap();
crate::ported::params::setaparam("_complete_help_funcs", Vec::new());
set_compadd_trace(true);
let prev = set_compadd_prefix_injector("(#a2)");
assert!(prev.is_none());
let _ = bin_compadd("compadd", &["x".to_string()], &make_test_ops(), 0);
clear_compadd_prefix_injector();
set_compadd_trace(false);
INCOMPFUNC.store(0, Ordering::Relaxed);
let after = crate::ported::params::getsparam("PREFIX").unwrap_or_default();
assert_eq!(after, "abc", "PREFIX restored after compadd call");
}
#[test]
fn compadd_prefix_injector_tilde_aware() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
INCOMPFUNC.store(1, Ordering::Relaxed);
crate::ported::params::setsparam("PREFIX", "~user").unwrap();
set_compadd_trace(true);
let _ = set_compadd_prefix_injector("(#a1)");
let _ = bin_compadd("compadd", &["x".to_string()], &make_test_ops(), 0);
clear_compadd_prefix_injector();
set_compadd_trace(false);
INCOMPFUNC.store(0, Ordering::Relaxed);
assert_eq!(
crate::ported::params::getsparam("PREFIX").unwrap_or_default(),
"~user"
);
}
fn make_test_ops() -> options {
options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
#[test]
fn complete_corpus_parse_ordering_empty_returns_neg_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut flags: Option<i32> = Some(0);
let r = parse_ordering("", &mut flags);
assert_eq!(r, -1, "empty token is not a valid orderopts name");
}
#[test]
fn complete_corpus_parse_ordering_unknown_returns_neg_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut flags: Option<i32> = Some(0);
let r = parse_ordering("zzz_not_a_real_ordering_xyz", &mut flags);
assert_eq!(r, -1, "unknown ordering name = -1");
}
#[test]
fn complete_corpus_parse_ordering_match_recognised() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut flags: Option<i32> = Some(0);
let r = parse_ordering("match", &mut flags);
assert_eq!(r, 0, "match is a valid orderopts name");
}
#[test]
fn complete_corpus_parse_ordering_csv_all_valid() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut flags: Option<i32> = Some(0);
let r = parse_ordering("match,reverse", &mut flags);
assert_eq!(r, 0);
}
#[test]
fn complete_corpus_parse_ordering_csv_with_invalid_returns_neg_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut flags: Option<i32> = Some(0);
let r = parse_ordering("match,zzz_bogus", &mut flags);
assert_eq!(r, -1, "any invalid token = -1");
}
#[test]
fn ignore_prefix_zero_is_noop() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ignore_prefix(0);
ignore_prefix(0);
}
#[test]
fn ignore_suffix_zero_is_noop() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ignore_suffix(0);
ignore_suffix(0);
}
#[test]
fn atoi_matches_c_semantics() {
assert_eq!(atoi("1"), 1);
assert_eq!(atoi(" 1"), 1, "leading whitespace is skipped");
assert_eq!(atoi("1 "), 1, "trailing junk is ignored");
assert_eq!(atoi("2x"), 2, "parse stops at the first non-digit");
assert_eq!(atoi("-3"), -3);
assert_eq!(atoi("+4"), 4);
assert_eq!(atoi("abc"), 0, "no leading digits = 0");
assert_eq!(atoi(""), 0);
}
#[test]
fn compset_p_moves_prefix_chars_to_iprefix() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _scope = crate::test_util::comp_scope();
INCOMPFUNC.store(1, Ordering::Relaxed);
*lock_str(&COMPPREFIX).lock().unwrap() = "~ro".to_string();
*lock_str(&COMPIPREFIX).lock().unwrap() = String::new();
let rc = bin_compset(
"compset",
&["-p".to_string(), "1".to_string()],
&make_test_ops(),
0,
);
INCOMPFUNC.store(0, Ordering::Relaxed);
assert_eq!(rc, 0, "a successful move reports exit status 0");
assert_eq!(*lock_str(&COMPPREFIX).lock().unwrap(), "ro");
assert_eq!(*lock_str(&COMPIPREFIX).lock().unwrap(), "~");
assert_eq!(
crate::ported::params::getsparam("PREFIX").unwrap_or_default(),
"ro"
);
assert_eq!(
crate::ported::params::getsparam("IPREFIX").unwrap_or_default(),
"~"
);
}
#[test]
fn compset_p_too_short_prefix_fails_without_moving() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
INCOMPFUNC.store(1, Ordering::Relaxed);
*lock_str(&COMPPREFIX).lock().unwrap() = "~r".to_string();
*lock_str(&COMPIPREFIX).lock().unwrap() = String::new();
let rc = bin_compset(
"compset",
&["-p".to_string(), "5".to_string()],
&make_test_ops(),
0,
);
INCOMPFUNC.store(0, Ordering::Relaxed);
assert_eq!(rc, 1, "no move reports non-zero");
assert_eq!(*lock_str(&COMPPREFIX).lock().unwrap(), "~r");
assert_eq!(*lock_str(&COMPIPREFIX).lock().unwrap(), "");
}
#[test]
fn restrict_range_zero_zero_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
restrict_range(0, 0);
}
#[test]
fn set_compadd_trace_toggle_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
set_compadd_trace(true);
set_compadd_trace(false);
set_compadd_trace(true);
}
#[test]
fn set_then_clear_compadd_prefix_injector_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let prev = set_compadd_prefix_injector("test_prefix");
clear_compadd_prefix_injector();
if let Some(p) = prev {
let _ = set_compadd_prefix_injector(p);
clear_compadd_prefix_injector();
}
}
#[test]
fn parse_ordering_empty_string_returns_neg_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut flags: Option<i32> = Some(0);
let r = parse_ordering("", &mut flags);
assert_eq!(r, -1, "empty input has no valid ordering token");
}
#[test]
fn freecmatcher_none_is_noop() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
freecmatcher(None);
}
#[test]
fn freecpattern_none_is_noop() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
freecpattern(None);
}
#[test]
fn freecmlist_none_is_noop() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
freecmlist(None);
}
#[test]
fn cpcpattern_none_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = cpcpattern(None);
assert!(r.is_none(), "None source → None copy");
}
#[test]
fn cpcmatcher_none_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = cpcmatcher(None);
assert!(r.is_none());
}
#[test]
fn cp_cpattern_element_copies_tp() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let src = Cpattern {
tp: CPAT_CHAR,
chr: 'A' as u32,
..Default::default()
};
let dup = cp_cpattern_element(&src);
assert_eq!(dup.tp, CPAT_CHAR, "tp must round-trip");
}
#[test]
fn cp_cpattern_element_copies_chr_for_cpat_char() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let src = Cpattern {
tp: CPAT_CHAR,
chr: 0x42,
..Default::default()
};
let dup = cp_cpattern_element(&src);
assert_eq!(dup.chr, 0x42);
}
#[test]
fn cp_cpattern_element_copies_str_for_cclass() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let src = Cpattern {
tp: CPAT_CCLASS,
str: Some(b"a-z".to_vec()),
..Default::default()
};
let dup = cp_cpattern_element(&src);
assert_eq!(dup.str.as_deref(), Some(&b"a-z"[..]));
}
#[test]
fn cp_cpattern_element_resets_next_to_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let src = Cpattern {
tp: CPAT_CHAR,
chr: 'x' as u32,
next: Some(Box::new(Cpattern::default())), ..Default::default()
};
let dup = cp_cpattern_element(&src);
assert!(dup.next.is_none(), "copy element MUST reset next to None");
}
#[test]
fn cpcpattern_preserves_chain_length() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let src = Cpattern {
tp: CPAT_CHAR,
chr: 'a' as u32,
next: Some(Box::new(Cpattern {
tp: CPAT_CHAR,
chr: 'b' as u32,
next: Some(Box::new(Cpattern {
tp: CPAT_CHAR,
chr: 'c' as u32,
..Default::default()
})),
..Default::default()
})),
..Default::default()
};
let dup = cpcpattern(Some(&src)).expect("should copy");
let mut cnt = 1;
let mut cur = &dup.next;
while let Some(n) = cur {
cnt += 1;
cur = &n.next;
}
assert_eq!(cnt, 3, "3-node source → 3-node copy");
}
#[test]
fn parse_cmatcher_empty_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = parse_cmatcher("test", "");
assert!(r.is_none(), "empty matcher spec → None per c:249");
}
#[test]
fn freecmlist_none_idempotent() {
for _ in 0..5 {
freecmlist(None);
}
}
#[test]
fn freecmatcher_none_idempotent() {
for _ in 0..5 {
freecmatcher(None);
}
}
#[test]
fn freecpattern_none_idempotent() {
for _ in 0..5 {
freecpattern(None);
}
}
#[test]
fn cpcmatcher_none_returns_none_type_pin() {
let r: Option<Box<Cmatcher>> = cpcmatcher(None);
assert!(r.is_none());
}
#[test]
fn cpcpattern_none_returns_none_type_pin() {
let r: Option<Box<Cpattern>> = cpcpattern(None);
assert!(r.is_none());
}
#[test]
fn parse_cmatcher_empty_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..3 {
assert!(parse_cmatcher("test", "").is_none());
}
}
#[test]
fn parse_ordering_returns_i32_type() {
let mut flags: Option<i32> = None;
let _: i32 = parse_ordering("name", &mut flags);
}
#[test]
fn ignore_prefix_zero_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ignore_prefix(0);
}
#[test]
fn ignore_suffix_zero_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ignore_suffix(0);
}
#[test]
fn restrict_range_zero_zero_no_panic_pin() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
restrict_range(0, 0);
}
use crate::ported::zle::comp_h::{CP_ALLKEYS, CP_ALLREALS, CP_PARAMETER, CP_QUOTE, CP_QUOTING, CP_REDIRECT};
fn comp_scope_enter() {
use crate::ported::zsh_h::hashtable;
*comprpms.lock().unwrap() = Some(vec![None; CP_REALPARAMS as usize]);
*compkpms.lock().unwrap() = Some(vec![None; CP_KEYPARAMS as usize]);
let mut scope: HashTable = newparamtable(1, "scope").unwrap();
let _: &hashtable = &scope;
crate::ported::params::startparamscope(&mut scope);
makecompparams();
}
fn comp_scope_leave() {
crate::ported::params::endparamscope();
*comprpms.lock().unwrap() = None;
*compkpms.lock().unwrap() = None;
}
fn gstr(g: &'static std::sync::OnceLock<Mutex<String>>) -> String {
g.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone()
}
#[test]
fn comprparams_are_views_of_the_c_globals() {
use crate::ported::params::{getaparam, getiparam, getsparam, setaparam, setiparam, setsparam};
let _g = crate::test_util::global_state_lock();
*COMPPREFIX.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "ab".into();
*COMPQIPREFIX.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "q".into();
*lock_vec(&COMPWORDS).lock().unwrap() = vec!["ls".into(), "x".into()];
COMPCURRENT.store(2, Ordering::Relaxed);
let present = |n: &str| paramtab().read().unwrap().get(n).is_some();
let (words_before, compstate_before) = (present("words"), present("compstate"));
comp_scope_enter();
assert_eq!(getsparam("PREFIX").as_deref(), Some("ab"));
assert_eq!(getsparam("QIPREFIX").as_deref(), Some("q"));
assert_eq!(getaparam("words"), Some(vec!["ls".to_string(), "x".to_string()]));
assert_eq!(getiparam("CURRENT"), 2);
*COMPPREFIX.get().unwrap().lock().unwrap() = "moved".into();
assert_eq!(getsparam("PREFIX").as_deref(), Some("moved"));
setsparam("PREFIX", "zz");
assert_eq!(gstr(&COMPPREFIX), "zz");
setaparam("words", vec!["a".into(), "b".into(), "c".into()]);
assert_eq!(*lock_vec(&COMPWORDS).lock().unwrap(), vec!["a", "b", "c"]);
setiparam("CURRENT", 3);
assert_eq!(COMPCURRENT.load(Ordering::Relaxed), 3);
setsparam("QIPREFIX", "changed");
assert_eq!(gstr(&COMPQIPREFIX), "q");
crate::ported::params::unsetparam("PREFIX");
assert_eq!(gstr(&COMPPREFIX), "");
comp_scope_leave();
*COMPPREFIX.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "after-scope".into();
assert_ne!(getsparam("PREFIX").as_deref(), Some("after-scope"));
assert_eq!(present("words"), words_before);
assert_eq!(present("compstate"), compstate_before);
}
#[test]
fn command_substitution_does_not_leak_completion_param_writes() {
use crate::ported::params::getsparam;
let _g = crate::test_util::global_state_lock();
*COMPPREFIX.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "keep".into();
*COMPINSERT.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "menu".into();
*lock_vec(&COMPWORDS).lock().unwrap() = vec!["a".into(), "b".into()];
COMPCURRENT.store(2, Ordering::Relaxed);
let mut exec = crate::vm_helper::ShellExecutor::new();
let _ctx = crate::fusevm_bridge::ExecutorContext::enter(&mut exec);
comp_scope_enter();
let out = exec.run_command_substitution(
"PREFIX=leaked; words=(x y z); CURRENT=9; compstate[insert]=zz; print -n \"$PREFIX $CURRENT $compstate[insert]\"",
);
assert_eq!(out, "leaked 9 zz", "the body sees its own writes");
assert_eq!(getsparam("PREFIX").as_deref(), Some("keep"));
assert_eq!(gstr(&COMPPREFIX), "keep");
assert_eq!(gstr(&COMPINSERT), "menu");
assert_eq!(*lock_vec(&COMPWORDS).lock().unwrap(), vec!["a", "b"]);
assert_eq!(COMPCURRENT.load(Ordering::Relaxed), 2);
comp_scope_leave();
}
#[test]
fn local_of_a_completion_param_writes_the_global_and_restores_it() {
let _g = crate::test_util::global_state_lock();
*COMPPREFIX.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "outer".into();
*lock_vec(&COMPWORDS).lock().unwrap() = vec!["a".into(), "b".into()];
COMPCURRENT.store(2, Ordering::Relaxed);
let mut exec = crate::vm_helper::ShellExecutor::new();
let _ctx = crate::fusevm_bridge::ExecutorContext::enter(&mut exec);
let _scope = crate::test_util::comp_scope();
exec.execute_script(
"f() { local PREFIX=inner words=(x y z) CURRENT=3; typeset -g SEEN=$PREFIX:$#words:$CURRENT; typeset -g DBG=\"${#words}|$words[2]|${(j:,:)words}|$#\" }; g() { f }; g",
)
.unwrap();
assert_eq!(crate::ported::params::getsparam("DBG").as_deref(), Some("3|y|x,y,z|0"));
assert_eq!(crate::ported::params::getsparam("SEEN").as_deref(), Some("inner:3:3"));
assert_eq!(gstr(&COMPPREFIX), "outer");
assert_eq!(*lock_vec(&COMPWORDS).lock().unwrap(), vec!["a", "b"]);
assert_eq!(COMPCURRENT.load(Ordering::Relaxed), 2);
crate::ported::params::unsetparam("SEEN");
crate::ported::params::unsetparam("DBG");
}
#[test]
fn compstate_keys_are_views_of_the_c_globals() {
use crate::ported::params::{assignsparam, gethkparam};
let _g = crate::test_util::global_state_lock();
*COMPINSERT.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "unambiguous".into();
comp_scope_enter();
comp_setunset(
CP_ALLREALS as i32,
0,
(CP_ALLKEYS & !(CP_PARAMETER | CP_REDIRECT | CP_QUOTE | CP_QUOTING)) as i32,
(!(CP_ALLKEYS & !(CP_PARAMETER | CP_REDIRECT | CP_QUOTE | CP_QUOTING)) & CP_ALLKEYS)
as i32,
);
let keys = gethkparam("compstate").unwrap();
assert!(keys.contains(&"insert".to_string()));
assert!(!keys.contains(&"quote".to_string()), "PM_UNSET key hidden");
assert_eq!(
crate::vm_helper::assoc_key_hit("compstate", "insert"),
Some((true, Some("unambiguous".to_string())))
);
assert!(assignsparam("compstate[insert]", "menu", 0).is_some());
assert_eq!(gstr(&COMPINSERT), "menu");
*COMPINSERT.get().unwrap().lock().unwrap() = "automenu".into();
assert_eq!(
crate::vm_helper::assoc_key_hit("compstate", "insert"),
Some((true, Some("automenu".to_string())))
);
*COMPQUOTE.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = "'".into();
assert!(assignsparam("compstate[quote]", "x", 0).is_none());
assert_eq!(gstr(&COMPQUOTE), "'");
assert!(assignsparam("compstate[parameter]", "foo", 0).is_some());
assert!(gethkparam("compstate").unwrap().contains(&"parameter".to_string()));
assert_eq!(gstr(&COMPPARAMETER), "foo");
assert!(crate::ported::params::sethparam(
"compstate",
vec!["insert".into(), "all".into(), "list_max".into(), "77".into()]
)
.is_some());
assert_eq!(gstr(&COMPINSERT), "all");
assert_eq!(COMPLISTMAX.load(Ordering::Relaxed), 77);
assert_eq!(crate::ported::params::unsetparam("compstate[insert]"), 0);
assert_eq!(gstr(&COMPINSERT), "");
assert!(!gethkparam("compstate").unwrap().contains(&"insert".to_string()));
comp_scope_leave();
assert!(paramtab().read().unwrap().get("compstate").is_none());
}
#[test]
fn freecmlist_none_idempotent_10_call() {
for _ in 0..10 {
freecmlist(None);
}
}
#[test]
fn ignore_prefix_positive_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for n in [1i32, 10, 100] {
ignore_prefix(n);
}
}
#[test]
fn ignore_suffix_positive_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for n in [1i32, 10, 100] {
ignore_suffix(n);
}
}
#[test]
fn ignore_prefix_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..10 {
ignore_prefix(0);
}
}
#[test]
fn restrict_range_various_endpoints_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for &(b, e) in &[(0, 0), (1, 10), (-1, -1), (i32::MAX, i32::MAX)] {
restrict_range(b, e);
}
}
#[test]
fn parse_ordering_empty_deterministic() {
let mut flags1: Option<i32> = None;
let first = parse_ordering("", &mut flags1);
let mut flags2: Option<i32> = None;
let second = parse_ordering("", &mut flags2);
assert_eq!(first, second, "parse_ordering('') must be deterministic");
}
#[test]
fn bin_compadd_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
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_compadd("compadd", &[], &ops, 0);
}
#[test]
fn bin_compset_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
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_compset("compset", &[], &ops, 0);
}
#[test]
fn parse_cmatcher_various_inputs_deterministic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for s in ["", "x", "m:{a-z}={A-Z}", "garbage"] {
let a = parse_cmatcher("test", s).is_some();
let b = parse_cmatcher("test", s).is_some();
assert_eq!(a, b, "parse_cmatcher({:?}) must be deterministic", s);
}
}
#[test]
fn compadd_prefix_injector_round_trip() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<String> = set_compadd_prefix_injector("test_prefix_xyz");
clear_compadd_prefix_injector();
}
#[test]
fn set_compadd_trace_toggle_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for active in [true, false, true, true, false] {
set_compadd_trace(active);
}
}
#[test]
fn setup_raises_hascompmod_and_finish_lowers_it() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let restore = HASCOMPMOD.load(Ordering::SeqCst);
HASCOMPMOD.store(false, Ordering::SeqCst);
assert_eq!(setup_(std::ptr::null()), 0, "c:1738 — setup_ returns 0");
assert!(
HASCOMPMOD.load(Ordering::SeqCst),
"c:1736 — `hascompmod = 1` is the load half"
);
assert_eq!(finish_(std::ptr::null()), 0, "c:1823 — finish_ returns 0");
assert!(
!HASCOMPMOD.load(Ordering::SeqCst),
"c:1821 — `hascompmod = 0` is the unload half"
);
HASCOMPMOD.store(restore, Ordering::SeqCst);
}
}