use crate::ported::module::gethookdef;
use crate::ported::utils::{write_loop, zwarn};
use crate::ported::zle::compcore::{
compfunc, ADDEDX, LASTCHAR, WB, WE, ZLEMETACS, ZLEMETALINE, ZLEMETALL,
};
use crate::ported::zle::zle_h::{
WidgetImpl, COMP_COMPLETE, COMP_EXPAND, COMP_EXPAND_COMPLETE, COMP_ISEXPAND,
COMP_LIST_COMPLETE, COMP_LIST_EXPAND, COMP_SPELL, CUT_RAW,
};
use crate::ported::zsh_h::{
isset, BASHAUTOLIST, GLOBCOMPLETE, MENUCOMPLETE, QT_BACKSLASH, QT_DOLLARS, QT_DOUBLE, QT_NONE,
QT_SINGLE, RECEXACT,
};
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
zle_params::*, zle_refresh::*, zle_utils::*, zle_vi::*, zle_word::*,
};
#[allow(unused_imports)]
#[allow(unused_imports)]
pub fn usetab() -> i32 {
let kb = crate::ported::zle::zle_keymap::keybuf.lock().unwrap();
if kb.first() != Some(&b'\t') || kb.len() > 1 {
return 0;
}
drop(kb);
let mut i = ZLECS.load(Ordering::SeqCst);
while i > 0 {
let c = ZLELINE.lock().unwrap()[i - 1];
if c == '\n' {
break;
}
if c != '\t' && c != ' ' {
return 0;
}
i -= 1;
}
let compfunc_set = compfunc
.get()
.and_then(|m| m.lock().ok().and_then(|g| g.clone()))
.is_some();
if compfunc_set {
WOULDINSTAB.store(1, Ordering::SeqCst);
return 0;
}
1
}
pub fn completecall(args: &[String]) -> i32 {
*cfargs.lock().unwrap() = args.to_vec();
cfret.store(0, Ordering::SeqCst);
let compwidget_g = COMPWIDGET.lock().unwrap();
let (base_fn, func_name) = match compwidget_g.as_ref().map(|w| (&w.u, w.flags)) {
Some((WidgetImpl::Comp { fn_, func, .. }, _)) => (Some(*fn_), Some(func.clone())),
_ => (None, None),
};
drop(compwidget_g);
if let Some(name) = func_name {
let g = compfunc.get_or_init(|| Mutex::new(None));
*g.lock().unwrap() = Some(name); }
let zlenoargs: &[String] = &[];
let r = match base_fn {
Some(f) => f(zlenoargs),
None => docomplete(COMP_COMPLETE),
};
if r != 0 && cfret.load(Ordering::SeqCst) == 0 {
cfret.store(1, Ordering::SeqCst); }
if let Some(g) = compfunc.get() {
*g.lock().unwrap() = None;
}
cfret.load(Ordering::SeqCst) }
pub fn completeword(args: &[String]) -> i32 {
USEMENU.store(isset(MENUCOMPLETE) as i32, Ordering::SeqCst); USEGLOB.store(isset(GLOBCOMPLETE) as i32, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); let lastch = LASTCHAR.load(Ordering::SeqCst);
if lastch == b'\t' as i32 && usetab() != 0 {
return selfinsert(args);
}
let usemenu_now = USEMENU.load(Ordering::SeqCst);
let menucmp_now = MENUCMP.load(Ordering::SeqCst);
if LASTAMBIG.load(Ordering::SeqCst) == 1
&& isset(BASHAUTOLIST)
&& usemenu_now == 0
&& menucmp_now == 0
{
BASHLISTFIRST.store(1, Ordering::SeqCst); let ret = docomplete(COMP_LIST_COMPLETE); BASHLISTFIRST.store(0, Ordering::SeqCst); LASTAMBIG.store(2, Ordering::SeqCst); return ret;
}
docomplete(COMP_COMPLETE) }
pub fn menucomplete(args: &[String]) -> i32 {
USEMENU.store(1, Ordering::SeqCst); USEGLOB.store(isset(GLOBCOMPLETE) as i32, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); let lastch = LASTCHAR.load(Ordering::SeqCst);
if lastch == b'\t' as i32 && usetab() != 0 {
return selfinsert(args);
}
docomplete(COMP_COMPLETE) }
pub fn listchoices(_args: &[String]) -> i32 {
let menu = isset(MENUCOMPLETE) as i32;
USEMENU.store(menu, Ordering::SeqCst);
let glob = isset(GLOBCOMPLETE) as i32;
USEGLOB.store(glob, Ordering::SeqCst);
WOULDINSTAB.store(0, Ordering::SeqCst);
docomplete(COMP_LIST_COMPLETE)
}
pub fn spellword(_args: &[String]) -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(0, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_SPELL) }
pub fn deletecharorlist(_args: &[String]) -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(1, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); if ZLECS.load(Ordering::SeqCst) == ZLELL.load(Ordering::SeqCst) {
docomplete(COMP_LIST_COMPLETE)
} else {
deletechar()
}
}
pub fn expandword(_args: &[String]) -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(0, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_EXPAND) }
pub fn expandorcomplete(args: &[String]) -> i32 {
USEMENU.store(isset(MENUCOMPLETE) as i32, Ordering::SeqCst); USEGLOB.store(isset(GLOBCOMPLETE) as i32, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); let lastch = LASTCHAR.load(Ordering::SeqCst);
if lastch == b'\t' as i32 && usetab() != 0 {
return selfinsert(args);
}
let usemenu_now = USEMENU.load(Ordering::SeqCst);
let menucmp_now = MENUCMP.load(Ordering::SeqCst);
if LASTAMBIG.load(Ordering::SeqCst) == 1
&& isset(BASHAUTOLIST)
&& usemenu_now == 0
&& menucmp_now == 0
{
BASHLISTFIRST.store(1, Ordering::SeqCst); let ret = docomplete(COMP_LIST_COMPLETE); BASHLISTFIRST.store(0, Ordering::SeqCst); LASTAMBIG.store(2, Ordering::SeqCst); return ret;
}
docomplete(COMP_EXPAND_COMPLETE) }
pub fn menuexpandorcomplete(args: &[String]) -> i32 {
USEMENU.store(1, Ordering::SeqCst); USEGLOB.store(isset(GLOBCOMPLETE) as i32, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); let lastch = LASTCHAR.load(Ordering::SeqCst);
if lastch == b'\t' as i32 && usetab() != 0 {
return selfinsert(args);
}
docomplete(COMP_EXPAND_COMPLETE) }
pub fn listexpand(_args: &[String]) -> i32 {
let menu = isset(MENUCOMPLETE) as i32;
USEMENU.store(menu, Ordering::SeqCst); let glob = isset(GLOBCOMPLETE) as i32;
USEGLOB.store(glob, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_LIST_EXPAND) }
pub fn reversemenucomplete(args: &[String]) -> i32 {
WOULDINSTAB.store(0, Ordering::SeqCst); {
let mut g = ZMOD.lock().unwrap();
g.mult = -g.mult;
}
menucomplete(args) }
pub fn acceptandmenucomplete(args: &[String]) -> i32 {
WOULDINSTAB.store(0, Ordering::SeqCst); if MENUCMP.load(Ordering::SeqCst) == 0 {
return 1;
}
let h_accept = gethookdef("accept_comp");
if !h_accept.is_null() {
crate::ported::module::runhookdef(h_accept, std::ptr::null_mut());
}
menucomplete(args)
}
pub fn checkparams(p: &str) -> i32 {
let l = p.len();
let mut n = 0;
let mut exact = false;
if let Ok(tab) = crate::ported::params::paramtab().read() {
for name in tab.keys() {
if name.starts_with(p) {
n += 1; if name.len() == l {
exact = true; }
if n >= 2 {
break;
}
}
}
}
if n == 1 {
return if crate::ported::params::getsparam(p).is_some() {
1
} else {
0
};
}
let menucmp = MENUCMP.load(Ordering::SeqCst) != 0;
let hascompmod = HASCOMPMOD.load(Ordering::SeqCst);
let recexact = isset(RECEXACT);
if !menucmp && exact && (!hascompmod || recexact) {
1
} else {
0
}
}
pub fn cmphaswilds(str: &str) -> i32 {
use crate::ported::zsh_h::{
isset, Equals, Hat, Inang, Inbrace, Inbrack, Inpar, Outang, Outbrace, Outbrack, Outpar,
Pound, Qstring, Quest, Star, Stringg, Tilde, EXTENDEDGLOB, IGNOREBRACES,
};
let mut s = str;
let bar_byte = b'|' as char;
let chars: Vec<char> = s.chars().collect();
if chars.len() == 1 && (chars[0] == Inbrack as char || chars[0] == Outbrack as char) {
return 0;
}
if chars.len() >= 2 && chars[0] == '%' && chars[1] == Quest as char {
s = &s[2..];
}
let chars2: Vec<char> = s.chars().collect();
if chars2.len() >= 2 && chars2[0] == Tilde as char && chars2[1] == Inbrack as char {
if let Some(pos) = s[2..].find(Outbrack as char) {
let advance = 2 + pos + (Outbrack as char).len_utf8();
s = &s[advance..];
}
}
while let Some(c) = s.chars().next() {
if c == Stringg as char || c == Qstring as char {
s = &s[c.len_utf8()..];
let next_c = s.chars().next();
if next_c == Some(Inbrace as char) {
let _ = crate::ported::utils::skipparens(Inbrace as char, Outbrace as char, &mut s);
} else if next_c == Some(Stringg as char) || next_c == Some(Qstring as char) {
s = &s[next_c.unwrap().len_utf8()..];
} else {
while let Some(p) = s.chars().next() {
if p != '^'
&& p != Hat as char
&& p != '='
&& p != Equals as char
&& p != '~'
&& p != Tilde as char
{
break;
}
s = &s[p.len_utf8()..];
}
let p = s.chars().next();
if p == Some('#') || p == Some(Pound as char) {
s = &s[p.unwrap().len_utf8()..];
}
let p = s.chars().next();
if p == Some(Star as char) || p == Some(Quest as char) {
s = &s[p.unwrap().len_utf8()..];
}
}
} else {
let is_extglob_meta = (c == Pound as char || c == Hat as char) && isset(EXTENDEDGLOB);
let is_simple_wild = c == Star as char || c == bar_byte || c == Quest as char;
let mut s_try = s;
let brack_balanced =
crate::ported::utils::skipparens(Inbrack as char, Outbrack as char, &mut s_try)
== 0;
let mut s_try = s;
let ang_balanced =
crate::ported::utils::skipparens(Inang as char, Outang as char, &mut s_try) == 0;
let mut s_try = s;
let brace_balanced = !isset(IGNOREBRACES)
&& crate::ported::utils::skipparens(Inbrace as char, Outbrace as char, &mut s_try)
== 0;
let mut s_try = s;
let pchars: Vec<char> = s.chars().collect();
let pair_colon = pchars.first() == Some(&(Inpar as char))
&& pchars.get(1) == Some(&':')
&& crate::ported::utils::skipparens(Inpar as char, Outpar as char, &mut s_try) == 0;
if is_extglob_meta
|| is_simple_wild
|| brack_balanced
|| ang_balanced
|| brace_balanced
|| pair_colon
{
return 1;
}
s = &s[c.len_utf8()..];
}
}
0 }
pub fn parambeg(s: &str, offs: usize) -> Option<usize> {
use crate::ported::zsh_h::{
Dnull, Equals, Hat, Inbrace, Inbrack, Inpar, Outbrace, Outpar, Pound, Qstring, Quest, Star,
Stringg, Tilde,
};
use crate::ported::ztype_h::{idigit, INAMESPC};
let bytes = s.as_bytes();
if offs > bytes.len() {
return None;
}
let mut p = offs.min(bytes.len());
while p > 0 {
let b = bytes[p.saturating_sub(1)];
if p < bytes.len() && (bytes[p] == Stringg as u8 || bytes[p] == Qstring as u8) {
break;
}
if b == Stringg as u8 || b == Qstring as u8 {
p -= 1;
break;
}
p -= 1;
}
if p >= bytes.len() {
return None;
}
let pchar = bytes[p];
if pchar == Stringg as u8 || pchar == Qstring as u8 {
while p > 0 && (bytes[p - 1] == Stringg as u8 || bytes[p - 1] == Qstring as u8) {
p -= 1;
}
while p + 2 < bytes.len()
&& (bytes[p + 1] == Stringg as u8 || bytes[p + 1] == Qstring as u8)
&& (bytes[p + 2] == Stringg as u8 || bytes[p + 2] == Qstring as u8)
{
p += 2;
}
}
if p >= bytes.len() {
return None;
}
let pchar = bytes[p];
let after = bytes.get(p + 1).copied().unwrap_or(0);
if !(pchar == Stringg as u8 || pchar == Qstring as u8)
|| after == Inpar as u8
|| after == Inbrack as u8
|| after == b'\''
{
return None;
}
let mut b = p + 1;
let mut br = 1;
let mut n: i32 = 0;
if b < bytes.len() && bytes[b] == Inbrace as u8 {
let tb_str = &s[b..];
let mut tb = tb_str;
if crate::ported::utils::skipparens(Inbrace as char, Outbrace as char, &mut tb) != 0 {
return None;
}
b += 1;
br += 1;
let _ = br;
let mut b_str: &str = &s[b..];
n = crate::ported::utils::skipparens(Inpar as char, Outpar as char, &mut b_str);
b = s.len() - b_str.len();
}
while b < bytes.len() {
let bb = bytes[b];
if bb != b'^'
&& bb != Hat as u8
&& bb != b'='
&& bb != Equals as u8
&& bb != b'~'
&& bb != Tilde as u8
{
break;
}
b += 1;
}
if b < bytes.len() && (bytes[b] == b'#' || bytes[b] == Pound as u8 || bytes[b] == b'+') {
b += 1;
}
let mut e = b;
if br != 0 {
while e < bytes.len() && bytes[e] == Dnull as u8 {
e += 1;
}
}
if e < bytes.len() {
let eb = bytes[e];
if eb == Quest as u8
|| eb == Star as u8
|| eb == Stringg as u8
|| eb == Qstring as u8
|| eb == b'?'
|| eb == b'*'
|| eb == b'$'
|| eb == b'-'
|| eb == b'!'
|| eb == b'@'
{
e += 1;
} else if idigit(eb) {
while e < bytes.len() && idigit(bytes[e]) {
e += 1;
}
} else {
let _ = INAMESPC;
let span =
crate::ported::utils::itype_end(&s[e..], crate::ported::ztype_h::INAMESPC, false);
e += span;
}
}
if offs <= e && offs >= b && n <= 0 {
if br != 0 {
let mut pp = e;
while pp < bytes.len() && bytes[pp] == Dnull as u8 {
pp += 1;
}
let _ = pp;
}
return Some(b);
}
None
}
pub fn docomplete(lst: i32) -> i32 {
thread_local! { static ACTIVE: std::cell::Cell<bool> =
const { std::cell::Cell::new(false) }; }
if ACTIVE.with(|c| c.get()) {
zwarn("completion cannot be used recursively (yet)");
return 1;
}
ACTIVE.with(|c| c.set(true));
let mut lst_box = lst;
let h_before = gethookdef("before_complete");
if !h_before.is_null() {
let lst_ptr = (&mut lst_box) as *mut i32 as *mut std::ffi::c_void;
crate::ported::module::runhookdef(h_before, lst_ptr);
}
if doexpandhist() != 0 {
ACTIVE.with(|c| c.set(false));
return 0;
}
let origword = get_comp_string();
let line = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let s_word: String = origword.unwrap_or_else(|| line.clone());
let lincmd = LINCMD.load(Ordering::SeqCst); let olst = lst;
let ret;
if lst == COMP_SPELL {
let wb = WB.load(Ordering::SeqCst);
let we = WE.load(Ordering::SeqCst);
if we > wb {
ZLEMETACS.store(wb, Ordering::SeqCst);
foredel(we - wb, CUT_RAW);
}
let mut x = s_word.clone(); let ox = s_word.clone(); crate::ported::utils::spckword(&mut x, 0, lincmd, 0);
let r = if x == ox { 1 } else { 0 };
let _ = inststrlen(&x, true, -1);
ret = r;
} else if COMP_ISEXPAND(lst) {
let ol_before = line.clone(); let ne = crate::ported::exec::noerrs.load(Ordering::SeqCst); crate::ported::exec::noerrs.store(1, Ordering::SeqCst); let mut ret_local = doexpansion(&s_word, lst, olst, lincmd); LASTAMBIG.store(0, Ordering::SeqCst); crate::ported::exec::noerrs.store(ne, Ordering::SeqCst);
let after = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if olst == COMP_EXPAND_COMPLETE && ol_before == after {
crate::ported::utils::errflag
.fetch_and(!crate::ported::utils::ERRFLAG_ERROR, Ordering::SeqCst);
ret_local = docompletion(&s_word, lst, lincmd); } else if ret_local != 0 {
CLEARLIST.store(1, Ordering::SeqCst);
}
ret = ret_local;
} else {
ret = docompletion(&s_word, lst, lincmd);
}
let mut dat: [i32; 2] = [ret, 0];
let h_after = gethookdef("after_complete");
if !h_after.is_null() {
let dat_ptr = dat.as_mut_ptr() as *mut std::ffi::c_void;
crate::ported::module::runhookdef(h_after, dat_ptr);
}
ACTIVE.with(|c| c.set(false));
ret
}
pub fn addx(ptmp: &mut String) -> i32 {
let cs = ZLECS.load(Ordering::SeqCst) as usize;
let ll = ZLELL.load(Ordering::SeqCst) as usize;
let instring = INSTRING.load(Ordering::SeqCst);
let comppref = COMPPREF.load(Ordering::SeqCst) != 0;
let (ch_at, prev_at): (Option<char>, Option<char>) = {
let line = ZLELINE.lock().unwrap();
let at = line.get(cs).copied();
let prev = if cs > 0 {
line.get(cs - 1).copied()
} else {
None
};
(at, prev)
};
let is_iblank = matches!(ch_at, Some(' ' | '\t'));
let is_blank_unescaped = is_iblank && (cs == 0 || prev_at != Some('\\'));
let cs_at_end = ch_at.is_none() || cs >= ll;
let is_newline = ch_at == Some('\n'); let is_separator = matches!(ch_at, Some(')' | '`' | '}' | ';' | '|' | '&' | '>' | '<')); let is_instring_quote = instring != QT_NONE && matches!(ch_at, Some('"' | '\''));
let addspace = comppref && ch_at.is_some()
&& !matches!(ch_at, Some(' ' | '\t'));
let fire = cs_at_end
|| is_newline
|| is_blank_unescaped
|| is_separator
|| is_instring_quote
|| addspace;
if fire {
let snap: String = ZLELINE.lock().unwrap().iter().collect();
*ptmp = snap;
let mut line = ZLELINE.lock().unwrap();
line.insert(cs, 'x');
if addspace {
line.insert(cs + 1, ' ');
}
drop(line);
let added = if addspace { 2 } else { 1 };
ADDEDX.store(added, Ordering::SeqCst);
ZLELL.fetch_add(added as usize, Ordering::SeqCst);
added
} else {
ADDEDX.store(0, Ordering::SeqCst);
ptmp.clear();
0
}
}
pub fn dupstrspace(str: &str) -> String {
let len = str.len(); let mut out = String::with_capacity(len + 2); out.push_str(str); out.push(' '); out }
pub fn freebrinfo(p: Option<crate::ported::zle::zle_h::BrinfoPtr>) {
drop(p);
}
pub fn dupbrinfo(
mut p: Option<&crate::ported::zle::zle_h::brinfo>,
) -> (
Option<crate::ported::zle::zle_h::BrinfoPtr>,
Option<*const crate::ported::zle::zle_h::brinfo>,
) {
let mut head: Option<crate::ported::zle::zle_h::BrinfoPtr> = None; let mut last_ptr: Option<*const crate::ported::zle::zle_h::brinfo> = None;
let mut tail: *mut Option<crate::ported::zle::zle_h::BrinfoPtr> = &mut head;
while let Some(node) = p {
let cloned = Box::new(crate::ported::zle::zle_h::brinfo {
next: None, prev: None, str: node.str.clone(), pos: node.pos, qpos: node.qpos, curpos: node.curpos, });
unsafe {
*tail = Some(cloned);
let inserted = (*tail).as_mut().unwrap();
last_ptr = Some(inserted.as_ref() as *const _);
tail = &mut inserted.next;
}
p = node.next.as_deref(); }
(head, last_ptr)
}
pub fn has_real_token(s: &str) -> bool {
let special = ['$', '`', '"', '\'', '\\', '{', '}', '[', ']', '*', '?', '~'];
let mut escaped = false;
for c in s.chars() {
if escaped {
escaped = false;
continue;
}
if c == '\\' {
escaped = true;
continue;
}
if special.contains(&c) {
return true;
}
}
false
}
pub fn get_comp_string() -> Option<String> {
let snap: String = ZLELINE.lock().unwrap().iter().collect();
let cs = ZLECS.load(Ordering::SeqCst).min(snap.len());
let bytes = snap.as_bytes();
let mut start = cs;
while start > 0 && !bytes[start - 1].is_ascii_whitespace() {
start -= 1;
}
let mut end = cs;
while end < bytes.len() && !bytes[end].is_ascii_whitespace() {
end += 1;
}
if start == end {
return None;
}
WB.store(start as i32, Ordering::SeqCst);
WE.store(end as i32, Ordering::SeqCst);
let mut p = start;
while p > 0 && bytes[p - 1].is_ascii_whitespace() {
p -= 1;
}
let in_cmdpos = if p == 0 {
true
} else {
let prev = bytes[p - 1];
matches!(prev, b';' | b'\n' | b'&' | b'|' | b'(' | b'{')
};
LINCMD.store(in_cmdpos as i32, Ordering::SeqCst);
Some(snap[start..end].to_string())
}
pub fn inststrlen(
str: &str,
move_cursor: bool,
mut len: i32,
) -> i32 {
if len == 0 || str.is_empty() {
return 0;
}
if len == -1 {
len = str.len() as i32;
}
let zml_active = ZLEMETALINE.get().is_some();
if zml_active {
if let Some(m) = ZLEMETALINE.get() {
if let Ok(mut g) = m.lock() {
let cs = ZLEMETACS.load(Ordering::SeqCst) as usize;
let cs = cs.min(g.len());
let take = (len as usize).min(str.len());
let bytes = g.as_bytes();
let new_line: String = String::from_utf8_lossy(&bytes[..cs]).into_owned()
+ &str[..take]
+ &String::from_utf8_lossy(&bytes[cs..]);
*g = new_line;
ZLEMETALL.store(g.len() as i32, Ordering::SeqCst); if move_cursor {
ZLEMETACS.fetch_add(take as i32, Ordering::SeqCst); }
}
}
return len;
}
let instr = &str[..(len as usize).min(str.len())]; let zlestr: Vec<char> = stringaszleline(instr, 0, None, None, None); let zlelen = zlestr.len();
spaceinline(zlelen as i32); {
let mut line = ZLELINE.lock().unwrap();
let pos = ZLECS.load(Ordering::SeqCst);
for (i, ch) in zlestr.iter().enumerate() {
if pos + i < line.len() {
line[pos + i] = *ch;
} else {
line.insert(pos + i, *ch);
}
}
}
if move_cursor {
ZLECS.fetch_add(zlelen, Ordering::SeqCst); }
len }
pub fn doexpansion(s: &str, lst: i32, olst: i32, explincmd: i32) -> i32 {
let mut ret: i32 = 1;
let mut first = true;
crate::ported::mem::pushheap();
let mut vl: crate::ported::linklist::LinkList<String> = crate::ported::linklist::newlinklist();
let ss = crate::ported::string::dupstring(s);
let ss: String = ss
.chars()
.map(|c| match c {
'"' => crate::ported::zle::compctl::Dnull,
'\'' => crate::ported::zle::compctl::Snull,
c => c,
})
.collect();
vl.push_back(ss.clone());
let mut ret_flags = 0i32;
crate::ported::subst::prefork(&mut vl, 0, &mut ret_flags);
let _result: i32 = (|| -> i32 {
if crate::ported::utils::errflag.load(Ordering::SeqCst) != 0 {
return ret;
}
if lst == COMP_LIST_EXPAND || lst == COMP_EXPAND {
let ng = crate::ported::options::opt_state_get("NULL_GLOB").unwrap_or(false);
crate::ported::options::opt_state_set("NULL_GLOB", true);
crate::ported::subst::globlist(&mut vl, crate::ported::zsh_h::PREFORK_NO_UNTOK);
crate::ported::options::opt_state_set("NULL_GLOB", ng);
}
if crate::ported::utils::errflag.load(Ordering::SeqCst) != 0 {
return ret;
}
if vl.empty() {
return ret;
}
let first_item = vl.front().cloned().unwrap_or_default();
if first_item.is_empty() {
return ret;
}
let len_vl = {
let mut n = 0;
let mut cur = vl.firstnode();
while cur.is_some() {
n += 1;
cur = cur.and_then(|i| vl.nextnode(i));
}
n
};
let no_change = first_item == ss;
let tilde_only = olst == COMP_EXPAND_COMPLETE
&& len_vl == 1
&& s.starts_with(crate::ported::zsh_h::Tilde)
&& crate::ported::subst::filesubstr(s, false)
.map(|exp| exp == first_item)
.unwrap_or(false);
if no_change || tilde_only {
if lst == COMP_EXPAND_COMPLETE {
docompletion(s, COMP_COMPLETE, explincmd);
}
return ret;
}
if lst == COMP_LIST_EXPAND {
ZLEMETACS.store(0, Ordering::SeqCst);
foredel(ZLEMETALL.load(Ordering::SeqCst), CUT_RAW);
spaceinline(ORIGLL.load(Ordering::SeqCst));
if let (Some(metabuf), Some(orig)) = (ZLEMETALINE.get(), ORIGLINE.get()) {
if let (Ok(mut m), Ok(o)) = (metabuf.lock(), orig.lock()) {
*m = o.clone();
}
}
ZLEMETACS.store(ORIGCS.load(Ordering::SeqCst), Ordering::SeqCst);
let mut items: Vec<String> = Vec::new();
while let Some(x) = crate::ported::linklist::ugetnode(&mut vl) {
items.push(x);
}
ret = listlist(&items, 0);
SHOWINGLIST.store(0, Ordering::SeqCst);
return ret;
}
let wb = WB.load(Ordering::SeqCst);
let we = WE.load(Ordering::SeqCst);
ZLEMETACS.store(wb, Ordering::SeqCst);
foredel(we - wb, CUT_RAW);
while let Some(node) = crate::ported::linklist::ugetnode(&mut vl) {
ret = 0;
let quoted = quotename(&node, 0);
let unt = crate::ported::lex::untokenize("ed);
inststr(&unt);
if !vl.empty() || !first {
spaceinline(1);
let pos = ZLEMETACS.load(Ordering::SeqCst);
if let Some(metabuf) = ZLEMETALINE.get() {
if let Ok(mut m) = metabuf.lock() {
if (pos as usize) < m.len() {
let mut bytes = m.as_bytes().to_vec();
if (pos as usize) < bytes.len() {
bytes[pos as usize] = b' ';
*m = String::from_utf8_lossy(&bytes).into_owned();
}
}
ZLEMETACS.store(pos + 1, Ordering::SeqCst);
}
}
}
first = false;
}
ret
})();
crate::ported::mem::popheap();
_result
}
pub fn docompletion(s: &str, lst: i32, incmd: i32) -> i32 {
let mut dat = crate::ported::zle::zle_h::compldat {
s: s.to_string(),
lst,
incmd,
};
let h = gethookdef("complete");
if !h.is_null() {
let dat_ptr =
(&mut dat) as *mut crate::ported::zle::zle_h::compldat as *mut std::ffi::c_void;
return crate::ported::module::runhookdef(h, dat_ptr);
}
crate::ported::zle::compcore::do_completion(s, incmd, lst)
}
pub fn pfxlen(s1: &str, s2: &str) -> usize {
s1.chars()
.zip(s2.chars())
.take_while(|(a, b)| a == b)
.count()
}
pub fn sfxlen(s1: &str, s2: &str) -> usize {
s1.chars()
.rev()
.zip(s2.chars().rev())
.take_while(|(a, b)| a == b)
.count()
}
pub fn printfmt(fmt: &str, n: i32, dopr: bool, doesc: bool) -> i32 {
let bytes = fmt.as_bytes();
let mut i = 0;
let mut cc = 0i32; let mut out = String::new();
while i < bytes.len() {
let c = bytes[i];
if doesc && c == b'%' {
i += 1;
while i < bytes.len() && (bytes[i]).is_ascii_digit() {
i += 1;
}
if i >= bytes.len() {
break;
}
match bytes[i] {
b'%' => {
out.push('%');
cc += 1;
}
b'n' => {
let s = n.to_string();
cc += s.chars().count() as i32;
out.push_str(&s);
}
b'B' | b'b' | b'S' | b's' | b'U' | b'u' | b'F' | b'f' | b'K' | b'k' => {
}
b'{' => {
i += 1;
while i < bytes.len() && bytes[i] != b'}' {
out.push(bytes[i] as char);
i += 1;
}
}
ch => {
out.push(ch as char);
cc += 1;
}
}
i += 1;
} else {
out.push(c as char);
cc += 1;
i += 1;
}
}
if dopr {
use std::sync::atomic::Ordering;
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out_fd, out.as_bytes());
let _ = write_loop(out_fd, b"\n");
}
cc
}
pub fn listlist(items: &[String], cols: usize) -> i32 {
let num = items.len(); if num == 0 {
return 0;
}
let mut lens: Vec<usize> = items.iter().map(|s| s.chars().count() + 2).collect(); let longest = *lens.iter().max().unwrap_or(&1); if longest >= cols {
return num as i32;
}
let ncols = (cols / longest).max(1);
let nlines = num.div_ceil(ncols); let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let mut row = String::new();
for (i, s) in items.iter().enumerate() {
row.push_str(s);
let pad = longest - lens[i];
row.push_str(&" ".repeat(pad));
if (i + 1) % ncols == 0 {
let line = row.trim_end();
let _ = write_loop(out_fd, line.as_bytes());
let _ = write_loop(out_fd, b"\n");
row.clear();
}
}
if !row.is_empty() {
let line = row.trim_end();
let _ = write_loop(out_fd, line.as_bytes());
let _ = write_loop(out_fd, b"\n");
}
let _ = (lens.pop(),);
nlines as i32
}
pub fn doexpandhist() -> i32 {
let line = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if line.is_empty() {
return 0;
}
if !line.contains('!') {
return 0;
} let expanded = line.clone(); if let Ok(mut g) = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = expanded.clone();
crate::ported::zle::compcore::ZLELL.store(g.len() as i32, Ordering::Relaxed);
crate::ported::zle::compcore::ZLECS.store(g.len() as i32, Ordering::Relaxed);
}
1 }
pub fn fixmagicspace() {
LASTCHAR.store((b' ' as i32) as i32, Ordering::SeqCst);
LASTCHAR_WIDE.store((b' ' as i32) as i32, Ordering::SeqCst);
LASTCHAR_WIDE_VALID.store(1, Ordering::SeqCst);
}
pub fn magicspace() -> i32 {
fixmagicspace(); let ret = expandhistory();
if ret != 0 {
ZLELINE
.lock()
.unwrap()
.insert(ZLECS.load(Ordering::SeqCst), ' ');
ZLECS.fetch_add(1, Ordering::SeqCst);
}
ret
}
pub fn expandhistory() -> i32 {
if doexpandhist() == 0 {
return 1;
}
0
}
pub fn getcurcmd() -> Option<String> {
let snap: String = ZLELINE.lock().unwrap().iter().collect();
let cs = ZLECS.load(Ordering::SeqCst).min(snap.len());
let prefix = &snap[..cs];
let mut last_seg_start = 0;
for (i, b) in prefix.bytes().enumerate() {
if matches!(b, b'|' | b';' | b'&') {
last_seg_start = i + 1;
}
}
let seg = prefix[last_seg_start..].trim_start();
let cmd: String = seg
.chars()
.take_while(|c| !c.is_ascii_whitespace())
.collect();
if cmd.is_empty() {
return None;
}
Some(cmd)
}
pub fn processcmd() -> i32 {
let s = match getcurcmd() {
Some(s) if !s.is_empty() => s,
_ => return 1, };
let m = ZMOD.lock().unwrap().mult; ZMOD.lock().unwrap().mult = 1; let _ = pushline(); ZMOD.lock().unwrap().mult = m; let bindk_nam = crate::ported::zle::zle_main::BINDK
.lock()
.ok()
.and_then(|b| b.as_ref().map(|t| t.nam.clone()))
.unwrap_or_else(|| "run-help".to_string());
let _ = inststr(&bindk_nam);
let _ = inststr(" ");
let q = quotename(&s, 0);
let _ = inststr(&q);
0
}
pub fn expandcmdpath() -> i32 {
let oldcs = ZLECS.load(Ordering::SeqCst);
let s = match getcurcmd() {
Some(c) if !c.is_empty() => c,
_ => return 1, };
let line: String = ZLELINE.lock().unwrap().iter().collect();
let cmdwb = line[..oldcs.min(line.len())]
.rfind(|c: char| c.is_ascii_whitespace())
.map(|i| i + 1)
.unwrap_or(0);
let cmdwe = line[oldcs.min(line.len())..]
.find(|c: char| c.is_ascii_whitespace())
.map(|i| i + oldcs)
.unwrap_or(line.len());
if cmdwe < cmdwb {
return 1;
}
let str_opt = crate::ported::builtin::findcmd(&s, 1, 0);
let str_full = match str_opt {
Some(p) => p,
None => return 1,
};
ZLECS.store(cmdwb, Ordering::SeqCst);
foredel((cmdwe - cmdwb) as i32, 0);
{
let mut zl = ZLELINE.lock().unwrap();
let cs = ZLECS.load(Ordering::SeqCst);
for (i, ch) in str_full.chars().enumerate() {
zl.insert(cs + i, ch);
}
}
let str_chars = str_full.chars().count();
ZLELL.fetch_add(str_chars, Ordering::SeqCst);
let new_cs = if oldcs >= cmdwe.saturating_sub(1) {
oldcs + str_chars - (cmdwe - cmdwb)
} else {
oldcs
};
ZLECS.store(new_cs.min(line.len() + str_chars), Ordering::SeqCst);
0
}
pub fn expandorcompleteprefix(args: &[String]) -> i32 {
COMPPREF.store(1, Ordering::SeqCst); let ret = expandorcomplete(args); if ZLECS.load(Ordering::SeqCst) > 0
&& ZLELINE.lock().unwrap()[ZLECS.load(Ordering::SeqCst) - 1] == ' '
{
makesuffixstr(None, Some("\\-"), 0); }
COMPPREF.store(0, Ordering::SeqCst); ret
}
pub fn endoflist() -> i32 {
let n = LASTLISTLEN.load(Ordering::SeqCst);
if n > 0 {
CLEARFLAG.store(0, Ordering::SeqCst);
trashzle();
for _ in 0..n {
tracing::trace!("endoflist: putc('\\n', shout)");
}
SHOWINGLIST.store(0, Ordering::SeqCst);
LASTLISTLEN.store(0, Ordering::SeqCst);
return 0; }
1 }
pub static USEMENU: AtomicI32 = AtomicI32::new(0);
pub static USEGLOB: AtomicI32 = AtomicI32::new(0);
pub static WOULDINSTAB: AtomicI32 = AtomicI32::new(0);
pub static NBRBEG: AtomicI32 = AtomicI32::new(0); pub static NBREND: AtomicI32 = AtomicI32::new(0);
pub static ORIGCS: AtomicI32 = AtomicI32::new(0); pub static ORIGLL: AtomicI32 = AtomicI32::new(0);
pub static INSUBSCR: AtomicI32 = AtomicI32::new(0);
pub static INSTRING: AtomicI32 = AtomicI32::new(0); pub static INBACKT: AtomicI32 = AtomicI32::new(0);
pub static ORIGLINE: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
pub static LASTPREBR: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new(); pub static LASTPOSTBR: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
pub static COMPQUOTE: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new(); pub static AUTOQ: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
pub static MENUCMP: AtomicI32 = AtomicI32::new(0);
pub static COMPPREF: AtomicI32 = AtomicI32::new(0);
pub static VALIDLIST: AtomicI32 = AtomicI32::new(0);
pub static SHOWAGAIN: AtomicI32 = AtomicI32::new(0);
pub static LASTAMBIG: AtomicI32 = AtomicI32::new(0);
pub static BASHLISTFIRST: AtomicI32 = AtomicI32::new(0);
pub static LINCMD: AtomicI32 = AtomicI32::new(0);
pub static cfargs: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub static cfret: AtomicI32 = AtomicI32::new(0);
pub static AMENU: AtomicI32 = AtomicI32::new(0);
pub const META: char = '\u{83}';
pub fn inststr(s: &str) -> i32 {
inststrlen(s, true, -1)
}
pub fn quotename(s: &str, instring: i32) -> String {
let raw = if instring == QT_NONE {
QT_BACKSLASH
} else {
instring
};
let qt = if raw == QT_BACKSLASH {
QT_BACKSLASH
} else if raw == QT_SINGLE {
QT_SINGLE
} else if raw == QT_DOUBLE {
QT_DOUBLE
} else if raw == QT_DOLLARS {
QT_DOLLARS
} else {
QT_NONE
};
crate::ported::utils::quotestring(s, qt)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::zle::zle_h::brinfo;
#[test]
fn test_pfxlen() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(pfxlen("hello", "help"), 3);
assert_eq!(pfxlen("abc", "xyz"), 0);
assert_eq!(pfxlen("test", "test"), 4);
}
#[test]
fn test_sfxlen() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(sfxlen("testing", "running"), 3);
assert_eq!(sfxlen("abc", "xyz"), 0);
}
#[test]
fn addx_skips_when_cursor_in_middle_of_word() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "hello".chars().collect();
ZLECS.store(2, Ordering::SeqCst); ZLELL.store(5, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(0, Ordering::SeqCst);
ADDEDX.store(99, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 0, "no insertion when cursor lands on word-char");
assert_eq!(ADDEDX.load(Ordering::SeqCst), 0);
assert!(
snap.is_empty(),
"ptmp must be NULL/empty when addx doesn't fire"
);
assert_eq!(
ZLELINE.lock().unwrap().iter().collect::<String>(),
"hello",
"buffer must be untouched"
);
}
#[test]
fn addx_inserts_at_end_of_line() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLECS.store(3, Ordering::SeqCst); ZLELL.store(3, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(0, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 1, "exactly one 'x' inserted at EOL");
assert_eq!(ADDEDX.load(Ordering::SeqCst), 1);
assert_eq!(snap, "abc", "snapshot is pre-edit buffer");
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "abcx");
}
#[test]
fn addx_inserts_x_space_when_comppref_on_nonblank() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "ab".chars().collect();
ZLECS.store(1, Ordering::SeqCst); ZLELL.store(2, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(1, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 2, "comppref non-blank → 'x ' (2 chars)");
assert_eq!(ADDEDX.load(Ordering::SeqCst), 2);
COMPPREF.store(0, Ordering::SeqCst);
}
#[test]
fn addx_inserts_when_cursor_on_separator() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "echo|".chars().collect();
ZLECS.store(4, Ordering::SeqCst); ZLELL.store(5, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(0, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 1, "separator at cursor → insert 'x'");
}
#[test]
fn checkparams_hascompmod_gate() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::params::setsparam("abc", "v1");
crate::ported::params::setsparam("abcd", "v2");
MENUCMP.store(0, Ordering::SeqCst);
HASCOMPMOD.store(true, Ordering::SeqCst);
HASCOMPMOD.store(true, Ordering::SeqCst);
HASCOMPMOD.store(false, Ordering::SeqCst);
assert_eq!(
checkparams("abc"),
1,
"with !hascompmod, exact + non-menu → return 1"
);
HASCOMPMOD.store(false, Ordering::SeqCst);
crate::ported::params::setsparam("abc", "");
crate::ported::params::setsparam("abcd", "");
}
#[test]
fn test_has_real_token() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(has_real_token("$HOME"));
assert!(has_real_token("*.txt"));
assert!(!has_real_token("hello"));
assert!(!has_real_token("test\\$var")); }
#[test]
fn dupstrspace_appends_space() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(dupstrspace("hello"), "hello ");
}
#[test]
fn dupstrspace_empty_input() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(dupstrspace(""), " ");
}
#[test]
fn freebrinfo_drops_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let head = Some(Box::new(brinfo {
next: Some(Box::new(brinfo {
next: None,
prev: None,
str: "second".into(),
pos: 7,
qpos: 8,
curpos: 9,
})),
prev: None,
str: "first".into(),
pos: 1,
qpos: 2,
curpos: 3,
}));
freebrinfo(head);
}
#[test]
fn dupbrinfo_clones_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let src = Box::new(brinfo {
next: Some(Box::new(brinfo {
next: Some(Box::new(brinfo {
next: None,
prev: None,
str: "C".into(),
pos: 30,
qpos: 31,
curpos: 32,
})),
prev: None,
str: "B".into(),
pos: 20,
qpos: 21,
curpos: 22,
})),
prev: None,
str: "A".into(),
pos: 10,
qpos: 11,
curpos: 12,
});
let (head, last) = dupbrinfo(Some(&*src));
assert!(last.is_some());
let h = head.as_ref().unwrap();
assert_eq!(h.str, "A");
assert_eq!(h.pos, 10);
assert_eq!(h.qpos, 11);
assert_eq!(h.curpos, 12);
let n = h.next.as_ref().unwrap();
assert_eq!(n.str, "B");
assert_eq!(n.pos, 20);
let n = n.next.as_ref().unwrap();
assert_eq!(n.str, "C");
assert_eq!(n.pos, 30);
assert!(n.next.is_none());
}
#[test]
fn dupbrinfo_empty_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (head, last) = dupbrinfo(None);
assert!(head.is_none());
assert!(last.is_none());
}
#[test]
fn spellword_zeroes_globals_returns_docomplete() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
USEMENU.store(99, Ordering::SeqCst);
USEGLOB.store(99, Ordering::SeqCst);
WOULDINSTAB.store(99, Ordering::SeqCst);
let _r = spellword(&[]);
assert_eq!(USEMENU.load(Ordering::SeqCst), 0);
assert_eq!(USEGLOB.load(Ordering::SeqCst), 0);
assert_eq!(WOULDINSTAB.load(Ordering::SeqCst), 0);
}
fn set_keybuf(bytes: &[u8]) {
*crate::ported::zle::zle_keymap::keybuf.lock().unwrap() = bytes.to_vec();
}
#[test]
fn zle_tricky_corpus_usetab_at_bol_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZLECS.store(0, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = Vec::new();
ZLELL.store(0, Ordering::SeqCst);
set_keybuf(b"\t");
assert_eq!(usetab(), 1, "tab at BOL = literal");
}
#[test]
fn zle_tricky_corpus_usetab_non_tab_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
set_keybuf(b"a");
assert_eq!(usetab(), 0, "non-tab byte = 0");
set_keybuf(b"");
assert_eq!(usetab(), 0, "empty buf = 0");
}
#[test]
fn zle_tricky_corpus_usetab_multibyte_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
set_keybuf(b"\t\t");
assert_eq!(usetab(), 0, "more than one byte = 0");
set_keybuf(b"\tx");
assert_eq!(usetab(), 0);
}
#[test]
fn zle_tricky_corpus_usetab_after_word_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
ZLECS.store(3, Ordering::SeqCst);
set_keybuf(b"\t");
assert_eq!(usetab(), 0, "cursor after non-WS char → no literal tab");
}
#[test]
fn zle_tricky_corpus_usetab_after_indent_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = " ".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
ZLECS.store(3, Ordering::SeqCst);
set_keybuf(b"\t");
assert_eq!(usetab(), 1, "after pure-WS indent, tab = literal");
}
#[test]
fn usetab_multi_char_keybuf_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*ZLELINE.lock().unwrap() = " ".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
ZLECS.store(3, Ordering::SeqCst);
set_keybuf(b"\t\t");
assert_eq!(usetab(), 0, "multi-char keybuf disables tab use");
}
#[test]
fn usetab_non_tab_keybuf_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*ZLELINE.lock().unwrap() = " ".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
ZLECS.store(3, Ordering::SeqCst);
set_keybuf(b"x");
assert_eq!(usetab(), 0, "non-tab keybuf returns 0");
}
#[test]
fn usetab_non_whitespace_in_line_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
ZLECS.store(3, Ordering::SeqCst);
set_keybuf(b"\t");
assert_eq!(usetab(), 0, "non-WS in line → tab = completion key");
}
#[test]
fn cmphaswilds_plain_string_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_eq!(cmphaswilds("foo"), 0, "no wildcards in plain string");
}
#[test]
fn cmphaswilds_empty_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_eq!(cmphaswilds(""), 0, "empty string has no wildcards");
}
#[test]
fn cmphaswilds_lone_inbrack_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let lone_inbrack = format!("{}", crate::ported::zsh_h::Inbrack);
assert_eq!(
cmphaswilds(&lone_inbrack),
0,
"lone Inbrack with no follower returns 0"
);
}
#[test]
fn dupstrspace_appends_single_trailing_space() {
let r = dupstrspace("foo");
assert_eq!(r, "foo ", "trailing space appended");
assert_eq!(r.len(), 4, "exactly 1 byte longer than input");
}
#[test]
fn dupstrspace_empty_input_returns_space() {
let r = dupstrspace("");
assert_eq!(r, " ", "empty input → single space");
}
#[test]
fn dupstrspace_preserves_input_verbatim() {
let r = dupstrspace("hello world");
assert_eq!(r, "hello world ", "inner space preserved + trailing space");
let r2 = dupstrspace("a\tb");
assert_eq!(r2, "a\tb ", "tab preserved");
}
#[test]
fn dupstrspace_preserves_multibyte() {
let r = dupstrspace("café");
assert_eq!(r, "café ", "multibyte preserved");
}
#[test]
fn freebrinfo_none_is_noop() {
freebrinfo(None);
}
#[test]
fn cmphaswilds_star_token_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let star = format!("{}", crate::ported::zsh_h::Star);
assert_eq!(cmphaswilds(&star), 1, "Star token → has wildcard");
}
#[test]
fn cmphaswilds_quest_token_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let quest = format!("{}", crate::ported::zsh_h::Quest);
assert_eq!(cmphaswilds(&quest), 1, "Quest token → has wildcard");
}
#[test]
fn cmphaswilds_pipe_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_eq!(cmphaswilds("|"), 1, "| literal → has wildcard");
}
#[test]
fn cmphaswilds_dot_is_not_wildcard() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_eq!(cmphaswilds("abc.def"), 0, "dot is literal");
}
#[test]
fn cmphaswilds_space_is_not_wildcard() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_eq!(cmphaswilds("foo bar"), 0, "space is literal");
}
#[test]
fn cmphaswilds_slash_is_not_wildcard() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_eq!(cmphaswilds("hello/world"), 0, "slash is literal");
}
#[test]
fn usetab_returns_boolean_i32() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = usetab();
assert!(r == 0 || r == 1, "usetab must return 0 or 1, got {}", r);
}
#[test]
fn cmphaswilds_is_deterministic_full_sweep() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for s in ["abc", "", "hello/world", "a.b"] {
let first = cmphaswilds(s);
for _ in 0..5 {
assert_eq!(
cmphaswilds(s),
first,
"cmphaswilds({:?}) must be deterministic",
s
);
}
}
}
#[test]
fn cmphaswilds_returns_boolean_i32() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for s in ["abc", "*.txt", "[a-z]", "?", "|", ""] {
let r = cmphaswilds(s);
assert!(
r == 0 || r == 1,
"cmphaswilds({:?}) = {} not in {{0,1}}",
s,
r
);
}
}
#[test]
fn dupstrspace_empty_returns_space_pin() {
assert_eq!(dupstrspace(""), " ", "empty + trailing space = ' '");
}
#[test]
fn dupstrspace_always_ends_in_space() {
for s in ["", "x", "hello", "包含中文"] {
let r = dupstrspace(s);
assert!(
r.ends_with(' '),
"dupstrspace({:?}) = {:?} must end in space",
s,
r
);
}
}
#[test]
fn dupstrspace_is_pure() {
for s in ["", "abc", "hello"] {
let first = dupstrspace(s);
for _ in 0..5 {
assert_eq!(dupstrspace(s), first, "dupstrspace({:?}) must be pure", s);
}
}
}
#[test]
fn listchoices_empty_args_returns_in_exit_range() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = listchoices(&[]);
assert!(
(0..256).contains(&r),
"exit code {} must fit in u8 range",
r
);
}
#[test]
fn spellword_empty_args_returns_in_exit_range() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = spellword(&[]);
assert!(
(0..256).contains(&r),
"exit code {} must fit in u8 range",
r
);
}
#[test]
fn listexpand_empty_args_returns_in_exit_range() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = listexpand(&[]);
assert!(
(0..256).contains(&r),
"exit code {} must fit in u8 range",
r
);
}
#[test]
fn freebrinfo_none_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
freebrinfo(None);
}
}
#[test]
fn completecall_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = completecall(&[]);
}
#[test]
fn completeword_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = completeword(&[]);
}
#[test]
fn menucomplete_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = menucomplete(&[]);
}
#[test]
fn expandorcomplete_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = expandorcomplete(&[]);
}
#[test]
fn menuexpandorcomplete_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = menuexpandorcomplete(&[]);
}
#[test]
fn reversemenucomplete_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = reversemenucomplete(&[]);
}
#[test]
fn acceptandmenucomplete_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = acceptandmenucomplete(&[]);
}
#[test]
fn checkparams_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = checkparams("");
}
#[test]
fn checkparams_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for s in ["", "FOO", "PATH", "__never_real_param__"] {
let first = checkparams(s);
for _ in 0..3 {
assert_eq!(
checkparams(s),
first,
"checkparams({:?}) must be deterministic",
s
);
}
}
}
#[test]
fn parambeg_returns_option_usize_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<usize> = parambeg("", 0);
}
#[test]
fn has_real_token_empty_returns_false() {
assert!(!has_real_token(""), "empty has no tokens");
}
#[test]
fn has_real_token_returns_bool_type() {
let _: bool = has_real_token("anything");
}
#[test]
fn has_real_token_is_pure() {
for s in ["", "abc", "hello world", "no tokens"] {
let first = has_real_token(s);
for _ in 0..3 {
assert_eq!(
has_real_token(s),
first,
"has_real_token({:?}) must be pure",
s
);
}
}
}
#[test]
fn get_comp_string_returns_option_string_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<String> = get_comp_string();
}
}