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::{
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, widget,
};
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::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::zle::termquery::mark_output;
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<()> {
let mut termios = termios::Termios::from_fd(
TTYFD.load(SeqCst),
)?;
termios.c_lflag &= !(termios::ICANON | termios::ECHO);
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);
}
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;
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(
TTYFD.load(SeqCst),
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;
}
}
}
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: bool) -> ztmout {
let kt = KEYTIMEOUT.load(SeqCst);
if do_keytmout && kt > 0 {
let exp = if kt > ZMAXTIMEOUT * 100 {
ZMAXTIMEOUT * 100
} else {
kt
};
ztmout {
tp: ztmouttp::ZTM_KEY,
exp100ths: exp as i64,
}
} else {
ztmout {
tp: ztmouttp::ZTM_NONE,
exp100ths: 0,
}
}
}
pub fn raw_getbyte(do_keytmout: bool) -> Option<u8> {
if let Some(b) = KUNGETBUF
.lock()
.unwrap()
.pop_front()
{
return Some(b);
}
let timeout = calc_timeout(do_keytmout);
let timeout_duration = if timeout.tp != ztmouttp::ZTM_NONE {
Some(Duration::from_millis((timeout.exp100ths * 10) as u64))
} else {
None
};
let mut buf = [0u8; 1];
if let Some(dur) = timeout_duration {
let start = Instant::now();
loop {
if start.elapsed() >= dur {
return None; }
match try_read_byte(&mut buf) {
Ok(true) => return Some(buf[0]),
Ok(false) => {
std::thread::sleep(Duration::from_millis(10));
}
Err(_) => return None,
}
}
} else {
use std::os::unix::io::AsRawFd;
let fd = io::stdin().as_raw_fd();
let is_tty = unsafe { libc::isatty(fd) } != 0;
let in_raw_mode = if is_tty {
let mut t: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(fd, &mut t) } == 0 {
(t.c_lflag & libc::ICANON) == 0
} else {
false
}
} else {
true
};
if !in_raw_mode {
return None;
}
match io::stdin().read(&mut buf) {
Ok(1) => Some(buf[0]),
_ => None,
}
}
}
pub fn getbyte(do_keytmout: bool) -> Option<u8> {
let b = raw_getbyte(do_keytmout)?;
let b = if b == b'\n' {
b'\r'
} else if b == b'\r' {
b'\n'
} else {
b
};
LASTCHAR
.store((b as i32) as i32, SeqCst);
Some(b)
}
pub fn getfullchar(do_keytmout: bool) -> Option<char> {
let inchar = getbyte(do_keytmout).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 {
LASTCHAR_WIDE_VALID.store(1, SeqCst); if inchar < 0 {
LASTCHAR_WIDE.store(-1, SeqCst); return -1;
}
let b0 = inchar as u8;
let expected = if b0 < 0x80 {
1
} else if b0 < 0xC0 {
1
}
else if b0 < 0xE0 {
2
} else if b0 < 0xF0 {
3
} else {
4
};
let mut bytes: Vec<u8> = vec![b0];
while bytes.len() < expected {
match getbyte(true) {
Some(n) if (n & 0xC0) == 0x80 => bytes.push(n), Some(n) => {
ungetbyte(n); break;
}
None => {
LASTCHAR_WIDE.store(-1, SeqCst);
return -1;
}
}
}
let c_opt = std::str::from_utf8(&bytes)
.ok()
.and_then(|s| s.chars().next());
match c_opt {
Some(c) => {
LASTCHAR_WIDE.store(c as i32, SeqCst); c as i32
}
None => {
LASTCHAR_WIDE.store(b0 as i32, SeqCst); b0 as i32
}
}
}
pub fn redrawhook() {
crate::ported::zle::zle_utils::call_hook("zle-line-pre-redraw", None);
}
pub fn zlecore() {
DONE.store(0, SeqCst);
while DONE.load(SeqCst) == 0 {
let thingy = match get_key_cmd() {
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());
if let Some(widget) = &thingy.widget {
execute_widget(widget);
} 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);
}
redrawhook();
if ZLE_RESET_NEEDED.load(SeqCst) != 0 {
zrefresh();
ZLE_RESET_NEEDED.store(0, SeqCst);
}
}
}
pub fn zleread(
lprompt: &str,
rprompt: &str,
flags: i32,
context: i32,
) -> io::Result<String> {
*RAW_LP.lock().unwrap() = lprompt.to_string();
*RAW_RP.lock().unwrap() = rprompt.to_string();
*LPROMPT.lock().unwrap() = crate::prompt::expand_prompt(lprompt);
*RPROMPT.lock().unwrap() = crate::prompt::expand_prompt(rprompt);
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);
zsetterm()?;
if crate::ported::zle::zle_thingy::rthingy_nocreate("zle-line-init") {
let _ = execzlefunc("zle-line-init", &["zle-line-init".to_string()], 1, 0);
}
{
use std::sync::atomic::Ordering;
let fd = SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out, lprompt.as_bytes());
}
zlecore();
if crate::ported::zle::zle_thingy::rthingy_nocreate("zle-line-finish") {
let _ = execzlefunc("zle-line-finish", &["zle-line-finish".to_string()], 1, 0);
}
Ok(ZLELINE.lock().unwrap().iter().collect())
}
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) {
return 1;
}
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_lbindk;
if getshfunc(name).is_some() {
let rc = crate::ported::exec_hooks::dispatch_function_call(name, args).unwrap_or(0);
LASTVAL.store(rc, Ordering::Relaxed);
if set_bindk != 0 {
*BINDK.lock().unwrap() = save_bindk; }
return rc;
}
if set_bindk != 0 {
*BINDK.lock().unwrap() = save_bindk; }
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_TMULT;
ZMOD.lock().unwrap().flags |= MOD_MULT;
let mut __g_zmod = ZMOD.lock().unwrap();
__g_zmod.mult = __g_zmod.tmult;
}
} else {
initmodifier();
}
}
pub fn savekeymap(
oldname: &str,
newname: &str,
) -> Option<std::sync::Arc<Keymap>> {
let km = openkeymap(newname)?;
let saved = openkeymap(oldname);
let same = saved
.as_ref()
.map(|s| std::sync::Arc::ptr_eq(s, &km))
.unwrap_or(false);
if !same {
linkkeymap(km, oldname, 0);
}
if same {
None
} else {
saved
}
}
pub fn restorekeymap(
oldname: &str,
savemap: Option<std::sync::Arc<Keymap>>,
) {
if let Some(km) = savemap {
linkkeymap(km, oldname, 0);
}
}
pub fn bin_vared(
name: &str,
args: &[String], ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
let mut type_: u32 = PM_SCALAR; let term = getsparam("TERM").unwrap_or_default();
if 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 { 1 }; 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 = OPT_ARG_SAFE(ops, b'p').unwrap_or(""); let p2 = OPT_ARG_SAFE(ops, b'r').unwrap_or(""); let main_keymapname = OPT_ARG_SAFE(ops, b'M').unwrap_or(""); let vicmd_keymapname = OPT_ARG_SAFE(ops, b'm').unwrap_or(""); let init = OPT_ARG_SAFE(ops, b'i').unwrap_or(""); let finish = OPT_ARG_SAFE(ops, b'f').unwrap_or(""); let _ = (main_keymapname, vicmd_keymapname, init, finish);
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 varname = &args[0]; crate::ported::mem::queue_signals();
let exists = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.contains_key(varname)
};
if !exists && !OPT_ISSET(ops, b'c') {
unqueue_signals(); zwarnnam(name, &format!("no such variable: {}", varname)); return 1; }
unqueue_signals();
let prompt = if !p1.is_empty() {
p1.to_string()
} else {
String::new()
};
let rprompt = if !p2.is_empty() {
p2.to_string()
} else {
String::new()
};
let current = getsparam(varname).unwrap_or_default();
{
use std::sync::atomic::Ordering;
let fd = SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 1 };
if !prompt.is_empty() {
let _ = write_loop(out, prompt.as_bytes());
}
let _ = write_loop(out, current.as_bytes());
if !rprompt.is_empty() {
let _ = write_loop(out, rprompt.as_bytes());
}
}
let mut input = String::new();
if io::stdin().read_line(&mut input).is_ok() {
let value = input.trim_end_matches('\n').to_string();
let _ = crate::ported::params::assignsparam(
varname, &value, warn_flags,
);
return 0; }
1
}
pub fn describekeybriefly() -> i32 {
describe_key_briefly(); 0
}
pub const MAXFOUND: usize = 4;
#[derive(Debug, Default)]
#[allow(non_camel_case_types)]
pub struct findfunc {
pub func: Option<usize>, pub found: usize, pub msg: String, }
pub fn scanfindfunc(seq: &str, func: &str, ff: &mut findfunc) {
const MAXFOUND: usize = 3; let want = ff.func.map(|i| i.to_string()).unwrap_or_default();
if !want.is_empty() && func != want {
return;
}
if ff.found == 0 {
ff.msg.push_str(" is on");
}
ff.found += 1;
if ff.found <= MAXFOUND {
ff.msg.push(' '); ff.msg.push_str(seq); }
}
pub fn whereis(widget_name: &str) -> Vec<String> {
let mut bindings = Vec::new();
let tab = keymapnamtab()
.lock()
.unwrap();
for (name, node) in tab.iter() {
let km = &node.keymap;
for (i, opt) in km.first.iter().enumerate() {
if let Some(t) = opt {
if t.nam == widget_name {
bindings.push(format!(
"{}:{}",
name,
printbind(&[i as u8])
));
}
}
}
for (seq, kb) in &km.multi {
if let Some(ref t) = kb.bind {
if t.nam == widget_name {
bindings.push(format!("{}:{}", name, printbind(seq)));
}
}
}
}
bindings
}
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);
loop {
let reexp = REEXPANDING.with(|c| c.get());
LOOPING.with(|c| c.set(reexp));
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);
*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);
*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() {
let fd = SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out, b"\r\x1b[K\x1b[0m");
ZLE_RESET_NEEDED.store(1, SeqCst);
}
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,
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 fn setup_(m: *const module) -> i32 {
crate::ported::zle::zle_thingy::init_thingies();
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 {
features.clear();
features.extend([
"b:bindkey".to_string(),
"b:vared".to_string(),
"b:zle".to_string(),
"p:KEYMAP".to_string(),
"p:CONTEXT".to_string(),
"p:KEYS".to_string(),
"p:NUMERIC".to_string(),
"p:PREDISPLAY".to_string(),
"p:POSTDISPLAY".to_string(),
"p:BUFFER".to_string(),
"p:CURSOR".to_string(),
"p:CUTBUFFER".to_string(),
"p:HISTNO".to_string(),
"p:KILLRING".to_string(),
"p:LASTSEARCH".to_string(),
"p:LASTWIDGET".to_string(),
"p:MARK".to_string(),
"p:PREBUFFER".to_string(),
"p:RBUFFER".to_string(),
"p:LBUFFER".to_string(),
"p:REGION_ACTIVE".to_string(),
"p:UNDO_CHANGE_NO".to_string(),
"p:UNDO_LIMIT_NO".to_string(),
"p:WIDGET".to_string(),
"p:WIDGETSTYLE".to_string(),
"p:WIDGETFUNC".to_string(),
"p:registers".to_string(),
"p:ZLE_LINE_ABORTED".to_string(),
]);
0 }
#[allow(unused_variables)]
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
0
}
pub fn boot_(_m: *const module) -> i32 {
addhookfunc("before_trap", zlebeforetrap); addhookfunc("after_trap", zleaftertrap);
for name in [
"insert_match", "menu_start", "compctl_make", "compctl_cleanup", "comp_list_matches",
]
{
let h = Box::into_raw(Box::new(hookdef {
next: std::ptr::null_mut(),
name: name.to_string(),
def: None,
flags: crate::ported::zsh_h::HOOKF_ALL,
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.clear();
}
}
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());
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(0, SeqCst);
STACKCS.store(0, SeqCst);
VISTARTCHANGE.store(0, 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.clear();
}
KILLRING.lock().unwrap().clear();
KILLRINGMAX.store(8, 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_u: usize = 0;
let mut cs_u: usize = 0;
let line = zlegetline(
&mut ll_u, &mut cs_u,
);
**ll = ll_u as i32;
**cs = cs_u as i32;
return Some(line.into_iter().collect()); }
}
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);
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 != 0,
)
.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(42),
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<Thingy> {
let km = {
let local = LOCALKEYMAP
.lock()
.unwrap()
.clone();
let cur = curkeymap
.lock()
.unwrap()
.clone();
local.or(cur)?
};
let mut buf: Vec<u8> = Vec::with_capacity(8);
let mut last_match: Option<Thingy> = None;
let mut last_match_len = 0usize;
loop {
let do_keytmout = last_match.is_some();
let b = getbyte(do_keytmout)?;
buf.push(b);
let (current_match, is_prefix) = if buf.len() == 1 {
let m = km.first[b as usize].clone();
let pfx = km.multi.keys().any(|k| k.len() > 1 && k[0] == b);
(m, pfx)
} else {
let entry = km.multi.get(&buf[..]);
let m = entry.and_then(|e| e.bind.clone());
let pfx = entry.map(|e| e.prefixct > 0).unwrap_or(false);
(m, pfx)
};
if let Some(t) = current_match {
last_match = Some(t);
last_match_len = buf.len();
}
if !is_prefix {
break;
}
}
if last_match.is_some() && buf.len() > last_match_len {
let extra = buf[last_match_len..].to_vec();
ungetbytes(&extra);
}
last_match
}
fn execute_widget(widget: &widget) {
if (widget.flags & ZLE_LASTCOL) == 0 {
LASTCOL.store(-1, SeqCst);
}
handleundo();
match &widget.u {
WidgetImpl::Internal(f) => {
let _ = f(&[]);
}
WidgetImpl::UserFunc(name) => {
let _ = execzlefunc(name, &[], 0, 0);
}
_ => {}
}
if (widget.flags & ZLE_NOTCOMMAND) == 0 {
LASTCMD.store(widget.flags as u32, SeqCst);
}
mkundoent();
}
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);
let old_done =
DONE.load(SeqCst) != 0;
let old_eofsent = EOFSENT.load(SeqCst);
redrawhook();
zrefresh();
DONE.store(0, SeqCst);
EOFSENT.store(0, SeqCst);
zlecore();
let locerror = EOFSENT.load(SeqCst);
DONE.store(
if old_done { 1 } else { 0 },
SeqCst,
);
EOFSENT.store(old_eofsent, SeqCst);
ZLE_RECURSIVE.fetch_sub(1, 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() {
if let Some(c) = getfullchar(false) {
let thingy = if c as u32 > 255 {
None
} else {
let km = LOCALKEYMAP
.lock()
.unwrap()
.clone()
.or_else(|| {
curkeymap
.lock()
.unwrap()
.clone()
});
km.and_then(|k| k.first[c as usize].clone())
};
if let Some(thingy) = thingy {
display_msg(&format!("{} is bound to {}", c, thingy.nam));
} else {
display_msg(&format!("{} is not bound", c));
}
}
}
#[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 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 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(0);
pub static STACKCS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static VISTARTCHANGE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
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<[Vec<char>; 36]>> =
std::sync::OnceLock::new();
pub fn is_emacs() -> bool {
let n = curkeymapname();
*n == "emacs" || *n == "main"
}
pub static KILLRING: std::sync::Mutex<VecDeque<Vec<char>>> =
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 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<[Vec<char>; 36]> {
VIBUF.get_or_init(|| std::sync::Mutex::new(std::array::from_fn(|_| Vec::new())))
}
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 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 = get_key_cmd().expect("should resolve Ctrl-E");
assert_eq!(t.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 = get_key_cmd().expect("should resolve ESC-d");
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());
}
}