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_LINE, CLF_SUF, CMF_ALL, CMF_DISPLINE, CMF_FILE, CMF_HIDE, CMF_MULT,
CMF_NOLIST, CMF_PACKED, CMF_ROWS,
};
use crate::ported::zle::compcore::{amatches, fromcomp, iforcemenu, insmnum, lastmatches, lastpermmnum, listdat as listdat_static, menuacc, nmatches as nmatches_g, nmatches, oldins, oldlist, onlyexpl, MINFO};
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::tcoutclear;
use crate::ported::zle::zle_tricky::printfmt;
use crate::ported::zsh_h::{isset, LISTPACKED, LISTROWSFIRST, LISTTYPES, USEZLE};
#[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::*,
};
#[allow(unused_imports)]
#[allow(unused_imports)]
pub fn cut_cline(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len.saturating_sub(3)])
}
}
pub fn cline_str(
l: Option<&Cline>,
) -> String {
let mut out = String::new();
let mut cur = l;
while let Some(node) = cur {
if node.olen != 0 && (node.flags & CLF_SUF) == 0 && node.prefix.is_none() {
if let Some(o) = &node.orig {
out.push_str(o);
}
} else {
let mut p = node.prefix.as_deref();
while let Some(part) = p {
let s = if (part.flags & CLF_LINE) != 0 {
part.line.as_deref()
} else {
part.word.as_deref()
};
if let Some(s) = s {
out.push_str(s);
}
p = part.next.as_deref();
}
}
let anchor = if (node.flags & CLF_LINE) != 0 {
node.line.as_deref()
} else {
node.word.as_deref()
};
if let Some(a) = anchor {
out.push_str(a);
}
if node.olen != 0 && (node.flags & CLF_SUF) != 0 && node.suffix.is_none() {
if let Some(o) = &node.orig {
out.push_str(o);
}
} else {
let mut p = node.suffix.as_deref();
while let Some(part) = p {
let s = if (part.flags & CLF_LINE) != 0 {
part.line.as_deref()
} else {
part.word.as_deref()
};
if let Some(s) = s {
out.push_str(s);
}
p = part.next.as_deref();
}
}
cur = node.next.as_deref();
}
out
}
pub fn build_pos_string(current: usize, total: usize) -> String {
format!("{}/{}", current + 1, total)
}
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(
buffer: &str,
cursor: usize,
word_start: usize,
word_end: usize,
replacement: &str,
) -> (String, usize) {
let mut result = String::with_capacity(buffer.len() + replacement.len());
result.push_str(&buffer[..word_start]);
result.push_str(replacement);
result.push_str(&buffer[word_end..]);
let new_cursor = word_start + replacement.len();
(result, new_cursor)
}
pub fn hasbrpsfx(s: &str) -> bool {
s.contains('{') || s.contains('}')
}
pub fn do_ambiguous(matches: &[String]) -> i32 {
MENUCMP.store(0, Relaxed);
LASTAMBIG.store(1, Relaxed);
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.as_ref())) } 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, 0); let _ = inststr(&prefix); }
}
if !prefix.is_empty() {
1
} else {
0
}
}
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(
buffer: &str,
cursor: usize,
word_start: usize,
word_end: usize,
matches: &[String],
separator: &str,
) -> (String, usize) {
let all = matches.join(separator);
instmatch(buffer, cursor, word_start, word_end, &all)
}
pub fn do_single(
buffer: &str,
cursor: usize,
word_start: usize,
word_end: usize,
the_match: &str,
add_space: bool,
) -> (String, usize) {
fixsuffix();
let suffix = if add_space { " " } else { "" };
let replacement = format!("{}{}", the_match, suffix);
instmatch(buffer, cursor, word_start, word_end, &replacement)
}
pub fn valid_match(word: &str, prefix: &str, suffix: &str) -> bool {
word.starts_with(prefix) && (suffix.is_empty() || word.ends_with(suffix))
}
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(
buffer: &str,
cursor: usize,
word_start: usize,
word_end: usize,
selected: &str,
) -> (String, usize) {
use std::sync::atomic::Ordering;
if menuacc.load(Relaxed) == 0 {
let prebr = LASTPREBR
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|s| s.clone())
.unwrap_or_default();
let postbr = LASTPOSTBR
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|s| s.clone())
.unwrap_or_default();
if let Ok(mut m) = MINFO
.get_or_init(|| std::sync::Mutex::new(Menuinfo::default()))
.lock()
{
m.prebr = Some(prebr.clone()); m.postbr = Some(postbr.clone()); }
if LISTSHOWN.load(Relaxed) != 0 && (!prebr.is_empty() || !postbr.is_empty()) {
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 m in &g.matches {
let s = m.str.as_deref().unwrap_or("");
if !hasbrpsfx(s) {
SHOWINGLIST.store(-2, Relaxed); break;
}
}
}
}
}
menuacc.fetch_add(1, Relaxed); do_single(buffer, cursor, word_start, word_end, selected, true)
}
pub fn comp_mod(mut v: i32, m: i32) -> i32 {
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("", 0, 0, 0, ""); }
} 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 chosen_group: Option<Cmgroup> = None;
for g in &groups {
if g.mcount > idx {
chosen_group = Some(g.clone());
break;
}
idx -= g.mcount;
}
let Some(g) = chosen_group 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;
};
let mc = g.matches.get(idx as usize).cloned();
if iforcemenu.load(Relaxed) != -1 {
if let Some(ref m) = mc {
if let Ok(mut g) = MINFO
.get_or_init(|| {
std::sync::Mutex::new(Menuinfo::default())
})
.lock()
{
g.cur = Some(Box::new(m.clone()));
}
}
}
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 zterm_columns = adjustcolumns() as i32; let zterm_lines = adjustlines() as i32;
{
let ld = listdat_static
.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.str.as_deref().unwrap_or("")) {
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 = listdat_static
.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 = listdat_static
.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 = listdat_static
.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;
tcoutclear(true); }
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;
tcoutclear(true);
}
}
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;
}
for m in &g.matches {
let visible = showall != 0 || (m.flags & (CMF_HIDE | CMF_NOLIST)) == 0;
if !visible {
continue;
}
let _ = iprintm(Some(g), Some(m), 0, 0, 1, 0);
if (m.flags & CMF_DISPLINE) == 0 {
let _ = write_loop(out_fd, b"\n");
}
ml += 1;
}
let _ = CGF_ROWS;
}
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 = listdat_static
.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;
}
let _ = printlist(0, 0);
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) = listdat_static
.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();
assert_eq!(cline_str(None), "");
}
#[test]
fn cline_str_emits_word_anchor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut n = Cline::default();
n.word = Some("hello".to_string());
n.wlen = 5;
assert_eq!(cline_str(Some(&n)), "hello");
}
#[test]
fn cline_str_emits_line_anchor_when_clf_line_set() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut n = Cline::default();
n.flags = CLF_LINE;
n.line = Some("LINE".to_string());
n.word = Some("word-should-not-emit".to_string());
assert_eq!(cline_str(Some(&n)), "LINE");
}
#[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();
let mut n = Cline::default();
n.orig = Some("original".to_string());
n.olen = 8;
n.word = Some("anchor".to_string());
assert_eq!(cline_str(Some(&n)), "originalanchor");
}
#[test]
fn cline_str_walks_prefix_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut p2 = Cline::default();
p2.word = Some("ond".to_string());
let mut p1 = Cline::default();
p1.word = Some("sec".to_string());
p1.next = Some(Box::new(p2));
let mut n = Cline::default();
n.prefix = Some(Box::new(p1));
n.word = Some("anchor".to_string());
assert_eq!(cline_str(Some(&n)), "secondanchor");
}
#[test]
fn cline_str_walks_next_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut n2 = Cline::default();
n2.word = Some("B".to_string());
let mut n1 = Cline::default();
n1.word = Some("A".to_string());
n1.next = Some(Box::new(n2));
assert_eq!(cline_str(Some(&n1)), "AB");
}
#[test]
fn test_instmatch() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (result, cursor) = instmatch("git co", 6, 4, 6, "commit");
assert_eq!(result, "git commit");
assert_eq!(cursor, 10);
}
#[test]
fn test_do_single() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (result, cursor) = do_single("git co", 6, 4, 6, "commit", true);
assert_eq!(result, "git commit ");
assert_eq!(cursor, 11);
}
#[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();
assert!(valid_match("foobar", "foo", ""));
assert!(valid_match("foobar", "foo", "bar"));
assert!(!valid_match("foobar", "baz", ""));
}
#[test]
fn test_build_pos_string() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(build_pos_string(0, 10), "1/10");
assert_eq!(build_pos_string(9, 10), "10/10");
}
#[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), "1/5");
assert_eq!(build_pos_string(4, 5), "5/5");
assert_eq!(build_pos_string(99, 100), "100/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, "3/10");
}
}