Skip to content
mathrenPublic

About

Jupyter python notebooks in Emacs with inline output, Eglot/Jupyter-LSP, remote kernels, and TRAMP support. Saves regular .ipynb and exports to .py.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

Emjupy

https://github.com/mathren/emjupy/actions/workflows/build-package.yml/badge.svg https://img.shields.io/badge/docs-online-green.svg https://codecov.io/gh/mathren/emjupy/graph/badge.svg https://img.shields.io/badge/License-GPL%20v3-blue.svg https://melpa.org/packages/emjupy-badge.svg

./html-content/images/emjupy-logo.png

emjupy edits and runs Jupyter notebooks in Emacs. Plots and widgets can be shown inline or in an external window or browser (see Interactive output and GIFs and screenshots). emjupy uses Emacs’s own tools – eglot, xref, eldoc, tramp – with whatever completion setup you already have (vertico, corfu, orderless or anything else). Completion, documentation and M-. come through eglot, from a language server running beside the kernel.

It talks to a running Jupyter server over the same HTTP and WebSocket API the browser uses, so a kernel on another machine works through an ssh tunnel, and the notebook stays a standard .ipynb that any other Jupyter tool opens.

Try it in under 2 minutes with emacs -Q…

…without touching your own Emacs configuration.

html-content/images/quickstart.gif

The repository ships a small notebook, test_notebooks/test.ipynb, with a module beside it, so there is something real to run. To test with your own notebooks, jump to step 4.

  1. Get emjupy.
    git clone https://github.com/mathren/emjupy
    cd emjupy
        
  2. A Python environment with Jupyter. Either create the one the notebook comes with,
    conda env create -f test_notebooks/environment.yml
    conda activate test_emjupy
        

    or install into one you have:

    pip install jupyter-server ipykernel jupyter-lsp 'python-lsp-server[all]' matplotlib numpy tqdm
        

    jupyter-lsp and python-lsp-server give completion, documentation and M-.; everything else works without them.

  3. Start a server in the notebook’s directory, in its own terminal:
    cd test_notebooks
    JUPYTER_TOKEN=abc jupyter server --no-browser --port=8888
        

    Or on another machine, the same way, in a copy of test_notebooks/ or any directory with notebooks, forwarding its port from this one:

    ssh -N -L 8888:localhost:8888 <remote machine>
        
  4. Start Emacs from the repository root:
    emacs -Q -l src/quicktry.el
        

    -Q leaves your configuration out; quicktry.el puts emjupy on the load path and installs websocket.el into a temporary package directory, touching nothing in your setup.

  5. Log in: M-x emjupy-login RET 8888 RET, and abc when asked for the token. Pick test.ipynb from the list.
  6. Try it:
    • C-c C-c runs the cell at point and moves to the next;
    • M-. on example_function jumps to its definition in library.py;
    • C-c C-v shows every command, grouped by what it is for.

Features

  • persistent kernel session (to load large dataset once and re-use them)
  • remote or local kernel (just needs port and token)
  • in-line rendering of output including images
  • adopts the kernel already running behind a port, and the Python environment it runs in
  • completion, documentation and M-. through eglot, from a language server running beside the kernel via jupyter-lsp
  • resilient to connection drops (remote kernel keeps running, reconnect Emacs)
  • toggle hide/show output without re-running cells
  • a debugger, as JupyterLab’s: breakpoints in cells, stepping, variables
  • an outline of the headings, to jump through: imenu, and a panel beside the notebook
  • interactive plotly figures open in a window of their own; ipywidgets controls work in the buffer, and widgets that draw themselves open in a live page
  • every cell command – insert, delete, move, split, join – is undone and redone like any edit
  • ein-like workflow and key bindings
  • emjupy-notebook-list, like ein:notebooklist, to browse notebooks and the files beside them

Installation

Clone this repo, and add its src/ directory to load-path in your init.el:

(add-to-list 'load-path "/path/to/emjupy/src")
(require 'emjupy)

websocket.el must be installed too, from GNU ELPA or MELPA.

Or with use-package (without :rev :newest, the latest release rather than the latest commit):

(use-package emjupy
  :vc (:url "https://github.com/mathren/emjupy"
       :lisp-dir "src"
       :rev :newest)
  :config
  (setq emjupy-render-latex t))

:lisp-dir "src" is needed: without it the package installs but emjupy cannot be loaded.

Or with whichever package manager you already use – each installs websocket.el too:

;; package-vc, built into Emacs:
(package-vc-install '(emjupy :url "https://github.com/mathren/emjupy"
                             :lisp-dir "src"))

;; elpaca:
(elpaca (emjupy :host github :repo "mathren/emjupy"
                :files ("src/*.el" "src/*.py" (:exclude "src/quicktry.el"))))

;; straight.el:
(straight-use-package
 '(emjupy :type git :host github :repo "mathren/emjupy"
          :files ("src/*.el" "src/*.py" (:exclude "src/quicktry.el"))))

