Data Analysis Inside Neovim

Molten.nvim, Jupyter kernels and reproducible environments

3 min read

I spent today setting up Molten.nvim with Jupyter kernels inside a Nix-managed environment.

What started as “I want inline code execution in Neovim” turned into a deeper look at how Jupyter actually works.

Why I wanted this

Typical data workflow:

  • write code
  • switch to notebook
  • run cell
  • inspect output
  • switch back
  • repeat

It works, but it breaks focus.

I wanted notebook-style interactivity without leaving Neovim.

Molten.nvim

Molten isn’t a Python runner.

It’s a Jupyter client inside Neovim.

So instead of switching tools, I can run:

# %%
import pandas as pd

df = pd.read_csv("sales.csv")
df.head()

and see output inline in the editor.

That changes the workflow:

  • write notes
  • run analysis
  • inspect results
  • continue writing

all in one place.

How it works

Molten talks to a Jupyter kernel over the standard protocol:

Neovim
→ Molten.nvim
→ Jupyter protocol (ZeroMQ)
→ Jupyter kernel
→ Python environment

The kernel runs as a separate process and keeps state in memory.

So this:

# %%
sales = df.groupby("Category")["Sales"].sum()

can later become:

# %%
sales.plot()

without restarting anything.

Same architecture used by:

Jupyter Lab VS Code Jupyter extension Molten.nvim

Why NixOS made this better

Instead of manually installing packages:

pip install pandas matplotlib ipykernel

I define the environment in Nix:

pkgs.python3.withPackages (ps: with ps; [
  pynvim
  ipykernel
  pandas
  numpy
  matplotlib
])

Then wire it into a Jupyter kernel using Home Manager & symlink at user:

# Symlink a reproducible Python environment to ~/.venv/molten
home.file.".venv/molten" = {
  source = pkgs.python3.withPackages (ps: with ps; [
    pynvim ipykernel pandas numpy matplotlib
  ]);
};

Finally, register it in my system config, and :MoltenInit picks it up automatically.*

# Register it as a Jupyter kernel so :MoltenInit can find it
home.file.".local/share/jupyter/kernels/molten/kernel.json" = {
  text = ''
    {
      "argv": [
        "~/.venv/molten/bin/python3",
        "-m",
        "ipykernel_launcher",
        "-f",
        "{connection_file}"
      ],
      "display_name": "Python 3 (molten)",
      "language": "python"
    }
  '';
};

Everything becomes reproducible from a Git clone + rebuild.

One thing that can be confusing at first is why the Jupyter kernel uses a separate Python environment instead of just using whatever project venv you’re already in. The key idea is that a Jupyter kernel is just a long-running Python process. It starts once, stays alive in memory, and executes code cell by cell. Because of that, it needs a stable, consistent environment to run inside. In practice, that means it needs a fixed Python interpreter with the required packages already available - that’s what the kernel.json is doing

Learning through AI

I used opencode heavily while setting this up.

Not just to generate config, but to understand what was happening.

The useful part wasn’t getting it working — it was figuring this out:

  • why Molten needs a bridge Python
  • how Jupyter kernels are discovered
  • why state lives in the kernel process
  • how Nix rebuilds environments

Now the system actually makes sense, even if i can't write the code.

How this post came together

I didn’t write this directly.

I used opencode to work through setup issues wrote a recap note in Obsidian (inside Neovim) turned that into this post

Flow:

experiment
→ debug with opencode
→ write notes
→ turn notes into post (once i understand what happened!)

It helped turn a messy setup process into something I can actually explain.

Final thoughts

I thought I was just configuring a Neovim plugin.

I ended up learning more about Jupyter, processes, and reproducible environments than I expected.

Molten.nvim sits in an interesting place between notebooks and traditional development.

For data work, keeping analysis, notes, and execution in the same editor feels more natural than I expected.