use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use super::zle_bindings::{EMACSBIND, METABIND, VICMDBIND, VIINSBIND};
use super::zle_main::zle_test_setup;
use super::zle_thingy::Thingy;
use crate::ported::utils::inittyptab;
#[cfg(test)]
use crate::ported::ztype_h::TYPTAB_TEST_LOCK;
use std::io::Write;
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
zle_params::*, zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
};
use crate::ported::zsh_h::options;
use crate::ported::ztype_h::imeta;
#[allow(unused_imports)]
#[allow(unused_imports)]
#[derive(Debug, Clone)]
pub struct KeymapName {
pub nam: String, pub flags: i32, pub keymap: Arc<Keymap>, }
pub const KMN_IMMORTAL: i32 = 1 << 1;
pub const KM_IMMUTABLE: i32 = 1 << 1;
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub struct remprefstate {
pub km: Arc<Keymap>, pub prefix: Vec<u8>, pub prefixlen: usize, }
pub const BS_LIST: i32 = 1 << 0;
pub const BS_ALL: i32 = 1 << 1;
pub static lastnamed: Mutex<Option<Thingy>> = Mutex::new(None);
pub fn createkeymapnamtab() {
let _ = keymapnamtab();
}
pub fn init_keymaps() {
createkeymapnamtab(); default_bindings(); *keybuf.lock().unwrap() = vec![0u8; 32]; *lastnamed.lock().unwrap() = None; }
pub fn cleanup_keymaps() {
*lastnamed.lock().unwrap() = None; keymapnamtab().lock().unwrap().clear(); keybuf.lock().unwrap().clear(); }
pub fn makekeymapnamnode(keymap: Arc<Keymap>) -> KeymapName {
KeymapName {
nam: String::new(),
flags: 0,
keymap: keymap,
}
}
pub fn emptykeymapnamtab() {
keymapnamtab().lock().unwrap().clear();
}
pub fn refkeymap_by_name(kmn: &str) {
let _ = keymapnamtab().lock().unwrap().get(kmn); }
pub fn scanprimaryname(_name: &str) { }
pub fn unrefkeymap_by_name(name: &str) {
let mut tab = match keymapnamtab().lock() {
Ok(t) => t,
Err(_) => return,
};
let Some(_kmn) = tab.get(name) else {
return;
};
let arc_to_remove = tab.get(name).map(|kmn| kmn.keymap.clone());
let shared_count = if let Some(ref arc) = arc_to_remove {
tab.values()
.filter(|kmn| Arc::ptr_eq(&kmn.keymap, arc))
.count()
} else {
0
};
if shared_count <= 1 {
tab.remove(name); }
}
pub fn freekeymapnamnode(hn: &str) {
keymapnamtab().lock().unwrap().remove(hn);
}
pub fn newkeytab() -> HashMap<Vec<u8>, KeyBinding> {
HashMap::new()
}
pub fn makekeynode(t: Thingy, str: String) -> KeyBinding {
KeyBinding {
bind: Some(t),
str: Some(str),
prefixct: 0,
}
}
impl Default for Keymap {
fn default() -> Self {
Keymap {
first: std::array::from_fn(|_| None),
multi: HashMap::new(),
primary: None,
flags: 0,
rc: 0,
}
}
}
impl Keymap {
pub fn new() -> Self {
Self::default()
}
pub fn bind_char(&mut self, c: u8, thingy: Thingy) {
self.first[c as usize] = Some(thingy);
}
pub fn unbind_char(&mut self, c: u8) {
self.first[c as usize] = None;
}
pub fn bind_seq(&mut self, seq: &[u8], thingy: Thingy) {
if seq.len() == 1 {
self.bind_char(seq[0], thingy);
} else {
for i in 1..seq.len() {
let prefix = &seq[..i];
self.multi
.entry(prefix.to_vec())
.and_modify(|kb| kb.prefixct += 1)
.or_insert(KeyBinding {
bind: None,
str: None,
prefixct: 1,
});
}
self.multi.insert(
seq.to_vec(),
KeyBinding {
bind: Some(thingy),
str: None,
prefixct: 0,
},
);
}
}
pub fn bind_str(&mut self, seq: &[u8], s: String) {
if seq.len() == 1 {
}
for i in 1..seq.len() {
let prefix = &seq[..i];
self.multi
.entry(prefix.to_vec())
.and_modify(|kb| kb.prefixct += 1)
.or_insert(KeyBinding {
bind: None,
str: None,
prefixct: 1,
});
}
self.multi.insert(
seq.to_vec(),
KeyBinding {
bind: None,
str: Some(s),
prefixct: 0,
},
);
}
pub fn unbind_seq(&mut self, seq: &[u8]) {
if seq.len() == 1 {
self.unbind_char(seq[0]);
} else {
if self.multi.remove(seq).is_some() {
for i in 1..seq.len() {
let prefix = &seq[..i];
if let Some(kb) = self.multi.get_mut(prefix) {
kb.prefixct -= 1;
if kb.prefixct == 0 && kb.bind.is_none() && kb.str.is_none() {
}
}
}
}
}
}
pub fn lookup_char(&self, c: u8) -> Option<&Thingy> {
self.first[c as usize].as_ref()
}
pub fn lookup_seq(&self, seq: &[u8]) -> Option<&KeyBinding> {
if seq.len() == 1 {
None
} else {
self.multi.get(seq)
}
}
pub fn is_prefix(&self, seq: &[u8]) -> bool {
if seq.len() == 1 {
self.multi.keys().any(|k| k.len() > 1 && k[0] == seq[0])
} else {
self.multi
.get(seq)
.map(|kb| kb.prefixct > 0)
.unwrap_or(false)
}
}
}
pub fn freekeynode(hn: KeyBinding) {
if let Some(t) = hn.bind {
crate::ported::zle::zle_thingy::unrefthingy(&t.nam);
}
}
pub fn newkeymap(tocopy: Option<&Keymap>, _kmname: &str) -> Arc<Keymap> {
let mut km = Keymap::default();
if let Some(src) = tocopy {
for i in 0..256 {
km.first[i] = src.first[i].clone(); }
km.multi = src.multi.clone();
}
Arc::new(km)
}
pub fn scancopykeys(_kb: &KeyBinding) { }
#[allow(unused_variables)]
pub fn deletekeymap(km: Arc<Keymap>) { }
pub fn scankeymap(km: &Keymap, sort: i32) -> Vec<Vec<u8>> {
let mut seqs: Vec<Vec<u8>> = Vec::new();
for (i, t) in km.first.iter().enumerate() {
if t.is_some() {
seqs.push(vec![i as u8]);
}
}
let mut multi_keys: Vec<Vec<u8>> = km.multi.keys().cloned().collect();
if sort != 0 {
multi_keys.sort();
}
seqs.extend(multi_keys);
seqs
}
pub fn scankeys(_kb: &KeyBinding) -> Vec<u8> {
Vec::new()
}
pub fn openkeymap(name: &str) -> Option<Arc<Keymap>> {
keymapnamtab()
.lock()
.unwrap()
.get(name)
.map(|n| n.keymap.clone())
}
pub fn unlinkkeymap(name: &str, ignm: i32) -> i32 {
let mut tab = keymapnamtab().lock().unwrap();
match tab.get(name) {
None => 2, Some(n) if ignm == 0 && (n.flags & KMN_IMMORTAL) != 0 => 1, Some(_) => {
tab.remove(name); 0
}
}
}
pub fn linkkeymap(km: Arc<Keymap>, name: &str, imm: i32) -> i32 {
let mut tab = keymapnamtab().lock().unwrap();
if let Some(existing) = tab.get_mut(name) {
if existing.flags & KMN_IMMORTAL != 0 {
return 1;
}
if Arc::ptr_eq(&existing.keymap, &km) {
return 0;
}
existing.keymap = km;
} else {
let mut n = KeymapName {
nam: name.to_string(),
flags: 0,
keymap: km,
};
if imm != 0 {
n.flags |= KMN_IMMORTAL;
}
tab.insert(name.to_string(), n);
}
drop(tab);
refkeymap_by_name(name); 0 }
pub fn refkeymap(km: &mut Keymap) {
km.rc += 1; }
pub fn unrefkeymap(km: &mut Keymap) -> i32 {
km.rc -= 1; if km.rc == 0 {
return 0; }
km.rc }
pub fn selectkeymap(name: &str, fb: i32) -> i32 {
let mut km = openkeymap(name); let mut resolved = name.to_string();
if km.is_none() {
if fb == 0 {
return 1; }
km = openkeymap(".safe"); if km.is_none() {
return 1;
}
resolved = ".safe".to_string();
}
*curkeymapname() = resolved;
*curkeymap.lock().unwrap() = km;
0 }
pub fn selectlocalmap(m: Option<Arc<Keymap>>) {
let oldm = {
let mut g = LOCALKEYMAP.lock().unwrap();
let prev = g.take();
*g = m.clone();
prev
};
if oldm.is_some() && m.is_none() {
let _ = selectkeymap("main", 1);
}
}
pub fn reselectkeymap() {
let name = curkeymapname().clone();
selectkeymap(&name, 1);
}
pub fn keybind(km: &Keymap, seq: &[u8]) -> (Option<Thingy>, Option<String>) {
let single = if seq.len() == 1 {
Some(seq[0])
} else if seq.len() == 2 && seq[0] == 0x83 {
Some(seq[1] ^ 32) } else {
None
};
if let Some(f) = single {
if let Some(bind) = km.first[f as usize].as_ref() {
return (Some(bind.clone()), None);
}
}
match km.multi.get(seq) {
None => (None, None), Some(k) => (k.bind.clone(), k.str.clone()), }
}
pub fn keyisprefix(km: &Keymap, seq: &[u8]) -> i32 {
if seq.is_empty() {
return 1;
}
let single = if seq.len() == 1 {
Some(seq[0])
} else if seq.len() == 2 && seq[0] == 0x83 {
Some(seq[1] ^ 32)
} else {
None
};
if let Some(f) = single {
if km.first[f as usize].is_some() {
return 0;
}
}
match km.multi.get(seq) {
Some(kb) if kb.prefixct > 0 => 1,
_ => 0,
}
}
pub fn bin_bindkey(
name: &str,
args: &[String], ops: &options,
_func: i32,
) -> i32 {
use crate::ported::zsh_h::{OPT_ARG, OPT_ISSET};
static KEYMAPS_INIT: std::sync::Once = std::sync::Once::new();
KEYMAPS_INIT.call_once(|| {
default_bindings();
});
#[derive(Clone, Copy)]
enum Op {
LsMaps,
DelAll,
Del,
Link,
New,
Meta,
Bind,
}
struct Opn {
o: u8,
selp: bool,
func: Op,
min: i32,
max: i32,
}
static OPNS: &[Opn] = &[
Opn {
o: b'l',
selp: false,
func: Op::LsMaps,
min: 0,
max: -1,
},
Opn {
o: b'd',
selp: false,
func: Op::DelAll,
min: 0,
max: 0,
},
Opn {
o: b'D',
selp: false,
func: Op::Del,
min: 1,
max: -1,
},
Opn {
o: b'A',
selp: false,
func: Op::Link,
min: 2,
max: 2,
},
Opn {
o: b'N',
selp: false,
func: Op::New,
min: 1,
max: 2,
},
Opn {
o: b'm',
selp: true,
func: Op::Meta,
min: 0,
max: 0,
},
Opn {
o: b'r',
selp: true,
func: Op::Bind,
min: 1,
max: -1,
},
Opn {
o: b's',
selp: true,
func: Op::Bind,
min: 2,
max: -1,
},
Opn {
o: 0,
selp: true,
func: Op::Bind,
min: 0,
max: -1,
},
];
let mut idx = OPNS.len() - 1;
for (i, op) in OPNS.iter().enumerate() {
if op.o != 0 && OPT_ISSET(ops, op.o) {
idx = i;
break;
}
}
let op = &OPNS[idx];
if op.o != 0 {
for opp in OPNS.iter().skip(idx + 1) {
if opp.o != 0 && OPT_ISSET(ops, opp.o) {
eprintln!("{}: incompatible operation selection options", name);
return 1;
}
}
}
let nsel = (OPT_ISSET(ops, b'e') as i32)
+ (OPT_ISSET(ops, b'v') as i32)
+ (OPT_ISSET(ops, b'a') as i32)
+ (OPT_ISSET(ops, b'M') as i32);
if !op.selp && nsel != 0 {
eprintln!("{}: keymap cannot be selected with -{}", name, op.o as char);
return 1;
}
if nsel > 1 {
eprintln!("{}: incompatible keymap selection options", name);
return 1;
}
let kmname: Option<String> = if op.selp {
let nm = if OPT_ISSET(ops, b'e') {
"emacs".to_string()
} else if OPT_ISSET(ops, b'v') {
"viins".to_string()
} else if OPT_ISSET(ops, b'a') {
"vicmd".to_string()
} else if OPT_ISSET(ops, b'M') {
OPT_ARG(ops, b'M')
.map(|s| s.to_string())
.unwrap_or_else(|| "main".to_string())
} else {
"main".to_string()
};
let km = match openkeymap(&nm) {
Some(k) => k,
None => {
eprintln!("{}: no such keymap `{}'", name, nm);
return 1;
}
};
if OPT_ISSET(ops, b'e') || OPT_ISSET(ops, b'v') {
linkkeymap(km, "main", 0);
}
Some(nm)
} else {
None
};
let argc = args.len() as i32;
if op.o == 0 && (args.is_empty() || args.len() < 2) {
if OPT_ISSET(ops, b'e') || OPT_ISSET(ops, b'v') {
return 0;
}
return bin_bindkey_list(name, kmname.as_deref(), None, args, ops, 0);
}
if argc < op.min {
eprintln!("{}: not enough arguments for -{}", name, op.o as char);
return 1;
}
if op.max != -1 && argc > op.max {
eprintln!("{}: too many arguments for -{}", name, op.o as char);
return 1;
}
let func_i: i32 = op.o as i32;
let km_ref: Option<&Keymap> = None; let km_str = kmname.as_deref();
match op.func {
Op::LsMaps => bin_bindkey_lsmaps(name, km_str, km_ref, args, ops, func_i),
Op::DelAll => bin_bindkey_delall(name, km_str, km_ref, args, ops, func_i),
Op::Del => bin_bindkey_del(name, km_str, km_ref, args, ops, func_i),
Op::Link => bin_bindkey_link(name, km_str, km_ref, args, ops, func_i),
Op::New => bin_bindkey_new(name, km_str, km_ref, args, ops, func_i),
Op::Meta => bin_bindkey_meta(name, km_str, km_ref, args, ops, func_i),
Op::Bind => bin_bindkey_bind(name, km_str, km_ref, args, ops, func_i),
}
}
pub fn bin_bindkey_lsmaps(
_name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
_argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
let snapshot: Vec<(String, std::sync::Arc<Keymap>)> = {
let g = keymapnamtab().lock().unwrap();
g.iter().map(|(n, k)| (n.clone(), k.keymap.clone())).collect()
};
let mut primary_for: std::collections::HashMap<usize, String> = std::collections::HashMap::new();
for (name, arc) in &snapshot {
let id = std::sync::Arc::as_ptr(arc) as usize;
if let Some(p) = &arc.primary {
primary_for.insert(id, p.clone());
} else {
primary_for.entry(id).or_insert_with(|| name.clone());
}
}
let mut names: Vec<(String, std::sync::Arc<Keymap>)> = snapshot;
names.sort_by(|a, b| a.0.cmp(&b.0));
for (name, arc) in &names {
let id = std::sync::Arc::as_ptr(arc) as usize;
let p = primary_for.get(&id).cloned().unwrap_or_else(|| name.clone());
if p == *name {
println!("{}", name); } else {
println!("{} -> {}", name, p); }
}
0
}
pub fn scanlistmaps() -> Vec<String> {
keymapnamtab().lock().unwrap().keys().cloned().collect()
}
pub fn bin_bindkey_delall(
name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
_argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
if openkeymap(name).is_none() {
return 1;
}
0
}
pub fn bin_bindkey_del(
_name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
if argv.is_empty() {
return 1;
}
let mut ret = 0;
for arg in argv {
match unlinkkeymap(arg, 0) {
0 => {}
_ => ret = 1,
}
}
ret
}
pub fn bin_bindkey_link(
_name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
if argv.len() < 2 {
return 1;
}
let Some(km) = openkeymap(&argv[0]) else {
return 1;
};
if linkkeymap(km, &argv[1], 0) != 0 {
return 1;
}
0
}
pub fn bin_bindkey_new(
_name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
if argv.is_empty() {
return 1;
}
let blocked = keymapnamtab()
.lock()
.unwrap()
.get(&argv[0])
.map(|n| n.flags & KMN_IMMORTAL != 0)
.unwrap_or(false);
if blocked {
return 1; }
let template = if argv.len() >= 2 {
let km = openkeymap(&argv[1]);
if km.is_none() {
return 1; }
km
} else {
None
};
let new_km = newkeymap(template.as_deref(), &argv[0]); linkkeymap(new_km, &argv[0], 0);
0 }
pub fn bin_bindkey_meta(
name: &str,
kmname: Option<&str>,
_km_arg: Option<&Keymap>,
_argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
use super::zle_bindings::METABIND;
use super::zle_thingy::{refthingy, Thingy};
let target = kmname.unwrap_or(name);
let km_arc = match openkeymap(target) {
Some(k) => k,
None => return 1,
};
for i in 128usize..256 {
let default_name = METABIND[i - 128]; if default_name == "undefined-key" {
continue;
}
let m = [0x83u8, (i as u8) ^ 32];
let (cur_fn, _str) = keybind(&km_arc, &m);
let should_rebind = match &cur_fn {
None => true,
Some(t) => t.nam == "self-insert",
};
if !should_rebind {
continue;
}
refthingy(default_name);
let new_thingy = Thingy {
nam: default_name.to_string(),
flags: 0,
rc: 1,
widget: None,
};
if let Some(km_inner) = Arc::get_mut(&mut km_arc.clone()) {
km_inner.bind_seq(&m, new_thingy);
} else {
let mut new_km: Keymap = (*km_arc).clone();
new_km.bind_seq(&m, new_thingy);
linkkeymap(Arc::new(new_km), target, 0);
}
}
0 }
pub fn bin_bindkey_bind(
name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
argv: &[String],
_ops: &options,
func: i32,
) -> i32 {
let Some(old_arc) = openkeymap(name) else {
return 1;
}; let func_c = if func == 0 { '\0' } else { func as u8 as char };
let needs_pairs = func_c == '\0' || func_c == 's';
if needs_pairs && (argv.len() % 2 != 0) {
return 1;
}
let mut km: Keymap = (*old_arc).clone();
let stride = if func_c == 'r' { 1 } else { 2 };
let mut i = 0;
while i + (stride - 1) < argv.len() {
let seq_bytes = argv[i].as_bytes();
let target = if stride == 2 {
Some(argv[i + 1].clone())
} else {
None
};
let kb_value: KeyBinding = match func_c {
'r' => KeyBinding {
bind: None,
str: None,
prefixct: 0,
}, 's' => KeyBinding {
bind: None,
str: target,
prefixct: 0,
},
_ => KeyBinding {
bind: target.map(|n| Thingy::builtin(&n)),
str: None,
prefixct: 0,
},
};
if seq_bytes.len() == 1 {
km.first[seq_bytes[0] as usize] = kb_value.bind.clone();
} else {
km.multi.insert(seq_bytes.to_vec(), kb_value); }
i += stride;
}
let new_arc = Arc::new(km);
if let Ok(mut tab) = keymapnamtab().lock() {
let names_to_update: Vec<String> = tab
.iter()
.filter(|(_, kmn)| Arc::ptr_eq(&kmn.keymap, &old_arc))
.map(|(n, _)| n.clone())
.collect();
for n in names_to_update {
if let Some(kmn) = tab.get_mut(&n) {
kmn.keymap = new_arc.clone();
}
}
}
0 }
pub fn scanremoveprefix(km: &mut Keymap, prefix: &[u8]) {
let to_remove: Vec<Vec<u8>> = km
.multi
.keys()
.filter(|k| k.starts_with(prefix))
.cloned()
.collect();
for k in to_remove {
km.unbind_seq(&k);
}
}
pub fn bin_bindkey_list(
name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
_argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
let Some(km) = openkeymap(name) else {
return 1;
}; let mut stdout = std::io::stdout().lock();
for (i, slot) in km.first.iter().enumerate() {
if let Some(t) = slot {
let _ = write!(stdout, "bindkey -K {} ", name);
if i < 0x20 {
let _ = write!(stdout, "\"^{}\"", (i as u8 + b'@') as char);
} else if i == 0x7f {
let _ = write!(stdout, "\"^?\"");
} else if i < 0x80 {
let _ = write!(stdout, "\"{}\"", i as u8 as char);
} else {
let _ = write!(stdout, "\"\\M-{}\"", (i as u8 ^ 0x80) as char);
}
let _ = writeln!(stdout, " {}", t.nam);
}
}
for (seq, kb) in km.multi.iter() {
let _ = write!(stdout, "bindkey -K {} \"", name);
for &b in seq {
if b < 0x20 {
let _ = write!(stdout, "^{}", (b + b'@') as char);
} else if b == 0x7f {
let _ = write!(stdout, "^?");
} else if b < 0x80 {
let _ = write!(stdout, "{}", b as char);
} else {
let _ = write!(stdout, "\\M-{}", (b ^ 0x80) as char);
}
}
let _ = write!(stdout, "\" ");
if let Some(t) = &kb.bind {
let _ = writeln!(stdout, "{}", t.nam);
} else if let Some(s) = &kb.str {
let _ = writeln!(stdout, "\"{}\"", s);
} else {
let _ = writeln!(stdout, "undefined-key");
}
}
0 }
pub fn scanbindlist(kb: &KeyBinding) -> Option<String> {
let mut out = String::new();
out.push('"');
out.push('"');
out.push(' ');
if let Some(t) = &kb.bind {
out.push_str(&t.nam);
} else if let Some(s) = &kb.str {
out.push('"');
out.push_str(s);
out.push('"');
} else {
out.push_str("undefined-key");
}
Some(out) }
pub fn add_cursor_char(buf: &mut Vec<u8>, c: u8) {
buf.push(c);
}
pub fn add_cursor_key(km: &mut Keymap, tccode: i32, thingy: Thingy, defchar: i32) {
use crate::ported::init::{tclen, tcstr};
use crate::ported::params::TERMFLAGS;
use crate::ported::zsh_h::{TERM_BAD, TERM_NOUP, TERM_UNKNOWN};
use std::sync::atomic::Ordering;
let cap_idx = tccode as usize;
let mut buf: Vec<u8> = Vec::with_capacity(8);
let mut ok = false;
let cap_present = {
let lens = tclen.lock().unwrap();
cap_idx < lens.len() && lens[cap_idx] > 0
};
let termflags = TERMFLAGS.load(Ordering::Relaxed);
let term_broken = termflags & (TERM_NOUP | TERM_BAD | TERM_UNKNOWN) != 0;
if cap_present && !term_broken {
let escape = tcstr.lock().unwrap()[cap_idx].clone();
buf.extend_from_slice(escape.as_bytes());
let len = buf.len();
if len >= 2 && (buf[0] != 0x83 || len >= 3) {
ok = true;
}
}
if !ok {
buf.clear();
buf.push(0x1b);
buf.push(b'[');
buf.push(defchar as u8);
}
km.bind_seq(&buf, thingy.clone());
if buf.len() == 3 && buf[0] == 0x1b && (buf[1] == b'[' || buf[1] == b'O') {
let mut alt = buf.clone();
alt[1] = if buf[1] == b'[' { b'O' } else { b'[' };
km.bind_seq(&alt, thingy);
}
}
pub fn default_bindings() {
for name in [
"emacs",
"vicmd",
"viins",
"menuselect",
"listscroll",
".safe",
] {
let mut km = Keymap::default();
km.primary = Some(name.to_string());
match name {
"emacs" => setup_emacs_keymap(&mut km),
"viins" => setup_viins_keymap(&mut km),
"vicmd" => setup_vicmd_keymap(&mut km),
_ => {}
}
let imm = if name == ".safe" { 1 } else { 0 };
linkkeymap(Arc::new(km), name, imm);
}
if let Some(emacs) = openkeymap("emacs") {
linkkeymap(emacs, "main", 0);
}
*curkeymap.lock().unwrap() = openkeymap("main"); *curkeymapname() = "main".to_string(); }
pub fn getrestchar_keybuf() -> i32 {
use crate::ported::zle::zle_main::{getbyte, ungetbyte, LASTCHAR_WIDE, LASTCHAR_WIDE_VALID};
use std::sync::atomic::Ordering;
LASTCHAR_WIDE_VALID.store(1, Ordering::SeqCst);
let keybuf_v = keybuf.lock().unwrap().clone();
let buflen = (keybuflen.load(Ordering::SeqCst) as usize).min(keybuf_v.len());
let mut bufind = 0usize;
let mut bytes: Vec<u8> = Vec::new();
loop {
let cur = if bufind < buflen {
let mut c = keybuf_v[bufind];
bufind += 1;
if c == 0x83 && bufind < buflen {
c = keybuf_v[bufind] ^ 32;
bufind += 1;
}
c
} else {
match getbyte(true) {
Some(b) => b,
None => {
LASTCHAR_WIDE.store(-1, Ordering::SeqCst);
return -1;
}
}
};
bytes.push(cur);
if let Ok(s) = std::str::from_utf8(&bytes) {
if let Some(c) = s.chars().next() {
LASTCHAR_WIDE.store(c as i32, Ordering::SeqCst);
return c as i32;
}
}
let lead = bytes[0];
let need = if lead < 0x80 {
1
} else if lead < 0xC0 {
1
} else if lead < 0xE0 {
2
} else if lead < 0xF0 {
3
} else {
4
};
if bytes.len() >= need {
if let Some(&last) = bytes.last() {
if bufind >= buflen && (last & 0xC0) != 0x80 {
ungetbyte(last);
}
}
LASTCHAR_WIDE.store(-1, Ordering::SeqCst);
return -1;
}
}
}
pub fn getkeymapcmd(km: &Keymap) -> Option<(super::zle_thingy::Thingy, Vec<u8>, Option<String>)> {
let mut buf: Vec<u8> = Vec::with_capacity(8); let mut last_match: Option<super::zle_thingy::Thingy> = None; let mut last_match_str: Option<String> = None;
let mut last_match_len = 0usize;
loop {
let do_keytmout = last_match.is_some();
let b = match super::zle_main::getbyte(do_keytmout) {
Some(b) => b,
None => break, };
buf.push(b);
let (current_match, current_str, 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, None, pfx)
} else {
let entry = km.multi.get(&buf[..]);
let m = entry.and_then(|e| e.bind.clone());
let s = entry.and_then(|e| e.str.clone());
let pfx = entry.map(|e| e.prefixct > 0).unwrap_or(false);
(m, s, pfx)
};
if let Some(t) = current_match {
last_match = Some(t);
last_match_str = current_str;
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();
super::zle_main::ungetbytes(&extra);
buf.truncate(last_match_len);
}
last_match.map(|t| (t, buf, last_match_str))
}
pub fn addkeybuf(c: i32) {
let c = c & 0xff;
let is_meta = imeta(c as u8); let mut buf = keybuf.lock().unwrap();
if is_meta {
buf.push(META as u8); buf.push((c ^ 32) as u8); } else {
buf.push(c as u8); }
}
pub fn getkeybuf(w: i32) -> i32 {
let _ = w; if let Some(b) = KUNGETBUF
.lock()
.unwrap()
.pop_front()
{
addkeybuf(b as i32);
b as i32
} else {
-1 }
}
pub fn ungetkeycmd() {
let buf = keybuf.lock().unwrap().clone();
ungetbytes_unmeta(&buf);
}
pub fn getkeycmd() -> Option<super::zle_thingy::Thingy> {
use super::zle_main::get_key_cmd;
let mut hops = 0; loop {
let func = get_key_cmd(); if func.is_none() {
return None;
}
let func = func.unwrap();
if func.nam.is_empty() {
hops += 1; if hops == 20 {
crate::ported::utils::zerr(
"string inserting another one too many times",
);
return None; }
continue; }
if func.nam == "execute-named-command" {
let mut resolved: Option<super::zle_thingy::Thingy> = None;
loop {
let name =
crate::ported::zle::zle_misc::executenamedcommand("execute: "); match name {
Some(n) if n == "execute-named-command" => continue, Some(n) => {
let lookup =
super::zle_thingy::thingytab().lock().unwrap().get(&n).cloned();
resolved = lookup;
break;
}
None => {
let undef = super::zle_thingy::thingytab()
.lock()
.unwrap()
.get("undefined-key")
.cloned();
resolved = undef;
break;
}
}
}
if let Some(ref f) = resolved {
if f.nam != "execute-last-named-cmd" {
*crate::ported::zle::zle_keymap::lastnamed
.lock()
.unwrap() = Some(f.clone()); }
}
return resolved;
}
if func.nam == "execute-last-named-cmd" {
return crate::ported::zle::zle_keymap::lastnamed
.lock()
.unwrap()
.clone();
}
return Some(func); }
}
pub fn zlesetkeymap(mode: i32) {
let kmname = if mode == 1 { "viins" } else { "emacs" };
if let Some(km) = openkeymap(kmname) {
linkkeymap(km, "main", 0);
}
}
pub fn readcommand() -> i32 {
let Some(name): Option<String> = None else {
return 1;
}; let _ = crate::ported::params::setsparam("REPLY", &name); 0 }
pub static CURKEYMAPNAME: OnceLock<Mutex<String>> =
OnceLock::new();
pub static curkeymap: Mutex<Option<Arc<Keymap>>> = Mutex::new(None);
pub static keybuf: Mutex<Vec<u8>> = Mutex::new(Vec::new());
pub static keybuflen: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
static KEYMAPNAMTAB: OnceLock<Mutex<HashMap<String, KeymapName>>> = OnceLock::new();
#[derive(Debug, Clone)]
pub struct Keymap {
pub first: [Option<Thingy>; 256],
pub multi: HashMap<Vec<u8>, KeyBinding>,
pub primary: Option<String>,
pub flags: i32,
pub rc: i32,
}
#[derive(Debug, Clone)]
pub struct KeyBinding {
pub bind: Option<Thingy>, pub str: Option<String>, pub prefixct: i32, }
pub static LOCALKEYMAP: Mutex<Option<Arc<Keymap>>> = Mutex::new(None);
pub fn curkeymapname() -> std::sync::MutexGuard<'static, String> {
CURKEYMAPNAME
.get_or_init(|| Mutex::new(String::from("main")))
.lock()
.unwrap()
}
pub fn setup_emacs_keymap(km: &mut Keymap) {
for i in 0..32 {
km.bind_char(i as u8, Thingy::builtin(EMACSBIND[i]));
}
for i in 32u8..=255u8 {
km.bind_char(i, Thingy::builtin("self-insert"));
}
km.bind_char(0x7F, Thingy::builtin(EMACSBIND[8]));
km.bind_seq(b"\x1b[A", Thingy::builtin("up-line-or-history"));
km.bind_seq(b"\x1b[B", Thingy::builtin("down-line-or-history"));
km.bind_seq(b"\x1b[C", Thingy::builtin("forward-char"));
km.bind_seq(b"\x1b[D", Thingy::builtin("backward-char"));
km.bind_seq(b"\x1bOA", Thingy::builtin("up-line-or-history"));
km.bind_seq(b"\x1bOB", Thingy::builtin("down-line-or-history"));
km.bind_seq(b"\x1bOC", Thingy::builtin("forward-char"));
km.bind_seq(b"\x1bOD", Thingy::builtin("backward-char"));
km.bind_seq(b"\x18*", Thingy::builtin("expand-word"));
km.bind_seq(b"\x18g", Thingy::builtin("list-expand"));
km.bind_seq(b"\x18G", Thingy::builtin("list-expand"));
km.bind_seq(b"\x18\x0e", Thingy::builtin("infer-next-history"));
km.bind_seq(b"\x18\x0b", Thingy::builtin("kill-buffer"));
km.bind_seq(b"\x18\x06", Thingy::builtin("vi-find-next-char"));
km.bind_seq(b"\x18\x0f", Thingy::builtin("overwrite-mode"));
km.bind_seq(b"\x18\x15", Thingy::builtin("undo"));
km.bind_seq(b"\x18\x16", Thingy::builtin("vi-cmd-mode"));
km.bind_seq(b"\x18\x0a", Thingy::builtin("vi-join"));
km.bind_seq(b"\x18\x02", Thingy::builtin("vi-match-bracket"));
km.bind_seq(
b"\x18s",
Thingy::builtin("history-incremental-search-forward"),
);
km.bind_seq(
b"\x18r",
Thingy::builtin("history-incremental-search-backward"),
);
km.bind_seq(b"\x18u", Thingy::builtin("undo"));
km.bind_seq(b"\x18\x18", Thingy::builtin("exchange-point-and-mark"));
km.bind_seq(b"\x18=", Thingy::builtin("what-cursor-position"));
km.bind_seq(b"\x1b[200~", Thingy::builtin("bracketed-paste"));
for i in 0..128 {
let name = METABIND[i];
if name == "undefined-key" {
continue;
}
km.bind_seq(&[0x1b, i as u8], Thingy::builtin(name));
}
}
pub fn setup_viins_keymap(km: &mut Keymap) {
for i in 0..32 {
km.bind_char(i as u8, Thingy::builtin(VIINSBIND[i]));
}
for i in 32u8..=255u8 {
km.bind_char(i, Thingy::builtin("self-insert"));
}
km.bind_char(0x7F, Thingy::builtin(VIINSBIND[8]));
km.bind_seq(b"\x1b[A", Thingy::builtin("up-line-or-history"));
km.bind_seq(b"\x1b[B", Thingy::builtin("down-line-or-history"));
km.bind_seq(b"\x1b[C", Thingy::builtin("vi-forward-char"));
km.bind_seq(b"\x1b[D", Thingy::builtin("vi-backward-char"));
km.bind_seq(b"\x1bOA", Thingy::builtin("up-line-or-history"));
km.bind_seq(b"\x1bOB", Thingy::builtin("down-line-or-history"));
km.bind_seq(b"\x1bOC", Thingy::builtin("vi-forward-char"));
km.bind_seq(b"\x1bOD", Thingy::builtin("vi-backward-char"));
km.bind_seq(b"\x1b[200~", Thingy::builtin("bracketed-paste"));
}
pub fn setup_vicmd_keymap(km: &mut Keymap) {
for i in 0..128 {
km.bind_char(i as u8, Thingy::builtin(VICMDBIND[i]));
}
for i in 128u8..=255u8 {
km.bind_char(i, Thingy::builtin("undefined-key"));
}
km.bind_seq(b"\x1b[A", Thingy::builtin("up-line-or-history"));
km.bind_seq(b"\x1b[B", Thingy::builtin("down-line-or-history"));
km.bind_seq(b"\x1b[C", Thingy::builtin("vi-forward-char"));
km.bind_seq(b"\x1b[D", Thingy::builtin("vi-backward-char"));
km.bind_seq(b"\x1bOA", Thingy::builtin("up-line-or-history"));
km.bind_seq(b"\x1bOB", Thingy::builtin("down-line-or-history"));
km.bind_seq(b"\x1bOC", Thingy::builtin("vi-forward-char"));
km.bind_seq(b"\x1bOD", Thingy::builtin("vi-backward-char"));
km.bind_seq(b"ga", Thingy::builtin("what-cursor-position"));
km.bind_seq(b"ge", Thingy::builtin("vi-backward-word-end"));
km.bind_seq(b"gE", Thingy::builtin("vi-backward-blank-word-end"));
km.bind_seq(b"gg", Thingy::builtin("beginning-of-buffer-or-history"));
km.bind_seq(b"gu", Thingy::builtin("vi-down-case"));
km.bind_seq(b"gU", Thingy::builtin("vi-up-case"));
km.bind_seq(b"g~", Thingy::builtin("vi-oper-swap-case"));
km.bind_seq(b"\x1b[200~", Thingy::builtin("bracketed-paste"));
}
pub(crate) fn keymapnamtab() -> &'static Mutex<HashMap<String, KeymapName>> {
KEYMAPNAMTAB.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emacs_default_has_quoted_insert_undo_yank_pop() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
createkeymapnamtab();
default_bindings();
let km = openkeymap("emacs").expect("emacs keymap created");
assert_eq!(
km.lookup_char(0x16).map(|t| t.nam.as_str()),
Some("quoted-insert")
);
assert_eq!(km.lookup_char(0x1F).map(|t| t.nam.as_str()), Some("undo"));
assert_eq!(
km.lookup_seq(b"\x1by")
.and_then(|kb| kb.bind.as_ref())
.map(|t| t.nam.as_str()),
Some("yank-pop")
);
}
#[test]
fn emacs_default_has_history_search_and_insert_last_word() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
createkeymapnamtab();
default_bindings();
let km = openkeymap("emacs").expect("emacs keymap created");
assert_eq!(
km.lookup_seq(b"\x1b.")
.and_then(|kb| kb.bind.as_ref())
.map(|t| t.nam.as_str()),
Some("insert-last-word")
);
assert_eq!(
km.lookup_seq(b"\x1bp")
.and_then(|kb| kb.bind.as_ref())
.map(|t| t.nam.as_str()),
Some("history-search-backward")
);
assert_eq!(
km.lookup_seq(b"\x18\x18")
.and_then(|kb| kb.bind.as_ref())
.map(|t| t.nam.as_str()),
Some("exchange-point-and-mark")
);
}
#[test]
fn vicmd_default_has_visual_marks_indent() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
createkeymapnamtab();
default_bindings();
let km = openkeymap("vicmd").expect("vicmd keymap created");
assert_eq!(
km.lookup_char(b'v').map(|t| t.nam.as_str()),
Some("visual-mode")
);
assert_eq!(
km.lookup_char(b'V').map(|t| t.nam.as_str()),
Some("visual-line-mode")
);
assert_eq!(
km.lookup_char(b'm').map(|t| t.nam.as_str()),
Some("vi-set-mark")
);
assert_eq!(
km.lookup_char(b'>').map(|t| t.nam.as_str()),
Some("vi-indent")
);
assert_eq!(
km.lookup_char(b'~').map(|t| t.nam.as_str()),
Some("vi-swap-case")
);
assert_eq!(
km.lookup_char(b'%').map(|t| t.nam.as_str()),
Some("vi-match-bracket")
);
}
#[test]
fn viins_default_matches_viinsbind_table() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
createkeymapnamtab();
default_bindings();
let km = openkeymap("viins").expect("viins keymap created");
assert_eq!(
km.lookup_char(0x12).map(|t| t.nam.as_str()),
Some("redisplay")
);
assert_eq!(
km.lookup_char(0x16).map(|t| t.nam.as_str()),
Some("vi-quoted-insert")
);
assert_eq!(
km.lookup_char(0x01).map(|t| t.nam.as_str()),
Some("self-insert")
);
assert_eq!(
km.lookup_char(0x1B).map(|t| t.nam.as_str()),
Some("vi-cmd-mode")
);
assert_eq!(
km.lookup_char(0x0D).map(|t| t.nam.as_str()),
Some("accept-line")
);
assert_eq!(
km.lookup_char(0x0A).map(|t| t.nam.as_str()),
Some("accept-line")
);
}
#[test]
fn refkeymap_increments_rc() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
assert_eq!(km.rc, 0);
refkeymap(&mut km);
assert_eq!(km.rc, 1);
refkeymap(&mut km);
assert_eq!(km.rc, 2);
}
#[test]
fn unrefkeymap_decrements_returns_new_count() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.rc = 3;
let r = unrefkeymap(&mut km);
assert_eq!(r, 2);
assert_eq!(km.rc, 2);
let r = unrefkeymap(&mut km);
assert_eq!(r, 1);
}
#[test]
fn unrefkeymap_returns_zero_at_last_ref() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.rc = 1;
assert_eq!(unrefkeymap(&mut km), 0);
assert_eq!(km.rc, 0);
}
fn dummy_thingy() -> Thingy {
Thingy::new("test")
}
#[test]
fn keyisprefix_empty_seq() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let km = Keymap::default();
assert_eq!(keyisprefix(&km, b""), 1);
}
#[test]
fn keyisprefix_single_byte_bound_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.bind_char(b'a', dummy_thingy());
assert_eq!(keyisprefix(&km, b"a"), 0);
}
#[test]
fn keyisprefix_single_byte_unbound() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let km = Keymap::default();
assert_eq!(keyisprefix(&km, b"x"), 0);
}
#[test]
fn keyisprefix_seq_is_real_prefix() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.bind_seq(b"ab", dummy_thingy());
assert_eq!(keyisprefix(&km, b"a"), 1);
}
#[test]
fn keyisprefix_seq_is_complete_binding() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.bind_seq(b"xyz", dummy_thingy());
assert_eq!(keyisprefix(&km, b"xyz"), 0);
}
#[test]
fn keyisprefix_meta_pair_decoded() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.bind_char(b'A', dummy_thingy());
assert_eq!(keyisprefix(&km, &[0x83, 0x61]), 0);
}
#[test]
fn keybind_single_byte_returns_first_bound_thingy() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.bind_char(b'q', Thingy::new("quit-widget"));
let (bind, send) = keybind(&km, b"q");
assert!(bind.is_some());
assert_eq!(bind.as_ref().unwrap().nam, "quit-widget");
assert!(send.is_none(), "no str on first[] path");
}
#[test]
fn keybind_meta_pair_decodes_to_single_byte() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.bind_char(b'A', Thingy::new("uppercase-A-widget"));
let (bind, _) = keybind(&km, &[0x83, 0x61]);
assert_eq!(
bind.as_ref().map(|t| t.nam.as_str()),
Some("uppercase-A-widget")
);
}
#[test]
fn keybind_unbound_byte_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let km = Keymap::default();
let (bind, send) = keybind(&km, b"z");
assert!(
bind.is_none(),
"unbound byte → t_undefinedkey sentinel (None)"
);
assert!(send.is_none());
}
#[test]
fn keybind_multi_byte_sequence_via_multi_map() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.multi.insert(
b"\x1b[A".to_vec(),
KeyBinding {
bind: Some(Thingy::new("up-line")),
str: None,
prefixct: 0,
},
);
let (bind, _) = keybind(&km, b"\x1b[A");
assert_eq!(bind.as_ref().map(|t| t.nam.as_str()), Some("up-line"));
}
#[test]
fn keybind_returns_send_string_when_multi_entry_has_str() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut km = Keymap::default();
km.multi.insert(
b"\x1b[Z".to_vec(),
KeyBinding {
bind: None,
str: Some("hello".to_string()),
prefixct: 0,
},
);
let (bind, send) = keybind(&km, b"\x1b[Z");
assert!(bind.is_none(), "send-string entries have no bind");
assert_eq!(send.as_deref(), Some("hello"));
}
#[test]
fn init_keymaps_seeds_keybuf_and_clears_lastnamed() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
init_keymaps();
assert!(
!keybuf.lock().unwrap().is_empty(),
"keybuf zshcalloc(keybufsz)"
);
assert!(
lastnamed.lock().unwrap().is_none(),
"lastnamed = t_undefinedkey (None)"
);
}
#[test]
fn cleanup_keymaps_drains_namtab_and_keybuf() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
init_keymaps();
assert!(!keybuf.lock().unwrap().is_empty());
cleanup_keymaps();
assert!(keybuf.lock().unwrap().is_empty(), "zfree(keybuf, ...)");
assert!(
keymapnamtab().lock().unwrap().is_empty(),
"deletehashtable(keymapnamtab)"
);
}
#[test]
fn addkeybuf_encodes_nul_byte_per_imeta() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _tg = TYPTAB_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
inittyptab();
keybuf.lock().unwrap().clear();
addkeybuf(0);
assert_eq!(*keybuf.lock().unwrap(), vec![0x83, 0x20],
"c:1721 — NUL must be Meta-encoded (was missed by old `c >= 0x83 && != 0x83 && != 0x84`)");
}
#[test]
fn addkeybuf_encodes_meta_byte_itself() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _tg = TYPTAB_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
inittyptab();
keybuf.lock().unwrap().clear();
addkeybuf(0x83);
assert_eq!(
*keybuf.lock().unwrap(),
vec![0x83, 0xa3],
"c:1721 — Meta byte (0x83) must itself be Meta-encoded"
);
}
#[test]
fn addkeybuf_encodes_pound_token_byte() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _tg = TYPTAB_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
inittyptab();
keybuf.lock().unwrap().clear();
addkeybuf(0x84);
assert_eq!(
*keybuf.lock().unwrap(),
vec![0x83, 0xa4],
"c:1721 — Pound (0x84) is IMETA per utils.c:4198, must be Meta-encoded"
);
}
#[test]
fn addkeybuf_passes_through_non_imeta_high_byte() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _tg = TYPTAB_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
inittyptab();
keybuf.lock().unwrap().clear();
addkeybuf(0xa3);
assert_eq!(
*keybuf.lock().unwrap(),
vec![0xa3],
"c:1721 — 0xa3 is NOT IMETA (past Marker=0xa2); must pass through"
);
keybuf.lock().unwrap().clear();
addkeybuf(0xff);
assert_eq!(
*keybuf.lock().unwrap(),
vec![0xff],
"c:1721 — 0xff is NOT IMETA; must pass through"
);
}
#[test]
fn addkeybuf_ascii_passes_through_literally() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _tg = TYPTAB_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
inittyptab();
for c in [0x01u8, 0x1f, 0x20, b'A', b'z', 0x7e, 0x7f] {
keybuf.lock().unwrap().clear();
addkeybuf(c as i32);
assert_eq!(
*keybuf.lock().unwrap(),
vec![c],
"c:1721 — ASCII byte 0x{:02x} must pass through",
c
);
}
}
#[test]
fn zle_keymap_corpus_newkeytab_is_empty() {
let _g = crate::test_util::global_state_lock();
let t = newkeytab();
assert!(t.is_empty());
}
#[test]
fn zle_keymap_corpus_newkeymap_returns_arc() {
let _g = crate::test_util::global_state_lock();
let km = newkeymap(None, "myname");
assert!(Arc::strong_count(&km) >= 1);
}
#[test]
fn zle_keymap_corpus_openkeymap_unknown_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert!(openkeymap("zshrs_never_keymap_xyz").is_none());
}
#[test]
fn zle_keymap_corpus_unlinkkeymap_missing_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_ne!(unlinkkeymap("never_keymap_xyz", 0), 0,
"unlinking nonexistent keymap = error");
}
#[test]
fn zle_keymap_corpus_selectkeymap_unknown_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_ne!(selectkeymap("zshrs_never_keymap_xyz", 0), 0);
}
}