The sources are in src/, so each is told where: :lisp-dir for package-vc, :files for elpaca and straight – which also install the figure window, emjupy-figure-window.py, and leave out quicktry.el, a development tool.

Alternatively, M-x package-install-file on the tarball make package builds (see Building & development).

Architecture

emjupy speaks to a Jupyter server the way the browser does: HTTP for files and kernels, WebSockets for a kernel’s messages and the language server’s. Everything goes over the server’s one port, so a remote server needs one SSH tunnel and nothing started on the far side.

 Emacs, on this machine                         Jupyter server, here or remote
┌─────────────────────────────────────┐        ┌───────────────────────────────────────┐
│ notebook buffer       (emjupy-mode) │  HTTP  │ Contents API ──── .ipynb files        │
│   cells ⇄ structs ⇄ .ipynb JSON ────┼───────▶│ Sessions and Kernels APIs             │
│     │ outputs, widget controls      │        │                                       │
│     ▼                               │   WS   │                          ZMQ          │
│   kernel connection ────────────────┼───────▶│ /api/kernels/…/channels ──▶ kernel    │
│                                     │   WS   │                                       │
│ shadow buffer (.py) ◀── Eglot ──────┼───────▶│ /lsp/ws/… ── jupyter-lsp ──▶ pylsp    │
│                                     │        │                                       │
│ figure window ◀── page ◀── bridge   │        └───────────────────────────────────────┘
└─────────────────────────────────────┘
   the bridge relays a widget page's messages over the
   kernel connection above
  • The notebook buffer is the notebook: cells drawn as text, kept in step with the cell structs, which are what is saved as .ipynb.
  • The kernel connection carries execution and its output; widgets’ messages too.
  • The shadow buffer is every code cell as one Python file, for Eglot, whose language server runs beside the kernel through jupyter-lsp.
  • The figure window shows what a text buffer cannot – plotly figures, widgets that draw themselves – and a widget’s page talks back through the bridge, a WebSocket server in Emacs, not to the Jupyter server.

Landscape of other Emacs and Jupyter packages

Featureemjupyeinemacs-jupyterob-jupyter
Notebook-style buffer of cells✓✓✗✗
Reads and writes standard .ipynb✓✓✗✗
Notebook list UI✓✓✗✗
Undo across cell operations✓✗–✓
Interactive widgets✓✗~ (in a browser)?
Kernel via Jupyter server HTTP + WebSocket (ssh tunnel is enough)✓✓✓✓
No emacs-zmq dynamic module required✓✓✗✗
eglot (completion, M-., rename across cells)✓✗✗✗
Kernels other than Python✗✓✓✓
Oldest Emacs supported30.126.12727
Maintained✓✗✓✓

✓ = supported, ✗ = not supported, ~ = partly, – = does not apply (a REPL has no cell operations), ? = not documented. Checked <2026-10-03 Sat> against each project’s README and package header.

They differ in what they edit and how they reach the kernel. ein and emjupy edit .ipynb notebooks as cells, over a Jupyter server’s HTTP and WebSocket API: an ssh tunnel is enough. emacs-jupyter is a kernel client for a REPL and Org-babel blocks, with no notebook buffer and no .ipynb; it requires the emacs-zmq dynamic module, whose ZMQ cannot reach a notebook server’s kernels, though it can use a server’s REST API through TRAMP-style /jpy:host#port:name names; its widgets are experimental, in a browser. For notebook-level eglot integration, emjupy is Python only (other kernels untested), needs jupyter-lsp on the server, and Emacs 30.1+.

ein was my working tool for over a decade, but it is unmaintained: sunset since 2023, its architecture “fundamentally incompatible with LSP”, its README says, pointing to xjupyter, a successor at 0.0.1 running local kernels through a Python jupyter_client helper, not a Jupyter server, and needing mode-overlay, which GNU Emacs 30.1 lacks. I share code with non-Emacs users, so not ob-jupyter, and emacs-jupyter does not suit me.

Documentation

The full documentation lives in docs/:

About

Jupyter python notebooks in Emacs with inline output, Eglot/Jupyter-LSP, remote kernels, and TRAMP support. Saves regular .ipynb and exports to .py.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages