Nothelix
Nothelix puts notebooks inside Helix. Cells run against a live Julia kernel, plots and typeset equations render inline in the buffer, and the notebook on disk stays a plain .jl file that diffs like source.
One buffer, twice. Here is a cell as it sits on disk, the LaTeX you typed.
<!-- gallery:conceal-fourier-source --># ## The Fourier transform
#
# The forward transform is $\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x) \, e^{-2\pi i x \xi} \, dx$
# and the inverse is $f(x) = \int_{-\infty}^{\infty} \hat{f}(\xi) \, e^{2\pi i x \xi} \, d\xi$.
#
# Parseval's theorem states $\int |f(x)|^2 \, dx = \int |\hat{f}(\xi)|^2 \, d\xi$, so the
# transform preserves energy: $\|f\|_2 = \|\hat{f}\|_2$ for every $f \in L^2(\mathbb{R})$.
<!-- /gallery -->
And here is the same cell as you read it a moment later, once concealment has swapped each construct for its Unicode form. The file still holds the LaTeX, and it comes back on the line you are editing.
Neither block is a transcription or a screenshot. just gallery writes both straight out of the rendering engine, from the same fixture the snapshot tests pin, so this README cannot drift from what the buffer draws.
# ## The Fourier transform
#
# The forward transform is f̂(ξ) = ∫_-∞^∞ f(x) e^-2π i x ξ dx
# and the inverse is f(x) = ∫_-∞^∞ f̂(ξ) e^2π i x ξ dξ.
#
# Parseval's theorem states ∫ |f(x)|² dx = ∫ |f̂(ξ)|² dξ, so the
# transform preserves energy: ‖f‖₂ = ‖f̂‖₂ for every f ∈ L²(ℝ).
<!-- /gallery -->
What you get
- Notebooks that behave like source. A notebook is Julia with
@cellmarkers. It diffs, greps, and edits with the motions you already use, and:convert-notebookand:sync-to-ipynbmove it to and from.ipynbwhen a collaborator needs that. - Results where the cell is. Text output, plots, Markdown tables, and display equations render in the buffer. No browser and no second pane.
<space>nyputs a cell’s output on the system clipboard. - Knobs you drive from the buffer. A
# @paramcomment turns a numeric literal into a live slider that draws a track above the line and follows the cursor, while# @selectcycles a value through a closed set and# @toggleflips a flag. Each nudge rewrites the literal in place and re-runs the owning cell. A run can also callnothelix_sliderornothelix_choiceto drop a knob under the cell that writes straight back to a kernel variable and lights a stale badge on every cell that reads it.]wwalks between all of them and names the keys that act on each, thewidgetssetting gates the walk, and any Julia library can project its own objects as knobs by defining a singlenothelix_towidgetmethod. - Audio that plays as you work. A
wavplaycall plays through the system output without blocking the cell and draws a peak-normalised braille waveform beneath it. While the clip runs the trace moves with it, a window of the signal centred on the playing position redrawn ten times a second, with an elapsed clock beside it. Flip the same cell to an STFT spectrogram heatmap for frequency work, through theaudio-viewknob orvin the scrub popup.]aand[aseek, accelerating through a step ladder as you tap, a modal scrub moves by ear withh,l,j, andk,<space>nsreplays, and a♪badge marks the playing cell in the navigator. - Output you can watch, and freshness you can see.
<space>nostreams a running cell’s stdout and stderr live as the kernel prints them, and shows the stored output once it finishes. After each run, cells whose inputs went stale, ran out of order, lost their writer, or were edited since they last ran wear a badge above the marker, their stored output dims, and the navigator carries the same glyph next to each cell’s run time. - A kernel that stays warm. One kernel per notebook, keyed to the file path. State persists across cells the way it does in a REPL, survives closing the buffer and restarting Helix, and runs in the notebook’s own directory so relative paths to your data resolve. The status line names the boot phase while it starts, and flags a kernel that predates the installed runner so you can restart it.
- Math that reads like math. Inline
$…$LaTeX conceals to Unicode as you read. Display$$…$$blocks and pipe tables compile through Typst and draw as typeset images. - One command to hand it over.
:export-markdown,:export-typst, and:export-pdfproduce Markdown, a Typst source file, or a finished PDF. No LaTeX distribution required.
A notebook is a Julia file
using Plots
@cell 0 :julia
x = 1:10
y = x.^2
@markdown 1 # Results
@cell 2 :julia
plot(x, y)
The markers are no-op macros the kernel defines, so julia notebook.jl still runs the file. Type @cell and press space and the marker is stamped with the next index for you. On save, indices compact back to a contiguous run.
Install
macOS on Apple Silicon, or Linux on x86_64.
curl -sSL https://raw.githubusercontent.com/koalazub/nothelix/main/install.sh | sh
Then open the bundled demo.
nothelix
You also need Julia 1.9 or newer on your PATH, via juliaup if you do not have it, and a Kitty-protocol terminal for inline plots and typeset math. Run nothelix doctor if anything looks wrong.
When notebooks get big
<space>nj opens the navigator. Every cell shows as index, kind, and label, with a live preview. Type a number to jump, or press / to fuzzy-search the labels. Labels come from a marker comment first, then Apple’s on-device model if you opt in on macOS 26 or newer, then the cell’s first meaningful line. A seventy-cell tutorial reads like a table of contents.
Cell errors get the same treatment. A failure never hands you a bare stacktrace. When a cell fails on an undefined symbol, nothelix scans the sibling cells, finds where that symbol is assigned, and names the cell to run first, so the fix is one cell away instead of a search.
<!-- gallery:error-undefined-variable -->error[E004]: `A` is not defined
--> cell 5, line 1
|
1 | vals, vecs = eigen(A)
| ^^^^^^^^^^^^^^^^^^^^^
|
= `A` is defined in @cell 2 (Build A) — run @cell 2 first, or run every cell above this one
<!-- /gallery -->
MethodError is handled the same way, listing the in-scope variables whose types match the failing signature so you can see which argument is wrong.
Status
Julia is the only supported kernel today. Python is planned. Inline rendering needs the Helix fork and a Kitty-protocol terminal. On stock Helix, or under tmux or Zellij, images fall back to text placeholders while Unicode math keeps working. Sixel is not implemented. Prebuilt binaries ship for macOS arm64 and Linux x86_64. Where something is unfinished, the docs say so.
Documentation
Everything lives at koalazub.github.io/nothelix. Installation covers requirements, the nothelix CLI, and building from source. Notebooks walks the write, run, and navigate loop. Rendering explains how figures, math, and exports reach you, and the Playground lets you try the rendering in a browser first. Commands and keys is the reference, Language server wires up Julia code intelligence, Architecture explains how the four layers fit together, and Troubleshooting starts with nothelix doctor.
Credits
Nothelix is a thin layer of glue over a great deal of excellent open-source work, and would not exist without any of it.
- Helix and Steel, the editor it lives in and the embedded Scheme the plugin is written in. A small fork of Helix adds the inline-image rendering everything visual depends on.
- Julia and juliaup, the kernel language and the supported way to install it.
- Typst and MiTeX, the typesetting system behind math rendering and export, and the LaTeX-to-Typst conversion that feeds it.
- resvg and tiny-skia rasterise the typeset output. The image crate, serde, comrak, and rayon do the rest of the system-shaped work in the Rust library.
- The Kitty graphics protocol carries images into the buffer.
- JETLS, LanguageServer.jl, and Runic provide optional Julia code intelligence and formatting.
- These docs are built with just-the-docs.
- Prior art that shaped the approach. Marimo for notebooks as source files, and euporie and SymPy’s pretty-printer for mathematics in a terminal.
The credits page names each project and explains in full why it is used.
Licence
MIT