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)))))