nghiant03/jove.nvim

github github
code-runner
stars 5
issues 0
subscribers 0
forks 1
CREATED

UPDATED


jove.nvim

Jupyter notebooks, edited natively in Neovim. Jove — /dʒoʊv/, as in Jupiter.

[!warning] This plugin is in the experimental phase and have frequent breaking changes. Version pinning or reading of release notes before update is highly recommended.

Features

  • Native buffers: Jupyter Notebooks open as ordinary buffers with LSP, Tree-sitter and git tooling attach like any other file.
  • Built-in kernel client: a first-party Python bridge starts and supervises kernel.
  • Inline outputs: text, tables, tracebacks and images render in each cell, with displayed state of each cell.
  • Interactive component: output which contains interactive components can be render using terminal-browser
  • Cell ergonomics: ic/ac cell text-objects, [c/]c cell motions, cell borders, per-cell execution counts and elapsed time.
  • Tooling: a sidebar combining the variable inspector, notebook table of contents, and kernel info.

Requirements

  • Neovim ≥ 0.11.
  • jupytext
  • A Python interpreter with jupyter_client and ipykernel installed.
  • snacks.nvim (optional): Image rendering.

Run :checkhealth jove to verify the requirements

Installation

lazy.nvim (recommended)

{
  "nghiant03/jove.nvim",
  version = "v*",
  lazy = false,
  opts = {
    auto_kernel = true,
  },
}

[!important] Do not load jupytext.nvim with jove since both register BufReadCmd on *.ipynb. Jove detects it and refuses to register its handlers with a warning.

Python environment

