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, OPT_ISSET};
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, Clone)]
#[allow(non_camel_case_types)]
pub struct bindstate {
pub flags: i32, pub kmname: String, pub firstseq: Vec<u8>, pub lastseq: Vec<u8>, pub bind: Option<Thingy>, pub str: Option<String>, pub prefix: Option<Vec<u8>>, pub prefixlen: usize, }
#[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,
func: &mut dyn FnMut(&[u8], Option<&Thingy>, Option<&str>),
) {
let mut skm_last: i32 = if sort != 0 { -1 } else { 255 };
let mut multi_keys: Vec<&Vec<u8>> = km.multi.keys().collect();
if sort != 0 {
multi_keys.sort();
}
for k_nam in multi_keys {
let kb = km.multi.get(k_nam).expect("key from iter");
scankeys(
k_nam,
kb.bind.as_ref(),
kb.str.as_deref(),
km,
&mut skm_last,
func,
);
}
if sort == 0 {
skm_last = -1;
}
while skm_last < 255 {
skm_last += 1;
if let Some(t) = &km.first[skm_last as usize] {
let m = [skm_last as u8];
func(&m, Some(t), None);
}
}
}
fn scankeys(
k_nam: &[u8],
k_bind: Option<&Thingy>,
k_str: Option<&str>,
km: &Keymap,
skm_last: &mut i32,
func: &mut dyn FnMut(&[u8], Option<&Thingy>, Option<&str>),
) {
let f = k_nam[0] as i32;
while *skm_last < f {
*skm_last += 1;
if *skm_last > 255 {
break;
}
if let Some(t) = &km.first[*skm_last as usize] {
let m = [*skm_last as u8];
func(&m, Some(t), None);
}
}
func(k_nam, k_bind, k_str);
}
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 bindkey(km: &mut Keymap, seq: &[u8], bind: Option<Thingy>, str: Option<String>) -> i32 {
if (km.flags & KM_IMMUTABLE) != 0 {
return 1;
}
if seq.is_empty() {
return 2;
}
match (bind, str, seq.len()) {
(Some(t), None, 1) => {
km.first[seq[0] as usize] = Some(t);
0
}
(Some(t), None, _) => {
for i in 1..seq.len() {
km.multi
.entry(seq[..i].to_vec())
.and_modify(|kb| kb.prefixct += 1)
.or_insert(KeyBinding {
bind: None,
str: None,
prefixct: 1,
});
}
km.multi.insert(
seq.to_vec(),
KeyBinding {
bind: Some(t),
str: None,
prefixct: 0,
},
);
0
}
(None, Some(s), _) => {
for i in 1..seq.len() {
km.multi
.entry(seq[..i].to_vec())
.and_modify(|kb| kb.prefixct += 1)
.or_insert(KeyBinding {
bind: None,
str: None,
prefixct: 1,
});
}
km.multi.insert(
seq.to_vec(),
KeyBinding {
bind: None,
str: Some(s),
prefixct: 0,
},
);
0
}
(None, None, _) => {
if seq.len() == 1 {
km.first[seq[0] as usize] = Some(Thingy::builtin("undefined-key"));
} else {
for i in 1..seq.len() {
km.multi
.entry(seq[..i].to_vec())
.and_modify(|kb| kb.prefixct += 1)
.or_insert(KeyBinding {
bind: None,
str: None,
prefixct: 1,
});
}
km.multi.insert(
seq.to_vec(),
KeyBinding {
bind: Some(Thingy::builtin("undefined-key")),
str: None,
prefixct: 0,
},
);
}
0
}
(Some(_), Some(_), _) => {
-1
}
}
}
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 list_verbose = OPT_ISSET(ops, b'L');
let mut ret = 0;
if !argv.is_empty() {
for a in argv {
let kmn = {
let g = keymapnamtab().lock().unwrap();
g.get(a).cloned()
};
match kmn {
None => {
eprintln!("{}: no such keymap: `{}'", name, a);
ret = 1;
}
Some(kmn) => {
scanlistmaps(&kmn, a, list_verbose);
}
}
}
} else {
let snapshot: Vec<(String, KeymapName)> = {
let g = keymapnamtab().lock().unwrap();
g.iter().map(|(n, k)| (n.clone(), k.clone())).collect()
};
let mut names: Vec<(String, KeymapName)> = snapshot;
names.sort_by(|a, b| a.0.cmp(&b.0));
for (n, kmn) in &names {
scanlistmaps(kmn, n, list_verbose);
}
}
ret
}
pub fn scanlistmaps(kmn: &KeymapName, n_nam: &str, list_verbose: bool) {
if list_verbose {
if n_nam == ".safe" {
return;
}
print!("bindkey -");
let primary_name = kmn.keymap.primary.as_deref();
let is_alias = primary_name.is_some() && primary_name != Some(n_nam);
if is_alias {
print!("A ");
let pn = primary_name.unwrap();
if pn.starts_with('-') {
print!("-- ");
}
print!("{} ", pn);
} else {
print!("N ");
if n_nam.starts_with('-') {
print!("-- ");
}
}
print!("{}", n_nam);
} else {
print!("{}", n_nam);
}
println!();
}
pub fn bin_bindkey_delall(
_name: &str,
_kmname: Option<&str>,
_km: Option<&Keymap>,
_argv: &[String],
_ops: &options,
_func: i32,
) -> i32 {
default_bindings();
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()) {
bindkey(km_inner, &m, Some(new_thingy), None);
} else {
let mut new_km: Keymap = (*km_arc).clone();
bindkey(&mut new_km, &m, Some(new_thingy), None);
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 lookup_name = kmname.unwrap_or("main");
let Some(old_arc) = openkeymap(lookup_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 = crate::ported::zle::zle_bindings::getkeystring(&argv[i]);
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); }
#[cfg(feature = "recorder")]
if func_c != 'r' && crate::recorder::is_enabled() {
let ctx = crate::recorder::recorder_ctx_global();
let seq_str = String::from_utf8_lossy(&seq_bytes);
let widget_default = String::new();
let widget_ref: &str = match func_c {
's' => "send-string", _ => argv.get(i + 1).unwrap_or(&widget_default).as_str(),
};
crate::recorder::emit_bindkey(&seq_str, widget_ref, ctx);
}
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 resolved_name: String = kmname
.map(|s| s.to_string())
.unwrap_or_else(|| curkeymapname().clone());
let Some(km) = openkeymap(&resolved_name) else {
eprintln!("{}: no such keymap `{}'", name, resolved_name);
return 1;
};
let mut bs = bindstate {
flags: if OPT_ISSET(ops, b'L') { BS_LIST } else { 0 },
kmname: resolved_name.clone(),
firstseq: Vec::new(),
lastseq: Vec::new(),
bind: None,
str: None,
prefix: None,
prefixlen: 0,
};
if !argv.is_empty() && !OPT_ISSET(ops, b'p') {
let seq = crate::ported::zle::zle_bindings::getkeystring(&argv[0]);
bs.flags |= BS_ALL;
bs.firstseq = seq.clone();
bs.lastseq = seq.clone();
let (bind, str_out) = keybind(&km, &seq);
bs.bind = bind;
bs.str = str_out;
bindlistout(&bs);
return 0;
}
if OPT_ISSET(ops, b'p') {
let arg0 = argv.first().map(|s| s.as_str()).unwrap_or("");
if arg0.is_empty() {
eprintln!("{}: option -p requires a prefix string", name);
return 1;
}
let pfx = crate::ported::zle::zle_bindings::getkeystring(arg0);
bs.prefixlen = pfx.len();
bs.prefix = Some(pfx);
}
bs.firstseq = Vec::new();
bs.lastseq = Vec::new();
bs.bind = None;
bs.str = None;
scankeymap(&km, 1, &mut |seq, bind, s| {
scanbindlist(seq, bind, s, &mut bs);
});
bindlistout(&bs);
0 }
pub fn scanbindlist(seq: &[u8], bind: Option<&Thingy>, str: Option<&str>, bs: &mut bindstate) {
if bs.prefixlen > 0 {
if let Some(p) = &bs.prefix {
if !seq.starts_with(p) || seq.len() == p.len() {
return;
}
}
}
let bind_eq = match (bind, &bs.bind) {
(Some(t1), Some(t2)) => t1.nam == t2.nam,
(None, None) => str == bs.str.as_deref(),
_ => false,
};
if bind_eq && seq.len() == 1 && bs.lastseq.len() == 1 {
let l = bs.lastseq[0] as i32;
let t = seq[0] as i32;
if t == l + 1 {
bs.lastseq = seq.to_vec();
return;
}
}
bindlistout(bs);
bs.firstseq = seq.to_vec();
bs.lastseq = seq.to_vec();
bs.bind = bind.cloned();
bs.str = str.map(|s| s.to_string());
}
pub fn bindlistout(bs: &bindstate) {
use std::io::Write;
let is_undefined = bs.str.is_none()
&& match &bs.bind {
None => true,
Some(t) => t.nam == "undefined-key",
};
if is_undefined && (bs.flags & BS_ALL) == 0 {
return;
}
let range = bs.firstseq != bs.lastseq;
let mut out = std::io::stdout().lock();
let mut nodash = true;
if (bs.flags & BS_LIST) != 0 {
let _ = write!(out, "bindkey ");
if range {
let _ = write!(out, "-R ");
}
if bs.bind.is_none() {
let _ = write!(out, "-s ");
}
if bs.kmname == "main" {
} else if bs.kmname == "vicmd" {
let _ = write!(out, "-a ");
} else {
let _ = write!(out, "-M {} ", bs.kmname);
nodash = false;
}
if nodash && bs.firstseq.first() == Some(&b'-') {
let _ = write!(out, "-- ");
}
}
let _ = write!(
out,
"{}",
crate::ported::zle::zle_utils::bindztrdup(&bs.firstseq),
);
if range {
let _ = write!(
out,
"-{}",
crate::ported::zle::zle_utils::bindztrdup(&bs.lastseq),
);
}
let _ = write!(out, " ");
if let Some(t) = &bs.bind {
let _ = writeln!(out, "{}", t.nam);
} else if let Some(s) = &bs.str {
let _ = writeln!(
out,
"{}",
crate::ported::zle::zle_utils::bindztrdup(s.as_bytes()),
);
} else {
let _ = writeln!(out, "undefined-key");
}
}
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);
}
bindkey(km, &buf, Some(thingy.clone()), None);
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'[' };
bindkey(km, &alt, Some(thingy), None);
}
}
pub fn default_bindings() {
let mut vmap = Keymap::default(); vmap.primary = Some("viins".to_string());
let mut emap = Keymap::default(); emap.primary = Some("emacs".to_string());
let mut amap = Keymap::default(); amap.primary = Some("vicmd".to_string());
let mut oppmap = Keymap::default(); oppmap.primary = Some("viopp".to_string());
let mut vismap = Keymap::default(); vismap.primary = Some("visual".to_string());
let mut smap = Keymap::default(); smap.primary = Some(".safe".to_string());
for i in 0..32 {
bindkey(
&mut vmap,
&[i as u8],
Some(Thingy::builtin(VIINSBIND[i])),
None,
);
bindkey(
&mut emap,
&[i as u8],
Some(Thingy::builtin(EMACSBIND[i])),
None,
);
}
for i in 32u8..=255u8 {
bindkey(&mut vmap, &[i], Some(Thingy::builtin("self-insert")), None);
bindkey(&mut emap, &[i], Some(Thingy::builtin("self-insert")), None);
}
bindkey(
&mut vmap,
&[0x7F],
Some(Thingy::builtin(VIINSBIND[8])),
None,
);
bindkey(
&mut emap,
&[0x7F],
Some(Thingy::builtin(EMACSBIND[8])),
None,
);
for i in 0..128 {
bindkey(
&mut amap,
&[i as u8],
Some(Thingy::builtin(VICMDBIND[i])),
None,
);
}
for i in 128u8..=255u8 {
bindkey(
&mut amap,
&[i],
Some(Thingy::builtin("undefined-key")),
None,
);
}
for i in 0u8..=255u8 {
bindkey(&mut smap, &[i], Some(Thingy::builtin(".self-insert")), None);
}
bindkey(
&mut smap,
&[b'\n'],
Some(Thingy::builtin(".accept-line")),
None,
);
bindkey(
&mut smap,
&[b'\r'],
Some(Thingy::builtin(".accept-line")),
None,
);
for kptr in [&mut vmap, &mut amap] {
add_cursor_key(
kptr,
crate::ported::zsh_h::TCUPCURSOR,
Thingy::builtin("up-line-or-history"),
b'A' as i32,
);
add_cursor_key(
kptr,
crate::ported::zsh_h::TCDOWNCURSOR,
Thingy::builtin("down-line-or-history"),
b'B' as i32,
);
add_cursor_key(
kptr,
crate::ported::zsh_h::TCLEFTCURSOR,
Thingy::builtin("vi-backward-char"),
b'D' as i32,
);
add_cursor_key(
kptr,
crate::ported::zsh_h::TCRIGHTCURSOR,
Thingy::builtin("vi-forward-char"),
b'C' as i32,
);
}
for kptr in [&mut oppmap, &mut vismap] {
add_cursor_key(
kptr,
crate::ported::zsh_h::TCUPCURSOR,
Thingy::builtin("up-line"),
b'A' as i32,
);
add_cursor_key(
kptr,
crate::ported::zsh_h::TCDOWNCURSOR,
Thingy::builtin("down-line"),
b'B' as i32,
);
bindkey(kptr, &[b'k'], Some(Thingy::builtin("up-line")), None); bindkey(kptr, &[b'j'], Some(Thingy::builtin("down-line")), None); bindkey(
kptr,
b"aa",
Some(Thingy::builtin("select-a-shell-word")),
None,
); bindkey(
kptr,
b"ia",
Some(Thingy::builtin("select-in-shell-word")),
None,
); bindkey(kptr, b"aw", Some(Thingy::builtin("select-a-word")), None); bindkey(kptr, b"iw", Some(Thingy::builtin("select-in-word")), None); bindkey(
kptr,
b"aW",
Some(Thingy::builtin("select-a-blank-word")),
None,
); bindkey(
kptr,
b"iW",
Some(Thingy::builtin("select-in-blank-word")),
None,
); }
bindkey(
&mut oppmap,
&[0x1B],
Some(Thingy::builtin("vi-cmd-mode")),
None,
);
bindkey(
&mut vismap,
&[0x1B],
Some(Thingy::builtin("deactivate-region")),
None,
);
bindkey(
&mut vismap,
&[b'o'],
Some(Thingy::builtin("exchange-point-and-mark")),
None,
);
bindkey(
&mut vismap,
&[b'p'],
Some(Thingy::builtin("put-replace-selection")),
None,
);
bindkey(
&mut vismap,
&[b'u'],
Some(Thingy::builtin("vi-down-case")),
None,
);
bindkey(
&mut vismap,
&[b'U'],
Some(Thingy::builtin("vi-up-case")),
None,
);
bindkey(
&mut vismap,
&[b'x'],
Some(Thingy::builtin("vi-delete")),
None,
);
bindkey(
&mut vismap,
&[b'~'],
Some(Thingy::builtin("vi-oper-swap-case")),
None,
);
bindkey(
&mut amap,
b"ga",
Some(Thingy::builtin("what-cursor-position")),
None,
);
bindkey(
&mut amap,
b"ge",
Some(Thingy::builtin("vi-backward-word-end")),
None,
);
bindkey(
&mut amap,
b"gE",
Some(Thingy::builtin("vi-backward-blank-word-end")),
None,
);
bindkey(
&mut amap,
b"gg",
Some(Thingy::builtin("beginning-of-buffer-or-history")),
None,
);
bindkey(
&mut amap,
b"gu",
Some(Thingy::builtin("vi-down-case")),
None,
);
bindkey(&mut amap, b"gU", Some(Thingy::builtin("vi-up-case")), None);
bindkey(
&mut amap,
b"g~",
Some(Thingy::builtin("vi-oper-swap-case")),
None,
);
bindkey(&mut amap, b"g~~", None, Some("g~g~".to_string()));
bindkey(&mut amap, b"guu", None, Some("gugu".to_string()));
bindkey(&mut amap, b"gUU", None, Some("gUgU".to_string()));
add_cursor_key(
&mut emap,
crate::ported::zsh_h::TCUPCURSOR,
Thingy::builtin("up-line-or-history"),
b'A' as i32,
);
add_cursor_key(
&mut emap,
crate::ported::zsh_h::TCDOWNCURSOR,
Thingy::builtin("down-line-or-history"),
b'B' as i32,
);
add_cursor_key(
&mut emap,
crate::ported::zsh_h::TCLEFTCURSOR,
Thingy::builtin("backward-char"),
b'D' as i32,
);
add_cursor_key(
&mut emap,
crate::ported::zsh_h::TCRIGHTCURSOR,
Thingy::builtin("forward-char"),
b'C' as i32,
);
bindkey(
&mut emap,
b"\x18*",
Some(Thingy::builtin("expand-word")),
None,
);
bindkey(
&mut emap,
b"\x18g",
Some(Thingy::builtin("list-expand")),
None,
);
bindkey(
&mut emap,
b"\x18G",
Some(Thingy::builtin("list-expand")),
None,
);
bindkey(
&mut emap,
b"\x18\x0e",
Some(Thingy::builtin("infer-next-history")),
None,
);
bindkey(
&mut emap,
b"\x18\x0b",
Some(Thingy::builtin("kill-buffer")),
None,
);
bindkey(
&mut emap,
b"\x18\x06",
Some(Thingy::builtin("vi-find-next-char")),
None,
);
bindkey(
&mut emap,
b"\x18\x0f",
Some(Thingy::builtin("overwrite-mode")),
None,
);
bindkey(&mut emap, b"\x18\x15", Some(Thingy::builtin("undo")), None);
bindkey(
&mut emap,
b"\x18\x16",
Some(Thingy::builtin("vi-cmd-mode")),
None,
);
bindkey(
&mut emap,
b"\x18\x0a",
Some(Thingy::builtin("vi-join")),
None,
);
bindkey(
&mut emap,
b"\x18\x02",
Some(Thingy::builtin("vi-match-bracket")),
None,
);
bindkey(
&mut emap,
b"\x18s",
Some(Thingy::builtin("history-incremental-search-forward")),
None,
);
bindkey(
&mut emap,
b"\x18r",
Some(Thingy::builtin("history-incremental-search-backward")),
None,
);
bindkey(&mut emap, b"\x18u", Some(Thingy::builtin("undo")), None);
bindkey(
&mut emap,
b"\x18\x18",
Some(Thingy::builtin("exchange-point-and-mark")),
None,
);
bindkey(
&mut emap,
b"\x18=",
Some(Thingy::builtin("what-cursor-position")),
None,
);
bindkey(
&mut emap,
b"\x1b[200~",
Some(Thingy::builtin("bracketed-paste")),
None,
);
bindkey(
&mut vmap,
b"\x1b[200~",
Some(Thingy::builtin("bracketed-paste")),
None,
);
bindkey(
&mut amap,
b"\x1b[200~",
Some(Thingy::builtin("bracketed-paste")),
None,
);
for i in 0..128 {
let name = METABIND[i];
if name == "undefined-key" {
continue;
}
bindkey(
&mut emap,
&[0x1b, i as u8],
Some(Thingy::builtin(name)),
None,
);
}
linkkeymap(Arc::new(vmap), "viins", 0); linkkeymap(Arc::new(emap), "emacs", 0); linkkeymap(Arc::new(amap), "vicmd", 0); linkkeymap(Arc::new(oppmap), "viopp", 0); linkkeymap(Arc::new(vismap), "visual", 0); linkkeymap(Arc::new(smap), ".safe", 1);
let pick_vi = if crate::ported::zsh_h::isset(crate::ported::zsh_h::VIMODE) {
true
} else {
let visual_has_vi = std::env::var("VISUAL")
.map(|v| v.contains("vi"))
.unwrap_or(false);
let editor_has_vi = std::env::var("EDITOR")
.map(|v| v.contains("vi"))
.unwrap_or(false);
visual_has_vi || editor_has_vi
};
let main_src = if pick_vi { "viins" } else { "emacs" };
if let Some(km) = openkeymap(main_src) {
linkkeymap(km, "main", 0);
}
let mut isearch_km = Keymap::default();
isearch_km.primary = Some("isearch".to_string());
linkkeymap(Arc::new(isearch_km), "isearch", 0);
let mut command_km = Keymap::default();
command_km.primary = Some("command".to_string());
bindkey(
&mut command_km,
&[b'\n'],
Some(Thingy::builtin("accept-line")),
None,
); bindkey(
&mut command_km,
&[b'\r'],
Some(Thingy::builtin("accept-line")),
None,
); bindkey(
&mut command_km,
&[0x07],
Some(Thingy::builtin("send-break")),
None,
); linkkeymap(Arc::new(command_km), "command", 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(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();
bindkey(&mut km, &[b'a'], Some(dummy_thingy()), None);
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();
bindkey(&mut km, b"ab", Some(dummy_thingy()), None);
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();
bindkey(&mut km, b"xyz", Some(dummy_thingy()), None);
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();
bindkey(&mut km, &[b'A'], Some(dummy_thingy()), None);
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();
bindkey(&mut km, &[b'q'], Some(Thingy::new("quit-widget")), None);
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();
bindkey(
&mut km,
&[b'A'],
Some(Thingy::new("uppercase-A-widget")),
None,
);
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);
}
#[test]
fn newkeytab_returns_empty_table() {
let _g = crate::test_util::global_state_lock();
let kt = newkeytab();
assert_eq!(kt.len(), 0, "fresh keytab must be empty");
}
#[test]
fn openkeymap_unknown_name_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert!(openkeymap("zshrs_unknown_keymap_xyz").is_none());
}
#[test]
fn unlinkkeymap_unknown_name_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_ne!(
unlinkkeymap("zshrs_doesnt_exist", 0),
0,
"unlinking missing keymap must return error"
);
}
#[test]
fn keyisprefix_empty_seq_returns_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "test");
assert_eq!(keyisprefix(&km, b""), 1, "empty seq is trivially a prefix");
}
#[test]
fn keyisprefix_unbound_seq_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "test");
assert_eq!(keyisprefix(&km, b"unbound"), 0);
}
#[test]
fn keybind_unbound_single_byte_returns_none_pair() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "test");
let (bind, s) = keybind(&km, &[0x42]); assert!(bind.is_none(), "no binding on fresh km");
assert!(s.is_none(), "no string on fresh km");
}
#[test]
fn keybind_empty_seq_returns_none_pair() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "test");
let (bind, s) = keybind(&km, b"");
assert!(bind.is_none());
assert!(s.is_none());
}
#[test]
fn keybind_meta_pair_unbound_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "test");
let (bind, _) = keybind(&km, &[0x83, b'a' ^ 32]);
assert!(bind.is_none(), "unbound Meta-pair on fresh km");
}
#[test]
fn newkeymap_fresh_has_no_first_bindings() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "test");
for (i, slot) in km.first.iter().enumerate() {
assert!(
slot.is_none(),
"first[{}] must be unbound on fresh keymap",
i
);
}
}
#[test]
fn newkeymap_fresh_has_empty_multi_table() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "test");
assert!(km.multi.is_empty(), "multi table must be empty on fresh km");
}
#[test]
fn newkeytab_returns_owned_independent_table() {
let _g = crate::test_util::global_state_lock();
let kt1 = newkeytab();
let kt2 = newkeytab();
assert!(kt1.is_empty());
assert!(kt2.is_empty());
}
#[test]
fn selectkeymap_empty_name_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_ne!(selectkeymap("", 0), 0, "empty name = invalid");
}
#[test]
fn selectkeymap_default_emacs_succeeds() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert_eq!(
selectkeymap("emacs", 0),
0,
"default 'emacs' keymap must exist"
);
}
#[test]
fn openkeymap_default_emacs_returns_some() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert!(
openkeymap("emacs").is_some(),
"default 'emacs' keymap must open"
);
}
#[test]
fn openkeymap_empty_name_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert!(openkeymap("").is_none(), "empty name → None");
}
#[test]
fn openkeymap_unknown_name_returns_none_pin() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert!(
openkeymap("__never_a_real_keymap_xyz__").is_none(),
"unknown keymap → None"
);
}
#[test]
fn selectkeymap_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = selectkeymap("emacs", 0);
}
#[test]
fn unlinkkeymap_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = unlinkkeymap("nothing_real", 0);
}
#[test]
fn unlinkkeymap_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _ = unlinkkeymap("", 0);
}
#[test]
fn selectlocalmap_none_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
selectlocalmap(None);
}
#[test]
fn reselectkeymap_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
reselectkeymap();
}
}
#[test]
fn refkeymap_by_name_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
refkeymap_by_name("");
}
#[test]
fn unrefkeymap_by_name_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
unrefkeymap_by_name("");
}
#[test]
fn selectkeymap_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let first = selectkeymap("emacs", 0);
for _ in 0..3 {
assert_eq!(
selectkeymap("emacs", 0),
first,
"selectkeymap('emacs') must be deterministic"
);
}
}
#[test]
fn createkeymapnamtab_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
createkeymapnamtab();
}
}
#[test]
fn init_keymaps_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
init_keymaps();
}
}
#[test]
fn cleanup_keymaps_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
cleanup_keymaps();
}
init_keymaps();
}
#[test]
fn scanprimaryname_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
scanprimaryname("");
}
#[test]
fn freekeymapnamnode_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
freekeymapnamnode("");
}
#[test]
fn reselectkeymap_returns_void_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: () = reselectkeymap();
}
#[test]
fn openkeymap_returns_option_arc_keymap_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Option<Arc<Keymap>> = openkeymap("");
}
#[test]
fn unlinkkeymap_empty_name_returns_nonzero_pin() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let r = unlinkkeymap("", 0);
assert_ne!(r, 0, "empty name → nonzero error");
}
#[test]
fn newkeytab_returns_hashmap_type() {
let _: HashMap<Vec<u8>, KeyBinding> = newkeytab();
}
#[test]
fn newkeytab_returns_empty_pin() {
let t = newkeytab();
assert!(t.is_empty(), "fresh keytab must be empty");
}
#[test]
fn selectlocalmap_none_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
selectlocalmap(None);
}
}
#[test]
fn selectkeymap_returns_i32_type_pin2() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = selectkeymap("", 0);
}
#[test]
fn unlinkkeymap_unknown_name_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let first = unlinkkeymap("__zshrs_never_keymap__", 0);
for _ in 0..3 {
assert_eq!(
unlinkkeymap("__zshrs_never_keymap__", 0),
first,
"unlinkkeymap unknown must be deterministic"
);
}
}
#[test]
fn createkeymapnamtab_idempotent_10_call() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..10 {
createkeymapnamtab();
}
}
#[test]
fn emptykeymapnamtab_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..10 {
emptykeymapnamtab();
}
}
#[test]
fn refkeymap_by_name_unknown_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
refkeymap_by_name("__never_keymap_xyz__");
}
#[test]
fn unrefkeymap_by_name_unknown_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
unrefkeymap_by_name("__never_keymap_xyz__");
}
#[test]
fn unrefkeymap_by_name_empty_no_panic_alt() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
unrefkeymap_by_name("");
}
#[test]
fn newkeymap_none_returns_arc_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: Arc<Keymap> = newkeymap(None, "");
}
#[test]
fn newkeymap_deterministic_shape() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
let _: Arc<Keymap> = newkeymap(None, "test");
}
}
#[test]
fn linkkeymap_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let km = newkeymap(None, "");
let _: i32 = linkkeymap(km, "test", 0);
}
#[test]
fn openkeymap_empty_name_returns_none_alt() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert!(openkeymap("").is_none(), "empty keymap name → None");
}
#[test]
fn openkeymap_unknown_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
assert!(openkeymap("__definitely_no_such_keymap_xyz__").is_none());
}
#[test]
fn newkeytab_deterministic_shape() {
for _ in 0..5 {
let t = newkeytab();
assert!(t.is_empty(), "newkeytab must always start empty");
}
}
}