<<<

issy

Source lives at github.com/davidemerson/issy.

issy is a text editor that looks like a printed page, not a terminal application. It’s written in Zig with zero external dependencies, cross-compiles to Linux, macOS, and OpenBSD, and a release build lands around 470KB. Single static binary, no runtime, no package tree.

why

I spend a lot of my day staring at text, and wanted something closer to the feeling of reading a well-typeset book: restrained contrast, gentle luminance shifts, structure you parse with your eyes rather than with your retina.

I also wanted a real print path. I want the PDF to look typeset, not like someone screenshot a terminal and hit print.

two themes

Pick the one that matches your environment. In both options, only a couple of token types get real chromatic contrast so the eye parses structure through gentle luminance shifts instead of a rainbow.

default dark theme with restrained syntax highlighting
the default theme. black background, violet keywords, soft green strings, dim comments.

paper theme based on solarized light
the paper theme. Solarized Light, warm cream background, violet keywords, cyan strings. Designed for readability in bright environments.

Ctrl+P (or --print on the command line) renders the current buffer to a real PDF 1.4 file with TTF/OTF font embedding, a separate ink-on-paper print theme, headers, and automatic page breaks. No external dependencies, no temporary PostScript — the PDF writer is hand-rolled in Zig.

printed pdf export of editor.zig in berkeley mono

Headless from the command line:

issy --font "Berkeley Mono.ttf" --print output.pdf source.py

The print theme is tuned for ink on white paper and never inherits the TUI theme. Recommended fonts: Berkeley Mono, Iosevka, JetBrains Mono, Commit Mono.

multiple cursors

Ctrl+D selects the word under the cursor and adds a cursor at the next occurrence. Press it again to keep adding. Every subsequent edit, whether typing, backspace, delete, or paste, applies to all cursors simultaneously, and Ctrl+Z undoes the whole multi-cursor tick as one step.

multi-cursor rename demo

Ctrl+F enters search mode; each keystroke re-runs the search and jumps the cursor to the first live match. Ctrl+G walks to the next match, Escape cancels and returns the cursor to where it started.

incremental search demo

keyboard and mouse selection

Shift with an arrow key extends the selection one character at a time; Ctrl+Shift+Left and Ctrl+Shift+Right grow it a word at a time. Click places the cursor, double-click selects the word under it, triple-click selects the line, and shift-click extends from the existing anchor. Drag past the viewport edge and the view autoscrolls.

word-wise keyboard selection demo

Copy and cut push to the OS clipboard via OSC 52, so yanking out of a tmux or SSH session lands in the host clipboard without extra plumbing. Pastes from the terminal arrive as bracketed paste (DECSET 2004): during a paste, auto-indent is suppressed and tabs land as literal \t, so already-indented content comes in verbatim instead of compounding.

path completion

Ctrl+O opens the file prompt seeded with the current directory. Type a partial directory or filename and press Tab to auto-complete against what’s on disk.

path completion in the open-file prompt

per-file cursor memory

Quit a file and issy remembers the cursor position at ~/.cache/issy/positions.txt; reopening the same file restores the caret automatically. Positions are keyed by absolute path, capped at 300 entries with a newest-on-top LRU layout, and degrade gracefully (corrupt file or missing HOME is a silent no-op). An explicit file:line on the command line always wins over the saved position.

install

one-line curl (linux, openbsd)

curl -sSL https://raw.githubusercontent.com/davidemerson/issy/main/install.sh | sh

Drops issy at ~/.local/bin/issy, verifies an Ed25519 signature over the release manifest against a public key baked into the script, verifies the binary’s SHA-256 against that signed manifest, seeds ~/.issyrc with commented defaults if you don’t already have one, and wires up the opt-in auto-update path. Prebuilt binaries cover Linux amd64/arm64 and OpenBSD amd64. On platforms without a prebuilt (or when you invoke the installer on macOS), it falls through to zig build -Doptimize=ReleaseSafe from source.

