Edit Jupyter .ipynb notebooks in Neovim as if they were native Python
buffers — with rendered cell outputs, real LSP/copilot/treesitter, and proper
round-tripping to disk. No otter, no quarto, no temp files, no lost outputs.
.ipynb on disk (JSON)
│ BufReadCmd ── jupytext CLI via stdio (no temp file) ──▶
buffer buftype=acwrite, filetype=python
│ pyright/ruff/copilot/treesitter attach natively
│ molten attaches → kernel + inline outputs
│ BufWriteCmd ── jupytext --update via stdio ──▶
.ipynb on disk (JSON, outputs preserved)
- Neovim ≥ 0.10
jupytexton$PATHmolten-nvim— optional but required for kernel execution and rich outputsnvim-various-textobjs— optional, foric/accell text-objects
{
"nghiant03/jove.nvim",
lazy = false, -- BufReadCmd must be registered before .ipynb is opened
dependencies = { "benlubas/molten-nvim" },
opts = {
auto_kernel = true, -- MoltenInit from kernelspec on open
auto_import_outputs = true, -- restore outputs from .ipynb JSON
auto_export_outputs = true, -- merge molten outputs back on :w
keymap = {
run_cell = "<leader>x",
next_cell = "]h",
prev_cell = "[h",
},
},
}
opts = {}(orconfig = true) is required — without it lazy.nvim never callsrequire("jove").setup(...), and the keymaps underkeymap = {}are not registered. TheBufReadCmd/BufWriteCmdhandlers are installed fromplugin/jove.luaat startup either way, but you'll lose the cell motions andrun_cellbinding.
pip install jupytext # or: pipx install jupytext / conda install jupytext
jupytext --version # must be on $PATH for the nvim processIf you launch Neovim from a conda env that doesn't have jupytext, prepend
the env's bin/ to vim.env.PATH early in your init.lua, or set
opts.jupytext = "/abs/path/to/jupytext".
Molten provides the kernel and inline outputs. Without it jove still edits
notebooks, but :JoveRunCell and outputs do nothing. Minimal lazy spec:
{
"benlubas/molten-nvim",
build = ":UpdateRemotePlugins",
init = function()
vim.g.molten_image_provider = "image.nvim" -- or "snacks.nvim"
vim.g.molten_auto_open_output = false
vim.g.molten_virt_text_output = true
vim.g.molten_virt_lines_off_by_1 = true -- correct for `# %%` cells
vim.g.molten_wrap_output = true
end,
}You also need a Jupyter kernel that matches your notebook's
metadata.kernelspec.name — typically python3 from ipykernel:
pip install ipykernel
python -m ipykernel install --user --name python3Verify inside nvim with :lua =vim.fn.MoltenAvailableKernels().
- Do not load
jupytext.nvimalongside jove — both registerBufReadCmdon*.ipynb. Jove will hard-refuse and warn on startup; remove one. - Do not duplicate molten's auto-init autocmds. If you copied the
BufAdd *.ipynb→MoltenInit+MoltenImportOutputsnippet from molten's README, delete it: jove already does this whenauto_kernel = trueandauto_import_outputs = true. Otherwise molten initializes twice and logs errors.
:checkhealth joveExpect green checks for: Neovim ≥ 0.10, jupytext binary found, molten
detected, no jupytext.nvim conflict. Then open any .ipynb:
nvim notebook.ipynbYou should land in a Python buffer split into # %% cells, with kernel
status visible via molten and outputs rendered as virtual text.
| Command | Action |
|---|---|
:JoveRunCell |
Run cell under cursor via molten |
:JoveRunAbove |
Run all cells from top to cursor |
:JoveRunAll |
Run every cell |
:JoveNextCell / :JovePrevCell |
Jump between cells |
:JoveInitKernel |
Start molten kernel for this notebook |
:JoveImportOutputs |
Re-display outputs stored in the .ipynb JSON |
:JoveExportOutputs |
Merge current molten outputs into .ipynb on disk |
:checkhealth jove |
Verify deps, versions, and conflicts |
| jove.nvim | jupytext.nvim | quarto-nvim + otter | jupynium.nvim | |
|---|---|---|---|---|
| Native python LSP | yes | yes | proxy/chunked | yes |
| Inline outputs | via molten | no | via molten | via browser |
| No temp files | yes | no (writes sidecar) | yes | yes |
| Output persistence | yes (JSON merge) | no | manual | yes |
| Browser required | no | no | no | yes |
| Scope | small | small | wide (.qmd) |
wide |
- Markdown prose rendering between cells (markdown cells stay as
# %% [markdown]comment blocks). - Browser sync à la jupynium.
- A custom Jupyter kernel client (delegated to molten).
- Reimplementing
jupytextformat conversions in Lua.
MIT