The DevOps Blog

Emacs and Org-mode

I have recently found out, late I know, that the VSCode distribution of the so called Code - OSS is exactly that; a distribution.

Let me make it clear, the VSCode binaries you download from Microsoft has an upstream the GitHub repository named VSCode but in fact is not exactly the same code. Microsoft has already added a few gifts for you, including telemetry, not cool huh ?! Well, they tell you this in the documentation, urrrmmm somewhere.

At the same time, I was giving Jupyter Notebook a try. I worked on my previous post in it before writing down the final result as a blog post. But at the back of my mind, there was always Org-mode.

Putting one and one together, you've guessed it. I have moved to Emacs… again… for the umm I can't remember time. But this time, it is different ! I hope…

Back story

I was using Jupyter Notebooks as a way to write down notes. Organize things. I had a work around the output and was able to clean it. But let's face it, it might work but it is designed more towards other goals. I want to write notes and the best way to work with notes is to keep in the text, literally. I found a VSCode extension that can handle Org-mode in some capacity (I haven't tested it) so I decided to switch to Emacs and keep the extention as a backup.

Emacs Distribution of Doom

Haha ! Very funny, I know. I went with Doom. Why? You may ask. I don't really have a good answer for you except the following.

  • I didn't want to start from scratch, I wanted something with batteries included.
  • At the same time, I've tried Doom before and I like how it does things. It is logical to me while at the same time very configurable.
  • I was able to get up and running very quickly. Granted, my needs are few.
  • I got Python and Golang auto-completion and evil mode. I'm good to go !

Now let's dig down to my main focus here. Sure I switched editors but it was for a reason; Org-mode.

Org-mode Configuration

I will be talking about two different configuartion options here. I am new to emacs so I will try to explain everything.

The two options are related to the difference between a vanilla configuration and Doom's version of the configuration. The differences are minor but they are worth talking about.

New Org File

If you've used Org-mode before and created org files, you already know that you need to set a few values at the top of the file. These include the title, author, description and a different other values to change setting and/or behavior.

It is a bit of a manual labor to write these few lines at the beginning of every file. I wanted to automate that. So I got inspiration from shakthimaan.

I used his method to create a small define-skeleton for a header. It looks something like this.

(define-skeleton generate-new-header-org
"Prompt for title, description and tags"
nil
'(setq title  (skeleton-read "Title: "))
'(setq author  (skeleton-read "Author: "))
'(setq description  (skeleton-read "Description: "))
'(setq tags (skeleton-read "tags: "))
"#+TITLE: " title \n
"#+AUTHOR: " author \n
"#+DESCRIPTION: " description \n
"#+TAGS: " tags \n
)

You can use this later with M-x + genrate-new-header-org.

Note

M-x is the Meta key and x combination. Your Meta key can differ between the Alt on Linux and Command on Mac OS X.

M-x will open a prompt for you to write in. Write the name you gave the skeleton, in this case it is generate-new-header-org and then hit the Return.

New Task

shakthimaan already created something for this. It looks like the following.

;; Create a new skeleton to generate a new =Task=
(define-skeleton insert-org-entry
"Prompt for task, estimate and category"
nil
'(setq task  (skeleton-read "Task: "))
'(setq estimate  (skeleton-read "Estimate: "))
'(setq owner  (skeleton-read "Owner: "))
'(setq category (skeleton-read "Category: "))
'(setq timestamp (format-time-string "%s"))
"** " task \n
":PROPERTIES:" \n
":ESTIMATED: " estimate \n
":ACTUAL:" \n
":OWNER: " owner \n
":ID: " category "." timestamp \n
":TASKID: " category "." timestamp \n
":END:")

This can also be used like the one above with M-x + insert-org-entry.

Doom specific configuration

Whatever defined so far should work if you just add it to your configuration but if you use Doom it would a nice touch to integrate it with the workflow.

