MIT Rust

Modal editor.
Reusable engine.

A vim-style editor in Rust — and the engine that powers it.

Multi-buffer TUI editor with window splits and tabs, a full LSP client (diagnostics, goto, hover, completion, code actions, rename, format), fuzzy file/buffer/grep pickers, a lazygit-adjacent <leader>g git surface, tree-sitter highlighting via runtime-loaded grammars, a persistent branching undo tree, and six bundled themes. Same vim FSM (hjkl-vim) drives sqeel, buffr, and inbx.

hjkl — ~/code/hjkl
main.rs
  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 }
NORMAL main.rs 10:21

why

One vim, many surfaces.

01 — REUSE

One FSM, many surfaces

Same FSM that drives sqeel, buffr, and inbx drives the standalone hjkl editor.

02 — PURE

No I/O in the core

Bring your own renderer, your own clipboard, your own filesystem. The engine just transitions states.

03 — PORTABLE

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.

04 — TYPED

Trait-driven

Buffers, clipboards, search providers — all traits. Swap implementations without touching the FSM.

05 — TESTED

Property-tested

Rope invariants hold under any edit sequence. Vim grammar regression-suite covers motions, operators, text objects, and ex commands.

06 — LSP

A real LSP client

Per-language server lifecycle, full text-sync, diagnostics, goto, hover, completion, code actions, rename, and format — in hjkl-lsp, usable from any host.

07 — UNDO

Branching undo, on disk

Undo history is a tree, not a stack: editing after an undo keeps the old branch, g-/g+ traverse by change number, and :undolist shows the tips. Delta-encoded into an undofile, so undo/redo and your position survive closing the file — a crash :recover restores the tree too.

08 — MEMORY

It remembers where you were

Reopening a file restores the last cursor position on that buffer (the last-moved window wins across splits), clamped to the file. Config: restore_cursor.

09 — THEMES

Six bundled, or bring your own

tokyonight (default), catppuccin, gruvbox, nord, dracula, onedark via :colorscheme — plus the built-in dark / light. Each switches UI chrome and syntax together, and themes drive the terminal background with a theme.transparent override.

10 — OPTIONS

:set that sticks

Every global :set option has a matching key under [options] in config.toml, named exactly as :set names it — and a :set from the command bar is written straight back to your config file, preserving comments, key order and quoting. Buffer-local options stay session-scoped. A deliberate divergence from vim, where :set is transient.

11 — OPEN

MIT, all of it

Every crate, every adapter. Fork it, vendor it, ship it. No catch.

crates

One monorepo. Fifty-plus crates, each published on its own.

Everything lives in kryptic-sh/hjkl under crates/<name> and ships independently to crates.io at a shared version. Most subsystems come as a pair: a renderer-agnostic core crate plus a -tui adapter, so a GUI host can take the model and leave the ratatui rendering behind. Add one line and go: cargo add hjkl-editor.

hjkl
binary · TUI app

The standalone editor. cargo install hjkl — multi-buffer, splits, tabs, pickers, LSP, tree-sitter, smart indent, .editorconfig.

hjkl-engine
core · controller

Pure controller: cursor, motions, operators, registers, marks, macros. No I/O deps. #![deny(missing_docs)] — new public API needs rustdoc.

hjkl-vim
core · vim FSM

The vim grammar reducer. Pending-state machine for chords (m<x>, q<x>, @<x>, 5dd, "a5dd), count accumulator, dot-repeat, @: last-ex replay. Vocabulary types split out into hjkl-vim-types.

hjkl-buffer
data · rope

Rope-backed text buffer with cursor, edits, folds, search, and the persistent branching undo tree. O(log n) inserts and deletes, with criterion budgets CI fails on.

hjkl-editor
std · front door

Facade crate: re-exports engine + buffer + spec types wired together. The "ready-to-host" surface — the one most consumers depend on.

hjkl-lsp
std · language servers

Per-language server lifecycle, full text-sync, diagnostics, goto, hover, completion, code actions, rename, format. Pairs with hjkl-completion and hjkl-hover.

hjkl-bonsai
std · highlighting

Runtime grammar loader — clones, compiles, and caches tree-sitter-* grammars on demand. Bare and @-prefixed captures both resolve; lua-match? predicates built in. Zero grammars baked in.

hjkl-syntax
std · pipeline

SyntaxWorker background thread, render cache, and viewport-scoped highlight passes — so a big file doesn't stall the keystroke loop.

hjkl-theme
core · theming

Backend-agnostic theme schema: TOML parse, palette interning, tree-sitter capture fallback chain. StyleSpec is shared across bonsai, hjkl, gpur, and any downstream renderer; hjkl-theme-tui converts it to ratatui types.

hjkl-picker
std · UI

Fuzzy picker subsystem — file walk, grep, custom sources, and a consumer-agnostic PreviewHighlighter trait. Scoring lives in hjkl-fuzzy.

hjkl-layout
core · windows

Renderer-agnostic window/split layout machinery. Tabs live in hjkl-tabs, the statusline model in hjkl-statusline, jump lists in hjkl-quickfix.

hjkl-form
std · UI

Vim-modal forms with the full vim grammar inside every text field. Powers : / / prompts and picker query lines.

hjkl-anvil
std · tooling

Mason-style installer for LSP servers and developer tools. hjkl-mangler is the formatter side — rustfmt, prettier, gofmt, ruff, stylua, shfmt, taplo, and more.

hjkl-clipboard
std · adapter

In-house clipboard for the ecosystem — sync + async, text / HTML / RTF / image/png, with Wayland, X11, macOS, Windows, and OSC 52 (SSH) backends. Bridges "+ / "* to the OS.

hjkl-config
std · loader

Shared TOML config loader: XDG paths (via hjkl-xdg), span-aware parse errors, opt-in Validate hook, deep-merged defaults + user overrides. Unknown keys are an error.

hjkl-compat-oracle
dev · conformance

Headless neovim diff harness. Runs a keystroke stream through both real vim and hjkl and fails on divergence — the vim-compatibility regression net.

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.

hjkl — macOS (Homebrew)
$ brew install kryptic-sh/tap/hjkl
$ hjkl
hjkl — Arch Linux (AUR)
$ yay -S hjkl-bin
$ paru -S hjkl-bin
hjkl — Alpine Linux
# download .apk from releases page, then:
$ apk add --allow-untrusted hjkl-*.apk
$ hjkl
hjkl — from source
# crates.io
$ cargo install hjkl

# or build from the repo
$ git clone https://github.com/kryptic-sh/hjkl
$ cd hjkl
$ cargo build --release