A cli-native ANSI and ASCII text editor inspired by ACiDDraw, its Unix successor DurDraw, and the rich tradition of concrete poetry in the Eastern Bloc samizdat.
cliizdat is a keyboard-only, cell-grid glyph editor for Unicode block art —
octants (U+1CD00–1CDE5), Symbols for Legacy Computing, and Nerd Font glyphs —
the material that ncurses editors silently drop because wcwidth() returns −1.
Editing is replace-only: there is no insert mode, so columns never drift and the grid physically cannot break. Keys just do things — no modes to toggle.
- Modeless, keyboard-only editing on a fixed cell grid
- Per-cell 256-color, stored as sparse git-diffable
.colorsidecars - Layers with a JSON manifest, knockout halos, solo view
- Rectangular selection: fill, repaint, copy / cut / paste, flip X / Y
- Braille dot-mode: draw at 2×4 sub-cell resolution, one dot per keystroke
- Loadable glyph palettes, one page per section (octants auto-sorted by density)
.ansexport (fg-only, terminal background is the paper)- Theme-adaptive grayscale: on a light terminal the gray ramp (256-color 232–255) is reflected around its midpoint, so faint two-tone art keeps its contrast when the paper flips from black to white — one file, both themes
.durimport — bring existing durdraw art in, colors and all- ANSI import — open
.ansart, or pipe atdfigletbanner straight into the copy buffer and stamp it at the cursor withp - Width authority is go-runewidth,
never libc
wcwidth
Requires Go 1.25+.
go build -o ~/bin/cliizdat .cliizdat new sigil # new 80x55 project with every palette found
cliizdat new -s 120x40 sigil # custom canvas
cliizdat new -p pal.txt sigil # explicit palettes (files or directories, comma-separated)
cliizdat project.json # a manifest project
cliizdat layer.txt # a single layer (implicit project)
cliizdat art.dur # import durdraw art
cliizdat art.ans # import ANSI art
cliizdat -p palette.txt file # attach a glyph palette
tdfiglet -f incorped.tdf A | cliizdat clip # banner → copy buffer, paste with p
tdfiglet -f incorped.tdf A | cliizdat clip -fg 246 # …flattened to one color
cliizdat clip -raw art.ans # keep the empty marginsnew writes <name>.json (canvas, slots, palettes) and opens it; the layer
<name>.txt lands on disk with the first w.
Palettes are discovered in $CLIIZDAT_PALETTES (:-separated files or
directories), else in the nearest palettes/ directory up the tree, else
~/.config/cliizdat/palettes. A directory holding all.txt yields just that
file — the convention for a palette assembled from the others, whose sources
would otherwise double every page. A project whose manifest has no "palette"
falls back to the same search, so the Tab picker is never empty; the choice
is recorded in the manifest on the next w.
Every == SECTION == is a page in the picker, shown one glyph row per two
screen lines so block glyphs do not fuse into a mass. Sections named OCTANT…
are merged into a single page sorted by pixel density, and non-2×4 glyphs are
dropped from them — that is how the prose in a hand-written octant table
disappears. The rule keys off the name alone, so octants deliberately mixed
with other glyphs on a shared page stay untouched; a single file can carry
octants, braille, blocks, and NF side by side.
Press h in the editor for the full control list.
| key | action |
|---|---|
| arrows | move cursor |
Shift+arrows / s |
select (s: anchor, arrows grow, Esc drops) |
1–9 |
stamp slot (fills the selection) |
0 / x |
erase (slot 0 is always the eraser) |
y d p |
copy · cut · paste (shared between running instances) |
X / Y |
flip selection/layer horizontally · vertically (mirrors glyphs) |
b |
braille dot-mode: arrows move per dot, . toggles the dot |
c / C / r |
color picker · eyedropper · repaint |
Tab |
glyph palette → slot |
[ ] / v |
prev/next layer · solo |
u / U |
undo · redo |
R |
resize canvas (crop / extend) |
g |
go to row,col |
w / e |
save · export .ans |
q |
quit |
project.json collage manifest (the single source of assembly)
layers/
sigils.txt layer = plain UTF-8 text, LF, space = transparent
sigils.color sparse per-cell color overrides: ROW COL FG, 1-based
Coordinates are 1-based everywhere in the UI, manifest, and sidecars. Saving strips trailing spaces, enforces LF, and sorts sidecars by row,col.
The copy buffer is a file, not process memory: y/d write it, p re-reads
it when another instance wrote last. So an element yanked in one terminal tab
pastes into a project open in another, glyphs and colors intact, and survives
quitting. Location: $CLIIZDAT_CLIPBOARD, else
${XDG_CACHE_HOME:-~/.cache}/cliizdat/clipboard.json.
cliizdat clip [-fg N] [-raw] [file|-] fills that same buffer from an ANSI
stream, so any external generator becomes a paste: run it in one terminal tab,
press p in the editor open in another. It understands SGR (16-color, bright,
bold-as-bright, 38;5;N), CR/LF, ESC[nC, strips a trailing SAUCE record,
and reads non-UTF-8 input as CP437. Empty margins are cropped so the banner
lands under the cursor; -raw keeps them.
A cell has a foreground only — the terminal background is the paper — so an
imported (glyph, fg, bg) triple is reduced: a black background is dropped, a
space on a colored background becomes █ in that color, █ hides whatever is
behind it, and anything else keeps its foreground. Only the last case loses
information: half blocks and shades over a colored background come in as one
tone instead of two.
Opening a bare .txt is an implicit project. On the first save it is
promoted to a manifest: a sibling <name>.json is written capturing the
canvas size, palette, and slots — so a resized canvas (whose empty margins
a bare .txt cannot record) persists. Reopening the same .txt afterwards
picks the manifest up automatically.