zshrs 0.13.46

The first JIT-compiled Unix shell — bytecode VM, Cranelift JIT, worker pool, AOP intercept, Rkyv caching
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# Native (Rust) Plugins

zshrs hosts plugins written in a native compiled language (Rust), loaded
at runtime with no recompile of the shell. A plugin is an ordinary
`cdylib` the shell `dlopen`s through a stable, versioned C ABI. This is
unique to zshrs — see the comparison below.

## Rust plugins vs zsh plugins

Shells already have two extension models, and zshrs adds a third. All
still work in zshrs (it runs zsh script plugins and its ported modules as
before); the Rust plugin path is **additive**.

Native runtime plugin loading itself is **not** new — bash has
`enable -f file.so name` (loadable builtins, since ~1996) and zsh has
`zmodload` for C modules. What is new is *how* zshrs exposes it:

|                     | script plugin (`.zsh`)   | bash `enable -f` / zsh `zmodload` native builtin | **zshrs Rust plugin** |
| ------------------- | ------------------------ | ------------------------------ | --------------------- |
| Language            | shell script (interpreted) | C                            | Rust (any native lang via the C ABI) |
| Artifact            | `.zsh` text file         | `.so` built in the shell's tree | `.dylib` / `.so` `cdylib` |
| Build against       | nothing — it's sourced   | the shell's **private** internal headers (`builtins.h`/`shell.h` for bash; the `.mdd` + `Src/Modules/` build for zsh) | the published `znative` crate — `cargo add znative` |
| ABI stability       | n/a                      | **none** — bound to the exact shell build; no version magic in either | stable, versioned `ABI_VERSION`; mismatches refused at load |
| Distribution        | a file to source         | must track and rebuild against each shell release | one crates.io SDK crate, independent of the shell's source |
| Load / unload       | `source` (re-parse every startup) | `enable -f` / `-d` (bash); `zmodload` (zsh) — `dlsym` internal symbols | `zmodload -R` / `-uR` — `dlsym` one symbol, `znative_init` |
| Execution           | interpreted each call     | native machine code            | native machine code   |
| Registers           | functions, aliases, options, ZLE widgets, fpath completions | builtins/params/hooks via the shell's internal API | builtins + a curated host API (`print`/`eval`/`getvar`/`setvar`) |
| Third-party viable  | yes (oh-my-zsh, zinit)   | **rare in practice** — needs the shell source tree and tracks its internals, so ~only the shell's own bundled modules exist | **yes** — depend on one crates.io crate, no zshrs source needed |
| Type safety         | none                     | C (unchecked against shell internals) | Rust type system, checked against the ABI crate |

The distinction that matters is **not** "loads native code" — bash and zsh
both do. It is that bash's and zsh's native interfaces are their
**internal** C APIs: you compile against the shell's private headers, in
its build tree, with no stable ABI and no version gate, so a plugin is
welded to one shell build and can crash a mismatched one. That is why
neither ecosystem has meaningful third-party native plugins — the native
modules that exist are almost all the ones the shell ships itself.

zshrs instead exposes a **stable, published, versioned C ABI** — the
`znative` crate on crates.io, gated by `ABI_VERSION`: a third party
runs `cargo add znative`, writes a handler, ships a `cdylib`, and it
loads into any compatible zshrs — native speed, no shell source tree, no
recompile, and version-mismatched plugins refused rather than crashing.
Entry 17 in the filtered register, [`INVENTIONS.md`](INVENTIONS.md).

First shell to make its native-plugin interface an independently-published,
versioned ABI package instead of its own build-tree internals.

## Architecture

```
┌────────────────────────┐        ┌──────────────────────────┐
│ zshrs (host)           │        │ libfoo.dylib (plugin)    │
│                        │        │                          │
│ zmodload -R libfoo ────┼─dlopen─▶ znative_init(host)  │
│                        │        │   host.register_builtin  │
│ plugin_host registry   ◀────────┤   ("foo", handler)       │
│                        │        │                          │
│ execute_external_bg    │        │                          │
│   └ plugin_host::      │        │                          │
│      dispatch("foo") ──┼─call───▶ handler(host,argc,argv)  │
└────────────────────────┘        └──────────────────────────┘
        stable C ABI: znative crate (#[repr(C)])
```

