5

When doing literate programming, I do web development in a single org file, and do org tangle to generate the relevant html and css files. This works, for a few hours/days. When the number of files in the org file becomes more than 20 for example, it's a real hassle to isearch/navigate between the #+BEGIN_SRC blocks.

I would very much prefer if I can use something similar to the usual emacs way of switching buffers C-x C-b switch-buffer. So in an possible solution, take this org file for example

#+BEGIN_SRC css :tangle public/styles.css
 some css
#+END_SRC

literate discussion here.

** org subheading
  + list item1
  + list item 2

#+BEGIN_SRC html :tangle public/index.html
<!doctype html>
...
#+END_SRC

When I press C-u C-x C-b, I am presented with these literate-buffer-menu options:

public/index.html
public/styles.css

If would switch/focus to one of these begin_src blocks using similar buffer-menu like flow.

Literate programming has been around for about 20 years, emacs more than double that, so I'm wondering other folks ran in to my issue and overcame it.

2
  • 1
    Literate programming has been around for over 30 years, not 20.
    – Drew
    Feb 12, 2018 at 5:03
  • Here is a related tool by John Kitchin: Jump to a tagged src block.
    – Juancho
    Feb 12, 2018 at 19:02

1 Answer 1

5

You can try the command list-org-src from the following elisp code.

It is tested with emacs 25.3.1 and org-mode 9.1.6.

Install the source in your init file (after (package-initialize)).

Open your org file and type M-x list-org-src RET.

That creates an org-src buffer with a tabulated list of the source code block entries. Each entry has the following components:

  • Name: the #+NAME:-component of the source code block
  • Language: the programming language
  • Headline: start of the parent-headline
  • Tangle: the tangle file

If you click on the name of a source block in the source block list that source block is searched for in the org file. If you click on the tangle entry the tangle file is found.

Bind list-org-src to whatever key sequence you want. Note that you shouldn't misuse C-x C-b since this binding is global and important. Better bind a user-specific key-sequence such as C-c b.

Note, that list-org-src does not save buffer positions. It identifies source blocks its sha1 code or by the Name, the Language, and the Tangle component. If you click on a Name link point of the org file is moved to the first source block in the file where either the sha1 code or all of the three indicators match. If you have edited the source code after the last call to list-org-src and there are multiple source blocks matching the indicators only the first source block in the org file is found.

This strategy allows you to edit the org buffer and use the org-src buffer without updating the latter.

