notes from /dev/null

by Charles Choi 최민수


Capturing Org Source Blocks

08 Jul 2025  Charles Choi

A commonplace activity is to copy source code from a web page into an Org file. Ideally such code is kept structurally intact by wrapping it in an Org source block. This is typically done as follows:

  1. Select source code text in web page.
  2. Copy source code text.
  3. Paste source code into Org file.
  4. Annotate the pasted source code with an Org source block, specifying language. (#+BEGIN_SRC {language}, #+END_SRC)
    • Either done manually or using the command org-insert-structure-template (C-c C-,)

While the above is not complicated, it can get onerous if done frequently. Thankfully Org provides you with the means to automate this. For example with Org protocol, the above steps can be reduced to:

  1. Select source code text in web page (no need to copy!).
  2. Run web bookmark to capture selected text to Emacs.
  3. Select source code language via mini-buffer completion in Emacs and commit capture (C-c C-c).

Interested? Read on. Some Elisp ahead, but hopefully not too challenging.

Capturing code with Org protocol

A convenient way to capture content from a web page is through Org protocol which builds on top of Org capture. Some setup is required before using it, but the effort is rewarded in saved time for a future you.

Shown below is a capture template with key name “code”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
("code"
 "Source Code (Org Protocol)"
 entry
 (file "~/org/notes.org")
 (function (lambda ()
             (string-join
              (list "* Source: %:description"
                    ":PROPERTIES:"
                    ":CREATED: %U"
                    ":END:"
                    "%:link"
                    (concat "#+BEGIN_SRC "
                            (cc-org-capture--code-choices "elisp"))
                    "%i"
                    "#+END_SRC"
                    ""
                    "%?")
              "\n")))
 :empty-lines 1)

The Elisp function cc-org-capture--code-choices will generate capture expansion markup to choose a language with mini-buffer completion. It is configured here to use “elisp” as the default language. The set of languages to choose from via completion is defined in cc-org-capture--src-languages. Both their definitions are shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(defun cc-org-capture--code-choices (lang)
  "Create selection of programming language choices with default LANG."
  (concat
   "%^{Language|"
   lang
   "|"
   (string-join cc-org-capture--src-languages "|")
   "}"))

(defvar cc-org-capture--src-languages
  (list
   "C" "F90" "R" "awk" "clojure" "cpp" "css" "ditaa" "dot" "elisp" "eshell"
   "forth" "gnuplot" "haskell" "java" "js" "julia" "kotlin" "latex" "lisp" "lua"
   "makefile" "matlab" "max" "ocaml" "octave" "org" "perl" "plantuml"
   "processing" "python" "ruby" "sass" "scheme" "sed" "shell" "sql" "sqlite"
   "swift" "swiftui" "tcl")
  "List of supported Org capture languages.")

To exercise the capture template named “code” from your JavaScript enabled web-browser, create a bookmark with the following JavaScript code as its location. When you find code you want to capture, select the code and run this bookmark.

1
2
3
4
5
javascript:location.href='org-protocol://capture?' +
      new URLSearchParams({
            template: 'code', url: window.location.href,
            title: document.title, body: window.getSelection()});
      void(0);

Aside: if you use Emacs for macOS, you’ll want Scrim to get Org protocol to work.

Capturing source code by copying

Often it is desirable to just copy the source code (putting it in the kill-ring) and paste (or rather capture) it direct into an Org (or Markdown) file of one’s choosing. The following capture template named “cc” can accomplish this.

1
2
3
4
5
6
7
("cc"
 "Code"
 plain
 (here)
 (function cc-org-capture--code-select-body)
 :empty-lines 1
 :immediate-finish 1)

In the above template, the target (here) is used to insert the captured source code at the current point. The template type is set to plain to not give the captured source code any special treatment with respect to Org structure. The :immediate-finish key is set to non-nil to not create a separate capture window.

The “cc” template is intended to be invoked with a multiple-key sequence starting with ’c’, so the following entry in org-capture-templates is expected.

1
("c" "Code")

The work of constructing the wrapped source code (in this case, presumed to be in the kill-ring via system copy) is done in cc-org-capture--code-select-body. Its source is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(defun cc-org-capture--code-select-body ()
  "Body capture code in `kill-ring' head with language prompt."
  (cc-org-capture--code-body-from-kill-ring
   (cc-org-capture--code-choices "elisp")))

(defun cc-org-capture--code-body-from-kill-ring (lang)
  "Generate capture body for LANG code at head of the `kill-ring'."
  (string-join
   (list
    (cc-org-capture--src-block-begin lang)
    (if kill-ring "%c" "")
    (cc-org-capture--src-block-end))
   "\n"))

(defun cc-org-capture--src-block-begin (&optional lang mode)
  "Annotate begin of source code block for given LANG and MODE."
  (let* ((lang (if lang lang "elisp"))
         (mode (if mode mode major-mode)))
    (cond
     ((eq (derived-mode-p mode) 'org-mode) (concat "#+BEGIN_SRC" " " lang))
     ((eq (derived-mode-p mode) 'markdown-mode) (concat "```" lang))
     (t (concat "#+BEGIN_SRC" " " lang)))))

(defun cc-org-capture--src-block-end (&optional mode)
  "Annotate end of source code block for given MODE."
  (let* ((mode (if mode mode major-mode)))
    (cond
     ((eq (derived-mode-p mode) 'org-mode) "#+END_SRC")
     ((eq (derived-mode-p mode) 'markdown-mode) "```")
     (t "#+END_SRC"))))

The implementation of cc-org-capture--code-select-body will check the current major mode (major-mode) and depending if it is Org or Markdown, wrap the copied source code in the kill-ring (expanded by the term “%c”) accordingly.

The following code shows how the “cc” template can be added to org-capture-templates.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(add-hook
 'org-mode-hook
 (lambda ()
   (add-to-list 'org-capture-templates
                '("cc"
                  "Code"
                  plain
                  (here)
                  (function cc-org-capture--code-select-body)
                  :empty-lines 1
                  :immediate-finish 1)
                t)))

If you have C-c c bound to org-capture then you should see the “cc” template in the ’c’ sub-menu.

Caveats

If the source code to be captured contains any text that matches a capture template expansion then it will be processed, likely resulting in unintended behavior. Look out for text that starts with ’%’.

Closing Thoughts

If you’re still here thanks for your attention! This post required a fair amount of Elisp understanding, but the reward for knowing it is the ability to build very bespoke Org capture workflows. Motivated readers are encouraged to take these examples and run with them. To this end I’ve deliberately left out a capture template example of “hard-coding” a language as an exercise to the reader.

If you end up making a template that you think others would find useful, please share it!

Links

If you're on macOS and have Org protocol setup, you might be interested in this post "Capturing an Org note via macOS Shortcuts".

emacs   org mode   scrim

 

AboutMastodonBlueskyGitHub

Feeds & Tags
Get Scrim for macOSGet Captee for macOS

Powered by Pelican