Emacs, scripting and anything text oriented.

Splitting an Org block into two

Kaushal Modi

I ventured out to start writing about a 100+ line Emacs Lisp snippet in my config, and then I thought — Wouldn’t it be nice if I can quickly split out that huge snippet into smaller Org Src blocks?

And so this blog post happened.

<2018-08-26 Sun>
Mention org-babel-demarcate-block, tweak the org-meta-return advice.
<2018-08-23 Thu>
Use M-return instead of C-return for splitting blocks, support upper-case blocks (though I don’t prefer those!)

Problem #

If I have a huge Org Src block, I’d like to split it into multiple Org Src blocks so that I can write my explanations in-between.

So I’d like to quickly split up:

#+begin_src emacs-lisp
(message "one")
(message "two")


#+begin_src emacs-lisp
(message "one")

#+begin_src emacs-lisp
(message "two")

.. like this.

Click for animation.

Action Plan #

  1. Write a function to return non-nil if point is in any Org block – Not just “src”, “example”, “export” or any of the inbuilt Org blocks.. but also any Org Special block like #+begin_foo .. #+end_foo.
  2. Write a function that does this imagined block splitting.
  3. Overload the M-return binding so that this block splitting function gets called only when the point is inside an Org block (detected using that first function).

<2018-08-26 Sun> Thanks to the comment by reader Mankoff, I learnt about the org-babel-demarcate-block function (bound by default to C-c C-v d and C-c C-v C-d).

This function varies from the solution in this post in at least two ways:

  1. It works only for Org Src blocks.
  2. It splits the block exactly at where the point is, whereas I would like to always split only at EOL or BOL.

But I can see that org-babel-demarcate-block can cover most of the block splitting use cases.

Am I in an Org block? #

Before venturing into writing this function, I looked at these existing ones, but none did what I exactly wanted:

