use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use super::zle_thingy::Thingy;
use std::io::Write;
#[allow(unused_imports)]
#[allow(unused_imports)]
use crate::ported::zle::zle_main::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_misc::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_hist::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_move::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_word::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_params::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_vi::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_utils::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_refresh::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_tricky::*;
#[allow(unused_imports)]
use crate::ported::zle::textobjects::*;
#[allow(unused_imports)]
use crate::ported::zle::deltochar::*;
pub const KMN_IMMORTAL: i32 = 1 << 1;
pub const KM_IMMUTABLE: i32 = 1 << 1;
pub const BS_LIST: i32 = 1 << 0;
pub const BS_ALL: i32 = 1 << 1;
pub static CURKEYMAPNAME: std::sync::OnceLock<std::sync::Mutex<String>> =
std::sync::OnceLock::new();
pub fn curkeymapname() -> std::sync::MutexGuard<'static, String> {
CURKEYMAPNAME
.get_or_init(|| std::sync::Mutex::new(String::from("main")))
.lock()
.unwrap()
}
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);
pub static lastnamed: Mutex<Option<Thingy>> = Mutex::new(None);
static KEYMAPNAMTAB: OnceLock<Mutex<HashMap<String, KeymapName>>> = OnceLock::new();
pub(crate) fn keymapnamtab() -> &'static Mutex<HashMap<String, KeymapName>> {
KEYMAPNAMTAB.get_or_init(|| Mutex::new(HashMap::new()))
}
#[derive(Debug, Clone)]
pub struct KeymapName { pub nam: String, pub flags: i32, pub keymap: Arc<Keymap>, }
#[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, }
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub struct remprefstate { pub km: std::sync::Arc<Keymap>, pub prefix: Vec<u8>, pub prefixlen: usize, }
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 setup_emacs_keymap(km: &mut Keymap) {
use super::zle_bindings::{EMACSBIND, METABIND};
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) {
use super::zle_bindings::VIINSBIND;
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) {
use super::zle_bindings::VICMDBIND;
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 fn bin_bindkey(name: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::zsh_h::{OPT_ISSET, OPT_ARG};
#[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, args);
}
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_char = if op.o == 0 { ' ' } else { op.o as char };
match op.func {
Op::LsMaps => {
for k in bin_bindkey_lsmaps() {
println!("{}", k);
}
0
}
Op::DelAll => bin_bindkey_delall(name),
Op::Del => bin_bindkey_del(args),
Op::Link => bin_bindkey_link(args),
Op::New => bin_bindkey_new(args),
Op::Meta => bin_bindkey_meta(name, args),
Op::Bind => bin_bindkey_bind(name, args, func_char),
};
let _ = kmname; 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emacs_default_has_quoted_insert_undo_yank_pop() {
let _g = crate::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
let km = Keymap::default();
assert_eq!(keyisprefix(&km, b""), 1);
}
#[test]
fn keyisprefix_single_byte_bound_returns_zero() {
let _g = crate::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
let km = Keymap::default();
assert_eq!(keyisprefix(&km, b"x"), 0);
}
#[test]
fn keyisprefix_seq_is_real_prefix() {
let _g = crate::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
let mut km = Keymap::default();
km.bind_char(b'A', dummy_thingy());
assert_eq!(keyisprefix(&km, &[0x83, 0x61]), 0);
}
}
pub fn add_cursor_char(buf: &mut Vec<u8>, c: u8) { buf.push(c);
}
#[allow(unused_variables)]
pub fn add_cursor_key(km: &mut Keymap, tccode: i32, thingy: Thingy, defchar: i32) { if defchar > 0 && defchar < 256 {
km.bind_char(defchar as u8, thingy);
}
}
pub fn addkeybuf(c: i32) { let c = c & 0xff;
let is_meta = c >= 0x83 && c != 0x83 && c != 0x84;
let mut buf = keybuf.lock().unwrap();
if is_meta {
buf.push(0x83); buf.push((c ^ 32) as u8);
} else {
buf.push(c as u8);
}
}
pub fn bin_bindkey_bind(name: &str, args: &[String], func: char) -> i32 {
let Some(old_arc) = openkeymap(name) else { return 1; }; let needs_pairs = func == '\0' || func == 's';
if needs_pairs && (args.len() % 2 != 0) { return 1; }
let mut km: Keymap = (*old_arc).clone();
let stride = if func == 'r' { 1 } else { 2 };
let mut i = 0;
while i + (stride - 1) < args.len() {
let seq_bytes = args[i].as_bytes();
let target = if stride == 2 { Some(args[i + 1].clone()) } else { None };
let kb_value: KeyBinding = match func { '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 = std::sync::Arc::new(km);
if let Ok(mut tab) = keymapnamtab().lock() {
let names_to_update: Vec<String> = tab.iter()
.filter(|(_, kmn)| std::sync::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 bin_bindkey_del(args: &[String]) -> i32 { if args.is_empty() {
return 1;
}
let mut ret = 0;
for arg in args {
match unlinkkeymap(arg, 0) {
0 => {}
_ => ret = 1,
}
}
ret
}
pub fn bin_bindkey_delall(name: &str) -> i32 { if openkeymap(name).is_none() {
return 1;
}
0
}
pub fn bin_bindkey_link(args: &[String]) -> i32 { if args.len() < 2 {
return 1;
}
let Some(km) = openkeymap(&args[0]) else {
return 1;
};
if linkkeymap(km, &args[1], 0) != 0 {
return 1;
}
0
}
pub fn bin_bindkey_list(name: &str, _ops: &[String]) -> 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 bin_bindkey_lsmaps() -> Vec<String> { keymapnamtab().lock().unwrap()
.keys()
.cloned()
.collect()
}
pub fn bin_bindkey_meta(name: &str, _argv: &[String]) -> i32 { if openkeymap(name).is_none() {
return 1;
}
0 }
pub fn bin_bindkey_new(args: &[String]) -> i32 { if args.is_empty() {
return 1;
}
let blocked = keymapnamtab().lock().unwrap()
.get(&args[0]).map(|n| n.flags & KMN_IMMORTAL != 0).unwrap_or(false);
if blocked {
return 1; }
let template = if args.len() >= 2 {
let km = openkeymap(&args[1]);
if km.is_none() {
return 1; }
km
} else {
None
};
let new_km = newkeymap(template.as_deref(), &args[0]); linkkeymap(new_km, &args[0], 0);
0 }
pub fn createkeymapnamtab() { let _ = keymapnamtab();
}
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(); }
#[allow(unused_variables)]
pub fn deletekeymap(km: Arc<Keymap>) { }
pub fn emptykeymapnamtab() { keymapnamtab().lock().unwrap().clear();
}
pub fn freekeymapnamnode(hn: &str) { keymapnamtab().lock().unwrap().remove(hn);
}
pub fn freekeynode(hn: KeyBinding) { if let Some(t) = hn.bind {
crate::ported::zle::zle_thingy::unrefthingy(&t.nam);
}
}
pub fn getkeybuf(w: i32) -> i32 { let _ = w; if let Some(b) = crate::ported::zle::zle_main::KUNGETBUF.lock().unwrap().pop_front() {
addkeybuf(b as i32);
b as i32
} else {
-1 }
}
pub fn getkeycmd() -> i32 { -1
}
pub fn getkeymapcmd(_km: i32) -> i32 { -1
}
pub fn getrestchar_keybuf() -> i32 { let c = getkeybuf(0);
crate::ported::zle::zle_main::getrestchar(c)
}
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 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 makekeymapnamnode(keymap: Arc<Keymap>) -> KeymapName { KeymapName {
nam: String::new(),
flags: 0,
keymap: keymap,
}
}
pub fn makekeynode(t: Thingy, str: String) -> KeyBinding { KeyBinding {
bind: Some(t),
str: Some(str),
prefixct: 0,
}
}
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 newkeytab() -> HashMap<Vec<u8>, KeyBinding> { HashMap::new()
}
pub fn openkeymap(name: &str) -> Option<Arc<Keymap>> { keymapnamtab().lock().unwrap()
.get(name)
.map(|n| n.keymap.clone())
}
pub fn readcommand() -> i32 { let Some(name): Option<String> = None else { return 1; }; let _ = crate::ported::params::setsparam("REPLY", &name); 0 }
pub fn refkeymap(km: &mut Keymap) { km.rc += 1; }
pub fn refkeymap_by_name(kmn: &str) { let _ = keymapnamtab().lock().unwrap().get(kmn); }
pub fn reselectkeymap() { let name = curkeymapname().clone();
selectkeymap(&name, 1);
}
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 scancopykeys(_kb: &KeyBinding) { }
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 scanlistmaps() -> Vec<String> { keymapnamtab().lock().unwrap().keys().cloned().collect()
}
pub fn scanprimaryname(_name: &str) { }
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 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 static LOCALKEYMAP: Mutex<Option<Arc<Keymap>>> = Mutex::new(None);
pub fn ungetkeycmd() { let buf = keybuf.lock().unwrap().clone();
crate::ported::zle::zle_main::ungetbytes_unmeta(&buf);
}
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 unrefkeymap(km: &mut Keymap) -> i32 { km.rc -= 1; if km.rc == 0 {
return 0; }
km.rc }
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| std::sync::Arc::ptr_eq(&kmn.keymap, arc)).count()
} else { 0 };
if shared_count <= 1 { tab.remove(name); }
}
pub fn zlesetkeymap(mode: i32) { let kmname = if mode == 1 { "viins" } else { "emacs" };
if let Some(km) = openkeymap(kmname) {
linkkeymap(km, "main", 0);
}
}