A modal Jupyter notebook editor for Neovim.
Important
ipynb.nvim is currently in alpha. There will be bugs!
- Edit
.ipynbfiles as actual notebooks with isolated cell buffers - Cell outputs render inline as virtual lines (open in float to copy)
- Inline image rendering (PNG, JPEG, SVG, etc.)
- Variable inspector with auto-hover (uses Jupyter inspect protocol)
- Partial language server support (diagnostics, completion, go to definition, rename)
- Multi-language support (Python, Julia, R, and more)
- Treesitter highlighting with dynamic language injection
The plugin uses a modal editing approach:
- Notebook mode: for navigating and managing cells
- Cell mode: for editing cell content in an isolated buffer
Under the hood, the notebook is rendered into a single buffer for display. Entering Cell mode opens an isolated buffer for just the active cell, and edits sync back to the notebook buffer. In parallel, all code cells are mirrored into a shared “shadow” buffer that LSP requests are proxied through. This design aims to preserve the feel of Jupyter notebooks.
- Neovim 0.10+
- nvim-treesitter
- Python 3.x with
jupyter_client(for kernel execution)
Optional (but highly recommended):
- nvim-lspconfig for LSP support (completion, diagnostics, etc.)
- nvim-web-devicons for language icons in cell borders
- snacks.nvim for inline image rendering
- a terminal that fully supports the kitty graphics protocol (e.g., kitty, Ghostty)
- ImageMagick required to display non-PNG image formats
Run :checkhealth ipynb to verify your setup.
{
"ajbucci/ipynb.nvim",
dependencies = {
"nvim-treesitter/nvim-treesitter",
"neovim/nvim-lspconfig",
-- "nvim-tree/nvim-web-devicons", -- optional, for language icons
-- "folke/snacks.nvim", -- optional, for inline images
},
opts = {},
}The treesitter parser is automatically compiled on first load.
- Add the plugin to your runtime path
- Call
require("ipynb").setup()(parser auto-compiles on first load)
nvim notebook.ipynb" or from in neovim:
:e notebook.ipynb
" or
:NotebookCreate notebook.ipynb - Place your cursor anywhere on a cell (you can quickly navigate using
]](next) and[[(previous), though any vim motions will work) - Press
i(Insert) or<CR>(Normal) to enter Cell mode for the current cell - Press
<Esc>to exit Cell mode and return to Notebook mode
- Start a Jupyter kernel with
:NotebookKernelStart(default keymap:<leader>ks)
- Execute a cell with
:NotebookExecuteCell(default keymap:<leader>kx)
- Outputs render inline as virtual lines below the cell
- Use
:NotebookOutput(default keymap:<leader>ko) to open the output in a floating window for easy copying
- Place your cursor on a variable and inspect it using
:NotebookInspect(default keymap:<leader>kh) - Inspect all variables in cell using
:NotebookInspectCell(default keymap:<leader>kv)
When starting a kernel, the plugin looks for Python in this order:
- Path argument passed to
:NotebookKernelStart kernel.python_pathin config- Virtual environment found by walking up from the notebook's directory, checking for
.venv,venv,.virtualenv, orenv - System Python (
python3orpythonin PATH)
" Use a specific Python for this session
:NotebookKernelStart /path/to/venv/bin/python
" Or configure a default in setuprequire("ipynb").setup({
kernel = {
python_path = "/path/to/your/venv/bin/python",
},
})Warning
R.nvim is unsupported! Please disable it when using this plugin.
The plugin reads the language from notebook metadata and automatically configures:
- Syntax highlighting via tree-sitter language injection
- LSP by setting the shadow buffer's filetype (triggers your LSP config)
- Kernel execution via
jupyter_client(supports any installed Jupyter kernel)
The default language when creating a new notebook is Python. You can specify a kernel when creating:
:NotebookCreate mynotebook julia-1.10
:NotebookCreate analysis ir " R kernelOr change an existing notebook's kernel with :NotebookSetKernel:
:NotebookSetKernel julia-1.12
:NotebookSetKernel ir " R kernel
:NotebookSetKernel python3This updates the notebook metadata and re-attaches the appropriate LSP.
Requirements per language:
- Install the Jupyter kernel (e.g.,
IJuliafor Julia,IRkernelfor R) - Install an LSP server (e.g.,
julials,r_language_server) - Configure the LSP in your Neovim setup
ipynb.nvim is highly configurable. Expand to see the default configuration below.
Default Options
require("ipynb").setup({
keymaps = {
-- Navigation (Notebook mode)
next_cell = "]]", -- jump to next cell
prev_cell = "[[", -- jump to previous cell
-- Navigation (both Notebook mode and Cell mode)
jump_to_cell = "<leader>kj", -- open cell picker
-- Cell operations (Notebook mode)
cut_cell = "dd", -- cut cell to register
paste_cell_below = "p", -- paste cell below
paste_cell_above = "P", -- paste cell above
move_cell_down = "<M-j>", -- move cell down
move_cell_up = "<M-k>", -- move cell up
-- Cell operations (both Notebook mode and Cell mode)
add_cell_above = "<leader>ka", -- insert cell above
add_cell_below = "<leader>kb", -- insert cell below
make_markdown = "<leader>km", -- convert to markdown cell
make_code = "<leader>ky", -- convert to code cell
make_raw = "<leader>kr", -- convert to raw cell
fold_toggle = "<leader>kf", -- toggle cell fold
-- Execution (both Notebook mode and Cell mode)
execute_cell = "<C-CR>", -- execute cell, stay
execute_and_next = "<S-CR>", -- execute cell, move to next
execute_and_insert = "<M-CR>", -- execute cell, insert new below
execute_all_below = nil, -- execute current and all below (unmapped)
menu_execute_cell = "<leader>kx", -- execute cell (menu, if <C-CR> conflicts)
menu_execute_and_next = "<leader>kX", -- execute and next (menu, if <S-CR> conflicts)
-- Output (both Notebook mode and Cell mode)
open_output = "<leader>ko", -- open cell output in float (for copying)
clear_output = "<leader>kc", -- clear current cell output
clear_all_outputs = "<leader>kC", -- clear all outputs
-- Kernel (both Notebook mode and Cell mode)
interrupt_kernel = "<C-c>", -- interrupt execution
kernel_interrupt = "<leader>ki", -- interrupt (menu)
kernel_restart = "<leader>k0", -- restart kernel
kernel_start = "<leader>ks", -- start kernel
kernel_shutdown = "<leader>kS", -- shutdown kernel
kernel_info = "<leader>kn", -- show kernel info
-- Inspector (both Notebook mode and Cell mode)
variable_inspect = "<leader>kh", -- inspect variable at cursor
cell_variables = "<leader>kv", -- show all variables in cell
toggle_auto_hover = "<leader>kH", -- toggle auto-hover on CursorHold
-- Note: i, a, I, A, o, O, <CR> enter Cell mode; <Esc> exits Cell mode
-- Note: u, <C-r> perform global undo/redo across cells in both Notebook mode and Cell mode
-- Note: <C-j>/<C-k> navigate cells while in Cell mode
-- Note: LSP commands (go to definition, references, hover, etc.) are proxied
},
-- Highlight groups for various notebook elements
highlights = {
-- For notebook
border = "Comment", -- Cell border
border_hover = "Special", -- Cell border when cursor on cell
border_active = "Number", -- Cell border when in Cell mode
exec_count = "Number", -- Execution count [N]
output = "Comment", -- Output text
hint = "Comment", -- Cell action keymap hints
-- For statusline
output_error = "DiagnosticError",
executing = "DiagnosticWarn", -- Executing indicator
queued = "DiagnosticHint", -- Queued indicator
},
-- Cell border action keymap hints
border_hints = {
enabled = true, -- Show action hints on active cell border
show_on_hover = true, -- Show hints in Notebook mode
show_on_edit = true, -- Show hints in Cell mode
},
kernel = {
auto_connect = false, -- Auto-connect to kernel on notebook open
show_status = true, -- Show kernel status in statusline
python_path = nil, -- Custom Python path (otherwise auto-discovered)
},
images = {
enabled = true,
cache_dir = vim.fn.stdpath("cache") .. "/ipynb.nvim",
max_width = nil, -- nil = window width minus sign/number columns
max_height = nil, -- nil = window height minus scrolloff minus 1
},
inspector = {
-- Keymaps while in cell variable inspector float window
close = { "q", "<Esc>" }, -- Keys to close inspector window
inspect = { "K", "<CR>" }, -- Keys to inspect variable under cursor
-- Auto-hover inspect configuration
auto_hover = {
enabled = false, -- Auto-show variable hover on CursorHold
delay = 500, -- Milliseconds before showing hover
},
},
-- Cell folding configuration
folding = {
hide_output = false, -- Hide output when folded (includes cell end marker in fold)
},
-- LSP formatting configuration -- vim.lsp.buf.format() integration
format = {
enabled = true, -- Wrap vim.lsp.buf.format() to work with notebooks
-- Each cell is formatted as an individual document
-- with some formatters adding trailing blank lines.
-- This option control how many blank lines to keep:
trailing_blank_lines = 0, -- # trailing blank lines to keep per cell when formatted
},
-- Shadow buffer location (use "workspace" if LSP needs project/module layout)
shadow = {
location = "temp", -- "temp" (default) or "workspace"
dir = ".ipynb.nvim", -- subdir used when location = "workspace" (opt-in)
},
})| Command | Description | Config | Default Keymap |
|---|---|---|---|
:NotebookCreate [path] [kernel] |
Create new notebook | ||
:NotebookSave |
Save notebook | ||
:NotebookInfo |
Show notebook and cell info | ||
:NotebookJumpToCell |
Open cell picker | jump_to_cell |
<leader>kj |
:NotebookInsertCellBelow |
Insert cell below | add_cell_below |
<leader>kb |
:NotebookInsertCellAbove |
Insert cell above | add_cell_above |
<leader>ka |
:NotebookDeleteCell |
Delete current cell | ||
:NotebookCutCell |
Cut cell to register | cut_cell |
dd |
:NotebookPasteCellBelow |
Paste cell below | paste_cell_below |
p |
:NotebookPasteCellAbove |
Paste cell above | paste_cell_above |
P |
:NotebookMoveCellUp |
Move cell up | move_cell_up |
<M-k> |
:NotebookMoveCellDown |
Move cell down | move_cell_down |
<M-j> |
:NotebookToggleCellType |
Toggle code/markdown | ||
:NotebookMakeMarkdown |
Convert to markdown | make_markdown |
<leader>km |
:NotebookMakeCode |
Convert to code | make_code |
<leader>ky |
:NotebookMakeRaw |
Convert to raw | make_raw |
<leader>kr |
:NotebookExecuteCell |
Execute cell, stay | execute_cell |
<C-CR> |
:NotebookExecuteAndNext |
Execute cell, move next | execute_and_next |
<S-CR> |
:NotebookExecuteAllBelow |
Execute current and below | execute_all_below |
(unmapped) |
:NotebookExecuteAll |
Execute all code cells | ||
:NotebookOutput |
Open output in float | open_output |
<leader>ko |
:NotebookClearOutput |
Clear cell output | clear_output |
<leader>kc |
:NotebookClearAllOutputs |
Clear all outputs | clear_all_outputs |
<leader>kC |
:NotebookFoldCell |
Toggle cell fold | fold_toggle |
<leader>kf |
:NotebookFoldAll |
Fold all cells | ||
:NotebookUnfoldAll |
Unfold all cells | ||
:NotebookFormatCell |
Format cell via LSP | ||
:NotebookFormatAll |
Format all code cells | ||
:NotebookKernelStart [path] |
Start kernel | kernel_start |
<leader>ks |
:NotebookKernelConnect <file> |
Connect to kernel | ||
:NotebookSetKernel <name> |
Set kernelspec | ||
:NotebookListKernels |
List available Jupyter kernels | ||
:NotebookKernelInterrupt |
Interrupt execution | interrupt_kernel |
<C-c> |
:NotebookKernelRestart |
Restart kernel | kernel_restart |
<leader>k0 |
:NotebookKernelShutdown |
Shutdown kernel | kernel_shutdown |
<leader>kS |
:NotebookKernelStatus |
Show kernel status | kernel_info |
<leader>kn |
:NotebookInspect |
Inspect variable at cursor | variable_inspect |
<leader>kh |
:NotebookInspectCell |
Show all cell variables | cell_variables |
<leader>kv |
:NotebookToggleAutoHover |
Toggle auto-hover | toggle_auto_hover |
<leader>kH |
Note: vim.lsp.buf.format() is wrapped to work with notebooks automatically.
Disable with format.enabled = false.
The plugin provides a statusline component showing kernel status. Add it to your statusline (e.g., lualine):
-- lualine example
require("lualine").setup({
sections = {
lualine_x = {
{
require("ipynb.kernel").statusline,
cond = require("ipynb.kernel").statusline_visible,
color = require("ipynb.kernel").statusline_color,
},
},
},
})Shows language icon (from nvim-web-devicons) + status with theme-aware colors:
IDLE(green, fromDiagnosticOk)BUSY(yellow, fromDiagnosticWarn)DISC(red, fromDiagnosticError)
Disable with kernel.show_status = false in setup.
Health check
Run :checkhealth ipynb to verify all dependencies are properly configured.
Parser not found
The parser should auto-compile on first load. If it fails:
- Ensure nvim-treesitter is installed
- Run
:TSInstall! ipynbto manually compile
No LSP
Ensure you have a language server installed for the notebook's language.
Check :LspInfo while in the notebook to see attached clients.
Kernel won't start
Check :NotebookKernelStatus for the Python path being used.
Ensure jupyter_client is installed: pip install jupyter_client
For non-Python kernels, ensure the kernel is installed (e.g., IJulia, IRkernel).
Images not showing
Requires snacks.nvim and a terminal which fully supports the kitty graphics protocol (kitty, Ghostty). For tmux, set allow-passthrough=on.
✅ Working:
- Read/write .ipynb files
- Cell navigation and editing
- Kernel execution and output capture
- Inline image rendering
- Variable inspector (Jupyter inspect protocol, auto-hover)
- Partial LSP support (diagnostics, completion, hover, definition, references, rename, formatting, document symbols, signature help, document highlight, inlay hints)
- Multi-language support (Python, Julia, R, etc.)
- Cell folding
- Cell formatting via LSP (
:NotebookFormatCell,:NotebookFormatAll, orvim.lsp.buf.format()) - Health check (
:checkhealth ipynb) - Code lens (
textDocument/codeLens) — server/project dependent (may requireshadow.location = "workspace") - Call hierarchy (
callHierarchy/*) — requires symbols in a project/module (may requireshadow.location = "workspace") - Type hierarchy (
typeHierarchy/*) — requires symbols in a project/module (may requireshadow.location = "workspace") - Selection range (
textDocument/selectionRange) — requires Neovim 0.12+
🚧 Not Tested (may or may not work):
- Semantic tokens (
textDocument/semanticTokens) - Document links (
textDocument/documentLink)
🚫 Not Supported:
- Code actions (
textDocument/codeAction) - code actions can modify arbitrary ranges potentially spanning cell boundaries, making them tricky to implement in this notebook architecture