use std::collections::VecDeque;
use std::io::{self, Read, Write};
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::atomic::Ordering;
use std::sync::atomic::Ordering::SeqCst;
use std::time::{Duration, Instant};
use super::zle_h::{
widget, MOD_CHAR, MOD_CLIP, MOD_LINE, MOD_MULT, MOD_NEG, MOD_NULL, MOD_OSSEL, MOD_PRI,
MOD_TMULT, MOD_VIAPP, MOD_VIBUF, ZLE_LASTCOL, ZLE_NOTCOMMAND,
};
use super::zle_keymap::Keymap;
use super::zle_thingy::Thingy;
use crate::ported::builtin::LASTVAL;
use crate::ported::builtins::sched::zleactive;
use crate::ported::init::SHTTY;
use crate::ported::mem::unqueue_signals;
use crate::ported::module::{addhookfunc, deletehookfunc};
use crate::ported::params::getsparam;
use crate::ported::utils::{errflag, getshfunc, write_loop, zwarnnam};
use crate::ported::zle::compcore::LASTCHAR;
use crate::ported::zle::termquery::mark_output;
use crate::ported::zle::zle_keymap::{
curkeymap, curkeymapname, keymapnamtab, linkkeymap, openkeymap, selectkeymap, LOCALKEYMAP,
};
use crate::ported::zle::zle_misc::DONE;
use crate::ported::zle::zle_thingy::rthingy_nocreate;
use crate::ported::zsh_h::{
hashtable, hookdef, module, HashTable, OPT_ARG_SAFE, OPT_ISSET, PM_ARRAY, PM_HASHED, PM_SCALAR,
ZLCON_LINE_START, ZLE_CMD_ADD_TO_LINE, ZLE_CMD_CHPWD, ZLE_CMD_GET_KEY, ZLE_CMD_GET_LINE,
ZLE_CMD_POSTEXEC, ZLE_CMD_PREEXEC, ZLE_CMD_READ, ZLE_CMD_REFRESH, ZLE_CMD_RESET_PROMPT,
ZLE_CMD_SET_HIST_LINE, ZLE_CMD_SET_KEYMAP, ZLE_CMD_TRASH, ZLRF_HISTORY, ZLRF_NOSETTY,
};
use crate::ported::zle::zle_h::{change, modifier};
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_h::*, zle_hist::*, zle_misc::*, zle_move::*, zle_params::*,
zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
};
pub fn zsetterm() -> io::Result<()> {
if let Ok(mut g) = crate::ported::utils::SHTTYINFO.lock() {
if g.is_none() {
*g = crate::ported::utils::gettyinfo(); }
}
let tty_fd = {
let s = SHTTY.load(SeqCst);
if s >= 0 {
s
} else {
TTYFD.load(SeqCst)
}
};
let mut termios = termios::Termios::from_fd(tty_fd)?;
termios.c_lflag &= !(termios::ICANON | termios::ECHO);
termios.c_lflag &= !(libc::FLUSHO as libc::tcflag_t);
let veof = termios.c_cc[termios::VEOF];
if veof != 0 {
EOFCHAR.store(veof as i32, SeqCst);
}
if !crate::ported::zsh_h::isset(crate::ported::zsh_h::FLOWCONTROL) {
termios.c_iflag &= !(libc::IXON as libc::tcflag_t);
}
#[cfg(target_os = "linux")]
{
termios.c_oflag &= !(libc::TAB3 as libc::tcflag_t);
}
#[cfg(not(target_os = "linux"))]
{
termios.c_oflag &= !(libc::OXTABS as libc::tcflag_t);
}
termios.c_oflag |= libc::ONLCR as libc::tcflag_t;
let vdisable: libc::cc_t = {
let v = unsafe { libc::fpathconf(0, libc::_PC_VDISABLE) };
if v >= 0 {
v as libc::cc_t
} else {
0xff
}
};
termios.c_cc[libc::VQUIT] = vdisable;
termios.c_cc[libc::VDISCARD] = vdisable;
termios.c_cc[libc::VSUSP] = vdisable;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
{
termios.c_cc[libc::VDSUSP] = vdisable;
}
#[cfg(target_os = "linux")]
{
termios.c_cc[libc::VSWTC] = vdisable;
}
termios.c_cc[libc::VLNEXT] = vdisable;
if !crate::ported::zsh_h::isset(crate::ported::zsh_h::FLOWCONTROL) {
termios.c_cc[libc::VSTART] = vdisable;
termios.c_cc[libc::VSTOP] = vdisable;
}
termios.c_cc[termios::VMIN] = 1;
termios.c_cc[termios::VTIME] = 0;
termios.c_iflag |= (libc::INLCR | libc::ICRNL) as libc::tcflag_t;
termios::tcsetattr(tty_fd, termios::TCSANOW, &termios)?;
Ok(())
}
pub fn ungetbyte(ch: u8) {
KUNGETBUF.lock().unwrap().push_front(ch);
}
pub fn ungetbytes(s: &[u8]) {
for &b in s.iter().rev() {
KUNGETBUF.lock().unwrap().push_front(b);
}
}
pub fn ungetbytes_unmeta(s: &[u8]) {
let mut i = s.len(); while i > 0 {
if i >= 2 && s[i - 2] == 0x83 {
ungetbyte(s[i - 1] ^ 32);
i -= 2;
} else {
ungetbyte(s[i - 1]);
i -= 1;
}
}
}
static ZLE_HIST_SYNC_FIRST: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
static ZLE_HIST_SYNC_CUR: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
impl Default for modifier {
fn default() -> Self {
modifier {
flags: 0,
mult: 1,
tmult: 1,
vibuf: 0,
base: 10,
}
}
}
pub fn breakread(fd: i32, buf: &mut [u8], n: usize) -> isize {
if n == 0 || buf.is_empty() {
return 0;
}
let count = n.min(buf.len());
let r = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, count) };
r as isize
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[allow(non_camel_case_types)]
pub enum ztmouttp {
ZTM_NONE = 0, ZTM_KEY = 1, ZTM_FUNC = 2, ZTM_MAX = 3, }
pub const ZMAXTIMEOUT: u64 = 1 << 21;
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types)]
pub struct ztmout {
pub tp: ztmouttp, pub exp100ths: i64, }
fn calc_timeout(do_keytmout: i64) -> ztmout {
let kt = KEYTIMEOUT.load(SeqCst);
let mut out = if do_keytmout != 0 && (kt > 0 || do_keytmout < 0) {
let exp = if do_keytmout < 0 {
-do_keytmout } else if kt > ZMAXTIMEOUT * 100 {
(ZMAXTIMEOUT * 100) as i64 } else {
kt as i64 };
ztmout {
tp: ztmouttp::ZTM_KEY,
exp100ths: exp,
}
} else {
ztmout {
tp: ztmouttp::ZTM_NONE,
exp100ths: 0,
}
};
if let Some(&(when, _)) = crate::ported::utils::TIMED_FNS.lock().unwrap().first() {
let now = unsafe { libc::time(std::ptr::null_mut()) } as i64;
let diff = (when - now).max(0);
let exp100 = diff.saturating_mul(100);
if out.tp == ztmouttp::ZTM_NONE || exp100 < out.exp100ths {
out.tp = ztmouttp::ZTM_FUNC;
out.exp100ths = exp100;
}
}
out
}
pub fn raw_getbyte(do_keytmout: i64) -> Option<u8> {
use std::os::unix::io::AsRawFd;
let mut timeout = calc_timeout(do_keytmout);
let have_timeout = timeout.tp != ztmouttp::ZTM_NONE;
let shtty = {
let fd = SHTTY.load(Ordering::Relaxed);
if fd >= 0 {
fd
} else {
io::stdin().as_raw_fd()
}
};
let initial_nwatch = WATCH_FDS.lock().map(|t| t.len()).unwrap_or(0);
if initial_nwatch > 0 || have_timeout {
let ready = libc::POLLIN | libc::POLLERR | libc::POLLHUP | libc::POLLNVAL;
let mut dont_poll_fds: Vec<i32> = Vec::new();
loop {
timeout = calc_timeout(do_keytmout);
let have_to = timeout.tp != ztmouttp::ZTM_NONE;
let watches: Vec<(i32, String, i32, u64)> = WATCH_FDS
.lock()
.unwrap()
.iter()
.filter(|w| !dont_poll_fds.contains(&w.fd))
.map(|w| (w.fd, w.func.clone(), w.widget, w.gen))
.collect();
let mut fds: Vec<libc::pollfd> = Vec::with_capacity(1 + watches.len());
fds.push(libc::pollfd {
fd: shtty,
events: libc::POLLIN,
revents: 0,
});
for w in &watches {
fds.push(libc::pollfd {
fd: w.0,
events: libc::POLLIN,
revents: 0,
});
}
let poll_timeout: libc::c_int = if have_to {
(timeout.exp100ths * 10) as libc::c_int
} else {
-1
};
crate::ported::signals_h::winch_unblock(); let selret =
unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, poll_timeout) };
crate::ported::signals_h::winch_block();
if selret < 0 && crate::ported::utils::errflag.load(Ordering::SeqCst) != 0 {
RAW_GETBYTE_ERRNO.store(
std::io::Error::last_os_error().raw_os_error().unwrap_or(0),
Ordering::SeqCst,
);
RAW_GETBYTE_R.store(-1, Ordering::SeqCst);
return None;
}
if selret < 0 {
let e = std::io::Error::last_os_error().raw_os_error();
if e == Some(libc::EINTR) {
crate::signal_queue_guard::settle_deferred_signals(shtty);
continue;
}
RAW_GETBYTE_ERRNO.store(e.unwrap_or(0), Ordering::SeqCst);
RAW_GETBYTE_R.store(-1, Ordering::SeqCst);
return None;
}
if selret == 0 {
if timeout.tp == ztmouttp::ZTM_FUNC {
crate::ported::builtins::sched::checksched();
continue;
}
RAW_GETBYTE_R.store(-2, Ordering::SeqCst);
return None;
}
if fds[0].revents & ready != 0 {
break;
}
let mut fired_any = false;
for (i, w) in watches.iter().enumerate() {
let re = fds[i + 1].revents;
if re & ready == 0 {
continue;
}
let fdbuf = w.0.to_string(); if w.2 != 0 {
crate::ported::zle::zle_utils::zlecallhook(&w.1, Some(&fdbuf));
} else {
let mut args: Vec<String> = vec![w.1.clone(), fdbuf];
if re & libc::POLLERR != 0 {
args.push("err".to_string()); }
if re & libc::POLLHUP != 0 {
args.push("hup".to_string()); }
if re & libc::POLLNVAL != 0 {
args.push("nval".to_string()); }
crate::ported::utils::callhookfunc(&w.1, Some(&args), 0, std::ptr::null_mut());
}
crate::ported::utils::errflag
.fetch_and(!crate::ported::zsh_h::ERRFLAG_ERROR, Ordering::SeqCst);
if re & (libc::POLLHUP | libc::POLLNVAL) != 0 {
WATCH_FDS
.lock()
.unwrap()
.retain(|x| !(x.fd == w.0 && x.gen == w.3));
} else if re & libc::POLLERR != 0 {
dont_poll_fds.push(w.0);
}
fired_any = true;
}
if fired_any {
crate::ported::zle::zle_refresh::zrefresh();
}
}
let mut buf = [0u8; 1];
let mut eintr_retries = 0i32;
let mut die = false; let n = loop {
crate::ported::signals_h::winch_unblock(); let n = unsafe { libc::read(shtty, buf.as_mut_ptr() as *mut libc::c_void, 1) };
let errno = std::io::Error::last_os_error().raw_os_error();
RAW_GETBYTE_ERRNO.store(errno.unwrap_or(0), Ordering::SeqCst);
crate::ported::signals_h::winch_block(); if (n == -1 || n == 0)
&& errno == Some(libc::EINTR)
&& (n == -1 || eintr_retries < 20)
&& crate::ported::utils::errflag.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::RETFLAG.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::BREAKS.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed) == 0
{
eintr_retries = if n == 0 { eintr_retries + 1 } else { 0 };
crate::signal_queue_guard::settle_deferred_signals(shtty);
continue; }
if n == -1 && errno == Some(libc::EIO) && !die {
let mon = crate::ported::zsh_h::isset(crate::ported::zsh_h::MONITOR); crate::ported::options::opt_state_set("monitor", true); crate::ported::utils::attachtty(
crate::ported::modules::clone::mypgrp.load(Ordering::Relaxed),
); crate::ported::zle::zle_refresh::zrefresh(); crate::ported::options::opt_state_set("monitor", mon); die = true; continue; }
break n;
};
if n != 1 {
tracing::warn!(
"DIAG raw_getbyte poll-path returns None (EOF): read({})=={} errno={}",
shtty,
n,
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
);
}
RAW_GETBYTE_R.store(n as i32, Ordering::SeqCst);
return if n == 1 { Some(buf[0]) } else { None };
}
let mut buf = [0u8; 1];
let is_tty = unsafe { libc::isatty(shtty) } != 0;
let in_raw_mode = if is_tty {
let mut t: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(shtty, &mut t) } == 0 {
(t.c_lflag & libc::ICANON) == 0
} else {
false
}
} else {
true
};
if !in_raw_mode {
RAW_GETBYTE_R.store(-2, Ordering::SeqCst);
tracing::warn!(
"DIAG raw_getbyte simple-read returns None (EOF): NOT in raw mode. shtty={} is_tty={}",
shtty,
is_tty
);
return None;
}
let mut eintr_retries = 0i32;
let mut die = false; let n = loop {
crate::ported::signals_h::winch_unblock(); let n = unsafe { libc::read(shtty, buf.as_mut_ptr() as *mut libc::c_void, 1) };
let errno = std::io::Error::last_os_error().raw_os_error();
RAW_GETBYTE_ERRNO.store(errno.unwrap_or(0), Ordering::SeqCst);
crate::ported::signals_h::winch_block(); if (n == -1 || n == 0)
&& errno == Some(libc::EINTR)
&& (n == -1 || eintr_retries < 20)
&& crate::ported::utils::errflag.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::RETFLAG.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::BREAKS.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed) == 0
{
eintr_retries = if n == 0 { eintr_retries + 1 } else { 0 };
crate::signal_queue_guard::settle_deferred_signals(shtty);
continue; }
if n == -1 && errno == Some(libc::EIO) && !die {
let mon = crate::ported::zsh_h::isset(crate::ported::zsh_h::MONITOR); crate::ported::options::opt_state_set("monitor", true); crate::ported::utils::attachtty(
crate::ported::modules::clone::mypgrp.load(Ordering::Relaxed),
); crate::ported::zle::zle_refresh::zrefresh(); crate::ported::options::opt_state_set("monitor", mon); die = true; continue; }
break n;
};
RAW_GETBYTE_R.store(n as i32, Ordering::SeqCst);
if n == 1 {
Some(buf[0])
} else {
tracing::warn!(
"DIAG raw_getbyte simple-read returns None (EOF): read({})=={} errno={}",
shtty,
n,
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
);
None
}
}
pub fn getbyte(do_keytmout: i64) -> Option<u8> {
LASTCHAR_WIDE_VALID.store(0, SeqCst);
let mut icnt = 0i32; let ungot = KUNGETBUF.lock().unwrap().pop_front();
let from_tty = ungot.is_none();
let b = if let Some(u) = ungot { u } else { loop {
let q = crate::ported::signals_h::queue_signal_level();
crate::ported::signals_h::dont_queue_signals();
let raw = raw_getbyte(do_keytmout);
crate::ported::signals_h::restore_queue_signals(q);
if let Some(b) = raw {
break b; }
let r = RAW_GETBYTE_R.load(SeqCst);
if r == -2 {
LASTCHAR.store(-1, SeqCst); return None;
}
if r == 0 {
if (ZLEREADFLAGS.load(SeqCst) & crate::ported::zsh_h::ZLRF_IGNOREEOF) != 0 && icnt < 20
{
icnt += 1;
continue;
}
if crate::ported::zsh_h::interact() {
crate::ported::builtin::STOPMSG.store(1, SeqCst); crate::ported::builtin::zexit(1, crate::ported::zsh_h::ZEXIT_NORMAL);
}
LASTCHAR.store(-1, SeqCst);
return None;
}
icnt = 0;
let e = RAW_GETBYTE_ERRNO.load(SeqCst);
if e == libc::EINTR || e == 0 {
LASTCHAR.store(-1, SeqCst); return None;
}
if e == libc::EWOULDBLOCK || e == libc::EAGAIN {
unsafe { libc::fcntl(0, libc::F_SETFL, 0) }; continue;
}
if crate::ported::zsh_h::interact() {
crate::ported::utils::zerrmsg("error on TTY read: %e", Some(e)); crate::ported::builtin::STOPMSG.store(1, SeqCst); crate::ported::builtin::zexit(1, crate::ported::zsh_h::ZEXIT_NORMAL); }
LASTCHAR.store(-1, SeqCst); return None;
} };
let b = if from_tty && b == b'\n' {
b'\r'
} else if from_tty && b == b'\r' {
b'\n'
} else {
b
};
if VICHGFLAG.load(SeqCst) != 0 {
let mut cur = CURVICHG.lock().unwrap();
if cur.bufptr == cur.bufsz {
cur.bufsz *= 2; }
let idx = cur.bufptr as usize;
if idx < cur.buf.len() {
cur.buf[idx] = b;
} else {
cur.buf.push(b);
}
cur.bufptr += 1;
}
LASTCHAR.store((b as i32) as i32, SeqCst);
Some(b)
}
pub fn getfullchar(do_keytmout: bool) -> Option<char> {
let inchar = getbyte(do_keytmout as i64).map(|b| b as i32).unwrap_or(-1); let r = getrestchar(inchar); if r < 0 {
None
} else {
char::from_u32(r as u32)
}
}
pub fn getrestchar(inchar: i32) -> i32 {
use crate::ported::utils::{mbrtowc, MbStateBuf, MBSTATE_ZERO, MB_INCOMPLETE, MB_INVALID};
LASTCHAR_WIDE_VALID.store(1, SeqCst); if inchar < 0 {
LASTCHAR_WIDE.store(-1, SeqCst); return -1;
}
let mut mbs: MbStateBuf = MBSTATE_ZERO;
let mut c = inchar as u8; loop {
let mut outchar: libc::wchar_t = 0;
let cnt = unsafe {
mbrtowc(
&mut outchar,
&c as *const u8 as *const libc::c_char,
1,
&mut mbs as *mut MbStateBuf as *mut libc::c_void,
)
};
if cnt == MB_INVALID {
LASTCHAR_WIDE.store(-1, SeqCst); return -1;
}
if cnt != MB_INCOMPLETE {
LASTCHAR_WIDE.store(outchar as i32, SeqCst);
return outchar as i32;
}
match getbyte(1) {
Some(n) => {
LASTCHAR_WIDE_VALID.store(1, SeqCst); c = n; }
None => {
LASTCHAR_WIDE.store(-1, SeqCst); return -1;
}
}
}
}
pub fn redrawhook() {
let name = "zle-line-pre-redraw";
if !crate::ported::zle::zle_thingy::rthingy_nocreate(name) {
return;
}
let saverrflag = errflag.load(Ordering::Relaxed);
let savretflag = crate::ported::builtin::RETFLAG.load(Ordering::Relaxed);
let lastcmd_prev = LASTCMD.load(SeqCst);
let old_incompfunc = crate::ported::utils::INCOMPFUNC.load(SeqCst);
let old_viinrepeat = crate::ported::zle::zle_vi::VIINREPEAT.load(SeqCst);
let lbindk_save = LBINDK.lock().unwrap().clone(); let bindk_save = BINDK.lock().unwrap().clone(); let args = vec![name.to_string()];
crate::ported::utils::INCOMPFUNC.store(0, SeqCst); execzlefunc(name, &args, 1, 0); crate::ported::utils::INCOMPFUNC.store(old_incompfunc, SeqCst); crate::ported::zle::zle_vi::VIINREPEAT.store(old_viinrepeat, SeqCst);
let cur = errflag.load(Ordering::Relaxed);
errflag.store(
saverrflag | (cur & crate::ported::zsh_h::ERRFLAG_INT),
Ordering::Relaxed,
); crate::ported::builtin::RETFLAG.store(savretflag, Ordering::Relaxed);
crate::ported::zle::zle_thingy::unrefthingy(name); *LBINDK.lock().unwrap() = lbindk_save; *BINDK.lock().unwrap() = bindk_save;
LASTCMD.store(lastcmd_prev, SeqCst); }
pub fn zlecore() {
DONE.store(0, SeqCst);
while DONE.load(SeqCst) == 0
&& errflag.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::EXIT_PENDING.load(SeqCst) == 0
{
crate::ported::zle::zle_vi::VILINERANGE.store(0, SeqCst);
crate::ported::zle::zle_keymap::reselectkeymap();
{
let kn = crate::ported::zle::zle_keymap::curkeymapname().clone();
let vis =
if crate::ported::zle::zle_h::invicmdmode(&kn) && REGION_ACTIVE.load(SeqCst) != 0 {
crate::ported::zle::zle_keymap::openkeymap("visual")
} else {
None
};
crate::ported::zle::zle_keymap::selectlocalmap(vis);
}
let thingy = crate::ported::zle::zle_keymap::getkeycmd();
crate::ported::zle::zle_keymap::selectlocalmap(None);
let thingy = match thingy {
Some(t) => t,
None => {
EOFSENT.store(1, SeqCst);
DONE.store(1, SeqCst);
continue;
}
};
if ZLELL.load(SeqCst) == 0
&& LASTCHAR.load(SeqCst) == EOFCHAR.load(SeqCst)
&& (ZLEREADFLAGS.load(SeqCst) & ZLRF_HISTORY) != 0
{
EOFSENT.store(1, SeqCst);
DONE.store(1, SeqCst);
continue;
}
*LBINDK.lock().unwrap() = BINDK.lock().unwrap().take();
*BINDK.lock().unwrap() = Some(thingy.clone());
let current_widget = crate::ported::zle::zle_thingy::thingytab()
.lock()
.ok()
.and_then(|t| t.get(&thingy.nam).and_then(|th| th.widget.clone()))
.or_else(|| thingy.widget.clone());
if thingy.nam.contains("complete") {
tracing::debug!(
target: "compsys_args",
name = %thingy.nam,
variant = current_widget.as_ref().map(|w| match &w.u {
crate::ported::zle::zle_h::WidgetImpl::Comp { .. } => "Comp",
crate::ported::zle::zle_h::WidgetImpl::Internal(_) => "Internal",
_ => "Other",
}),
"zlecore widget resolution"
);
}
if crate::zle_fx::on_pre_widget(&thingy.nam) {
} else if let Some(widget) = ¤t_widget {
if execute_widget(widget) != 0 {
handle_feep();
}
} else {
handle_feep();
}
handleprefixes();
if in_vi_cmd_mode()
&& ZLECS.load(SeqCst) > findbol()
&& (ZLECS.load(SeqCst) == ZLELL.load(SeqCst)
|| ZLELINE.lock().unwrap().get(ZLECS.load(SeqCst)).copied() == Some('\n'))
&& ZLECS.load(SeqCst) > 0
{
ZLECS.fetch_sub(1, SeqCst);
}
crate::ported::zle::zle_utils::handleundo(); crate::zle_fx::on_post_widget(&thingy.nam);
crate::p10k::on_zle_redraw();
redrawhook();
if KUNGETBUF.lock().unwrap().is_empty() {
zrefresh();
}
ZLE_RESET_NEEDED.store(0, SeqCst);
}
}
pub fn zleread(
lprompt: &str,
rprompt: &str,
flags: i32,
context: i32,
init: &str,
finish: &str,
) -> io::Result<String> {
ZLE_MODULE_SETUP.call_once(|| {
let _ = setup_(std::ptr::null());
});
let tmout = crate::ported::params::getiparam("TMOUT") as i32; *RAW_LP.lock().unwrap() = lprompt.to_string();
*RAW_RP.lock().unwrap() = rprompt.to_string();
KEYTIMEOUT.store(
crate::ported::params::getiparam("KEYTIMEOUT").max(0) as u64,
SeqCst,
);
if crate::ported::params::TERMFLAGS.load(SeqCst) & crate::ported::zsh_h::TERM_UNKNOWN != 0 {
crate::ported::init::init_term();
}
INSMODE.store(
(!crate::ported::zsh_h::isset(crate::ported::zsh_h::OVERSTRIKE)) as i32,
SeqCst,
);
*crate::ported::prompt::current_attrs_lock().lock().unwrap() = 0;
crate::ported::prompt::set_pending_text_attrs(0);
crate::ported::prompt::txtunknownattrs.store(0, SeqCst);
*LPROMPT.lock().unwrap() = crate::prompt::expand_prompt(lprompt);
*RPROMPT.lock().unwrap() = crate::prompt::expand_prompt(rprompt);
crate::ported::zle::zle_refresh::LAST_PAINT_ROWS.store(0, Ordering::Relaxed);
ZLEREADFLAGS.store(flags, SeqCst);
ZLECONTEXT.store(context, SeqCst);
ZLELINE.lock().unwrap().clear();
ZLECS.store(0, SeqCst);
ZLELL.store(0, SeqCst);
MARK.store(0, SeqCst);
DONE.store(0, SeqCst);
EOFSENT.store(0, SeqCst); crate::ported::zle::zle_hist::histline
.store(crate::ported::hist::curhist.load(SeqCst) as i32, SeqCst);
crate::ported::zle::zle_vi::VIRANGEFLAG.store(0, SeqCst); crate::ported::zle::zle_vi::VICHGFLAG.store(0, SeqCst); crate::ported::zle::zle_vi::VIINREPEAT.store(0, SeqCst);
selectkeymap("main", 1);
crate::ported::zle::zle_utils::initundo();
let stacked = {
let mut bs = BUFSTACK.lock().unwrap();
if bs.is_empty() {
None
} else {
Some(bs.remove(0)) }
};
if let Some(s) = stacked {
crate::ported::zle::zle_utils::setline(&s, crate::ported::zle::zle_h::ZSL_TOEND); let stackcs = STACKCS.load(SeqCst);
if stackcs != -1 {
STACKCS.store(-1, SeqCst); let zlell = ZLELL.load(SeqCst);
ZLECS.store((stackcs as usize).min(zlell), SeqCst); ZLE_RESET_NEEDED.store(1, SeqCst); }
let stackhist = STACKHIST.load(SeqCst);
if stackhist != -1 {
crate::ported::zle::zle_hist::histline.store(stackhist, SeqCst); STACKHIST.store(-1, SeqCst); }
crate::ported::zle::zle_utils::handleundo(); }
{
use crate::ported::zle::zle_keymap::openkeymap;
let main = openkeymap("main");
if main.is_some()
&& match (&main, &openkeymap("viins")) {
(Some(a), Some(b)) => std::sync::Arc::ptr_eq(a, b),
_ => false,
}
{
crate::ported::zle::zle_vi::viinsert_init(); }
}
if (flags & crate::ported::zsh_h::ZLRF_HISTORY) != 0 {
let first = crate::ported::hist::firsthist();
let cur = crate::ported::hist::curhist.load(SeqCst);
let mut h = history().lock().unwrap();
let last_first = ZLE_HIST_SYNC_FIRST.load(SeqCst);
let last_cur = ZLE_HIST_SYNC_CUR.load(SeqCst);
let synced =
last_cur >= 0 && !h.entries.is_empty() && first >= last_first && cur >= last_cur;
let (start_ev, full) = if synced {
if first > last_first {
let cut = h.entries.partition_point(|e| e.num < first);
h.entries.drain(..cut);
}
(last_cur + 1, false)
} else {
(first, true)
};
if full {
h.entries.clear();
let ring = crate::ported::hist::hist_ring.lock().unwrap();
h.entries.reserve(ring.len());
let mut items: Vec<(i64, String)> = ring
.iter()
.filter(|he| he.histnum >= first && he.histnum <= cur && !he.node.nam.is_empty())
.map(|he| (he.histnum, he.node.nam.clone()))
.collect();
drop(ring);
items.sort_unstable_by_key(|(n, _)| *n);
for (num, line) in items {
h.entries.push(crate::ported::zle::zle_hist::HistEntry {
line,
num,
time: None,
});
}
} else {
let mut ev = start_ev;
while ev <= cur {
if let Some(he) = crate::ported::hist::quietgethist(ev) {
let t = he.node.nam;
if !t.is_empty() && h.entries.last().map_or(true, |e| e.num < ev) {
h.entries.push(crate::ported::zle::zle_hist::HistEntry {
line: t,
num: ev,
time: None,
});
}
}
ev += 1;
}
}
ZLE_HIST_SYNC_FIRST.store(first, SeqCst);
ZLE_HIST_SYNC_CUR.store(cur - 1, SeqCst);
h.cursor = h.entries.len(); h.saved_line = None;
}
zsetterm()?;
if tmout != 0 {
unsafe {
libc::alarm(tmout as libc::c_uint); }
}
zleactive.store(1, SeqCst);
crate::ported::zle::zle_refresh::RESETNEEDED.store(1, SeqCst);
crate::utils::errflag.store(0, SeqCst); crate::ported::builtin::RETFLAG.store(0, SeqCst); LASTCOL.store(-1, SeqCst); initmodifier(); PREFIXFLAG.store(0, SeqCst); REGION_ACTIVE.store(0, SeqCst);
if crate::ported::zle::zle_thingy::rthingy_nocreate(init) {
let _ = execzlefunc(init, &[init.to_string()], 1, 0);
}
crate::ported::zle::termquery::start_edit();
zrefresh();
crate::startup_trace::mark_first_prompt();
zlecore();
if crate::utils::errflag.load(SeqCst) != 0 {
let mut ll = 0i32;
let mut cs = 0i32;
let line = crate::ported::zle::zle_utils::zlegetline(&mut ll, &mut cs);
let name = if ZLECONTEXT.load(SeqCst) == crate::ported::zsh_h::ZLCON_VARED {
"ZLE_VARED_ABORTED"
} else {
"ZLE_LINE_ABORTED"
};
let _ = crate::ported::params::setsparam(name, &line);
}
crate::ported::zle::termquery::end_edit();
let stryke_line = {
let z = ZLELINE.lock().unwrap();
z.first() == Some(&'@') && z.get(1).map_or(true, |c| c.is_whitespace())
};
if DONE.load(SeqCst) != 0
&& !stryke_line
&& crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed) == 0
&& crate::utils::errflag.load(SeqCst) == 0
&& crate::ported::zle::zle_thingy::rthingy_nocreate(finish)
{
let _ = execzlefunc(finish, &[finish.to_string()], 1, 0);
}
if let Some((tp, trp)) = crate::p10k::transient::transient_swap_for_accept() {
*LPROMPT.lock().unwrap() = crate::prompt::expand_prompt(&tp);
*RPROMPT.lock().unwrap() = crate::prompt::expand_prompt(&trp);
}
*STATUSLINE.lock().unwrap() = None; crate::ported::zle::zle_h::invalidatelist(); trashzle();
zleactive.store(0, SeqCst);
unsafe {
libc::alarm(0); }
crate::ported::zle::zle_utils::freeundo();
crate::zle_fx::on_line_finish();
let is_eof = EOFSENT.load(SeqCst) != 0
|| errflag.load(SeqCst) != 0
|| crate::ported::builtin::EXIT_PENDING.load(SeqCst) != 0;
if is_eof {
Ok(String::new()) } else {
let mut s: String = ZLELINE.lock().unwrap().iter().collect();
s.push('\n'); Ok(s) }
}
pub fn execimmortal(func: &str, args: &[String]) -> i32 {
let dotted = format!(".{}", func);
if rthingy_nocreate(&dotted) {
return execzlefunc(&dotted, args, 0, 0);
}
1 }
pub fn execzlefunc(name: &str, args: &[String], set_bindk: i32, set_lbindk: i32) -> i32 {
if !rthingy_nocreate(name) {
if getshfunc(name).is_none() {
return 1;
}
}
let nestedvichg = crate::ported::zle::zle_vi::VICHGFLAG.load(SeqCst);
let isrepeat = crate::ported::zle::zle_vi::VIINREPEAT.load(SeqCst) == 3;
if isrepeat {
crate::ported::zle::zle_vi::VIINREPEAT.store(2, SeqCst);
}
let save_bindk = BINDK.lock().ok().and_then(|b| b.clone());
let save_lbindk = LBINDK.lock().ok().and_then(|b| b.clone());
if set_bindk != 0 {
let t = crate::ported::zle::zle_thingy::thingytab()
.lock()
.ok()
.and_then(|tab| tab.get(name).cloned());
if let Some(t) = t {
*BINDK.lock().unwrap() = Some(t); }
}
let set_lastbindk = |r: bool| {
if set_lbindk != 0 {
*LBINDK.lock().unwrap() = save_lbindk.clone(); } else if r {
let func = crate::ported::zle::zle_thingy::thingytab()
.lock()
.ok()
.and_then(|tab| tab.get(name).cloned());
if func.is_some() {
*LBINDK.lock().unwrap() = func; }
}
};
let widget_opt = crate::ported::zle::zle_thingy::thingytab()
.lock()
.ok()
.and_then(|tab| tab.get(name).and_then(|t| t.widget.clone()));
if let Some(w) = widget_opt.as_ref() {
let wflags = w.flags;
if (wflags
& (crate::ported::zle::zle_h::WIDGET_INT | crate::ported::zle::zle_h::WIDGET_NCOMP))
!= 0
{
if (wflags & crate::ported::zle::zle_h::ZLE_KEEPSUFFIX) == 0 {
let _ = crate::ported::zle::zle_h::removesuffix(); }
if (wflags & crate::ported::zle::zle_h::ZLE_MENUCMP) == 0 {
crate::ported::zle::zle_misc::fixsuffix(); crate::ported::zle::zle_h::invalidatelist(); }
if (wflags & crate::ported::zle::zle_h::ZLE_LINEMOVE) != 0 {
crate::ported::zle::zle_vi::VILINERANGE.store(1, SeqCst); }
if (wflags & ZLE_LASTCOL) == 0 {
LASTCOL.store(-1, SeqCst); }
let rc = if (wflags & crate::ported::zle::zle_h::WIDGET_NCOMP) != 0 {
*COMPWIDGET.lock().unwrap() = Some((**w).clone()); let r = crate::ported::zle::zle_tricky::completecall(args); *COMPWIDGET.lock().unwrap() = None;
r
} else {
match &w.u {
crate::ported::zle::zle_h::WidgetImpl::Internal(f) => f(args), _ => 0,
}
};
if (wflags & ZLE_NOTCOMMAND) == 0 {
LASTCMD.store(wflags as u32, SeqCst); }
LASTVAL.store(rc, Ordering::Relaxed);
set_lastbindk(true); if set_bindk != 0 {
*BINDK.lock().unwrap() = save_bindk; }
crate::zle_param_sync::end_vichg_frame(nestedvichg, isrepeat, rc); return rc;
}
}
let shfunc_name = widget_opt.as_ref().and_then(|w| match &w.u {
crate::ported::zle::zle_h::WidgetImpl::UserFunc(s) => Some(s.clone()),
_ => None,
});
let call_name = shfunc_name.as_deref().unwrap_or(name);
if let Some(mut shf) = getshfunc(call_name) {
let osc = crate::ported::exec::sfcontext.load(Ordering::Relaxed);
let outer_widget_flags =
crate::ported::zle::zle_thingy::USER_WIDGET_FLAGS.swap(0, Ordering::SeqCst);
let osi = crate::ported::utils::movefd(0);
if osi > 0 {
unsafe {
let devnull = std::ffi::CString::new("/dev/null").unwrap();
let _ = libc::open(devnull.as_ptr(), libc::O_RDWR | libc::O_NOCTTY);
}
}
let oxt = crate::ported::zsh_h::isset(crate::ported::zsh_h::XTRACE);
let mut largs: Vec<String> = vec![call_name.to_string()];
largs.extend(args.iter().cloned());
let name_for_body = call_name.to_string();
let body_args: Vec<String> = args.to_vec();
let body_runner = move || -> i32 {
let ret =
crate::ported::exec::run_function_body(&name_for_body, &body_args).unwrap_or(0);
crate::zle_param_sync::sync_from_paramtab();
crate::zle_param_sync::clear_snapshot();
ret
};
let mut local_scope: crate::ported::zsh_h::HashTable =
Box::new(crate::ported::zsh_h::hashtable {
hsize: 0,
ct: 0,
nodes: Vec::new(),
tmpdata: 0,
parnodes: Default::default(),
hash: None,
emptytable: None,
filltable: None,
cmpnodes: None,
addnode: None,
getnode: None,
getnode2: None,
removenode: None,
disablenode: None,
enablenode: None,
freenode: None,
printnode: None,
scantab: None,
});
crate::ported::params::startparamscope(&mut local_scope); makezleparams(0); crate::ported::exec::sfcontext.store(crate::ported::zsh_h::SFC_WIDGET, Ordering::Relaxed); crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::XTRACE),
false,
); let rc = crate::ported::exec::doshfunc(&mut shf, largs, true, body_runner); crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::XTRACE),
oxt,
); crate::ported::exec::sfcontext.store(osc, Ordering::Relaxed); crate::zle_param_sync::clear_snapshot();
crate::ported::params::endparamscope(); let uflags = crate::ported::zle::zle_thingy::USER_WIDGET_FLAGS
.swap(outer_widget_flags, Ordering::SeqCst);
let wf = widget_opt.as_ref().map_or(0, |w| w.flags);
LASTCMD.store(
((wf | uflags) & !(crate::ported::zle::zle_h::WIDGET_INUSE
| crate::ported::zle::zle_h::WIDGET_FREE)) as u32,
SeqCst,
);
LASTVAL.store(rc, Ordering::Relaxed);
set_lastbindk(true); if set_bindk != 0 {
*BINDK.lock().unwrap() = save_bindk;
}
let _ = crate::ported::utils::redup(osi, 0); crate::zle_param_sync::end_vichg_frame(nestedvichg, isrepeat, rc); return rc;
}
set_lastbindk(false); if set_bindk != 0 {
*BINDK.lock().unwrap() = save_bindk; }
crate::zle_param_sync::end_vichg_frame(nestedvichg, isrepeat, 0); 0
}
pub fn initmodifier() {
*ZMOD.lock().unwrap() = modifier {
flags: 0,
mult: 1,
tmult: 1,
vibuf: 0,
base: 10,
};
}
pub fn handleprefixes() {
if (PREFIXFLAG.load(SeqCst) != 0) {
PREFIXFLAG.store(0, SeqCst);
if ZMOD.lock().unwrap().flags & MOD_TMULT != 0 {
ZMOD.lock().unwrap().flags |= MOD_MULT; let mut __g_zmod = ZMOD.lock().unwrap();
__g_zmod.mult = __g_zmod.tmult;
}
} else {
initmodifier();
}
}
pub fn savekeymap(
cmdname: &str,
oldname: &str,
newname: &str,
savemapptr: &mut Option<std::sync::Arc<Keymap>>,
) -> i32 {
let km = openkeymap(newname);
if let Some(km) = km {
*savemapptr = openkeymap(oldname);
if savemapptr
.as_ref()
.map(|s| std::sync::Arc::ptr_eq(s, &km))
.unwrap_or(false)
{
*savemapptr = None; } else {
linkkeymap(km, oldname, 0); }
0 } else {
zwarnnam(cmdname, &format!("no such keymap: {}", newname)); 1 }
}
pub fn restorekeymap(
cmdname: &str,
oldname: &str,
newname: Option<&str>,
savemap: Option<std::sync::Arc<Keymap>>,
) {
if let Some(savemap) = savemap {
linkkeymap(savemap, oldname, 0);
} else if newname.is_some() {
zwarnnam(
cmdname,
&format!("keymap {} was not defined, not restored", oldname),
); }
}
pub fn bin_vared(
name: &str,
args: &[String], ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
use crate::ported::zsh_h::{
value, ASSPM_WARN, ERRFLAG_ERROR, PM_TYPE, SCANPM_MATCHMANY, SCANPM_WANTKEYS,
SCANPM_WANTVALS, USEZLE, ZLCON_VARED,
};
use crate::ported::ztype_h::{ISEP, WC_ZISTYPE};
let ova: Option<String> = varedarg.lock().unwrap().clone(); let mut type_: u32 = PM_SCALAR; let obreaks = crate::ported::builtin::BREAKS.load(SeqCst); let mut haso = false; let mut oshtty: i32 = 0; let mut oshout: usize = 0; let mut main_keymapsave: Option<std::sync::Arc<Keymap>> = None; let mut vicmd_keymapsave: Option<std::sync::Arc<Keymap>> = None;
let term = getsparam("TERM").unwrap_or_default();
if (crate::ported::zsh_h::interact() && crate::ported::zsh_h::unset(USEZLE)) || term == "emacs"
{
zwarnnam(name, "ZLE not enabled"); return 1; }
if zleactive.load(Ordering::Relaxed) != 0 {
zwarnnam(name, "ZLE cannot be used recursively (yet)"); return 1; }
let warn_flags = if OPT_ISSET(ops, b'g') { 0 } else { ASSPM_WARN }; if OPT_ISSET(ops, b'A') {
if OPT_ISSET(ops, b'a') {
zwarnnam(name, "specify only one of -a and -A"); return 1; }
type_ = PM_HASHED; } else if OPT_ISSET(ops, b'a') {
type_ = PM_ARRAY; }
let p1: &str = OPT_ARG_SAFE(ops, b'p').unwrap_or(""); let p2: &str = OPT_ARG_SAFE(ops, b'r').unwrap_or(""); let mut main_keymapname = OPT_ARG_SAFE(ops, b'M'); let mut vicmd_keymapname = OPT_ARG_SAFE(ops, b'm'); let init = OPT_ARG_SAFE(ops, b'i'); let finish = OPT_ARG_SAFE(ops, b'f');
if type_ != PM_SCALAR && !OPT_ISSET(ops, b'c') {
zwarnnam(
name,
&format!("-{} ignored", if type_ == PM_ARRAY { "a" } else { "A" }),
); }
if args.is_empty() {
zwarnnam(name, "not enough arguments");
return 1;
}
let mut vbuf = value {
pm: None,
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let mut cursor: &str = &args[0]; let s: String;
crate::ported::mem::queue_signals(); let v = crate::ported::params::fetchvalue(
Some(&mut vbuf),
&mut cursor,
(!OPT_ISSET(ops, b'c') || type_ == PM_SCALAR) as i32,
(SCANPM_WANTKEYS | SCANPM_WANTVALS | SCANPM_MATCHMANY) as i32,
); if v.is_none() && !OPT_ISSET(ops, b'c') {
unqueue_signals(); zwarnnam(name, &format!("no such variable: {}", args[0])); return 1; } else if let Some(v) = v {
if !cursor.is_empty() {
unqueue_signals(); zwarnnam(name, &format!("not an identifier: `{}'", args[0])); return 1; }
if v.scanflags != 0 {
let arr = crate::ported::params::getvaluearr(Some(&mut *v)); let mut tmparr: Vec<String> = Vec::with_capacity(arr.len()); for aptr in arr.iter() {
let mut sepcount = 0;
for c in aptr.chars() {
if c == '\\' || WC_ZISTYPE(c, ISEP as u32) {
sepcount += 1; }
}
if sepcount != 0 {
let mut newstr = String::with_capacity(aptr.len() + sepcount + 1);
for c in aptr.chars() {
if c == '\\' {
newstr.push('\\'); newstr.push(c); } else {
if WC_ZISTYPE(c, ISEP as u32) {
newstr.push('\\'); }
newstr.push(c); }
}
tmparr.push(newstr); } else {
tmparr.push(aptr.clone());
}
}
s = crate::ported::utils::sepjoin(&tmparr, None); } else {
s = crate::ported::params::getstrvalue(Some(&mut *v)); }
unqueue_signals(); } else if !cursor.is_empty() {
unqueue_signals(); zwarnnam(name, &format!("invalid parameter name: {}", args[0])); return 1; } else {
unqueue_signals(); s = String::new(); }
if SHTTY.load(SeqCst) == -1 || OPT_ISSET(ops, b't') {
oshtty = SHTTY.load(SeqCst); let tpath = OPT_ARG_SAFE(ops, b't').unwrap_or("/dev/tty");
let ctpath = std::ffi::CString::new(tpath).unwrap_or_default();
let fd = unsafe { libc::open(ctpath.as_ptr(), libc::O_RDWR | libc::O_NOCTTY) };
SHTTY.store(fd, SeqCst); if fd == -1 {
zwarnnam(name, "can't access terminal"); return 1; }
if unsafe { libc::isatty(SHTTY.load(SeqCst)) } == 0 {
zwarnnam(name, &format!("{}: not a terminal", tpath)); unsafe {
libc::close(SHTTY.load(SeqCst)); }
SHTTY.store(oshtty, SeqCst); return 1; }
oshout = *crate::ported::init::shout.lock().unwrap(); crate::ported::init::init_shout();
haso = true; }
BUFSTACK.lock().unwrap().insert(0, s);
if let Some(n) = main_keymapname {
if savekeymap(name, "main", n, &mut main_keymapsave) != 0 {
main_keymapname = None; }
}
if let Some(n) = vicmd_keymapname {
if savekeymap(name, "vicmd", n, &mut vicmd_keymapsave) != 0 {
vicmd_keymapname = None; }
}
*varedarg.lock().unwrap() = Some(args[0].clone()); let ifl = crate::ported::lex::LEX_ISFIRSTLN.with(|c| c.get()); if OPT_ISSET(ops, b'h') {
crate::ported::hist::hbegin(2); }
crate::ported::lex::LEX_ISFIRSTLN.with(|c| c.set(OPT_ISSET(ops, b'e')));
let t: Option<String> = match zleread(
p1,
p2,
if OPT_ISSET(ops, b'h') { ZLRF_HISTORY } else { 0 },
ZLCON_VARED,
init.unwrap_or("zle-line-init"),
finish.unwrap_or("zle-line-finish"),
) {
Ok(line) if !line.is_empty() => Some(line),
_ => None,
};
if OPT_ISSET(ops, b'h') {
crate::ported::hist::hend(None); }
crate::ported::lex::LEX_ISFIRSTLN.with(|c| c.set(ifl)); *varedarg.lock().unwrap() = ova;
restorekeymap(name, "main", main_keymapname, main_keymapsave); restorekeymap(name, "vicmd", vicmd_keymapname, vicmd_keymapsave);
if haso {
unsafe {
libc::close(SHTTY.load(SeqCst)); }
*crate::ported::init::shout.lock().unwrap() = oshout; SHTTY.store(oshtty, SeqCst); }
if t.is_none() || errflag.load(SeqCst) != 0 {
errflag.fetch_and(!ERRFLAG_ERROR, SeqCst); crate::ported::builtin::BREAKS.store(obreaks, SeqCst); return 1; }
let mut t = t.unwrap_or_default();
if t.ends_with('\n') {
t.pop(); }
if OPT_ISSET(ops, b'c') {
crate::ported::params::unsetparam(&args[0]); crate::ported::params::createparam(&args[0], type_ as i32); }
crate::ported::mem::queue_signals(); let pm_type = crate::ported::params::paramtab()
.read()
.unwrap()
.get(&args[0])
.map(|pm| PM_TYPE(pm.node.flags as u32)); if let Some(pt) = pm_type.filter(|pt| (pt & (PM_ARRAY | PM_HASHED)) != 0) {
let a: Vec<String> = crate::ported::utils::spacesplit(&t, true, true); if pt == PM_ARRAY {
crate::ported::params::assignaparam(&args[0], a, warn_flags); } else {
crate::ported::params::sethparam(&args[0], a); }
} else {
crate::ported::params::assignsparam(&args[0], &t, warn_flags); }
unqueue_signals(); 0 }
pub fn describekeybriefly() -> i32 {
describe_key_briefly() }
pub const MAXFOUND: usize = 4;
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct findfunc {
pub func: Option<String>, pub found: usize, pub msg: String, }
pub fn scanfindfunc(seq: &[u8], func: Option<&Thingy>, ff: &mut findfunc) {
if func.map(|t| t.nam.as_str()) != ff.func.as_deref() {
return;
}
if ff.found == 0 {
ff.msg.push_str(" is on");
}
ff.found += 1;
if ff.found <= MAXFOUND {
let b = crate::ported::zle::zle_utils::bindztrdup(seq); ff.msg.push(' '); ff.msg.push_str(&b); }
}
pub fn whereis() -> i32 {
let mut ff = findfunc::default(); let Some(name) = crate::ported::zle::zle_misc::executenamedcommand("Where is: ") else {
return 1; };
ff.found = 0; ff.msg = crate::ported::utils::nicedup(&name, 0); ff.func = Some(name);
let km_name = curkeymapname().clone();
if let Some(km) = openkeymap(&km_name) {
crate::ported::zle::zle_keymap::scankeymap(&km, 1, &mut |seq, func, _str| {
scanfindfunc(seq, func, &mut ff)
});
}
if ff.found == 0 {
ff.msg.push_str(" is not bound to any key"); } else if ff.found > MAXFOUND {
ff.msg.push_str(" et al"); }
crate::ported::zle::zle_utils::showmsg(&ff.msg); 0 }
pub fn recursiveedit() -> i32 {
ZLE_RECURSIVE.fetch_add(1, SeqCst); redrawhook(); crate::ported::zle::zle_refresh::zrefresh(); zlecore(); ZLE_RECURSIVE.fetch_sub(1, SeqCst); let cur_errflag = errflag.load(Ordering::Relaxed);
let locerror = if cur_errflag != 0 { 1 } else { 0 }; errflag.store(0, Ordering::Relaxed); DONE.store(0, SeqCst); locerror }
pub fn reexpandprompt() {
thread_local! {
static REEXPANDING: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static LOOPING: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
let prev = REEXPANDING.with(|c| {
let v = c.get();
c.set(v + 1);
v
});
if prev == 0 {
let local_lastval = LASTVAL.load(SeqCst);
let pre_zs = PRE_ZLE_STATUS.load(SeqCst);
LASTVAL.store(pre_zs, SeqCst);
if ZLECONTEXT.load(SeqCst) == crate::ported::zsh_h::ZLCON_LINE_START {
let first = crate::ported::lex::LEX_ISFIRSTLN.with(|c| c.get());
let (ps, rp_primary, rp_legacy) = if first {
("PS1", "RPS1", "RPROMPT")
} else {
("PS2", "RPS2", "RPROMPT2")
};
if let Some(lp) = crate::ported::params::getsparam(ps) {
*RAW_LP.lock().unwrap() = lp;
}
let rp = match crate::ported::params::getsparam(rp_primary) {
Some(s) if !s.is_empty() => Some(s),
_ => crate::ported::params::getsparam(rp_legacy),
};
*RAW_RP.lock().unwrap() = rp.unwrap_or_default();
}
loop {
let reexp = REEXPANDING.with(|c| c.get());
LOOPING.with(|c| c.set(reexp));
*crate::ported::prompt::current_attrs_lock().lock().unwrap() = 0;
*crate::ported::prompt::pending_attrs_lock().lock().unwrap() = 0;
crate::ported::prompt::txtunknownattrs.store(0, Ordering::Relaxed);
let raw_lp = RAW_LP.lock().unwrap().clone();
let new_lp = crate::prompt::expand_prompt(&raw_lp);
crate::ported::zle::zle_refresh::PMPT_ATTR.store(
*crate::ported::prompt::current_attrs_lock().lock().unwrap(),
Ordering::SeqCst,
);
*LPROMPT.lock().unwrap() = new_lp;
if LOOPING.with(|c| c.get()) != REEXPANDING.with(|c| c.get()) {
continue;
}
let raw_rp = RAW_RP.lock().unwrap().clone();
let new_rp = crate::prompt::expand_prompt(&raw_rp);
crate::ported::zle::zle_refresh::RPMPT_ATTR.store(
*crate::ported::prompt::current_attrs_lock().lock().unwrap(),
Ordering::SeqCst,
);
*RPROMPT.lock().unwrap() = new_rp;
if LOOPING.with(|c| c.get()) == REEXPANDING.with(|c| c.get()) {
break;
}
}
LASTVAL.store(local_lastval, SeqCst);
ZLE_RESET_NEEDED.store(1, SeqCst);
} else {
let reexp = REEXPANDING.with(|c| c.get());
LOOPING.with(|c| c.set(reexp));
}
REEXPANDING.with(|c| c.set(c.get() - 1));
}
pub fn resetprompt() {
ZLE_RESET_NEEDED.store(1, SeqCst);
CLEARFLAG.store(1, SeqCst);
}
pub fn zle_resetprompt() {
reexpandprompt(); ZLE_RESET_NEEDED.store(1, SeqCst);
if zleactive.load(Ordering::Relaxed) != 0 {
crate::ported::zle::zle_refresh::redisplay(); SHOWINGLIST.store(-2, SeqCst);
}
}
pub fn trashzle() {
use crate::ported::prompt::{applytextattributes, treplaceattrs};
use crate::ported::utils::errflag;
use crate::ported::zle::zle_refresh::{
moveto, tcout, NLNCT, PROMPT_ATTR, SHOWINGLIST, TRASHEDZLE,
};
use crate::ported::zsh_h::{TCCLEAREOD, ZLRF_NOSETTY};
if zleactive.load(Ordering::Relaxed) != 0 && TRASHEDZLE.load(Ordering::Relaxed) == 0 {
let sl = SHOWINGLIST.load(Ordering::Relaxed); SHOWINGLIST.store(0, Ordering::Relaxed); TRASHEDZLE.store(1, Ordering::Relaxed); crate::ported::zle::zle_refresh::zrefresh(); SHOWINGLIST.store(sl, Ordering::Relaxed);
treplaceattrs(PROMPT_ATTR.load(Ordering::Relaxed));
crate::shout::write(applytextattributes(0).as_bytes());
moveto(NLNCT.load(Ordering::Relaxed) as usize, 0);
if crate::ported::zle::zle_refresh::CLEARFLAG.load(Ordering::Relaxed) != 0 {
tcout(TCCLEAREOD); crate::ported::zle::zle_refresh::CLEARFLAG.store(0, Ordering::Relaxed);
crate::ported::zle::zle_refresh::LISTSHOWN.store(0, Ordering::Relaxed);
}
let postedit = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get("POSTEDIT").and_then(|p| p.u_str.clone()))
.unwrap_or_default();
if !postedit.is_empty() {
let fd = SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out, postedit.as_bytes());
}
crate::shout::flush();
crate::ported::zle::zle_refresh::RESETNEEDED.store(1, SeqCst);
ZLE_RESET_NEEDED.store(1, SeqCst);
if (ZLEREADFLAGS.load(Ordering::Relaxed) & ZLRF_NOSETTY) == 0 {
if let Some(ti) = crate::ported::utils::SHTTYINFO.lock().ok().and_then(|g| *g) {
crate::ported::utils::settyinfo(&ti);
}
}
}
if errflag.load(Ordering::Relaxed) != 0 {
KUNGETCT.store(0, Ordering::Relaxed);
}
}
pub fn zlebeforetrap(
_dummy: *mut hookdef,
_dat: *mut std::ffi::c_void,
) -> i32 {
use std::sync::atomic::Ordering;
if zleactive.load(Ordering::Relaxed) != 0 {
let mut local_scope: HashTable = Box::new(hashtable {
hsize: 0,
ct: 0,
nodes: Vec::new(),
tmpdata: 0,
parnodes: Default::default(),
hash: None,
emptytable: None,
filltable: None,
cmpnodes: None,
addnode: None,
getnode: None,
getnode2: None,
removenode: None,
disablenode: None,
enablenode: None,
freenode: None,
printnode: None,
scantab: None,
});
crate::ported::params::startparamscope(&mut local_scope);
makezleparams(1);
}
0 }
pub fn zleaftertrap(
_dummy: *mut hookdef,
_dat: *mut std::ffi::c_void,
) -> i32 {
use std::sync::atomic::Ordering;
if zleactive.load(Ordering::Relaxed) != 0 {
crate::ported::params::endparamscope(); }
0 }
#[allow(unused_variables)]
pub(crate) static ZLE_MODULE_SETUP: std::sync::Once = std::sync::Once::new();
pub fn setup_(m: *const module) -> i32 {
crate::ported::zle::zle_thingy::init_thingies();
if *crate::ported::init::shout.lock().unwrap() != 0 {
crate::ported::zle::termquery::query_terminal();
}
let bpaste = vec![
"\u{1b}[?2004h".to_string(), "\u{1b}[?2004l".to_string(), ];
let _ = crate::ported::params::assignaparam("zle_bracketed_paste", bpaste, 0); 0 }
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
let _ = m;
features.clear();
features.extend([
"b:bindkey".to_string(), "b:vared".to_string(), "b:zle".to_string(), ]);
0 }
#[allow(unused_variables)]
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
let mut feats: Vec<String> = Vec::new();
features_(m, &mut feats); crate::ported::module::handlefeatures("zsh/zle", &feats, enables)
}
pub fn boot_(_m: *const module) -> i32 {
addhookfunc("before_trap", zlebeforetrap); addhookfunc("after_trap", zleaftertrap);
let comphooks: [(&str, Option<crate::ported::zsh_h::Hookfn>, i32); 5] = [
("insert_match", None, crate::ported::zsh_h::HOOKF_ALL), ("menu_start", None, crate::ported::zsh_h::HOOKF_ALL), ("compctl_make", None, 0), ("compctl_cleanup", None, 0), (
"comp_list_matches", Some(crate::ported::zle::compresult::ilistmatches),
0,
),
];
let zlehooks = [
"list_matches", "complete", "before_complete", "after_complete", "accept_completion", "invalidate_list", ];
let all: Vec<(&str, Option<crate::ported::zsh_h::Hookfn>, i32)> = comphooks
.into_iter()
.chain(zlehooks.into_iter().map(|n| (n, None, 0)))
.collect();
for (name, def, flags) in all {
let h = Box::into_raw(Box::new(hookdef {
next: std::ptr::null_mut(),
name: name.to_string(),
def,
flags,
funcs: std::ptr::null_mut(),
}));
if crate::ported::module::addhookdef(h) != 0 {
unsafe {
drop(Box::from_raw(h));
}
}
}
0 }
pub fn cleanup_(_m: *const module) -> i32 {
if zleactive.load(Ordering::Relaxed) != 0 {
return 1;
}
deletehookfunc("before_trap", zlebeforetrap); deletehookfunc("after_trap", zleaftertrap); if let Ok(mut tab) = keymapnamtab().lock() {
tab.clear();
}
0 }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
free_isrch_spots();
if let Ok(mut ring) = KILLRING.lock() {
ring.clear();
}
if let Ok(mut vb) = vibuf().lock() {
for slot in vb.iter_mut() {
*slot = crate::ported::zle::zle_h::cutbuffer::default();
}
}
0 }
#[doc(hidden)]
pub static ZLE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[doc(hidden)]
pub fn zle_test_setup() -> std::sync::MutexGuard<'static, ()> {
let guard = ZLE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
{
use crate::ported::zle::complete::{
COMPCONTEXT, COMPIPREFIX, COMPISUFFIX, COMPLASTPREFIX, COMPLASTSUFFIX, COMPLIST,
COMPPARAMETER, COMPPATINSERT, COMPPREFIX, COMPQIPREFIX, COMPQISUFFIX, COMPQSTACK,
COMPQUOTE, COMPQUOTING, COMPREDIRECT, COMPSUFFIX, COMPVARED, COMPWORDS,
};
for g in [
&COMPPREFIX,
&COMPSUFFIX,
&COMPLASTPREFIX,
&COMPLASTSUFFIX,
&COMPIPREFIX,
&COMPISUFFIX,
&COMPQIPREFIX,
&COMPQISUFFIX,
&COMPQUOTE,
&COMPQUOTING,
&COMPQSTACK,
&COMPLIST,
&COMPCONTEXT,
&COMPPARAMETER,
&COMPREDIRECT,
&COMPPATINSERT,
&COMPVARED,
] {
if let Some(m) = g.get() {
m.clear_poison();
}
}
if let Some(m) = COMPWORDS.get() {
m.clear_poison();
}
}
zle_reset();
guard
}
pub fn zle_reset() {
crate::ported::zle::zle_keymap::createkeymapnamtab();
crate::ported::zle::zle_keymap::default_bindings();
ZLELINE.lock().unwrap().clear();
ZLECS.store(0, SeqCst);
ZLELL.store(0, SeqCst);
MARK.store(0, SeqCst);
*LBINDK.lock().unwrap() = None;
*BINDK.lock().unwrap() = None;
*ZMOD.lock().unwrap() = modifier {
flags: 0,
mult: 1,
tmult: 1,
vibuf: 0,
base: 10,
};
*STATUSLINE.lock().unwrap() = None;
STACKHIST.store(-1, SeqCst);
STACKCS.store(-1, SeqCst);
VISTARTCHANGE.store(u64::MAX, SeqCst);
UNDO_STACK.lock().unwrap().clear();
CHANGENO.store(0, SeqCst);
KUNGETBUF.lock().unwrap().clear();
BAUD.store(38400, SeqCst);
WATCH_FDS.lock().unwrap().clear();
*COMPWIDGET.lock().unwrap() = None;
HASCOMPMOD.store(false, SeqCst);
TTYFD.store(0, SeqCst);
LPROMPT.lock().unwrap().clear();
RPROMPT.lock().unwrap().clear();
PRE_ZLE_STATUS.store(0, SeqCst);
for slot in vibuf().lock().unwrap().iter_mut() {
*slot = crate::ported::zle::zle_h::cutbuffer::default();
}
KILLRING.lock().unwrap().clear();
KILLRINGMAX.store(8, SeqCst);
*CUTBUF.lock().unwrap() = crate::ported::zle::zle_h::cutbuffer::default();
crate::ported::zle::zle_misc::KCT.store(-1, SeqCst);
crate::ported::zle::zle_misc::KCTBUF_SEL.store(-2, SeqCst);
YANKLAST.store(false, SeqCst);
NEG_ARG.store(false, SeqCst);
MULT.store(1, SeqCst);
*history().lock().unwrap() = History::new(2000);
LASTCOL.store(-1, SeqCst);
BUFSTACK.lock().unwrap().clear();
VICHGBUF.lock().unwrap().clear();
*SRCH_STR.lock().unwrap() = None;
LASTLINE.lock().unwrap().clear();
LASTLL.store(0, SeqCst);
LASTCS.store(0, SeqCst);
CURCHANGE.store(0, SeqCst);
UNDO_CHANGENO.store(0, SeqCst);
UNDO_LIMITNO.store(0, SeqCst);
VIINSBEGIN.store(0, SeqCst);
YANKB.store(0, SeqCst);
YANKE.store(0, SeqCst);
YANKCS.store(0, SeqCst);
*KCT.lock().unwrap() = None;
*vimarks().lock().unwrap() = [None; 27];
REGION_ACTIVE.store(0, SeqCst);
PENDING_HOOKS.lock().unwrap().clear();
RAW_LP.lock().unwrap().clear();
RAW_RP.lock().unwrap().clear();
*highlight().lock().unwrap() = HighlightManager::new();
}
fn try_read_byte(buf: &mut [u8]) -> io::Result<bool> {
let mut fds = [libc::pollfd {
fd: io::stdin().as_raw_fd(),
events: libc::POLLIN,
revents: 0,
}];
let ret = unsafe { libc::poll(fds.as_mut_ptr(), 1, 0) };
if ret > 0 && (fds[0].revents & libc::POLLIN) != 0 {
match io::stdin().read(buf) {
Ok(1) => Ok(true),
Ok(_) => Ok(false),
Err(e) => Err(e),
}
} else {
Ok(false)
}
}
pub enum zle_main_entry_args<'a> {
GetLine { ll: &'a mut i32, cs: &'a mut i32 }, Read {
lp: &'a mut Option<String>,
rp: &'a mut Option<String>,
flags: i32,
context: i32,
}, AddToLine(i32), Trash, ResetPrompt, Refresh, SetKeymap(i32), GetKey {
do_keytmout: i64,
timeout: &'a mut i32,
chrp: &'a mut i32,
}, SetHistLine(i64), Preexec, Postexec, Chpwd, }
pub fn zle_main_entry(cmd: i32, ap: &mut zle_main_entry_args) -> Option<String> {
match cmd {
x if x == ZLE_CMD_GET_LINE => {
if let zle_main_entry_args::GetLine { ll, cs } = ap {
let mut ll_i: i32 = 0;
let mut cs_i: i32 = 0;
let line = zlegetline(&mut ll_i, &mut cs_i); **ll = ll_i;
**cs = cs_i;
return Some(line); }
}
x if x == ZLE_CMD_READ => {
if let zle_main_entry_args::Read {
lp,
rp,
flags,
context,
} = ap
{
let lprompt = lp.as_deref().unwrap_or("");
let rprompt = rp.as_deref().unwrap_or("");
let r = zleread(
lprompt,
rprompt,
*flags,
*context,
"zle-line-init",
"zle-line-finish",
);
return r.ok();
}
}
x if x == ZLE_CMD_ADD_TO_LINE => {
if let zle_main_entry_args::AddToLine(c) = ap {
zleaddtoline(*c); }
}
x if x == ZLE_CMD_TRASH => {
trashzle(); }
x if x == ZLE_CMD_RESET_PROMPT => {
zle_resetprompt(); }
x if x == ZLE_CMD_REFRESH => {
zrefresh(); }
x if x == ZLE_CMD_SET_KEYMAP => {
if let zle_main_entry_args::SetKeymap(m) = ap {
crate::ported::zle::zle_keymap::zlesetkeymap(*m); }
}
x if x == ZLE_CMD_GET_KEY => {
if let zle_main_entry_args::GetKey {
do_keytmout,
timeout: _,
chrp,
} = ap
{
let byte = getbyte(
*do_keytmout,
)
.unwrap_or(0);
**chrp = byte as i32;
}
}
x if x == ZLE_CMD_SET_HIST_LINE => {
if let zle_main_entry_args::SetHistLine(v) = ap {
histline.store(*v as i32, SeqCst);
}
}
x if x == ZLE_CMD_PREEXEC => {
mark_output(true); }
x if x == ZLE_CMD_POSTEXEC => {
mark_output(false); }
x if x == ZLE_CMD_CHPWD => {
crate::ported::zle::termquery::notify_pwd(); }
_ => {
tracing::debug!("Bad command {} in zle_main_entry", cmd);
}
}
None }
mod termios {
pub use libc::{ECHO, ICANON, TCSANOW, VEOF, VMIN, VTIME};
use std::io;
use std::os::unix::io::RawFd;
#[derive(Clone)]
pub struct Termios {
inner: libc::termios,
}
impl Termios {
pub fn from_fd(fd: RawFd) -> io::Result<Self> {
let mut termios = std::mem::MaybeUninit::uninit();
let ret = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
if ret != 0 {
return Err(io::Error::last_os_error());
}
Ok(Termios {
inner: unsafe { termios.assume_init() },
})
}
}
impl std::ops::Deref for Termios {
type Target = libc::termios;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for Termios {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub fn tcsetattr(fd: RawFd, action: i32, termios: &Termios) -> io::Result<()> {
let ret = unsafe { libc::tcsetattr(fd, action, &termios.inner) };
if ret != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
#[cfg(test)]
mod ztmout_findfunc_tests {
use super::*;
#[test]
fn ztmouttp_discriminant_values() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(ztmouttp::ZTM_NONE as i32, 0);
assert_eq!(ztmouttp::ZTM_KEY as i32, 1);
assert_eq!(ztmouttp::ZTM_FUNC as i32, 2);
assert_eq!(ztmouttp::ZTM_MAX as i32, 3);
}
#[test]
fn ztmout_default_carries_none_type() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let t = ztmout {
tp: ztmouttp::ZTM_NONE,
exp100ths: 0,
};
assert_eq!(t.tp, ztmouttp::ZTM_NONE);
}
#[test]
fn findfunc_default_is_empty() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let f = findfunc::default();
assert_eq!(f.func, None);
assert_eq!(f.found, 0);
assert!(f.msg.is_empty());
}
#[test]
fn findfunc_can_accumulate_message() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut f = findfunc {
func: Some("beginning-of-line".to_string()),
found: 0,
msg: String::new(),
};
f.found += 1;
f.msg.push_str(" is on KEY1");
assert_eq!(f.found, 1);
assert!(f.msg.contains("is on"));
}
}
pub fn in_vi_cmd_mode() -> bool {
*curkeymapname() == "vicmd"
}
pub fn get_key_cmd() -> Option<(Option<Thingy>, Option<String>)> {
let local_km = LOCALKEYMAP.lock().unwrap().clone();
let global_km = curkeymap.lock().unwrap().clone();
let km = match (&local_km, &global_km) {
(None, None) => return None,
(Some(l), None) => l.clone(),
(_, Some(g)) => g.clone(),
};
let mut buf: Vec<u8> = Vec::with_capacity(8);
let mut last_match: Option<Thingy> = None;
let mut last_str: Option<String> = None;
let mut matched = false;
let mut last_match_len = 0usize;
let mut lastc = LASTCHAR.load(SeqCst); let mut timeout = false;
let mut csi: usize = 0;
let mut csi_raw: usize = 0;
let mut oscdcs: usize = 0;
let mut oscdcs_raw: usize = 0;
let mut lastlen_meta: usize = 0;
crate::ported::zle::zle_keymap::keybuf
.lock()
.unwrap()
.clear(); crate::ported::zle::zle_keymap::keybuflen.store(0, SeqCst);
loop {
let do_keytmout = timeout;
let b = match getbyte(do_keytmout as i64) {
Some(b) => b,
None => {
if do_keytmout || !buf.is_empty() {
break;
}
return None;
}
};
buf.push(b);
crate::ported::zle::zle_keymap::addkeybuf(b as i32);
crate::ported::zle::zle_keymap::keybuflen.store(
crate::ported::zle::zle_keymap::keybuf.lock().unwrap().len() as i32,
SeqCst,
);
type Bound = (Option<Thingy>, Option<String>);
let lookup = |m: &crate::ported::zle::zle_keymap::Keymap| -> (Option<Bound>, bool) {
if buf.len() == 1 {
if let Some(t) = m.first[b as usize].clone() {
let pfx = m.multi.keys().any(|k| k.len() > 1 && k[0] == b);
return (Some((Some(t), None)), pfx);
}
let entry = m.multi.get(&buf[..]);
let bind = entry.map(|e| (e.bind.clone(), e.str.clone()));
let pfx = m.multi.keys().any(|k| k.len() > 1 && k[0] == b);
(bind, pfx)
} else {
let entry = m.multi.get(&buf[..]);
let bind = entry.map(|e| (e.bind.clone(), e.str.clone()));
let pfx = entry.map(|e| e.prefixct > 0).unwrap_or(false)
|| m.multi
.keys()
.any(|k| k.len() > buf.len() && k.starts_with(&buf[..]));
(bind, pfx)
}
};
let (mut current_match, mut is_prefix) = match (&local_km, &global_km) {
(Some(l), Some(_)) => lookup(l), _ => (None, false),
};
let (km_match, km_pfx) = lookup(&km);
if current_match.is_none() && !is_prefix {
current_match = km_match;
}
is_prefix |= km_pfx;
if let Some((bind, s)) = current_match {
let is_undef = bind
.as_ref()
.map(|t| t.nam == "undefined-key")
.unwrap_or(false);
if !is_undef && (bind.is_some() || s.is_some()) {
let vioper = bind
.as_ref()
.and_then(|t| t.widget.as_ref())
.map(|w| w.flags & crate::ported::zle::zle_h::ZLE_VIOPER != 0)
.unwrap_or(false);
timeout = !(crate::ported::zle::zle_vi::VIRANGEFLAG.load(SeqCst) == 0
&& REGION_ACTIVE.load(SeqCst) == 0
&& vioper);
let selfins = bind
.as_ref()
.map(|t| t.nam == "self-insert" || t.nam == "self-insert-unmeta")
.unwrap_or(false);
last_match = bind; last_str = s; matched = true;
last_match_len = buf.len();
lastlen_meta = crate::ported::zle::zle_keymap::keybuf.lock().unwrap().len();
lastc = LASTCHAR.load(SeqCst); if selfins && LASTCHAR_WIDE_VALID.load(SeqCst) == 0 && !is_prefix {
crate::ported::zle::zle_keymap::getrestchar_keybuf(); let mut raw = crate::ported::zle::zle_keymap::keybuf.lock().unwrap().clone();
lastlen_meta = raw.len();
crate::ported::utils::unmetafy(&mut raw);
buf = raw;
last_match_len = buf.len();
}
}
}
let kb: Vec<u8> = crate::ported::zle::zle_keymap::keybuf.lock().unwrap().clone();
let kl = kb.len();
if csi == 0 && kl >= 3 && kb[kl - 3] == 0x1b && kb[kl - 2] == b'[' {
csi = kl - 1; csi_raw = buf.len() - 1;
}
if csi != 0 {
if kb[kl - 2] == crate::ported::zsh_h::Meta || kb[kl - 1] < 0x20 || kb[kl - 1] > 0x3f {
if kb[kl - 1] >= 0x40
&& kb[kl - 1] <= 0x7e
&& lastlen_meta + 2 > csi
&& lastlen_meta <= csi
{
if kb[csi] == b'?' && (kb[kl - 1] == b'c' || kb[kl - 1] == b'u') {
buf.truncate(csi_raw - 2);
crate::ported::zle::zle_keymap::keybuf.lock().unwrap().truncate(csi - 2); crate::ported::zle::zle_keymap::keybuflen.store((csi - 2) as i32, SeqCst);
timeout = false; csi = 0;
continue; }
last_match = crate::ported::zle::zle_thingy::thingytab()
.lock()
.ok()
.and_then(|t| t.get("undefined-key").cloned())
.or_else(|| Some(Thingy::new("undefined-key"))); last_str = None;
matched = true;
last_match_len = buf.len(); lastlen_meta = kl;
}
csi = 0; }
}
if oscdcs != 0 {
if kb[kl - 1] == 0x07 || (kl >= 2 && kb[kl - 2] == 0x1b && kb[kl - 1] == b'\\')
|| (kl >= 2 && kb[kl - 2] == crate::ported::zsh_h::Meta && kb[kl - 1] == (0x9c ^ 32))
{
buf.truncate(oscdcs_raw - 2);
crate::ported::zle::zle_keymap::keybuf.lock().unwrap().truncate(oscdcs - 2); crate::ported::zle::zle_keymap::keybuflen.store((oscdcs - 2) as i32, SeqCst);
timeout = false; oscdcs = 0;
continue; }
} else if kl >= 2 && kb[kl - 2] == 0x1b && (kb[kl - 1] == b']' || kb[kl - 1] == b'P') {
oscdcs = kl; oscdcs_raw = buf.len();
}
if !is_prefix && csi == 0 && oscdcs == 0 {
break;
}
}
if !(last_match_len == 0 && !buf.is_empty()) {
LASTCHAR.store(lastc, SeqCst); }
if matched && buf.len() > last_match_len {
let extra = buf[last_match_len..].to_vec();
ungetbytes(&extra);
if crate::ported::zle::zle_vi::VICHGFLAG.load(std::sync::atomic::Ordering::SeqCst) != 0 {
let mut cur = crate::ported::zle::zle_vi::CURVICHG.lock().unwrap();
cur.bufptr -= extra.len() as i32;
let keep = cur.bufptr.max(0) as usize;
cur.buf.truncate(keep);
}
buf.truncate(last_match_len);
crate::ported::zle::zle_keymap::keybuf
.lock()
.unwrap()
.clear();
for &kb in &buf {
crate::ported::zle::zle_keymap::addkeybuf(kb as i32);
}
crate::ported::zle::zle_keymap::keybuflen.store(
crate::ported::zle::zle_keymap::keybuf.lock().unwrap().len() as i32,
SeqCst,
); }
if !matched && !buf.is_empty() {
return Some((
crate::ported::zle::zle_thingy::thingytab()
.lock()
.ok()
.and_then(|t| t.get("undefined-key").cloned())
.or_else(|| Some(Thingy::new("undefined-key"))),
None,
));
}
if std::env::var_os("ZSHRS_ZLE_LOG").is_some() {
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("/tmp/zshrs_zle.log")
{
let _ = writeln!(
f,
"key: bytes={:?} matchlen={} widget={:?}",
buf,
last_match_len,
last_match.as_ref().map(|t| t.nam.clone())
);
}
}
if !matched {
return None;
}
Some((last_match, last_str))
}
fn execute_widget(widget: &widget) -> i32 {
let nestedvichg = crate::ported::zle::zle_vi::VICHGFLAG.load(SeqCst);
let isrepeat = crate::ported::zle::zle_vi::VIINREPEAT.load(SeqCst) == 3;
if isrepeat {
crate::ported::zle::zle_vi::VIINREPEAT.store(2, SeqCst);
}
if (widget.flags
& (crate::ported::zle::zle_h::WIDGET_INT | crate::ported::zle::zle_h::WIDGET_NCOMP))
!= 0
{
if (widget.flags & crate::ported::zle::zle_h::ZLE_KEEPSUFFIX) == 0 {
let _ = crate::ported::zle::zle_h::removesuffix(); }
if (widget.flags & crate::ported::zle::zle_h::ZLE_MENUCMP) == 0 {
crate::ported::zle::zle_misc::fixsuffix(); crate::ported::zle::zle_h::invalidatelist(); }
if (widget.flags & crate::ported::zle::zle_h::ZLE_LINEMOVE) != 0 {
crate::ported::zle::zle_vi::VILINERANGE.store(1, SeqCst); }
}
if (widget.flags & ZLE_LASTCOL) == 0 {
LASTCOL.store(-1, SeqCst);
}
let ret = match &widget.u {
WidgetImpl::Comp { .. } => {
*COMPWIDGET.lock().unwrap() = Some(widget.clone());
let r = crate::ported::zle::zle_tricky::completecall(&[]);
*COMPWIDGET.lock().unwrap() = None;
r
}
WidgetImpl::Internal(f) => f(&[]),
WidgetImpl::UserFunc(name) => {
execzlefunc(name, &[], 0, 0)
}
_ => 0,
};
if (widget.flags & ZLE_NOTCOMMAND) == 0
&& !matches!(widget.u, crate::ported::zle::zle_h::WidgetImpl::UserFunc(_))
{
LASTCMD.store(widget.flags as u32, SeqCst);
}
crate::zle_param_sync::end_vichg_frame(nestedvichg, isrepeat, ret);
ret
}
fn do_self_insert(c: char) {
if (INSMODE.load(SeqCst) != 0) {
ZLELINE.lock().unwrap().insert(ZLECS.load(SeqCst), c);
ZLECS.fetch_add(1, SeqCst);
ZLELL.fetch_add(1, SeqCst);
} else {
if ZLECS.load(SeqCst) < ZLELL.load(SeqCst) {
ZLELINE.lock().unwrap()[ZLECS.load(SeqCst)] = c;
} else {
ZLELINE.lock().unwrap().push(c);
ZLELL.fetch_add(1, SeqCst);
}
ZLECS.fetch_add(1, SeqCst);
}
ZLE_RESET_NEEDED.store(1, SeqCst);
}
pub fn recursive_edit() -> i32 {
ZLE_RECURSIVE.fetch_add(1, SeqCst);
redrawhook();
zrefresh();
zlecore();
ZLE_RECURSIVE.fetch_sub(1, SeqCst);
let locerror = i32::from(crate::ported::utils::errflag.load(Ordering::Relaxed) != 0);
crate::ported::utils::errflag.store(0, Ordering::Relaxed);
DONE.store(0, SeqCst);
EOFSENT.store(0, SeqCst);
locerror
}
pub fn finish_line() {
DONE.store(1, SeqCst);
}
pub fn abort_line() {
ZLELINE.lock().unwrap().clear();
ZLECS.store(0, SeqCst);
ZLELL.store(0, SeqCst);
DONE.store(1, SeqCst);
}
pub fn describe_key_briefly() -> i32 {
use crate::ported::utils::nicedup;
use crate::ported::zle::zle_h::invicmdmode;
use crate::ported::zle::zle_keymap::{getkeymapcmd, selectlocalmap};
use crate::ported::zle::zle_refresh::{zrefresh, CLEARLIST};
use crate::ported::zle::zle_utils::{bindztrdup, showmsg};
if STATUSLINE.lock().unwrap().is_some() {
return 1;
}
CLEARLIST.store(1, SeqCst);
*STATUSLINE.lock().unwrap() = Some("Describe key briefly: _".to_string());
let _ = crate::ported::zle::termquery::start_edit();
zrefresh();
if invicmdmode(&curkeymapname()) && REGION_ACTIVE.load(SeqCst) != 0 {
if let Some(km) = openkeymap("visual") {
selectlocalmap(Some(km));
}
}
let effective = {
let local = LOCALKEYMAP.lock().unwrap().clone();
let cur = curkeymap.lock().unwrap().clone();
local.or(cur)
};
let resolved = effective.and_then(|km| getkeymapcmd(&km));
selectlocalmap(None);
let _ = crate::ported::zle::termquery::end_edit();
*STATUSLINE.lock().unwrap() = None;
let (func, seq, str) = match resolved {
Some(r) => r,
None => return 1,
};
if seq.is_empty() {
return 1;
}
let mut msg = bindztrdup(&seq); msg.push_str(" is "); let is = if func.nam.is_empty() {
bindztrdup(str.as_deref().unwrap_or("").as_bytes())
} else {
nicedup(&func.nam, 0)
};
msg.push_str(&is); showmsg(&msg); 0 }
#[allow(unused_variables)]
#[allow(unused_variables)]
fn display_msg(msg: &str) {
eprintln!("{}", msg);
}
pub fn prompt() -> String {
LPROMPT.lock().unwrap().clone()
}
pub fn rprompt() -> String {
RPROMPT.lock().unwrap().clone()
}
pub fn set_prompt(prompt: &str) {
*LPROMPT.lock().unwrap() = prompt.to_string();
ZLE_RESET_NEEDED.store(1, SeqCst);
}
pub fn get_mult() -> i32 {
if ZMOD.lock().unwrap().flags & MOD_MULT != 0 {
ZMOD.lock().unwrap().mult
} else {
1
}
}
pub fn toggle_neg_arg() {
ZMOD.lock().unwrap().flags ^= MOD_NEG;
}
pub fn is_neg() -> bool {
ZMOD.lock().unwrap().flags & MOD_NEG != 0
}
pub fn is_vicmd() -> bool {
*curkeymapname() == "vicmd"
}
pub fn is_viins() -> bool {
*curkeymapname() == "viins"
}
pub static ZLE_RESET_NEEDED: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static INSMODE: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
pub static RAW_GETBYTE_R: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
pub static RAW_GETBYTE_ERRNO: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static LASTCHAR_WIDE: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static LASTCHAR_WIDE_VALID: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static EOFCHAR: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(4);
pub static EOFSENT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static PREFIXFLAG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ZLEREADFLAGS: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(ZLRF_HISTORY | ZLRF_NOSETTY);
pub static ZLECONTEXT: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(ZLCON_LINE_START);
pub static KUNGETCT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ZLE_RECURSIVE: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static KEYTIMEOUT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(40);
pub static LASTCMD: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
pub static WATCH_FDS: std::sync::Mutex<Vec<watch_fd>> = std::sync::Mutex::new(Vec::new());
pub static ZLELINE: std::sync::Mutex<Vec<char>> = std::sync::Mutex::new(Vec::new());
pub static ZLECS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static ZLELL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static MARK: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static LBINDK: std::sync::Mutex<Option<Thingy>> = std::sync::Mutex::new(None);
pub static BINDK: std::sync::Mutex<Option<Thingy>> = std::sync::Mutex::new(None);
pub static ZMOD: std::sync::Mutex<modifier> = std::sync::Mutex::new(modifier {
flags: 0,
mult: 1,
tmult: 1,
vibuf: 0,
base: 10,
});
pub static STATUSLINE: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub static STACKHIST: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
pub static STACKCS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
pub static VISTARTCHANGE: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(u64::MAX);
pub static UNDO_STACK: std::sync::Mutex<Vec<change>> = std::sync::Mutex::new(Vec::new());
pub static CHANGENO: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub static KUNGETBUF: std::sync::Mutex<VecDeque<u8>> = std::sync::Mutex::new(VecDeque::new());
pub static BAUD: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(38400);
pub static COMPWIDGET: std::sync::Mutex<Option<widget>> = std::sync::Mutex::new(None);
pub static HASCOMPMOD: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub static TTYFD: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static LPROMPT: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
pub static RPROMPT: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
pub static PRE_ZLE_STATUS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static VIBUF: std::sync::OnceLock<
std::sync::Mutex<[crate::ported::zle::zle_h::cutbuffer; 36]>,
> = std::sync::OnceLock::new();
pub fn is_emacs() -> bool {
let n = curkeymapname();
*n == "emacs" || *n == "main"
}
pub static KILLRING: std::sync::Mutex<VecDeque<crate::ported::zle::zle_h::cutbuffer>> = std::sync::Mutex::new(VecDeque::new());
pub static KILLRINGMAX: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(8);
pub static KRINGNUM: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static CUTBUF: std::sync::Mutex<crate::ported::zle::zle_h::cutbuffer> = std::sync::Mutex::new(crate::ported::zle::zle_h::cutbuffer {
buf: String::new(),
len: 0,
flags: 0,
});
pub static YANKLAST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub static NEG_ARG: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub static MULT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
pub static HISTORY: std::sync::OnceLock<std::sync::Mutex<History>> = std::sync::OnceLock::new();
pub fn was_yank() -> bool {
(LASTCMD.load(SeqCst) as i32 & ZLE_YANK) != 0
}
pub static LASTCOL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
pub static BUFSTACK: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
pub static varedarg: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub static VICHGBUF: std::sync::Mutex<Vec<u8>> = std::sync::Mutex::new(Vec::new());
pub static SRCH_STR: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub static LASTLINE: std::sync::Mutex<Vec<char>> = std::sync::Mutex::new(Vec::new());
pub static LASTLL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static LASTCS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static CURCHANGE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static UNDO_CHANGENO: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub static UNDO_LIMITNO: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub static VIINSBEGIN: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static YANKB: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static YANKE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static YANKCS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static KCT: std::sync::Mutex<Option<usize>> = std::sync::Mutex::new(None);
pub static VIMARKS: std::sync::OnceLock<std::sync::Mutex<[Option<(usize, i32)>; 27]>> =
std::sync::OnceLock::new();
pub static REGION_ACTIVE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
pub static PENDING_HOOKS: std::sync::Mutex<Vec<(String, Option<String>)>> =
std::sync::Mutex::new(Vec::new());
pub static RAW_LP: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
pub static RAW_RP: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
pub static HIGHLIGHT: std::sync::OnceLock<std::sync::Mutex<HighlightManager>> =
std::sync::OnceLock::new();
pub fn vibuf() -> &'static std::sync::Mutex<[crate::ported::zle::zle_h::cutbuffer; 36]> {
VIBUF.get_or_init(|| {
std::sync::Mutex::new(std::array::from_fn(|_| {
crate::ported::zle::zle_h::cutbuffer::default()
}))
})
}
pub fn history() -> &'static std::sync::Mutex<History> {
HISTORY.get_or_init(|| std::sync::Mutex::new(History::new(2000)))
}
pub fn vimarks() -> &'static std::sync::Mutex<[Option<(usize, i32)>; 27]> {
VIMARKS.get_or_init(|| std::sync::Mutex::new([None; 27]))
}
pub fn highlight() -> &'static std::sync::Mutex<HighlightManager> {
HIGHLIGHT.get_or_init(|| std::sync::Mutex::new(HighlightManager::new()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn getbyte_no_byte_sets_lastchar_eof() {
let _g = crate::test_util::global_state_lock();
KUNGETBUF.lock().unwrap().clear();
WATCH_FDS.lock().unwrap().clear();
LASTCHAR.store(b'x' as i32, SeqCst); let r = getbyte(0);
assert_eq!(r, None, "no input available → None");
assert_eq!(
LASTCHAR.load(SeqCst),
-1,
"c:891 — lastchar must be EOF (-1), not the stale 'x'"
);
}
#[test]
fn handleprefixes_promotes_tmult_to_mult_when_prefixflag_set() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMOD.lock().unwrap().flags |= MOD_TMULT;
ZMOD.lock().unwrap().tmult = 7;
PREFIXFLAG.store(1, SeqCst);
handleprefixes();
assert_ne!(ZMOD.lock().unwrap().flags & MOD_MULT, 0);
assert_ne!(ZMOD.lock().unwrap().flags & MOD_TMULT, 0);
assert_eq!(ZMOD.lock().unwrap().mult, 7);
assert_eq!(PREFIXFLAG.load(SeqCst), 0);
}
#[test]
fn handleprefixes_resets_modifier_when_prefixflag_cleared() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
ZMOD.lock().unwrap().flags |= MOD_MULT;
ZMOD.lock().unwrap().mult = 9;
PREFIXFLAG.store(0, SeqCst);
handleprefixes();
assert_eq!(ZMOD.lock().unwrap().mult, 1);
assert_ne!(!ZMOD.lock().unwrap().flags & MOD_MULT, 0);
}
#[test]
fn get_key_cmd_resolves_single_byte_binding() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
selectkeymap("emacs", 1);
ungetbytes(b"\x05"); let (t, _str) = get_key_cmd().expect("should resolve Ctrl-E");
assert_eq!(t.expect("widget, not a send-string").nam, "end-of-line");
}
#[test]
fn get_key_cmd_resolves_multi_byte_sequence() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
selectkeymap("emacs", 1);
ungetbytes(b"\x1bd");
let (t, _str) = get_key_cmd().expect("should resolve ESC-d");
let t = t.expect("widget, not a send-string");
assert_ne!(t.nam, "self-insert");
}
#[test]
fn get_key_cmd_returns_none_on_eof() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
selectkeymap("emacs", 1);
let result = get_key_cmd();
let _ = result;
}
#[test]
fn handle_undo_snapshots_line_for_subsequent_diff() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLELL.store(3, SeqCst);
ZLECS.store(3, SeqCst);
handleundo();
assert_eq!(LASTLINE.lock().unwrap().iter().collect::<String>(), "abc");
assert_eq!(LASTLL.load(SeqCst), 3);
assert_eq!(LASTCS.load(SeqCst), 3);
}
#[test]
fn in_vi_cmd_mode_reflects_active_keymap_name() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*curkeymapname() = "emacs".to_string();
assert!(!in_vi_cmd_mode());
*curkeymapname() = "vicmd".to_string();
assert!(in_vi_cmd_mode());
}
#[test]
fn ungetbytes_unmeta_plain_bytes() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_reset();
KUNGETBUF.lock().unwrap().clear();
ungetbytes_unmeta(b"abc");
assert_eq!(KUNGETBUF.lock().unwrap().pop_front(), Some(b'a'));
assert_eq!(KUNGETBUF.lock().unwrap().pop_front(), Some(b'b'));
assert_eq!(KUNGETBUF.lock().unwrap().pop_front(), Some(b'c'));
}
#[test]
fn ungetbytes_unmeta_decodes_meta_pair() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_reset();
KUNGETBUF.lock().unwrap().clear();
ungetbytes_unmeta(&[0x83, 0x41]);
assert_eq!(KUNGETBUF.lock().unwrap().pop_front(), Some(b'a'));
assert!(KUNGETBUF.lock().unwrap().is_empty());
}
#[test]
fn ungetbytes_unmeta_mixed_meta_and_plain() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_reset();
KUNGETBUF.lock().unwrap().clear();
ungetbytes_unmeta(&[0x58, 0x83, 0x41, 0x5a]);
assert_eq!(KUNGETBUF.lock().unwrap().pop_front(), Some(b'X'));
assert_eq!(KUNGETBUF.lock().unwrap().pop_front(), Some(b'a'));
assert_eq!(KUNGETBUF.lock().unwrap().pop_front(), Some(b'Z'));
assert!(KUNGETBUF.lock().unwrap().is_empty());
}
#[test]
fn ungetbytes_unmeta_empty_input() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbytes_unmeta(b"");
assert!(KUNGETBUF.lock().unwrap().is_empty());
}
#[test]
fn ungetbyte_single_byte_appears_in_buffer() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbyte(b'X');
let buf = KUNGETBUF.lock().unwrap();
assert!(
buf.contains(&b'X'),
"ungetbyte should push 'X' into KUNGETBUF"
);
}
#[test]
fn ungetbytes_reverses_input_for_consume_order() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbytes(b"ABC");
let buf = KUNGETBUF.lock().unwrap().clone();
assert!(buf.contains(&b'A'), "A pushed");
assert!(buf.contains(&b'B'), "B pushed");
assert!(buf.contains(&b'C'), "C pushed");
assert_eq!(buf.len(), 3, "exactly 3 bytes pushed");
}
#[test]
fn ungetbytes_empty_input_pushes_nothing() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbytes(b"");
assert!(KUNGETBUF.lock().unwrap().is_empty());
}
#[test]
fn ungetbyte_then_getbyte_round_trips() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbyte(b'Z');
let got = getbyte(0);
assert_eq!(got, Some(b'Z'), "round-trip: pushed → got");
}
#[test]
fn insmode_defaults_to_one() {
let _g = crate::test_util::global_state_lock();
let v = INSMODE.load(std::sync::atomic::Ordering::SeqCst);
assert!(v == 0 || v == 1, "insmode must be 0 or 1, got {}", v);
}
#[test]
fn eofchar_defaults_to_ctrl_d() {
let _g = crate::test_util::global_state_lock();
let v = EOFCHAR.load(std::sync::atomic::Ordering::SeqCst);
assert!(v >= 0 && v < 256, "eofchar must be a valid byte, got {}", v);
}
#[test]
fn keytimeout_default_is_40_hundredths() {
let _g = crate::test_util::global_state_lock();
let v = KEYTIMEOUT.load(std::sync::atomic::Ordering::SeqCst);
assert!(v < 100_000, "keytimeout absurdly large: {}", v);
}
#[test]
fn prompt_returns_owned_string_not_borrowed() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
set_prompt("zshrs_test_prompt");
let mut p = prompt();
let original = p.clone();
p.push_str("MUTATED");
let p2 = prompt();
assert_eq!(
p2, original,
"mutation of returned prompt must not leak back"
);
}
#[test]
fn set_prompt_round_trips_via_getter() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
set_prompt("ROUND_TRIP_PROMPT");
assert_eq!(prompt(), "ROUND_TRIP_PROMPT");
}
#[test]
fn set_prompt_signals_reset_needed() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ZLE_RESET_NEEDED.store(0, std::sync::atomic::Ordering::SeqCst);
set_prompt("trigger_reset");
let v = ZLE_RESET_NEEDED.load(std::sync::atomic::Ordering::SeqCst);
assert_eq!(v, 1, "set_prompt must set ZLE_RESET_NEEDED");
}
#[test]
fn get_mult_default_is_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ZMOD.lock().unwrap().flags &= !MOD_MULT; assert_eq!(get_mult(), 1, "no MOD_MULT → mult defaults to 1");
}
#[test]
fn is_neg_clean_read_no_mutation() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let before = ZMOD.lock().unwrap().flags;
let _ = is_neg();
let after = ZMOD.lock().unwrap().flags;
assert_eq!(before, after, "is_neg must not mutate ZMOD");
}
#[test]
fn toggle_neg_arg_flips_bit() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ZMOD.lock().unwrap().flags &= !MOD_NEG;
toggle_neg_arg();
assert!(is_neg(), "toggle from off → on");
toggle_neg_arg();
assert!(!is_neg(), "toggle from on → off");
}
#[test]
fn is_vicmd_strict_string_match() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
if *curkeymapname() == "emacs" {
assert!(!is_vicmd());
assert!(!is_viins());
}
}
#[test]
fn lastchar_wide_valid_default_zero() {
let _g = crate::test_util::global_state_lock();
let v = LASTCHAR_WIDE_VALID.load(std::sync::atomic::Ordering::SeqCst);
assert!(v == 0 || v == 1, "lastchar_wide_valid is bool-int");
}
#[test]
fn zle_recursive_default_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let v = ZLE_RECURSIVE.load(std::sync::atomic::Ordering::SeqCst);
assert!(v >= 0, "zle_recursive cannot be negative, got {}", v);
}
#[test]
fn kungetct_reflects_kungetbuf_state() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbytes(b"AB");
let n = KUNGETBUF.lock().unwrap().len();
assert_eq!(n, 2, "2 bytes pushed → KUNGETBUF len 2");
}
#[test]
fn ungetbyte_pushes_single_byte() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbyte(b'X');
assert_eq!(KUNGETBUF.lock().unwrap().len(), 1);
}
#[test]
fn ungetbytes_empty_no_op() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbytes(b"");
assert_eq!(KUNGETBUF.lock().unwrap().len(), 0);
}
#[test]
fn ungetbytes_unmeta_empty_no_op() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbytes_unmeta(b"");
assert_eq!(KUNGETBUF.lock().unwrap().len(), 0);
}
#[test]
fn ungetbytes_repeated_pushes_accumulate() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
KUNGETBUF.lock().unwrap().clear();
ungetbytes(b"A");
ungetbytes(b"BC");
ungetbytes(b"DEF");
assert_eq!(KUNGETBUF.lock().unwrap().len(), 6);
}
fn thingy_named(nam: &str) -> Thingy {
Thingy {
nam: nam.to_string(),
flags: 0,
rc: 0,
widget: None,
}
}
#[test]
fn scanfindfunc_ignores_other_widgets() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut ff = findfunc {
func: Some("beginning-of-line".to_string()),
found: 0,
msg: "beginning-of-line".to_string(),
};
scanfindfunc(b"x", Some(&thingy_named("end-of-line")), &mut ff);
scanfindfunc(b"y", None, &mut ff);
assert_eq!(ff.found, 0);
assert_eq!(ff.msg, "beginning-of-line");
}
#[test]
fn scanfindfunc_lists_up_to_maxfound_then_only_counts() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let t = thingy_named("beginning-of-line");
let mut ff = findfunc {
func: Some("beginning-of-line".to_string()),
found: 0,
msg: "beginning-of-line".to_string(),
};
for seq in [b"a", b"b", b"c", b"d", b"e"] {
scanfindfunc(seq, Some(&t), &mut ff);
}
assert_eq!(ff.found, 5);
assert_eq!(ff.msg, r#"beginning-of-line is on "a" "b" "c" "d""#);
assert!(ff.found > MAXFOUND);
}
#[test]
fn resetprompt_idempotent_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
resetprompt();
}
}
#[test]
fn reexpandprompt_idempotent_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
reexpandprompt();
}
}
#[test]
fn redrawhook_idempotent_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
redrawhook();
}
}
#[test]
fn getfullchar_returns_option_char_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<char> = getfullchar(false);
}
#[test]
fn ungetbyte_signature_void() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: () = ungetbyte(b'x');
}
#[test]
fn ungetbytes_signature_void() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: () = ungetbytes(&[]);
}
#[test]
fn ungetbytes_unmeta_signature_void() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: () = ungetbytes_unmeta(&[]);
}
#[test]
fn getrestchar_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = getrestchar(b'a' as i32);
}
#[test]
fn initmodifier_idempotent_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
initmodifier();
}
}
#[test]
fn handleprefixes_idempotent_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
handleprefixes();
}
}
#[test]
fn describekeybriefly_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = describekeybriefly();
}
#[test]
fn recursiveedit_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = recursiveedit();
}
#[test]
fn execimmortal_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = execimmortal("", &[]);
}
#[test]
fn ungetbyte_returns_void_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: () = ungetbyte(b'a');
}
#[test]
fn ungetbyte_repeated_calls_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for b in [b'a', b'b', b'c', 0, 0xff] {
ungetbyte(b);
}
}
#[test]
fn ungetbytes_empty_slice_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ungetbytes(&[]);
}
#[test]
fn ungetbytes_various_inputs_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ungetbytes(b"hello");
ungetbytes(&[0xff, 0x00, 0x7f]);
}
#[test]
fn ungetbytes_unmeta_empty_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
ungetbytes_unmeta(&[]);
}
#[test]
fn raw_getbyte_returns_option_u8_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<u8> = raw_getbyte(0);
}
#[test]
fn getbyte_returns_option_u8_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<u8> = getbyte(0);
}
#[test]
fn getfullchar_returns_option_char_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<char> = getfullchar(false);
}
#[test]
fn execimmortal_returns_i32_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = execimmortal("name", &[]);
}
#[test]
fn execimmortal_exit_code_in_u8_range() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = execimmortal("", &[]);
assert!(
(0..256).contains(&r),
"execimmortal exit code {} must fit u8",
r
);
}
}