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);
tracing::debug!(target: "compsys_args", ?func_name, has_base = base_fn.is_some(), "completecall ENTER");
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(isset(MENUCOMPLETE) as i32, Ordering::SeqCst); USEGLOB.store(isset(GLOBCOMPLETE) as i32, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); if ZLECS.load(Ordering::SeqCst) != ZLELL.load(Ordering::SeqCst) {
crate::ported::zle::zle_misc::fixsuffix(); crate::ported::zle::zle_h::invalidatelist(); return deletechar(); }
docomplete(COMP_LIST_COMPLETE) }
pub fn expandword(args: &[String]) -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(0, 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) }
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, Bar, 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 = Bar;
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;
if is_extglob_meta || is_simple_wild {
return 1; }
if crate::ported::utils::skipparens(Inbrack as char, Outbrack as char, &mut s) == 0 {
return 1;
}
if crate::ported::utils::skipparens(Inang as char, Outang as char, &mut s) == 0 {
return 1;
}
if !isset(IGNOREBRACES)
&& crate::ported::utils::skipparens(Inbrace as char, Outbrace as char, &mut s) == 0
{
return 1;
}
let pchars: Vec<char> = s.chars().take(2).collect();
if pchars.first() == Some(&(Inpar as char))
&& pchars.get(1) == Some(&':')
&& crate::ported::utils::skipparens(Inpar as char, Outpar as char, &mut s) == 0
{
return 1;
}
match s.chars().next() {
Some(cc) => s = &s[cc.len_utf8()..],
None => break,
}
}
}
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 sv: Vec<char> = s.chars().collect();
let slen = sv.len();
if offs > slen {
return None;
}
let mut bidx: Vec<usize> = Vec::with_capacity(slen + 1);
for (bi, _) in s.char_indices() {
bidx.push(bi);
}
bidx.push(s.len());
let at = |i: usize| -> char { sv.get(i).copied().unwrap_or('\0') };
let is_str = |c: char| c == Stringg || c == Qstring;
let mut p = offs;
while p > 0 && !is_str(at(p)) {
p -= 1;
}
if is_str(at(p)) {
while p > 0 && is_str(at(p - 1)) {
p -= 1;
}
while is_str(at(p + 1)) && is_str(at(p + 2)) {
p += 2;
}
}
let after = at(p + 1);
if !is_str(at(p)) || after == Inpar || after == Inbrack || after == '\'' {
return None;
}
let mut b = p + 1;
let mut br = 1;
let mut n: i32 = 0;
if at(b) == Inbrace {
let mut tb: &str = &s[bidx[b]..];
if crate::ported::utils::skipparens(Inbrace, Outbrace, &mut tb) == 0 {
return None;
}
b += 1;
br += 1;
let mut b_str: &str = &s[bidx[b]..];
n = crate::ported::utils::skipparens(Inpar, Outpar, &mut b_str);
let b_byte = s.len() - b_str.len();
b = s[..b_byte].chars().count();
}
while at(b) != '\0' {
let bb = at(b);
if bb != '^' && bb != Hat && bb != '=' && bb != Equals && bb != '~' && bb != Tilde {
break;
}
b += 1;
}
if at(b) == '#' || at(b) == Pound || at(b) == '+' {
b += 1;
}
let mut e = b;
if br != 0 {
while at(e) == Dnull {
e += 1;
}
}
let ec = at(e);
if ec == Quest
|| ec == Star
|| ec == Stringg
|| ec == Qstring
|| ec == '?'
|| ec == '*'
|| ec == '$'
|| ec == '-'
|| ec == '!'
|| ec == '@'
{
e += 1; } else if ec.is_ascii() && idigit(ec as u8) {
while at(e).is_ascii() && idigit(at(e) as u8) {
e += 1;
}
} else if e < slen {
let span = crate::ported::utils::itype_end(&s[bidx[e]..], INAMESPC, false);
e += s[bidx[e]..bidx[e] + span].chars().count();
}
if offs <= e && offs >= b && n <= 0 {
return Some(b);
}
None
}
pub fn docomplete(lst: i32) -> i32 {
struct FtimeDump;
impl Drop for FtimeDump {
fn drop(&mut self) {
crate::ftime::dump_and_reset();
}
}
let _ftdump = FtimeDump;
let mut lst = lst;
let olst = lst; 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);
crate::ported::utils::makecommaspecial(false);
tracing::debug!(target: "compsys_args", lst, "docomplete ENTER");
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
};
lst = lst_box;
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);
}
crate::ported::zle::compcore::metafy_line();
crate::ported::zle::compcore::INWHAT.store(crate::ported::zsh_h::IN_NOTHING, Ordering::SeqCst); if let Ok(mut g) = QIPRE.get_or_init(|| Mutex::new(String::new())).lock() {
g.clear(); }
if let Ok(mut g) = QISUF.get_or_init(|| Mutex::new(String::new())).lock() {
g.clear(); }
if let Ok(mut g) = AUTOQ.get_or_init(|| Mutex::new(String::new())).lock() {
g.clear(); }
let s = get_comp_string(); let s_word: String = s.clone().unwrap_or_default();
tracing::debug!(target: "compsys_args", ?s, wb = WB.load(Ordering::SeqCst), we = WE.load(Ordering::SeqCst), lincmd = LINCMD.load(Ordering::SeqCst), inwhat = crate::ported::zle::compcore::INWHAT.load(Ordering::SeqCst), "get_comp_string result");
{
use crate::ported::zle::compcore::INWHAT;
use crate::ported::zsh_h::IN_ENV;
if INWHAT.load(Ordering::SeqCst) == IN_ENV {
LINCMD.store(0, Ordering::SeqCst); }
}
let lincmd = LINCMD.load(Ordering::SeqCst);
if s.is_some() && lst == COMP_EXPAND_COMPLETE {
use crate::ported::hashtable::cmdnamtab_lock;
use crate::ported::utils::{itype_end, skipparens, strpfx};
use crate::ported::zsh_h::{
Equals, Hat, Inbrace, Inbrack, Inpar, Inparmath, Outbrace, Pound, Qstring, Qtick,
Quest, Star, Stringg, Tick, Tilde, GLOBCOMPLETE, RECEXACT,
};
use crate::ported::ztype_h::idigit;
let s_tok: String = COMP_STRING_TOK
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let sc: Vec<char> = if s_tok.is_empty() {
s_word.chars().collect()
} else {
s_tok.chars().collect()
};
let cs_off = (ZLEMETACS.load(Ordering::SeqCst) - WB.load(Ordering::SeqCst)).max(0) as usize;
let mut q: usize = 0; if sc.first() == Some(&Equals) {
q = 1; let name: String = sc[q..].iter().collect();
let hashed = cmdnamtab_lock()
.read()
.map(|t| t.get(&name).is_some())
.unwrap_or(false);
let path: Vec<String> = crate::ported::params::getaparam("path").unwrap_or_default();
let pc = crate::ported::hashtable::pathchecked.load(Ordering::Relaxed);
if hashed || crate::ported::exec::hashcmd(&name, &path[pc.min(path.len())..]).is_some()
{
if !HASCOMPMOD.load(Ordering::SeqCst) || isset(RECEXACT) {
lst = COMP_EXPAND; } else {
let mut n = 0;
if let Ok(t) = cmdnamtab_lock().read() {
for (nam, _) in t.iter() {
if strpfx(&name, nam)
&& crate::ported::exec::findcmd(nam, 0, 0).is_some()
{
n += 1;
}
if n == 2 {
break; }
}
}
if n == 1 {
lst = COMP_EXPAND; }
}
}
}
if lst == COMP_EXPAND_COMPLETE {
loop {
while q < sc.len() && sc[q] != Stringg {
q += 1;
}
if q >= sc.len()
|| sc.get(q + 1) == Some(&Inpar)
|| sc.get(q + 1) == Some(&Inparmath)
|| sc.get(q + 1) == Some(&Inbrack)
{
break;
}
q += 1; if sc.get(q) == Some(&Inbrace) {
let tail: String = sc[q..].iter().collect();
let mut rest: &str = &tail;
let n = skipparens(Inbrace, Outbrace, &mut rest);
q += tail.chars().count() - rest.chars().count();
if n == 0 && q == cs_off {
lst = COMP_EXPAND; }
} else {
while q < sc.len() {
let c = sc[q];
if c != '^' && c != Hat && c != '=' && c != Equals && c != '~' && c != Tilde
{
break;
}
q += 1;
}
if matches!(sc.get(q), Some('#') | Some(&Pound) | Some('+'))
&& sc.get(q + 1) != Some(&Stringg)
{
q += 1;
}
let t = q;
let sav2 = sc.get(t).copied();
let mut detok: Option<char> = None;
match sav2 {
Some(c) if c == Quest || c == Star || c == Stringg || c == Qstring => {
let idx = (c as u32 - Pound as u32) as usize;
detok = crate::ported::lex::ztokens.chars().nth(idx);
q += 1; }
Some('?') | Some('*') | Some('$') | Some('-') | Some('!') | Some('@') => {
q += 1; }
Some(c) if idigit(c as u8) => {
while matches!(sc.get(q), Some(&d) if idigit(d as u8)) {
q += 1;
}
}
_ => {
let rest: String = sc[t.min(sc.len())..].iter().collect();
let span = itype_end(&rest, crate::ported::ztype_h::INAMESPC, false);
q = t + rest[..span].chars().count();
}
}
let mut nm: Vec<char> = sc[t.min(sc.len())..q.min(sc.len())].to_vec();
if let (Some(fc), false) = (detok, nm.is_empty()) {
nm[0] = fc;
}
let name: String = nm.into_iter().collect();
if cs_off == q
&& (sav2.map(|c| idigit(c as u8)).unwrap_or(false)
|| checkparams(&name) != 0)
{
lst = COMP_EXPAND; }
}
if lst != COMP_EXPAND {
lst = COMP_COMPLETE; }
if q >= cs_off {
break;
}
}
}
if lst == COMP_EXPAND_COMPLETE {
let has_subst = sc
.iter()
.any(|&c| c == Tick || c == Qtick || c == Stringg || c == Qstring);
lst = if has_subst {
COMP_EXPAND
} else {
COMP_COMPLETE
};
}
let s_for_wilds: String = sc.iter().collect();
if !isset(GLOBCOMPLETE) && cmphaswilds(&s_for_wilds) != 0 {
lst = COMP_EXPAND;
}
}
{
use crate::ported::zle::compcore::INWHAT;
use crate::ported::zsh_h::{IN_CMD, IN_NOTHING};
if s.is_some() && lincmd != 0 && INWHAT.load(Ordering::SeqCst) == IN_NOTHING {
INWHAT.store(IN_CMD, Ordering::SeqCst); }
}
tracing::debug!(target: "compsys_args", lst, olst, s_null = s.is_none(), "docomplete dispatch");
let ret;
if s.is_none() {
ret = 1;
} else if lst == COMP_SPELL {
let wb = WB.load(Ordering::SeqCst);
let we = WE.load(Ordering::SeqCst);
let origword: String = ORIGWORD
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let w: String = origword
.chars()
.map(|c| {
use crate::ported::zsh_h::{Bnull, Dnull, Nularg, Qstring, Snull, Stringg};
if matches!(c, Snull | Dnull | Bnull | Stringg | Qstring) {
Nularg } else {
c
}
})
.collect();
ZLEMETACS.store(wb, Ordering::SeqCst); foredel(we - wb, CUT_RAW); let ox = crate::ported::lex::untokenize(&w); let mut x = ox.clone(); if let Some(fc) = w.chars().next() {
use crate::ported::zsh_h::{Equals, Stringg, Tilde};
if fc == Tilde || fc == Equals || fc == Stringg {
let mut xc: Vec<char> = x.chars().collect();
if xc.is_empty() {
xc.push(fc);
} else {
xc[0] = fc;
}
x = xc.into_iter().collect(); }
}
crate::ported::utils::spckword(&mut x, 0, lincmd, 0);
let r = if x == ox { 1 } else { 0 };
let x = crate::ported::lex::untokenize(&x);
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 ocs = ZLEMETACS.load(Ordering::SeqCst); let ne = *crate::ported::utils::noerrs_lock().lock().unwrap(); *crate::ported::utils::noerrs_lock().lock().unwrap() = 1; let mut ret_local = doexpansion(
&ORIGWORD
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default(),
lst,
olst,
lincmd,
); LASTAMBIG.store(0, Ordering::SeqCst); *crate::ported::utils::noerrs_lock().lock().unwrap() = ne;
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 {
ZLEMETACS.store(ocs, Ordering::SeqCst);
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);
}
let ol_aliases_line = !(olst == COMP_EXPAND || olst == COMP_EXPAND_COMPLETE); if ol_aliases_line || ol_before == after {
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(); ZLEMETALL.store(m.len() as i32, Ordering::SeqCst);
}
}
ZLEMETACS.store(ORIGCS.load(Ordering::SeqCst), Ordering::SeqCst);
}
}
ret = ret_local;
} else {
ret = docompletion(&s_word, lst, lincmd);
}
let mut dat: [i32; 2] = [lst, ret]; 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);
}
crate::ported::utils::makecommaspecial(false);
crate::ported::utils::errflag.fetch_and(!crate::ported::zsh_h::ERRFLAG_INT, Ordering::SeqCst);
dat[1] }
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 {
use crate::ported::zsh_h::{Qstring, Snull, Stringg};
use crate::ported::ztype_h::{inull, itok};
let sc: Vec<char> = s.chars().collect();
let mut i = 0usize;
while i < sc.len() {
let c = sc[i];
if (c == Qstring && sc.get(i + 1) == Some(&'\''))
|| (c == Stringg && sc.get(i + 1) == Some(&Snull))
{
i += 2;
continue;
}
let b = if (c as u32) < 0x100 { c as u32 as u8 } else { 0 };
if itok(b) && !inull(b) {
return true; }
i += 1; }
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, Inbrack, Meta, Outbrack, 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, 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();
if let Ok(mut g) = COMP_STRING_TOK
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
g.clear();
}
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 Ok(mut g) = RDSTRS.lock() {
g.clear(); }
let mut rdop = String::new(); if let Ok(mut g) = RDSTR.lock() {
*g = None; }
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 mut 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>;
let param_is_hashed = |name: &str| -> bool {
!name.is_empty()
&& crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| {
t.get(name)
.map(|p| (p.node.flags & crate::ported::zsh_h::PM_HASHED as i32) != 0)
})
.unwrap_or(false)
};
'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; LINREDIR.store(linredir, Ordering::SeqCst); cmdstr = None; if let Ok(mut g) = CMDSTR.lock() {
*g = None; }
let mut cmdtok: lextok = NULLTOK; varname = None; if let Ok(mut g) = VARNAME.get_or_init(|| Mutex::new(None)).lock() {
*g = 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;
LINARR.store(0, Ordering::SeqCst); 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;
LINREDIR.store(linredir, Ordering::SeqCst); 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;
LINARR.store(1, Ordering::SeqCst); varname = tokstr().map(|s| ztrdup(&s));
if let Ok(mut g) = VARNAME.get_or_init(|| Mutex::new(None)).lock() {
*g = varname.clone();
}
} else if tokv == INPAR_TOK {
parct += 1;
} else if tokv == OUTPAR_TOK {
if parct != 0 {
parct -= 1;
} else if linarr != 0 {
linarr = 0;
LINARR.store(0, Ordering::SeqCst); set_incmdpos(true);
}
}
if inredir() && IS_REDIROP(tokv) {
let op = crate::ported::lex::tokstrings
.get(tokv as usize)
.copied()
.flatten()
.unwrap_or(""); let tokfd = crate::ported::lex::tokfd();
rdop = if tokfd >= 0 {
format!("{}{}", tokfd, op) } else {
op.to_string() };
if let Ok(mut g) = RDSTR.lock() {
*g = Some(rdop.clone()); }
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); if let Ok(mut g) = CMDSTR.lock() {
*g = cmdstr.clone();
}
}
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 {
if !rdop.is_empty() {
if let Some(ts) = tokstr() {
if let Ok(mut g) = RDSTRS.lock() {
g.push(format!("{}:{}", rdop, ts)); }
}
}
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;
LINREDIR.store(0, Ordering::SeqCst); INWHAT.store(IN_ENV, Ordering::SeqCst);
} else {
LINCMD.store(cp, Ordering::SeqCst);
linredir = rd;
LINREDIR.store(rd, Ordering::SeqCst); }
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 && li >= 0 && (li as usize) <= zml.len() {
if parend >= 0 {
zlemetall -= parend;
ZLEMETALL.store(zlemetall, Ordering::SeqCst);
let cut = zlemetall + addedx;
if cut >= 0
&& (cut as usize) <= zml.len()
&& zml.is_char_boundary(cut as usize)
{
zml.truncate(cut as usize);
}
}
zcontext_restore(); tt = None; linptr = zml[(li as usize).min(zml.len())..].to_string();
continue 's_restart; }
}
let mut s: String = String::new();
if INWHAT.load(Ordering::SeqCst) == IN_MATH {
} 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])); if let Ok(mut g) = VARNAME.get_or_init(|| Mutex::new(None)).lock() {
*g = varname.clone(); }
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) {
INWHAT.store(IN_MATH, Ordering::SeqCst); INSUBSCR.store(
if param_is_hashed(varname.as_deref().unwrap_or("")) {
2
} else {
1
},
Ordering::SeqCst,
);
} 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 {
{
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.load(Ordering::SeqCst).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);
}
}
set_noaliases(ona);
zcontext_restore();
return None; }
if WE.load(Ordering::SeqCst) > zlemetall {
WE.store(zlemetall, Ordering::SeqCst);
}
zlemetall = meta_snap.len() as i32;
ZLEMETALL.store(zlemetall, Ordering::SeqCst);
set_noaliases(ona);
if INWHAT.load(Ordering::SeqCst) != IN_MATH {
let sc: Vec<char> = s.chars().collect();
let wb0 = WB.load(Ordering::SeqCst);
let cursor_off = ((zlemetacs_qsub - wb0).max(0) as usize).min(sc.len());
let is_ident = |c: char| {
let mut b = [0u8; 4];
crate::ported::utils::itype_end(
c.encode_utf8(&mut b),
crate::ported::ztype_h::IIDENT as u32,
true,
) != 0
};
let mut depth = 0i32; let mut nb: Option<usize> = None; let mut ne: Option<usize> = None;
let mut nnb: usize = match sc.first() {
Some(&c) if is_ident(c) => 0,
_ => 1,
};
let mut tt_i = 0usize; if LINCMD.load(Ordering::SeqCst) != 0 {
while tt_i < cursor_off && sc[tt_i] == Inbrack {
tt_i += 1;
}
}
while tt_i < cursor_off {
if sc[tt_i] == Inbrack {
depth += 1;
nb = Some(nnb);
ne = Some(tt_i);
tt_i += 1;
} else if depth != 0 && sc[tt_i] == Outbrack {
depth -= 1;
tt_i += 1;
} else {
if !is_ident(sc[tt_i]) {
nnb = tt_i + 1;
}
tt_i += 1;
}
}
if depth != 0 {
INWHAT.store(IN_MATH, Ordering::SeqCst); INSUBSCR.store(1, Ordering::SeqCst); if let (Some(nb), Some(ne)) = (nb, ne) {
if nb < ne {
let vn: String = sc[nb..ne].iter().collect(); if param_is_hashed(&vn) {
INSUBSCR.store(2, Ordering::SeqCst); }
varname = Some(vn);
if let Ok(mut g) = VARNAME.get_or_init(|| Mutex::new(None)).lock() {
*g = varname.clone(); }
}
}
}
}
if INWHAT.load(Ordering::SeqCst) == IN_MATH {
let mut line: Vec<char> = ZLEMETALINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.chars().collect())
.unwrap_or_default();
let addedx_here = ADDEDX.load(Ordering::SeqCst);
if addedx_here > 0 {
let cs = (zlemetacs.max(0) as usize).min(line.len());
let end = (cs + addedx_here as usize).min(line.len());
if cs < end {
line.drain(cs..end);
}
}
let line = line;
let ll = line.len() as i32;
let compfunc_active = crate::ported::zle::compcore::compfunc
.get_or_init(|| Mutex::new(None))
.lock()
.ok()
.and_then(|g| g.clone())
.map(|f| !f.is_empty())
.unwrap_or(false);
let at = |i: i32| -> char {
if i >= 0 && i < ll {
line[i as usize]
} else {
'\0'
}
};
let is_ident_ch = |c: char| {
let mut b = [0u8; 4];
crate::ported::utils::itype_end(
c.encode_utf8(&mut b),
crate::ported::ztype_h::IIDENT as u32,
true,
) != 0
};
if compfunc_active || INSUBSCR.load(Ordering::SeqCst) == 2 {
let mut wbv = zlemetacs - 1;
let mut lev = 0i32;
while wbv > 0 {
let c = at(wbv);
if c == ']' || c == ')' {
lev += 1;
} else if c == '[' {
let was = lev;
lev -= 1;
if was == 0 {
break;
}
} else if c == '(' {
if lev == 0 && at(wbv - 1) == '(' {
break;
}
if lev != 0 {
lev -= 1;
}
}
wbv -= 1;
}
let p_at = wbv; wbv += 1; WB.store(wbv, Ordering::SeqCst);
let open = at(p_at);
if open == '[' || open == '(' {
let close = if open == '[' { ']' } else { ')' };
let byte_off: usize = line[..(p_at.max(0) as usize).min(line.len())]
.iter()
.map(|c| c.len_utf8())
.sum();
let full: String = line.iter().collect();
let mut rest: &str = &full[byte_off..];
if crate::ported::utils::skipparens(open, close, &mut rest) == 0 {
let consumed = full[byte_off..].len() - rest.len();
let consumed_chars = full[byte_off..byte_off + consumed].chars().count();
WE.store(p_at + consumed_chars as i32 - 1, Ordering::SeqCst); if INSUBSCR.load(Ordering::SeqCst) == 2 {
INSUBSCR.store(3, Ordering::SeqCst); }
}
}
} else {
let mut we_i = zlemetacs;
while we_i < ll && is_ident_ch(at(we_i)) {
we_i += 1;
}
WE.store(we_i, Ordering::SeqCst); let mut wb_i = zlemetacs;
while wb_i > 0 && is_ident_ch(at(wb_i - 1)) {
wb_i -= 1;
}
WB.store(wb_i, Ordering::SeqCst); }
let wbv = WB.load(Ordering::SeqCst).clamp(0, ll);
let wev = WE.load(Ordering::SeqCst).clamp(wbv, ll);
s = line[wbv as usize..wev as usize].iter().collect();
if wbv > 2 && at(wbv - 1) == '[' {
let sqbr = wbv - 1;
let mut w_i = sqbr;
while w_i > 0 && is_ident_ch(at(w_i - 1)) {
w_i -= 1;
}
if w_i < sqbr {
let vn: String = line[w_i as usize..sqbr as usize].iter().collect(); if param_is_hashed(&vn) {
if INSUBSCR.load(Ordering::SeqCst) != 3 {
INSUBSCR.store(2, Ordering::SeqCst); }
} else {
INSUBSCR.store(1, Ordering::SeqCst); }
varname = Some(vn);
if let Ok(mut g) = VARNAME.get_or_init(|| Mutex::new(None)).lock() {
*g = varname.clone(); }
}
}
let _ = crate::ported::lex::parse_subst_string(&s);
}
let wb = WB.load(Ordering::SeqCst);
OFFS.store(zlemetacs - wb, Ordering::SeqCst);
{
let ws: Vec<String> = clwords
.iter()
.map(|w| crate::ported::lex::untokenize_ztokens(w))
.collect();
let n = ws.len() as i32;
if let Ok(mut g) = CLWORDS.lock() {
*g = ws.clone();
}
CLWPOS.store(clwpos, Ordering::SeqCst); 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);
tracing::debug!(target: "compsys_args", ?ws, cur, "get_comp_string publish words");
crate::ported::params::setaparam("words", ws);
let _ = crate::ported::params::setiparam("CURRENT", cur as i64);
}
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);
}
{
use crate::ported::zsh_h::{Inbrace, Outbrace, Qstring, Stringg};
let offs_now = OFFS.load(Ordering::SeqCst).max(0) as usize; let sc: Vec<char> = s.chars().collect();
if parambeg(&s, offs_now.min(sc.len())).is_some() {
s = sc
.iter()
.map(|&c| {
if c == dnull {
'"'
} else if c == snull {
'\''
} else {
c
}
})
.collect();
} else {
let mut level: i32 = 0;
let mut out = String::with_capacity(s.len());
for (i, &c) in sc.iter().enumerate() {
if level != 0 && c == snull {
out.push('\''); } else if level != 0 && c == dnull {
out.push('"'); } else {
if (c == Stringg || c == Qstring)
&& sc.get(i + 1).copied() == Some(Inbrace)
{
level += 1; } else if c == Outbrace {
level -= 1; }
out.push(c);
}
}
s = out;
}
}
{
use crate::ported::zsh_h::{Qstring, Stringg, QT_DOLLARS, QT_DOUBLE, QT_SINGLE};
let sc: Vec<char> = s.chars().collect();
let c0 = sc.first().copied();
let c1 = sc.get(1).copied();
let quoted = (c0 == Some(snull)
|| c0 == Some(dnull)
|| ((c0 == Some(Stringg) || c0 == Some(Qstring)) && c1 == Some(snull)))
&& !has_real_token(&sc[1.min(sc.len())..].iter().collect::<String>());
if quoted {
let mut sl = sc.len(); let mut qtptr = 0usize; let mut q: &str;
match c0 {
x if x == Some(snull) => {
q = "'";
INSTRING.store(QT_SINGLE, Ordering::SeqCst);
}
x if x == Some(dnull) => {
q = "\"";
INSTRING.store(QT_DOUBLE, Ordering::SeqCst);
}
_ => {
q = "$'";
INSTRING.store(QT_DOLLARS, Ordering::SeqCst);
qtptr += 1;
sl -= 1;
}
}
if let Ok(mut g) = QIPRE.get_or_init(|| Mutex::new(String::new())).lock() {
g.push_str(q); }
if q.starts_with('$') {
q = &q[1..];
}
if sl > 1 && sc.get(qtptr + sl - 1).copied() == sc.get(qtptr).copied() {
if let Ok(mut g) = QISUF.get_or_init(|| Mutex::new(String::new())).lock() {
g.insert_str(0, q); }
}
if let Ok(mut g) = AUTOQ.get_or_init(|| Mutex::new(String::new())).lock() {
*g = q.to_string();
}
tracing::debug!(
target: "compsys_args",
qipre = %qipre_get(), qisuf = %qisuf_get(),
instring = INSTRING.load(Ordering::SeqCst),
"get_comp_string quote-form"
);
}
}
{
use crate::ported::zsh_h::{Equals, EQUALSOPT};
if s.starts_with(Equals) && !isset(EQUALSOPT) {
s = format!("={}", &s[Equals.len_utf8()..]);
}
}
{
use crate::ported::lex::getkeystring_dollar_quote;
use crate::ported::zsh_h::{Qstring, Stringg, RCQUOTES};
use crate::ported::ztype_h::inull;
fn inull_ch(c: char) -> bool {
(c as u32) < 0x100 && inull(c as u32 as u8)
}
fn write_line(at: usize, t: &str) {
if let Some(m) = ZLEMETALINE.get() {
if let Ok(mut g) = m.lock() {
let end = at + t.len();
if end <= g.len() && g.is_char_boundary(at) && g.is_char_boundary(end) {
g.replace_range(at..end, t);
}
}
}
}
let wb = WB.load(Ordering::SeqCst);
let mut sc: Vec<char> = s.chars().collect();
let mut p: usize = 0;
let mut i: i32 = wb;
let mut j: i32 = 0;
let mut offs = OFFS.load(Ordering::SeqCst);
let mut we = WE.load(Ordering::SeqCst);
while p < sc.len() {
let c = sc[p];
let mut skipchars: i32; if c == Stringg && sc.get(p + 1).copied() == Some(snull) {
let cs_now = ZLEMETACS.load(Ordering::SeqCst);
let mut pe = p + 2;
while pe < sc.len() && sc[pe] != snull && i + ((pe - p) as i32) < cs_now {
pe += 1;
}
if pe >= sc.len() || sc[pe] != snull {
skipchars = 2;
if pe < sc.len() {
j = 1; }
} else {
let (t, snull_idx) = getkeystring_dollar_quote(&sc, p + 2);
let len = (snull_idx - p + 1) as i32; let tlen = t.chars().count() as i32; skipchars = len - tlen; if skipchars >= 0 {
let tchars: Vec<char> = t.chars().collect();
sc[p..p + tlen as usize].copy_from_slice(&tchars);
let ocs = ZLEMETACS.load(Ordering::SeqCst);
ZLEMETACS.store(i, Ordering::SeqCst);
if skipchars > 0 {
sc.drain(p + tlen as usize..p + len as usize);
if i < ocs {
offs -= skipchars;
}
foredel(skipchars, CUT_RAW);
ZLEMETACS.store(ocs, Ordering::SeqCst);
if ocs > i {
ZLEMETACS.store(ocs - skipchars, Ordering::SeqCst);
}
we -= skipchars; }
write_line(i as usize, &t);
p += tlen as usize;
i += tlen;
continue;
} else {
skipchars = 2;
j = 1;
}
}
} else if c == Qstring && sc.get(p + 1).copied() == Some(snull) {
skipchars = 2; } else if inull_ch(c) {
skipchars = 1; } else {
skipchars = 0; }
if skipchars != 0 {
if i < ZLEMETACS.load(Ordering::SeqCst) {
offs -= skipchars; }
if c == snull && isset(RCQUOTES) {
j = 1 - j; }
if sc.get(p + 1).is_some() || c != bnull {
if c == bnull {
if ZLEMETACS.load(Ordering::SeqCst) == i + 1 {
ZLEMETACS.fetch_add(1, Ordering::SeqCst);
offs += 1;
}
i += 1;
} else {
let ocs = ZLEMETACS.load(Ordering::SeqCst); ZLEMETACS.store(i, Ordering::SeqCst); foredel(skipchars, CUT_RAW); i -= 1; ZLEMETACS.store(ocs, Ordering::SeqCst);
if ocs > i {
let mut cs = ocs - skipchars; if wb > cs {
cs = wb; }
ZLEMETACS.store(cs, Ordering::SeqCst);
}
we -= skipchars; i += 1; }
} else {
let ocs = ZLEMETACS.load(Ordering::SeqCst); ZLEMETACS.store(we, Ordering::SeqCst); backdel(skipchars, CUT_RAW); let mut cs = if ocs == we { we - skipchars } else { ocs }; if wb > cs {
cs = wb; }
ZLEMETACS.store(cs, Ordering::SeqCst);
we -= skipchars; i += 1; }
for _ in 0..skipchars {
if p < sc.len() {
sc.remove(p);
}
}
continue;
} else if j != 0 && c == '\'' && i < ZLEMETACS.load(Ordering::SeqCst) {
offs -= 1; }
p += 1;
i += 1;
}
s = sc.into_iter().collect();
OFFS.store(offs, Ordering::SeqCst);
WE.store(we, Ordering::SeqCst);
tracing::debug!(
target: "compsys_args",
s = %s, offs, we,
zlemetacs = ZLEMETACS.load(Ordering::SeqCst),
"get_comp_string quote cleanup"
);
}
if let Ok(mut g) = ORIGWORD.get_or_init(|| Mutex::new(String::new())).lock() {
*g = s.clone(); }
if !crate::ported::zsh_h::isset(crate::ported::zsh_h::IGNOREBRACES) {
use crate::ported::lex::untokenize_ztokens;
use crate::ported::utils::{itype_end, makecommaspecial, skipparens};
use crate::ported::zle::comp_h::Brinfo;
use crate::ported::zsh_h::{
Comma, Equals, Hat, Inbrace, Inbrack, Inpar, Outbrace, Outbrack, Outpar, Pound,
Qstring, Quest, Star, Stringg, Tilde, COMPLETEINWORD,
};
use crate::ported::ztype_h::{idigit, IIDENT};
let instring = INSTRING.load(Ordering::SeqCst); let sv: Vec<char> = s.chars().collect();
let slen = sv.len();
let offs0 = OFFS.load(Ordering::SeqCst);
let curs: usize = if crate::ported::zsh_h::isset(COMPLETEINWORD) {
(offs0.max(0) as usize).min(slen)
} else {
slen
};
let mut predup: Vec<char> = sv.clone();
let mut dp: usize = 0;
let mut bbeg: Option<usize> = None;
let mut bend: usize = 0;
let mut dbeg: usize = 0;
let mut lastp: Option<usize> = None;
let mut firsts: Option<usize> = None;
let mut cant = 0i32;
let mut begi = 0i32;
let mut boffs = offs0;
let mut hascom = 0i32;
let mut brbeg_v: Vec<Brinfo> = Vec::new();
let mut brend_v: Vec<Brinfo> = Vec::new();
let mut i: i32 = 0;
let mut p: usize = 0;
while p < slen {
let c = sv[p];
if c == Stringg || c == Qstring {
let n1 = sv.get(p + 1).copied();
if n1 == Some(Inbrace) || n1 == Some(Inpar) || n1 == Some(Inbrack) {
let open = n1.unwrap(); let close = if open == Inbrace {
Outbrace } else if open == Inpar {
Outpar } else {
Outbrack };
let tail: String = sv[p + 1..].iter().collect();
let mut rest: &str = &tail;
let unbalanced = skipparens(open, close, &mut rest); let adv = tail.chars().count() - rest.chars().count();
if unbalanced != 0 {
tt = None;
break;
}
let tp = p + 1 + adv;
i += (tp - p) as i32; dp += tp - p; p = tp; } else if n1 != Some(snull) {
let mut tp = p + 1; while let Some(&tc) = sv.get(tp) {
if tc == '^'
|| tc == Hat
|| tc == '='
|| tc == Equals
|| tc == '~'
|| tc == Tilde
|| tc == '#'
|| tc == Pound
|| tc == '+'
{
tp += 1;
} else {
break;
}
}
let tc = sv.get(tp).copied();
if tc == Some(Quest)
|| tc == Some(Star)
|| tc == Some(Stringg)
|| tc == Some(Qstring)
|| tc == Some('?')
|| tc == Some('*')
|| tc == Some('$')
|| tc == Some('-')
|| tc == Some('!')
|| tc == Some('@')
{
p += 1;
i += 1;
} else {
if sv
.get(tp)
.is_some_and(|&x| (x as u32) < 128 && idigit(x as u8))
{
while sv
.get(tp)
.is_some_and(|&x| (x as u32) < 128 && idigit(x as u8))
{
tp += 1;
}
} else {
let tail: String = sv[tp..].iter().collect();
let ie_bytes = itype_end(&tail, IIDENT as u32, false);
let ie = tail[..ie_bytes].chars().count();
if ie != 0 {
tp += ie;
} else {
tt = None;
break;
}
}
if sv.get(tp).copied() == Some(Inbrace) {
cant = 1;
break;
}
tp -= 1; i += (tp - p) as i32; dp += tp - p; p = tp; }
}
} else if p < curs {
if c == Outbrace {
cant = 1;
break;
}
if c == Inbrace {
let tail: String = sv[p..].iter().collect(); let mut rest: &str = &tail;
let unbalanced = skipparens(Inbrace, Outbrace, &mut rest); if unbalanced == 0 {
let tp = p + (tail.chars().count() - rest.chars().count());
i += (tp - p) as i32 - 1; dp += tp - p - 1; p = tp - 1; p += 1;
dp += 1;
i += 1;
continue;
}
makecommaspecial(true); if let Some(bb) = bbeg {
let len = bend - bb; NBRBEG.fetch_add(1, Ordering::SeqCst); let raw: String = sv[bb..bb + len].iter().collect();
let bstr = untokenize_ztokens("ename(&raw, instring));
let pre: String = predup[..dbeg].iter().collect();
let qpos = quotename(&pre, instring).chars().count() as i32;
brbeg_v.push(Brinfo {
next: None, prev: None, str: Some(bstr),
pos: begi, qpos, curpos: 0,
});
i -= len as i32; boffs -= len as i32; predup.drain(dbeg..dbeg + len); dp -= len; }
bbeg = Some(p); lastp = Some(p); dbeg = dp; bend = p + 1; begi = i; } else if c == Comma && bbeg.is_some() {
bend = p + 1;
hascom = 1;
}
} else {
if c == Inbrace {
let tail: String = sv[p..].iter().collect(); let mut rest: &str = &tail;
let unbalanced = skipparens(Inbrace, Outbrace, &mut rest); if unbalanced == 0 {
let tp = p + (tail.chars().count() - rest.chars().count());
i += (tp - p) as i32 - 1; dp += tp - p - 1; p = tp - 1; p += 1; dp += 1;
i += 1;
continue;
}
cant = 1; makecommaspecial(true); break; }
if p == curs {
if let Some(bb) = bbeg {
let len = bend - bb; NBRBEG.fetch_add(1, Ordering::SeqCst); let raw: String = sv[bb..bb + len].iter().collect();
let bstr = untokenize_ztokens("ename(&raw, instring)); let pre: String = predup[..dbeg].iter().collect();
let qpos = quotename(&pre, instring).chars().count() as i32; brbeg_v.push(Brinfo {
next: None, prev: None,
str: Some(bstr),
pos: begi, qpos,
curpos: 0,
});
i -= len as i32; boffs -= len as i32; predup.drain(dbeg..dbeg + len); dp -= len; }
bbeg = None; }
if c == Comma {
if bbeg.is_none() {
bbeg = Some(p);
}
hascom = 2;
} else if c == Outbrace {
if bbeg.is_none() {
bbeg = Some(p); }
let bb = bbeg.unwrap();
let len = p + 1 - bb; if firsts.is_none() {
firsts = Some(p + 1); }
NBREND.fetch_add(1, Ordering::SeqCst); let raw: String = sv[bb..bb + len].iter().collect();
let bstr = untokenize_ztokens("ename(&raw, instring)); brend_v.insert(
0,
Brinfo {
next: None,
prev: None,
str: Some(bstr),
pos: dp as i32 - len as i32 + 1, qpos: len as i32, curpos: 0,
},
);
bbeg = None; }
}
p += 1; dp += 1;
i += 1;
}
if cant != 0 {
brbeg_v.clear();
brend_v.clear();
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);
} else {
if p == curs {
if let Some(bb) = bbeg {
let len = bend - bb; NBRBEG.fetch_add(1, Ordering::SeqCst); let raw: String = sv[bb..bb + len].iter().collect();
let bstr = untokenize_ztokens("ename(&raw, instring)); let pre: String = predup[..dbeg].iter().collect();
let qpos = quotename(&pre, instring).chars().count() as i32; brbeg_v.push(Brinfo {
next: None, prev: None,
str: Some(bstr),
pos: begi, qpos,
curpos: 0,
});
boffs -= len as i32; predup.drain(dbeg..dbeg + len); }
}
if !brend_v.is_empty() {
for bp in brend_v.iter_mut() {
let pos = bp.pos.max(0) as usize; let l = bp.qpos.max(0) as usize; let cut = (pos + l).min(predup.len());
let tail: String = predup[cut..].iter().collect();
bp.pos = tail.chars().count() as i32; bp.qpos = quotename(&tail, instring).chars().count() as i32; predup.drain(pos.min(predup.len())..cut); }
}
if hascom != 0 {
if let Some(lp) = lastp {
let pre: String = sv[..lp].iter().collect();
let v = untokenize_ztokens(&pre);
if let Ok(mut g) = LASTPREBR.get_or_init(|| Mutex::new(None)).lock() {
*g = Some(v);
}
}
if let Ok(mut g) = LASTPOSTBR.get_or_init(|| Mutex::new(None)).lock() {
*g = firsts.map(|f| {
let post: String = sv[f.min(slen)..].iter().collect();
untokenize_ztokens(&post)
});
}
}
s = predup.iter().collect();
OFFS.store(boffs, Ordering::SeqCst);
let link = |mut v: Vec<Brinfo>| -> Option<Box<Brinfo>> {
let mut head: Option<Box<Brinfo>> = None;
while let Some(mut node) = v.pop() {
node.next = head;
head = Some(Box::new(node));
}
head
};
if let Ok(mut g) = BRBEG.get_or_init(|| Mutex::new(None)).lock() {
*g = link(brbeg_v);
}
if let Ok(mut g) = BREND.get_or_init(|| Mutex::new(None)).lock() {
*g = link(brend_v);
}
tracing::debug!(
target: "compsys_args",
s = %s, offs = boffs,
nbrbeg = NBRBEG.load(Ordering::SeqCst),
nbrend = NBREND.load(Ordering::SeqCst),
"get_comp_string brace tail"
);
}
}
if let Ok(mut g) = COMP_STRING_TOK
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = s.clone();
}
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;
}
fn safe_prefix_len(s: &str, n: usize) -> usize {
let mut n = n.min(s.len());
while n > 0 && !s.is_char_boundary(n) {
n -= 1;
}
n
}
let zml_active = ZLEMETALINE.get().is_some();
if zml_active {
if let Some(m) = ZLEMETALINE.get() {
if let Ok(mut g) = m.lock() {
let mut cs = (ZLEMETACS.load(Ordering::SeqCst) as usize).min(g.len());
while cs > 0 && !g.is_char_boundary(cs) {
cs -= 1;
}
let take = safe_prefix_len(str, len as usize);
g.insert_str(cs, &str[..take]);
ZLEMETALL.store(g.len() as i32, Ordering::SeqCst); if move_cursor {
ZLEMETACS.fetch_add(take as i32, Ordering::SeqCst); }
}
}
return len;
}
let instr = &str[..safe_prefix_len(str, len as usize)]; 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 nullglob = crate::ported::zsh_h::opt_name(crate::ported::zsh_h::NULLGLOB);
let ng = crate::ported::zsh_h::isset(crate::ported::zsh_h::NULLGLOB); crate::ported::options::opt_state_set(nullglob, true); crate::ported::subst::globlist(&mut vl, crate::ported::zsh_h::PREFORK_NO_UNTOK); crate::ported::options::opt_state_set(nullglob, 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();
let _ = &first_item; 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 = {
let mut ss_unnulled = ss.clone(); crate::ported::glob::remnulargs(&mut ss_unnulled); first_item == ss_unnulled
};
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 {
let _ = inststrlen(" ", true, 1);
}
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 l = 0i32; let mut cc = 0i32; let zterm_columns = crate::ported::zle::zle_refresh::WINW
.load(Ordering::Relaxed)
.max(1);
let mut out: Vec<u8> = Vec::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(b'%');
cc += 1;
}
b'n' => {
let s = n.to_string();
cc += s.chars().count() as i32;
out.extend_from_slice(s.as_bytes());
}
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]);
i += 1;
}
}
ch => {
out.push(ch);
cc += 1;
}
}
i += 1;
} else if c == b'\n' {
cc += 1; l += 1 + ((cc - 1) / zterm_columns); cc = 0; out.push(b'\n'); i += 1;
} else {
let ch = fmt[i..].chars().next().unwrap_or(c as char);
let clen = ch.len_utf8();
out.extend_from_slice(&bytes[i..i + clen]);
cc += crate::ported::utils::zwcwidth(ch) as i32; i += clen;
}
}
if dopr {
crate::shout::write(&out);
}
l + (cc / zterm_columns)
}
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 expanded == line {
return 0; }
if let Ok(mut g) = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = expanded;
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 mut new_cs = if oldcs >= cmdwb {
(oldcs + str_chars).saturating_sub(cmdwe - cmdwb) } else {
oldcs
};
let ll = ZLELL.load(Ordering::SeqCst);
if new_cs > ll {
new_cs = ll; }
ZLECS.store(new_cs, 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 CLWORDS: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub static CLWPOS: 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 VARNAME: std::sync::OnceLock<Mutex<Option<String>>> = std::sync::OnceLock::new();
pub static ORIGWORD: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
pub static COMP_STRING_TOK: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
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<Option<String>>> = std::sync::OnceLock::new(); pub static LASTPOSTBR: std::sync::OnceLock<Mutex<Option<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 QIPRE: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new(); pub static QISUF: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
pub fn qipre_get() -> String {
QIPRE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default()
}
pub fn qisuf_get() -> String {
QISUF
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default()
}
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 LINREDIR: AtomicI32 = AtomicI32::new(0);
pub static LINARR: AtomicI32 = AtomicI32::new(0);
pub static RDSTR: Mutex<Option<String>> = Mutex::new(None);
pub static RDSTRS: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub static CMDSTR: Mutex<Option<String>> = Mutex::new(None);
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();
use crate::ported::zsh_h::{Dnull, Qstring, Snull, Star, Stringg};
assert!(has_real_token(&format!("{Stringg}HOME")));
assert!(has_real_token(&format!("{Star}.txt")));
assert!(!has_real_token("hello"));
assert!(!has_real_token("test$var"));
assert!(!has_real_token(&format!("{Snull}:completion:*{Snull}")));
assert!(!has_real_token(&format!("{Snull}abc{Snull}")));
assert!(!has_real_token(&format!("{Dnull}abc{Dnull}")));
assert!(!has_real_token(&format!("{Qstring}'abc'")));
assert!(!has_real_token(&format!("{Stringg}{Snull}abc{Snull}")));
}
#[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();
let bar = format!("{}", crate::ported::zsh_h::Bar);
assert_eq!(cmphaswilds(&bar), 1, "Bar token → has wildcard");
assert_eq!(
cmphaswilds("|"),
0,
"untokenized ASCII | is not a wildcard (c:502 tests Bar)"
);
}
#[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 parambeg_cursor_inside_vs_past_name_in_double_quotes() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
use crate::ported::zsh_h::{Dnull, Qstring};
let inside: String = [Dnull, Qstring, 'P', 'A'].iter().collect();
assert_eq!(
parambeg(&inside, 4),
Some(2),
"cursor inside the name of `\"$PA` must report the name start"
);
let past: String = [Dnull, Qstring, 'H', 'O', 'M', 'E', '/']
.iter()
.collect();
assert_eq!(
parambeg(&past, 7),
None,
"cursor past the name of `\"$HOME/` is not a parameter completion"
);
}
#[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();
}
use crate::ported::zle::compcore::INWHAT;
use crate::ported::zsh_h::IN_MATH;
#[test]
fn get_comp_string_unterminated_subscript_enters_math_context() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let line = "echo $fpath[";
seed_metaline(line, line.chars().count() as i32);
let word = get_comp_string();
assert_eq!(
INWHAT.load(Ordering::SeqCst),
IN_MATH,
"unclosed `[` must switch the completion to the math/subscript context"
);
assert_eq!(
INSUBSCR.load(Ordering::SeqCst),
1,
"a plain (non-assoc) array subscript is insubscr == 1"
);
assert_eq!(
VARNAME
.get_or_init(|| Mutex::new(None))
.lock()
.unwrap()
.clone(),
Some("fpath".to_string()),
"the identifier before `[` names the subscripted parameter"
);
assert_eq!(
word.as_deref(),
Some(""),
"the completion word is the subscript text, not the `$fpath[` line word"
);
assert_eq!(WB.load(Ordering::SeqCst), line.chars().count() as i32);
}
#[test]
fn get_comp_string_closed_subscript_stays_out_of_math() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let line = "echo $fpath[1] ";
seed_metaline(line, line.chars().count() as i32);
let _ = get_comp_string();
assert_ne!(
INWHAT.load(Ordering::SeqCst),
IN_MATH,
"a balanced `[...]` is not a subscript context"
);
assert_eq!(INSUBSCR.load(Ordering::SeqCst), 0);
}
fn seed_metaline(line: &str, cursor: i32) {
use crate::ported::zle::compcore as cc;
if let Ok(mut g) = cc::ZLELINE.get_or_init(|| Mutex::new(String::new())).lock() {
*g = line.to_string();
}
if let Ok(mut g) = cc::ZLEMETALINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
{
*g = line.to_string();
}
cc::ZLECS.store(cursor, Ordering::SeqCst);
cc::ZLELL.store(line.chars().count() as i32, Ordering::SeqCst);
cc::ZLEMETACS.store(cursor, Ordering::SeqCst);
cc::ZLEMETALL.store(line.chars().count() as i32, Ordering::SeqCst);
}
#[test]
fn doexpansion_separates_expansions_with_spaces() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let dir = tempfile::tempdir().expect("tempdir");
let d = dir.path().to_str().expect("utf-8 tempdir").to_string();
for n in ["zzA", "zzB"] {
std::fs::write(dir.path().join(n), b"").expect("fixture file");
}
let word = format!("{}/zz*", d);
let line = format!("ls {}", word);
seed_metaline(&line, line.chars().count() as i32);
WB.store(3, Ordering::SeqCst);
WE.store(line.chars().count() as i32, Ordering::SeqCst);
let rc = doexpansion(&word, COMP_EXPAND, COMP_EXPAND_COMPLETE, 0);
let got = crate::ported::zle::compcore::ZLEMETALINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
assert_eq!(rc, 0, "an expansion that changed the word returns 0");
assert_eq!(got, format!("ls {}/zzA {}/zzB ", d, d));
}
#[test]
fn doexpansion_nullglob_toggle_drops_nonmatching_glob_silently() {
use crate::ported::options::opt_state_set;
use crate::ported::zsh_h::{
isset, opt_name, CSHNULLGLOB, EXECOPT, GLOBOPT, NOMATCH, NULLGLOB,
};
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
opt_state_set(opt_name(GLOBOPT), true);
opt_state_set(opt_name(EXECOPT), true);
opt_state_set(opt_name(NOMATCH), true);
opt_state_set(opt_name(NULLGLOB), false);
opt_state_set(opt_name(CSHNULLGLOB), false);
*crate::ported::utils::noerrs_lock().lock().unwrap() = 0;
crate::ported::utils::errflag.store(0, Ordering::SeqCst);
let dir = tempfile::tempdir().expect("tempdir");
let d = dir.path().to_str().expect("utf-8 tempdir").to_string();
std::fs::create_dir_all(dir.path().join("aa/bb")).expect("fixture dirs");
let word = format!("{}/**/zz-no-such-entry", d);
let line = format!("ls -d {}", word);
seed_metaline(&line, line.chars().count() as i32);
WB.store(6, Ordering::SeqCst);
WE.store(line.chars().count() as i32, Ordering::SeqCst);
let rc = doexpansion(&word, COMP_EXPAND, COMP_EXPAND_COMPLETE, 0);
assert_eq!(
crate::ported::utils::errflag.load(Ordering::SeqCst),
0,
"a non-matching glob under the c:2286 NULLGLOB toggle must not \
raise NOMATCH — errflag set means `no matches found` was printed"
);
assert_eq!(rc, 1, "no expansion happened, so doexpansion returns 1");
let got = crate::ported::zle::compcore::ZLEMETALINE
.get_or_init(|| Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
assert_eq!(got, line, "the command line must be untouched");
assert!(
!isset(NULLGLOB),
"the c:2288 restore must return NULLGLOB to its pre-expansion state"
);
}
}