Org Babel Evaluation From Afar

I often write many src-block​s; I prototype a lot of things. And I love seeing what my code spits out a lot.

tl;dr: I built an auto-completion to evaluate source blocks in an org buffer.

output-2025-01-08-16:58:03.gif
Figure 1: animation of executing a block when point is not on block

In an hour's time of prototyping I can't avoid having a bunch of blocks in a big, structured mess. In fact, this blog post was originally just a sandbox for trying to build a completing-read interface to evalaute src-block​s.

What I've done up till now when I'm somewhere in the buffer is

  • isearch for the block, be it the code in the body of the #+begin_src or the name of the block if I've done that
  • go through isearch candidates until I find the one
  • C-c C-c to evaluate the block
  • then pop mark until I'm back to where I was

And it's too slow! I never felt like I was able enough to find more flow in this, but then

and I just realized…yeah I can fix that.

I've just gotten gradually much better at elisp over time than when I started, maybe around 2015.

So here's what we'll do:

  • Ask org to give us an AST1,
  • use AST helper methods to grab source blocks and their data
  • build candidates from relevant nodes of the tree
  • use completing-read, the minibuffer completion mechanism, to let to user select which code block to run
  • finally, execute the block

and we didn't need to move our cursor!

I debated whether to write this blog post to show you how to debug and figure this out on your own, but it didn't come out that way this time. That's one thing I think is lacking–those lispers don't show their process!

Inspiration

This is the piece of code that inspired me from worg. Maybe it'll inspire you in how easy it is to work with org's abstract syntax tree:

(org-element-map (org-element-parse-buffer) 'item
  (lambda (item) (eq (org-element-property :checkbox item) 'on))
  nil t)

(Don't forget to read the docs on those arguments!)2

Code

Anyway, here are the goodies.

An example block

First I need a src-block in this org document that I'll call for this tutorial:

(message "running my block")
(format "%s" "I'm currently looking for work")

The function called by org-element-map

I create a list of two-element lists. The first element is for display, the second element is for the point position.

(defun jwow/transform-src-block-candidates (item)
  (let* ((name (org-element-property :name item))
         (begin (org-element-property :begin item))
         (data `(,(format "%s:L%s" name (line-number-at-pos begin)) ,begin)))
    data))

Why do I want the point position? Because I plan to call org-babel-execute-src-block3. Next let's see the results from using org-element-map.

Using org-element-map

There are multiple types of source blocks; I don't use the inline ones at all, but I included them:

(org-element-map (org-element-parse-buffer) '(src-block inline-src-block)
  #'jwow/transform-src-block-candidates)
| inspiration from org element api:L46          | 2316 |
| the block I'll call:L60                       | 2709 |
| jwow/get-src-block-candidates:L72             | 3069 |
| org-element-map example:L93                   | 3991 |
| completing read example:L117                  | 4838 |
| finished code, find-src-blocks-in-buffer:L129 | 5180 |

Autocompleting candidates

I began prototyping this separately. Now I needed to show candidates that the lambda returned.

Prototyping completing read is super easy:

(format "Completed for this: %s" (completing-read "Execute block: " '(foo boo roo)))

Yields:

2025-01-08_15-57-57_screenshot.png

Org Src Run

Try running this in one of your org buffers and try to run a piece of code.

(defun jwow/org-src-run ()
  (interactive)
  (let* ((found-src-blocks  (org-element-map
                                (org-element-parse-buffer)
                                '(src-block inline-src-block)
                              #'jwow/transform-src-block-candidates))
         (chosen-block (completing-read
                        "Execute block: "
                        found-src-blocks))
         (block-start (cadr (assoc chosen-block found-src-blocks))))
    (save-excursion 
      (goto-char block-start)
      (org-babel-execute-src-block nil
                                   (org-babel-get-src-block-info)))))

Takes about a second to get completing read loadaed for a 7k line org file on my box.

More efficiently?

Of course I thought of making the code faster.

One thing that could be improved is avoid using assoc, which is a linear walk through a linked list. For repeat calls, we might be able to use a hash table mapping src-block names to point positions.

But that creates the issue of stale cache (the block position in the buffer changes as you type). We could save the AST for the block, to call functions on it–but then the code might change too.

This introduces way more complexity. And it's good enough for now.

I've learned I should spend more time communicating vs less time tweaking code.


1

I avoid using regexp; asking org to parse is slower, but more accurate. Parsing is much better if your use case covers other elements than just src-blocks.

2

I didn't read what the last argument (here it's t) did, so I kept wondering why it was exiting only after listing one element. Once I removed it, I saw my lamda fire for each matching element.

3

Found by looking through C-h k C-c C-c to find org-ctrl-c-ctrl-c jump to source, find out some functions there, then jump to some more.