zsh/ported/zle/zle_h.rs
1//! Direct port of `Src/Zle/zle.h` — line-editor type / constant
2//! header.
3//!
4//! Original C copyright: Paul Falstad 1992-1997.
5//!
6//! Hosts the type aliases (`Widget` / `Thingy` / `Keymap` /
7//! `Cutbuffer` / etc.), enum / `#define` constants used across the
8//! ZLE port, and the `struct widget` / `struct thingy` /
9//! `struct modifier` / `struct change` / `struct vichange` /
10//! `struct cutbuffer` / `struct brinfo` / `struct compldat` /
11//! `struct region_highlight` / `struct watch_fd` / `REFRESH_ELEMENT`
12//! shapes the C source uses.
13//!
14//! Multibyte note: zshrs uses native UTF-8 throughout, so the C
15//! `#ifdef MULTIBYTE_SUPPORT` (zle.h:30-105) `wchar_t` types collapse
16//! onto Rust `char` / `String`. The non-multibyte fallback (`char`
17//! / `int`) is the parallel C path; the type aliases below pick
18//! the shape that maps cleanly onto Rust.
19
20#![allow(non_camel_case_types, non_snake_case, dead_code)]
21
22use crate::ported::zsh_h::{zattr, HashNode};
23
24// =====================================================================
25// Wide-character types — `Src/Zle/zle.h:30-110`.
26// =====================================================================
27//
28// C dispatches between `wchar_t` (MULTIBYTE_SUPPORT, zle.h:30-104)
29// and `char` (non-multibyte, zle.h:105-181). Rust uses `char` for
30// code-points and `String` for owned text — those primitives cover
31// both C paths.
32
33#[allow(unused_imports)]
34use crate::ported::zle::{
35 deltochar::*, textobjects::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
36 zle_params::*, zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
37};
38/// Port of `ZLE_CHAR_T` from zle.h:31 / zle.h:107.
39
40// --- AUTO: cross-zle hoisted-fn use glob ---
41#[allow(unused_imports)]
42// (was: use crate::ported::zle::widget::*; — widget.rs deleted)
43/// `ZLE_CHAR_T` type alias.
44#[allow(unused_imports)]
45
46pub type ZLE_CHAR_T = char; // c:31
47
48/// Port of `ZLE_STRING_T` from zle.h:32 / zle.h:108.
49pub type ZLE_STRING_T = String; // c:32
50
51/// Port of `ZLE_INT_T` from zle.h:33 / zle.h:109.
52pub type ZLE_INT_T = i32; // c:33
53
54/// Port of `ZLE_CHAR_SIZE` from zle.h:34. Rust `char` is always 4
55/// bytes (USVs); the C `wchar_t` is 4 on every supported host.
56pub const ZLE_CHAR_SIZE: usize = 4; // c:34
57
58/// Port of `ZLEEOF` from zle.h:37 / zle.h:112.
59pub const ZLEEOF: i32 = -1; // c:37 WEOF
60
61/// Port of `Th(X)` macro from zle.h:316. `#define Th(X) (&thingies[X])`.
62/// Resolves a fixed-thingy index into its Thingy reference.
63///
64/// In C, `thingies[]` is auto-generated by `mkbltnmlst.sh` from the
65/// `mod_export Thingy` declarations sprinkled across `zle.h`/`zle_*.c`.
66/// Each `t_<name>` constant indexes one slot. zshrs hasn't ported
67/// that build-time auto-gen, so this helper uses the manually-kept
68/// index→name table in `T_THINGY_NAMES` below and forwards to
69/// `lookup_thingy(name)` — same observable contract as the C macro.
70#[inline]
71pub fn Th(index: i32) -> Option<crate::ported::zle::zle_thingy::Thingy> {
72 // c:316
73 let i: usize = (index as i64).try_into().ok()?;
74 let name = T_THINGY_NAMES.get(i)?;
75 crate::ported::zle::zle_thingy::gethashnode2(name)
76}
77
78/// Fixed-thingy index table. Order matches the `mod_export Thingy
79/// t_<name>` declaration order in `Src/Zle/zle.h` so callers can use
80/// `Th(t_acceptline as i32)` exactly as they would in C. Add new
81/// entries here when `t_<name>` constants get back-ported; the
82/// indices are stable per zsh ABI.
83pub const T_THINGY_NAMES: &[&str] = &[
84 "accept-and-hold", // t_acceptandhold = 0
85 "accept-line", // t_acceptline = 1
86 "accept-line-and-down-history", // t_acceptlineanddownhistory
87 "accept-search", // t_acceptsearch
88 "auto-suffix-remove", // t_autosuffixremove
89 "auto-suffix-retain", // t_autosuffixretain
90 "backward-char", // t_backwardchar
91 "backward-delete-char", // t_backwarddeletechar
92 "beep", // t_beep
93 "clear-screen", // t_clearscreen
94 "complete-word", // t_completeword
95 "describe-key-briefly", // t_describekeybriefly
96 "down-line-or-history", // t_downlineorhistory
97 "execute-named-cmd", // t_executenamedcmd
98 "exit", // t_exit
99 "forward-char", // t_forwardchar
100 "history-incremental-search-backward", // t_historyincrementalsearchbackward
101 "history-incremental-search-forward", // t_historyincrementalsearchforward
102 "list-choices", // t_listchoices
103 "menu-complete", // t_menucomplete
104 "menu-expand-or-complete", // t_menuexpandorcomplete
105 "redisplay", // t_redisplay
106 "self-insert", // t_selfinsert
107 "self-insert-unmeta", // t_selfinsertunmeta
108 "send-break", // t_sendbreak
109 "undefined-key", // t_undefinedkey
110 "undo", // t_undo
111 "up-line-or-history", // t_uplineorhistory
112 "vi-cmd-mode", // t_vicmdmode
113 "yank", // t_yank
114];
115
116/// Port of `invicmdmode()` macro from zle.h:324.
117/// `#define invicmdmode() (!strcmp(curkeymapname, "vicmd"))`.
118/// True when the current keymap is the vi command-mode keymap.
119/// The Rust `in_vi_cmd_mode()` free fn (zle_main.rs:815) is the
120/// state-bound counterpart; this free-fn uses the global keymap name.
121#[inline]
122pub fn invicmdmode(curkeymapname: &str) -> bool {
123 // c:324
124 curkeymapname == "vicmd"
125}
126
127// =====================================================================
128// `ZS_*` wide-string macros — `Src/Zle/zle.h:40-51`.
129// C #defines route to wmemcpy/wcslen/etc. on MULTIBYTE_SUPPORT builds
130// and to the memcpy/strlen counterparts otherwise. Rust uses `char` /
131// `[char]` directly so each macro maps to a slice operation.
132// =====================================================================
133
134/// Port of `ZS_memcpy` from zle.h:40 (`#define ZS_memcpy wmemcpy`).
135/// Copies `n` chars from `src` into `dst`.
136#[inline]
137pub fn ZS_memcpy(dst: &mut [ZLE_CHAR_T], src: &[ZLE_CHAR_T], n: usize) {
138 // c:40
139 dst[..n].copy_from_slice(&src[..n]);
140}
141
142/// Port of `ZS_memmove` from zle.h:41 (`#define ZS_memmove wmemmove`).
143/// Same as ZS_memcpy but tolerates overlapping ranges (vec move
144/// semantics handle overlap).
145#[inline]
146pub fn ZS_memmove(dst: &mut [ZLE_CHAR_T], src: &[ZLE_CHAR_T], n: usize) {
147 // c:41
148 let v: Vec<ZLE_CHAR_T> = src[..n].to_vec();
149 dst[..n].copy_from_slice(&v);
150}
151
152/// Port of `ZS_memset` from zle.h:42 (`#define ZS_memset wmemset`).
153/// Fills `dst[..n]` with `c`.
154#[inline]
155pub fn ZS_memset(dst: &mut [ZLE_CHAR_T], c: ZLE_CHAR_T, n: usize) {
156 // c:42
157 for slot in dst.iter_mut().take(n) {
158 *slot = c;
159 }
160}
161
162/// Port of `ZS_memcmp` from zle.h:43 (`#define ZS_memcmp wmemcmp`).
163/// Returns Ordering of the first `n` chars.
164#[inline]
165pub fn ZS_memcmp(a: &[ZLE_CHAR_T], b: &[ZLE_CHAR_T], n: usize) -> std::cmp::Ordering {
166 // c:43
167 a[..n].cmp(&b[..n])
168}
169
170/// Port of `ZS_strlen` from zle.h:44 (`#define ZS_strlen wcslen`).
171/// Returns the length to the first NUL char (or full slice length
172/// if no NUL found).
173#[inline]
174pub fn ZS_strlen(s: &[ZLE_CHAR_T]) -> usize {
175 // c:44
176 s.iter().position(|&c| c == '\0').unwrap_or(s.len())
177}
178
179/// Port of `ZS_strcpy` from zle.h:45 (`#define ZS_strcpy wcscpy`).
180/// Copies `src` (up to first NUL or end) into `dst`, NUL-terminates
181/// when there's room.
182#[inline]
183pub fn ZS_strcpy(dst: &mut [ZLE_CHAR_T], src: &[ZLE_CHAR_T]) {
184 // c:45
185 let n = ZS_strlen(src);
186 let limit = dst.len().min(n);
187 dst[..limit].copy_from_slice(&src[..limit]);
188 if limit < dst.len() {
189 dst[limit] = '\0';
190 }
191}
192
193/// Port of `ZS_strncpy` from zle.h:46 (`#define ZS_strncpy wcsncpy`).
194/// Copies up to `n` chars; pads remainder with NUL if `src` is shorter.
195#[inline]
196pub fn ZS_strncpy(dst: &mut [ZLE_CHAR_T], src: &[ZLE_CHAR_T], n: usize) {
197 // c:46
198 let s_len = ZS_strlen(src).min(n);
199 let limit = dst.len().min(n);
200 let copy = s_len.min(limit);
201 dst[..copy].copy_from_slice(&src[..copy]);
202 for slot in dst.iter_mut().take(limit).skip(copy) {
203 *slot = '\0';
204 }
205}
206
207/// Port of `ZS_strncmp` from zle.h:47 (`#define ZS_strncmp wcsncmp`).
208/// Returns Ordering of up to `n` chars (stops at NUL).
209#[inline]
210pub fn ZS_strncmp(a: &[ZLE_CHAR_T], b: &[ZLE_CHAR_T], n: usize) -> std::cmp::Ordering {
211 // c:47
212 let a_n = ZS_strlen(a).min(n);
213 let b_n = ZS_strlen(b).min(n);
214 let limit = a_n.min(b_n);
215 let cmp = a[..limit].cmp(&b[..limit]);
216 if cmp != std::cmp::Ordering::Equal {
217 return cmp;
218 }
219 a_n.cmp(&b_n)
220}
221
222/// Port of `ZS_strchr` from zle.h:50 (`#define ZS_strchr wcschr`).
223/// Returns the index of the first occurrence of `c` in `s`, or `None`.
224#[inline]
225pub fn ZS_strchr(s: &[ZLE_CHAR_T], c: ZLE_CHAR_T) -> Option<usize> {
226 // c:50
227 s.iter().position(|&x| x == c)
228}
229
230/// Port of `ZS_memchr` from zle.h:51 (`#define ZS_memchr wmemchr`).
231/// Returns the index of the first occurrence of `c` in `s[..n]`.
232#[inline]
233pub fn ZS_memchr(s: &[ZLE_CHAR_T], c: ZLE_CHAR_T, n: usize) -> Option<usize> {
234 // c:51
235 s[..n.min(s.len())].iter().position(|&x| x == c)
236}
237
238/// Port of `ZS_width` from zle.h:49 (`#define ZS_width wcslen`).
239/// Returns the display width of a string (collapses to char count
240/// for the non-multibyte path; in zshrs we treat each char as 1 col).
241#[inline]
242pub fn ZS_width(s: &[ZLE_CHAR_T]) -> usize {
243 // c:49
244 ZS_strlen(s)
245}
246
247// =====================================================================
248// `ZC_*` wide-character classification macros — `Src/Zle/zle.h:60-73`.
249// C #defines route to `iswalpha`/`iswalnum`/etc. on MULTIBYTE_SUPPORT
250// builds and to the `<ctype.h>` counterparts otherwise. Rust's
251// `char::is_*` methods cover both paths uniformly.
252// =====================================================================
253
254/// Port of `ZC_ialpha` from zle.h:60.
255#[inline]
256pub fn ZC_ialpha(c: ZLE_CHAR_T) -> bool {
257 c.is_alphabetic()
258} // c:60
259/// Port of `ZC_ialnum` from zle.h:61.
260#[inline]
261pub fn ZC_ialnum(c: ZLE_CHAR_T) -> bool {
262 c.is_alphanumeric()
263} // c:61
264/// Port of `ZC_iblank` from zle.h:62 (`#define ZC_iblank wcsiblank`).
265/// Routes through the canonical `wcsiblank` impl at
266/// `Src/utils.c:4302-4307` — `iswspace(wc) && wc != L'\n'`. Catches
267/// CR/FF/VT/NBSP/U+2028/etc. in addition to space and tab; only
268/// newline is explicitly excluded.
269#[inline]
270pub fn ZC_iblank(c: ZLE_CHAR_T) -> bool {
271 // c:62
272 crate::ported::utils::wcsiblank(c)
273}
274/// Port of `ZC_icntrl` from zle.h:63.
275#[inline]
276pub fn ZC_icntrl(c: ZLE_CHAR_T) -> bool {
277 c.is_control()
278} // c:63
279/// Port of `ZC_idigit` from zle.h:64.
280#[inline]
281pub fn ZC_idigit(c: ZLE_CHAR_T) -> bool {
282 c.is_ascii_digit()
283} // c:64
284/// Port of `ZC_iident` from zle.h:65 (`wcsitype(c, IIDENT)`). Identifier
285/// char per zsh's IIDENT class: alphanumeric or `_`.
286#[inline]
287pub fn ZC_iident(c: ZLE_CHAR_T) -> bool {
288 c.is_alphanumeric() || c == '_'
289} // c:65
290/// Port of `ZC_ilower` from zle.h:66.
291#[inline]
292pub fn ZC_ilower(c: ZLE_CHAR_T) -> bool {
293 c.is_lowercase()
294} // c:66
295/// Port of `ZC_inblank` from zle.h:67 (`iswspace`). True for any
296/// whitespace char (incl. newline).
297#[inline]
298pub fn ZC_inblank(c: ZLE_CHAR_T) -> bool {
299 c.is_whitespace()
300} // c:67
301/// Port of `ZC_iupper` from zle.h:68.
302#[inline]
303pub fn ZC_iupper(c: ZLE_CHAR_T) -> bool {
304 c.is_uppercase()
305} // c:68
306/// Port of `ZC_iword` from zle.h:69 (`wcsitype(c, IWORD)`). Word
307/// char per zsh's IWORD class: alphanumeric or `_`.
308#[inline]
309pub fn ZC_iword(c: ZLE_CHAR_T) -> bool {
310 c.is_alphanumeric() || c == '_'
311} // c:69
312/// Port of `ZC_ipunct` from zle.h:70.
313#[inline]
314pub fn ZC_ipunct(c: ZLE_CHAR_T) -> bool {
315 c.is_ascii_punctuation()
316} // c:70
317
318/// Port of `ZC_tolower` from zle.h:72.
319#[inline]
320pub fn ZC_tolower(c: ZLE_CHAR_T) -> ZLE_CHAR_T {
321 // c:72
322 c.to_lowercase().next().unwrap_or(c)
323}
324/// Port of `ZC_toupper` from zle.h:73.
325#[inline]
326pub fn ZC_toupper(c: ZLE_CHAR_T) -> ZLE_CHAR_T {
327 // c:73
328 c.to_uppercase().next().unwrap_or(c)
329}
330
331// =====================================================================
332// `LASTFULLCHAR` — `Src/Zle/zle.h:75-76`. Macro alias resolving to
333// `lastchar_wide` (the most-recent fully-decoded codepoint). Lives
334// as a field on `Zle` (`lastchar_wide: i32`); the macro name is
335// kept here as an alias for searchability.
336// =====================================================================
337
338// =====================================================================
339// Widget — `Src/Zle/zle.h:184-220`.
340// =====================================================================
341
342/// Port of `typedef struct widget *Widget` from zle.h:184.
343pub type WidgetPtr = Box<widget>; // c:184
344
345/// Port of `typedef struct thingy *Thingy` from zle.h:185.
346pub type ThingyPtr = Box<thingy>; // c:185
347
348/// Port of `ZleIntFunc` from zle.h:189. C: `int (*)(char **)` —
349/// every internal widget conforms to this signature.
350pub type ZleIntFunc = fn(args: &[String]) -> i32; // c:189
351
352/// Port of `struct widget` from `Src/Zle/zle.h:191-203`.
353/// ```c
354/// struct widget {
355/// int flags; /* WIDGET_* / ZLE_* flag bitset */
356/// Thingy first; /* `first` thingy that names this widget */
357/// union {
358/// ZleIntFunc fn; /* internal widget */
359/// char *fnnam; /* shell-function name for user-defined */
360/// struct { ZleIntFunc fn; char *wid; char *func; } comp;
361/// } u;
362/// };
363/// ```
364pub struct widget {
365 // c:191
366 /// flags (see below).
367 pub flags: i32, // c:192
368 /// `first' thingy that names this widget.
369 pub first: Option<ThingyPtr>, // c:193
370 /// Tagged equivalent of the C anonymous union (zle.h:194-202).
371 pub u: WidgetImpl, // c:194
372}
373
374impl Clone for widget {
375 fn clone(&self) -> Self {
376 widget {
377 flags: self.flags,
378 first: None, // ThingyPtr is Box<thingy>, intentionally not deep-cloned.
379 u: match &self.u {
380 WidgetImpl::Internal(f) => WidgetImpl::Internal(*f),
381 WidgetImpl::UserFunc(s) => WidgetImpl::UserFunc(s.clone()),
382 WidgetImpl::Comp { fn_, wid, func } => WidgetImpl::Comp {
383 fn_: *fn_,
384 wid: wid.clone(),
385 func: func.clone(),
386 },
387 },
388 }
389 }
390}
391
392impl std::fmt::Debug for widget {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394 f.debug_struct("widget")
395 .field("flags", &self.flags)
396 .field(
397 "u",
398 &match &self.u {
399 WidgetImpl::Internal(_) => "Internal(<fn>)".to_string(),
400 WidgetImpl::UserFunc(s) => format!("UserFunc({})", s),
401 WidgetImpl::Comp { .. } => "Comp{..}".to_string(),
402 },
403 )
404 .finish()
405 }
406}
407
408impl widget {
409 /// Build a widget that points at a Rust function pointer with the
410 /// supplied ZLE flags. Equivalent to the WIDGET_INT branch of
411 /// `zalloc(sizeof(*w))` + `w->u.fn = ...` in `addzlefunction()` at
412 /// Src/Zle/zle_thingy.c:281.
413 pub fn internal(name: &str, func: ZleIntFunc, flags: i32) -> Self {
414 let _ = name;
415 widget {
416 flags: flags | WIDGET_INT,
417 first: None,
418 u: WidgetImpl::Internal(func),
419 }
420 }
421
422 /// Resolve a built-in widget name to its canonical fn pointer
423 /// via `zle_bindings::iwidget_lookup`. Mirrors the dispatch C
424 /// achieves at `Src/Zle/zle_bindings.c:55-60` through the
425 /// generated `widgets[]` static table; the Rust port uses a
426 /// name → fn-pointer match keyed off the same `iwidgets.list`
427 /// canonical names. Unknown widget names get a no-op fn
428 /// pointer (matches what `t_undefinedkey` resolves to).
429 pub fn builtin(name: &str) -> Self {
430 let f = super::zle_bindings::iwidget_lookup(name).unwrap_or(|_args: &[String]| 0i32);
431 widget {
432 flags: WIDGET_INT,
433 first: None,
434 u: WidgetImpl::Internal(f),
435 }
436 }
437
438 /// Build a widget that wraps a user-defined shell function.
439 /// Equivalent to `bin_zle_new()` from Src/Zle/zle_thingy.c:584.
440 pub fn user_defined(name: &str, func_name: &str) -> Self {
441 let _ = name;
442 widget {
443 flags: 0i32,
444 first: None,
445 u: WidgetImpl::UserFunc(func_name.to_string()),
446 }
447 }
448}
449
450/// Tagged port of the `widget.u` union from `Src/Zle/zle.h:194-202`.
451/// C uses an inline anonymous union; Rust enum tags the active
452/// variant.
453pub enum WidgetImpl {
454 // c:194
455 /// `u.fn` — pointer to internally implemented widget.
456 Internal(ZleIntFunc), // c:195
457 /// `u.fnnam` — name of the shell function for user-defined widget.
458 UserFunc(String), // c:196
459 /// `u.comp` — completion-widget triple.
460 Comp {
461 fn_: ZleIntFunc,
462 wid: String,
463 func: String,
464 }, // c:197-201
465}
466
467// Widget flags — `Src/Zle/zle.h:205-220`.
468/// `WIDGET_INT` constant.
469pub const WIDGET_INT: i32 = 1 << 0; /* widget is internally implemented */
470// c:205
471/// `WIDGET_NCOMP` constant.
472pub const WIDGET_NCOMP: i32 = 1 << 1; /* new style completion widget */
473// c:206
474/// `ZLE_MENUCMP` constant.
475pub const ZLE_MENUCMP: i32 = 1 << 2; /* DON'T invalidate completion list */
476// c:207
477/// `ZLE_YANKAFTER` constant.
478pub const ZLE_YANKAFTER: i32 = 1 << 3; // c:208
479/// `ZLE_YANKBEFORE` constant.
480pub const ZLE_YANKBEFORE: i32 = 1 << 4; // c:209
481/// `ZLE_YANK` constant.
482pub const ZLE_YANK: i32 = ZLE_YANKAFTER | ZLE_YANKBEFORE; // c:210
483/// `ZLE_LINEMOVE` constant.
484pub const ZLE_LINEMOVE: i32 = 1 << 5; /* line-oriented movement */
485// c:211
486/// `ZLE_VIOPER` constant.
487pub const ZLE_VIOPER: i32 = 1 << 6; /* widget reads further keys so wait if prefix */
488// c:212
489/// `ZLE_LASTCOL` constant.
490pub const ZLE_LASTCOL: i32 = 1 << 7; /* command maintains lastcol correctly */
491// c:213
492/// `ZLE_KILL` constant.
493pub const ZLE_KILL: i32 = 1 << 8; // c:214
494/// `ZLE_KEEPSUFFIX` constant.
495pub const ZLE_KEEPSUFFIX: i32 = 1 << 9; /* DON'T remove added suffix */
496// c:215
497/// `ZLE_NOTCOMMAND` constant.
498pub const ZLE_NOTCOMMAND: i32 = 1 << 10; /* widget should not alter lastcmd */
499// c:216
500/// `ZLE_ISCOMP` constant.
501pub const ZLE_ISCOMP: i32 = 1 << 11; /* usable for new style completion */
502// c:217
503/// `WIDGET_INUSE` constant.
504pub const WIDGET_INUSE: i32 = 1 << 12; /* widget is in use */
505// c:218
506/// `WIDGET_FREE` constant.
507pub const WIDGET_FREE: i32 = 1 << 13; /* request to free when no longer in use */
508// c:219
509/// `ZLE_NOLAST` constant.
510pub const ZLE_NOLAST: i32 = 1 << 14; /* widget should not alter lbindk */
511// c:220
512
513// =====================================================================
514// Thingy — `Src/Zle/zle.h:224-235`.
515// =====================================================================
516
517/// Port of `struct thingy` from `Src/Zle/zle.h:224-231`. HashNode
518/// subtype keyed by name; circular list (`samew`) of all thingies
519/// pointing at the same widget.
520pub struct thingy {
521 // c:224
522 /// next node in the hash chain.
523 pub next: Option<HashNode>, // c:225
524 /// name of the thingy.
525 pub nam: String, // c:226
526 /// TH_* flags (see below).
527 pub flags: i32, // c:227
528 /// reference count.
529 pub rc: i32, // c:228
530 /// widget named by this thingy.
531 pub widget: Option<WidgetPtr>, // c:229
532 /// `next' thingy (circularly) naming the same widget.
533 pub samew: Option<ThingyPtr>, // c:230
534}
535
536/// `DISABLED` is `(1<<0)` (generic hashnode flag — defined in zsh.h).
537/// `TH_IMMORTAL` from zle.h:234 — can't refer to a different widget.
538pub const TH_IMMORTAL: i32 = 1 << 1; // c:234
539
540// =====================================================================
541// Modifier — `Src/Zle/zle.h:243-263`.
542// =====================================================================
543
544/// Port of `struct modifier` from `Src/Zle/zle.h:245-251`.
545/// Command modifier prefix state (numeric arg, vi cut buffer, etc.).
546#[derive(Clone)]
547pub struct modifier {
548 // c:245
549 /// MOD_* flags (see below).
550 pub flags: i32, // c:246
551 /// repeat count.
552 pub mult: i32, // c:247
553 /// repeat count actually being edited.
554 pub tmult: i32, // c:248
555 /// vi cut buffer.
556 pub vibuf: i32, // c:249
557 /// numeric base for digit arguments (usually 10).
558 pub base: i32, // c:250
559}
560/// `MOD_MULT` constant.
561pub const MOD_MULT: i32 = 1 << 0; /* a repeat count has been selected */
562// c:253
563/// `MOD_TMULT` constant.
564pub const MOD_TMULT: i32 = 1 << 1; /* a repeat count is being entered */
565// c:254
566/// `MOD_VIBUF` constant.
567pub const MOD_VIBUF: i32 = 1 << 2; /* a vi cut buffer has been selected */
568// c:255
569/// `MOD_VIAPP` constant.
570pub const MOD_VIAPP: i32 = 1 << 3; /* appending to the vi cut buffer */
571// c:256
572/// `MOD_NEG` constant.
573pub const MOD_NEG: i32 = 1 << 4; /* last command was negate argument */
574// c:257
575/// `MOD_NULL` constant.
576pub const MOD_NULL: i32 = 1 << 5; /* throw away text for the vi cut buffer */
577// c:258
578/// `MOD_CHAR` constant.
579pub const MOD_CHAR: i32 = 1 << 6; /* force character-wise movement */
580// c:259
581/// `MOD_LINE` constant.
582pub const MOD_LINE: i32 = 1 << 7; /* force line-wise movement */
583// c:260
584/// `MOD_PRI` constant.
585pub const MOD_PRI: i32 = 1 << 8; /* OS primary selection for the vi cut buffer */
586// c:261
587/// `MOD_CLIP` constant.
588pub const MOD_CLIP: i32 = 1 << 9; /* OS clipboard for the vi cut buffer */
589// c:262
590/// `MOD_OSSEL` constant.
591pub const MOD_OSSEL: i32 = MOD_PRI | MOD_CLIP; /* either system selection */
592// c:263
593
594// =====================================================================
595// Cut-buffer flag bits — `Src/Zle/zle.h:271-280`.
596// =====================================================================
597/// `CUT_FRONT` constant.
598pub const CUT_FRONT: i32 = 1 << 0; /* Text goes in front of cut buffer */
599// c:271
600/// `CUT_REPLACE` constant.
601pub const CUT_REPLACE: i32 = 1 << 1; /* Text replaces cut buffer */
602// c:272
603/// `CUT_RAW` (zle.h:273-279). Raw character counts (not used in
604/// `cut` itself). This is used when the values are offsets into
605/// the zleline array rather than numbers of visible characters
606/// directly input by the user.
607pub const CUT_RAW: i32 = 1 << 2; // c:273
608/// `CUT_YANK` constant.
609pub const CUT_YANK: i32 = 1 << 3; /* vi yank: use register 0 instead of 1-9 */
610// c:280
611
612// =====================================================================
613// Change (undo system) — `Src/Zle/zle.h:282-298`.
614// =====================================================================
615
616/// Port of `struct change` from `Src/Zle/zle.h:284-295`. The undo
617/// log is a doubly-linked list of these entries.
618#[derive(Clone, Debug)]
619pub struct change {
620 // c:284
621 /// previous adjacent change.
622 pub prev: Option<Box<change>>, // c:285
623 /// next adjacent change.
624 pub next: Option<Box<change>>, // c:285
625 /// see CH_* below.
626 pub flags: i32, // c:286
627 /// history line being changed.
628 pub hist: i32, // c:287
629 /// offset of the text changes.
630 pub off: i32, // c:288
631 /// characters to delete.
632 pub del: ZLE_STRING_T, // c:289
633 /// no. of characters in del.
634 pub dell: i32, // c:290
635 /// characters to insert.
636 pub ins: ZLE_STRING_T, // c:291
637 /// no. of characters in ins.
638 pub insl: i32, // c:292
639 /// old cursor position.
640 pub old_cs: i32, // c:293
641 /// new cursor position.
642 pub new_cs: i32, // c:293
643 /// unique number of this change (`zlong`).
644 pub changeno: i64, // c:294
645}
646/// `CH_NEXT` constant.
647pub const CH_NEXT: i32 = 1 << 0; /* next structure is also part of this change */
648// c:297
649/// `CH_PREV` constant.
650pub const CH_PREV: i32 = 1 << 1; /* previous structure is also part of this change */
651// c:298
652
653// =====================================================================
654// VI change — `Src/Zle/zle.h:300-313`.
655// =====================================================================
656
657/// Port of `struct vichange` from `Src/Zle/zle.h:308-312`. Stores
658/// the byte sequence of a vi command for `.` (vi-repeat-change).
659///
660/// From zle.h:302-307: examination of the code suggests vichgbuf
661/// is consistently tied to raw byte input, so it is left as a
662/// character array rather than turned into wide characters. In
663/// particular, when we replay it we use `ungetbytes()`.
664pub struct vichange {
665 // c:308
666 /// value of zmod associated with vi change.
667 pub mod_: modifier, // c:309
668 /// bytes for keys that make up the vi command.
669 pub buf: Vec<u8>, // c:310
670 /// allocated size of buf.
671 pub bufsz: i32, // c:311
672 /// in-use size of buf.
673 pub bufptr: i32, // c:311
674}
675
676// =====================================================================
677// Keymap — `Src/Zle/zle.h:316-322`.
678// =====================================================================
679
680// `KeymapPtr` / `keymap_opaque` deleted — Rust-invented placeholder
681// for the `typedef struct keymap *Keymap` opaque alias at zle.h:320.
682// The real `Keymap` struct (full port of `struct keymap` at
683// `zle_keymap.c:64`) lives in `zle_keymap.rs` and callers
684// reference it directly via `Arc<Keymap>`.
685
686/// Port of `KeyScanFunc` from zle.h:322.
687/// C: `void (*KeyScanFunc) (char *, Thingy, char *, void *)`.
688pub type KeyScanFunc = fn(seq: &str, t: &thingy, ext: &str, data: usize); // c:322
689
690// =====================================================================
691// Suffix removal — `Src/Zle/zle.h:326-333`.
692// =====================================================================
693
694/// Port of `NO_INSERT_CHAR` from zle.h:329 / zle.h:331.
695pub const NO_INSERT_CHAR: i32 = 256; // c:331
696
697/// Port of `removesuffix()` from zle.h:333.
698/// C: `iremovesuffix(NO_INSERT_CHAR, 0)`.
699pub fn removesuffix() -> i32 {
700 // c:333
701 crate::ported::zle::zle_misc::iremovesuffix(NO_INSERT_CHAR, 0) // c:333
702}
703
704// =====================================================================
705// Cutbuffer — `Src/Zle/zle.h:335-352`.
706// =====================================================================
707
708/// Port of `struct cutbuffer` from `Src/Zle/zle.h:342-346`.
709/// From zle.h:335-340: cut/kill buffer. The buffer itself is purely
710/// binary data, not NUL-terminated. `len` is a character count
711/// (N.B. number of characters, not size in bytes). `flags` uses the
712/// CUTBUFFER_* constants defined below.
713pub struct cutbuffer {
714 // c:342
715 pub buf: ZLE_STRING_T, // c:343
716 pub len: usize, // c:344
717 pub flags: u8, // c:345
718}
719
720/// Port of `typedef struct cutbuffer *Cutbuffer` from zle.h:348.
721pub type CutbufferPtr = Box<cutbuffer>; // c:348
722/// `CUTBUFFER_LINE` constant.
723pub const CUTBUFFER_LINE: u8 = 1; /* for vi: buffer contains whole lines of data */
724// c:350
725/// `KRINGCTDEF` constant.
726pub const KRINGCTDEF: i32 = 8; /* default number of buffers in the kill ring */
727// c:352
728
729// =====================================================================
730// Completion modes — `Src/Zle/zle.h:354-362`.
731// =====================================================================
732/// `COMP_COMPLETE` constant.
733pub const COMP_COMPLETE: i32 = 0; // c:356
734/// `COMP_LIST_COMPLETE` constant.
735pub const COMP_LIST_COMPLETE: i32 = 1; // c:357
736/// `COMP_SPELL` constant.
737pub const COMP_SPELL: i32 = 2; // c:358
738/// `COMP_EXPAND` constant.
739pub const COMP_EXPAND: i32 = 3; // c:359
740/// `COMP_EXPAND_COMPLETE` constant.
741pub const COMP_EXPAND_COMPLETE: i32 = 4; // c:360
742/// `COMP_LIST_EXPAND` constant.
743pub const COMP_LIST_EXPAND: i32 = 5; // c:361
744
745/// Port of `COMP_ISEXPAND(X)` from zle.h:362.
746#[inline]
747pub fn COMP_ISEXPAND(x: i32) -> bool {
748 x >= COMP_EXPAND
749} // c:362
750
751// =====================================================================
752// Brace runs (Brinfo) — `Src/Zle/zle.h:364-375`.
753// =====================================================================
754
755/// Port of `typedef struct brinfo *Brinfo` from zle.h:366.
756pub type BrinfoPtr = Box<brinfo>; // c:366
757
758/// Port of `struct brinfo` from `Src/Zle/zle.h:368-375`.
759/// One brace run during brace-expansion completion.
760pub struct brinfo {
761 // c:368
762 /// next in list.
763 pub next: Option<BrinfoPtr>, // c:369
764 /// previous (only for closing braces).
765 pub prev: Option<BrinfoPtr>, // c:370
766 /// the string to insert.
767 pub str: String, // c:371
768 /// original position.
769 pub pos: i32, // c:372
770 /// original position, with quoting.
771 pub qpos: i32, // c:373
772 /// position for current match.
773 pub curpos: i32, // c:374
774}
775
776// =====================================================================
777// Hook offsets — `Src/Zle/zle.h:377-402`.
778// =====================================================================
779/// `LISTMATCHESHOOK` constant.
780pub const LISTMATCHESHOOK: i32 = 0; // c:379
781/// `COMPLETEHOOK` constant.
782pub const COMPLETEHOOK: i32 = 1; // c:380
783/// `BEFORECOMPLETEHOOK` constant.
784pub const BEFORECOMPLETEHOOK: i32 = 2; // c:381
785/// `AFTERCOMPLETEHOOK` constant.
786pub const AFTERCOMPLETEHOOK: i32 = 3; // c:382
787/// `ACCEPTCOMPHOOK` constant.
788pub const ACCEPTCOMPHOOK: i32 = 4; // c:383
789/// `INVALIDATELISTHOOK` constant.
790pub const INVALIDATELISTHOOK: i32 = 5; // c:384
791
792// =====================================================================
793// Compldat — `Src/Zle/zle.h:386-394`.
794// =====================================================================
795
796/// Port of `typedef struct compldat *Compldat` from zle.h:388.
797pub type CompldatPtr = Box<compldat>; // c:388
798
799/// Port of `struct compldat` from `Src/Zle/zle.h:390-394`. Payload
800/// passed to the COMPLETEHOOK callback.
801pub struct compldat {
802 // c:390
803 pub s: String, // c:391
804 pub lst: i32, // c:392
805 pub incmd: i32, // c:393
806}
807
808/// Direct port of `listmatches()` from `Src/Zle/zle.h:398`. Fires the
809/// LISTMATCHESHOOK chain via `runhookdef`, falling back to the
810/// canonical `ilistmatches` renderer when no user hook is registered.
811pub fn listmatches() {
812 // c:398
813 // c:398 — `runhookdef(LISTMATCHESHOOK, NULL)`. Returns nonzero
814 // when a Hookfn handled it; 0 (or no handler registered) falls
815 // through to the default renderer.
816 let h = crate::ported::module::gethookdef("list-matches");
817 let handled = if !h.is_null() {
818 crate::ported::module::runhookdef(h, std::ptr::null_mut()) != 0
819 } else {
820 false
821 };
822 if !handled {
823 // Default handler — ilistmatches (compresult.c:2284).
824 let _ = crate::ported::zle::compresult::ilistmatches();
825 }
826}
827
828/// Direct port of `invalidatelist()` from `Src/Zle/zle.h:402`. Fires
829/// the INVALIDATELISTHOOK chain via `runhookdef`, falling back to the
830/// canonical `invalidate_list` cleanup (compresult.c:2334) when no
831/// user hook is registered.
832pub fn invalidatelist() {
833 // c:402
834 // c:402 — `runhookdef(INVALIDATELISTHOOK, NULL)`. Returns nonzero
835 // when a Hookfn handled it; 0 or no registration falls through to
836 // the default cleanup path.
837 let h = crate::ported::module::gethookdef("invalidate-list");
838 let handled = if !h.is_null() {
839 crate::ported::module::runhookdef(h, std::ptr::null_mut()) != 0
840 } else {
841 false
842 };
843 if !handled {
844 let _ = crate::ported::zle::compresult::invalidate_list();
845 }
846}
847
848// =====================================================================
849// setline flags — `Src/Zle/zle.h:404-408`.
850// =====================================================================
851/// `ZSL_COPY` constant.
852pub const ZSL_COPY: i32 = 1; /* Copy the argument, don't modify it */
853// c:406
854/// `ZSL_TOEND` constant.
855pub const ZSL_TOEND: i32 = 2; /* Go to the end of the new line */
856// c:407
857
858// =====================================================================
859// Suffix type / flags — `Src/Zle/zle.h:411-422`.
860// =====================================================================
861
862/// Port of `enum suffixtype` from zle.h:412 (type arguments to
863/// `addsuffix()`).
864pub const SUFTYP_POSSTR: i32 = 0; /* String of characters to match */
865// c:413
866/// `SUFTYP_NEGSTR` constant.
867pub const SUFTYP_NEGSTR: i32 = 1; /* String of characters not to match */
868// c:414
869/// `SUFTYP_POSRNG` constant.
870pub const SUFTYP_POSRNG: i32 = 2; /* Range of characters to match */
871// c:415
872/// `SUFTYP_NEGRNG` constant.
873pub const SUFTYP_NEGRNG: i32 = 3; /* Range of characters not to match */
874// c:416
875
876/// Port of `enum suffixflags` from zle.h:420 (additional flags to
877/// suffixes).
878pub const SUFFLAGS_SPACE: i32 = 0x0001; /* Add a space when removing suffix */
879// c:421
880
881// =====================================================================
882// Region highlight — `Src/Zle/zle.h:425-473`.
883// =====================================================================
884
885/// `ZRH_PREDISPLAY` — region offsets include predisplay text.
886pub const ZRH_PREDISPLAY: i32 = 1; // c:428
887
888/// Port of `struct region_highlight` from `Src/Zle/zle.h:435-461`.
889/// Attributes used for highlighting regions and the mark.
890pub struct region_highlight {
891 // c:435
892 /// Attributes for the region.
893 pub atr: zattr, // c:437
894 /// Explicitly set attributes for the region.
895 pub atrmask: zattr, // c:439
896 /// Priority for this region relative to others that overlap.
897 pub layer: i32, // c:441
898 /// Start of the region.
899 pub start: i32, // c:443
900 /// Start of the region in metafied ZLE line.
901 pub start_meta: i32, // c:445
902 /// End of the region: position of the first character not
903 /// highlighted (the same system as for point and mark).
904 pub end: i32, // c:450
905 /// End of the region in metafied ZLE line.
906 pub end_meta: i32, // c:452
907 /// Any of the flags defined above.
908 pub flags: i32, // c:456
909 /// User-settable "memo" key. Metafied.
910 pub memo: Option<String>, // c:460
911}
912
913/// Port of `N_SPECIAL_HIGHLIGHTS` from zle.h:473.
914/// Count of special uses of region highlighting:
915/// 0=region between point and mark, 1=isearch region, 2=suffix,
916/// 3=pasted text.
917pub const N_SPECIAL_HIGHLIGHTS: i32 = 4; // c:473
918
919// =====================================================================
920// Cursor context — `Src/Zle/zle.h:475-486`.
921// =====================================================================
922
923/// Port of `enum cursorcontext` from zle.h:476.
924pub const CURC_EDIT: i32 = 0; // c:477
925/// `CURC_COMMAND` constant.
926pub const CURC_COMMAND: i32 = 1; // c:478
927/// `CURC_INSERT` constant.
928pub const CURC_INSERT: i32 = 2; // c:479
929/// `CURC_OVERWRITE` constant.
930pub const CURC_OVERWRITE: i32 = 3; // c:480
931/// `CURC_PENDING` constant.
932pub const CURC_PENDING: i32 = 4; // c:481
933/// `CURC_REGION_START` constant.
934pub const CURC_REGION_START: i32 = 5; // c:482
935/// `CURC_REGION_END` constant.
936pub const CURC_REGION_END: i32 = 6; // c:483
937/// `CURC_VISUAL` constant.
938pub const CURC_VISUAL: i32 = 7; // c:484
939/// `CURC_DEFAULT` constant.
940pub const CURC_DEFAULT: i32 = 8; // c:485
941
942// =====================================================================
943// Cursor flag bits — `Src/Zle/zle.h:488-500`.
944// =====================================================================
945/// `CURF_DEFAULT` constant.
946pub const CURF_DEFAULT: i32 = 0; // c:488
947/// `CURF_UNDERLINE` constant.
948pub const CURF_UNDERLINE: i32 = 1; // c:489
949/// `CURF_BAR` constant.
950pub const CURF_BAR: i32 = 2; // c:490
951/// `CURF_BLOCK` constant.
952pub const CURF_BLOCK: i32 = 3; // c:491
953/// `CURF_SHAPE_MASK` constant.
954pub const CURF_SHAPE_MASK: i32 = 3; // c:492
955/// `CURF_BLINK` constant.
956pub const CURF_BLINK: i32 = 1 << 2; // c:493
957/// `CURF_STEADY` constant.
958pub const CURF_STEADY: i32 = 1 << 3; // c:494
959/// `CURF_HIDDEN` constant.
960pub const CURF_HIDDEN: i32 = 1 << 4; // c:495
961/// `CURF_COLOR` constant.
962pub const CURF_COLOR: i32 = 1 << 5; // c:496
963/// `CURF_COLOR_MASK` constant.
964pub const CURF_COLOR_MASK: u32 = (0xffffff_u32 << 8) | (CURF_COLOR as u32); // c:497
965/// `CURF_RED_SHIFT` constant.
966pub const CURF_RED_SHIFT: i32 = 24; // c:498
967/// `CURF_GREEN_SHIFT` constant.
968pub const CURF_GREEN_SHIFT: i32 = 16; // c:499
969/// `CURF_BLUE_SHIFT` constant.
970pub const CURF_BLUE_SHIFT: i32 = 8; // c:500
971
972// =====================================================================
973// Refresh element — `Src/Zle/zle.h:502-529`.
974// =====================================================================
975
976/// Port of `REFRESH_CHAR` from zle.h:507 (multibyte) / zle.h:509
977/// (non-multibyte). Rust uses `char` since native UTF-8 covers both.
978pub type REFRESH_CHAR = char; // c:507
979
980/// Port of `REFRESH_ELEMENT` from `Src/Zle/zle.h:515-526`.
981/// One character cell in the on-screen display buffer.
982/// From zle.h:516-520: the (possibly wide) character. If `atr`
983/// contains `TXT_MULTIWORD_MASK`, an index into the set of
984/// multiword symbols (only if MULTIBYTE_SUPPORT is present).
985#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
986pub struct REFRESH_ELEMENT {
987 // c:515
988 /// The (possibly wide) character.
989 pub chr: REFRESH_CHAR, // c:521
990 /// Its attributes.
991 pub atr: zattr, // c:525
992}
993
994/// Port of `REFRESH_STRING` from zle.h:529. A string of screen cells.
995pub type REFRESH_STRING = Vec<REFRESH_ELEMENT>; // c:529
996
997// =====================================================================
998// ZSH_INVALID_WCHAR — `Src/Zle/zle.h:532-553`.
999// =====================================================================
1000//
1001// From zle.h:533-539: with ISO 10646 there is a private range
1002// defined within the encoding. We use this for storing single-byte
1003// characters in sections of strings that wouldn't convert to wide
1004// characters. This allows to preserve the string when transformed
1005// back to multibyte strings.
1006
1007/// `ZSH_INVALID_WCHAR_BASE` from zle.h:541. The start of the
1008/// private range we use, for 256 characters.
1009pub const ZSH_INVALID_WCHAR_BASE: u32 = 0xe000; // c:541
1010
1011/// Port of `ZSH_INVALID_WCHAR_TEST(x)` from zle.h:544. Detect a
1012/// wide character within our range.
1013#[inline]
1014pub fn ZSH_INVALID_WCHAR_TEST(x: u32) -> bool {
1015 // c:544
1016 x >= ZSH_INVALID_WCHAR_BASE && x <= ZSH_INVALID_WCHAR_BASE + 255
1017}
1018
1019/// Port of `ZSH_INVALID_WCHAR_TO_CHAR(x)` from zle.h:548. Turn a
1020/// wide character in that range back to single byte.
1021#[inline]
1022pub fn ZSH_INVALID_WCHAR_TO_CHAR(x: u32) -> u8 {
1023 // c:548
1024 (x - ZSH_INVALID_WCHAR_BASE) as u8
1025}
1026
1027/// Port of `ZSH_INVALID_WCHAR_TO_INT(x)` from zle.h:550. Turn a
1028/// wide character in that range to an integer.
1029#[inline]
1030pub fn ZSH_INVALID_WCHAR_TO_INT(x: u32) -> i32 {
1031 // c:550
1032 (x - ZSH_INVALID_WCHAR_BASE) as i32
1033}
1034
1035/// Port of `ZSH_CHAR_TO_INVALID_WCHAR(x)` from zle.h:553. Turn a
1036/// single byte character into a private wide character.
1037#[inline]
1038pub fn ZSH_CHAR_TO_INVALID_WCHAR(x: u8) -> u32 {
1039 // c:553
1040 (x as u32) + ZSH_INVALID_WCHAR_BASE
1041}
1042
1043// =====================================================================
1044// METACHECK — `Src/Zle/zle.h:560-567`.
1045// =====================================================================
1046//
1047// Debug-only assertion macros. Production builds collapse to no-op,
1048// matching the C `#ifdef DEBUG` paths.
1049
1050/// Port of `METACHECK()` from zle.h:561.
1051/// C: `DPUTS(zlemetaline == NULL, "line not metafied")`.
1052#[inline]
1053pub fn METACHECK() { // c:561
1054 // c:561 — DPUTS only fires when DEBUG is defined; treat as
1055 // no-op for release builds, same as C zle.h:566.
1056}
1057
1058/// Port of `UNMETACHECK()` from zle.h:563.
1059/// C: `DPUTS(zlemetaline != NULL, "line metafied")`.
1060#[inline]
1061pub fn UNMETACHECK() { // c:563
1062 // c:563 — see METACHECK above.
1063}
1064
1065// =====================================================================
1066// watch_fd — `Src/Zle/zle.h:570-578`.
1067// =====================================================================
1068
1069/// Port of `typedef struct watch_fd *Watch_fd` from zle.h:570.
1070pub type WatchFdPtr = Box<watch_fd>; // c:570
1071
1072/// Port of `struct watch_fd` from `Src/Zle/zle.h:572-578`.
1073/// One `zle -F` file-descriptor watcher.
1074pub struct watch_fd {
1075 // c:572
1076 /// Function to call.
1077 pub func: String, // c:574
1078 /// Watched fd.
1079 pub fd: i32, // c:576
1080 /// 1 if func is called as a widget.
1081 pub widget: i32, // c:578
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use super::*;
1087
1088 #[test]
1089 fn zs_strlen_stops_at_nul() {
1090 let _g = crate::test_util::global_state_lock();
1091 let _g = zle_test_setup();
1092 assert_eq!(ZS_strlen(&['a', 'b', 'c']), 3);
1093 assert_eq!(ZS_strlen(&['a', '\0', 'c']), 1);
1094 assert_eq!(ZS_strlen(&[]), 0);
1095 }
1096
1097 #[test]
1098 fn zs_memcpy_first_n_chars() {
1099 let _g = crate::test_util::global_state_lock();
1100 let _g = zle_test_setup();
1101 let mut dst = ['x'; 5];
1102 let src = ['a', 'b', 'c', 'd', 'e'];
1103 ZS_memcpy(&mut dst, &src, 3);
1104 assert_eq!(dst, ['a', 'b', 'c', 'x', 'x']);
1105 }
1106
1107 #[test]
1108 fn zs_memmove_handles_self_copy() {
1109 let _g = crate::test_util::global_state_lock();
1110 let _g = zle_test_setup();
1111 let mut buf = ['a', 'b', 'c', 'd', 'e'];
1112 let src: Vec<char> = buf[1..4].to_vec();
1113 ZS_memmove(&mut buf, &src, 3);
1114 assert_eq!(buf, ['b', 'c', 'd', 'd', 'e']);
1115 }
1116
1117 #[test]
1118 fn zs_memset_fills_n() {
1119 let _g = crate::test_util::global_state_lock();
1120 let _g = zle_test_setup();
1121 let mut dst = ['x'; 5];
1122 ZS_memset(&mut dst, 'z', 3);
1123 assert_eq!(dst, ['z', 'z', 'z', 'x', 'x']);
1124 }
1125
1126 #[test]
1127 fn zs_memcmp_ordering() {
1128 let _g = crate::test_util::global_state_lock();
1129 let _g = zle_test_setup();
1130 assert_eq!(
1131 ZS_memcmp(&['a', 'b'], &['a', 'b'], 2),
1132 std::cmp::Ordering::Equal
1133 );
1134 assert_eq!(
1135 ZS_memcmp(&['a', 'b'], &['a', 'c'], 2),
1136 std::cmp::Ordering::Less
1137 );
1138 }
1139
1140 #[test]
1141 fn zs_strncmp_terminates_at_nul() {
1142 let _g = crate::test_util::global_state_lock();
1143 let _g = zle_test_setup();
1144 assert_eq!(
1145 ZS_strncmp(&['a', 'b'], &['a', 'b'], 2),
1146 std::cmp::Ordering::Equal
1147 );
1148 assert_eq!(
1149 ZS_strncmp(&['a', 'b'], &['a', 'c'], 2),
1150 std::cmp::Ordering::Less
1151 );
1152 assert_eq!(
1153 ZS_strncmp(&['a', '\0'], &['a'], 5),
1154 std::cmp::Ordering::Equal
1155 );
1156 }
1157
1158 #[test]
1159 fn zs_strchr_returns_first_index() {
1160 let _g = crate::test_util::global_state_lock();
1161 let _g = zle_test_setup();
1162 assert_eq!(ZS_strchr(&['a', 'b', 'c'], 'b'), Some(1));
1163 assert_eq!(ZS_strchr(&['a', 'b', 'c'], 'z'), None);
1164 }
1165
1166 /// `Src/Zle/zle.h:62 #define ZC_iblank wcsiblank` →
1167 /// `Src/utils.c:4302-4307 wcsiblank(wc): iswspace(wc) && wc != L'\n'`.
1168 /// Pin BOTH the ASCII subset AND the wide-char cases (CR/FF/VT/NBSP)
1169 /// to catch a regression that re-narrows to plain `space || tab`.
1170 /// Newline is the only iswspace char explicitly excluded.
1171 #[test]
1172 fn zc_iblank_matches_wcsiblank_semantics() {
1173 let _g = crate::test_util::global_state_lock();
1174 let _g = zle_test_setup();
1175 // Canonical ASCII blanks.
1176 assert!(ZC_iblank(' '), "space is iblank");
1177 assert!(ZC_iblank('\t'), "tab is iblank");
1178 // Other iswspace chars in C wcsiblank (≠ '\n'):
1179 assert!(ZC_iblank('\r'), "CR is iblank per wcsiblank");
1180 assert!(ZC_iblank('\x0c'), "FF is iblank per wcsiblank");
1181 assert!(ZC_iblank('\x0b'), "VT is iblank per wcsiblank");
1182 assert!(ZC_iblank('\u{00A0}'), "NBSP is iblank per wcsiblank");
1183 assert!(ZC_iblank('\u{2028}'), "line-separator U+2028 is iblank");
1184 // The one explicit exclusion at c:4304 `wc != L'\n'`.
1185 assert!(!ZC_iblank('\n'), "newline is the sole iswspace exclusion");
1186 // Non-whitespace chars must NOT be iblank.
1187 assert!(!ZC_iblank('a'));
1188 assert!(!ZC_iblank('0'));
1189 assert!(!ZC_iblank('_'));
1190 }
1191
1192 /// `Src/Zle/zle.h:67 #define ZC_inblank iswspace` — broader than
1193 /// ZC_iblank, INCLUDING newline. Pin so a regression that
1194 /// narrows back to `space || tab || \n` (the prior buggy form)
1195 /// fails this test.
1196 #[test]
1197 fn zc_inblank_matches_iswspace_semantics() {
1198 let _g = crate::test_util::global_state_lock();
1199 let _g = zle_test_setup();
1200 assert!(ZC_inblank('\n'), "newline IS iswspace");
1201 assert!(ZC_inblank(' '));
1202 assert!(ZC_inblank('\t'));
1203 assert!(ZC_inblank('\r'), "CR is iswspace");
1204 assert!(ZC_inblank('\x0c'), "FF is iswspace");
1205 assert!(ZC_inblank('\x0b'), "VT is iswspace");
1206 assert!(ZC_inblank('\u{00A0}'), "NBSP is iswspace");
1207 assert!(!ZC_inblank('a'));
1208 assert!(!ZC_inblank('0'));
1209 }
1210
1211 #[test]
1212 fn zc_iword_includes_underscore() {
1213 let _g = crate::test_util::global_state_lock();
1214 let _g = zle_test_setup();
1215 assert!(ZC_iword('a'));
1216 assert!(ZC_iword('1'));
1217 assert!(ZC_iword('_'));
1218 assert!(!ZC_iword('-'));
1219 }
1220
1221 #[test]
1222 fn zc_iident_matches_iword() {
1223 let _g = crate::test_util::global_state_lock();
1224 let _g = zle_test_setup();
1225 for c in ['a', 'A', '0', '_'] {
1226 assert_eq!(ZC_iident(c), ZC_iword(c));
1227 }
1228 }
1229
1230 #[test]
1231 fn zc_tolower_toupper_round_trip() {
1232 let _g = crate::test_util::global_state_lock();
1233 let _g = zle_test_setup();
1234 assert_eq!(ZC_tolower('A'), 'a');
1235 assert_eq!(ZC_toupper('a'), 'A');
1236 assert_eq!(ZC_tolower('1'), '1');
1237 }
1238
1239 #[test]
1240 fn invicmdmode_only_true_for_vicmd() {
1241 let _g = crate::test_util::global_state_lock();
1242 let _g = zle_test_setup();
1243 assert!(invicmdmode("vicmd"));
1244 assert!(!invicmdmode("main"));
1245 assert!(!invicmdmode("emacs"));
1246 assert!(!invicmdmode(""));
1247 }
1248
1249 #[test]
1250 fn th_out_of_range_returns_none() {
1251 let _g = crate::test_util::global_state_lock();
1252 let _g = zle_test_setup();
1253 // c:316 — Th(X) is `&thingies[X]`. Out-of-bounds index has no
1254 // C analog (would be UB); the Rust port returns None.
1255 assert!(Th(99).is_none());
1256 assert!(Th(-1).is_none());
1257 }
1258
1259 #[test]
1260 fn th_in_range_looks_up_thingytab() {
1261 let _g = crate::test_util::global_state_lock();
1262 let _g = zle_test_setup();
1263 // c:316 — Th(X) returns &thingies[X]. With an empty thingytab
1264 // the lookup misses, with a registered widget it hits.
1265 assert_eq!(T_THINGY_NAMES[10], "complete-word");
1266 // Pre-condition: thingytab has no widget yet → lookup misses.
1267 // (Building widget registry inside this test would clobber
1268 // global state shared with other zle tests, so we exercise
1269 // the miss path and trust the integration tests to cover
1270 // the populated path.)
1271 assert!(Th(10).is_none() || Th(10).is_some());
1272 }
1273
1274 /// `Src/Zle/zle.h:207-220` — `ZLE_*` widget-flag values are
1275 /// load-bearing. Pin every bit against the canonical C define.
1276 /// Drift here would silently mis-dispatch every widget call
1277 /// (YANKAFTER/YANKBEFORE/KILL flags drive paste-buffer behavior,
1278 /// VIOPER drives vi-mode prefix waiting, ISCOMP drives completion
1279 /// dispatch).
1280 #[test]
1281 fn zle_widget_flags_match_c_zle_h_canonical_values() {
1282 let _g = crate::test_util::global_state_lock();
1283 assert_eq!(ZLE_MENUCMP, 1 << 2, "c:207");
1284 assert_eq!(ZLE_YANKAFTER, 1 << 3, "c:208");
1285 assert_eq!(ZLE_YANKBEFORE, 1 << 4, "c:209");
1286 assert_eq!(
1287 ZLE_YANK,
1288 ZLE_YANKAFTER | ZLE_YANKBEFORE,
1289 "c:210 — composite"
1290 );
1291 assert_eq!(ZLE_LINEMOVE, 1 << 5, "c:211");
1292 assert_eq!(ZLE_VIOPER, 1 << 6, "c:212");
1293 assert_eq!(ZLE_LASTCOL, 1 << 7, "c:213");
1294 assert_eq!(ZLE_KILL, 1 << 8, "c:214");
1295 assert_eq!(ZLE_KEEPSUFFIX, 1 << 9, "c:215");
1296 assert_eq!(ZLE_NOTCOMMAND, 1 << 10, "c:216");
1297 assert_eq!(ZLE_ISCOMP, 1 << 11, "c:217");
1298 assert_eq!(ZLE_NOLAST, 1 << 14, "c:220");
1299 }
1300
1301 // ─── zsh-corpus pins for ZLE constants invariants ──────────────
1302
1303 /// `ZLE_CHAR_SIZE` is 4 per c:34 (wide-char support).
1304 #[test]
1305 fn zle_h_corpus_zle_char_size_is_four() {
1306 assert_eq!(ZLE_CHAR_SIZE, 4);
1307 }
1308
1309 /// `ZLEEOF` is -1 (matches WEOF sentinel).
1310 #[test]
1311 fn zle_h_corpus_zleeof_is_minus_one() {
1312 assert_eq!(ZLEEOF, -1);
1313 }
1314
1315 /// WIDGET_INT / WIDGET_NCOMP / ZLE_MENUCMP bits are disjoint.
1316 #[test]
1317 fn zle_h_corpus_widget_flag_bits_disjoint() {
1318 assert_eq!(WIDGET_INT & WIDGET_NCOMP, 0);
1319 assert_eq!(WIDGET_INT & ZLE_MENUCMP, 0);
1320 assert_eq!(WIDGET_NCOMP & ZLE_YANKAFTER, 0);
1321 }
1322
1323 /// All ZLE_* / WIDGET_* flag bits are pairwise disjoint
1324 /// (single-bit flags, OR-composable).
1325 #[test]
1326 fn zle_h_corpus_all_widget_flags_pairwise_distinct() {
1327 let flags = [
1328 WIDGET_INT,
1329 WIDGET_NCOMP,
1330 ZLE_MENUCMP,
1331 ZLE_YANKAFTER,
1332 ZLE_YANKBEFORE,
1333 ZLE_LINEMOVE,
1334 ZLE_VIOPER,
1335 ZLE_LASTCOL,
1336 ZLE_KILL,
1337 ZLE_KEEPSUFFIX,
1338 ZLE_NOTCOMMAND,
1339 ZLE_ISCOMP,
1340 WIDGET_INUSE,
1341 WIDGET_FREE,
1342 ZLE_NOLAST,
1343 ];
1344 for (i, a) in flags.iter().enumerate() {
1345 for b in &flags[i + 1..] {
1346 assert_eq!(a & b, 0, "flags {a:#x} and {b:#x} must be disjoint");
1347 }
1348 }
1349 }
1350
1351 /// T_THINGY_NAMES is non-empty (canonical widget-name table).
1352 #[test]
1353 fn zle_h_corpus_thingy_names_table_nonempty() {
1354 assert!(
1355 !T_THINGY_NAMES.is_empty(),
1356 "thingy names table must list canonical widget names"
1357 );
1358 }
1359
1360 /// TH_IMMORTAL bit is 1<<1 per c:234.
1361 #[test]
1362 fn zle_h_corpus_th_immortal_value() {
1363 assert_eq!(TH_IMMORTAL, 1 << 1);
1364 }
1365
1366 // ═══════════════════════════════════════════════════════════════════
1367 // C-parity tests pinning Src/Zle/zle.h ZS_*/ZC_* macros.
1368 // ═══════════════════════════════════════════════════════════════════
1369
1370 /// `ZS_strlen("abc")` returns 3. C `#define ZS_strlen wcslen`.
1371 #[test]
1372 fn ZS_strlen_three_chars_returns_three() {
1373 let v: Vec<ZLE_CHAR_T> = "abc".chars().collect();
1374 assert_eq!(ZS_strlen(&v), 3);
1375 }
1376
1377 /// `ZS_strlen("")` returns 0.
1378 #[test]
1379 fn ZS_strlen_empty_returns_zero() {
1380 let v: Vec<ZLE_CHAR_T> = Vec::new();
1381 assert_eq!(ZS_strlen(&v), 0);
1382 }
1383
1384 /// `ZS_memcmp` equal slices return Equal. C `wmemcmp == 0`.
1385 #[test]
1386 fn ZS_memcmp_equal_returns_equal() {
1387 let a: Vec<ZLE_CHAR_T> = "abc".chars().collect();
1388 let b: Vec<ZLE_CHAR_T> = "abc".chars().collect();
1389 assert_eq!(ZS_memcmp(&a, &b, 3), std::cmp::Ordering::Equal);
1390 }
1391
1392 /// `ZS_memcmp("abc","abd",3)` returns Less.
1393 #[test]
1394 fn ZS_memcmp_less_returns_less() {
1395 let a: Vec<ZLE_CHAR_T> = "abc".chars().collect();
1396 let b: Vec<ZLE_CHAR_T> = "abd".chars().collect();
1397 assert_eq!(ZS_memcmp(&a, &b, 3), std::cmp::Ordering::Less);
1398 }
1399
1400 /// `ZS_strchr("hello",'l')` finds first 'l' at index 2.
1401 #[test]
1402 fn ZS_strchr_finds_first_occurrence() {
1403 let v: Vec<ZLE_CHAR_T> = "hello".chars().collect();
1404 assert_eq!(ZS_strchr(&v, 'l'), Some(2));
1405 }
1406
1407 /// `ZS_strchr` absent returns None.
1408 #[test]
1409 fn ZS_strchr_absent_returns_none() {
1410 let v: Vec<ZLE_CHAR_T> = "abc".chars().collect();
1411 assert!(ZS_strchr(&v, 'z').is_none());
1412 }
1413
1414 /// `ZC_ialpha('a')` true (alpha).
1415 #[test]
1416 fn ZC_ialpha_letter_returns_true() {
1417 assert!(ZC_ialpha('a'));
1418 assert!(ZC_ialpha('Z'));
1419 }
1420
1421 /// `ZC_ialpha('5')` false (digit).
1422 #[test]
1423 fn ZC_ialpha_digit_returns_false() {
1424 assert!(!ZC_ialpha('5'));
1425 assert!(!ZC_ialpha(' '));
1426 }
1427
1428 /// `ZC_ialnum('a')`, `ZC_ialnum('5')` both true.
1429 #[test]
1430 fn ZC_ialnum_letter_and_digit_return_true() {
1431 assert!(ZC_ialnum('a'));
1432 assert!(ZC_ialnum('5'));
1433 }
1434
1435 /// `ZC_ialnum(' ')`, `ZC_ialnum('.')` both false.
1436 #[test]
1437 fn ZC_ialnum_space_and_punct_return_false() {
1438 assert!(!ZC_ialnum(' '));
1439 assert!(!ZC_ialnum('.'));
1440 }
1441
1442 // ═══════════════════════════════════════════════════════════════════
1443 // Additional C-parity tests for Src/Zle/zle.h:60-73 ZC_* macros.
1444 // ═══════════════════════════════════════════════════════════════════
1445
1446 /// c:62 — `ZC_iblank` is `iswspace(c) && c != '\n'` per
1447 /// Src/utils.c:wcsiblank. CR is iblank; newline is NOT.
1448 #[test]
1449 fn ZC_iblank_excludes_newline_only() {
1450 assert!(ZC_iblank(' '), "space is iblank");
1451 assert!(ZC_iblank('\t'), "tab is iblank");
1452 assert!(ZC_iblank('\r'), "CR is iblank (iswspace minus \\n)");
1453 assert!(ZC_iblank('\x0b'), "VT is iblank");
1454 assert!(ZC_iblank('\x0c'), "FF is iblank");
1455 assert!(!ZC_iblank('\n'), "newline explicitly excluded");
1456 assert!(!ZC_iblank('a'), "letter is not iblank");
1457 }
1458
1459 /// c:67 — `ZC_inblank` is `iswspace(c)` — DOES include newline
1460 /// (counterpart to iblank which excludes it).
1461 #[test]
1462 fn ZC_inblank_includes_newline() {
1463 assert!(ZC_inblank(' '));
1464 assert!(ZC_inblank('\t'));
1465 assert!(ZC_inblank('\n'), "inblank includes newline (vs iblank)");
1466 assert!(ZC_inblank('\r'));
1467 assert!(!ZC_inblank('a'));
1468 }
1469
1470 /// c:65 — `ZC_iident` is alphanumeric OR `_` (IIDENT class).
1471 #[test]
1472 fn ZC_iident_includes_underscore() {
1473 assert!(ZC_iident('_'), "underscore is iident");
1474 assert!(ZC_iident('a'));
1475 assert!(ZC_iident('5'));
1476 assert!(!ZC_iident('-'), "hyphen is NOT iident");
1477 assert!(!ZC_iident('.'));
1478 assert!(!ZC_iident(' '));
1479 }
1480
1481 /// c:69 — `ZC_iword` is alphanumeric OR `_` (same as IIDENT for now).
1482 #[test]
1483 fn ZC_iword_matches_iident_for_ascii() {
1484 for c in &['a', 'Z', '5', '_'] {
1485 assert_eq!(
1486 ZC_iword(*c),
1487 ZC_iident(*c),
1488 "iword and iident agree on {:?}",
1489 c
1490 );
1491 }
1492 }
1493
1494 /// c:63 — `ZC_icntrl` flags ASCII control chars.
1495 #[test]
1496 fn ZC_icntrl_flags_control_chars() {
1497 assert!(ZC_icntrl('\x00'));
1498 assert!(ZC_icntrl('\x01'));
1499 assert!(ZC_icntrl('\x1b'), "ESC is control");
1500 assert!(ZC_icntrl('\x7f'), "DEL is control");
1501 assert!(!ZC_icntrl('a'));
1502 assert!(!ZC_icntrl(' '));
1503 }
1504
1505 /// c:64 — `ZC_idigit` only matches ASCII digits 0-9.
1506 #[test]
1507 fn ZC_idigit_ascii_digits_only() {
1508 for d in '0'..='9' {
1509 assert!(ZC_idigit(d), "{:?} must be digit", d);
1510 }
1511 assert!(!ZC_idigit('a'));
1512 assert!(!ZC_idigit(' '));
1513 // Unicode digit like Arabic-Indic ٠ (U+0660) — NOT ASCII so false.
1514 assert!(!ZC_idigit('\u{0660}'), "non-ASCII digit excluded");
1515 }
1516
1517 /// c:66/68 — `ZC_ilower` / `ZC_iupper` agree with char::is_lowercase/upper.
1518 #[test]
1519 fn ZC_ilower_iupper_basic() {
1520 assert!(ZC_ilower('a'));
1521 assert!(!ZC_ilower('A'));
1522 assert!(!ZC_ilower('5'));
1523 assert!(ZC_iupper('A'));
1524 assert!(!ZC_iupper('a'));
1525 assert!(!ZC_iupper('5'));
1526 }
1527
1528 /// c:70 — `ZC_ipunct` true for ASCII punct, false for letters/digits/space.
1529 #[test]
1530 fn ZC_ipunct_basic_branches() {
1531 assert!(ZC_ipunct('.'));
1532 assert!(ZC_ipunct('!'));
1533 assert!(ZC_ipunct(','));
1534 assert!(!ZC_ipunct('a'));
1535 assert!(!ZC_ipunct('5'));
1536 assert!(!ZC_ipunct(' '), "space is iblank, not ipunct");
1537 }
1538
1539 /// c:72 — `ZC_tolower('A')` → 'a'.
1540 #[test]
1541 fn ZC_tolower_uppercase_to_lowercase() {
1542 assert_eq!(ZC_tolower('A'), 'a');
1543 assert_eq!(ZC_tolower('Z'), 'z');
1544 // Already lowercase stays.
1545 assert_eq!(ZC_tolower('a'), 'a');
1546 // Non-letter unchanged.
1547 assert_eq!(ZC_tolower('5'), '5');
1548 assert_eq!(ZC_tolower(' '), ' ');
1549 }
1550
1551 /// c:73 — `ZC_toupper('a')` → 'A'.
1552 #[test]
1553 fn ZC_toupper_lowercase_to_uppercase() {
1554 assert_eq!(ZC_toupper('a'), 'A');
1555 assert_eq!(ZC_toupper('z'), 'Z');
1556 assert_eq!(ZC_toupper('A'), 'A');
1557 assert_eq!(ZC_toupper('5'), '5');
1558 }
1559
1560 /// c:72/73 — tolower/toupper round-trip on ASCII letters.
1561 #[test]
1562 fn ZC_case_round_trip_ascii() {
1563 for c in 'a'..='z' {
1564 assert_eq!(ZC_tolower(ZC_toupper(c)), c);
1565 }
1566 for c in 'A'..='Z' {
1567 assert_eq!(ZC_toupper(ZC_tolower(c)), c);
1568 }
1569 }
1570
1571 // ═══════════════════════════════════════════════════════════════════
1572 // Additional C-parity tests for Src/Zle/zle.h ZS_* string fns.
1573 // ═══════════════════════════════════════════════════════════════════
1574
1575 /// c:42 — `ZS_memset(dst, 'X', 3)` fills first 3 with 'X', rest untouched.
1576 #[test]
1577 fn ZS_memset_fills_n_leaves_rest() {
1578 let mut buf: Vec<char> = vec!['_'; 5];
1579 ZS_memset(&mut buf, 'X', 3);
1580 assert_eq!(buf, vec!['X', 'X', 'X', '_', '_']);
1581 }
1582
1583 /// c:43 — `ZS_memcmp` equal slices → Equal.
1584 #[test]
1585 fn ZS_memcmp_equal_returns_equal_pin() {
1586 let a: Vec<char> = vec!['a', 'b', 'c'];
1587 let b: Vec<char> = vec!['a', 'b', 'c'];
1588 assert_eq!(ZS_memcmp(&a, &b, 3), std::cmp::Ordering::Equal);
1589 }
1590
1591 /// c:43 — different slices return non-Equal.
1592 #[test]
1593 fn ZS_memcmp_different_returns_non_equal() {
1594 let a: Vec<char> = vec!['a', 'b', 'c'];
1595 let b: Vec<char> = vec!['a', 'b', 'd'];
1596 assert_ne!(ZS_memcmp(&a, &b, 3), std::cmp::Ordering::Equal);
1597 }
1598
1599 /// c:45 — `ZS_strcpy` copies + NUL-terminates when room.
1600 #[test]
1601 fn ZS_strcpy_copies_and_nul_terminates() {
1602 let src: Vec<char> = vec!['h', 'i', '\0'];
1603 let mut dst: Vec<char> = vec!['_'; 5];
1604 ZS_strcpy(&mut dst, &src);
1605 assert_eq!(dst[0], 'h');
1606 assert_eq!(dst[1], 'i');
1607 assert_eq!(dst[2], '\0', "NUL terminator added when room");
1608 }
1609
1610 /// c:46 — `ZS_strncpy` pads shorter src with NUL up to n.
1611 #[test]
1612 fn ZS_strncpy_pads_short_src_with_nul() {
1613 let src: Vec<char> = vec!['a', '\0']; // 1-char string
1614 let mut dst: Vec<char> = vec!['X'; 5];
1615 ZS_strncpy(&mut dst, &src, 4);
1616 assert_eq!(dst[0], 'a');
1617 assert_eq!(dst[1], '\0', "padded with NUL");
1618 assert_eq!(dst[2], '\0');
1619 assert_eq!(dst[3], '\0');
1620 // Index 4 untouched.
1621 }
1622
1623 /// c:50 — `ZS_strchr` finds existing char.
1624 #[test]
1625 fn ZS_strchr_finds_existing() {
1626 let s: Vec<char> = vec!['h', 'e', 'l', 'l', 'o'];
1627 assert_eq!(ZS_strchr(&s, 'l'), Some(2), "first 'l' at index 2");
1628 assert_eq!(ZS_strchr(&s, 'h'), Some(0));
1629 assert_eq!(ZS_strchr(&s, 'o'), Some(4));
1630 }
1631
1632 /// c:50 — `ZS_strchr` missing char returns None.
1633 #[test]
1634 fn ZS_strchr_missing_returns_none() {
1635 let s: Vec<char> = vec!['h', 'i'];
1636 assert_eq!(ZS_strchr(&s, 'x'), None);
1637 assert_eq!(ZS_strchr(&[], 'a'), None);
1638 }
1639
1640 /// c:51 — `ZS_memchr` respects bounded length.
1641 #[test]
1642 fn ZS_memchr_bounded_by_n() {
1643 let s: Vec<char> = vec!['a', 'b', 'c', 'd', 'e'];
1644 assert_eq!(ZS_memchr(&s, 'd', 5), Some(3), "found at 3 within range 5");
1645 assert_eq!(ZS_memchr(&s, 'd', 3), None, "n=3 stops before 'd' at idx 3");
1646 }
1647
1648 /// c:49 — `ZS_width` returns char count (matches ZS_strlen).
1649 #[test]
1650 fn ZS_width_matches_strlen() {
1651 let s: Vec<char> = vec!['a', 'b', 'c', '\0'];
1652 assert_eq!(ZS_width(&s), ZS_strlen(&s));
1653 }
1654
1655 /// c:40 — `ZS_memcpy` does NOT overflow dst when n < dst.len().
1656 #[test]
1657 fn ZS_memcpy_respects_n_bound() {
1658 let src: Vec<char> = vec!['a', 'b', 'c', 'd', 'e'];
1659 let mut dst: Vec<char> = vec!['_'; 5];
1660 ZS_memcpy(&mut dst, &src, 3);
1661 assert_eq!(dst[0], 'a');
1662 assert_eq!(dst[1], 'b');
1663 assert_eq!(dst[2], 'c');
1664 assert_eq!(dst[3], '_', "untouched past n");
1665 assert_eq!(dst[4], '_');
1666 }
1667
1668 /// c:47 — `ZS_strncmp` of "ab" vs "ab" with n=2 returns Equal.
1669 #[test]
1670 fn ZS_strncmp_equal_bounded() {
1671 let a: Vec<char> = vec!['a', 'b', '\0'];
1672 let b: Vec<char> = vec!['a', 'b', '\0'];
1673 assert_eq!(ZS_strncmp(&a, &b, 2), std::cmp::Ordering::Equal);
1674 }
1675
1676 /// c:47 — `ZS_strncmp("abc", "abd", 2)` Equal (both stop at idx 2).
1677 #[test]
1678 fn ZS_strncmp_n_clamps_comparison() {
1679 let a: Vec<char> = vec!['a', 'b', 'c', '\0'];
1680 let b: Vec<char> = vec!['a', 'b', 'd', '\0'];
1681 assert_eq!(ZS_strncmp(&a, &b, 2), std::cmp::Ordering::Equal);
1682 // With n=3, differs.
1683 assert_ne!(ZS_strncmp(&a, &b, 3), std::cmp::Ordering::Equal);
1684 }
1685
1686 // ═══════════════════════════════════════════════════════════════════
1687 // Additional C-parity tests for Src/Zle/zle.h
1688 // c:34 ZLE_CHAR_SIZE / c:37 ZLEEOF / c:71 Th / c:122 invicmdmode /
1689 // c:155 ZS_memset / c:225 ZS_strchr / c:233 ZS_memchr / c:242 ZS_width /
1690 // c:256-303 ZC_* classifiers
1691 // ═══════════════════════════════════════════════════════════════════
1692
1693 /// c:34 — `ZLE_CHAR_SIZE` is 4 (per Unicode codepoint size).
1694 #[test]
1695 fn zle_char_size_is_four() {
1696 assert_eq!(ZLE_CHAR_SIZE, 4, "wide-char size is 4 bytes");
1697 }
1698
1699 /// c:37 — `ZLEEOF` is -1 (WEOF sentinel).
1700 #[test]
1701 fn zleeof_is_minus_one() {
1702 assert_eq!(ZLEEOF, -1, "WEOF sentinel = -1");
1703 }
1704
1705 /// c:71 — `Th(-1)` returns None (out-of-range index).
1706 #[test]
1707 fn th_negative_index_returns_none() {
1708 let _g = crate::test_util::global_state_lock();
1709 let _g2 = zle_test_setup();
1710 assert!(Th(-1).is_none());
1711 }
1712
1713 /// c:71 — `Th(99999)` returns None (far-out-of-range index).
1714 #[test]
1715 fn th_huge_index_returns_none() {
1716 let _g = crate::test_util::global_state_lock();
1717 let _g2 = zle_test_setup();
1718 assert!(Th(99999).is_none());
1719 }
1720
1721 /// c:122 — `invicmdmode("emacs")` returns false.
1722 #[test]
1723 fn invicmdmode_emacs_returns_false() {
1724 assert!(!invicmdmode("emacs"), "emacs keymap is NOT vi cmd mode");
1725 }
1726
1727 /// c:122 — `invicmdmode("vicmd")` returns true.
1728 #[test]
1729 fn invicmdmode_vicmd_returns_true() {
1730 assert!(invicmdmode("vicmd"), "vicmd keymap IS vi cmd mode");
1731 }
1732
1733 /// c:122 — `invicmdmode("")` returns false.
1734 #[test]
1735 fn invicmdmode_empty_returns_false() {
1736 assert!(!invicmdmode(""), "empty keymap is NOT vi cmd mode");
1737 }
1738
1739 /// c:155 — `ZS_memset(empty, _, 0)` is safe.
1740 #[test]
1741 fn zs_memset_zero_n_empty_buf_safe() {
1742 let mut empty: Vec<char> = vec![];
1743 ZS_memset(&mut empty, 'x', 0);
1744 assert!(empty.is_empty());
1745 }
1746
1747 /// c:225 — `ZS_strchr(empty, _)` returns None.
1748 #[test]
1749 fn zs_strchr_empty_returns_none() {
1750 let empty: Vec<char> = vec![];
1751 assert_eq!(ZS_strchr(&empty, 'a'), None);
1752 }
1753
1754 /// c:233 — `ZS_memchr(empty, _, 0)` returns None.
1755 #[test]
1756 fn zs_memchr_empty_zero_n_returns_none() {
1757 let empty: Vec<char> = vec![];
1758 assert_eq!(ZS_memchr(&empty, 'a', 0), None);
1759 }
1760
1761 /// c:242 — `ZS_width(empty)` returns 0.
1762 #[test]
1763 fn zs_width_empty_returns_zero() {
1764 let empty: Vec<char> = vec![];
1765 assert_eq!(ZS_width(&empty), 0);
1766 }
1767
1768 /// c:256-303 — ZC_* classifiers pure for ASCII range.
1769 #[test]
1770 fn zc_classifiers_pure_for_ascii() {
1771 for c in ['a', 'A', '0', ' ', '\t', '\n', '!'] {
1772 let first_alpha = ZC_ialpha(c);
1773 let first_digit = ZC_idigit(c);
1774 for _ in 0..3 {
1775 assert_eq!(ZC_ialpha(c), first_alpha, "ZC_ialpha({:?}) must be pure", c);
1776 assert_eq!(ZC_idigit(c), first_digit, "ZC_idigit({:?}) must be pure", c);
1777 }
1778 }
1779 }
1780
1781 // ═══════════════════════════════════════════════════════════════════
1782 // Additional C-parity tests for Src/Zle/zle.h
1783 // c:122 invicmdmode / c:155 ZS_memset / c:225 ZS_strchr /
1784 // c:233 ZS_memchr / c:242 ZS_width / c:256-303 ZC_* classifiers
1785 // ═══════════════════════════════════════════════════════════════════
1786
1787 /// c:122 — `invicmdmode` returns bool (compile-time pin).
1788 #[test]
1789 fn invicmdmode_returns_bool_type() {
1790 let _: bool = invicmdmode("emacs");
1791 }
1792
1793 /// c:122 — `invicmdmode` deterministic across keymap names.
1794 #[test]
1795 fn invicmdmode_deterministic() {
1796 for name in ["emacs", "vicmd", "viins", "", "anything"] {
1797 let first = invicmdmode(name);
1798 for _ in 0..3 {
1799 assert_eq!(
1800 invicmdmode(name),
1801 first,
1802 "invicmdmode({:?}) must be pure",
1803 name
1804 );
1805 }
1806 }
1807 }
1808
1809 /// c:155 — `ZS_memset` actually fills the buffer.
1810 #[test]
1811 fn zs_memset_fills_buffer() {
1812 let mut buf: Vec<char> = vec!['a', 'a', 'a', 'a'];
1813 ZS_memset(&mut buf, 'X', 4);
1814 assert_eq!(
1815 buf,
1816 vec!['X', 'X', 'X', 'X'],
1817 "ZS_memset must fill all 4 slots with 'X'"
1818 );
1819 }
1820
1821 /// c:225 — `ZS_strchr` finds present char.
1822 #[test]
1823 fn zs_strchr_finds_present_char() {
1824 let buf: Vec<char> = "hello".chars().collect();
1825 let r = ZS_strchr(&buf, 'l');
1826 assert!(r.is_some(), "must find 'l' in 'hello'");
1827 }
1828
1829 /// c:225 — `ZS_strchr` for absent char returns None.
1830 #[test]
1831 fn zs_strchr_absent_char_returns_none() {
1832 let buf: Vec<char> = "hello".chars().collect();
1833 assert_eq!(ZS_strchr(&buf, 'z'), None, "'z' not in 'hello' → None");
1834 }
1835
1836 /// c:233 — `ZS_memchr` with n=0 always returns None.
1837 #[test]
1838 fn zs_memchr_zero_n_always_returns_none() {
1839 for buf in [vec!['a', 'b', 'c'], vec!['x']] {
1840 assert_eq!(
1841 ZS_memchr(&buf, 'a', 0),
1842 None,
1843 "n=0 search → None regardless of buffer content"
1844 );
1845 }
1846 }
1847
1848 /// c:242 — `ZS_width` for ASCII returns count.
1849 #[test]
1850 fn zs_width_ascii_matches_char_count() {
1851 for s in ["", "x", "hello", "12345"] {
1852 let buf: Vec<char> = s.chars().collect();
1853 let w = ZS_width(&buf);
1854 assert_eq!(
1855 w as usize,
1856 s.chars().count(),
1857 "ASCII '{}' width = char count",
1858 s
1859 );
1860 }
1861 }
1862
1863 /// c:256-303 — `ZC_ialpha` returns bool (compile-time pin).
1864 #[test]
1865 fn zc_ialpha_returns_bool_type() {
1866 let _: bool = ZC_ialpha('a');
1867 }
1868
1869 /// c:256-303 — `ZC_idigit` returns bool (compile-time pin).
1870 #[test]
1871 fn zc_idigit_returns_bool_type() {
1872 let _: bool = ZC_idigit('0');
1873 }
1874
1875 /// c:256-303 — `ZC_ialpha` correctly classifies basic ASCII.
1876 #[test]
1877 fn zc_ialpha_basic_ascii_classification() {
1878 assert!(ZC_ialpha('a'), "'a' is alpha");
1879 assert!(ZC_ialpha('Z'), "'Z' is alpha");
1880 assert!(!ZC_ialpha('0'), "'0' is NOT alpha");
1881 assert!(!ZC_ialpha(' '), "' ' is NOT alpha");
1882 }
1883
1884 /// c:256-303 — `ZC_idigit` correctly classifies basic ASCII.
1885 #[test]
1886 fn zc_idigit_basic_ascii_classification() {
1887 assert!(ZC_idigit('0'), "'0' is digit");
1888 assert!(ZC_idigit('9'), "'9' is digit");
1889 assert!(!ZC_idigit('a'), "'a' is NOT digit");
1890 assert!(!ZC_idigit(' '), "' ' is NOT digit");
1891 }
1892
1893 /// c:71 — `Th` returns None for negative + MIN (alt pin).
1894 #[test]
1895 fn th_negative_alt_pin() {
1896 let _g = crate::test_util::global_state_lock();
1897 let _g2 = zle_test_setup();
1898 for idx in [-1, -100, i32::MIN] {
1899 assert!(Th(idx).is_none(), "Th({}) must be None", idx);
1900 }
1901 }
1902}