write.as

<div class="ox-hugo-toc toc"> <div></div> <div class="heading">Table of Contents</div> - [Problem](#problem) - [Action Plan](#action-plan) - [Am I in an Org block?](#org-in-any-block-p) - [If so, split the block](#org-block-split) - [Now make <kbd>M-return</kbd> do that](#M-return-split-block-dwim) - [Full code](#full-code) </div> <!--endtoc--> <span class="timestamp-wrapper"><span class="timestamp"><2018-08-26 Sun></span></span> : Mention `org-babel-demarcate-block`, tweak the `org-meta-return` advice. <span class="timestamp-wrapper"><span class="timestamp"><2018-08-23 Thu></span></span> : Use <kbd>M-return</kbd> instead of <kbd>C-return</kbd> for splitting blocks, support upper-case blocks (though I [don't prefer those](https://scripter.co/org-keywords-lower-case/)!) ## Problem {#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: ```org #+begin_src emacs-lisp (message "one") (message "two") #+end_src ``` into: ```org #+begin_src emacs-lisp (message "one") #+end_src #+begin_src emacs-lisp (message "two") #+end_src ``` .. [like this](org-block-splitting-in-action.gif). ☝ _Click for animation._ ## Action Plan {#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 <kbd>M-return</kbd> binding so that this block splitting function gets called only when the point is inside an Org block (detected using that first function). <div class="note"> <div></div> <span class="timestamp-wrapper"><span class="timestamp"><2018-08-26 Sun> </span></span> Thanks to the comment by reader **Mankoff**, I learnt about the `org-babel-demarcate-block` function (bound by default to <kbd>C-c C-v d</kbd> and <kbd>C-c C-v C-d</kbd>). 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. </div> ## Am I in an Org block? {#org-in-any-block-p} Before venturing into writing this function, I looked at these existing ones, but none did what I exactly wanted: `org-in-src-block-p` : Returns _non-nil_ only if the point is in a `#+begin_src .. #+end_src` block; not when point is in any other Org block. `org-in-block-p` : 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). <a id="code-snippet-1"></a> ```emacs-lisp (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'." (save-match-data (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) (save-excursion ;; 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)))))) ``` <div class="src-block-caption"> <span class="src-block-number"><a href="#code-snippet-1">Code Snippet 1</a></span>: Function to check if point is in any Org block </div> - `(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`. <!--listend--> Caveat : 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: ```text #+begin_src org ▮ #+begin_src emacs-lisp (message "hello!") #+end_src #+end_src ``` ## If so, split the block {#org-block-split} 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 point[^fn:1] is after the first `message` identifier, or at the end of that first `message` line: ```org #+begin_src emacs-lisp (message "one")▮ (message "two") #+end_src ``` Split the block at the point **after** `(message "one")` and move the point to between the split blocks: ```org #+begin_src emacs-lisp (message "one") #+end_src ▮ #+begin_src emacs-lisp (message "two") #+end_src ``` 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: ```org #+begin_src emacs-lisp (message "one") ▮(message "two") #+end_src ``` Split the block at the point **before** `(message "two")` and move the point to between the split blocks: ```org #+begin_src emacs-lisp (message "one") #+end_src ▮ #+begin_src emacs-lisp (message "two") #+end_src ``` So here's the code that follows that spec: <a id="code-snippet-2"></a> ```emacs-lisp (defun modi/org-split-block () "Sensibly split the current Org block at point." (interactive) (if (modi/org-in-any-block-p) (save-match-data (save-restriction (widen) (let ((case-fold-search t) (at-bol (bolp)) block-start block-end) (save-excursion (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") block-end "\n\n" block-start (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"))) ``` <div class="src-block-caption"> <span class="src-block-number"><a href="#code-snippet-2">Code Snippet 2</a></span>: Function to split the current Org block in sensible fashion </div> - The regexp for extracting `block-start` is the same as `block-begin-re` in code snippet [1](#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 <kbd>M-return</kbd> do that {#M-return-split-block-dwim} With these two functions evaluated, `M-x modi/org-split-block` will work right away.     But where's the fun in that‽<br /> I needed to have the Org block splitting happen with an intuitive binding --- like <kbd>M-return</kbd>. - By default, <kbd>M-return</kbd> 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 <span class="timestamp-wrapper"><span class="timestamp"><2018-08-26 Sun></span></span>), 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._ <a id="code-snippet-3"></a> ```emacs-lisp (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 `org-insert-heading'." (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) ``` <div class="src-block-caption"> <span class="src-block-number"><a href="#code-snippet-3">Code Snippet 3</a></span>: Advising <code>org-meta-return</code> to add context of point being inside any Org block </div> Now with the point in **any** Org block, <kbd>M-return</kbd> away! ## Full code {#full-code} Look for the source of `modi/org-split-block` (and dependent functions) added to [`setup-org.el`](https://github.com/kaushalmodi/.emacs.d/blob/master/setup-files/setup-org.el) in my Emacs config. [^fn:1]: The point is denoted by the _BLACK VERTICAL RECTANGLE_ unicode char (▮). [//]: # "Exported with love from a post written in Org mode" [//]: # "- https://github.com/kaushalmodi/ox-hugo"