Where parallels cross

Interesting bits of life

Org crypt do not forget my point!

Too long; didn't read

When you encrypt and decrypt an Org Mode heading with Org crypt you lose the position of your cursor. In this blog I show you how to restore it automatically, so that you can feel free to save your buffer at will.

The problem

A lot of my headings are encrypted because I like to keep my sensitive information private. I use Org crypt because its encryption has the side effect of making the file smaller and also it leaves the file in plain text so that I can check changes with my version control system.

Org crypt is a great mode overall, and those problems I had with it I could fix easily (see here and here for example). Now something I kept doing was to recover my position after saving a file. Org crypt comes with the function org-crypt-use-before-save-magic; it sets encryption of all your headings (tagged with :crypt:) after you save an Org Mode file. That is smart, but after encryption you lose the point! If your heading is long enough, that means you have to search where you were. That is boring enough to make me wish to act!

It is a problem indeed

The real bad part is how your brain reacts to this kind of boredom. Mine slowly made me save my files less often! Result: I have lost some useful contents (because I crashed Emacs opening a big JSON file without using vlf)!

That really bored me enough to act!

And there is a solution

In a previous blog I already implemented a way to store the position after encrypting and decrypting a heading. That previous experience lead me to design a solution for my problem:

  1. save the point for each heading that I encrypt
  2. restore the position when if I reveal the encrypted heading.

Let's start from storage:

(setq my/org-last-positions nil)

(defun my/store-last-org-position ()
  "Store point  for current heading."
  (when (org-get-heading)
    (setq my/org-last-positions (cons (list (org-id-get-create) (point)) my/org-last-positions))))

So my/org-last-positions is our accumulator of heading positions. I use org-id as a way to identify headings because it works particularly well for finding archived entries. So in this code org-id is the way I identify a heading. Also notice that the code runs only if we are in a Org heading (org-get-heading will return nothing otherwise and will make the when body miss execution).

The result of calling this function on a heading is like (setq my/store-last-heading (list ("someId" (point)))).

Once we have a collection of stored positions we need to use them:

(defun my/pop-last-org-position ()
  "Go to old point for current heading."
  (when (org-get-heading)
    (let* ((org-id (org-id-get-create))
           (pair (--find (string= org-id (car it)) my/org-last-positions)))
      (when pair
        (goto-char (cadr pair))
        (setq my/org-last-positions (--remove (string= org-id (car it)) my/org-last-positions))))))

Again this only works if we are in a heading and, given we --find the stored position, it moves to that and pops that position from our accumulator.

With this two elements we are about done. We just need to call them at the right moment.

First, we need to call my/store-last-org-position just before we encrypt an entry. We can copy org-crypt-use-before-save-magic for that: let's use the before-save-hook:

(remove-hook 'before-save-hook 'org-encrypt-entries)
(defun my/wrapper-org-encrypt-entries ()
  (my/store-last-org-position)
  (org-encrypt-entries))
(add-hook 'before-save-hook 'my/wrapper-org-encrypt-entries)

You can see that I prefer to cleanup that hook from org-encrypt-entries because we have to ensure that we catch the position just before the entry gets encrypted. I then enforce that by running the store function before encryption in my/wrapper-org-encrypt-entries.

Similarly we want to use that position when we revealing a heading:

(remove-hook 'org-reveal-start-hook 'org-decrypt-entry)
(defun my/wrapper-org-decrypt-entry ()
  (org-decrypt-entry)
  (my/pop-last-org-position)
  (recenter-top-bottom))
(add-hook 'org-reveal-start-hook 'my/wrapper-org-decrypt-entry)

The hook this time is org-reveal-start-hook a function from Org Mode that opens up a folded heading. Org crypt again adds its decryption function to this hook and we again have to enforce that decryption happens before us trying to restore the old position. I also like to recenter the screen in that case, so I use (recenter-top-bottom) but that is up to your tastes.

And that is all!

Conclusion

Org crypt users: you will like this! Just load the code above and enjoy the time saving!

Happy decrypting!

Comments