Minimal helper to run IPython/Jupyter in a terminal split and send code from the current buffer, tuned for Neovim 0.11+.
demo captures the day-to-day workflow: Neovim buffers on the left, the IPython console on the right. Cells run with runfile()/runcell() helpers, and the console echoes section markers so you can see each phase finish. While the script executes, a preview overlay lists numpy arrays, dataclasses, and ctypes structures so you can drill into live data without leaving the editor.

demo_debug dives into the debugger integration:
debugfile, so this plugin natively supports step-by-step debugging.ipdb come from nvim-cmp, giving you the same rich suggestions you rely on during normal editing.
nvim-cmp (used to render TAB completion inside ipdb)jupyter (for jupyter console)ipykernel, jupyter_client, pyzmq (for variable explorer / preview)ipython (for the console experience){
"ok97465/ipybridge.nvim",
config = function()
require("ipybridge").setup({
profile_name = "vim", -- or nil to omit --profile
startup_script = "import_in_console.py", -- looked up in CWD
sleep_ms_after_open = 1000, -- defer init to allow IPython to start
set_default_keymaps = true, -- applies by default (can set false)
-- Matplotlib backend / ion
matplotlib_backend = nil, -- 'qt'|'tk'|'macosx'|'inline' or 'QtAgg'|'TkAgg'|'MacOSX'
matplotlib_ion = true, -- call plt.ion() on startup
-- Spyder-like runcell support
prefer_runcell_magic = false, -- run cells via helper instead of raw text
runcell_save_before_run = true, -- save buffer before runcell to use up-to-date file
runfile_save_before_run = true, -- save buffer before runfile to use up-to-date file
debugfile_save_before_run = true, -- save buffer before debugfile to use up-to-date file
-- Variable explorer / preview (ZMQ backend)
use_zmq = true, -- requires ipykernel + jupyter_client + pyzmq
viewer_max_rows = 30,
viewer_max_cols = 20,
-- Autoreload: 1, 2, or 'disable' (default 2)
autoreload = 2,
})
end,
}
profile_name (string|nil): IPython profile passed as --profile=<name>. If nil, the flag is omitted.startup_script (string): If this file exists under current working directory, ipython -i <startup_script> is used; otherwise the plugin sends a minimal set of numeric imports automatically.sleep_ms_after_open (number): Milliseconds to wait (non-blocking) before running initial setup such as plt.ion().set_default_keymaps (boolean, default: true): Apply buffer-local keymaps for Python files only.matplotlib_backend (string|nil): 'qt'|'tk'|'macosx'|'inline' via IPython magic, or backend name 'QtAgg'|'TkAgg'|'MacOSX' via matplotlib.use().matplotlib_ion (boolean): If true, plt.ion() is called on startup (default true).prefer_runcell_magic (boolean): If true, run cells via an IPython helper (runcell(index, path) / %runcell).runcell_save_before_run (boolean): Save the buffer before runcell execution (default true).runfile_save_before_run (boolean): Save the buffer before runfile execution (default true).debugfile_save_before_run (boolean): Save the buffer before %debugfile execution (default true).exec_cwd_mode (string): Working directory behavior for run_cell / run_file.'file': change directory to the current file's directory before executing'pwd': change directory to Neovim's getcwd() (default)'none': do not change directoryuse_zmq (boolean): Enable ZMQ backend for variable explorer/preview (default true). Requires ipykernel, jupyter_client, pyzmq.viewer_max_rows / viewer_max_cols (numbers): DataFrame/ndarray preview limits.simple_prompt (boolean): Use simplified prompt in Jupyter console. Defaults to false for richer colors/UI.ipython_colors (string|nil): Color scheme applied via IPython's %colors magic (e.g., Linux, LightBG, NoColor). Some jupyter-console versions ignore CLI flags; this runtime magic is used for portability.hidden_var_names (string[]): Variable names to hide in the Variable Explorer (exact match; supports * suffix for prefix match). Example: { 'pi', 'newaxis' }.hidden_type_names (string[]): Type names to hide (exact or prefix with *). Examples: { 'ZMQInteractiveShell', 'Axes', 'Figure', 'AxesSubplot' }.autoreload (1|2|'disable'): Configure IPython's autoreload on console startup. Default 2.1: Reload modules imported with %aimport.2: Reload all modules automatically (except excluded); recommended default.'disable': Do not configure or enable autoreload.multiline_send_mode (string): How selections/cells are sent. 'exec' executes a hex-encoded block via exec(); 'paste'(default) sends a plain-text bracketed paste so the console echoes the code like typed.# %% (one or more %) mark cell boundaries.# %% (or file start) up to the line before the next # %% (or file end).<leader>b toggles a persistent breakpoint sign at the cursor and sends the updated list to the IPython debugger.%debugfile <path> [cwd] mirrors Spyder's helper and runs the current file under an IPython Pdb instance.plt.pause() calls.F6 → launch %debugfile for the active buffer (uses exec_cwd_mode to set the working directory)F10 → nextF11 → stepF12 → continue%debugfile becomes available automatically once the console starts; no manual %load_ext needed.require('ipybridge').setup(opts) — Configure the plugin.require('ipybridge').toggle() — Toggle the IPython terminal split.require('ipybridge').open(go_back) — Open the terminal. If go_back is true, jump back to the previous window after initialization.require('ipybridge').close() — Close the terminal job if running.require('ipybridge').goto_ipy() — Focus the IPython split and enter insert mode.require('ipybridge').goto_vi() — Return focus from the IPython split to the previous window.require('ipybridge').run_file() — Run the current file via %run <filebase> in IPython.require('ipybridge').run_line() — Send the current line, then move the cursor down.require('ipybridge').run_lines() — Send the current visual selection (linewise) to IPython.require('ipybridge').send_lines(start_line, end_line) — Send lines [start_line, end_line) by 0-indexed range.require('ipybridge').run_cmd(cmd) — Send an arbitrary command string.require('ipybridge').run_cell() — Run the current cell and move the cursor to the beginning of the next one.require('ipybridge').up_cell() / down_cell() — Move to the previous/next cell.jupyter console --existing in a botright vsplit.matplotlib.use()), then plt.ion() is called (configurable).startup_script exists in the current working directory, it is executed in the console; otherwise minimal numeric imports are sent.'exec' which hex-encodes the selection and executes it via exec() (robust; minimal echo).multiline_send_mode = 'paste' to send plain text using bracketed paste (ESC[200~ ... ESC[201~) so IPython shows the exact code as if it was typed (similar to Spyder).# %%-style marker and is implemented with vim.regex and vim.iter (Neovim 0.11+ APIs) for clarity and performance.set_default_keymaps is enabled, keymaps are also applied to already-open Python buffers at startup.matplotlib_backend = 'qt'|'tk'|'macosx'|'inline' to use IPython magic, or 'QtAgg'|'TkAgg'|'MacOSX' for matplotlib.use().matplotlib_ion = true enables interactive mode. For GUI windows instead of inline PNGs, use a GUI backend (e.g. 'qt').PyQt5 or PySide6. Tk requires Tk support. macOS may require framework build Python.prefer_runcell_magic = true to execute cells via a helper registered in IPython.runcell(index, path, cwd=None) and a %runcell line magic. Cells are delimited by lines matching ^# %%+.runcell(index, <current file path>, <cwd according to exec_cwd_mode>).runcell_save_before_run = true (default), the buffer is saved first to ensure the helper runs the latest contents.runfile(path, cwd=None) and registers %runfile.prefer_runcell_magic = true, run_file() uses runfile('<abs_path>', '<cwd>') instead of %run and avoids changing the global working directory.ipykernel, jupyter_client, pyzmq (in the Python environment of the kernel).<leader>vx → open variable explorer<leader>vr → refresh variablesq → close, r → refresh, <CR> → open preview when available (DataFrame/ndarray/dataclass/ctypes or truncated repr)r to refresh, q to close. In the viewer, <CR> on a dataclass/ctypes field drills down (e.g., yy.b, hh.h2).<leader>ti → toggle IPython terminal<leader>ii → focus IPython terminal<leader><CR> → run current cell (# %% delimited)F5 → run current file (%run)F6 → debug current file (%debugfile)<leader>r → run current line<leader>b → toggle debugger breakpointF9 → run current lineF10 → debugger step overF11 → debugger step intoF12 → debugger continue]c / [c → next/prev cell<leader>vx → variable explorer (global command also available)<leader>vr → refresh variables<leader>r → run selectionF9 → run selection]c / [c → next/prev cell<leader>iv → back to editor (works anywhere; exits terminal and jumps back):IpybridgeVars → open variable explorer:IpybridgeVarsRefresh → refresh variables:IpybridgeDebugFile → debug the current file via %debugfile:IpybridgePreview <name> → open preview for a variable or path (supports dotted/indexed paths, e.g., yy.b, yy.c, hh.h2, arr[0])<leader>iv → back to editor (works in any terminal buffer)local ipybridge = require('ipybridge')
vim.api.nvim_create_autocmd('FileType', {
pattern = 'python',
callback = function()
vim.keymap.set('n', '<leader>ti', ipybridge.toggle, { buffer = true })
vim.keymap.set('n', '<leader>ii', ipybridge.goto_ipy, { buffer = true })
vim.keymap.set('n', '<leader>iv', ipybridge.goto_vi, { buffer = true })
vim.keymap.set('n', '<leader><CR>', ipybridge.run_cell, { buffer = true })
vim.keymap.set('n', '<F5>', ipybridge.run_file, { buffer = true })
vim.keymap.set('n', '<F6>', ipybridge.debug_file, { buffer = true })
vim.keymap.set('n', '<leader>r', ipybridge.run_line, { buffer = true })
vim.keymap.set('n', '<leader>b', ipybridge.toggle_breakpoint, { buffer = true })
vim.keymap.set('v', '<leader>r', ipybridge.run_lines, { buffer = true })
vim.keymap.set('n', '<F9>', ipybridge.run_line, { buffer = true })
vim.keymap.set('v', '<F9>', ipybridge.run_lines, { buffer = true })
vim.keymap.set('n', '<F10>', ipybridge.debug_step_over, { buffer = true })
vim.keymap.set('n', '<F11>', ipybridge.debug_step_into, { buffer = true })
vim.keymap.set('n', '<F12>', ipybridge.debug_continue, { buffer = true })
vim.keymap.set('n', ']c', ipybridge.down_cell, { buffer = true })
vim.keymap.set('n', '[c', ipybridge.up_cell, { buffer = true })
vim.keymap.set('v', ']c', ipybridge.down_cell, { buffer = true })
vim.keymap.set('v', '[c', ipybridge.up_cell, { buffer = true })
-- In the terminal buffer, set this (example):
-- vim.keymap.set('t', '<leader>iv', ipybridge.goto_vi, { buffer = <ipy_bufnr> })
end,
})
ipython is installed and discoverable in your environment.ipykernel, jupyter_client, and pyzmq are installed in the kernel’s environment.lua/ipybridge/init.lua: public API and orchestration of features.lua/ipybridge/term_ipy.lua: terminal split wrapper (open/send/scroll/cleanup).lua/ipybridge/utils.lua: small utilities (quoting, selection range, exec helpers).lua/ipybridge/keymaps.lua: default keymaps and user commands.lua/ipybridge/kernel.lua: standalone ipykernel lifecycle and connection file.lua/ipybridge/zmq_client.lua: NDJSON ZMQ bridge to the kernel (vars/preview).lua/ipybridge/dispatch.lua: routes decoded messages to UI modules.lua/ipybridge/var_explorer.lua: variable explorer floating window.lua/ipybridge/data_viewer.lua: preview window for arrays/dataframes/objects.lua/ipybridge/exec_magics.lua: IPython runcell/runfile execution magics.