Returns non-nil only if the point is in a #+begin_src .. #+end_src block; not when point is in any other Org block.
Returns non-nil only if the point is in one of the pre-defined block names passed as a list ('("src" "example" "quote" ..)). So this again won’t work as I cannot pre-define all Org Special blocks.

So I define the below modi/org-in-any-block-p function that returns non-nil if the point is in-between any #+begin_FOOBAR .. #+end_FOOBAR. Thankfully, I was able to reuse a lot of logic from the org-between-regexps-p function (org-in-block-p uses that function internally).

(defun modi/org-in-any-block-p ()
  "Return non-nil if the point is in any Org block.

The Org block can be *any*: src, example, verse, etc., even any
Org Special block.

This function is heavily adapted from `org-between-regexps-p'."
    (let ((pos (point))
          (case-fold-search t)
          (block-begin-re "^[[:blank:]]*#\\+begin_\\(?1:.+?\\)\\(?: .*\\)*$")
          (limit-up (save-excursion (outline-previous-heading)))
          (limit-down (save-excursion (outline-next-heading)))
          beg end)
        ;; Point is on a block when on BLOCK-BEGIN-RE or if
        ;; BLOCK-BEGIN-RE can be found before it...
        (and (or (org-in-regexp block-begin-re)
                 (re-search-backward block-begin-re limit-up :noerror))
             (setq beg (match-beginning 0))
             ;; ... and BLOCK-END-RE after it...
             (let ((block-end-re (concat "^[[:blank:]]*#\\+end_"
                                         (match-string-no-properties 1)
                                         "\\( .*\\)*$")))
               (goto-char (match-end 0))
               (re-search-forward block-end-re limit-down :noerror))
             (> (setq end (match-end 0)) pos)
             ;; ... without another BLOCK-BEGIN-RE in-between.
             (goto-char (match-beginning 0))
             (not (re-search-backward block-begin-re (1+ beg) :noerror))
             ;; Return value.
             (cons beg end))))))
Code Snippet 1: Function to check if point is in any Org block
  • (case-fold-search t) ensures that either #+BEGIN_ .. or #+begin_ .. match.
  • The regular expression in block-begin-re matches with "#+begin_src foo" or " #+begin_src foo" or "#+BEGIN_EXAMPLE" or "#+begin_FOOBAR" or ..
  • The limit-up and limit-down are set to the buffer locations of the previous and next Org headings. The following regexp searches are limited to happen in those bounds for better performance.
  • The block-end-re is dynamically constructed based on the string matched using block-begin-re. This is so that if "#+begin_quote" is found initially, it matches the block ending with specifically "#+end_quote" and not something like "#+end_src".
  • nil is returned if the point is not between #+begin_FOOBAR .. #+end_FOOBAR.

I haven’t gone extra lengths to support nested block cases, specifically where the point is outside the inner-most block, but still inside the outer block:

#+begin_src org
#+begin_src emacs-lisp
(message "hello!")

If so, split the block #

With the “point in an Org block” detection working, I now needed the split to happen with these rules:

  1. If the point is anywhere on the line, but not at the beginning of the line (BOL),

    • Go to the end of the line, and then split the block.

      So if the point1 is after the first message identifier, or at the end of that first message line:

      #+begin_src emacs-lisp
      (message "one")
      (message "two")

      Split the block at the point after (message "one") and move the point to between the split blocks:

      #+begin_src emacs-lisp
      (message "one")
      #+end_src#+begin_src emacs-lisp
      (message "two")
  2. Otherwise (if point is at BOL),

    • Split the block exactly at that point.

      So if the point is at the beginning of the second message line:

      #+begin_src emacs-lisp
      (message "one")
      (message "two")

      Split the block at the point before (message "two") and move the point to between the split blocks:

      #+begin_src emacs-lisp
      (message "one")
      #+end_src#+begin_src emacs-lisp
      (message "two")

So here’s the code that follows that spec:

(defun modi/org-split-block ()
  "Sensibly split the current Org block at point."
  (if (modi/org-in-any-block-p)
          (let ((case-fold-search t)
                (at-bol (bolp))
              (re-search-backward "^\\(?1:[[:blank:]]*#\\+begin_.+?\\)\\(?: .*\\)*$" nil nil 1)
              (setq block-start (match-string-no-properties 0))
              (setq block-end (replace-regexp-in-string
                               "begin_" "end_" ;Replaces "begin_" with "end_", "BEGIN_" with "END_"
                               (match-string-no-properties 1))))
            ;; Go to the end of current line, if not at the BOL
            (unless at-bol
              (end-of-line 1))
            (insert (concat (if at-bol "" "\n")
                            (if at-bol "\n" "")))
            ;; Go to the line before the inserted "#+begin_ .." line
            (beginning-of-line (if at-bol -1 0)))))
    (message "Point is not in an Org block")))
Code Snippet 2: Function to split the current Org block in sensible fashion
  • The regexp for extracting block-start is the same as block-begin-re in Code Snippet 1, but with different sub-grouping.
  • The block-end string is derived from sub-group 1 of block-start string – just replacing “begin_” with “end_”.
  • And then based on if the point was initially at BOL (at-bol), the insertion of newlines and movement of point is done accordingly.

Now make M-return do that #

With these two functions evaluated, M-x modi/org-split-block will work right away.

    But where’s the fun in that‽

I needed to have the Org block splitting happen with an intuitive binding — like M-return.

  • By default, M-return is used to either create new headings, or do other things like insert an item, wrap a region in table, etc. based on the context. See the doc-string of org-meta-return (function bound to this key by default) for more info.
  • But it doesn’t have a context for “point in an Org block”. So it tries to create a heading when inside a block too, which doesn’t make much sense.
  • So fix that by adding that context.

So I advise org-meta-return to call modi/org-split-block when the point is inside an Org block.

The advising function modi/org-meta-return is the same as the advised function org-meta-return (as of <2018-08-26 Sun>), except that a new context (modi/org-in-any-block-p) is added.

You can tweak the precedence of this new context by moving the ((modi/org-in-any-block-p) #'modi/org-split-block) form in that cond form.

(defun modi/org-meta-return (&optional arg)
  "Insert a new heading or wrap a region in a table.

Calls `org-insert-heading', `org-insert-item',
`org-table-wrap-region', or `modi/org-split-block' depending on
context.  When called with an argument, unconditionally call
  (interactive "P")
  (org-check-before-invisible-edit 'insert)
  (or (run-hook-with-args-until-success 'org-metareturn-hook)
      (call-interactively (cond (arg #'org-insert-heading)
                                ((org-at-table-p) #'org-table-wrap-region)
                                ((org-in-item-p) #'org-insert-item)
                                ((modi/org-in-any-block-p) #'modi/org-split-block)
                                (t #'org-insert-heading)))))
(advice-add 'org-meta-return :override #'modi/org-meta-return)
Code Snippet 3: Advising org-meta-return to add context of point being inside any Org block

Now with the point in any Org block, M-return away!

Full code #

Look for the source of modi/org-split-block (and dependent functions) added to setup-org.el in my Emacs config.

  1. The point is denoted by the BLACK VERTICAL RECTANGLE unicode char (▮). [return]

Versions used: emacs 27.0.50 , org release_9.1.13-894-gf79545

If you have written a response to this, enter your response post's URL below.

Or, you can send a "comment" webmention (it's OK if you don't know what that means). When asked about your website on an IndieAuth login screen, simply type https://commentpara.de.

Markdown Support**bold**, _italics_, ~~strikethrough~~, [descr](link), `monospace`, ```LANG\nline1\nline2\n``` (Yep, multi-line code blocks too, with syntax highlighting!), auto-hyperlinking.

Webmentions #

Comment by Adam Porter on Sun Aug 26, 2018 13:37 EDT
This is great, and I like your optimization, compared to the alternatives, that splits at EOL/BOL. May I suggest that you submit this as a patch to Org? :) Maybe it could be called org-block-split.
Comment by Kaushal Modi on Sun Aug 26, 2018 02:02 EDT


Have a look at the function scimax-split-src-block from John Kitchin

I was not aware of that function. I looked it up, and from its source, it looks like it supports only the Org Src blocks.

As I noted above, I needed a function to split any Org block; Src blocks is one of them. For example, I find myself splitting #+begin_example .. #+end_example blocks a lot too.

Also it might be interesting to see how this function differentiates from the org-babel-demarcate-block function that @Mankoff mentions in his comment.

Best wishes and thanks for the post!

Thanks, and welcome! :)


What about C-c C-v C-d?

I did not know about org-babel-demarcate-block before this!

Though, after going through its doc-string and source, I am thankful that all the exercise in this post did not go to waste :)

org-babel-demarcate-block also deals only with Org Src blocks, and I needed a function that splits any Org block as I note above.

I tried it out, and it also behaves a bit differently than how I would like when point is in-between a line. See my spec about “If the point is anywhere on the line .. Go to the end of the line, and then split the block.”.

That said, now that I know how this function, I’ll find ways to use this vs the one I define in this post.


Comment by Cumol on Sat Aug 25, 2018 04:55 EDT

Have a look at the function scimax-split-src-block from John Kitchin


I guess it is doing the same?

Best wishes and thanks for the post!

Comment by Mankoff on Sat Aug 25, 2018 00:53 EDT
What about C-c C-v C-d?
Mentioned at