- **Host loader**: `src/extensions/plugin_host.rs` — `dlopen` via
  `libloading`, an in-process command registry, and the host-callback
  table plugins call back through.
- **Shared ABI**: the [`znative`]../znative/ crate. Both the
  host and every plugin depend on it, so both agree on the exact
  `#[repr(C)]` struct layout. `ABI_VERSION` gates loading: a plugin whose
  version does not match the host is refused (a wrong layout would be
  undefined behaviour).
- **Command resolution**: fusevm compiles names it does not recognise as
  builtins into *external* execution. A plugin command is unknown at
  compile time, so it arrives at `execute_external_bg`
  (`src/vm_helper.rs`), which consults `plugin_host::dispatch` **before**
  spawning a process — the analog of zsh's `resolvebuiltin` slot for
  `zmodload -ab` autoloaded builtins. Plugin commands therefore resolve
  after real builtins and shell functions, before PATH lookup.

## Writing a plugin

`Cargo.toml`:

```toml
[lib]
crate-type = ["cdylib"]

[dependencies]
znative = "0.12"
```

`src/lib.rs`:

```rust
use znative::{declare_plugin, Args, Host};
use std::os::raw::c_int;

fn rhello(host: &Host, args: &Args) -> c_int {
    let who = if args.rest().is_empty() {
        "world".into()
    } else {
        args.rest().join(", ")
    };
    host.print(&format!("hello, {who}\n"));
    0
}

declare_plugin! {
    name: "hello",
    version: "0.1.0",
    builtins: {
        "rhello" => rhello,
    },
}
```

`cargo build` produces `libhello.dylib` (macOS) or `libhello.so` (Linux).

## Host API

Inside a handler, `Host` is the shell's callback table:

| Method                          | Purpose                                   |
| ------------------------------- | ----------------------------------------- |
| `host.print(s)`                 | write to the shell's stdout               |
| `host.eval(code) -> i32`        | run shell code, return its exit status    |
| `host.getvar(name) -> Option`   | read a shell scalar parameter             |
| `host.setvar(name, value)`      | set a shell scalar parameter              |
| `host.getfunction(name) -> Option` | read a shell function's deparsed body (`${functions[name]}`) — ABI v3 |
| `host.addfunction(name, body) -> bool` | define/replace a shell function (`functions[name]=body`) — ABI v3 |
| `host.register_builtin(n, f)`   | register a command handler dynamically    |
| `host.add_match(word)`          | emit one completion candidate (see below) |
| `host.install_completion(cmd, gen)` | wire a native completion into compsys  |
| `host.register_compfn(name, f)` | override a compsys `_fn` with a native handler — ABI v4 |
| `host.comp_dispatch(name, args) -> i32` | invoke a compsys `_fn` (`_alternative`, …) — ABI v4 |
| `host.empty_command_hash()`     | empty the command-name hash (`cmdnamtab`) — ABI v4 |

`Args` decodes `argv`: `.name()` is `argv[0]`, `.rest()` the arguments,
`.to_vec()` the whole vector.

`getfunction`/`addfunction` are the only structured access to shell
*functions* (`getvar` is scalars only, `eval` returns just a status).
Because `addfunction` then `getfunction` round-trips a body through the
shell's own parser and pretty-printer, the pair doubles as
deparse-as-a-service — define a temp function from arbitrary source, read
back its normalized form (one statement per line, tab-indented). That is
exactly how history-formatting plugins (e.g. `zsh-hist`) auto-format a
command line.

A handler returns the command's exit status (`0` = success), exactly like
a shell builtin.

## Native completions