In ~/.doom.d/config.el, wrap the previous definitions with (after! org). It's a nice touch to add these skeletons after Org-mode has loaded.

(after! org
;; Create a skeleton to generate header org
(define-skeleton generate-new-header-org
    "Prompt for title, description and tags"
    nil
    '(setq title  (skeleton-read "Title: "))
    '(setq author  (skeleton-read "Author: "))
    '(setq description  (skeleton-read "Description: "))
    '(setq tags (skeleton-read "tags: "))
    "#+TITLE: " title \n
    "#+AUTHOR: " author \n
    "#+DESCRIPTION: " description \n
    "#+TAGS: " tags \n)

;; Create a new skeleton to generate a new =Task=
(define-skeleton insert-org-entry
    "Prompt for task, estimate and category"
    nil
    '(setq task  (skeleton-read "Task: "))
    '(setq estimate  (skeleton-read "Estimate: "))
    '(setq owner  (skeleton-read "Owner: "))
    '(setq category (skeleton-read "Category: "))
    '(setq timestamp (format-time-string "%s"))
    "** " task \n
    ":PROPERTIES:" \n
    ":ESTIMATED: " estimate \n
    ":ACTUAL:" \n
    ":OWNER: " owner \n
    ":ID: " category "." timestamp \n
    ":TASKID: " category "." timestamp \n
    ":END:")
)

warning

If you modify any file in ~/.doom.d/, do not forget to run doom sync and doom doctor to update and check your configuration respectively.

Final touches

I wanted to add it to the menu system that comes with Doom so I included the following in my (after! ...) block.

;; Add keybindings with the leader menu for everything above
(map! :map org-mode-map
    (:leader
	(:prefix ("m", "+<localleader>")
	:n :desc "Generate New Header Org" "G" 'generate-new-header-org
	:n :desc "New Task Entry" "N" 'insert-org-entry
	))
)

Making the final configuration look like the following.

(after! org
;; Create a skeleton to generate header org
(define-skeleton generate-new-header-org
    "Prompt for title, description and tags"
    nil
    '(setq title  (skeleton-read "Title: "))
    '(setq author  (skeleton-read "Author: "))
    '(setq description  (skeleton-read "Description: "))
    '(setq tags (skeleton-read "tags: "))
    "#+TITLE: " title \n
    "#+AUTHOR: " author \n
    "#+DESCRIPTION: " description \n
    "#+TAGS: " tags \n)

;; Create a new skeleton to generate a new =Task=
(define-skeleton insert-org-entry
    "Prompt for task, estimate and category"
    nil
    '(setq task  (skeleton-read "Task: "))
    '(setq estimate  (skeleton-read "Estimate: "))
    '(setq owner  (skeleton-read "Owner: "))
    '(setq category (skeleton-read "Category: "))
    '(setq timestamp (format-time-string "%s"))
    "** " task \n
    ":PROPERTIES:" \n
    ":ESTIMATED: " estimate \n
    ":ACTUAL:" \n
    ":OWNER: " owner \n
    ":ID: " category "." timestamp \n
    ":TASKID: " category "." timestamp \n
    ":END:")

(map! (:when (featurep! :lang org)
    (:map org-mode-map
    (:localleader
	:n :desc "Generate New Header Org" "G" 'generate-new-header-org
	:n :desc "New Task Entry" "N" 'insert-org-entry
	))
    ))
)

What do I do now ?

You might be asking yourself at this point, what does this all mean ? What do I do with this ? Where do I go ?

Well here's the thing. You find yourself wanting to create a new org file. You do so in emacs and follow it with M-x + generate-new-header-org (or SPC m G in Doom). Emacs will ask you a few questions in the bottom left corner and once you answer then, your header should be all set.

You can follow that with M-x + insert-org-entry (or SPC m N) to generate a task. This will also ask you for input in the bottom left corner.

Conclusion

This should help me pick up the usage of Org-mode faster. It is also a good idea if you've already configured your Emacs to read all your org file for a wider agenda view.