Zero-configuration Python notebook development for Neovim
A Neovim plugin that provides automatic environment setup, package detection, and code execution for Python projects and Jupyter notebooks.
⚠️ Active Development: This plugin is under active development and may introduce breaking changes between versions. Check the CHANGELOG for migration guides when updating.
- Zero Configuration - Just open files and start coding
- Auto Environment Setup - Creates and manages virtual environments automatically
- Smart Package Detection - Detects and installs missing packages from imports
- Jupyter Notebook Support - Edit .ipynb files as naturally as .py files
- Molten Integration - Execute code cells with Jupyter-like experience
- Inline Plots - Display matplotlib output directly in Neovim
- Cell Folding & Numbering - Collapse/expand cells and see cell numbers inline
- Execution Status Tracking - Cell numbers turn green when executed, red when unrun
- Project-Based Activation - Only runs in actual project directories
- Neovim ≥ 0.10.0 (uses
vim.system()andvim.uvAPIs) - Python 3.8+
jupytextCLI (automatically installed by pyworks)- Optional:
uvfor faster package management (10-100x faster than pip)
With lazy.nvim
Create ~/.config/nvim/lua/plugins/pyworks.lua:
return {
{
"jeryldev/pyworks.nvim",
config = function()
require("pyworks").setup() -- See Configuration section for options
end,
lazy = false,
},
}Dependencies (jeryldev/molten-nvim, jeryldev/image.nvim) are declared in pyworks' lazy.lua and installed automatically.
Pyworks automatically:
- Handles .ipynb files directly - Uses jupytext CLI to convert notebooks to percent-style Python (
# %%) - Detects jupytext CLI - Checks PATH and common venv locations
- Provides graceful fallback - If jupytext CLI isn't installed, notebooks open as read-only JSON with helpful messages guiding you to run
:PyworksSetup
For inline plot/image display:
- Kitty terminal (recommended) -
brew install --cask kitty - Ghostty terminal (alternative) - Supports Kitty graphics protocol
" Create Python file with cells
:PyworksNewPython analysis
" → Creates analysis.py with cell markers
" Create Jupyter notebook
:PyworksNewPythonNotebook report
" → Creates report.ipynbCreating either one sets up .venv and installs the essentials, which include
jupyterlab. The same project therefore opens in the browser without extra
setup — the kernel is the venv's own ipykernel, so Neovim and the browser
share one environment:
.venv/bin/jupyter lab # or: source .venv/bin/activate && jupyter lab-
Create a notebook:
:PyworksNewPython ml_model
-
Write code (cells marked with
# %%):# %% import numpy as np import pandas as pd # %% data = pd.read_csv('data.csv') print(data.head())
-
Execute code:
<leader>jl- Run current line (auto-initializes kernel on first use)<leader>jj- Run cell and move to next (Shift+Enter in Jupyter)
-
Create new cells:
<leader>ja/jb/jma/jmb(above/below, code/markdown) -
Package management:
- Missing packages detected automatically
- Press
<leader>pito install missing packages - Or use
:PyworksAdd numpy pandas matplotlib
| Keymap | Mode | Description |
|---|---|---|
<leader>jl |
Normal | Run current line (auto-inits kernel) |
<leader>jr |
Visual | Run selection |
<leader>jj |
Normal | Run cell and move to next |
<leader>jk |
Normal | Run cell without moving cursor (stay in place) |
<leader>jR |
Normal | Run all cells sequentially (waits for each) |
| Keymap | Mode | Description |
|---|---|---|
<leader>jv |
Normal | Visual select current cell |
<leader>j] |
Normal | Next cell |
<leader>j[ |
Normal | Previous cell |
<leader>jg |
Normal | Go to cell N (works without running cells) |
| Keymap | Description |
|---|---|
<leader>jd |
Clear cell output |
| Keymap | Description |
|---|---|
<leader>ja |
Insert code cell above |
<leader>jb |
Insert code cell below |
<leader>jma |
Insert markdown cell above |
<leader>jmb |
Insert markdown cell below |
| Keymap | Description |
|---|---|
<leader>jt |
Toggle cell type (code ↔ markdown) |
<leader>jJ |
Merge with cell below |
<leader>js |
Split cell at cursor |
| Keymap | Description |
|---|---|
<leader>jf |
Toggle cell folding on/off |
<leader>jc |
Collapse current cell |
<leader>jC |
Collapse all cells |
<leader>je |
Expand current cell |
<leader>jE |
Expand all cells |
<leader>jn |
Refresh cell numbers |
| Keymap | Description |
|---|---|
<leader>mi |
Initialize kernel |
<leader>mr |
Restart kernel |
<leader>mx |
Interrupt execution |
<leader>mI |
Show kernel info |
| Keymap | Description |
|---|---|
<leader>pi |
Install missing packages |
<leader>ps |
Show package status |
For skip_keymaps users who prefer command-based workflows:
| Command | Description |
|---|---|
:PyworksNextCell |
Move to next cell |
:PyworksPrevCell |
Move to previous cell |
:PyworksInsertCellAbove |
Insert code cell above |
:PyworksInsertCellBelow |
Insert code cell below |
:PyworksToggleCellType |
Toggle cell type (code/markdown) |
:PyworksMergeCellBelow |
Merge with cell below |
:PyworksSplitCell |
Split cell at cursor |
:PyworksRunCell |
Run the current cell |
:PyworksRunCellAdvance |
Run the current cell and advance |
| Command | Description |
|---|---|
:PyworksNewPython [name] |
Create Python file with cells |
:PyworksNewPythonNotebook [name] |
Create Python Jupyter notebook |
| Command | Description |
|---|---|
:PyworksSetup |
Create venv and install essential packages |
:PyworksStatus |
Show package status (imports/installed/missing) |
:PyworksDiagnostics |
Run diagnostics (environment, plugins, cache) |
:PyworksHelp |
Show all commands and keymaps |
:PyworksReloadNotebook |
Reload notebook (useful after session restore) |
| Command | Description |
|---|---|
:PyworksSync |
Install missing packages from imports |
:PyworksAdd <packages> |
Add packages to venv |
:PyworksRemove <packages> |
Remove packages from venv |
:PyworksList |
List all installed packages |
All settings are optional. Here are the available options with their defaults:
require("pyworks").setup({
-- Python environment settings
python = {
use_uv = true, -- Use uv for faster package management (10-100x faster than pip)
preferred_venv_name = ".venv",
auto_install_essentials = true,
-- jupytext opens .ipynb in Neovim; jupyterlab lets you open the same
-- project in the browser with `.venv/bin/jupyter lab`
essentials = {
"pynvim", "ipykernel", "jupyter_client", "jupytext", "jupyterlab",
"numpy", "pandas", "matplotlib",
},
},
-- Package detection settings
packages = {
-- Patterns for detecting custom/local packages (won't suggest installing these)
custom_package_prefixes = {
"^my_", "^custom_", "^local_", "^internal_", "^private_",
"^app_", "^lib_", "^src$", "^utils$", "^helpers$",
},
},
-- Cache TTL in seconds
cache = {
kernel_list = 60,
installed_packages = 300,
},
-- Notification settings
notifications = {
verbose_first_time = true,
silent_when_ready = true,
show_progress = true,
debug_mode = false,
},
-- Cell delimiter pattern (e.g. "# COMMAND ----------" for Databricks)
cell_marker = "# %%",
-- Auto-detection
auto_detect = true, -- Automatically detect and setup on file open
-- Image rendering (for inline plots)
image_backend = "kitty", -- "kitty" or "ueberzug"
-- Skip auto-configuration of specific features (all default to false)
skip_molten = false, -- Skip Molten configuration
skip_jupytext = false, -- Skip jupytext setup (set true if using jupytext.nvim)
skip_image = false, -- Skip image.nvim configuration
skip_keymaps = false, -- Skip keymap setup (define your own)
})- Open any Python file - pyworks detects file type
- Automatic setup - Creates environment, installs essentials
- Package detection - Scans imports, shows missing packages
- Kernel initialization - Auto-starts Python kernel
- Ready to code - Use keymaps to execute code immediately
Pyworks uses a maintained fork (jeryldev/molten-nvim) that includes bug fixes for dict iteration safety and MoltenTick reentrancy, baked directly into the source. No runtime patching is needed.
Pyworks finds your project root by looking for these markers (in priority order):
.venv- Virtual environment (highest priority)pyproject.toml,setup.py,requirements.txt- Python project filesmanage.py,app.py,main.py- Framework entry points (Django, Flask, FastAPI)Pipfile,poetry.lock,uv.lock- Package manager lock filesconda.yaml,environment.yml- Conda environmentsdvc.yaml,mlflow.yaml- ML pipeline configs.git- Git repository (lowest priority fallback)
For virtual environment detection, pyworks also respects environment variables:
- Local
.venvdirectory (highest priority) $VIRTUAL_ENVenvironment variable$CONDA_PREFIXenvironment variable- Fallback to creating a new
.venv
- Auto-detects package manager - Uses
uvif available (10-100x faster), falls back topip - Handles compatibility - Skips packages incompatible with your Python version
- Filters intelligently:
- Excludes standard library modules (os, sys, base64, etc.)
- Ignores custom/local packages
- Only suggests real PyPI packages
plugin/pyworks.lua Entry point: autocmds (FileType, BufWinEnter, SessionLoadPost)
└── lua/pyworks/
├── init.lua Setup, user commands, configuration
├── dependencies.lua Dependency checks
├── keymaps.lua Cell execution, navigation, kernel management
├── ui.lua Cell numbering, folding, floating windows
├── utils.lua Project root detection, venv paths, system calls
├── core/
│ ├── cache.lua TTL-based in-memory cache
│ ├── cell_engine.lua Cell navigation, manipulation, folding
│ ├── detector.lua File routing, kernel auto-init
│ ├── error_handler.lua Protected calls, filepath validation
│ ├── notifications.lua Deduped user notifications
│ ├── packages.lua Import scanning, missing package detection
│ ├── recursion_guard.lua Prevents reload loops
│ └── state.lua Persistent state (JSON on disk)
├── languages/
│ └── python.lua Venv management, pip/uv commands, package ops
├── notebook/
│ └── jupytext.lua .ipynb read/write via jupytext CLI
└── commands/
└── create.lua :PyworksNewPython, :PyworksNewPythonNotebook
plugin/pyworks.luaregistersFileType pythonautocmd- On first Python file open:
require("pyworks").setup()runs dependencies.setup()defers (100ms) to check molten-nvim, image.nvimdetector.on_file_open()routes to Python handler- Python handler: setup venv, detect packages, auto-init Molten kernel
Q: :PyworksDiagnostics command not found
A: Fixed in a past release — update to the latest version. The command was previously registered in a module that wasn't loaded during startup.
Q: "No kernel initialized" warning when using <leader>jj
A: Press <leader>jl first to auto-initialize the kernel for the current file type.
Q: Jupytext command not found
A: Pyworks adds .venv/bin to PATH automatically. Run :PyworksSetup to install jupytext.
Q: Images open in external viewer A: Fixed by default. Ensure you're using Kitty or Ghostty terminal.
Q: Matplotlib opens external window
A: Don't use plt.show(). Just create the plot and let Molten capture it.
Q: Notebook appears blank after session restore
A: Run :PyworksReloadNotebook to reload with jupytext conversion.
Q: E132 "Function call depth" error or notebook won't reload
A: Run :PyworksResetReloadGuard to reset the reload guard, then retry.
Enable debug mode to see what's happening:
require("pyworks").setup({
notifications = { debug_mode = true }
})Or temporarily:
:lua vim.g.pyworks_debug = true| Plugin | Purpose | Required |
|---|---|---|
molten-nvim |
Code execution | Yes (for notebooks) |
image.nvim |
Display plots inline | Yes (for plots) |
Note: pyworks.nvim handles .ipynb files directly using the jupytext CLI (automatically installed as a Python package). No separate jupytext.nvim plugin is required.
If you previously used jupytext.nvim, pyworks now handles notebooks directly:
- Remove jupytext.nvim from your plugin dependencies
- Pyworks will auto-detect and warn if jupytext.nvim is still installed
- If you prefer jupytext.nvim, set
skip_jupytext = truein your pyworks config
MIT License - see LICENSE for details
- molten-nvim - Jupyter integration for Neovim (fork of benlubas/molten-nvim with bug fixes)
- image.nvim - Inline image rendering (fork of 3rd/image.nvim)
- jupytext - Jupyter notebook conversion CLI
- uv - Lightning-fast Python package management