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())
&& crate::ported::zle::complist::COMPRECURSIVE
.load(std::sync::atomic::Ordering::Relaxed)
== 0
{
zwarn("completion cannot be used recursively (yet)");
return 1;
}
struct ActiveGuard;
impl Drop for ActiveGuard {
fn drop(&mut self) {
ACTIVE.with(|c| c.set(false));
}
}
ACTIVE.with(|c| c.set(true));
let _active_guard = ActiveGuard;
crate::ported::zle::complist::COMPRECURSIVE.store(0, std::sync::atomic::Ordering::Relaxed);
let mut lst_box = lst;
let h_before = gethookdef("before_complete");
let bc_handled = 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) != 0
} else {
crate::ported::zle::compcore::before_complete(&mut lst_box) != 0
};
if bc_handled {
if crate::ported::zle::compcore::ZLEMETALL.load(Ordering::SeqCst) != 0 {
crate::ported::zle::compcore::unmetafy_line();
}
let comp_line: Vec<char> = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.chars().collect())
.unwrap_or_default();
let comp_ll = comp_line.len() as i32;
let comp_cs = crate::ported::zle::compcore::ZLECS.load(Ordering::SeqCst);
if let Ok(mut g) = crate::ported::zle::zle_main::ZLELINE.lock() {
*g = comp_line;
}
crate::ported::zle::zle_main::ZLECS
.store(comp_cs.clamp(0, comp_ll) as usize, Ordering::SeqCst);
crate::ported::zle::zle_main::ZLELL.store(comp_ll as usize, Ordering::SeqCst);
return 0; }
if doexpandhist() != 0 {
return 0; }
{
let ed_line: String = crate::ported::zle::zle_main::ZLELINE
.lock()
.map(|g| g.iter().collect())
.unwrap_or_default();
let ed_cs = crate::ported::zle::zle_main::ZLECS.load(Ordering::SeqCst) as i32;
let ed_ll = ed_line.chars().count() as i32;
if let Ok(mut g) = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = ed_line;
}
crate::ported::zle::compcore::ZLECS.store(ed_cs, Ordering::SeqCst);
crate::ported::zle::compcore::ZLELL.store(ed_ll, Ordering::SeqCst);
}
if crate::ported::zle::compcore::ZLEMETALL.load(Ordering::SeqCst) == 0 {
crate::ported::zle::compcore::metafy_line();
}
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 = crate::ported::zle::compcore::ZLEMETALINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
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::ZLEMETALINE
.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);
}
if crate::ported::zle::compcore::ZLEMETALL.load(Ordering::SeqCst) != 0 {
crate::ported::zle::compcore::unmetafy_line();
}
{
let comp_line: Vec<char> = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.chars().collect())
.unwrap_or_default();
let comp_ll = comp_line.len() as i32;
let comp_cs = crate::ported::zle::compcore::ZLECS.load(Ordering::SeqCst);
if let Ok(mut g) = crate::ported::zle::zle_main::ZLELINE.lock() {
*g = comp_line;
}
crate::ported::zle::zle_main::ZLECS
.store(comp_cs.clamp(0, comp_ll) as usize, Ordering::SeqCst);
crate::ported::zle::zle_main::ZLELL.store(comp_ll as usize, Ordering::SeqCst);
}
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> {
use crate::ported::context::{zcontext_restore, zcontext_save};
use crate::ported::lex::{
ctxtlex, incmdpos, incond, inredir, noaliases, set_incmdpos, set_intypeset, set_noaliases,
set_tok, set_tokstr, tok, tokstr, untokenize, IS_REDIROP, LEX_LEXFLAGS, LEX_PARBEGIN,
LEX_PAREND, LEX_WORDBEG,
};
use crate::ported::string::{dupstring, ztrdup};
use crate::ported::zle::compcore::{BRBEG, BREND, INWHAT, OFFS};
use crate::ported::zsh_h::{
isset, lextok, AMPER, AMPERBANG, BARAMP, BAR_TOK, CASE, COMPLETEALIASES, DAMPER, DBAR,
DINPAR, DOLOOP, ENDINPUT, ENVARRAY, ENVSTRING, FOR, FOREACH, INPAR_TOK, IN_COND, IN_ENV,
IN_MATH, IN_NOTHING, IN_PAR, LEXERR, LEXFLAGS_ZLE, Inbrack, Meta, Outbrack, NULLTOK,
OUTPAR_TOK, RCQUOTES, REPEAT, SELECT, SEPER, STRING_LEX, TYPESET,
};
use crate::ported::ztype_h::INAMESPC;
let snull = crate::ported::zle::compctl::Snull;
let dnull = crate::ported::zle::compctl::Dnull;
let bnull = crate::ported::zle::compctl::Bnull;
let ona = noaliases();
let meta_snap: String = match ZLEMETALINE.get() {
Some(m) => m.lock().unwrap().clone(),
None => return None,
};
let zlemetacs = ZLEMETACS.load(Ordering::SeqCst);
{
if let Ok(mut g) = ORIGLINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = meta_snap.clone();
}
ORIGCS.store(zlemetacs, Ordering::SeqCst);
ORIGLL.store(meta_snap.len() as i32, Ordering::SeqCst);
}
if let Some(b) = BRBEG.get() {
*b.lock().unwrap() = None;
}
if let Some(b) = BREND.get() {
*b.lock().unwrap() = None;
}
NBRBEG.store(0, Ordering::SeqCst); NBREND.store(0, Ordering::SeqCst);
set_noaliases(isset(COMPLETEALIASES));
{
let (mut i, mut j, mut k) = (0i32, 0i32, 0i32);
let ub = meta_snap.as_bytes();
let cs = (zlemetacs.max(0) as usize).min(ub.len());
let mut u = 0usize;
while u < cs {
let c = ub[u];
if c == b'`' && (k & 1) == 0 {
i += 1;
} else if c == b'"' && (k & 1) == 0 && (i & 1) == 0 {
j += 1;
} else if c == b'\'' && (j & 1) == 0 {
k += 1;
} else if c == b'\\' && u + 1 < ub.len() && (k & 1) == 0 {
u += 1; }
u += 1;
}
INBACKT.store(i & 1, Ordering::SeqCst); }
INSTRING.store(QT_NONE as i32, Ordering::SeqCst);
let mut zml: String = meta_snap.clone();
let addedx: i32;
{
let bytes = zml.as_bytes().to_vec();
let cs = (zlemetacs.max(0) as usize).min(bytes.len());
let ll = bytes.len();
let ch_at = bytes.get(cs).copied();
let prev_at = if cs > 0 { bytes.get(cs - 1).copied() } else { None };
let comppref = COMPPREF.load(Ordering::SeqCst) != 0;
let instr = INSTRING.load(Ordering::SeqCst);
let is_iblank = matches!(ch_at, Some(b' ' | b'\t'));
let is_blank_unescaped = is_iblank && (cs == 0 || prev_at != Some(b'\\'));
let cs_at_end = ch_at.is_none() || cs >= ll;
let is_newline = ch_at == Some(b'\n');
let is_separator =
matches!(ch_at, Some(b')' | b'`' | b'}' | b';' | b'|' | b'&' | b'>' | b'<'));
let is_instring_quote = instr != QT_NONE as i32 && matches!(ch_at, Some(b'"' | b'\''));
let addspace = comppref && ch_at.is_some() && !matches!(ch_at, Some(b' ' | b'\t'));
if cs_at_end
|| is_newline
|| is_blank_unescaped
|| is_separator
|| is_instring_quote
|| addspace
{
let mut nb = Vec::with_capacity(ll + 2);
nb.extend_from_slice(&bytes[..cs]);
nb.push(b'x'); if addspace {
nb.push(b' '); }
nb.extend_from_slice(&bytes[cs..]);
zml = String::from_utf8_lossy(&nb).into_owned();
addedx = if addspace { 2 } else { 1 }; } else {
addedx = 0; }
}
ADDEDX.store(addedx, Ordering::SeqCst);
if let Some(m) = ZLEMETALINE.get() {
*m.lock().unwrap() = zml.clone();
}
let zlemetall = meta_snap.len() as i32; ZLEMETALL.store(zlemetall, Ordering::SeqCst);
crate::ported::mem::pushheap();
let mut linptr = zml.clone();
let mut t0: lextok;
let mut clwords: Vec<String> = Vec::new(); let mut clwpos: i32; let mut clwnum: i32;
let mut cp: i32 = 0; let mut rd: i32 = 0; let mut ia: i32 = 0; let mut varq: i32; let mut cmdstr: Option<String>; let mut varname: Option<String>; let mut zlemetacs_qsub: i32; let mut tt: Option<String>;
's_restart: loop {
INWHAT.store(IN_NOTHING, Ordering::SeqCst); LEX_PARBEGIN.set(-1); LEX_PAREND.set(-1);
LINCMD.store(incmdpos() as i32, Ordering::SeqCst); let mut linredir: i32 = inredir() as i32; cmdstr = None; let mut cmdtok: lextok = NULLTOK; varname = None; INSUBSCR.store(0, Ordering::SeqCst); clwpos = -1; zcontext_save(); LEX_LEXFLAGS.set(LEXFLAGS_ZLE | crate::ported::zsh_h::LEXFLAGS_ACTIVE);
crate::ported::input::inpush(&dupstrspace(&linptr), 0, None); crate::ported::hist::strinbeg(0);
let mut wordpos: i32 = 0;
let mut ins: i32 = 0;
let mut oins: i32 = 0; let mut linarr: i32 = 0;
let mut parct: i32 = 0;
let mut redirpos: i32 = 0;
WB.store(zlemetacs, Ordering::SeqCst); WE.store(zlemetacs, Ordering::SeqCst);
let mut tt0: lextok = NULLTOK; clwords.clear();
tt = None;
varq = 0;
zlemetacs_qsub = 0;
loop {
let mut qsub: i32 = 0; let mut noword: i32 = 0;
linredir = (inredir() && ins == 0) as i32;
let lincmd_val = (!inredir()
&& ((incmdpos() && ins == 0 && incond() == 0)
|| (oins == 2 && wordpos == 2)
|| (ins == 3 && wordpos == 1)
|| (cmdtok == NULLTOK && incond() == 0))) as i32;
LINCMD.store(lincmd_val, Ordering::SeqCst);
oins = ins; if linarr != 0 {
set_incmdpos(false); }
if cmdtok == TYPESET {
set_intypeset(linarr == 0); }
ctxtlex();
let mut tokv = tok();
if tokv == LEXERR {
match tokstr() {
None => break,
Some(ts) => {
let jcnt = ts.chars().filter(|&c| c == snull || c == dnull).count();
if jcnt & 1 == 1 {
if LINCMD.load(Ordering::SeqCst) != 0 && ts.contains('=') {
varq = 1;
tokv = ENVSTRING;
set_tok(ENVSTRING);
} else {
tokv = STRING_LEX;
set_tok(STRING_LEX);
}
}
}
}
} else if tokv == ENVSTRING {
varq = 0; }
if tokv == ENVARRAY {
linarr = 1;
varname = tokstr().map(|s| ztrdup(&s));
} else if tokv == INPAR_TOK {
parct += 1;
} else if tokv == OUTPAR_TOK {
if parct != 0 {
parct -= 1;
} else if linarr != 0 {
linarr = 0;
set_incmdpos(true);
}
}
if inredir() && IS_REDIROP(tokv) {
if wordpos == redirpos {
redirpos += 1;
}
let inbufct = crate::ported::input::inbufct.with(|c| c.get());
let wb = WB.load(Ordering::SeqCst);
let we = WE.load(Ordering::SeqCst);
let wordbeg = LEX_WORDBEG.get();
if zlemetacs < (zlemetall - inbufct) && zlemetacs >= wordbeg && wb == we {
let new_we = zlemetall - (inbufct + addedx); WE.store(new_we, Ordering::SeqCst);
if addedx != 0 && new_we > wb {
WB.store(wb + 1, Ordering::SeqCst); } else {
WB.store(zlemetacs, Ordering::SeqCst); }
}
}
if tokv == DINPAR {
set_tokstr(None); }
if tokv == ENDINPUT {
break; }
let is_sep = (ins != 0 && (tokv == DOLOOP || tokv == SEPER))
|| (ins == 2 && wordpos == 2)
|| (ins == 3 && wordpos == 3)
|| tokv == BAR_TOK
|| tokv == AMPER
|| tokv == BARAMP
|| tokv == AMPERBANG
|| ((tokv == DBAR || tokv == DAMPER) && incond() == 0)
|| (tt.is_some() && incmdpos());
if is_sep {
if tt.is_some() {
break; }
if ins < 2 {
noword = 1; }
wordpos = 0; redirpos = 0;
ins = 0;
tt0 = NULLTOK; }
if LINCMD.load(Ordering::SeqCst) != 0
&& (tokv == STRING_LEX
|| tokv == FOR
|| tokv == FOREACH
|| tokv == SELECT
|| tokv == REPEAT
|| tokv == CASE
|| tokv == TYPESET)
{
ins = if tokv == REPEAT {
2
} else {
(tokv != STRING_LEX && tokv != TYPESET) as i32
};
if let Some(ts) = tokstr() {
let mut c = ztrdup(&untokenize(&ts));
crate::ported::glob::remnulargs(&mut c);
cmdstr = Some(c);
}
cmdtok = tokv;
if wordpos != redirpos && clwpos == -1 {
wordpos = 0; redirpos = 0;
}
} else if tokv == SEPER {
ins = (cmdtok != STRING_LEX && cmdtok != TYPESET) as i32; }
if LEX_LEXFLAGS.get() == 0 && tt0 == NULLTOK {
tt = tokstr().as_ref().map(|s| dupstring(s));
let wb = WB.load(Ordering::SeqCst);
let we = WE.load(Ordering::SeqCst);
{
let ub = zml.as_bytes();
let (mut i, mut j, mut k) = (0i32, 0i32, 0i32);
let mut u = wb.max(0) as usize;
let we_u = (we.max(0) as usize).min(ub.len());
while u < we_u {
let c = ub[u];
if c == b'`' && (k & 1) == 0 {
i += 1;
} else if c == b'"' && (k & 1) == 0 && (i & 1) == 0 {
j += 1;
} else if c == b'\'' && (j & 1) == 0 {
k += 1;
} else if c == b'\\' && u + 1 < ub.len() && (k & 1) == 0 {
if ub[u + 1] == b'\n' {
qsub += 2; }
u += 1;
}
u += 1;
}
}
if isset(RCQUOTES) {
if let Some(ref ttv) = tt {
let e = (zlemetacs - wb - qsub).max(0) as usize;
for (idx, ch) in ttv.char_indices() {
if ch == snull {
for (off, pc) in ttv[idx..].char_indices() {
if idx + off >= e {
break;
}
if pc == '\'' {
qsub += 1;
}
}
}
}
}
}
if addedx != 0 {
if let Some(ref mut ttv) = tt {
let at = (zlemetacs - wb - qsub).max(0) as usize;
if at < ttv.len() && ttv.is_char_boundary(at) {
ttv.remove(at);
}
}
}
tt0 = tokv; clwpos = wordpos; cp = LINCMD.load(Ordering::SeqCst); rd = linredir; ia = linarr; if INWHAT.load(Ordering::SeqCst) == IN_NOTHING && incond() != 0 {
INWHAT.store(IN_COND, Ordering::SeqCst); }
} else if linredir != 0 {
let lexflags_nz = LEX_LEXFLAGS.get() != 0;
let end = tokv == LEXERR
|| tokv == ENDINPUT
|| (tokv == SEPER && !(lexflags_nz && tt0 == NULLTOK));
if end {
break;
}
continue;
}
let mut cur_tokstr = tokstr();
if incond() != 0 {
if tokv == DBAR {
cur_tokstr = Some("||".to_string());
} else if tokv == DAMPER {
cur_tokstr = Some("&&".to_string());
}
}
if cur_tokstr.is_none() || noword != 0 {
let lexflags_nz = LEX_LEXFLAGS.get() != 0;
let end = tokv == LEXERR
|| tokv == ENDINPUT
|| (tokv == SEPER && !(lexflags_nz && tt0 == NULLTOK));
if end {
break;
}
continue;
}
let cur_tokstr_s = cur_tokstr.unwrap();
if oins == 2 && wordpos == 0 && cur_tokstr_s == "do" {
ins = 3;
}
let mut word = ztrdup(&cur_tokstr_s);
{
let meta = Meta as char;
loop {
let chars: Vec<char> = word.chars().collect();
let sl = chars.len();
if sl == 0 || chars[sl - 1] != ' ' {
break;
}
if sl >= 2 && (chars[sl - 2] == bnull || chars[sl - 2] == meta) {
break;
}
word.pop();
}
}
while clwords.len() <= wordpos as usize {
clwords.push(String::new());
}
clwords[wordpos as usize] = word;
let sl = clwords[wordpos as usize].chars().count() as i32;
let prev_wordpos = wordpos;
wordpos += 1;
if clwpos == prev_wordpos && addedx != 0 {
let wb = WB.load(Ordering::SeqCst);
zlemetacs_qsub = zlemetacs - qsub;
let word_diff = zlemetacs_qsub - wb;
let chuck_at = if word_diff >= sl {
sl - 1
} else if word_diff < 0 {
0
} else {
word_diff
};
let w = &mut clwords[prev_wordpos as usize];
if let Some((byte_at, _)) = w.char_indices().nth(chuck_at.max(0) as usize) {
w.remove(byte_at);
}
}
let lexflags_nz = LEX_LEXFLAGS.get() != 0;
let end = tokv == LEXERR
|| tokv == ENDINPUT
|| (tokv == SEPER && !(lexflags_nz && tt0 == NULLTOK));
if end {
break;
}
}
clwnum = if tt.is_some() || wordpos == 0 {
wordpos
} else {
wordpos - 1
};
t0 = tt0; if ia != 0 {
LINCMD.store(0, Ordering::SeqCst);
linredir = 0;
INWHAT.store(IN_ENV, Ordering::SeqCst);
} else {
LINCMD.store(cp, Ordering::SeqCst);
linredir = rd;
}
crate::ported::hist::strinend(); crate::ported::input::inpop(); LEX_LEXFLAGS.set(0); crate::ported::utils::errflag
.fetch_and(!crate::ported::utils::ERRFLAG_ERROR, Ordering::SeqCst);
if LEX_PARBEGIN.get() != -1 {
let parend = LEX_PAREND.get();
let off = zlemetall + addedx - LEX_PARBEGIN.get() + 1;
let ub = zml.as_bytes();
let li = off as isize;
let is_dollar_dparen = li >= 3
&& (li as usize) < ub.len()
&& ub[li as usize] == b'('
&& ub[(li - 1) as usize] == b'('
&& ub[(li - 2) as usize] == b'$';
if !is_dollar_dparen {
if parend >= 0 {
let new_ll = zlemetall - parend;
if new_ll >= 0
&& (new_ll as usize) <= zml.len()
&& zml.is_char_boundary(new_ll as usize)
{
zml.truncate(new_ll as usize);
}
}
zcontext_restore(); tt = None; linptr = zml.clone();
continue 's_restart; }
}
let s: String;
if INWHAT.load(Ordering::SeqCst) == IN_MATH {
set_noaliases(ona);
zcontext_restore();
return None;
} else if t0 == NULLTOK || t0 == ENDINPUT {
s = String::new();
WB.store(zlemetacs, Ordering::SeqCst);
WE.store(zlemetacs, Ordering::SeqCst);
clwpos = clwnum;
t0 = STRING_LEX;
} else if t0 == STRING_LEX || t0 == TYPESET {
s = clwords
.get(clwpos.max(0) as usize)
.cloned()
.unwrap_or_default();
} else if t0 == ENVSTRING {
tt = clwords.get(clwpos.max(0) as usize).cloned();
let ttv = tt.clone().unwrap_or_default();
let ns_off = crate::ported::utils::itype_end(&ttv, INAMESPC, false).min(ttv.len());
varname = Some(ztrdup(&ttv[..ns_off])); let mut soff = ns_off;
if ttv.as_bytes().get(soff) == Some(&b'+') {
soff += 1; }
let mut rest: &str = &ttv[soff..];
let sp = crate::ported::utils::skipparens(Inbrack, Outbrack, &mut rest);
let after_paren_off = ttv.len() - rest.len();
let wb0 = WB.load(Ordering::SeqCst);
if sp > 0 || (after_paren_off as i32) > (zlemetacs_qsub - wb0) {
set_noaliases(ona);
zcontext_restore();
return None;
} else if {
let c = ttv[after_paren_off..].chars().next();
c == Some('=') || c == Some(crate::ported::zsh_h::Equals)
} {
let eq_ch = ttv[after_paren_off..].chars().next().unwrap();
let eq_len = eq_ch.len_utf8();
let val_boff = (after_paren_off + eq_len).min(ttv.len());
let eq_char_pos = ttv[..after_paren_off].chars().count() as i32; if zlemetacs_qsub > wb0 + eq_char_pos {
let val_char_pos = ttv[..val_boff].chars().count() as i32;
WB.store(wb0 + val_char_pos, Ordering::SeqCst);
s = ztrdup(&ttv[val_boff..]);
INWHAT.store(IN_ENV, Ordering::SeqCst);
} else {
let mut poff = after_paren_off;
if poff > 0 && ttv.as_bytes()[poff - 1] == b'+' {
poff -= 1;
}
INWHAT.store(IN_PAR, Ordering::SeqCst);
s = ztrdup(&ttv[..poff]);
WE.store(wb0 + ttv[..poff].chars().count() as i32, Ordering::SeqCst);
}
t0 = STRING_LEX; } else {
s = ztrdup(&ttv);
}
LINCMD.store(1, Ordering::SeqCst); } else {
set_noaliases(ona);
zcontext_restore();
return None; }
if WE.load(Ordering::SeqCst) > zlemetall {
WE.store(zlemetall, Ordering::SeqCst);
}
set_noaliases(ona);
let wb = WB.load(Ordering::SeqCst);
OFFS.store(zlemetacs - wb, Ordering::SeqCst);
{
let ws: Vec<String> = clwords.iter().map(|w| untokenize(w)).collect();
let n = ws.len() as i32;
let cur = if clwpos < 0 { n + 1 } else { clwpos + 1 }.max(1);
if let Ok(mut g) = crate::ported::zle::complete::COMPWORDS
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
{
*g = ws.clone();
}
crate::ported::zle::complete::COMPCURRENT.store(cur, Ordering::SeqCst);
crate::ported::params::setaparam("words", ws);
crate::ported::params::setsparam("CURRENT", &cur.to_string());
}
let _ = (clwnum, &cmdstr, &varname, cp, rd, ia);
let addedx = ADDEDX.load(Ordering::SeqCst);
if addedx > 0 {
if let Some(m) = ZLEMETALINE.get() {
if let Ok(mut g) = m.lock() {
let mut bytes = g.as_bytes().to_vec();
let cs = (zlemetacs.max(0) as usize).min(bytes.len());
let end = (cs + addedx as usize).min(bytes.len());
if cs < end {
bytes.drain(cs..end);
*g = String::from_utf8_lossy(&bytes).into_owned();
}
}
}
ADDEDX.store(0, Ordering::SeqCst);
}
zcontext_restore();
return Some(untokenize(&s));
}
}
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 {
ret = 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 1; }
let zterm_columns: i32 = if cols > 0 {
cols as i32
} else {
let c = crate::ported::utils::adjustcolumns();
if c == 0 {
80
} else {
c as i32
} };
let mut data: Vec<String> = items.to_vec();
let sort_flags = (crate::ported::zsh_h::SORTIT_IGNORING_BACKSLASHES as u32)
| if isset(crate::ported::zsh_h::NUMERICGLOBSORT) {
crate::ported::zsh_h::SORTIT_NUMERICALLY as u32
} else {
0
};
crate::ported::sort::strmetasort(&mut data, sort_flags, None);
let mut lens: Vec<i32> = Vec::with_capacity(num);
let mut longest: i32 = 0; let mut shortest: i32 = zterm_columns; let mut totl: i32 = 0; for s in &data {
let len = crate::ported::utils::niceztrlen(s) as i32 + 2; lens.push(len);
if len > longest {
longest = len;
} if len < shortest {
shortest = len;
} totl += len; }
let mut widths: Vec<i32> = vec![0; zterm_columns.max(1) as usize];
let num_i = num as i32;
let mut pack: i32 = 0; let mut ncols: i32; let mut nlines: i32;
ncols = (zterm_columns + 2) / longest; if ncols != 0 {
let mut tlines: i32 = 0;
let mut tcols: i32 = 0;
nlines = (num_i + ncols - 1) / ncols;
if isset(crate::ported::zsh_h::LISTPACKED) {
if isset(crate::ported::zsh_h::LISTROWSFIRST) {
let mut maxlines: i32 = 0;
tcols = zterm_columns / shortest;
while tcols > ncols {
let mut nth: i32 = 0;
let mut first: i32 = 0;
let mut maxlen: i32 = 0;
let mut width: i32 = 0;
let mut llines: i32 = 0;
let mut tcol: i32 = 0;
maxlines = 0;
let mut count: i32 = num_i;
while count > 0 {
if nth % tcols == 0 {
llines += 1;
} if lens[nth as usize] > maxlen {
maxlen = lens[nth as usize];
} nth += tcols; tlines += 1; if nth >= num_i {
width += maxlen; if width >= zterm_columns {
break;
} widths[tcol as usize] = maxlen; tcol += 1;
maxlen = 0; first += 1;
nth = first; if llines > maxlines {
maxlines = llines;
} llines = 0; }
count -= 1; }
if nth < num_i {
widths[tcol as usize] = maxlen; width += maxlen; }
if count == 0 && width < zterm_columns {
break;
} tcols -= 1; }
if tcols > ncols {
tlines = maxlines;
} } else {
tlines = (totl + zterm_columns) / zterm_columns;
while tlines < nlines {
let mut nth: i32 = 0;
let mut tline: i32 = 0;
let mut width: i32 = 0;
let mut maxlen: i32 = 0;
tcols = 0;
while (nth as usize) < num {
if lens[nth as usize] > maxlen {
maxlen = lens[nth as usize];
} tline += 1; if tline == tlines {
width += maxlen; if width >= zterm_columns {
break;
} widths[tcols as usize] = maxlen; tcols += 1;
maxlen = 0;
tline = 0; }
nth += 1; }
if tline != 0 {
widths[tcols as usize] = maxlen; tcols += 1;
width += maxlen; }
if (nth as usize) == num && width < zterm_columns {
break;
} tlines += 1; }
}
pack = if tlines < nlines { 1 } else { 0 };
if pack != 0 {
nlines = tlines;
ncols = tcols;
}
}
} else {
nlines = 0;
for s in &data {
nlines += 1 + (s.len() as i32) / zterm_columns; }
}
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let mut buf: Vec<u8> = Vec::new();
if ncols != 0 {
if isset(crate::ported::zsh_h::LISTROWSFIRST) {
let mut col: i32 = 1;
for i in 0..num {
crate::ported::utils::nicezputs(&data[i], &mut buf); if col == ncols {
col = 0; if i + 1 < num {
buf.push(b'\n');
} } else {
let pad = (if pack != 0 {
widths[(col - 1) as usize]
} else {
longest
}) - lens[i]
+ 2;
for _ in 0..pad.max(0) {
buf.push(b' ');
} }
col += 1; }
} else {
for line in 0..nlines {
let mut col: i32 = 1; let mut idx: i32 = line; while (idx as usize) < num {
crate::ported::utils::nicezputs(&data[idx as usize], &mut buf); if col == ncols {
break;
} let pad = (if pack != 0 {
widths[(col - 1) as usize]
} else {
longest
}) - lens[idx as usize]
+ 2;
for _ in 0..pad.max(0) {
buf.push(b' ');
} let mut i = nlines;
while i != 0 && (idx as usize) < num {
idx += 1;
i -= 1;
}
col += 1; }
if line + 1 < nlines {
buf.push(b'\n');
} }
}
} else {
for i in 0..num {
crate::ported::utils::nicezputs(&data[i], &mut buf); if i + 1 < num {
buf.push(b'\n');
} }
}
buf.push(b'\n');
let _ = write_loop(out_fd, &buf);
0
}
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();
}
}