use std::sync::atomic::{AtomicI32, AtomicI64, Ordering};
use std::sync::Mutex;
use crate::ported::glob::{remnulargs, tokenize};
use crate::ported::params::{createparam, getsparam, paramtab};
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_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::zsh_h::{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<Box<crate::ported::zle::comp_h::Cmlist>>) {
let mut cur = l;
while let Some(node) = cur {
cur = node.next; }
}
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 static INCOMPFUNC: AtomicI32 = AtomicI32::new(0);
pub static COMPCURRENT: AtomicI32 = AtomicI32::new(0);
pub static COMPLISTMAX: AtomicI64 = AtomicI64::new(0);
pub static NMATCHES_GLOBAL: 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);
pub static COMPWORDS: std::sync::OnceLock<Mutex<Vec<String>>> = std::sync::OnceLock::new();
pub fn parse_cmatcher(name: &str, s: &str) -> Option<Box<Cmatcher>> {
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),
);
}
return None; }
};
let mut chars = rest.chars();
chars.next();
if chars.clone().next() != Some(':') {
if !name.is_empty() {
zwarnnam(name, "missing `:'");
}
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",
);
}
return None;
}
}
return ret;
}
rest = chars.as_str();
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 {
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"
},
);
}
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 {
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 && rest.len() <= 1 {
if !name.is_empty() {
zwarnnam(name, "missing right anchor");
}
return None;
}
if (fl & CMF_RIGHT) == 0 || fl2 != 0 {
if rest.is_empty() {
if !name.is_empty() {
zwarnnam(name, "missing word pattern");
}
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 {
return None;
}
right = rt;
ral = r_len;
rest = r3;
if rest.is_empty() {
if !name.is_empty() {
zwarnnam(name, "missing word pattern");
}
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 `*'");
}
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 {
return None;
}
if w.is_none() && line_pat.is_none() {
if !name.is_empty() {
zwarnnam(name, "need non-empty word or line pattern");
}
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 == '{' {
let before_len = rest.len();
rest = parse_class(&mut node, rest);
if rest.len() == before_len {
if !name.is_empty() {
zwarnnam(name, "unterminated character class");
}
return (None, rest, 0, true);
}
} 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);
let consumed = (i + 1).min(bytes.len());
&iptr[consumed..]
}
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 }
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; }
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 fn set_compadd_trace(active: bool) {
COMPADD_TRACE_ACTIVE.store(active, std::sync::atomic::Ordering::Relaxed);
}
fn bin_compadd_body(
name: &str,
argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
let mut dat = crate::ported::zle::comp_h::Cadata::default();
dat.dummies = -1;
let mut idx = 0usize;
let take_arg = |argv: &[String], idx: &mut usize, arg: &str| -> Option<String> {
if arg.len() > 2 {
Some(arg[2..].to_string())
} else if *idx < argv.len() {
let s = argv[*idx].clone();
*idx += 1;
Some(s)
} else {
None
}
};
while idx < argv.len() {
let arg = argv[idx].clone();
if arg == "--" {
idx += 1;
break;
} if !arg.starts_with('-') || arg.len() < 2 {
break;
} idx += 1;
let c = arg.as_bytes()[1] as char;
match c {
'a' => dat.aflags |= CAF_ARRAYS, 'k' => dat.aflags |= CAF_KEYS, 'l' => dat.flags |= CMF_NOLIST as i32, 'o' => dat.aflags |= CAF_NOSORT, 'Q' => dat.aflags |= CAF_QUOTE, '1' => dat.aflags |= CAF_UNIQALL, '2' => dat.aflags |= CAF_UNIQCON, 'C' => dat.aflags |= CAF_ALL, 'F' => dat.flags |= CMF_FILE as i32, 'f' => dat.flags |= CMF_FILE as i32, 'P' => dat.pre = take_arg(argv, &mut idx, &arg), 'S' => dat.suf = take_arg(argv, &mut idx, &arg), 'p' => dat.ppre = take_arg(argv, &mut idx, &arg), 's' => dat.psuf = take_arg(argv, &mut idx, &arg), 'W' => dat.prpre = take_arg(argv, &mut idx, &arg), 'i' => dat.ipre = take_arg(argv, &mut idx, &arg), 'I' => dat.isuf = take_arg(argv, &mut idx, &arg), 'J' => dat.group = take_arg(argv, &mut idx, &arg), 'V' => {
dat.group = take_arg(argv, &mut idx, &arg);
dat.aflags |= CAF_NOSORT;
}
'X' => dat.exp = take_arg(argv, &mut idx, &arg), 'x' => dat.mesg = take_arg(argv, &mut idx, &arg), 'd' => dat.disp = take_arg(argv, &mut idx, &arg), 'O' => dat.opar = take_arg(argv, &mut idx, &arg), 'A' => dat.apar = take_arg(argv, &mut idx, &arg), 'D' => {
if let Some(s) = take_arg(argv, &mut idx, &arg) {
dat.dpar.push(s);
}
}
'E' => {
if let Some(s) = take_arg(argv, &mut idx, &arg) {
dat.dummies = s.parse::<i32>().unwrap_or(-1).max(0);
}
}
'M' => {
if let Some(s) = take_arg(argv, &mut idx, &arg) {
if let Some(m) = parse_cmatcher(name, &s) {
dat.match_ = Some(m);
dat.aflags |= CAF_MATCH;
} else {
return 1;
}
}
}
'q' => dat.flags |= CMF_REMOVE as i32, 'r' => dat.rems = take_arg(argv, &mut idx, &arg), 'R' => dat.remf = take_arg(argv, &mut idx, &arg), 'n' => dat.flags |= CMF_NOLIST as i32, 'U' => dat.flags |= CMF_HIDE as i32, 'e' => dat.aflags |= CAF_NOSORT, 'Y' => dat.flags |= CMF_ISPAR as i32, _ => {
zwarnnam(name, &format!("bad option: -{}", c));
return 1;
}
}
}
let matches = &argv[idx..];
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);
COMPCURRENT.store(cur - b, 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);
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) - 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) {
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()
};
if (target_str.chars().count() as i32) < na {
return 0;
}
if test == CVT_PRENUM {
ignore_prefix(na); } else {
ignore_suffix(na); }
}
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 {
ignore_prefix(p);
} } 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 {
ignore_suffix(l - p);
} }
1 }
_ => 0, }
}
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 sa_ref = sa.as_deref().unwrap_or("");
let sb_ref = sb.as_deref();
match test {
CVT_RANGENUM => {
na = sa_ref.parse::<i32>().unwrap_or(0); nb = sb_ref.and_then(|s| s.parse::<i32>().ok()).unwrap_or(-1); }
CVT_RANGEPAT => {
let mut sa_buf = sa_ref.to_string();
tokenize(&mut sa_buf);
remnulargs(&mut sa_buf);
if let Some(sb_inner) = sb_ref {
let mut sb_buf = sb_inner.to_string();
tokenize(&mut sb_buf);
remnulargs(&mut sb_buf);
}
let _ = sa_buf;
nb = 0;
}
CVT_PRENUM | CVT_SUFNUM => {
na = sa_ref.parse::<i32>().unwrap_or(0); nb = 0;
}
CVT_PREPAT | CVT_SUFPAT => {
if let Some(s2) = sb_ref {
na = sa_ref.parse::<i32>().unwrap_or(0); let _ = s2; nb = 0;
} else {
nb = 0;
}
}
_ => {
nb = 0;
}
}
let _ = (na, nb);
do_comp_vars(test, na, sa_ref, nb, sb_ref.unwrap_or(""), 0) }
#[allow(unused_variables)]
pub fn addcompparams(cp: &[compparam], pp: &mut Vec<*mut param>) {
for entry in cp {
let flags = entry.r#type | PM_SPECIAL as i32 | PM_REMOVABLE as i32 | PM_LOCAL as i32;
let pm = createparam(entry.name, flags);
if let Some(mut pm_val) = pm {
pm_val.u_data = entry.var; pp.push(std::ptr::null_mut::<param>());
} else {
pp.push(std::ptr::null_mut::<param>());
}
}
}
pub fn makecompparams() {
let mut comprpms: Vec<*mut param> = Vec::new();
addcompparams(COMPRPARAMS, &mut comprpms);
let _ = createparam(
"compstate",
(PM_SPECIAL | PM_REMOVABLE | PM_SINGLE | PM_LOCAL | PM_HASHED) as i32,
);
let mut compkpms: Vec<*mut param> = Vec::new();
addcompparams(COMPKPARAMS, &mut compkpms);
}
pub fn get_compstate(pm: *mut param) -> Option<usize> {
unsafe { pm.as_ref() } .and_then(|p| p.u_hash.as_ref().map(|_| &p.u_hash as *const _ as usize))
}
#[allow(unused_variables)]
pub fn set_compstate(
pm: *mut param, ht: Option<usize>,
) {
let Some(_handle) = ht else {
return;
};
}
#[allow(unused_variables)]
pub fn get_nmatches(pm: *mut param) -> i64 {
if compcore::permmatches(0) != 0 {
return 0;
}
NMATCHES_GLOBAL.load(Ordering::Relaxed) }
#[allow(unused_variables)]
pub fn get_listlines(pm: *mut param) -> i64 {
let _ = compresult::calclist(0); compcore::listdat
.get()
.and_then(|m| m.lock().ok().map(|g| g.nlines as i64))
.unwrap_or_else(|| COMPLISTLINES.load(Ordering::Relaxed))
}
#[allow(unused_variables)]
pub fn set_complist(pm: *mut param, v: &str) {
compresult::comp_list(Some(v)); }
#[allow(unused_variables)]
pub fn get_complist(pm: *mut param) -> 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: *mut param) -> 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.as_ref())))
.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: *mut param) -> i64 {
let prefix = get_unambig(std::ptr::null_mut());
prefix.chars().count() as i64
}
#[allow(non_camel_case_types)]
pub struct compparam {
pub name: &'static str, pub r#type: i32, pub var: usize, pub gsu: usize, }
const COMPRPARAMS: &[compparam] = &[
compparam {
name: "words",
r#type: PM_ARRAY as i32,
var: 0,
gsu: 0,
},
compparam {
name: "redirections",
r#type: PM_ARRAY as i32,
var: 0,
gsu: 0,
},
compparam {
name: "CURRENT",
r#type: PM_INTEGER as i32,
var: 0,
gsu: 0,
},
compparam {
name: "PREFIX",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "SUFFIX",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "IPREFIX",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "ISUFFIX",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "QIPREFIX",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "QISUFFIX",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
];
const COMPKPARAMS: &[compparam] = &[
compparam {
name: "nmatches",
r#type: (PM_INTEGER | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "context",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "parameter",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "redirect",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "quote",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "quoting",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "restore",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "list",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "insert",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "exact",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "exact_string",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "pattern_match",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "pattern_insert",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "unambiguous",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "unambiguous_cursor",
r#type: (PM_INTEGER | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "unambiguous_positions",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "insert_positions",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "list_max",
r#type: PM_INTEGER as i32,
var: 0,
gsu: 0,
},
compparam {
name: "last_prompt",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "to_end",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "old_list",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "old_insert",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "vared",
r#type: PM_SCALAR as i32,
var: 0,
gsu: 0,
},
compparam {
name: "list_lines",
r#type: (PM_INTEGER | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "all_quotes",
r#type: (PM_SCALAR | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
compparam {
name: "ignored",
r#type: (PM_INTEGER | PM_READONLY) as i32,
var: 0,
gsu: 0,
},
];
#[allow(unused_variables)]
pub fn get_unambig_pos(pm: *mut param) -> 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.as_ref())))
.filter(|s| !s.is_empty())
{
return format!("{}", s.chars().count());
}
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: *mut param) -> String {
get_unambig_pos(std::ptr::null_mut())
}
#[allow(unused_variables)]
pub fn get_compqstack(pm: *mut param) -> 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 pm.is_null() {
return;
}
let name = unsafe { (*pm).node.nam.clone() };
if exp != 0 {
match PM_TYPE(unsafe { (*pm).node.flags } as u32) {
PM_SCALAR => unsafe {
(*pm).u_str = Some(String::new());
}, PM_ARRAY => unsafe {
(*pm).u_arr = Some(Vec::new());
}, PM_HASHED => unsafe {
(*pm).u_hash = None;
}, _ => {}
}
} else if PM_TYPE(unsafe { (*pm).node.flags } as u32) == PM_HASHED {
unsafe {
(*pm).u_hash = None;
} for entry in COMPKPARAMS {
if let Ok(mut tab) = paramtab().write() {
if let Some(p) = tab.get_mut(entry.name) {
p.node.flags |= PM_UNSET as i32;
}
}
}
}
for entry in COMPRPARAMS {
if entry.name == name {
if let Ok(mut tab) = paramtab().write() {
if let Some(p) = tab.get_mut(entry.name) {
p.node.flags |= PM_UNSET as i32;
}
}
break;
}
}
}
pub fn comp_setunset(
mut rset: i32,
mut runset: i32, mut kset: i32,
mut kunset: i32,
) {
if rset >= 0 || runset >= 0 {
for entry in COMPRPARAMS {
if rset != 0 || runset != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(p) = tab.get_mut(entry.name) {
if rset & 1 != 0 {
p.node.flags &= !(PM_UNSET as i32); }
if runset & 1 != 0 {
p.node.flags |= PM_UNSET as i32; }
}
}
rset >>= 1;
runset >>= 1;
} else {
break;
}
}
}
if kset >= 0 || kunset >= 0 {
for entry in COMPKPARAMS {
if kset != 0 || kunset != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(p) = tab.get_mut(entry.name) {
if kset & 1 != 0 {
p.node.flags &= !(PM_UNSET as i32);
}
if kunset & 1 != 0 {
p.node.flags |= PM_UNSET as i32;
}
}
}
kset >>= 1;
kunset >>= 1;
} else {
break;
}
}
}
}
pub fn comp_wrapper(
_prog: *const eprog, _w: *const funcwrap,
name: &str,
) -> i32 {
use std::sync::atomic::Ordering;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 {
return 1; }
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 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 oqs = snap(&COMPQSTACK); let owords = COMPWORDS
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|v| v.clone())
.unwrap_or_default();
let _ = compcore::shfunc_call(name);
let comprestore_val =
getsparam("comprestore").unwrap_or_else(|| "auto".to_string());
if comprestore_val == "auto" {
restore(&COMPPREFIX, opre);
restore(&COMPSUFFIX, osuf);
restore(&COMPIPREFIX, oipre);
restore(&COMPISUFFIX, oisuf);
restore(&COMPQIPREFIX, oqipre);
restore(&COMPQISUFFIX, oqisuf);
restore(&COMPQUOTE, oq);
restore(&COMPQSTACK, oqs);
if let Ok(mut g) = COMPWORDS.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = owords;
}
}
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 {
let _ = a;
return 0;
}
0 }
pub fn cond_range(a: &[String], id: i32) -> i32 {
let sa = a.first().map(|s| s.as_str()).unwrap_or(""); let sb = if id != 0 {
a.get(1).map(|s| s.as_str()).unwrap_or("")
}
else {
""
};
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);
crate::ported::zle::zle_tricky::COMPQUOTE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.ok()
.map(|mut s| s.clear());
clear(&COMPLIST);
clear(&COMPQSTACK);
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();
}
0 }
#[allow(unused_variables)]
pub fn features_(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn enables_(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
let _ = crate::ported::module::addhookfunc("list_matches", list_matches_hook);
let _ = crate::ported::module::addhookfunc("invalidate_list", invalidate_list_hook);
0 }
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);
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(&COMPLIST); if let Ok(mut g) = COMPWORDS.get_or_init(|| Mutex::new(Vec::new())).lock()
{
g.clear();
}
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 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, 3); assert_eq!(cm.ralen, 2); assert_eq!(cm.wlen, 3); assert!(cm.left.is_some());
assert!(cm.right.is_some());
}
#[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");
}
}