#![allow(non_snake_case, non_upper_case_globals, dead_code)]
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::DPUTS;
use crate::ported::context::zcontext_restore_partial;
use crate::ported::module::{gethookdef, runhookdef};
use crate::ported::params::{getsparam, paramtab, paramtab_hashed_storage, setaparam, setsparam};
use crate::ported::signals::{queue_signals, unqueue_signals};
use crate::ported::zle::comp_h::{
Aminfo, Brinfo, Cadata, Ccmakedat, Cexpl, Cline, Cmatch, Cmgroup, Cmlist, Menuinfo, CAF_ALL,
CAF_MATCH, CAF_MATSORT, CAF_NOSORT, CAF_NUMSORT, CAF_QUOTE, CAF_REVSORT, CAF_UNIQALL,
CAF_UNIQCON, CGF_MATSORT, CGF_NOSORT, CGF_NUMSORT, CGF_REVSORT, CGF_UNIQALL, CGF_UNIQCON,
CMF_DELETE, CMF_DISPLINE, CMF_FMULT, CMF_MULT, CMF_NOLIST, CMF_PACKED, CMF_PARBR, CMF_PARNEST,
CMF_ROWS,
};
use crate::ported::zle::complete::{COMPIPREFIX, COMPLIST, COMPPREFIX, COMPQSTACK, COMPSUFFIX, INCOMPFUNC};
use crate::ported::zle::compmatch::{bld_parts, cline_matched};
use crate::ported::zle::compresult::{do_ambig_menu, ztat};
use crate::ported::zle::zle_h::{invalidatelist, COMP_LIST_COMPLETE, COMP_LIST_EXPAND, CUT_RAW};
use crate::ported::zle::zle_refresh::{CLEARLIST, SHOWINGLIST};
use crate::ported::zle::zle_tricky::{inststr, MENUCMP, ORIGCS, ORIGLINE, USEGLOB, USEMENU, VALIDLIST, WOULDINSTAB};
use crate::ported::zle::zle_utils::foredel;
use crate::ported::zsh_h::{isset, Bnull, Dnull, Equals, Hat, Inbrace, Inbrack, Inpar, Outbrace, Outpar, Pound, Qstring, Quest, Snull, Star, Stringg, Tilde, BASHAUTOLIST, NUMERICGLOBSORT, PM_HASHED, PM_TYPE, QT_BACKSLASH, QT_DOLLARS, QT_DOUBLE, QT_NONE, QT_SINGLE, RCQUOTES, SORTIT_IGNORING_BACKSLASHES, SORTIT_NUMERICALLY, ZCONTEXT_HIST, ZCONTEXT_LEX, ZCONTEXT_PARSE};
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_h::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
zle_params::*, zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
};
pub fn do_completion(s: &str, incmd: i32, lst: i32) -> i32 {
let osl = SHOWINGLIST.load(Ordering::Relaxed); let mut ret: i32 = 0;
if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
*g = None;
}
if let Ok(mut g) = fainfo.get_or_init(|| Mutex::new(None)).lock() {
*g = None;
}
if let Ok(mut g) = matchers.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear(); }
let instring = INSTRING.load(Ordering::Relaxed); let head_q: char = if instring == QT_NONE {
QT_BACKSLASH as u8 as char
} else {
instring as u8 as char
};
if let Ok(mut g) = compqstack.get_or_init(|| Mutex::new(String::new())).lock() {
*g = head_q.to_string(); }
hasunqu.store(0, Ordering::Relaxed); let wouldinstab_v = WOULDINSTAB.load(Ordering::Relaxed); useline.store(
if wouldinstab_v != 0 {
-1
} else if lst != COMP_LIST_COMPLETE {
1
} else {
0
},
Ordering::Relaxed,
);
useexact.store(opt_isset("RECEXACT"), Ordering::Relaxed); set_compstate_str("exact_string", ""); let useline_v = useline.load(Ordering::Relaxed);
uselist.store(
if useline_v != 0 {
if opt_isset("AUTOLIST") != 0 && opt_isset("BASHAUTOLIST") == 0 {
if opt_isset("LISTAMBIGUOUS") != 0 {
3
} else {
2
}
} else {
0
}
} else {
1
},
Ordering::Relaxed,
);
let useglob_v = USEGLOB.load(Ordering::Relaxed); let opm: String = if useglob_v != 0 {
"*".into()
} else {
"".into()
};
if let Ok(mut g) = comppatmatch.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(opm.clone()); }
set_compstate_str("pattern_insert", "menu"); forcelist.store(0, Ordering::Relaxed); haspattern.store(0, Ordering::Relaxed); let _complistmax = env_iparam("LISTMAX");
set_compstate_str(
"last_prompt",
if opt_isset("ALWAYSLASTPROMPT") != 0 {
"yes"
} else {
""
},
);
dolastprompt.store(1, Ordering::Relaxed);
let cl_str = if opt_isset("LISTROWSFIRST") != 0 {
if opt_isset("LISTPACKED") != 0 {
"packed rows"
} else {
"rows"
}
} else if opt_isset("LISTPACKED") != 0 {
"packed"
} else {
""
};
if let Ok(mut g) = COMPLIST
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = cl_str.into(); }
startauto.store(opt_isset("AUTOMENU"), Ordering::Relaxed);
let zlc = ZLEMETACS.load(Ordering::Relaxed);
let we_v = WE.load(Ordering::Relaxed);
movetoend.store(
if zlc == we_v || opt_isset("ALWAYSTOEND") != 0 {
2
} else {
1
},
Ordering::Relaxed,
);
SHOWINGLIST.store(0, Ordering::Relaxed); hasmatched.store(0, Ordering::Relaxed); hasunmatched.store(0, Ordering::Relaxed); minmlen.store(1_000_000, Ordering::Relaxed); maxmlen.store(-1, Ordering::Relaxed); nmessages.store(0, Ordering::Relaxed); hasallmatch.store(0, Ordering::Relaxed);
if makecomplist(s, incmd, lst) != 0 {
ZLEMETACS.store(0, Ordering::Relaxed); foredel(
ZLEMETALL.load(Ordering::Relaxed),
CUT_RAW,
); let _ = inststr(
&ORIGLINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default(),
); ZLEMETACS.store(
ORIGCS.load(Ordering::Relaxed),
Ordering::Relaxed,
); CLEARLIST.store(1, Ordering::Relaxed); ret = 1;
if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = None;
} if useline.load(Ordering::Relaxed) < 0 {
unmetafy_line();
ret = selfinsert(); metafy_line();
}
return goto_compend(ret); }
lastprebr_set(""); lastpostbr_set("");
let curpm = comppatmatch
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone())
.unwrap_or_default();
if !curpm.is_empty() && curpm != opm {
haspattern.store(1, Ordering::Relaxed); }
let nm = nmatches.load(Ordering::Relaxed); let dm = diffmatches.load(Ordering::Relaxed);
if iforcemenu.load(Ordering::Relaxed) != 0 {
if nm != 0 {
{
let _ = do_ambig_menu();
};
} ret = if nm == 0 { 1 } else { 0 }; } else if useline.load(Ordering::Relaxed) < 0 {
unmetafy_line();
ret = selfinsert(); metafy_line();
} else if useline.load(Ordering::Relaxed) == 0 && uselist.load(Ordering::Relaxed) != 0 {
ZLEMETACS.store(0, Ordering::Relaxed); foredel(
ZLEMETALL.load(Ordering::Relaxed),
CUT_RAW,
); let _ = inststr(
&ORIGLINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default(),
); ZLEMETACS.store(
ORIGCS.load(Ordering::Relaxed),
Ordering::Relaxed,
); SHOWINGLIST.store(-2, Ordering::Relaxed);
} else if useline.load(Ordering::Relaxed) == 2 && nm > 1 {
{
let groups = amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let mut all: Vec<String> = Vec::new();
for g in groups {
for m in g.matches {
if let Some(s) = m.str {
all.push(s);
}
}
}
let buf = ZLEMETALINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let cs = ZLEMETACS.load(Ordering::Relaxed) as usize;
let wb = WB.load(Ordering::Relaxed) as usize;
let we = WE.load(Ordering::Relaxed) as usize;
let (new_buf, new_cs) =
crate::ported::zle::compresult::do_allmatches(&buf, cs, wb, we, &all, " ");
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
*g = new_buf;
ZLEMETALL.store(g.len() as i32, Ordering::Relaxed);
}
ZLEMETACS.store(new_cs as i32, Ordering::Relaxed);
}
if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = None;
} if forcelist.load(Ordering::Relaxed) != 0 {
SHOWINGLIST.store(-2, Ordering::Relaxed);
} else {
invalidatelist(); }
} else if useline.load(Ordering::Relaxed) != 0 {
if nm > 1 && dm != 0 {
ret = {
let groups = amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let all: Vec<String> = groups
.into_iter()
.flat_map(|g| g.matches.into_iter().filter_map(|m| m.str))
.collect();
crate::ported::zle::compresult::do_ambiguous(&all)
};
if SHOWINGLIST.load(Ordering::Relaxed) == 0
&& uselist.load(Ordering::Relaxed) != 0
&& LISTSHOWN.load(Ordering::Relaxed) != 0
&& (USEMENU.load(Ordering::Relaxed) == 2
|| oldlist.load(Ordering::Relaxed) != 0)
{
SHOWINGLIST.store(osl, Ordering::Relaxed);
}
} else if nm == 1 || (nm > 1 && dm == 0) {
do_single_first_match(); if forcelist.load(Ordering::Relaxed) != 0 {
if uselist.load(Ordering::Relaxed) != 0 {
SHOWINGLIST.store(-2, Ordering::Relaxed);
} else {
CLEARLIST.store(1, Ordering::Relaxed);
}
} else {
invalidatelist(); }
} else if nmessages.load(Ordering::Relaxed) != 0 && forcelist.load(Ordering::Relaxed) != 0 {
if uselist.load(Ordering::Relaxed) != 0 {
SHOWINGLIST.store(-2, Ordering::Relaxed);
} else {
CLEARLIST.store(1, Ordering::Relaxed);
}
}
} else {
invalidatelist(); LASTAMBIG.store(
opt_isset("BASHAUTOLIST"),
Ordering::Relaxed,
);
if forcelist.load(Ordering::Relaxed) != 0 {
CLEARLIST.store(1, Ordering::Relaxed);
} ZLEMETACS.store(0, Ordering::Relaxed); foredel(
ZLEMETALL.load(Ordering::Relaxed),
CUT_RAW,
); let _ = inststr(
&ORIGLINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default(),
); ZLEMETACS.store(
ORIGCS.load(Ordering::Relaxed),
Ordering::Relaxed,
); }
if SHOWINGLIST.load(Ordering::Relaxed) == 0
&& VALIDLIST.load(Ordering::Relaxed) != 0
&& USEMENU.load(Ordering::Relaxed) != 2
&& uselist.load(Ordering::Relaxed) != 0
&& (nm != 1 || dm != 0)
&& useline.load(Ordering::Relaxed) >= 0
&& useline.load(Ordering::Relaxed) != 2
&& (oldlist.load(Ordering::Relaxed) == 0
|| LISTSHOWN.load(Ordering::Relaxed) == 0)
{
onlyexpl.store(3, Ordering::Relaxed); SHOWINGLIST.store(-2, Ordering::Relaxed);
}
goto_compend(ret)
}
pub fn before_complete(lst: &mut i32) -> i32 {
OLDMENUCMP.store(MENUCMP.load(Ordering::Relaxed), Ordering::Relaxed);
if SHOWAGAIN.load(Ordering::Relaxed) != 0 && VALIDLIST.load(Ordering::Relaxed) != 0 {
SHOWINGLIST.store(-2, Ordering::Relaxed);
}
SHOWAGAIN.store(0, Ordering::Relaxed);
let has_cur = MINFO
.get()
.and_then(|m| m.lock().ok())
.map(|m| m.cur.is_some())
.unwrap_or(false);
let menucmp_v = MENUCMP.load(Ordering::Relaxed);
if has_cur && menucmp_v != 0 && *lst != COMP_LIST_EXPAND {
return 1; }
if has_cur
&& menucmp_v != 0
&& VALIDLIST.load(Ordering::Relaxed) != 0
&& *lst == COMP_LIST_COMPLETE
{
SHOWINGLIST.store(-2, Ordering::Relaxed);
onlyexpl.store(0, Ordering::Relaxed); if let Some(ld) = listdat.get() {
if let Ok(mut g) = ld.lock() {
g.valid = 0;
}
}
return 1; }
if (fromcomp.load(Ordering::Relaxed) & crate::ported::zle::comp_h::FC_INWORD) != 0 {
let le = lastend.load(Ordering::Relaxed);
let ll = ZLEMETALL.load(Ordering::Relaxed);
let new_cs = if le > ll { ll } else { le };
ZLEMETACS.store(new_cs, Ordering::Relaxed);
}
if startauto.load(Ordering::Relaxed) != 0 && LASTAMBIG.load(Ordering::Relaxed) != 0 {
let bashauto = isset(BASHAUTOLIST);
let last = LASTAMBIG.load(Ordering::Relaxed);
if !bashauto || last == 2 {
USEMENU.store(2, Ordering::Relaxed);
}
}
0 }
pub fn after_complete(dat: &mut [i32]) -> i32 {
let menucmp_v = MENUCMP.load(Ordering::Relaxed);
let oldmenucmp_v = OLDMENUCMP.load(Ordering::Relaxed);
if menucmp_v == 0 || oldmenucmp_v != 0 {
return 0; }
let mut ret: i32 = 0;
let h_menu_start = gethookdef("menu_start");
if !h_menu_start.is_null() {
ret = runhookdef(h_menu_start, std::ptr::null_mut());
}
if ret == 0 {
return 0; }
if dat.len() > 1 {
dat[1] = 0;
}
MENUCMP.store(0, Ordering::Relaxed);
menuacc.store(0, Ordering::Relaxed);
if let Some(m) = MINFO.get() {
if let Ok(mut mi) = m.lock() {
mi.cur = None;
}
}
if ret >= 2 {
fixsuffix();
ZLEMETACS.store(0, Ordering::Relaxed);
let metall = ZLEMETALL.load(Ordering::Relaxed);
foredel(metall, CUT_RAW);
let origline_v: String = ORIGLINE
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let _ = inststr(&origline_v);
let origcs_v = ORIGCS.load(Ordering::Relaxed);
ZLEMETACS.store(origcs_v, Ordering::Relaxed);
if ret == 2 {
CLEARLIST.store(1, Ordering::Relaxed);
invalidatelist();
}
}
0 }
pub fn callcompfunc(s: &str, fn_name: &str) {
if fn_name.is_empty() {
return;
} let _lv = crate::ported::builtin::LASTVAL.load(Ordering::Relaxed); let _icf = INCOMPFUNC.load(Ordering::Relaxed); let _osc = crate::ported::builtin::SFCONTEXT.load(Ordering::Relaxed);
let _useglob = USEGLOB.load(Ordering::Relaxed);
let context = compcontext_for(s); set_compstate_str("context", &context);
set_compstate_str(
"last_prompt",
if dolastprompt.load(Ordering::Relaxed) != 0 {
"yes"
} else {
""
},
);
let cl_value = COMPLIST
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
set_compstate_str("list", &cl_value);
let ul = useline.load(Ordering::Relaxed);
let um = USEMENU.load(Ordering::Relaxed);
let ins = if ul != 0 {
match um {
0 => "unambiguous",
1 => "menu",
2 => "automenu",
_ => "",
}
} else {
""
};
set_compstate_str("insert", ins);
set_compstate_str(
"exact",
if useexact.load(Ordering::Relaxed) != 0 {
"accept"
} else {
""
},
);
set_compstate_str(
"to_end",
if movetoend.load(Ordering::Relaxed) == 1 {
"single"
} else {
"match"
},
);
INCOMPFUNC.store(1, Ordering::Relaxed);
let _ = shfunc_call(fn_name);
let post_insert = getsparam("compstate[insert]").unwrap_or_default();
if !post_insert.is_empty() {
if post_insert.contains("automenu") {
USEMENU.store(2, Ordering::Relaxed);
} else if post_insert.contains("menu") {
USEMENU.store(1, Ordering::Relaxed);
}
}
INCOMPFUNC.store(_icf, Ordering::Relaxed);
}
pub fn makecomplist(s: &str, incmd: i32, lst: i32) -> i32 {
let owb = WB.load(Ordering::Relaxed); let owe = WE.load(Ordering::Relaxed);
let ooffs = OFFS.load(Ordering::Relaxed);
let mut s_owned = s.to_string();
if compfunc_active() {
if let Some(p) = check_param(&s_owned, false, false) {
s_owned = s_owned[p..].to_string(); PARWB.store(owb, Ordering::Relaxed); PARWE.store(owe, Ordering::Relaxed); PAROFFS.store(ooffs, Ordering::Relaxed); } else {
PARWB.store(-1, Ordering::Relaxed); }
} else {
PARWB.store(-1, Ordering::Relaxed); }
linwhat.store(INWHAT.load(Ordering::Relaxed), Ordering::Relaxed);
if compfunc_active() {
let os = s_owned.clone(); let onm = nmatches.load(Ordering::Relaxed); let odm = diffmatches.load(Ordering::Relaxed); let osi = movefd(0);
if let Ok(mut g) = bmatchers.get_or_init(|| Mutex::new(None)).lock() {
*g = None;
}
if let Ok(mut g) = mstack.get_or_init(|| Mutex::new(None)).lock() {
*g = None;
}
if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(Aminfo::default());
}
if let Ok(mut g) = fainfo.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(Aminfo::default());
}
if let Ok(mut g) = freecl.get_or_init(|| Mutex::new(None)).lock() {
*g = None; }
if VALIDLIST.load(Ordering::Relaxed) == 0 {
LASTAMBIG.store(0, Ordering::Relaxed);
}
if let Ok(mut g) = amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear(); }
mnum.store(0, Ordering::Relaxed); unambig_mnum.store(-1, Ordering::Relaxed); if let Ok(mut g) = isuf.get_or_init(|| Mutex::new(String::new())).lock() {
g.clear(); }
insmnum.store(ZMULT.load(Ordering::Relaxed), Ordering::Relaxed); oldlist.store(0, Ordering::Relaxed); oldins.store(0, Ordering::Relaxed); begcmgroup(Some("default"), 0); MENUCMP.store(0, Ordering::Relaxed); menuacc.store(0, Ordering::Relaxed); newmatches.store(0, Ordering::Relaxed); onlyexpl.store(0, Ordering::Relaxed);
let dup_s = crate::ported::mem::dupstring(&os); let cf_name = compfunc
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone())
.unwrap_or_default();
callcompfunc(&dup_s, &cf_name); endcmgroup(None);
runhookdef_compcore("COMPCTLCLEANUPHOOK");
if oldlist.load(Ordering::Relaxed) != 0 {
nmatches.store(onm, Ordering::Relaxed); diffmatches.store(odm, Ordering::Relaxed); VALIDLIST.store(1, Ordering::Relaxed); if let Ok(mut g) = amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
if let Ok(last) = lastmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = last.clone(); }
}
if let Ok(mut g) = lmatches.get_or_init(|| Mutex::new(None)).lock() {
let last_l = lastlmatches
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone());
*g = last_l; }
let drained = pmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|mut g| std::mem::take(&mut *g))
.unwrap_or_default();
freematches(drained, 1); hasperm.store(0, Ordering::Relaxed); redup(osi); return 0; }
if !lastmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|g| g.is_empty())
.unwrap_or(true)
{
if let Ok(mut g) = lastmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear(); }
}
permmatches(1); let p_snap = pmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
if let Ok(mut g) = amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = p_snap.clone(); }
lastpermmnum.store(permmnum.load(Ordering::Relaxed), Ordering::Relaxed); lastpermgnum.store(permgnum.load(Ordering::Relaxed), Ordering::Relaxed); if let Ok(mut g) = lastmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = p_snap; }
let lm_snap = lmatches
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone());
if let Ok(mut g) = lastlmatches.get_or_init(|| Mutex::new(None)).lock() {
*g = lm_snap; }
if let Ok(mut g) = pmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear(); }
hasperm.store(0, Ordering::Relaxed); hasoldlist.store(1, Ordering::Relaxed);
let any_nm =
nmatches.load(Ordering::Relaxed) != 0 || nmessages.load(Ordering::Relaxed) != 0;
let errset = errflag_get();
if any_nm && !errset {
VALIDLIST.store(1, Ordering::Relaxed); redup(osi); return 0; }
redup(osi); return 1; } else {
let mut dat = Ccmakedat {
str: Some(s_owned.clone()), incmd, lst, };
runhookdef_compctlmake(&mut dat); runhookdef_compcore("COMPCTLCLEANUPHOOK"); return dat.lst; }
}
pub fn multiquote(s: &str, ign: i32) -> String {
let stack = COMPQSTACK .get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let p_bytes = stack.as_bytes();
if !p_bytes.is_empty() && (ign == 0 || p_bytes.len() > 1) {
let start = if ign != 0 { 1 } else { 0 }; let mut cur = s.to_string();
for &q in &p_bytes[start..] {
let qt = match q as i32 {
x if x == QT_BACKSLASH => QT_BACKSLASH,
x if x == QT_SINGLE => QT_SINGLE,
x if x == QT_DOUBLE => QT_DOUBLE,
x if x == QT_DOLLARS => QT_DOLLARS,
_ => QT_BACKSLASH,
};
cur = crate::ported::utils::quotestring(&cur, qt);
}
cur } else {
s.to_string() }
}
pub fn tildequote(s: &str, ign: i32) -> String {
let bytes = s.as_bytes(); let tilde = !bytes.is_empty() && bytes[0] == b'~'; let staged = if tilde {
let mut tmp = String::with_capacity(s.len());
tmp.push('x');
tmp.push_str(&s[1..]);
tmp
} else {
s.to_string()
};
let mut quoted = multiquote(&staged, ign); if tilde && !quoted.is_empty() {
let mut new_q = String::with_capacity(quoted.len());
let mut swapped = false;
for c in quoted.chars() {
if !swapped && c == 'x' {
new_q.push('~');
swapped = true;
} else {
new_q.push(c);
}
}
quoted = new_q;
}
quoted }
pub fn check_param(s: &str, set: bool, test: bool) -> Option<usize> {
if let Ok(mut g) = parpre.get_or_init(|| Mutex::new(String::new())).lock() {
g.clear();
}
if !test {
ispar.store(0, Ordering::Relaxed); parq.store(0, Ordering::Relaxed); eparq.store(0, Ordering::Relaxed); }
let bytes = s.as_bytes(); let offs_v = OFFS.load(Ordering::Relaxed) as usize;
let mut found = false; let mut qstring = false; let mut p: usize = offs_v.min(bytes.len().saturating_sub(1));
loop {
if p < bytes.len() {
let ch = char_at(bytes, p);
if ch == Stringg || ch == Qstring {
let next = char_at(bytes, p + ch.len_utf8());
let snull_next = ch == Stringg && next == Snull; let qstr_quot = ch == Qstring && next == '\''; if p < offs_v && !snull_next && !qstr_quot {
found = true; qstring = ch == Qstring; break;
}
}
}
if p == 0 {
break;
} p = prev_char_index(bytes, p);
}
if found {
while p > 0 {
let prev = prev_char_index(bytes, p);
let pc = char_at(bytes, prev);
if pc == Stringg || pc == Qstring {
p = prev;
} else {
break;
}
}
loop {
let n1 = p + char_at(bytes, p).len_utf8();
if n1 >= bytes.len() {
break;
}
let c1 = char_at(bytes, n1);
let n2 = n1 + c1.len_utf8();
if n2 >= bytes.len() {
break;
}
let c2 = char_at(bytes, n2);
if (c1 == Stringg || c1 == Qstring) && (c2 == Stringg || c2 == Qstring) {
p = n2;
} else {
break;
}
}
}
let next_char = if p + 1 <= bytes.len() {
let dollar_len = char_at(bytes, p).len_utf8();
char_at(bytes, p + dollar_len)
} else {
'\0'
};
if !(found && next_char != Inpar && next_char != Inbrack && next_char != Snull) {
return None; }
let dollar_len = char_at(bytes, p).len_utf8();
let mut b: usize = p + dollar_len; let mut br: i32 = 1; let mut nest: i32 = 0;
if char_at(bytes, b) == Inbrace {
let close = skip_token_parens(bytes, b, Inbrace, Outbrace);
if let Some(end) = close {
if end <= s.len() && offs_v >= end - bytes.iter().take(end).count() {
return None; }
} else {
return None;
}
b += Inbrace.len_utf8(); br += 1;
let (open_p, close_p) = if qstring { ('(', ')') } else { (Inpar, Outpar) };
let after_flags = skip_token_parens(bytes, b, open_p, close_p);
if let Some(end) = after_flags {
if end > offs_v + 1 {
ispar.store(2, Ordering::Relaxed); return None; }
b = end;
}
let mut tb = p;
while tb > 0 {
let prev = prev_char_index(bytes, tb);
let pc = char_at(bytes, prev);
if pc == Outbrace || pc == Inbrace {
tb = prev;
break;
}
tb = prev;
}
if tb > 0 {
let cc = char_at(bytes, tb);
let prev = prev_char_index(bytes, tb);
let pp = char_at(bytes, prev);
if cc == Inbrace && (pp == Stringg || cc == Qstring) {
nest = 1; }
}
}
while b < bytes.len() {
let c = char_at(bytes, b);
if c == '^' || c == Hat || c == '=' || c == Equals || c == '~' || c == Tilde {
b += c.len_utf8();
} else {
break;
}
}
if b < bytes.len() {
let c = char_at(bytes, b);
if c == '#' || c == Pound || c == '+' {
b += c.len_utf8();
}
}
let mut e: usize = b; if br != 0 {
let qopen = if test { Dnull } else { '"' };
while e < bytes.len() && char_at(bytes, e) == qopen {
e += qopen.len_utf8();
parq.fetch_add(1, Ordering::Relaxed); }
if !test {
b = e;
} }
if e < bytes.len() {
let c = char_at(bytes, e);
let one_char_name = matches!(c,
ch if ch == Quest || ch == Star || ch == Stringg || ch == Qstring
|| ch == '?' || ch == '*' || ch == '$' || ch == '-' || ch == '!' || ch == '@');
if one_char_name {
e += c.len_utf8();
} else if c.is_ascii_digit() {
while e < bytes.len() && char_at(bytes, e).is_ascii_digit() {
e += 1;
}
} else {
let walked = walk_namespace(&bytes[e..]);
if walked > 0 {
e += walked;
} else if c == '.' {
e += 1;
}
}
}
if offs_v <= e && offs_v >= b {
if br != 0 {
let qopen = if test { Dnull } else { '"' };
let mut pq = e;
while pq < bytes.len() && char_at(bytes, pq) == qopen {
pq += qopen.len_utf8();
parq.fetch_sub(1, Ordering::Relaxed);
eparq.fetch_add(1, Ordering::Relaxed);
}
}
if test {
return Some(b); }
if set {
if br >= 2 {
mflags.fetch_or(CMF_PARBR, Ordering::Relaxed); if nest != 0 {
mflags.fetch_or(CMF_PARNEST, Ordering::Relaxed); }
}
let mut tail = String::from_utf8_lossy(&bytes[e..]).into_owned();
tail = strip_tokens(&tail); if let Ok(mut g) = isuf.get_or_init(|| Mutex::new(String::new())).lock() {
*g = tail;
}
let head = String::from_utf8_lossy(&bytes[..b]).into_owned();
if let Ok(mut g) = ripre.get_or_init(|| Mutex::new(String::new())).lock() {
*g = format!("{}{}", *g, head);
}
if let Ok(mut g) = ipre.get_or_init(|| Mutex::new(String::new())).lock() {
*g = strip_tokens(&format!("{}{}", *g, head));
}
}
let cf_active = compfunc
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone())
.map(|s| !s.is_empty())
.unwrap_or(false);
if cf_active {
let pf = if br >= 2 {
CMF_PARBR | (if nest != 0 { CMF_PARNEST } else { 0 })
} else {
0
};
parflags.store(pf, Ordering::Relaxed); let head = String::from_utf8_lossy(&bytes[..b]).into_owned();
if let Ok(mut g) = parpre.get_or_init(|| Mutex::new(String::new())).lock() {
*g = strip_tokens(&head); }
}
let off_delta = b as i32;
OFFS.fetch_sub(off_delta, Ordering::Relaxed); let new_offs = OFFS.load(Ordering::Relaxed);
let zlc = ZLEMETACS.load(Ordering::Relaxed);
WB.store(zlc - new_offs, Ordering::Relaxed); WE.store(
WB.load(Ordering::Relaxed) + (e - b) as i32,
Ordering::Relaxed,
); ispar.store(if br >= 2 { 2 } else { 1 }, Ordering::Relaxed); return Some(b); } else if offs_v > e && e < bytes.len() && char_at(bytes, e) == ':' {
let offsptr = offs_v;
let mut e2 = e;
while e2 < offsptr && e2 < bytes.len() {
let c = char_at(bytes, e2);
if c != ':' && !c.is_alphanumeric() {
break;
}
e2 += c.len_utf8();
}
ispar.store(if br >= 2 { 2 } else { 1 }, Ordering::Relaxed); return None; }
let _ = (Bnull,); None }
pub fn rembslash(s: &str) -> String {
let mut result = String::with_capacity(s.len()); let mut chars = s.chars().peekable(); while let Some(c) = chars.next() {
if c == '\\' {
if let Some(nxt) = chars.next() {
result.push(nxt);
}
} else {
result.push(c); }
}
result }
pub fn remsquote(s: &mut String) -> i32 {
let rcquotes = isset(RCQUOTES); let qa: usize = if rcquotes { 1 } else { 3 };
let bytes = s.as_bytes(); let mut t = Vec::<u8>::with_capacity(bytes.len());
let mut ret: i32 = 0;
let mut i = 0;
while i < bytes.len() {
let matched = if qa == 1 {
i + 1 < bytes.len() && bytes[i] == b'\'' && bytes[i + 1] == b'\''
} else {
i + 3 < bytes.len() && bytes[i] == b'\''
&& bytes[i + 1] == b'\\'
&& bytes[i + 2] == b'\''
&& bytes[i + 3] == b'\''
};
if matched {
ret += qa as i32; t.push(b'\''); i += qa + 1; } else {
t.push(bytes[i]); i += 1;
}
}
*s = String::from_utf8(t).unwrap_or_default(); ret }
pub fn ctokenize(p: &str) -> String {
let bytes = p.as_bytes(); let mut out = Vec::<u8>::with_capacity(bytes.len());
let mut bslash = false; let mut prev_idx: Option<usize> = None;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i]; if b == b'\\' {
bslash = true;
out.push(b);
prev_idx = Some(out.len() - 1);
} else {
if b == b'$' || b == b'{' || b == b'}' {
if bslash {
if let Some(pi) = prev_idx {
out.truncate(pi);
let mut buf = [0u8; 4];
out.extend_from_slice(Bnull.encode_utf8(&mut buf).as_bytes());
}
out.push(b);
} else {
let tok = if b == b'$' {
Stringg
}
else if b == b'{' {
Inbrace
}
else {
Outbrace
}; let mut buf = [0u8; 4];
out.extend_from_slice(tok.encode_utf8(&mut buf).as_bytes());
}
} else {
out.push(b);
}
bslash = false; prev_idx = Some(out.len().saturating_sub(1));
}
i += 1;
}
String::from_utf8(out).unwrap_or_default() }
pub fn comp_str(untok: bool) -> (String, i32, i32) {
let mut p = COMPPREFIX
.get_or_init(|| Mutex::new(String::new())) .lock()
.unwrap()
.clone();
let mut s = COMPSUFFIX
.get_or_init(|| Mutex::new(String::new())) .lock()
.unwrap()
.clone();
let ip = COMPIPREFIX
.get_or_init(|| Mutex::new(String::new())) .lock()
.unwrap()
.clone();
if !untok {
p = ctokenize(&p); p = p.chars().filter(|&c| c != Bnull).collect(); s = ctokenize(&s); s = s.chars().filter(|&c| c != Bnull).collect(); }
let lp = p.len() as i32; let lip = ip.len() as i32; let mut str = String::with_capacity(ip.len() + p.len() + s.len() + 1); str.push_str(&ip); str.push_str(&p); str.push_str(&s); (str, lip, lp) }
pub fn comp_quoting_string(stype: i32) -> &'static str {
match stype {
x if x == QT_SINGLE => "'", x if x == QT_DOUBLE => "\"", x if x == QT_DOLLARS => "$'", _ => {
let _ = QT_BACKSLASH;
"\\" }
}
}
pub fn set_comp_sep() -> i32 {
let (_s, _lip, _lp) = comp_str(false); let owe = WE.load(Ordering::Relaxed); let owb = WB.load(Ordering::Relaxed);
let _ooffs = OFFS.load(Ordering::Relaxed);
let lex_saved = lexsave();
lexrestore(lex_saved);
WB.store(owb, Ordering::Relaxed);
WE.store(owe, Ordering::Relaxed);
1 }
#[doc(hidden)]
pub fn set_list_array(name: &str, l: &[String]) {
let _ = setaparam(name, l.to_vec()); }
pub fn get_user_var(nam: Option<&str>) -> Option<Vec<String>> {
let nam = nam?; if nam.starts_with('(') {
let mut arrlist: Vec<String> = Vec::new();
let bytes = nam.as_bytes();
let mut buf = Vec::<u8>::new();
let mut notempty = false; let mut brk = false;
let mut i = 1; while i < bytes.len() {
let b = bytes[i];
if b == b'\\' && i + 1 < bytes.len() {
buf.push(bytes[i + 1]); notempty = true;
i += 2;
continue;
}
if b == b',' || b == b' ' || b == b'\t' || b == b'\n' || b == b')' {
if b == b')' {
brk = true;
} if notempty {
let mut start = 0;
if !buf.is_empty() && buf[0] == b'\n' {
start = 1;
} let s = String::from_utf8_lossy(&buf[start..]).into_owned();
arrlist.push(s); }
buf.clear(); notempty = false;
} else {
notempty = true; buf.push(b);
}
i += 1;
if brk {
break;
} }
if !brk || arrlist.is_empty() {
return None;
} Some(arrlist) } else {
queue_signals();
let result = {
let tab = match paramtab().read() {
Ok(t) => t,
Err(_) => {
unqueue_signals();
return None;
}
};
tab.get(nam).and_then(|pm| {
if let Some(arr) = pm.u_arr.as_ref() {
Some(arr.clone()) } else if let Some(s) = pm.u_str.as_ref() {
Some(vec![s.clone()]) } else {
None
}
})
};
unqueue_signals(); result
}
}
pub fn get_data_arr(name: &str, keys: bool) -> Option<Vec<String>> {
queue_signals();
let is_hashed = match paramtab().read() {
Ok(t) => t
.get(name)
.map(|pm| PM_TYPE(pm.node.flags as u32) == PM_HASHED)
.unwrap_or(false),
Err(_) => false,
};
let result = if is_hashed {
paramtab_hashed_storage().lock().ok().and_then(|m| {
m.get(name).map(|map| {
if keys {
map.keys().cloned().collect::<Vec<_>>()
} else {
map.values().cloned().collect::<Vec<_>>()
}
})
})
} else {
None
};
unqueue_signals(); result
}
pub fn addmatch(str: &str, flags: i32, disp: Option<&str>, line: bool) {
let mut cm = Cmatch::default(); cm.str = Some(str.to_string()); let complist_extra = {
let s = COMPLIST
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let packed = if s.contains("packed") { CMF_PACKED } else { 0 }; let rows = if s.contains("rows") { CMF_ROWS } else { 0 }; if s.is_empty() {
0
} else {
packed | rows
}
};
cm.flags = flags | complist_extra; if let Some(d) = disp {
cm.disp = Some(d.to_string()); } else if line {
cm.disp = Some(String::new()); cm.flags |= CMF_DISPLINE; }
mnum.fetch_add(1, Ordering::Relaxed); {
let cell = curexpl.get_or_init(|| Mutex::new(None)); if let Ok(mut g) = cell.lock() {
if let Some(e) = g.as_mut() {
e.count += 1;
}
}
}
let mcell = matches.get_or_init(|| Mutex::new(Vec::new())); if let Ok(mut g) = mcell.lock() {
g.push(cm);
}
newmatches.store(1, Ordering::Relaxed); {
let cell = mgroup.get_or_init(|| Mutex::new(None)); if let Ok(mut g) = cell.lock() {
if let Some(grp) = g.as_mut() {
grp.new_ = 1;
}
}
}
}
pub fn addmatches(
dat: &mut Cadata, argv: &[String],
) -> i32 {
let _nm = mnum.load(Ordering::Relaxed);
if dat.dummies >= 0 {
dat.aflags = (dat.aflags | CAF_NOSORT | CAF_UNIQCON) & !CAF_UNIQALL; }
let gflags = (if (dat.aflags & CAF_NOSORT) != 0 {
CGF_NOSORT
} else {
0
}) | (if (dat.aflags & CAF_MATSORT) != 0 {
CGF_MATSORT
} else {
0
}) | (if (dat.aflags & CAF_NUMSORT) != 0 {
CGF_NUMSORT
} else {
0
}) | (if (dat.aflags & CAF_REVSORT) != 0 {
CGF_REVSORT
} else {
0
}) | (if (dat.aflags & CAF_UNIQALL) != 0 {
CGF_UNIQALL
} else {
0
}) | (if (dat.aflags & CAF_UNIQCON) != 0 {
CGF_UNIQCON
} else {
0
});
if let Some(g) = dat.group.as_deref() {
endcmgroup(None); begcmgroup(Some(g), gflags); } else {
endcmgroup(None); begcmgroup(Some("default"), 0); }
if dat.mesg.is_some() || dat.exp.is_some() {
let mut e = Cexpl::default(); e.always = if dat.mesg.is_some() { 1 } else { 0 }; e.count = 0;
e.fcount = 0; e.str = Some(
dat.mesg
.clone() .or_else(|| dat.exp.clone())
.unwrap_or_default(),
);
if let Ok(mut g) = curexpl.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(e);
}
if dat.mesg.is_some() && dat.dpar.is_empty() && dat.opar.is_none() && dat.apar.is_none() {
addexpl(true); }
} else if let Ok(mut g) = curexpl.get_or_init(|| Mutex::new(None)).lock() {
*g = None; }
if argv.is_empty() && dat.dummies == 0 && (dat.aflags & CAF_ALL) == 0 {
return 1; }
let _quote_mode = (dat.aflags & CAF_QUOTE) != 0;
if (dat.flags & 0x0008) != 0 {
dat.flags |= parflags.load(Ordering::Relaxed); }
let qc = compquote_first(); if let Some(q) = qc {
match q {
'`' => {
instring_set(0);
inbackt_set(0);
autoq_set("");
} '\'' => instring_set(QT_SINGLE), '"' => instring_set(QT_DOUBLE), '$' => instring_set(QT_DOLLARS), _ => {}
}
} else {
instring_set(0);
inbackt_set(0);
autoq_set(""); }
let exact_str = getsparam("compexact").unwrap_or_default();
useexact.store(if exact_str == "accept" { 1 } else { 0 }, Ordering::Relaxed);
if let Some(ref m) = dat.match_ {
if let Ok(mut mst) = mstack.get_or_init(|| Mutex::new(None)).lock() {
let new_link = Box::new(Cmlist {
next: mst.take(),
matcher: m.clone(),
str: String::new(),
});
*mst = Some(new_link);
}
crate::ported::zle::compmatch::add_bmatchers(Some(m));
if let Ok(mut g) = matchers
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
{
g.push(m.clone());
}
}
let (aign, pign) = if let Some(ign_name) = dat.ign.as_deref() {
let aign_raw = get_user_var(Some(ign_name)).unwrap_or_default();
let mut literal_suffixes: Vec<String> = Vec::new();
let mut pat_progs: Vec<crate::ported::pattern::Patprog> = Vec::new();
for entry in aign_raw {
let bytes = entry.as_bytes();
let star_prefix = bytes.len() >= 3
&& ((bytes[0] == b'?' && bytes[1] == b'*')
|| (bytes[0] == b'*' && bytes[1] == b'?'))
&& !crate::ported::pattern::haswilds(
std::str::from_utf8(&bytes[2..]).unwrap_or(""),
);
if star_prefix {
literal_suffixes.push(std::str::from_utf8(&bytes[2..]).unwrap_or("").to_string());
} else if let Some(prog) =
crate::ported::pattern::patcompile(&entry, 0, None::<&mut String>)
{
pat_progs.push(prog);
}
}
(literal_suffixes, pat_progs)
} else {
(Vec::new(), Vec::new())
};
let disp_arr: Vec<String> = if let Some(ref d) = dat.disp {
get_user_var(Some(d.as_str())).unwrap_or_default()
} else {
Vec::new()
};
let compiprefix_s = COMPIPREFIX
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let compisuffix_s = crate::ported::zle::complete::COMPISUFFIX
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let compprefix_s = COMPPREFIX
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let compsuffix_s = COMPSUFFIX
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let lipre = compiprefix_s.clone();
let lisuf = compisuffix_s.clone();
let lpre = compprefix_s.clone();
let lsuf = compsuffix_s.clone();
if let Some(ref existing) = dat.ipre.clone() {
dat.ipre = Some(if !lipre.is_empty() {
format!("{}{}", lipre, existing)
} else {
existing.clone()
});
} else if !lipre.is_empty() {
dat.ipre = Some(lipre.clone());
}
if let Some(ref existing) = dat.isuf.clone() {
dat.isuf = Some(if !lisuf.is_empty() {
format!("{}{}", lisuf, existing)
} else {
existing.clone()
});
} else if !lisuf.is_empty() {
dat.isuf = Some(lisuf.clone());
}
let quote_flag = if (dat.aflags & CAF_QUOTE) != 0 { 1 } else { 0 };
if let Some(ref existing) = dat.ppre.clone() {
let quoted = if (dat.flags & 0x0001) != 0 {
tildequote(existing, quote_flag)
} else {
multiquote(existing, quote_flag)
};
dat.ppre = Some(quoted);
}
if let Some(ref existing) = dat.psuf.clone() {
dat.psuf = Some(multiquote(existing, quote_flag));
}
let ppl = dat.ppre.as_deref().map(|s| s.len()).unwrap_or(0);
let psl = dat.psuf.as_deref().map(|s| s.len()).unwrap_or(0);
let doadd = dat.apar.is_none() && dat.opar.is_none() && dat.dpar.is_empty();
let mut apar_list: Vec<String> = Vec::new();
let mut opar_list: Vec<String> = Vec::new();
let mut added = 0i32;
let mut disp_idx = 0usize;
let mut compignored_local = 0i32;
'cand: for word in argv {
let cur_disp = if !disp_arr.is_empty() && disp_idx < disp_arr.len() {
let d = disp_arr[disp_idx].clone();
disp_idx += 1;
Some(d)
} else {
None
};
if !aign.is_empty() || !pign.is_empty() {
let full = format!(
"{}{}{}",
dat.ppre.as_deref().unwrap_or(""),
word,
dat.psuf.as_deref().unwrap_or("")
);
for suf in &aign {
if full.len() >= suf.len() && full.ends_with(suf.as_str()) {
compignored_local += 1;
continue 'cand;
}
}
for prog in &pign {
if crate::ported::pattern::pattry(prog, &full) {
compignored_local += 1;
continue 'cand;
}
}
}
let ms: String;
let _lc;
let isexact;
if (dat.aflags & CAF_MATCH) == 0 {
ms = if (dat.aflags & CAF_QUOTE) != 0 {
word.clone()
} else {
multiquote(word, 0)
};
let sl = ms.len() as i32;
_lc = bld_parts(&ms, sl, -1, None, None);
isexact = 0;
} else {
let qu = if (dat.aflags & CAF_QUOTE) != 0 {
0
} else if dat.ppre.is_some() || (dat.flags & 0x0001) == 0 {
1
} else {
2
};
let mut lc_out: Option<Box<Cline>> = None;
let mut isexact_out = 0i32;
match crate::ported::zle::compmatch::comp_match(
&lpre,
&lsuf,
word,
None,
Some(&mut lc_out),
qu,
None,
0,
None,
0,
&mut isexact_out,
) {
Some(matched) => {
ms = matched;
_lc = lc_out;
isexact = isexact_out;
}
None => {
continue 'cand; }
}
}
if doadd {
let cm = add_match_data(
0,
&ms,
word,
_lc.clone(), dat.ipre.as_deref().unwrap_or(""),
"", dat.isuf.as_deref().unwrap_or(""),
dat.pre.as_deref().unwrap_or(""),
dat.prpre.as_deref().unwrap_or(""),
dat.ppre.as_deref().unwrap_or(""),
None, dat.psuf.as_deref().unwrap_or(""),
None, dat.suf.as_deref().unwrap_or(""),
dat.flags,
isexact,
);
let _ = cur_disp;
let _ = cm;
added += 1;
} else {
if dat.apar.is_some() {
apar_list.push(ms.clone());
}
if dat.opar.is_some() {
opar_list.push(word.clone());
}
}
}
if let Some(ref name) = dat.apar {
setaparam(name, apar_list);
}
if let Some(ref name) = dat.opar {
setaparam(name, opar_list);
}
if dat.exp.is_some() {
addexpl(false);
}
let hasall = hasallmatch.load(Ordering::Relaxed);
if hasall == 0 && (dat.aflags & CAF_ALL) != 0 {
addmatch(
"<all>",
dat.flags | crate::ported::zle::comp_h::CMF_ALL,
None,
true,
);
hasallmatch.store(1, Ordering::Relaxed);
}
while dat.dummies > 0 {
addmatch(
"",
dat.flags | crate::ported::zle::comp_h::CMF_DUMMY,
None,
false,
);
dat.dummies -= 1;
}
let _ = (ppl, psl, compignored_local, added);
0 }
#[allow(clippy::too_many_arguments)]
pub fn add_match_data(
alt: i32,
str: &str,
orig: &str,
mut line: Option<Box<Cline>>,
ipre_: &str,
ripre_: &str,
isuf_: &str,
pre: &str,
prpre: &str,
ppre: &str,
mut pline: Option<Box<Cline>>,
psuf: &str,
mut sline: Option<Box<Cline>>,
suf: &str,
flags: i32,
exact: i32,
) -> Cmatch {
DPUTS!(line.is_none(), "BUG: add_match_data() without cline"); let _ai_ref = if alt != 0 { &fainfo } else { &ainfo }; cline_matched(&mut line);
if pline.is_some() {
cline_matched(&mut pline);
}
if sline.is_some() {
cline_matched(&mut sline);
}
let psl = psuf.len();
let isl = isuf_.len();
let qisuf_v = qisuf_get(); let qisl = qisuf_v.len();
let _salen = (if sline.is_none() { psl } else { 0 }) + isl + qisl;
let ipl = ipre_.len();
let _ppl = ppre.len();
let _pl = pre.len();
let qipre_v = qipre_get(); let qipl_v = qipre_v.clone();
let _qipl = qipl_v.len();
let _stl = str.len();
let _lpl = ripre_.len();
let _lsl = suf.len();
let _ml = ipl;
let psl_local = if sline.is_none() && !psuf.is_empty() {
psuf.len() as i32
} else {
0
};
let isl_local = isuf_.len() as i32;
let qisl_local = qisuf_v.len() as i32;
let salen = psl_local + isl_local + qisl_local;
if salen > 0 && line.is_some() {
unsafe {
let mut tail: *mut Option<Box<Cline>> = &mut line;
while let Some(ref n) = *tail {
if n.next.is_none() {
break;
}
tail = &mut (*tail).as_mut().unwrap().next;
}
if psl_local > 0 {
let s = bld_parts(
psuf, psl_local, psl_local, None, None,
);
if let Some(node) = (*tail).as_mut() {
node.next = s;
while let Some(ref nn) = node.next {
if nn.next.is_none() {
break;
}
break;
}
}
while let Some(ref n) = *tail {
if n.next.is_none() {
break;
}
tail = &mut (*tail).as_mut().unwrap().next;
}
}
if isl_local > 0 {
let s = bld_parts(
isuf_, isl_local, isl_local, None, None,
);
if let Some(node) = (*tail).as_mut() {
node.next = s;
}
while let Some(ref n) = *tail {
if n.next.is_none() {
break;
}
tail = &mut (*tail).as_mut().unwrap().next;
}
}
if qisl_local > 0 {
let mut s = bld_parts(
&qisuf_v, qisl_local, qisl_local, None, None,
);
if let Some(qsl) = s.as_mut() {
qsl.flags |= crate::ported::zle::comp_h::CLF_SUF;
qsl.suffix = qsl.prefix.take();
}
if let Some(node) = (*tail).as_mut() {
node.next = s;
}
}
}
}
let qipl_local = qipre_v.len() as i32;
let ipl_local = ipre_.len() as i32;
let pl_local = pre.len() as i32;
let ppl_local = if pline.is_none() && !ppre.is_empty() {
ppre.len() as i32
} else {
0
};
if pl_local > 0 {
if ppl_local > 0 {
let p =
bld_parts(ppre, ppl_local, ppl_local, None, None);
if p.is_some() {
let mut p_chain = p;
let mut tail: *mut Option<Box<Cline>> = &mut p_chain;
unsafe {
while let Some(ref n) = *tail {
if n.next.is_none() {
break;
}
tail = &mut (*tail).as_mut().unwrap().next;
}
if let Some(t) = (*tail).as_mut() {
t.next = line.take();
}
}
line = p_chain;
}
}
let p = bld_parts(pre, pl_local, pl_local, None, None);
if let Some(mut head) = p {
let mut t: *mut Option<Box<Cline>> = &mut head.next;
unsafe {
while (*t).is_some() {
if (*t).as_deref().unwrap().next.is_none() {
break;
}
t = &mut (*t).as_mut().unwrap().next;
}
*t = line.take();
}
line = Some(head);
}
if ipl_local > 0 {
let p =
bld_parts(ipre_, ipl_local, ipl_local, None, None);
if let Some(mut head) = p {
let mut t: *mut Option<Box<Cline>> = &mut head.next;
unsafe {
while (*t).is_some() {
if (*t).as_deref().unwrap().next.is_none() {
break;
}
t = &mut (*t).as_mut().unwrap().next;
}
*t = line.take();
}
line = Some(head);
}
}
if qipl_local > 0 {
let p = bld_parts(
&qipre_v, qipl_local, qipl_local, None, None,
);
if let Some(mut head) = p {
let mut t: *mut Option<Box<Cline>> = &mut head.next;
unsafe {
while (*t).is_some() {
if (*t).as_deref().unwrap().next.is_none() {
break;
}
t = &mut (*t).as_mut().unwrap().next;
}
*t = line.take();
}
line = Some(head);
}
}
} else if qipl_local + ipl_local + pl_local + ppl_local > 0 || pline.is_some() {
let apre = format!(
"{}{}{}{}",
qipre_v.as_str(),
ipre_,
pre,
if pline.is_none() { ppre } else { "" }
);
let apre_len = apre.len() as i32;
if apre_len > 0 {
let p = bld_parts(&apre, apre_len, apre_len, None, None);
if let Some(mut head) = p {
let mut t: *mut Option<Box<Cline>> = &mut head.next;
unsafe {
while (*t).is_some() {
if (*t).as_deref().unwrap().next.is_none() {
break;
}
t = &mut (*t).as_mut().unwrap().next;
}
*t = line.take();
}
line = Some(head);
}
}
}
let stl = str.len();
let mut cm = Cmatch::default(); cm.str = Some(str.to_string()); cm.orig = Some(orig.to_string()); cm.ppre = if ppre.is_empty() {
None
} else {
Some(ppre.into())
}; cm.psuf = if psuf.is_empty() {
None
} else {
Some(psuf.into())
};
cm.prpre = if (flags & CMF_FILE) != 0 && !prpre.is_empty() {
Some(prpre.into())
} else {
None
};
cm.ipre = if !qipre_v.is_empty() {
if !ipre_.is_empty() {
Some(format!("{}{}", qipre_v, ipre_))
} else {
Some(qipre_v.clone())
}
} else if !ipre_.is_empty() {
Some(ipre_.into())
} else {
None
};
cm.ripre = if ripre_.is_empty() {
None
} else {
Some(ripre_.into())
};
cm.isuf = if !qisuf_v.is_empty() {
if !isuf_.is_empty() {
Some(format!("{}{}", isuf_, qisuf_v))
} else {
Some(qisuf_v.clone())
}
} else if !isuf_.is_empty() {
Some(isuf_.into())
} else {
None
};
cm.pre = if pre.is_empty() {
None
} else {
Some(pre.into())
}; cm.suf = if suf.is_empty() {
None
} else {
Some(suf.into())
};
let complist_s = COMPLIST
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
let extra_flags = (if complist_s.contains("packed") {
CMF_PACKED
} else {
0
}) | (if complist_s.contains("rows") {
CMF_ROWS
} else {
0
});
cm.flags = flags | extra_flags;
cm.mode = 0;
cm.fmode = 0;
cm.modec = '\0';
cm.fmodec = '\0';
use crate::ported::zle::comp_h::CMF_FILE;
if (flags & CMF_FILE) != 0 && !orig.is_empty() && !orig.ends_with('/') {
let pb = format!("{}{}", cm.prpre.as_deref().unwrap_or("./"), orig);
if let Some(meta) = ztat(&pb, false) {
use std::os::unix::fs::MetadataExt;
cm.mode = meta.mode();
}
if let Some(meta) = ztat(&pb, true) {
use std::os::unix::fs::MetadataExt;
cm.fmode = meta.mode();
}
}
cm.brpl = BRBEG
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| {
g.as_ref().map(|head| {
let mut out: Vec<i32> = Vec::new();
let mut cur = Some(head.as_ref());
while let Some(n) = cur {
out.push(n.qpos);
cur = n.next.as_deref();
}
out
})
})
.unwrap_or_default();
cm.brsl = BREND
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| {
g.as_ref().map(|head| {
let mut out: Vec<i32> = Vec::new();
let mut cur = Some(head.as_ref());
while let Some(n) = cur {
out.push(n.qpos);
cur = n.next.as_deref();
}
out
})
})
.unwrap_or_default();
cm.qipl = qipre_v.len() as i32; cm.qisl = qisuf_v.len() as i32; let autoq_v = AUTOQ
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
cm.autoq = if !autoq_v.is_empty() {
Some(autoq_v)
} else if INBACKT.load(Ordering::Relaxed) != 0 {
Some("`".into())
} else {
None
};
cm.rems = None;
cm.remf = None;
cm.disp = None;
if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
if let Some(a) = g.as_mut() {
let old_line = a.line.take();
a.line = crate::ported::zle::compmatch::join_clines(old_line, line);
}
}
mnum.fetch_add(1, Ordering::Relaxed);
if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
if let Some(a) = g.as_mut() {
a.count += 1;
}
}
newmatches.store(1, Ordering::Relaxed);
if alt != 0 {
crate::ported::zle::complete::COMPIGNORED.fetch_add(1, Ordering::Relaxed);
}
let complastprompt_v = crate::ported::zle::complete::COMPLASTPREFIX
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()))
.unwrap_or_default();
if complastprompt_v.is_empty() {
dolastprompt.store(0, Ordering::Relaxed);
}
if let Ok(mut g) = curexpl.get_or_init(|| Mutex::new(None)).lock() {
if let Some(e) = g.as_mut() {
if alt != 0 {
e.fcount += 1;
} else {
e.count += 1;
}
}
}
if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
if let Some(a) = g.as_mut() {
if a.firstm.is_none() {
a.firstm = Some(Box::new(cm.clone()));
}
}
}
let lpl = cm.ppre.as_deref().map(|s| s.len()).unwrap_or(0);
let lsl = cm.psuf.as_deref().map(|s| s.len()).unwrap_or(0);
let ml = (stl + lpl + lsl) as i32;
let cur_min = minmlen.load(Ordering::Relaxed);
let cur_max = maxmlen.load(Ordering::Relaxed);
if ml < cur_min {
minmlen.store(ml, Ordering::Relaxed);
}
if ml > cur_max {
maxmlen.store(ml, Ordering::Relaxed);
}
if exact != 0 {
if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
if let Some(a) = g.as_mut() {
if a.exact == 0 {
a.exact = useexact.load(Ordering::Relaxed);
a.exactm = Some(Box::new(cm.clone())); } else if useexact.load(Ordering::Relaxed) != 0 {
a.exact = 2;
a.exactm = None;
}
}
}
}
let cell = if alt != 0 {
fmatches.get_or_init(|| Mutex::new(Vec::new()))
} else {
matches.get_or_init(|| Mutex::new(Vec::new()))
};
if let Ok(mut g) = cell.lock() {
g.push(cm.clone());
}
cm }
pub fn begcmgroup(n: Option<&str>, flags: i32) {
if let Some(name) = n {
let mask = CGF_NOSORT | CGF_UNIQALL | CGF_UNIQCON | CGF_MATSORT | CGF_NUMSORT | CGF_REVSORT;
let cell = amatches.get_or_init(|| Mutex::new(Vec::new()));
if let Ok(g) = cell.lock() {
for grp in g.iter() {
if grp.name.as_deref() == Some(name) && (grp.flags & mask) == flags
{
let active = grp.clone(); let mc = mgroup.get_or_init(|| Mutex::new(None));
if let Ok(mut s) = mc.lock() {
*s = Some(active);
}
return; }
}
}
}
let mut grp = Cmgroup::default(); grp.name = n.map(String::from); grp.flags = flags; let cell = amatches.get_or_init(|| Mutex::new(Vec::new()));
if let Ok(mut g) = cell.lock() {
g.insert(0, grp.clone()); }
let mc = mgroup.get_or_init(|| Mutex::new(None));
if let Ok(mut s) = mc.lock() {
*s = Some(grp);
}
if let Ok(mut g) = expls.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
if let Ok(mut g) = matches.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
if let Ok(mut g) = fmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
if let Ok(mut g) = allccs.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
}
pub fn endcmgroup(ylist: Option<Vec<String>>) {
if let Ok(mut g) = mgroup.get_or_init(|| Mutex::new(None)).lock() {
if let Some(grp) = g.as_mut() {
grp.ylist = ylist.unwrap_or_default();
} }
}
pub fn addexpl(always: bool) {
let curexpl_snap = {
let cell = curexpl.get_or_init(|| Mutex::new(None));
cell.lock().ok().and_then(|g| g.clone())
};
let curexpl_str = match curexpl_snap.as_ref().and_then(|e| e.str.clone()) {
Some(s) => s,
None => return,
};
let curexpl_count = curexpl_snap.as_ref().map(|e| e.count).unwrap_or(0);
let curexpl_fcount = curexpl_snap.as_ref().map(|e| e.fcount).unwrap_or(0);
let elist = expls.get_or_init(|| Mutex::new(Vec::new()));
if let Ok(mut g) = elist.lock() {
for e in g.iter_mut() {
if e.str.as_deref() == Some(curexpl_str.as_str()) {
e.count += curexpl_count; e.fcount += curexpl_fcount; if always {
e.always = 1;
nmessages.fetch_add(1, Ordering::Relaxed); newmatches.store(1, Ordering::Relaxed); let mc = mgroup.get_or_init(|| Mutex::new(None));
if let Ok(mut mg) = mc.lock() {
if let Some(grp) = mg.as_mut() {
grp.new_ = 1;
}
}
}
return; }
}
if let Some(e) = curexpl_snap {
g.push(e);
}
}
newmatches.store(1, Ordering::Relaxed); if always {
let mc = mgroup.get_or_init(|| Mutex::new(None));
if let Ok(mut mg) = mc.lock() {
if let Some(grp) = mg.as_mut() {
grp.new_ = 1;
}
}
nmessages.fetch_add(1, Ordering::Relaxed); }
}
pub fn matchcmp(a: &Cmatch, b: &Cmatch) -> std::cmp::Ordering {
let order = MATCHORDER.load(Ordering::Relaxed);
let sortdir = if (order & CGF_REVSORT) != 0 { -1 } else { 1 };
let cmp = (b.disp.is_some() as i32) - (a.disp.is_some() as i32); let (as_, bs) = if (order & CGF_MATSORT) != 0 || (cmp == 0 && a.disp.is_none()) {
(
a.str.clone().unwrap_or_default(), b.str.clone().unwrap_or_default(),
) } else {
if cmp != 0 {
let raw = (cmp as i32) * sortdir;
return if raw < 0 {
std::cmp::Ordering::Less
}
else if raw > 0 {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
};
}
let displine_cmp = (b.flags & CMF_DISPLINE) - (a.flags & CMF_DISPLINE); if displine_cmp != 0 {
let raw = displine_cmp * sortdir;
return if raw < 0 {
std::cmp::Ordering::Less
} else if raw > 0 {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
};
}
(
a.disp.clone().unwrap_or_default(), b.disp.clone().unwrap_or_default(),
) };
let raw = sortdir
* if as_ == bs {
0
} else if as_ < bs {
-1
} else {
1
};
if raw < 0 {
std::cmp::Ordering::Less
}
else if raw > 0 {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
}
}
pub fn matcheq(a: &Cmatch, b: &Cmatch) -> bool {
matchstreq(a.ipre.as_ref(), b.ipre.as_ref()) && matchstreq(a.pre.as_ref(), b.pre.as_ref()) && matchstreq(a.ppre.as_ref(), b.ppre.as_ref()) && matchstreq(a.psuf.as_ref(), b.psuf.as_ref()) && matchstreq(a.suf.as_ref(), b.suf.as_ref()) && matchstreq(a.str.as_ref(), b.str.as_ref()) }
pub fn makearray(mut rp: Vec<Cmatch>, flags: i32) -> (Vec<Cmatch>, i32, i32, i32) {
let mut n: i32 = rp.len() as i32; let mut nl: i32 = 0; let mut ll: i32 = 0;
if n > 0 {
if (flags & CGF_NOSORT) == 0 {
MATCHORDER.store(flags, Ordering::Relaxed); rp.sort_by(matchcmp);
if (flags & CGF_UNIQCON) == 0 {
let mut cp = 0usize; let mut ap = 0usize;
while ap < rp.len() {
if ap != cp {
rp.swap(ap, cp);
} cp += 1;
let mut bp = ap;
while bp + 1 < rp.len() && matcheq(&rp[ap], &rp[bp + 1]) {
bp += 1;
n -= 1; }
let mut dup = 0i32; while bp + 1 < rp.len()
&& rp[ap].disp.is_none()
&& rp[bp + 1].disp.is_none() && rp[ap].str == rp[bp + 1].str
{
rp[bp + 1].flags |= CMF_MULT; dup = 1; bp += 1;
}
if dup != 0 {
rp[ap].flags |= CMF_FMULT; }
ap = bp + 1; }
rp.truncate(cp); }
for m in rp.iter() {
if m.disp.is_some() && (m.flags & CMF_DISPLINE) != 0 {
ll += 1;
}
if (m.flags & (CMF_NOLIST | CMF_MULT)) != 0 {
nl += 1;
}
}
} else {
if (flags & CGF_UNIQALL) == 0 && (flags & CGF_UNIQCON) == 0 {
MATCHORDER.store(flags, Ordering::Relaxed); let mut sp: Vec<Cmatch> = rp.clone(); sp.sort_by(matchcmp);
let mut del = false; for w in sp.windows(2) {
if matcheq(&w[0], &w[1]) {
for m in rp.iter_mut() {
if matcheq(m, &w[1]) {
m.flags = CMF_DELETE; del = true; break;
}
}
} else if w[0].disp.is_none() {
if w[1].disp.is_none() && w[0].str == w[1].str {
for m in rp.iter_mut() {
if matcheq(m, &w[1]) {
m.flags |= CMF_MULT; break;
}
}
for m in rp.iter_mut() {
if matcheq(m, &w[0]) {
m.flags |= CMF_FMULT; break;
}
}
}
}
}
if del {
rp.retain(|m| (m.flags & CMF_DELETE) == 0); n = rp.len() as i32;
}
} else if (flags & CGF_UNIQCON) == 0 {
let mut cp = 0usize;
let mut ap = 0usize;
while ap < rp.len() {
if ap != cp {
rp.swap(ap, cp);
}
cp += 1;
let mut bp = ap;
while bp + 1 < rp.len() && matcheq(&rp[ap], &rp[bp + 1]) {
bp += 1;
n -= 1; }
let mut dup = 0i32;
while bp + 1 < rp.len()
&& rp[ap].disp.is_none()
&& rp[bp + 1].disp.is_none()
&& rp[ap].str == rp[bp + 1].str
{
rp[bp + 1].flags |= CMF_MULT; dup = 1; bp += 1;
}
if dup != 0 {
rp[ap].flags |= CMF_FMULT; }
ap = bp + 1;
}
rp.truncate(cp); }
for m in rp.iter() {
if m.disp.is_some() && (m.flags & CMF_DISPLINE) != 0 {
ll += 1;
}
if (m.flags & (CMF_NOLIST | CMF_MULT)) != 0 {
nl += 1;
}
}
}
}
(rp, n, nl, ll) }
pub fn dupmatch(m: &Cmatch, nbeg: i32, nend: i32) -> Cmatch {
let mut r = Cmatch::default(); r.str = m.str.clone(); r.orig = m.orig.clone(); r.ipre = m.ipre.clone(); r.ripre = m.ripre.clone(); r.isuf = m.isuf.clone(); r.ppre = m.ppre.clone(); r.psuf = m.psuf.clone(); r.prpre = m.prpre.clone(); r.pre = m.pre.clone(); r.suf = m.suf.clone(); r.flags = m.flags; if !m.brpl.is_empty() {
let take = (nbeg as usize).min(m.brpl.len()); r.brpl = m.brpl[..take].to_vec(); } else {
r.brpl = Vec::new(); }
if !m.brsl.is_empty() {
let take = (nend as usize).min(m.brsl.len()); r.brsl = m.brsl[..take].to_vec(); } else {
r.brsl = Vec::new(); }
r.rems = m.rems.clone(); r.remf = m.remf.clone(); r.autoq = m.autoq.clone(); r.qipl = m.qipl; r.qisl = m.qisl; r.disp = m.disp.clone(); r.mode = m.mode; r.modec = m.modec; r.fmode = m.fmode; r.fmodec = m.fmodec; r }
pub fn permmatches(last: i32) -> i32 {
let ofi = PERMMATCHES_FI.load(Ordering::Relaxed);
let pmatches_set = pmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.map(|g| !g.is_empty())
.unwrap_or(false);
if pmatches_set && newmatches.load(Ordering::Relaxed) == 0 {
if last != 0 && PERMMATCHES_FI.load(Ordering::Relaxed) != 0 {
let famref = fainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone());
if let Ok(mut a) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
*a = famref;
}
}
return PERMMATCHES_FI.load(Ordering::Relaxed); }
newmatches.store(0, Ordering::Relaxed); PERMMATCHES_FI.store(0, Ordering::Relaxed);
{
if let Ok(mut g) = pmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear();
}
if let Ok(mut g) = lmatches.get_or_init(|| Mutex::new(None)).lock() {
*g = None;
}
}
nmatches.store(0, Ordering::Relaxed); smatches.store(0, Ordering::Relaxed); diffmatches.store(0, Ordering::Relaxed);
let ainfo_count = ainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().map(|a| a.count))
.unwrap_or(0);
if ainfo_count == 0 {
if last != 0 {
let famref = fainfo
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone());
if let Ok(mut a) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
*a = famref;
}
}
PERMMATCHES_FI.store(1, Ordering::Relaxed); }
let nbeg = NBRBEG.load(Ordering::Relaxed);
let nend = NBREND.load(Ordering::Relaxed);
let mut gn: i32 = 1; let mut mn: i32 = 1; let fi = PERMMATCHES_FI.load(Ordering::Relaxed);
let groups_snapshot: Vec<Cmgroup> = {
amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default()
};
let mut new_pmatches: Vec<Cmgroup> = Vec::with_capacity(groups_snapshot.len());
for g_orig in groups_snapshot.into_iter() {
let mut g = g_orig; let must_rebuild = fi != ofi || g.perm.is_none() || g.new_ != 0; if must_rebuild {
let src_list = if fi != 0 {
g.lfmatches.clone()
}
else {
g.lmatches.clone()
};
let (arr, nn, nl, ll) = makearray(src_list, g.flags); g.mcount = nn; g.lcount = nn - nl; if g.lcount < 0 {
g.lcount = 0;
} g.llcount = ll; if !g.ylist.is_empty() {
g.lcount = g.ylist.len() as i32; smatches.store(2, Ordering::Relaxed); }
let mut exps = g.lexpls.clone(); g.ecount = exps.len() as i32;
g.ccount = 0; nmatches.fetch_add(g.mcount, Ordering::Relaxed); smatches.fetch_add(g.lcount, Ordering::Relaxed); if g.mcount > 1 {
diffmatches.store(1, Ordering::Relaxed); }
let mut n_grp = Cmgroup::default();
g.perm = None;
n_grp.num = gn;
gn += 1; n_grp.flags = g.flags; n_grp.mcount = g.mcount; n_grp.matches = arr
.iter() .map(|m| dupmatch(m, nbeg, nend))
.collect();
n_grp.name = g.name.clone(); n_grp.lcount = g.lcount; n_grp.llcount = g.llcount; if !g.ylist.is_empty() {
n_grp.ylist = g.ylist.clone(); } else {
n_grp.ylist = Vec::new(); }
if g.ecount != 0 {
n_grp.expls = exps
.drain(..)
.map(|o| Cexpl {
count: if fi != 0 { o.fcount } else { o.count }, always: o.always, fcount: 0, str: o.str.clone(), })
.collect();
n_grp.ecount = g.ecount;
} else {
n_grp.expls = Vec::new(); }
n_grp.widths = Vec::new(); g.matches = arr; g.perm = Some(Box::new(n_grp.clone())); new_pmatches.push(n_grp); } else {
nmatches.fetch_add(g.mcount, Ordering::Relaxed); smatches.fetch_add(g.lcount, Ordering::Relaxed); if g.mcount > 1 {
diffmatches.store(1, Ordering::Relaxed); }
g.num = gn;
gn += 1; if let Some(p) = g.perm.as_deref() {
new_pmatches.push(p.clone()); }
}
g.new_ = 0; }
let mut first_first: Option<Cmatch> = None;
for g_pm in new_pmatches.iter_mut() {
g_pm.nbrbeg = nbeg; g_pm.nbrend = nend; let mut rn = 1i32; for m in g_pm.matches.iter_mut() {
m.rnum = rn;
rn += 1; m.gnum = mn;
mn += 1; }
if diffmatches.load(Ordering::Relaxed) == 0 && !g_pm.matches.is_empty() {
match first_first.as_ref() {
Some(p0) => {
if !matcheq(&g_pm.matches[0], p0) {
diffmatches.store(1, Ordering::Relaxed); }
}
None => first_first = Some(g_pm.matches[0].clone()), }
}
}
if let Ok(mut g) = pmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
*g = new_pmatches;
}
hasperm.store(1, Ordering::Relaxed); permmnum.store(mn - 1, Ordering::Relaxed); permgnum.store(gn - 1, Ordering::Relaxed); if let Ok(mut ld) = listdat
.get_or_init(|| Mutex::new(Default::default()))
.lock()
{
ld.valid = 0; }
fi }
pub fn freematch(m: Cmatch, _nbeg: i32, _nend: i32) {
drop(m); }
pub fn freematches(g: Vec<Cmgroup>, cm: i32) {
drop(g);
if cm != 0 {
if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = None; }
}
}
pub static LASTEND: AtomicI32 = AtomicI32::new(0);
pub static WB: AtomicI32 = AtomicI32::new(0); pub static WE: AtomicI32 = AtomicI32::new(0); pub static ZLEMETACS: AtomicI32 = AtomicI32::new(0); pub static ZLEMETALL: AtomicI32 = AtomicI32::new(0); pub static ADDEDX: AtomicI32 = AtomicI32::new(0);
pub static ZLEMETALINE: OnceLock<Mutex<String>> = OnceLock::new(); pub static ZLELINE: OnceLock<Mutex<String>> = OnceLock::new(); pub static ZLECS: AtomicI32 = AtomicI32::new(0); pub static ZLELL: AtomicI32 = AtomicI32::new(0); pub static INWHAT: AtomicI32 = AtomicI32::new(0); pub static ZMULT: AtomicI32 = AtomicI32::new(1); pub static compfunc: OnceLock<Mutex<Option<String>>> = OnceLock::new(); pub static comppatmatch: OnceLock<Mutex<Option<String>>> = OnceLock::new();
pub static compqstack: OnceLock<Mutex<String>> = OnceLock::new();
pub static useexact: AtomicI32 = AtomicI32::new(0); pub static useline: AtomicI32 = AtomicI32::new(0); pub static uselist: AtomicI32 = AtomicI32::new(0); pub static forcelist: AtomicI32 = AtomicI32::new(0); pub static startauto: AtomicI32 = AtomicI32::new(0);
pub static iforcemenu: AtomicI32 = AtomicI32::new(0);
pub static dolastprompt: AtomicI32 = AtomicI32::new(0);
pub static oldlist: AtomicI32 = AtomicI32::new(0); pub static oldins: AtomicI32 = AtomicI32::new(0);
pub static origlpre: AtomicI32 = AtomicI32::new(0); pub static origlsuf: AtomicI32 = AtomicI32::new(0); pub static lenchanged: AtomicI32 = AtomicI32::new(0);
pub static movetoend: AtomicI32 = AtomicI32::new(0);
pub static insmnum: AtomicI32 = AtomicI32::new(0); pub static insspace: AtomicI32 = AtomicI32::new(0);
pub static menuacc: AtomicI32 = AtomicI32::new(0);
pub static hasunqu: AtomicI32 = AtomicI32::new(0); pub static useqbr: AtomicI32 = AtomicI32::new(0); pub static brpcs: AtomicI32 = AtomicI32::new(0); pub static brscs: AtomicI32 = AtomicI32::new(0);
pub static ispar: AtomicI32 = AtomicI32::new(0); pub static linwhat: AtomicI32 = AtomicI32::new(0);
pub static parpre: OnceLock<Mutex<String>> = OnceLock::new();
pub static parflags: AtomicI32 = AtomicI32::new(0);
pub static mflags: AtomicI32 = AtomicI32::new(0);
pub static parq: AtomicI32 = AtomicI32::new(0); pub static eparq: AtomicI32 = AtomicI32::new(0);
pub static ipre: OnceLock<Mutex<String>> = OnceLock::new(); pub static ripre: OnceLock<Mutex<String>> = OnceLock::new(); pub static isuf: OnceLock<Mutex<String>> = OnceLock::new();
pub static matches: OnceLock<Mutex<Vec<Cmatch>>> = OnceLock::new(); pub static fmatches: OnceLock<Mutex<Vec<Cmatch>>> = OnceLock::new();
pub static amatches: OnceLock<Mutex<Vec<Cmgroup>>> = OnceLock::new(); pub static pmatches: OnceLock<Mutex<Vec<Cmgroup>>> = OnceLock::new(); pub static lastmatches: OnceLock<Mutex<Vec<Cmgroup>>> = OnceLock::new(); pub static lmatches: OnceLock<Mutex<Option<Cmgroup>>> = OnceLock::new(); pub static lastlmatches: OnceLock<Mutex<Option<Cmgroup>>> = OnceLock::new();
pub static hasoldlist: AtomicI32 = AtomicI32::new(0); pub static hasperm: AtomicI32 = AtomicI32::new(0); pub static hasallmatch: AtomicI32 = AtomicI32::new(0);
pub static newmatches: AtomicI32 = AtomicI32::new(0);
pub static permmnum: AtomicI32 = AtomicI32::new(0); pub static permgnum: AtomicI32 = AtomicI32::new(0); pub static lastpermmnum: AtomicI32 = AtomicI32::new(0); pub static lastpermgnum: AtomicI32 = AtomicI32::new(0);
pub static nmatches: AtomicI32 = AtomicI32::new(0); pub static smatches: AtomicI32 = AtomicI32::new(0);
pub static diffmatches: AtomicI32 = AtomicI32::new(0);
pub static nmessages: AtomicI32 = AtomicI32::new(0);
pub static onlyexpl: AtomicI32 = AtomicI32::new(0);
pub static listdat: OnceLock<Mutex<crate::ported::zle::comp_h::Cldata>> = OnceLock::new();
pub static ispattern: AtomicI32 = AtomicI32::new(0); pub static haspattern: AtomicI32 = AtomicI32::new(0);
pub static hasmatched: AtomicI32 = AtomicI32::new(0); pub static hasunmatched: AtomicI32 = AtomicI32::new(0);
pub static mgroup: OnceLock<Mutex<Option<Cmgroup>>> = OnceLock::new();
pub static mnum: AtomicI32 = AtomicI32::new(0);
pub static unambig_mnum: AtomicI32 = AtomicI32::new(0);
pub static maxmlen: AtomicI32 = AtomicI32::new(0); pub static minmlen: AtomicI32 = AtomicI32::new(0);
pub static expls: OnceLock<Mutex<Vec<Cexpl>>> = OnceLock::new();
pub static curexpl: OnceLock<Mutex<Option<Cexpl>>> = OnceLock::new();
pub static matchers: OnceLock<Mutex<Vec<Box<crate::ported::zle::comp_h::Cmatcher>>>> =
OnceLock::new();
pub static ainfo: OnceLock<Mutex<Option<Aminfo>>> = OnceLock::new(); pub static fainfo: OnceLock<Mutex<Option<Aminfo>>> = OnceLock::new();
pub static allccs: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
pub static fromcomp: AtomicI32 = AtomicI32::new(0);
pub static lastend: AtomicI32 = AtomicI32::new(0);
pub static BRBEG: OnceLock<Mutex<Option<Box<Brinfo>>>> =
OnceLock::new();
pub static BREND: OnceLock<Mutex<Option<Box<Brinfo>>>> =
OnceLock::new();
pub static OLDMENUCMP: AtomicI32 = AtomicI32::new(0);
pub static PARWB: AtomicI32 = AtomicI32::new(0); pub static PARWE: AtomicI32 = AtomicI32::new(0); pub static PAROFFS: AtomicI32 = AtomicI32::new(0);
pub static MATCHORDER: AtomicI32 = AtomicI32::new(0);
pub static LASTCHAR: AtomicI32 = AtomicI32::new(0);
pub static MINFO: OnceLock<Mutex<Menuinfo>> = OnceLock::new();
#[inline]
fn matchstreq(a: Option<&String>, b: Option<&String>) -> bool {
match (a, b) {
(None, None) => true,
(Some(x), Some(y)) => x == y,
_ => false,
}
}
fn do_single_first_match() {
let groups = amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
let first = groups
.into_iter()
.find(|g| g.mcount > 0)
.and_then(|g| g.matches.first().cloned());
if let Some(m) = first {
if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = None;
} if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.asked = 0;
} if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = Some(Box::new(m));
}
}
}
fn goto_compend(ret: i32) -> i32 {
if let Ok(mut g) = matchers.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.clear(); }
let line_len = ZLEMETALL.load(Ordering::Relaxed); if ZLEMETACS.load(Ordering::Relaxed) > line_len {
ZLEMETACS.store(line_len, Ordering::Relaxed); }
ret }
pub const IN_NOTHING_LW: i32 = 0; pub const IN_CMD_LW: i32 = 1; pub const IN_COND_LW: i32 = 2; pub const IN_MATH_LW: i32 = 3; pub const IN_PAR_LW: i32 = 4; pub const IN_ENV_LW: i32 = 5; fn unmetafy_line() {
let meta = ZLEMETALINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let unmeta = crate::ported::zle::zle_tricky::unmetafy_line(&meta);
let new_len = unmeta.len() as i32;
let cs = ZLEMETACS.load(Ordering::Relaxed); if let Ok(mut g) = ZLELINE.get_or_init(|| Mutex::new(String::new())).lock() {
*g = unmeta;
}
ZLELL.store(new_len, Ordering::Relaxed);
ZLECS.store(cs.min(new_len), Ordering::Relaxed);
}
fn metafy_line() {
let raw = ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let meta = crate::ported::zle::zle_tricky::metafy_line(&raw);
let new_len = meta.len() as i32;
let cs = ZLECS.load(Ordering::Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
*g = meta;
}
ZLEMETALL.store(new_len, Ordering::Relaxed);
ZLEMETACS.store(cs.min(new_len), Ordering::Relaxed);
}
fn opt_isset(name: &str) -> i32 {
if crate::ported::options::opt_state_get(name).unwrap_or(false) {
1
} else {
0
}
}
fn env_iparam(name: &str) -> i32 {
crate::ported::params::getiparam(name) as i32
}
fn lastprebr_set(s: &str) {
if let Ok(mut g) = LASTPREBR
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = s.to_string();
}
}
fn lastpostbr_set(s: &str) {
if let Ok(mut g) = LASTPOSTBR
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = s.to_string();
}
}
fn compcontext_for(_s: &str) -> String {
let ip = ispar.load(Ordering::Relaxed); if ip == 2 {
return "brace_parameter".into();
} if ip == 1 {
return "parameter".into();
} let lw = linwhat.load(Ordering::Relaxed); match lw {
x if x == IN_PAR_LW => "assign_parameter".into(), x if x == IN_MATH_LW => "math".into(), x if x == IN_COND_LW => "condition".into(), x if x == IN_ENV_LW => "value".into(), _ => "command".into(), }
}
pub static OFFS: AtomicI32 = AtomicI32::new(0);
pub static freecl: OnceLock<Mutex<Option<i32>>> = OnceLock::new();
pub fn shfunc_call(name: &str) -> i32 {
if crate::ported::utils::getshfunc(name).is_none() {
return 1; }
crate::ported::exec_hooks::dispatch_function_call(name, &[])
.unwrap_or_else(|| crate::ported::builtin::LASTVAL.load(Ordering::Relaxed))
}
pub fn set_compstate_str(key: &str, val: &str) {
let pname = format!("compstate[{}]", key);
let _ = setsparam(&pname, val);
if let Ok(mut tab) = paramtab_hashed_storage().lock() {
tab.entry("compstate".to_string())
.or_default()
.insert(key.to_string(), val.to_string());
}
}
pub fn get_compstate_str(key: &str) -> Option<String> {
if let Ok(tab) = paramtab_hashed_storage().lock() {
if let Some(hash) = tab.get("compstate") {
if let Some(v) = hash.get(key) {
return Some(v.clone());
}
}
}
let pname = format!("compstate[{}]", key);
getsparam(&pname)
}
#[inline]
fn prev_char_index(bytes: &[u8], pos: usize) -> usize {
if pos == 0 {
return 0;
}
let mut i = pos - 1;
while i > 0 && (bytes[i] & 0xC0) == 0x80 {
i -= 1;
}
i
}
#[inline]
fn char_at(bytes: &[u8], pos: usize) -> char {
if pos >= bytes.len() {
return '\0';
}
let s = match std::str::from_utf8(&bytes[pos..]) {
Ok(s) => s,
Err(_) => return '\0',
};
s.chars().next().unwrap_or('\0')
}
static LEXSAVE_DEPTH: AtomicI32 = AtomicI32::new(0);
fn skip_token_parens(bytes: &[u8], start: usize, open: char, close: char) -> Option<usize> {
let mut depth: i32 = 0;
let mut i = start;
while i < bytes.len() {
let c = char_at(bytes, i);
if c == open {
depth += 1;
} else if c == close {
depth -= 1;
if depth == 0 {
return Some(i + c.len_utf8());
}
}
i += c.len_utf8();
}
if depth == 0 {
Some(i)
} else {
None
}
}
fn walk_namespace(bytes: &[u8]) -> usize {
let s = match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(_) => return 0,
};
let mut len = 0usize;
for c in s.chars() {
if c.is_alphanumeric() || c == '_' {
len += c.len_utf8();
} else {
break;
}
}
len
}
fn strip_tokens(s: &str) -> String {
crate::lex::untokenize(s).to_string()
}
fn compfunc_active() -> bool {
compfunc
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone())
.map(|s| !s.is_empty())
.unwrap_or(false)
}
fn lexsave() -> usize {
crate::ported::context::zcontext_save();
(LEXSAVE_DEPTH.fetch_add(1, Ordering::SeqCst) + 1) as usize
}
fn lexrestore(_token: usize) {
let parts = ZCONTEXT_HIST
| ZCONTEXT_LEX
| ZCONTEXT_PARSE;
zcontext_restore_partial(parts);
LEXSAVE_DEPTH.fetch_sub(1, Ordering::SeqCst);
}
fn compquote_first() -> Option<char> {
COMPQUOTE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.ok()
.and_then(|g| g.chars().next())
}
fn instring_set(v: i32) {
INSTRING.store(v, Ordering::Relaxed);
}
fn inbackt_set(v: i32) {
INBACKT.store(v, Ordering::Relaxed);
}
fn autoq_set(s: &str) {
if let Ok(mut g) = AUTOQ
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = s.to_string();
}
}
pub static bmatchers: OnceLock<Mutex<Option<Box<Cmlist>>>> =
OnceLock::new();
pub static mstack: OnceLock<Mutex<Option<Box<Cmlist>>>> =
OnceLock::new();
fn cline_matched_compcore(line: Option<&str>) {
let Some(s) = line else {
return;
};
if s.is_empty() {
return;
}
let mut head = Some(Box::new(Cline {
line: Some(s.to_string()),
llen: s.len() as i32,
..Default::default()
}));
cline_matched(&mut head);
}
fn qisuf_get() -> String {
getsparam("qisuf").unwrap_or_default()
}
fn qipre_get() -> String {
getsparam("qipre").unwrap_or_default()
}
fn movefd(fd: i32) -> i32 {
crate::ported::utils::movefd(fd)
}
fn redup(new: i32) {
crate::ported::utils::redup(new, -1);
}
pub static HOOK_FNS: OnceLock<Mutex<std::collections::HashMap<String, Vec<String>>>> =
OnceLock::new();
fn errflag_get() -> bool {
crate::ported::utils::errflag.load(Ordering::Relaxed) != 0 }
fn runhookdef_compcore(hook: &str) -> i32 {
let h = gethookdef(hook);
if h.is_null() {
return 0;
}
runhookdef(h, std::ptr::null_mut())
}
fn runhookdef_compctlmake(
dat: &mut Ccmakedat,
) {
let s = dat.str.clone().unwrap_or_default();
let _ = crate::ported::zle::compctl::makecomplistctl(dat.lst);
let _ = s;
}
static PERMMATCHES_FI: AtomicI32 = AtomicI32::new(0);
pub fn makearray_strings(mut rp: Vec<String>, flags: i32) -> (Vec<String>, i32) {
let mut n: i32 = rp.len() as i32;
if flags != 0 && n > 0 {
let numeric = isset(NUMERICGLOBSORT); let mut sf = SORTIT_IGNORING_BACKSLASHES as u32;
if numeric {
sf |= SORTIT_NUMERICALLY as u32;
}
crate::ported::sort::strmetasort(&mut rp, sf, None);
let mut cp = 0usize;
let mut ap = 0usize;
while ap < rp.len() {
if ap != cp {
rp.swap(ap, cp);
}
cp += 1;
let mut bp = ap;
while bp + 1 < rp.len() && rp[ap] == rp[bp + 1] {
bp += 1;
n -= 1;
}
ap = bp + 1; }
rp.truncate(cp); }
(rp, n)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rembslash_basic() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(rembslash("hello\\ world"), "hello world");
assert_eq!(rembslash("no\\\\slash"), "no\\slash");
assert_eq!(rembslash("plain"), "plain");
}
#[test]
fn comp_quoting_string_table() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(comp_quoting_string(QT_SINGLE), "'");
assert_eq!(comp_quoting_string(QT_DOUBLE), "\"");
assert_eq!(comp_quoting_string(QT_DOLLARS), "$'");
assert_eq!(comp_quoting_string(0), "\\");
assert_eq!(comp_quoting_string(QT_BACKSLASH), "\\");
}
#[test]
fn matcheq_equal_strings() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.str = Some("foo".into());
let mut b = Cmatch::default();
b.str = Some("foo".into());
assert!(matcheq(&a, &b));
}
#[test]
fn matcheq_different_strings() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.str = Some("foo".into());
let mut b = Cmatch::default();
b.str = Some("bar".into());
assert!(!matcheq(&a, &b));
}
#[test]
fn matcheq_one_side_none() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.pre = Some("p".into());
let b = Cmatch::default();
assert!(!matcheq(&a, &b));
}
#[test]
fn get_user_var_reads_array_from_paramtab() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
setaparam("__test_arr", vec!["a".into(), "bb".into(), "ccc".into()]);
let got = get_user_var(Some("__test_arr"));
assert_eq!(got, Some(vec!["a".into(), "bb".into(), "ccc".into()]));
setaparam("__test_arr", vec![]);
}
#[test]
fn get_user_var_reads_scalar_as_single_element_array() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
setsparam("__test_scalar", "hello");
let got = get_user_var(Some("__test_scalar"));
assert_eq!(got, Some(vec!["hello".to_string()]));
setsparam("__test_scalar", "");
}
#[test]
fn get_user_var_paren_list_splits_on_separators() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let got = get_user_var(Some("(one two three)"));
assert_eq!(got, Some(vec!["one".into(), "two".into(), "three".into()]));
}
#[test]
fn get_user_var_none_for_missing() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let got = get_user_var(Some("__definitely_not_a_param_xyz"));
assert_eq!(got, None);
}
#[test]
fn get_data_arr_reads_hashed_keys_or_values() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::params::sethparam(
"__test_hash",
vec!["k1".into(), "v1".into(), "k2".into(), "v2".into()],
);
let keys = get_data_arr("__test_hash", true);
assert!(keys.is_some(), "hashed param should have keys");
let mut keys = keys.unwrap();
keys.sort();
assert_eq!(keys, vec!["k1".to_string(), "k2".to_string()]);
let vals = get_data_arr("__test_hash", false);
assert!(vals.is_some(), "hashed param should have values");
let mut vals = vals.unwrap();
vals.sort();
assert_eq!(vals, vec!["v1".to_string(), "v2".to_string()]);
}
#[test]
fn get_data_arr_none_for_non_hashed() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
setsparam("__test_scalar2", "value");
let got = get_data_arr("__test_scalar2", false);
assert_eq!(got, None, "scalar params must NOT come out of get_data_arr");
}
#[test]
fn before_complete_snapshots_oldmenucmp() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
MENUCMP.store(7, Ordering::Relaxed);
OLDMENUCMP.store(0, Ordering::Relaxed);
let mut lst = 0;
let _ = before_complete(&mut lst);
assert_eq!(OLDMENUCMP.load(Ordering::Relaxed), 7);
MENUCMP.store(0, Ordering::Relaxed);
OLDMENUCMP.store(0, Ordering::Relaxed);
}
#[test]
fn before_complete_clears_showagain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
SHOWAGAIN.store(5, Ordering::Relaxed);
let mut lst = 0;
let _ = before_complete(&mut lst);
assert_eq!(
SHOWAGAIN.load(Ordering::Relaxed),
0,
"SHOWAGAIN must be cleared by before_complete"
);
}
#[test]
fn remsquote_default_quoting() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut s = String::from("a'\\''b");
let n = remsquote(&mut s);
assert_eq!(s, "a'b");
assert_eq!(n, 3);
}
#[test]
fn ctokenize_dollar_substitution() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let out = ctokenize("$x{y}");
let chars: Vec<char> = out.chars().collect();
assert_eq!(chars[0], Stringg);
assert_eq!(chars[1], 'x');
assert_eq!(chars[2], Inbrace);
assert_eq!(chars[3], 'y');
assert_eq!(chars[4], Outbrace);
}
#[test]
fn get_user_var_inline_list() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let result = get_user_var(Some("(a b c)")).unwrap();
assert_eq!(result, vec!["a", "b", "c"]);
}
#[test]
fn matchcmp_str_sort_default() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
MATCHORDER.store(CGF_MATSORT, Ordering::Relaxed);
let mut a = Cmatch::default();
a.str = Some("apple".into());
let mut b = Cmatch::default();
b.str = Some("banana".into());
assert_eq!(matchcmp(&a, &b), std::cmp::Ordering::Less);
assert_eq!(matchcmp(&b, &a), std::cmp::Ordering::Greater);
assert_eq!(matchcmp(&a, &a), std::cmp::Ordering::Equal);
MATCHORDER.store(0, Ordering::Relaxed);
}
#[test]
fn dupmatch_clones_strings_and_truncates_braces() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut src = Cmatch::default();
src.str = Some("foo".into());
src.ipre = Some("ipre".into());
src.flags = 7;
src.brpl = vec![10, 20, 30, 40];
src.brsl = vec![5, 6, 7];
src.qipl = 1;
src.qisl = 2;
src.mode = 0o755;
src.modec = 'd';
let r = dupmatch(&src, 2, 1);
assert_eq!(r.str.as_deref(), Some("foo"));
assert_eq!(r.ipre.as_deref(), Some("ipre"));
assert_eq!(r.flags, 7);
assert_eq!(r.brpl, vec![10, 20]); assert_eq!(r.brsl, vec![5]); assert_eq!(r.qipl, 1);
assert_eq!(r.qisl, 2);
assert_eq!(r.mode, 0o755);
assert_eq!(r.modec, 'd');
}
#[test]
fn dupmatch_empty_braces_stay_empty() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let src = Cmatch::default();
let r = dupmatch(&src, 5, 5);
assert!(r.brpl.is_empty());
assert!(r.brsl.is_empty());
}
#[test]
fn makearray_sorted_and_deduped() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.str = Some("z".into());
let mut b = Cmatch::default();
b.str = Some("a".into());
let mut c = Cmatch::default();
c.str = Some("a".into());
let (arr, n, _nl, _ll) = makearray(vec![a, b, c], CGF_MATSORT);
assert_eq!(arr.len(), 2);
assert_eq!(n, 2);
assert_eq!(arr[0].str.as_deref(), Some("a"));
assert_eq!(arr[1].str.as_deref(), Some("z"));
}
#[test]
fn makearray_nosort_unchanged_order() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.str = Some("z".into());
let mut b = Cmatch::default();
b.str = Some("a".into());
let (arr, n, _, _) = makearray(vec![a, b], CGF_NOSORT | CGF_UNIQALL);
assert_eq!(n, 2);
assert_eq!(arr[0].str.as_deref(), Some("z"));
assert_eq!(arr[1].str.as_deref(), Some("a"));
}
#[test]
fn makearray_strings_dedup_consecutive() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (arr, n) = makearray_strings(vec!["b".into(), "a".into(), "a".into(), "c".into()], 1);
assert_eq!(n, 3);
assert_eq!(arr, vec!["a", "b", "c"]);
}
#[test]
fn check_param_no_dollar_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
OFFS.store(2, Ordering::Relaxed);
assert_eq!(check_param("abc", false, false), None);
}
#[test]
fn check_param_simple_dollar_var_at_cursor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
OFFS.store(2, Ordering::Relaxed);
let s = format!("{}FOO", Stringg);
let r = check_param(&s, false, true);
assert!(r.is_some(), "expected Some(b) inside $FOO");
}
#[test]
fn callcompfunc_empty_fn_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
callcompfunc("anything", "");
}
#[test]
fn callcompfunc_sets_compstate_context() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
ispar.store(0, Ordering::Relaxed);
linwhat.store(IN_PAR_LW, Ordering::Relaxed);
assert_eq!(compcontext_for("foo"), "assign_parameter");
callcompfunc("foo", "_test_fn");
}
static GLOBAL_MUT_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn compcontext_for_routes_ispar_first() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
ispar.store(2, Ordering::Relaxed);
linwhat.store(IN_NOTHING_LW, Ordering::Relaxed);
assert_eq!(compcontext_for("x"), "brace_parameter");
ispar.store(1, Ordering::Relaxed);
assert_eq!(compcontext_for("x"), "parameter");
ispar.store(0, Ordering::Relaxed);
linwhat.store(IN_MATH_LW, Ordering::Relaxed);
assert_eq!(compcontext_for("x"), "math");
linwhat.store(IN_COND_LW, Ordering::Relaxed);
assert_eq!(compcontext_for("x"), "condition");
linwhat.store(IN_ENV_LW, Ordering::Relaxed);
assert_eq!(compcontext_for("x"), "value");
linwhat.store(IN_NOTHING_LW, Ordering::Relaxed);
assert_eq!(compcontext_for("x"), "command");
}
#[test]
fn addmatches_empty_argv_early_return() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut dat = Cadata::default();
dat.dummies = 0;
dat.aflags = 0;
assert_eq!(addmatches(&mut dat, &[]), 1);
}
#[test]
fn addmatches_appends_argv_to_default_group() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clear();
matches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clear();
let mut dat = Cadata::default();
dat.dummies = -1;
let _ = addmatches(&mut dat, &["a".into(), "b".into()]);
let n = matches.get().unwrap().lock().unwrap().len();
assert!(n >= 2);
}
#[test]
fn add_match_data_returns_populated_cmatch() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
matches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clear();
let before = mnum.load(Ordering::Relaxed);
let cm = add_match_data(
0,
"match",
"match-orig",
None,
"ipre",
"ripre",
"isuf",
"pre",
"prpre",
"ppre",
None,
"psuf",
None,
"suf",
0,
0,
);
assert_eq!(cm.str.as_deref(), Some("match"));
assert_eq!(cm.orig.as_deref(), Some("match-orig"));
assert_eq!(cm.pre.as_deref(), Some("pre"));
assert_eq!(cm.suf.as_deref(), Some("suf"));
assert_eq!(mnum.load(Ordering::Relaxed), before + 1);
}
#[test]
fn add_match_data_exact_records_into_ainfo() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
let saved_useexact = useexact.load(Ordering::Relaxed);
useexact.store(1, Ordering::Relaxed);
if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(Aminfo::default());
}
let _ = add_match_data(
0, "x", "x", None, "", "", "", "", "", "", None, "", None, "", 0, 1,
);
let a = ainfo.get().unwrap().lock().unwrap().clone().unwrap();
useexact.store(saved_useexact, Ordering::Relaxed);
assert_eq!(a.exact, 1);
assert!(a.exactm.is_some());
}
#[test]
fn set_comp_sep_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(set_comp_sep(), 1);
}
#[test]
fn foredel_deletes_forward_from_zlemetacs() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
*g = "abcdef".to_string();
}
ZLEMETACS.store(2, Ordering::Relaxed);
ZLEMETALL.store(6, Ordering::Relaxed);
foredel(3, CUT_RAW);
let line = ZLEMETALINE.get().unwrap().lock().unwrap().clone();
assert_eq!(line, "abf");
assert_eq!(ZLEMETALL.load(Ordering::Relaxed), 3);
}
#[test]
fn inststr_inserts_at_zlemetacs() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
*g = "hello".to_string();
}
ZLEMETACS.store(5, Ordering::Relaxed);
ZLEMETALL.store(5, Ordering::Relaxed);
let _ = inststr(" world");
let line = ZLEMETALINE.get().unwrap().lock().unwrap().clone();
assert_eq!(line, "hello world");
assert_eq!(ZLEMETACS.load(Ordering::Relaxed), 11);
}
#[test]
fn metafy_and_unmetafy_roundtrip_globals() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
if let Ok(mut g) = ZLELINE.get_or_init(|| Mutex::new(String::new())).lock() {
*g = "plain ascii".to_string();
}
ZLECS.store(3, Ordering::Relaxed);
ZLELL.store(11, Ordering::Relaxed);
metafy_line();
assert_eq!(
ZLEMETALINE.get().unwrap().lock().unwrap().clone(),
"plain ascii"
);
assert_eq!(ZLEMETACS.load(Ordering::Relaxed), 3);
unmetafy_line();
assert_eq!(
ZLELINE.get().unwrap().lock().unwrap().clone(),
"plain ascii"
);
}
#[test]
fn selfinsert_appends_lastchar_at_zlecs() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
{
let mut g = crate::ported::zle::zle_main::ZLELINE.lock().unwrap();
*g = "ab".chars().collect();
}
crate::ported::zle::zle_main::ZLECS.store(2, Ordering::Relaxed);
crate::ported::zle::zle_main::ZLELL.store(2, Ordering::Relaxed);
LASTCHAR.store(b'c' as i32, Ordering::Relaxed);
LASTCHAR_WIDE_VALID.store(0, Ordering::Relaxed);
let rv = selfinsert();
assert_eq!(rv, 0);
let buf: String = crate::ported::zle::zle_main::ZLELINE
.lock()
.unwrap()
.iter()
.collect();
assert_eq!(buf, "abc");
assert_eq!(
crate::ported::zle::zle_main::ZLECS.load(Ordering::Relaxed),
3,
);
}
#[test]
fn minfo_clear_and_asked_zero_mutate_state() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
let mut cm = Cmatch::default();
cm.str = Some("x".into());
g.cur = Some(Box::new(cm));
g.asked = 1;
}
if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = None;
}
if let Ok(mut g) = MINFO
.get_or_init(|| Mutex::new(Menuinfo::default()))
.lock()
{
g.asked = 0;
}
let m = MINFO.get().unwrap().lock().unwrap().clone();
assert!(m.cur.is_none());
assert_eq!(m.asked, 0);
}
#[test]
fn cline_matched_stub_marks_node() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
cline_matched_compcore(Some("foo"));
cline_matched_compcore(None);
cline_matched_compcore(Some(""));
}
#[test]
fn permmatches_returns_fi_zero_when_count_present() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _g = GLOBAL_MUT_LOCK.lock().unwrap();
amatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clear();
pmatches
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clear();
if let Ok(mut a) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
*a = Some(Aminfo {
count: 5,
..Default::default()
});
}
newmatches.store(1, Ordering::Relaxed);
let fi = permmatches(0);
assert_eq!(fi, 0);
assert_eq!(hasperm.load(Ordering::Relaxed), 1);
}
#[test]
fn rembslash_unescapes_canonical_pairs() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(r"\a"), "a");
assert_eq!(rembslash(r"\\"), r"\");
assert_eq!(rembslash(r"\$foo"), "$foo");
assert_eq!(rembslash("plain"), "plain");
}
#[test]
fn rembslash_empty_input_returns_empty() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(""), "");
}
#[test]
fn rembslash_trailing_lone_backslash_drops_silently() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(r"foo\"), "foo");
}
#[test]
fn ctokenize_passes_alphanumerics_through() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ctokenize("foo123"), "foo123");
assert_eq!(ctokenize(""), "");
assert_eq!(ctokenize("path/to"), "path/to");
}
#[test]
fn comp_quoting_string_dispatches_known_styles() {
let _g = crate::test_util::global_state_lock();
for stype in 0..=8 {
let _ = comp_quoting_string(stype);
}
}
#[test]
fn multiquote_empty_stack_returns_input_unchanged() {
let _g = crate::test_util::global_state_lock();
if let Some(c) = COMPQSTACK.get() {
if let Ok(mut g) = c.lock() {
g.clear();
}
}
assert_eq!(multiquote("hello", 0), "hello");
assert_eq!(multiquote("", 0), "");
}
#[test]
fn tildequote_non_tilde_input_unchanged() {
let _g = crate::test_util::global_state_lock();
if let Some(c) = COMPQSTACK.get() {
if let Ok(mut g) = c.lock() {
g.clear();
}
}
assert_eq!(tildequote("foo/bar", 0), "foo/bar");
}
#[test]
fn tildequote_empty_input_empty_output() {
let _g = crate::test_util::global_state_lock();
if let Some(c) = COMPQSTACK.get() {
if let Ok(mut g) = c.lock() {
g.clear();
}
}
assert_eq!(tildequote("", 0), "");
}
#[test]
fn compcore_corpus_rembslash_strips_escapes() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(r"\a\b\c"), "abc");
}
#[test]
fn compcore_corpus_rembslash_no_escapes_identity() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash("hello"), "hello");
}
#[test]
fn compcore_corpus_rembslash_empty_is_empty() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(""), "");
}
#[test]
fn compcore_corpus_rembslash_escaped_backslash() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(r"\\"), r"\");
}
#[test]
fn compcore_corpus_rembslash_preserves_context() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(r"hello\ world"), "hello world");
}
#[test]
fn compcore_corpus_rembslash_trailing_backslash_consumed() {
let _g = crate::test_util::global_state_lock();
assert_eq!(rembslash(r"abc\"), "abc");
}
}