use std::sync::atomic::Ordering;
use std::sync::atomic::Ordering::Relaxed;
use crate::ported::init::SHTTY;
use crate::ported::utils::{adjustcolumns, adjustlines, write_loop, zputs};
use crate::ported::zle::comp_h::{
Aminfo, Chdata, Cldata, Cline, Cmatch, Cmgroup, Menuinfo, CGF_FILES, CGF_HASDL, CGF_LINES,
CGF_PACKED, CGF_ROWS, CLF_DIFF, CLF_JOIN, CLF_LINE, CLF_MATCHED, CLF_MID, CLF_MISS, CLF_NEW,
CLF_SUF, CMF_ALL, CMF_DISPLINE, CMF_DUMMY, CMF_FILE, CMF_HIDE, CMF_ISPAR, CMF_MULT, CMF_NOLIST,
CMF_NOSPACE, CMF_PACKED, CMF_PARBR, CMF_PARNEST, CMF_REMOVE, CMF_ROWS,
};
use crate::ported::zle::compcore::{
amatches, brpcs, brscs, eparq, fromcomp, iforcemenu, insmnum, insspace, lastend, lastmatches,
lastpermmnum, lmatches, menuacc, metafy_line, movetoend, nmatches as nmatches_g, nmatches,
oldins, oldlist, onlyexpl, parpre, parq, unmetafy_line, BRBEG, BREND, MINFO, WB, WE, ZLEMETACS,
ZLEMETALINE, ZLEMETALL, ZMULT,
};
use crate::ported::zle::complete::COMPLISTMAX;
use crate::ported::zle::computil::CM_SPACE;
use crate::ported::zle::zle_h::COMP_LIST_COMPLETE;
use crate::ported::zle::zle_refresh::tcout;
use crate::ported::zle::zle_tricky::printfmt;
#[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::{
isset, AUTOPARAMKEYS, AUTOPARAMSLASH, AUTOREMOVESLASH, LISTPACKED, LISTROWSFIRST, LISTTYPES,
USEZLE,
};
use crate::ported::zle::compmatch::{cline_setlens, cline_sublen, cp_cline};
use crate::ported::zle::zle_h::{CUT_RAW, SUFTYP_POSSTR};
use crate::ported::zle::zle_misc::suffixlen;
use crate::ported::module::{gethookdef, runhookdef};
use crate::ported::exec::noerrs;
use crate::ported::utils::errflag;
use crate::ported::zsh_h::ERRFLAG_ERROR;
use crate::ported::params::paramtab;
use crate::ported::zsh_h::{PM_SCALAR, PM_TYPE};
use crate::ported::lex::parsestr;
use crate::ported::subst::singsub;
#[allow(unused_imports)]
#[allow(unused_imports)]
pub fn cut_cline(l: Option<Box<Cline>>) -> Option<Box<Cline>> {
use crate::ported::zle::compcore::{hasmatched, minmlen};
if hasmatched.load(Relaxed) == 0 {
let mut ll = l;
cline_setlens(&mut ll, 0); return ll; }
let copied = cp_cline(l.as_deref(), 0);
let mut nodes: Vec<Box<Cline>> = Vec::new();
let mut cur = copied;
while let Some(mut node) = cur {
cur = node.next.take();
nodes.push(node);
}
let n0 = nodes.len();
if n0 == 0 {
return None;
}
let mut e: Option<usize> = Some(0); let mut q: Option<usize> = None;
for i in 0..n0 {
let p = &nodes[i];
if p.orig.is_some() || p.olen != 0 || (p.flags & CLF_NEW) == 0 {
e = if i + 1 < n0 { Some(i + 1) } else { None }; }
if p.suffix.is_none() && (p.wlen != 0 || p.llen != 0 || p.prefix.is_some()) {
q = Some(i); }
}
if e.is_none() {
if let Some(qi) = q {
let cond = {
let qn = &nodes[qi];
qn.orig.is_none()
&& qn.olen == 0
&& (qn.flags & CLF_MISS) != 0
&& ((qn.flags & CLF_MATCHED) == 0 || (qn.prefix.is_none() && qn.suffix.is_none()))
&& (if qn.word.is_some() { qn.wlen } else { qn.llen }) < 3
};
if cond {
let qn = &mut nodes[qi];
qn.word = None; qn.line = None;
qn.wlen = 0; qn.llen = 0;
}
}
}
while let Some(ei) = e {
if (nodes[ei].flags & CLF_MISS) != 0 {
break;
}
e = if ei + 1 < n0 { Some(ei + 1) } else { None };
}
let mut ls = false;
let mut end_len = nodes.len();
if let Some(mut ei) = e {
let mut pidx: Option<usize> = if ei + 1 < n0 { Some(ei + 1) } else { None };
while let Some(pi) = pidx {
if (nodes[pi].flags & CLF_MISS) != 0 {
break;
}
pidx = if pi + 1 < n0 { Some(pi + 1) } else { None };
}
if pidx.is_some() {
let mut sum = 0i32;
let mut max = 0i32;
let mut maxp: Option<usize> = None;
for pi in ei..n0 {
let p = &nodes[pi];
if (p.flags & CLF_MISS) == 0 {
sum += p.max; } else {
let tmp = cline_sublen(p); if tmp > 2 && tmp > ((p.max + p.min) >> 1) {
sum += tmp - (p.max - tmp); } else if tmp < p.min {
sum -= (((p.max + p.min) >> 1) - tmp) << (if tmp < 2 { 1 } else { 0 });
}
}
if sum > max {
max = sum; maxp = Some(pi); }
}
let mut do_truncate = true;
if max != 0 {
ei = maxp.unwrap(); } else {
for node in nodes.iter_mut() {
node.min = cline_sublen(node); }
ls = true; let mut len = 0i32;
for pi in ei..nodes.len() {
len += nodes[pi].min; }
if len > ((minmlen.load(Relaxed) << 1) / 3) {
do_truncate = false; }
}
if do_truncate {
{
let en = &mut nodes[ei];
en.line = None; en.word = None;
en.llen = 0; en.wlen = 0;
en.olen = 0;
}
nodes.truncate(ei + 1); end_len = nodes.len();
}
}
}
let _ = end_len;
let mut ee: Option<usize> = None; let mut tmp2 = 0i32; let mut miss = 0i32;
for pi in 0..nodes.len() {
let p = &nodes[pi];
if (p.flags & (CLF_MISS | CLF_DIFF)) != 0 {
miss = 1; }
let mut qq = p.prefix.as_deref();
while let Some(qn) = qq {
if (qn.flags & CLF_JOIN) != 0 {
ee = Some(pi);
tmp2 = 0;
break;
}
qq = qn.next.as_deref();
}
let mut qq = p.suffix.as_deref();
while let Some(qn) = qq {
if (qn.flags & CLF_JOIN) != 0 {
ee = Some(pi);
tmp2 = 1;
break;
}
qq = qn.next.as_deref();
}
}
if let Some(ei) = ee {
let cond = {
let e = &nodes[ei];
miss == 0 || cline_sublen(e) == e.min };
if cond {
let sub = if tmp2 != 0 {
&mut nodes[ei].suffix
} else {
&mut nodes[ei].prefix
};
if let Some(head) = sub.as_deref_mut() {
let mut node: &mut Cline = head;
loop {
let advance = match node.next.as_deref() {
Some(nx) => (nx.flags & CLF_JOIN) == 0,
None => false,
};
if advance {
node = node.next.as_deref_mut().unwrap();
} else {
break;
}
}
node.next = None; }
}
}
let mut head: Option<Box<Cline>> = None;
while let Some(mut node) = nodes.pop() {
node.next = head.take();
head = Some(node);
}
if !ls {
cline_setlens(&mut head, 0); }
head }
pub fn cline_str(
l: Option<Box<Cline>>,
ins: i32,
mut csp: Option<&mut i32>,
mut posl: Option<&mut Vec<i32>>,
) -> Option<String> {
let cs = || ZLEMETACS.load(Relaxed);
let set_cs = |v: i32| ZLEMETACS.store(v, Relaxed);
let l = cut_cline(l);
let ocs = cs(); let wb = WB.load(Relaxed);
let we = WE.load(Relaxed);
let padd = if ins != 0 { wb - ocs } else { -ocs };
let (mut pcs, mut scs) = (0i32, 0i32);
let mut opos = -1i32;
let (mut pmm, mut pma, mut smm, mut sma, mut dm) = (0i32, 0i32, 0i32, 0i32, 0i32);
let (mut pm, mut pmax, mut sm, mut smax, mut d, mut mid, mut cbr) =
(-1i32, -1i32, -1i32, -1i32, -1i32, -1i32, -1i32);
let mut li = 0i32;
let mut brbeg_v: Vec<(String, i32, i32)> = Vec::new();
let mut brend_v: Vec<(String, i32, i32)> = Vec::new();
let mut brp_idx = 0usize;
let mut brs_idx: i32 = -1;
if ins != 0 {
{
let mut cur = BRBEG.get().and_then(|m| m.lock().ok());
if let Some(ref g) = cur {
let mut node = g.as_deref();
while let Some(n) = node {
brbeg_v.push((n.str.clone().unwrap_or_default(), n.qpos, 0));
node = n.next.as_deref();
}
}
let _ = cur.take();
let mut cur = BREND.get().and_then(|m| m.lock().ok());
if let Some(ref g) = cur {
let mut node = g.as_deref();
while let Some(n) = node {
brend_v.push((n.str.clone().unwrap_or_default(), n.qpos, 0));
node = n.next.as_deref();
}
}
let _ = cur.take();
}
let mut olen = we - wb; for b in brbeg_v.iter_mut() {
b.2 = b.1; olen -= b.0.len() as i32; }
if !brend_v.is_empty() {
for b in brend_v.iter() {
olen -= b.0.len() as i32; }
for b in brend_v.iter_mut() {
b.2 = olen - b.1; }
brs_idx = brend_v.len() as i32 - 1; }
brp_idx = 0;
while brp_idx < brbeg_v.len() && brbeg_v[brp_idx].2 == 0 {
inststrlen(&brbeg_v[brp_idx].0, true, -1);
brp_idx += 1;
}
while brs_idx >= 0 && brend_v[brs_idx as usize].2 == 0 {
if cbr < 0 {
cbr = cs();
}
inststrlen(&brend_v[brs_idx as usize].0, true, -1);
brs_idx -= 1;
}
}
let mut cur = l.as_deref();
while let Some(node) = cur {
if node.olen != 0 && (node.flags & CLF_SUF) == 0 && node.prefix.is_none() {
pcs = cs() + node.olen; inststrlen(node.orig.as_deref().unwrap_or(""), true, node.olen); } else {
let mut sp = node.prefix.as_deref();
while let Some(s) = sp {
pcs = cs() + s.llen; if (s.flags & CLF_LINE) != 0 {
inststrlen(s.line.as_deref().unwrap_or(""), true, s.llen); } else {
inststrlen(s.word.as_deref().unwrap_or(""), true, s.wlen); }
scs = cs(); if (s.flags & CLF_DIFF) != 0 && (dm == 0 || (s.flags & CLF_MATCHED) != 0) {
d = cs(); dm = s.flags & CLF_MATCHED;
if let Some(pl) = posl.as_mut() {
let np = cs() + padd;
if np != opos {
opos = np;
pl.push(np); }
}
}
li += s.llen; sp = s.next.as_deref();
}
}
if ins != 0 {
while brp_idx < brbeg_v.len() && li >= brbeg_v[brp_idx].2 {
let ocs2 = cs();
let bl = brbeg_v[brp_idx].0.len() as i32;
set_cs(pcs - (li - brbeg_v[brp_idx].2));
inststrlen(&brbeg_v[brp_idx].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
scs += bl;
brp_idx += 1;
}
}
if (node.flags & CLF_MISS) != 0 && (node.flags & CLF_SUF) == 0 {
if let Some(pl) = posl.as_mut() {
let np = cs() + padd;
if np != opos {
opos = np;
pl.push(np);
}
}
if ((pmax <= (node.max - node.min) || (pma != 0 && node.max != node.min))
&& (pmm == 0 || (node.flags & CLF_MATCHED) != 0))
|| ((node.flags & CLF_MATCHED) != 0 && pmm == 0)
{
pm = cs();
pmax = node.max - node.min;
pmm = node.flags & CLF_MATCHED;
pma = ((node.prefix.is_some() || node.suffix.is_some())
&& node.min == cline_sublen(node)) as i32;
}
}
if ins != 0 {
while brs_idx >= 0 && li >= brend_v[brs_idx as usize].2 {
let ocs2 = cs();
let bl = brend_v[brs_idx as usize].0.len() as i32;
set_cs(scs - (li - brend_v[brs_idx as usize].2));
if cbr < 0 {
cbr = cs();
}
inststrlen(&brend_v[brs_idx as usize].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
brs_idx -= 1;
}
}
pcs = cs(); if (node.flags & CLF_LINE) != 0 {
inststrlen(node.line.as_deref().unwrap_or(""), true, node.llen);
} else {
inststrlen(node.word.as_deref().unwrap_or(""), true, node.wlen);
}
scs = cs(); if ins != 0 {
li += node.llen; while brp_idx < brbeg_v.len() && li >= brbeg_v[brp_idx].2 {
let ocs2 = cs();
let bl = brbeg_v[brp_idx].0.len() as i32;
set_cs(pcs + node.llen - (li - brbeg_v[brp_idx].2));
inststrlen(&brbeg_v[brp_idx].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
scs += bl;
brp_idx += 1;
}
}
if (node.flags & CLF_MISS) != 0 {
if (node.flags & CLF_MID) != 0 {
mid = cs(); } else if (node.flags & CLF_SUF) != 0 {
if let Some(pl) = posl.as_mut() {
let np = cs() + padd;
if np != opos {
opos = np;
pl.push(np);
}
}
if ((smax <= (node.min - node.max) || (sma != 0 && node.max != node.min))
&& (smm == 0 || (node.flags & CLF_MATCHED) != 0))
|| ((node.flags & CLF_MATCHED) != 0 && smm == 0)
{
sm = cs();
smax = node.min - node.max;
smm = node.flags & CLF_MATCHED;
sma = ((node.prefix.is_some() || node.suffix.is_some())
&& node.min == cline_sublen(node)) as i32;
}
}
}
if ins != 0 {
while brs_idx >= 0 && li >= brend_v[brs_idx as usize].2 {
let ocs2 = cs();
let bl = brend_v[brs_idx as usize].0.len() as i32;
set_cs(scs - (li - brend_v[brs_idx as usize].2));
if cbr < 0 {
cbr = cs();
}
inststrlen(&brend_v[brs_idx as usize].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
brs_idx -= 1;
}
}
if node.olen != 0 && (node.flags & CLF_SUF) != 0 && node.suffix.is_none() {
pcs = cs(); inststrlen(node.orig.as_deref().unwrap_or(""), true, node.olen); if ins != 0 {
li += node.olen; while brp_idx < brbeg_v.len() && li >= brbeg_v[brp_idx].2 {
let ocs2 = cs();
let bl = brbeg_v[brp_idx].0.len() as i32;
set_cs(pcs + node.olen - (li - brbeg_v[brp_idx].2));
inststrlen(&brbeg_v[brp_idx].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
brp_idx += 1;
}
while brs_idx >= 0 && li >= brend_v[brs_idx as usize].2 {
let ocs2 = cs();
let bl = brend_v[brs_idx as usize].0.len() as i32;
set_cs(pcs + node.olen - (li - brend_v[brs_idx as usize].2));
if cbr < 0 {
cbr = cs();
}
inststrlen(&brend_v[brs_idx as usize].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
brs_idx -= 1;
}
}
} else {
let mut jflag: i32 = -1;
let mut js_matched = 0i32;
let mut i_acc = 0i32;
let mut sp = node.suffix.as_deref();
while let Some(s) = sp {
if jflag < 0 && (s.flags & CLF_DIFF) != 0 {
jflag = i_acc;
js_matched = s.flags & CLF_MATCHED;
}
pcs = cs(); if (s.flags & CLF_LINE) != 0 {
inststrlen(s.line.as_deref().unwrap_or(""), false, s.llen); i_acc += s.llen;
scs = cs() + s.llen;
} else {
inststrlen(s.word.as_deref().unwrap_or(""), false, s.wlen); i_acc += s.wlen;
scs = cs() + s.wlen;
}
if ins != 0 {
li += s.llen; while brp_idx < brbeg_v.len() && li >= brbeg_v[brp_idx].2 {
let ocs2 = cs();
let bl = brbeg_v[brp_idx].0.len() as i32;
set_cs(pcs + (li - brbeg_v[brp_idx].2));
inststrlen(&brbeg_v[brp_idx].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
scs += bl;
brp_idx += 1;
}
while brs_idx >= 0 && li >= brend_v[brs_idx as usize].2 {
let ocs2 = cs();
let bl = brend_v[brs_idx as usize].0.len() as i32;
set_cs(scs - (li - brend_v[brs_idx as usize].2));
if cbr < 0 {
cbr = cs();
}
inststrlen(&brend_v[brs_idx as usize].0, true, bl);
set_cs(ocs2 + bl);
pcs += bl;
brs_idx -= 1;
}
}
sp = s.next.as_deref();
}
set_cs(cs() + i_acc); if jflag >= 0 && (dm == 0 || js_matched != 0) {
d = cs() - jflag; dm = js_matched;
if let Some(pl) = posl.as_mut() {
let np = cs() - jflag + padd;
if np != opos {
opos = np;
pl.push(np);
}
}
}
}
cur = node.next.as_deref(); }
if let Some(pl) = posl.as_mut() {
let np = cs() + padd;
if np != opos {
pl.push(np);
}
}
if ins != 0 {
let ocs2b = cs();
while brp_idx < brbeg_v.len() {
inststrlen(&brbeg_v[brp_idx].0, true, -1);
brp_idx += 1;
}
while brs_idx >= 0 {
if cbr < 0 {
cbr = cs();
}
inststrlen(&brend_v[brs_idx as usize].0, true, -1);
brs_idx -= 1;
}
let shift = cs() - ocs2b;
if mid >= ocs2b {
mid += shift;
}
if pm >= ocs2b {
pm += shift;
}
if sm >= ocs2b {
sm += shift;
}
if d >= ocs2b {
d += shift;
}
if let Some(pl) = posl.as_mut() {
for p in pl.iter_mut() {
if *p >= ocs2b {
*p += shift;
}
}
}
}
let ncs = if mid >= 0 {
mid
} else if cbr >= 0 {
cbr
} else if pm >= 0 {
pm
} else if sm >= 0 {
sm
} else if d >= 0 {
d
} else {
cs()
};
if ins != 1 {
let cur_cs = cs();
let ilen = cur_cs - ocs;
let r = ZLEMETALINE
.get()
.and_then(|m| m.lock().ok())
.map(|g| {
let b = g.as_bytes();
let start = (ocs.max(0) as usize).min(b.len());
let end = (cur_cs.max(0) as usize).min(b.len()).max(start);
String::from_utf8_lossy(&b[start..end]).into_owned()
})
.unwrap_or_default();
set_cs(ocs);
foredel(ilen, CUT_RAW); if let Some(cp) = csp.as_deref_mut() {
*cp = ncs - ocs; }
Some(r) } else {
lastend.store(cs(), Relaxed); set_cs(ncs); None }
}
pub fn build_pos_string(list: &[i32]) -> String {
let mut s = String::new();
for (i, p) in list.iter().enumerate() {
if i != 0 {
s.push(':'); }
s.push_str(&p.to_string()); }
s }
pub fn unambig_data(matches: &[String]) -> String {
if matches.is_empty() {
return String::new();
}
if matches.len() == 1 {
return matches[0].clone();
}
let first = &matches[0];
let mut prefix_len = first.len();
for m in &matches[1..] {
let common = first
.chars()
.zip(m.chars())
.take_while(|(a, b)| a == b)
.count();
prefix_len = prefix_len.min(common);
}
first[..first
.char_indices()
.nth(prefix_len)
.map(|(i, _)| i)
.unwrap_or(first.len())]
.to_string()
}
pub fn instmatch(
m: &Cmatch,
mut scs: Option<&mut i32>,
) -> i32 {
let cs = || ZLEMETACS.load(Relaxed);
let set_cs = |v: i32| ZLEMETACS.store(v, Relaxed);
let mut r = 0i32;
let a0 = cs();
if let Some(mx) = LASTPREBR.get() {
if let Ok(mut g) = mx.lock() {
g.clear();
}
}
if let Some(mx) = LASTPOSTBR.get() {
if let Ok(mut g) = mx.lock() {
g.clear();
}
}
if let Some(s) = m.ipre.as_deref() {
let l = s.len() as i32;
inststrlen(s, true, l);
r += l;
}
if let Some(s) = m.pre.as_deref() {
let l = s.len() as i32;
inststrlen(s, true, l);
r += l;
}
if let Some(s) = m.ppre.as_deref() {
let l = s.len() as i32;
inststrlen(s, true, l);
r += l;
}
{
let s = m.str.as_deref().unwrap_or("");
let l = s.len() as i32;
inststrlen(s, true, l);
r += l;
}
let mut ocs = cs();
let brbeg_v: Vec<String> = {
let mut v = Vec::new();
if let Some(g) = BRBEG.get().and_then(|m| m.lock().ok()) {
let mut node = g.as_deref();
while let Some(nn) = node {
v.push(nn.str.clone().unwrap_or_default());
node = nn.next.as_deref();
}
}
v
};
let brend_v: Vec<String> = {
let mut v = Vec::new();
if let Some(g) = BREND.get().and_then(|m| m.lock().ok()) {
let mut node = g.as_deref();
while let Some(nn) = node {
v.push(nn.str.clone().unwrap_or_default());
node = nn.next.as_deref();
}
}
v
};
if !brbeg_v.is_empty() {
let mut pcs = cs(); let mut bradd = m.pre.as_deref().map_or(0, |s| s.len() as i32); for (i, bs) in brbeg_v.iter().enumerate() {
let brpos = *m.brpl.get(i).unwrap_or(&0); set_cs(a0 + brpos + bradd); pcs = cs(); let l = bs.len() as i32; bradd += l; brpcs.store(cs(), Relaxed); inststrlen(bs, true, l); r += l; ocs += l; }
let prebr = {
ZLEMETALINE
.get()
.and_then(|m| m.lock().ok())
.map(|g| {
let b = g.as_bytes();
let s = (a0.max(0) as usize).min(b.len());
let e = (pcs.max(0) as usize).min(b.len()).max(s);
String::from_utf8_lossy(&b[s..e]).into_owned()
})
.unwrap_or_default()
};
if let Ok(mut g) = LASTPREBR
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = prebr;
}
set_cs(ocs); }
if let Some(s) = m.psuf.as_deref() {
let l = s.len() as i32;
inststrlen(s, true, l);
r += l;
}
let mut brb = 0i32;
if !brend_v.is_empty() {
let a1 = cs(); let mut bradd = 0i32;
for (i, bs) in brend_v.iter().enumerate() {
let brpos = *m.brsl.get(i).unwrap_or(&0);
set_cs(a1 - brpos); ocs = cs(); brscs.store(ocs, Relaxed);
let l = bs.len() as i32;
bradd += l;
inststrlen(bs, true, l); brb = cs(); r += l;
}
set_cs(a1 + bradd); if let Some(s) = scs.as_deref_mut() {
*s = ocs; }
} else {
brscs.store(-1, Relaxed); if let Some(s) = scs.as_deref_mut() {
*s = cs(); }
}
if let Some(s) = m.suf.as_deref() {
let l = s.len() as i32;
inststrlen(s, true, l);
r += l;
}
if let Some(s) = m.isuf.as_deref() {
let l = s.len() as i32;
inststrlen(s, true, l);
r += l;
}
if !brend_v.is_empty() {
let end = cs();
let postbr = {
ZLEMETALINE
.get()
.and_then(|m| m.lock().ok())
.map(|g| {
let b = g.as_bytes();
let s = (brb.max(0) as usize).min(b.len());
let e = (end.max(0) as usize).min(b.len()).max(s);
String::from_utf8_lossy(&b[s..e]).into_owned()
})
.unwrap_or_default()
};
if let Ok(mut g) = LASTPOSTBR
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = postbr;
}
}
lastend.store(cs(), Relaxed); set_cs(ocs); r }
pub fn hasbrpsfx(m: &Cmatch, pre: Option<&str>, suf: Option<&str>) -> bool {
if (m.flags & CMF_ALL) != 0 {
return true;
}
let was_meta = ZLEMETALL.load(Relaxed) != 0;
if !was_meta {
metafy_line();
}
let op = LASTPREBR
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let os = LASTPOSTBR
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let oline = ZLEMETALINE
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let oll = ZLEMETALL.load(Relaxed);
let ole = lastend.load(Relaxed);
let opcs = brpcs.load(Relaxed);
let oscs = brscs.load(Relaxed);
let ometacs = ZLEMETACS.load(Relaxed);
zle_save_positions();
if let Some(mx) = LASTPREBR.get() {
if let Ok(mut g) = mx.lock() {
g.clear();
}
}
if let Some(mx) = LASTPOSTBR.get() {
if let Ok(mut g) = mx.lock() {
g.clear();
}
}
instmatch(m, None);
if let Ok(mut g) = ZLEMETALINE
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = oline;
}
ZLEMETALL.store(oll, Relaxed);
zle_restore_positions(); ZLEMETALL.store(oll, Relaxed); ZLEMETACS.store(ometacs, Relaxed); lastend.store(ole, Relaxed); brpcs.store(opcs, Relaxed); brscs.store(oscs, Relaxed);
let lpb = LASTPREBR
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let lsb = LASTPOSTBR
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let pre_ok = match pre {
None => lpb.is_empty(),
Some(p) => !lpb.is_empty() && p == lpb,
};
let suf_ok = match suf {
None => lsb.is_empty(),
Some(s) => !lsb.is_empty() && s == lsb,
};
let ret = pre_ok && suf_ok;
if let Ok(mut g) = LASTPREBR
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = op;
}
if let Ok(mut g) = LASTPOSTBR
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = os;
}
if !was_meta {
unmetafy_line(); }
ret }
pub fn do_ambiguous(matches: &[String]) -> i32 {
MENUCMP.store(0, Relaxed);
crate::ported::zle::compcore::menuacc.store(0, Relaxed);
LASTAMBIG.store(1, Relaxed);
let iforcemenu_top = crate::ported::zle::compcore::iforcemenu.load(Relaxed);
let usemenu_top = crate::ported::zle::zle_tricky::USEMENU.load(Relaxed);
if iforcemenu_top != -1 && usemenu_top != 0 {
let _ = do_ambig_menu();
} else {
let ainfo_line = crate::ported::zle::compcore::ainfo
.get_or_init(|| std::sync::Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.as_ref().and_then(|a| a.line.clone()));
let prefix = if let Some(line) = ainfo_line {
cline_str(Some(line), 0, None, None).unwrap_or_default()
} else {
unambig_data(matches)
};
if prefix.is_empty() && matches.is_empty() {
return 0; }
if !prefix.is_empty() {
let wb = crate::ported::zle::compcore::WB.load(Relaxed);
let we = crate::ported::zle::compcore::WE.load(Relaxed);
if we > wb && wb >= 0 {
let span = we - wb;
crate::ported::zle::compcore::ZLEMETACS.store(wb, Relaxed); foredel(span, CUT_RAW); let _ = inststr(&prefix); }
}
let origll = crate::ported::zle::zle_tricky::ORIGLL.load(Relaxed);
let zlemetall = crate::ported::zle::compcore::ZLEMETALL.load(Relaxed);
let origline_s = crate::ported::zle::zle_tricky::ORIGLINE
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let metaline_s = crate::ported::zle::compcore::ZLEMETALINE
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let la = zlemetall != origll || {
let n = (zlemetall.max(0) as usize)
.min(origline_s.len())
.min(metaline_s.len());
origline_s.as_bytes()[..n] != metaline_s.as_bytes()[..n]
};
let uselist_v = crate::ported::zle::compcore::uselist.load(Relaxed);
let iforcemenu_v = crate::ported::zle::compcore::iforcemenu.load(Relaxed);
if (uselist_v == 3
|| (uselist_v == 0
&& isset(crate::ported::zsh_h::BASHAUTOLIST)
&& isset(crate::ported::zsh_h::LISTAMBIGUOUS)))
&& la
&& iforcemenu_v != -1
{
crate::ported::zle::zle_h::invalidatelist();
crate::ported::zle::zle_tricky::LASTAMBIG.store(0, Relaxed);
crate::ported::zle::zle_refresh::CLEARLIST.store(1, Relaxed);
return 0;
}
}
let mut ret = 0;
let oldlist_v = crate::ported::zle::compcore::oldlist.load(Relaxed);
if isset(crate::ported::zsh_h::LISTBEEP) && oldlist_v == 0 {
ret = 1;
}
let uselist_v = crate::ported::zle::compcore::uselist.load(Relaxed);
let usemenu_v = crate::ported::zle::zle_tricky::USEMENU.load(Relaxed);
let listshown_v = LISTSHOWN.load(Relaxed);
let showinglist_v = SHOWINGLIST.load(Relaxed);
let smatches_v = crate::ported::zle::compcore::smatches.load(Relaxed);
let forcelist_v = crate::ported::zle::compcore::forcelist.load(Relaxed);
if uselist_v != 0
&& (usemenu_v != 2 || (listshown_v == 0 && oldlist_v == 0))
&& ((showinglist_v == 0 && (listshown_v == 0 || oldlist_v == 0))
|| (usemenu_v == 3 && oldlist_v == 0))
&& (smatches_v >= 2 || forcelist_v != 0)
{
SHOWINGLIST.store(-2, Relaxed);
}
ret
}
pub fn ztat(path: &str, follow_symlink: bool) -> Option<std::fs::Metadata> {
if follow_symlink {
std::fs::symlink_metadata(path).ok()
} else {
std::fs::metadata(path).ok()
}
}
pub fn do_allmatches(_end: i32) {
let mut first = true; let mut nm = nmatches.load(Relaxed) - 1; let omc = MENUCMP.load(Relaxed); let oma = menuacc.load(Relaxed);
let saved_brbeg: Option<String> = BRBEG
.get()
.and_then(|mx| mx.lock().ok())
.and_then(|g| {
g.as_deref().map(|head| {
let mut node = head;
while let Some(n) = node.next.as_deref() {
node = n;
}
node.str.clone().unwrap_or_default()
})
});
let mi_saved = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
MENUCMP.store(1, Relaxed); menuacc.store(0, Relaxed);
let groups = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let mut gi: usize = 0;
while gi < groups.len() && groups[gi].mcount == 0 {
gi += 1;
}
let mut have = gi < groups.len(); let mut mi: usize = 0;
while have {
if (groups[gi].matches[mi].flags & CMF_ALL) == 0 {
if !first {
accept_last(); }
first = false; if omc == 0 {
nm -= 1; if nm == 0 {
MENUCMP.store(0, Relaxed); }
}
let m = groups[gi].matches[mi].clone();
do_single(&m); }
{
let m = groups[gi].matches[mi].clone();
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = Some(Box::new(m));
g.group_idx = gi as i32;
g.cur_idx = mi as i32;
}
}
mi += 1;
if mi >= groups[gi].matches.len() {
loop {
gi += 1; if gi >= groups.len() {
break;
}
if groups[gi].mcount != 0 {
break;
}
}
if gi >= groups.len() {
have = false; break;
}
mi = 0; }
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.group_idx = gi as i32;
g.cur_idx = mi as i32;
g.cur = Some(Box::new(groups[gi].matches[mi].clone()));
}
}
MENUCMP.store(omc, Relaxed); menuacc.store(oma, Relaxed);
let e = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| g.end)
.unwrap_or(0);
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
*g = mi_saved;
g.end = e; g.len = e - g.pos; }
if let Some(s) = saved_brbeg {
if let Some(mx) = BRBEG.get() {
if let Ok(mut guard) = mx.lock() {
if let Some(head) = guard.as_mut() {
let mut node = head.as_mut();
while node.next.is_some() {
node = node.next.as_mut().unwrap();
}
node.str = Some(s);
}
}
}
}
}
pub fn do_single(m: &Cmatch) {
let cs = || ZLEMETACS.load(Relaxed);
let set_cs = |v: i32| ZLEMETACS.store(v, Relaxed);
let mut sr: i32 = 0; let mut havesuff = false; let parpre_nonempty = parpre
.get()
.and_then(|p| p.lock().ok())
.map(|g| !g.is_empty())
.unwrap_or(false);
let partest = m.ripre.is_some() || ((m.flags & CMF_ISPAR) != 0 && parpre_nonempty);
let strv = m.orig.as_deref().unwrap_or("");
let psuf = m.psuf.as_deref().unwrap_or("");
let prpre = m.prpre.as_deref().unwrap_or("");
fixsuffix();
let cur_present = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| g.cur.is_some())
.unwrap_or(false);
let wb = WB.load(Relaxed);
let we = WE.load(Relaxed);
if !cur_present {
let movetoend_v = movetoend.load(Relaxed);
let menucmp_v = MENUCMP.load(Relaxed);
let we_flag = if movetoend_v >= 2
|| (movetoend_v == 1 && menucmp_v == 0)
|| (movetoend_v == 0 && cs() == we)
{
1
} else {
0
};
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.pos = wb; g.we = we_flag; g.end = we; }
}
let l_del = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| {
if g.cur.is_some() {
g.len + g.insc } else {
we - wb }
})
.unwrap_or(we - wb);
let mi_pos = if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc = 0; g.pos
} else {
wb
};
set_cs(mi_pos); foredel(l_del, CUT_RAW);
if (m.flags & CMF_ALL) != 0 {
do_allmatches(0);
return;
}
let mut scs: i32 = 0;
let ins_len = instmatch(m, Some(&mut scs)); let end_v = cs();
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.len = ins_len; g.end = end_v; }
set_cs(mi_pos + ins_len);
let mi_we = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| g.we)
.unwrap_or(0);
let menucmp_v = MENUCMP.load(Relaxed);
if let Some(suf) = m.suf.as_deref() {
havesuff = true; let insc = suf.len() as i32; if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc = insc; g.len -= insc; }
if mi_we != 0 {
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.end += insc; }
if (m.flags & CMF_REMOVE) != 0 {
let mut wlen: i32 = 0;
let wsuf = stringaszleline(suf, 0, Some(&mut wlen), None, None); makesuffixstr(m.remf.as_deref(), m.rems.as_deref(), wlen); if wlen == 1 {
addsuffix(SUFTYP_POSSTR, 0, wsuf, 1, 1); }
}
}
} else {
set_cs(scs); if partest && (m.flags & CMF_PARBR) != 0 {
set_cs(cs() + eparq.load(Relaxed)); let parq_v = parq.load(Relaxed);
for _ in 0..parq_v {
inststrlen("\"", true, 1); }
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc += parq_v; }
inststrlen("}", true, 1); let insc_now = if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc += 1; g.insc
} else {
0
};
if mi_we != 0 {
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.end += insc_now; }
}
if (m.flags & CMF_PARNEST) != 0 {
havesuff = true; }
}
let cur = cs();
let prev_not_slash = cur > 0
&& ZLEMETALINE
.get()
.and_then(|mx| mx.lock().ok())
.map(|g| g.as_bytes().get((cur - 1) as usize).copied() != Some(b'/'))
.unwrap_or(true);
if ((m.flags & CMF_FILE) != 0 || (partest && isset(AUTOPARAMSLASH))) && cur > 0 && prev_not_slash
{
let mut t = false; if m.ipre.as_deref() == Some("~") {
t = true;
} else {
let p: String;
if partest && psuf.is_empty() && (m.flags & CMF_PARNEST) == 0 {
let base = if (m.flags & CMF_ISPAR) != 0 {
parpre
.get()
.and_then(|px| px.lock().ok())
.map(|g| g.clone())
.unwrap_or_default()
} else {
m.ripre.clone().unwrap_or_default()
};
let mut pp = format!(
"{}{}{}",
base,
strv,
if (m.flags & CMF_PARBR) != 0 { "}" } else { "" }
);
let mut tryit = true;
if pp.starts_with('$') {
let n: String = if pp.as_bytes().get(1) == Some(&b'{') {
let mut nn = pp[2..].to_string();
if nn.ends_with('}') {
nn.pop();
}
nn
} else {
pp[1..].to_string()
};
if let Ok(tab) = paramtab().read() {
if let Some(pm) = tab.get(&n) {
if PM_TYPE(pm.node.flags as u32) != PM_SCALAR {
tryit = false; }
}
}
}
if tryit {
let ne = noerrs.load(Relaxed);
noerrs.store(1, Relaxed);
let parsed = parsestr(&pp).unwrap_or_else(|_| pp.clone());
pp = singsub(&parsed);
errflag.fetch_and(!ERRFLAG_ERROR, Relaxed); noerrs.store(ne, Relaxed);
}
p = pp;
} else {
let base = if prpre.is_empty() { "./" } else { prpre };
p = format!("{}{}{}", base, strv, psuf);
}
match ztat(&p, false) {
Some(md) => {
sr = 0;
t = md.is_dir();
}
None => {
sr = -1;
t = false;
}
}
}
if t {
havesuff = true; inststrlen("/", true, 1); if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc += 1; if mi_we != 0 {
g.end += 1; }
}
if menucmp_v == 0 || mi_we != 0 {
if m.remf.is_some() || m.rems.is_some() {
makesuffixstr(m.remf.as_deref(), m.rems.as_deref(), 1); } else if isset(AUTOREMOVESLASH) {
makesuffix(1); addsuffix(SUFTYP_POSSTR, 0, vec!['/'], 1, 1); }
}
}
}
let insc_now = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| g.insc)
.unwrap_or(0);
if insc_now == 0 {
let len_now = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| g.len)
.unwrap_or(0);
set_cs(mi_pos + len_now - m.qisl); }
}
let brbeg_present = BRBEG
.get()
.and_then(|mx| mx.lock().ok())
.map(|g| g.is_some())
.unwrap_or(false);
if brbeg_present {
if havesuff {
if isset(AUTOPARAMKEYS) {
addsuffix(SUFTYP_POSSTR, 0, vec![',', '}'], 2, suffixlen.load(Relaxed));
}
} else if menucmp_v == 0 {
set_cs(scs); inststrlen(",", true, 1); if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc += 1; }
makesuffix(1); if (menucmp_v == 0 || mi_we != 0) && isset(AUTOPARAMKEYS) {
addsuffix(SUFTYP_POSSTR, 0, vec![',', '}'], 2, 1); }
}
} else if !havesuff && ((m.flags & CMF_FILE) == 0 || sr == 0) {
if let Some(autoq) = m.autoq.as_deref() {
let isuf_prefixed = m
.isuf
.as_deref()
.map(|isuf| isuf.starts_with(autoq))
.unwrap_or(false);
if !isuf_prefixed {
let al = autoq.len() as i32; inststrlen(autoq, true, al); if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc += al; }
}
}
let usemenu_v = USEMENU.load(Relaxed);
if menucmp_v == 0
&& (m.flags & CMF_NOSPACE) == 0
&& (usemenu_v != 3 || insspace.load(Relaxed) != 0)
{
inststrlen(" ", true, 1); if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc += 1; }
if mi_we != 0 {
makesuffixstr(m.remf.as_deref(), m.rems.as_deref(), 1); }
}
}
let parq_v = parq.load(Relaxed);
let insc_now = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| g.insc)
.unwrap_or(0);
if mi_we != 0 && partest && isset(AUTOPARAMKEYS) && insc_now - parq_v > 0 {
let take = (insc_now - parq_v).max(0);
let tmpstr = ZLEMETALINE
.get()
.and_then(|mx| mx.lock().ok())
.map(|g| {
let b = g.as_bytes();
let s = (parq_v.max(0) as usize).min(b.len());
let e = (s + take as usize).min(b.len());
String::from_utf8_lossy(&b[s..e]).into_owned()
})
.unwrap_or_default();
let mut outlen: i32 = 0;
let _subline = stringaszleline(&tmpstr, 0, Some(&mut outlen), None, None); makeparamsuffix(if (m.flags & CMF_PARBR) != 0 { 1 } else { 0 }, outlen); }
let movetoend_v = movetoend.load(Relaxed);
if (menucmp_v != 0 && mi_we == 0) || movetoend_v == 0 {
let (end_v, insc_v) = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| (g.end, g.insc))
.unwrap_or((0, 0));
set_cs(end_v); if cs() + m.qisl == lastend.load(Relaxed) {
set_cs(cs() + insc_v); }
set_cs(cs() + psuf.len() as i32); set_cs(cs() + m.suf.as_deref().map_or(0, |s| s.len() as i32)); }
{
let (om_cur, om_cur_idx, om_group_idx) = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| (g.cur.clone(), g.cur_idx, g.group_idx))
.unwrap_or((None, 0, 0));
let mut dat = Chdata {
matches: amatches
.get()
.and_then(|g| g.lock().ok())
.and_then(|g| g.first().cloned())
.map(Box::new), num: nmatches.load(Relaxed), nmesg: 0,
cur: Some(Box::new(m.clone())), };
if menucmp_v != 0 {
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = Some(Box::new(m.clone()));
}
}
let h = gethookdef("insert_match");
if !h.is_null() {
runhookdef(h, &mut dat as *mut Chdata as *mut std::ffi::c_void);
}
redrawhook(); if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.cur = om_cur;
g.cur_idx = om_cur_idx;
g.group_idx = om_group_idx;
}
}
}
pub fn valid_match(mut mi: i32, next: i32) -> Option<Cmatch> {
let groups = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if groups.is_empty() {
return None;
}
let _ = &lmatches; let (gi0, prebr, postbr) = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| (g.group_idx, g.prebr.clone(), g.postbr.clone()))
.unwrap_or((0, None, None));
let mut gi = gi0.clamp(0, groups.len() as i32 - 1);
let menuacc_v = menuacc.load(Relaxed);
let zmult_v = ZMULT.load(Relaxed);
let total: i32 = groups.iter().map(|g| g.mcount).sum();
let mut next = next;
let mut steps = 0i32;
loop {
if gi < 0 || gi as usize >= groups.len() {
return None;
}
let m = groups[gi as usize].matches.get(mi.max(0) as usize);
let invalid = match m {
None => true,
Some(m) => {
next != 0
|| (menuacc_v != 0
&& !hasbrpsfx(m, prebr.as_deref(), postbr.as_deref()))
|| (m.flags & CMF_DUMMY) != 0
|| ((m.flags & (CMF_NOLIST | CMF_MULT)) != 0
&& m.str.as_deref().map_or(true, |s| s.is_empty()))
}
};
if !invalid {
break;
}
if zmult_v > 0 {
mi += 1;
if mi as usize >= groups[gi as usize].matches.len() {
loop {
gi += 1; if gi as usize >= groups.len() {
gi = 0; }
if groups[gi as usize].mcount != 0 {
break;
}
}
mi = 0;
}
} else {
if mi == 0 {
loop {
gi -= 1; if gi < 0 {
gi = groups.len() as i32 - 1; }
if groups[gi as usize].mcount != 0 {
break;
}
}
mi = groups[gi as usize].mcount - 1;
} else {
mi -= 1;
}
}
next = 0; steps += 1;
if steps > total.max(1) * 2 + 2 {
return None;
}
}
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.group_idx = gi;
g.cur_idx = mi;
}
groups
.get(gi as usize)
.and_then(|g| g.matches.get(mi as usize))
.cloned() }
pub fn do_menucmp(matches: &[String], current: usize, forward: bool) -> (usize, &str) {
let _ = COMP_LIST_COMPLETE;
if matches.is_empty() {
return (0, "");
}
if matches.len() == 1 {
SHOWINGLIST.store(-2, Relaxed);
}
let next = if forward {
(current + 1) % matches.len()
} else {
if current == 0 {
matches.len() - 1
} else {
current - 1
}
};
(next, &matches[next])
}
pub fn accept_last() -> i32 {
let cs = || ZLEMETACS.load(Relaxed);
let set_cs = |v: i32| ZLEMETACS.store(v, Relaxed);
let was_meta = ZLEMETALL.load(Relaxed) != 0;
if !was_meta {
metafy_line();
}
if menuacc.load(Relaxed) == 0 {
let lpb = LASTPREBR
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let lsb = LASTPOSTBR
.get()
.and_then(|m| m.lock().ok())
.map(|g| g.clone())
.unwrap_or_default();
let prebr_opt = if lpb.is_empty() { None } else { Some(lpb.clone()) };
let postbr_opt = if lsb.is_empty() { None } else { Some(lsb.clone()) };
if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
m.prebr = prebr_opt.clone(); m.postbr = postbr_opt.clone(); }
if LISTSHOWN.load(Relaxed) != 0 && (!lpb.is_empty() || !lsb.is_empty()) {
let groups = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
'outer: for g in &groups {
for m in &g.matches {
if !hasbrpsfx(m, prebr_opt.as_deref(), postbr_opt.as_deref()) {
SHOWINGLIST.store(-2, Relaxed); break 'outer;
}
}
}
}
}
menuacc.fetch_add(1, Relaxed);
let brbeg_present = BRBEG
.get()
.and_then(|mx| mx.lock().ok())
.map(|g| g.is_some())
.unwrap_or(false);
if brbeg_present {
iremovesuffix(',' as i32, 1); let brscs_v = brscs.load(Relaxed);
let brpcs_v = brpcs.load(Relaxed);
let l = (if brscs_v >= 0 { brscs_v } else { cs() }) - brpcs_v; let slice = ZLEMETALINE
.get()
.and_then(|m| m.lock().ok())
.map(|g| {
let b = g.as_bytes();
let s = (brpcs_v.max(0) as usize).min(b.len());
let e = (s + l.max(0) as usize).min(b.len());
String::from_utf8_lossy(&b[s..e]).into_owned()
})
.unwrap_or_default();
let newstr = format!("{},", slice); if let Some(mx) = BRBEG.get() {
if let Ok(mut guard) = mx.lock() {
if let Some(head) = guard.as_mut() {
let mut node = head.as_mut();
while node.next.is_some() {
node = node.next.as_mut().unwrap();
}
node.str = Some(newstr);
}
}
}
} else {
let (pos, len, insc, qisl) = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| {
(
g.pos,
g.len,
g.insc,
g.cur.as_ref().map(|c| c.qisl).unwrap_or(0),
)
})
.unwrap_or((0, 0, 0, 0));
set_cs(pos + len + insc); iremovesuffix(' ' as i32, 1); let l = cs(); set_cs(pos + len + insc - qisl); if cs() < l {
foredel(l - cs(), CUT_RAW); } else if cs() > ZLEMETALL.load(Relaxed) {
set_cs(ZLEMETALL.load(Relaxed)); }
inststrlen(" ", true, 1); let newpos = cs();
if let Ok(mut g) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
g.insc = 0; g.len = 0;
g.pos = newpos; g.we = 1; }
}
if !was_meta {
unmetafy_line(); }
0 }
pub fn comp_mod(mut v: i32, m: i32) -> i32 {
if m <= 0 {
return 0;
}
if v >= 0 {
v -= 1; }
if v >= 0 {
v % m } else {
while v < 0 {
v += m; }
v }
}
pub fn do_ambig_menu() -> i32 {
if iforcemenu.load(Relaxed) == -1 {
let _ = do_ambiguous(&[]); }
let um = USEMENU.load(Relaxed);
if um != 3 {
MENUCMP.store(1, Relaxed); menuacc.store(0, Relaxed); if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
m.cur = None; }
} else {
if oldlist.load(Relaxed) != 0 {
let has_cur = MINFO
.get()
.and_then(|m| m.lock().ok())
.map(|m| m.cur.is_some())
.unwrap_or(false);
if oldins.load(Relaxed) != 0 && has_cur {
let _ = accept_last();
}
} else {
if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
m.cur = None; }
}
}
let mut idx = comp_mod(insmnum.load(Relaxed), lastpermmnum.load(Relaxed));
insmnum.store(idx, Relaxed);
let groups = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
let mut gidx: Option<usize> = None;
for (gi, g) in groups.iter().enumerate() {
if g.mcount > idx {
gidx = Some(gi);
break;
}
idx -= g.mcount; }
let Some(gi) = gidx else {
if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
m.cur = None;
m.asked = 0;
}
return 0;
};
if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
m.group = Some(Box::new(groups[gi].clone()));
m.group_idx = gi as i32;
m.cur_idx = idx;
}
insmnum.store(idx, Relaxed);
let mc = valid_match(idx, 0);
if iforcemenu.load(Relaxed) != -1 {
if let Some(ref m) = mc {
do_single(m);
}
}
if let Ok(mut mst) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
mst.cur = mc.map(Box::new);
}
0
}
pub fn list_lines(matches: &[String], columns: usize) -> usize {
if columns == 0 {
return matches.len();
}
matches.len().div_ceil(columns)
}
pub fn comp_list(v: Option<&str>) {
let mut g = crate::ported::zle::complete::COMPLIST .get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.unwrap();
g.clear();
if let Some(s) = v {
g.push_str(s);
}
let val = v.map_or(0, |s| {
(s.contains("expl") as i32) | (s.contains("messages") as i32) << 1
}); onlyexpl.store(val, Ordering::SeqCst); }
pub fn skipnolist(p: &[Cmatch], showall: i32) -> usize {
let mask = if showall != 0 {
0
} else {
CMF_NOLIST | CMF_MULT
} | CMF_HIDE;
let mut i = 0usize; while i < p.len() {
let m = &p[i];
let f = m.flags;
let skip_mask = (f & mask) != 0; let skip_disp = m.disp.is_some() && (f & (CMF_DISPLINE | CMF_HIDE)) != 0; if !(skip_mask || skip_disp) {
break;
}
i += 1; }
i }
pub fn calclist(showall: i32) -> i32 {
let invcount = INVCOUNT.load(Relaxed);
let onlyexpl_v = onlyexpl.load(Relaxed);
let menuacc_v = menuacc.load(Relaxed);
let (minfo_prebr, minfo_postbr) = MINFO
.get()
.and_then(|g| g.lock().ok())
.map(|g| (g.prebr.clone(), g.postbr.clone()))
.unwrap_or((None, None));
let zterm_columns = adjustcolumns() as i32; let zterm_lines = adjustlines() as i32;
{
let ld = crate::ported::zle::compcore::listdat.get_or_init(|| std::sync::Mutex::new(Cldata::default()));
let g = ld.lock().unwrap();
if LASTINVCOUNT.with(|c| c.get()) == invcount
&& g.valid != 0
&& onlyexpl_v == g.onlyexpl
&& menuacc_v == g.menuacc
&& showall == g.showall
&& zterm_lines == g.zterm_lines
&& zterm_columns == g.zterm_columns
{
return 0; }
}
LASTINVCOUNT.with(|c| c.set(invcount));
let am = amatches.get_or_init(|| std::sync::Mutex::new(Vec::new()));
let mut groups = am.lock().unwrap();
let nmatches2 = nmatches.load(Relaxed);
let mut mlens: Vec<i32> = vec![0; (nmatches2 + 1) as usize];
let mut hidden = 0i32;
let mut nlist = 0i32;
let mut nlines = 0i32;
let mut max = 0i32;
let listpacked = isset(LISTPACKED);
let listrowsfirst = isset(LISTROWSFIRST);
let listtypes = isset(LISTTYPES);
for g in groups.iter_mut() {
let mut nl = false;
let mut glong = 1i32;
let mut gshort = zterm_columns;
let mut ndisp = 0i32;
let mut totl = 0i32;
let mut hasf = false;
g.flags |= CGF_PACKED | CGF_ROWS;
if onlyexpl_v == 0 && !g.ylist.is_empty() {
if !listpacked {
g.flags &= !CGF_PACKED;
} if !listrowsfirst {
g.flags &= !CGF_ROWS;
}
hidden = 1; for s in g.ylist.iter() {
if (s.chars().count() as i32) >= zterm_columns || s.contains('\n') {
nl = true;
break;
}
}
if nl || g.ylist.len() < 2 {
g.flags |= CGF_LINES; hidden = 1; for s in g.ylist.iter() {
let mut acc = 0i32;
for chunk in s.split('\n') {
let w = chunk.chars().count().saturating_sub(1) as i32;
acc += 1 + w / zterm_columns;
}
nlines += acc;
}
} else {
for s in g.ylist.iter() {
let l = s.chars().count() as i32;
ndisp += 1;
if l > glong {
glong = l;
}
if l < gshort {
gshort = l;
}
totl += l;
nlist += 1;
}
}
} else if onlyexpl_v == 0 {
for m in g.matches.iter_mut() {
if (m.flags & CMF_FILE) != 0 {
hasf = true;
}
if menuacc_v != 0
&& !hasbrpsfx(m, minfo_prebr.as_deref(), minfo_postbr.as_deref())
{
m.flags |= CMF_HIDE;
continue;
}
m.flags &= !CMF_HIDE;
if showall != 0 || (m.flags & (CMF_NOLIST | CMF_MULT)) == 0 {
if (m.flags & (CMF_NOLIST | CMF_MULT)) != 0
&& m.str.as_deref().is_none_or(|s| s.is_empty())
{
m.flags |= CMF_HIDE;
continue;
}
if let Some(disp) = m.disp.clone() {
if (m.flags & CMF_DISPLINE) != 0 {
nlines += 1 + printfmt(&disp, 0, false, false);
g.flags |= CGF_HASDL;
} else {
let l =
disp.chars().count() as i32 + if m.modec != '\0' { 1 } else { 0 };
ndisp += 1;
if l > glong {
glong = l;
}
if l < gshort {
gshort = l;
}
totl += l;
mlens[m.gnum as usize] = l;
}
nlist += 1;
if (m.flags & CMF_PACKED) == 0 {
g.flags &= !CGF_PACKED;
}
if (m.flags & CMF_ROWS) == 0 {
g.flags &= !CGF_ROWS;
}
} else {
let s = m.str.as_deref().unwrap_or("");
let l = s.chars().count() as i32 + if m.modec != '\0' { 1 } else { 0 };
ndisp += 1;
if l > glong {
glong = l;
}
if l < gshort {
gshort = l;
}
totl += l;
mlens[m.gnum as usize] = l;
nlist += 1;
if (m.flags & CMF_PACKED) == 0 {
g.flags &= !CGF_PACKED;
}
if (m.flags & CMF_ROWS) == 0 {
g.flags &= !CGF_ROWS;
}
}
} else {
hidden = 1;
}
}
}
for e in g.expls.iter() {
if (e.count != 0 || e.always != 0)
&& (onlyexpl_v == 0 || (onlyexpl_v & if e.always > 0 { 2 } else { 1 }) != 0)
{
nlines += 1 + printfmt(
e.str.as_deref().unwrap_or(""),
if e.always != 0 { -1 } else { e.count },
false,
true,
);
}
}
if listtypes && hasf {
g.flags |= CGF_FILES;
} g.totl = totl + ndisp * CM_SPACE; g.dcount = ndisp; g.width = glong + CM_SPACE; g.shortest = gshort + CM_SPACE; if g.width > 0 {
g.cols = (zterm_columns / g.width).min(g.dcount); }
if g.cols > 0 {
let i = g.cols * g.width - CM_SPACE; if i > max {
max = i;
}
}
}
if onlyexpl_v == 0 {
for g in groups.iter_mut() {
let mut glines = 0i32;
g.widths.clear(); if !g.ylist.is_empty() {
if (g.flags & CGF_LINES) == 0 {
if g.cols > 0 {
glines += (g.ylist.len() as i32 + g.cols - 1) / g.cols;
if g.cols > 1 {
g.width += (max - (g.width * g.cols - CM_SPACE)) / g.cols;
}
} else {
g.cols = 1;
g.width = 1;
for s in g.ylist.iter() {
glines += 1 + s.chars().count() as i32 / zterm_columns;
}
}
}
} else if g.cols > 0 {
glines += (g.dcount + g.cols - 1) / g.cols;
if g.cols > 1 {
g.width += (max - (g.width * g.cols - CM_SPACE)) / g.cols;
}
} else if (g.flags & CGF_LINES) == 0 {
g.cols = 1;
g.width = 0;
for m in g.matches.iter() {
if (m.flags & CMF_HIDE) == 0 {
if m.disp.is_some() {
if (m.flags & CMF_DISPLINE) == 0 {
glines +=
1 + (mlens[m.gnum as usize].saturating_sub(1)) / zterm_columns;
}
} else if showall != 0 || (m.flags & (CMF_NOLIST | CMF_MULT)) == 0 {
glines +=
1 + (mlens[m.gnum as usize].saturating_sub(1)) / zterm_columns;
}
}
}
}
g.lins = glines;
nlines += glines;
}
for g in groups.iter_mut() {
if (g.flags & CGF_PACKED) == 0 {
continue;
} g.widths = vec![0i32; zterm_columns as usize];
let mut tlines = g.lins; let mut tcols = g.cols; let mut width: i32 = 0;
if !g.ylist.is_empty() {
if (g.flags & CGF_LINES) == 0 {
let ylens: Vec<i32> = g
.ylist
.iter()
.map(|s| s.chars().count() as i32 + CM_SPACE)
.collect();
if (g.flags & CGF_ROWS) != 0 {
let mut t = zterm_columns / (g.shortest + CM_SPACE);
while t > g.cols {
for w in &mut g.widths[..t as usize] {
*w = 0;
} let mut w = 0i32;
let mut nth = 0i32;
let mut tcol = 0i32;
let mut tl = 1i32;
while w < zterm_columns && nth < g.dcount {
if tcol == t {
tcol = 0;
tl += 1;
} let len = ylens[nth as usize]; if len > g.widths[tcol as usize] {
w += len - g.widths[tcol as usize]; g.widths[tcol as usize] = len; }
nth += 1;
tcol += 1;
}
width = w;
tcols = t;
tlines = tl;
if w < zterm_columns {
break;
} t -= 1;
}
} else {
let mut t = zterm_columns / (g.shortest + CM_SPACE);
while t > g.cols {
let mut tl = ((g.dcount + t - 1) / t).max(1); for w in &mut g.widths[..t as usize] {
*w = 0;
} let mut w = 0i32;
let mut nth = 0i32;
let mut tcol = 0i32;
let mut tline = 0i32;
while w < zterm_columns && nth < g.dcount {
if tline == tl {
tcol += 1;
tline = 0;
} if tcol == t {
tcol = 0;
tl += 1;
} let len = ylens[nth as usize]; if len > g.widths[tcol as usize] {
w += len - g.widths[tcol as usize];
g.widths[tcol as usize] = len;
}
nth += 1;
tline += 1;
}
width = w;
tcols = t;
tlines = tl;
if w < zterm_columns {
break;
} t -= 1;
}
}
}
} else if g.width != 0 {
if (g.flags & CGF_ROWS) != 0 {
let mut t = zterm_columns / (g.shortest + CM_SPACE);
while t > g.cols {
for w in &mut g.widths[..t as usize] {
*w = 0;
} let mut w = 0i32;
let mut tcol = 0i32;
let mut tl = 1i32;
let mut nth = 0i32;
let mut p_idx = skipnolist(&g.matches, showall);
while p_idx < g.matches.len() && w < zterm_columns && nth < g.dcount {
if tcol == t {
tcol = 0;
tl += 1;
} let m = &g.matches[p_idx]; let len =
mlens[m.gnum as usize] + if tcol == t - 1 { 0 } else { CM_SPACE }; if len > g.widths[tcol as usize] {
w += len - g.widths[tcol as usize];
g.widths[tcol as usize] = len;
}
nth += 1;
let nxt = p_idx + 1;
if nxt >= g.matches.len() {
p_idx = g.matches.len();
} else {
p_idx = nxt + skipnolist(&g.matches[nxt..], showall);
}
tcol += 1;
}
width = w;
tcols = t;
tlines = tl;
if w < zterm_columns {
break;
} t -= 1;
}
} else {
let mut t = zterm_columns / (g.shortest + CM_SPACE);
while t > g.cols {
let mut tl = ((g.dcount + t - 1) / t).max(1); for w in &mut g.widths[..t as usize] {
*w = 0;
} let mut w = 0i32;
let mut nth = 0i32;
let mut tcol = 0i32;
let mut tline = 0i32;
let mut p_idx = skipnolist(&g.matches, showall); while p_idx < g.matches.len() && w < zterm_columns && nth < g.dcount {
if tline == tl {
tcol += 1;
tline = 0;
} if tcol == t {
tcol = 0;
tl += 1;
} let m = &g.matches[p_idx]; let len =
mlens[m.gnum as usize] + if tcol == t - 1 { 0 } else { CM_SPACE }; if len > g.widths[tcol as usize] {
w += len - g.widths[tcol as usize];
g.widths[tcol as usize] = len;
}
nth += 1;
let nxt = p_idx + 1;
if nxt >= g.matches.len() {
p_idx = g.matches.len();
} else {
p_idx = nxt + skipnolist(&g.matches[nxt..], showall);
}
tline += 1;
}
width = w;
tcols = t;
tlines = tl;
if w < zterm_columns {
if tcol + 1 < tcols {
tcols = tcol + 1;
}
break;
}
t -= 1;
}
}
}
if tcols <= g.cols {
tlines = g.lins;
} if tlines == g.lins {
g.widths.clear(); } else {
nlines += tlines - g.lins; g.lins = tlines; g.cols = tcols; g.totl = width; let width_adj = width - CM_SPACE; if width_adj > max {
max = width_adj;
} }
}
for g in groups.iter_mut() {
if g.widths.is_empty() && g.width != 0 && g.cols > 1 {
g.width += (max - (g.width * g.cols - CM_SPACE)) / g.cols;
}
}
} else {
for g in groups.iter_mut() {
g.widths.clear(); }
}
let ld = crate::ported::zle::compcore::listdat.get_or_init(|| std::sync::Mutex::new(Cldata::default()));
let mut g = ld.lock().unwrap();
g.valid = 1;
g.hidden = hidden;
g.nlist = nlist;
g.nlines = nlines;
g.menuacc = menuacc_v;
g.onlyexpl = onlyexpl_v;
g.zterm_columns = zterm_columns;
g.zterm_lines = zterm_lines;
g.showall = showall;
1 }
pub fn asklist() -> i32 {
trashzle(); SHOWINGLIST.store(0, Relaxed);
LISTSHOWN.store(0, Relaxed);
LASTLISTLEN.store(0, Relaxed);
let usezle = isset(USEZLE);
let termflags = crate::ported::params::TERMFLAGS.load(Relaxed);
let dolastprompt = crate::ported::zle::compcore::dolastprompt.load(Relaxed) != 0;
let clearflag = usezle && termflags == 0 && dolastprompt;
CLEARFLAG.store(if clearflag { 1 } else { 0 }, Relaxed);
let listdat = crate::ported::zle::compcore::listdat
.get_or_init(|| std::sync::Mutex::new(Default::default()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
let zterm_lines = adjustlines() as i32;
let cmax = COMPLISTMAX.load(Relaxed) as i32;
let has_cur = MINFO
.get()
.and_then(|m| m.lock().ok())
.map(|m| m.cur.is_some())
.unwrap_or(false);
let already_asked = MINFO
.get()
.and_then(|m| m.lock().ok())
.map(|m| m.asked)
.unwrap_or(0);
let over_threshold = (cmax > 0 && listdat.nlist >= cmax)
|| (cmax < 0 && listdat.nlines <= -cmax)
|| (cmax == 0 && listdat.nlines >= zterm_lines);
if (!has_cur || already_asked == 0) && over_threshold {
let prompt = if listdat.nlist > 0 {
format!(
"zsh: do you wish to see all {} possibilities ({} lines)? ",
listdat.nlist, listdat.nlines
)
} else {
format!("zsh: do you wish to see all {} lines? ", listdat.nlines)
};
let fd = SHTTY.load(Relaxed);
let out = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out, prompt.as_bytes());
let said_yes = getzlequery() != 0;
if !said_yes {
let _ = write_loop(out, b"\n");
if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Default::default()))
.lock()
{
m.asked = 2;
}
return 1; }
let _ = write_loop(out, b"\n");
if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Default::default()))
.lock()
{
m.asked = 1;
}
}
let asked_now = MINFO
.get()
.and_then(|m| m.lock().ok())
.map(|m| m.asked)
.unwrap_or(0);
if asked_now != 0 {
asked_now - 1
} else {
0
}
}
thread_local! {
static LASTINVCOUNT: std::cell::Cell<i32> = const { std::cell::Cell::new(-1) };
}
pub fn printlist(over: i32, showall: i32) -> i32 {
let out_fd: i32 = {
let fd = SHTTY.load(Relaxed);
if fd >= 0 {
fd
} else {
1
}
};
let listdat = crate::ported::zle::compcore::listdat
.get_or_init(|| std::sync::Mutex::new(Default::default()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
let mut cl: i32 = if over != 0 { listdat.nlines } else { -1 }; let mut pnl: i32 = 0; let mut ml: i32 = 0;
if cl < 2 {
cl = -1;
if crate::ported::init::tclen.lock().unwrap()[crate::ported::zsh_h::TCCLEAREOD as usize]
!= 0
{
tcout(crate::ported::zsh_h::TCCLEAREOD);
}
}
let groups = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
for g in &groups {
for e in g.expls.iter() {
let active = (e.count != 0 || e.always != 0) && (listdat.onlyexpl == 0
|| (listdat.onlyexpl
& (if e.always > 0 { 2 } else { 1 })) != 0);
if !active {
continue;
}
if pnl != 0 {
let _ = write_loop(out_fd, b"\n"); ml += 1;
cl -= 1;
if cl >= 0 && cl <= 1 {
cl = -1;
if crate::ported::init::tclen.lock().unwrap()
[crate::ported::zsh_h::TCCLEAREOD as usize]
!= 0
{
tcout(crate::ported::zsh_h::TCCLEAREOD);
}
}
}
let n = if e.always != 0 { -1 } else { e.count };
let l = printfmt(e.str.as_deref().unwrap_or(""), n, true, true);
ml += l;
if cl >= 0 && (cl - l) <= 1 {
cl = -1;
}
pnl = 1;
}
if listdat.onlyexpl == 0 && !g.ylist.is_empty() {
if pnl != 0 {
let _ = write_loop(out_fd, b"\n");
pnl = 0;
ml += 1;
if cl >= 0 && cl <= 1 {
cl = -1;
}
}
if (g.flags & CGF_LINES) != 0 {
let mut so = std::io::stdout();
let last_idx = g.ylist.len().saturating_sub(1);
for (i, p) in g.ylist.iter().enumerate() {
let _ = zputs(p, &mut so);
if i != last_idx {
let _ = write_loop(out_fd, b"\n");
}
}
} else {
let mut so = std::io::stdout();
for entry in &g.ylist {
let _ = zputs(entry, &mut so);
let _ = write_loop(out_fd, b"\n");
ml += 1;
}
}
} else if listdat.onlyexpl == 0 && (g.lcount != 0 || (showall != 0 && g.mcount != 0)) {
if pnl != 0 {
let _ = write_loop(out_fd, b"\n");
pnl = 0;
ml += 1;
}
if (g.flags & CGF_HASDL) != 0 {
for m in g.matches.iter() {
if m.disp.is_some()
&& (m.flags & CMF_DISPLINE) != 0
&& (showall != 0 || (m.flags & (CMF_HIDE | CMF_NOLIST)) == 0)
{
if pnl != 0 {
let _ = write_loop(out_fd, b"\n");
ml += 1;
}
let _ = iprintm(Some(g), Some(m), 0, ml, 1, 0);
pnl = 1;
}
}
if pnl != 0 {
let _ = write_loop(out_fd, b"\n");
ml += 1;
pnl = 0;
}
}
let visible: Vec<&crate::ported::zle::comp_h::Cmatch> = g
.matches
.iter()
.filter(|m| (m.flags & CMF_DISPLINE) == 0)
.filter(|m| showall != 0 || (m.flags & (CMF_HIDE | CMF_NOLIST)) == 0)
.collect();
let n = visible.len();
if n > 0 {
let cols = g.cols.max(1);
let lins = g.lins.max(1);
let rows_major = (g.flags & CGF_ROWS) != 0;
for row in 0..lins {
for col in 0..cols {
let idx = if rows_major {
row * cols + col
} else {
row + col * lins
};
let lastc = if col == cols - 1 { 1 } else { 0 };
let wid = if g.widths.is_empty() {
g.width
} else {
g.widths.get(col as usize).copied().unwrap_or(g.width)
};
if idx >= 0 && (idx as usize) < n {
let _ = iprintm(Some(g), Some(visible[idx as usize]), col, ml, lastc, wid);
}
}
ml += 1;
if row < lins - 1 {
let _ = write_loop(out_fd, b"\n");
}
}
}
}
pnl = 1;
}
let _ = Relaxed;
ml }
pub fn bld_all_str() -> String {
let groups = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
let cols: i32 = adjustcolumns() as i32;
let mut len: i32 = cols - 5; let mut add: i32 = 0;
let mut buf = String::new();
let mut g_idx = groups.iter().position(|g| g.mcount != 0);
'outer: while let Some(gi) = g_idx {
let g = &groups[gi];
let mut mp = 0usize;
while mp < g.matches.len() {
let m = &g.matches[mp];
let visible = (m.flags & (CMF_ALL | CMF_HIDE)) == 0 && m.str.is_some();
if visible {
let s = m.str.as_deref().unwrap();
let t = s.len() as i32 + add;
if len >= t {
if add != 0 {
buf.push(' ');
} buf.push_str(s); len -= t;
add = 1;
} else {
if len > add + 2 {
if add != 0 {
buf.push(' ');
}
buf.push_str(&s[..((len - 2).max(0) as usize).min(s.len())]);
}
buf.push_str("..."); break 'outer; }
}
mp += 1;
if mp >= g.matches.len() {
g_idx = (gi + 1..).find(|&i| i < groups.len() && groups[i].mcount != 0);
if g_idx.is_none() {
break 'outer;
}
continue 'outer;
}
}
let _ = Relaxed;
g_idx = (gi + 1..).find(|&i| i < groups.len() && groups[i].mcount != 0);
}
buf }
#[allow(unused_variables)]
pub fn iprintm(
g: Option<&Cmgroup>,
mp: Option<&Cmatch>,
mc: i32,
ml: i32,
lastc: i32,
width: i32,
) -> i32 {
use std::sync::atomic::Ordering;
let m = match mp {
None => return 0,
Some(m) => m,
}; let mut disp_owned: String = String::new();
let disp_ref: Option<&str> = m.disp.as_deref();
if (m.flags & CMF_ALL) != 0 && disp_ref.map(|s| s.is_empty()).unwrap_or(true) {
disp_owned = bld_all_str(); }
let disp_now: Option<&str> = if !disp_owned.is_empty() {
Some(disp_owned.as_str())
} else {
disp_ref
};
let mut len: i32;
let fd = SHTTY.load(Relaxed);
let out = if fd >= 0 { fd } else { 1 };
if let Some(d) = disp_now {
if (m.flags & CMF_DISPLINE) != 0 {
let _ = write_loop(out, d.as_bytes());
let _ = write_loop(out, b"\n");
return 0; }
let _ = write_loop(out, d.as_bytes()); len = d.chars().count() as i32;
} else {
let s = m.str.as_deref().unwrap_or("");
let _ = write_loop(out, s.as_bytes()); len = s.chars().count() as i32;
if let Some(grp) = g {
if (grp.flags & CGF_FILES) != 0 && m.modec != '\0' {
let mut buf = [0u8; 4];
let mb = m.modec.encode_utf8(&mut buf);
let _ = write_loop(out, mb.as_bytes());
len += 1;
}
}
}
if lastc == 0 {
let pad = width - len;
if pad > 0 {
let spaces = vec![b' '; pad as usize];
let _ = write_loop(out, &spaces);
}
}
len }
pub fn ilistmatches() -> i32 {
let _ = calclist(0); let nlines = crate::ported::zle::compcore::listdat
.get_or_init(|| std::sync::Mutex::new(Default::default()))
.lock()
.map(|g| g.nlines)
.unwrap_or(0);
if nlines == 0 {
SHOWINGLIST.store(0, Relaxed);
LISTSHOWN.store(0, Relaxed);
return 0;
}
if asklist() != 0 {
return 0;
}
let _ = printlist(0, 0);
SHOWINGLIST.store(-1, Relaxed);
LISTSHOWN.store(1, Relaxed);
0 }
pub fn list_matches() -> i32 {
if VALIDLIST.load(Ordering::SeqCst) == 0 {
showmsg("BUG: listmatches called with bogus list");
return 1; }
let groups = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.ok()
.map(|g| g.clone())
.unwrap_or_default();
let mut dat = Chdata::default();
dat.matches = groups.into_iter().next().map(Box::new); dat.num = nmatches_g.load(Relaxed); let h = crate::ported::module::gethookdef("complist-matches");
let handled = if !h.is_null() {
let dat_ptr = (&mut dat) as *mut Chdata as *mut std::ffi::c_void;
crate::ported::module::runhookdef(h, dat_ptr) != 0
} else {
false
};
if !handled {
ilistmatches();
}
0
}
pub fn invalidate_list() -> i32 {
INVCOUNT.fetch_add(1, Ordering::SeqCst); if VALIDLIST.load(Ordering::SeqCst) != 0 {
if SHOWINGLIST.load(Ordering::SeqCst) == -2 {
zrefresh();
}
let drained = lastmatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.map(|mut g| std::mem::take(&mut *g))
.unwrap_or_default();
crate::ported::zle::compcore::freematches(drained, 1);
crate::ported::zle::compcore::hasoldlist.store(0, Ordering::SeqCst); }
LASTAMBIG.store(0, Ordering::SeqCst);
MENUCMP.store(0, Ordering::SeqCst);
menuacc.store(0, Ordering::SeqCst);
VALIDLIST.store(0, Ordering::SeqCst);
SHOWINGLIST.store(0, Ordering::SeqCst);
fromcomp.store(0, Ordering::SeqCst);
if let Ok(mut ld) = crate::ported::zle::compcore::listdat
.get_or_init(|| std::sync::Mutex::new(Default::default()))
.lock()
{
ld.valid = 0;
}
if LISTSHOWN.load(Ordering::SeqCst) < 0 {
LISTSHOWN.store(0, Ordering::SeqCst);
}
nmatches_g.store(0, Ordering::SeqCst); if let Ok(mut g) = amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
{
g.clear(); }
0 }
pub static INVCOUNT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::zle::comp_h::Cline;
#[test]
fn test_unambig_data() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(unambig_data(&["foobar".into(), "foobaz".into()]), "fooba");
assert_eq!(unambig_data(&["abc".into()]), "abc");
assert_eq!(unambig_data(&[]), "");
}
#[test]
fn cline_str_none_returns_empty() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = String::new();
}
ZLEMETACS.store(0, Relaxed);
ZLEMETALL.store(0, Relaxed);
WB.store(0, Relaxed);
WE.store(0, Relaxed);
assert_eq!(cline_str(None, 0, None, None), Some(String::new()));
}
#[test]
fn cline_str_emits_word_anchor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = String::new();
}
ZLEMETACS.store(0, Relaxed);
ZLEMETALL.store(0, Relaxed);
WB.store(0, Relaxed);
WE.store(0, Relaxed);
let mut n = Cline::default();
n.word = Some("hello".to_string());
n.wlen = 5;
assert_eq!(cline_str(Some(Box::new(n)), 0, None, None), Some("hello".to_string()));
assert_eq!(
ZLEMETALINE.get().unwrap().lock().unwrap().clone(),
""
);
}
#[test]
fn cline_str_emits_line_anchor_when_clf_line_set() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = String::new();
}
ZLEMETACS.store(0, Relaxed);
ZLEMETALL.store(0, Relaxed);
WB.store(0, Relaxed);
WE.store(0, Relaxed);
let mut n = Cline::default();
n.flags = CLF_LINE;
n.line = Some("LINE".to_string());
n.llen = 4;
n.word = Some("word-should-not-emit".to_string());
n.wlen = 20;
assert_eq!(cline_str(Some(Box::new(n)), 0, None, None), Some("LINE".to_string()));
}
#[test]
fn cline_str_emits_orig_when_olen_set_and_no_prefix() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = String::new();
}
ZLEMETACS.store(0, Relaxed);
ZLEMETALL.store(0, Relaxed);
WB.store(0, Relaxed);
WE.store(0, Relaxed);
let mut n = Cline::default();
n.orig = Some("original".to_string());
n.olen = 8;
n.word = Some("anchor".to_string());
n.wlen = 6;
assert_eq!(
cline_str(Some(Box::new(n)), 0, None, None),
Some("originalanchor".to_string())
);
}
#[test]
fn cline_str_walks_prefix_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = String::new();
}
ZLEMETACS.store(0, Relaxed);
ZLEMETALL.store(0, Relaxed);
WB.store(0, Relaxed);
WE.store(0, Relaxed);
let mut p2 = Cline::default();
p2.word = Some("ond".to_string());
p2.wlen = 3;
let mut p1 = Cline::default();
p1.word = Some("sec".to_string());
p1.wlen = 3;
p1.next = Some(Box::new(p2));
let mut n = Cline::default();
n.prefix = Some(Box::new(p1));
n.word = Some("anchor".to_string());
n.wlen = 6;
assert_eq!(
cline_str(Some(Box::new(n)), 0, None, None),
Some("secondanchor".to_string())
);
}
#[test]
fn cline_str_walks_next_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = String::new();
}
ZLEMETACS.store(0, Relaxed);
ZLEMETALL.store(0, Relaxed);
WB.store(0, Relaxed);
WE.store(0, Relaxed);
let mut n2 = Cline::default();
n2.word = Some("B".to_string());
n2.wlen = 1;
let mut n1 = Cline::default();
n1.word = Some("A".to_string());
n1.wlen = 1;
n1.next = Some(Box::new(n2));
assert_eq!(cline_str(Some(Box::new(n1)), 0, None, None), Some("AB".to_string()));
}
#[test]
fn test_instmatch() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
if let Some(mx) = BRBEG.get() {
*mx.lock().unwrap() = None;
}
if let Some(mx) = BREND.get() {
*mx.lock().unwrap() = None;
}
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = "git ".to_string();
}
ZLEMETACS.store(4, Relaxed);
ZLEMETALL.store(4, Relaxed);
let mut m = Cmatch::default();
m.str = Some("commit".to_string());
let r = instmatch(&m, None);
assert_eq!(r, 6, "returns bytes inserted");
assert_eq!(
ZLEMETALINE.get().unwrap().lock().unwrap().clone(),
"git commit"
);
assert_eq!(ZLEMETACS.load(Relaxed), 10);
}
#[test]
fn test_do_single() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
if let Some(mx) = BRBEG.get() {
*mx.lock().unwrap() = None;
}
if let Some(mx) = BREND.get() {
*mx.lock().unwrap() = None;
}
if let Ok(mut g) = MINFO.get_or_init(|| std::sync::Mutex::new(Menuinfo::default())).lock() {
*g = Menuinfo::default();
}
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = "git co".to_string();
}
ZLEMETACS.store(6, Relaxed);
ZLEMETALL.store(6, Relaxed);
WB.store(4, Relaxed);
WE.store(6, Relaxed);
MENUCMP.store(0, Relaxed);
menuacc.store(0, Relaxed);
movetoend.store(0, Relaxed);
USEMENU.store(0, Relaxed);
insspace.store(0, Relaxed);
let mut m = Cmatch::default();
m.str = Some("commit".to_string());
m.orig = Some("commit".to_string());
do_single(&m);
assert_eq!(
ZLEMETALINE.get().unwrap().lock().unwrap().clone(),
"git commit ",
"old word deleted, match + trailing space inserted"
);
let len = MINFO.get().unwrap().lock().unwrap().len;
assert_eq!(len, 6);
}
#[test]
fn test_do_menucmp() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let matches = vec!["commit".into(), "checkout".into(), "cherry-pick".into()];
let (next, word) = do_menucmp(&matches, 0, true);
assert_eq!(next, 1);
assert_eq!(word, "checkout");
let (next, word) = do_menucmp(&matches, 2, true);
assert_eq!(next, 0);
assert_eq!(word, "commit");
}
#[test]
fn test_valid_match() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMULT.store(1, Relaxed); menuacc.store(0, Relaxed); let mut dummy = Cmatch::default();
dummy.str = Some("dummy".to_string());
dummy.flags = CMF_DUMMY;
let mut real = Cmatch::default();
real.str = Some("real".to_string());
let mut g = Cmgroup::default();
g.matches = vec![dummy, real];
g.mcount = 2;
if let Ok(mut a) = amatches.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock() {
*a = vec![g];
}
if let Ok(mut mi) = MINFO.get_or_init(|| std::sync::Mutex::new(Menuinfo::default())).lock() {
*mi = Menuinfo::default();
mi.group_idx = 0;
}
let mc = valid_match(0, 0);
assert_eq!(mc.and_then(|m| m.str), Some("real".to_string()));
assert_eq!(MINFO.get().unwrap().lock().unwrap().cur_idx, 1);
}
#[test]
fn test_build_pos_string() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(build_pos_string(&[1, 5, 10]), "1:5:10");
assert_eq!(build_pos_string(&[7]), "7");
assert_eq!(build_pos_string(&[]), "");
}
#[test]
fn test_list_lines() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(list_lines(&vec!["a".into(); 10], 3), 4);
assert_eq!(list_lines(&vec!["a".into(); 6], 3), 2);
}
#[test]
fn comp_mod_positive() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(comp_mod(1, 5), 0); assert_eq!(comp_mod(3, 5), 2); assert_eq!(comp_mod(5, 5), 4); assert_eq!(comp_mod(6, 5), 0); }
#[test]
fn comp_mod_zero_branches_negative() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(comp_mod(0, 5), 4); assert_eq!(comp_mod(-1, 5), 4); assert_eq!(comp_mod(-5, 5), 0); assert_eq!(comp_mod(-6, 5), 4); }
#[test]
fn comp_list_sets_onlyexpl() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
comp_list(Some("expl"));
assert_eq!(onlyexpl.load(Ordering::SeqCst), 1);
comp_list(Some("messages"));
assert_eq!(onlyexpl.load(Ordering::SeqCst), 2);
comp_list(Some("expl messages"));
assert_eq!(onlyexpl.load(Ordering::SeqCst), 3);
comp_list(Some("nothing"));
assert_eq!(onlyexpl.load(Ordering::SeqCst), 0);
comp_list(None);
assert_eq!(onlyexpl.load(Ordering::SeqCst), 0);
}
#[test]
fn skipnolist_skips_hide_and_nolist() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.flags = CMF_NOLIST;
let mut b = Cmatch::default();
b.flags = CMF_HIDE;
let c = Cmatch::default(); let v = vec![a, b, c];
assert_eq!(skipnolist(&v, 0), 2);
}
#[test]
fn skipnolist_showall_keeps_nolist() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.flags = CMF_NOLIST;
let v = vec![a];
assert_eq!(skipnolist(&v, 1), 0);
}
#[test]
fn skipnolist_skips_disp_displine() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut a = Cmatch::default();
a.disp = Some("display".into());
a.flags = CMF_DISPLINE;
let b = Cmatch::default();
let v = vec![a, b];
assert_eq!(skipnolist(&v, 0), 1);
}
#[test]
fn compresult_corpus_unambig_data_empty_input_empty_output() {
let _g = crate::test_util::global_state_lock();
assert_eq!(unambig_data(&[]), "");
}
#[test]
fn compresult_corpus_unambig_data_single_input_returns_it() {
let _g = crate::test_util::global_state_lock();
let matches = vec!["only_one".to_string()];
assert_eq!(unambig_data(&matches), "only_one");
}
#[test]
fn compresult_corpus_unambig_data_returns_lcp() {
let _g = crate::test_util::global_state_lock();
let matches = vec![
"prefix_alpha".to_string(),
"prefix_beta".to_string(),
"prefix_gamma".to_string(),
];
assert_eq!(unambig_data(&matches), "prefix_");
}
#[test]
fn compresult_corpus_unambig_data_no_shared_prefix() {
let _g = crate::test_util::global_state_lock();
let matches = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
assert_eq!(unambig_data(&matches), "");
}
#[test]
fn compresult_corpus_unambig_data_one_is_prefix_of_other() {
let _g = crate::test_util::global_state_lock();
let matches = vec!["abc".to_string(), "abcdef".to_string()];
assert_eq!(unambig_data(&matches), "abc");
}
#[test]
fn compresult_corpus_build_pos_string_one_indexed() {
let _g = crate::test_util::global_state_lock();
assert_eq!(build_pos_string(&[0, 5]), "0:5");
assert_eq!(build_pos_string(&[4, 5]), "4:5");
assert_eq!(build_pos_string(&[99, 100]), "99:100");
}
#[test]
fn compresult_corpus_build_pos_string_includes_slash() {
let _g = crate::test_util::global_state_lock();
let s = build_pos_string(&[2, 10]);
assert!(s.contains(':'));
assert_eq!(s, "2:10");
}
#[test]
fn build_pos_string_first_of_one_returns_one_slash_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(build_pos_string(&[1]), "1");
}
#[test]
fn build_pos_string_first_index_is_one_not_zero() {
let _g = crate::test_util::global_state_lock();
let s = build_pos_string(&[0, 5]);
assert_eq!(s, "0:5", "positions rendered verbatim; got {s}");
}
#[test]
fn unambig_data_all_identical_returns_full_string() {
let _g = crate::test_util::global_state_lock();
let matches = vec!["hello".to_string(), "hello".to_string()];
let r = unambig_data(&matches);
assert_eq!(r, "hello", "identical matches → full string");
}
#[test]
fn unambig_data_empty_matches_returns_empty() {
let _g = crate::test_util::global_state_lock();
let r = unambig_data(&[]);
assert!(r.is_empty(), "no matches → empty unambig prefix");
}
#[test]
fn unambig_data_common_prefix_of_three() {
let _g = crate::test_util::global_state_lock();
let matches = vec![
"foobar".to_string(),
"football".to_string(),
"foo".to_string(),
];
assert_eq!(unambig_data(&matches), "foo", "common prefix = 'foo'");
}
#[test]
fn unambig_data_no_common_prefix_returns_empty() {
let _g = crate::test_util::global_state_lock();
let matches = vec!["abc".to_string(), "xyz".to_string()];
let r = unambig_data(&matches);
assert!(r.is_empty(), "no common prefix → empty");
}
#[test]
fn valid_match_pre_suf_wrap_matches() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMULT.store(1, Relaxed);
menuacc.store(0, Relaxed);
let mut a = Cmatch::default();
a.str = Some("alpha".to_string());
let mut b = Cmatch::default();
b.str = Some("beta".to_string());
let mut g = Cmgroup::default();
g.matches = vec![a, b];
g.mcount = 2;
*amatches.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock().unwrap() = vec![g];
{
let mut mi = MINFO.get_or_init(|| std::sync::Mutex::new(Menuinfo::default())).lock().unwrap();
*mi = Menuinfo::default();
}
assert_eq!(valid_match(0, 0).and_then(|m| m.str), Some("alpha".to_string()));
}
#[test]
fn valid_match_wrong_prefix_returns_false() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMULT.store(1, Relaxed);
menuacc.store(0, Relaxed);
let mut skip = Cmatch::default();
skip.str = Some(String::new()); skip.flags = CMF_NOLIST;
let mut real = Cmatch::default();
real.str = Some("real".to_string());
let mut g = Cmgroup::default();
g.matches = vec![skip, real];
g.mcount = 2;
*amatches.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock().unwrap() = vec![g];
{
let mut mi = MINFO.get_or_init(|| std::sync::Mutex::new(Menuinfo::default())).lock().unwrap();
*mi = Menuinfo::default();
}
assert_eq!(valid_match(0, 0).and_then(|m| m.str), Some("real".to_string()));
}
#[test]
fn comp_mod_positive_v_subtracts_one_then_mods() {
let _g = crate::test_util::global_state_lock();
assert_eq!(comp_mod(7, 3), 0, "(7-1)%3 = 6%3 = 0");
assert_eq!(comp_mod(6, 3), 2, "(6-1)%3 = 5%3 = 2");
assert_eq!(comp_mod(4, 3), 0, "(4-1)%3 = 3%3 = 0");
}
#[test]
fn comp_mod_zero_v_wraps_to_m_minus_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(comp_mod(0, 5), 4, "(0-1) wrapped to [0,5) = 4");
}
#[test]
fn cut_cline_short_string_is_identity() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
assert!(cut_cline(None).is_none());
let mut n = Cline::default();
n.word = Some("abc".to_string());
n.wlen = 3;
let out = cut_cline(Some(Box::new(n))).expect("kept");
assert_eq!(out.word.as_deref(), Some("abc"));
}
#[test]
fn cut_cline_long_string_truncates_with_ellipsis() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
let mut n = Cline::default();
n.word = Some("abcdefghij".to_string());
n.wlen = 10;
let out = cut_cline(Some(Box::new(n))).expect("kept");
assert_eq!(out.min, 10, "min set from cline_sublen (wlen)");
}
#[test]
fn cut_cline_at_exact_length_is_identity() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
let mut n2 = Cline::default();
n2.word = Some("lo".to_string());
n2.wlen = 2;
let mut n1 = Cline::default();
n1.word = Some("hel".to_string());
n1.wlen = 3;
n1.next = Some(Box::new(n2));
let out = cut_cline(Some(Box::new(n1))).expect("kept");
assert_eq!(out.word.as_deref(), Some("hel"));
assert_eq!(out.next.as_ref().and_then(|n| n.word.as_deref()), Some("lo"));
}
#[test]
fn build_pos_string_1_indexed_display() {
assert_eq!(build_pos_string(&[0, 10]), "0:10");
assert_eq!(build_pos_string(&[4, 10]), "4:10");
assert_eq!(build_pos_string(&[9, 10]), "9:10");
}
#[test]
fn build_pos_string_single_match() {
assert_eq!(build_pos_string(&[1]), "1");
}
#[test]
fn cline_str_none_returns_empty_pin() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = String::new();
}
ZLEMETACS.store(0, Relaxed);
ZLEMETALL.store(0, Relaxed);
WB.store(0, Relaxed);
WE.store(0, Relaxed);
assert_eq!(cline_str(None, 0, None, None), Some(String::new()));
}
#[test]
fn valid_match_empty_pfx_sfx_accepts_anything() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMULT.store(1, Relaxed);
menuacc.store(0, Relaxed);
let mut only = Cmatch::default();
only.str = Some("foo".to_string());
let mut g = Cmgroup::default();
g.matches = vec![only];
g.mcount = 1;
*amatches.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock().unwrap() = vec![g];
{
let mut mi = MINFO.get_or_init(|| std::sync::Mutex::new(Menuinfo::default())).lock().unwrap();
*mi = Menuinfo::default();
}
assert_eq!(valid_match(0, 0).and_then(|m| m.str), Some("foo".to_string()));
}
#[test]
fn valid_match_prefix_only() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMULT.store(1, Relaxed);
menuacc.store(0, Relaxed);
let mut a = Cmatch::default();
a.str = Some("first".to_string());
let mut b = Cmatch::default();
b.str = Some("second".to_string());
let mut g = Cmgroup::default();
g.matches = vec![a, b];
g.mcount = 2;
*amatches.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock().unwrap() = vec![g];
{
let mut mi = MINFO.get_or_init(|| std::sync::Mutex::new(Menuinfo::default())).lock().unwrap();
*mi = Menuinfo::default();
}
assert_eq!(valid_match(0, 1).and_then(|m| m.str), Some("second".to_string()));
}
#[test]
fn valid_match_suffix_only() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMULT.store(-1, Relaxed);
menuacc.store(0, Relaxed);
let mut a = Cmatch::default();
a.str = Some("first".to_string());
let mut b = Cmatch::default();
b.str = Some("last".to_string());
let mut g = Cmgroup::default();
g.matches = vec![a, b];
g.mcount = 2;
*amatches.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock().unwrap() = vec![g];
{
let mut mi = MINFO.get_or_init(|| std::sync::Mutex::new(Menuinfo::default())).lock().unwrap();
*mi = Menuinfo::default();
}
assert_eq!(valid_match(0, 1).and_then(|m| m.str), Some("last".to_string()));
}
#[test]
fn comp_mod_v_above_m_wraps() {
assert_eq!(comp_mod(10, 3), 0, "(10-1)%3 = 9%3 = 0");
assert_eq!(comp_mod(11, 3), 1, "(11-1)%3 = 10%3 = 1");
assert_eq!(comp_mod(12, 3), 2, "(12-1)%3 = 11%3 = 2");
}
#[test]
fn comp_mod_negative_v_wraps_via_loop() {
assert_eq!(comp_mod(-1, 5), 4);
assert_eq!(comp_mod(-3, 5), 2);
assert_eq!(comp_mod(-7, 5), 3, "-7 + 5 + 5 = 3");
}
#[test]
fn unambig_data_empty_matches_returns_empty_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(unambig_data(&[]), "");
}
#[test]
fn unambig_data_single_match_returns_it() {
let _g = crate::test_util::global_state_lock();
let r = unambig_data(&["single".to_string()]);
assert_eq!(r, "single", "single match → common prefix is itself");
}
#[test]
fn cut_cline_empty_input_returns_empty() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
assert!(cut_cline(None).is_none());
crate::ported::zle::compcore::hasmatched.store(1, Relaxed);
assert!(cut_cline(None).is_none());
}
#[test]
fn cut_cline_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::zle::compcore::hasmatched.store(0, Relaxed);
for w in ["", "abc", "hello world"] {
let mut base = Cline::default();
base.word = Some(w.to_string());
base.wlen = w.len() as i32;
let first = cut_cline(Some(Box::new(base.clone()))).and_then(|n| n.word);
for _ in 0..3 {
let again = cut_cline(Some(Box::new(base.clone()))).and_then(|n| n.word);
assert_eq!(again, first, "cut_cline({w:?}) must be deterministic");
}
}
}
#[test]
fn build_pos_string_returns_string_type() {
let _: String = build_pos_string(&[1]);
}
#[test]
fn build_pos_string_is_pure() {
for v in [vec![1i32], vec![5, 10], vec![100, 1000, 3]] {
let first = build_pos_string(&v);
for _ in 0..3 {
assert_eq!(
build_pos_string(&v),
first,
"build_pos_string({v:?}) must be pure"
);
}
}
}
#[test]
fn unambig_data_empty_returns_empty_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(unambig_data(&[]), "");
}
#[test]
fn hasbrpsfx_returns_bool_type() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut m = Cmatch::default();
m.flags = CMF_ALL;
let r: bool = hasbrpsfx(&m, None, None);
assert!(r, "CMF_ALL short-circuits to true");
}
#[test]
fn hasbrpsfx_is_pure() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
if let Some(mx) = BRBEG.get() {
*mx.lock().unwrap() = None;
}
if let Some(mx) = BREND.get() {
*mx.lock().unwrap() = None;
}
if let Ok(mut g) = ZLEMETALINE.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*g = "foo".to_string();
}
ZLEMETACS.store(3, Relaxed);
ZLEMETALL.store(3, Relaxed);
let mut m = Cmatch::default();
m.str = Some("bar".to_string());
for _ in 0..3 {
assert!(hasbrpsfx(&m, None, None), "no braces → matches None/None");
assert_eq!(ZLEMETALINE.get().unwrap().lock().unwrap().clone(), "foo");
}
}
#[test]
fn comp_mod_zero_returns_m_minus_one() {
for m in [1i32, 5, 100, 1000] {
assert_eq!(
comp_mod(0, m),
m - 1,
"comp_mod(0, {}) = {}-1 = {} per c:1367 decrement+wrap",
m,
m,
m - 1
);
}
}
#[test]
fn comp_mod_result_less_than_modulus() {
for v in [-100i32, -1, 0, 1, 50, 100] {
for m in [1i32, 5, 10] {
let r = comp_mod(v, m);
assert!(
r >= 0 && r < m,
"comp_mod({}, {}) = {} must be in [0, {})",
v,
m,
r,
m
);
}
}
}
#[test]
fn valid_match_returns_bool_type() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*amatches.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock().unwrap() = Vec::new();
let r: Option<Cmatch> = valid_match(0, 0);
assert!(r.is_none(), "no groups → no valid match");
}
#[test]
fn skipnolist_empty_returns_zero() {
assert_eq!(skipnolist(&[], 0), 0);
assert_eq!(skipnolist(&[], 1), 0);
}
#[test]
fn do_ambiguous_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = do_ambiguous(&[]);
}
#[test]
fn ztat_returns_option_metadata_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<std::fs::Metadata> = ztat("/__never_zshrs__", false);
}
#[test]
fn ztat_nonexistent_returns_none() {
assert!(
ztat("/__never_real_path_zshrs_xyz__", false).is_none(),
"nonexistent → None"
);
assert!(
ztat("/__never_real_path_zshrs_xyz__", true).is_none(),
"nonexistent w/ symlink follow → None"
);
}
#[test]
#[cfg(unix)]
fn ztat_tmp_returns_some() {
assert!(ztat("/tmp", false).is_some(), "/tmp must stat → Some");
}
#[test]
fn do_ambig_menu_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = do_ambig_menu();
}
#[test]
fn list_lines_empty_matches_returns_zero() {
assert_eq!(list_lines(&[], 80), 0, "0 matches → 0 lines");
}
#[test]
fn list_lines_returns_usize_type() {
let _: usize = list_lines(&[], 80);
}
#[test]
fn calclist_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = calclist(0);
}
#[test]
fn asklist_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = asklist();
}
#[test]
fn printlist_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = printlist(0, 0);
}
#[test]
fn bld_all_str_returns_string_type() {
let _g = crate::test_util::global_state_lock();
let _: String = bld_all_str();
}
#[test]
fn ilistmatches_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = ilistmatches();
}
#[test]
fn list_matches_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = list_matches();
}
}