Linking to and exporting function definitions in Org Mode

| emacs, org

2023-09-05: fixed the completion to include defun:

I'd like to write more blog posts about little Emacs hacks, and I'd like to do it with less effort. Including source code is handy even when it's missing some context from other functions defined in the same file, since sometimes people pick up ideas and having the source code right there means less flipping between links. When I'm working inside my config file or other literate programming documents, I can just write my blog post around the function definitions. When I'm talking about Emacs Lisp functions defined elsewhere, though, it's a little more annoying to copy the function definition and put it in a source block, especially if there are updates.

The following code creates a defun link type that exports the function definition. It works for functions that can be located with find-function, so only functions loaded from .el files, but that does what I need for now. Probably once I post this, someone will mention a much more elegant way to do things. Anyway, it makes it easier to use org-store-link to capture a link to the function, insert it into a blog post, navigate back to the function, and export HTML.

(defun my-org-defun-complete ()
  "Return function definitions."
  (concat "defun:"
          (completing-read
           "Function: "
           #'help--symbol-completion-table
           #'fboundp
           'confirm
           nil nil))) ;    (and fn (symbol-name fn)) ?
(defun my-org-defun-link-description (link description)
  "Add documentation string as part of the description"
  (unless description
    (when (string-match "defun:\\(.+\\)" link)
      (let ((symbol (intern (match-string 1 link))))
        (when (documentation symbol)
          (concat (symbol-name symbol) ": "
                  (car (split-string (documentation symbol) "\n"))))))))

(defun my-org-defun-export (symbol description format _)
  "Export the function."
  (save-window-excursion
    (find-function (intern symbol))
    (let ((function-body (buffer-substring (point)
                                           (progn (forward-sexp) (point)))))
      (pcase format
        ((or '11ty 'html)
         (format " <details><summary>%s</summary><div class=\"org-src-container\"><pre class=\"src src-emacs-lisp\">%s</pre></div></details>"
                 (or description
                     (and (documentation (intern symbol))
                          (concat
                           symbol
                           ": "
                           (car (split-string (documentation (intern symbol)) "\n")))) 
                     symbol)
                 (org-html-do-format-code function-body "emacs-lisp" nil nil nil nil)))
        (`ascii function-body)
        (_ function-body)))))

(defun my-org-defun-store ()
  "Store a link to the function."
  (when (derived-mode-p 'emacs-lisp-mode)
    (org-link-store-props :type "defun"
                          :link (concat "defun:" (lisp-current-defun-name)))))

(defun my-org-defun-open (symbol _)
  "Jump to the function definition."
  (find-function (intern symbol)))

(org-link-set-parameters "defun" :follow #'my-org-defun-open
                         :export #'my-org-defun-export
                         :complete #'my-org-defun-complete
                         :insert-description #'my-org-defun-link-description
                         :store #'my-org-defun-store)
This is part of my Emacs configuration.
You can comment with Disqus or you can e-mail me at sacha@sachachua.com.