Anvil is a Linux-first terminal editor. The project targets a reusable Rust kernel, a per-session Go policy gateway, and a capability-scoped plugin host. The official standard edition is one composition; third-party distributions can compose the kernel with their own UI and trusted Rust code. Runtime plugins remain untrusted and sandboxed.
Status: 0.4.5 released; 0.4.x feature line active.
Release model: 0.3.0 → 0.4.0 maintenance closed.
Development uses Rust from rust-toolchain.toml and Go 1.26.10 or newer;
CI installs the exact Go version from the root go.mod. The ratatui editor now
includes insert
mode (i/a/A/o/O), multi-cursor selection (s/Alt-s/C),
:w/:wa/:wq/:w file/:q!, an unsaved-change guard, undo/redo,
x/dd/yy/p, w/b/e word motions with
dw/db/de/yw/yb/ye, literal :s/:%s substitution, / search
with n/N, multi-buffer switching, vertical splits with independent pane cursors,
horizontal scrolling, and dirty-only redraws, insert-mode Tab indentation, a workspace file tree, and a fuzzy file
finder. Rust, TOML, Go, Bash, Python, YAML, JSON/JSONC, Markdown, Makefile,
and Dockerfile use Tree-sitter highlighting on a debounced background thread,
capped at 256 KiB so large files keep plain-text latency.
Trusted language servers (rust-analyzer, gopls, pyright/pylsp,
typescript-language-server/vtsls, clangd, jdtls, lua-language-server,
vscode-json-language-server, yaml-language-server, bash-language-server) run
without the plugin sandbox; diagnostics, completion (Ctrl+Space), definition (gd),
hover (K), rename (Space r), code actions/quick fixes (Space a),
formatting (Space F), document symbols (Space s), signature help, and
workspace symbols (Space S) use canonicalized LSP UTF-16 positions. Rename, code-action, and formatting edits go through a shared
WorkspaceEdit engine; unopened files become background buffers, and :wa
saves every modified buffer without silently writing files during an edit.
Supported files start their language server automatically; each
(tool, language_id) keeps its own worker while the session is open.
Language servers are not bundled; install them on PATH.
- TypeScript support requires a project-local compiler: run
npm install -D typescript(orpnpm add -D typescript) in the project. - C/C++ support works best with a compilation database:
bear -- make, or configure CMake with-DCMAKE_EXPORT_COMPILE_COMMANDS=ON. Without it, clangd may report unresolved includes.
The plugin-host path has an empty sandboxed host with token-FD delivery,
close_range, no_new_privs, Landlock read/no-exec rules, native-ABI seccomp
with AF_UNIX-only sockets, cgroup v2 limits with OOM classification, framed
sys.ping/sys.exec dispatch, and a TUI debug PluginSession
(:plugin-start/:plugin-exec/:plugin-stop/:plugin-oom). Local plugin
manifests are discovered from child directories of ANVIL_PLUGIN_DIR.
onLanguage and onWorkspaceContains events start matching sandboxed hosts
with no grants. onStartup requires explicit opt-in via
ANVIL_PLUGIN_ALLOW_STARTUP=1; workspace activation is disabled with
ANVIL_PLUGIN_DISABLE_WORKSPACE_ACTIVATION=1. onCommand dispatch and the
supervisor lifecycle are still pending. This is not a VM or public plugin API.
Phase 0 still consists of the anvil-api contracts, anvil-core PieceTable,
anvil-editor snapshots, anvil-ipc framing/JSON-RPC/Unix transport, and the
Go gateway with openat2, Landlock/seccomp exec sandboxing, SSRF-constrained
fetch, and a synchronous Rust gateway client. The TUI discovers the gateway
beside the binary or via ANVIL_GATEWAY.
anvil [OPTIONS] [FILE]
-w, --workspace <DIR> Workspace directory; --workspace=DIR is also accepted
-h, --help Show help and exit
-V, --version Show the version and exit
-- Treat the next argument as FILE
FILE must be inside the selected workspace. Use -- before a file name that
starts with a dash. Launching without FILE shows the start dashboard; the first
key dismisses it.
- Installing Anvil
- Project charter
- Known issues
- High-frequency command architecture plan
- 0.3.0 → 0.4.0: Polish & Specification
- 0.3.0 → 0.4.0 / Phase 0.7 plan
- 0.4.x feature plan
- Keymap configuration
- Plugin-host isolation ADR
- Threat model
- Architecture
- Current and future architecture maps
- Deployment model
- Ratatui TUI Elm loop plan
- Performance baseline
- Go gateway benchmarks
- File operation benchmarks
- Text model benchmark
- Editor benchmarks
- Line index stress
- TUI edit-loop latency
- Phase 0.5 plan
- Phase 0.6 plan
- Upstream dependencies
- JSON-RPC and token contract
- Kernel/distribution boundary
- Selection-first interaction
- Text-model snapshot gate
- Cursor and snapshot ownership
- Workspace file I/O protocol
- Workspace tree watching
- Terminal keyboard enhancement
- Architecture decision records
- Governance
- Maintainers
- Release process
- Support
- Roadmap
- Security policy
- Contributing
- Code of conduct
- Changelog
-
Plugins are untrusted by default and run in a separate plugin-host process, never in the editor process.
-
The Rust v1 stack uses
ratatui+crosstermdirectly, with no Tokio or Ferriso runtime dependency. Ferriso is reference material. -
Lua plugins use
mluawith Luau; TS usesrquickjs(QuickJS-NG), not V8. -
The Go gateway is a per-session policy gateway, not the plugin sandbox. One gateway process owns one session socket; closing the server removes the socket.
-
sys.execroutes throughanvil-exec-runner. The Phase 0.6 gateway uses a one-shot 32-byte bootstrap token: the gateway holds only its SHA-256 verifier, the raw token appears only insession.hello, and a second handshake is rejected.SO_PEERCREDUID plus the token are both required.-allow-execis currently per gateway instance; per-session permission negotiation remains open. -
The public API starts small and is defined from Rust types plus generated JSON Schema and TypeScript/Lua stubs. Smithy is deferred until the API justifies its code-generation cost.
-
The text model is
PieceTable, exposed asanvil-core::TextDocument; Rope and Gap Buffer remain benchmark-only. See ADR-0009. -
Syntax highlighting is derived state, not stored in
PieceTable. Parsing runs on a background worker after a 50 ms debounce; stale spans remain visible until the replacement arrives. Files over 256 KiB are intentionally plain text. -
The empty plugin host is a real process/sandbox boundary, but not yet a plugin runtime. cgroup limits require a delegated cgroup2 root; without one the host fails closed rather than pretending limits are active.
-
The native editor/API interaction model is selection-first. The current TUI exposes that core through a Vim-like keymap implemented in Rust. Normal and operator-pending bindings can be remapped through keymap.toml; Lua/TS keymap plugins are not implemented yet.
-
Full untrusted-plugin support targets Linux 5.13+ (Landlock). Linux 5.6-5.12 may run the editor with trusted plugins only.
Licensed under either of Apache-2.0 or MIT, at your option. See LICENSE-APACHE and LICENSE-MIT.