Flags: --prefix DIR to relocate, --version VER to pin to a specific release, --no-rc to skip the ~/.issyrc seed, --help for the rest. Prefer to read the script before piping it to a shell? Fetch it with -o install.sh and run it once you’re happy.

linux packages

Pre-built .deb, .rpm, and raw binaries for amd64 and arm64 are attached to every GitHub release. Grab them from the releases page if you’d rather install via your package manager than curl.

macos via homebrew

brew tap davidemerson/issy https://github.com/davidemerson/issy
brew install issy

The formula tracks tagged releases (vX.Y.Z), so upgrades are just brew upgrade issy — the same one-liner you’d use for any Homebrew package, no --fetch-HEAD, no uninstall-and-reinstall dance. CI rewrites the formula’s url and sha256 on every tag push via a release-tag job, so the versioned formula stays current without manual edits.

Want the bleeding edge between releases? brew install --HEAD issy still builds from main, and brew upgrade --fetch-HEAD issy pulls the latest commit.

The formula pins its build dependency to zig@0.15 rather than the unversioned zig formula. Homebrew bumped zig to 0.16, which broke the build; zig@0.15 is pinned, carries Apple’s Xcode 26.4 TBD compatibility patch, and won’t move out from under us. The pin is kept in sync with the ZIG_VERSION used by CI.

Prebuilt macOS binaries aren’t shipped because cross-compiled Mach-O from Linux has no code signature and Apple Silicon refuses to run it. Homebrew — or the curl installer, which falls through to a source build on macOS — produces a native host-signed binary that runs on both Intel and Apple Silicon with no xattr or codesign workarounds.

openbsd

The curl installer downloads a prebuilt amd64 binary. Builds are verified on every push by a real OpenBSD 7.8 amd64 VM in CI (openbsd-test job); the full unit and integration suite must pass on OpenBSD before main accepts a merge. An editors/issy ports submission is in flight — once it lands, pkg_add issy will be the preferred path.

Building from source: pkg_add zig then zig build -Doptimize=ReleaseSafe. bash and expect (also via pkg_add) are needed if you want to run the integration test suite.

build from source

Requires Zig 0.15.2+.

git clone https://github.com/davidemerson/issy
cd issy
zig build -Doptimize=ReleaseSafe
install -m 0755 zig-out/bin/issy ~/.local/bin/issy

cross compile

Zig makes this trivial:

zig build -Dtarget=x86_64-linux-gnu
zig build -Dtarget=aarch64-linux-gnu
zig build -Dtarget=x86_64-macos
zig build -Dtarget=aarch64-macos
zig build -Dtarget=x86_64-openbsd

Or zig build cross to build all targets at once.

usage

issy [options] [file[:line]]

issy main.zig
issy src/editor.zig:42    # open at line 42
issy newdoc.md            # start a new file at that path
issy                      # empty buffer

Command-line options:

FlagDescription
--version, -vPrint version and exit
--help, -hPrint usage and exit
--config FILEUse a specific config file
--theme NAMEOverride theme (default, paper)
--font PATHTTF/OTF font for PDF output
--no-configSkip loading config file
--print FILEExport to PDF and exit (no TUI)
--rollbackSwap in the previous binary (if auto-update has run) and exit

keybindings

The ones you’d expect, mostly.

KeyAction
Ctrl+SSave
Ctrl+Q / Ctrl+WQuit (on unsaved changes, Enter or Ctrl+Q again discards; Escape cancels)
Ctrl+Z / Ctrl+YUndo / redo (typing runs coalesce within 500ms — one step per word)
Ctrl+C / Ctrl+X / Ctrl+VCopy / cut / paste (copy and cut also push to the OS clipboard via OSC 52)
Ctrl+ASelect all
Ctrl+FIncremental search
Ctrl+GFind next match
Ctrl+HSearch and replace
Ctrl+OOpen file
Ctrl+NNew empty buffer
Ctrl+PExport to PDF
Ctrl+RReload file from disk
Ctrl+LGo to line
Ctrl+DAdd cursor at next occurrence of word
Ctrl+Left / Ctrl+RightJump by word
Shift+ArrowExtend selection by character
Ctrl+Shift+Left / RightExtend selection by word
Ctrl+/ or F1Show keybindings overlay

