use std::sync::atomic::{AtomicI32, Ordering};
use crate::ported::utils::write_loop;
use crate::ported::zle::compcore::{ADDEDX, ZLEMETACS, ZLEMETALINE, ZLEMETALL};
use crate::ported::zle::zle_h::{
COMP_COMPLETE, COMP_EXPAND, COMP_EXPAND_COMPLETE, COMP_LIST_COMPLETE, COMP_LIST_EXPAND,
COMP_SPELL,
};
use crate::ported::zsh_h::{
isset, GLOBCOMPLETE, MENUCOMPLETE, QT_BACKSLASH, QT_DOLLARS, QT_DOUBLE, QT_NONE, QT_SINGLE,
RECEXACT,
};
#[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(keybuf: &[u8]) -> i32 {
if keybuf.first() != Some(&b'\t') || keybuf.len() > 1 {
return 0;
}
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 _ = WOULDINSTAB.load(Ordering::SeqCst);
1
}
pub fn completecall(args: &[String]) -> i32 {
docomplete(COMP_COMPLETE)
}
pub fn completeword() -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(1, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); let lastch = crate::ported::zle::compcore::LASTCHAR.load(Ordering::SeqCst);
if lastch == b'\t' as i32 && usetab(&[b'\t']) != 0 {
return crate::ported::zle::zle_misc::selfinsert();
}
docomplete(COMP_COMPLETE) }
pub fn menucomplete() -> i32 {
USEMENU.store(1, Ordering::SeqCst); USEGLOB.store(1, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_COMPLETE) }
pub fn listchoices() -> 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() -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(0, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_SPELL) }
pub fn deletecharorlist() -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(1, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); if ZLECS.load(Ordering::SeqCst)
== ZLELL.load(Ordering::SeqCst)
{
docomplete(COMP_LIST_COMPLETE)
} else {
deletechar()
}
}
pub fn expandword() -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(0, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_EXPAND) }
pub fn expandorcomplete() -> i32 {
USEMENU.store(0, Ordering::SeqCst); USEGLOB.store(1, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_EXPAND_COMPLETE) }
pub fn menuexpandorcomplete() -> i32 {
USEMENU.store(1, Ordering::SeqCst); USEGLOB.store(1, Ordering::SeqCst); WOULDINSTAB.store(0, Ordering::SeqCst); docomplete(COMP_EXPAND_COMPLETE) }
pub fn listexpand() -> 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() -> i32 {
WOULDINSTAB.store(0, Ordering::SeqCst); ZMOD.lock().unwrap().mult =
-ZMOD.lock().unwrap().mult; menucomplete() }
pub fn acceptandmenucomplete() -> i32 {
if MENUCMP.load(Ordering::SeqCst) == 0 {
return 1;
}
MENUCMP.store(2, Ordering::SeqCst);
docomplete(COMP_COMPLETE)
}
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 {
let bytes = str.as_bytes();
if bytes.len() == 1 && (bytes[0] == b'[' || bytes[0] == b']') {
return 0;
}
let mut idx = 0;
if bytes.len() >= 2 && bytes[0] == b'%' && bytes[1] == b'?' {
idx = 2;
}
let mut esc = false;
while idx < bytes.len() {
let c = bytes[idx];
if esc {
esc = false;
} else if c == b'\\' {
esc = true;
} else if c == b'*' || c == b'?' || c == b'[' {
return 1;
}
idx += 1;
}
0
}
pub fn parambeg(s: &str, offs: usize) -> Option<usize> {
let bytes = s.as_bytes();
if offs > bytes.len() || offs == 0 {
return None;
}
let mut p = offs.min(bytes.len()) - 1;
loop {
if bytes[p] == b'$' {
while p > 0 && bytes[p - 1] == b'$' {
p -= 1;
}
while p + 2 < bytes.len() && bytes[p + 1] == b'$' && bytes[p + 2] == b'$' {
p += 2;
}
return Some(p);
}
if p == 0 {
return None;
}
p -= 1;
}
}
pub fn docomplete(lst: i32) -> i32 {
thread_local! { static ACTIVE: std::cell::Cell<bool> =
const { std::cell::Cell::new(false) }; }
if ACTIVE.with(|c| c.get()) {
crate::ported::utils::zwarn("completion cannot be used recursively (yet)");
return 1;
}
ACTIVE.with(|c| c.set(true));
let mut lst_box = lst;
let h_before = crate::ported::module::gethookdef("before_complete");
if !h_before.is_null() {
let lst_ptr = (&mut lst_box) as *mut i32 as *mut std::ffi::c_void;
crate::ported::module::runhookdef(h_before, lst_ptr);
}
if doexpandhist() != 0 {
ACTIVE.with(|c| c.set(false));
return 0;
}
let line = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let ret = crate::ported::zle::compcore::do_completion(&line, 0, lst);
let mut dat: [i32; 2] = [0, 0];
let h_after = crate::ported::module::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);
}
let _ = Ordering::Relaxed;
ACTIVE.with(|c| c.set(false));
ret
}
pub fn addx(ptmp: &mut String) -> i32 {
let cs = ZLECS.load(Ordering::SeqCst) as usize;
let ll = ZLELL.load(Ordering::SeqCst) as usize;
let instring = INSTRING.load(Ordering::SeqCst);
let comppref = COMPPREF.load(Ordering::SeqCst) != 0;
let (ch_at, prev_at): (Option<char>, Option<char>) = {
let line = ZLELINE.lock().unwrap();
let at = line.get(cs).copied();
let prev = if cs > 0 {
line.get(cs - 1).copied()
} else {
None
};
(at, prev)
};
let is_iblank = matches!(ch_at, Some(' ' | '\t'));
let is_blank_unescaped = is_iblank && (cs == 0 || prev_at != Some('\\'));
let cs_at_end = ch_at.is_none() || cs >= ll;
let is_newline = ch_at == Some('\n'); let is_separator = matches!(ch_at, Some(')' | '`' | '}' | ';' | '|' | '&' | '>' | '<')); let is_instring_quote = instring != QT_NONE && matches!(ch_at, Some('"' | '\''));
let addspace = comppref && ch_at.is_some()
&& !matches!(ch_at, Some(' ' | '\t'));
let fire = cs_at_end
|| is_newline
|| is_blank_unescaped
|| is_separator
|| is_instring_quote
|| addspace;
if fire {
let snap: String = ZLELINE.lock().unwrap().iter().collect();
*ptmp = snap;
let mut line = ZLELINE.lock().unwrap();
line.insert(cs, 'x');
if addspace {
line.insert(cs + 1, ' ');
}
drop(line);
let added = if addspace { 2 } else { 1 };
ADDEDX.store(added, Ordering::SeqCst);
ZLELL.fetch_add(added as usize, Ordering::SeqCst);
added
} else {
ADDEDX.store(0, Ordering::SeqCst);
ptmp.clear();
0
}
}
pub fn dupstrspace(str: &str) -> String {
let len = str.len(); let mut out = String::with_capacity(len + 2); out.push_str(str); out.push(' '); out }
pub fn metafy_line(s: &str) -> String {
let mut result = String::with_capacity(s.len() * 2);
for c in s.chars() {
if c == META || (c as u32) >= 0x83 {
result.push(META);
result.push(char::from_u32((c as u32) ^ 32).unwrap_or(c));
} else {
result.push(c);
}
}
result
}
pub fn unmetafy_line(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == META {
if let Some(&next) = chars.peek() {
chars.next();
result.push(char::from_u32((next as u32) ^ 32).unwrap_or(next));
}
} else {
result.push(c);
}
}
result
}
pub fn freebrinfo(p: Option<crate::ported::zle::zle_h::BrinfoPtr>) {
drop(p);
}
pub fn dupbrinfo(
mut p: Option<&crate::ported::zle::zle_h::brinfo>,
) -> (
Option<crate::ported::zle::zle_h::BrinfoPtr>,
Option<*const crate::ported::zle::zle_h::brinfo>,
) {
let mut head: Option<crate::ported::zle::zle_h::BrinfoPtr> = None; let mut last_ptr: Option<*const crate::ported::zle::zle_h::brinfo> = None;
let mut tail: *mut Option<crate::ported::zle::zle_h::BrinfoPtr> = &mut head;
while let Some(node) = p {
let cloned = Box::new(crate::ported::zle::zle_h::brinfo {
next: None, prev: None, str: node.str.clone(), pos: node.pos, qpos: node.qpos, curpos: node.curpos, });
unsafe {
*tail = Some(cloned);
let inserted = (*tail).as_mut().unwrap();
last_ptr = Some(inserted.as_ref() as *const _);
tail = &mut inserted.next;
}
p = node.next.as_deref(); }
(head, last_ptr)
}
pub fn has_real_token(s: &str) -> bool {
let special = ['$', '`', '"', '\'', '\\', '{', '}', '[', ']', '*', '?', '~'];
let mut escaped = false;
for c in s.chars() {
if escaped {
escaped = false;
continue;
}
if c == '\\' {
escaped = true;
continue;
}
if special.contains(&c) {
return true;
}
}
false
}
pub fn get_comp_string() -> Option<String> {
let snap: String = ZLELINE
.lock()
.unwrap()
.iter()
.collect();
let cs = ZLECS
.load(Ordering::SeqCst)
.min(snap.len());
let bytes = snap.as_bytes();
let mut start = cs;
while start > 0 && !bytes[start - 1].is_ascii_whitespace() {
start -= 1;
}
let mut end = cs;
while end < bytes.len() && !bytes[end].is_ascii_whitespace() {
end += 1;
}
if start == end {
return None;
}
Some(snap[start..end].to_string())
}
pub fn inststrlen(
str: &str,
move_cursor: bool,
mut len: i32,
) -> i32 {
if len == 0 || str.is_empty() {
return 0;
}
if len == -1 {
len = str.len() as i32;
}
let zml_active = ZLEMETALINE.get().is_some();
if zml_active {
if let Some(m) = ZLEMETALINE.get() {
if let Ok(mut g) = m.lock() {
let cs = ZLEMETACS.load(Ordering::SeqCst) as usize;
let cs = cs.min(g.len());
let take = (len as usize).min(str.len());
let bytes = g.as_bytes();
let new_line: String = String::from_utf8_lossy(&bytes[..cs]).into_owned()
+ &str[..take]
+ &String::from_utf8_lossy(&bytes[cs..]);
*g = new_line;
ZLEMETALL.store(g.len() as i32, Ordering::SeqCst); if move_cursor { ZLEMETACS.fetch_add(take as i32, Ordering::SeqCst); }
}
}
return len;
}
let instr = &str[..(len as usize).min(str.len())]; let zlestr: Vec<char> = stringaszleline(instr); 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() -> i32 {
0
}
pub fn docompletion() -> i32 {
crate::ported::zle::compcore::do_completion(
"",
0,
COMP_LIST_COMPLETE,
)
}
pub fn pfxlen(s1: &str, s2: &str) -> usize {
s1.chars()
.zip(s2.chars())
.take_while(|(a, b)| a == b)
.count()
}
pub fn sfxlen(s1: &str, s2: &str) -> usize {
s1.chars()
.rev()
.zip(s2.chars().rev())
.take_while(|(a, b)| a == b)
.count()
}
pub fn printfmt(fmt: &str, n: i32, dopr: bool, doesc: bool) -> i32 {
let bytes = fmt.as_bytes();
let mut i = 0;
let mut cc = 0i32; let mut out = String::new();
while i < bytes.len() {
let c = bytes[i];
if doesc && c == b'%' {
i += 1;
while i < bytes.len() && (bytes[i]).is_ascii_digit() {
i += 1;
}
if i >= bytes.len() {
break;
}
match bytes[i] {
b'%' => {
out.push('%');
cc += 1;
}
b'n' => {
let s = n.to_string();
cc += s.chars().count() as i32;
out.push_str(&s);
}
b'B' | b'b' | b'S' | b's' | b'U' | b'u' | b'F' | b'f' | b'K' | b'k' => {
}
b'{' => {
i += 1;
while i < bytes.len() && bytes[i] != b'}' {
out.push(bytes[i] as char);
i += 1;
}
}
ch => {
out.push(ch as char);
cc += 1;
}
}
i += 1;
} else {
out.push(c as char);
cc += 1;
i += 1;
}
}
if dopr {
use std::sync::atomic::Ordering;
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out_fd, out.as_bytes());
let _ = write_loop(out_fd, b"\n");
}
cc
}
pub fn listlist(items: &[String], cols: usize) -> i32 {
let num = items.len(); if num == 0 {
return 0;
}
let mut lens: Vec<usize> = items.iter().map(|s| s.chars().count() + 2).collect(); let longest = *lens.iter().max().unwrap_or(&1); if longest >= cols {
return num as i32;
}
let ncols = (cols / longest).max(1);
let nlines = num.div_ceil(ncols); let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let mut row = String::new();
for (i, s) in items.iter().enumerate() {
row.push_str(s);
let pad = longest - lens[i];
row.push_str(&" ".repeat(pad));
if (i + 1) % ncols == 0 {
let line = row.trim_end();
let _ = write_loop(out_fd, line.as_bytes());
let _ = write_loop(out_fd, b"\n");
row.clear();
}
}
if !row.is_empty() {
let line = row.trim_end();
let _ = write_loop(out_fd, line.as_bytes());
let _ = write_loop(out_fd, b"\n");
}
let _ = (lens.pop(),);
nlines as i32
}
pub fn doexpandhist() -> i32 {
let line = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|g| g.clone())
.unwrap_or_default();
if line.is_empty() {
return 0;
}
if !line.contains('!') {
return 0;
} let expanded = line.clone(); if let Ok(mut g) = crate::ported::zle::compcore::ZLELINE
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = expanded.clone();
crate::ported::zle::compcore::ZLELL.store(g.len() as i32, Ordering::Relaxed);
crate::ported::zle::compcore::ZLECS.store(g.len() as i32, Ordering::Relaxed);
}
1 }
pub fn fixmagicspace() {
crate::ported::zle::compcore::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 q = quotename(&s, 0);
let combined = format!("run-help {}", q);
for (i, ch) in combined.chars().enumerate() {
ZLELINE
.lock()
.unwrap()
.insert(
ZLECS.load(Ordering::SeqCst) + i,
ch,
);
}
ZLECS.fetch_add(
combined.chars().count(),
Ordering::SeqCst,
);
0
}
pub fn expandcmdpath() -> i32 {
let oldcs = ZLECS.load(Ordering::SeqCst);
let s = match getcurcmd() {
Some(c) if !c.is_empty() => c,
_ => return 1, };
let line: String = ZLELINE.lock().unwrap().iter().collect();
let cmdwb = line[..oldcs.min(line.len())]
.rfind(|c: char| c.is_ascii_whitespace())
.map(|i| i + 1)
.unwrap_or(0);
let cmdwe = line[oldcs.min(line.len())..]
.find(|c: char| c.is_ascii_whitespace())
.map(|i| i + oldcs)
.unwrap_or(line.len());
if cmdwe < cmdwb {
return 1;
}
let str_opt = crate::ported::builtin::findcmd(&s, 1, 0);
let str_full = match str_opt {
Some(p) => p,
None => return 1,
};
ZLECS.store(cmdwb, Ordering::SeqCst);
foredel((cmdwe - cmdwb) as i32, 0);
{
let mut zl = ZLELINE.lock().unwrap();
let cs = ZLECS.load(Ordering::SeqCst);
for (i, ch) in str_full.chars().enumerate() {
zl.insert(cs + i, ch);
}
}
let str_chars = str_full.chars().count();
ZLELL.fetch_add(str_chars, Ordering::SeqCst);
let new_cs = if oldcs >= cmdwe.saturating_sub(1) {
oldcs + str_chars - (cmdwe - cmdwb)
} else {
oldcs
};
ZLECS.store(new_cs.min(line.len() + str_chars), Ordering::SeqCst);
0
}
pub fn expandorcompleteprefix() -> i32 {
COMPPREF.store(1, Ordering::SeqCst); let ret = expandorcomplete(); if ZLECS.load(Ordering::SeqCst) > 0
&& ZLELINE.lock().unwrap()
[ZLECS.load(Ordering::SeqCst) - 1]
== ' '
{
makesuffixstr(None, Some("\\-"), 0); }
COMPPREF.store(0, Ordering::SeqCst); ret
}
pub fn endoflist() -> i32 {
let n = LASTLISTLEN.load(Ordering::SeqCst);
if n > 0 {
CLEARFLAG.store(0, Ordering::SeqCst);
trashzle();
for _ in 0..n {
tracing::trace!("endoflist: putc('\\n', shout)");
}
SHOWINGLIST.store(0, Ordering::SeqCst);
LASTLISTLEN.store(0, Ordering::SeqCst);
return 0; }
1 }
pub static USEMENU: AtomicI32 = AtomicI32::new(0);
pub static USEGLOB: AtomicI32 = AtomicI32::new(0);
pub static WOULDINSTAB: AtomicI32 = AtomicI32::new(0);
pub static NBRBEG: AtomicI32 = AtomicI32::new(0); pub static NBREND: AtomicI32 = AtomicI32::new(0);
pub static ORIGCS: AtomicI32 = AtomicI32::new(0); pub static ORIGLL: AtomicI32 = AtomicI32::new(0);
pub static INSUBSCR: AtomicI32 = AtomicI32::new(0);
pub static INSTRING: AtomicI32 = AtomicI32::new(0); pub static INBACKT: AtomicI32 = AtomicI32::new(0);
pub static ORIGLINE: std::sync::OnceLock<std::sync::Mutex<String>> = std::sync::OnceLock::new();
pub static LASTPREBR: std::sync::OnceLock<std::sync::Mutex<String>> = std::sync::OnceLock::new(); pub static LASTPOSTBR: std::sync::OnceLock<std::sync::Mutex<String>> = std::sync::OnceLock::new();
pub static COMPQUOTE: std::sync::OnceLock<std::sync::Mutex<String>> = std::sync::OnceLock::new(); pub static AUTOQ: std::sync::OnceLock<std::sync::Mutex<String>> = std::sync::OnceLock::new();
pub static MENUCMP: AtomicI32 = AtomicI32::new(0);
pub static COMPPREF: AtomicI32 = AtomicI32::new(0);
pub static VALIDLIST: AtomicI32 = AtomicI32::new(0);
pub static SHOWAGAIN: AtomicI32 = AtomicI32::new(0);
pub static LASTAMBIG: AtomicI32 = AtomicI32::new(0);
pub static BASHLISTFIRST: AtomicI32 = AtomicI32::new(0);
pub static 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 crate::zle::zle_h::brinfo;
use super::*;
#[test]
fn test_pfxlen() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(pfxlen("hello", "help"), 3);
assert_eq!(pfxlen("abc", "xyz"), 0);
assert_eq!(pfxlen("test", "test"), 4);
}
#[test]
fn test_sfxlen() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(sfxlen("testing", "running"), 3);
assert_eq!(sfxlen("abc", "xyz"), 0);
}
#[test]
fn addx_skips_when_cursor_in_middle_of_word() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "hello".chars().collect();
ZLECS.store(2, Ordering::SeqCst); ZLELL.store(5, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(0, Ordering::SeqCst);
ADDEDX.store(99, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 0, "no insertion when cursor lands on word-char");
assert_eq!(ADDEDX.load(Ordering::SeqCst), 0);
assert!(
snap.is_empty(),
"ptmp must be NULL/empty when addx doesn't fire"
);
assert_eq!(
ZLELINE.lock().unwrap().iter().collect::<String>(),
"hello",
"buffer must be untouched"
);
}
#[test]
fn addx_inserts_at_end_of_line() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLECS.store(3, Ordering::SeqCst); ZLELL.store(3, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(0, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 1, "exactly one 'x' inserted at EOL");
assert_eq!(ADDEDX.load(Ordering::SeqCst), 1);
assert_eq!(snap, "abc", "snapshot is pre-edit buffer");
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "abcx");
}
#[test]
fn addx_inserts_x_space_when_comppref_on_nonblank() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "ab".chars().collect();
ZLECS.store(1, Ordering::SeqCst); ZLELL.store(2, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(1, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 2, "comppref non-blank → 'x ' (2 chars)");
assert_eq!(ADDEDX.load(Ordering::SeqCst), 2);
COMPPREF.store(0, Ordering::SeqCst);
}
#[test]
fn addx_inserts_when_cursor_on_separator() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "echo|".chars().collect();
ZLECS.store(4, Ordering::SeqCst); ZLELL.store(5, Ordering::SeqCst);
INSTRING.store(QT_NONE, Ordering::SeqCst);
COMPPREF.store(0, Ordering::SeqCst);
let mut snap = String::new();
let added = addx(&mut snap);
assert_eq!(added, 1, "separator at cursor → insert 'x'");
}
#[test]
fn checkparams_hascompmod_gate() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
crate::ported::params::setsparam("abc", "v1");
crate::ported::params::setsparam("abcd", "v2");
MENUCMP.store(0, Ordering::SeqCst);
HASCOMPMOD.store(true, Ordering::SeqCst);
HASCOMPMOD.store(true, Ordering::SeqCst);
HASCOMPMOD.store(false, Ordering::SeqCst);
assert_eq!(
checkparams("abc"),
1,
"with !hascompmod, exact + non-menu → return 1"
);
HASCOMPMOD.store(false, Ordering::SeqCst);
crate::ported::params::setsparam("abc", "");
crate::ported::params::setsparam("abcd", "");
}
#[test]
fn test_has_real_token() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(has_real_token("$HOME"));
assert!(has_real_token("*.txt"));
assert!(!has_real_token("hello"));
assert!(!has_real_token("test\\$var")); }
#[test]
fn dupstrspace_appends_space() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(dupstrspace("hello"), "hello ");
}
#[test]
fn dupstrspace_empty_input() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(dupstrspace(""), " ");
}
#[test]
fn freebrinfo_drops_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let head = Some(Box::new(brinfo {
next: Some(Box::new(brinfo {
next: None,
prev: None,
str: "second".into(),
pos: 7,
qpos: 8,
curpos: 9,
})),
prev: None,
str: "first".into(),
pos: 1,
qpos: 2,
curpos: 3,
}));
freebrinfo(head);
}
#[test]
fn dupbrinfo_clones_chain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let src = Box::new(brinfo {
next: Some(Box::new(brinfo {
next: Some(Box::new(brinfo {
next: None,
prev: None,
str: "C".into(),
pos: 30,
qpos: 31,
curpos: 32,
})),
prev: None,
str: "B".into(),
pos: 20,
qpos: 21,
curpos: 22,
})),
prev: None,
str: "A".into(),
pos: 10,
qpos: 11,
curpos: 12,
});
let (head, last) = dupbrinfo(Some(&*src));
assert!(last.is_some());
let h = head.as_ref().unwrap();
assert_eq!(h.str, "A");
assert_eq!(h.pos, 10);
assert_eq!(h.qpos, 11);
assert_eq!(h.curpos, 12);
let n = h.next.as_ref().unwrap();
assert_eq!(n.str, "B");
assert_eq!(n.pos, 20);
let n = n.next.as_ref().unwrap();
assert_eq!(n.str, "C");
assert_eq!(n.pos, 30);
assert!(n.next.is_none());
}
#[test]
fn dupbrinfo_empty_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let (head, last) = dupbrinfo(None);
assert!(head.is_none());
assert!(last.is_none());
}
#[test]
fn spellword_zeroes_globals_returns_docomplete() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
USEMENU.store(99, Ordering::SeqCst);
USEGLOB.store(99, Ordering::SeqCst);
WOULDINSTAB.store(99, Ordering::SeqCst);
let _r = spellword();
assert_eq!(USEMENU.load(Ordering::SeqCst), 0);
assert_eq!(USEGLOB.load(Ordering::SeqCst), 0);
assert_eq!(WOULDINSTAB.load(Ordering::SeqCst), 0);
}
#[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);
assert_eq!(usetab(b"\t"), 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();
assert_eq!(usetab(b"a"), 0, "non-tab byte = 0");
assert_eq!(usetab(b""), 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();
assert_eq!(usetab(b"\t\t"), 0, "more than one byte = 0");
assert_eq!(usetab(b"\tx"), 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);
assert_eq!(usetab(b"\t"), 0,
"cursor after non-WS char → no literal tab (need completion)");
}
#[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);
assert_eq!(usetab(b"\t"), 1,
"after pure-WS indent, tab = literal");
}
}