- Zig 96%
- Python 4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| examples | ||
| scripts | ||
| src | ||
| .devkit.conf | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| CLAUDE.md | ||
| context.md | ||
| DESIGN.md | ||
| LICENSE | ||
| README.md | ||
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.