A small readline alternative for Zig.
  • Zig 96%
  • Python 4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-04 09:37:57 +02:00
examples Add bracket-pair highlighting: Balance.matchAt, renderer marks, Config.match 2026-09-04 09:11:20 +02:00
scripts Add bracket-pair highlighting: Balance.matchAt, renderer marks, Config.match 2026-09-04 09:11:20 +02:00
src Add bracket-pair highlighting: Balance.matchAt, renderer marks, Config.match 2026-09-04 09:11:20 +02:00
.devkit.conf Add devkit config 2026-09-04 09:37:46 +02:00
.gitignore Initial commit: project instructions and gitignore 2026-09-03 09:54:20 +02:00
build.zig Add pty end-to-end test step; update DESIGN.md and context.md to the landed code 2026-09-03 20:02:36 +02:00
build.zig.zon Add MIT license 2026-09-04 09:23:08 +02:00
CLAUDE.md Initial commit: project instructions and gitignore 2026-09-03 09:54:20 +02:00
context.md context: remote and license 2026-09-04 09:23:24 +02:00
DESIGN.md Add bracket-pair highlighting: Balance.matchAt, renderer marks, Config.match 2026-09-04 09:11:20 +02:00
LICENSE Add MIT license 2026-09-04 09:23:08 +02:00
README.md Add MIT license 2026-09-04 09:23:08 +02:00

caret

A small readline alternative for Zig, built for interactive REPL prompts. No libc, caller-supplied allocator, one blocking call.

  • Line editing with Emacs keys, kill/yank, transpose, undo/redo
  • History with prefix-aware navigation
  • Tab completion with a cycling menu under the prompt
  • Multiline input driven by a completeness callback, with a built-in bracket balancer that understands strings, comments and char literals
  • Bracket-pair highlighting
  • Bracketed paste
  • Plain-line fallback when stdin is a pipe or file
  • UTF-8 with East Asian wide character widths
  • macOS, Linux, Windows 10 1809+

Requires Zig 0.16.

Usage

const caret = @import("caret");

var ed = try caret.Editor.init(allocator, .{
    .prompt = "> ",
    .continuation = "  ",
    .is_complete = caret.Balance.isComplete(.scheme),
    .match = .scheme,
    .completer = complete,
});
defer ed.deinit();

while (try ed.readLine()) |line| {
    defer allocator.free(line);
    // ...
}

readLine returns an owned copy of the line, null at end of input (Ctrl-D on an empty line, or a closed pipe), and error.Interrupted on Ctrl-C.

A completer fills in candidates for the word before the cursor:

fn complete(_: ?*anyopaque, text: []const u8, cursor: usize, out: *caret.Completions) !void {
    const word = text[out.start..cursor];
    for (symbols) |s| if (std.mem.startsWith(u8, s, word)) try out.add(s);
}

One candidate completes outright. Several insert the first and show all of them under the prompt; Tab and Shift-Tab step through them.

Multiline

Enter submits when is_complete says the text is done, otherwise it inserts a newline. Balance describes a syntax, bracket pairs, string quotes, comments and a char-literal prefix, and comes with scheme and c_like presets. A stray or mismatched closer counts as complete so a typo submits rather than trapping you. Ctrl-J, Ctrl-O, Alt-Enter and Shift-Enter always insert a newline.

Pasted text goes in verbatim through bracketed paste, and when stdin is not a terminal lines are read plainly and joined until complete, so a piped file of forms works the same as typing them.

Keys

Key Action
Left, Right, Ctrl-B, Ctrl-F move by character
Alt-B, Alt-F, Ctrl-Left, Ctrl-Right move by word
Home, End, Ctrl-A, Ctrl-E line start and end
Ctrl-Home, Ctrl-End, Alt-<, Alt-> buffer start and end
Up, Down previous/next line, then history
Ctrl-P, Ctrl-N history
Backspace, Delete, Ctrl-H delete
Ctrl-K, Ctrl-U kill to end and start of line
Ctrl-W, Alt-Backspace, Alt-D, Ctrl-Delete kill word
Ctrl-Y yank
Ctrl-T transpose
Ctrl-_ , Ctrl-Z undo
Alt-/ , Ctrl-Shift-Z redo
Tab, Shift-Tab complete, cycle
Ctrl-J, Ctrl-O, Alt-Enter newline
Ctrl-L clear screen
Ctrl-D delete forward, or end of input on an empty line
Ctrl-C interrupt

The keymap is a value in the config; copy Keymap.emacs and bind or unbind to change it. On macOS Terminal.app, turn on "Use Option as Meta key" for the Alt chords.

Build

zig build test   # unit tests, no terminal needed
zig build run    # demo REPL, examples/repl.zig
zig build pty    # drive the demo through a pseudo-terminal (POSIX, python3)

See DESIGN.md for the layering and the decisions behind it.

License

MIT — see LICENSE.