Files
viexp/docs/research/plugin-architecture.md

4.2 KiB

type, status, confidence, created, question, tags
type status confidence created question tags
research analyzed high 2026-10-17 How should viexp's file-type modules and plugins be designed so Linux users can disable, replace and extend them easily?
research
plugins
architecture
lua
wasm

Extension architecture research

Mechanism comparison

Mechanism Safety Ease for users Host complexity Hot reload Proof
External process contract (argv/env/stdout) low (= shell) highest — any language very low free nnn plugins, lf previewer, ranger scope.sh
Declarative rules (TOML / rifle / mailcap) high (data, not code) high — one-liners low free rifle.conf, yazi [plugin] tables
In-process Lua (mlua) medium — sandboxes leak (2025 CVE); Luau is the hardened dialect medium medium-high yes yazi (mlua 0.12, vendored Lua 5.5), wezterm, neovim
Rhai high low (niche language) low-medium yes niche
JS (deno_core/V8) medium-high medium high (~30 MB) yes Deno
WASM (wasmtime/extism) highest lowest (compile a component) high (WIT churn) yes zellij
Host-language plugins (Python) lowest (monkeypatching) high for pythonistas medium, fragile poor ranger

Case-study lessons

  • ranger: the script pipeline (scope.sh + rifle) carried ~90% of real customization; the Python layer exists because previews/commands need state. Three disjoint mechanisms = confusion.
  • nnn: cleanest "process as API": $1=file, $2=cwd, $3=picker; context in env (NNN_FIFO hover stream); control back via NNN_PIPE with tiny opcodes (1c/etc = cd ctx 1).
  • lf: minimum viable preview contract = (path, w, h, x, y) + cleaner hook + exit-code caching semantics. Document SIGPIPE/exit codes or users suffer.
  • yazi: built-ins are preset plugins routed through the same [plugin] TOML tables users edit (prepend_*/append_* merge) — one dispatch model for built-in and user code. mlua with vendored Lua so users install nothing. Still BETA → in-process scripting is the most expensive layer to stabilize.
  • neovim: Lua won because the host API is rich, stable and uniform — half-exposed APIs are worse than external scripts.
  • zellij: WASM gives real sandboxing but locks architecture (single-threaded synchronous plugins; WASIp2 migration "a significant undertaking"). Not worth it for v1.
  • kakoune: disabled_hooks regex = cheap runtime kill-switch worth copying.

Cargo feature flags = compile-time layer, not the primary switch

Features (pdf, video, … default-on) trim binary size and C deps, but feature unification breaks packagers, combinations explode, and removing a default feature is a breaking change. Runtime enabled = false in modules config must remain the real disable mechanism; every #[cfg]-gated module should degrade to a TOML rule pointing at an external tool.

  • v0.1 — declarative core: every file-type module = rule in a TOML registry mapping mime/glob → previewer/opener, with per-module enabled switch and prepend_/append_ merge. Built-ins already dispatch through this table → replacing/reordering needs no recompile.
  • v0.2 — external process protocol: freeze the script contract — argv = (path, width, height, x, y), env context, FIFO/socket write-back, cleaner hook. Ship bash + Python reference plugins. This is where 90% of Linux-user extensibility lives.
  • v1.0+ — embedded Lua (mlua, vendored): only for what scripts cannot do — stateful, in-process previews/fetchers — registered through the same tables. Evaluate Luau if sandboxing becomes a requirement. WASM explicitly rejected for v1.

Outcome

Adopted as decisions/ADR-002-extension-architecture and decisions/ADR-003-file-type-modules.

Sources

  • yazi docs (plugins overview, configuration), yazi Cargo.tomls (mlua 0.12 lua55 vendored)
  • nnn plugins README; lf doc.md; rifle(1); RFC 1524 (mailcap)
  • zellij plugin docs + discussion #4139; wezterm config docs; neovim lua.txt; kakoune FAQ
  • mlua/rhai/gluon repos; deno blog; Lua sandbox-bypass CVE coverage (2025)
  • Cargo book: features.md