Notebooks with Org Babel
Org Babel turns an ordinary.org file into a notebook: prose,
executable source blocks, and their captured output in one plain-text
document that diffs, greps, and merges like any other file. Jotain
configures it in lisp/init-org.el for that workflow specifically —
languages preloaded, evaluation prompt made unobtrusive, plots
redisplayed after every run.
Nothing here is a separate mode. You are in org-mode the whole time.
The loop
Write a block, put point in it, pressC-c C-c:
#+RESULTS: block is written back into the buffer, so results are
part of the document and survive a restart. C-c C-c on a block that
already has results replaces them.
Type <py then TAB to insert an empty Python block —
org-tempo has entries for the languages this
config actually runs. C-c ' opens the block in a real major-mode
buffer with LSP, formatting, and everything else you get in a
standalone file; C-c ' again returns.
The C-c b prefix
Cell-at-a-time is C-c C-c. Everything wider than one block is under
C-c b in Org buffers:
Org’s own
C-c C-v babel map is untouched and still has everything
else (C-c C-v v to expand a block, C-c C-v i to view header args,
and so on).
Sessions
By default each block runs in a fresh process, so nothing carries over between them. Add:session to keep a live interpreter around, which is
what makes an Org file behave like a notebook rather than a pile of
scripts:
df. C-c b s drops you into that interpreter
to poke at state interactively; C-c b r throws it away and replays the
file from the top, which is the reliable way to check that a notebook
still runs end-to-end after you have been editing out of order.
<jp TAB inserts a Python block with :session notebook :results output already filled in.
Results
:results decides what gets captured. The two that matter:
output— everything the block printed. This is Jotain’s default for Python, because the notebook habit is toprintthings and Org’s own default (value) shows nothing at all unless the block ends in areturn.value— the value of the last expression. Set it per block when you want a table or a number rather than a transcript.
Plots
Write the figure to:file and Org inserts a link to it; the image is
redisplayed automatically after every run, so re-running a cell updates
the picture in place rather than leaving the previous one on screen.
org-image-actual-width) so a large
figure does not push the text column off-screen. C-c C-x C-v toggles
inline images if you want the raw links back.
Evaluation prompts and trust
Running a source block runs arbitrary code, so Org asks for confirmation by default — which is correct for a file someone sent you and unbearable for the notebook you are editing right now. Jotain replaces the blanket prompt with a trust check (jotain-org-babel-confirm-evaluate). A file is trusted when it lives
either:
- under
org-directory— the notes tree,~/notesby default, shared withorg-capture,org-roam, anddenote; or - inside the current project, as
project-currentsees it.
.org, a mail attachment, a gist opened from a browser — still prompts
on every block. To go back to Org’s stock behaviour:
Export
Export (C-c C-e) uses the results already in the buffer:
org-babel-default-header-args sets :eval never-export, so producing
an HTML or PDF copy never re-runs your code. What you see in the buffer
is what gets published. Pass :eval yes on a block to opt it back into
running at export time.
The matching default is :exports both, so a block and its output both
appear in the exported document. :exports results hides the code;
:exports none hides both.
Tangling
C-c b t writes blocks out to real files, which is how a notebook
graduates into a program:
org-src-preserve-indentation),
which is not Org’s default and is not optional for Python — Org’s
re-indent-on-exit behaviour corrupts blocks where leading whitespace is
syntax.
Languages
jotain-org-babel-languages in lisp/init-org.el lists what is
enabled:
ob-LANG library that ships with Org
itself, so no source block depends on a package being installed. C
covers C, C++ and D; shell covers bash, sh and the other shell
dialects Org knows. A test (test/test-org-babel.el) requires each
one, so a language that gets renamed or moved out to org-contrib during
an Org bump fails CI rather than a C-c C-c months later.
Enabling a language teaches Org how to run the block — it does not
provide the interpreter. Python, R, gnuplot, dot and the rest come
from the project’s own environment, the same way LSP servers do.
Add one by editing the list:
Python and IPython
init-lang-python.el points the Python REPL at ipython when it is on
PATH, which gets you IPython’s completion, tracebacks and %magic in
run-python and in :session blocks. When it is absent, python3 is
used and nothing else changes.
Blocks without a session always use plain python3
(org-babel-python-command), deliberately: a one-shot block handed an
interactive ipython -i would wait for input instead of returning a
result.
Structure templates
org-tempo expands <KEY + TAB. Jotain adds:
Org’s own entries (
<s source, <q quote, <e example, and the rest)
still work.
What this is not
This is Org Babel, not a Jupyter client. Blocks run through Org’s ownob-* backends and comint, so there are no Jupyter kernels, no
.ipynb files, and no rich MIME output beyond images and tables.
If you need actual kernels — a remote kernel, a language with no
ob- backend, or .ipynb interchange — the
emacs-jupyter package
provides jupyter-python blocks alongside everything above. It is not
included here because it needs the zmq dynamic module and a jupyter
binary at runtime, neither of which this configuration ships.
Long-running blocks also execute synchronously and will block Emacs. Use
:session and a REPL you can watch (C-c b s) rather than waiting on a
30-minute C-c C-c.