Introduction
p5.nvim is an attempt to bring a better DX when working with p5.js in Neovim .
🌌importantWhat's a sketchspace ? Because naming things is hard in programming (is it easier in any other field ?) , the term sketchspace is a combination of sketch + workspace. I didn't want the "work" part because that just makes it sound less fun. So yes, its a completely 'imaginary' term just like "algorist" (algorithm + artist).
Modules overview
The plugin consists of Lua modules that integrate with Neovim and a standalone Python server that handles live reload and console streaming.
Automated assets update
The plugin bundles p5.js core modules, type definitions, and manpages. This allows users to have all they need to get started using p5.js in one place. These assets are automatically generated via GitHub Actions in a separate repository (automata):
Process:
- Trigger: p5.js GitHub release OR manual workflow dispatch
- Extract: documentation from
@types/p5TypeScript definitions - Generate: manpages & tags (
doc/p5-*.txt) - Fetch: latest p5.js release
- Push: updates to p5.nvim repository
Design Decisions
Command Pattern (single command with sub-commands)
Instead of multiple commands like :P5Create, :P5Install, :P5Server, I used a more 'CLI-like' approach where a single command (P5) has sub-commands:
This pattern provides:
- Consistent command interface
- Built-in argument handling
- Tab completion out of the box
sh
Live server (python3)
Initially, the plugin supported multiple server backends:
- Python HTTP server
live-server(npm)- Bun
- Deno
However, all these options proved to be overkill and a not needed complexity and decided tp just use Python and call it a day
Console streaming with SSE
The original implementation used WebSocket for streaming browser console logs to the editor. This was refactored to use Server-Sent Events (SSE) because:
- SSE works with the existing HTTP server (no separate WebSocket server needed)
- Simpler implementation
- Better compatibility with Neovim's terminal API
p5.json (a ripoff of the package.json)
Sketchspace info is stored in a simple p5.json file:
json
It allows the user to:
- Setup a sketchspace using the
:P5 setupcommand - Save the libraries, Gist URL (optional) used in the sketchspace.
It is also used by the assets/libs.js module to load the contributor libraries into the index.html.
Gotchas
Below is a summary of some of the issues I encountered and how they were resolved.
E488 : command prefix conflict
Running any :P5* command threw E488: Arguments not accepted.The plugin registered both P5 (for the picker) and P5Create, P5Install, etc. as separate commands. Neovim's command prefix matching caused conflicts.
🌬noteI had previously defined the user commands to have the following format P5Create, P5Install, etc but it felt unnatural given the amount of sub-commands I wanted.
The solution looked something like this:
lua
Gist Creation Fails on Different Platforms
Creating a Gist failed on Windows or when the temp directory had unusual paths.
The root cause(s) were:
- Hardcoded path separators (
/), which are not compatible with Windows - Using
os.tmpname()which creates paths theghCLI couldn't read
In the end, vim.fn.tempname() was the solution for cross-platform temp paths.
CDN Links Breaking
Installing some libraries failed with "invalid response". CDN links (jsdelivr, unpkg) occasionally changed or deprecated libraries. The plugin was blindly downloading without validation.
I added regex validation to reject error pages masquerading as valid JavaScript files (but maybe checking the status code would have had been wiser).
Path Traversal Vulnerability
Root Cause: The Python server used os.path.join() directly without validating that resolved paths stayed within the sketchspace directory.
Fix: Added path resolution validation:
py
Circular Dependency
At some point, the plugin failed to load with "module not found" errors. The cause was circular dependency, console.lua required server.lua, which in turn required console.lua. Lua's require detected the cycle and failed.
Fix: Restructured modules so that core.lua acts as an intermediary, and moved shared code to prevent circular imports.
Server Features
The Python server (server.py) handles live reload and console streaming. Key features include:
on-the-fly "index.html" generation
If index.html doesn't exist in the sketchspace, the server generates one automatically based on p5.json:
Console Integration
The server injects a script that intercepts console.log, console.error, console.warn, and console.info, then sends them to Neovim via SSE:
js
Live Reload
File changes trigger a WebSocket message to the browser, which reloads the page automatically:
Available Commands
sh
The plugin is usable today and covers the core p5.js development workflow. Contributions and feedback are welcome 💙