The kernel needs a Python interpreter that can import jupyter_client and ipykernel. Jove picks one automatically, in this order:

  1. $CONDA_PREFIX/bin/python (active conda env)
  2. $VIRTUAL_ENV/bin/python (active virtualenv)
  3. g:python3_host_prog (Neovim's configured Python host)
  4. your configured bridge_python value (default "python3")

If you launch Neovim from outside the environment that has the Jupyter stack, set the fallback explicitly:

opts = {
  bridge_python = "/home/you/envs/jupyter/bin/python",
}

Configuration

Full option list with defaults:

require("jove").setup({
  jupytext = "jupytext",        -- path to the jupytext binary
  bridge_python = "python3",    -- fallback Python for the kernel helper
  auto_kernel = true,           -- start kernel automatically on open
  auto_import_outputs = true,   -- render persisted outputs on open/reload
  auto_export_outputs = true,   -- merge session outputs into the notebook on save
  persist_exec_counts = true,   -- persist kernel execution counts into the notebook
  elapsed = true,               -- show per-cell elapsed execution time
  auto_reload = false,          -- auto-reload when the notebook changes on disk
  cell_motions = true,          -- map cell motions
  signs = {
    queued = "…",               -- gutter sign: queued for execution
    running = "▶",              -- gutter sign: currently running
    ok = "✓",                   -- gutter sign: finished successfully
    error = "✗",                -- gutter sign: finished with an error
  },
  output = {
    max_lines = 50,             -- inline output truncation limit
    images = true,              -- render images via snacks.image when available
    image_max_width = 80,       -- cap rendered image width in terminal cells
    image_max_height = 40,      -- cap rendered image height in terminal cells
    header = true,              -- draw the Output top frame
    guide = "▎ ",               -- per-line inner output rail
    inside_border = false,      -- render output inside the cell border instead of its own bordered block below it
    hl = nil,                   -- output background tint
  },
  variables = {
    auto_refresh = true,        -- refresh the variables inspector on idle
    size = 0.25,                -- sidebar size as a fraction of the screen
  },
  ui = {
    conceal_headers = true,     -- conceal cell headers
    active_cell = true,         -- highlight the active cell
    exec_counts = true,         -- show per-cell execution counts
    elapsed = true,             -- show per-cell elapsed time
    borders = true,             -- draw a closing line below each cell
    border_hl = nil,            -- cell border highlight
    window_mode = "vsplit",     -- all Jove viewers: "vsplit" (right), "hsplit" (below), or "float"
    window_overrides = {},       -- optional per-view modes: sidebar, output, inspect, webview
  },
  lsp = {
    auto_attach = false,        -- start the servers in `servers` on notebook open
    servers = {},               -- language id to lsp server names,
  },
  webview = {
    enabled = true, 
    cmd = "terminal-browser",   -- terminal-browser binary
    width = 0.5,                -- webview width in float/vsplit mode, as a fraction of the editor
    height = 0.5,               -- webview height in float/hsplit mode, as a fraction of the editor
  },
})

Window layout

The sidebar, output viewer, variable details, and webview all open in a right-hand split by default. Set ui.window_mode to "float" or "hsplit" to change the default for all of them. Override individual views when needed:

require("jove").setup({
  ui = {
    window_mode = "vsplit",
    window_overrides = {
      output = "hsplit",
      inspect = "float",
      webview = "float",
    },
  },
})

Webview interaction

:Jove open-webview runs terminal-browser in a terminal buffer and enters Terminal mode automatically. The webview recognizes HTML, Plotly, Vega-Lite v4–v6, and Vega v5–v6 output.

[!note] Hover events require Neovim's mousemoveevent option to be enabled.

[!warning] Plotly and Vega renderers load JavaScript from CDNs, so they require network access. With Plotly, emit its rich MIME bundle explicitly if your kernel selects a different renderer:

fig.show(renderer="plotly_mimetype")

[!warning] Live ipywidgets, FigureWidget, and Python callbacks require a widget manager and kernel comms, which are not yet supported.

Languages and LSP

Built-in languages:

Language Filetype Cell marker Known servers
Python python # %% pyright, basedpyright, ruff
Julia julia # %% julials
R r # %% r_language_server
JavaScript javascript // %% ts_ls
TypeScript typescript // %% ts_ls

Unknown languages fall back to Python conventions. Register more yourself:

require("jove.lang").register("scala", { fmt = "scala", comment = "//", servers = { "metals" } })

Because the buffer carries the language's real filetype, any LSP server you have configured attaches to notebook buffers automatically. If you prefer jove to start the servers for you:

opts = {
  lsp = {
    auto_attach = true,
    servers = { python = { "pyright" }, javascript = { "ts_ls" } },
  },
}

Usage

Commands

Command Action
:Jove run-cell Run current notebook cell
:Jove run-above Run all notebook cells above the cursor
:Jove run-all Run all notebook cells
:Jove run-selection Run the visual selection as one unit
:Jove run-cell-and-advance Run the current cell and jump to the next
:Jove next-cell Jump to next notebook cell
:Jove prev-cell Jump to previous notebook cell
:Jove goto-running-cell Jump to the currently executing cell
:Jove toggle-follow-running Toggle following the currently executing cell with the cursor
:Jove init-kernel Start a kernel for the current notebook
:Jove select-kernel Pick a kernelspec for the current notebook (replaces running kernel)
:Jove interrupt Interrupt the running execution
:Jove restart-kernel Restart the current notebook kernel
:Jove shutdown-kernel Shut down the current notebook kernel and bridge
:Jove toggle-output Show/hide rendered outputs of the current cell
:Jove open-output Open the current cell's outputs in a viewer
:Jove open-webview Open the current cell's rich output in an interactive terminal-browser webview
:Jove clear-output Clear outputs of the current cell
:Jove clear-outputs Clear all rendered outputs in this buffer
:Jove reload Reload the current notebook buffer from disk
:Jove sidebar Toggle the sidebar (variables, kernel info, table of contents)

Keymaps and motions

On jove buffers:

  • ic / ac cell text-objects (operator-pending and visual modes): always on, no extra plugin needed.
  • [c / ]c cell motions (normal mode): on by default, disable with cell_motions = false.
  • Bind the buffer-local <Plug> mappings to your custom keys:
vim.keymap.set("n", "<leader>x", "<Plug>(JoveRunCell)", { desc = "Run cell" })
vim.keymap.set("n", "<leader>X", "<Plug>(JoveRunCellAndAdvance)", { desc = "Run cell, advance" })
vim.keymap.set("x", "<leader>xx", "<Plug>(JoveRunSelection)", { desc = "Run selection" })
vim.keymap.set("n", "<leader>j", "<Plug>(JoveGotoRunningCell)", { desc = "Go to running cell" })
vim.keymap.set("n", "<leader>J", "<Plug>(JoveToggleFollowRunning)", { desc = "Follow running cell" })

Available <Plug> mappings:

Mapping Action
<Plug>(JoveRunCell) Run current notebook cell
<Plug>(JoveRunAbove) Run all notebook cells above the cursor
<Plug>(JoveRunAll) Run all notebook cells
<Plug>(JoveRunSelection) Run the visual selection as one unit (visual mode)
<Plug>(JoveRunCellAndAdvance) Run the current cell and jump to the next
<Plug>(JoveNextCell) Jump to next notebook cell
<Plug>(JovePrevCell) Jump to previous notebook cell
<Plug>(JoveGotoRunningCell) Jump to the currently executing cell
<Plug>(JoveToggleFollowRunning) Toggle following the currently executing cell