A terminal emoji picker that respects your terminal. One static Zig binary, inline TUI or floating GUI, zero heap allocations in the search loop, zero daemons, zero Electron.
Honest by construction: the picker logs its own RSS to
/tmp/emojig.log on every exit — you can check
the numbers below on your machine.
Measured, not marketed.
The emoji database is embedded with @embedFile;
queries return string slices pointing straight into the
binary's read-only memory. No JSON parsing, no disk reads,
no allocations at query time.
────────────────────────────────────────────────────────────────────────────
This is the real search engine and grid logic, ported 1:1 from the Zig source to JS — same scoring, same fallbacks, same keybindings. Type a command at the prompt, then hit Ctrl+E.
At the prompt, type
echo
then Ctrl+E opens the picker.
Fuzzy-search, arrows navigate, Enter inserts
into the command line. In the picker, try
? and ?? for
the built-in help pages, and the
e:/t: width
filters. Tab cycles themes,
Esc bails out.
Because the existing ones are either a browser in disguise, a desktop-environment plugin, or a script that leaves your terminal in raw mode when it crashes.
The interactive loop performs no heap
allocations. Search scores 2,249 entries per
keystroke against an @embedFile'd
database and returns slices into read-only
binary memory. Written in Zig, compiled with
ReleaseSmall.
fzf-style auto-detection: a TTY means inline TUI
at your prompt; a desktop hotkey (no TTY) means
a compact floating window via your own terminal
(foot, kitty,
alacritty, …). No daemon, no IPC,
no background anything.
The same hotkey opens and closes the
floating picker. Implemented with a pidfile and
a /proc/<pid>/cmdline check —
against PID reuse — in ~70 lines of plain POSIX.
Press it twice: no window stacking, ever.
A custom Zig panic handler plus
SIGINT/SIGTERM handlers
restore termios, disable mouse tracking, and
reset the cursor on every exit path —
including crashes. Your prompt comes back clean.
Subsequence scoring with word-start and
consecutive-run bonuses, AND-matching for
multi-word queries, and automatic fallbacks for
plurals (cars→car), verb stems
(running→run), and trailing
e. Plus e:/t:
width filters and paged in-picker help
(?, ??).
Layout, themes, keybindings, and all UI strings
(10 languages) live in JSON specs, embedded at
compile time. The Go port (mojigo)
consumes the same specs — one source of truth,
verified by tests in both languages.
Things the status bar won't tell you (but ??
will). All of these work in the playground above.
| query | what happens |
|---|---|
| racing |
no direct hit → stem fallback tries
rac and race → 🏎️
|
| cars |
plural fallback retries car
(also handles -es, -ies)
|
| fire truck | multi-word AND: both terms must match → 🚒, not 400 fire-adjacent things |
| t:arrow | text-width symbols only: → ↔ ⇒ … (single-cell, safe for ASCII-ish contexts) |
| e:arrow | wide emoji only: ⬆️ ↗️ 🏹 … (double-cell, colorful) |
| b:double corner |
box-drawing & block art: ╔ ╗ ╚ ╝ ░ ▓ … —
systematic names ("top left double border"),
ranked below emojis unless asked for with
b:
|
| ? then ?? | paged help inside the picker — page 1: keys, page 2: these filters |
| (empty) | most-recently-used emojis first; the MRU list is a plain file write at exit, no cache daemon |
────────────────────────────────────────────────────────────────────────────
Demos here are not screen-recorded by hand. Each one is a
.reel script checked into
spec/reels/
and recorded reproducibly with
wayreel
in a headless compositor. If a demo looks wrong, the script
is the bug report.
Scripted next — titles map 1:1 to .reel files;
steps are sketched, recordings follow:
Half-typed commit message, Ctrl+E, fuzzy "rock", Enter — 🚀 lands in the command line. Prompt stays pristine.
scripted · not yet recordedStems, plurals, AND-terms, and the e:/t: width filters — the engine being smarter than a substring.
scripted · not yet recordedThe picker documents itself in 25 columns; backspace walks back from page 2 to the grid.
scripted · not yet recordedOne hotkey: open, close, open — never two windows. Pidfile + /proc check, no daemon.
scripted · not yet recordedThe picker asks the terminal for its background color and matches it; Tab cycles 🌙/🌞/🔆 live.
scripted · not yet recordedPick 🔥 once and it's in cell #1 next time. MRU survives restarts via one POSIX file write.
scripted · not yet recordedA roadmap, not a promise of dates. Tracked in the issues directory — in the repo, like everything else.
2,249 is the curated core; the full Unicode set is much bigger. Grow the database without giving up the compile-time packing or the <900 KB budget.
⌘ ∑ ≠ × — math, arrows, technical and typographic symbols, searchable by their real Unicode names.
Switch a selected color emoji to its plain text presentation (❤️ → ❤) before copying — for commit messages, logs, and ASCII-leaning contexts.
Smarter synonym ranking, frecency over plain MRU, fewer surprising top hits. Still zero-allocation, still a plain file, still no daemon.
Pick 👋, choose 👋🏽 — a modifier sub-picker without bloating the grid or the binary.
Octocat, git branch arrows, and friends — with detection of whether your font can actually render them before offering them.
Pretty borders around the grid would be nice, but the picker has to render correctly across too many terminal stacks to bet the UI on it.
AUR, Nix, and Homebrew can wait — the
curl | sh and make install
paths work today.
────────────────────────────────────────────────────────────────────────────
| Ctrl+E | open the picker at the shell prompt; pick lands in the command line |
| ↑↓←→ | 2D grid navigation, wraps at the edges |
| Enter / click | copy selection (and insert, in widget mode) |
| Tab | cycle theme dark / light / system, persisted |
| ? ?? | help pages 1 and 2 |
| Esc / Ctrl+C | exit; terminal state fully restored |
x86_64 or
aarch64; works over SSH and on raw VTs.
foot
(cell-precise), or kitty,
alacritty, wezterm,
ghostty, konsole,
gnome-terminal, ptyxis.
wl-copy
(Wayland) or xclip (X11), if present.
source ~/.local/share/emojig/shell/emojig.<shell>
in your bash/zsh/fish config enables
Ctrl+E.