A high-performance terminal emulator for Emacs, powered by a Rust dynamic module with an Emacs Lisp display layer.
- High Performance: >100MB/s VT parse rate, <1us FFI call overhead, <16ms/frame rendering
- VTE Compliance: VT100/VT220 compatible with cursor movement, erase, scroll regions, insert/delete, tab stops
- SGR Attributes: Bold, italic, underline, blink, reverse, strikethrough, conceal, dim; 256-color and TrueColor
- Kitty Protocols: Kitty Graphics Protocol (APC), Kitty Keyboard Protocol
- OSC Support: OSC 7 (CWD), OSC 8 (hyperlinks), OSC 52 (clipboard), OSC 133 shell integration with FinalTerm/Ghostty extras (
aid=,duration=,err=, exit code on D-mark), with extras (aid, duration, err) rendered as left-margin status indicators and end-of-line annotations - Device Attributes: DA1, DA2, DA3 (
CSI = c→DCS ! | 00000000 ST) - Color Scheme Notifications: DEC private mode 2031 + DSR 996 (Contour/Ghostty extension), automatically synchronized to Emacs's current theme via
enable-theme-functions - In-band Resize Notifications: DEC private mode 2048 reports text-area size changes as
CSI 48 ; rows ; cols ; 0 ; 0 tsequences directly in the PTY stream — a robust modern alternative to SIGWINCH, compatible with foot/Ghostty/kitty/iTerm2/Contour - Window Size Queries: XTWINOPS
CSI 14/18/19 treport the text-area/screen size on demand (CSI 8 ; rows ; cols t); window-manipulation and position/title ops are ignored for security - Status String Queries: DECRQSS
DCS $ q ... STreports the current cursor style (DECSCUSR), scroll region (DECSTBM), and SGR rendition (round-trip-faithful) — e.g. so neovim can restore your cursor shape on exit - Desktop Notifications: OSC 9 (iTerm2) and OSC 777 notifications surface to Emacs and display via
notifications-notify(D-Bus, with echo-area fallback) — long-running TUIs can alert you when you're looking elsewhere - Sixel Graphics: Inline image display via Sixel protocol
- Unicode: Full CJK support, grapheme clusters, emoji (unicode-width)
- Multi-session: Multiple terminal sessions with independent state, auto-reaping of dead sessions
- Scrollback: Configurable scrollback buffer with efficient memory usage
- Emacs Integration: Native theme support, face-based rendering, prompt navigation (OSC 133)
- Emacs 29.4 or later
- Rust 1.84.0 or later (MSRV)
- Linux or macOS
- Nix (recommended — provides all other dependencies)
git clone https://github.com/takeokunn/kuro.git
cd kuro
nix run .#install # build release + copy to ~/.local/share/kuroOptionally add the takeokunn-kuro Cachix binary cache to avoid recompiling:
cachix use takeokunn-kuroBuild the Rust dynamic module with cargo and place it where Emacs can load it:
git clone https://github.com/takeokunn/kuro.git
cd kuro
cargo build --release --manifest-path rust-core/Cargo.toml
mkdir -p ~/.local/share/kuro
# The workspace target directory is at the repo root.
# Linux:
cp target/release/libkuro_core.so ~/.local/share/kuro/
# macOS:
cp target/release/libkuro_core.dylib ~/.local/share/kuro/After installing Kuro, you can also use the native module helpers below to build locally or fetch a prebuilt binary.
Use these commands after installing Kuro:
;; Fetch the prebuilt native module for your platform:
M-x kuro-module-download
;; Or compile from source via cargo (requires a Rust toolchain):
M-x kuro-module-build(require 'kuro)
(kuro-create "bash")| Key | Action |
|---|---|
C-c C-c |
Send interrupt (SIGINT) |
C-c C-z |
Send SIGSTOP |
C-c C-\ |
Send SIGQUIT |
C-c C-p |
Previous prompt (OSC 133) |
C-c C-n |
Next prompt (OSC 133) |
C-c C-t |
Toggle copy mode |
C-c C-SPC |
Toggle copy mode (alternative) |
C-c C-q |
Send next key directly (bypass exceptions) |
M-w |
Copy region and exit copy mode (copy mode only) |
These bindings forward to the PTY even if you run evil-mode, god-mode, meow, or a similar modal-editing package: Kuro installs its keymap at the same emulation-mode-map-alists precedence tier those packages use, and re-asserts its entry at the head of that list every time the input keymap is (re)installed — on initial setup, on every input-mode switch, and on every kuro-keymap-exceptions change — so a package that registers its own emulation-mode-map-alists entry later in the session (e.g. evil-mode enabled after Kuro) does not end up shadowing Kuro's keys either. This does not apply while copy mode is active, since copy mode intentionally hands control back to Emacs-native navigation. If you want a specific key to bypass PTY forwarding and reach your own bindings instead, add it to kuro-keymap-exceptions rather than relying on another package's precedence.
Kuro is feature-complete at v1.1.0. The Rust core passes 3220 tests (2678 unit + 542 integration) and the Emacs Lisp layer passes 4459 ERT tests. Clippy runs with -D warnings and 0 warnings. CI uses nix flake check on Linux and macOS; the Elisp checks run on Emacs 30 with binary caching via Cachix. The project includes 8 fuzz targets and 4 criterion benchmark suites.
Kuro uses the Remote Display Model -- all terminal state lives in Rust, Emacs is purely the display layer.
graph LR
A["Emacs Lisp<br/>(Display)"] <-->|"FFI (~100ns)"| B["Rust Core<br/>(Terminal)"]
B --> C["PTY / Shell"]
| Module | Responsibility |
|---|---|
parser/ |
VT100/CSI/OSC/DCS/Sixel/Kitty protocol parsing |
grid/ |
Terminal grid, cell storage, scrollback buffer |
pty/ |
POSIX PTY spawning and I/O |
types/ |
Domain types (Color, SgrAttributes, OscData) |
ffi/ |
Emacs module FFI bridge and session management |
28 modules including: kuro-module (FFI bridge), kuro-config, kuro-faces, kuro-renderer, kuro-renderer-pipeline, kuro-binary-decoder, kuro-input, kuro-stream, kuro-lifecycle, kuro-navigation (OSC 133 prompt navigation), kuro-poll-modes, kuro-typewriter, kuro-tui-mode, kuro-color-scheme (Emacs theme bridge to DEC 2031 / DSR 996), kuro-prompt-status (OSC 133 exit-status indicators and prompt extras annotations).
nix develop # Rust toolchain + Emacs + cargo-tarpaulin on PATHnix build # Release build → result/
nix run .#install # Build + install to ~/.local/share/kuro
nix run .#run # Build + install + launch Emacsnix flake check # All checks (Rust + ERT + byte-compile + package-lint + checkdoc + treefmt)
nix develop --command bash test/scripts/runners/run-e2e.sh # E2E tests (PTY — outside sandbox)
nix develop --command bash test/scripts/runners/vttest-compliance.sh # VTE compliancenix fmt # Format Rust + Nix files (treefmt: rustfmt + nixfmt-rfc-style)
nix run .#doc # Generate + open Rust API docs
nix run .#coverage # cargo-tarpaulin coverage (stdout)
nix run .#bench # Criterion benchmarks (nightly Rust)nix develop .#fuzz --command bash -c "
cd rust-core/fuzz
cargo fuzz run advance -- -max_total_time=30 -runs=1000
"The fuzz devShell provides nightly Rust + cargo-fuzz. Available targets: advance, kitty_params, apc_payload, decode_png, csi_sequence, utf8_input, sgr, insert_delete.
nix flake check runs all of the following in the Nix sandbox:
| Check | What it verifies |
|---|---|
kuro-core |
Package builds cleanly |
kuro-clippy |
Clippy with -D warnings |
kuro-fmt |
cargo fmt --check |
kuro-test |
Rust unit + integration tests |
kuro-elisp |
ERT test suite on Emacs 30 |
kuro-byte-compile |
Byte-compile on Emacs 30 |
kuro-package-lint |
package-lint on user-facing entry points |
kuro-checkdoc |
checkdoc across emacs-lisp/ |
treefmt |
Rust + Nix files are formatted (treefmt) |
flake.nix # Inputs + outputs wiring (~85 lines)
nix/
treefmt.nix # treefmt formatter config (Rust + Nix)
checks.nix # All flake check derivations
apps.nix # nix run .#<name> app definitions
Contributions welcome! See CONTRIBUTING.md.
MIT -- see LICENSE.
- Inspired by emacs-libvterm
- Uses vte for VT parsing
- Uses emacs-module-rs for FFI