#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
use crate::ported::builtin::findcmd;
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::utils::errflag;
use crate::ported::zle::comp_h::{Aminfo, Ccmakedat, Cmlist};
use crate::ported::zle::compctl_h::{
Compcond, CompcondData, Compctl, CCT_CURPAT, CCT_CURPRE, CCT_CURSTR, CCT_CURSUB, CCT_CURSUBC,
CCT_CURSUF, CCT_NUMWORDS, CCT_POS, CCT_QUOTE, CCT_RANGEPAT, CCT_RANGESTR, CCT_WORDPAT,
CCT_WORDSTR, CC_ALGLOB, CC_ALREG, CC_ARRAYS, CC_BINDINGS, CC_BUILTINS, CC_CCCONT, CC_COMMPATH,
CC_DEFCONT, CC_DELETE, CC_DIRS, CC_DISCMDS, CC_ENVVARS, CC_EXCMDS, CC_EXPANDEXPL, CC_EXTCMDS,
CC_FILES, CC_INTVARS, CC_JOBS, CC_NAMED, CC_NOSORT, CC_OPTIONS, CC_PARAMS, CC_PATCONT,
CC_QUOTEFLAG, CC_READONLYS, CC_REMOVE, CC_RESWDS, CC_RUNNING, CC_SCALARS, CC_SHFUNCS,
CC_SPECIALS, CC_STOPPED, CC_UNIQALL, CC_UNIQCON, CC_USERS, CC_VARS, CC_XORCONT,
};
use crate::ported::zle::complete::parse_cmatcher;
use crate::ported::zle::complete::{COMPCURRENT, COMPQSTACK, COMPWORDS};
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
zle_params::*, zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
};
use crate::ported::zsh_h::PAT_HEAPDUP;
use crate::ported::zsh_h::{
IN_NOTHING, QT_BACKSLASH, QT_BACKTICK, QT_DOLLARS, QT_DOUBLE, QT_NONE, QT_SINGLE,
};
use std::collections::HashMap;
use std::os::unix::fs::PermissionsExt;
use std::sync::{Arc, Mutex};
#[allow(unused_imports)]
#[allow(unused_imports)]
pub const COMP_LIST: i32 = 1 << 0; pub const COMP_COMMAND: i32 = 1 << 1; pub const COMP_DEFAULT: i32 = 1 << 2; pub const COMP_FIRST: i32 = 1 << 3; pub const COMP_REMOVE: i32 = 1 << 4; pub const COMP_LISTMATCH: i32 = 1 << 5;
pub const COMP_SPECIAL: i32 = COMP_COMMAND | COMP_DEFAULT | COMP_FIRST;
pub(crate) fn createcompctltable() {
let mut g = COMPCTL_TAB.write().unwrap();
*g = Some(HashMap::new());
let mut p = PATCOMPS.write().unwrap();
p.clear();
}
pub(crate) fn freecompctlp(name: &str) {
let mut g = COMPCTL_TAB.write().unwrap();
if let Some(map) = g.as_mut() {
map.remove(name);
}
}
pub(crate) fn freecompctl(cc: Arc<Compctl>) {
let (cc_default_ref, cc_first_ref, cc_compos_ref) = {
match COMPCTL_TAB.read().ok().and_then(|g| g.clone()) {
Some(map) => (
map.get("__cc_default").cloned(),
map.get("__cc_first").cloned(),
map.get("__cc_compos").cloned(),
),
None => (None, None, None),
}
};
let is_sentinel = cc_default_ref.as_ref().is_some_and(|s| Arc::ptr_eq(s, &cc))
|| cc_first_ref.as_ref().is_some_and(|s| Arc::ptr_eq(s, &cc))
|| cc_compos_ref.as_ref().is_some_and(|s| Arc::ptr_eq(s, &cc));
if is_sentinel || Arc::strong_count(&cc) > 1 {
return; }
let _ = &cc.keyvar; let _ = &cc.glob; let _ = &cc.str; let _ = &cc.func; let _ = &cc.explain; let _ = &cc.ylist; let _ = &cc.prefix; let _ = &cc.suffix; let _ = &cc.hpat; let _ = &cc.gname; let _ = &cc.subcmd; let _ = &cc.substr;
if let Some(cond) = cc.cond.as_deref() {
freecompcond((*cond).clone()); }
if cc.ext.is_some() {
let mut n: Option<Arc<Compctl>> = cc.ext.clone(); while let Some(node) = n.take() {
let m = node.next.clone(); freecompctl(node); n = m; }
}
if let Some(xor) = cc.xor.clone() {
let xor_is_default = cc_default_ref
.as_ref()
.is_some_and(|s| Arc::ptr_eq(s, &xor));
if !xor_is_default {
freecompctl(xor); }
}
let _ = &cc.matcher;
let _ = &cc.mstr;
drop(cc); }
pub(crate) fn freecompcond(cc: Compcond) {
let mut or_cur: Option<Box<Compcond>> = Some(Box::new(cc)); while let Some(mut or_node) = or_cur {
let next_or = or_node.or.take(); let mut and_cur: Option<Box<Compcond>> = Some(or_node); while let Some(mut and_node) = and_cur {
let next_and = and_node.and.take(); let _ = and_node.u; and_cur = next_and; }
or_cur = next_or; }
}
pub(crate) fn cpcmlist(
mut l: Option<&Cmlist>,
) -> Option<std::sync::Arc<Cmlist>> {
let mut nodes: Vec<(Box<crate::ported::zle::comp_h::Cmatcher>, String)> = Vec::new();
while let Some(src) = l {
let matcher_chain = crate::ported::zle::complete::cpcmatcher(
Some(&*src.matcher),
)
.expect("cpcmatcher returned None for non-null source");
nodes.push((matcher_chain, src.str.clone())); l = src.next.as_deref(); }
let mut head: Option<std::sync::Arc<Cmlist>> = None; for (matcher, str) in nodes.into_iter().rev() {
head = Some(std::sync::Arc::new(Cmlist {
next: head, matcher, str, }));
}
head }
thread_local! {
static CCLIST: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
thread_local! {
static SHOWMASK: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
pub(crate) fn set_gmatcher(name: &str, argv: &[String]) -> i32 {
let mut parsed: Vec<(Box<crate::ported::zle::comp_h::Cmatcher>, String)> = Vec::new();
for word in argv {
let m = match parse_cmatcher(name, word) {
Some(m) => m, None => return 1, };
parsed.push((m, word.clone())); }
let mut head: Option<std::sync::Arc<Cmlist>> = None; for (matcher, str) in parsed.into_iter().rev() {
head = Some(std::sync::Arc::new(Cmlist {
next: head, matcher, str, }));
}
let new_list = cpcmlist(head.as_deref()); if let Ok(mut guard) = CMATCHER.write() {
*guard = new_list;
}
1 }
pub(crate) fn get_gmatcher(name: &str, argv: &[String]) -> i32 {
if argv.first().map(|s| s.as_str()) != Some("-M") {
return 0; }
let rest = &argv[1..]; for w in rest {
if w.starts_with('-') {
return 0; }
}
if set_gmatcher(name, rest) != 0 {
return 2; }
1 }
pub(crate) fn print_gmatcher(ac: i32) {
let guard = CMATCHER.read().ok();
let head = match guard.as_ref().and_then(|g| g.as_deref()) {
Some(h) => h,
None => return, };
let prefix = if ac != 0 { "compctl -M" } else { "MATCH" }; print!("{}", prefix);
let mut cur: Option<&Cmlist> = Some(head);
while let Some(p) = cur {
print!(" '{}'", p.str); cur = p.next.as_deref();
}
println!(); }
pub(crate) fn get_compctl(
name: &str,
av: &mut Vec<String>,
cc: &mut Compctl,
first: bool,
mut isdef: bool,
cl: i32,
) -> i32 {
let mut i: usize = 0;
let hx = false;
let mut cclist_local = CCLIST.with(|c| c.get());
cc.mask2 = CC_CCCONT;
if first
&& i < av.len()
&& av[i] == "+"
&& !(i + 1 < av.len() && av[i + 1].starts_with('-') && av[i + 1].len() > 1)
{
i += 1;
if i < av.len() && av[i].starts_with('-') {
i += 1;
}
av.drain(0..i);
if cl != 0 {
return 1;
} else {
CCLIST.with(|c| c.set(COMP_REMOVE));
return 0;
}
}
let mut ready = false;
while !ready && i < av.len() && av[i].starts_with('-') && (av[i].len() > 1 || !first) {
if av[i].len() == 1 {
av[i] = "-+".to_string();
}
let arg = av[i].clone();
let chars: Vec<char> = arg.chars().skip(1).collect();
let mut consumed = false;
for c in chars {
if ready {
break;
}
match c {
'f' => cc.mask |= CC_FILES, 'c' => cc.mask |= CC_COMMPATH, 'm' => cc.mask |= CC_EXTCMDS, 'w' => cc.mask |= CC_RESWDS, 'o' => cc.mask |= CC_OPTIONS, 'v' => cc.mask |= CC_VARS, 'b' => cc.mask |= CC_BINDINGS, 'A' => cc.mask |= CC_ARRAYS, 'I' => cc.mask |= CC_INTVARS, 'F' => cc.mask |= CC_SHFUNCS, 'p' => cc.mask |= CC_PARAMS, 'E' => cc.mask |= CC_ENVVARS, 'j' => cc.mask |= CC_JOBS, 'r' => cc.mask |= CC_RUNNING, 'z' => cc.mask |= CC_STOPPED, 'B' => cc.mask |= CC_BUILTINS, 'a' => cc.mask |= CC_ALREG | CC_ALGLOB, 'R' => cc.mask |= CC_ALREG, 'G' => cc.mask |= CC_ALGLOB, 'u' => cc.mask |= CC_USERS, 'd' => cc.mask |= CC_DISCMDS, 'e' => cc.mask |= CC_EXCMDS, 'N' => cc.mask |= CC_SCALARS, 'O' => cc.mask |= CC_READONLYS, 'Z' => cc.mask |= CC_SPECIALS, 'q' => cc.mask |= CC_REMOVE, 'U' => cc.mask |= CC_DELETE, 'n' => cc.mask |= CC_NAMED, 'Q' => cc.mask |= CC_QUOTEFLAG, '/' => cc.mask |= CC_DIRS, '1' => {
cc.mask2 |= CC_UNIQALL;
cc.mask2 &= !CC_UNIQCON;
}
'2' => {
cc.mask2 |= CC_UNIQCON;
cc.mask2 &= !CC_UNIQALL;
}
'C' => {
if cl != 0 {
eprintln!("{}: bad option: -{}", name, c);
return 1;
}
if first && !hx {
cclist_local |= COMP_COMMAND;
} else {
eprintln!("{}: misplaced command completion (-C) flag", name);
return 1;
}
}
'D' => {
if cl != 0 {
eprintln!("{}: bad option: -{}", name, c);
return 1;
}
if first && !hx {
isdef = true;
cclist_local |= COMP_DEFAULT;
} else {
eprintln!("{}: misplaced default completion (-D) flag", name);
return 1;
}
}
'T' => {
if cl != 0 {
eprintln!("{}: bad option: -{}", name, c);
return 1;
}
if first && !hx {
cclist_local |= COMP_FIRST;
} else {
eprintln!("{}: misplaced first completion (-T) flag", name);
return 1;
}
}
'L' => {
if cl != 0 {
eprintln!("{}: bad option: -{}", name, c);
return 1;
}
if !first || hx {
eprintln!("{}: invalid use of -L flag", name);
return 1;
}
cclist_local |= COMP_LIST;
}
'+' => {
ready = true;
consumed = true;
break;
}
_ => {
let (has_inline, inline_val) = (
arg.len() > 2 && arg.chars().nth(1) == Some(c),
if arg.len() > 2 {
arg[2..].to_string()
} else {
String::new()
},
);
let mut val: Option<String> = None;
if has_inline {
val = Some(inline_val);
} else if i + 1 < av.len() {
val = Some(av[i + 1].clone());
i += 1;
}
match c {
'k' => cc.keyvar = val, 'K' => cc.func = val, 'Y' => {
cc.mask |= CC_EXPANDEXPL;
cc.explain = val;
}
'X' => {
cc.mask &= !CC_EXPANDEXPL;
cc.explain = val;
}
'y' => cc.ylist = val, 'P' => cc.prefix = val, 'S' => cc.suffix = val, 'g' => cc.glob = val, 's' => cc.str = val, 'l' => cc.subcmd = val, 'h' => cc.substr = val, 'W' => cc.withd = val, 'J' => cc.gname = val, 'V' => {
cc.gname = val;
cc.mask2 |= CC_NOSORT;
}
'M' => {
if let Some(s) = val {
if parse_cmatcher(name, &s).is_none() {
eprintln!("{}: bad matcher specification `{}'", name, s);
return 1;
}
cc.mstr = Some(s);
}
}
'H' => {
if let Some(s) = val {
cc.hnum = s.parse::<i32>().unwrap_or(0).max(0);
}
if i + 1 < av.len() {
cc.hpat = Some(av[i + 1].clone());
if cc.hpat.as_deref() == Some("*") {
cc.hpat = Some(String::new());
}
i += 1;
}
}
't' => {
if let Some(s) = val {
let bit = match s.as_str() {
"+" => CC_XORCONT,
"n" => 0,
"-" => CC_PATCONT,
"x" => CC_DEFCONT,
_ => {
eprintln!(
"{}: invalid retry specification character `{}`",
name, s
);
return 1;
}
};
cc.mask2 = bit;
}
}
_ => {
eprintln!("{}: unknown compctl flag `-{}`", name, c);
return 1;
}
}
consumed = true;
break;
}
}
}
i += 1;
if !consumed {
}
}
av.drain(0..i);
let _ = isdef;
CCLIST.with(|c| c.set(cclist_local));
0
}
pub(crate) fn get_xcompctl(name: &str, av: &mut Vec<String>, cc: &mut Compctl, isdef: bool) -> i32 {
let mut ready = false;
let mut next_chain: Vec<Arc<Compctl>> = Vec::new();
while !ready {
let mut head: Compcond = Compcond::default();
let mut current_or = &mut head as *mut Compcond;
if av.is_empty() {
eprintln!("{}: missing command names", name);
return 1;
}
let arg = av[0].clone();
let bytes: Vec<char> = arg.chars().collect();
let mut t = 0_usize;
let mut current_and: Option<*mut Compcond> = None;
while t < bytes.len() {
while t < bytes.len() && bytes[t] == ' ' {
t += 1;
}
if t >= bytes.len() {
break;
}
let typ = match bytes[t] {
'q' => CCT_QUOTE, 's' => CCT_CURSUF, 'S' => CCT_CURPRE, 'p' => CCT_POS, 'c' => CCT_CURSTR, 'C' => CCT_CURPAT, 'w' => CCT_WORDSTR, 'W' => CCT_WORDPAT, 'n' => CCT_CURSUB, 'N' => CCT_CURSUBC, 'm' => CCT_NUMWORDS, 'r' => CCT_RANGESTR, 'R' => CCT_RANGEPAT, _ => {
eprintln!("{}: unknown condition code: {}", name, bytes[t]);
return 1;
}
};
if t + 1 >= bytes.len() || bytes[t + 1] != '[' {
eprintln!(
"{}: expected condition after condition code: {}",
name, bytes[t]
);
return 1;
}
t += 1;
let mut bodies: Vec<String> = Vec::new();
while t < bytes.len() && bytes[t] == '[' {
t += 1; while t < bytes.len() && bytes[t] == ' ' {
t += 1;
}
let body_start = t;
let mut depth = 1_i32;
while t < bytes.len() && depth > 0 {
if bytes[t] == '\\' && t + 1 < bytes.len() {
t += 2;
continue;
}
if bytes[t] == '[' {
depth += 1;
} else if bytes[t] == ']' {
depth -= 1;
if depth == 0 {
break;
}
}
t += 1;
}
if t >= bytes.len() {
eprintln!("{}: error after condition code", name);
return 1;
}
let body: String = bytes[body_start..t].iter().collect();
bodies.push(body);
t += 1; }
let n = bodies.len() as i32;
let data = match typ {
t if t == CCT_POS || t == CCT_NUMWORDS => {
let mut a: Vec<i32> = Vec::with_capacity(n as usize);
let mut b: Vec<i32> = Vec::with_capacity(n as usize);
for body in &bodies {
let parts: Vec<&str> = body.splitn(2, ',').collect();
let av_n: i32 = parts[0].trim().parse().unwrap_or(0);
let bv_n: i32 = if parts.len() == 2 {
parts[1].trim().parse().unwrap_or(0)
} else {
av_n };
a.push(av_n);
b.push(bv_n);
}
CompcondData::R { a, b }
}
t if t == CCT_CURSUF || t == CCT_CURPRE || t == CCT_QUOTE => {
let s: Vec<String> = bodies.iter().cloned().collect();
let p: Vec<i32> = vec![0; s.len()];
CompcondData::S { p, s }
}
t if t == CCT_RANGESTR || t == CCT_RANGEPAT => {
let mut a: Vec<String> = Vec::with_capacity(n as usize);
let mut b: Vec<String> = Vec::with_capacity(n as usize);
for body in &bodies {
let parts: Vec<&str> = body.splitn(2, ',').collect();
a.push(parts[0].to_string());
b.push(parts.get(1).map(|s| s.to_string()).unwrap_or_default());
}
CompcondData::L { a, b }
}
_ => {
let mut p: Vec<i32> = Vec::with_capacity(n as usize);
let mut s: Vec<String> = Vec::with_capacity(n as usize);
for body in &bodies {
let parts: Vec<&str> = body.splitn(2, ',').collect();
if parts.len() != 2 {
eprintln!("{}: error in condition", name);
return 1;
}
p.push(parts[0].trim().parse().unwrap_or(0));
s.push(parts[1].to_string());
}
CompcondData::S { p, s }
}
};
unsafe {
let cur = match current_and {
Some(p) => p,
None => current_or,
};
(*cur).typ = typ;
(*cur).n = n;
(*cur).u = data;
}
while t < bytes.len() && bytes[t] == ' ' {
t += 1;
}
if t < bytes.len() && bytes[t] == ',' {
let new_node = Box::new(Compcond::default());
let new_ptr = Box::into_raw(new_node);
unsafe {
let cur = current_and.unwrap_or(current_or);
(*cur).or = Some(Box::from_raw(new_ptr));
current_or = (*cur).or.as_mut().unwrap().as_mut() as *mut Compcond;
}
current_and = None;
t += 1;
} else if t < bytes.len() {
let new_node = Box::new(Compcond::default());
let new_ptr = Box::into_raw(new_node);
unsafe {
let cur = current_and.unwrap_or(current_or);
(*cur).and = Some(Box::from_raw(new_ptr));
current_and = Some((*cur).and.as_mut().unwrap().as_mut() as *mut Compcond);
}
}
}
let mut next_cc = Compctl::default();
next_cc.cond = Some(Box::new(head));
av.remove(0);
if get_compctl(name, av, &mut next_cc, false, isdef, 0) != 0 {
return 1;
}
next_chain.push(Arc::new(next_cc));
let cclist = CCLIST.with(|c| c.get());
if (av.is_empty()) && (cclist & COMP_SPECIAL) != 0 {
ready = true;
continue;
}
if av.is_empty() || !av[0].starts_with('-') || (av[0].len() == 1 && av.len() < 2) {
eprintln!("{}: missing command names", name);
return 1;
}
if av[0] == "--" {
ready = true;
} else if av[0] == "-+" && av.len() >= 2 && av[1] == "--" {
ready = true;
av.remove(0);
}
av.remove(0);
}
if let Some(first) = next_chain.into_iter().next() {
cc.ext = Some(first);
}
0
}
pub(crate) fn cc_assign(name: &str, cct: Arc<Compctl>, reass: bool) {
let cclist = CCLIST.with(|c| c.get());
if reass && (cclist & COMP_LIST) == 0 {
let conflicts = cclist == (COMP_COMMAND | COMP_DEFAULT)
|| cclist == (COMP_COMMAND | COMP_FIRST)
|| cclist == (COMP_DEFAULT | COMP_FIRST)
|| cclist == COMP_SPECIAL;
if conflicts {
eprintln!("{}: can't set -D, -T, and -C simultaneously", name);
return;
}
if (cclist & COMP_COMMAND) != 0 {
let _ = cc_reassign(cct.clone());
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() {
*g = Some(HashMap::new());
}
if let Some(map) = g.as_mut() {
map.insert("__cc_compos".to_string(), cct);
}
return;
}
if (cclist & COMP_DEFAULT) != 0 {
let _ = cc_reassign(cct.clone());
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() {
*g = Some(HashMap::new());
}
if let Some(map) = g.as_mut() {
map.insert("__cc_default".to_string(), cct);
}
return;
}
if (cclist & COMP_FIRST) != 0 {
let _ = cc_reassign(cct.clone());
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() {
*g = Some(HashMap::new());
}
if let Some(map) = g.as_mut() {
map.insert("__cc_first".to_string(), cct);
}
return;
}
}
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() {
*g = Some(HashMap::new());
}
if let Some(map) = g.as_mut() {
map.insert(name.to_string(), cct);
}
}
pub(crate) fn cc_reassign(_cc: Arc<Compctl>) -> Arc<Compctl> {
Arc::new(Compctl::default())
}
pub(crate) fn compctl_name_pat(p: &str) -> (bool, String) {
let has_glob = p.chars().any(|c| matches!(c, '*' | '?' | '['));
if has_glob {
(true, p.to_string())
} else {
let mut out = String::with_capacity(p.len());
let mut chars = p.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&nx) = chars.peek() {
out.push(nx);
chars.next();
continue;
}
}
out.push(c);
}
(false, out)
}
}
pub(crate) fn delpatcomp(n: &str) {
let mut patcomps = PATCOMPS.write().unwrap();
for i in 0..patcomps.len() {
if patcomps[i].0 == n {
patcomps.remove(i);
break;
}
}
}
pub(crate) fn compctl_process_cc(s: &[String], cc: Arc<Compctl>) -> i32 {
let cclist = CCLIST.with(|c| c.get());
if (cclist & COMP_REMOVE) != 0 {
for n in s {
let mut p = PATCOMPS.write().unwrap();
let len_before = p.len();
p.retain(|(pat, _)| pat != n);
let pat_removed = p.len() != len_before;
drop(p);
if !pat_removed {
if let Some(map) = COMPCTL_TAB.write().unwrap().as_mut() {
map.remove(n);
}
}
}
} else {
for n in s {
let mut g = COMPCTL_TAB.write().unwrap();
if g.is_none() {
*g = Some(HashMap::new());
}
if let Some(map) = g.as_mut() {
map.insert(n.clone(), cc.clone());
}
}
}
0
}
pub(crate) fn printcompctl(s: &str, cc: &Compctl, printflags: i32, ispat: bool) {
const CSS: &str = "fcqovbAIFpEjrzBRGudeNOZUnQmw/";
const MSS: &str = " pcCwWsSnNmrRq";
let mut flags = cc.mask;
let flags2 = cc.mask2;
const PRINT_LIST: i32 = 1 << 0;
const PRINT_TYPE: i32 = 1 << 1;
let mut cclist = CCLIST.with(|c| c.get());
if (printflags & PRINT_LIST) != 0 {
cclist |= COMP_LIST;
} else if (printflags & PRINT_TYPE) != 0 {
cclist &= !COMP_LIST;
}
if (flags & CC_EXCMDS) != 0 && (flags & CC_DISCMDS) == 0 {
flags &= !CC_EXCMDS;
}
let showmask = SHOWMASK.with(|c| c.get());
if showmask != 0 && (flags & showmask) == 0 {
return;
}
let oldshowmask = showmask;
SHOWMASK.with(|c| c.set(0));
if (cclist & COMP_LIST) != 0 {
print!("compctl");
} else if !s.is_empty() {
print!("compctl");
}
for (i, ch) in CSS.chars().enumerate() {
if ch == ' ' {
continue;
}
if (flags & (1u64 << i)) != 0 {
print!(" -{}", ch);
}
}
let _ = MSS;
if let Some(s) = &cc.keyvar {
print!(" -k '{}'", s);
}
if let Some(s) = &cc.glob {
print!(" -g '{}'", s);
}
if let Some(s) = &cc.str {
print!(" -s '{}'", s);
}
if let Some(s) = &cc.func {
print!(" -K '{}'", s);
}
if let Some(s) = &cc.explain {
if (cc.mask & CC_EXPANDEXPL) != 0 {
print!(" -Y '{}'", s);
} else {
print!(" -X '{}'", s);
}
}
if let Some(s) = &cc.ylist {
print!(" -y '{}'", s);
}
if let Some(s) = &cc.prefix {
print!(" -P '{}'", s);
}
if let Some(s) = &cc.suffix {
print!(" -S '{}'", s);
}
if let Some(s) = &cc.subcmd {
print!(" -l '{}'", s);
}
if let Some(s) = &cc.substr {
print!(" -h '{}'", s);
}
if let Some(s) = &cc.withd {
print!(" -W '{}'", s);
}
if let Some(s) = &cc.gname {
if (flags2 & CC_NOSORT) != 0 {
print!(" -V '{}'", s);
} else {
print!(" -J '{}'", s);
}
}
if let Some(s) = &cc.mstr {
print!(" -M '{}'", s);
}
if cc.hnum > 0 {
if let Some(p) = &cc.hpat {
print!(" -H {} '{}'", cc.hnum, if p.is_empty() { "*" } else { p });
}
}
if cc.xor.is_some() {
print!(" +");
}
if !s.is_empty() && (cclist & COMP_LIST) != 0 {
if ispat {
print!(" -p '{}'", s);
} else {
print!(" '{}'", s);
}
} else if !s.is_empty() {
print!(" '{}'", s);
}
println!();
SHOWMASK.with(|c| c.set(oldshowmask));
}
pub(crate) fn printcompctlp(name: &str, hn: &Compctl, printflags: i32) {
printcompctl(name, hn, printflags, false);
}
pub fn bin_compctl(
name: &str,
argv: &[String],
_ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
let mut argv: Vec<String> = argv.to_vec();
let mut ret: i32 = 0;
CCLIST.with(|c| c.set(0));
SHOWMASK.with(|c| c.set(0));
if !argv.is_empty() {
let gret = get_gmatcher(name, &argv);
if gret != 0 {
return gret - 1;
}
let mut cc = Compctl::default();
if get_compctl(name, &mut argv, &mut cc, true, false, 0) != 0 {
return 1;
}
let mut showmask = cc.mask;
if (showmask & CC_EXCMDS) != 0 && (showmask & CC_DISCMDS) == 0 {
showmask &= !CC_EXCMDS;
}
SHOWMASK.with(|c| c.set(showmask));
let cclist = CCLIST.with(|c| c.get());
if argv.is_empty() || (cclist & COMP_LIST) != 0 {
} else {
if (cclist & COMP_SPECIAL) != 0 {
eprintln!("{}: extraneous commands ignored", name);
} else {
let cc_arc = Arc::new(cc);
ret = compctl_process_cc(&argv, cc_arc);
}
return ret;
}
}
let cclist = CCLIST.with(|c| c.get());
if argv.is_empty() && (cclist & (COMP_SPECIAL | COMP_LISTMATCH)) == 0 {
let pats = PATCOMPS.read().unwrap().clone();
for (pat, cc) in &pats {
printcompctl(pat, cc, 0, true);
}
if let Some(map) = COMPCTL_TAB.read().unwrap().as_ref() {
let mut names: Vec<&String> = map.keys().collect();
names.sort();
for n in names {
if let Some(cc) = map.get(n) {
printcompctlp(n, cc, 0);
}
}
}
print_gmatcher((cclist & COMP_LIST) as i32);
return ret;
}
if (cclist & COMP_LIST) != 0 {
SHOWMASK.with(|c| c.set(0));
for n in &argv {
let mut found = false;
let pats = PATCOMPS.read().unwrap().clone();
for (pat, cc) in &pats {
if pat == n {
printcompctl(pat, cc, 0, true);
found = true;
break;
}
}
if !found {
if let Some(map) = COMPCTL_TAB.read().unwrap().as_ref() {
if let Some(cc) = map.get(n) {
printcompctlp(n, cc, 0);
found = true;
}
}
}
if !found {
eprintln!("{}: no compctl defined for {}", name, n);
ret = 1;
}
}
if (cclist & COMP_LISTMATCH) != 0 {
print_gmatcher(COMP_LIST as i32);
}
}
ret
}
pub const CFN_FIRST: i32 = 1; pub const CFN_DEFAULT: i32 = 2;
pub fn bin_compcall(
name: &str,
argv: &[String],
ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
let incompfunc = INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed);
if incompfunc != 1 {
crate::ported::utils::zwarnnam(name, "can only be called from completion function");
return 1; }
let t_set = crate::ported::zsh_h::OPT_ISSET(ops, b'T') || argv.iter().any(|a| a == "-T");
let d_set = crate::ported::zsh_h::OPT_ISSET(ops, b'D') || argv.iter().any(|a| a == "-D");
if let Some(w) = crate::ported::params::getaparam("words") {
if let Ok(mut g) = COMPWORDS.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = w;
}
}
COMPCURRENT.store(
crate::ported::params::getiparam("CURRENT") as i32,
std::sync::atomic::Ordering::Relaxed,
);
let flags = (if t_set { 0 } else { CFN_FIRST }) | (if d_set { 0 } else { CFN_DEFAULT }); makecomplistctl(flags) }
pub(crate) fn ccmakehookfn(_dummy: *mut crate::ported::zsh_h::hookdef, dat: *mut std::ffi::c_void) -> i32 {
use crate::ported::zle::compcore as cc;
use std::sync::atomic::Ordering;
let dat: &mut Ccmakedat = unsafe { &mut *(dat as *mut Ccmakedat) };
let os = dat.str.clone().unwrap_or_default(); let incmd = dat.incmd; let lst = dat.lst; let onm = cc::nmatches.load(Ordering::Relaxed); let odm = cc::diffmatches.load(Ordering::Relaxed); let osi = crate::ported::utils::movefd(0);
crate::ported::signals_h::queue_signals();
let mut m: Option<Arc<Cmlist>> = CMATCHER.read().ok().and_then(|g| g.clone()); if let Ok(mut mlist) = cc::matchers
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
{
let mut cur = m.clone();
while let Some(node) = cur {
mlist.push(node.matcher.clone()); cur = node.next.clone();
}
}
loop {
if let Ok(mut g) = cc::bmatchers.get_or_init(|| Mutex::new(None)).lock() {
*g = None; }
if let Some(mm) = m.as_ref() {
let ms = Arc::new(Cmlist {
next: None, matcher: mm.matcher.clone(), str: String::new(),
});
if let Ok(mut g) = cc::mstack.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(ms); }
crate::ported::zle::compmatch::add_bmatchers(Some(&*mm.matcher)); } else if let Ok(mut g) = cc::mstack.get_or_init(|| Mutex::new(None)).lock() {
*g = None; }
if let Ok(mut g) = cc::ainfo.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(Aminfo::default()); }
if let Ok(mut g) = cc::fainfo.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(Aminfo::default()); }
if let Ok(mut g) = cc::freecl.get_or_init(|| Mutex::new(None)).lock() {
*g = None; }
if crate::ported::zle::zle_tricky::VALIDLIST.load(Ordering::Relaxed) == 0 {
crate::ported::zle::zle_tricky::LASTAMBIG.store(0, Ordering::Relaxed); }
if let Ok(mut g) = cc::amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear(); }
cc::mnum.store(0, Ordering::Relaxed); cc::unambig_mnum.store(-1, Ordering::Relaxed); if let Ok(mut g) = cc::isuf.get_or_init(|| Mutex::new(String::new())).lock() {
g.clear(); }
cc::insmnum.store(
crate::ported::zle::zle_main::ZMOD.lock().map(|g| g.mult).unwrap_or(1),
Ordering::Relaxed,
); cc::oldlist.store(0, Ordering::Relaxed); cc::oldins.store(0, Ordering::Relaxed); cc::begcmgroup(Some("default"), 0); crate::ported::zle::zle_tricky::MENUCMP.store(0, Ordering::Relaxed); cc::menuacc.store(0, Ordering::Relaxed); cc::newmatches.store(0, Ordering::Relaxed); cc::onlyexpl.store(0, Ordering::Relaxed);
CCUSED.with(|r| r.borrow_mut().clear());
let s = crate::ported::mem::dupstring(&os); makecomplistglobal(&s, incmd != 0, lst, 0); cc::endcmgroup(None);
let have_amatches = cc::amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|g| !g.is_empty())
.unwrap_or(false);
if have_amatches && cc::oldlist.load(Ordering::Relaxed) == 0 {
*LASTCCUSED.lock().unwrap() = CCUSED.with(|r| r.borrow().clone()); }
if cc::oldlist.load(Ordering::Relaxed) != 0 {
cc::nmatches.store(onm, Ordering::Relaxed); cc::diffmatches.store(odm, Ordering::Relaxed); crate::ported::zle::zle_tricky::VALIDLIST.store(1, Ordering::Relaxed); let last = cc::lastmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = cc::amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = last; }
let last_l = cc::lastlmatches
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone());
if let Ok(mut g) = cc::lmatches.get_or_init(|| Mutex::new(None)).lock() {
*g = last_l; }
let pm = cc::pmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|mut g| std::mem::take(&mut *g))
.unwrap_or_default();
if !pm.is_empty() {
cc::freematches(pm, 1); cc::hasperm.store(0, Ordering::Relaxed); }
crate::ported::utils::redup(osi, 0);
dat.lst = 0; crate::ported::signals_h::unqueue_signals(); return 0; }
let old_last = cc::lastmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|mut g| std::mem::take(&mut *g))
.unwrap_or_default();
if !old_last.is_empty() {
cc::freematches(old_last, 1); }
cc::permmatches(1); let pm = cc::pmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|mut g| std::mem::take(&mut *g))
.unwrap_or_default();
if let Ok(mut g) = cc::amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = pm.clone(); }
cc::lastpermmnum.store(cc::permmnum.load(Ordering::Relaxed), Ordering::Relaxed); cc::lastpermgnum.store(cc::permgnum.load(Ordering::Relaxed), Ordering::Relaxed);
if let Ok(mut g) = cc::lastmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = pm; }
let lm = cc::lmatches
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone());
if let Ok(mut g) = cc::lastlmatches.get_or_init(|| Mutex::new(None)).lock() {
*g = lm; }
cc::hasperm.store(0, Ordering::Relaxed); cc::hasoldlist.store(1, Ordering::Relaxed);
if cc::nmatches.load(Ordering::Relaxed) != 0 && errflag.load(Ordering::Relaxed) == 0 {
crate::ported::zle::zle_tricky::VALIDLIST.store(1, Ordering::Relaxed);
crate::ported::utils::redup(osi, 0);
dat.lst = 0; crate::ported::signals_h::unqueue_signals(); return 0; }
m = match m.and_then(|node| node.next.clone()) {
Some(next) => Some(next),
None => break,
};
errflag.fetch_and(!crate::ported::utils::ERRFLAG_ERROR, Ordering::Relaxed); }
crate::ported::utils::redup(osi, 0); dat.lst = 1;
crate::ported::signals_h::unqueue_signals(); 0 }
pub(crate) fn cccleanuphookfn(
_dummy: *mut crate::ported::zsh_h::hookdef,
_dat: *mut std::ffi::c_void,
) -> i32 {
CCUSED.with(|r| r.borrow_mut().clear()); CCSTACK.with(|r| r.borrow_mut().clear()); 0
}
pub(crate) fn maketildelist() {
crate::ported::hashnameddir::fillnameddirtable();
let only_users = ADDWHAT.with(|c| c.get()) != -1;
let uname_bit = crate::ported::zsh_h::ND_USERNAME as i32;
let entries: Vec<String> = crate::ported::hashnameddir::nameddirtab()
.lock()
.ok()
.map(|t| {
t.iter()
.filter(|(_, nd)| !only_users || (nd.node.flags & uname_bit) != 0)
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
for name in entries {
addmatch(&name, None);
}
}
use crate::ported::zle::complete::INCOMPFUNC;
pub(crate) fn compctlread(
name: &str,
args: &[String],
ops: &crate::ported::zsh_h::options,
reply: &str,
) -> i32 {
use crate::ported::zsh_h::OPT_ISSET;
use std::io::Write;
if !INCOMPCTLFUNC.with(|c| c.get()) {
crate::ported::utils::zwarnnam(name, "option valid only in functions called via compctl");
return 1; }
crate::ported::zle::zle_h::METACHECK();
let zlemetacs = crate::ported::zle::compcore::ZLEMETACS.load(std::sync::atomic::Ordering::Relaxed);
let clwpos = crate::ported::zle::zle_tricky::CLWPOS.load(std::sync::atomic::Ordering::Relaxed);
let print_it = OPT_ISSET(ops, b'e') || OPT_ISSET(ops, b'E');
let mut stdout = std::io::stdout();
if OPT_ISSET(ops, b'l') {
if OPT_ISSET(ops, b'n') {
if print_it {
let _ = writeln!(stdout, "{}", zlemetacs + 1); }
if !OPT_ISSET(ops, b'e') {
crate::ported::params::setsparam(reply, &(zlemetacs + 1).to_string()); }
return 0; }
let zlemetaline = crate::ported::zle::compcore::ZLEMETALINE
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
if print_it {
crate::ported::utils::zputs(&zlemetaline, &mut stdout); let _ = writeln!(stdout); }
if !OPT_ISSET(ops, b'e') {
crate::ported::params::setsparam(reply, &zlemetaline); }
} else {
let clwords: Vec<String> = crate::ported::zle::zle_tricky::CLWORDS
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let clwnum = clwords.len();
if OPT_ISSET(ops, b'n') {
if print_it {
let _ = writeln!(stdout, "{}", clwpos + 1); }
if !OPT_ISSET(ops, b'e') {
crate::ported::params::setsparam(reply, &(clwpos + 1).to_string()); }
return 0; }
if OPT_ISSET(ops, b'A') && !OPT_ISSET(ops, b'e') {
crate::ported::params::setaparam(reply, clwords.clone()); return 0; }
if print_it {
for w in &clwords {
crate::ported::utils::zputs(w, &mut stdout); let _ = writeln!(stdout); }
if OPT_ISSET(ops, b'e') {
return 0; }
}
let mut reply = reply.to_string();
let mut rest = args;
let mut i = 0usize;
while i < clwnum && !rest.is_empty() {
crate::ported::params::setsparam(&reply, &clwords[i]);
reply = rest[0].clone();
rest = &rest[1..];
i += 1;
}
let buf = if i < clwnum {
clwords[i..].join(" ")
} else {
String::new() };
crate::ported::params::setsparam(&reply, &buf); }
0 }
thread_local! {
pub(crate) static INCOMPCTLFUNC: std::cell::Cell<bool> =
const { std::cell::Cell::new(false) };
}
pub(crate) fn getcpat(str: &str, cpatindex: i32, cpat: &str, class: i32) -> i32 {
if str.is_empty() {
return -1;
}
let cpat_clean: String = {
let mut out = String::with_capacity(cpat.len());
let mut chars = cpat.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&nx) = chars.peek() {
out.push(nx);
chars.next();
continue;
}
}
out.push(c);
}
out
};
let (mut idx, backward) = if cpatindex == 0 {
(1_i32, false)
} else if cpatindex < 0 {
(-cpatindex, true)
} else {
(cpatindex, false)
};
let str_chars: Vec<char> = str.chars().collect();
let cpat_chars: Vec<char> = cpat_clean.chars().collect();
let n = str_chars.len();
let positions: Vec<usize> = if backward {
(0..n).rev().collect()
} else {
(0..n).collect()
};
for s_start in positions {
if class != 0 {
let sc = str_chars[s_start];
if cpat_chars.iter().any(|&p| p == sc) {
idx -= 1;
if idx == 0 {
return (s_start + 1) as i32;
}
}
} else {
let mut t = s_start;
let mut p = 0;
while t < n && p < cpat_chars.len() && str_chars[t] == cpat_chars[p] {
t += 1;
p += 1;
}
if p == cpat_chars.len() {
idx -= 1;
if idx == 0 {
return t as i32;
}
}
}
}
-1
}
pub(crate) fn dumphashtable<I: IntoIterator<Item = String>>(names: I, what: i32) {
ADDWHAT.with(|c| c.set(what));
for nam in names {
addmatch(&nam, None);
}
}
thread_local! { static ADDWHAT: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
thread_local! { static MATCH_LIST: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) }; }
thread_local! {
static LPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static RPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static RSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static PPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static PSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LPPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LPSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static FPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static FSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QFPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QFSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QRPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QRSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QLPRE: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static QLSUF: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
static LPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static LSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static RPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static RSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static FPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static FSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static LPPL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static LPSL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static NOREAL: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static IC: std::cell::Cell<char> = const { std::cell::Cell::new('\0') };
static PATCOMP: std::cell::RefCell<Option<crate::ported::pattern::Patprog>> =
const { std::cell::RefCell::new(None) };
static FILECOMP: std::cell::RefCell<Option<crate::ported::pattern::Patprog>> =
const { std::cell::RefCell::new(None) };
static CURCC: std::cell::RefCell<Option<Arc<Compctl>>> =
const { std::cell::RefCell::new(None) };
}
pub(crate) fn addmatch(s: &str, t: Option<&str>) {
let _ = t;
use crate::ported::zle::comp_h::{Cline, CMF_FILE};
use crate::ported::zle::compcore::{add_match_data, multiquote, tildequote};
use crate::ported::zle::compmatch::comp_match;
use std::sync::atomic::Ordering;
let aw = ADDWHAT.with(|c| c.get());
let mut isfile: i32 = 0; let mut isalt: i32 = 0; let mut isexact: i32 = 0; let mut lc: Option<Box<Cline>> = None; let ms: Option<String>;
let ppre = PPRE.with(|r| r.borrow().clone());
let psuf = PSUF.with(|r| r.borrow().clone());
if aw == -1 || aw == -5 || aw == -6 || aw == CC_FILES as i32 || aw == -7 || aw == -8 {
let ppl = ppre.len() as i32; let psl = psuf.len() as i32;
if (aw == CC_FILES as i32 || aw == -5) && psuf.is_empty() {
let sl = s.len();
if let Some(fign) = crate::ported::params::getaparam("fignore") {
for pt in &fign {
let filell = pt.len();
if filell < sl && s.ends_with(pt.as_str()) {
isalt = 1; break;
}
}
}
}
if aw == CC_FILES as i32 || aw == -6 || aw == -5 || aw == -8 {
let pfx = tildequote(&QFPRE.with(|r| r.borrow().clone()), 1);
let sfx = multiquote(&QFSUF.with(|r| r.borrow().clone()), 1);
let qu = if !ppre.is_empty() { 1 } else { 2 }; ms = FILECOMP.with(|fc| {
let fb = fc.borrow();
comp_match(
&pfx,
&sfx,
s,
fb.as_ref(),
Some(&mut lc),
qu,
None,
ppl,
None,
psl,
&mut isexact,
)
});
} else {
let pfx = multiquote(&FPRE.with(|r| r.borrow().clone()), 1);
let sfx = multiquote(&FSUF.with(|r| r.borrow().clone()), 1);
ms = FILECOMP.with(|fc| {
let fb = fc.borrow();
comp_match(
&pfx,
&sfx,
s,
fb.as_ref(),
Some(&mut lc),
0,
None,
ppl,
None,
psl,
&mut isexact,
)
});
}
if ms.is_none() {
return; }
if aw == -7 && findcmd(s, 0, 0).is_none() {
return;
}
isfile = CMF_FILE; } else if aw == CC_QUOTEFLAG as i32 || aw == -2 || aw == -3 || aw == -4 || aw == -9 || aw > 0 {
let (p1s, s1s, p2s, s2s) = if aw == CC_QUOTEFLAG as i32 {
(
QRPRE.with(|r| r.borrow().clone()),
QRSUF.with(|r| r.borrow().clone()),
RPRE.with(|r| r.borrow().clone()),
RSUF.with(|r| r.borrow().clone()),
)
} else {
(
QLPRE.with(|r| r.borrow().clone()),
QLSUF.with(|r| r.borrow().clone()),
LPRE.with(|r| r.borrow().clone()),
LSUF.with(|r| r.borrow().clone()),
)
};
let p1 = multiquote(&p1s, 1); let s1 = multiquote(&s1s, 1); let p2 = multiquote(&p2s, 1); let s2 = multiquote(&s2s, 1); let qflag = (aw == CC_QUOTEFLAG as i32) as i32; let p1l = p1.len() as i32;
let s1l = s1.len() as i32;
let first = PATCOMP.with(|pc| {
let pb = pc.borrow();
comp_match(
&p1,
&s1,
s,
pb.as_ref(),
Some(&mut lc),
qflag,
None,
p1l,
None,
s1l,
&mut isexact,
)
});
if first.is_some() {
ms = first;
} else {
let p2l = p2.len() as i32;
let s2l = s2.len() as i32;
ms = comp_match(
&p2,
&s2,
s,
None,
Some(&mut lc),
qflag,
None,
p2l,
None,
s2l,
&mut isexact,
);
if ms.is_none() {
return; }
}
} else {
return; }
let ms = match ms {
Some(m) => m,
None => return, };
let ipre_v = crate::ported::zle::compcore::ipre
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let ripre_v = crate::ported::zle::compcore::ripre
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let isuf_v = ISUF.lock().map(|g| g.clone()).unwrap_or_default();
let prpre_v = PRPRE.with(|r| r.borrow().clone()).unwrap_or_default();
let (pre_v, suf_v): (Option<String>, Option<String>) = CURCC.with(|r| {
r.borrow()
.as_ref()
.map(|cc| (cc.prefix.clone(), cc.suffix.clone()))
.unwrap_or((None, None))
});
let lppre_v = if isfile != 0 {
LPPRE.with(|r| r.borrow().clone())
} else {
String::new()
};
let lpsuf_v = if isfile != 0 {
LPSUF.with(|r| r.borrow().clone())
} else {
String::new()
};
let mflags_v = crate::ported::zle::compcore::mflags.load(Ordering::Relaxed);
let _ = add_match_data(
isalt, &ms, s, lc, &ipre_v, &ripre_v, &isuf_v, pre_v.as_deref(), &prpre_v, &lppre_v, None, &lpsuf_v, None, suf_v.as_deref(), mflags_v | isfile, isexact,
);
MATCH_LIST.with(|r| r.borrow_mut().push(s.to_string()));
}
pub(crate) fn addhnmatch(name: &str, _flags: i32) {
addmatch(name, None);
}
pub(crate) fn getreal(str_in: &str) -> String {
let mut ne_guard = crate::ported::utils::noerrs_lock()
.lock()
.expect("NOERRS poisoned");
let ne = *ne_guard;
*ne_guard = 1;
drop(ne_guard);
let s = crate::ported::subst::singsub(str_in);
*crate::ported::utils::noerrs_lock()
.lock()
.expect("NOERRS poisoned") = ne;
if errflag.load(std::sync::atomic::Ordering::Relaxed) == 0 && !s.is_empty() {
return s;
}
errflag.fetch_and(
!crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
str_in.to_string()
}
pub(crate) fn gen_matches_files(dirs: bool, execs: bool, all: bool) {
let prpre = PRPRE
.with(|r| r.borrow().clone())
.unwrap_or_else(|| ".".to_string());
let entries = match std::fs::read_dir(&prpre) {
Ok(e) => e,
Err(_) => return,
};
let dot_prefix = FPRE.with(|r| r.borrow().starts_with('.'));
for entry in entries.flatten() {
let name = match entry.file_name().into_string() {
Ok(n) => n,
Err(_) => continue,
};
if !all && (name == "." || name == "..") {
continue;
}
if !all && !dot_prefix && name.starts_with('.') {
continue;
}
let meta = match entry.metadata() {
Ok(m) => m,
Err(_) => continue,
};
let is_dir = meta.is_dir();
#[cfg(unix)]
let is_exec = !is_dir && (meta.permissions().mode() & 0o111 != 0);
#[cfg(not(unix))]
let is_exec = false;
let keep = match (dirs, execs) {
(false, false) => true,
(true, false) => is_dir,
(false, true) => is_exec,
(true, true) => is_dir || is_exec,
};
if !keep {
continue;
}
addmatch(&name, None);
}
}
pub(crate) fn makecomplistglobal(os: &str, incmd: bool, _lst: i32, flags: i32) -> i32 {
use std::sync::atomic::Ordering;
CCONT.with(|c| c.set(CC_CCCONT));
if let Some(d) = CC_DUMMY.lock().unwrap().as_mut() {
if let Some(inner) = std::sync::Arc::get_mut(d) {
inner.suffix = None;
}
}
const CFN_FIRST: i32 = 1;
const CFN_DEFAULT: i32 = 2;
if !incmd {
let cmd_word = crate::ported::zle::complete::COMPWORDS
.get()
.and_then(|m| m.lock().ok())
.and_then(|g| g.first().cloned());
CMDSTR.with(|r| *r.borrow_mut() = cmd_word);
}
let lw = crate::ported::zle::compcore::linwhat.load(Ordering::Relaxed);
let in_env = lw == crate::ported::zle::compcore::IN_ENV_LW; let in_math = lw == crate::ported::zle::compcore::IN_MATH_LW; let in_cond = lw == crate::ported::zle::compcore::IN_COND_LW;
let mut cc: Option<Arc<Compctl>> = None;
if in_env {
if (flags & CFN_DEFAULT) == 0 {
cc = CC_DEFAULT.lock().unwrap().clone(); }
} else if in_math {
if (flags & CFN_DEFAULT) == 0 {
let mut dummy_inner = Compctl::default();
dummy_inner.mask = CC_PARAMS; dummy_inner.refc = 10000; cc = Some(Arc::new(dummy_inner));
}
} else if in_cond {
if (flags & CFN_DEFAULT) == 0 {
let lwpos = *CLWPOS.lock().unwrap() as usize;
let words = CLWORDS.lock().unwrap().clone();
let prev = if lwpos > 0 {
words.get(lwpos - 1).cloned()
} else {
None
};
let prev_s = prev.as_deref().unwrap_or("");
let mask = if prev_s == "-o" {
CC_OPTIONS
} else if (prev_s.starts_with('-') && prev_s.len() == 2)
|| prev_s == "-nt"
|| prev_s == "-ot"
|| prev_s == "-ef"
{
CC_FILES
} else {
CC_FILES | CC_PARAMS };
let mut dummy_inner = Compctl::default();
dummy_inner.mask = mask;
dummy_inner.refc = 10000;
cc = Some(Arc::new(dummy_inner));
}
} else {
return makecomplistcmd(os, incmd, flags);
}
if let Some(cc) = cc {
if (flags & CFN_FIRST) == 0 {
if let Some(cc_first) = CC_FIRST.lock().unwrap().clone() {
makecomplistcc(&cc_first, os, incmd); if (CCONT.with(|c| c.get()) & CC_CCCONT) == 0 {
return 0;
}
}
}
makecomplistcc(&cc, os, incmd); return 1; }
0 }
pub(crate) fn makecomplistcmd(os: &str, incmd: bool, flags: i32) -> i32 {
const CFN_FIRST: i32 = 1;
const CFN_DEFAULT: i32 = 2;
let mut ret: i32 = 0;
if (flags & CFN_FIRST) == 0 {
if let Some(cc_first) = CC_FIRST.lock().unwrap().clone() {
makecomplistcc(&cc_first, os, incmd);
if (CCONT.with(|c| c.get()) & CC_CCCONT) == 0 {
return 0;
}
}
}
let cmdstr = CMDSTR.with(|r| r.borrow().clone());
if cmdstr.is_some() {
ret |= makecomplistpc(os, incmd);
if (CCONT.with(|c| c.get()) & CC_CCCONT) == 0 {
return ret;
}
}
let cc = if incmd {
CC_COMPOS.lock().unwrap().clone()
} else {
let name = match &cmdstr {
Some(s) => s.clone(),
None => return ret,
};
let table = COMPCTL_TAB.read().unwrap();
let from_table = table.as_ref().and_then(|m| m.get(&name).cloned());
drop(table);
match from_table {
Some(c) => Some(c),
None => {
if (flags & CFN_DEFAULT) != 0 {
return ret;
}
ret |= 1;
CC_DEFAULT.lock().unwrap().clone()
}
}
};
if let Some(c) = cc {
makecomplistcc(&c, os, incmd);
}
ret
}
pub(crate) fn makecomplistcc(cc: &Arc<Compctl>, s: &str, incmd: bool) {
let _ = cc.clone();
CCUSED.with(|r| r.borrow_mut().push(cc.clone()));
CCONT.with(|c| c.set(0));
makecomplistor(cc, s, incmd, 0, 0);
}
thread_local! { static PRPRE: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) }; }
pub(crate) fn findnode<T: PartialEq>(list: &[T], dat: &T) -> Option<usize> {
list.iter().position(|x| x == dat)
}
thread_local! { static CDEPTH: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; }
pub const MAX_CDEPTH: i32 = 16;
thread_local! { static CCONT: std::cell::Cell<u64> = const { std::cell::Cell::new(0) }; }
pub(crate) fn makecomplistctl(flags: i32) -> i32 {
let cdepth = CDEPTH.with(|c| c.get());
if cdepth == MAX_CDEPTH {
return 0;
}
CDEPTH.with(|c| c.set(cdepth + 1));
let (str_in, lip, lp) = crate::ported::zle::compcore::comp_str(false);
let os = CMDSTR.with(|r| r.borrow().clone());
let ow = CLWORDS.lock().unwrap().clone();
let on = *CLWNUM.lock().unwrap();
let op = *CLWPOS.lock().unwrap();
let ois = *INSTRING.lock().unwrap();
let oib = *INBACKT.lock().unwrap();
let oisuf = ISUF.lock().unwrap().clone();
let oqp = QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let oqs = QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let oaq = AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap()
.clone();
let ooffs = crate::ported::zle::compcore::OFFS.load(std::sync::atomic::Ordering::Relaxed);
let compquote = crate::ported::zle::zle_tricky::COMPQUOTE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
match compquote.chars().next() {
Some('`') => {
*INSTRING.lock().unwrap() = QT_NONE;
*INBACKT.lock().unwrap() = 0;
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = String::new();
}
Some(c) if c == '\'' || c == '"' || c == '$' => {
*INSTRING.lock().unwrap() = match c {
'\'' => QT_SINGLE,
'"' => QT_DOUBLE,
_ => QT_DOLLARS,
};
*INBACKT.lock().unwrap() = 0;
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = if c == '$' {
compquote[1..].to_string()
} else {
compquote.clone()
};
}
_ => {
*INSTRING.lock().unwrap() = QT_NONE;
*INBACKT.lock().unwrap() = 0;
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = String::new();
}
}
*QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = COMPQIPREFIX.lock().unwrap().clone();
*QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = COMPQISUFFIX.lock().unwrap().clone();
let mut isuf_v = crate::ported::zle::compcore::ctokenize(&COMPISUFFIX.lock().unwrap().clone());
crate::ported::glob::remnulargs(&mut isuf_v);
*ISUF.lock().unwrap() = isuf_v;
let compwords = COMPWORDS
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clone();
*CLWNUM.lock().unwrap() = compwords.len() as i32; *CLWPOS.lock().unwrap() = COMPCURRENT.load(std::sync::atomic::Ordering::Relaxed) - 1; CMDSTR.with(|r| *r.borrow_mut() = compwords.first().cloned()); let clw: Vec<String> = compwords
.iter()
.map(|w| {
let mut t = w.clone(); crate::ported::glob::tokenize(&mut t); crate::ported::glob::remnulargs(&mut t); t
})
.collect();
*CLWORDS.lock().unwrap() = clw;
crate::ported::zle::compcore::OFFS.store(lip + lp, std::sync::atomic::Ordering::Relaxed);
INCOMPFUNC.store(2, std::sync::atomic::Ordering::Relaxed);
let incmd = *CLWPOS.lock().unwrap() == 0; let ret = makecomplistglobal(
&str_in,
incmd,
crate::ported::zle::zle_h::COMP_COMPLETE,
flags,
); INCOMPFUNC.store(1, std::sync::atomic::Ordering::Relaxed);
*ISUF.lock().unwrap() = oisuf;
*QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = oqp;
*QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = oqs;
*INSTRING.lock().unwrap() = ois;
*INBACKT.lock().unwrap() = oib;
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = oaq;
crate::ported::zle::compcore::OFFS.store(ooffs, std::sync::atomic::Ordering::Relaxed);
CMDSTR.with(|r| *r.borrow_mut() = os);
*CLWORDS.lock().unwrap() = ow;
*CLWNUM.lock().unwrap() = on;
*CLWPOS.lock().unwrap() = op;
CDEPTH.with(|c| c.set(c.get() - 1)); ret }
pub(crate) fn makecomplistlist(cc: &Arc<Compctl>, s: &str, incmd: bool, compadd: i32) {
if cc.ext.is_some() {
makecomplistext(cc, s, incmd);
} else {
makecomplistflags(cc, s.to_string(), incmd, compadd);
}
}
pub(crate) fn makecomplistext(occ: &Arc<Compctl>, os: &str, incmd: bool) {
use crate::ported::glob::tokenize;
use crate::ported::lex::untokenize;
use crate::ported::zle::compcore::rembslash;
let ins = {
let is = *INSTRING.lock().unwrap();
if is != QT_NONE {
is
} else if *INBACKT.lock().unwrap() != 0 {
QT_BACKTICK
} else {
0
}
};
let complete_in_word = crate::ported::options::opt_state_get("COMPLETEINWORD").unwrap_or(false);
let mut m = 0; let mut d = 0;
let mut compc_opt = occ.ext.clone();
while let Some(compc) = compc_opt {
let mut compadd = 0i32; let mut t;
t = 0;
let mut cc_opt: Option<&Compcond> = compc.cond.as_deref();
while let Some(cc0) = cc_opt {
if t != 0 {
break; }
let or = cc0.or.as_deref();
t = 1;
let mut and_opt: Option<&Compcond> = Some(cc0);
while let Some(cc) = and_opt {
if t == 0 {
break;
}
let clwnum = *CLWNUM.lock().unwrap();
let clwpos = *CLWPOS.lock().unwrap();
t = 0;
let mut i = 0i32;
while i < cc.n && t == 0 {
*BRANGE.lock().unwrap() = 0;
*ERANGE.lock().unwrap() = clwnum - 1;
let iu = i as usize;
match cc.typ {
x if x == CCT_QUOTE => {
if let CompcondData::S { s, .. } = &cc.u {
let c0 = s.get(iu).and_then(|x| x.chars().next());
t = match c0 {
Some('s') if ins == QT_SINGLE => 1,
Some('d') if ins == QT_DOUBLE => 1,
Some('b') if ins == QT_BACKTICK => 1,
_ => 0,
};
}
}
x if x == CCT_POS || x == CCT_NUMWORDS => {
let tt = if x == CCT_POS { clwpos } else { clwnum }; if let CompcondData::R { a, b } = &cc.u {
let mut av = *a.get(iu).unwrap_or(&0); if av < 0 {
av += clwnum;
}
let mut bv = *b.get(iu).unwrap_or(&0); if bv < 0 {
bv += clwnum;
}
if x == CCT_POS {
*BRANGE.lock().unwrap() = av;
*ERANGE.lock().unwrap() = bv;
}
t = if tt >= av && tt <= bv { 1 } else { 0 }; }
}
x if x == CCT_CURSUF || x == CCT_CURPRE => {
if let CompcondData::S { s, .. } = &cc.u {
let mut sv = if clwpos < clwnum {
os.to_string()
} else {
String::new()
};
sv = untokenize(&sv); if complete_in_word {
let off = crate::ported::zle::compcore::OFFS
.load(std::sync::atomic::Ordering::Relaxed)
.max(0)
as usize;
if off <= sv.len() && sv.is_char_boundary(off) {
sv.truncate(off);
}
}
let sc = rembslash(s.get(iu).map(|x| x.as_str()).unwrap_or("")); let a = sc.len(); if sv.len() >= a && sv.starts_with(&sc) {
compadd = if x == CCT_CURSUF { a as i32 } else { 0 }; t = 1;
}
}
}
x if x == CCT_CURSUB || x == CCT_CURSUBC => {
if clwpos < 0 || clwpos >= clwnum {
t = 0; } else if let CompcondData::S { p, s } = &cc.u {
let mut sv = untokenize(os); if complete_in_word {
let off = crate::ported::zle::compcore::OFFS
.load(std::sync::atomic::Ordering::Relaxed)
.max(0)
as usize; if off <= sv.len() && sv.is_char_boundary(off) {
sv.truncate(off);
}
}
let a = getcpat(
&sv,
*p.get(iu).unwrap_or(&0),
s.get(iu).map(|x| x.as_str()).unwrap_or(""),
if x == CCT_CURSUBC { 1 } else { 0 },
); if a != -1 {
compadd = a; t = 1;
}
}
}
x if x == CCT_CURPAT
|| x == CCT_CURSTR
|| x == CCT_WORDPAT
|| x == CCT_WORDSTR =>
{
let tt = if x == CCT_CURPAT || x == CCT_CURSTR {
clwpos
} else {
0
};
if let CompcondData::S { p, s } = &cc.u {
let mut a = tt + *p.get(iu).unwrap_or(&0); if a < 0 {
a += clwnum;
}
let sv0 = if a < 0 || a >= clwnum {
String::new()
} else {
CLWORDS
.lock()
.unwrap()
.get(a as usize)
.cloned()
.unwrap_or_default()
}; let sv = untokenize(&sv0); let patstr = s.get(iu).map(|x| x.as_str()).unwrap_or("");
if x == CCT_CURPAT || x == CCT_WORDPAT {
let mut ss = patstr.to_string();
tokenize(&mut ss);
t = patcompile(&ss, PAT_HEAPDUP as i32, None)
.map_or(false, |pp| pattry(&pp, &sv))
as i32;
} else {
t = (sv == rembslash(patstr)) as i32;
}
}
}
x if x == CCT_RANGESTR || x == CCT_RANGEPAT => {
let is_pat = x == CCT_RANGEPAT;
if let CompcondData::L { a, b } = &cc.u {
let astr = a.get(iu).cloned().unwrap_or_default();
let bstr = b.get(iu).cloned().unwrap_or_default();
let words = CLWORDS.lock().unwrap().clone();
let sc_a = if is_pat {
let mut z = astr.clone();
tokenize(&mut z);
z
} else {
rembslash(&astr) };
let mut j = clwpos - 1;
while j > 0 {
let sv = untokenize(
words.get(j as usize).map(|x| x.as_str()).unwrap_or(""),
); let matched = if is_pat {
patcompile(&sc_a, PAT_HEAPDUP as i32, None)
.map_or(false, |pp| pattry(&pp, &sv))
} else {
sv.len() >= sc_a.len() && sv.starts_with(&sc_a)
}; if matched {
*BRANGE.lock().unwrap() = j + 1; t = 1;
break;
}
j -= 1;
}
if t != 0 && !bstr.is_empty() {
let sc_b = if is_pat {
let mut z = bstr.clone();
tokenize(&mut z);
z
} else {
rembslash(&bstr)
};
let mut k = j + 1; while k < clwnum {
let sv = untokenize(
words.get(k as usize).map(|x| x.as_str()).unwrap_or(""),
);
let matched = if is_pat {
patcompile(&sc_b, PAT_HEAPDUP as i32, None)
.map_or(false, |pp| pattry(&pp, &sv))
} else {
sv.len() >= sc_b.len() && sv.starts_with(&sc_b)
};
if matched {
*ERANGE.lock().unwrap() = k - 1; t = if clwpos <= k - 1 { 1 } else { 0 }; break;
}
k += 1;
}
}
}
}
_ => {}
}
i += 1;
}
and_opt = cc.and.as_deref();
}
cc_opt = or;
}
if t != 0 {
m = 1;
CCONT.with(|c| c.set(c.get() & !(CC_PATCONT | CC_DEFCONT))); makecomplistor(&compc, os, incmd, compadd, 1); if d == 0 && (CCONT.with(|c| c.get()) & CC_DEFCONT) != 0 {
d = 1;
*BRANGE.lock().unwrap() = 0; *ERANGE.lock().unwrap() = *CLWNUM.lock().unwrap() - 1; makecomplistflags(occ, os.to_string(), incmd, 0); }
if (CCONT.with(|c| c.get()) & CC_PATCONT) == 0 {
break; }
}
compc_opt = compc.next.clone();
}
if m == 0 {
*BRANGE.lock().unwrap() = 0; *ERANGE.lock().unwrap() = *CLWNUM.lock().unwrap() - 1; makecomplistflags(occ, os.to_string(), incmd, 0); }
}
thread_local! { static CMDSTR: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) }; }
pub(crate) fn makecomplistpc(os: &str, incmd: bool) -> i32 {
let mut ret: i32 = 0; let cmdstr = match CMDSTR.with(|r| r.borrow().clone()) {
Some(s) => s,
None => return 0,
};
let is_function = crate::ported::hashtable::shfunctab_lock()
.read()
.map(|t| t.contains_key(&cmdstr))
.unwrap_or(false);
let is_builtin = crate::ported::builtin::BUILTINS
.iter()
.any(|b| b.node.nam == cmdstr);
let s_resolved: Option<String> = if is_function || is_builtin {
None } else {
findcmd(&cmdstr, 1, 0) };
let pats = PATCOMPS.read().unwrap().clone();
for (pat, cc) in &pats {
let matches = patcompile(
&{
let mut __pat_tok = (pat).to_string();
crate::ported::glob::tokenize(&mut __pat_tok);
__pat_tok
},
PAT_HEAPDUP as i32,
None,
)
.map_or(false, |prog| {
pattry(&prog, &cmdstr) || s_resolved.as_deref()
.map(|sr| pattry(&prog, sr)) .unwrap_or(false)
});
if matches {
makecomplistcc(cc, os, incmd); ret |= 2; if (CCONT.with(|c| c.get()) & CC_CCCONT) == 0 {
return ret; }
}
}
ret }
pub(crate) fn sep_comp_string(ss: &str, s: &str, noffs: i32) -> i32 {
use crate::ported::zle::compcore::{ZLEMETACS as CS_G, ZLEMETALINE as LINE_G};
use std::sync::atomic::Ordering;
let owe = crate::ported::zle::compcore::WE.load(std::sync::atomic::Ordering::Relaxed);
let owb = crate::ported::zle::compcore::WB.load(std::sync::atomic::Ordering::Relaxed);
let ocs = CS_G.load(Ordering::Relaxed);
let oll = *ZLEMETALL.lock().unwrap();
let ois = *INSTRING.lock().unwrap();
let oib = *INBACKT.lock().unwrap();
let ona = *NOALIASES.lock().unwrap();
let ne = *crate::ported::utils::noerrs_lock().lock().unwrap();
let ol = LINE_G
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap()
.clone();
let oaq = AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap()
.clone();
let sl = ss.len() as i32;
let mut got = false;
let mut i = 0_i32;
let mut cur: i32 = -1;
let mut swb = 0_i32;
let mut swe = 0_i32;
let mut soffs = 0_i32;
let mut ns: String = String::new();
let mut foo: Vec<String> = Vec::new();
*ADDEDX.lock().unwrap() = 1;
*crate::ported::utils::noerrs_lock().lock().unwrap() = 1;
*LEXFLAGS.lock().unwrap() = LEXFLAGS_ZLE;
let mut tmp = String::with_capacity(ss.len() + 3 + s.len());
tmp.push_str(ss);
tmp.push(' ');
let s_chars: Vec<char> = s.chars().collect();
let noffs_u = (noffs as usize).min(s_chars.len());
let s_pre: String = s_chars[..noffs_u].iter().collect();
let s_post: String = s_chars[noffs_u..].iter().collect();
tmp.push_str(&s_pre);
let scs_initial = sl + 1 + noffs;
CS_G.store(scs_initial, Ordering::Relaxed);
let mut scs = scs_initial;
tmp.push('x');
tmp.push_str(&s_post);
let tl = tmp.len() as i32;
let qstack_head = COMPQSTACK
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap()
.chars()
.next()
.unwrap_or(QT_NONE as u8 as char);
let remq = qstack_head as i32 == QT_BACKSLASH;
if remq {
let mut stripped = String::with_capacity(tmp.len());
let mut chars = tmp.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&_nx) = chars.peek() {
continue;
}
}
stripped.push(c);
}
tmp = stripped;
}
*LINE_G
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = tmp.clone();
*ZLEMETALL.lock().unwrap() = tl - 1;
*NOALIASES.lock().unwrap() = 1;
{
let chars: Vec<char> = tmp.chars().collect();
let mut t_start = 0_usize;
let mut idx = 0_usize;
let mut word_idx = 0_i32;
while idx <= chars.len() {
let at_end = idx == chars.len();
let is_sep = !at_end && chars[idx] == ' ';
if at_end || is_sep {
if idx > t_start {
let token: String = chars[t_start..idx].iter().collect();
let abs_start = t_start as i32;
let abs_end = idx as i32;
foo.push(token.clone());
if !got && scs >= abs_start && scs <= abs_end {
got = true;
cur = word_idx;
swb = abs_start;
swe = abs_end;
soffs = scs - swb;
let mut t = token.clone();
if (soffs as usize) < t.len() {
t.remove(soffs as usize);
}
ns = t;
}
word_idx += 1;
}
t_start = idx + 1;
}
if at_end {
break;
}
idx += 1;
}
i = word_idx;
}
*NOALIASES.lock().unwrap() = ona;
*crate::ported::utils::noerrs_lock().lock().unwrap() = ne;
crate::ported::zle::compcore::WB.store(owb, std::sync::atomic::Ordering::Relaxed);
crate::ported::zle::compcore::WE.store(owe, std::sync::atomic::Ordering::Relaxed);
CS_G.store(ocs, Ordering::Relaxed);
*LINE_G
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = ol;
*ZLEMETALL.lock().unwrap() = oll;
if cur < 0 || i < 1 {
return 1;
}
let ts = ns.clone();
let _ = ts.clone();
let first_char = ns.chars().next();
let is_quoted_open = matches!(first_char, Some(Snull) | Some(Dnull))
|| (matches!(first_char, Some(Stringg) | Some(QSTRING_TOK))
&& ns.chars().nth(1) == Some(Snull));
if is_quoted_open {
let new_instring = match first_char {
Some(Snull) => QT_SINGLE,
Some(Dnull) => QT_DOUBLE,
_ => QT_DOLLARS,
};
*INSTRING.lock().unwrap() = new_instring;
*INBACKT.lock().unwrap() = 0;
swb += 1;
if let (Some(first), Some(last)) = (ns.chars().next(), ns.chars().last()) {
if first == last && ns.len() >= 2 {
swe -= 1;
}
}
let qstack = COMPQSTACK
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap()
.clone();
if qstack.len() >= 2 {
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = String::new();
} else {
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = ts.clone();
}
} else {
*INSTRING.lock().unwrap() = QT_NONE;
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = String::new();
}
let mut ns_chars: Vec<char> = ns.chars().collect();
let mut p_idx = 0_usize;
let mut walk_i = swb;
while p_idx < ns_chars.len() {
let c = ns_chars[p_idx];
if inull(c) {
if walk_i < scs {
soffs -= 1;
if remq && c == Bnull && p_idx + 1 < ns_chars.len() {
swb -= 2;
}
}
let next = ns_chars.get(p_idx + 1).copied();
if next.is_some() || c != Bnull {
if c == Bnull {
if scs == walk_i + 1 {
scs += 1;
soffs += 1;
}
} else if scs > walk_i {
scs -= 1;
walk_i -= 1; }
} else if scs == swe {
scs -= 1;
}
ns_chars.remove(p_idx);
walk_i -= 1;
} else {
p_idx += 1;
walk_i += 1;
}
}
ns = ns_chars.iter().collect();
let qipre_val = QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let qisuf_val = QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let qp = format!(
"{}{}",
qipre_val,
&s[..((swb - sl - 1).max(0) as usize).min(s.len())]
);
if swe < swb {
swe = swb;
}
swe -= sl + 1;
let s_len = s.len() as i32;
if swe > s_len {
swe = s_len;
if (ns.len() as i32) > swe - swb + 1 {
ns.truncate((swe - swb + 1) as usize);
}
}
let qs_start = (swe.max(0) as usize).min(s.len());
let qs = format!("{}{}", &s[qs_start..], qisuf_val);
let s_chars_len = ns.len() as i32;
if soffs > s_chars_len {
soffs = s_chars_len;
}
let ow = CLWORDS.lock().unwrap().clone();
let os = CMDSTR.with(|r| r.borrow().clone());
let oqp = QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let oqs = QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let oqst = COMPQSTACK
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap()
.clone();
let olws = *CLWSIZE.lock().unwrap();
let olwn = *CLWNUM.lock().unwrap();
let olwp = *CLWPOS.lock().unwrap();
let obr = *BRANGE.lock().unwrap();
let oer = *ERANGE.lock().unwrap();
let oof = crate::ported::zle::compcore::OFFS.load(Ordering::Relaxed);
let occ = CCONT.with(|c| c.get());
let new_quote_char = if *INSTRING.lock().unwrap() != QT_NONE {
char::from_u32(*INSTRING.lock().unwrap() as u32).unwrap_or('\\')
} else {
char::from_u32(QT_BACKSLASH as u32).unwrap_or('\\')
};
let mut new_compqstack = String::new();
new_compqstack.push(new_quote_char);
new_compqstack.push_str(&oqst);
*COMPQSTACK
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = new_compqstack;
*CLWSIZE.lock().unwrap() = foo.len() as i32;
*CLWNUM.lock().unwrap() = foo.len() as i32;
*CLWORDS.lock().unwrap() = foo.clone();
*CLWPOS.lock().unwrap() = cur;
CMDSTR.with(|r| *r.borrow_mut() = foo.first().cloned());
*BRANGE.lock().unwrap() = 0;
*ERANGE.lock().unwrap() = (foo.len() as i32) - 1;
*QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = qp;
*QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = qs;
crate::ported::zle::compcore::OFFS.store(soffs, Ordering::Relaxed);
CCONT.with(|c| c.set(CC_CCCONT));
const CFN_FIRST: i32 = 1;
let _ = makecomplistcmd(&ns, cur == 0, CFN_FIRST);
CCONT.with(|c| c.set(occ));
crate::ported::zle::compcore::OFFS.store(oof, Ordering::Relaxed);
CMDSTR.with(|r| *r.borrow_mut() = os);
*CLWORDS.lock().unwrap() = ow;
*CLWSIZE.lock().unwrap() = olws;
*CLWNUM.lock().unwrap() = olwn;
*CLWPOS.lock().unwrap() = olwp;
*BRANGE.lock().unwrap() = obr;
*ERANGE.lock().unwrap() = oer;
*QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = oqp;
*QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap() = oqs;
*COMPQSTACK
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = oqst;
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = oaq;
*INSTRING.lock().unwrap() = ois;
*INBACKT.lock().unwrap() = oib;
0
}
thread_local! { static CCUSED: std::cell::RefCell<Vec<Arc<Compctl>>> = const { std::cell::RefCell::new(Vec::new()) }; }
thread_local! { static CCSTACK: std::cell::RefCell<Vec<Arc<Compctl>>> = const { std::cell::RefCell::new(Vec::new()) }; }
pub(crate) fn makecomplistor(cc: &Arc<Compctl>, s: &str, incmd: bool, compadd: i32, sub: i32) {
let mut current = cc.clone();
loop {
makecomplistlist(¤t, s, incmd, compadd);
match ¤t.xor {
Some(next) => current = next.clone(),
None => break,
}
let _ = sub;
}
}
pub(crate) fn makecomplistflags(cc: &Arc<Compctl>, mut s: String, incmd: bool, compadd: i32) {
use crate::ported::string::dupstrpfx;
use crate::ported::utils::quotestring;
use crate::ported::zle::comp_h::{Cexpl, CGF_NOSORT, CGF_UNIQALL, CGF_UNIQCON, CMF_REMOVE};
use crate::ported::zle::compcore::{begcmgroup, check_param, endcmgroup, rembslash};
use crate::ported::zsh_h::{Dash, Equals, Star, Stringg, Tick, Tilde};
use crate::ported::zsh_h::{
ALIAS_GLOBAL, DISABLED, PM_ARRAY, PM_EXPORTED, PM_INTEGER, PM_READONLY, PM_SCALAR,
PM_SPECIAL, PM_UNSET,
};
use std::sync::atomic::Ordering;
let um = crate::ported::zle::zle_tricky::USEMENU.load(Ordering::Relaxed);
let mn = crate::ported::zle::compcore::mnum.load(Ordering::Relaxed);
let ohp = crate::ported::zle::compcore::haspattern.load(Ordering::Relaxed);
let os = s.clone();
if INCOMPFUNC.load(Ordering::Relaxed) != 1
&& CCSTACK.with(|r| r.borrow().iter().any(|c| Arc::ptr_eq(c, cc)))
{
return;
}
CCSTACK.with(|r| r.borrow_mut().push(cc.clone()));
struct CcstackPop {
matcher_pushed: bool,
}
impl Drop for CcstackPop {
fn drop(&mut self) {
CCSTACK.with(|r| {
r.borrow_mut().pop();
});
if self.matcher_pushed {
if let Some(cell) = crate::ported::zle::compcore::mstack.get() {
if let Ok(mut g) = cell.lock() {
*g = g.take().and_then(|top| top.next.clone());
}
}
}
}
}
let mut ccstack_pop = CcstackPop { matcher_pushed: false };
{
use crate::ported::zle::compcore::{OFFS, WE, ZLEMETACS};
let we = WE.load(Ordering::Relaxed);
if !crate::ported::zsh_h::isset(crate::ported::zsh_h::COMPLETEINWORD)
&& ZLEMETACS.load(Ordering::Relaxed) != we
{
ZLEMETACS.store(we, Ordering::Relaxed);
OFFS.store(s.chars().count() as i32, Ordering::Relaxed);
}
}
let incompfunc = INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed);
let instr = *INSTRING.lock().unwrap_or_else(|e| e.into_inner());
let quotename = |x: &str| -> String {
if x.is_empty() {
String::new()
} else {
quotestring(
x,
if instr == QT_NONE {
QT_BACKSLASH
} else {
instr
},
)
}
};
let mut delit = false; crate::ported::zle::zle_tricky::USEMENU.store(um, Ordering::Relaxed); PATCOMP.with(|r| *r.borrow_mut() = None);
FILECOMP.with(|r| *r.borrow_mut() = None);
LPRE.with(|r| r.borrow_mut().clear());
LSUF.with(|r| r.borrow_mut().clear());
RPRE.with(|r| r.borrow_mut().clear());
RSUF.with(|r| r.borrow_mut().clear());
PPRE.with(|r| r.borrow_mut().clear());
PSUF.with(|r| r.borrow_mut().clear());
LPPRE.with(|r| r.borrow_mut().clear());
LPSUF.with(|r| r.borrow_mut().clear());
FPRE.with(|r| r.borrow_mut().clear());
FSUF.with(|r| r.borrow_mut().clear());
QFPRE.with(|r| r.borrow_mut().clear());
QFSUF.with(|r| r.borrow_mut().clear());
QRPRE.with(|r| r.borrow_mut().clear());
QRSUF.with(|r| r.borrow_mut().clear());
QLPRE.with(|r| r.borrow_mut().clear());
QLSUF.with(|r| r.borrow_mut().clear());
if let Some(m) = crate::ported::zle::compcore::ipre.get() {
if let Ok(mut g) = m.lock() {
g.clear();
}
}
if let Some(m) = crate::ported::zle::compcore::ripre.get() {
if let Ok(mut g) = m.lock() {
g.clear();
}
}
CURCC.with(|r| *r.borrow_mut() = Some(cc.clone()));
crate::ported::zle::compcore::mflags.store(0, Ordering::Relaxed);
let gflags = (if (cc.mask2 & CC_NOSORT) != 0 {
CGF_NOSORT
} else {
0
}) | (if (cc.mask2 & CC_UNIQALL) != 0 {
CGF_UNIQALL
} else {
0
}) | (if (cc.mask2 & CC_UNIQCON) != 0 {
CGF_UNIQCON
} else {
0
});
if cc.gname.is_some() {
endcmgroup(None);
begcmgroup(cc.gname.as_deref(), gflags);
}
if cc.ylist.is_some() {
endcmgroup(None);
begcmgroup(None, gflags);
}
if (cc.mask & CC_REMOVE) != 0 {
crate::ported::zle::compcore::mflags.fetch_or(CMF_REMOVE, Ordering::Relaxed);
}
let cell = crate::ported::zle::compcore::curexpl.get_or_init(|| Mutex::new(None));
if let Ok(mut g) = cell.lock() {
*g = if cc.explain.is_some() {
Some(Cexpl::default())
} else {
None
};
}
let mut offs = crate::ported::zle::compcore::OFFS.load(Ordering::Relaxed);
if compadd > 0 {
let mut ca = (compadd as usize).min(s.len());
while ca > 0 && !s.is_char_boundary(ca) {
ca -= 1;
}
let ip = crate::ported::lex::untokenize(&s[..ca]);
let cell = crate::ported::zle::compcore::ipre.get_or_init(|| Mutex::new(String::new()));
if let Ok(mut g) = cell.lock() {
*g = ip;
}
crate::ported::zle::compcore::WB.fetch_add(compadd, std::sync::atomic::Ordering::Relaxed);
s = s[ca..].to_string();
offs -= compadd;
if offs < 0 {
return;
}
}
if let Some(m) = cc.matcher.as_ref() {
let cell = crate::ported::zle::compcore::mstack.get_or_init(|| Mutex::new(None));
if let Ok(mut g) = cell.lock() {
let prev = g.take(); *g = Some(Arc::new(Cmlist {
next: prev,
matcher: m.clone(), str: String::new(),
})); }
ccstack_pop.matcher_pushed = true;
if crate::ported::zle::compcore::mnum.load(Ordering::Relaxed) == 0 {
crate::ported::zle::compmatch::add_bmatchers(Some(&**m)); }
if let Ok(mut mlist) = crate::ported::zle::compcore::matchers
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
{
mlist.push(m.clone()); }
}
if crate::ported::zle::compcore::mnum.load(Ordering::Relaxed) != 0
&& (crate::ported::zle::compcore::mstack
.get()
.and_then(|c| c.lock().ok().map(|g| g.is_some()))
.unwrap_or(false)
|| crate::ported::zle::compcore::bmatchers
.get()
.and_then(|c| c.lock().ok().map(|g| g.is_some()))
.unwrap_or(false))
{
crate::ported::zle::compmatch::update_bmatchers(); }
if let Some(prefix) = cc.prefix.as_ref() {
if !s.is_empty() {
let dp = rembslash(prefix);
let sd = crate::ported::lex::untokenize(&s);
let mut pl = crate::ported::zle::zle_tricky::pfxlen(&dp, &sd).min(s.len());
while pl > 0 && !s.is_char_boundary(pl) {
pl -= 1;
}
s = s[pl..].to_string();
offs -= pl as i32;
}
}
if let Some(suffix) = cc.suffix.as_ref() {
let mut sdup = rembslash(suffix);
while sdup.ends_with(' ') {
sdup.pop();
}
let sd = crate::ported::lex::untokenize(&s);
let sl = sdup.len();
let suffixll = sd.len();
if !sd.is_empty()
&& suffixll >= sl
&& (offs as usize) <= suffixll - sl
&& sd.ends_with(sdup.as_str())
{
let cut = suffixll - sl;
if cut <= s.len() && s.is_char_boundary(cut) {
s.truncate(cut);
}
}
}
let ic_char = s.chars().next().unwrap_or('\0');
let mut ic = if incompfunc != 0 {
'\0'
} else if ic_char == Tilde || ic_char == '~' {
Tilde
} else if ic_char == Equals || ic_char == '=' {
Equals
} else {
'\0'
};
let param_cc: Option<Arc<Compctl>> = if incompfunc == 0 {
let saved_wb = crate::ported::zle::compcore::WB.load(Ordering::Relaxed);
crate::ported::zle::compcore::OFFS.store(offs, Ordering::Relaxed);
if let Some(p) = check_param(&s, true, false, true) {
let mut p = p.min(s.len());
while p > 0 && !s.is_char_boundary(p) {
p -= 1;
}
s = s[p..].to_string(); offs = crate::ported::zle::compcore::OFFS.load(Ordering::Relaxed);
crate::ported::zle::compcore::WB.store(saved_wb, Ordering::Relaxed);
Some(Arc::new(Compctl {
refc: 10000,
mask: CC_PARAMS | CC_ENVVARS,
..Default::default()
}))
} else {
None
}
} else {
None
};
let cc: &Arc<Compctl> = param_cc.as_ref().unwrap_or(cc);
let ooffs = offs; if (cc.mask & CC_DELETE) != 0 {
delit = true;
s.clear();
offs = 0;
if crate::ported::zsh_h::isset(crate::ported::zsh_h::AUTOMENU) {
crate::ported::zle::zle_tricky::USEMENU.store(1, Ordering::Relaxed); }
}
crate::ported::zle::compcore::OFFS.store(offs, Ordering::Relaxed);
let cut = {
let mut c = (offs.max(0) as usize).min(s.len());
while c > 0 && !s.is_char_boundary(c) {
c -= 1;
}
c
};
let cpm: Option<String> = crate::ported::zle::compcore::comppatmatch
.get()
.and_then(|m| m.lock().ok())
.and_then(|g| g.clone());
let cpm_nonempty = cpm.as_deref().is_some_and(|c| !c.is_empty()); let lpre_s: String = if cpm.is_some() {
s[..cut].chars().map(|c| if c == Dash { '-' } else { c }).collect()
} else {
s[..cut].to_string()
};
let lsuf_s = s[cut..].to_string();
LPL.with(|c| c.set(lpre_s.len() as i32));
LSL.with(|c| c.set(lsuf_s.len() as i32));
let qlpre_s = quotename(&lpre_s);
let qlsuf_s = quotename(&lsuf_s);
if ic == Tilde && lpre_s.contains('/') {
ic = '\0';
}
IC.with(|c| c.set(ic));
let ispar = crate::ported::zle::compcore::ispar.load(Ordering::Relaxed);
let mut noreal = if delit { 0 } else { 1 };
let lpre_has_tok = lpre_s.chars().any(|c| c == Stringg || c == Tick);
let tt: &str = if ic != '\0' && ispar == 0 && !lpre_s.is_empty() {
&lpre_s[lpre_s.chars().next().map(|c| c.len_utf8()).unwrap_or(0)..]
} else {
&lpre_s
};
let first_lpre = lpre_s.chars().next();
let mut rpre_s = if lpre_has_tok || first_lpre == Some(Tilde) || first_lpre == Some(Equals) {
noreal = 0;
getreal(tt)
} else if (first_lpre == Some('~') && lpre_s.contains('/')) || first_lpre == Some('=') {
noreal = 0;
let mut tok = tt.to_string();
crate::ported::glob::tokenize(&mut tok);
getreal(&tok)
} else {
tt.to_string()
};
let qrpre_s = quotename(&rpre_s);
let lsuf_has_tok = lsuf_s.chars().any(|c| c == Stringg || c == Tick);
let mut rsuf_s = if lsuf_has_tok {
noreal = 0;
getreal(&lsuf_s)
} else {
lsuf_s.clone()
};
let qrsuf_s = quotename(&rsuf_s);
NOREAL.with(|c| c.set(noreal));
let mut ispattern_v: i32 = 0;
let mut s1_t: Option<usize> = None;
let mut sf1: i32 = 0;
for (idx, ch) in rpre_s.char_indices().rev() {
if ispattern_v == 3 && sf1 != 0 {
break; }
if crate::token_char::itok_char(ch) && (idx > 0 || (ch != Equals && ch != Tilde)) {
ispattern_v |= if sf1 != 0 { 1 } else { 2 }; } else if ch == '/' {
sf1 += 1; if s1_t.is_none() {
s1_t = Some(idx); }
}
}
let mut s2_t: Option<usize> = None;
let mut sf2: i32 = 0;
let mut t: i32 = 0;
for (idx, ch) in rsuf_s.char_indices() {
if t != 0 && sf2 != 0 {
break; }
if crate::token_char::itok_char(ch) {
t |= if sf2 != 0 { 4 } else { 2 }; } else if ch == '/' {
sf2 += 1; if s2_t.is_none() {
s2_t = Some(idx); }
}
}
ispattern_v |= t;
if !cpm_nonempty {
ispattern_v = 0;
}
let mut patcomp_set = false;
if ispattern_v != 0 {
let mut p = rpre_s.clone(); if !rpre_s.is_empty()
&& !rpre_s.ends_with(Star)
&& cpm.as_deref().is_some_and(|c| c.starts_with('*'))
{
p.push(Star); }
p.push_str(&rsuf_s); let prog = patcompile(&p, 0, None); patcomp_set = prog.is_some();
PATCOMP.with(|r| *r.borrow_mut() = prog);
crate::ported::zle::compcore::haspattern.store(1, Ordering::Relaxed); }
crate::ported::zle::compcore::ispattern.store(ispattern_v, Ordering::Relaxed);
let rpre_t = rpre_s;
let rsuf_t = rsuf_s;
let fix = |tok: &str| -> String {
if patcomp_set {
tok.chars().map(|c| if c == Dash { '-' } else { c }).collect()
} else {
crate::ported::lex::untokenize(tok)
}
};
let s1: Option<usize> = s1_t.map(|i| fix(&rpre_t[..i]).len());
let s2: Option<usize> = s2_t.map(|i| fix(&rsuf_t[..i]).len());
let rpre_s = fix(&rpre_t);
let rsuf_s = fix(&rsuf_t);
RPL.with(|c| c.set(rpre_s.len() as i32));
RSL.with(|c| c.set(rsuf_s.len() as i32));
let lpre_s = crate::ported::lex::untokenize(&lpre_s);
let lsuf_s = crate::ported::lex::untokenize(&lsuf_s);
LPRE.with(|r| *r.borrow_mut() = lpre_s.clone());
LSUF.with(|r| *r.borrow_mut() = lsuf_s.clone());
RPRE.with(|r| *r.borrow_mut() = rpre_s.clone());
RSUF.with(|r| *r.borrow_mut() = rsuf_s.clone());
QLPRE.with(|r| *r.borrow_mut() = qlpre_s);
QLSUF.with(|r| *r.borrow_mut() = qlsuf_s);
QRPRE.with(|r| *r.borrow_mut() = qrpre_s);
QRSUF.with(|r| *r.borrow_mut() = qrsuf_s);
if (cc.mask & CC_DELETE) == 0 {
crate::ported::zle::compcore::hasmatched.store(1, Ordering::Relaxed);
}
CCONT.with(|c| c.set(c.get() | (cc.mask2 & (CC_CCCONT | CC_DEFCONT | CC_PATCONT))));
let mut ppre = String::new();
let mut psuf = String::new();
if (cc.mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) != 0 || cc.glob.is_some() {
let ppre_s = match s1 {
Some(idx) => rpre_s[..idx + 1].to_string(),
None => String::new(),
};
let psuf_s = match s2 {
Some(idx) => rsuf_s[idx..].to_string(),
None => String::new(),
};
let meta = crate::ported::zle::compcore::ZLEMETALINE
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let wb = crate::ported::zle::compcore::WB.load(Ordering::Relaxed).max(0) as usize;
let we = crate::ported::zle::compcore::WE.load(Ordering::Relaxed).max(0) as usize;
let cs = crate::ported::zle::compcore::ZLEMETACS.load(Ordering::Relaxed).max(0) as usize;
let lppre_s = if cs != wb {
let qi = QIPRE.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let skip = if qi.is_empty() {
0
} else {
qi.len() - usize::from(qi.starts_with('\'') || qi.starts_with('"'))
};
let mut lp = meta.get((wb + skip).min(cs)..cs).unwrap_or("").to_string();
if let Some(g) = crate::ported::zle::compcore::BRBEG
.get()
.and_then(|m| m.lock().ok())
{
let mut bp = g.as_deref();
while let Some(b) = bp {
let q = b.qpos.max(0) as usize;
let e = (q + b.str.as_ref().map_or(0, |x| x.len())).min(lp.len());
if q <= e && lp.is_char_boundary(q) && lp.is_char_boundary(e) {
lp.replace_range(q..e, "");
}
bp = b.next.as_deref();
}
}
if let Some(p) = lp.rfind('/') {
lp.truncate(p + 1); lp
} else if sf1 == 0 {
String::new() } else {
ppre_s.clone() }
} else {
String::new() };
let lpsuf_s = if cs != we {
let qs = QISUF.get_or_init(|| Mutex::new(String::new())).lock().unwrap().clone();
let mut end = we as i64;
if !qs.is_empty() {
end -= qs.len() as i64 - i64::from(qs.ends_with('\'') || qs.ends_with('"'));
}
let mut ls = meta.get(cs..end.max(0) as usize).unwrap_or("").to_string();
if let Some(g) = crate::ported::zle::compcore::BREND
.get()
.and_then(|m| m.lock().ok())
{
let mut bp = g.as_deref();
while let Some(b) = bp {
let p2 = (we as i64 - cs as i64 - b.qpos as i64).max(0) as usize;
let p1 = p2.saturating_sub(b.str.as_ref().map_or(0, |x| x.len()));
if p2 <= ls.len() && ls.is_char_boundary(p1) && ls.is_char_boundary(p2) {
ls.replace_range(p1..p2, "");
}
bp = b.next.as_deref();
}
}
match ls.find('/') {
Some(i) => ls[i..].to_string(), None if sf2 != 0 => psuf_s.clone(), None => String::new(),
}
} else {
String::new() };
let s_first = s.as_bytes().first().copied();
let cs_eq_wb = cs == wb; let s1_is_start = s1.is_none() || s1 == Some(0);
let start_cond = s1_is_start || ic != '\0';
let mut fpre_s = if start_cond && (s_first != Some(b'/') || cs_eq_wb) {
match s1 {
Some(idx) => rpre_s[idx..].to_string(),
None => rpre_s.clone(),
}
} else {
match s1 {
Some(idx) => rpre_s[idx + 1..].to_string(),
None => {
let skip = rpre_s.chars().next().map(|c| c.len_utf8()).unwrap_or(0);
rpre_s[skip..].to_string()
}
}
};
let mut fsuf_s = match s2 {
Some(idx) => dupstrpfx(&rsuf_s, idx),
None => rsuf_s.clone(),
};
let qfpre_s = quotename(&fpre_s);
let qfsuf_s = quotename(&fsuf_s);
let mut filecomp_set = false;
if cpm_nonempty && (ispattern_v & 2) != 0 {
let mut p = fpre_s.clone(); if (fpre_s.is_empty() || !fpre_s.ends_with(Star))
&& !fsuf_s.starts_with(Star)
&& cpm.as_deref().is_some_and(|c| c.starts_with('*'))
{
p.push(Star); }
p.push_str(&fsuf_s); let prog = patcompile(&p, 0, None); filecomp_set = prog.is_some();
FILECOMP.with(|r| *r.borrow_mut() = prog);
}
if !filecomp_set {
fpre_s = crate::ported::lex::untokenize(&fpre_s); fsuf_s = crate::ported::lex::untokenize(&fsuf_s); FPL.with(|c| c.set(fpre_s.len() as i32)); FSL.with(|c| c.set(fsuf_s.len() as i32)); }
LPPL.with(|c| c.set(lppre_s.len() as i32));
LPSL.with(|c| c.set(lpsuf_s.len() as i32));
PPRE.with(|r| *r.borrow_mut() = ppre_s.clone());
PSUF.with(|r| *r.borrow_mut() = psuf_s.clone());
FPRE.with(|r| *r.borrow_mut() = fpre_s);
FSUF.with(|r| *r.borrow_mut() = fsuf_s);
QFPRE.with(|r| *r.borrow_mut() = qfpre_s);
QFSUF.with(|r| *r.borrow_mut() = qfsuf_s);
LPPRE.with(|r| *r.borrow_mut() = lppre_s);
LPSUF.with(|r| *r.borrow_mut() = lpsuf_s);
ppre = ppre_s;
psuf = psuf_s;
ADDWHAT.with(|c| c.set(-1));
let saved_prpre = PRPRE.with(|r| r.borrow().clone());
if ic == Tilde {
let oi = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = format!("~{}", oi); }
maketildelist();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = oi; }
} else if ic == Equals {
let oi = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = format!("={}", oi); }
if crate::ported::zsh_h::isset(crate::ported::zsh_h::HASHLISTALL) {
let path = crate::ported::params::getaparam("path").unwrap_or_default();
crate::ported::hashtable::fillcmdnamtable(&path);
}
let cmds: Vec<String> = crate::ported::hashtable::cmdnamtab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(cmds, -7);
let aliases: Vec<String> = crate::ported::hashtable::aliastab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(aliases, -2);
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = oi; }
} else if (ispattern_v & 1) != 0 {
let ng = crate::ported::zsh_h::isset(crate::ported::zsh_h::NULLGLOB);
crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::NULLGLOB),
true,
);
ADDWHAT.with(|c| c.set(0)); let lpre_u = LPRE.with(|r| r.borrow().clone());
let lsuf_u = LSUF.with(|r| r.borrow().clone());
let mut p = lpre_u.clone(); if !lsuf_u.starts_with('*') && !lpre_u.is_empty() && !lpre_u.ends_with('*') {
p.push('*'); }
p.push_str(&lsuf_u); if !lsuf_u.is_empty() && !lsuf_u.ends_with('*') && !lsuf_u.ends_with(')') {
p.push('*'); }
crate::ported::glob::tokenize(&mut p);
crate::ported::glob::remnulargs(&mut p);
let mut l: crate::ported::subst::LinkList = vec![p].into_iter().collect();
crate::ported::subst::globlist(&mut l, 0);
let found: Vec<String> = l.into_iter().collect();
if !found.is_empty() {
crate::ported::zle::compcore::mflags
.fetch_or(crate::ported::zle::comp_h::CMF_FILE, Ordering::Relaxed);
for n in &found {
addmatch(n, None);
}
crate::ported::zle::compcore::mflags
.fetch_and(!crate::ported::zle::comp_h::CMF_FILE, Ordering::Relaxed);
}
crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::NULLGLOB),
ng,
); } else {
ADDWHAT.with(|c| c.set(CC_FILES as i32));
let mut dirs: Option<Vec<String>> = None;
if let Some(withd) = cc.withd.as_ref() {
if let Some(pp) = crate::ported::zle::compcore::get_user_var(Some(withd.as_str())) {
dirs = Some(pp.iter().map(|d| format!("{}/{}", d, ppre)).collect());
}
}
let dirs: Vec<String> = dirs.unwrap_or_else(|| {
vec![match cc.withd.as_ref() {
Some(withd) => format!("{}/{}", withd, ppre), None => ppre.clone(), }]
});
for dir in dirs {
PRPRE.with(|r| *r.borrow_mut() = if dir.is_empty() { None } else { Some(dir.clone()) });
ADDWHAT.with(|c| c.set(-5));
if sf2 != 0 {
gen_matches_files(true, false, false);
} else {
if (cc.mask & CC_FILES) != 0 {
gen_matches_files(false, false, false);
} else if (cc.mask & CC_COMMPATH) != 0 {
if sf1 != 0 || cc.withd.is_some() {
gen_matches_files(true, true, false);
} else {
let path = crate::ported::params::getaparam("path").unwrap_or_default();
if path.iter().any(|p| p.is_empty() || p == ".") {
let pp = PRPRE.with(|r| r.borrow().clone());
PRPRE.with(|r| *r.borrow_mut() = Some("./".to_string())); gen_matches_files(true, true, false); PRPRE.with(|r| *r.borrow_mut() = pp); }
}
} else if (cc.mask & CC_DIRS) != 0 {
gen_matches_files(true, false, false); }
if let Some(globstr) = cc.glob.as_ref() {
let pl = dir.len(); let ne = std::mem::replace(
&mut *crate::ported::utils::noerrs_lock().lock().unwrap(),
1,
); let md = crate::ported::zsh_h::isset(crate::ported::zsh_h::MARKDIRS);
ADDWHAT.with(|c| c.set(-6)); crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::MARKDIRS),
false,
);
let mut rest: &str = globstr.as_str();
loop {
rest = rest.trim_start_matches(|c: char| {
c.is_ascii() && crate::ported::ztype_h::inblank(c as u8)
});
if rest.is_empty() {
break;
}
let b = rest.as_bytes();
let mut i = rest.chars().next().map_or(0, |c| c.len_utf8());
while i < b.len() && !crate::ported::ztype_h::inblank(b[i]) {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
let i = i.min(b.len());
let mut g = rest[..i].to_string();
rest = &rest[i..];
crate::ported::glob::tokenize(&mut g);
if g.starts_with('=')
&& crate::ported::zsh_h::isset(crate::ported::zsh_h::EQUALSOPT)
{
g.replace_range(..1, &Equals.to_string()); }
if g.starts_with('~') {
g.replace_range(..1, &Tilde.to_string()); }
crate::ported::glob::remnulargs(&mut g);
let first = g.chars().next();
let l_word: String;
if (first == Some(Equals) || first == Some(Tilde)) && cc.withd.is_none() {
g = crate::ported::subst::filesub(&g, 0);
l_word = g.clone();
} else if first == Some('/') && cc.withd.is_none() {
l_word = g.clone();
} else {
l_word = format!("{}{}", dir, g);
}
let ng = crate::ported::zsh_h::isset(crate::ported::zsh_h::NULLGLOB);
crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::NULLGLOB),
true,
);
let mut l: crate::ported::subst::LinkList =
vec![l_word].into_iter().collect();
crate::ported::subst::globlist(&mut l, 0);
crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::NULLGLOB),
ng,
);
let results: Vec<String> = l.into_iter().collect();
if let Some(first_res) = results.first() {
if !first_res.chars().any(crate::token_char::itok_char) {
let gf = g.chars().next();
if gf == Some(Equals)
|| gf == Some(Tilde)
|| gf == Some('/')
|| cc.withd.is_some()
{
for p2 in &results {
if crate::ported::utils::strpfx(&dir, p2) {
addmatch(&p2[pl..], None);
}
}
} else {
for p3 in &results {
let mut ns = sf1;
let mut start = 0;
for (ix, ch) in p3.char_indices() {
if ns == 0 {
break;
}
start = ix + ch.len_utf8();
if ch == '/' {
ns -= 1;
}
}
addmatch(&p3[start..], None);
}
}
}
}
}
*crate::ported::utils::noerrs_lock().lock().unwrap() = ne; crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::MARKDIRS),
md,
); }
}
}
}
PRPRE.with(|r| *r.borrow_mut() = saved_prpre);
LPPRE.with(|r| r.borrow_mut().clear());
LPSUF.with(|r| r.borrow_mut().clear());
LPPL.with(|c| c.set(0));
LPSL.with(|c| c.set(0));
}
if ic != '\0' {
let pfx = if ic == Tilde { "~" } else { "=" };
RPRE.with(|r| {
let mut b = r.borrow_mut();
*b = format!("{}{}", pfx, *b);
});
RPL.with(|c| c.set(c.get() + 1));
QRPRE.with(|r| {
let mut b = r.borrow_mut();
*b = format!("{}{}", pfx, *b);
});
}
let s: &str = &s;
if (cc.mask & CC_NAMED) != 0 {
ADDWHAT.with(|c| c.set(-1));
let oi = crate::ported::zle::compcore::ipre
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = format!("~{}", oi);
}
maketildelist();
if let Ok(mut g) = crate::ported::zle::compcore::ipre.get().unwrap().lock() {
*g = oi;
}
}
if ic == '\0' && (cc.mask & CC_COMMPATH) != 0 && ppre.is_empty() && psuf.is_empty() {
let aliases: Vec<String> = crate::ported::hashtable::aliastab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(aliases, -3);
let reswds: Vec<String> = crate::ported::hashtable::reswdtab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(reswds, -3);
let funcs: Vec<String> = crate::ported::hashtable::shfunctab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(funcs, -3);
let mut builtins: Vec<String> = crate::ported::builtin::BUILTINS
.iter()
.map(|b| b.node.nam.clone())
.filter(|n| crate::ext_builtins::builtin_in_builtintab(n))
.collect();
if !crate::ext_builtins::hide_ext_builtins() {
builtins.extend(
crate::ext_builtins::EXT_BUILTIN_NAMES
.iter()
.map(|s| (*s).to_string()),
);
}
dumphashtable(builtins, -3);
if crate::ported::zsh_h::isset(crate::ported::zsh_h::HASHLISTALL) {
let path = crate::ported::params::getaparam("path").unwrap_or_default();
crate::ported::hashtable::fillcmdnamtable(&path);
}
let cmds: Vec<String> = crate::ported::hashtable::cmdnamtab_lock()
.read()
.map(|tab| tab.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default();
dumphashtable(cmds, -3);
if crate::ported::zsh_h::isset(crate::ported::zsh_h::AUTOCD)
&& crate::ported::zsh_h::isset(crate::ported::zsh_h::CDABLEVARS)
{
let names: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| {
crate::ported::zsh_h::PM_TYPE(pm.node.flags as u32) == PM_SCALAR
&& pm.level == 0
})
.map(|(n, _)| n.clone())
.collect()
};
let names: Vec<String> = names
.into_iter()
.filter(|n| crate::ported::params::getsparam(n).is_some_and(|v| v.starts_with('/')))
.collect();
dumphashtable(names, -4);
}
}
let oaw: i32 = if (cc.mask & CC_QUOTEFLAG) != 0 {
-2
} else {
CC_QUOTEFLAG as i32
};
ADDWHAT.with(|c| c.set(oaw));
if (cc.mask & CC_OPTIONS) != 0 {
let names: Vec<String> = crate::ported::options::OPTIONTAB
.iter()
.map(|o| o.to_string())
.collect();
dumphashtable(names, oaw);
}
if (cc.mask & CC_VARS) != 0 {
let names: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| (pm.node.flags & PM_UNSET as i32) == 0 && pm.level == 0)
.map(|(n, _)| n.clone())
.collect()
};
dumphashtable(names, -9);
ADDWHAT.with(|c| c.set(oaw)); }
if (cc.mask & CC_BINDINGS) != 0 {
let names: Vec<String> = crate::ported::zle::zle_thingy::thingytab()
.lock()
.map(|t| t.keys().cloned().collect())
.unwrap_or_default();
dumphashtable(names, CC_BINDINGS as i32);
ADDWHAT.with(|c| c.set(oaw)); }
if let Some(kv) = cc.keyvar.as_ref() {
if let Some(usr) = crate::ported::zle::compcore::get_user_var(Some(kv.as_str())) {
ADDWHAT.with(|c| c.set(oaw));
for u in usr {
addmatch(&u, None);
}
}
}
if (cc.mask & CC_USERS) != 0 {
maketildelist();
ADDWHAT.with(|c| c.set(oaw)); }
if let Some(func_name) = cc.func.as_ref() {
if let Some(mut shfunc) = crate::ported::utils::getshfunc(func_name) {
let lv = crate::ported::builtin::LASTVAL.load(std::sync::atomic::Ordering::Relaxed); let (arg_pre, arg_suf) = if delit {
let mut at = ooffs.max(0) as usize;
at = at.min(os.len());
while at > 0 && !os.is_char_boundary(at) {
at -= 1;
}
(
crate::ported::lex::untokenize(&os[..at]),
crate::ported::lex::untokenize(&os[at..]),
)
} else {
(
LPRE.with(|r| r.borrow().clone()),
LSUF.with(|r| r.borrow().clone()),
)
};
let largs: Vec<String> = vec![func_name.clone(), arg_pre.clone(), arg_suf.clone()];
let in_compfunc = INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed);
if in_compfunc != 1 {
INCOMPCTLFUNC.with(|c| c.set(true));
}
let osc = crate::ported::builtin::SFCONTEXT.swap(
crate::ported::zsh_h::SFC_COMPLETE,
std::sync::atomic::Ordering::Relaxed,
);
let name_for_body = func_name.clone();
let body_args: Vec<String> = vec![arg_pre, arg_suf];
let body_runner = move || -> i32 {
crate::ported::exec::run_function_body(&name_for_body, &body_args).unwrap_or(0)
};
let _ = crate::ported::exec::doshfunc(&mut shfunc, largs, true, body_runner);
crate::ported::builtin::SFCONTEXT.store(osc, std::sync::atomic::Ordering::Relaxed);
INCOMPCTLFUNC.with(|c| c.set(false));
if let Some(reply) = crate::ported::params::getaparam("reply") {
ADDWHAT.with(|c| c.set(oaw));
for m in reply {
addmatch(&m, None);
}
}
crate::ported::builtin::LASTVAL.store(lv, std::sync::atomic::Ordering::Relaxed); }
}
if (cc.mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) != 0 {
let texts: Vec<(String, bool)> = crate::ported::jobs::JOBTAB
.get()
.and_then(|m| m.lock().ok())
.map(|tab| {
tab.iter()
.filter(|j| {
(j.stat & crate::ported::zsh_h::STAT_INUSE) != 0
&& j.procs.first().is_some_and(|p| !p.text.is_empty())
})
.map(|j| {
(
j.procs[0].text.clone(),
(j.stat & crate::ported::zsh_h::STAT_STOPPED) != 0,
)
})
.collect()
})
.unwrap_or_default();
for (text, stopped) in texts {
if (cc.mask & CC_JOBS) != 0
|| (stopped && (cc.mask & CC_STOPPED) != 0)
|| (!stopped && (cc.mask & CC_RUNNING) != 0)
{
addmatch(&text, None); }
}
}
if let Some(ylist) = cc.ylist.as_ref() {
let is_var_ref = ylist.starts_with('$') || ylist.starts_with('(');
if !is_var_ref {
if let Some(mut shfunc) = crate::ported::utils::getshfunc(ylist) {
let largs: Vec<String> = vec![ylist.clone()];
let in_compfunc = INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed);
if in_compfunc != 1 {
INCOMPCTLFUNC.with(|c| c.set(true));
}
let osc = crate::ported::builtin::SFCONTEXT.swap(
crate::ported::zsh_h::SFC_COMPLETE,
std::sync::atomic::Ordering::Relaxed,
);
let name_for_body = ylist.clone();
let body_runner = move || -> i32 {
crate::ported::exec::run_function_body(&name_for_body, &[]).unwrap_or(0)
};
let _ = crate::ported::exec::doshfunc(&mut shfunc, largs, true, body_runner);
crate::ported::builtin::SFCONTEXT.store(osc, std::sync::atomic::Ordering::Relaxed);
INCOMPCTLFUNC.with(|c| c.set(false));
}
}
}
if let Some(s) = &cc.str {
let expanded = getreal(s);
ADDWHAT.with(|c| c.set(-6));
addmatch(&expanded, None);
}
if let Some(hpat) = cc.hpat.as_ref() {
use crate::ported::zsh_h::{GETHIST_UPWARD, HIST_FOREIGN};
let pprog = if !hpat.is_empty() {
let mut thpat = hpat.clone();
crate::ported::glob::tokenize(&mut thpat);
patcompile(&thpat, PAT_HEAPDUP as i32, None)
} else {
None
};
let mut n = cc.hnum;
if n == 0 {
n = -1;
}
let cur = crate::ported::hist::curhist.load(std::sync::atomic::Ordering::Relaxed);
let start = crate::ported::hist::addhistnum(cur, -1, HIST_FOREIGN as i32); let mut he_ev = crate::ported::hist::gethistent(start, GETHIST_UPWARD); ADDWHAT.with(|c| c.set(oaw));
while n != 0 {
let ev = match he_ev {
Some(e) => e,
None => break,
};
n -= 1;
if let Some(he) = crate::ported::hist::gethist(ev) {
let nam = &he.node.nam;
for iw in (0..he.nwords).rev() {
let bi = (iw * 2) as usize;
if bi + 1 >= he.words.len() {
continue;
}
let hstart = he.words[bi] as usize; let hend = he.words[bi + 1] as usize; if hstart > hend
|| hend > nam.len()
|| !nam.is_char_boundary(hstart)
|| !nam.is_char_boundary(hend)
{
continue;
}
let word = &nam[hstart..hend];
let c0 = word.chars().next();
if !matches!(c0, Some('\'') | Some('"') | Some('`') | Some('$'))
&& pprog.as_ref().map_or(true, |pp| pattry(pp, word))
{
addmatch(word, None); }
}
}
he_ev = crate::ported::hist::up_histent(ev); }
}
{
let t = cc.mask
& (CC_ARRAYS
| CC_INTVARS
| CC_ENVVARS
| CC_SCALARS
| CC_READONLYS
| CC_SPECIALS
| CC_PARAMS);
if t != 0 {
let names: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| {
let f = pm.node.flags as u32;
if (f & PM_UNSET) != 0 || pm.level != 0 {
return false;
}
((t & CC_ARRAYS) != 0 && (f & PM_ARRAY) != 0)
|| ((t & CC_INTVARS) != 0 && (f & PM_INTEGER) != 0)
|| ((t & CC_ENVVARS) != 0 && (f & PM_EXPORTED) != 0)
|| ((t & CC_SCALARS) != 0 && (f & PM_SCALAR) != 0)
|| ((t & CC_READONLYS) != 0 && (f & PM_READONLY) != 0)
|| ((t & CC_SPECIALS) != 0 && (f & PM_SPECIAL) != 0)
|| ((t & CC_PARAMS) != 0 && (f & PM_EXPORTED) == 0)
})
.map(|(n, _)| n.clone())
.collect()
};
dumphashtable(names, t as i32);
}
}
let want_dis = (cc.mask & CC_DISCMDS) != 0;
let want_ex = (cc.mask & CC_EXCMDS) != 0;
let en_ok = |disabled: bool| (want_dis && disabled) || (want_ex && !disabled);
if (cc.mask & CC_SHFUNCS) != 0 {
let names: Vec<String> = crate::ported::hashtable::shfunctab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, f)| en_ok((f.node.flags & DISABLED) != 0))
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & CC_SHFUNCS) as i32);
}
if (cc.mask & CC_BUILTINS) != 0 {
let names: Vec<String> = crate::ported::builtin::BUILTINS
.iter()
.filter(|b| en_ok((b.node.flags & DISABLED) != 0))
.map(|b| b.node.nam.clone())
.collect();
dumphashtable(names, (cc.mask & CC_BUILTINS) as i32);
}
if (cc.mask & CC_EXTCMDS) != 0 {
let names: Vec<String> = crate::ported::hashtable::cmdnamtab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, c)| en_ok((c.node.flags & DISABLED) != 0))
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & CC_EXTCMDS) as i32);
}
if (cc.mask & CC_RESWDS) != 0 {
let names: Vec<String> = crate::ported::hashtable::reswdtab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, r)| en_ok((r.node.flags & DISABLED) != 0))
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & CC_RESWDS) as i32);
}
if (cc.mask & (CC_ALREG | CC_ALGLOB)) != 0 {
let want_reg = (cc.mask & CC_ALREG) != 0;
let want_glob = (cc.mask & CC_ALGLOB) != 0;
let names: Vec<String> = crate::ported::hashtable::aliastab_lock()
.read()
.map(|tab| {
tab.iter()
.filter(|(_, a)| {
let g = (a.node.flags & ALIAS_GLOBAL) != 0;
let type_ok = (want_reg && !g) || (want_glob && g);
type_ok && en_ok((a.node.flags & DISABLED) != 0)
})
.map(|(n, _)| n.clone())
.collect()
})
.unwrap_or_default();
dumphashtable(names, (cc.mask & (CC_ALREG | CC_ALGLOB)) as i32);
}
if let Some(subcmd) = cc.subcmd.as_ref() {
let ow = CLWORDS.lock().unwrap().clone(); let ocmd = CMDSTR.with(|r| r.borrow().clone()); let oldn = *CLWNUM.lock().unwrap(); let oldp = *CLWPOS.lock().unwrap(); let occ = CCONT.with(|c| c.get());
CCONT.with(|c| c.set(CC_CCCONT));
let mut brange = *BRANGE.lock().unwrap();
let mut erange = *ERANGE.lock().unwrap();
if brange >= oldn {
brange = oldn - 1;
}
if brange < 1 {
brange = 1;
}
if erange >= oldn {
erange = oldn - 1;
}
if erange < 1 {
erange = 1;
}
*BRANGE.lock().unwrap() = brange;
*ERANGE.lock().unwrap() = erange;
let mut clwnum = erange - brange + 1; let mut clwpos = oldp - brange; let br = brange as usize; let mut incmd_sub = incmd;
let new_words: Vec<String>;
let new_cmd: Option<String>;
if !subcmd.is_empty() {
clwpos += 1;
clwnum += 1;
incmd_sub = false;
let mut w: Vec<String> = ow.get(br - 1..).unwrap_or(&[]).to_vec();
if let Some(first) = w.first_mut() {
*first = subcmd.clone(); } else {
w.push(subcmd.clone());
}
new_words = w;
new_cmd = Some(subcmd.clone()); } else {
new_words = ow.get(br..).unwrap_or(&[]).to_vec();
new_cmd = ow.get(br).cloned();
incmd_sub = clwpos == 0;
}
*CLWORDS.lock().unwrap() = new_words;
CMDSTR.with(|r| *r.borrow_mut() = new_cmd);
*CLWNUM.lock().unwrap() = clwnum;
*CLWPOS.lock().unwrap() = clwpos;
makecomplistcmd(s, incmd_sub, CFN_FIRST);
*CLWORDS.lock().unwrap() = ow;
CMDSTR.with(|r| *r.borrow_mut() = ocmd);
*CLWNUM.lock().unwrap() = oldn;
*CLWPOS.lock().unwrap() = oldp;
CCONT.with(|c| c.set(occ));
}
if let Some(substr) = cc.substr.as_ref() {
sep_comp_string(substr, s, offs);
}
if mn == crate::ported::zle::compcore::mnum.load(Ordering::Relaxed) {
crate::ported::zle::compcore::haspattern.store(ohp, Ordering::Relaxed); }
}
pub(crate) fn setup_() -> i32 {
*COMPCTLREAD_INSTALLED.lock().unwrap() = true;
createcompctltable();
*CC_COMPOS.lock().unwrap() = Some(Arc::new(Compctl {
mask: CC_COMMPATH, ..Default::default()
}));
*CC_DEFAULT.lock().unwrap() = Some(Arc::new(Compctl {
refc: 10000, mask: CC_FILES, ..Default::default()
}));
*CC_FIRST.lock().unwrap() = Some(Arc::new(Compctl {
refc: 10000, mask2: CC_CCCONT, ..Default::default()
}));
*LASTCCUSED.lock().unwrap() = Vec::new(); 0
}
static ZLEMETALL: Mutex<i32> = Mutex::new(0);
pub(crate) fn features_() -> Vec<String> {
vec!["b:compctl".to_string(), "b:compcall".to_string()]
}
pub(crate) fn enables_() -> Vec<i32> {
vec![1, 1]
}
pub(crate) fn boot_() -> i32 {
crate::ported::module::addhookfunc("compctl_make", ccmakehookfn);
crate::ported::module::addhookfunc("compctl_cleanup", cccleanuphookfn);
0
}
pub(crate) fn cleanup_() -> i32 {
crate::ported::module::deletehookfunc("compctl_make", ccmakehookfn);
crate::ported::module::deletehookfunc("compctl_cleanup", cccleanuphookfn);
0
}
pub(crate) fn finish_() -> i32 {
*COMPCTL_TAB.write().unwrap() = None; LASTCCUSED.lock().unwrap().clear(); *COMPCTLREAD_INSTALLED.lock().unwrap() = false; 0
}
pub(crate) static CMATCHER: std::sync::RwLock<Option<std::sync::Arc<Cmlist>>> =
std::sync::RwLock::new(None);
static COMPCTL_TAB: std::sync::RwLock<Option<HashMap<String, Arc<Compctl>>>> =
std::sync::RwLock::new(None);
static PATCOMPS: std::sync::RwLock<Vec<(String, Arc<Compctl>)>> =
std::sync::RwLock::new(Vec::new());
static NOALIASES: Mutex<i32> = Mutex::new(0);
static INSTRING: Mutex<i32> = Mutex::new(QT_NONE);
static INBACKT: Mutex<i32> = Mutex::new(0);
static COMPQIPREFIX: Mutex<String> = Mutex::new(String::new());
static COMPQISUFFIX: Mutex<String> = Mutex::new(String::new());
static COMPISUFFIX: Mutex<String> = Mutex::new(String::new());
static ISUF: Mutex<String> = Mutex::new(String::new());
static CLWORDS: Mutex<Vec<String>> = Mutex::new(Vec::new());
static CLWSIZE: Mutex<i32> = Mutex::new(0);
static CLWNUM: Mutex<i32> = Mutex::new(0);
static CLWPOS: Mutex<i32> = Mutex::new(0);
static ADDEDX: Mutex<i32> = Mutex::new(0);
static LEXFLAGS: Mutex<i32> = Mutex::new(0);
const LEXFLAGS_ZLE: i32 = 1 << 0;
static BRANGE: Mutex<i32> = Mutex::new(0);
static ERANGE: Mutex<i32> = Mutex::new(0);
static LINWHAT: Mutex<i32> = Mutex::new(IN_NOTHING);
static LINREDIR: Mutex<i32> = Mutex::new(0);
static INSUBSCR: Mutex<i32> = Mutex::new(0);
pub const Snull: char = '\u{e19d}'; pub const Dnull: char = '\u{e19e}'; pub const Bnull: char = '\u{e19f}'; pub const Stringg: char = '\u{e185}'; pub const QSTRING_TOK: char = '\u{e184}';
pub(crate) static CC_COMPOS: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
pub(crate) static CC_DEFAULT: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
pub(crate) static CC_FIRST: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
pub(crate) static CC_DUMMY: Mutex<Option<Arc<Compctl>>> = Mutex::new(None);
static LASTCCUSED: Mutex<Vec<Arc<Compctl>>> = Mutex::new(Vec::new());
static COMPCTLREAD_INSTALLED: Mutex<bool> = Mutex::new(false);
fn inull(c: char) -> bool {
matches!(c, Snull | Dnull | Bnull | Stringg | QSTRING_TOK)
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn createcompctltable_initializes_table() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
let g = COMPCTL_TAB.read().unwrap();
assert!(g.is_some());
assert_eq!(g.as_ref().unwrap().len(), 0);
}
#[test]
fn cc_assign_inserts_into_table() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
let cc = Arc::new(Compctl {
mask: CC_FILES,
..Default::default()
});
cc_assign("ls", cc, false);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("ls"));
}
#[test]
fn freecompctlp_removes_entry() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
cc_assign("rm", Arc::new(Compctl::default()), false);
freecompctlp("rm");
let g = COMPCTL_TAB.read().unwrap();
assert!(!g.as_ref().unwrap().contains_key("rm"));
}
#[test]
fn cc_flags_bit_layout_matches_c_compctlh() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(CC_FILES, 1);
assert_eq!(CC_COMMPATH, 2);
assert_eq!(CC_OPTIONS, 8);
assert_eq!(CC_JOBS, 1 << 11);
}
#[test]
fn cct_constants_match_c_compctlh() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(CCT_POS, 1);
assert_eq!(CCT_CURPAT, 3);
assert_eq!(CCT_QUOTE, 13);
}
#[test]
fn comp_op_special_combines_command_default_first() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(COMP_SPECIAL, COMP_COMMAND | COMP_DEFAULT | COMP_FIRST);
}
#[test]
fn cc_flags2_constants_match_c_compctlh() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(CC_NOSORT, 1);
assert_eq!(CC_CCCONT, 4);
assert_eq!(CC_UNIQALL, 1 << 6);
}
#[test]
fn get_compctl_simple_flag_chars_set_mask() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut argv = vec!["-fcv".to_string(), "ls".to_string()];
let mut cc = Compctl::default();
let r = get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(r, 0);
assert_ne!(cc.mask & CC_FILES, 0);
assert_ne!(cc.mask & CC_COMMPATH, 0);
assert_ne!(cc.mask & CC_VARS, 0);
assert_eq!(argv, vec!["ls".to_string()]);
}
#[test]
fn get_compctl_combined_a_sets_alreg_and_alglob() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut argv = vec!["-a".to_string(), "ls".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_ne!(cc.mask & CC_ALREG, 0);
assert_ne!(cc.mask & CC_ALGLOB, 0);
}
#[test]
fn get_compctl_arg_taking_K_captures_function_name() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut argv = vec![
"-K".to_string(),
"_my_completer".to_string(),
"myfunc".to_string(),
];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.func.as_deref(), Some("_my_completer"));
assert_eq!(argv, vec!["myfunc".to_string()]);
}
#[test]
fn get_compctl_inline_arg_K_captures_function_name() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut argv = vec!["-K_my_func".to_string(), "myfunc".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.func.as_deref(), Some("_my_func"));
}
#[test]
fn get_compctl_P_S_capture_prefix_suffix() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut argv = vec![
"-P".to_string(),
"before-".to_string(),
"-S".to_string(),
"-after".to_string(),
"cmd".to_string(),
];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.prefix.as_deref(), Some("before-"));
assert_eq!(cc.suffix.as_deref(), Some("-after"));
}
#[test]
fn get_compctl_1_2_set_uniq_flags() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut argv = vec!["-1".to_string(), "ls".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_ne!(cc.mask2 & CC_UNIQALL, 0);
assert_eq!(cc.mask2 & CC_UNIQCON, 0);
}
#[test]
fn get_compctl_V_implies_NOSORT() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut argv = vec!["-V".to_string(), "mygroup".to_string(), "cmd".to_string()];
let mut cc = Compctl::default();
get_compctl("compctl", &mut argv, &mut cc, true, false, 0);
assert_eq!(cc.gname.as_deref(), Some("mygroup"));
assert_ne!(cc.mask2 & CC_NOSORT, 0);
}
#[test]
fn bin_compctl_install_then_lookup_via_table() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compctl("compctl", &["-f".to_string(), "mycmd".to_string()], &ops, 0);
assert_eq!(r, 0);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("mycmd"));
let cc = g.as_ref().unwrap().get("mycmd").unwrap();
assert_ne!(cc.mask & CC_FILES, 0);
}
#[test]
fn compctl_name_pat_detects_glob_wildcards() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (is_pat, _) = compctl_name_pat("ls*");
assert!(is_pat);
let (is_pat, _) = compctl_name_pat("foo?bar");
assert!(is_pat);
let (is_pat, _) = compctl_name_pat("[abc]");
assert!(is_pat);
}
#[test]
fn compctl_name_pat_strips_backslashes_from_literal() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (is_pat, out) = compctl_name_pat("\\$home");
assert!(!is_pat);
assert_eq!(out, "$home");
}
#[test]
fn delpatcomp_removes_matching_pattern() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut p = PATCOMPS.write().unwrap();
p.push(("foo*".to_string(), Arc::new(Compctl::default())));
p.push(("bar*".to_string(), Arc::new(Compctl::default())));
drop(p);
delpatcomp("foo*");
let p = PATCOMPS.read().unwrap();
assert_eq!(p.len(), 1);
assert_eq!(p[0].0, "bar*");
}
#[test]
fn delpatcomp_removes_only_first_match_when_duplicates() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
{
let mut p = PATCOMPS.write().unwrap();
p.clear();
for tag in ["a", "b", "c"] {
p.push((
"dup*".to_string(),
Arc::new(Compctl {
keyvar: Some(tag.to_string()),
..Compctl::default()
}),
));
}
}
delpatcomp("dup*");
let p = PATCOMPS.read().unwrap();
assert_eq!(
p.len(),
2,
"c:1308 — only ONE entry removed; got len={}",
p.len()
);
assert_eq!(
p[0].1.keyvar.as_deref(),
Some("b"),
"remaining first entry must be the second-inserted (b)"
);
assert_eq!(
p[1].1.keyvar.as_deref(),
Some("c"),
"remaining second entry must be the third-inserted (c)"
);
}
#[test]
fn cc_assign_with_reass_command_target_uses_special_key() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
CCLIST.with(|c| c.set(COMP_COMMAND));
cc_assign(
"compctl",
Arc::new(Compctl {
mask: CC_FILES,
..Default::default()
}),
true,
);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("__cc_compos"));
drop(g);
CCLIST.with(|c| c.set(0));
}
#[test]
fn cc_assign_with_reass_default_target_uses_special_key() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
CCLIST.with(|c| c.set(COMP_DEFAULT));
cc_assign("compctl", Arc::new(Compctl::default()), true);
let g = COMPCTL_TAB.read().unwrap();
assert!(g.as_ref().unwrap().contains_key("__cc_default"));
drop(g);
CCLIST.with(|c| c.set(0));
}
#[test]
fn setup_initializes_special_targets_and_table() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
setup_();
let cc_compos = CC_COMPOS.lock().unwrap().clone();
assert!(cc_compos.is_some());
assert_eq!(cc_compos.unwrap().mask, CC_COMMPATH);
let cc_default = CC_DEFAULT.lock().unwrap().clone();
assert!(cc_default.is_some());
let cc_default = cc_default.unwrap();
assert_eq!(cc_default.mask, CC_FILES);
assert_eq!(cc_default.refc, 10000);
let cc_first = CC_FIRST.lock().unwrap().clone();
assert!(cc_first.is_some());
assert_eq!(cc_first.unwrap().mask2, CC_CCCONT);
assert!(COMPCTL_TAB.read().unwrap().is_some());
assert!(*COMPCTLREAD_INSTALLED.lock().unwrap());
}
#[test]
fn finish_tears_down_state() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
setup_();
finish_();
assert!(COMPCTL_TAB.read().unwrap().is_none());
assert!(!*COMPCTLREAD_INSTALLED.lock().unwrap());
assert_eq!(LASTCCUSED.lock().unwrap().len(), 0);
}
#[test]
fn features_returns_two_builtins() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let f = features_();
assert_eq!(f, vec!["b:compctl".to_string(), "b:compcall".to_string()]);
}
#[test]
fn enables_returns_two_enabled_bits() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let e = enables_();
assert_eq!(e, vec![1, 1]);
}
#[test]
fn bin_compcall_outside_compfunc_errors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPFUNC.store(0, 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_compcall("compcall", &[], &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn bin_compcall_inside_compfunc_succeeds() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPFUNC.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_compcall("compcall", &["-T".to_string()], &ops, 0);
assert_eq!(r, 0);
INCOMPFUNC.store(0, std::sync::atomic::Ordering::Relaxed);
}
#[test]
fn compctlread_outside_compctl_func_errors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
INCOMPCTLFUNC.with(|c| c.set(false));
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = compctlread("compctlread", &[], &ops, "reply");
assert_eq!(r, 1);
}
#[test]
fn compctl_hooks_have_hookfn_signature() {
let _: crate::ported::zsh_h::Hookfn = ccmakehookfn;
let _: crate::ported::zsh_h::Hookfn = cccleanuphookfn;
}
#[test]
fn cccleanuphookfn_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(cccleanuphookfn(std::ptr::null_mut(), std::ptr::null_mut()), 0);
}
fn mnum_now() -> i32 {
crate::ported::zle::compcore::mnum.load(std::sync::atomic::Ordering::Relaxed)
}
fn clear_matches() {
crate::comp_match_handles::matches_arc()
.lock()
.unwrap()
.clear();
}
fn last_match_orig() -> Option<String> {
crate::comp_match_handles::matches_arc()
.lock()
.unwrap()
.last()
.and_then(|cm| cm.orig.clone())
}
fn reset_compctl_statics() {
PATCOMP.with(|r| *r.borrow_mut() = None);
FILECOMP.with(|r| *r.borrow_mut() = None);
for st in [
&LPRE, &LSUF, &RPRE, &RSUF, &PPRE, &PSUF, &LPPRE, &LPSUF, &FPRE, &FSUF, &QFPRE, &QFSUF,
&QRPRE, &QRSUF, &QLPRE, &QLSUF,
] {
st.with(|r| r.borrow_mut().clear());
}
crate::ported::zle::compcore::mflags.store(0, std::sync::atomic::Ordering::Relaxed);
}
#[test]
fn addmatch_rejects_unset_addwhat() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(0));
addmatch("dropped", None);
assert!(
MATCH_LIST.with(|r| r.borrow().is_empty()),
"addwhat=0 should drop the match"
);
assert_eq!(mnum_now(), before, "no Cmatch may be registered");
assert_eq!(
crate::comp_match_handles::matches_arc()
.lock()
.unwrap()
.len(),
0,
"match registry stays empty"
);
}
#[test]
fn addmatch_accepts_files_kind() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(-5));
addmatch("foo.txt", None);
addmatch("bar.txt", None);
assert_eq!(mnum_now(), before + 2, "two Cmatches registered");
let reg = crate::comp_match_handles::matches_arc()
.lock()
.unwrap()
.clone();
assert_eq!(reg.len(), 2);
assert_eq!(reg[0].orig.as_deref(), Some("foo.txt"));
assert_eq!(reg[1].orig.as_deref(), Some("bar.txt"));
use crate::ported::zle::comp_h::CMF_FILE;
assert_ne!(reg[0].flags & CMF_FILE, 0, "file matches flagged CMF_FILE");
}
#[test]
fn addmatch_accepts_param_kind() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(-9));
addmatch("HOME", None);
assert_eq!(mnum_now(), before + 1, "one Cmatch registered");
assert_eq!(last_match_orig().as_deref(), Some("HOME"));
use crate::ported::zle::comp_h::CMF_FILE;
let reg = crate::comp_match_handles::matches_arc()
.lock()
.unwrap()
.clone();
assert_eq!(reg[0].flags & CMF_FILE, 0, "param match is not a file");
}
#[test]
fn addmatch_accepts_cc_files_positive_mask() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
clear_matches();
reset_compctl_statics();
let before = mnum_now();
ADDWHAT.with(|c| c.set(CC_FILES as i32));
addmatch("foo", None);
assert_eq!(mnum_now(), before + 1);
assert_eq!(last_match_orig().as_deref(), Some("foo"));
}
#[test]
fn getcpat_finds_first_substring() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("abcabc", 1, "bc", 0);
assert_eq!(r, 3);
}
#[test]
fn getcpat_finds_second_substring() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("abcabc", 2, "bc", 0);
assert_eq!(r, 6);
}
#[test]
fn getcpat_negative_index_searches_backward() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("abcabc", -1, "bc", 0);
assert!(r >= 0, "should find match (got {})", r);
}
#[test]
fn getcpat_class_mode_matches_any_char_in_set() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("abcdef", 1, "bdf", 1);
assert_eq!(r, 2); }
#[test]
fn getcpat_not_found_returns_negative_one() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("hello", 1, "xyz", 0);
assert_eq!(r, -1);
}
#[test]
fn getcpat_strips_backslashes_in_pattern() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = getcpat("foo$bar", 1, "\\$", 0);
assert_eq!(r, 4); }
#[test]
fn dumphashtable_calls_addmatch_per_entry() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
let entries = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
dumphashtable(entries, -5);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 3);
}
#[test]
fn addhnmatch_forwards_to_addmatch() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
ADDWHAT.with(|c| c.set(-5));
addhnmatch("xyz", 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 1);
assert_eq!(m[0], "xyz");
}
#[test]
fn makecomplistctl_recursion_guard() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
CDEPTH.with(|c| c.set(MAX_CDEPTH));
let r = makecomplistctl(0);
assert_eq!(r, 0);
CDEPTH.with(|c| c.set(0));
}
#[test]
fn makecomplistflags_cc_files_invokes_gen_matches() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
PRPRE.with(|r| *r.borrow_mut() = Some(".".to_string()));
let cc = Arc::new(Compctl {
mask: CC_FILES,
..Default::default()
});
makecomplistflags(&cc, String::new(), false, 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert!(!m.is_empty(), "expected file matches in pwd");
}
#[test]
fn makecomplistflags_cc_str_expansion_emits_one_match() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
let cc = Arc::new(Compctl {
str: Some("hardcoded".to_string()),
..Default::default()
});
makecomplistflags(&cc, String::new(), false, 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 1);
assert_eq!(m[0], "hardcoded");
}
#[test]
fn makecomplistor_walks_xor_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
MATCH_LIST.with(|r| r.borrow_mut().clear());
let cc2 = Arc::new(Compctl {
str: Some("second".to_string()),
..Default::default()
});
let cc1 = Arc::new(Compctl {
str: Some("first".to_string()),
xor: Some(cc2),
..Default::default()
});
makecomplistor(&cc1, "", false, 0, 0);
let m = MATCH_LIST.with(|r| r.borrow().clone());
assert_eq!(m.len(), 2);
assert_eq!(m[0], "first");
assert_eq!(m[1], "second");
}
#[test]
fn makecomplistcc_pushes_to_ccused() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
CCUSED.with(|r| r.borrow_mut().clear());
let cc = Arc::new(Compctl::default());
makecomplistcc(&cc, "", false);
let used = CCUSED.with(|r| r.borrow().clone());
assert_eq!(used.len(), 1);
}
#[test]
fn makecomplistpc_iterates_patcomps() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
CMDSTR.with(|r| *r.borrow_mut() = None);
let r = makecomplistpc("", false);
assert_eq!(r, 0);
}
#[test]
fn findnode_returns_index_of_match() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let list = vec!["a".to_string(), "b".to_string(), "c".to_string()];
assert_eq!(findnode(&list, &"b".to_string()), Some(1));
assert_eq!(findnode(&list, &"z".to_string()), None);
}
#[test]
fn cc_assign_rejects_conflicting_special_targets() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
CCLIST.with(|c| c.set(COMP_COMMAND | COMP_DEFAULT));
cc_assign("compctl", Arc::new(Compctl::default()), true);
let g = COMPCTL_TAB.read().unwrap();
assert!(!g.as_ref().unwrap().contains_key("__cc_compos"));
assert!(!g.as_ref().unwrap().contains_key("__cc_default"));
drop(g);
CCLIST.with(|c| c.set(0));
}
#[test]
fn compctl_process_cc_remove_deletes_named_entries() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
cc_assign("foo", Arc::new(Compctl::default()), false);
cc_assign("bar", Arc::new(Compctl::default()), false);
CCLIST.with(|c| c.set(COMP_REMOVE));
compctl_process_cc(&["foo".to_string()], Arc::new(Compctl::default()));
let g = COMPCTL_TAB.read().unwrap();
let map = g.as_ref().unwrap();
assert!(!map.contains_key("foo"));
assert!(map.contains_key("bar"));
CCLIST.with(|c| c.set(0));
}
#[test]
fn sep_comp_string_returns_zero_or_one() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let r = sep_comp_string("", "", 0);
assert!(r == 0 || r == 1, "expected 0 or 1, got {}", r);
}
#[test]
fn sep_comp_string_round_trips_zle_state() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createcompctltable();
*CC_DEFAULT.lock().unwrap_or_else(|e| e.into_inner()) = None;
*CC_FIRST.lock().unwrap_or_else(|e| e.into_inner()) = None;
*CC_COMPOS.lock().unwrap_or_else(|e| e.into_inner()) = None;
use crate::ported::zle::compcore::{WB, WE, ZLEMETACS as CS_G, ZLEMETALINE as LINE_G};
use std::sync::atomic::Ordering;
WE.store(42, Ordering::Relaxed);
WB.store(7, Ordering::Relaxed);
CS_G.store(11, Ordering::Relaxed);
*ZLEMETALL.lock().unwrap() = 99;
*INSTRING.lock().unwrap() = QT_DOUBLE;
*INBACKT.lock().unwrap() = 1;
*NOALIASES.lock().unwrap() = 1;
*crate::ported::utils::noerrs_lock().lock().unwrap() = 0;
*LINE_G
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = "hello".to_string();
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap() = "Q".to_string();
let _ = sep_comp_string("", "x", 0);
assert_eq!(WE.load(Ordering::Relaxed), 42);
assert_eq!(WB.load(Ordering::Relaxed), 7);
assert_eq!(CS_G.load(Ordering::Relaxed), 11);
assert_eq!(*ZLEMETALL.lock().unwrap(), 99);
assert_eq!(*INSTRING.lock().unwrap(), QT_DOUBLE);
assert_eq!(*INBACKT.lock().unwrap(), 1);
assert_eq!(*NOALIASES.lock().unwrap(), 1);
assert_eq!(*crate::ported::utils::noerrs_lock().lock().unwrap(), 0);
assert_eq!(
*LINE_G
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap(),
"hello"
);
assert_eq!(
*AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
.unwrap(),
"Q"
);
}
#[test]
fn inull_recognises_marker_chars() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(inull(Snull));
assert!(inull(Dnull));
assert!(inull(Bnull));
assert!(inull(Stringg));
assert!(inull(QSTRING_TOK));
assert!(!inull('a'));
assert!(!inull(' '));
}
#[test]
fn qt_constants_match_c_zsh_h() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(QT_NONE, 0);
assert_eq!(QT_BACKSLASH, 1);
assert_eq!(QT_SINGLE, 2);
assert_eq!(QT_DOUBLE, 3);
assert_eq!(QT_DOLLARS, 4);
assert_eq!(QT_BACKTICK, 5);
}
#[test]
fn compctl_corpus_inull_recognises_null_tokens() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(inull(Snull));
assert!(inull(Dnull));
assert!(inull(Bnull));
assert!(inull(Stringg));
assert!(inull(QSTRING_TOK));
}
#[test]
fn compctl_corpus_inull_rejects_printables() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
for c in ['a', 'Z', '0', ' ', '!', '~'] {
assert!(!inull(c), "{c:?} should NOT be inull");
}
}
#[test]
fn compctl_corpus_qt_pairwise_distinct() {
let qs = [
QT_NONE,
QT_BACKSLASH,
QT_SINGLE,
QT_DOUBLE,
QT_DOLLARS,
QT_BACKTICK,
];
for (i, a) in qs.iter().enumerate() {
for b in &qs[i + 1..] {
assert_ne!(a, b);
}
}
}
#[test]
fn compctl_corpus_qt_all_within_range() {
for q in [
QT_NONE,
QT_BACKSLASH,
QT_SINGLE,
QT_DOUBLE,
QT_DOLLARS,
QT_BACKTICK,
] {
assert!((0..=5).contains(&q), "QT_* value {q} out of [0,5]");
}
}
#[test]
fn createcompctltable_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
createcompctltable();
}
}
#[test]
fn freecompctlp_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
freecompctlp("");
}
#[test]
fn freecompctlp_unknown_name_no_panic() {
let _g = crate::test_util::global_state_lock();
freecompctlp("__never_a_real_compctl_xyz__");
}
#[test]
fn compctl_name_pat_returns_tuple_type() {
let _: (bool, String) = compctl_name_pat("");
}
#[test]
fn compctl_name_pat_is_pure() {
for s in ["", "name", "*pat*", "with[brackets]"] {
let first = compctl_name_pat(s);
for _ in 0..3 {
assert_eq!(
compctl_name_pat(s),
first,
"compctl_name_pat({:?}) must be pure",
s
);
}
}
}
#[test]
fn delpatcomp_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
delpatcomp("");
}
#[test]
fn cccleanuphookfn_returns_i32_type() {
let _: i32 = cccleanuphookfn(std::ptr::null_mut(), std::ptr::null_mut());
}
#[test]
fn cccleanuphookfn_idempotent() {
for _ in 0..5 {
let _ = cccleanuphookfn(std::ptr::null_mut(), std::ptr::null_mut());
}
}
#[test]
fn set_gmatcher_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = set_gmatcher("", &[]);
}
#[test]
fn get_gmatcher_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = get_gmatcher("", &[]);
}
#[test]
fn print_gmatcher_safe_for_zero() {
let _g = crate::test_util::global_state_lock();
print_gmatcher(0);
}
#[test]
fn maketildelist_safe_no_panic() {
let _g = crate::test_util::global_state_lock();
maketildelist();
}
#[test]
fn maketildelist_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
maketildelist();
}
}
#[test]
fn set_gmatcher_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = set_gmatcher("test", &[]);
for _ in 0..3 {
assert_eq!(
set_gmatcher("test", &[]),
first,
"set_gmatcher must be deterministic"
);
}
}
#[test]
fn get_gmatcher_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = get_gmatcher("test", &[]);
for _ in 0..3 {
assert_eq!(
get_gmatcher("test", &[]),
first,
"get_gmatcher must be deterministic"
);
}
}
#[test]
fn bin_compctl_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let _: i32 = bin_compctl("compctl", &[], &ops, 0);
}
#[test]
fn bin_compcall_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let _: i32 = bin_compcall("compcall", &[], &ops, 0);
}
#[test]
fn cccleanuphookfn_is_deterministic() {
let first = cccleanuphookfn(std::ptr::null_mut(), std::ptr::null_mut());
for _ in 0..3 {
assert_eq!(
cccleanuphookfn(std::ptr::null_mut(), std::ptr::null_mut()),
first,
"cccleanuphookfn must be deterministic"
);
}
}
#[test]
fn print_gmatcher_full_index_range_safe() {
let _g = crate::test_util::global_state_lock();
for ac in [-1i32, 0, 1, 10, 100, i32::MAX, i32::MIN] {
print_gmatcher(ac);
}
}
#[test]
fn compctl_name_pat_empty_returns_false_empty() {
let (is_pat, name) = compctl_name_pat("");
assert!(!is_pat, "empty input not a pattern");
assert_eq!(name, "", "empty input → empty name");
}
#[test]
fn createcompctltable_idempotent_10_call_alt() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
createcompctltable();
}
}
#[test]
fn freecompctlp_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
freecompctlp("");
}
#[test]
fn freecompctlp_unknown_name_no_panic_alt() {
let _g = crate::test_util::global_state_lock();
freecompctlp("__never_real_compctl_xyz__");
}
#[test]
fn set_gmatcher_returns_i32_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: i32 = set_gmatcher("test", &[]);
}
#[test]
fn get_gmatcher_returns_i32_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: i32 = get_gmatcher("test", &[]);
}
#[test]
fn compctl_name_pat_returns_bool_string_tuple_type() {
let _: (bool, String) = compctl_name_pat("");
}
#[test]
fn compctl_name_pat_deterministic() {
for s in ["", "abc", "*", "(pattern)", "name"] {
let a = compctl_name_pat(s);
let b = compctl_name_pat(s);
assert_eq!(a, b, "compctl_name_pat({:?}) must be pure", s);
}
}
#[test]
fn delpatcomp_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
delpatcomp("");
}
#[test]
fn delpatcomp_unknown_no_panic() {
let _g = crate::test_util::global_state_lock();
delpatcomp("__never_real_pattern_xyz__");
}
#[test]
fn bin_compctl_exit_code_non_negative() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compctl("compctl", &[], &ops, 0);
assert!(r >= 0, "bin_compctl exit code must be ≥ 0, got {}", r);
}
#[test]
fn bin_compcall_exit_code_non_negative() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_compcall("compcall", &[], &ops, 0);
assert!(r >= 0, "bin_compcall exit code must be ≥ 0, got {}", r);
}
}