A plugin can provide a **completion written in Rust** for a command. Add a
`completions:` section to `declare_plugin!`, mapping the command to a
*generator* function. The generator receives `$CURRENT` (1-based index of
the word being completed) followed by every word on the line, and emits
candidates with `host.add_match`:

```rust
const NAMES: &[&str] = &["alice", "bob", "carol", "dave", "erin"];

fn greet_complete(host: &Host, args: &Args) -> c_int {
    let a = args.rest();                       // [CURRENT, word0, word1, ...]
    let current: usize = a.first().and_then(|s| s.parse().ok()).unwrap_or(0);
    let words = &a[1..];                        // words[0] == "greet"
    let prefix = current.checked_sub(1).and_then(|i| words.get(i))
        .map(String::as_str).unwrap_or("");
    for &c in NAMES {
        if c.starts_with(prefix) { host.add_match(c); }
    }
    0
}

declare_plugin! {
    name: "greet",
    version: "0.1.0",
    builtins:    { "greet" => greet },
    completions: { "greet" => greet_complete },
}
```

Then `greet <TAB>` offers `alice bob carol dave erin`, filtered by the
current prefix — all decided in Rust.

How it wires up: the macro registers the generator as a hidden builtin and
records a completion request. The host defers the `compdef` wiring until
the first time completion fires (a safe point in the completion pipeline;
doing it during plugin-init would run compsys glue too early). So **load
the plugin after `compinit`**. No leading-underscore is used for the
generator name — zsh treats `_*` command names as autoloadable completers,
which would shadow the builtin.

## Compsys function overrides (ABI v4)

A plugin can also **replace a compsys internal `_fn`** — e.g. `_command_names`
— with a native Rust handler, not just add a per-command completion. Add a
`compfns:` section to `declare_plugin!`, mapping the `_NAME` to a handler
(`fn(&Host, &Args) -> c_int`):

```rust
declare_plugin! {
    name: "command_names",
    version: "0.1.0",
    compfns: { "_command_names" => command_names },
}
```

