Linking to and exporting function definitions in Org Mode
| emacs, org- : Added ?link=1 to copy the context link
- 2023-09-12: added a way to force the defun to start open with ?open=1
- 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-open-complete () "Return function definitions." (concat "defun-open:" (completing-read "Function: " #'help--symbol-completion-table #'fboundp 'confirm nil nil))) (defun my-org-defun-open-export (link description format _) (my-org-defun-export (concat link (if (string-match "\\?" link) "&open=1" "?open=1")) description format _)) (defun my-org-defun-export (link description format _) "Export the function." (let (symbol params path-and-query) (if (string-match "\\?" link) (setq path-and-query (url-path-and-query (url-generic-parse-url link)) symbol (car path-and-query) params (url-parse-query-string (cdr path-and-query))) (setq symbol link)) (save-window-excursion (my-org-defun-open symbol) (let ((function-body (buffer-substring (point) (progn (forward-sexp) (point)))) body) (pcase format ((or '11ty 'html) (setq body (if (assoc-default "bare" params 'string=) (format "<div class=\"org-src-container\"><pre class=\"src src-emacs-lisp\">%s</pre></div>" (org-html-do-format-code function-body "emacs-lisp" nil nil nil nil)) (format "<details%s><summary>%s</summary><div class=\"org-src-container\"><pre class=\"src src-emacs-lisp\">%s</pre></div></details>" (if (assoc-default "open" params 'string=) " open" "") (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)))) (when (assoc-default "link" params) (setq body (format "%s<div><a href=\"%s\">Context</a></div>" body (my-copy-link)))) body) (`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 &rest _) "Jump to the function definition. If it's from a tangled file, follow the link." (find-function (intern (replace-regexp-in-string "\\?.*$" "" symbol))) (when (re-search-backward "^;; \\[\\[file:" nil t) (goto-char (match-end 0)) (org-open-at-point-global) (when (re-search-forward (concat "( *defun +" (regexp-quote (replace-regexp-in-string "\\?.*$" "" symbol))) nil t) (goto-char (match-beginning 0))))) (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-def-store) (org-link-set-parameters "defun-open" :follow #'my-org-defun-open :export #'my-org-defun-open-export :complete #'my-org-defun-open-complete :insert-description #'my-org-defun-link-description :store #'my-org-def-store)
my-copy-link
is at https://sachachua.com/dotemacs#web-link.
TODO Still allow linking to the file
Sometimes I want to link to a defun and sometimes I want to link to
the file itself. Maybe I can have a file link with the same kind of
scoping so that it kicks in only when defun:
would also kick in.
(defun my-org-defun-store-file-link () "Store a link to the file itself." (when (derived-mode-p 'emacs-lisp-mode) (org-link-store-props :type "file" :link (concat "file:" (buffer-file-name))))) (with-eval-after-load 'org (org-link-set-parameters "_file" :store #'my-org-defun-store-file-link))