Org-mode configuration

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([email protected]/!)" "HOLD([email protected]/!)" "|" "CANCELLED([email protected]/!)"))))

“TODO” faces and export settings

This sets the colors of keywords.

(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))))
(setq-default org-export-with-todo-keywords nil)
(setq-default org-enforce-todo-dependencies t)

Org “TODO” bullets

I use org-superstar to remove the keywords from my org files and replace them with nice icons.

(with-eval-after-load 'org-superstar
  ;; Every non-TODO headline now have no bullet
  (setq org-superstar-headline-bullets-list '("\u200b"))
  (setq org-superstar-leading-bullet "\u200b")
  (setq org-superstar-item-bullet-alist
        '((?* . ?•)
          (?+ . ?➤)
          (?- . ?•)))
  ;; Enable custom bullets for TODO items
  (setq org-superstar-special-todo-items t)
  (setq org-superstar-todo-bullet-alist
        '(("TODO" "☐")
          ("NEXT" "✒")
          ("HOLD" "✰")
          ("WAITING" "☕")
          ("CANCELLED" "✘")
          ("DONE" "✔")))
  (org-superstar-restart))
(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.

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

Hide face characters

Hide * and / in org text.

(setq org-hide-emphasis-markers t)

Faces setup

(defun my/style-org ()
  ;; I have removed indentation to make the file look cleaner
  (org-indent-mode -1)
  (my/buffer-face-mode-variable)
  (setq line-spacing 0.1
        org-pretty-entities t
        org-startup-indented t)
  (variable-pitch-mode +1)
  (mapc
    (lambda (face) ;; Other fonts that require it are set to fixed-pitch.
      (set-face-attribute face nil :inherit 'fixed-pitch))
    (list 'org-block
          'org-table
          'org-verbatim
          'org-block-begin-line
          'org-block-end-line
          'org-meta-line
          'org-document-info-keyword))
  (mapc ;; This sets the fonts to a smaller size
    (lambda (face)
      (set-face-attribute face nil :height 0.7))
    (list 'org-document-info-keyword
          'org-block-begin-line
          'org-block-end-line
          'org-meta-line))
  (set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch))
  (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.15
                      :foreground "#BEA4DB")
  (set-face-attribute 'org-level-2 nil
                      :height 1.1
                      :foreground "#A382FF"
                      :slant 'italic)
  (set-face-attribute 'org-level-3 nil
                      :height 1.05
                      :foreground "#5E65CC"
                      :slant 'italic)
  (set-face-attribute 'org-level-4 nil
                      :foreground "#ABABFF")
  (set-face-attribute 'org-level-5 nil
                      :foreground "#2843FB")
  (mapc (lambda (pair) (push pair prettify-symbols-alist))
        '(;; Syntax
          ("TODO" .     "")
          ("DONE" .     "")
          ("WAITING" .  "")
          ("HOLD" .     "")
          ("NEXT" .     "")
          ("CANCELLED" . "")
          ("#+begin_quote" . "“")
          ("#+end_quote" . "”")
          ))
  (prettify-symbols-mode +1)
)

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

Org agenda

(setq spacemacs-theme-org-agenda-height nil
      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)

(setq-default org-icalendar-include-todo t)
(setq org-agenda-files '("~/org" "~/org/roam/notes/"))

(with-eval-after-load 'org-journal
  (add-to-list 'org-agenda-files org-journal-dir)
  )
(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

(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 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
    (holiday-sexp
     '(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()
  (my/buffer-face-mode-variable)
  (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)
                                                    "\n"
                                                    (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-super-agenda-header-separator "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n")
(setq org-agenda-custom-commands
      '(("z" "Hugo view"
         ((agenda "" ((org-agenda-span 'day)
                      (org-super-agenda-groups
                       '((:name "Today"
                                :time-grid t
                                :date today
                                :todo "TODAY"
                                :scheduled today
                                :order 1)))))
          (alltodo "" ((org-agenda-overriding-header "")
                       (org-super-agenda-groups
                        '(;; 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 "A" :category "Work" :todo "TODO"))
                          (:name "Work other"
                                 :and (:category "Work" :todo "TODO"))
                          (: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 custom commands

(add-hook 'org-mode-hook 'turn-on-auto-fill)
(add-hook 'org-mode-hook
          (lambda ()
            (setq fill-column 80)
            (org-zotxt-mode 1)
            (define-key org-mode-map
              (kbd (cond ((eq system-type 'darwin) "H-i")
                         ((eq system-type 'gnu/linux) "s-i"))) 'org-clock-in)
            (define-key org-mode-map
              (kbd (cond ((eq system-type 'darwin) "H-o")
                         ((eq system-type 'gnu/linux) "s-o"))) 'org-clock-out)
            (define-key org-mode-map (kbd "H-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

(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"
  (interactive)
  (save-buffer)
  (kill-buffer-and-window))

(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

  (with-eval-after-load 'org-ref
    (setq reftex-default-bibliography '("~/Papers/library.bib"))
    (setq org-ref-default-bibliography '("~/Papers/library.bib")
          org-ref-pdf-directory "~/Papers/pdf/"
          org-ref-bibliography-notes "~/org/roam/notes")
    (setq org-ref-notes-function
          (lambda (thekey)
            (let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
              (bibtex-completion-edit-notes
               (list (car (org-ref-get-bibtex-key-and-file thekey)))))))
  )

  ;; Bibtex setup
  (setq bibtex-completion-notes-path "~/org/roam/notes")
  (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
        "#+TITLE: Notes on: ${title} by ${author-or-editor} (${year})\n#+HUGO_BASE_DIR: ~/website/personal-website/
#+HUGO_SECTION: notes\n#+hugo_lastmod: Time-stamp: <>\n#+ROAM_KEY: cite:${=key=}\n\n- source :: cite:${=key=}
\n\n* TODO Summary\n* TODO Comments\n\n
bibliography:~/Papers/library_bibtex.bib")

Org capture

(setq org-capture-templates
      '(("n" "Notes" entry
         (file "~/org/inbox.org") "* %^{Description} %^g\n Added: %U\n%?")
        ("m" "Meeting notes" entry
         (file "~/org/meetings.org") "* TODO %^{Title} %t\n- %?")
        ("t" "TODO" entry
         (file "~/org/inbox.org") "* TODO %^{Title}")
        ("e" "Event" entry
         (file "~/org/calendar.org") "* %^{Is it a todo?| |TODO |NEXT }%^{Title}\n%^t\n%?")
        ("w" "Work TODO" entry
         (file "~/org/work.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.

(with-eval-after-load 'org-roam
  (add-hook 'org-roam-backlinks-mode-hook 'my/style-org)
  (setq org-roam-graphviz-executable (executable-find "neato"))
  (setq org-roam-graphviz-extra-options '(("overlap" . "false")))
  (setq org-roam-completion-system 'helm)
  (setq org-roam-capture-templates
        '(("d" "default" plain #'org-roam--capture-get-point "%?"
           :file-name "${slug}"
           :head "#+TITLE: ${title}\n#+HUGO_BASE_DIR: ~/website/personal-\
website/\n#+HUGO_SECTION: notes\n#+hugo_lastmod: Time-stamp: <>\n\n"
           :unnarrowed t)
          ("t" "temp" plain #'org-roam--capture-get-point "%?"
           :file-name "%<%Y%m%d%H%M%S>-${slug}"
           :head "#+TITLE: ${title}\n#+HUGO_BASE_DIR: ~/website/personal-\
website/\n#+HUGO_SECTION: notes\n#+hugo_lastmod: Time-stamp: <>\n\n"
           :unnarrowed t))))

(with-eval-after-load 'ox-hugo
  (setq python-graph-script-location "/Users/hugo/scripts/dot_to_json.py")
  (setq json-graph-location
        "/Users/hugo/website/personal-website/static/js/graph.json")
  (defun my/run-python-script-roam-graph (graph-fname output-fname)
    (insert (shell-command-to-string (format "python %s %s %s"
                                             python-graph-script-location
                                             graph-fname
                                             output-fname))))
  (defun my/org-export-all-roam ()
    (interactive)
    (mapc (lambda (fPath)
            (ignore-errors (with-temp-buffer
                             (find-file-read-only fPath)
                             (org-hugo-export-to-md)
                             (kill-buffer))))
          (org-roam--list-files "/Users/hugo/org/roam")))
  (citeproc-org-setup))
;; Using Deft in org-mode
(setq deft-directory "~/org/roam/")

This function makes sure all the Backlinks of the current org-buffer are being exported and appended to the end of the resulting file. Files with a name containing private will not be exported.

(defun my/org-roam--backlinks-list (file)
  (if (org-roam--org-roam-file-p file)
      (--reduce-from
       (concat acc (format "- [[file:%s][%s]]\n"
                           (file-relative-name (car it) org-roam-directory)
                           (org-roam--get-title-or-slug (car it))))
       "" (delete-dups
           (org-roam-sql [:select [from] :from links :where (= to $s1)
                                  :and from :not :like $s2]
                         file "%private%"))) ""))

(defun my/org-export-preprocessor (_backend)
  (let ((links (my/org-roam--backlinks-list (buffer-file-name))))
    (unless (string= links "")
      (save-excursion
        (goto-char (point-max))
        (insert (concat "\n* Backlinks\n" links))))))
(add-hook 'org-export-before-processing-hook 'my/org-export-preprocessor)

Misc

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.

(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)