(require 'org)
(require 'org-element)
(require 'ob-core)
(require 'pulse)

(defgroup org-list-src nil
  "Customization group for listing source blocks."
  :group 'org-babel)

(defcustom org-list-src-search '(sha1 name-language-tangle)
  "Search method for org source blocks.
Possible choices are:
sha1: Use sha1 key for comparing source blocks.
      Source blocks match if they and their properties are not edited.
      This method may cause lower performance for large org files.
name-language-tangle: Use the source block name, the programming language,
                      and the tangle file.
                      Source blocks match if those three indicators match."
  :type '(set (const sha1) (const name-language-tangle))
  :group 'org-list-src)

(defvar-local org-list-src-org-buf nil
  "Original org mode buffer corresponding to the org source block list")

(defun org-list-src-assert ()
  "Make sure we have an original org buffer."
  (cl-assert (buffer-live-p org-list-src-org-buf) nil "Missing org-mode buffer %s" org-list-src-org-buf))

(defun org-list-src-tangle (element)
  "Get tangle header argument from org element ELEMENT."
  (cdr (assoc :tangle (org-babel-parse-header-arguments (org-element-property :parameters element)))))

(defun org-list-src-sha1 (sblock)
  "Get the sha1 key of the source block SBLOCK.
SBLOCK can be the result of `org-element-context'
or one of the elements in the result of `org-element-parse-buffer'."
  (org-babel-sha1-hash (org-babel-get-src-block-info nil sblock)))

(defun org-list-src-show (sblock)
  "Show source block SBLOCK in currently selected org buffer window."
  (display-buffer (current-buffer))
  (let ((beg (org-element-property :begin sblock))
        (end (org-element-property :end sblock)))
    (with-selected-window (get-buffer-window)
      (goto-char beg)
      (org-reveal)
      (pulse-momentary-highlight-region beg end)
      (recenter))))

(defun org-list-src-button-find (button)
  "Find org source block with matching BUTTON.
BUTTON is a button with the properties retrived by `button-get':
:src-name   The name of the source block.
:src-language   The type of the source block.
:src-tangle The target file name."
  (org-list-src-assert)
  (let ((name (button-get button :src-name))
        (language (button-get button :src-language))
        (tangle (button-get button :src-tangle))
        (sha1 (button-get button :src-sha1)))
    (with-current-buffer org-list-src-org-buf
      (let ((data (org-element-parse-buffer)))
        (cond
         ;; 1st try comparing sha1 keys (may fail if the block is edited)
         ((memq 'sha1 org-list-src-search)
              (org-element-map data 'src-block
                (lambda (sblock)
                  (when
                      (string=
                       (org-list-src-sha1 sblock)
                       sha1)
                    (org-list-src-show sblock)
                    t))
                nil t))
         ;; 2nd try comparing name, language, and tangle file
         ((memq 'name-language-tangle org-list-src-search)
          (org-element-map data 'src-block
            (lambda (sblock)
              (when
                  (and
                   (equal name (org-element-property :name sblock))
                   (string= language (org-element-property :language sblock))
                   (equal tangle (org-list-src-tangle sblock)))
                (org-list-src-show sblock)
                t))
            nil t))
         (t
          (message "Source block not found.")))))))

(defun org-list-src-button-find-file (button)
  "Find file with name retrieved as property :src-tangle from BUTTON."
  (let ((fname (button-get button :src-tangle)))
    (when (stringp fname)
      (find-file fname))))

(defun org-list-src-button-headline (button)
  "Find headline from button in org buffer."
  (org-list-src-assert)
  (let ((headline (button-get button :src-headline)))
    (when (stringp headline)
      (with-current-buffer org-list-src-org-buf
    (goto-char (org-find-exact-headline-in-buffer headline))
    (org-list-src-show (org-element-context))))))

(defun org-list-src-headline (sblock)
  "Determine parent headline of source block SBLOCK."
  (catch :found
    (while
        (progn
          (setq sblock (org-element-property :parent sblock))
          (and sblock
               (null
                (and (eq (org-element-type sblock) 'headline)
                     (throw :found (org-element-property :raw-value sblock)))))))))

(defun org-list-src-entries ()
  "Create list of org source code blocks suitable for `tabulated-list-entries'.
The command works in `org-list-src-mode' buffers and the list is generated for org buffer `org-list-src-org-buf'."
  (org-list-src-assert)
  (with-current-buffer org-list-src-org-buf
    (let ((data (org-element-parse-buffer)))
      (org-element-map
          data 'src-block
        (lambda (sblock)
          (let* ((name (org-element-property :name sblock))
                 (language (or (org-element-property :language sblock)
                               (error "Not accepting source block without language")))
                 (headline (or (org-list-src-headline sblock) ""))
                 (tangle (org-list-src-tangle sblock))
                 (sha1 (org-list-src-sha1 sblock)))
            (list name
                  (vector
                   (list (or name "*no name*")
                         'action #'org-list-src-button-find
                         :src-name name
                         :src-language language
                         :src-headline headline
                         :src-tangle tangle
                         :src-sha1 sha1)
                   language
                   (if (stringp headline)
               (list headline 'action #'org-list-src-button-headline :src-headline headline)
             "")
                   (if tangle
                       (list tangle 'action #'org-list-src-button-find-file :src-tangle tangle)
                       "")
                   ))))))))

(define-derived-mode org-list-src-mode tabulated-list-mode "OrgSrc"
  "Mode for listing org src blocks."
  (setq tabulated-list-format [("Name" 20 t) ("Language" 20 t) ("Headline" 40 t) ("Tangle" 0 t)]
        tabulated-list-entries #'org-list-src-entries))

(defun list-org-src ()
  "List source blocks of current org buffer."
  (interactive)
  (let* ((orig-buffer (current-buffer)))
    (with-current-buffer (get-buffer-create (format "*org-src:%S*" (buffer-name)))
      (setq buffer-read-only nil) ;; tabulated-list-mode sets this to t
      (delete-region (point-min) (point-max))
      (org-list-src-mode)
      (setq org-list-src-org-buf orig-buffer)
      (tabulated-list-init-header)
      (tabulated-list-print)
      (display-buffer (current-buffer)))))
3
  • I gave this code a try today. looks legit. Feb 23, 2018 at 18:03
  • theres a typo when you said: Open your org file and type M-x list-org-src-blocks RET. Feb 23, 2018 at 18:07
  • @joshsverns I have corrected all occurrences of org-list-src-blocks and list-org-src-blocks to the shorter list-org-src. From what I know the imperative form list-... is legal and preferred even if it does not have the prefix org-src- of the package.
    – Tobias
    Feb 23, 2018 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.