zsh/ported/zle/zle_keymap.rs
1//! ZLE keymap and key bindings - Direct port from zsh/Src/Zle/zle_keymap.c
2//!
3//! currently selected keymap, and its name // c:121
4//! the hash table of keymap names // c:128
5//! key sequence reading data // c:133
6//! main initialisation entry point // c:1220
7//!
8//! Keymap structures:
9//!
10//! There is a hash table of keymap names. Each name just points to a keymap.
11//! More than one name may point to the same keymap.
12//!
13//! Each keymap consists of a table of bindings for each character, and a
14//! hash table of multi-character key bindings. The keymap has no individual
15//! name, but maintains a reference count.
16
17use std::collections::HashMap;
18use std::sync::{Arc, Mutex, OnceLock};
19
20use super::zle_thingy::Thingy;
21use std::io::Write;
22
23// =====================================================================
24// Flag constants — `Src/Zle/zle_keymap.c:62/83/114-115`.
25// =====================================================================
26
27/// Port of `KMN_IMMORTAL` from `Src/Zle/zle_keymap.c:62`. Marks a
28/// keymap-name node that can't be deleted (the `.safe` keymap).
29
30// --- AUTO: cross-zle hoisted-fn use glob ---
31#[allow(unused_imports)]
32#[allow(unused_imports)]
33use crate::ported::zle::zle_main::*;
34#[allow(unused_imports)]
35use crate::ported::zle::zle_misc::*;
36#[allow(unused_imports)]
37use crate::ported::zle::zle_hist::*;
38#[allow(unused_imports)]
39use crate::ported::zle::zle_move::*;
40#[allow(unused_imports)]
41use crate::ported::zle::zle_word::*;
42#[allow(unused_imports)]
43use crate::ported::zle::zle_params::*;
44#[allow(unused_imports)]
45use crate::ported::zle::zle_vi::*;
46#[allow(unused_imports)]
47use crate::ported::zle::zle_utils::*;
48#[allow(unused_imports)]
49use crate::ported::zle::zle_refresh::*;
50#[allow(unused_imports)]
51use crate::ported::zle::zle_tricky::*;
52#[allow(unused_imports)]
53use crate::ported::zle::textobjects::*;
54#[allow(unused_imports)]
55use crate::ported::zle::deltochar::*;
56
57pub const KMN_IMMORTAL: i32 = 1 << 1; // c:62
58
59/// Port of `KM_IMMUTABLE` from `Src/Zle/zle_keymap.c:83`. Marks a
60/// keymap that can't have its bindings modified.
61pub const KM_IMMUTABLE: i32 = 1 << 1; // c:83
62
63/// Port of `BS_LIST` from `Src/Zle/zle_keymap.c:114`. `bin_bindkey -L`:
64/// list bindings in `bindkey -M` syntax.
65pub const BS_LIST: i32 = 1 << 0; // c:114
66
67/// Port of `BS_ALL` from `Src/Zle/zle_keymap.c:115`. `bin_bindkey -aL`:
68/// list ALL bindings, including default sequences.
69pub const BS_ALL: i32 = 1 << 1; // c:115
70
71/// Port of `mod_export char *curkeymapname` from `Src/Zle/zle_keymap.c:126`.
72/// Name of the currently active keymap (driven by `bindkey -A` and the
73/// `KEYMAP` parameter). The Rust port wraps in OnceLock<Mutex<>> for
74/// thread-safe access from widget bodies.
75pub static CURKEYMAPNAME: std::sync::OnceLock<std::sync::Mutex<String>> =
76 std::sync::OnceLock::new(); // c:126
77
78/// Get-or-init accessor for `CURKEYMAPNAME`. Mirrors the C convention
79/// of treating the string as always-initialised — first read seeds it
80/// with "main".
81pub fn curkeymapname() -> std::sync::MutexGuard<'static, String> {
82 CURKEYMAPNAME
83 .get_or_init(|| std::sync::Mutex::new(String::from("main")))
84 .lock()
85 .unwrap()
86}
87
88/// Port of `Keymap curkeymap` from `Src/Zle/zle_keymap.c:124`. The
89/// currently active keymap (per `bindkey -A` selection or KEYMAP
90/// parameter). Used inline at zle_keymap.c:519 (`curkeymap = km;`)
91/// and read by `getkeycmd`/`getkeybuf` to dispatch the next key.
92pub static curkeymap: Mutex<Option<Arc<Keymap>>> = Mutex::new(None); // c:124
93
94/// Port of `char *keybuf` from `Src/Zle/zle_keymap.c:136`. The key
95/// sequence currently being read by `getkeycmd`. C uses a flat
96/// `char*` heap allocation sized by `keybufsz`; Rust uses
97/// `Vec<u8>` which manages its own capacity.
98pub static keybuf: Mutex<Vec<u8>> = Mutex::new(Vec::new()); // c:136
99
100/// Port of `int keybuflen` from `Src/Zle/zle_keymap.c:139`. Current
101/// number of bytes in `keybuf`. Rust mirrors via `keybuf.lock().len()`
102/// but exposes the count as a separate static for callers that need
103/// it without holding the buffer lock.
104pub static keybuflen: std::sync::atomic::AtomicI32 = // c:139
105 std::sync::atomic::AtomicI32::new(0);
106
107/// Port of `static Thingy lastnamed` from `Src/Zle/zle_keymap.c:145`.
108/// Last command executed by `execute-named-command` — used to
109/// re-execute via `bindkey -A name` then `getkeycmd`.
110pub static lastnamed: Mutex<Option<Thingy>> = Mutex::new(None); // c:145
111
112// =====================================================================
113// keymapnamtab — `Src/Zle/zle_keymap.c:128/153`.
114// =====================================================================
115//
116// C: `mod_export HashTable keymapnamtab` — global hash mapping
117// keymap names to KeymapName entries (each KeymapName holds an
118// Arc'd Keymap + flags). zshrs uses Mutex<HashMap<String, KeymapName>>.
119
120static KEYMAPNAMTAB: OnceLock<Mutex<HashMap<String, KeymapName>>> = OnceLock::new();
121
122pub(crate) fn keymapnamtab() -> &'static Mutex<HashMap<String, KeymapName>> {
123 KEYMAPNAMTAB.get_or_init(|| Mutex::new(HashMap::new()))
124}
125
126/// Direct port of `struct keymapname` from `Src/Zle/zle_keymap.c:54`.
127/// One node in the global `keymapnamtab` — maps a name to a Keymap
128/// + per-node flags (KMN_IMMORTAL for `.safe`).
129#[derive(Debug, Clone)]
130pub struct KeymapName { // c:54
131 pub nam: String, // c:56 char *nam
132 pub flags: i32, // c:57 int flags
133 pub keymap: Arc<Keymap>, // c:58 Keymap keymap
134}
135
136/// Direct port of `struct keymap` from `Src/Zle/zle_keymap.c:64`.
137/// A keymap — binding of keys to thingies.
138#[derive(Debug, Clone)]
139pub struct Keymap { // c:64
140 /// `Thingy first[256]` — c:65, base binding for each byte.
141 pub first: [Option<Thingy>; 256],
142 /// `HashTable multi` — c:66, multi-character bindings.
143 pub multi: HashMap<Vec<u8>, KeyBinding>,
144 /// `KeymapName primary` — c:78, primary alias for this map.
145 pub primary: Option<String>,
146 /// `int flags` — c:79 (KM_IMMUTABLE).
147 pub flags: i32,
148 /// `int rc` — c:80, reference count (refkeymap/unrefkeymap/
149 /// deletekeymap).
150 pub rc: i32,
151}
152
153/// Direct port of `struct key` from `Src/Zle/zle_keymap.c:85`.
154/// A key binding (either a thingy or a string to send).
155#[derive(Debug, Clone)]
156pub struct KeyBinding { // c:85
157 pub bind: Option<Thingy>, // c:88 Thingy bind
158 pub str: Option<String>, // c:89 char *str
159 pub prefixct: i32, // c:90 int prefixct
160}
161
162// `BindState` / `BindStateFlags` deleted — the C `struct bindstate`
163// at zle_keymap.c:95 is only used as a local in `printbinding()`/
164// `scanbindings()`/`bin_bindkey -L`; ports of those fns will model
165// it as a stack-local struct when they land. The previous Rust
166// declaration had no callers (dead code) and used a fake bitflags
167// wrapper over a single int field.
168
169/// Port of `struct remprefstate` from `Src/Zle/zle_keymap.c:108`.
170/// Closure state for `scanremoveprefix` — removes every multi-char
171/// binding that starts with the given prefix from a keymap.
172///
173/// C definition (c:108-112):
174/// ```c
175/// struct remprefstate {
176/// Keymap km;
177/// char *prefix;
178/// int prefixlen;
179/// };
180/// ```
181#[derive(Debug)]
182#[allow(non_camel_case_types)]
183pub struct remprefstate { // c:108
184 /// Target keymap (Arc handle for shared ownership).
185 pub km: std::sync::Arc<Keymap>, // c:109
186 /// Byte prefix to match against each multi-key binding.
187 pub prefix: Vec<u8>, // c:110
188 /// `prefix.len()` cached for the scan inner loop (kept as a field
189 /// to mirror the C struct shape; `self.prefix.len()` reads the
190 /// same value).
191 pub prefixlen: usize, // c:111
192}
193
194impl Default for Keymap {
195 fn default() -> Self {
196 Keymap {
197 first: std::array::from_fn(|_| None),
198 multi: HashMap::new(),
199 primary: None,
200 flags: 0,
201 rc: 0,
202 }
203 }
204}
205
206impl Keymap {
207 /// Construct an empty keymap with no bindings.
208 /// Equivalent to `newkeytab()` from Src/Zle/zle_keymap.c:278 — the
209 /// C source allocates a Keymap with the first[] array zeroed out
210 /// and an empty multi-byte hashtab.
211 pub fn new() -> Self { // c:278
212 Self::default()
213 }
214
215 /// Bind a 1-byte key to a Thingy via the `first[]` fast-path table.
216 /// Direct port of the single-byte path in `bindkey()` at
217 /// Src/Zle/zle_keymap.c:566; the C source writes into `km->first[c]`
218 /// when `seq` has length 1.
219 pub fn bind_char(&mut self, c: u8, thingy: Thingy) { // c:566
220 self.first[c as usize] = Some(thingy);
221 }
222
223 /// Clear a 1-byte binding.
224 /// Equivalent to `bindkey -r` against a single-byte sequence at
225 /// Src/Zle/zle_keymap.c:566 — flips the `first[c]` slot to None.
226 pub fn unbind_char(&mut self, c: u8) {
227 self.first[c as usize] = None;
228 }
229
230 /// Install a multi-byte key sequence binding.
231 /// Direct port of `bindkey(Keymap km, const char *seq, Thingy bind, char *str)` from Src/Zle/zle_keymap.c:566 for the
232 /// len > 1 path: marks every proper prefix of `seq` as a prefix
233 /// node (prefixct increment) so getkeymapcmd's trie walk knows to
234 /// keep reading bytes when it sees a partial match.
235 pub fn bind_seq(&mut self, seq: &[u8], thingy: Thingy) { // c:566
236 if seq.len() == 1 {
237 self.bind_char(seq[0], thingy);
238 } else {
239 // Mark prefixes
240 for i in 1..seq.len() {
241 let prefix = &seq[..i];
242 self.multi
243 .entry(prefix.to_vec())
244 .and_modify(|kb| kb.prefixct += 1)
245 .or_insert(KeyBinding {
246 bind: None,
247 str: None,
248 prefixct: 1,
249 });
250 }
251
252 // Add the binding
253 self.multi.insert(
254 seq.to_vec(),
255 KeyBinding {
256 bind: Some(thingy),
257 str: None,
258 prefixct: 0,
259 },
260 );
261 }
262 }
263
264 /// Install a multi-byte key sequence that maps to a literal string.
265 /// Port of the send-string variant of `bindkey()` at
266 /// Src/Zle/zle_keymap.c:566 — the C source stores `str` instead of
267 /// a Thingy when invoked via `bindkey -s 'seq' 'string'`. When the
268 /// trie hits this entry, getkeycmd ungets the string via
269 /// `ungetbytes_unmeta` (zle_keymap.c:1784) so it gets re-resolved
270 /// against the keymap.
271 pub fn bind_str(&mut self, seq: &[u8], s: String) {
272 if seq.len() == 1 {
273 // Single char can't be send-string in first[] table
274 // Store in multi
275 }
276
277 // Mark prefixes
278 for i in 1..seq.len() {
279 let prefix = &seq[..i];
280 self.multi
281 .entry(prefix.to_vec())
282 .and_modify(|kb| kb.prefixct += 1)
283 .or_insert(KeyBinding {
284 bind: None,
285 str: None,
286 prefixct: 1,
287 });
288 }
289
290 self.multi.insert(
291 seq.to_vec(),
292 KeyBinding {
293 bind: None,
294 str: Some(s),
295 prefixct: 0,
296 },
297 );
298 }
299
300 /// Remove a multi-byte binding and decrement prefix counts on its
301 /// ancestors so the trie shrinks correctly.
302 /// Port of `bindkey -r` against a multi-byte sequence at
303 /// Src/Zle/zle_keymap.c:566 — the C source mirrors the prefix
304 /// reference-count machinery via the same prefixct decrement
305 /// pattern when removing a leaf.
306 pub fn unbind_seq(&mut self, seq: &[u8]) {
307 if seq.len() == 1 {
308 self.unbind_char(seq[0]);
309 } else {
310 if self.multi.remove(seq).is_some() {
311 // Decrement prefix counts
312 for i in 1..seq.len() {
313 let prefix = &seq[..i];
314 if let Some(kb) = self.multi.get_mut(prefix) {
315 kb.prefixct -= 1;
316 if kb.prefixct == 0 && kb.bind.is_none() && kb.str.is_none() {
317 // Remove empty prefix entry
318 // (can't remove while iterating, so we'll leave it)
319 }
320 }
321 }
322 }
323 }
324 }
325
326 /// Fast-path single-byte lookup through `first[]`.
327 /// Equivalent to the 1-byte branch of `keybind()` at
328 /// Src/Zle/zle_keymap.c:659 — the C source's `km->first[*seq]`
329 /// access for single-byte resolution.
330 pub fn lookup_char(&self, c: u8) -> Option<&Thingy> {
331 self.first[c as usize].as_ref()
332 }
333
334 /// Multi-byte sequence lookup through the `multi` hashtab.
335 /// Equivalent to the >1-byte branch of `keybind()` at
336 /// zle_keymap.c:659 — returns the KeyBinding entry if `seq`
337 /// matches a leaf, or one carrying `prefixct > 0` if `seq` is a
338 /// prefix of one or more bound sequences.
339 pub fn lookup_seq(&self, seq: &[u8]) -> Option<&KeyBinding> {
340 if seq.len() == 1 {
341 // For single char, use lookup_char instead
342 None
343 } else {
344 self.multi.get(seq)
345 }
346 }
347
348 /// Test whether `seq` is a prefix of any bound sequence.
349 /// Equivalent to `keyisprefix()` from Src/Zle/zle_keymap.c. Used
350 /// by `getkeymapcmd` to decide whether to keep reading bytes
351 /// during a multi-byte sequence resolve (the trie-walk loop at
352 /// zle_keymap.c:1604).
353 pub fn is_prefix(&self, seq: &[u8]) -> bool {
354 if seq.len() == 1 {
355 // Check if this char is a prefix in multi table
356 self.multi.keys().any(|k| k.len() > 1 && k[0] == seq[0])
357 } else {
358 self.multi
359 .get(seq)
360 .map(|kb| kb.prefixct > 0)
361 .unwrap_or(false)
362 }
363 }
364}
365
366/// Zero-sized namespace for the three default-binding tables
367/// (emacs / viins / vicmd) that `default_bindings()` populates at
368/// startup. The state these used to wrap (keymaps / current /
369/// current_name / local / keybuf / lastnamed) now lives in the
370/// six file-scope statics declared above (KEYMAPNAMTAB / curkeymap
371/// / CURKEYMAPNAME / LOCALKEYMAP / keybuf / lastnamed) — matching
372/// the C globals at `Src/Zle/zle_keymap.c:124-145`.
373///
374/// The setup_*_keymap methods stay as methods (drift-gate
375/// exempts impl-block fns) because zsh's C `default_bindings()`
376/// has the equivalent 330+ bindkey calls inline in one function;
377/// the Rust port keeps them factored by keymap for readability.
378// `KeymapManager` unit struct (and its 32-method impl block) deleted —
379// was a Rust-only namespace wrapper around the file-scope statics
380// (KEYMAPNAMTAB / curkeymap / CURKEYMAPNAME / LOCALKEYMAP / keybuf /
381// lastnamed) with no `struct keymap_manager` in zsh C. 29 of the 32
382// methods were never called; 3 (`setup_emacs_keymap` /
383// `setup_viins_keymap` / `setup_vicmd_keymap`) are factored out below
384// as free fns because zsh's C `default_bindings()` inlines the ~300
385// equivalent `bindkey` calls in one body (Src/Zle/zle_keymap.c:124).
386// The Rust port keeps them factored by keymap for readability.
387
388// WARNING: NOT IN ZLE_KEYMAP.C — Rust-only factoring of zsh's
389// inlined `default_bindings()` body (Src/Zle/zle_keymap.c:124).
390
391/// Set up emacs keymap bindings. Direct port of the emacs branch of
392/// `default_bindings()` from `Src/Zle/zle_keymap.c:1309` driven by the
393/// canonical `EMACSBIND` / `METABIND` tables in
394/// `zle_bindings.rs` (which mirror `Src/Zle/zle_bindings.c:88-253`).
395pub fn setup_emacs_keymap(km: &mut Keymap) {
396 use super::zle_bindings::{EMACSBIND, METABIND};
397
398 // c:1326-1329 — first 32 entries come from emacsbind table.
399 for i in 0..32 {
400 km.bind_char(i as u8, Thingy::builtin(EMACSBIND[i]));
401 }
402 // c:1330-1333 — 32-255 self-insert.
403 for i in 32u8..=255u8 {
404 km.bind_char(i, Thingy::builtin("self-insert"));
405 }
406 // c:1337 — entry[127] = entry[8] (DEL == ^H).
407 km.bind_char(0x7F, Thingy::builtin(EMACSBIND[8]));
408
409 // c:1410-1413 — emacs cursor keys (vt100 fallback).
410 km.bind_seq(b"\x1b[A", Thingy::builtin("up-line-or-history"));
411 km.bind_seq(b"\x1b[B", Thingy::builtin("down-line-or-history"));
412 km.bind_seq(b"\x1b[C", Thingy::builtin("forward-char"));
413 km.bind_seq(b"\x1b[D", Thingy::builtin("backward-char"));
414 km.bind_seq(b"\x1bOA", Thingy::builtin("up-line-or-history"));
415 km.bind_seq(b"\x1bOB", Thingy::builtin("down-line-or-history"));
416 km.bind_seq(b"\x1bOC", Thingy::builtin("forward-char"));
417 km.bind_seq(b"\x1bOD", Thingy::builtin("backward-char"));
418
419 // c:1415-1431 — ^X sequences.
420 km.bind_seq(b"\x18*", Thingy::builtin("expand-word"));
421 km.bind_seq(b"\x18g", Thingy::builtin("list-expand"));
422 km.bind_seq(b"\x18G", Thingy::builtin("list-expand"));
423 km.bind_seq(b"\x18\x0e", Thingy::builtin("infer-next-history"));
424 km.bind_seq(b"\x18\x0b", Thingy::builtin("kill-buffer"));
425 km.bind_seq(b"\x18\x06", Thingy::builtin("vi-find-next-char"));
426 km.bind_seq(b"\x18\x0f", Thingy::builtin("overwrite-mode"));
427 km.bind_seq(b"\x18\x15", Thingy::builtin("undo"));
428 km.bind_seq(b"\x18\x16", Thingy::builtin("vi-cmd-mode"));
429 km.bind_seq(b"\x18\x0a", Thingy::builtin("vi-join"));
430 km.bind_seq(b"\x18\x02", Thingy::builtin("vi-match-bracket"));
431 km.bind_seq(b"\x18s", Thingy::builtin("history-incremental-search-forward"));
432 km.bind_seq(b"\x18r", Thingy::builtin("history-incremental-search-backward"));
433 km.bind_seq(b"\x18u", Thingy::builtin("undo"));
434 km.bind_seq(b"\x18\x18", Thingy::builtin("exchange-point-and-mark"));
435 km.bind_seq(b"\x18=", Thingy::builtin("what-cursor-position"));
436
437 // c:1434 — bracketed paste.
438 km.bind_seq(b"\x1b[200~", Thingy::builtin("bracketed-paste"));
439
440 // c:1438-1445 — ESC sequences from metabind table.
441 for i in 0..128 {
442 let name = METABIND[i];
443 if name == "undefined-key" {
444 continue;
445 }
446 km.bind_seq(&[0x1b, i as u8], Thingy::builtin(name));
447 }
448}
449
450// WARNING: NOT IN ZLE_KEYMAP.C — Rust-only factoring of zsh's
451// inlined `default_bindings()` body (Src/Zle/zle_keymap.c:124).
452
453/// Set up viins (vi insert mode) keymap bindings. Direct port of the
454/// viins branch of `default_bindings()` (`Src/Zle/zle_keymap.c:1311`)
455/// driven by the canonical `VIINSBIND` table
456/// (`Src/Zle/zle_bindings.c:256-289`).
457pub fn setup_viins_keymap(km: &mut Keymap) {
458 use super::zle_bindings::VIINSBIND;
459
460 // c:1326-1329 — first 32 entries from viinsbind.
461 for i in 0..32 {
462 km.bind_char(i as u8, Thingy::builtin(VIINSBIND[i]));
463 }
464 // c:1330-1333 — 32-255 self-insert.
465 for i in 32u8..=255u8 {
466 km.bind_char(i, Thingy::builtin("self-insert"));
467 }
468 // c:1336 — entry[127] = entry[8] (DEL == ^H).
469 km.bind_char(0x7F, Thingy::builtin(VIINSBIND[8]));
470
471 // c:1361-1370 — vi cursor keys.
472 km.bind_seq(b"\x1b[A", Thingy::builtin("up-line-or-history"));
473 km.bind_seq(b"\x1b[B", Thingy::builtin("down-line-or-history"));
474 km.bind_seq(b"\x1b[C", Thingy::builtin("vi-forward-char"));
475 km.bind_seq(b"\x1b[D", Thingy::builtin("vi-backward-char"));
476 km.bind_seq(b"\x1bOA", Thingy::builtin("up-line-or-history"));
477 km.bind_seq(b"\x1bOB", Thingy::builtin("down-line-or-history"));
478 km.bind_seq(b"\x1bOC", Thingy::builtin("vi-forward-char"));
479 km.bind_seq(b"\x1bOD", Thingy::builtin("vi-backward-char"));
480
481 // c:1435 — bracketed paste.
482 km.bind_seq(b"\x1b[200~", Thingy::builtin("bracketed-paste"));
483}
484
485// WARNING: NOT IN ZLE_KEYMAP.C — Rust-only factoring of zsh's
486// inlined `default_bindings()` body (Src/Zle/zle_keymap.c:124).
487
488/// Set up vicmd (vi command mode) keymap bindings. Direct port of the
489/// vicmd branch of `default_bindings()` (`Src/Zle/zle_keymap.c:1313`)
490/// driven by the canonical `VICMDBIND` table
491/// (`Src/Zle/zle_bindings.c:292-421`).
492pub fn setup_vicmd_keymap(km: &mut Keymap) {
493 use super::zle_bindings::VICMDBIND;
494
495 // c:1342-1343 — 0-127 from vicmdbind.
496 for i in 0..128 {
497 km.bind_char(i as u8, Thingy::builtin(VICMDBIND[i]));
498 }
499 // c:1344-1345 — 128-255 undefined-key.
500 for i in 128u8..=255u8 {
501 km.bind_char(i, Thingy::builtin("undefined-key"));
502 }
503
504 // c:1361-1369 — vi cursor keys.
505 km.bind_seq(b"\x1b[A", Thingy::builtin("up-line-or-history"));
506 km.bind_seq(b"\x1b[B", Thingy::builtin("down-line-or-history"));
507 km.bind_seq(b"\x1b[C", Thingy::builtin("vi-forward-char"));
508 km.bind_seq(b"\x1b[D", Thingy::builtin("vi-backward-char"));
509 km.bind_seq(b"\x1bOA", Thingy::builtin("up-line-or-history"));
510 km.bind_seq(b"\x1bOB", Thingy::builtin("down-line-or-history"));
511 km.bind_seq(b"\x1bOC", Thingy::builtin("vi-forward-char"));
512 km.bind_seq(b"\x1bOD", Thingy::builtin("vi-backward-char"));
513
514 // c:1398-1407 — vi g-prefix sequences.
515 km.bind_seq(b"ga", Thingy::builtin("what-cursor-position"));
516 km.bind_seq(b"ge", Thingy::builtin("vi-backward-word-end"));
517 km.bind_seq(b"gE", Thingy::builtin("vi-backward-blank-word-end"));
518 km.bind_seq(b"gg", Thingy::builtin("beginning-of-buffer-or-history"));
519 km.bind_seq(b"gu", Thingy::builtin("vi-down-case"));
520 km.bind_seq(b"gU", Thingy::builtin("vi-up-case"));
521 km.bind_seq(b"g~", Thingy::builtin("vi-oper-swap-case"));
522
523 // c:1436 — bracketed paste.
524 km.bind_seq(b"\x1b[200~", Thingy::builtin("bracketed-paste"));
525}
526
527
528/// Direct port of `static int bin_bindkey(char *name, char **argv,
529/// Options ops, UNUSED(int func))` from `Src/Zle/zle_keymap.c:743`.
530/// Top-level dispatcher for the `bindkey` builtin.
531pub fn bin_bindkey(name: &str, args: &[String], // c:743
532 ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
533 use crate::ported::zsh_h::{OPT_ISSET, OPT_ARG};
534
535 // c:zle_keymap.c boot_ - the zsh/zle module's boot handler
536 // calls `default_bindings()` once on module load (zle_main.c
537 // setup_), which is what gives the "main" / "emacs" / "viins" /
538 // "vicmd" / "menuselect" / "listscroll" / ".safe" keymaps a
539 // chance to exist before user `bindkey` invocations.
540 //
541 // zshrs in script (non-interactive) mode doesn't autoload zsh/zle,
542 // so the keymaps are never populated. /etc/zshrc bindkey calls
543 // then fail with `no such keymap 'main'`. Auto-init on first
544 // bindkey call — idempotent because default_bindings is a no-op
545 // after the keymaps already exist.
546 static KEYMAPS_INIT: std::sync::Once = std::sync::Once::new();
547 KEYMAPS_INIT.call_once(|| {
548 default_bindings();
549 });
550
551 // c:751-759 — opns[] dispatch table. Each entry: (flag-char,
552 // selp, min, max, sub-handler kind). selp=1 means -e/-v/-a/-M
553 // keymap-selection is allowed for this op.
554 #[derive(Clone, Copy)]
555 enum Op { LsMaps, DelAll, Del, Link, New, Meta, Bind }
556 struct Opn { o: u8, selp: bool, func: Op, min: i32, max: i32 }
557 static OPNS: &[Opn] = &[
558 Opn { o: b'l', selp: false, func: Op::LsMaps, min: 0, max: -1 },
559 Opn { o: b'd', selp: false, func: Op::DelAll, min: 0, max: 0 },
560 Opn { o: b'D', selp: false, func: Op::Del, min: 1, max: -1 },
561 Opn { o: b'A', selp: false, func: Op::Link, min: 2, max: 2 },
562 Opn { o: b'N', selp: false, func: Op::New, min: 1, max: 2 },
563 Opn { o: b'm', selp: true, func: Op::Meta, min: 0, max: 0 },
564 Opn { o: b'r', selp: true, func: Op::Bind, min: 1, max: -1 },
565 Opn { o: b's', selp: true, func: Op::Bind, min: 2, max: -1 },
566 Opn { o: 0, selp: true, func: Op::Bind, min: 0, max: -1 },
567 ];
568
569 // c:767-773 — find selected op + ensure no clashing flags.
570 let mut idx = OPNS.len() - 1;
571 for (i, op) in OPNS.iter().enumerate() {
572 if op.o != 0 && OPT_ISSET(ops, op.o) { idx = i; break; }
573 }
574 let op = &OPNS[idx];
575 if op.o != 0 {
576 for opp in OPNS.iter().skip(idx + 1) {
577 if opp.o != 0 && OPT_ISSET(ops, opp.o) {
578 eprintln!("{}: incompatible operation selection options", name);
579 return 1;
580 }
581 }
582 }
583
584 // c:774-783 — keymap-selection flag validation.
585 let nsel = (OPT_ISSET(ops, b'e') as i32) + (OPT_ISSET(ops, b'v') as i32)
586 + (OPT_ISSET(ops, b'a') as i32) + (OPT_ISSET(ops, b'M') as i32);
587 if !op.selp && nsel != 0 {
588 eprintln!("{}: keymap cannot be selected with -{}", name, op.o as char);
589 return 1;
590 }
591 if nsel > 1 {
592 eprintln!("{}: incompatible keymap selection options", name);
593 return 1;
594 }
595
596 // c:786-807 — resolve keymap.
597 let kmname: Option<String> = if op.selp {
598 let nm = if OPT_ISSET(ops, b'e') {
599 "emacs".to_string()
600 } else if OPT_ISSET(ops, b'v') {
601 "viins".to_string()
602 } else if OPT_ISSET(ops, b'a') {
603 "vicmd".to_string()
604 } else if OPT_ISSET(ops, b'M') {
605 OPT_ARG(ops, b'M').map(|s| s.to_string()).unwrap_or_else(|| "main".to_string())
606 } else {
607 "main".to_string()
608 };
609 let km = match openkeymap(&nm) {
610 Some(k) => k,
611 None => {
612 eprintln!("{}: no such keymap `{}'", name, nm);
613 return 1;
614 }
615 };
616 if OPT_ISSET(ops, b'e') || OPT_ISSET(ops, b'v') {
617 linkkeymap(km, "main", 0);
618 }
619 Some(nm)
620 } else {
621 None
622 };
623
624 // c:810-814 — listing is a special case.
625 let argc = args.len() as i32;
626 if op.o == 0 && (args.is_empty() || args.len() < 2) {
627 if OPT_ISSET(ops, b'e') || OPT_ISSET(ops, b'v') {
628 return 0;
629 }
630 return bin_bindkey_list(name, args);
631 }
632
633 // c:816-824 — arity check.
634 if argc < op.min {
635 eprintln!("{}: not enough arguments for -{}", name, op.o as char);
636 return 1;
637 }
638 if op.max != -1 && argc > op.max {
639 eprintln!("{}: too many arguments for -{}", name, op.o as char);
640 return 1;
641 }
642
643 // c:826-827 — dispatch.
644 let func_char = if op.o == 0 { ' ' } else { op.o as char };
645 match op.func {
646 Op::LsMaps => {
647 for k in bin_bindkey_lsmaps() {
648 println!("{}", k);
649 }
650 0
651 }
652 Op::DelAll => bin_bindkey_delall(name),
653 Op::Del => bin_bindkey_del(args),
654 Op::Link => bin_bindkey_link(args),
655 Op::New => bin_bindkey_new(args),
656 Op::Meta => bin_bindkey_meta(name, args),
657 Op::Bind => bin_bindkey_bind(name, args, func_char),
658 };
659 let _ = kmname; // kmname currently unused by sub-handlers; will be threaded once they take it.
660 0
661}
662
663#[cfg(test)]
664mod tests {
665 use super::*;
666
667 #[test]
668 fn emacs_default_has_quoted_insert_undo_yank_pop() {
669 let _g = crate::ported::zle::zle_main::zle_test_setup();
670 createkeymapnamtab();
671 default_bindings();
672
673 let km = openkeymap("emacs").expect("emacs keymap created");
674 // Ctrl-V quoted-insert (zle_bindings.c emacs '^V').
675 assert_eq!(
676 km.lookup_char(0x16).map(|t| t.nam.as_str()),
677 Some("quoted-insert")
678 );
679 // Ctrl-_ undo (zle_bindings.c emacs '^_').
680 assert_eq!(km.lookup_char(0x1F).map(|t| t.nam.as_str()), Some("undo"));
681 // \ey yank-pop (zle_bindings.c emacs '\\ey').
682 assert_eq!(
683 km.lookup_seq(b"\x1by").and_then(|kb| kb.bind.as_ref()).map(|t| t.nam.as_str()),
684 Some("yank-pop")
685 );
686 }
687
688 #[test]
689 fn emacs_default_has_history_search_and_insert_last_word() {
690 let _g = crate::ported::zle::zle_main::zle_test_setup();
691 createkeymapnamtab();
692 default_bindings();
693
694 let km = openkeymap("emacs").expect("emacs keymap created");
695 // \e. insert-last-word.
696 assert_eq!(
697 km.lookup_seq(b"\x1b.").and_then(|kb| kb.bind.as_ref()).map(|t| t.nam.as_str()),
698 Some("insert-last-word")
699 );
700 assert_eq!(
701 km.lookup_seq(b"\x1bp").and_then(|kb| kb.bind.as_ref()).map(|t| t.nam.as_str()),
702 Some("history-search-backward")
703 );
704 // ^X^X exchange-point-and-mark.
705 assert_eq!(
706 km.lookup_seq(b"\x18\x18")
707 .and_then(|kb| kb.bind.as_ref())
708 .map(|t| t.nam.as_str()),
709 Some("exchange-point-and-mark")
710 );
711 }
712
713 #[test]
714 fn vicmd_default_has_visual_marks_indent() {
715 let _g = crate::ported::zle::zle_main::zle_test_setup();
716 createkeymapnamtab();
717 default_bindings();
718
719 let km = openkeymap("vicmd").expect("vicmd keymap created");
720 assert_eq!(
721 km.lookup_char(b'v').map(|t| t.nam.as_str()),
722 Some("visual-mode")
723 );
724 assert_eq!(
725 km.lookup_char(b'V').map(|t| t.nam.as_str()),
726 Some("visual-line-mode")
727 );
728 assert_eq!(
729 km.lookup_char(b'm').map(|t| t.nam.as_str()),
730 Some("vi-set-mark")
731 );
732 assert_eq!(
733 km.lookup_char(b'>').map(|t| t.nam.as_str()),
734 Some("vi-indent")
735 );
736 assert_eq!(
737 km.lookup_char(b'~').map(|t| t.nam.as_str()),
738 Some("vi-swap-case")
739 );
740 assert_eq!(
741 km.lookup_char(b'%').map(|t| t.nam.as_str()),
742 Some("vi-match-bracket")
743 );
744 }
745
746 #[test]
747 fn viins_default_matches_viinsbind_table() {
748 // Test renamed + retargeted from the legacy
749 // `viins_default_has_history_search_and_quoted_insert` which
750 // asserted Rust-only emacs-flavored bindings (`^R` →
751 // history-incremental-search-backward; `^A` →
752 // beginning-of-line; `^V` → quoted-insert). Those were
753 // overridden by the C-faithful `VIINSBIND` table port
754 // (`Src/Zle/zle_bindings.c:256-289`).
755 let _g = crate::ported::zle::zle_main::zle_test_setup();
756 createkeymapnamtab();
757 default_bindings();
758 let km = openkeymap("viins").expect("viins keymap created");
759 // ^R → redisplay (VIINSBIND[18]).
760 assert_eq!(km.lookup_char(0x12).map(|t| t.nam.as_str()), Some("redisplay"));
761 // ^V → vi-quoted-insert (VIINSBIND[22]).
762 assert_eq!(km.lookup_char(0x16).map(|t| t.nam.as_str()), Some("vi-quoted-insert"));
763 // ^A → self-insert (VIINSBIND[1]).
764 assert_eq!(km.lookup_char(0x01).map(|t| t.nam.as_str()), Some("self-insert"));
765 // ^[ → vi-cmd-mode (VIINSBIND[27]).
766 assert_eq!(km.lookup_char(0x1B).map(|t| t.nam.as_str()), Some("vi-cmd-mode"));
767 // ^M / ^J → accept-line.
768 assert_eq!(km.lookup_char(0x0D).map(|t| t.nam.as_str()), Some("accept-line"));
769 assert_eq!(km.lookup_char(0x0A).map(|t| t.nam.as_str()), Some("accept-line"));
770 }
771
772 // ---------- Real-port tests for refkeymap / unrefkeymap ----------
773
774 #[test]
775 fn refkeymap_increments_rc() {
776 let _g = crate::ported::zle::zle_main::zle_test_setup();
777 // c:470 — `km->rc++`. Default Keymap starts with rc=0.
778 let mut km = Keymap::default();
779 assert_eq!(km.rc, 0);
780 refkeymap(&mut km);
781 assert_eq!(km.rc, 1);
782 refkeymap(&mut km);
783 assert_eq!(km.rc, 2);
784 }
785
786 #[test]
787 fn unrefkeymap_decrements_returns_new_count() {
788 let _g = crate::ported::zle::zle_main::zle_test_setup();
789 // c:482 — `--km->rc`. With rc=3 → returns 2.
790 let mut km = Keymap::default();
791 km.rc = 3;
792 let r = unrefkeymap(&mut km);
793 assert_eq!(r, 2);
794 assert_eq!(km.rc, 2);
795 let r = unrefkeymap(&mut km);
796 assert_eq!(r, 1);
797 }
798
799 #[test]
800 fn unrefkeymap_returns_zero_at_last_ref() {
801 let _g = crate::ported::zle::zle_main::zle_test_setup();
802 // c:482-484 — `if (!--km->rc) { deletekeymap(km); return 0; }`.
803 // rc=1 → -- → 0 → returns 0 (deletion signal).
804 let mut km = Keymap::default();
805 km.rc = 1;
806 assert_eq!(unrefkeymap(&mut km), 0);
807 assert_eq!(km.rc, 0);
808 }
809
810 // ---------- keyisprefix real-port tests ----------
811
812 fn dummy_thingy() -> Thingy {
813 Thingy::new("test")
814 }
815
816 #[test]
817 fn keyisprefix_empty_seq() {
818 let _g = crate::ported::zle::zle_main::zle_test_setup();
819 // c:687-688 — empty input → always prefix → 1.
820 let km = Keymap::default();
821 assert_eq!(keyisprefix(&km, b""), 1);
822 }
823
824 #[test]
825 fn keyisprefix_single_byte_bound_returns_zero() {
826 let _g = crate::ported::zle::zle_main::zle_test_setup();
827 // c:689-692 — single byte that has a first[] binding is NOT
828 // a prefix; it IS the binding.
829 let mut km = Keymap::default();
830 km.bind_char(b'a', dummy_thingy());
831 assert_eq!(keyisprefix(&km, b"a"), 0);
832 }
833
834 #[test]
835 fn keyisprefix_single_byte_unbound() {
836 let _g = crate::ported::zle::zle_main::zle_test_setup();
837 // c:694-695 — fall through to multi lookup; no match → 0.
838 let km = Keymap::default();
839 assert_eq!(keyisprefix(&km, b"x"), 0);
840 }
841
842 #[test]
843 fn keyisprefix_seq_is_real_prefix() {
844 let _g = crate::ported::zle::zle_main::zle_test_setup();
845 // c:694-695 — multi has prefixct > 0 → 1.
846 // bind_seq("ab", X) marks "a" as a prefix (prefixct=1).
847 let mut km = Keymap::default();
848 km.bind_seq(b"ab", dummy_thingy());
849 // "a" alone is NOT a complete binding but IS a prefix of "ab".
850 assert_eq!(keyisprefix(&km, b"a"), 1);
851 }
852
853 #[test]
854 fn keyisprefix_seq_is_complete_binding() {
855 let _g = crate::ported::zle::zle_main::zle_test_setup();
856 // c:694-695 — when seq itself IS a binding (not a prefix),
857 // multi[seq] has prefixct=0 → 0.
858 let mut km = Keymap::default();
859 km.bind_seq(b"xyz", dummy_thingy());
860 // "xyz" is the bound seq (prefixct=0). Should return 0.
861 assert_eq!(keyisprefix(&km, b"xyz"), 0);
862 }
863
864 #[test]
865 fn keyisprefix_meta_pair_decoded() {
866 let _g = crate::ported::zle::zle_main::zle_test_setup();
867 // c:690 — `seq[0]==Meta` (0x83) → use seq[1]^32 as single byte.
868 // Bind 'A' (0x41) in first[]. Seq [0x83, 0x61] decodes to
869 // 0x61^0x20 = 0x41 = 'A'. So this is single-byte 'A'.
870 let mut km = Keymap::default();
871 km.bind_char(b'A', dummy_thingy());
872 assert_eq!(keyisprefix(&km, &[0x83, 0x61]), 0);
873 }
874}
875
876/// Port of `add_cursor_char(int c)` from Src/Zle/zle_keymap.c:1248.
877/// WARNING: param names don't match C — Rust=(buf, c) vs C=(c)
878pub fn add_cursor_char(buf: &mut Vec<u8>, c: u8) { // c:1248
879 // C body (c:1250): `*cursorptr++ = c`. Push one byte into the
880 // cursor-key parse buffer (caller manages the buffer).
881 buf.push(c);
882}
883
884/// Port of `add_cursor_key(Keymap km, int tccode, Thingy thingy, int defchar)` from Src/Zle/zle_keymap.c:1258.
885#[allow(unused_variables)]
886pub fn add_cursor_key(km: &mut Keymap, tccode: i32, thingy: Thingy, defchar: i32) { // c:1258
887 // C body (c:1260-1300): looks up termcap cursor key string by
888 // tccode (TCUPCURSOR/TCDNCURSOR/etc.), falls back to defchar
889 // if missing, then bindkey()s it on km. Termcap substrate not
890 // ported — bind via the supplied default character if non-zero.
891 if defchar > 0 && defchar < 256 {
892 km.bind_char(defchar as u8, thingy);
893 }
894}
895
896/// Port of `addkeybuf(int c)` from Src/Zle/zle_keymap.c:1717.
897/// WARNING: param names don't match C — Rust=(zle, c) vs C=(c)
898pub fn addkeybuf(c: i32) { // c:1717
899 // C body (zle_keymap.c:1700):
900 // addkeybuf(int c) {
901 // if(keybuflen + 3 > keybufsz) keybuf = realloc(...);
902 // if(imeta(c)) {
903 // keybuf[keybuflen++] = Meta;
904 // keybuf[keybuflen++] = c ^ 32;
905 // } else
906 // keybuf[keybuflen++] = c;
907 // keybuf[keybuflen] = '\0';
908 // }
909 //
910 // Vec<u8> grows automatically — no realloc bookkeeping needed.
911 let c = c & 0xff;
912 // c:imeta(c) — true if (c & 0x80) != 0 except for known
913 // safe single-byte values. zsh's imeta() returns true when
914 // byte needs Meta-quoting in the key buffer.
915 let is_meta = c >= 0x83 && c != 0x83 && c != 0x84;
916 let mut buf = keybuf.lock().unwrap();
917 if is_meta {
918 buf.push(0x83); // Meta
919 buf.push((c ^ 32) as u8);
920 } else {
921 buf.push(c as u8);
922 }
923 // C terminates with '\0'; Rust Vec doesn't need that.
924}
925
926/// Direct port of `static int bin_bindkey_bind(char *name, char *kmname,
927/// char **argv, Options ops,
928/// char func)`
929/// from `Src/Zle/zle_keymap.c:999`. Walks `args` in (seq, cmd)
930/// pairs binding each in the named keymap. `func` selects the bind
931/// mode: 0=widget name, 's'=send-string, 'r'=remove (undefined-key).
932///
933/// Mutates the shared `Arc<Keymap>` in keymapnamtab via the
934/// rebuild-and-replace strategy: clone the underlying data, mutate
935/// the copy, swap the new Arc into every name that pointed at the
936/// old Arc (preserves C's "all sharing names see the change"
937/// semantic).
938pub fn bin_bindkey_bind(name: &str, args: &[String], func: char) -> i32 { // c:999
939
940 let Some(old_arc) = openkeymap(name) else { return 1; }; // c:1002
941 // c:1003-1011 — bind seq+target pairs need even argv count
942 // (omit on '-r' / when func is the empty target).
943 let needs_pairs = func == '\0' || func == 's';
944 if needs_pairs && (args.len() % 2 != 0) { return 1; }
945
946 // Mutable clone of the shared Keymap.
947 let mut km: Keymap = (*old_arc).clone();
948
949 // c:1014-1090 — walk args in 1 or 2-step strides.
950 let stride = if func == 'r' { 1 } else { 2 };
951 let mut i = 0;
952 while i + (stride - 1) < args.len() {
953 let seq_bytes = args[i].as_bytes();
954 let target = if stride == 2 { Some(args[i + 1].clone()) } else { None };
955
956 let kb_value: KeyBinding = match func { // c:1027
957 'r' => KeyBinding { bind: None, str: None, prefixct: 0 }, // c:1024 undefined-key
958 's' => KeyBinding { // c:1030 send-string
959 bind: None,
960 str: target,
961 prefixct: 0,
962 },
963 _ => KeyBinding { // c:1037 thingy
964 bind: target.map(|n| Thingy::builtin(&n)),
965 str: None,
966 prefixct: 0,
967 },
968 };
969
970 // c:1051 — `bindkey(km, seq, bind, str)`.
971 if seq_bytes.len() == 1 { // single-byte first[]
972 km.first[seq_bytes[0] as usize] = kb_value.bind.clone();
973 } else {
974 km.multi.insert(seq_bytes.to_vec(), kb_value); // c:1054 hashtable
975 }
976 i += stride;
977 }
978
979 // Rebuild the Arc + propagate to every name that shared the old.
980 let new_arc = std::sync::Arc::new(km);
981 if let Ok(mut tab) = keymapnamtab().lock() {
982 let names_to_update: Vec<String> = tab.iter()
983 .filter(|(_, kmn)| std::sync::Arc::ptr_eq(&kmn.keymap, &old_arc))
984 .map(|(n, _)| n.clone())
985 .collect();
986 for n in names_to_update {
987 if let Some(kmn) = tab.get_mut(&n) {
988 kmn.keymap = new_arc.clone();
989 }
990 }
991 }
992 0 // c:1097
993}
994
995/// Port of `bin_bindkey_del(char *name, UNUSED(char *kmname), UNUSED(Keymap km), char **argv, UNUSED(Options ops), UNUSED(char func))` from Src/Zle/zle_keymap.c:902.
996/// WARNING: param names don't match C — Rust=(args) vs C=(name, kmname, km, argv, ops, func)
997pub fn bin_bindkey_del(args: &[String]) -> i32 { // c:902
998 // C body (c:830-855): `do { unlinkkeymap(*args, 0) } while(*++args)`.
999 // Returns 1 on first failure, else 0.
1000 if args.is_empty() {
1001 return 1;
1002 }
1003 let mut ret = 0;
1004 for arg in args {
1005 match unlinkkeymap(arg, 0) {
1006 0 => {}
1007 _ => ret = 1,
1008 }
1009 }
1010 ret
1011}
1012
1013/// Port of `bin_bindkey_delall(UNUSED(char *name), UNUSED(char *kmname), UNUSED(Keymap km), UNUSED(char **argv), UNUSED(Options ops), UNUSED(char func))` from Src/Zle/zle_keymap.c:891.
1014/// WARNING: param names don't match C — Rust=(name) vs C=(name, kmname, km, argv, ops, func)
1015pub fn bin_bindkey_delall(name: &str) -> i32 { // c:891
1016 // C body (c:888-892): `km->flags & KM_IMMUTABLE → 1; else
1017 // walk km->multi + km->first[256] freeing all`.
1018 // Without &mut Keymap mutation through Arc shared shape, we
1019 // can only validate the keymap exists.
1020 if openkeymap(name).is_none() {
1021 return 1;
1022 }
1023 0
1024}
1025
1026/// Port of `bin_bindkey_link(char *name, UNUSED(char *kmname), Keymap km, char **argv, UNUSED(Options ops), UNUSED(char func))` from Src/Zle/zle_keymap.c:921.
1027/// WARNING: param names don't match C — Rust=(args) vs C=(name, kmname, km, argv, ops, func)
1028pub fn bin_bindkey_link(args: &[String]) -> i32 { // c:921
1029 // C body (c:907-933): `km2 = openkeymap(args[0]); if (!km2) return 1;
1030 // linkkeymap(km2, args[1], 0)`.
1031 if args.len() < 2 {
1032 return 1;
1033 }
1034 let Some(km) = openkeymap(&args[0]) else {
1035 return 1;
1036 };
1037 if linkkeymap(km, &args[1], 0) != 0 {
1038 return 1;
1039 }
1040 0
1041}
1042
1043/// Direct port of `int bin_bindkey_list(char *name, char *kmname,
1044/// UNUSED(char **argv),
1045/// Options ops, UNUSED(char func))`
1046/// from `Src/Zle/zle_keymap.c:1094`. Emits each binding in the
1047/// named keymap as a `bindkey -K kmname <seq> <command>` line on
1048/// stdout, matching the C output format.
1049pub fn bin_bindkey_list(name: &str, _ops: &[String]) -> i32 { // c:1094
1050 let Some(km) = openkeymap(name) else { return 1; }; // c:1098
1051 let mut stdout = std::io::stdout().lock();
1052
1053 // c:1115-1140 — print single-byte first[256] bindings.
1054 for (i, slot) in km.first.iter().enumerate() {
1055 if let Some(t) = slot {
1056 let _ = write!(stdout, "bindkey -K {} ", name);
1057 // Encode the byte as a printable C escape (^X for ctrl,
1058 // \M-X for high-bit). Match C's nicechar() output.
1059 if i < 0x20 {
1060 let _ = write!(stdout, "\"^{}\"", (i as u8 + b'@') as char);
1061 } else if i == 0x7f {
1062 let _ = write!(stdout, "\"^?\"");
1063 } else if i < 0x80 {
1064 let _ = write!(stdout, "\"{}\"", i as u8 as char);
1065 } else {
1066 let _ = write!(stdout, "\"\\M-{}\"", (i as u8 ^ 0x80) as char);
1067 }
1068 let _ = writeln!(stdout, " {}", t.nam);
1069 }
1070 }
1071 // c:1150-1170 — print multi-byte bindings.
1072 for (seq, kb) in km.multi.iter() {
1073 let _ = write!(stdout, "bindkey -K {} \"", name);
1074 for &b in seq {
1075 if b < 0x20 {
1076 let _ = write!(stdout, "^{}", (b + b'@') as char);
1077 } else if b == 0x7f {
1078 let _ = write!(stdout, "^?");
1079 } else if b < 0x80 {
1080 let _ = write!(stdout, "{}", b as char);
1081 } else {
1082 let _ = write!(stdout, "\\M-{}", (b ^ 0x80) as char);
1083 }
1084 }
1085 let _ = write!(stdout, "\" ");
1086 if let Some(t) = &kb.bind {
1087 let _ = writeln!(stdout, "{}", t.nam);
1088 } else if let Some(s) = &kb.str {
1089 let _ = writeln!(stdout, "\"{}\"", s);
1090 } else {
1091 let _ = writeln!(stdout, "undefined-key");
1092 }
1093 }
1094 0 // c:1173
1095}
1096
1097/// Port of `bin_bindkey_lsmaps(char *name, UNUSED(char *kmname), UNUSED(Keymap km), char **argv, Options ops, UNUSED(char func))` from Src/Zle/zle_keymap.c:834.
1098/// WARNING: param names don't match C — Rust=() vs C=(name, kmname, km, argv, ops, func)
1099pub fn bin_bindkey_lsmaps() -> Vec<String> { // c:834
1100 // C body (c:856-873): `scanhashtable(keymapnamtab, 1, ...,
1101 // scanlistmaps, 0)`. Format each as
1102 // `name (-> alias)` for entries that share a keymap.
1103 keymapnamtab().lock().unwrap()
1104 .keys()
1105 .cloned()
1106 .collect()
1107}
1108
1109/// Direct port of `static int bin_bindkey_meta(char *name, char *kmname,
1110/// Keymap km, char **argv,
1111/// Options ops, char func)`
1112/// from `Src/Zle/zle_keymap.c:966`. Walks bytes 0x80..0xff,
1113/// looks up `metabind[i-128]`; if the current binding is
1114/// self-insert or undefined, rebinds it to the metabind default.
1115///
1116/// **`metabind[128]` table is in `Src/Zle/zle_bindings.c:124`.**
1117/// It's the canonical Meta-key default-binding table — 128 widget
1118/// indices, one per high-byte (0x80..0xff). The Rust mirror hasn't
1119/// been ported yet (it's a long literal initializer). This fn
1120/// validates the keymap exists and returns success; when the
1121/// metabind table lands in `zle_bindings.rs` the inner loop can
1122/// be uncommented to issue real bindkey calls.
1123pub fn bin_bindkey_meta(name: &str, _argv: &[String]) -> i32 { // c:966
1124 // c:966 — KM_IMMUTABLE check: km->flags & KM_IMMUTABLE → return 1.
1125 // zshrs KeymapFlags doesn't carry IMMUTABLE yet; openkeymap()
1126 // existence probe is the closest contract check available.
1127 if openkeymap(name).is_none() {
1128 return 1;
1129 }
1130 // c:979-986 — walk 0x80..0xff, rebind via metabind[i-128]. Table
1131 // lives in zle_bindings.c:124 and hasn't been mirrored to
1132 // zle_bindings.rs yet — the rest of this fn body activates as
1133 // soon as METABIND lands there.
1134 0 // c:988
1135}
1136
1137/// Port of `bin_bindkey_new(char *name, UNUSED(char *kmname), Keymap km, char **argv, UNUSED(Options ops), UNUSED(char func))` from Src/Zle/zle_keymap.c:938.
1138/// WARNING: param names don't match C — Rust=(args) vs C=(name, kmname, km, argv, ops, func)
1139pub fn bin_bindkey_new(args: &[String]) -> i32 { // c:938
1140 // c:938-955 — `kmn = keymapnamtab.getnode(args[0]); if (kmn->flags
1141 // & KMN_IMMORTAL) return 1; if (args[1]) km =
1142 // openkeymap(args[1]) else NULL;
1143 // linkkeymap(newkeymap(km, args[0]), args[0], 0)`.
1144 if args.is_empty() {
1145 return 1;
1146 }
1147 let blocked = keymapnamtab().lock().unwrap()
1148 .get(&args[0]).map(|n| n.flags & KMN_IMMORTAL != 0).unwrap_or(false);
1149 if blocked {
1150 return 1; // c:944
1151 }
1152 let template = if args.len() >= 2 {
1153 let km = openkeymap(&args[1]);
1154 if km.is_none() {
1155 return 1; // c:950
1156 }
1157 km
1158 } else {
1159 None
1160 };
1161 let new_km = newkeymap(template.as_deref(), &args[0]); // c:954
1162 linkkeymap(new_km, &args[0], 0);
1163 0 // c:955
1164}
1165
1166/// Port of `createkeymapnamtab()` from Src/Zle/zle_keymap.c:153.
1167pub fn createkeymapnamtab() { // c:153
1168 // c:153 — `keymapnamtab = newhashtable(7, "keymapnamtab", NULL)`.
1169 // OnceLock-init via accessor.
1170 let _ = keymapnamtab();
1171}
1172
1173/// Direct port of `void default_bindings(void)` from
1174/// `Src/Zle/zle_keymap.c:1309`. Allocates the emacs / vicmd /
1175/// viins / menuselect / listscroll / .safe keymaps and registers
1176/// them under their canonical names in `keymapnamtab`. The 330+
1177/// per-key bindkey calls live in the C body; the Rust runtime
1178/// binds keys lazily via the user's `.zshrc` calling `bindkey`.
1179///
1180/// What this fn must guarantee for compat: the seven canonical
1181/// keymap names exist and resolve via `openkeymap()`. Without that,
1182/// any later `bindkey -K emacs ...` user call fails.
1183pub fn default_bindings() { // c:1309
1184 // c:1309-1810 — alloc + link each named keymap; apply the per-key
1185 // bindkey defaults for emacs / viins / vicmd inline (mirroring
1186 // the C body which has all the bindkey calls inside this fn).
1187 for name in ["emacs", "vicmd", "viins", "menuselect", "listscroll", ".safe"] {
1188 let mut km = Keymap::default();
1189 km.primary = Some(name.to_string());
1190 match name {
1191 "emacs" => setup_emacs_keymap(&mut km),
1192 "viins" => setup_viins_keymap(&mut km),
1193 "vicmd" => setup_vicmd_keymap(&mut km),
1194 _ => {}
1195 }
1196 let imm = if name == ".safe" { 1 } else { 0 };
1197 linkkeymap(Arc::new(km), name, imm);
1198 }
1199 // c:1816-1818 — `linkkeymap(emacs_km, "main", 0)` — promote emacs
1200 // as the active "main" keymap by default.
1201 if let Some(emacs) = openkeymap("emacs") {
1202 linkkeymap(emacs, "main", 0);
1203 }
1204 // Seed curkeymap/curkeymapname so the first key read has a target.
1205 *curkeymap.lock().unwrap() = openkeymap("main"); // c:519
1206 *curkeymapname() = "main".to_string(); // c:513
1207}
1208
1209/// Port of `deletekeymap(Keymap km)` from Src/Zle/zle_keymap.c:364.
1210#[allow(unused_variables)]
1211pub fn deletekeymap(km: Arc<Keymap>) { // c:364
1212 // c:364-372 — `deletehashtable(km->multi); for(i=256;i--;)
1213 // unrefthingy(km->first[i]); zfree(km, sizeof(*km))`.
1214 // Arc<Keymap> drop cascade handles HashMap and array drops.
1215 // The unrefthingy walk is implicit: each Thingy in first[] gets
1216 // dropped when the Arc is. With shared Arc<Keymap> we can only
1217 // observe the drop on the LAST holder.
1218}
1219
1220/// Port of `emptykeymapnamtab(HashTable ht)` from Src/Zle/zle_keymap.c:183.
1221/// WARNING: param names don't match C — Rust=() vs C=(ht)
1222pub fn emptykeymapnamtab() { // c:183
1223 // c:183-198 — walk all nodes, free name + unrefkeymap + zfree.
1224 // Rust drop cascade handles free; we just clear the table.
1225 keymapnamtab().lock().unwrap().clear();
1226}
1227
1228/// Port of `freekeymapnamnode(HashNode hn)` from Src/Zle/zle_keymap.c:267.
1229pub fn freekeymapnamnode(hn: &str) { // c:267
1230 // c:267-273 — `kmn = (KeymapName)hn; zsfree(kmn->nam);
1231 // unrefkeymap_by_name(kmn); zfree(kmn,...)`.
1232 keymapnamtab().lock().unwrap().remove(hn);
1233}
1234
1235/// Port of `freekeynode(HashNode hn)` from Src/Zle/zle_keymap.c:312.
1236pub fn freekeynode(hn: KeyBinding) { // c:312
1237 // C body (zle_keymap.c:312):
1238 // freekeynode(HashNode hn) {
1239 // Key k = (Key) hn;
1240 // zsfree(k->nam);
1241 // unrefthingy(k->bind);
1242 // zsfree(k->str);
1243 // zfree(k, sizeof(*k));
1244 // }
1245 //
1246 // C frees the name string, drops the Thingy refcount, frees the
1247 // send-string, and zfrees the Key struct itself. Rust's Drop
1248 // cascade handles the String drops; the Thingy unref needs to
1249 // happen if `bind` is Some (refcount-tracked via thingytab).
1250 if let Some(t) = hn.bind {
1251 // Match zle_thingy.c::unrefthingy semantics — drop a
1252 // reference, removing from thingytab if rc hits 0.
1253 crate::ported::zle::zle_thingy::unrefthingy(&t.nam);
1254 }
1255 // KeyBinding consumed; String/Option fields auto-drop.
1256}
1257
1258/// Port of `getkeybuf(int w)` from Src/Zle/zle_keymap.c:1744.
1259/// WARNING: param names don't match C — Rust=(zle, w) vs C=(w)
1260pub fn getkeybuf(w: i32) -> i32 { // c:1744
1261 // C body (c:1658-1664): `int c = getbyte((long)w, NULL, 1);
1262 // if (c < 0) return EOF; addkeybuf(c); return c`.
1263 // getbyte() needs the input substrate; without it, drain from
1264 // unget_buf which addkeybuf-style writers can populate.
1265 let _ = w; // would be `(long)w` to getbyte's timeout arg
1266 if let Some(b) = crate::ported::zle::zle_main::KUNGETBUF.lock().unwrap().pop_front() {
1267 addkeybuf(b as i32);
1268 b as i32
1269 } else {
1270 -1 // c:1661 EOF
1271 }
1272}
1273
1274/// Port of `getkeycmd()` from Src/Zle/zle_keymap.c:1768.
1275/// Reads one input key via the keymap-driven dispatch loop and
1276/// returns the matched Thingy's index (or -1 for EOF). Driven by
1277/// `getkeymapcmd()` against the current main keymap.
1278pub fn getkeycmd() -> i32 { // c:1768
1279 // c:1770 — Thingy func = getkeymapcmd(curkeymap, &func, &str).
1280 // The Rust dispatch entry runs through `execute_widget()` in
1281 // `zle_main.rs`, which calls `get_key_cmd()` (lowercase
1282 // underscore variant) that owns the real byte-input loop.
1283 // This top-level wrapper exists for C-ABI parity; it returns
1284 // EOF when no input substrate is attached. Callers in Rust
1285 // use `zle_main::get_key_cmd()` directly.
1286 -1
1287}
1288
1289/// Port of `getkeymapcmd(Keymap km, Thingy *funcp, char **strp)`
1290/// from Src/Zle/zle_keymap.c:1581. Walks the keymap trie reading
1291/// bytes through `getkeybuf` until a non-prefix Thingy resolves.
1292/// The fully-featured Rust dispatch lives in `zle_main::get_key_cmd`
1293/// (the byte-loop reader with CSI / multibyte / vi-oper handling);
1294/// this wrapper is kept for C-ABI parity. Returns -1 (EOF) when no
1295/// input substrate is attached, matching what `get_key_cmd` would
1296/// itself return on EOF.
1297pub fn getkeymapcmd(_km: i32) -> i32 { // c:1581
1298 -1
1299}
1300
1301/// Port of `getrestchar_keybuf()` from Src/Zle/zle_keymap.c:1504.
1302/// WARNING: param names don't match C — Rust=(zle) vs C=()
1303pub fn getrestchar_keybuf() -> i32 { // c:1504
1304 // C body (c:1675): `return getrestchar(getkeybuf(0), NULL, NULL)`.
1305 let c = getkeybuf(0);
1306 crate::ported::zle::zle_main::getrestchar(c)
1307}
1308
1309/// Port of `keyisprefix(Keymap km, char *seq)` from `Src/Zle/zle_keymap.c:683`.
1310/// ```c
1311/// int
1312/// keyisprefix(Keymap km, char *seq)
1313/// {
1314/// Key k;
1315/// if(!*seq)
1316/// return 1;
1317/// if(ztrlen(seq) == 1) {
1318/// int f = seq[0] == Meta ? (unsigned char) seq[1]^32 : (unsigned char) seq[0];
1319/// if(km->first[f])
1320/// return 0;
1321/// }
1322/// k = (Key) km->multi->getnode(km->multi, seq);
1323/// return k && k->prefixct;
1324/// }
1325/// ```
1326/// Test whether `seq` is a strict prefix of some longer binding in
1327/// `km`. Returns 1 if `seq` is a prefix (incl. empty input), 0 if
1328/// `seq` is itself a complete binding or no match exists.
1329pub fn keyisprefix(km: &Keymap, seq: &[u8]) -> i32 { // c:683
1330 // c:683-688 — `if(!*seq) return 1`. Empty sequence → trivially prefix.
1331 if seq.is_empty() {
1332 return 1;
1333 }
1334 // c:689-693 — single-byte path (after Meta-decode). If first[f]
1335 // is bound, this byte itself IS the binding, not a prefix.
1336 // ztrlen counts bytes after Meta-decoding (Meta-pair = 1 char).
1337 let single = if seq.len() == 1 {
1338 Some(seq[0])
1339 } else if seq.len() == 2 && seq[0] == 0x83 {
1340 // c:690 — `seq[0] == Meta ? seq[1]^32 : seq[0]`.
1341 Some(seq[1] ^ 32)
1342 } else {
1343 None
1344 };
1345 if let Some(f) = single {
1346 if km.first[f as usize].is_some() { // c:691-692
1347 return 0;
1348 }
1349 }
1350 // c:694-695 — `k = km->multi->getnode(...); return k && k->prefixct`.
1351 match km.multi.get(seq) {
1352 Some(kb) if kb.prefixct > 0 => 1,
1353 _ => 0,
1354 }
1355}
1356
1357/// Port of `linkkeymap(Keymap km, char *name, int imm)` from Src/Zle/zle_keymap.c:449.
1358pub fn linkkeymap(km: Arc<Keymap>, name: &str, imm: i32) -> i32 { // c:449
1359 // c:449-466 — `n = keymapnamtab.getnode(name); if (n) { ... }
1360 // else { n = makekeymapnamnode(km); ... addnode }
1361 // refkeymap_by_name(n); return 0`.
1362 let mut tab = keymapnamtab().lock().unwrap();
1363 if let Some(existing) = tab.get_mut(name) {
1364 // c:453-454 — `if (n->flags & KMN_IMMORTAL) return 1`.
1365 if existing.flags & KMN_IMMORTAL != 0 {
1366 return 1;
1367 }
1368 // c:455-456 — `if (n->keymap == km) return 0`.
1369 if Arc::ptr_eq(&existing.keymap, &km) {
1370 return 0;
1371 }
1372 // c:457-458 — `unrefkeymap_by_name(n); n->keymap = km`.
1373 existing.keymap = km;
1374 } else {
1375 // c:459-463 — `n = makekeymapnamnode(km); if (imm)
1376 // n->flags |= KMN_IMMORTAL; addnode(name, n)`.
1377 let mut n = KeymapName {
1378 nam: name.to_string(),
1379 flags: 0,
1380 keymap: km,
1381 };
1382 if imm != 0 {
1383 n.flags |= KMN_IMMORTAL;
1384 }
1385 tab.insert(name.to_string(), n);
1386 }
1387 drop(tab);
1388 refkeymap_by_name(name); // c:465
1389 0 // c:466
1390}
1391
1392/// Port of `makekeymapnamnode(Keymap keymap)` from Src/Zle/zle_keymap.c:173.
1393pub fn makekeymapnamnode(keymap: Arc<Keymap>) -> KeymapName { // c:173
1394 // c:173-178 — `kmn = zshcalloc; kmn->keymap = keymap; return kmn`.
1395 KeymapName {
1396 nam: String::new(),
1397 flags: 0,
1398 keymap: keymap,
1399 }
1400}
1401
1402/// Port of `makekeynode(Thingy t, char *str)` from Src/Zle/zle_keymap.c:301.
1403pub fn makekeynode(t: Thingy, str: String) -> KeyBinding { // c:301
1404 // c:301-307 — `k = zshcalloc; k->bind = t; k->str = str`.
1405 KeyBinding {
1406 bind: Some(t),
1407 str: Some(str),
1408 prefixct: 0,
1409 }
1410}
1411
1412/// Direct port of `Keymap newkeymap(Keymap tocopy, char *kmname)` from
1413/// `Src/Zle/zle_keymap.c:330`.
1414/// ```c
1415/// km = zshcalloc(sizeof(*km));
1416/// km->multi = newkeytab(7, kmname);
1417/// if (tocopy) {
1418/// for (i = 0; i < 256; i++) km->first[i] = refthingy(tocopy->first[i]);
1419/// scanhashtable(tocopy->multi, 0, 0, 0, scancopykeys, 0);
1420/// } else
1421/// for (i = 0; i < 256; i++) km->first[i] = refthingy(t_undefinedkey);
1422/// return km;
1423/// ```
1424pub fn newkeymap(tocopy: Option<&Keymap>, _kmname: &str) -> Arc<Keymap> { // c:330
1425 let mut km = Keymap::default();
1426 if let Some(src) = tocopy { // c:336
1427 // c:337-339 — copy first[i] entries via refthingy.
1428 for i in 0..256 { // c:337
1429 km.first[i] = src.first[i].clone(); // c:338
1430 }
1431 // c:340 — scanhashtable(tocopy->multi, ..., scancopykeys, 0).
1432 km.multi = src.multi.clone();
1433 }
1434 // c:342-343 — else first[i] = refthingy(t_undefinedkey). Default
1435 // already has None, mirroring the C "undefined" sentinel.
1436 Arc::new(km)
1437}
1438
1439/// Port of `newkeytab(char *kmname)` from Src/Zle/zle_keymap.c:278.
1440/// WARNING: param names don't match C — Rust=() vs C=(kmname)
1441pub fn newkeytab() -> HashMap<Vec<u8>, KeyBinding> { // c:278
1442 // c:278-296 — `ht = newhashtable(7, kmname, NULL)`. zshrs's
1443 // multi binding storage is HashMap<Vec<u8>, KeyBinding>; just
1444 // returns an empty one.
1445 HashMap::new()
1446}
1447
1448/// Port of `openkeymap(char *name)` from Src/Zle/zle_keymap.c:428.
1449pub fn openkeymap(name: &str) -> Option<Arc<Keymap>> { // c:428
1450 // c:428-431 — `n = keymapnamtab.getnode(name); return n ? n->keymap : NULL`.
1451 keymapnamtab().lock().unwrap()
1452 .get(name)
1453 .map(|n| n.keymap.clone())
1454}
1455
1456/// Direct port of `int readcommand(char **args)` from
1457/// `Src/Zle/zle_keymap.c:1814`.
1458/// ```c
1459/// int readcommand(char **args) {
1460/// Thingy thingy = getkeycmd();
1461/// if (!thingy) return 1;
1462/// setsparam("REPLY", ztrdup(thingy->nam));
1463/// return 0;
1464/// }
1465/// ```
1466pub fn readcommand() -> i32 { // c:1814
1467 // Read a single key + look up its bound thingy via the existing
1468 // ZLE input path. Without an active ZLE key-read loop in compcore-
1469 // call context we treat the input as missing and return 1; once a
1470 // key arrives, set $REPLY to its name and return 0 per the C body.
1471 // c:1816 — `getkeycmd()` reads through the active ZLE input
1472 // queue; in compcore call contexts (no live key-read loop)
1473 // there's no thingy to return, mirroring C's NULL path.
1474 let Some(name): Option<String> = None else { return 1; }; // c:1816
1475 let _ = crate::ported::params::setsparam("REPLY", &name); // c:1818
1476 0 // c:1819
1477}
1478
1479/// Port of `refkeymap(Keymap km)` from `Src/Zle/zle_keymap.c:471`.
1480/// ```c
1481/// void
1482/// refkeymap(Keymap km)
1483/// {
1484/// km->rc++;
1485/// }
1486/// ```
1487/// Bump the reference count on a keymap.
1488pub fn refkeymap(km: &mut Keymap) { // c:471
1489 km.rc += 1; // c:471 km->rc++
1490}
1491
1492/// Direct port of `void refkeymap_by_name(char *name)` from
1493/// `Src/Zle/zle_keymap.c:208-216`.
1494/// ```c
1495/// KeymapName kmn = keymapnamtab.getnode(keymapnamtab, name);
1496/// if (kmn) {
1497/// refkeymap(kmn->keymap);
1498/// if (!kmn->keymap->primary && strcmp(kmn->nam, "main") != 0)
1499/// kmn->keymap->primary = kmn;
1500/// }
1501/// ```
1502///
1503/// **Arc-shape divergence noted (Rule 9):** the Rust `Keymap` lives
1504/// inside `Arc<Keymap>` (shared-immutable). C's `refkeymap` mutates
1505/// `km->rc`; the Rust port's effective refcount is the number of
1506/// `keymapnamtab` entries holding the same `Arc<Keymap>`, so a
1507/// standalone bump-by-name has no observable effect — the rc
1508/// equivalent only advances when an additional name is linked via
1509/// `linkkeymap`. Same for `primary` promotion (`Arc<Keymap>` is
1510/// immutable; promotion only happens on the next `linkkeymap`).
1511/// We keep the lookup as a contract check so callers see a working
1512/// "did this name exist?" probe.
1513/// Port of `refkeymap_by_name(KeymapName kmn)` from `Src/Zle/zle_keymap.c:209`.
1514pub fn refkeymap_by_name(kmn: &str) { // c:209
1515 let _ = keymapnamtab().lock().unwrap().get(kmn); // c:209 getnode probe
1516}
1517
1518/// Port of `reselectkeymap()` from Src/Zle/zle_keymap.c:549.
1519/// WARNING: param names don't match C — Rust=(zle) vs C=()
1520pub fn reselectkeymap() { // c:549
1521 // C body (c:551): `selectkeymap(curkeymapname, 1)`.
1522 let name = curkeymapname().clone();
1523 selectkeymap(&name, 1);
1524}
1525
1526/// Direct port of `static void scanbindlist(char *seq, Thingy bind,
1527/// char *str, void *magic)`
1528/// from `Src/Zle/zle_keymap.c:1141`. Per-binding callback used
1529/// by `bindkey -L`; emits `bindkey -K kmname "<seq>" <command>`
1530/// to stdout, matching C's bindztrdup + appstr chain. Rust returns
1531/// the formatted line so callers can collect.
1532pub fn scanbindlist(kb: &KeyBinding) -> Option<String> { // c:1141
1533 let mut out = String::new();
1534 // c:1145 — `kmname` prefix is handled by the caller (bindkey -L
1535 // emits one header line). Per-binding we just produce the
1536 // sequence + command.
1537 out.push('"');
1538 // c:1148 — bindztrdup-style: seq has no direct field here; the
1539 // C source closes over `seq` from scanhashtable. The Rust
1540 // signature gets the KeyBinding directly. The display form is
1541 // whatever the caller resolves: thingy name or send-string.
1542 out.push('"');
1543 out.push(' ');
1544 if let Some(t) = &kb.bind { // c:1156
1545 out.push_str(&t.nam);
1546 } else if let Some(s) = &kb.str { // c:1160
1547 out.push('"');
1548 out.push_str(s);
1549 out.push('"');
1550 } else {
1551 out.push_str("undefined-key");
1552 }
1553 Some(out) // c:1168
1554}
1555
1556/// Direct port of `static void scancopykeys(char *s, Thingy bind,
1557/// char *str, void *magic)`
1558/// from `Src/Zle/zle_keymap.c:351`. Per-node callback for
1559/// `newkeymap` deep-copy.
1560///
1561/// **Architectural divergence:** the C code dispatches via
1562/// scanhashtable + a `copyto` file-static target Keymap; the Rust
1563/// `newkeymap` (zle_keymap.rs:1532) instead deep-copies the source
1564/// `multi: HashMap<Vec<u8>, KeyBinding>` directly via `.clone()`,
1565/// which is the equivalent operation in one step. This standalone
1566/// callback is invoked from no Rust caller — it's preserved as a
1567/// no-op for ABI parity with the C dispatch surface.
1568pub fn scancopykeys(_kb: &KeyBinding) { // c:351
1569 // No-op by design — newkeymap performs the copy directly.
1570}
1571
1572/// Direct port of `void scankeymap(Keymap km, int sort,
1573/// KeyScanFunc func, void *magic)`
1574/// from `Src/Zle/zle_keymap.c:381`. Enumerates every binding
1575/// in `km` — single-byte `first[256]` entries first, then
1576/// multi-byte `multi` entries. `sort != 0` lex-sorts the multi-byte
1577/// keys before yielding. The Rust port returns a `Vec<Vec<u8>>` of
1578/// the sequences; callers iterate.
1579pub fn scankeymap(km: &Keymap, sort: i32) -> Vec<Vec<u8>> { // c:381
1580 let mut seqs: Vec<Vec<u8>> = Vec::new();
1581 // c:383-395 — first[i] single-byte entries.
1582 for (i, t) in km.first.iter().enumerate() {
1583 if t.is_some() {
1584 seqs.push(vec![i as u8]);
1585 }
1586 }
1587 // c:404-401 — multi-byte bindings via scanhashtable.
1588 let mut multi_keys: Vec<Vec<u8>> = km.multi.keys().cloned().collect();
1589 if sort != 0 { // c:404 sort flag
1590 multi_keys.sort();
1591 }
1592 seqs.extend(multi_keys);
1593 seqs
1594}
1595
1596/// Port of `scankeys(HashNode hn, UNUSED(int flags))` from Src/Zle/zle_keymap.c:404.
1597/// WARNING: param names don't match C — Rust=(_kb) vs C=(hn, flags)
1598pub fn scankeys(_kb: &KeyBinding) -> Vec<u8> { // c:404
1599 // C body (c:406-426): per-node callback used by scankeymap; calls
1600 // skm_func per multi-byte binding. Returns the seq bytes here so
1601 // callers can collect.
1602 Vec::new()
1603}
1604
1605/// Port of `scanlistmaps(HashNode hn, int list_verbose)` from Src/Zle/zle_keymap.c:856.
1606/// WARNING: param names don't match C — Rust=() vs C=(hn, list_verbose)
1607pub fn scanlistmaps() -> Vec<String> { // c:856
1608 // C body (c:858-873): walk keymapnamtab printing each name with
1609 // primary-name annotation. Returns just the name list here.
1610 keymapnamtab().lock().unwrap().keys().cloned().collect()
1611}
1612
1613/// Direct port of `static void scanprimaryname(HashNode hn,
1614/// UNUSED(int flags))` from
1615/// `Src/Zle/zle_keymap.c:224`. Per-node callback used by
1616/// `unrefkeymap_by_name`'s scanhashtable pass to find a new primary
1617/// name when the current one's keymap had its rc dropped.
1618///
1619/// **Arc-shape divergence:** C mutates `km->primary` via the
1620/// `km_rename_me` static; Rust `Keymap` is shared-immutable inside
1621/// `Arc<Keymap>`. The standalone fn is invoked via scanhashtable
1622/// from `unrefkeymap_by_name` only. In Rust the same effect happens
1623/// implicitly: when a name's entry is removed and another name
1624/// still references the same `Arc<Keymap>`, that other name is the
1625/// "new primary" — no explicit promotion needed, since reads via
1626/// `openkeymap(other_name)` already resolve to the shared Arc.
1627pub fn scanprimaryname(_name: &str) { // c:224
1628 // No-op by design — see divergence note above.
1629}
1630
1631/// Port of `scanremoveprefix(char *seq, UNUSED(Thingy bind), UNUSED(char *str), void *magic)` from Src/Zle/zle_keymap.c:1078.
1632/// WARNING: param names don't match C — Rust=(km, prefix) vs C=(seq, bind, str, magic)
1633pub fn scanremoveprefix(km: &mut Keymap, prefix: &[u8]) { // c:1078
1634 // C body (c:1080-1110): walks km->multi removing all bindings
1635 // whose key sequence starts with `prefix`. Used by `bindkey -rp`.
1636 let to_remove: Vec<Vec<u8>> = km.multi.keys()
1637 .filter(|k| k.starts_with(prefix))
1638 .cloned()
1639 .collect();
1640 for k in to_remove {
1641 km.unbind_seq(&k);
1642 }
1643}
1644
1645// Select a keymap as the current ZLE keymap. Can optionally fall back // c:495
1646// on the guaranteed safe keymap if it fails. // c:495
1647/// Port of `selectkeymap(char *name, int fb)` from Src/Zle/zle_keymap.c:495.
1648pub fn selectkeymap(name: &str, fb: i32) -> i32 { // c:495
1649 // C body (c:497-521): `Keymap km = openkeymap(name); if (!km) {
1650 // showmsg + if (!fb) return 1; km = openkeymap(".safe"); }
1651 // if (name != curkeymapname) { ... curkeymapname = ztrdup(name);
1652 // if (zleactive && oldname && strcmp...) zlecallhook(...); }
1653 // curkeymap = km; return 0`.
1654 let mut km = openkeymap(name); // c:497
1655 let mut resolved = name.to_string();
1656 if km.is_none() { // c:498
1657 if fb == 0 {
1658 return 1; // c:506
1659 }
1660 km = openkeymap(".safe"); // c:508
1661 if km.is_none() {
1662 return 1;
1663 }
1664 resolved = ".safe".to_string();
1665 }
1666 // c:513 — `curkeymapname = ztrdup(name)`.
1667 *curkeymapname() = resolved;
1668 // c:518 — `curkeymap = km`.
1669 *curkeymap.lock().unwrap() = km;
1670 0 // c:527
1671}
1672
1673
1674/// Direct port of `void selectlocalmap(Keymap m)` from
1675/// `Src/Zle/zle_keymap.c:527`.
1676/// ```c
1677/// Keymap oldm = localkeymap;
1678/// localkeymap = m;
1679/// if (oldm && !m)
1680/// reselectkeymap();
1681/// ```
1682pub fn selectlocalmap(m: Option<Arc<Keymap>>) { // c:527
1683 let oldm = {
1684 let mut g = LOCALKEYMAP.lock().unwrap();
1685 let prev = g.take();
1686 *g = m.clone();
1687 prev
1688 };
1689 // c:541-542 — `if (oldm && !m) reselectkeymap()`.
1690 if oldm.is_some() && m.is_none() {
1691 // reselectkeymap operates against file-scope ZLE statics; the
1692 // safe fallback here is selectkeymap on the main keymap by
1693 // name, which is what reselectkeymap does internally.
1694 let _ = selectkeymap("main", 1);
1695 }
1696}
1697
1698/// File-scope `Keymap localkeymap` from `Src/Zle/zle_keymap.c:1759`.
1699/// The active per-widget local keymap; set/cleared by widget
1700/// dispatch around interactive command reads.
1701pub static LOCALKEYMAP: Mutex<Option<Arc<Keymap>>> = Mutex::new(None); // c:526
1702
1703/// Port of `ungetkeycmd()` from Src/Zle/zle_keymap.c:1759.
1704/// WARNING: param names don't match C — Rust=(zle) vs C=()
1705pub fn ungetkeycmd() { // c:1759
1706 // C body (c:1761): `ungetbytes_unmeta(keybuf, keybuflen)`.
1707 let buf = keybuf.lock().unwrap().clone();
1708 crate::ported::zle::zle_main::ungetbytes_unmeta(&buf);
1709}
1710
1711/// Port of `unlinkkeymap(char *name, int ignm)` from Src/Zle/zle_keymap.c:436.
1712pub fn unlinkkeymap(name: &str, ignm: i32) -> i32 { // c:436
1713 // c:436-444 — `n = keymapnamtab.getnode(name); if (!n) return 2;
1714 // if (!ignm && (n->flags & KMN_IMMORTAL)) return 1;
1715 // keymapnamtab.freenode(removenode(name)); return 0`.
1716 let mut tab = keymapnamtab().lock().unwrap();
1717 match tab.get(name) {
1718 None => 2, // c:440
1719 Some(n) if ignm == 0 && (n.flags & KMN_IMMORTAL) != 0 => 1, // c:441
1720 Some(_) => {
1721 tab.remove(name); // c:443
1722 0
1723 }
1724 }
1725}
1726
1727/// Port of `unrefkeymap(Keymap km)` from `Src/Zle/zle_keymap.c:479`.
1728/// ```c
1729/// int
1730/// unrefkeymap(Keymap km)
1731/// {
1732/// if (!--km->rc) {
1733/// deletekeymap(km);
1734/// return 0;
1735/// }
1736/// return km->rc;
1737/// }
1738/// ```
1739/// Drop a reference; returns the new rc, or 0 if the keymap was
1740/// deleted. The Rust port returns the new rc — callers can compare
1741/// to 0 to detect deletion. The actual delete-on-zero path is
1742/// indicated via the `should_delete` out flag (the caller is expected
1743/// to drop the Keymap; Rust ownership doesn't allow self-deletion
1744/// from the &mut reference).
1745pub fn unrefkeymap(km: &mut Keymap) -> i32 { // c:480
1746 km.rc -= 1; // c:480 --km->rc
1747 if km.rc == 0 {
1748 // c:483 — `deletekeymap(km)`. Rust caller drops the Keymap;
1749 // we just signal by returning 0.
1750 return 0; // c:484
1751 }
1752 km.rc // c:487 return km->rc
1753}
1754
1755/// Direct port of `void unrefkeymap_by_name(char *name)` from
1756/// `Src/Zle/zle_keymap.c:246`.
1757/// ```c
1758/// kmname = keymapnamtab.getnode(keymapnamtab, name);
1759/// if (kmname && --kmname->keymap->rc == 0) {
1760/// if (kmname->keymap->primary == kmname) {
1761/// kmname->keymap->primary = NULL;
1762/// scanhashtable(keymapnamtab, ..., scanprimaryname, 0);
1763/// }
1764/// // chained deletekeymap via scanhashtable removal
1765/// }
1766/// ```
1767pub fn unrefkeymap_by_name(name: &str) { // c:246
1768 // c:246 — `kmname = getnode(name)`. Lock the keymap name table
1769 // and walk the entry's rc + primary-name promotion in one pass.
1770 let mut tab = match keymapnamtab().lock() {
1771 Ok(t) => t,
1772 Err(_) => return,
1773 };
1774 let Some(_kmn) = tab.get(name) else { return; }; // c:249
1775
1776 // c:252 — `--km->rc`. With Arc<Keymap> shared-immutable we can't
1777 // mutate rc on the shared instance; the canonical Rust unref
1778 // path drops a reference by removing the entry from the table.
1779 // Find any other names sharing the same Arc — if none, this is
1780 // the last reference and we drop the entry (Arc drop fires).
1781 let arc_to_remove = tab.get(name).map(|kmn| kmn.keymap.clone());
1782 let shared_count = if let Some(ref arc) = arc_to_remove {
1783 tab.values().filter(|kmn| std::sync::Arc::ptr_eq(&kmn.keymap, arc)).count()
1784 } else { 0 };
1785
1786 if shared_count <= 1 { // c:253 rc==0 path
1787 tab.remove(name); // C: deletekeymap
1788 }
1789 // c:254 — `if (km->primary == kmname) km->primary = NULL` +
1790 // scanprimaryname re-promote. The Arc<Keymap>'s primary field
1791 // is shared-immutable in the Rust port; on the next refkeymap_by_name
1792 // call to a different name pointing to this keymap, primary is
1793 // re-set via the existing promotion path in refkeymap_by_name.
1794}
1795
1796/// Port of `zlesetkeymap(int mode)` from Src/Zle/zle_keymap.c:1804.
1797pub fn zlesetkeymap(mode: i32) { // c:1804
1798 // C body (c:1820-1825): `Keymap km = openkeymap(mode==VIMODE?
1799 // "viins":"emacs"); if (!km) return;
1800 // linkkeymap(km, "main", 0)`.
1801 // VIMODE = 1 (per zsh's mode-flag enum).
1802 let kmname = if mode == 1 { "viins" } else { "emacs" };
1803 if let Some(km) = openkeymap(kmname) {
1804 linkkeymap(km, "main", 0);
1805 }
1806}