Modal editor.
Reusable engine.
A vim-style editor in Rust — and the engine that powers it.
Multi-buffer TUI editor with fuzzy file/buffer/grep pickers,
lazygit-adjacent <leader>g git surface (status,
log, branches, file history, stash, tags, remotes), tree-sitter
highlighting via runtime-loaded grammars (hjkl-bonsai 0.7), the new
backend-agnostic hjkl-theme +
hjkl-theme-tui stack, marks / macros / dot-repeat /
@: last-ex repeat, and focus-regain disk auto-reload.
Same vim FSM (hjkl-vim) drives sqeel, buffr, inbx.
1 // vim FSM driving a rope buffer. 2 use hjkl_engine::{Editor, Host}; 3 use hjkl_buffer::Rope; 4 5 fn main() -> io::Result<()> { 6 let mut ed = Editor::<Rope, _>::new(TuiHost); 7 ed.open("src/main.rs")?; 8 9 while let Some(key) = ed.poll()? { 10 ed.feed(key); 11 } 12 Ok(()) 13 }
why
One vim, many surfaces.
One FSM, many surfaces
Same FSM that drives sqeel, hjkl, and inbx drives the standalone hjkl editor.
No I/O in the core
Bring your own renderer, your own clipboard, your own filesystem. The engine just transitions states.
Pure Rust, host-agnostic
The engine doesn't pin a renderer, clipboard, or filesystem. Same crate drives ratatui TUIs, GPU-composited overlays, and egui windows.
Trait-driven
Buffers, clipboards, search providers — all traits. Swap implementations without touching the FSM.
Property-tested
Rope invariants hold under any edit sequence. Vim grammar regression-suite covers motions, operators, text objects, and ex commands.
MIT, all of it
Every crate, every adapter. Fork it, vendor it, ship it. No catch.
crates
One binary. Twelve ecosystem crates. Each in its own repo.
The standalone editor. cargo install hjkl —
multi-buffer, pickers, tree-sitter, smart indent,
.editorconfig.
Pure controller: cursor, motions, operators, registers, marks,
macros. No I/O deps. The vim FSM was extracted to
hjkl-vim in 0.7 — the engine just executes primitives
now.
The vim grammar reducer. Pending-state machine for chords
(m<x>, q<x>,
@<x>, 5dd, "a5dd),
count accumulator, dot-repeat, @: last-ex replay.
hjkl_vim::handle_key is the canonical FSM entry
point.
Rope-backed text buffer with cursor, edits, and undo tree. O(log n) inserts and deletes.
Engine + buffer + registers + ex commands wired together. The "ready-to-host" surface.
Runtime grammar loader. Clones, compiles, and caches
tree-sitter-* grammars on demand. Bare and
@-prefixed capture names both resolve;
lua-match? / not-lua-match? predicates
built in. Comment-marker overlay (TODO/FIXME/NOTE/WARN).
Re-exports hjkl_theme::StyleSpec — zero grammars
baked in.
Backend-agnostic theme schema. TOML parse, palette interning,
tree-sitter capture fallback chain. StyleSpec shared
between bonsai, hjkl, and any downstream renderer.
ToRatatui extension trait — converts
hjkl_theme::Color / Modifiers /
StyleSpec into ratatui types. Drop into any TUI.
Fuzzy picker subsystem. Picker,
PickerLogic, FileSource,
RgSource, scorer. Reusable across hosts.
Single-line form input built on the engine. Powers
: / / prompts and picker query lines.
Shared TOML config loader. XDG path resolution, span-aware parse
errors, opt-in Validate hook, layered defaults + user
overrides.
System clipboard adapter. Bridges "+ /
"*
registers to the OS pasteboard.
Ratatui Style adapters,
KeyEvent conversions, and a shared braille spinner.
Drop into any TUI.
install
Prebuilt binaries on every tagged release.
macOS via Homebrew tap, Arch via AUR, or grab a prebuilt binary. Linux (x86_64, aarch64) — .tar.gz / .deb / .rpm / .apk / musl. macOS (Apple Silicon, Intel) — .tar.gz. Windows (x64) — .zip. SHA-256 sidecars on every artifact.
$ brew install kryptic-sh/tap/hjkl $ hjkl
$ yay -S hjkl-bin $ paru -S hjkl-bin
# download .apk from releases page, then: $ apk add --allow-untrusted hjkl-*.apk $ hjkl
# crates.io $ cargo install hjkl # or build from the repo $ git clone https://github.com/kryptic-sh/hjkl $ cd hjkl $ cargo build --release