Vim FSM + grammar. Pending-operator state, motions, text objects, registers. No I/O deps.
Vim engine.
Rope buffer. No I/O.
Modal editor primitives in Rust.
One vim implementation, many surfaces. TUI, GUI, browser — same FSM.
// drive a vim FSM. no I/O, no terminal, no allocator beyond `alloc`. use hjkl_engine::{Engine, Mode, Key}; use hjkl_buffer::Rope; let mut rope = Rope::from_str("hello, world"); let mut eng = Engine::new(); eng.feed(Key::Char('d')); // pending: delete eng.feed(Key::Char('w')); // motion: word match eng.commit() { Action::Delete(range) => rope.remove(range), _ => {} } assert_eq!(rope.to_string(), "world"); assert_eq!(eng.mode(), Mode::Normal);
crates
Four crates. Pick what you need.
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.
Ratatui Style adapters and
KeyEvent conversions. Drop into any TUI.
why
One vim, many surfaces.
Extracted from sqeel
Same FSM that drives sqeel will drive buffr and a future standalone hjkl binary.
No I/O in the core
Bring your own renderer, your own clipboard, your own filesystem. The engine just transitions states.
no_std + alloc
Runs anywhere Rust does. Embedded, WASM, kernel modules, your weird side project — all fair game.
Trait-driven
Buffers, clipboards, search providers — all traits. Swap implementations without touching the FSM.
Property-tested
Vim grammar fuzzed against reference behaviour. Rope invariants hold under any edit sequence.
MIT, all of it
Every crate, every adapter. Fork it, vendor it, ship it. No catch.
status
Pre-release. Names reserved.
Status: 0.0.0 placeholders on
crates.io. No public API yet.
Active extraction from sqeel. See MIGRATION.md for the full plan and design rationale. Watch github.com/kryptic-sh/hjkl for the first real release.