zsh/lib.rs
1//! Zsh interpreter and parser in Rust
2//!
3//! This crate provides:
4//! - A complete zsh lexer (`lexer` module)
5//! - A zsh parser (`parser` module)
6//! - Shell execution engine (`exec` module)
7//! - Job control (`jobs` module)
8//! - History management (`history` module)
9//! - ZLE (Zsh Line Editor) support (`zle` module)
10//! - ZWC (compiled zsh) support (`zwc` module)
11//! - Fish-style features (`fish_features` module)
12//! - Mathematical expression evaluation (`math` module)
13
14// Many doc comments reference C-source pages, shell constructs, and
15// zsh-internal identifiers by name in `[...]` form; they don't resolve as
16// rustdoc intra-doc links. Silence so docs build clean on CI.
17#![allow(rustdoc::broken_intra_doc_links)]
18#![allow(rustdoc::private_intra_doc_links)]
19#![allow(rustdoc::invalid_html_tags)]
20#![allow(dead_code)]
21#![allow(unused_variables)]
22#![allow(unused_imports)]
23#![allow(unused_assignments)]
24#![allow(unused_mut)]
25#![allow(unused_parens)]
26#![allow(unused_doc_comments)]
27#![allow(unreachable_patterns)]
28#![allow(deprecated)]
29#![allow(unexpected_cfgs)]
30// Allow zsh-canonical identifier names (lowercase statics/constants/types
31// like `ca_parsed`, `convchar_t`, `P_ISBRANCH`) so the ports stay
32// faithful to the C source per PORT.md.
33#![allow(non_snake_case)]
34#![allow(non_camel_case_types)]
35#![allow(non_upper_case_globals)]
36// Function-pointer-to-integer casts appear in ported dispatch tables.
37#![allow(function_casts_as_integer)]
38// Clippy: the C → Rust ports preserve idioms from the zsh source
39// (raw pointer derefs, dead-loop `do { ... } while (0)` shapes, bitmasks
40// that look redundant but match the C, etc.). Silence the whole group so
41// port fidelity wins over Rust-idiom rewrites. New non-ported code
42// should still aim for clippy-clean, but at file/function scope, not
43// crate-wide.
44#![allow(clippy::all)]
45
46/// Runtime shell-mode flag set by the binary entrypoint (`bins/zshrs.rs`)
47/// at startup. The library can't directly read `bins/zshrs.rs::shell_mode()`
48/// (it lives in the binary crate), so the binary writes this atomic when
49/// parsing `--zsh` / `--bash` / `--posix` and the library reads it from
50/// bridge / dispatch sites that need to gate bash-compat-vs-zsh behavior.
51/// Defaults to `false` (zshrs-native mode) when not explicitly set.
52///
53/// Bugs #475 / #504 / #555 in docs/BUGS.md — bash-only builtins
54/// (`caller`/`help`/`mapfile`/`readarray`/`compgen`/etc.) should
55/// dispatch as "command not found" when this is true.
56pub static IS_ZSH_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
57
58/// `compsys` submodule.
59pub mod compsys;
60/// `exec_jobs` submodule.
61pub mod exec_jobs;
62/// `extensions` submodule.
63pub mod extensions;
64/// `metafied_key` submodule (Rust-only; see the module docs).
65pub mod metafied_key;
66/// `ported` submodule.
67pub mod ported;
68/// `pattern_data_escape` submodule (Rust-only; see the module docs).
69pub mod pattern_data_escape;
70/// `script_bytes` submodule (Rust-only; see the module docs).
71pub mod script_bytes;
72/// `token_char` submodule (Rust-only): where token chars live in a `char` string.
73pub mod token_char;
74/// `zlong_cast` submodule (Rust-only): C's `(zlong) double` with the host CPU's out-of-range answer.
75pub mod zlong_cast;
76/// `host_libc` submodule (Rust-only): host-libc behaviours C zsh inherits by calling libc.
77pub mod host_libc;
78/// `fork_safe_pool` submodule (Rust-only): a rayon pool that survives `fork()`.
79pub mod fork_safe_pool;
80/// `subscript_escape` submodule (Rust-only; see the module docs).
81pub mod subscript_escape;
82/// `test_util` submodule.
83#[cfg(test)]
84pub mod test_util;
85/// `tiers` submodule.
86pub mod tiers;
87pub mod tolerant_sort;
88
89// Back-compat: re-export every ported submodule at the crate root so
90// historical call sites (`crate::exec::`, `crate::subst::`,
91// `crate::zle::`, `crate::modules::`, `crate::builtins::`, etc.)
92// continue to resolve unchanged after the physical move into
93// `src/ported/`. New code should prefer `crate::ported::<name>`.
94pub use ported::*;
95/// `ai` submodule — the `ai` builtin's provider engine (config, cost
96/// accounting, response cache, mock mode, SSE streaming). Ported from
97/// `strykelang/strykelang/ai.rs`.
98#[path = "extensions/ai.rs"]
99pub mod ai;
100/// `alias_input_frames` submodule — per-thread record of popped alias
101/// input-stack frames, restoring the reachability C's manually-indexed
102/// `instack` gives `input_hasalias`.
103#[path = "extensions/alias_input_frames.rs"]
104pub mod alias_input_frames;
105/// `aot` submodule.
106#[path = "extensions/aot.rs"]
107pub mod aot;
108/// `arith_compiler` submodule.
109#[path = "extensions/arith_compiler.rs"]
110pub mod arith_compiler;
111/// `atexit_teardown` submodule — whether a libc `atexit` hook is running,
112/// so the log sites they reach can skip TLS-backed `tracing`.
113#[path = "extensions/atexit_teardown.rs"]
114pub mod atexit_teardown;
115/// `atomic_write` submodule — the shared temp-file-safe shard writer
116/// used by both rkyv caches.
117#[path = "extensions/atomic_write.rs"]
118pub mod atomic_write;
119/// `autoload_cache` submodule.
120#[path = "extensions/autoload_cache.rs"]
121pub mod autoload_cache;
122/// `autoload_prewarm` submodule.
123#[path = "extensions/autoload_prewarm.rs"]
124pub mod autoload_prewarm;
125/// `deparse_cache` submodule.
126#[path = "extensions/deparse_cache.rs"]
127pub mod deparse_cache;
128/// `banner` submodule — the `zbanner` builtin and `zshrs --banner`
129/// (logo, builtin totals, daemon and shell counts). Ported from ztmux.
130#[path = "extensions/banner.rs"]
131pub mod banner;
132/// `bundled_docs` submodule — zsh's man/info pages, shipped in-binary.
133#[path = "extensions/bundled_docs.rs"]
134pub mod bundled_docs;
135#[path = "extensions/bundled_functions.rs"]
136pub mod bundled_functions;
137/// `c_locale` submodule — `mbrtowc`/`wcrtomb` with one C-locale (ASCII)
138/// answer on every platform; re-exported by `ported::utils`.
139#[path = "extensions/c_locale.rs"]
140pub mod c_locale;
141/// `system_libm` submodule — the C library's `cbrt` for zsh/mathfunc (Rust's
142/// compiler_builtins shadows it on Linux).
143#[path = "extensions/system_libm.rs"]
144pub mod system_libm;
145/// `bash_complete` submodule.
146#[path = "extensions/bash_complete.rs"]
147pub mod bash_complete;
148/// `canonical_apply` submodule.
149#[path = "extensions/canonical_apply.rs"]
150pub mod canonical_apply;
151/// Shared-handle accessors for the completion match accumulators (Rust-original
152/// glue restoring C's `matches = mgroup->lmatches` pointer alias; see the module
153/// doc). Deliberately outside `src/ported/` — not a C-fn port.
154pub mod comp_match_handles;
155pub mod comp_word_tok;
156/// `compile_zsh` submodule.
157#[path = "extensions/compile_zsh.rs"]
158pub mod compile_zsh;
159/// `completion` submodule.
160#[path = "extensions/completion.rs"]
161pub mod completion;
162/// `config` submodule.
163#[path = "extensions/config.rs"]
164pub mod config;
165/// `cow_map` submodule — copy-on-write HashMap wrapper for cheap subshell
166/// snapshot/restore (Rust-only helper).
167#[path = "extensions/cow_map.rs"]
168pub mod cow_map;
169/// `daemon_presence` submodule.
170#[path = "extensions/daemon_presence.rs"]
171pub mod daemon_presence;
172/// `errflag_cell` submodule — per-thread storage for `errflag`, restoring
173/// the copy-on-fork semantics C zsh gets for free.
174#[path = "extensions/errflag_cell.rs"]
175pub mod errflag_cell;
176/// `fast_hash` submodule — dependency-free FxHash for internal name tables.
177#[path = "extensions/fast_hash.rs"]
178pub mod fast_hash;
179/// `thread_shell_state` submodule — per-thread storage for the state C
180/// keeps per thread of execution (`scriptname`, `funcstack`,
181/// `zsh_eval_context`, the diagnostic half of `locallevel`).
182#[path = "extensions/thread_shell_state.rs"]
183pub mod thread_shell_state;
184/// `opts_cache` submodule — fast-path `isset()` option-state cache.
185#[path = "extensions/opts_cache.rs"]
186pub mod opts_cache;
187/// `overlay_snapshot` submodule.
188#[path = "extensions/overlay_snapshot.rs"]
189pub mod overlay_snapshot;
190/// `pat_cache` submodule — global compiled-pattern cache (Rust-only opt).
191#[path = "extensions/pat_cache.rs"]
192pub mod pat_cache;
193/// `provenance` submodule — value-lineage ledger over bytecode
194/// execution (zshrs-original; ported from stryke's `provenance.rs`).
195#[path = "extensions/provenance.rs"]
196pub mod provenance;
197/// `reaped_status` submodule — the raw wait statuses the SIGCHLD reaper
198/// collected, so a targeted `waitpid` that loses the race to it can
199/// still read the status it needed (C never needs this: it has exactly
200/// one collector).
201#[path = "extensions/reaped_status.rs"]
202pub mod reaped_status;
203/// `script_cache` submodule.
204#[path = "extensions/script_cache.rs"]
205pub mod script_cache;
206/// `stdout_ferror` submodule — the `ferror(stdout)` indicator for builtin
207/// output, which `std::io::Stdout` cannot report for a closed fd 1.
208#[path = "extensions/stdout_ferror.rs"]
209pub mod stdout_ferror;
210/// `shout` submodule — buffered terminal-output stream for the ZLE display
211/// (the stdio buffering C gets from libc's `FILE *shout`).
212#[path = "extensions/shout.rs"]
213pub mod shout;
214/// `startup_signals` submodule.
215#[path = "extensions/startup_signals.rs"]
216pub mod startup_signals;
217/// `subexp_cleanup` submodule — RAII eviction of `__subexp_arr_*`
218/// paramtab scratch temps created during array sub-expression expansion.
219#[path = "extensions/subexp_cleanup.rs"]
220pub mod subexp_cleanup;
221/// `vm_pool` submodule — per-thread pool of recyclable fusevm VMs.
222#[path = "extensions/vm_pool.rs"]
223pub mod vm_pool;
224// Daemon lives in the `zshrs-daemon` workspace crate. Re-export it as `daemon`
225// so existing `crate::daemon::...` (in vm_helper) and `zsh::daemon::...` (in bins,
226// integration tests) paths keep resolving without churn.
227//
228// The `daemon` feature gates the actual zshrs-daemon dep. When disabled
229// (--no-default-features), a stub module covers the call sites in vm_helper.
230// This lets the library compile in isolation while the daemon crate is
231// being refactored in a concurrent session.
232#[cfg(feature = "daemon")]
233pub use zshrs_daemon as daemon;
234/// `daemon` submodule.
235#[cfg(not(feature = "daemon"))]
236pub mod daemon {
237 //! Stub module used when the `daemon` feature is disabled. Provides
238 //! the minimal surface that `src/vm_helper` calls — the real
239 //! implementation lives in the `zshrs-daemon` workspace crate.
240 pub mod builtins {
241 pub const ZSHRS_BUILTIN_NAMES: &[&str] = &[];
242 /// `is_zshrs_builtin` — see implementation.
243 pub fn is_zshrs_builtin(_name: &str) -> bool {
244 false
245 }
246 /// `try_dispatch` — see implementation.
247 pub fn try_dispatch(_name: &str, _argv: &[String]) -> Option<i32> {
248 None
249 }
250 /// `dispatch` — see implementation.
251 pub fn dispatch(_name: &str, _args: &[String]) -> Option<i32> {
252 None
253 }
254 }
255}
256/// `ast_sexp` submodule.
257#[path = "extensions/ast_sexp.rs"]
258pub mod ast_sexp;
259/// `bash_arrays` submodule — bash sparse-array holes tracker (Rust-only).
260#[path = "extensions/bash_arrays.rs"]
261pub mod bash_arrays;
262/// `bash_prompt` submodule — bash `$PS1` backslash escapes (Rust-only).
263#[path = "extensions/bash_prompt.rs"]
264pub mod bash_prompt;
265/// `emulation_output` submodule — per-shell builtin output formats (Rust-only).
266#[path = "extensions/emulation_output.rs"]
267pub mod emulation_output;
268/// `emulation_startup` submodule — per-drop-in startup/logout files (Rust-only).
269#[path = "extensions/emulation_startup.rs"]
270pub mod emulation_startup;
271/// `dap` submodule.
272#[path = "extensions/dap.rs"]
273pub mod dap;
274/// `dash_mode` submodule — strict-dash emulation flag (Rust-only).
275#[path = "extensions/dash_mode.rs"]
276pub mod dash_mode;
277/// `dumpers` submodule.
278#[path = "extensions/dumpers.rs"]
279pub mod dumpers;
280/// `ext_builtins` submodule.
281#[path = "extensions/ext_builtins.rs"]
282pub mod ext_builtins;
283/// `fds` submodule.
284#[path = "extensions/fds.rs"]
285pub mod fds;
286/// `fish_features` submodule.
287#[path = "extensions/fish_features.rs"]
288pub mod fish_features;
289/// `fmt` submodule — zsh source formatter (CLI `--fmt` + LSP
290/// `textDocument/formatting`).
291#[path = "extensions/fmt.rs"]
292pub mod fmt;
293/// `ftime` submodule — TEMPORARY per-function timing scaffold (Rust-only).
294#[path = "extensions/ftime.rs"]
295pub mod ftime;
296/// `func_body_fmt` submodule.
297#[path = "extensions/func_body_fmt.rs"]
298pub mod func_body_fmt;
299/// `funcdef_capture` submodule — Rust-only verbatim capture of function
300/// body source text as `hgetc` consumes it, so `functions` / `typeset -f`
301/// work for functions defined interactively or on stdin (where zshrs's
302/// `LEX_INPUT` window does not exist). No C counterpart: C zsh
303/// reconstructs the text from wordcode via `getpermtext` (Src/text.c:189).
304#[path = "extensions/funcdef_capture.rs"]
305pub mod funcdef_capture;
306/// `global_rc` submodule — runtime sysconfdir resolution for the
307/// system-wide startup files (Rust-only; zsh bakes the path in at build
308/// time).
309#[path = "extensions/global_rc.rs"]
310pub mod global_rc;
311/// `lsp` submodule.
312#[path = "extensions/lsp.rs"]
313pub mod lsp;
314/// `lsp_symbols` submodule.
315#[path = "extensions/lsp_symbols.rs"]
316pub mod lsp_symbols;
317/// `native_cmds` submodule — builtins contributed by the linking binary
318/// (the fat `zshrs-native` build registers `git` / `arb` / `stryke` here).
319#[path = "extensions/native_cmds.rs"]
320pub mod native_cmds;
321// Lexer + parser live in `src/ported/lex.rs` and `src/ported/parse.rs`.
322// Re-export the modules so existing call sites (`zsh::lex::…`,
323// `zsh::parse::…`, `zsh::tokens::…`) keep resolving.
324// `tokens` aliases `lex` because tokens.rs's contents (lextok enum +
325// reserved-word table) now live inside lex.rs. Char tokens (Pound / Inpar /
326// Equals / …) and the REDIR_* / COND_* constants are not duplicated — they
327// live as flat `pub const` items in `ported::zsh_h` per `Src/zsh.h:144-679`.
328pub use ported::lex;
329pub use ported::lex as tokens;
330pub use ported::parse;
331/// `heredoc_ast` submodule.
332#[path = "extensions/heredoc_ast.rs"]
333pub mod heredoc_ast;
334/// `history` submodule.
335#[path = "extensions/history.rs"]
336pub mod history;
337/// `history_lazy` submodule — on-demand HISTFILE paging; the history
338/// is never slurped whole.
339#[path = "extensions/history_lazy.rs"]
340pub mod history_lazy;
341/// `log` submodule.
342#[path = "extensions/log.rs"]
343pub mod log;
344/// `startup_trace` submodule — phase timer for time-to-first-prompt work.
345#[path = "extensions/startup_trace.rs"]
346pub mod startup_trace;
347/// `lowfd` submodule — keeps the shell's own descriptors out of the user's fd space.
348#[path = "extensions/lowfd.rs"]
349pub mod lowfd;
350/// `signal_thread` submodule — helper threads that never run the shell's signal handlers.
351#[path = "extensions/signal_thread.rs"]
352pub mod signal_thread;
353/// `zsh_ast` submodule.
354#[path = "extensions/zsh_ast.rs"]
355pub mod zsh_ast;
356// Backwards-compat flat re-exports — call sites that still write
357// `crate::datetime::…`, `crate::stat::…`, etc. resolve to the
358// `crate::modules::<modname>` ports without churn. New code should
359// reach for `crate::modules::<modname>` directly.
360pub use builtins::sched;
361pub use modules::attr;
362pub use modules::cap;
363pub use modules::clone;
364pub use modules::curses;
365pub use modules::datetime;
366pub use modules::db_gdbm;
367pub use modules::example;
368pub use modules::files;
369pub use modules::hlgroup;
370pub use modules::ksh93;
371pub use modules::langinfo;
372pub use modules::mapfile;
373pub use modules::mathfunc;
374pub use modules::nearcolor;
375pub use modules::newuser;
376pub use modules::param_private;
377pub use modules::parameter;
378pub use modules::pcre;
379pub use modules::random;
380pub use modules::random_real;
381pub use modules::regex as regex_module;
382pub use modules::socket;
383pub use modules::stat;
384pub use modules::system;
385pub use modules::tcp;
386pub use modules::termcap;
387pub use modules::terminfo;
388pub use modules::watch;
389pub use modules::zftp;
390pub use modules::zprof;
391pub use modules::zpty;
392pub use modules::zselect;
393pub use modules::zutil;
394/// `compinit_bg` submodule.
395#[path = "extensions/compinit_bg.rs"]
396pub mod compinit_bg;
397/// `fusevm_bridge` submodule.
398pub mod fusevm_bridge;
399/// `fusevm_disasm` submodule.
400pub mod fusevm_disasm;
401/// `intercepts` submodule.
402#[path = "extensions/intercepts.rs"]
403pub mod intercepts;
404/// `p10k` submodule — native powerlevel10k prompt engine.
405#[path = "extensions/p10k/mod.rs"]
406pub mod p10k;
407/// `pkg` — the `zpm` plugin package manager (global store).
408#[path = "extensions/pkg/mod.rs"]
409pub mod pkg;
410/// `plugin_cache` submodule.
411#[path = "extensions/plugin_cache.rs"]
412pub mod plugin_cache;
413/// `plugin_host` submodule — native (Rust) plugin loader (`zmodload -R`).
414#[path = "extensions/plugin_host.rs"]
415pub mod plugin_host;
416/// `recorder_ext` submodule.
417#[path = "extensions/recorder.rs"]
418pub mod recorder_ext;
419/// `rust_ffi` submodule — inline `rust { ... }` FFI desugaring.
420pub mod rust_ffi;
421// Plugin-Framework-Agnostic State-Modification Recorder. Entire module
422// is `#![cfg(feature = "recorder")]` so it disappears from the default
423// `zshrs` build at the rustc-expansion stage. See docs/RECORDER.md.
424/// `async_precmd` submodule — run precmd-style hooks on the worker pool so they
425/// don't block prompt rendering (writes into the shared param table).
426#[path = "extensions/async_precmd.rs"]
427pub mod async_precmd;
428/// `autopair` submodule — native bracket/quote auto-pairing
429/// (port of hlissner/zsh-autopair).
430#[path = "extensions/autopair.rs"]
431pub mod autopair;
432/// `autosuggest` submodule — native fish-style autosuggestions
433/// (port of the reader.rs autosuggestion state machine).
434#[path = "extensions/autosuggest.rs"]
435pub mod autosuggest;
436/// `gen_docs` submodule.
437#[path = "extensions/gen_docs.rs"]
438pub mod gen_docs;
439/// `history_search` submodule — native up-arrow prefix/substring/token history
440/// search (port of fish reader/history_search.rs).
441#[path = "extensions/history_search.rs"]
442pub mod history_search;
443/// `recorder` submodule.
444#[cfg(feature = "recorder")]
445pub mod recorder;
446/// `regex_mod` submodule.
447#[path = "extensions/regex_mod.rs"]
448pub mod regex_mod;
449/// `stringsort` submodule.
450#[path = "extensions/stringsort.rs"]
451pub mod stringsort;
452/// `terminfo_caps` submodule — the frozen terminfo capability-name tables
453/// that replace ncurses' exported `boolnames`/`numnames`/`strnames` arrays.
454#[path = "extensions/terminfo_caps.rs"]
455pub mod terminfo_caps;
456/// `terminfo_db` submodule — pure-Rust reader for the compiled terminfo
457/// database, replacing `setupterm`/`tigetstr`/`tgetent`/… from libtinfo.
458#[path = "extensions/terminfo_db.rs"]
459pub mod terminfo_db;
460/// `tparm` submodule — the terminfo parameterized-string evaluator plus
461/// `tgoto` and `tputs` padding, replacing the last libtinfo entry points.
462#[path = "extensions/tparm.rs"]
463pub mod tparm;
464/// `syntax_highlight` submodule — native command-line syntax highlighting
465/// (port of fish highlight/highlight.rs, driven by the zshrs lexer).
466#[path = "extensions/syntax_highlight.rs"]
467pub mod syntax_highlight;
468/// `worker` submodule.
469#[path = "extensions/worker.rs"]
470pub mod worker;
471/// `zle_file_tester` submodule — file-existence/permission tests for native ZLE
472/// syntax highlighting (port of fish highlight/file_tester.rs).
473#[path = "extensions/zle_file_tester.rs"]
474pub mod zle_file_tester;
475/// `zle_fx` submodule — wiring for the native ZLE effects (autosuggest,
476/// syntax highlight, history search, autopair) into zlecore + the renderer.
477#[path = "extensions/zle_fx.rs"]
478pub mod zle_fx;
479/// `zle_param_sync` submodule — ZLE special-param write-back sync
480/// (Rust-only adapter for C's live GSU setters).
481#[path = "extensions/zle_param_sync.rs"]
482pub mod zle_param_sync;
483/// `zsh_builtin_docs` submodule.
484#[path = "extensions/zsh_builtin_docs.rs"]
485pub mod zsh_builtin_docs;
486/// `zsh_ext_builtin_docs` submodule.
487#[path = "extensions/zsh_ext_builtin_docs.rs"]
488pub mod zsh_ext_builtin_docs;
489/// `zsh_keyword_docs` submodule.
490#[path = "extensions/zsh_keyword_docs.rs"]
491pub mod zsh_keyword_docs;
492/// `zsh_option_docs` submodule.
493#[path = "extensions/zsh_option_docs.rs"]
494pub mod zsh_option_docs;
495/// `zsh_special_var_docs` submodule.
496#[path = "extensions/zsh_special_var_docs.rs"]
497pub mod zsh_special_var_docs;
498/// `ztest` submodule — shell-level unit test framework
499/// (port of `../strykelang` test framework).
500#[path = "extensions/ztest.rs"]
501pub mod ztest;
502/// `zwc` submodule.
503#[path = "extensions/zwc.rs"]
504pub mod zwc;
505/// `zwc_decode` submodule.
506#[path = "extensions/zwc_decode.rs"]
507pub mod zwc_decode;
508// Backwards-compat re-export so `crate::rlimits::…` keeps resolving.
509pub use builtins::rlimits;
510
511// Top-level shell executor state + fusevm bridge glue. Not a port of
512// any single Src/*.c file — zsh's native wordcode VM lives in `Src/exec.c`;
513// zshrs runs fusevm instead (see src/fusevm_bridge.rs).
514/// `vm_helper` submodule.
515pub mod vm_helper;
516
517pub use fish_features::{
518 autosuggest_from_history,
519 colorize_line,
520 expand_abbreviation,
521 // Syntax highlighting
522 highlight_shell,
523 // Private mode
524 is_private_mode,
525 // Killring
526 kill_add,
527 kill_replace,
528 kill_yank,
529 kill_yank_rotate,
530 set_private_mode,
531 validate_autosuggestion,
532 // Validation
533 validate_command,
534 with_abbrs,
535 with_abbrs_mut,
536 AbbrPosition,
537 // Abbreviations
538 Abbreviation,
539 AbbreviationSet,
540 // Autosuggestions
541 Autosuggestion,
542 HighlightRole,
543 HighlightSpec,
544 KillRing,
545 ValidationStatus,
546};
547pub use tokens::lextok;
548pub use vm_helper::ShellExecutor;
549
550// ── Stryke integration hook ──
551// The fat binary registers a handler for @ prefix dispatch.
552// The thin binary leaves this as None — @ is treated as a normal character.
553
554use std::sync::OnceLock;
555
556type StrykeHandler = Box<dyn Fn(&str) -> i32 + Send + Sync>;
557static STRYKE_HANDLER: OnceLock<StrykeHandler> = OnceLock::new();
558
559/// Register a handler for @ prefix lines (fat binary sets this to stryke::run).
560pub fn set_stryke_handler<F>(f: F)
561where
562 F: Fn(&str) -> i32 + Send + Sync + 'static,
563{
564 let _ = STRYKE_HANDLER.set(Box::new(f));
565}
566
567/// Try to dispatch a line starting with @ to stryke.
568/// Returns Some(exit_code) if handled, None if no handler registered.
569pub fn try_stryke_dispatch(code: &str) -> Option<i32> {
570 STRYKE_HANDLER.get().map(|f| f(code))
571}
572
573/// Whether a stryke handler is registered (fat build). Read-only probe for
574/// the p10k `stryke` segment; unlike [`try_stryke_dispatch`] it runs nothing.
575pub(crate) fn stryke_handler_registered() -> bool {
576 STRYKE_HANDLER.get().is_some()
577}
578
579/// Run an interactive `@ <code>` line through the registered stryke handler.
580///
581/// Called by `inputline` on each line it reads, before the lexer sees it:
582/// stryke syntax is not shell syntax (`1:10 |> sum` is a pipe into a
583/// redirect, `$_` would be expanded), so it cannot be parsed as a command
584/// named `@`. Only the first line of an interactive command is considered,
585/// so a `@` inside a continuation (heredoc body, open quote) stays shell
586/// input. On dispatch `$?` takes the handler's status, the line is queued for
587/// history (`stryke_history_flush`) unless HIST_IGNORE_SPACE drops it, and
588/// the lexer is handed an empty line. Without a handler — the
589/// thin binary — the line is returned untouched.
590pub(crate) fn stryke_line(line: String) -> String {
591 use crate::ported::zsh_h::{isset, HISTIGNORESPACE, SHINSTDIN};
592 use std::sync::atomic::Ordering;
593
594 if !(crate::ported::zsh_h::interact()
595 && isset(SHINSTDIN)
596 && crate::ported::lex::LEX_ISFIRSTLN.with(|c| c.get()))
597 {
598 return line;
599 }
600 let text = line.trim_end_matches('\n');
601 let Some(code) = text.trim_start().strip_prefix('@') else {
602 return line;
603 };
604 let code = code.trim();
605 if code.is_empty() {
606 return line;
607 }
608 let Some(status) = try_stryke_dispatch(code) else {
609 return line;
610 };
611 crate::ported::builtin::LASTVAL.store(status, Ordering::SeqCst);
612 if !(text.starts_with(' ') && isset(HISTIGNORESPACE)) {
613 *STRYKE_HISTORY.lock().unwrap() = Some(text.to_string());
614 }
615 "\n".to_string()
616}
617
618/// A dispatched `@` line waiting to be recorded in history.
619static STRYKE_HISTORY: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
620
621/// Record the last dispatched `@` line in history, the way `print -s` does.
622///
623/// Called by `r#loop` between the `hend` that closed the `@` line's (empty)
624/// event and the `hbegin` that opens the next one. Recording earlier, from
625/// inside `inputline`, takes the event number `hbegin` already gave the
626/// current line, and the duplicate number sends `fc -l` round the ring forever.
627pub(crate) fn stryke_history_flush() {
628 let Some(text) = STRYKE_HISTORY.lock().unwrap().take() else {
629 return;
630 };
631 let event_id = crate::ported::hist::prepnexthistent();
632 crate::ported::hashtable::addhistnode(&text, event_id as i32);
633 let ent = crate::ported::hist::make_histent(event_id, text);
634 if let Ok(mut ring) = crate::ported::hist::hist_ring.lock() {
635 ring.insert(0, ent);
636 crate::ported::hist::histlinect.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
637 }
638}
639
640/// Register a native command contributed by the linking binary.
641///
642/// Convenience re-spelling of [`native_cmds::register`] at the crate root, so
643/// a fat binary's `main` reads as one call per runtime:
644///
645/// ```ignore
646/// zsh::register_native_command("git", |argv| zvcs::run_argv(argv));
647/// ```
648///
649/// The name then dispatches in-process — `whence -w git` says `builtin`,
650/// `${+builtins[git]}` is 1, `builtin git` reaches it, a user `git()` function
651/// still shadows it, and `command git` still runs the one on `PATH`.
652pub fn register_native_command<F>(name: &str, f: F)
653where
654 F: Fn(&[String]) -> i32 + Send + Sync + 'static,
655{
656 native_cmds::register(name, f);
657}