Org-mode configuration

Table of Contents

This is my org-mode configuration that you can download as an org file. You will find the rest of my dotfiles (including my full Doom Emacs configuration) on Github.

If you want to use this file for configuration, open it in Emacs and run org-babel-tangle, or C-c C-v t (you need to have org-mode and org-babel installed).

Setting up “TODO” keywords

Those keywords are from “Organize your life in plain text!”. There are three “main” keywords and a WAITING, HOLD and CANCELLED keyword that each require a note for justifying moving to these states.

(setq org-todo-keywords
    (quote ((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
            (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)"))))

This is so I cannot set a headline to DONE if children aren’t DONE.

(setq-default org-enforce-todo-dependencies t)

“TODO” faces and export settings

This sets the colors of keywords. Not that important because I also hide them with org-superstar. The keywords are still shown when placing the cursor on them.

(setq org-todo-keyword-faces
    (quote (("TODO" :foreground "red" :weight bold)
            ("NEXT" :foreground "blue" :weight bold)
            ("DONE" :foreground "forest green" :weight bold)
            ("WAITING" :foreground "orange" :weight bold)
            ("HOLD" :foreground "magenta" :weight bold)
            ("CANCELLED" :foreground "forest green" :weight bold)
            ("MEETING" :foreground "forest green" :weight bold)
            ("PHONE" :foreground "forest green" :weight bold))))
;; I don't wan't the keywords in my exports by default
(setq-default org-export-with-todo-keywords nil)

Org “TODO” bullets

I use org-superstar to remove the keywords from my org files and replace them with nice icons. This also hides the stars at the beginning of headlines. Different headline levels are recognized with the font-size, formatting and color. This is defined later.

(with-eval-after-load 'org-superstar
  (setq org-superstar-item-bullet-alist
        '((?* . ?•)
          (?+ . ?➤)
          (?- . ?•)))
  (setq org-superstar-headline-bullets-list '(?\d))
  (setq org-superstar-special-todo-items t)
  (setq org-superstar-remove-leading-stars t)
  (setq org-hide-leading-stars t)
  ;; Enable custom bullets for TODO items
  (setq org-superstar-todo-bullet-alist
        '(("TODO" . ?☐)
          ("NEXT" . ?✒)
          ("HOLD" . ?✰)
          ("WAITING" . ?☕)
          ("CANCELLED" . ?✘)
          ("DONE" . ?✔)))
(setq org-ellipsis " ▼ ")

This results in the following appearance for different keywords:

General style

This section covers the general look and feel of my org files.

Proportional width

I use a function that I add as a hook to all the files I want to automatically set to variable width (text-like font by opposition with programming fonts). You need to have the font installed for this to work.

(defun my/buffer-face-mode-variable ()
  "Set font to a variable width (proportional) fonts in current buffer"
  (setq buffer-face-mode-face '(:family "Roboto Slab"
                                :height 150
                                :width normal))

Hide face characters

Hide *, ~ and / in org text.

(setq org-hide-emphasis-markers t)

Faces setup

We define a few functions that will be run when we open an org file to style it.

The first one sets the text to be variable pitch but revert it back to fixed-pitch for code block, properties, etc.

(defun my/set-general-faces-org ()
  (setq line-spacing 0.1
        org-pretty-entities t
        org-startup-indented t
        org-adapt-indentation nil)
  (variable-pitch-mode +1)
   (lambda (face) ;; Other fonts that require it are set to fixed-pitch.
     (set-face-attribute face nil :inherit 'fixed-pitch))
   (list 'org-block
  (mapc ;; This sets the fonts to a smaller size
   (lambda (face)
     (set-face-attribute face nil :height 0.8))
   (list 'org-document-info-keyword

This function set the colors and size of headlines.

(defun my/set-specific-faces-org ()
  (set-face-attribute 'org-code nil
                      :inherit '(shadow fixed-pitch))
  ;; Without indentation the headlines need to be different to be visible
  (set-face-attribute 'org-level-1 nil
                      :height 1.25
                      :foreground "#BEA4DB")
  (set-face-attribute 'org-level-2 nil
                      :height 1.15
                      :foreground "#A382FF"
                      :slant 'italic)
  (set-face-attribute 'org-level-3 nil
                      :height 1.1
                      :foreground "#5E65CC"
                      :slant 'italic)
  (set-face-attribute 'org-level-4 nil
                      :height 1.05
                      :foreground "#ABABFF")
  (set-face-attribute 'org-level-5 nil
                      :foreground "#2843FB")
  (set-face-attribute 'org-date nil
                      :foreground "#ECBE7B"
                      :height 0.8)
  (set-face-attribute 'org-document-title nil
                      :foreground "DarkOrange3"
                      :height 1.3)
  (set-face-attribute 'org-ellipsis nil
                      :foreground "#4f747a" :underline nil)
  (set-face-attribute 'variable-pitch nil
                      :family "Roboto Slab" :height 1.2))

This function will hide some TODO keywords and other keywords and replace them with special symbols.

(defun my/set-keyword-faces-org ()
  (mapc (lambda (pair) (push pair prettify-symbols-alist))
        '(;; Syntax
          ("TODO" .     "")
          ("DONE" .     "")
          ("WAITING" .  "")
          ("HOLD" .     "")
          ("NEXT" .     "")
          ("CANCELLED" . "")
          ("#+begin_quote" . "“")
          ("#+end_quote" . "”")))
  (prettify-symbols-mode +1)
  (org-superstar-mode +1)

(defun my/style-org ()
(add-hook 'org-mode-hook 'my/style-org)

Org agenda

(setq org-agenda-skip-scheduled-if-done t
      org-agenda-skip-deadline-if-done t
      org-agenda-include-deadlines t
      org-agenda-block-separator #x2501
      org-agenda-compact-blocks t
      org-agenda-start-with-log-mode t)
(with-eval-after-load 'org-journal
  (define-key org-journal-mode-map (kbd "<C-tab>") 'yas-expand))
(setq org-agenda-clockreport-parameter-plist
      (quote (:link t :maxlevel 5 :fileskip0 t :compact t :narrow 80)))
(setq org-agenda-deadline-faces
      '((1.0001 . org-warning)              ; due yesterday or before
        (0.0    . org-upcoming-deadline)))  ; due today or later

Most of this was copied from bzg’s dotfiles.

(setq-default org-icalendar-include-todo t)
(setq org-combined-agenda-icalendar-file "~/org/calendar.ics")
(setq org-icalendar-combined-name "Hugo Org")
(setq org-icalendar-use-scheduled '(todo-start event-if-todo event-if-not-todo))
(setq org-icalendar-use-deadline '(todo-due event-if-todo event-if-not-todo))
(setq org-icalendar-timezone "Europe/Paris")
(setq org-icalendar-store-UID t)
(setq org-icalendar-alarm-time 30)
(setq french-holiday
      '((holiday-fixed 1 1 "Jour de l'an")
        (holiday-fixed 5 8 "Victoire 45")
        (holiday-fixed 7 14 "Fête nationale")
        (holiday-fixed 8 15 "Assomption")
        (holiday-fixed 11 1 "Toussaint")
        (holiday-fixed 11 11 "Armistice 18")
        (holiday-easter-etc 1 "Lundi de Pâques")
        (holiday-easter-etc 39 "Ascension")
        (holiday-easter-etc 50 "Lundi de Pentecôte")
        (holiday-fixed 1 6 "Épiphanie")
        (holiday-fixed 2 2 "Chandeleur")
        (holiday-fixed 2 14 "Saint Valentin")
        (holiday-fixed 5 1 "Fête du travail")
        (holiday-fixed 5 8 "Commémoration de la capitulation de l'Allemagne en 1945")
        (holiday-fixed 6 21 "Fête de la musique")
        (holiday-fixed 11 2 "Commémoration des fidèles défunts")
        (holiday-fixed 12 25 "Noël")
        ;; fêtes à date variable
        (holiday-easter-etc 0 "Pâques")
        (holiday-easter-etc 49 "Pentecôte")
        (holiday-easter-etc -47 "Mardi gras")
        (holiday-float 6 0 3 "Fête des pères") ;; troisième dimanche de juin
        ;; Fête des mères
         '(if (equal
               ;; Pentecôte
               (holiday-easter-etc 49)
               ;; Dernier dimanche de mai
               (holiday-float 5 0 -1 nil))
              ;; -> Premier dimanche de juin si coïncidence
              (car (car (holiday-float 6 0 1 nil)))
            ;; -> Dernier dimanche de mai sinon
            (car (car (holiday-float 5 0 -1 nil))))
         "Fête des mères")))
(setq calendar-date-style 'european
      holiday-other-holidays french-holiday
      calendar-mark-holidays-flag t
      calendar-week-start-day 1
      calendar-mark-diary-entries-flag nil)

Agenda style

Those settings adjust the look and feel of the org-agenda.

(defun my/style-org-agenda()
  (set-face-attribute 'org-agenda-date nil :height 1.1)
  (set-face-attribute 'org-agenda-date-today nil :height 1.1 :slant 'italic)
  (set-face-attribute 'org-agenda-date-weekend nil :height 1.1))

(add-hook 'org-agenda-mode-hook 'my/style-org-agenda)

(setq org-agenda-breadcrumbs-separator " ❱ "
      org-agenda-current-time-string "⏰ ┈┈┈┈┈┈┈┈┈┈┈ now"
      org-agenda-time-grid '((weekly today require-timed)
                             (800 1000 1200 1400 1600 1800 2000)
                             "---" "┈┈┈┈┈┈┈┈┈┈┈┈┈")
      org-agenda-prefix-format '((agenda . "%i %-12:c%?-12t%b% s")
                                 (todo . " %i %-12:c")
                                 (tags . " %i %-12:c")
                                 (search . " %i %-12:c")))

(setq org-agenda-format-date (lambda (date) (concat "\n" (make-string (window-width) 9472)
                                                    (org-agenda-format-date-aligned date))))
(setq org-cycle-separator-lines 2)
(setq org-agenda-category-icon-alist
      `(("Work" ,(list (all-the-icons-faicon "cogs")) nil nil :ascent center)
        ("Personal" ,(list (all-the-icons-material "person")) nil nil :ascent center)
        ("Calendar" ,(list (all-the-icons-faicon "calendar")) nil nil :ascent center)
        ("Reading" ,(list (all-the-icons-faicon "book")) nil nil :ascent center)))

Super agenda

Org super agenda is a powerful package for easily filtering and grouping agenda items in your views.

(setq org-agenda-custom-commands
      '(("z" "Hugo view"
         ((agenda "" ((org-agenda-span 'day)
                       '((:name "Today"
                          :time-grid t
                          :date today
                          :todo "TODAY"
                          :scheduled today
                          :order 1)))))
          (alltodo "" ((org-agenda-overriding-header "")
                        '(;; Each group has an implicit boolean OR operator between its selectors.
                          (:name "Today"
                           :deadline today
                           :face (:background "black"))
                          (:name "Passed deadline"
                           :and (:deadline past :todo ("TODO" "WAITING" "HOLD" "NEXT"))
                           :face (:background "#7f1b19"))
                          (:name "Work important"
                           :and (:priority>= "B" :category "Work" :todo ("TODO" "NEXT")))
                          (:name "Work other"
                           :and (:category "Work" :todo ("TODO" "NEXT")))
                          (:name "Important"
                           :priority "A")
                          (:priority<= "B"
                           ;; Show this section after "Today" and "Important", because
                           ;; their order is unspecified, defaulting to 0. Sections
                           ;; are displayed lowest-number-first.
                           :order 1)
                          (:name "Papers"
                           :file-path "org/roam/notes")
                          (:name "Waiting"
                           :todo "WAITING"
                           :order 9)
                          (:name "On hold"
                           :todo "HOLD"
                           :order 10)))))))))
(add-hook 'org-agenda-mode-hook 'org-super-agenda-mode)

Org clock

;; Resume clocking task when emacs is restarted
;; Show lot of clocking history so it's easy to pick items off the C-F11 list
(setq org-clock-history-length 23)
;; Resume clocking task on clock-in if the clock is open
(setq org-clock-in-resume t)
;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration
(setq org-clock-out-remove-zero-time-clocks t)
;; Clock out when moving task to a done state
(setq org-clock-out-when-done t)
;; Save the running clock and all clock history when exiting Emacs, load it on startup
(setq org-clock-persist t)
;; Include current clocking task in clock reports
(setq org-clock-report-include-clocking-task t)

Org custom commands

Define some keybindings and options in org-mode.

(add-hook 'org-mode-hook 'turn-on-auto-fill)
(add-hook 'org-mode-hook
          (lambda ()
            (setq fill-column 80)
            (define-key org-mode-map (kbd "s-i") 'org-clock-in)
            (define-key org-mode-map (kbd "s-o") 'org-clock-out)
            (define-key org-mode-map (kbd "s-d") 'org-todo)
            (define-key org-mode-map (kbd "M-+") 'text-scale-increase)
            (define-key org-mode-map (kbd "M-°") 'text-scale-decrease)
            (define-key org-mode-map (kbd "C-c \" \"")
              (lambda () (interactive) (org-zotxt-insert-reference-link '(4))))))

Close journal on exit

A small function to make journaling faster by closing the buffer when I save it.

(defun org-journal-save-entry-and-exit()
  "Simple convenience function.
    Saves the buffer of the current day's entry and kills the window
    Similar to org-capture like behavior"

(add-hook 'org-journal-mode-hook
          (lambda ()
            (define-key org-journal-mode-map
              (kbd "C-x C-s") 'org-journal-save-entry-and-exit)))

Org Ref and Bibtex

(setq org-cite-global-bibliography nil)
(add-to-list 'org-cite-global-bibliography (expand-file-name "~/Papers/library.json"))
(require 'oc-csl)
(with-eval-after-load 'org-ref
  (setq bibtex-completion-bibliography '("~/Papers/library.json")
        bibtex-completion-library-path '("~/Papers/pdf/")
        bibtex-completion-notes-path "~/org/roam/notes"
        bibtex-completion-additional-search-fields '(keywords)
        '((article       . "${=has-pdf=:1}${=has-note=:1} ${year:4} ${author:36} ${title:*} ${journal:40}")
          (inbook        . "${=has-pdf=:1}${=has-note=:1} ${year:4} ${author:36} ${title:*} Chapter ${chapter:32}")
          (incollection  . "${=has-pdf=:1}${=has-note=:1} ${year:4} ${author:36} ${title:*} ${booktitle:40}")
          (inproceedings . "${=has-pdf=:1}${=has-note=:1} ${year:4} ${author:36} ${title:*} ${booktitle:40}")
          (t             . "${=has-pdf=:1}${=has-note=:1} ${year:4} ${author:36} ${title:*}"))
        (lambda (fpath)
          (call-process "open" nil 0 nil fpath)))
  ;; (require 'org-ref-helm)
  (define-key org-mode-map (kbd "C-c ]") 'org-ref-insert-link)
  (setq org-ref-insert-link-function 'org-ref-insert-link-hydra/body
        ;; org-ref-insert-cite-function 'org-ref-cite-insert-helm
        org-ref-insert-label-function 'org-ref-insert-label-link
        org-ref-insert-ref-function 'org-ref-insert-ref-link
        org-ref-cite-onclick-function (lambda (_) (org-ref-citation-hydra/body)))

;; Bibtex setup
(setq bibtex-completion-pdf-open-function
      (lambda (fpath)
        (cond ((eq system-type 'darwin) (start-process "open" "*open*" "open" fpath))
              ((eq system-type 'gnu/linux) (start-process "evince" "*evince*" "evince" fpath)))))
(setq bibtex-completion-pdf-field "file")
(setq bibtex-completion-pdf-symbol "⌘")
(setq bibtex-completion-notes-symbol "✎")
(setq bibtex-completion-notes-template-multiple-files
      ":PROPERTIES:\n:ROAM_REFS: cite:${=key=}\n:END:\n#+TITLE: Notes on: ${title} by ${author-or-editor} (${year})\n#+hugo_lastmod: Time-stamp: <>\n#+ROAM_KEY: cite:${=key=}\n\n- source :: cite:${=key=}
  \n\n* TODO Summary\n* TODO Comments\n\n

Org capture

I use org captures mostly for quickly creating new events and event-related TODOs.

(setq org-capture-templates
      '(("n" "Notes" entry
         (file "~/org/") "* %^{Description} %^g\n Added: %U\n%?")
        ("m" "Meeting notes" entry
         (file "~/org/") "* TODO %^{Title} %t\n- %?")
        ("t" "TODO" entry
         (file "~/org/") "* TODO %^{Title}")
        ("e" "Event" entry
         (file "~/org/") "* %^{Is it a todo?||TODO |NEXT }%^{Title}\n%^t\n%?")
        ("w" "Work TODO" entry
         (file "~/org/") "* TODO %^{Title}")))

Org Refile

(setq org-refile-targets '((org-agenda-files . (:maxlevel . 6))))
(setq org-refile-use-outline-path 'file)
(setq org-refile-allow-creating-parent-nodes 'confirm)

Org Roam

I use org-roam a lot to take notes and link between them. A large portion of this configuration is heavily borrowed from jethrokuan’s dot files.


You’ll find the exported notes on my notes page.

(with-eval-after-load 'org-roam
  ;; Roam is always one level deep in my org-directory
  (setq org-id-link-to-org-use-id t)
  (setq org-roam-completion-system 'helm)
  (add-to-list 'display-buffer-alist
                  (direction . right)
                  (window-width . 0.33)
                  (window-height . fit-window-to-buffer))))
  (setq org-roam-capture-templates
        '(("d" "default" plain "%?"
           :immediate-finish t
           :if-new (file+head "${slug}.org"
                              "#+TITLE: ${title}\n#+hugo_lastmod: Time-stamp: <>\n\n")
           :unnarrowed t)
          ("t" "temp" plain "%?"
           :if-new(file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                             "#+TITLE: ${title}\n#+hugo_lastmod: Time-stamp: <>\n\n")
           :immediate-finish t
           :unnarrowed t)
          ("p" "private" plain "%?"
           :if-new (file+head "${slug}"
                              "#+TITLE: ${title}\n")
           :immediate-finish t
           :unnarrowed t)))
(setq org-id-extra-files (org-roam--list-files org-roam-directory))

Org Caldav

I use this function to sync my calendar with a remote calendar on a server running Nextcloud with the org-caldav package. This should also work with Google calendar. It allows me to get my org-mode calendar on all my devices and get notifications of upcoming events.

(defun my/caldav-sync-perso ()
  "Sync my local calendar in ~/org/ with my remote calendar"
  (let ((org-caldav-inbox "~/org/")
        (org-caldav-calendar-id "org")
        (org-caldav-url "")
        (org-caldav-files '("~/org/")))
    (call-interactively 'org-caldav-sync)))

Org journal

Org journal is a package to create and maintain a journal/diary within Emacs. Here I set some of the options: the base directory for the journal and GPG encryption of journal files (uses org-crypt).

(setq org-journal-dir "~/org/journal/")
(setq org-journal-enable-encryption nil)


Timestamp on save

Org-roam notes are created with a #+hugo_lastmod: Time-stamp: <> line in the beginning. The hook below makes sure the current time is inserted between the brackets each time I save a file. This is useful to keep track of when I last edited a note.

(add-hook 'before-save-hook 'time-stamp)

Make markdown mode variable width

This is not part of the org-mode configuration, but applies variable width mode to markdown files when I (rarely) edit one.

(add-hook 'markdown-mode-hook 'my/buffer-face-mode-variable)
(setq org-return-follows-link t)