Pixel-art pets living inside your Neovim editor — vscode-pets equivalent.
One (or a small flock) of animated pixel-art pets live in your editor.
They wander across the whole buffer — walking, idling, lying down to rest
— while carefully stepping only on empty cells, never on your code. They
react to what you do: a pet walks over to watch the line you're editing,
pauses to look at you when you save, frets when LSP errors appear and
cheers when they're gone, fetches a ball you throw, drifts to sleep when
you go idle, and keeps a persistent happiness you can feed. Species, sprite
size, flock size and wander area are configurable at setup and at runtime
via :Pets* commands.
Why build this when
pets.nvimexists? See docs/why-built-from-scratch.md.
Read this first. The pet is a real image drawn through the Kitty Graphics Protocol, so it only renders where three things line up: a supported OS, a supported terminal, and ImageMagick. If any one is missing,
:Petsruns without error but nothing appears.
All of these are required at runtime:
- Neovim >= 0.10
- OS: macOS or Linux. Windows (native) is not supported — see below.
- A Kitty Graphics Protocol terminal — Kitty, WezTerm, or Ghostty (see the table below).
- ImageMagick.
image.nvimuses it to decode and render every sprite, so it is needed at runtime, not just for regenerating sprites. On macOS:brew install imagemagick. image.nvim also needs its image processor set up: eitherprocessor = "magick_cli"(uses the ImageMagick CLI you just installed — simplest) or the defaultmagickluarock. Verify the whole chain with:checkhealth image. - image.nvim — loaded automatically as a dependency, but it has its own system requirements (the ImageMagick bullet above).
| Platform | Status |
|---|---|
| macOS | ✅ supported |
| Linux | ✅ supported |
| Windows (native) | ❌ not supported — image.nvim + ImageMagick don't run natively, and Kitty/Ghostty have no Windows build |
| Windows (WSL2) |
| Terminal | macOS | Linux | Windows | Notes |
|---|---|---|---|---|
| Kitty | ✅ | ✅ | — | no Windows build |
| WezTerm | ✅ | ✅ | Windows build exists, but the ImageMagick/OS limits above still apply | |
| Ghostty | ✅ | ✅ | — | no Windows build |
| iTerm2 | ❌ | — | — | no Kitty Graphics support (uses its own image protocol) |
| Apple Terminal | ❌ | — | — | no graphics protocol |
| Alacritty | ❌ | ❌ | ❌ | no graphics protocol |
| VS Code integrated terminal | ❌ | ❌ | ❌ | no graphics protocol |
If your TERM_PROGRAM isn't on the known-good list the plugin prints a
one-shot warning on first :Pets and continues anyway, so a new terminal
that supports the protocol works without a code change. A warning (or a
silent no-show) almost always means the terminal or ImageMagick is the
problem — run :checkhealth image.
If you run nvim inside tmux, also enable in your tmux.conf:
set -g allow-passthrough on
set -g focus-events onnvim-pets is a standalone plugin — drop the spec below into your lazy.nvim
config and it clones from GitHub. It renders through image.nvim, which
you must configure yourself (declaring it as a dependency is not enough): it
needs processor = "magick_cli" (or the default magick luarock) and the
kitty backend. A minimal working pair:
{
-- image.nvim: the rendering backend. Required, and must be set up.
"3rd/image.nvim",
event = "VeryLazy",
opts = {
processor = "magick_cli", -- uses the ImageMagick CLI (brew install imagemagick)
backend = "kitty", -- Kitty Graphics Protocol
},
},
{
"ChickenPaella/nvim-pets",
dependencies = { "3rd/image.nvim" },
-- `cmd` matters: with only `keys`, the plugin stays unloaded until you
-- press one of them, so typing `:Pets` first fails with "Not an editor
-- command". Listing the commands lets them load it too.
cmd = {
"Pets", "PetsHelp", "PetsState", "PetsStatus",
"PetsThrow", "PetsFeed", "PetsPomodoro",
"PetsType", "PetsCount", "PetsResize", "PetsArea", "PetsMove",
},
keys = { { "<leader>pp", "<cmd>Pets<cr>", desc = "Pets: toggle" } },
config = function()
require("pets").setup({
-- All optional; values shown are the defaults.
pet = "fox", -- "fox" | "panda" | "dog" | "turtle"
count = 1, -- how many pets roam at once (1-6)
settle_ms = 1200, -- freeze the pets after this idle time (0 = never)
-- width / height default to the species' own cell size (fox is 7x3),
-- not to any fixed number. Set them only to override that.
width = 7,
height = 3,
fps = 6,
area = {
corner = "br", -- "br" | "bl" | "tr" | "tl"
cols = 0, -- 0 = auto: cover most of the editor width
rows = 0, -- 0 = auto: cover most of the editor height
},
})
end,
}Nothing showing up? It's almost always the terminal or ImageMagick — run
:checkhealth imageand see docs/troubleshooting.md.
Local development: if you've cloned this repo and want your edits to load
live, replace "ChickenPaella/nvim-pets" with dir = "/path/to/your/checkout".
| Command | What it does |
|---|---|
:Pets (or <leader>pp) |
toggle the pet on/off |
:PetsHelp |
show a floating cheat sheet of all commands |
:PetsResize <w> <h> |
resize sprite (cells); auto-grows the box if needed |
:PetsArea <cols> <rows> |
resize the wander box (cells) |
:PetsMove <corner> |
move box to corner (br / bl / tr / tl) |
:PetsType <name> |
switch species (fox / panda / dog / turtle) |
:PetsCount <n> |
set how many pets roam at once (1–6) |
:PetsState |
print current pet / action / direction / position / size / area |
:PetsThrow (or <leader>pb) |
throw a ball to the cursor; the pet fetches it |
:PetsFeed (or <leader>pf) |
feed the pet — raises its happiness |
:PetsStatus |
show the pet's happiness (0–100) and mood |
:PetsPomodoro [min] |
start a focus session (default 25 min); the pet celebrates when it ends |
:PetsPeek / :PetsWiggle / :PetsSwipe / :PetsObject / :PetsFollow / :PetsSleep / :PetsWake |
manually trigger lifestyle events (debug) |
Any :Pets* config command applied while the pet is visible briefly hides
and re-shows it with the new settings.
The pet roams a wander box that covers most of the editor by default
(area.cols = 0, area.rows = 0). It walks in any of 8 directions —
up, down, diagonals included — and refuses to step on any cell that
currently contains visible text, signs, line numbers, or sidebar
content. The grid of "blocked" cells is recomputed (debounced ~150ms)
on TextChanged, WinScrolled, WinResized, and a few other events
so the pet's view stays up to date as you edit.
If you'd rather keep the pet in a small corner, set area.cols and
area.rows to specific values and the area.corner to your preferred
anchor.
Each species has its own wander transition probabilities so the way it spends time differs:
- fox — restless: highest
walkprobability, shortestlieruns - dog — balanced: walks and idles a lot, occasionally lies down
- panda — mellow: lies down often even from
idle, walks less - turtle — sleepy: spends most of its time lying down, walks slowly
Combined with the per-species sprite and the periodic swipe animation, the four pets read as visibly distinct over a session.
The pet stays in its corner but reacts to your editing rhythm:
-
Peek on save —
BufWritePostfires the pet into a briefpeekstate: it pauses for ~1 second and faces toward the screen center (away from the corner) so it looks like it's looking at you. -
Sleep on long idle — after ~10 minutes with no cursor movement / text change / save, the pet curls up into a
liepose and stays there. Any user activity wakes it back to idle automatically. -
Random wiggle — every ~5 minutes (±2 min jitter), if the pet isn't busy, it does a short head-shake (~1.5s of rapid left/right flips). Background sign of life.
-
Species swipe — every ~4 minutes (±90s jitter), the pet plays its species-specific swipe animation (fox paw, panda hands-up, dog paw, turtle reach). This is what makes each species feel different beyond just the sprite.
-
Cursor follow — every ~35 seconds (±15s jitter), if you've edited in the last ~20 seconds, the pet walks over to a free cell next to the cursor and watches it for ~2.6 seconds before drifting back to wandering. Only fires while you're actively editing, so the pet looks attentive rather than constantly crowding the cursor. Reuses the walk → peek sprites; on arrival it faces toward the cursor.
-
Environment objects — every ~3 minutes (±60s jitter), a ball appears somewhere in the wander box. Every pet on screen runs for it, fanning out around the ball so they converge rather than stack, plays the species swipe animation for ~3 seconds, and the ball disappears once the last one is done. Only one object is active at a time, and the timer is skipped while the pet is busy or asleep.
-
Focus loss —
FocusLost(e.g. tmux window/session switch, app switch) pauses the animation;FocusGainedresumes it and forces one full redraw, because tmux drops images for inactive windows. The pets stay visible and keep their position, action and mood — nothing is torn down. Pausing is enough to stop stale Kitty placements building up, since a paused pet issues no draws at all.If pets vanish when you switch to another application, that's image.nvim's
editor_only_render_when_focusedclearing its images — set it tofalse.
All of this reuses the existing idle / lie sprites — no new assets.
Manual debug triggers: :PetsPeek, :PetsWiggle, :PetsSwipe, :PetsObject, :PetsFollow, :PetsSleep, :PetsWake.
The pet now reacts to what's happening and keeps a little persistent state:
- Speech bubbles — a small rounded bubble pops above the pet for
reactions:
zzzon sleep, an occasionalsaved!on:w,!?when LSP errors first appear andyay!when the last one clears, hearts on feed, a sigh when it's bored. - LSP reactions — on
DiagnosticChangedthe pet does a brief in-place fluster the moment errors appear and a happy swipe when they're all gone. It never leaves its spot (the old run-to-the-error behavior was too disruptive). - Ball throw —
:PetsThrow(<leader>pb) drops a ball at the cursor and the pet runs to fetch it. The automatic ~3-minute ball spawn still happens on its own too. - Happiness (tamagotchi-lite) — the pet has a happiness value (0–100)
that slowly decays in real time and rises when you feed it (
:PetsFeed), play fetch, or let it watch you work. It's saved tostdpath("data")/nvim-pets-mood.jsonso the pet remembers across sessions.:PetsStatusreports it; a sad pet sighs more. - Pomodoro companion —
:PetsPomodoro [minutes]starts a focus session; the pet announces it, sits with you, and celebrates with a swipe + bubble when the timer rings. - Day / night — at night (22:00–06:00) the pet nods off to sleep sooner,
and it yawns (
~) now and then while idle.
Set count (1–6) and that many pets roam the editor at once, each an
independent wanderer with the species' size, pace and personality:
require("pets").setup({ pet = "fox", count = 3 })or at runtime with :PetsCount 3. The "lead" pet is the one that reacts to
saves, the cursor, the ball and LSP changes; the rest keep it company. When
two pets drift within a few cells of each other they turn and glance — a
little flock that notices itself. The whole flock naps and wakes together.
The pet is built to be ambient, not a distraction:
-
Pets settle when you settle — after ~1.2s with no cursor movement, typing or scrolling, the pets freeze completely (no motion, no redraws) and spring back to life the instant you do anything. Every Kitty redraw briefly nudges the terminal cursor, so holding still while you read a screenful of code is what actually stops the cursor from flickering.
The flip side is that pets only move while you're touching the keyboard. If you'd rather have them roaming all the time, set
settle_ms = 0— at the cost of some terminals flickering the text cursor while you read. -
Calm cursor — even while active, a pet is only redrawn when it actually moves or its frame changes; a resting pet breathes about once a second rather than every frame.
-
Never covers your cursor while typing — if a pet would be drawn over the insertion point in insert mode, that frame is skipped, so it never hides what you're writing.
-
Avoids your code — a pet only stands where its entire sprite footprint is clear, not just the cell under its feet, so its body never covers a line. The blocked map includes text, signs, numbers, fold columns, end-of-line virtual text (diagnostics), inlay hints and sidebar content, and it tracks wrapped lines, closed folds and
virt_linescorrectly rather than assuming one screen row per buffer line. -
One bounded image per frame per pet — sprite placements are reused and repositioned rather than re-transmitted per cell, so a long session doesn't pile up Kitty images.
-
Timers only run while visible — toggling the pet off stops all background work; losing focus pauses the animation timer.
- tmux window switching may leave a "ghost" frame at the pet's last
position. This is a tmux + Kitty Graphics Protocol limitation (tmux doesn't
virtualize the graphics layer).
FocusGainedforces a full redraw, which clears it in most cases; if one lingers, run:Petstwice (off → on). Requiresfocus-events onintmux.conf— without it nvim never learns that the window came back.
- v0: static sprite in a float window (toggle)
- v1.0: frame-by-frame idle animation
- v1.1: wandering — state machine (idle ↔ walk ↔ lie), sprite flipping, edge bouncing
- v1.1.5: bounded wander box, runtime size/area/corner commands, image-cache fix
- v1.2: lifestyle events — save peek, idle sleep, random wiggle
- v1.3.1: multi-species —
pandaadded alongsidefox, runtime switch via:PetsType - v1.3.2:
dog(akita shiba) andturtle(green) added - v1.3.3: per-species personality — distinct wander probabilities + a periodic swipe signature animation
- v1.4.1: environment objects — ball spawns, pet approaches, plays swipe, ball despawns
- v1.4.2 (Phase 1): wander across the whole editor — 8-direction motion + buffer-aware avoidance so the pet never walks on top of code or sidebar contents
- v1.4.3 (Phase 2): pet reacts to where the cursor is — walks over to the active edit and watches (cursor follow)
- v1.5: speech bubbles, LSP reactions, ball-throw command, happiness/feeding (persisted), pomodoro companion, day/night behavior
- v1.6: multiple pets on screen at once — per-instance pet state, a configurable flock (
count), and pets that glance at each other - v1.6.1: non-intrusive polish — pets never draw over the cursor while you're typing
- v1.4.4: more objects (food bowl, box) and species-specific interaction sprites
- v1.4: environment objects (food bowl, ball, box) for richer pet interaction
MIT — see LICENSE.
Sprite asset credits: see LICENSES.md.