use std::collections::HashMap;
use crate::ported::utils::{quotedzputs, zwarnnam};
use crate::ported::zle::complete::INCOMPFUNC;
use crate::ported::zle::complete::COMPQSTACK;
use crate::ported::zsh_h::OPT_ISSET;
#[allow(unused_imports)]
#[allow(unused_imports)]
use crate::ported::zle::zle_main::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_misc::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_hist::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_move::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_word::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_params::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_vi::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_utils::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_refresh::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_tricky::*;
#[allow(unused_imports)]
use crate::ported::zle::textobjects::*;
#[allow(unused_imports)]
use crate::ported::zle::deltochar::*;
pub const CRT_SIMPLE: i32 = 0; pub const CRT_DESC: i32 = 1; pub const CRT_SPEC: i32 = 2; pub const CRT_DUMMY: i32 = 3; pub const CRT_EXPL: i32 = 4;
pub const CDF_SEP: i32 = 1;
pub const CAO_NEXT: i32 = 1; pub const CAO_DIRECT: i32 = 2; pub const CAO_ODIRECT: i32 = 3; pub const CAO_EQUAL: i32 = 4; pub const CAO_OEQUAL: i32 = 5;
pub const CAA_NORMAL: i32 = 1; pub const CAA_OPT: i32 = 2; pub const CAA_REST: i32 = 3; pub const CAA_RARGS: i32 = 4; pub const CAA_RREST: i32 = 5;
pub const MAX_CACACHE: usize = 8;
pub const CVV_NOARG: i32 = 0; pub const CVV_ARG: i32 = 1; pub const CVV_OPT: i32 = 2;
pub const MAX_CVCACHE: usize = 8;
pub const MAX_TAGS: usize = 256;
pub const PATH_MAX2: usize = 8192;
pub type Cdset = Box<cdset>; pub type Cdstr = Box<cdstr>; pub type Cdrun = Box<cdrun>;
#[derive(Debug, Default, Clone)]
#[allow(non_camel_case_types)]
pub struct cdstr { pub next: Option<Box<cdstr>>, pub str: Option<String>, pub desc: Option<String>, pub r#match: Option<String>, pub sortstr: Option<String>, pub len: i32, pub width: i32, pub other: Option<Box<cdstr>>, pub kind: i32, pub set: usize, pub run: Option<Box<cdstr>>, }
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct cdrun { pub next: Option<Box<cdrun>>, pub r#type: i32, pub strs: Option<Box<cdstr>>, pub count: i32, }
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct cdset { pub next: Option<Box<cdset>>, pub opts: Option<Vec<String>>, pub strs: Option<Box<cdstr>>, pub count: i32, pub desc: i32, }
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct cdstate { pub showd: i32, pub sep: Option<String>, pub slen: i32, pub swidth: i32, pub maxmlen: i32, pub sets: Option<Box<cdset>>, pub pre: i32, pub premaxw: i32, pub suf: i32, pub maxg: i32, pub maxglen: i32, pub groups: i32, pub descs: i32, pub gprew: i32, pub runs: Option<Box<cdrun>>, }
pub static cd_state: std::sync::Mutex<cdstate> = std::sync::Mutex::new(cdstate {
showd: 0, sep: None, slen: 0, swidth: 0, maxmlen: 0,
sets: None, pre: 0, premaxw: 0, suf: 0, maxg: 0, maxglen: 0,
groups: 0, descs: 0, gprew: 0, runs: None,
});
pub static cd_parsed: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub fn cd_calc() { let mut st = cd_state.lock().unwrap();
st.pre = 0; st.suf = 0;
let mut max_pre = 0i32;
let mut max_premaxw = st.premaxw;
let mut max_suf = 0i32;
let mut set = st.sets.as_deref_mut();
while let Some(s) = set {
s.count = 0; s.desc = 0;
let mut str_iter = s.strs.as_deref();
while let Some(st_node) = str_iter {
s.count += 1; let str_s = st_node.str.as_deref().unwrap_or("");
let l = str_s.len() as i32;
if l > max_pre { max_pre = l; } let nw = crate::ported::utils::niceztrlen(str_s) as i32;
if nw > max_premaxw { max_premaxw = nw; }
if let Some(d) = st_node.desc.as_deref() { s.desc += 1;
let dl = d.len() as i32;
if dl > max_suf { max_suf = dl; } }
str_iter = st_node.next.as_deref();
}
set = s.next.as_deref_mut();
}
st.pre = max_pre;
st.premaxw = max_premaxw;
st.suf = max_suf;
}
pub fn cd_group(maxg: i32) { let mut st = cd_state.lock().unwrap();
st.groups = 0; st.descs = 0;
st.maxglen = 0;
st.maxg = 0;
let st_ptr: *mut cdstate = &mut *st;
unsafe {
let mut set = (*st_ptr).sets.as_deref_mut();
while let Some(s) = set {
let mut sp = s.strs.as_deref_mut();
while let Some(sn) = sp {
sn.kind = 0;
sn.other = None;
sp = sn.next.as_deref_mut();
}
set = s.next.as_deref_mut();
}
let mut set1 = (*st_ptr).sets.as_deref_mut();
while let Some(s1) = set1 {
let s1_ptr: *mut cdset = s1;
let mut str1 = (*s1_ptr).strs.as_deref_mut();
while let Some(t1) = str1 {
if t1.desc.is_none() || t1.kind != 0 { str1 = t1.next.as_deref_mut();
continue;
}
let mut num = 1i32; let mut width = t1.width + (*st_ptr).swidth; if width > (*st_ptr).maxglen {
(*st_ptr).maxglen = width;
}
let t1_desc = t1.desc.clone().unwrap_or_default();
let mut other_tail: *mut Option<Box<cdstr>> = &mut t1.other;
let mut hit_break = false;
let mut set2 = Some(&mut *s1_ptr);
let mut first_iter = true;
while let Some(s2) = set2 {
let s2_ptr: *mut cdset = s2;
let mut str2 = if first_iter {
t1.next.as_deref_mut()
} else {
(*s2_ptr).strs.as_deref_mut()
};
first_iter = false;
while let Some(t2) = str2 {
if t2.desc.as_deref() == Some(t1_desc.as_str()) {
width += CM_SPACE + t2.width; if width > (*st_ptr).maxmlen || num == maxg { hit_break = true;
break;
}
if width > (*st_ptr).maxglen { (*st_ptr).maxglen = width;
}
t1.kind = 1; t2.kind = 2;
num += 1;
let clone = Box::new(cdstr {
next: None,
str: t2.str.clone(),
desc: t2.desc.clone(),
r#match: t2.r#match.clone(),
sortstr: t2.sortstr.clone(),
len: t2.len,
width: t2.width,
other: None,
kind: t2.kind,
set: t2.set,
run: None,
});
*other_tail = Some(clone);
let nxt = &mut (*other_tail).as_mut().unwrap().other;
other_tail = nxt as *mut _;
}
str2 = t2.next.as_deref_mut();
}
if hit_break { break; }
set2 = (*s2_ptr).next.as_deref_mut();
}
if num > 1 { (*st_ptr).groups += 1;
} else {
(*st_ptr).descs += 1; }
if num > (*st_ptr).maxg { (*st_ptr).maxg = num;
}
str1 = t1.next.as_deref_mut();
}
set1 = s1.next.as_deref_mut();
}
}
}
pub const CM_SPACE: i32 = 2;
pub fn cd_sort(a: &cdstr, b: &cdstr) -> std::cmp::Ordering { crate::ported::sort::zstrcmp(
a.sortstr.as_deref().unwrap_or(""),
b.sortstr.as_deref().unwrap_or(""),
0,
)
}
pub fn cd_groups_want_sorting() -> i32 { let st = cd_state.lock().unwrap();
let mut set = st.sets.as_deref();
while let Some(s) = set {
if let Some(opts) = s.opts.as_deref() {
for o in opts {
if o.starts_with("-V") { return 0; } if o.starts_with("-J") { return 1; } }
}
set = s.next.as_deref();
}
1 }
pub fn cd_prep() -> i32 {
let mut new_runs: Vec<Box<cdrun>> = Vec::new();
let mut st = cd_state.lock().unwrap();
st.runs = None;
if st.groups != 0 {
let maxg = st.maxg.max(1) as usize;
let maxmlen = st.maxmlen;
let maxglen = st.maxglen;
let swidth = st.swidth;
let mut wids: Vec<i32> = vec![0; maxg];
let mut prep_lines: Vec<Box<cdstr>> = Vec::new();
let mut set = st.sets.as_deref();
while let Some(s) = set {
let mut str_iter = s.strs.as_deref();
while let Some(node) = str_iter {
if node.kind != 1 {
if node.kind == 0 && node.desc.is_some() { if node.width > wids[0] { wids[0] = node.width;
}
let mut clone = Box::new({
let n = node;
cdstr {
next: None, str: n.str.clone(), desc: n.desc.clone(),
r#match: n.r#match.clone(), sortstr: n.sortstr.clone(),
len: n.len, width: n.width, other: None,
kind: n.kind, set: n.set, run: None,
}
});
clone.other = None; prep_lines.push(clone);
}
str_iter = node.next.as_deref();
continue;
}
let mut gs = Box::new({
let n = node;
cdstr {
next: None, str: n.str.clone(), desc: n.desc.clone(),
r#match: n.r#match.clone(), sortstr: n.sortstr.clone(),
len: n.len, width: n.width, other: None,
kind: n.kind, set: n.set, run: None,
}
});
gs.kind = 2; gs.other = None;
let mut gp = node.other.as_deref();
while let Some(g_node) = gp {
let new_clone = Box::new({
let n = g_node;
cdstr {
next: None, str: n.str.clone(), desc: n.desc.clone(),
r#match: n.r#match.clone(), sortstr: n.sortstr.clone(),
len: n.len, width: n.width, other: None,
kind: n.kind, set: n.set, run: None,
}
});
let mut chain: Vec<Box<cdstr>> = Vec::new();
chain.push(Box::new(cdstr {
next: None, str: gs.str.clone(), desc: gs.desc.clone(),
r#match: gs.r#match.clone(), sortstr: gs.sortstr.clone(),
len: gs.len, width: gs.width, other: None,
kind: gs.kind, set: gs.set, run: None,
}));
let mut rest = gs.other.take();
while let Some(mut n) = rest {
rest = n.other.take();
chain.push(n);
}
let mut ins = chain.len();
for (i, c) in chain.iter().enumerate() {
if c.width <= new_clone.width {
ins = i;
break;
}
}
chain.insert(ins, new_clone);
let mut new_head = chain.remove(0);
let mut tail_ptr: *mut Option<Box<cdstr>> = &mut new_head.other;
for entry in chain {
unsafe {
*tail_ptr = Some(entry);
let nxt = &mut (*tail_ptr).as_mut().unwrap().other;
tail_ptr = nxt as *mut _;
}
}
gs = new_head;
gp = g_node.other.as_deref();
}
let mut col = 0usize;
let mut walker = Some(gs.as_ref());
while let Some(g) = walker {
if col < wids.len() && g.width > wids[col] {
wids[col] = g.width;
}
col += 1;
walker = g.other.as_deref();
}
prep_lines.push(gs);
str_iter = node.next.as_deref();
}
set = s.next.as_deref();
}
let mut gprew = 0i32;
for w in &wids {
gprew += w + CM_SPACE;
}
st.gprew = gprew;
if gprew > maxmlen && maxglen > 1 {
let _ = swidth;
return 1;
}
for line in prep_lines.iter_mut() {
let s = line.str.clone().unwrap_or_default();
line.sortstr = Some(crate::ported::zle::zle_utils::unmetafy(&s));
}
let want_sort = {
drop(st);
let r = cd_groups_want_sorting();
st = cd_state.lock().unwrap();
r
};
if want_sort != 0 { prep_lines.sort_by(|a, b| cd_sort(a, b)); }
let mut i = 0usize;
while i + 1 < prep_lines.len() {
let strp_desc = prep_lines[i].desc.clone().unwrap_or_default();
let next_desc = prep_lines[i + 1].desc.clone().unwrap_or_default();
if strp_desc == next_desc {
i += 1;
continue;
}
let mut found: Option<usize> = None;
for j in i + 2..prep_lines.len() {
if prep_lines[j].desc.clone().unwrap_or_default() == strp_desc {
found = Some(j);
break;
}
}
if let Some(j) = found {
let entry = prep_lines.remove(j);
prep_lines.insert(i + 1, entry);
}
i += 1;
}
let preplines = prep_lines.len();
if preplines > 0 {
let mut expl_head: Option<Box<cdstr>> = None;
let mut tail_ptr: *mut Option<Box<cdstr>> = &mut expl_head;
for line in &prep_lines {
let header = Box::new(cdstr {
next: None,
str: line.str.clone(),
desc: line.desc.clone(),
r#match: line.r#match.clone(),
sortstr: line.sortstr.clone(),
len: line.len,
width: line.width,
other: None,
kind: line.kind,
set: line.set,
run: None,
});
unsafe {
*tail_ptr = Some(header);
let nxt = &mut (*tail_ptr).as_mut().unwrap().run;
tail_ptr = nxt as *mut _;
}
}
let expl_run = Box::new(cdrun {
next: None,
r#type: CRT_EXPL,
strs: expl_head,
count: preplines as i32,
});
let mut grps: Vec<Option<Box<cdstr>>> = prep_lines
.into_iter()
.map(Some)
.collect();
for line_opt in grps.iter_mut() {
if let Some(line) = line_opt.take() {
let mut owned = *line;
let next_col = owned.other.take();
owned.run = None;
let spec_run = Box::new(cdrun {
next: None,
r#type: CRT_SPEC,
strs: Some(Box::new(owned)),
count: 1,
});
new_runs.push(spec_run);
*line_opt = next_col;
}
}
for _col in 1..maxg {
let mut dummy_count = 0i32;
for line_opt in grps.iter_mut() {
if let Some(line) = line_opt.take() {
if dummy_count > 0 {
new_runs.push(Box::new(cdrun {
next: None,
r#type: CRT_DUMMY,
strs: None,
count: dummy_count,
}));
dummy_count = 0;
}
let mut owned = *line;
let next_col = owned.other.take();
owned.run = None;
new_runs.push(Box::new(cdrun {
next: None,
r#type: CRT_SPEC,
strs: Some(Box::new(owned)),
count: 1,
}));
*line_opt = next_col;
} else {
dummy_count += 1;
}
}
if dummy_count > 0 { new_runs.push(Box::new(cdrun {
next: None,
r#type: CRT_DUMMY,
strs: None,
count: dummy_count,
}));
}
}
new_runs.push(expl_run);
}
let mut set = st.sets.as_deref();
while let Some(s) = set {
let mut head: Option<Box<cdstr>> = None;
let mut tail_ptr: *mut Option<Box<cdstr>> = &mut head;
let mut count = 0i32;
let mut str_iter = s.strs.as_deref();
while let Some(node) = str_iter {
if node.kind == 0 && node.desc.is_none() {
let clone = Box::new(cdstr {
next: None,
str: node.str.clone(),
desc: None,
r#match: node.r#match.clone(),
sortstr: node.sortstr.clone(),
len: node.len,
width: node.width,
other: None,
kind: 0,
set: node.set,
run: None,
});
unsafe {
*tail_ptr = Some(clone);
let nxt = &mut (*tail_ptr).as_mut().unwrap().run;
tail_ptr = nxt as *mut _;
}
count += 1;
}
str_iter = node.next.as_deref();
}
if count > 0 {
new_runs.push(Box::new(cdrun {
next: None,
r#type: CRT_SIMPLE,
strs: head,
count,
}));
}
set = s.next.as_deref();
}
} else if st.showd != 0 {
let mut set = st.sets.as_deref();
while let Some(s) = set {
if s.desc > 0 {
let mut head: Option<Box<cdstr>> = None;
let mut tail: *mut Option<Box<cdstr>> = &mut head;
let mut str_iter = s.strs.as_deref();
while let Some(st_node) = str_iter {
if st_node.desc.is_some() {
let clone = Box::new(cdstr {
next: None,
str: st_node.str.clone(),
desc: st_node.desc.clone(),
r#match: st_node.r#match.clone(),
sortstr: st_node.sortstr.clone(),
len: st_node.len,
width: st_node.width,
other: None,
kind: st_node.kind,
set: st_node.set,
run: None,
});
unsafe {
*tail = Some(clone);
let nxt = &mut (*tail).as_mut().unwrap().run;
tail = nxt as *mut _;
}
}
str_iter = st_node.next.as_deref();
}
new_runs.push(Box::new(cdrun {
next: None, r#type: CRT_DESC,
strs: head, count: s.desc,
}));
}
if s.desc != s.count {
let mut head: Option<Box<cdstr>> = None;
let mut tail: *mut Option<Box<cdstr>> = &mut head;
let mut str_iter = s.strs.as_deref();
while let Some(st_node) = str_iter {
if st_node.desc.is_none() {
let clone = Box::new(cdstr {
next: None,
str: st_node.str.clone(),
desc: st_node.desc.clone(),
r#match: st_node.r#match.clone(),
sortstr: st_node.sortstr.clone(),
len: st_node.len,
width: st_node.width,
other: None,
kind: st_node.kind,
set: st_node.set,
run: None,
});
unsafe {
*tail = Some(clone);
let nxt = &mut (*tail).as_mut().unwrap().run;
tail = nxt as *mut _;
}
}
str_iter = st_node.next.as_deref();
}
new_runs.push(Box::new(cdrun {
next: None, r#type: CRT_SIMPLE,
strs: head, count: s.count - s.desc,
}));
}
set = s.next.as_deref();
}
} else {
let mut set = st.sets.as_deref();
while let Some(s) = set {
if s.count != 0 {
let mut head: Option<Box<cdstr>> = None;
let mut tail: *mut Option<Box<cdstr>> = &mut head;
let mut str_iter = s.strs.as_deref();
while let Some(st_node) = str_iter {
let clone = Box::new(cdstr {
next: None,
str: st_node.str.clone(),
desc: st_node.desc.clone(),
r#match: st_node.r#match.clone(),
sortstr: st_node.sortstr.clone(),
len: st_node.len,
width: st_node.width,
other: None,
kind: st_node.kind,
set: st_node.set,
run: None,
});
unsafe {
*tail = Some(clone);
let nxt = &mut (*tail).as_mut().unwrap().run;
tail = nxt as *mut _;
}
str_iter = st_node.next.as_deref();
}
new_runs.push(Box::new(cdrun {
next: None, r#type: CRT_SIMPLE,
strs: head, count: s.count,
}));
}
set = s.next.as_deref();
}
}
let mut head: Option<Box<cdrun>> = None;
for run in new_runs.into_iter().rev() {
let mut run = run;
run.next = head;
head = Some(run);
}
st.runs = head;
0 }
pub fn cd_init(nam: &str, hide: &str, mlen: &str, sep: &str, opts: &[String], args: &[String], disp: i32) -> i32 {
use crate::ported::zle::compcore::{get_user_var, rembslash};
if cd_parsed.load(std::sync::atomic::Ordering::Relaxed) != 0 {
let mut st = cd_state.lock().unwrap();
st.sep = None;
freecdsets(st.sets.take());
cd_parsed.store(0, std::sync::atomic::Ordering::Relaxed);
}
{
let mut st = cd_state.lock().unwrap();
st.sep = Some(sep.to_string());
st.slen = sep.len() as i32;
st.swidth = crate::ported::utils::niceztrlen(sep) as i32;
st.sets = None;
st.showd = disp;
st.maxg = 0;
st.groups = 0;
st.descs = 0;
st.maxmlen = mlen.parse::<i32>().unwrap_or(0);
st.premaxw = 0;
let cols = crate::ported::utils::adjustcolumns() as i32;
let itmp = cols - st.swidth - 4; if st.maxmlen > itmp { st.maxmlen = itmp; }
if st.maxmlen < 4 { st.maxmlen = 4; }
}
let mut idx = 0usize;
let grp = if args.first().map(|s| s.as_str()) == Some("-g") {
idx = 1;
true
} else {
false
};
let mut sets_collected: Vec<Box<cdset>> = Vec::new();
while idx < args.len() {
let arg = &args[idx];
let Some(mat_arr) = get_user_var(Some(arg.as_str())) else { zwarnnam(nam, &format!("invalid argument: {}", arg));
let mut st = cd_state.lock().unwrap();
st.sep = None;
freecdsets(st.sets.take());
return 1;
};
idx += 1;
let mut strs_vec: Vec<Box<cdstr>> = Vec::new();
for entry in &mat_arr {
let bytes = entry.as_bytes();
let mut p = 0usize;
while p < bytes.len() && bytes[p] != b':' { if bytes[p] == b'\\' && p + 1 < bytes.len() { p += 1; }
p += 1;
}
let (match_part, desc_part) = if p < bytes.len() {
let m = std::str::from_utf8(&bytes[..p]).unwrap_or("");
let d = std::str::from_utf8(&bytes[p + 1..]).unwrap_or("");
(rembslash(m), Some(rembslash(d)))
} else {
(rembslash(entry), None)
};
let str_s = match_part.clone();
let mut new_str = Box::new(cdstr::default());
new_str.str = Some(str_s.clone());
new_str.r#match = Some(str_s.clone());
new_str.desc = desc_part;
new_str.len = str_s.len() as i32;
new_str.width = crate::ported::utils::niceztrlen(&str_s) as i32;
new_str.kind = 0;
strs_vec.push(new_str);
}
if idx < args.len() && !args[idx].starts_with('-') {
let Some(match_arr) = get_user_var(Some(args[idx].as_str())) else {
zwarnnam(nam, &format!("invalid argument: {}", args[idx]));
let mut st = cd_state.lock().unwrap();
st.sep = None;
freecdsets(st.sets.take());
return 1;
};
for (i, m) in match_arr.iter().enumerate() {
if i < strs_vec.len() {
strs_vec[i].r#match = Some(m.clone());
}
}
idx += 1;
}
if !hide.is_empty() {
let hb = hide.as_bytes();
let double = hb.len() > 1;
for s in strs_vec.iter_mut() {
if let Some(cur) = s.str.clone() {
let mut bytes = cur.into_bytes();
if double && bytes.len() >= 2 && bytes[0] == b'-' && bytes[1] == b'-' {
bytes.drain(0..2); } else if !bytes.is_empty() && (bytes[0] == b'-' || bytes[0] == b'+') {
bytes.drain(0..1); }
s.str = String::from_utf8(bytes).ok();
}
}
}
let opt_start = idx;
while idx < args.len()
&& !(args[idx].as_bytes().len() == 2
&& args[idx].as_bytes()[0] == b'-'
&& args[idx].as_bytes()[1] == b'-')
{
idx += 1;
}
let per_set: &[String] = &args[opt_start..idx];
let combined = cd_arrcat(per_set, opts);
if idx < args.len() { idx += 1; }
let mut strs_head: Option<Box<cdstr>> = None;
for s in strs_vec.into_iter().rev() {
let mut s = s;
s.next = strs_head;
strs_head = Some(s);
}
let mut set = Box::new(cdset::default());
set.opts = Some(combined);
set.strs = strs_head;
sets_collected.push(set);
}
{
let mut head: Option<Box<cdset>> = None;
for s in sets_collected.into_iter().rev() {
let mut s = s;
s.next = head;
head = Some(s);
}
cd_state.lock().unwrap().sets = head;
}
if disp != 0 && grp {
let cols = crate::ported::utils::adjustcolumns() as i32;
let mut mg = cols;
loop {
cd_group(mg);
mg = {
let st = cd_state.lock().unwrap();
st.maxg - 1
};
cd_calc();
if cd_prep() == 0 || mg <= 0 { break; }
}
} else {
cd_calc();
cd_prep();
}
cd_parsed.store(1, std::sync::atomic::Ordering::Relaxed); 0
}
pub fn cd_get(params: &[String]) -> i32 { use crate::ported::params::{setsparam, setaparam};
let run_opt = {
let mut st = cd_state.lock().unwrap();
st.runs.take().map(|mut r| {
let next = r.next.take();
st.runs = next;
r
})
};
let Some(run) = run_opt else { return 1; };
let mut mats: Vec<String> = Vec::new();
let mut dpys: Vec<String> = Vec::new();
let mut opts: Vec<String> = Vec::new();
let mut csl: String = String::new();
let rtype = run.r#type;
let mut walk_run = |head: &Option<Box<cdstr>>, mut f: Box<dyn FnMut(&cdstr)>| {
let mut cur = head.as_deref();
while let Some(s) = cur {
f(s);
cur = s.run.as_deref();
}
};
if rtype == CRT_SIMPLE { let head_opts = run.strs.as_deref()
.map(|s| {
let st = cd_state.lock().unwrap();
let mut set_iter = st.sets.as_deref();
let mut found: Option<Vec<String>> = None;
let mut idx_count = 0usize;
while let Some(set) = set_iter {
if idx_count == s.set {
found = set.opts.clone();
break;
}
idx_count += 1;
set_iter = set.next.as_deref();
}
found.unwrap_or_default()
})
.unwrap_or_default();
walk_run(&run.strs, Box::new(|s| { mats.push(s.r#match.clone().unwrap_or_default());
dpys.push(s.str.clone().or_else(|| s.r#match.clone()).unwrap_or_default());
}));
let groups_flag = cd_state.lock().unwrap().groups;
opts = if groups_flag != 0 { let mut filtered: Vec<String> = Vec::new();
let mut skip_next = false;
for o in head_opts.iter() {
if skip_next { skip_next = false; continue; }
if o.starts_with("-X") { if o.len() == 2 { skip_next = true; } continue;
}
filtered.push(o.clone());
}
filtered
} else {
head_opts
};
} else if rtype == CRT_DESC { let st_snapshot = {
let st = cd_state.lock().unwrap();
(st.pre, st.suf, st.premaxw, st.slen, st.swidth, st.sep.clone(),
crate::ported::utils::adjustcolumns() as i32)
};
let (cd_pre, _cd_suf, cd_premaxw, _cd_slen, cd_swidth, cd_sep, cols) = st_snapshot;
let sep_str = cd_sep.unwrap_or_default();
walk_run(&run.strs, Box::new(|s| { let str_s = s.str.clone().unwrap_or_default();
let desc_s = s.desc.clone().unwrap_or_default();
let mut buf = String::with_capacity(
(cd_pre + cd_premaxw + cd_swidth + 16) as usize);
buf.push_str(&str_s);
let pad = (cd_premaxw - s.width + CM_SPACE).max(0) as usize;
for _ in 0..pad { buf.push(' '); }
let mut remw = cols - cd_premaxw - cd_swidth - 3;
while remw < 0 && cols > 0 { remw += cols; }
if (sep_str.len() as i32) < remw { buf.push_str(&sep_str);
remw -= sep_str.len() as i32;
let dw = crate::ported::utils::niceztrlen(&desc_s) as i32;
if dw <= remw {
buf.push_str(&desc_s);
} else { let mut w_used = 0i32;
for ch in desc_s.chars() {
let cw = crate::ported::utils::niceztrlen(&ch.to_string()) as i32;
if w_used + cw > remw { break; }
buf.push(ch);
w_used += cw;
}
}
}
mats.push(s.r#match.clone().unwrap_or_default()); dpys.push(buf);
}));
let head_opts = run.strs.as_deref()
.map(|s| {
let st = cd_state.lock().unwrap();
let mut set_iter = st.sets.as_deref();
let mut found: Option<Vec<String>> = None;
let mut idx_count = 0usize;
while let Some(set) = set_iter {
if idx_count == s.set { found = set.opts.clone(); break; }
idx_count += 1;
set_iter = set.next.as_deref();
}
found.unwrap_or_default()
})
.unwrap_or_default();
opts = std::iter::once("-l".to_string()).chain(head_opts).collect();
} else if rtype == CRT_SPEC { let s = run.strs.as_deref();
if let Some(s) = s {
mats.push(s.r#match.clone().unwrap_or_default());
dpys.push(s.str.clone().unwrap_or_default());
}
let head_opts = s.map(|s| {
let st = cd_state.lock().unwrap();
let mut set_iter = st.sets.as_deref();
let mut found: Option<Vec<String>> = None;
let mut idx_count = 0usize;
while let Some(set) = set_iter {
if idx_count == s.set { found = set.opts.clone(); break; }
idx_count += 1;
set_iter = set.next.as_deref();
}
found.unwrap_or_default()
}).unwrap_or_default();
let mut new_opts: Vec<String> = head_opts.clone();
let mut found_jv = false;
for i in 1..new_opts.len() {
if new_opts[i].starts_with("-J") || new_opts[i].starts_with("-V") {
let rest = new_opts[i][2..].to_string();
new_opts[i] = format!("-2V{}", rest);
found_jv = true;
break;
}
}
if !found_jv {
new_opts.insert(0, "-2V-default-".to_string()); }
opts = new_opts;
csl = "packed".to_string();
} else if rtype == CRT_DUMMY { let head_opts = run.strs.as_deref().map(|s| {
let st = cd_state.lock().unwrap();
let mut set_iter = st.sets.as_deref();
let mut found: Option<Vec<String>> = None;
let mut idx_count = 0usize;
while let Some(set) = set_iter {
if idx_count == s.set { found = set.opts.clone(); break; }
idx_count += 1;
set_iter = set.next.as_deref();
}
found.unwrap_or_default()
}).unwrap_or_default();
opts = std::iter::once(format!("-E{}", run.count))
.chain(head_opts).collect();
csl = "packed".to_string();
} else if rtype == CRT_EXPL { let st_snapshot = {
let st = cd_state.lock().unwrap();
(st.suf, st.slen, st.swidth, st.gprew, st.sep.clone(),
crate::ported::utils::adjustcolumns() as i32)
};
let (_cd_suf, _cd_slen, cd_swidth, cd_gprew, cd_sep, cols) = st_snapshot;
let sep_str = cd_sep.unwrap_or_default();
let count = run.count;
walk_run(&run.strs, Box::new(|s| { let next_desc = s.run.as_deref().and_then(|n| n.desc.clone());
if next_desc.is_some() && next_desc == s.desc {
dpys.push(String::new());
return;
}
let mut buf = String::new();
buf.push_str(&sep_str);
let mut remw = cols - cd_gprew - cd_swidth - CM_SPACE;
let desc_s = s.desc.clone().unwrap_or_default();
let dw = crate::ported::utils::niceztrlen(&desc_s) as i32;
if dw <= remw { buf.push_str(&desc_s);
remw -= dw;
} else {
for ch in desc_s.chars() {
let cw = crate::ported::utils::niceztrlen(&ch.to_string()) as i32;
if cw > remw { break; }
buf.push(ch);
remw -= cw;
}
}
while remw > 0 { buf.push(' ');
remw -= 1;
}
dpys.push(buf);
}));
let head_opts = run.strs.as_deref().map(|s| {
let st = cd_state.lock().unwrap();
let mut set_iter = st.sets.as_deref();
let mut found: Option<Vec<String>> = None;
let mut idx_count = 0usize;
while let Some(set) = set_iter {
if idx_count == s.set { found = set.opts.clone(); break; }
idx_count += 1;
set_iter = set.next.as_deref();
}
found.unwrap_or_default()
}).unwrap_or_default();
opts = std::iter::once(format!("-E{}", count))
.chain(head_opts).collect();
csl = "packed".to_string();
}
if params.len() >= 4 {
setsparam(¶ms[0], &csl);
setaparam(¶ms[1], opts);
setaparam(¶ms[2], mats);
setaparam(¶ms[3], dpys);
}
0 }
pub fn cd_arrcat(a: &[String], b: &[String]) -> Vec<String> { let mut out = a.to_vec();
out.extend_from_slice(b);
out
}
pub fn cd_arrdup(a: &[String]) -> Vec<String> { a.to_vec()
}
pub fn freecdsets(mut p: Option<Box<cdset>>) { while let Some(mut set) = p { p = set.next.take(); set.opts = None;
let mut s = set.strs.take();
while let Some(mut node) = s {
s = node.next.take();
node.sortstr = None; node.str = None; node.desc = None; node.r#match = None;
drop(node); }
if let Ok(mut st) = cd_state.lock() {
let mut r = st.runs.take();
while let Some(mut run) = r {
r = run.next.take();
drop(run); }
}
drop(set); }
}
pub type Cadef = Box<cadef>; pub type Caopt = Box<caopt>; pub type Caarg = Box<caarg>;
#[derive(Debug, Default, Clone)]
#[allow(non_camel_case_types)]
pub struct caarg { pub next: Option<Box<caarg>>, pub descr: Option<String>, pub xor: Option<Vec<String>>, pub action: Option<String>, pub r#type: i32, pub end: Option<String>, pub opt: Option<String>, pub num: i32, pub min: i32, pub direct: i32, pub active: i32, pub gsname: Option<String>, }
#[derive(Debug, Default, Clone)]
#[allow(non_camel_case_types)]
pub struct caopt { pub next: Option<Box<caopt>>, pub name: Option<String>, pub descr: Option<String>, pub xor: Option<Vec<String>>, pub r#type: i32, pub args: Option<Box<caarg>>, pub active: i32, pub num: i32, pub gsname: Option<String>, pub not: i32, }
#[derive(Debug, Default, Clone)]
#[allow(non_camel_case_types)]
pub struct cadef { pub next: Option<Box<cadef>>, pub snext: Option<Box<cadef>>, pub opts: Option<Box<caopt>>, pub nopts: i32, pub ndopts: i32, pub nodopts: i32, pub args: Option<Box<caarg>>, pub rest: Option<Box<caarg>>, pub defs: Option<Vec<String>>, pub ndefs: i32, pub lastt: i64, pub single: Option<Vec<Option<Box<caopt>>>>, pub r#match: Option<String>, pub argsactive: i32, pub set: Option<String>, pub flags: i32, pub nonarg: Option<String>, }
pub fn freecaargs(mut a: Option<Box<caarg>>) { while let Some(mut node) = a { a = node.next.take(); node.descr = None; node.xor = None; node.action = None; node.end = None; node.opt = None; drop(node); }
}
pub fn freecadef(mut d: Option<Box<cadef>>) { while let Some(mut node) = d { d = node.snext.take(); node.r#match = None;
node.set = None;
node.defs = None;
let mut p = node.opts.take();
while let Some(mut popt) = p {
p = popt.next.take();
popt.name = None;
popt.descr = None;
popt.xor = None;
freecaargs(popt.args.take()); drop(popt); }
freecaargs(node.args.take()); freecaargs(node.rest.take()); node.nonarg = None; node.single = None; drop(node); }
}
pub type Castate = Box<castate>;
#[derive(Debug, Default, Clone)]
#[allow(non_camel_case_types)]
pub struct castate { pub snext: Option<Box<castate>>, pub d: Option<Box<cadef>>, pub nopts: i32, pub def: Option<Box<caarg>>, pub ddef: Option<Box<caarg>>, pub curopt: Option<Box<caopt>>, pub dopt: Option<Box<caopt>>, pub opt: i32, pub arg: i32, pub argbeg: i32, pub optbeg: i32, pub nargbeg: i32, pub restbeg: i32, pub curpos: i32, pub argend: i32, pub inopt: i32, pub inarg: i32, pub nth: i32, pub singles: i32, pub oopt: i32, pub actopts: i32, pub args: Option<Vec<String>>, pub oargs: Option<Vec<Option<Vec<String>>>>, }
pub static ca_laststate: std::sync::Mutex<castate> = std::sync::Mutex::new(castate {
snext: None, d: None, nopts: 0, def: None, ddef: None,
curopt: None, dopt: None, opt: 0, arg: 0, argbeg: 0, optbeg: 0,
nargbeg: 0, restbeg: 0, curpos: 0, argend: 0, inopt: 0,
inarg: 0, nth: 0, singles: 0, oopt: 0, actopts: 0,
args: None, oargs: None,
});
pub static ca_parsed: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ca_alloced: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ca_doff: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub fn freecastate(s: &mut castate) { s.args = None; s.oargs = None; }
pub type Cvdef = Box<cvdef>; pub type Cvval = Box<cvval>;
#[derive(Debug, Default, Clone)]
#[allow(non_camel_case_types)]
pub struct cvdef { pub descr: Option<String>, pub hassep: i32, pub sep: i32, pub argsep: i32, pub next: Option<Box<cvdef>>, pub vals: Option<Box<cvval>>, pub defs: Option<Vec<String>>, pub ndefs: i32, pub lastt: i64, pub words: i32, }
#[derive(Debug, Default, Clone)]
#[allow(non_camel_case_types)]
pub struct cvval { pub next: Option<Box<cvval>>, pub name: Option<String>, pub descr: Option<String>, pub xor: Option<Vec<String>>, pub r#type: i32, pub arg: Option<Box<caarg>>, pub active: i32, }
pub fn freecvdef(d: Option<Box<cvdef>>) { let Some(mut node) = d else { return; }; node.descr = None; node.defs = None; let mut p = node.vals.take();
while let Some(mut v) = p { p = v.next.take(); v.name = None; v.descr = None; v.xor = None; freecaargs(v.arg.take()); drop(v); }
drop(node); }
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct cvstate { pub d: Option<Box<cvdef>>, pub def: Option<Box<caarg>>, pub val: Option<Box<cvval>>, pub vals: Option<Vec<String>>, }
pub static cv_laststate: std::sync::Mutex<cvstate> = std::sync::Mutex::new(cvstate {
d: None, def: None, val: None, vals: None,
});
pub static cv_parsed: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static cv_alloced: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static cadef_cache: std::sync::Mutex<[Option<Box<cadef>>; MAX_CACACHE]> = std::sync::Mutex::new([const { None }; MAX_CACACHE]);
pub static cvdef_cache: std::sync::Mutex<[Option<Box<cvdef>>; MAX_CVCACHE]> = std::sync::Mutex::new([const { None }; MAX_CVCACHE]);
pub static comptags: std::sync::Mutex<[Option<Box<ctags>>; MAX_TAGS]> = std::sync::Mutex::new([const { None }; MAX_TAGS]);
pub static lasttaglevel: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub type Ctags = Box<ctags>; pub type Ctset = Box<ctset>;
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct ctags { pub all: Option<Vec<String>>, pub context: Option<String>, pub init: i32, pub sets: Option<Box<ctset>>, }
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct ctset { pub next: Option<Box<ctset>>, pub tags: Option<Vec<String>>, pub tag: Option<String>, pub ptr: i32, }
pub fn freectset(mut s: Option<Box<ctset>>) { while let Some(mut node) = s { s = node.next.take(); node.tags = None; node.tag = None; drop(node); }
}
pub fn freectags(t: Option<Box<ctags>>) { let Some(mut node) = t else { return; }; node.all = None; node.context = None; freectset(node.sets.take()); drop(node); }
pub fn rembslashcolon(s: &str) -> String { let bytes = s.as_bytes(); let mut out = Vec::<u8>::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() { let drop = bytes[i] == b'\\'
&& i + 1 < bytes.len()
&& bytes[i + 1] == b':';
if !drop {
out.push(bytes[i]); }
i += 1; }
String::from_utf8(out).unwrap_or_default() }
pub fn bslashcolon(s: &str) -> String { let bytes = s.as_bytes(); let mut out = Vec::<u8>::with_capacity(2 * bytes.len() + 1);
for &b in bytes { if b == b':' { out.push(b'\\'); }
out.push(b); }
String::from_utf8(out).unwrap_or_default() }
pub fn single_index(pre: u8, opt: u8) -> i32 { if opt <= 0x20 || opt > 0x7e { return -1; }
let off: i32 = if pre == b'-' { -0x21 } else { 94 - 0x21 };
(opt as i32) + off
}
#[cfg(test)]
mod cao_caa_tests {
use super::*;
#[test]
fn cao_values_match_c_source() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CAO_NEXT, 1);
assert_eq!(CAO_DIRECT, 2);
assert_eq!(CAO_ODIRECT, 3);
assert_eq!(CAO_EQUAL, 4);
assert_eq!(CAO_OEQUAL, 5);
}
#[test]
fn caa_values_match_c_source() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CAA_NORMAL, 1);
assert_eq!(CAA_OPT, 2);
assert_eq!(CAA_REST, 3);
assert_eq!(CAA_RARGS, 4);
assert_eq!(CAA_RREST, 5);
}
#[test]
fn crt_values_match_c_source() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CRT_SIMPLE, 0);
assert_eq!(CRT_DESC, 1);
assert_eq!(CRT_SPEC, 2);
assert_eq!(CRT_DUMMY, 3);
assert_eq!(CRT_EXPL, 4);
}
#[test]
fn cvv_values_match_c_source() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CVV_NOARG, 0);
assert_eq!(CVV_ARG, 1);
assert_eq!(CVV_OPT, 2);
}
#[test]
fn cache_sizes_are_8() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(MAX_CACACHE, 8);
assert_eq!(MAX_CVCACHE, 8);
}
#[test]
fn max_tags_is_256() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(MAX_TAGS, 256);
}
#[test]
fn path_max2_is_8192() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(PATH_MAX2, 8192);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rembslashcolon() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(rembslashcolon("a\\:b\\:c"), "a:b:c");
}
#[test]
fn test_rembslashcolon_lone_backslash_kept() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(rembslashcolon("a\\nb"), "a\\nb");
}
#[test]
fn test_rembslashcolon_trailing_backslash() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(rembslashcolon("a\\"), "a\\");
}
#[test]
fn test_rembslashcolon_unescaped_colon_passes_through() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(rembslashcolon("a:b"), "a:b");
}
#[test]
fn test_bslashcolon() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(bslashcolon("a:b:c"), "a\\:b\\:c");
}
#[test]
fn test_bslashcolon_no_colons() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(bslashcolon("hello"), "hello");
}
#[test]
fn test_bslashcolon_already_escaped_doubled() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(bslashcolon("a\\:b"), "a\\\\:b");
}
#[test]
fn test_single_index_dash_prefix() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(single_index(b'-', b'a'), 64);
assert_eq!(single_index(b'-', b'A'), 32);
assert_eq!(single_index(b'-', b'!'), 0);
assert_eq!(single_index(b'-', b'~'), 93);
}
#[test]
fn test_single_index_plus_prefix() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(single_index(b'+', b'a'), 158);
assert_eq!(single_index(b'+', b'!'), 94);
assert_eq!(single_index(b'+', b'~'), 187);
}
#[test]
fn test_single_index_out_of_range() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(single_index(b'-', 0x20), -1); assert_eq!(single_index(b'-', 0x00), -1); assert_eq!(single_index(b'-', 0x7f), -1); assert_eq!(single_index(b'+', 0xff), -1); }
#[test]
fn caarg_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let a = caarg::default();
assert!(a.next.is_none());
assert!(a.descr.is_none());
assert!(a.action.is_none());
assert_eq!(a.r#type, 0);
assert_eq!(a.num, 0);
assert_eq!(a.active, 0);
}
#[test]
fn caopt_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let o = caopt::default();
assert!(o.next.is_none());
assert!(o.name.is_none());
assert!(o.args.is_none());
assert_eq!(o.r#type, 0);
assert_eq!(o.num, 0);
assert_eq!(o.not, 0);
}
#[test]
fn cadef_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let d = cadef::default();
assert!(d.next.is_none());
assert!(d.opts.is_none());
assert!(d.args.is_none());
assert_eq!(d.nopts, 0);
assert_eq!(d.flags, 0);
}
#[test]
fn freecaargs_walks_chain() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut head = caarg { descr: Some("a".into()), ..Default::default() };
let mid = caarg { descr: Some("b".into()), ..Default::default() };
let tail = caarg { descr: Some("c".into()), ..Default::default() };
let mut mid_box = Box::new(mid);
mid_box.next = Some(Box::new(tail));
head.next = Some(mid_box);
freecaargs(Some(Box::new(head)));
}
#[test]
fn cao_caa_constants_match_c() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CAO_NEXT, 1);
assert_eq!(CAO_DIRECT, 2);
assert_eq!(CAO_ODIRECT, 3);
assert_eq!(CAO_EQUAL, 4);
assert_eq!(CAO_OEQUAL, 5);
assert_eq!(CAA_NORMAL, 1);
assert_eq!(CAA_OPT, 2);
assert_eq!(CAA_REST, 3);
assert_eq!(CAA_RARGS, 4);
assert_eq!(CAA_RREST, 5);
}
#[test]
fn cdf_max_cacache_constants_match_c() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CDF_SEP, 1);
assert_eq!(MAX_CACACHE, 8);
}
#[test]
fn crt_constants_match_c() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CRT_SIMPLE, 0);
assert_eq!(CRT_DESC, 1);
assert_eq!(CRT_SPEC, 2);
assert_eq!(CRT_DUMMY, 3);
assert_eq!(CRT_EXPL, 4);
}
#[test]
fn cdstr_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let s = cdstr::default();
assert!(s.next.is_none());
assert!(s.str.is_none());
assert!(s.desc.is_none());
assert!(s.r#match.is_none());
assert_eq!(s.len, 0);
assert_eq!(s.width, 0);
assert_eq!(s.kind, 0);
}
#[test]
fn cdrun_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = cdrun::default();
assert!(r.next.is_none());
assert!(r.strs.is_none());
assert_eq!(r.r#type, 0);
assert_eq!(r.count, 0);
}
#[test]
fn cdset_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let s = cdset::default();
assert!(s.next.is_none());
assert!(s.opts.is_none());
assert!(s.strs.is_none());
assert_eq!(s.count, 0);
assert_eq!(s.desc, 0);
}
#[test]
fn cdstate_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let st = cdstate::default();
assert_eq!(st.showd, 0);
assert!(st.sep.is_none());
assert!(st.sets.is_none());
assert!(st.runs.is_none());
}
#[test]
fn freecdsets_walks_chain() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let head_str = cdstr {
str: Some("foo".into()),
desc: Some("first".into()),
..Default::default()
};
let tail_str = cdstr {
str: Some("bar".into()),
..Default::default()
};
let mut head_str_b = Box::new(head_str);
head_str_b.next = Some(Box::new(tail_str));
let set = cdset {
strs: Some(head_str_b),
count: 2,
..Default::default()
};
freecdsets(Some(Box::new(set)));
}
#[test]
fn castate_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let s = castate::default();
assert!(s.snext.is_none());
assert!(s.d.is_none());
assert!(s.def.is_none());
assert!(s.args.is_none());
assert_eq!(s.nopts, 0);
assert_eq!(s.curpos, 0);
}
#[test]
fn cvdef_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let d = cvdef::default();
assert!(d.descr.is_none());
assert!(d.vals.is_none());
assert_eq!(d.hassep, 0);
assert_eq!(d.sep, 0);
assert_eq!(d.argsep, 0);
}
#[test]
fn cvval_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let v = cvval::default();
assert!(v.next.is_none());
assert!(v.name.is_none());
assert!(v.arg.is_none());
assert_eq!(v.r#type, 0);
assert_eq!(v.active, 0);
}
#[test]
fn cvstate_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let s = cvstate::default();
assert!(s.d.is_none());
assert!(s.def.is_none());
assert!(s.val.is_none());
assert!(s.vals.is_none());
}
#[test]
fn ctags_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let t = ctags::default();
assert!(t.all.is_none());
assert!(t.context.is_none());
assert!(t.sets.is_none());
assert_eq!(t.init, 0);
}
#[test]
fn ctset_default_zero_initialized() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let s = ctset::default();
assert!(s.next.is_none());
assert!(s.tags.is_none());
assert!(s.tag.is_none());
assert_eq!(s.ptr, 0);
}
#[test]
fn cvv_constants_match_c() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(CVV_NOARG, 0);
assert_eq!(CVV_ARG, 1);
assert_eq!(CVV_OPT, 2);
}
#[test]
fn max_tags_cvcache_match_c() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(MAX_TAGS, 256);
assert_eq!(MAX_CVCACHE, 8);
}
#[test]
fn freectset_walks_chain() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut head = ctset { tag: Some("foo".into()), ..Default::default() };
let tail = ctset { tag: Some("bar".into()), ..Default::default() };
head.next = Some(Box::new(tail));
freectset(Some(Box::new(head)));
}
#[test]
fn freectags_drops_one_node() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let t = ctags {
all: Some(vec!["a".into(), "b".into()]),
context: Some("ctx".into()),
..Default::default()
};
freectags(Some(Box::new(t)));
}
#[test]
fn freecvdef_walks_vals_chain() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let v_tail = cvval { name: Some("opt2".into()), ..Default::default() };
let mut v_head = cvval { name: Some("opt1".into()), ..Default::default() };
v_head.next = Some(Box::new(v_tail));
let d = cvdef {
descr: Some("test".into()),
vals: Some(Box::new(v_head)),
..Default::default()
};
freecvdef(Some(Box::new(d)));
}
#[test]
fn parse_cadef_simple_opt_and_rest() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from(""), String::from("-foo[only foo]"),
String::from("*:file:_files"),
];
let def = parse_cadef("_arguments", &args).expect("cadef built");
let opt = def.opts.as_deref().expect("opt linked");
assert_eq!(opt.name.as_deref(), Some("-foo"));
assert_eq!(opt.descr.as_deref(), Some("only foo"));
assert_eq!(opt.r#type, CAO_NEXT);
let xor = opt.xor.as_ref().expect("xor list");
assert!(xor.iter().any(|s| s == "-foo"), "xor must include -foo: {:?}", xor);
let rest = def.rest.as_deref().expect("rest linked");
assert_eq!(rest.r#type, CAA_REST);
assert_eq!(rest.descr.as_deref(), Some("file"));
assert_eq!(rest.action.as_deref(), Some("_files"));
}
#[test]
fn parse_cadef_numbered_positional_arg() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from(""),
String::from("1:cmd:_commands"),
];
let def = parse_cadef("_arguments", &args).expect("cadef built");
let pos = def.args.as_deref().expect("positional arg linked");
assert_eq!(pos.num, 1);
assert_eq!(pos.r#type, CAA_NORMAL);
assert_eq!(pos.descr.as_deref(), Some("cmd"));
assert_eq!(pos.action.as_deref(), Some("_commands"));
assert_eq!(pos.direct, 1, "explicit numbering sets direct=1");
}
#[test]
fn parse_cadef_doubled_arg_errors() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from(""),
String::from("1:a:_a"),
String::from("1:b:_b"),
];
let def = parse_cadef("_arguments", &args);
assert!(def.is_none(), "duplicate arg num=1 must reject");
}
#[test]
fn parse_cadef_xor_list_populated() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from(""),
String::from("(opt-x opt-y)-foo[descr]"),
];
let def = parse_cadef("_arguments", &args).expect("cadef built");
let opt = def.opts.as_deref().expect("opt linked");
let xor = opt.xor.as_ref().expect("xor list");
assert_eq!(xor.len(), 3, "xor: {:?}", xor);
assert_eq!(xor[0], "opt-x");
assert_eq!(xor[1], "opt-y");
assert_eq!(xor[2], "-foo");
}
#[test]
fn settags_populates_slot() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
if let Ok(mut tab) = comptags.lock() {
tab[0] = None;
}
settags(0, &[
"ctx".to_string(),
"tag-a".to_string(),
"tag-b".to_string(),
]);
let tab = comptags.lock().unwrap();
let slot = tab[0].as_deref().expect("comptags[0] populated");
assert_eq!(slot.context.as_deref(), Some("ctx"));
assert_eq!(slot.init, 1);
let all = slot.all.as_ref().expect("all populated");
assert_eq!(all.len(), 2);
assert_eq!(all[0], "tag-a");
assert_eq!(all[1], "tag-b");
assert!(slot.sets.is_none());
}
#[test]
fn ca_get_opt_exact_match() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from(""),
String::from("-foo[d]"),
];
let mut def = *parse_cadef("_arguments", &args).expect("cadef built");
let mut cur = def.opts.as_deref_mut();
while let Some(o) = cur {
o.active = 1;
cur = o.next.as_deref_mut();
}
let mut end: usize = 0;
let hit = ca_get_opt(&def, "-foo", 1, &mut end).expect("hit");
assert_eq!(hit.name.as_deref(), Some("-foo"));
assert_eq!(end, 4);
}
#[test]
fn ca_get_arg_argsactive_zero_returns_none() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from(""),
String::from("1:c:_c"),
];
let def = *parse_cadef("_arguments", &args).expect("cadef built");
assert!(ca_get_arg(&def, 1).is_none());
}
#[test]
fn ca_get_arg_in_range_active() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from(""),
String::from("1:c:_c"),
];
let mut def = *parse_cadef("_arguments", &args).expect("cadef built");
def.argsactive = 1;
if let Some(a) = def.args.as_deref_mut() {
a.active = 1;
}
let hit = ca_get_arg(&def, 1).expect("hit");
assert_eq!(hit.num, 1);
assert_eq!(hit.descr.as_deref(), Some("c"));
}
#[test]
fn parse_cvdef_sep_and_two_vals() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let args = vec![
String::from("-s"),
String::from(","),
String::from("descr"),
String::from("opt1[a]:val1:"),
String::from("opt2[b]"),
];
let def = parse_cvdef("_values", &args).expect("cvdef built");
assert_eq!(def.hassep, 1);
assert_eq!(def.sep, b',' as i32);
assert_eq!(def.descr.as_deref(), Some("descr"));
let v1 = def.vals.as_deref().expect("val1");
assert_eq!(v1.name.as_deref(), Some("opt1"));
assert_eq!(v1.descr.as_deref(), Some("a"));
assert_eq!(v1.r#type, CVV_ARG);
let v2 = v1.next.as_deref().expect("val2");
assert_eq!(v2.name.as_deref(), Some("opt2"));
assert_eq!(v2.descr.as_deref(), Some("b"));
assert_eq!(v2.r#type, CVV_NOARG);
}
#[test]
fn ca_foreign_opt_finds_in_other_set() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let other = cadef {
opts: Some(Box::new(caopt {
name: Some("-bar".into()), active: 1,
..Default::default()
})),
..Default::default()
};
let all = cadef {
opts: Some(Box::new(caopt {
name: Some("-foo".into()), active: 1,
..Default::default()
})),
snext: Some(Box::new(other)),
..Default::default()
};
assert_eq!(ca_foreign_opt(&all, &all, "-bar"), 1);
assert_eq!(ca_foreign_opt(&all, &all, "-foo"), 0);
assert_eq!(ca_foreign_opt(&all, &all, "-missing"), 0);
}
#[test]
fn ca_inactive_guard_noop_keeps_active() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let mut d = cadef {
opts: Some(Box::new(caopt {
name: Some("-foo".into()), active: 1, ..Default::default()
})),
argsactive: 1,
..Default::default()
};
ca_inactive(&mut d, &[], 0, 0);
assert_eq!(d.opts.as_deref().unwrap().active, 1);
assert_eq!(d.argsactive, 1);
}
#[test]
fn ca_inactive_opts_flag_deactivates_options() {
use std::sync::atomic::Ordering;
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let saved_compcur = crate::ported::zle::complete::COMPCURRENT.load(Ordering::Relaxed);
let mut d = cadef {
opts: Some(Box::new(caopt {
name: Some("-foo".into()), active: 1, num: 0,
next: Some(Box::new(caopt {
name: Some("-bar".into()), active: 1, num: 1,
..Default::default()
})),
..Default::default()
})),
argsactive: 1,
..Default::default()
};
crate::ported::zle::complete::COMPCURRENT.store(2, Ordering::Relaxed);
ca_inactive(&mut d, &[], 1, 1);
crate::ported::zle::complete::COMPCURRENT.store(saved_compcur, Ordering::Relaxed);
let mut p = d.opts.as_deref();
while let Some(o) = p {
assert_eq!(o.active, 0,
"{:?} should be deactivated", o.name);
p = o.next.as_deref();
}
}
#[test]
fn bin_comptags_init_and_context() {
use std::sync::atomic::Ordering;
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let saved_incompfunc = INCOMPFUNC.load(Ordering::Relaxed);
let saved_locallevel = crate::ported::builtin::LOCALLEVEL.load(Ordering::Relaxed);
if let Ok(mut tab) = comptags.lock() {
tab[0] = None;
}
crate::ported::builtin::LOCALLEVEL.store(0, Ordering::Relaxed);
INCOMPFUNC.store(1, Ordering::Relaxed);
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_comptags("comptags", &[
"-i".into(), "my-ctx".into(),
"tag-a".into(), "tag-b".into(),
], &ops, 0);
assert_eq!(r, 0);
let r = bin_comptags("comptags", &[
"-T".into(),
], &ops, 0);
assert_eq!(r, 1);
let ctx = comptags.lock().unwrap()[0].as_ref()
.and_then(|t| t.context.clone());
INCOMPFUNC.store(saved_incompfunc, Ordering::Relaxed);
crate::ported::builtin::LOCALLEVEL.store(saved_locallevel, Ordering::Relaxed);
assert_eq!(ctx.as_deref(), Some("my-ctx"));
}
#[test]
fn cv_get_val_hits_and_misses() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let d = cvdef {
vals: Some(Box::new(cvval {
name: Some("foo".into()),
r#type: CVV_NOARG,
next: Some(Box::new(cvval {
name: Some("bar".into()),
r#type: CVV_ARG,
..Default::default()
})),
..Default::default()
})),
..Default::default()
};
let hit = cv_get_val(&d, "bar").expect("hit");
assert_eq!(hit.name.as_deref(), Some("bar"));
assert_eq!(hit.r#type, CVV_ARG);
assert!(cv_get_val(&d, "missing").is_none());
}
#[test]
fn setup_clears_all_caches() {
use std::sync::atomic::Ordering;
let _g = crate::ported::zle::zle_main::zle_test_setup();
if let Ok(mut cache) = cadef_cache.lock() {
cache[0] = Some(Box::new(cadef::default()));
}
if let Ok(mut cache) = cvdef_cache.lock() {
cache[0] = Some(Box::new(cvdef::default()));
}
if let Ok(mut tab) = comptags.lock() {
tab[0] = Some(Box::new(ctags::default()));
}
lasttaglevel.store(42, Ordering::Relaxed);
let r = setup_();
assert_eq!(r, 0);
assert!(cadef_cache.lock().unwrap()[0].is_none(),
"cadef_cache[0] should be cleared");
assert!(cvdef_cache.lock().unwrap()[0].is_none(),
"cvdef_cache[0] should be cleared");
assert!(comptags.lock().unwrap()[0].is_none(),
"comptags[0] should be cleared");
assert_eq!(lasttaglevel.load(Ordering::Relaxed), 0,
"lasttaglevel should reset to 0");
}
#[test]
fn finish_frees_all_caches() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
if let Ok(mut cache) = cadef_cache.lock() {
cache[1] = Some(Box::new(cadef::default()));
}
if let Ok(mut cache) = cvdef_cache.lock() {
cache[1] = Some(Box::new(cvdef::default()));
}
if let Ok(mut tab) = comptags.lock() {
tab[1] = Some(Box::new(ctags::default()));
}
let r = finish_();
assert_eq!(r, 0);
assert!(cadef_cache.lock().unwrap()[1].is_none());
assert!(cvdef_cache.lock().unwrap()[1].is_none());
assert!(comptags.lock().unwrap()[1].is_none());
}
#[test]
fn cfp_matcher_pats_empty_matcher_passthrough() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let r = cfp_matcher_pats("", "abc");
assert_eq!(r, "abc");
}
#[test]
fn cfp_matcher_range_no_matchers_verbatim() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let ms: Vec<Option<Box<crate::ported::zle::comp_h::Cmatcher>>> =
vec![None, None, None];
let r = cfp_matcher_range(&ms, "abc");
assert_eq!(r, "abc");
}
#[test]
fn cd_prep_groups_emits_expl_and_spec_runs() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
{
let mut st = cd_state.lock().unwrap();
st.showd = 0;
st.groups = 2;
st.descs = 2;
st.maxg = 1;
st.maxglen = 5;
st.maxmlen = 100;
st.sets = Some(Box::new(cdset {
count: 2,
desc: 2,
strs: Some(Box::new(cdstr {
str: Some("alpha".into()),
r#match: Some("alpha".into()),
desc: Some("first".into()),
width: 5, len: 5,
kind: 0,
next: Some(Box::new(cdstr {
str: Some("beta".into()),
r#match: Some("beta".into()),
desc: Some("second".into()),
width: 4, len: 4,
kind: 0,
..Default::default()
})),
..Default::default()
})),
..Default::default()
}));
st.runs = None;
}
let r = cd_prep();
assert_eq!(r, 0);
let st = cd_state.lock().unwrap();
let r1 = st.runs.as_deref().expect("first run");
assert_eq!(r1.r#type, CRT_SPEC, "first run is CRT_SPEC");
let r2 = r1.next.as_deref().expect("second run");
assert_eq!(r2.r#type, CRT_SPEC, "second run is CRT_SPEC");
let r3 = r2.next.as_deref().expect("third run");
assert_eq!(r3.r#type, CRT_EXPL, "third run is CRT_EXPL");
assert_eq!(r3.count, 2, "CRT_EXPL covers both prep_lines");
}
#[test]
fn cfp_bld_pats_concatenates_skipped_and_pat() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let out = cfp_bld_pats(0,
&["dir".to_string()],
"",
&["*.c".to_string()]);
assert_eq!(out, vec!["dir*.c".to_string()]);
}
#[test]
fn cfp_bld_pats_globdots_variant() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let m = crate::ported::zle::complete::COMPPREFIX
.get_or_init(|| std::sync::Mutex::new(String::new()));
*m.lock().unwrap() = ".foo".to_string();
let out = cfp_bld_pats(0,
&["d".to_string()],
"",
&["*.x".to_string()]);
*m.lock().unwrap() = String::new();
assert!(out.contains(&"d*.x".to_string()));
assert!(out.contains(&"d.*.x".to_string()),
"dot-variant must be emitted: {:?}", out);
}
#[test]
fn cfp_opt_pats_passthrough_empty_compprefix() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let m = crate::ported::zle::complete::COMPPREFIX
.get_or_init(|| std::sync::Mutex::new(String::new()));
*m.lock().unwrap() = String::new();
let pats = vec!["*".to_string(), "*.c".to_string()];
let out = cfp_opt_pats(&pats, "");
assert_eq!(out, pats);
}
#[test]
fn cfp_test_exact_no_anchor_returns_none() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let pm = crate::ported::zle::complete::COMPPREFIX
.get_or_init(|| std::sync::Mutex::new(String::new()));
*pm.lock().unwrap() = String::new();
let sm = crate::ported::zle::complete::COMPSUFFIX
.get_or_init(|| std::sync::Mutex::new(String::new()));
*sm.lock().unwrap() = String::new();
let r = cfp_test_exact(&["/tmp".to_string()],
&["true".to_string()], "");
assert!(r.is_none());
}
#[test]
fn bin_compgroups_empty_args_succeeds() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let saved = INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed);
INCOMPFUNC.store(1, std::sync::atomic::Ordering::Relaxed);
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(), argscount: 0, argsalloc: 0,
};
let r = bin_compgroups("compgroups", &[], &ops, 0);
INCOMPFUNC.store(saved, std::sync::atomic::Ordering::Relaxed);
assert_eq!(r, 0);
}
#[test]
fn cd_groups_want_sorting_respects_opts() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
{
let mut st = cd_state.lock().unwrap();
st.sets = None;
}
assert_eq!(cd_groups_want_sorting(), 1);
{
let mut st = cd_state.lock().unwrap();
st.sets = Some(Box::new(cdset {
opts: Some(vec!["-V".into(), "grpname".into()]),
..Default::default()
}));
}
assert_eq!(cd_groups_want_sorting(), 0);
cd_state.lock().unwrap().sets = None;
}
#[test]
fn cd_sort_orders_by_sortstr() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let a = cdstr {
sortstr: Some("apple".into()), ..Default::default()
};
let b = cdstr {
sortstr: Some("banana".into()), ..Default::default()
};
assert_eq!(cd_sort(&a, &b), std::cmp::Ordering::Less);
assert_eq!(cd_sort(&b, &a), std::cmp::Ordering::Greater);
assert_eq!(cd_sort(&a, &a), std::cmp::Ordering::Equal);
}
#[test]
fn cd_prep_default_builds_simple_run_per_set() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
{
let mut st = cd_state.lock().unwrap();
st.showd = 0;
st.groups = 0;
st.sets = Some(Box::new(cdset {
count: 1,
strs: Some(Box::new(cdstr {
str: Some("a".into()),
r#match: Some("a".into()),
..Default::default()
})),
next: Some(Box::new(cdset {
count: 1,
strs: Some(Box::new(cdstr {
str: Some("b".into()),
r#match: Some("b".into()),
..Default::default()
})),
..Default::default()
})),
..Default::default()
}));
st.runs = None;
}
let r = cd_prep();
assert_eq!(r, 0);
let st = cd_state.lock().unwrap();
let r1 = st.runs.as_deref().expect("first run");
assert_eq!(r1.r#type, CRT_SIMPLE);
assert_eq!(r1.count, 1);
let r2 = r1.next.as_deref().expect("second run");
assert_eq!(r2.r#type, CRT_SIMPLE);
assert_eq!(r2.count, 1);
assert!(r2.next.is_none(), "no third run");
}
#[test]
fn bin_compdescribe_rejects_bad_option() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let saved_incompfunc = INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed);
INCOMPFUNC.store(1, std::sync::atomic::Ordering::Relaxed);
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(), argscount: 0, argsalloc: 0,
};
let r = bin_compdescribe("compdescribe", &["-x".into()], &ops, 0);
INCOMPFUNC.store(saved_incompfunc, std::sync::atomic::Ordering::Relaxed);
assert_eq!(r, 1);
}
#[test]
fn cf_remove_other_filters_by_dir_head() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let names = vec![
"dir/a".to_string(),
"dir/b".to_string(),
"other/c".to_string(),
];
let mut amb = 99;
let ret = cf_remove_other(&names, "dir/foo", &mut amb);
assert_eq!(amb, 0);
let v = ret.expect("matching names returned");
assert_eq!(v, vec!["dir/a".to_string(), "dir/b".to_string()]);
}
#[test]
fn cf_remove_other_no_slash_diverge_sets_amb() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let names = vec!["a".to_string(), "b".to_string()];
let mut amb = 0;
let ret = cf_remove_other(&names, "x", &mut amb);
assert!(ret.is_none());
assert_eq!(amb, 1);
}
#[test]
fn cf_ignore_no_style_is_noop() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut ign: Vec<String> = vec!["x".into()];
cf_ignore(&["/tmp".into()], &mut ign, "", "/tmp/foo");
assert_eq!(ign, vec!["x".to_string()],
"no style match must leave ign untouched");
}
#[test]
fn bin_compquote_returns_zero_when_qstack_empty() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let saved_incompfunc = INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed);
INCOMPFUNC.store(1, std::sync::atomic::Ordering::Relaxed);
if let Some(m) = COMPQSTACK.get() {
if let Ok(mut s) = m.lock() {
s.clear();
}
}
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0, argsalloc: 0,
};
let r = bin_compquote("compquote", &["foo".into()], &ops, 0);
INCOMPFUNC.store(saved_incompfunc, std::sync::atomic::Ordering::Relaxed);
assert_eq!(r, 0);
}
#[test]
fn cv_quote_get_val_unquotes_then_lookup() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
crate::ported::utils::inittyptab();
let d = cvdef {
vals: Some(Box::new(cvval {
name: Some("foo".into()),
r#type: CVV_NOARG,
..Default::default()
})),
..Default::default()
};
assert!(cv_quote_get_val(&d, "foo").is_some());
assert!(cv_quote_get_val(&d, "bar").is_none());
}
#[test]
fn cv_inactive_clears_named_vals() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut d = cvdef {
vals: Some(Box::new(cvval {
name: Some("a".into()), active: 1,
next: Some(Box::new(cvval {
name: Some("b".into()), active: 1,
next: Some(Box::new(cvval {
name: Some("c".into()), active: 1,
..Default::default()
})),
..Default::default()
})),
..Default::default()
})),
..Default::default()
};
cv_inactive(&mut d, &["a".into(), "c".into()]);
let mut p = d.vals.as_deref();
let mut by_name = std::collections::HashMap::new();
while let Some(v) = p {
by_name.insert(v.name.clone().unwrap_or_default(), v.active);
p = v.next.as_deref();
}
assert_eq!(by_name["a"], 0);
assert_eq!(by_name["b"], 1, "untouched val stays active");
assert_eq!(by_name["c"], 0);
}
}
pub fn alloc_cadef(args: Option<&[String]>, single: i32, matchstr: &str, nonarg: Option<&str>, flags: i32) -> Box<cadef> {
Box::new(cadef {
next: None, snext: None, opts: None, args: None, rest: None, nonarg: nonarg.map(|s| s.to_string()), defs: args.map(|a| a.to_vec()), ndefs: args.map_or(0, |a| a.len() as i32), nopts: 0, ndopts: 0, nodopts: 0, lastt: { use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now().duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64).unwrap_or(0)
},
set: None, single: if single != 0 {
Some((0..188).map(|_| None).collect())
} else {
None
},
r#match: Some(matchstr.to_string()), argsactive: 0,
flags, })
}
pub fn arrcontains(a: &[String], s: &str, colon: bool) -> i32 { for entry in a {
if colon {
let p = s.split(':').next().unwrap_or(s);
let q = entry.split(':').next().unwrap_or(entry);
if p == q {
return 1; }
} else if entry == s {
return 1; }
}
0 }
pub fn bin_comparguments(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::params::{setsparam, setaparam, sethparam, setiparam};
use crate::ported::zle::complete::{COMPCURRENT, COMPWORDS};
use std::sync::atomic::Ordering;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function");
return 1;
}
if args.is_empty() { return 1; }
let a0 = args[0].as_bytes();
if a0.len() != 2 || a0[0] != b'-' {
zwarnnam(nam, &format!("invalid argument: {}", args[0]));
return 1;
}
let sub = a0[1];
if sub != b'i' && sub != b'I' && ca_parsed.load(Ordering::Relaxed) == 0 {
zwarnnam(nam, "no parsed state");
return 1;
}
let (min, max): (i32, i32) = match sub {
b'i' => (2, -1),
b'D' => (3, 3),
b'O' => (4, 4),
b'L' => (3, 4),
b's' => (1, 1),
b'M' => (1, 1),
b'a' => (0, 0),
b'W' => (3, 3),
b'n' => (1, 1),
_ => {
zwarnnam(nam, &format!("invalid option: {}", args[0]));
return 1;
}
};
let n = (args.len() as i32) - 1;
if n < min { zwarnnam(nam, "not enough arguments"); return 1; }
if max >= 0 && n > max { zwarnnam(nam, "too many arguments"); return 1; }
match sub {
b'i' => { let compcur = COMPCURRENT.load(Ordering::Relaxed);
let compwords_nonempty = COMPWORDS.get()
.and_then(|m| m.lock().ok().map(|w| !w.is_empty() && !w[0].is_empty()))
.unwrap_or(false);
if compcur <= 1 || !compwords_nonempty {
return 1; }
let spec = &args[1..];
let _ = get_cadef(nam, spec); let cached: Option<Box<cadef>> = {
let cache = cadef_cache.lock().ok();
cache.and_then(|c| {
c.iter().find_map(|slot| {
slot.as_ref().filter(|e| {
e.ndefs == spec.len() as i32
&& e.defs.as_deref().map_or(false, |d| {
d.len() == spec.len()
&& d.iter().zip(spec.iter()).all(|(a, b)| a == b)
})
}).cloned()
})
})
};
let Some(mut def_head) = cached else { return 1; };
ca_parsed.store(0, Ordering::Relaxed); ca_doff.store(0, Ordering::Relaxed);
let all_clone = Box::new(clone_cadef_shallow(&def_head));
let mut first = 1;
let mut def_opt: Option<Box<cadef>> = Some(def_head);
let mut multi = 0;
if let Some(ref d) = def_opt {
if d.snext.is_some() { multi = 1; }
}
let mut states: Vec<castate> = Vec::new(); let mut ret = 0i32;
while let Some(mut current) = def_opt {
let next = current.snext.take();
let parse_ret = ca_parse_line(&mut current, &all_clone, multi, first);
let use_state = parse_ret == 0; let has_next = next.is_some();
if use_state && has_next { if let Ok(ls) = ca_laststate.lock() {
states.push(clone_castate_full(&ls));
}
} else if !use_state && !has_next { if let Some(saved) = states.pop() {
if let Ok(mut ls) = ca_laststate.lock() {
freecastate(&mut ls);
*ls = saved;
}
} else {
ret = 1; }
}
first = 0; def_opt = next;
}
ca_parsed.store(1, Ordering::Relaxed);
if !states.is_empty() {
if let Ok(mut ls) = ca_laststate.lock() {
let mut head: Option<Box<castate>> = None;
for s in states.into_iter().rev() {
let mut s = s;
s.snext = head;
head = Some(Box::new(s));
}
ls.snext = head;
}
}
ret }
b'D' => { let mut descr: Vec<String> = Vec::new();
let mut act: Vec<String> = Vec::new();
let mut subc: Vec<String> = Vec::new();
let mut ret = 1i32;
crate::ported::zle::complete::ignore_prefix(
ca_doff.load(Ordering::Relaxed));
let mut state_clone = ca_laststate.lock().map(|s| clone_castate_full(&s))
.ok();
while let Some(s) = state_clone {
let arg = s.def.clone();
if let Some(a) = arg {
ret = 0;
let opt_str = a.opt.clone();
let optdef = s.curopt.clone();
ca_set_data(&mut descr, &mut act, &mut subc,
opt_str.as_deref(),
Some(a),
optdef.as_deref(),
if ca_doff.load(Ordering::Relaxed) > 0 { 1 } else { 0 });
}
state_clone = s.snext.map(|b| *b);
}
if ret == 0 { setaparam(&args[1], descr);
setaparam(&args[2], act);
setaparam(&args[3], subc);
}
ret
}
b'M' => { let m = ca_laststate.lock().ok()
.and_then(|s| s.d.as_ref().and_then(|d| d.r#match.clone()))
.unwrap_or_default();
setsparam(&args[1], &m);
0
}
b'a' => { let mut state_clone = ca_laststate.lock().map(|s| clone_castate_full(&s))
.ok();
while let Some(s) = state_clone {
if s.d.as_ref().map_or(false, |d| d.args.is_some() || d.rest.is_some()) {
return 0;
}
state_clone = s.snext.map(|b| *b);
}
1 }
b'n' => { let optbeg = ca_laststate.lock().map(|s| s.optbeg).unwrap_or(0);
let kshoffset = if crate::ported::zsh_h::isset(
crate::ported::zsh_h::KSHARRAYS) { 0 } else { 1 };
setiparam(&args[1], (optbeg + kshoffset) as i64);
0
}
b'O' => { let mut next_l: Vec<String> = Vec::new();
let mut direct_l: Vec<String> = Vec::new();
let mut odirect_l: Vec<String> = Vec::new();
let mut equal_l: Vec<String> = Vec::new();
let mut ret = 1i32;
let mut state_clone = ca_laststate.lock().map(|s| clone_castate_full(&s))
.ok();
while let Some(s) = state_clone {
let actopts_ok = s.actopts != 0 &&
(s.opt != 0
|| (ca_doff.load(Ordering::Relaxed) != 0 && s.def.is_some())
|| (s.def.is_some()
&& s.def.as_deref().map_or(false, |d|
d.opt.is_some()
&& (d.r#type == CAA_OPT
|| (d.r#type >= CAA_RARGS && d.num < 0)))));
let pos_ok = s.def.is_none()
|| s.def.as_deref().map_or(true, |d| d.r#type < CAA_RARGS)
|| (s.def.as_deref().map_or(false, |d| d.r#type == CAA_RARGS)
&& s.curpos == s.argbeg + 1)
|| COMPCURRENT.load(Ordering::Relaxed) == 1;
if actopts_ok && pos_ok {
ret = 0;
if let Some(d) = s.d.as_ref() {
let mut p = d.opts.as_deref();
while let Some(opt) = p {
if opt.active != 0 && opt.not == 0 {
let bucket: &mut Vec<String> = match opt.r#type {
t if t == CAO_NEXT => &mut next_l,
t if t == CAO_DIRECT => &mut direct_l,
t if t == CAO_ODIRECT => &mut odirect_l,
_ => &mut equal_l,
};
let name_esc = bslashcolon(
opt.name.as_deref().unwrap_or(""));
let str_val = if let Some(desc) = opt.descr.as_deref() {
format!("{}:{}", name_esc, desc)
} else {
name_esc
};
if !bucket.iter().any(|s| s == &str_val) {
bucket.push(str_val);
}
}
p = opt.next.as_deref();
}
}
}
state_clone = s.snext.map(|b| *b);
}
if ret == 0 {
setaparam(&args[1], next_l);
setaparam(&args[2], direct_l);
setaparam(&args[3], odirect_l);
setaparam(&args[4], equal_l);
0
} else {
let singles = ca_laststate.lock().map(|s| s.singles).unwrap_or(0);
if singles != 0 { 2 } else { 1 } }
}
b'L' => { let mut descr: Vec<String> = Vec::new();
let mut act: Vec<String> = Vec::new();
let mut subc: Vec<String> = Vec::new();
let mut ret = 1i32;
let mut state_clone = ca_laststate.lock().map(|s| clone_castate_full(&s))
.ok();
while let Some(s) = state_clone {
if let Some(d) = s.d.as_ref() {
let mut end = 0usize;
if let Some(opt) = ca_get_opt(d, &args[1], 1, &mut end) {
if opt.args.is_some() {
ret = 0;
let opt_name = opt.name.clone();
let opt_args = opt.args.clone();
ca_set_data(&mut descr, &mut act, &mut subc,
opt_name.as_deref(),
opt_args,
Some(&opt),
1);
}
}
}
state_clone = s.snext.map(|b| *b);
}
if ret == 0 {
setaparam(&args[2], descr);
setaparam(&args[3], act);
setaparam(&args[4], subc);
}
ret
}
b's' => { let mut state_clone = ca_laststate.lock().map(|s| clone_castate_full(&s))
.ok();
while let Some(s) = state_clone {
let single_active = s.d.as_ref().map_or(false, |d| d.single.is_some())
&& s.singles != 0
&& s.actopts != 0;
if single_active {
let kind = if let (Some(_), Some(dopt)) = (&s.ddef, &s.dopt) {
match dopt.r#type {
t if t == CAO_DIRECT => "direct",
t if t == CAO_OEQUAL || t == CAO_EQUAL => "equal",
_ => "next",
}
} else { "" };
setsparam(&args[1], kind);
return 0;
}
state_clone = s.snext.map(|b| *b);
}
1
}
b'W' => { let mut all_args: Vec<String> = Vec::new();
let opt_args_use_nul = !args[3].starts_with('0');
let mut state_clone = ca_laststate.lock().map(|s| clone_castate_full(&s))
.ok();
let mut snapshot = state_clone.clone();
while let Some(s) = snapshot {
if let Some(a) = s.args.as_ref() {
all_args.extend(a.iter().cloned());
}
snapshot = s.snext.map(|b| *b);
}
setaparam(&args[1], all_args);
let mut hash_vec: Vec<String> = Vec::new();
while let Some(s) = state_clone {
if let Some(d) = s.d.as_ref() {
let mut o = d.opts.as_deref();
let mut a_idx = 0usize;
let oargs_ref = s.oargs.as_deref();
while let Some(op) = o {
if let Some(oa) = oargs_ref.and_then(|v| v.get(a_idx)).and_then(|x| x.as_ref()) {
let key = match (op.gsname.as_deref(), op.name.as_deref()) {
(Some(gs), Some(n)) =>
format!("{}{}", gs, n),
(None, Some(n)) => n.to_string(),
_ => String::new(),
};
hash_vec.push(key);
let joined = if opt_args_use_nul {
String::from_utf8_lossy(&ca_nullist(oa)).into_owned()
} else {
ca_colonlist(oa)
};
hash_vec.push(joined);
}
a_idx += 1;
o = op.next.as_deref();
}
}
state_clone = s.snext.map(|b| *b);
}
sethparam(&args[2], hash_vec);
0
}
_ => 0,
}
}
#[allow(dead_code)]
fn clone_castate_full(s: &castate) -> castate {
castate {
snext: s.snext.clone(),
d: s.d.clone(),
nopts: s.nopts,
def: s.def.clone(),
ddef: s.ddef.clone(),
curopt: s.curopt.clone(),
dopt: s.dopt.clone(),
opt: s.opt, arg: s.arg,
argbeg: s.argbeg, optbeg: s.optbeg,
nargbeg: s.nargbeg, restbeg: s.restbeg,
curpos: s.curpos, argend: s.argend,
inopt: s.inopt, inarg: s.inarg,
nth: s.nth, singles: s.singles, oopt: s.oopt, actopts: s.actopts,
args: s.args.clone(),
oargs: s.oargs.clone(),
}
}
pub fn bin_compdescribe(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::params::paramtab;
use std::sync::atomic::Ordering;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function");
return 1;
}
if args.is_empty() { return 1; }
let a0 = args[0].as_bytes();
if a0.len() != 2 || a0[0] != b'-' {
zwarnnam(nam, &format!("invalid argument: {}", args[0]));
return 1;
}
let n = args.len() as i32;
match a0[1] {
b'i' => { if n < 3 {
zwarnnam(nam, "not enough arguments");
return 1;
}
cd_init(nam, &args[1], &args[2], "", &[], &args[3..], 0) }
b'I' => { if n < 6 {
zwarnnam(nam, "not enough arguments");
return 1;
}
let opts_arr: Vec<String> = paramtab().read().ok()
.and_then(|tab| tab.get(&args[4])
.and_then(|pm| pm.u_arr.clone()))
.unwrap_or_default();
if opts_arr.is_empty() && paramtab().read().ok()
.map_or(true, |tab| tab.get(&args[4]).is_none())
{
zwarnnam(nam, &format!("unknown parameter: {}", args[4]));
return 1;
}
cd_init(nam, &args[1], &args[2], &args[3], &opts_arr, &args[5..], 1)
}
b'g' => { if cd_parsed.load(Ordering::Relaxed) == 0 { zwarnnam(nam, "no parsed state"); return 1;
}
if n != 5 {
zwarnnam(nam,
if n < 5 { "not enough arguments" } else { "too many arguments" });
return 1;
}
cd_get(&args[1..]) }
_ => {
zwarnnam(nam, &format!("invalid option: {}", args[0]));
1
}
}
}
pub fn bin_compfiles(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::params::{paramtab, setaparam};
use crate::ported::utils::quotestring;
use crate::ported::zsh_h::QT_BACKSLASH_PATTERN;
use std::sync::atomic::Ordering;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function");
return 1;
}
if args.is_empty() || !args[0].starts_with('-') { let bad = args.first().map(|s| s.as_str()).unwrap_or("");
zwarnnam(nam, &format!("missing option: {}", bad));
return 1;
}
let a0 = args[0].as_bytes();
if a0.len() < 2 { zwarnnam(nam, &format!("missing option: {}", args[0])); return 1; }
let sub = a0[1];
let get_arr = |name: &str| -> Option<Vec<String>> {
paramtab().read().ok().and_then(|tab| {
tab.get(name).and_then(|pm| pm.u_arr.clone())
})
};
match sub {
b'p' | b'P' => { let noopt = a0.len() > 2;
if noopt && (a0.len() != 4 || a0[2] != b'-' || a0[3] != b'-') {
zwarnnam(nam, &format!("invalid option: {}", args[0]));
return 1;
}
let required = if sub == b'p' { 8 } else { 7 };
if args.len() <= required { zwarnnam(nam, "too few arguments");
return 1;
}
let Some(src) = get_arr(&args[1]) else {
zwarnnam(nam, &format!("unknown parameter: {}", args[1]));
return 0;
};
let l: Vec<String> = src.iter()
.map(|s| quotestring(s, QT_BACKSLASH_PATTERN))
.collect();
let result = cf_pats(
if sub == b'P' { 1 } else { 0 },
if noopt { 1 } else { 0 },
&l,
&get_arr(&args[2]).unwrap_or_default(),
&args[3],
&args[4],
&args[5],
&get_arr(&args[6]).unwrap_or_default(),
&args[7..],
);
setaparam(&args[1], result);
0
}
b'i' => { if a0.len() > 2 { zwarnnam(nam, &format!("invalid option: {}", args[0]));
return 1;
}
if args.len() < 5 { zwarnnam(nam, "too few arguments");
return 1;
}
if args.len() > 5 { zwarnnam(nam, "too many arguments");
return 1;
}
let mut l: Vec<String> = get_arr(&args[2]).unwrap_or_default();
let Some(tmp) = get_arr(&args[1]) else { zwarnnam(nam, &format!("unknown parameter: {}", args[1]));
return 0;
};
cf_ignore(&tmp, &mut l, &args[3], &args[4]); setaparam(&args[2], l); 0
}
b'r' => { if args.len() < 3 { zwarnnam(nam, "too few arguments");
return 1;
}
if args.len() > 3 { zwarnnam(nam, "too many arguments");
return 1;
}
let Some(tmp) = get_arr(&args[1]) else { zwarnnam(nam, &format!("unknown parameter: {}", args[1]));
return 0;
};
let mut ret = 0i32;
if let Some(l) = cf_remove_other(&tmp, &args[2], &mut ret) {
setaparam(&args[1], l);
}
ret }
_ => {
zwarnnam(nam, &format!("invalid option: {}", args[0]));
1
}
}
}
pub fn bin_compgroups(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::zle::comp_h::{CGF_NOSORT, CGF_UNIQALL, CGF_UNIQCON};
use crate::ported::zle::compcore::{begcmgroup, endcmgroup};
if INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function");
return 1;
}
for n in args { endcmgroup(None); begcmgroup(Some(n), CGF_NOSORT | CGF_UNIQCON); endcmgroup(None);
begcmgroup(Some(n), CGF_UNIQALL); endcmgroup(None);
begcmgroup(Some(n), CGF_NOSORT | CGF_UNIQCON); endcmgroup(None);
begcmgroup(Some(n), CGF_UNIQALL); endcmgroup(None);
begcmgroup(Some(n), CGF_NOSORT); endcmgroup(None);
begcmgroup(Some(n), 0); }
0 }
pub fn boot_() -> i32 { 0
}
pub fn ca_colonlist(l: &[String]) -> String { if l.is_empty() {
return String::new(); }
let mut out = String::new();
for (i, item) in l.iter().enumerate() { if i > 0 {
out.push(':'); }
for ch in item.chars() {
if ch == ':' || ch == '\\' { out.push('\\');
}
out.push(ch);
}
}
out
}
pub fn ca_foreign_opt(curset: &cadef, all: &cadef, option: &str) -> i32 { let curset_ptr = curset as *const cadef;
let mut d_opt = Some(all);
while let Some(d) = d_opt { if std::ptr::addr_eq(d as *const cadef, curset_ptr) { d_opt = d.snext.as_deref();
continue;
}
let mut p = d.opts.as_deref();
while let Some(opt) = p { if opt.name.as_deref() == Some(option) { return 1; }
p = opt.next.as_deref();
}
d_opt = d.snext.as_deref();
}
0 }
pub fn ca_get_arg(d: &cadef, mut n: i32) -> Option<Box<caarg>> { if d.argsactive == 0 { return None; }
let mut a = d.args.as_deref();
while let Some(node) = a { let in_range = node.active != 0 && n >= node.min && n <= node.num;
if in_range { break; } if node.active == 0 { n += 1; }
a = node.next.as_deref(); }
if let Some(node) = a { if node.active != 0 && node.min <= n && node.num >= n {
return Some(Box::new(caarg { next: None,
descr: node.descr.clone(),
xor: node.xor.clone(),
action: node.action.clone(),
r#type: node.r#type,
end: node.end.clone(),
opt: node.opt.clone(),
num: node.num,
min: node.min,
direct: node.direct,
active: node.active,
gsname: node.gsname.clone(),
}));
}
}
if let Some(r) = d.rest.as_deref() {
if r.active != 0 {
return Some(Box::new(caarg {
next: None,
descr: r.descr.clone(),
xor: r.xor.clone(),
action: r.action.clone(),
r#type: r.r#type,
end: r.end.clone(),
opt: r.opt.clone(),
num: r.num,
min: r.min,
direct: r.direct,
active: r.active,
gsname: r.gsname.clone(),
}));
}
}
None }
pub fn ca_get_opt(d: &cadef, line: &str, full: i32, end: &mut usize) -> Option<Box<caopt>> {
let line_bytes = line.as_bytes();
let mut cur = d.opts.as_deref();
while let Some(p) = cur { if p.active != 0 { if let Some(name) = p.name.as_deref() {
if name == line {
*end = line_bytes.len(); return Some(Box::new(caopt { next: None,
name: p.name.clone(),
descr: p.descr.clone(),
xor: p.xor.clone(),
r#type: p.r#type,
args: None,
active: p.active,
num: p.num,
gsname: p.gsname.clone(),
not: p.not,
}));
}
}
}
cur = p.next.as_deref();
}
if full == 0 { let mut cur = d.opts.as_deref();
while let Some(p) = cur {
if p.active != 0 { if let Some(name) = p.name.as_deref() {
let is_match = if p.args.is_none() || p.r#type == CAO_NEXT {
name == line
} else {
crate::ported::utils::strpfx(name, line)
};
if is_match {
let l = name.len();
if (p.r#type == CAO_OEQUAL || p.r#type == CAO_EQUAL)
&& l < line_bytes.len() && line_bytes[l] != b'='
{
cur = p.next.as_deref();
continue; }
let mut at = l;
if (p.r#type == CAO_OEQUAL || p.r#type == CAO_EQUAL)
&& l < line_bytes.len() && line_bytes[l] == b'='
{
at += 1; }
*end = at; return Some(Box::new(caopt { next: None,
name: p.name.clone(),
descr: p.descr.clone(),
xor: p.xor.clone(),
r#type: p.r#type,
args: None,
active: p.active,
num: p.num,
gsname: p.gsname.clone(),
not: p.not,
}));
}
}
}
cur = p.next.as_deref();
}
}
None }
pub fn ca_get_sopt(d: &cadef, line: &str, end: &mut usize,
lp: &mut Option<Vec<Box<caopt>>>) -> Option<Box<caopt>> {
let line_bytes = line.as_bytes();
if line_bytes.is_empty() {
*lp = None;
return None;
}
let pre = line_bytes[0]; let mut idx: usize = 1;
*lp = None;
let single = match d.single.as_ref() { Some(s) => s,
None => return None,
};
let mut p_cur: Option<&caopt> = None; let mut pp_cur: Option<&caopt> = None;
let mut list_acc: Option<Vec<Box<caopt>>> = None;
while idx < line_bytes.len() { let ch = line_bytes[idx];
let sidx = single_index(pre, ch);
let lookup: Option<&caopt> = if sidx >= 0 && (sidx as usize) < single.len() {
single[sidx as usize].as_deref()
} else {
None
};
if lookup.is_some() {
p_cur = lookup;
}
let active_with_args = lookup
.filter(|p| p.active != 0 && p.args.is_some());
if let Some(p) = active_with_args { if p.r#type == CAO_NEXT { let list = list_acc.get_or_insert_with(Vec::new);
list.push(Box::new(caopt { next: None,
name: p.name.clone(),
descr: p.descr.clone(),
xor: p.xor.clone(),
r#type: p.r#type,
args: None,
active: p.active,
num: p.num,
gsname: p.gsname.clone(),
not: p.not,
}));
} else { idx += 1; if (p.r#type == CAO_OEQUAL || p.r#type == CAO_EQUAL) && idx < line_bytes.len() && line_bytes[idx] == b'='
{
idx += 1; }
*end = idx; pp_cur = Some(p); break; }
} else if p_cur.is_none() || p_cur.map_or(true, |p| p.active == 0) { return None; }
pp_cur = p_cur.filter(|p| {
p.name.as_deref()
.and_then(|n| n.as_bytes().first().copied())
.map_or(false, |b| b == pre)
});
p_cur = None;
idx += 1; }
if pp_cur.is_some() {
*end = idx;
}
*lp = list_acc;
pp_cur.map(|p| Box::new(caopt { next: None,
name: p.name.clone(),
descr: p.descr.clone(),
xor: p.xor.clone(),
r#type: p.r#type,
args: None,
active: p.active,
num: p.num,
gsname: p.gsname.clone(),
not: p.not,
}))
}
pub fn ca_inactive(d: &mut cadef, xor: &[String], cur: i32, opts: i32) { use crate::ported::ztype_h::idigit;
use crate::ported::zle::complete::COMPCURRENT;
if (xor.is_empty() && opts == 0) || cur > COMPCURRENT.load(std::sync::atomic::Ordering::Relaxed)
{
return;
}
let single = opts == 0
&& cur == COMPCURRENT.load(std::sync::atomic::Ordering::Relaxed);
let iter_xor: Vec<String> = if opts != 0 {
vec!["-".to_string()]
} else {
xor.to_vec()
};
for x_orig in iter_xor.iter() {
let mut x = x_orig.as_str();
let mut excludeall = 0; let mut grp: Option<&str> = None;
let mut grplen: usize = 0;
let xb = x.as_bytes();
let mut sep_byte = if xb.is_empty() { 0u8 } else { xb[0] };
let mut sep_pos = 0usize;
loop {
if sep_pos >= xb.len() { break; }
sep_byte = xb[sep_pos];
if sep_byte == b'+' || sep_byte == b'-' || sep_byte == b':'
|| sep_byte == b'*' || idigit(sep_byte)
{
break;
}
let after = &xb[sep_pos..];
let dash_off = after.iter().position(|&b| b == b'-');
match dash_off {
None => {
excludeall = 1; sep_pos = xb.len();
break;
}
Some(d) => {
let next = sep_pos + d + 1;
if next >= xb.len() { excludeall = 1;
sep_pos = xb.len();
break;
}
sep_pos = next;
}
}
}
if sep_pos > 0 && sep_pos < xb.len() { grp = Some(&x[..sep_pos]);
grplen = sep_pos;
x = &x[sep_pos..];
} else if sep_pos > 0 && excludeall != 0 && sep_pos == xb.len() {
grp = Some(x);
grplen = sep_pos;
x = "";
}
let xb = x.as_bytes();
if excludeall != 0 || (xb.len() == 1 && xb[0] == b':') {
if let Some(g) = grp { let mut cur_arg = d.args.as_deref_mut();
while let Some(a) = cur_arg {
let matches = a.gsname.as_deref().map_or(false, |gn| {
let gnb = gn.as_bytes();
gnb.len() == grplen + (excludeall as usize)
&& gn.starts_with(g)
});
if matches {
a.active = 0; }
cur_arg = a.next.as_deref_mut();
}
if let Some(r) = d.rest.as_deref_mut() {
let matches = r.gsname.as_deref().map_or(false, |gn| {
let gnb = gn.as_bytes();
gnb.len() == grplen + (excludeall as usize)
&& gn.starts_with(g)
});
if matches {
r.active = 0; }
}
} else {
d.argsactive = 0; }
}
if excludeall != 0 || (xb.len() == 1 && xb[0] == b'-') {
let mut cur_opt = d.opts.as_deref_mut();
while let Some(p) = cur_opt {
let grp_ok = grp.map_or(true, |g| {
p.gsname.as_deref().map_or(false, |gn| {
gn.len() == grplen + (excludeall as usize)
&& gn.starts_with(g)
})
});
let single_skip = single && p.name.as_deref().map_or(false, |n| {
let nb = n.as_bytes();
nb.len() >= 3 && nb[0] != 0
});
if grp_ok && !single_skip {
p.active = 0; }
cur_opt = p.next.as_deref_mut();
}
}
if excludeall != 0 || (xb.len() == 1 && xb[0] == b'*') {
if let Some(r) = d.rest.as_deref_mut() {
let grp_ok = grp.map_or(true, |g| {
r.gsname.as_deref().map_or(false, |gn| {
gn.len() == grplen + (excludeall as usize)
&& gn.starts_with(g)
})
});
if grp_ok {
r.active = 0; }
}
}
if excludeall == 0 { if !xb.is_empty() && idigit(xb[0]) { let n: i32 = x.bytes().take_while(|b| idigit(*b))
.fold(0i32, |acc, b| acc * 10 + (b - b'0') as i32);
let mut cur_arg = d.args.as_deref_mut();
let mut hit: Option<&mut caarg> = None;
while let Some(a) = cur_arg {
if a.num >= n {
hit = Some(a);
break;
}
cur_arg = a.next.as_deref_mut();
}
if let Some(a) = hit {
if a.num == n {
let grp_ok = grp.map_or(true, |g| {
a.gsname.as_deref().map_or(false, |gn| gn.starts_with(g))
});
if grp_ok {
a.active = 0; }
}
}
} else {
let mut end_unused = 0usize;
if let Some(matched) = ca_get_opt(d, x, 1, &mut end_unused) {
let grp_ok = grp.map_or(true, |g| {
matched.gsname.as_deref().map_or(false, |gn| gn.starts_with(g))
});
let single_skip = single
&& matched.name.as_deref().map_or(false, |n| {
let nb = n.as_bytes();
nb.len() >= 3 && nb[0] != 0
});
if grp_ok && !single_skip {
let target_name = matched.name.clone();
let mut cur_opt = d.opts.as_deref_mut();
while let Some(p) = cur_opt {
if p.name == target_name {
p.active = 0; break;
}
cur_opt = p.next.as_deref_mut();
}
}
}
}
if opts != 0 {
break; }
}
let _ = sep_byte;
}
}
pub fn ca_nullist(l: &[String]) -> Vec<u8> { if l.is_empty() {
return Vec::new(); }
let mut out = Vec::new();
for (i, item) in l.iter().enumerate() {
if i > 0 {
out.push(0);
}
out.extend_from_slice(item.as_bytes());
}
out
}
pub fn ca_opt_arg(opt_name: &str, line: &str, equal_kind: bool) -> String { let o_bytes = opt_name.as_bytes();
let l_bytes = line.as_bytes();
let mut oi = 0usize;
let mut li = 0usize;
loop { if oi >= o_bytes.len() || li >= l_bytes.len() {
break;
}
let mut oc = o_bytes[oi];
if oc == b'\\' { oi += 1;
if oi >= o_bytes.len() {
break;
}
oc = o_bytes[oi];
}
let mut lc = l_bytes[li];
if matches!(lc, b'\\' | b'\'' | b'"') { li += 1;
if li >= l_bytes.len() {
break;
}
lc = l_bytes[li];
}
if oc != lc { break;
}
oi += 1;
li += 1;
}
let rest = &l_bytes[li..];
let mut s = String::from_utf8_lossy(rest).into_owned();
if equal_kind && s.starts_with('\\') { s.remove(0);
}
if equal_kind {
s = s.strip_prefix('=').map(|t| t.to_string()).unwrap_or(s); }
s
}
pub fn ca_parse_line(d: &mut cadef, all: &cadef, multi: i32, first: i32) -> i32 { use crate::ported::zle::complete::{COMPCURRENT, COMPWORDS};
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::glob::remnulargs;
use crate::ported::lex::untokenize;
use std::sync::atomic::Ordering;
let compcur = COMPCURRENT.load(Ordering::Relaxed);
if first != 0 && ca_alloced.load(Ordering::Relaxed) != 0 {
if let Ok(mut ls) = ca_laststate.lock() {
freecastate(&mut ls);
ls.snext = None;
}
}
let mut p = d.opts.as_deref_mut();
while let Some(o) = p {
o.active = 1;
p = o.next.as_deref_mut();
}
d.argsactive = 1;
if let Some(r) = d.rest.as_deref_mut() { r.active = 1; }
let mut a = d.args.as_deref_mut();
while let Some(ar) = a {
ar.active = 1;
a = ar.next.as_deref_mut();
}
let compwords: Vec<String> = COMPWORDS
.get()
.and_then(|m| m.lock().ok().map(|w| w.clone()))
.unwrap_or_default();
let argend_init = (compwords.len() as i32) - 1;
let mut state = castate {
snext: None,
d: None,
nopts: d.nopts,
def: None,
ddef: None,
curopt: None,
dopt: None,
opt: 1, arg: 1, argbeg: 1, optbeg: 1, nargbeg: 1, restbeg: 1,
actopts: 1,
nth: 1, inarg: 1, inopt: 0,
singles: 0, oopt: 0,
argend: argend_init,
curpos: compcur,
args: Some(Vec::new()),
oargs: Some((0..d.nopts as usize).map(|_| None).collect()),
};
ca_alloced.store(1, Ordering::Relaxed);
if let Ok(mut ls) = ca_laststate.lock() {
*ls = clone_castate(&state, d);
}
if compwords.len() < 2 { if let Ok(mut ls) = ca_laststate.lock() {
ls.opt = 0; ls.arg = 0;
}
} else {
let napat = d.nonarg.as_deref().and_then(|s| {
patcompile(s, 0, None::<&mut String>)
});
let mut endpat: Option<crate::ported::pattern::Patprog> = None;
let mut cur = 2i32;
let mut argxor: Option<Vec<String>> = None;
let mut sopts: Vec<Box<caopt>> = Vec::new();
let mut wasopt_idx: Option<usize> = None;
let mut doff: i32 = 0;
let mut adef: Option<Box<caarg>> = None;
let mut ddef: Option<Box<caarg>> = None;
let mut dopt: Option<Box<caopt>> = None;
state.curopt = None; state.def = None;
loop {
let line_idx = (cur - 1) as usize;
if line_idx >= compwords.len() { break; }
let oline = compwords[line_idx].clone();
let mut line = oline.clone();
ddef = None;
adef = None;
dopt = None;
state.singles = 0;
let mut arglast = 0;
remnulargs(&mut line);
line = untokenize(&line);
if let Some(xor) = argxor.take() {
ca_inactive(d, &xor, cur - 1, 0);
}
if (d.flags & CDF_SEP) != 0 && cur != compcur && state.actopts != 0
&& line == "--"
{
ca_inactive(d, &[], cur, 1);
state.actopts = 0;
cur += 1;
continue;
}
if state.def.is_some() {
state.arg = 0;
if let Some(co) = state.curopt.as_deref() {
let cn = co.num as usize;
if let Some(oargs) = state.oargs.as_mut() {
if cn < oargs.len() {
oargs[cn].get_or_insert_with(Vec::new).push(oline.clone());
}
}
}
let def_type = state.def.as_deref().map_or(0, |d| d.r#type);
let def_is_opt = def_type == CAA_OPT;
state.opt = if def_is_opt { 1 } else { 0 };
if def_is_opt {
if state.def.as_deref().map_or(false, |d| d.opt.is_some()) {
state.oopt += 1;
}
}
if def_type == CAA_REST || def_type == CAA_RARGS
|| def_type == CAA_RREST
{
let matched_end = state.def.as_deref()
.and_then(|d| d.end.as_deref())
.map_or(false, |_| endpat.as_ref()
.map_or(false, |ep| pattry(ep, &line)));
if matched_end {
state.def = None;
state.curopt = None;
state.opt = 1; state.arg = 1;
state.argend = cur - 1;
if let Ok(mut ls) = ca_laststate.lock() {
ls.argend = cur - 1;
}
}
} else {
let next = state.def.as_deref().and_then(|d| d.next.clone());
if next.is_some() {
state.def = next;
state.argbeg = cur;
state.argend = argend_init;
} else if let Some(s) = sopts.first().cloned() {
sopts.remove(0);
state.curopt = Some(s);
state.def = state.curopt.as_deref().and_then(|c| c.args.clone());
state.opt = 0;
state.argbeg = cur; state.optbeg = cur; state.inopt = cur;
state.argend = argend_init;
doff = 0;
state.singles = 1;
if let Some(co) = state.curopt.as_deref() {
let cn = co.num as usize;
if let Some(oargs) = state.oargs.as_mut() {
if cn < oargs.len() && oargs[cn].is_none() {
oargs[cn] = Some(Vec::new());
}
}
}
} else {
state.curopt = None;
state.opt = 1;
}
}
} else {
state.opt = 1; state.arg = 1;
state.curopt = None;
}
if state.opt != 0 {
let lb = line.as_bytes();
state.opt = if lb.is_empty() { 0 }
else if lb.len() == 1 { 1 } else { 2 };
}
let mut pe_off: i32 = 0;
wasopt_idx = None;
let opt_match = if state.opt == 2 {
let lb = line.as_bytes();
if !lb.is_empty() && (lb[0] == b'-' || lb[0] == b'+') {
let mut end = 0usize;
if let Some(found) = ca_get_opt(d, &line, 0, &mut end) {
pe_off = end as i32;
let pe_ok = match found.r#type {
t if t == CAO_OEQUAL =>
(line_idx + 1 < compwords.len())
|| (pe_off > 0
&& lb.get(pe_off as usize - 1) == Some(&b'=')),
t if t == CAO_EQUAL =>
pe_off > 0
&& (lb.get(pe_off as usize - 1) == Some(&b'=')
|| pe_off as usize >= lb.len()),
_ => true,
};
if pe_ok { Some(found) } else { None }
} else { None }
} else { None }
} else { None };
if let Some(co) = opt_match {
let co_name = co.name.clone().unwrap_or_default();
let co_type = co.r#type;
let co_num = co.num;
let co_xor = co.xor.clone().unwrap_or_default();
let co_args = co.args.clone();
state.curopt = Some(co);
let pe_at_eq = pe_off > 0
&& line.as_bytes().get(pe_off as usize - 1) == Some(&b'=');
let pe_tail_present = (pe_off as usize) < line.as_bytes().len();
let take_args = co_type != CAO_EQUAL || pe_at_eq;
state.def = if take_args { co_args.clone() } else { None };
if state.def.is_some() { ddef = state.def.clone(); dopt = state.curopt.clone(); }
doff = pe_off;
state.optbeg = cur; state.argbeg = cur; state.inopt = cur;
state.argend = argend_init;
let single_ok = d.single.is_some()
&& !pe_tail_present
&& co_name.as_bytes().len() >= 2
&& co_name.as_bytes()[1] != b'-'
&& co_name.as_bytes().get(2).is_none();
state.singles = if single_ok { 1 } else { 0 };
let cn = co_num as usize;
if let Some(oargs) = state.oargs.as_mut() {
if cn < oargs.len() && oargs[cn].is_none() {
oargs[cn] = Some(Vec::new());
}
}
ca_inactive(d, &co_xor, cur, 0);
let collect_arg = state.def.is_some() && (
co_type == CAO_DIRECT
|| co_type == CAO_EQUAL
|| (co_type == CAO_ODIRECT && pe_tail_present)
|| (co_type == CAO_OEQUAL && (pe_tail_present || pe_at_eq))
);
if collect_arg {
let dtype = state.def.as_deref().map_or(0, |d| d.r#type);
if dtype != CAA_REST && dtype != CAA_RARGS && dtype != CAA_RREST {
let next = state.def.as_deref().and_then(|d| d.next.clone());
state.def = next;
}
let arg_str = ca_opt_arg(&co_name, &oline, false);
if let Some(oargs) = state.oargs.as_mut() {
if cn < oargs.len() {
oargs[cn].get_or_insert_with(Vec::new).push(arg_str);
}
}
}
if state.def.is_some() {
state.opt = 0;
} else {
if d.single.is_none()
|| (co_name.as_bytes().len() >= 3
&& co_name.as_bytes()[1] != 0)
{
wasopt_idx = Some(co_num as usize); }
state.curopt = None;
}
} else if state.opt == 2 && d.single.is_some()
&& line.as_bytes().first().copied().map_or(false,
|b| b == b'-' || b == b'+')
{
let mut end = 0usize;
let mut tmp_sopts: Option<Vec<Box<caopt>>> = None;
let s_match = ca_get_sopt(d, &line, &mut end, &mut tmp_sopts);
if let Some(queued) = tmp_sopts {
sopts.extend(queued);
}
let active_sopt = s_match.or_else(|| {
if cur != compcur && !sopts.is_empty() {
Some(sopts.remove(0))
} else { None }
});
if let Some(co) = active_sopt {
let co_name = co.name.clone().unwrap_or_default();
let co_type = co.r#type;
let co_num = co.num;
let co_xor = co.xor.clone().unwrap_or_default();
let co_args = co.args.clone();
state.curopt = Some(co);
let cn = co_num as usize;
if let Some(oargs) = state.oargs.as_mut() {
if cn < oargs.len() && oargs[cn].is_none() {
oargs[cn] = Some(Vec::new());
}
}
state.def = co_args.clone();
if co_type == CAO_NEXT && cur == compcur {
ddef = None;
} else {
ddef = state.def.clone();
}
dopt = state.curopt.clone();
doff = end as i32;
state.optbeg = cur; state.argbeg = cur; state.inopt = cur;
state.argend = argend_init;
state.singles = if end >= line.as_bytes().len() { 1 } else { 0 };
let lb = line.as_bytes();
let pre = lb.first().copied().unwrap_or(0);
let mut p_idx = 1usize;
while p_idx < end.min(lb.len()) {
let sidx = single_index(pre, lb[p_idx]);
if sidx >= 0 && (sidx as usize)
< d.single.as_ref().map_or(0, |s| s.len())
{
let tmp_xor = d.single.as_ref()
.and_then(|s| s.get(sidx as usize))
.and_then(|so| so.as_ref())
.and_then(|so| so.xor.clone());
let tmp_num = d.single.as_ref()
.and_then(|s| s.get(sidx as usize))
.and_then(|so| so.as_ref())
.map_or(-1, |so| so.num);
let tn = tmp_num as usize;
if tmp_num >= 0 {
if let Some(oargs) = state.oargs.as_mut() {
if tn < oargs.len() && oargs[tn].is_none() {
oargs[tn] = Some(Vec::new());
}
}
}
if let Some(xor) = tmp_xor {
ca_inactive(d, &xor, cur, 0);
}
}
p_idx += 1;
}
let pe_tail_present = end < line.as_bytes().len();
let pe_at_eq = end > 0
&& line.as_bytes().get(end - 1) == Some(&b'=');
let collect_arg = state.def.is_some() && (
co_type == CAO_DIRECT
|| co_type == CAO_EQUAL
|| (co_type == CAO_ODIRECT && pe_tail_present)
|| (co_type == CAO_OEQUAL && (pe_tail_present || pe_at_eq))
);
if collect_arg {
let dtype = state.def.as_deref().map_or(0, |d| d.r#type);
if dtype != CAA_REST && dtype != CAA_RARGS && dtype != CAA_RREST {
let next = state.def.as_deref().and_then(|d| d.next.clone());
state.def = next;
}
let arg_str = ca_opt_arg(&co_name, &line, false);
if let Some(oargs) = state.oargs.as_mut() {
if cn < oargs.len() {
oargs[cn].get_or_insert_with(Vec::new).push(arg_str);
}
}
}
if state.def.is_some() {
state.opt = 0;
} else {
state.curopt = None;
}
}
} else if multi != 0
&& line.as_bytes().first().copied().map_or(false,
|b| b == b'-' || b == b'+')
&& cur != compcur
&& ca_foreign_opt(d, all, &line) != 0
{
return 1; } else if state.arg != 0 && cur <= compcur { if let Some(np) = napat.as_ref() {
if cur < compcur && state.actopts != 0 {
if pattry(np, &line) {
cur += 1;
continue;
}
ca_inactive(d, &[], cur + 1, 1);
state.actopts = 0;
}
}
arglast = 1;
if state.inopt != 0 { state.inopt = 0;
state.nargbeg = cur - 1;
state.argend = argend_init;
}
let lb = line.as_bytes();
let non_flag = !lb.is_empty() && lb[0] != b'-' && lb[0] != b'+';
if d.args.is_none() && d.rest.is_none() && non_flag {
if multi == 0 && cur > compcur { break; }
return 1;
}
adef = ca_get_arg(d, state.nth);
state.def = adef.clone();
let dtype = state.def.as_deref().map_or(0, |d| d.r#type);
if state.def.is_some() && (dtype == CAA_RREST || dtype == CAA_RARGS) {
if ca_laststate.lock().map(|ls| ls.def.is_some()).unwrap_or(false) {
break;
}
state.opt = if cur == state.nargbeg + 1
&& (multi == 0
|| line.is_empty()
|| lb[0] == b'-'
|| lb[0] == b'+')
{ 1 } else { 0 };
state.optbeg = state.nargbeg;
state.argbeg = cur - 1;
state.argend = argend_init;
while line_idx < compwords.len() {
state.args.get_or_insert_with(Vec::new)
.push(compwords[line_idx].clone());
cur += 1;
if (cur - 1) as usize >= compwords.len() { break; }
}
if let Ok(mut ls) = ca_laststate.lock() {
*ls = clone_castate(&state, d);
ls.ddef = None;
ls.dopt = None;
}
break;
}
state.args.get_or_insert_with(Vec::new).push(line.clone());
if let Some(a) = adef.as_deref() {
state.oopt = a.num - state.nth;
}
if state.def.is_some() && cur != compcur { argxor = state.def.as_deref().and_then(|d| d.xor.clone());
}
let dtype2 = state.def.as_deref().map_or(0, |d| d.r#type);
if state.def.is_some() && dtype2 != CAA_NORMAL && dtype2 != CAA_OPT
&& state.inarg != 0
{
state.restbeg = cur;
state.inarg = 0;
} else if state.def.is_none() || dtype2 == CAA_NORMAL || dtype2 == CAA_OPT {
state.inarg = 1;
}
state.nth += 1;
state.def = None;
}
if state.def.is_some() && state.curopt.is_some() {
let dt = state.def.as_deref().map_or(0, |d| d.r#type);
if dt == CAA_RREST || dt == CAA_RARGS {
let end_pat_str = state.def.as_deref()
.and_then(|d| d.end.clone());
if let Some(eps) = end_pat_str {
endpat = patcompile(&eps, 0, None::<&mut String>);
} else {
if cur < compcur {
if let Ok(mut ls) = ca_laststate.lock() {
*ls = clone_castate(&state, d);
}
}
let cn = state.curopt.as_deref().map(|c| c.num as usize);
if let Some(cn) = cn {
if let Some(oargs) = state.oargs.as_mut() {
if cn < oargs.len() {
let bucket = oargs[cn].get_or_insert_with(Vec::new);
let mut k = line_idx;
while k < compwords.len() {
bucket.push(compwords[k].clone());
k += 1;
}
}
}
}
if let Ok(mut ls) = ca_laststate.lock() {
ls.ddef = None;
ls.dopt = None;
}
break;
}
}
} else if state.def.is_some() {
let eps = state.def.as_deref().and_then(|d| d.end.clone());
if let Some(eps) = eps {
endpat = patcompile(&eps, 0, None::<&mut String>);
}
}
if cur + 1 == compcur {
if let Ok(mut ls) = ca_laststate.lock() {
*ls = clone_castate(&state, d);
ls.ddef = None;
ls.dopt = None;
}
} else if cur == compcur {
let mut ls = ca_laststate.lock().unwrap();
if ls.def.is_none() {
if let Some(ddef_v) = ddef.clone() {
ls.def = Some(ddef_v);
ls.singles = state.singles;
if state.curopt.as_deref().map_or(false, |c| c.r#type == CAO_NEXT) {
ls.ddef = ddef.clone();
ls.dopt = dopt.clone();
ls.def = None;
ls.opt = 1;
if let Some(co) = state.curopt.as_deref() {
let target_name = co.name.clone();
let mut p = d.opts.as_deref_mut();
while let Some(op) = p {
if op.name == target_name {
op.active = 1;
break;
}
p = op.next.as_deref_mut();
}
}
} else {
ca_doff.store(doff, Ordering::Relaxed);
ls.opt = 0;
}
} else {
ls.def = adef.clone();
ls.opt = if arglast == 0
|| multi == 0
|| line.is_empty()
|| line.as_bytes()[0] == b'-'
|| line.as_bytes()[0] == b'+'
{ 1 } else { 0 };
ls.ddef = None;
ls.dopt = None;
ls.optbeg = state.nargbeg;
ls.argbeg = state.restbeg;
ls.argend = state.argend;
ls.singles = state.singles;
ls.oopt = state.oopt;
if let Some(wi) = wasopt_idx {
let mut p = d.opts.as_deref_mut();
while let Some(op) = p {
if op.num as usize == wi {
op.active = 1;
break;
}
p = op.next.as_deref_mut();
}
}
}
}
}
cur += 1;
}
let _ = (endpat, ddef, dopt, adef);
}
let mut actopts = 0i32;
let mut p = d.opts.as_deref();
while let Some(o) = p {
if o.active != 0 { actopts += 1; }
p = o.next.as_deref();
}
if let Ok(mut ls) = ca_laststate.lock() {
ls.actopts = actopts;
ls.d = Some(Box::new(clone_cadef_shallow(d)));
}
0
}
#[allow(dead_code)]
fn clone_castate(s: &castate, d: &cadef) -> castate {
castate {
snext: None,
d: Some(Box::new(clone_cadef_shallow(d))),
nopts: s.nopts,
def: s.def.clone(),
ddef: s.ddef.clone(),
curopt: s.curopt.clone(),
dopt: s.dopt.clone(),
opt: s.opt, arg: s.arg,
argbeg: s.argbeg, optbeg: s.optbeg,
nargbeg: s.nargbeg, restbeg: s.restbeg,
curpos: s.curpos, argend: s.argend,
inopt: s.inopt, inarg: s.inarg,
nth: s.nth,
singles: s.singles, oopt: s.oopt, actopts: s.actopts,
args: s.args.clone(),
oargs: s.oargs.clone(),
}
}
#[allow(dead_code)]
fn clone_cadef_shallow(d: &cadef) -> cadef {
cadef {
next: None,
snext: None,
opts: d.opts.clone(),
nopts: d.nopts,
ndopts: d.ndopts,
nodopts: d.nodopts,
args: d.args.clone(),
rest: d.rest.clone(),
defs: d.defs.clone(),
ndefs: d.ndefs,
lastt: d.lastt,
single: d.single.clone(),
r#match: d.r#match.clone(),
argsactive: d.argsactive,
set: d.set.clone(),
flags: d.flags,
nonarg: d.nonarg.clone(),
}
}
pub fn ca_set_data(descr: &mut Vec<String>, act: &mut Vec<String>,
subc: &mut Vec<String>,
opt: Option<&str>,
start_arg: Option<Box<caarg>>,
optdef: Option<&caopt>,
single: i32)
{
use crate::ported::zle::complete::restrict_range;
let mut arg: Option<Box<caarg>> = start_arg;
let mut opt = opt.map(|s| s.to_string());
let mut restr = 0;
let mut miss = 0;
let mut oopt = 1i32;
let mut lopt;
'rec: loop {
let addopt = if opt.is_some() {
0
} else {
ca_laststate.lock().map(|s| s.oopt).unwrap_or(0)
};
while let Some(a) = arg.as_ref() {
let cont = {
let nth = ca_laststate.lock().map(|s| s.nth).unwrap_or(0);
opt.is_some()
|| a.num < 0
|| (a.min <= nth + addopt && a.num >= nth)
};
if !cont { break; }
lopt = a.r#type == CAA_OPT; if opt.is_none() && !lopt && oopt > 0 { oopt = 0;
}
let mut dup = false;
let descr_str = a.descr.clone().unwrap_or_default();
let act_str = a.action.clone().unwrap_or_default();
for (d, ac) in descr.iter().zip(act.iter()) {
if d == &descr_str && ac == &act_str {
dup = true;
break;
}
}
if single != 0 && a.opt.is_none() {
return;
}
if !dup { descr.push(descr_str.clone()); act.push(act_str.clone());
if restr == 0 { let nrestr = if a.r#type == CAA_RARGS { let (optbeg, argend) = ca_laststate.lock()
.map(|s| (s.optbeg, s.argend)).unwrap_or((0, 0));
restrict_range(optbeg, argend);
1
} else if a.r#type == CAA_RREST { let (argbeg, argend) = ca_laststate.lock()
.map(|s| (s.argbeg, s.argend)).unwrap_or((0, 0));
restrict_range(argbeg, argend);
1
} else { 0 };
restr = nrestr;
}
let buf = if let Some(o) = a.opt.as_deref() { let gs = a.gsname.as_deref().unwrap_or("");
if a.num > 0 && a.r#type < CAA_REST { format!("{}option{}-{}", gs, o, a.num)
} else { format!("{}option{}-rest", gs, o)
}
} else if a.num > 0 { if let Some(gs) = a.gsname.as_deref() {
format!("{}argument-{}", gs, a.num)
} else {
format!("argument-{}", a.num)
}
} else { if let Some(gs) = a.gsname.as_deref() {
format!("{}argument-rest", gs)
} else {
"argument-rest".to_string()
}
};
subc.push(buf); }
if a.r#type == CAA_NORMAL && opt.is_some() {
if let Some(od) = optdef {
if od.r#type == CAO_NEXT
|| od.r#type == CAO_ODIRECT
|| od.r#type == CAO_OEQUAL
{
return;
}
}
}
if single != 0 { break; }
if opt.is_none() { let next_is_none_and_miss = a.num >= 0 && a.next.is_none() && miss != 0;
if next_is_none_and_miss { let rest = ca_laststate.lock().ok().and_then(|s| {
s.d.as_ref().and_then(|d| d.rest.clone())
});
arg = rest.filter(|r| r.active != 0); } else {
let onum = a.num; let nth = ca_laststate.lock().map(|s| s.nth).unwrap_or(0);
let rest_flag = onum != a.min && onum == nth; let next = a.next.clone();
if let Some(n) = next { if n.num != onum + 1 { miss = 1; } arg = Some(n);
} else if rest_flag || (oopt > 0 && opt.is_none()) { let rest = ca_laststate.lock().ok().and_then(|s| {
s.d.as_ref().and_then(|d| d.rest.clone())
});
arg = rest.filter(|r| r.active != 0);
oopt = -1;
} else {
arg = None;
}
}
} else { if !lopt { break; } arg = a.next.clone(); }
}
let laststate_oopt = ca_laststate.lock().map(|s| s.oopt).unwrap_or(0);
let cur_lopt = arg.as_ref().map_or(false, |a| a.r#type == CAA_OPT);
if single == 0 && opt.is_some() && (cur_lopt || laststate_oopt != 0) {
opt = None;
let nth = ca_laststate.lock().map(|s| s.nth).unwrap_or(0);
arg = ca_laststate.lock().ok().and_then(|s| {
s.d.as_ref().and_then(|d| ca_get_arg(d, nth))
});
continue 'rec;
}
if opt.is_none() && oopt > 0 {
oopt = -1;
let rest = ca_laststate.lock().ok().and_then(|s| {
s.d.as_ref().and_then(|d| d.rest.clone())
});
arg = rest.filter(|r| r.active != 0);
continue 'rec;
}
break 'rec;
}
}
pub fn cf_ignore(names: &[String], ign: &mut Vec<String>, style: &str, path: &str) { use std::os::unix::fs::MetadataExt;
use crate::ported::utils::quotestring;
use crate::ported::zsh_h::QT_BACKSLASH;
use crate::ported::zle::compresult::ztat;
let pl = path.len();
let tpar = style.contains("parent"); let pwd = crate::ported::params::getsparam("PWD").unwrap_or_default();
let est = if !pwd.is_empty() { ztat(&pwd, true) } else { None };
let tpwd = style.contains("pwd") && est.is_some();
if !tpar && !tpwd { return; }
for n in names { let nst = match ztat(n, true) { Some(m) if m.is_dir() => m,
_ => continue,
};
if tpwd {
if let Some(ref est) = est {
if nst.dev() == est.dev() && nst.ino() == est.ino() { ign.push(quotestring(n, QT_BACKSLASH)); continue;
}
}
}
if tpar && pl > 0 && n.starts_with(path) { let mut c = n.clone();
let mut found = false;
while let Some(idx) = c.rfind('/') {
if idx <= pl { break; }
c.truncate(idx);
if let Some(st) = ztat(&c, false) { if st.dev() == nst.dev() && st.ino() == nst.ino() {
found = true;
break;
}
}
}
let last_match = if !found {
if let Some(idx) = c.rfind('/') {
if idx > pl {
c.truncate(idx);
ztat(&c, true).map_or(false, |st|
st.dev() == nst.dev() && st.ino() == nst.ino())
} else { false }
} else { false }
} else { false };
if found || last_match {
ign.push(quotestring(n, QT_BACKSLASH)); }
}
}
}
pub fn cf_pats(dirs: i32, noopt: i32, names: &[String], accept: &[String], skipped: &str, matcher: &str,
sdirs: &str, fake: &[String], pats: &[String]) -> Vec<String> {
if let Some(exact) = cfp_test_exact(names, accept, skipped) {
let mut out = exact;
cfp_add_sdirs(&mut out, names, skipped, sdirs, fake); return out;
}
let dir_pats = vec!["*(-/)".to_string()];
let active_pats: Vec<String> = if dirs != 0 {
dir_pats
} else if noopt == 0 {
cfp_opt_pats(pats, matcher)
} else {
pats.to_vec()
};
let mut out = cfp_bld_pats(dirs, names, skipped, &active_pats);
cfp_add_sdirs(&mut out, names, skipped, sdirs, fake);
out
}
pub fn cf_remove_other(names: &[String], pre: &str, amb: &mut i32) -> Option<Vec<String>>
{
use crate::ported::utils::strpfx;
if let Some(slash) = pre.find('/') { let pre2 = format!("{}/", &pre[..slash]);
let any_match = names.iter().any(|n| strpfx(&pre2, n));
if any_match { let ret: Vec<String> = names.iter()
.filter(|n| strpfx(&pre2, n))
.cloned()
.collect();
*amb = 0;
return Some(ret); } else { let mut it = names.iter();
let Some(first) = it.next() else {
*amb = 0; return None;
};
let p_head = match first.find('/') {
Some(i) => format!("{}/", &first[..i]),
None => format!("{}/", first),
};
for n in it { if !strpfx(&p_head, n) {
*amb = 1; return None;
}
}
}
} else { let mut it = names.iter();
let Some(first) = it.next() else {
*amb = 0;
return None;
};
for n in it { if first != n { *amb = 1;
return None;
}
}
}
None }
pub fn cfp_add_sdirs(final_list: &mut Vec<String>, orig: &[String], _skipped: &str, sdirs: &str, fake: &[String]) {
let mut add = 0;
if !sdirs.is_empty() { match sdirs {
"yes" | "true" | "on" | "1" => add = 2, ".." => add = 1, _ => {}
}
}
if add > 0 {
for f in fake {
final_list.push(f.clone());
}
for o in orig {
if !final_list.contains(o) {
final_list.push(o.clone());
}
}
}
}
pub fn cfp_bld_pats(_dirs: i32, names: &[String], skipped: &str, pats: &[String]) -> Vec<String> {
use crate::ported::zsh_h::{unset, GLOBDOTS};
use crate::ported::zle::complete::COMPPREFIX;
let compprefix = COMPPREFIX.get()
.and_then(|m| m.lock().ok().map(|s| s.clone()))
.unwrap_or_default();
let dot = unset(GLOBDOTS) && compprefix.starts_with('.');
let mut ret: Vec<String> = Vec::new();
for o in names { for p in pats { ret.push(format!("{}{}{}", o, skipped, p));
if dot && !p.starts_with('.') {
ret.push(format!("{}{}.{}", o, skipped, p));
}
}
}
ret }
pub fn cfp_matcher_pats(matcher: &str, add: &str) -> String { use crate::ported::zle::comp_h::{Cmatcher, CMF_LEFT, CMF_RIGHT};
use crate::ported::zle::compmatch::pattern_match;
use crate::ported::zle::complete::parse_cmatcher;
use crate::ported::utils::ztrlen;
let m_chain = parse_cmatcher("", matcher);
let Some(mut m_chain) = m_chain else {
return add.to_string(); };
let zl = ztrlen(add); let mut ms: Vec<Option<Box<Cmatcher>>> = (0..zl).map(|_| None).collect();
let mut add_owned = add.to_string();
let mut m_opt: Option<&Cmatcher> = Some(&*m_chain);
while let Some(m) = m_opt {
let mut stopp: Option<&crate::ported::zle::comp_h::Cpattern> = None;
let mut stopl: i32 = 0;
if (m.flags & (CMF_LEFT | CMF_RIGHT)) == 0 { if m.llen == 1 && m.wlen == 1 { let chars: Vec<(usize, char)> = add_owned.char_indices().collect();
for (i, (byte_idx, _ch)) in chars.iter().enumerate() {
if i >= ms.len() { break; }
let slice = &add_owned[*byte_idx..];
if pattern_match(m.line.as_deref(), slice, None, "") != 0 {
if ms[i].is_some() {
add_owned.truncate(*byte_idx); break;
} else {
ms[i] = Some(Box::new(m.clone())); }
}
}
} else {
stopp = m.line.as_deref(); stopl = m.llen;
}
} else if (m.flags & CMF_RIGHT) != 0 { if m.wlen < 0 && m.llen == 0 && m.ralen == 1 { let chars: Vec<(usize, char)> = add_owned.char_indices().collect();
for (i, (byte_idx, _ch)) in chars.iter().enumerate() {
if i >= ms.len() { break; }
let slice = &add_owned[*byte_idx..];
if pattern_match(m.right.as_deref(), slice, None, "") != 0 {
let leading_dot = *byte_idx == 0 && slice.starts_with('.');
if ms[i].is_some() || leading_dot {
add_owned.truncate(*byte_idx); break;
} else {
ms[i] = Some(Box::new(m.clone()));
}
}
}
} else if m.llen != 0 { stopp = m.line.as_deref();
stopl = m.llen;
} else {
stopp = m.right.as_deref(); stopl = m.ralen;
}
} else { if m.lalen == 0 { return String::new(); }
stopp = m.left.as_deref();
stopl = m.lalen;
}
if let Some(sp) = stopp {
let chars: Vec<(usize, char)> = add_owned.char_indices().collect();
let mut bytes_remaining = add_owned.len() as i32;
for (_i, (byte_idx, _ch)) in chars.iter().enumerate() {
if bytes_remaining < stopl { break; }
let slice = &add_owned[*byte_idx..];
if pattern_match(Some(sp), slice, None, "") != 0 {
add_owned.truncate(*byte_idx); break;
}
bytes_remaining -= 1;
}
}
m_opt = m.next.as_deref();
}
if !add_owned.is_empty() {
cfp_matcher_range(&ms, &add_owned)
} else {
add_owned }
}
pub fn cfp_matcher_range(ms: &[Option<Box<crate::ported::zle::comp_h::Cmatcher>>], add: &str) -> String
{
use crate::ported::zle::comp_h::{CMF_RIGHT, Cpattern};
use crate::ported::zle::comp_h::{CPAT_ANY, CPAT_CCLASS, CPAT_CHAR,
CPAT_EQUIV, CPAT_NCLASS};
use crate::ported::zle::compmatch::{pattern_match1, pattern_match_equivalence};
use crate::ported::pattern::pattern_range_to_string;
use crate::ported::utils::imeta;
fn patmatchrange_local(s: Option<&str>, c: u32) -> Option<(u32, i32)> {
let s = s?;
let mut idx: u32 = 0;
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if let Some(&peek) = chars.peek() {
if peek == '-' {
chars.next();
if let Some(hi) = chars.next() {
if c >= ch as u32 && c <= hi as u32 {
return Some((idx, 0));
}
idx += 1;
continue;
}
}
}
if c == ch as u32 {
return Some((idx, 0));
}
idx += 1;
}
None
}
let mut out = String::with_capacity(add.len() * 2);
let add_chars: Vec<(usize, char)> = add.char_indices().collect();
for (i, (_byte_idx, ch)) in add_chars.iter().enumerate() {
let addc = *ch as u32;
let m_opt = ms.get(i).and_then(|x| x.as_deref());
match m_opt {
None => {
out.push(*ch);
}
Some(m) if (m.flags & CMF_RIGHT) != 0 => {
out.push('*');
out.push(*ch);
}
Some(m) => {
let word: Option<&Cpattern> = m.word.as_deref();
let line: Option<&Cpattern> = m.line.as_deref();
if let (Some(l), Some(w)) = (line, word) {
if l.tp == CPAT_EQUIV && w.tp == CPAT_EQUIV {
out.push('[');
out.push(*ch);
if let Some((ind, mtp)) = patmatchrange_local(
l.str.as_deref(), addc)
{
let eq = pattern_match_equivalence(
w, ind + 1, mtp, addc);
if eq != u32::MAX {
if let Some(c) = char::from_u32(eq) {
let _ = imeta(c); out.push(c);
}
}
}
out.push(']');
continue;
}
}
if let Some(w) = word {
match w.tp {
x if x == CPAT_NCLASS => { out.push('[');
out.push('^');
if let Some(idx_str) = w.str.as_deref() {
if let Ok(idx) = idx_str.parse::<usize>() {
if let Some(cls) = pattern_range_to_string(idx) {
out.push_str(&cls);
} else {
out.push_str(idx_str);
}
} else {
out.push_str(idx_str);
}
}
out.push(']');
}
x if x == CPAT_CCLASS
|| x == CPAT_EQUIV
|| x == CPAT_CHAR => { out.push('[');
let mut mt = 0i32;
let addadd = pattern_match1(w, addc, &mut mt) == 0;
if addadd && *ch == ']' {
out.push(*ch);
}
if w.tp == CPAT_CHAR { if let Some(c) = char::from_u32(w.chr) {
out.push(c);
}
} else { if let Some(idx_str) = w.str.as_deref() {
if let Ok(idx) = idx_str.parse::<usize>() {
if let Some(cls) = pattern_range_to_string(idx) {
out.push_str(&cls);
} else {
out.push_str(idx_str);
}
} else {
out.push_str(idx_str);
}
}
}
if addadd && *ch != ']' { out.push(*ch);
}
out.push(']');
}
x if x == CPAT_ANY => { out.push('?');
}
_ => {
out.push(*ch);
}
}
} else {
out.push(*ch);
}
}
}
}
out
}
pub fn cfp_opt_pats(pats: &[String], matcher: &str) -> Vec<String> { use crate::ported::glob::{remnulargs, tokenize};
use crate::ported::pattern::haswilds;
use crate::ported::zle::compcore::comppatmatch;
use crate::ported::zle::compcore::rembslash;
use crate::ported::zle::complete::{COMPPREFIX, COMPSUFFIX};
use crate::ported::ztype_h::idigit;
let compprefix = COMPPREFIX.get()
.and_then(|m| m.lock().ok().map(|s| s.clone()))
.unwrap_or_default();
if compprefix.is_empty() { return pats.to_vec();
}
let compsuffix = COMPSUFFIX.get()
.and_then(|m| m.lock().ok().map(|s| s.clone()))
.unwrap_or_default();
let cpm_set = comppatmatch.get()
.and_then(|m| m.lock().ok().map(|g| g.is_some()))
.unwrap_or(false);
if cpm_set {
let merged = format!("{}{}", compprefix, compsuffix);
let mut t = rembslash(&merged);
tokenize(&mut t);
remnulargs(&mut t);
if haswilds(&t) {
return pats.to_vec(); }
}
const SPECIALS: &[u8] = b"*?<>()[]|#^~=";
let cp_bytes = compprefix.as_bytes();
let mut add: Vec<u8> = Vec::with_capacity(cp_bytes.len() * 2);
let mut i = 0usize;
while i < cp_bytes.len() {
let c = cp_bytes[i];
let keep = if c == b'\\' && i + 1 < cp_bytes.len() {
let next = cp_bytes[i + 1];
!SPECIALS.contains(&next)
} else {
true
};
if keep {
let unescaped_at_start = i == 0 || cp_bytes[i - 1] != b'\\';
if unescaped_at_start && SPECIALS.contains(&c) { add.push(b'\\');
}
add.push(c);
}
i += 1;
}
let mut add_s: String = String::from_utf8_lossy(&add).into_owned();
for p_orig in pats {
if add_s.is_empty() { break; }
let mut q_bytes: Vec<u8> = p_orig.as_bytes().to_vec();
if q_bytes.is_empty() { continue; }
if let Some(b')') = q_bytes.last().copied() {
let mut t = q_bytes.len() - 1;
let mut found = None;
while t > 0 {
t -= 1;
let c = q_bytes[t];
if c == b')' || c == b'|' || c == b'~' || c == b'(' {
found = Some((t, c));
break;
}
}
if let Some((idx, c)) = found {
if c == b'(' {
q_bytes.truncate(idx);
}
}
}
let mut qi = 0usize;
while qi < q_bytes.len() && !add_s.is_empty() {
let c = q_bytes[qi];
if c == b'\\' && qi + 1 < q_bytes.len() { qi += 1;
let target = q_bytes[qi];
if let Some(pos) = add_s.find(target as char) {
add_s.truncate(pos);
}
} else if c == b'<' { let cut_at = add_s.bytes().position(|b| idigit(b));
if let Some(pos) = cut_at {
add_s.truncate(pos);
}
} else if c == b'[' { let mut xi = qi + 1;
let not = xi < q_bytes.len()
&& (q_bytes[xi] == b'!' || q_bytes[xi] == b'^');
if not { xi += 1; }
let _ = not;
while xi < q_bytes.len() && q_bytes[xi] != b']' {
if xi + 2 < q_bytes.len() && q_bytes[xi + 1] == b'-' {
let c1 = q_bytes[xi];
let c2 = q_bytes[xi + 2];
let cut_at = add_s.bytes().position(|b| b >= c1 && b <= c2);
if let Some(pos) = cut_at {
add_s.truncate(pos);
}
xi += 3;
} else {
let cut_at = add_s.find(q_bytes[xi] as char);
if let Some(pos) = cut_at {
add_s.truncate(pos);
}
xi += 1;
}
}
qi = xi;
} else if c != b'?' && c != b'*' && c != b'(' && c != b')'
&& c != b'|' && c != b'~' && c != b'#' {
let cut_at = add_s.find(c as char);
if let Some(pos) = cut_at {
add_s.truncate(pos);
}
}
qi += 1;
}
}
let mut out: Vec<String> = pats.to_vec();
if !add_s.is_empty() {
let final_add = if !matcher.is_empty() {
let m = cfp_matcher_pats(matcher, &add_s);
if m.is_empty() { return out; } m
} else {
add_s
};
for p in out.iter_mut() {
if p.starts_with('*') {
*p = format!("{}{}", final_add, p);
}
}
}
out
}
pub fn cfp_test_exact(names: &[String], accept: &[String], skipped: &str) -> Option<Vec<String>>
{
use crate::ported::glob::tokenize;
use crate::ported::pattern::{patcompile, pattry, Patprog};
use crate::ported::zle::compcore::rembslash;
use crate::ported::zle::complete::{COMPPREFIX, COMPSUFFIX};
use crate::ported::zle::compresult::ztat;
let compprefix = COMPPREFIX.get()
.and_then(|m| m.lock().ok().map(|s| s.clone()))
.unwrap_or_default();
let compsuffix = COMPSUFFIX.get()
.and_then(|m| m.lock().ok().map(|s| s.clone()))
.unwrap_or_default();
if compprefix.is_empty() && compsuffix.is_empty() {
return None;
}
let accept_off = accept.is_empty()
|| (accept.len() == 1 && matches!(accept[0].as_str(),
"false" | "no" | "off" | "0"));
if accept_off { return None;
}
let mut alist: Option<Vec<Patprog>> = None;
let is_boolean_true = accept.len() == 1
&& matches!(accept[0].as_str(), "true" | "yes" | "on" | "1");
if !is_boolean_true {
let mut list: Vec<Patprog> = Vec::new();
let mut all_star = false;
for p in accept {
if p == "*" { all_star = true;
break;
}
let mut p_copy = p.clone();
tokenize(&mut p_copy);
if let Some(prog) = patcompile(&p_copy, 0, None::<&mut String>) {
list.push(prog);
}
}
if !all_star { alist = Some(list); }
}
let sl = skipped.len() + compprefix.len() + compsuffix.len();
if sl > PATH_MAX2 { return None;
}
let suf = format!("{}{}",
skipped,
rembslash(&format!("{}{}", compprefix, compsuffix)));
let mut ret: Vec<String> = Vec::new();
for p in names { let l = p.len();
if l + sl >= PATH_MAX2 { continue; } let buf = format!("{}{}", p, suf);
if ztat(&buf, false).is_none() { continue; } if let Some(ref ps) = alist {
let any_match = ps.iter().any(|prog| pattry(prog, &buf));
if !any_match { continue; }
}
ret.push(buf); }
if ret.is_empty() { None } else { Some(ret) } }
pub fn cleanup_() -> i32 { 0
}
pub fn comp_quote(str: &str, prefix: i32) -> String { let (s_eff, x) = if prefix != 0 && str.starts_with('=') { ("x".to_string() + &str[1..], true) } else {
(str.to_string(), false)
};
let qhead = COMPQSTACK.get()
.and_then(|m| m.lock().ok().and_then(|str| str.bytes().next()))
.unwrap_or(0);
let mut ret = crate::ported::zle::zle_tricky::quotename(&s_eff, qhead as i32);
if x {
if !ret.is_empty() {
ret.replace_range(0..1, "=");
}
}
ret
}
pub fn cv_get_val(d: &cvdef, name: &str) -> Option<Box<cvval>> { let mut p = d.vals.as_deref();
while let Some(v) = p { if v.name.as_deref() == Some(name) { return Some(Box::new(cvval {
next: None,
name: v.name.clone(),
descr: v.descr.clone(),
xor: v.xor.clone(),
r#type: v.r#type,
arg: v.arg.clone(),
active: v.active,
}));
}
p = v.next.as_deref();
}
None }
pub fn cv_inactive(d: &mut cvdef, xor: &[String]) { for name in xor { let mut p = d.vals.as_deref_mut();
while let Some(v) = p {
if v.name.as_deref() == Some(name.as_str()) {
v.active = 0; }
p = v.next.as_deref_mut();
}
}
}
pub fn cv_next(d: &cvdef, sp: &mut Option<String>, ap: &mut Option<String>) -> Option<Box<cvval>>
{
let s_in = sp.take().unwrap_or_default();
if s_in.is_empty() { *sp = None;
*ap = None;
return None;
}
let bytes = s_in.as_bytes();
if (d.hassep != 0 && d.sep == 0) || d.argsep == 0 { let ec_byte: u8 = if d.hassep != 0 && d.sep != 0 {
d.sep as u8
} else {
d.argsep as u8
};
let mut s_idx: usize = 0;
let mut r: Option<Box<cvval>> = None;
loop {
s_idx += 1;
if s_idx > bytes.len() { break; }
let candidate = std::str::from_utf8(&bytes[..s_idx])
.unwrap_or("");
r = cv_quote_get_val(d, candidate);
if r.is_some() { break; }
if s_idx >= bytes.len() || bytes[s_idx] == ec_byte { break; }
}
let os = s_idx;
if d.hassep != 0 && d.sep != 0 {
let sep_byte = d.sep as u8;
if let Some(off) = bytes[s_idx.min(bytes.len())..]
.iter().position(|&b| b == sep_byte)
{
let after = s_idx + off + 1;
*sp = Some(String::from_utf8_lossy(&bytes[after..]).into_owned());
} else {
*sp = None;
}
} else {
*sp = if s_idx < bytes.len() {
Some(String::from_utf8_lossy(&bytes[s_idx..]).into_owned())
} else { None };
}
let argsep_b = d.argsep as u8;
if d.argsep != 0 && os < bytes.len() && bytes[os] == argsep_b {
*ap = Some(String::from_utf8_lossy(&bytes[os + 1..]).into_owned());
*sp = None;
} else if r.as_deref().map_or(false, |v| v.r#type != CVV_NOARG) {
*ap = if os < bytes.len() {
Some(String::from_utf8_lossy(&bytes[os..]).into_owned())
} else { None };
} else {
*ap = None;
}
return r;
}
if d.hassep != 0 { let sep_b = d.sep as u8;
let argsep_b = d.argsep as u8;
let ns = bytes.iter().position(|&b| b == sep_b);
let mut as_off: Option<usize> = None;
let mut skip = false;
if d.argsep != 0 {
if let Some(a_pos) = bytes.iter().position(|&b| b == argsep_b) {
if ns.map_or(true, |n| a_pos <= n) { as_off = Some(a_pos);
*ap = Some(String::from_utf8_lossy(&bytes[a_pos + 1..])
.into_owned());
skip = true;
}
}
}
let sap = as_off.or(ns);
let head = match sap {
Some(p) => std::str::from_utf8(&bytes[..p]).unwrap_or(""),
None => std::str::from_utf8(&bytes).unwrap_or(""),
};
let r = cv_quote_get_val(d, head);
let ns_eff = if (r.as_deref().map_or(true, |v| v.r#type == CVV_NOARG)) && skip {
as_off
} else if skip {
ns.filter(|&n| as_off.map_or(true, |a| n > a))
} else {
ns
};
let next_off = match ns_eff {
None => None,
Some(n) if Some(n) == as_off
&& r.as_deref().map_or(false, |v| v.r#type != CVV_NOARG) => None,
Some(n) => Some(n + 1),
};
*sp = next_off.map(|o|
String::from_utf8_lossy(&bytes[o..]).into_owned());
if !skip { *ap = None; }
return r;
}
*sp = None; let argsep_b = d.argsep as u8;
let as_pos = bytes.iter().position(|&b| b == argsep_b);
let head = match as_pos {
Some(p) => {
*ap = Some(String::from_utf8_lossy(&bytes[p + 1..]).into_owned());
std::str::from_utf8(&bytes[..p]).unwrap_or("")
}
None => {
*ap = None;
&s_in
}
};
cv_quote_get_val(d, head) }
pub fn cv_parse_word(d: &mut cvdef) { use crate::ported::zle::complete::{COMPCURRENT, COMPWORDS,
COMPPREFIX, COMPSUFFIX, ignore_prefix, ignore_suffix};
use std::sync::atomic::Ordering;
if cv_alloced.load(Ordering::Relaxed) != 0 {
if let Ok(mut ls) = cv_laststate.lock() {
ls.vals = None;
}
}
let mut v = d.vals.as_deref_mut();
while let Some(vv) = v {
vv.active = 1;
v = vv.next.as_deref_mut();
}
let mut state_vals: Vec<String> = Vec::new();
let mut state_def: Option<Box<caarg>> = None;
let mut state_val: Option<Box<cvval>> = None;
cv_alloced.store(1, Ordering::Relaxed);
let compcur = COMPCURRENT.load(Ordering::Relaxed);
let compwords: Vec<String> = COMPWORDS
.get()
.and_then(|m| m.lock().ok().map(|w| w.clone()))
.unwrap_or_default();
let compprefix: String = COMPPREFIX.get()
.and_then(|m| m.lock().ok().map(|s| s.clone()))
.unwrap_or_default();
let compsuffix: String = COMPSUFFIX.get()
.and_then(|m| m.lock().ok().map(|s| s.clone()))
.unwrap_or_default();
let mut pign = compprefix.clone(); let mut nosfx = false;
if d.words != 0 && !compwords.is_empty() && !compwords[0].is_empty() {
for i in 1..compwords.len() {
if (i as i32) == compcur - 1 { continue; }
let mut str_opt: Option<String> = Some(compwords[i].clone());
while str_opt.as_deref().map_or(false, |s| !s.is_empty()) {
let mut ap: Option<String> = None;
let val = cv_next(d, &mut str_opt, &mut ap);
if let Some(v) = val {
state_vals.push(v.name.clone().unwrap_or_default());
state_vals.push(ap.unwrap_or_default());
if (i as i32) + 1 < compcur {
let xor = v.xor.clone().unwrap_or_default();
cv_inactive(d, &xor);
}
} else {
break;
}
}
}
}
let mut str_opt: Option<String> = Some(compprefix.clone());
let mut last_arg: Option<String> = None;
while str_opt.as_deref().map_or(false, |s| !s.is_empty()) {
let mut ap: Option<String> = None;
let val = cv_next(d, &mut str_opt, &mut ap);
if let Some(v) = val {
state_vals.push(v.name.clone().unwrap_or_default());
match ap.as_deref() {
Some(arg_v) => {
if str_opt.is_some() {
state_vals.push(arg_v.to_string());
} else {
let joined = format!("{}{}", arg_v, compsuffix);
state_vals.push(joined);
nosfx = true;
}
last_arg = ap.clone();
}
None => state_vals.push(String::new()),
}
let xor = v.xor.clone().unwrap_or_default();
cv_inactive(d, &xor);
if let Some(s) = str_opt.as_deref() {
pign = s.to_string();
} else {
let target_name = v.name.clone();
let mut p = d.vals.as_deref_mut();
while let Some(vv) = p {
if vv.name == target_name { vv.active = 1; break; }
p = vv.next.as_deref_mut();
}
}
state_val = Some(v);
} else {
break;
}
}
if state_val.is_some() && last_arg.is_some() && str_opt.is_none() { state_def = state_val.as_ref().and_then(|v| v.arg.clone());
}
if !nosfx && d.hassep != 0 {
let pign_len = pign.len();
let cp_len = compprefix.len();
ignore_prefix(cp_len as i32 - pign_len as i32);
let mut ign = 0usize;
let mut more: Option<String> = None;
if d.sep == 0 && (state_val.is_none()
|| state_val.as_deref().map_or(true, |v| v.r#type == CVV_NOARG))
{
ign = compsuffix.len();
more = Some(compsuffix.clone());
} else if d.sep != 0 {
let sep_b = d.sep as u8;
let ns_pos = compsuffix.as_bytes().iter().position(|&b| b == sep_b);
let as_pos = if d.argsep != 0 {
compsuffix.as_bytes().iter().position(|&b| b == d.argsep as u8)
} else { None };
if let Some(a) = as_pos {
if ns_pos.map_or(true, |n| a <= n) {
ign = compsuffix.len() - a;
} else {
ign = ns_pos.map_or(0, |n| compsuffix.len() - n);
}
} else {
ign = ns_pos.map_or(0, |n| compsuffix.len() - n);
}
more = ns_pos.map(|n| compsuffix[n + 1..].to_string());
} else if d.argsep != 0 {
let as_pos = compsuffix.as_bytes().iter()
.position(|&b| b == d.argsep as u8);
if let Some(a) = as_pos {
ign = compsuffix.len() - a;
}
}
if ign > 0 {
ignore_suffix(ign as i32); }
let mut more_opt = more;
while more_opt.as_deref().map_or(false, |s| !s.is_empty()) { let mut ap: Option<String> = None;
let val = cv_next(d, &mut more_opt, &mut ap);
if let Some(v) = val {
state_vals.push(v.name.clone().unwrap_or_default());
match ap.as_deref() {
Some(arg_v) => {
if more_opt.is_some() {
state_vals.push(arg_v.to_string());
} else {
state_vals.push(format!("{}{}", arg_v, compsuffix));
}
}
None => state_vals.push(String::new()),
}
let xor = v.xor.clone().unwrap_or_default();
cv_inactive(d, &xor);
} else { break; }
}
} else if last_arg.is_some() {
let cp_len = compprefix.len();
let arg_off = compprefix.find(last_arg.as_deref().unwrap_or(""))
.map(|i| i as i32).unwrap_or(cp_len as i32);
ignore_prefix(arg_off); } else {
let cp_len = compprefix.len();
ignore_prefix(cp_len as i32 - pign.len() as i32); }
if let Ok(mut ls) = cv_laststate.lock() {
*ls = cvstate {
d: Some(Box::new(d.clone())),
def: state_def,
val: state_val,
vals: if state_vals.is_empty() { None } else { Some(state_vals) },
};
}
}
pub fn cv_quote_get_val(d: &cvdef, name: &str) -> Option<Box<cvval>> { let mut s = name.to_string();
crate::ported::utils::set_noerrs(2);
let parsed = crate::ported::lex::parse_subst_string(&s).ok();
crate::ported::utils::set_noerrs(0);
if let Some(p) = parsed { s = p; }
crate::ported::glob::remnulargs(&mut s);
let s = crate::ported::lex::untokenize(&s);
cv_get_val(d, &s)
}
pub fn enables_() -> i32 { 0
}
pub fn features_() -> i32 { 0
}
pub fn finish_() -> i32 { if let Ok(mut cache) = cadef_cache.lock() {
for slot in cache.iter_mut() {
freecadef(slot.take());
}
}
if let Ok(mut cache) = cvdef_cache.lock() {
for slot in cache.iter_mut() {
freecvdef(slot.take());
}
}
if let Ok(mut tab) = comptags.lock() {
for slot in tab.iter_mut() {
freectags(slot.take());
}
}
0 }
pub fn setup_() -> i32 { if let Ok(mut cache) = cadef_cache.lock() {
for slot in cache.iter_mut() {
freecadef(slot.take());
}
}
if let Ok(mut cache) = cvdef_cache.lock() {
for slot in cache.iter_mut() {
freecvdef(slot.take());
}
}
if let Ok(mut tab) = comptags.lock() {
for slot in tab.iter_mut() {
freectags(slot.take());
}
}
lasttaglevel.store(0, std::sync::atomic::Ordering::Relaxed);
0 }
pub fn get_cadef(nam: &str, args: &[String]) -> i32 { let na = args.len() as i32;
let now = { use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now().duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64).unwrap_or(0)
};
if let Ok(mut cache) = cadef_cache.lock() {
let mut min_idx: Option<usize> = None;
let mut min_lastt: i64 = i64::MAX;
let mut hit_idx: Option<usize> = None;
for (i, slot) in cache.iter().enumerate() {
match slot {
Some(entry) => {
if entry.ndefs == na
&& entry.defs.as_deref()
.map_or(false, |d| d.len() == args.len()
&& d.iter().zip(args.iter()).all(|(a, b)| a == b))
{
hit_idx = Some(i);
break; }
if entry.lastt < min_lastt {
min_lastt = entry.lastt;
min_idx = Some(i);
}
}
None => {
min_idx = Some(i);
break;
}
}
}
if let Some(i) = hit_idx {
if let Some(entry) = cache[i].as_mut() {
entry.lastt = now; }
return 1; }
if let Some(new) = parse_cadef(nam, args) {
let idx = min_idx.unwrap_or(0);
cache[idx] = Some(new);
}
}
0 }
pub fn parse_caarg(mult: i32, atype: i32, num: i32, opt: i32, oname: Option<&str>, bytes: &[u8], idx: &mut usize,
set: Option<&str>) -> Box<caarg> {
let mut ret = Box::new(caarg::default());
ret.num = num; ret.min = num - opt; ret.r#type = atype; ret.opt = oname.map(|s| s.to_string()); ret.direct = 0; ret.gsname = set.map(|s| s.to_string());
let n = bytes.len();
let d_start = *idx;
while *idx < n && bytes[*idx] != b':' {
if bytes[*idx] == b'\\' && *idx + 1 < n {
*idx += 1;
}
*idx += 1;
}
let has_sav = *idx < n;
let descr_slice = &bytes[d_start..*idx];
let descr_str = std::str::from_utf8(descr_slice).unwrap_or("");
ret.descr = Some(rembslashcolon(descr_str));
if has_sav { if mult != 0 { *idx += 1;
let a_start = *idx;
while *idx < n && bytes[*idx] != b':' {
if bytes[*idx] == b'\\' && *idx + 1 < n {
*idx += 1;
}
*idx += 1;
}
let action_slice = &bytes[a_start..*idx];
let action_str = std::str::from_utf8(action_slice).unwrap_or("");
ret.action = Some(rembslashcolon(action_str)); } else { let action_slice = &bytes[*idx + 1..];
let action_str = std::str::from_utf8(action_slice).unwrap_or("");
ret.action = Some(rembslashcolon(action_str));
*idx = n;
}
} else { ret.action = Some(String::new()); }
ret
}
pub fn parse_cadef(nam: &str, args: &[String]) -> Option<Box<cadef>> { use crate::ported::ztype_h::{iblank, idigit, inblank};
if args.is_empty() {
return None; }
let orig_args = args;
let mut idx = 0usize;
let mut single: i32 = 0;
let mut flags: i32 = 0;
let mut match_spec: String = "r:|[_-]=* r:|=*".to_string(); let mut nonarg: Option<String> = None;
let (adpre, adsuf): (Option<String>, Option<String>) = {
let first = args[0].as_bytes();
let mut split_at: Option<usize> = None;
let mut i = 0usize;
while i + 1 < first.len() {
if first[i] == b'%' && first[i + 1] == b'd' {
split_at = Some(i);
break;
}
i += 1;
}
if let Some(at) = split_at {
let pre = String::from_utf8_lossy(&first[..at]).into_owned();
let suf = String::from_utf8_lossy(&first[at + 2..]).into_owned();
(Some(pre), Some(suf))
} else {
(None, None)
}
};
idx += 1;
while idx < args.len() {
let p = &args[idx];
let bytes = p.as_bytes();
if bytes.len() < 2 || bytes[0] != b'-' { break;
}
let cluster = &bytes[1..];
let mut ok = true;
for (i, &c) in cluster.iter().enumerate() {
match c {
b's' => single = 1, b'S' => flags |= CDF_SEP, b'A' => { if i + 1 < cluster.len() { nonarg = Some(String::from_utf8_lossy(&cluster[i + 1..]).into_owned());
} else if idx + 1 < args.len() { nonarg = Some(args[idx + 1].clone());
idx += 1;
} else {
ok = false;
}
break;
}
b'M' => { if i + 1 < cluster.len() { match_spec = String::from_utf8_lossy(&cluster[i + 1..]).into_owned();
} else if idx + 1 < args.len() { match_spec = args[idx + 1].clone();
idx += 1;
} else {
ok = false;
}
break;
}
_ => {
ok = false;
break;
}
}
}
if !ok {
break; }
idx += 1; }
if idx < args.len() && args[idx] == ":" { idx += 1;
}
if idx >= args.len() { return None;
}
let first_def = alloc_cadef(
Some(orig_args),
single,
&match_spec,
nonarg.as_deref(),
flags,
);
let mut sets: Vec<Box<cadef>> = vec![first_def];
let mut opts_per_set: Vec<Vec<Box<caopt>>> = vec![Vec::new()];
let mut args_per_set: Vec<Vec<Box<caarg>>> = vec![Vec::new()];
let mut rest_per_set: Vec<Option<Box<caarg>>> = vec![None];
let sargs = idx; let mut anum: i32 = 1; let mut doset: Option<String> = None;
let mut axor: Option<String> = None;
let mut curset: Option<usize> = None; let mut pendset: Option<usize> = None;
let mut foreignset = false;
'outer: loop {
if idx >= args.len() {
if pendset.is_none() {
break 'outer;
}
{
let cur = sets.last_mut().unwrap();
let cur_args = args_per_set.last_mut().unwrap();
let mut head: Option<Box<caarg>> = None;
for arg_box in cur_args.drain(..).rev() {
let mut a = arg_box;
a.next = head;
head = Some(a);
}
cur.args = head;
set_cadef_opts(cur); let mut walk = cur.args.take();
while let Some(mut node) = walk {
walk = node.next.take();
cur_args.push(node);
}
}
idx = sargs; doset = None; sets.push(alloc_cadef(None, single, &match_spec, nonarg.as_deref(), flags));
opts_per_set.push(Vec::new());
args_per_set.push(Vec::new());
rest_per_set.push(None);
anum = 1; foreignset = false; curset = pendset; pendset = None; }
let arg = &args[idx];
let arg_bytes = arg.as_bytes();
if arg_bytes == b"-" && idx + 1 < args.len() {
if curset.is_some() && curset != Some(idx) { foreignset = true;
if pendset.is_none() && Some(idx) > curset { pendset = Some(idx);
}
idx += 1; } else { foreignset = false;
idx += 1;
let p_str = &args[idx]; let pb = p_str.as_bytes();
let l = pb.len().saturating_sub(1);
let (set_name, ax) = if !pb.is_empty()
&& pb[0] == b'(' && pb[l] == b')'
{
let inner = String::from_utf8_lossy(&pb[1..l]).into_owned();
(inner.clone(), Some(inner))
} else {
(p_str.clone(), None)
};
axor = ax;
if set_name.is_empty() { zwarnnam(nam, "empty set name");
return None;
}
let new_set = crate::ported::string::tricat(&set_name, "-", ""); doset = Some(new_set.clone());
{
let cur = sets.last_mut().unwrap();
cur.set = Some(new_set);
}
curset = Some(idx); }
idx += 1;
continue; }
if arg_bytes == b"+" && idx + 1 < args.len() {
foreignset = false; idx += 1;
let p_str = &args[idx]; let pb = p_str.as_bytes();
let l = pb.len().saturating_sub(1);
let (group_name, ax) = if !pb.is_empty()
&& pb[0] == b'(' && pb[l] == b')'
{
let inner = String::from_utf8_lossy(&pb[1..l]).into_owned();
(inner.clone(), Some(inner))
} else {
(p_str.clone(), None)
};
axor = ax;
if group_name.is_empty() { zwarnnam(nam, "empty group name");
return None;
}
doset = Some(crate::ported::string::tricat(&group_name, "-", "")); idx += 1;
continue; }
if foreignset {
idx += 1;
continue;
}
let bytes = arg_bytes;
let mut p = 0usize;
let mut xnum: i32 = 0; let mut not_flag = false;
if p < bytes.len() && bytes[p] == b'!' { not_flag = true;
p += 1;
}
let mut xor: Option<Vec<String>> = None;
if p < bytes.len() && bytes[p] == b'(' { let mut list: Vec<String> = Vec::new();
let mut bad = false;
'paren: loop {
if p >= bytes.len() || bytes[p] == b')' { break; }
p += 1; while p < bytes.len() && inblank(bytes[p]) { p += 1; } if p >= bytes.len() { bad = true; break 'paren; }
if bytes[p] == b')' { break 'paren; }
let q = p;
p += 1;
while p < bytes.len() && bytes[p] != b')' && !inblank(bytes[p]) {
p += 1;
}
if p >= bytes.len() { bad = true; break 'paren; } let word = String::from_utf8_lossy(&bytes[q..p]).into_owned();
list.push(word);
xnum += 1; }
if bad || p >= bytes.len() || bytes[p] != b')' { zwarnnam(nam, &format!("invalid argument: {}", arg));
return None;
}
if doset.is_some() && axor.is_some() { xnum += 1;
list.push(axor.clone().unwrap()); }
xor = Some(list);
p += 1; } else if doset.is_some() && axor.is_some() { xnum = 1;
xor = Some(vec![axor.clone().unwrap()]);
}
let is_opt = p < bytes.len() && (
bytes[p] == b'-' || bytes[p] == b'+'
|| (bytes[p] == b'*' && p + 1 < bytes.len()
&& (bytes[p + 1] == b'-' || bytes[p + 1] == b'+'))
);
if is_opt {
let mut again_iter = 0i32; let mut againp_start: Option<usize> = None;
let mut p_state = p;
let mut xor_state = xor;
let mut xnum_state = xnum;
'rec: loop {
let mut multi = false; if p_state < bytes.len() && bytes[p_state] == b'*' {
multi = true;
p_state += 1;
}
let mut name_start: usize;
let mut name_buf: Vec<u8>;
let need_flip = p_state + 2 < bytes.len()
&& ((bytes[p_state] == b'-' && bytes[p_state + 1] == b'+')
|| (bytes[p_state] == b'+' && bytes[p_state + 1] == b'-'))
&& bytes[p_state + 2] != b':'
&& bytes[p_state + 2] != b'['
&& bytes[p_state + 2] != b'='
&& bytes[p_state + 2] != b'-'
&& bytes[p_state + 2] != b'+';
if need_flip { if again_iter == 0 {
againp_start = Some(p_state);
}
name_start = p_state + 1;
name_buf = bytes[name_start..].to_vec();
if !name_buf.is_empty() {
name_buf[0] = if again_iter != 0 { b'-' } else { b'+' };
}
again_iter += 1;
p_state = name_start;
} else { name_start = p_state;
name_buf = bytes[name_start..].to_vec();
if p_state + 1 < bytes.len()
&& bytes[p_state] == b'-' && bytes[p_state + 1] == b'-'
{
p_state += 1; }
}
if p_state + 1 >= bytes.len() { zwarnnam(nam, &format!("invalid argument: {}", arg));
return None;
}
let mut np = p_state - name_start + 1;
let nlen = name_buf.len();
while np < nlen
&& name_buf[np] != b':'
&& name_buf[np] != b'['
&& !((name_buf[np] == b'-' || name_buf[np] == b'+')
&& np + 1 < nlen
&& (name_buf[np + 1] == b':' || name_buf[np + 1] == b'['))
&& !(name_buf[np] == b'='
&& np + 1 < nlen
&& (name_buf[np + 1] == b':'
|| name_buf[np + 1] == b'['
|| name_buf[np + 1] == b'-'))
{
if name_buf[np] == b'\\' && np + 1 < nlen {
np += 1;
}
np += 1;
}
let mut c_byte = if np < nlen { name_buf[np] } else { 0 };
let opt_name_slice = &name_buf[..np];
let opt_name = String::from_utf8_lossy(opt_name_slice).into_owned();
let mut otype = CAO_NEXT; if c_byte == b'-' { otype = CAO_DIRECT;
np += 1;
c_byte = if np < nlen { name_buf[np] } else { 0 };
} else if c_byte == b'+' { otype = CAO_ODIRECT;
np += 1;
c_byte = if np < nlen { name_buf[np] } else { 0 };
} else if c_byte == b'=' { otype = CAO_OEQUAL;
np += 1;
c_byte = if np < nlen { name_buf[np] } else { 0 };
if c_byte == b'-' {
otype = CAO_EQUAL; np += 1;
c_byte = if np < nlen { name_buf[np] } else { 0 };
}
}
let mut descr_str: Option<String> = None;
if c_byte == b'[' { np += 1;
let d_start = np;
while np < nlen && name_buf[np] != b']' {
if name_buf[np] == b'\\' && np + 1 < nlen { np += 1; }
np += 1;
}
if np >= nlen { zwarnnam(nam, &format!("invalid option definition: {}", arg));
return None;
}
let d_slice = &name_buf[d_start..np];
descr_str = Some(String::from_utf8_lossy(d_slice).into_owned());
np += 1;
c_byte = if np < nlen { name_buf[np] } else { 0 };
}
if c_byte != 0 && c_byte != b':' { zwarnnam(nam, &format!("invalid option definition: {}", arg));
return None;
}
let clean_name = rembslashcolon(&opt_name);
if !multi {
let xv = xor_state.get_or_insert_with(Vec::new);
if xv.len() <= xnum_state as usize {
xv.resize(xnum_state as usize + 1, String::new());
}
xv[xnum_state as usize] = clean_name.clone();
}
let mut oargs: Vec<Box<caarg>> = Vec::new();
if c_byte == b':' {
let mut oanum: i32 = 1; let mut onum: i32 = 0;
while c_byte == b':' { let mut rest = 0;
let mut end_str: Option<String> = None;
np += 1; let atype: i32;
c_byte = if np < nlen { name_buf[np] } else { 0 };
if c_byte == b':' { atype = CAA_OPT;
np += 1;
} else if c_byte == b'*' { np += 1;
if np < nlen && name_buf[np] != b':' { let end_start = np;
while np < nlen && name_buf[np] != b':' {
if name_buf[np] == b'\\' && np + 1 < nlen {
np += 1;
}
np += 1;
}
let e_slice = &name_buf[end_start..np];
end_str = Some(String::from_utf8_lossy(e_slice).into_owned());
}
if np >= nlen || name_buf[np] != b':' { zwarnnam(nam, &format!("invalid option definition: {}", arg));
return None;
}
np += 1; if np < nlen && name_buf[np] == b':' { np += 1;
if np < nlen && name_buf[np] == b':' { atype = CAA_RREST;
np += 1;
} else {
atype = CAA_RARGS;
}
} else {
atype = CAA_REST;
}
rest = 1;
} else {
atype = CAA_NORMAL;
}
let mut oarg = parse_caarg(
if rest != 0 { 0 } else { 1 },
atype, oanum, onum,
Some(&clean_name),
&name_buf, &mut np,
doset.as_deref(),
);
oanum += 1;
if atype == CAA_OPT { onum += 1; } if let Some(end) = end_str {
oarg.end = Some(end); }
oargs.push(oarg);
if rest != 0 { break; } c_byte = if np < nlen { name_buf[np] } else { 0 }; }
}
let mut opt_box = Box::new(caopt::default());
opt_box.gsname = doset.clone(); opt_box.name = Some(clean_name.clone()); opt_box.descr = if let Some(d) = descr_str.clone() { Some(d)
} else if adpre.is_some() && oargs.len() == 1 { let first_arg = &oargs[0];
let d_field = first_arg.descr.as_deref().unwrap_or("");
let has_visible = d_field.bytes().any(|b| !iblank(b));
if has_visible { Some(crate::ported::string::tricat(
adpre.as_deref().unwrap_or(""),
d_field,
adsuf.as_deref().unwrap_or(""),
))
} else {
None }
} else {
None
};
let xor_clone = if again_iter == 1 { xor_state.clone()
} else {
xor_state.take()
};
opt_box.xor = xor_clone;
opt_box.r#type = otype; opt_box.not = if not_flag { 1 } else { 0 };
let mut head: Option<Box<caarg>> = None;
for a in oargs.into_iter().rev() {
let mut a = a;
a.next = head;
head = Some(a);
}
opt_box.args = head;
{
let cur = sets.last_mut().unwrap();
opt_box.num = cur.nopts;
cur.nopts += 1; if otype == CAO_DIRECT || otype == CAO_EQUAL { cur.ndopts += 1;
} else if otype == CAO_ODIRECT || otype == CAO_OEQUAL { cur.nodopts += 1;
}
if single != 0 {
let nb = clean_name.as_bytes();
if nb.len() == 2 && nb[1] != b'-' {
let sidx = single_index(nb[0], nb[1]);
if sidx >= 0 {
if let Some(ref mut s) = cur.single {
if (sidx as usize) < s.len() {
s[sidx as usize] = Some(Box::new(
caopt {
next: None,
name: opt_box.name.clone(),
descr: opt_box.descr.clone(),
xor: opt_box.xor.clone(),
r#type: opt_box.r#type,
args: None,
active: 0,
num: opt_box.num,
gsname: opt_box.gsname.clone(),
not: opt_box.not,
}
));
}
}
}
}
}
}
opts_per_set.last_mut().unwrap().push(opt_box);
if again_iter == 1 { if let Some(start) = againp_start {
p_state = start;
xnum_state = xnum; xor_state = xor_state.clone();
continue 'rec;
}
}
break 'rec;
}
} else if p < bytes.len() && bytes[p] == b'*' {
if not_flag { idx += 1;
continue;
}
p += 1; if p >= bytes.len() || bytes[p] != b':' {
zwarnnam(nam, &format!("invalid rest argument definition: {}", arg));
return None;
}
if rest_per_set.last().unwrap().is_some() { zwarnnam(nam, &format!("doubled rest argument definition: {}", arg));
return None;
}
let mut atype = CAA_REST; p += 1; if p < bytes.len() && bytes[p] == b':' { p += 1;
if p < bytes.len() && bytes[p] == b':' { atype = CAA_RREST;
p += 1;
} else {
atype = CAA_RARGS;
}
}
let mut rarg = parse_caarg(0, atype, -1, 0, None, bytes, &mut p,
doset.as_deref()); rarg.xor = xor; *rest_per_set.last_mut().unwrap() = Some(rarg);
} else {
if not_flag { idx += 1;
continue;
}
let mut direct = 0; if p < bytes.len() && idigit(bytes[p]) { direct = 1;
let mut num: i32 = 0;
while p < bytes.len() && idigit(bytes[p]) {
num = num * 10 + (bytes[p] - b'0') as i32;
p += 1;
}
anum = num + 1; } else {
anum += 1; }
if p >= bytes.len() || bytes[p] != b':' { zwarnnam(nam, &format!("invalid argument: {}", arg));
return None;
}
let mut atype = CAA_NORMAL;
p += 1; if p < bytes.len() && bytes[p] == b':' { atype = CAA_OPT;
p += 1;
}
let mut narg = parse_caarg(0, atype, anum - 1, 0, None,
bytes, &mut p, doset.as_deref()); narg.xor = xor; narg.direct = direct;
let target = anum - 1;
let cur_args = args_per_set.last_mut().unwrap();
let mut insert_at = cur_args.len();
for (i, existing) in cur_args.iter().enumerate() {
if existing.num >= target {
insert_at = i;
break;
}
}
if insert_at < cur_args.len() && cur_args[insert_at].num == target {
zwarnnam(nam, &format!("doubled argument definition: {}", arg));
return None;
}
cur_args.insert(insert_at, narg);
}
idx += 1;
}
{
let last_idx = sets.len() - 1;
let cur = &mut sets[last_idx];
let cur_args = &mut args_per_set[last_idx];
let mut head: Option<Box<caarg>> = None;
for a in cur_args.drain(..).rev() {
let mut a = a;
a.next = head;
head = Some(a);
}
cur.args = head;
set_cadef_opts(cur);
}
let n_sets = sets.len();
for i in 0..n_sets {
let mut head: Option<Box<caopt>> = None;
for o in opts_per_set[i].drain(..).rev() {
let mut o = o;
o.next = head;
head = Some(o);
}
sets[i].opts = head;
if !args_per_set[i].is_empty() {
let mut head: Option<Box<caarg>> = None;
for a in args_per_set[i].drain(..).rev() {
let mut a = a;
a.next = head;
head = Some(a);
}
sets[i].args = head;
}
sets[i].rest = rest_per_set[i].take();
}
while sets.len() > 1 {
let tail = sets.pop().unwrap();
let prev = sets.last_mut().unwrap();
let mut cursor: &mut Option<Box<cadef>> = &mut prev.snext;
while cursor.is_some() {
cursor = &mut cursor.as_mut().unwrap().snext;
}
*cursor = Some(tail);
}
Some(sets.pop().unwrap())
}
pub fn get_cvdef(nam: &str, args: &[String]) -> i32 { let na = args.len() as i32;
let now = { use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now().duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64).unwrap_or(0)
};
if let Ok(mut cache) = cvdef_cache.lock() {
let mut min_idx: Option<usize> = None;
let mut min_lastt: i64 = i64::MAX;
let mut hit_idx: Option<usize> = None;
for (i, slot) in cache.iter().enumerate() { match slot {
Some(entry) => {
if entry.ndefs == na && entry.defs.as_deref()
.map_or(false, |d| d.len() == args.len()
&& d.iter().zip(args.iter()).all(|(a, b)| a == b))
{
hit_idx = Some(i);
break;
}
if entry.lastt < min_lastt { min_lastt = entry.lastt;
min_idx = Some(i);
}
}
None => { min_idx = Some(i);
break;
}
}
}
if let Some(i) = hit_idx { if let Some(entry) = cache[i].as_mut() {
entry.lastt = now; }
return 1; }
if let Some(new) = parse_cvdef(nam, args) {
let idx = min_idx.unwrap_or(0);
cache[idx] = Some(new); }
}
0 }
pub fn parse_cvdef(nam: &str, args: &[String]) -> Option<Box<cvdef>> { use crate::ported::ztype_h::inblank;
let orig_args = args;
let mut idx = 0usize;
let mut sep: i32 = 0; let mut asep: i32 = b'=' as i32; let mut hassep: i32 = 0; let mut words: i32 = 0;
while idx + 1 < args.len()
&& args[idx].len() == 2
&& args[idx].starts_with('-')
&& (args[idx].as_bytes()[1] == b's'
|| args[idx].as_bytes()[1] == b'S'
|| args[idx].as_bytes()[1] == b'w')
{
let flag = args[idx].as_bytes()[1];
if flag == b's' { hassep = 1;
sep = args[idx + 1].as_bytes().first().copied().unwrap_or(0) as i32;
idx += 2;
} else if flag == b'S' { asep = args[idx + 1].as_bytes().first().copied().unwrap_or(0) as i32;
idx += 2;
} else { words = 1;
idx += 1;
}
}
if idx + 1 >= args.len() { zwarnnam(nam, "not enough arguments");
return None;
}
let descr = args[idx].clone(); idx += 1;
let mut ret = Box::new(cvdef {
descr: Some(descr), hassep, sep, argsep: asep, next: None, vals: None, defs: Some(orig_args.to_vec()), ndefs: orig_args.len() as i32, lastt: { use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now().duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64).unwrap_or(0)
},
words, });
let mut vals_collected: Vec<Box<cvval>> = Vec::new();
while idx < args.len() {
let spec = &args[idx];
let bytes = spec.as_bytes();
let mut p: usize = 0;
let mut xnum: i32 = 0; let mut bs = 0; let mut xor: Option<Vec<String>> = None;
if p < bytes.len() && bytes[p] == b'(' { let mut list: Vec<String> = Vec::new();
let mut bad = false;
'paren: loop {
if p >= bytes.len() || bytes[p] == b')' { break; }
p += 1; while p < bytes.len() && inblank(bytes[p]) { p += 1; }
if p >= bytes.len() { bad = true; break 'paren; }
if bytes[p] == b')' { break 'paren; }
let q = p;
p += 1;
while p < bytes.len() && bytes[p] != b')' && !inblank(bytes[p]) {
p += 1;
}
if p >= bytes.len() { bad = true; break 'paren; }
let word = String::from_utf8_lossy(&bytes[q..p]).into_owned();
list.push(word);
xnum += 1;
}
if bad || p >= bytes.len() || bytes[p] != b')' { zwarnnam(nam, &format!("invalid argument: {}", spec));
return None;
}
xor = Some(list);
p += 1; }
let multi = p < bytes.len() && bytes[p] == b'*';
if multi { p += 1; }
let name_start = p;
while p < bytes.len() && bytes[p] != b':' && bytes[p] != b'[' { if bytes[p] == b'\\' && p + 1 < bytes.len() {
p += 1;
bs = 1; }
p += 1;
}
if hassep != 0 && sep == 0 && name_start + (bs as usize) + 1 < p { zwarnnam(nam,
"no multi-letter values with empty separator allowed");
return None;
}
let name_bytes = &bytes[name_start..p];
let name = String::from_utf8_lossy(name_bytes).into_owned();
let mut value_descr: Option<String> = None;
let mut c_byte = if p < bytes.len() { bytes[p] } else { 0 };
if c_byte == b'[' { p += 1;
let d_start = p;
while p < bytes.len() && bytes[p] != b']' { if bytes[p] == b'\\' && p + 1 < bytes.len() { p += 1; }
p += 1;
}
if p >= bytes.len() { zwarnnam(nam, &format!("invalid value definition: {}", spec));
return None;
}
value_descr = Some(String::from_utf8_lossy(&bytes[d_start..p]).into_owned());
p += 1; c_byte = if p < bytes.len() { bytes[p] } else { 0 };
}
if c_byte != 0 && c_byte != b':' { zwarnnam(nam, &format!("invalid value definition: {}", spec));
return None;
}
let mut vtype = CVV_NOARG;
let mut arg: Option<Box<caarg>> = None;
if c_byte == b':' { if hassep != 0 && sep == 0 { zwarnnam(nam,
"no value with argument with empty separator allowed");
return None;
}
p += 1; if p < bytes.len() && bytes[p] == b':' { p += 1;
vtype = CVV_OPT; } else {
vtype = CVV_ARG; }
arg = Some(parse_caarg(0, 0, 0, 0, Some(&name), bytes, &mut p, None)); }
if !multi { let xv = xor.get_or_insert_with(Vec::new);
if xv.len() <= xnum as usize {
xv.resize(xnum as usize + 1, String::new());
}
xv[xnum as usize] = name.clone(); }
let v = Box::new(cvval { next: None,
name: Some(name), descr: value_descr, xor, r#type: vtype, arg, active: 0,
});
vals_collected.push(v);
idx += 1;
}
let mut head: Option<Box<cvval>> = None;
for v in vals_collected.into_iter().rev() {
let mut v = v;
v.next = head;
head = Some(v);
}
ret.vals = head;
Some(ret)
}
pub fn set_cadef_opts(def: &mut cadef) { let mut xnum: i32 = 0;
let mut argp = def.args.as_deref_mut(); while let Some(node) = argp { if node.direct == 0 { node.min = node.num - xnum; }
if node.r#type == CAA_OPT { xnum += 1; }
argp = node.next.as_deref_mut(); }
}
pub fn settags(level: i32, tags: &[String]) { let idx = level as usize;
if idx >= MAX_TAGS { return; }
if let Ok(mut tab) = comptags.lock() {
if tab[idx].is_some() { freectags(tab[idx].take()); }
let context = tags.first().cloned(); let all: Vec<String> = tags.iter().skip(1).cloned().collect(); tab[idx] = Some(Box::new(ctags { all: Some(all), context, init: 1, sets: None, }));
}
}
pub fn bin_compquote(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::params::{getvalue, getstrvalue, getvaluearr,
setstrvalue, setarrvalue};
use crate::ported::zsh_h::{value, PM_ARRAY, PM_TYPE};
use std::sync::atomic::Ordering;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function");
return 1;
}
let qstack_empty = COMPQSTACK.get()
.map(|m| m.lock().map(|s| s.is_empty()).unwrap_or(true))
.unwrap_or(true);
if qstack_empty { return 0; }
let p_flag = OPT_ISSET(ops, b'p');
for name in args { let mut vbuf = value {
pm: None, arr: Vec::new(), scanflags: 0,
valflags: 0, start: 0, end: 0,
};
let mut nameref: &str = name.as_str();
let v = getvalue(Some(&mut vbuf), &mut nameref, 0); if v.is_none() { zwarnnam(nam, &format!("unknown parameter: {}", name));
continue;
}
let v = v.unwrap();
let flags = v.pm.as_ref().map(|pm| pm.node.flags).unwrap_or(0);
let pm_type = PM_TYPE(flags as u32);
if pm_type == 0 || (flags as u32 & crate::ported::zsh_h::PM_NAMEREF) != 0 {
let s = getstrvalue(Some(v));
let q = comp_quote(&s, p_flag as i32);
let mut nameref_re: &str = name.as_str();
setstrvalue(getvalue(Some(&mut vbuf), &mut nameref_re, 0), &q);
} else if pm_type == PM_ARRAY { let arr = getvaluearr(Some(v));
let new_arr: Vec<String> = arr.into_iter()
.map(|elem| comp_quote(&elem, p_flag as i32))
.collect();
let mut vbuf2 = value {
pm: None, arr: Vec::new(), scanflags: 0,
valflags: 0, start: 0, end: 0,
};
let mut nameref2: &str = name.as_str();
if let Some(v2) = getvalue(Some(&mut vbuf2), &mut nameref2, 0) {
setarrvalue(v2, new_arr);
}
} else { zwarnnam(nam, &format!("invalid parameter type: {}", name));
}
}
0 }
pub fn bin_comptags(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::builtin::LOCALLEVEL;
use crate::ported::params::{setsparam, setaparam};
use crate::ported::utils::strpfx;
use std::sync::atomic::Ordering;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function");
return 1;
}
if args.is_empty() { return 1; }
let a0 = args[0].as_bytes();
if a0.len() < 2 || a0[0] != b'-'
|| (a0.len() > 2 && (a0[2] != b'-' || a0.len() > 3))
{
zwarnnam(nam, &format!("invalid argument: {}", args[0]));
return 1;
}
let level: i32 = LOCALLEVEL.load(Ordering::Relaxed) - if a0.len() > 2 { 1 } else { 0 };
if level < 0 || (level as usize) >= MAX_TAGS { zwarnnam(nam, "nesting level too deep");
return 1;
}
let lvl_idx = level as usize;
let sub = a0[1];
if sub != b'i' && sub != b'I' {
let registered = {
let tab = comptags.lock().unwrap();
tab[lvl_idx].is_some()
};
if !registered {
zwarnnam(nam, "no tags registered");
return 1;
}
}
let (min, max): (i32, i32) = match sub {
b'i' => (2, -1),
b'C' => (1, 1),
b'T' => (0, 0),
b'N' => (0, 0),
b'R' => (1, 1),
b'S' => (1, 1),
b'A' => (2, 3),
_ => {
zwarnnam(nam, &format!("invalid option: {}", args[0]));
return 1;
}
};
let n = (args.len() as i32) - 1;
if n < min { zwarnnam(nam, "not enough arguments"); return 1; }
if max >= 0 && n > max { zwarnnam(nam, "too many arguments"); return 1; }
match sub {
b'i' => { settags(level, &args[1..]);
lasttaglevel.store(level, Ordering::Relaxed); 0
}
b'C' => { let ctx = {
let tab = comptags.lock().unwrap();
tab[lvl_idx].as_ref()
.and_then(|t| t.context.clone())
.unwrap_or_default()
};
setsparam(&args[1], &ctx); 0
}
b'T' => { let empty = {
let tab = comptags.lock().unwrap();
tab[lvl_idx].as_ref().map_or(true, |t| t.sets.is_none())
};
if empty { 1 } else { 0 } }
b'N' => { let mut tab = comptags.lock().unwrap();
if let Some(t) = tab[lvl_idx].as_mut() {
if t.init != 0 { t.init = 0;
} else if let Some(mut s) = t.sets.take() { t.sets = s.next.take(); freectset(Some(s)); }
if t.sets.is_some() { 0 } else { 1 } } else {
1
}
}
b'R' => { let tab = comptags.lock().unwrap();
let hit = tab[lvl_idx].as_ref()
.and_then(|t| t.sets.as_ref())
.map(|s| {
s.tags.as_deref()
.map_or(false, |tgs| arrcontains(tgs, &args[1], true) != 0)
})
.unwrap_or(false);
if hit { 0 } else { 1 } }
b'A' => { let mut tab = comptags.lock().unwrap();
let Some(t) = tab[lvl_idx].as_mut() else { return 1; };
let Some(s) = t.sets.as_mut() else { return 1; };
if s.tag.as_deref() != Some(args[1].as_str()) {
s.tag = Some(args[1].clone()); s.ptr = 0; }
let tags_vec = s.tags.clone().unwrap_or_default();
let mut found: Option<(usize, String, String)> = None;
for (i, q) in tags_vec.iter().enumerate().skip(s.ptr as usize) {
if strpfx(&args[1], q) { let l = args[1].len();
let qb = q.as_bytes();
if qb.len() == l { found = Some((i, q.clone(), q.clone()));
break;
} else if qb.len() > l && qb[l] == b':' { let v = String::from_utf8_lossy(&qb[l + 1..]).into_owned();
found = Some((i, q.clone(), v));
break;
}
}
}
let (q_idx, q_full, v) = match found {
None => { s.tag = None;
return 1;
}
Some(t) => t,
};
s.ptr = (q_idx + 1) as i32; let value = if v.starts_with('-') {
crate::ported::string::dyncat(&args[1], &v)
} else {
v.clone()
};
setsparam(&args[2], &value);
if args.len() > 3 {
let pre_colon: String = q_full
.splitn(2, ':').next().unwrap_or("").to_string();
setsparam(&args[3], &pre_colon);
}
0 }
b'S' => { let tab = comptags.lock().unwrap();
if let Some(tags) = tab[lvl_idx].as_ref()
.and_then(|t| t.sets.as_ref())
.and_then(|s| s.tags.clone())
{
setaparam(&args[1], tags); 0
} else {
1 }
}
_ => 0,
}
}
pub fn bin_comptry(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function"); return 1; }
if args.is_empty() { return 0; } 0 }
pub fn bin_compvalues(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::params::{setsparam, setaparam, sethparam};
use std::sync::atomic::Ordering;
if INCOMPFUNC.load(Ordering::Relaxed) != 1 { zwarnnam(nam, "can only be called from completion function");
return 1;
}
if args.is_empty() { return 1; }
let a0 = args[0].as_bytes();
if a0.len() != 2 || a0[0] != b'-' { zwarnnam(nam, &format!("invalid argument: {}", args[0]));
return 1;
}
let sub = a0[1];
if sub != b'i' && cv_parsed.load(Ordering::Relaxed) == 0 { zwarnnam(nam, "no parsed state");
return 1;
}
let (min, max): (i32, i32) = match sub { b'i' => (2, -1),
b'D' => (2, 2),
b'C' => (1, 1),
b'V' => (3, 3),
b's' => (1, 1),
b'S' => (1, 1),
b'd' => (1, 1),
b'L' => (3, 4),
b'v' => (1, 1),
_ => {
zwarnnam(nam, &format!("invalid option: {}", args[0]));
return 1;
}
};
let n = (args.len() as i32) - 1;
if n < min { zwarnnam(nam, "not enough arguments"); return 1; }
if max >= 0 && n > max { zwarnnam(nam, "too many arguments"); return 1; }
match sub {
b'i' => { let spec = &args[1..];
let _ = get_cvdef(nam, spec);
let mut cached: Option<Box<cvdef>> = {
let cache = cvdef_cache.lock().ok();
cache.and_then(|c| {
c.iter().find_map(|slot| {
slot.as_ref().filter(|e| {
e.ndefs == spec.len() as i32
&& e.defs.as_deref().map_or(false, |d| {
d.len() == spec.len()
&& d.iter().zip(spec.iter()).all(|(a, b)| a == b)
})
}).cloned()
})
})
};
let Some(ref mut def) = cached else { return 1; };
cv_parsed.store(0, Ordering::Relaxed); cv_parse_word(def); cv_parsed.store(1, Ordering::Relaxed); 0
}
b'D' => { let arg = cv_laststate.lock().ok().and_then(|s| s.def.clone());
if let Some(a) = arg {
setsparam(&args[1], a.descr.as_deref().unwrap_or("")); setsparam(&args[2], a.action.as_deref().unwrap_or("")); 0
} else {
1 }
}
b'C' => { let arg = cv_laststate.lock().ok().and_then(|s| s.def.clone());
if let Some(a) = arg {
setsparam(&args[1], a.opt.as_deref().unwrap_or("")); 0
} else {
1 }
}
b'V' => { let mut noarg: Vec<String> = Vec::new();
let mut arg_l: Vec<String> = Vec::new();
let mut opt_l: Vec<String> = Vec::new();
if let Ok(ls) = cv_laststate.lock() {
if let Some(d) = ls.d.as_ref() {
let mut p = d.vals.as_deref();
while let Some(v) = p {
if v.active != 0 { let bucket: &mut Vec<String> = match v.r#type {
t if t == CVV_NOARG => &mut noarg,
t if t == CVV_ARG => &mut arg_l,
_ => &mut opt_l,
};
let name = v.name.as_deref().unwrap_or("");
let str_val = if let Some(d) = v.descr.as_deref() {
format!("{}:{}", name, d)
} else {
name.to_string()
};
bucket.push(str_val); }
p = v.next.as_deref();
}
}
}
setaparam(&args[1], noarg);
setaparam(&args[2], arg_l);
setaparam(&args[3], opt_l);
0 }
b's' => { let (hassep, sep) = cv_laststate.lock().ok()
.and_then(|ls| ls.d.as_ref().map(|d| (d.hassep, d.sep)))
.unwrap_or((0, 0));
if hassep != 0 {
let tmp = (sep as u8 as char).to_string();
setsparam(&args[1], &tmp);
0 } else {
1 }
}
b'S' => { let argsep = cv_laststate.lock().ok()
.and_then(|ls| ls.d.as_ref().map(|d| d.argsep))
.unwrap_or(0);
let tmp = (argsep as u8 as char).to_string();
setsparam(&args[1], &tmp);
0 }
b'd' => { let descr = cv_laststate.lock().ok()
.and_then(|ls| ls.d.as_ref().and_then(|d| d.descr.clone()))
.unwrap_or_default();
setsparam(&args[1], &descr);
0
}
b'L' => { let val = cv_laststate.lock().ok().and_then(|ls| {
ls.d.as_ref().and_then(|d| cv_get_val(d, &args[1]))
});
if let Some(v) = val {
if let Some(a) = v.arg.as_deref() { setsparam(&args[2], a.descr.as_deref().unwrap_or(""));
setsparam(&args[3], a.action.as_deref().unwrap_or(""));
if args.len() > 4 { setsparam(&args[4], v.name.as_deref().unwrap_or(""));
}
return 0;
}
}
1 }
b'v' => { let vals = cv_laststate.lock().ok()
.and_then(|ls| ls.vals.clone());
if let Some(v) = vals {
sethparam(&args[1], v);
0
} else {
1 }
}
_ => 1, }
}