The completion router resolves an override **before** the built-in Rust port
and before the autoloaded shell function, so the plugin's version wins.
This is how a user reproduces a *customized* completer (one whose behavior
differs from stock zsh — such as a framework's patched `_command_names`) at
native speed, instead of the built-in port silently running stock behavior.

The handler builds its tag/spec list in Rust, then delegates the heavy
lifting with `host.comp_dispatch("_alternative", &specs)`; `host.getvar`
reads `$PREFIX`/`$PATH`/etc., and `host.eval` runs any snippet needing shell
scope (e.g. a `local -A +h commands` shadow that must stay live across the
nested `_alternative`/`_path_commands` calls). See
[`examples/plugin-command-names/`](../examples/plugin-command-names/).

Overrides are opt-in per `zmodload -R`: without the plugin loaded, the
built-in port runs unchanged. Unload purges the override **before**
`dlclose`, like command registrations.

## Managing plugins

```bash
zmodload -R <path>...    # load each cdylib
zmodload -R              # list loaded plugins:  name  version  path
zmodload -uR <name>...   # unload each plugin by name
```

`-R` without `-A` is a zshrs extension. In C zsh `-R` removes a module
alias and is only meaningful alongside `-A`; `zmodload -A -R <name>` keeps
that behaviour, so no zsh parity is lost.

Loading a plugin whose name is already loaded is refused — unload first.
Unload purges the plugin's command registrations **before** `dlclose`, so
no live function pointer survives the library it lives in.

## Installing with znative

`zmodload -R` is the low-level primitive. For distribution, **znative** (zshrs's
package manager) installs a plugin straight from a GitHub repo — it clones,
`cargo build --release`s the cdylib, and `zmodload -R`s it. The full command,
source-spec, store-layout, and `znative.toml` reference is in
[**ZNATIVE.md**](ZNATIVE.md); this section covers the plugin-authoring side.

The one line a `.zshrc` needs per plugin is `znative load owner/repo` — it
installs on the first start and loads from the store (zero-network) after:

```bash
znative load MenkeTechnologies/zshrs-forgit      # native (Rust) plugin, self-installing
znative load MenkeTechnologies/zshrs-git-fuzzy
```

`znative add` (install without a `.zshrc` line), `znative list`, `znative remove` round
out the store — see [ZNATIVE.md](ZNATIVE.md) for all commands.

znative auto-detects a native plugin from a `Cargo.toml` with a `cdylib`
crate-type (ordinary `*.plugin.zsh` script repos install too, no metadata
needed). An optional `znative.toml` at the repo root supplies metadata and the
lib stem:

```toml
[plugin]
name = "forgit"
version = "0.1.0"
description = "forgit ported to a native zshrs plugin"

[native]
lib = "forgit"        # produces libforgit.{dylib,so}
```

A plugin published this way depends on the SDK as a git dependency so it
builds standalone: `znative = { git = "https://github.com/MenkeTechnologies/zshrs" }`.

### Script (`.zsh`) plugins

znative installs ordinary zsh script plugins too — the ones that register ZLE
widgets, functions, and completions. These stay script (the native ABI
registers builtins and completions, not ZLE widgets), and znative loads them
by adding the repo to `$fpath` and sourcing its `*.plugin.zsh`:

```bash
znative add zdharma-continuum/history-search-multi-word   # Ctrl-R multi-word history search
```

`history-search-multi-word` is a good stress test: a ZLE widget with its
own forked syntax-highlighter, paged `POSTDISPLAY` output, live
`region_highlight`, and in-widget key reads. It installs, `znative load`s, binds
`^R`, and runs under zshrs's ZLE unchanged.

Beyond `owner/repo`, `znative add` also takes `github:owner/repo`, a `git+URL`
(optionally with an `@ref` tag/branch), and `path:DIR` for a local checkout —
see [ZNATIVE.md](ZNATIVE.md#sources) for all forms. A runnable `.zshrc` using znative is
at [`examples/zshrc`](../examples/zshrc); the one line a startup file needs is
`znative load`, which is zero-network (store + index only) and safe even before
anything is installed.

## ABI versioning

`ABI_VERSION` in the `znative` crate is bumped on any change to the
`HostApi` / `PluginInfo` / `BuiltinFn` layout or semantics. The host
refuses to load a mismatched plugin. Keep the crate's major/minor aligned
with your target zshrs release. The single exported symbol every plugin
must provide is `znative_init` (generated by `declare_plugin!`).

## Examples

Two runnable examples ship in `examples/`.

[`plugin-hello/`](../examples/plugin-hello/) — builtins, host API:

```bash
cargo build --manifest-path examples/plugin-hello/Cargo.toml
zshrs -c '
  zmodload -R examples/plugin-hello/target/debug/libhello.dylib
  rhello alice bob
  renv HOME
  renv MYVAR hi; echo $MYVAR
  zmodload -uR hello
'
```

[`plugin-complete/`](../examples/plugin-complete/) — a native Rust
completion for a `greet` command:

```bash
cargo build --manifest-path examples/plugin-complete/Cargo.toml
zshrs   # interactive
# % autoload -Uz compinit; compinit
# % zmodload -R examples/plugin-complete/target/debug/libgreet.dylib
# % greet <TAB>          → alice  bob  carol  dave  erin
# % greet --lang <TAB>   → rust  ruby  python  perl  go
```

[`plugin-kubectl/`](../examples/plugin-kubectl/) — a real-world completion:
`kubectl` completion that delegates to cobra's `kubectl __complete`, so it
tracks the installed kubectl version (subcommands, flags, live resources)
with no static tree to maintain. Published as:
`znative load MenkeTechnologies/zshrs-kubectl-completion`.

[`plugin-forgit/`](../examples/plugin-forgit/) — the **forgit** git+fzf
plugin ported command-for-command from zsh (`ga glo gd gcf …`). See
[PORTING_ZSH_PLUGIN.md](PORTING_ZSH_PLUGIN.md) for the full zsh→Rust
walkthrough. Published as a standalone repo:
`znative load MenkeTechnologies/zshrs-forgit`.

[`plugin-git-fuzzy/`](../examples/plugin-git-fuzzy/) — **git-fuzzy**'s
`status` command: a *self-reentrant* fzf UI (every preview/keybind
re-invokes a helper, plus a `--listen` live-reload watcher). Shows the shim
technique that lets fzf binds reach native builtins — see the "self-reentrant
fzf tools" section of the porting guide. Published as:
`znative load MenkeTechnologies/zshrs-git-fuzzy`.

[`plugin-zsh-z/`](../examples/plugin-zsh-z/) — **zsh-z**, the frecency
directory jumper (`z <partial>`), reimplemented in Rust: the `~/.z` datafile,
the frecency formula, aging, matching, and all `z` options. `cd` is delegated
to the shell (`host.eval`) so `$PWD`/hooks stay correct; a `chpwd` hook does
the recording. Published as: `znative load MenkeTechnologies/zshrs-zsh-z`.

[`plugin-git-repos/`](../examples/plugin-git-repos/) — **zsh-git-repo-cache**:
scan the filesystem for every git repo, cache it, and `fzf`-pick one to `cd`
into, with `--clean`/`--dirty` filters. The shell version does a sequential
`git status` per repo; the native version classifies **in parallel across
threads** — a real speedup. Published as: `znative load MenkeTechnologies/zshrs-git-repos`.

[`plugin-revolver/`](../examples/plugin-revolver/) — **revolver**, the
progress spinner (`revolver start/update/stop/demo`, 55 styles), ported from
zsh. Upstream forks a background process and coordinates through a
`$PPID`-keyed statefile; the native port keeps the animator as an in-process
**thread** and the running spinner in a `static` slot — no fork, no
statefile, since `start`/`update`/`stop` now share memory. Shows how a plugin
does concurrent/background work under a non-forking shell. Published as a
standalone repo: `znative load MenkeTechnologies/zshrs-revolver`.

[`plugin-fasd/`](../examples/plugin-fasd/) — **fasd**, frecency for **files
and directories** (`a s d f j v` + `fasd`), ported from the fasd shell script.
A `preexec` hook feeds every command's path arguments to the datafile; queries
match terms in order (last term in the basename) case-sensitive → insensitive
→ fuzzy, and score `Σrank * frecent(dx)`. Where `zsh-z` tracks only `cd`
targets, fasd tracks files too — the regex + fuzzy matcher and awk scoring are
reimplemented in Rust. Published as: `znative load MenkeTechnologies/zshrs-fasd`.

[`plugin-reveal/`](../examples/plugin-reveal/) — **reveal** (from gh_reveal):
open the current repo's GitHub page(s) in the browser. Picks the platform
opener, reads `git remote -v`, normalizes each SSH/HTTPS URL to
`host/user/repo` (dropping scheme, userinfo, port, `.git`), and opens
`https://<that>` — Heroku remotes open their dashboard + app URLs. Shows a
plugin that shells out (`git`, the OS opener) rather than reimplementing.
Published as: `znative load MenkeTechnologies/zshrs-reveal`.

## Porting an existing zsh plugin

If you have a zsh plugin (shell functions + aliases + completions) and want
it as a native plugin, [**PORTING_ZSH_PLUGIN.md**](PORTING_ZSH_PLUGIN.md)
is a step-by-step guide: the construct-by-construct mapping, the
list→fzf→act pattern, subprocess/tty handling, and what stays shell —
worked end-to-end on forgit.

## Safety notes

- The loaded `libloading::Library` is kept alive for the process (or until
  unload); its `Drop` is a `dlclose`.
- A plugin panicking across the C-ABI boundary is undefined behaviour.
  Handlers should catch their own panics or avoid panicking; the host does
  not unwind through the FFI boundary for you.
- Plugin builtins run synchronously in-process, even when backgrounded
  (`foo &`) — zshrs is non-forking and an in-process builtin has nothing
  to background.