configuration

The installer seeds ~/.issyrc on first run with every setting commented out, so you can see what’s available and uncomment what you want. Unknown keys are ignored; missing keys fall back to compiled-in defaults. The editor watches ~/.issyrc for mtime changes and reloads live, so edits to config land in the running editor without a restart. The full reference is in CONFIGURATION.md in the repo. A minimal example:

tab_width = 4
expand_tabs = true
line_numbers = true
right_margin = 100
cursor_style = bar
font_file = "/path/to/font.ttf"

[theme.paper]

syntax highlighting

C, C++, Zig, Python, JavaScript, TypeScript, Rust, Go, Shell, HTML, CSS, JSON, YAML, TOML, Makefile, Markdown, and TeX/LaTeX. Language is detected by file extension. Seventeen in total, which covers just about everything I touch on a given day.

auto update

Release builds check for newer versions on startup. The check is a one-shot HTTPS request to a commit.txt asset on the latest GitHub release, made by a detached grandchild process so the editor itself never blocks on the network. If the commit SHA on the release differs from the one the running binary was built from, the footer shows update available: <sha>. Dev builds skip the check entirely; only ReleaseSafe builds produced by CI participate.

By default the editor only notifies. Opt into automatic download and in-session apply with autoupdate = true in ~/.issyrc. With auto-apply on:

  1. The background worker downloads sha256sums.txt and its Ed25519 signature, verifies the signature against the public key embedded in src/update_key.zig, then downloads the platform-specific binary and checks it against the signed manifest.
  2. The verified binary is staged under ~/.cache/issy/issy.staged and the footer switches to update staged: <sha>.
  3. Next time the buffer is clean and the editor has been idle for 60 seconds, it writes a small resume record, snapshots the current binary to ~/.cache/issy/issy.prev, atomically renames the staged binary over its own executable, tears down the terminal, and execve()s the new binary with --resume <path>. The terminal state survives execve, so the visible effect is a single re-render and the cursor lands back where it was.
  4. If anything goes wrong — non-writable binary, signature mismatch, dirty buffer, failed rename — the editor falls back to notify-only and keeps running the old version.

The signing private key is held as a GitHub Actions secret and only the repo’s CI workflow can sign releases. A tampered manifest or binary fails signature verification and staging aborts. The worker runs as the editor’s user, not root, and refuses to operate on root-owned install paths (so a .deb/.rpm install to /usr/bin/issy silently stays in notify-only mode and you update via the package manager).

To roll back after an apply: issy --rollback swaps the previous binary back in a one-shot atomic rename.

If you’d rather not think about any of this, notify_updates = false disables the check entirely.

architecture and tests

Gap buffer for text storage, hand-rolled tokenizer per language, hand-rolled PDF writer, and a TUI layer that speaks raw terminal escapes. There’s an ARCHITECTURE.md in the repo that walks through the source code if you’re curious.

Tests run in two layers:

zig build test              # 789 unit tests: gap buffer, unicode, tokenizer, editor ops, mouse/selection
bash tests/run_tests.sh     # integration tests via expect

The integration suite launches the real binary in a PTY, sends keystrokes, and verifies outcomes by checking saved file contents. It’s slower than pure unit tests but it catches the kind of bug where the editor thinks it saved a file but actually didn’t. The OpenBSD CI job runs the same suites inside a real OpenBSD 7.8 amd64 VM on every push, so OpenBSD isn’t a “should work” target — it’s a gate.

home | about | github | mastodon

XXIIVV webring

built
epoch
1776726416