A VS Code extension that opens a new file type, .ipywb ("I Py Whiteboard") —
a notebook that behaves exactly like .ipynb, except markdown cells are
replaced by freeform drawing boards (graphics-tablet friendly, with
pressure support), alongside normal Python code cells running on a real
Jupyter kernel.
Built on VS Code's native notebook UI
.ipywb files open in the same notebook editor .ipynb uses, so all the
notebook features come for free and work exactly as you expect:
- Shift+Enter / Ctrl+Enter to run cells, run-all, run-above/below
- The native Select Kernel button (top right of the notebook)
- Monaco code cells — syntax highlighting, IntelliSense with the Python extension installed
- Native undo/redo (including drawing strokes — each stroke is an undo step)
- Cell reordering, collapse, outline, find, diff view on save conflicts
- Stop button interrupts a running cell (
KeyboardInterrupt, variables survive — just like Jupyter) - Live streaming output,
matplotlib/pandasrich output, ANSI-colored tracebacks
The whiteboard is implemented as a notebook output renderer: a board cell is a cell whose output is an interactive canvas. Draw on it with a mouse or graphics tablet (pen pressure is recorded); strokes persist into the file as vector data and participate in undo.
How the pieces fit
VS Code extensions run in a Node.js extension host, so the shell is TypeScript. Python appears exactly where it should: executing your code.
MarkerNebula/
├── package.json # notebook type, board renderer, commands
├── src/
│ ├── extension.ts # activation, commands, board-edit persistence
│ ├── ipywbTypes.ts # the .ipywb JSON format types
│ ├── ipywbSerializer.ts # .ipywb JSON <-> vscode.NotebookData
│ ├── controllers.ts # kernels for the native kernel picker
│ └── kernelClient.ts # talks to kernel_bridge.py (spawn + JSON pipes)
├── webview/
│ └── boardRenderer.js # the drawing board (notebook output renderer)
├── python/
│ ├── kernel_bridge.py # owns a real ipykernel via jupyter_client
│ └── requirements.txt
└── examples/
└── sample.ipywb
Execution flow: each open .ipywb gets its own kernel_bridge.py process,
which launches a genuine ipykernel on the same interpreter you picked in
the kernel picker and streams outputs back over line-delimited JSON. State
persists across cells; interrupt uses the same Win32-event/SIGINT machinery
as Jupyter itself.
Picking a kernel (like ipynb)
Click Select Kernel in the notebook toolbar. You'll see:
- MarkerNebula Python — uses the
markernebula.pythonPathsetting. - One entry per interpreter/venv discovered by the ms-python extension (if installed) — conda envs, venvs, system Pythons.
Whichever you pick must have the bridge deps installed:
pip install jupyter_client ipykernel.
The .ipywb file format
Plain JSON, in the spirit of .ipynb but with whiteboard cells (markdown
cells are also supported if you add one via the native "+ Markdown" button):
{
"metadata": { "name": "...", "kernelspec": {...}, "ipywb_version": "1.0" },
"cells": [
{ "id": "...", "type": "whiteboard", "canvas": { "width": 1600, "height": 1000, "strokes": [...] } },
{ "id": "...", "type": "code", "source": "...", "outputs": [...], "execution_count": 1 }
]
}
Strokes are stored as vector points with pressure, not raster pixels — crisp at any size, small diffs in git. Boards grow with the W/H buttons on each board's toolbar (an effectively unbounded page).
Adding board cells
Several ways, pick your favourite:
- "+ Board above" / "+ Board below" — always visible in the status bar of every cell (bottom edge of the cell, next to the language indicator). The built-in between-cell "+ Code / + Markdown" hover bar is not extensible by extensions, so this is the equivalent for boards.
- the + Board button in the notebook toolbar (top of the editor),
- hover any cell and click the paint-can icon in its cell toolbar ("Insert Board Below"),
- Ctrl+Alt+B (Cmd+Alt+B on Mac),
- "MarkerNebula: Insert Board Cell" from the command palette.
Drawing is flicker-free: strokes are stored via cell-metadata updates that never re-render the cell. Every board also has its own Undo / Redo buttons (stroke-level), and notebook-level Ctrl+Z tracks strokes too.
Boards are self-healing: strokes live in cell metadata, not in the output, so "Clear All Outputs" only clears code cell results — if anything removes a board's on-screen canvas, the extension instantly rebuilds it from the stored strokes. A board can only be removed by deleting its cell.
Added a markdown cell by accident with the native "+ Markdown" button? Hover it and click Convert to Whiteboard in its cell toolbar. And if VS Code creates a "+ Code" cell with the board language (it copies the language of the neighbouring cell), the extension automatically rewrites it to Python so it always runs.
Setting it up locally
npm installpip install -r python/requirements.txt(into whichever Python your cells should run on — e.g. the bundled.venv)npm run compile- Press F5 to launch an Extension Development Host, then open
examples/sample.ipywbthere.
If python isn't the interpreter you want, set markernebula.pythonPath,
or just install the ms-python extension and pick any environment from the
kernel picker.
Publishing
npm install -g @vscode/vsce
vsce package # produces marker-nebula-0.2.0.vsix
vsce publish # requires a Marketplace publisher account
Known limitations
- On Windows, interrupting a cell only takes effect between Python
statements — blocking C calls (e.g. a single long
time.sleep, heavy native NumPy ops) can't be broken mid-call. This is a Python/Jupyter platform limitation; real Jupyter behaves the same. Use MarkerNebula: Restart Kernel if a cell is truly stuck. - Exporting a board to PNG/SVG is not implemented yet.