EmacsConf2020: Lead your future with Org
Lead your future with Org
This is the presentation I gave at EmacsConf2020 about how lead my future with Org Mode. The abstract:
Title: Lead your future with Org Preferred format: Standard talk (or even Lighting talk by only giving references to the modes I plan to show) Abstract: The world is full of possibilities. A person life is rather short though, and one can easily end up carry on without focus. In this short talk I want to share how Org mode empowers me into organizing and monitoring my tasks to make sure I am working towards achieving my vision. The emphasis of the talk is on defining a direction, monitoring the progress towards your planned destination, and keeping a trail of your actions to review and set up a healthy feedback loop. Tools for the job that I will (at least) mention: Org files, Org agenda, Org archive, org-ql, and Org-roam.
In the following presentation you can find a source block that lets
you setup an Emacs instance ready for testing this file out. I suggest
to copy the source Org file for this blog as /tmp/test.org, and run
the block to try things out.
Who am I
I am Andrea.
I work as a Scala software engineer somewhere in the middle of The Netherlands.
I inherited my passion for Emacs from my Ph.D. supervisor and I got in synergy with it.
Find more about me at: https://ag91.github.io
Why I needed a vision
- Too many interests
- Too little time
The need to act
- Even when you have a vision you need goals to get there
How I keep track of my vision progress with Org Agenda
Running example vision: I want to bring joy to people, I want to live in synergy with the planet
(require 'package)
(eval-and-compile
(setq
package-archives
'(("melpa-stable" . "https://stable.melpa.org/packages/")
("melpa" . "https://melpa.org/packages/")
("marmalade" . "https://marmalade-repo.org/packages/")
("org" . "https://orgmode.org/elpa/")
("gnu" . "https://elpa.gnu.org/packages/"))))
(package-initialize)
;;; Bootstrap use-package
;; Install use-package if it's not already installed.
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(setq use-package-always-ensure 't)
(require 'use-package)
(require 'diminish)
(require 'bind-key)
;; extra for talk
(write-region "#+TITLE: I want to bring joy to people\n#+CATEGORY: Bring joy to people" nil "/tmp/BringJoy.org")
(write-region "#+TITLE: I want to live in synergy with the planet\n#+CATEGORY: Synergy with planet" nil "/tmp/SynergyWithPlanet.org")
(setq org-capture-templates
(list
'("b" "Joy task"
entry
(file "/tmp/BringJoy.org")
"* TODO %^{Short description} :%(completing-read \"Choose tag\" (list \"0yr\" \"5yr\" \"10yr\" \"20yr\")):\n SCHEDULED:%t\n\n %?"
:empty-lines 2)
'("s" "Synergy task"
entry
(file "/tmp/SynergyWithPlanet.org")
"* TODO %^{Short description} :%(completing-read \"Choose tag\" (list \"0yr\" \"5yr\" \"10yr\" \"20yr\")):\n SCHEDULED:%t\n\n %?"
:empty-lines 2)))
(defun my/org-agenda-sort-longterm-tags (el1 el2)
"Prioritize agenda items EL1 and EL2 that contains tags in the form :[0-9]yr: relatively to the number of years."
(let* ((regex ":\\([0-9]+\\)yr:")
(years-el1 (progn
;; this can fail because the regex is not found or there is no tag for :Xyr:, so I am handling with the zeros
(if (string-match regex el1)
(string-to-number (or (match-string 1 el1) "0"))
0)))
(years-el2 (progn
(if (string-match regex el2)
(string-to-number (or (match-string 1 el2) "0"))
0))
))
;; TODO I am sure there is a cleaner way to implement this comparison...
(if (> years-el1 years-el2)
1
(if (> years-el2 years-el1)
-1
nil))))
(setq org-agenda-cmp-user-defined 'my/org-agenda-sort-longterm-tags)
(setq org-agenda-sorting-strategy
'((agenda habit-down time-up user-defined-down priority-down category-keep)
(todo priority-down category-keep)
(tags priority-down category-keep)
(search category-keep)))
(setq org-agenda-files '("/tmp/BringJoy.org" "/tmp/SynergyWithPlanet.org"))
(global-set-key (kbd "C-c o a") 'org-agenda)
(global-set-key (kbd "C-c o c") 'org-capture)
(use-package org-ql
:ensure t)
(defun my/get-stats-tasks (todo-tag from &optional tag to files category)
"Get stats for tasks of last week with TODO-TAG TAG FROM optionally define TO date and source FILES to use."
(let ((tasks
(org-ql-query
:from (or files (org-agenda-files))
:where
`(and (todo ,todo-tag)
(if ,tag (tags ,tag) t)
(if ,category (category ,category) t)
(ts :from ,from :to ,(or to 'today))))))
`((tasks . ,(length tasks))
(tasks-per-day . ,(/ (length tasks) (abs from))))))
(use-package org-roam
:diminish
:pin melpa-stable
;; :hook (org-mode . org-roam-mode)
:custom
(org-roam-link-file-path-type 'noabbrev)
(org-roam-update-db-idle-seconds 30)
(org-roam-rename-file-on-title-change nil)
(org-roam-db-location "/tmp/org-roam.db")
(org-roam-directory "/tmp/notes/")
(org-roam-index-file "/tmp/index.org")
(org-roam-capture-templates
(list '("d" "default" plain (function org-roam--capture-get-point)
"- tags :: %?"
:file-name "%<%Y%m%d%H%M%S>-${slug}"
:head "#+TITLE: ${title}\n\n"
:unnarrowed t
:immediate-finish t)))
:bind (("C-c n i" . org-roam-insert)
("C-c n l" . org-roam)
("C-c n f" . org-roam-find-file)
("C-c n g" . org-roam-graph)
("C-c n j" . org-roam-jump-to-index))
:config
(use-package emacsql-sqlite)
(require 'org-roam-protocol))
(use-package org-crypt
:ensure nil
:after org
:custom
(org-crypt-disable-auto-save 't)
(org-tags-exclude-from-inheritance (list "crypt"))
(org-crypt-key "8197AC77B80C848FA0C99EDA2E4FA6504167C2AC")
:config
(defun my/with-catching-all-drawers (fn)
(let ((org-property-drawer-re
(concat "^[ ]*:[A-Z]*:[ ]*\n"
"[^*]*"
"[ \t]*:END:[ \t]*$")))
(funcall fn)))
(advice-add
'org-encrypt-entry :around
'my/with-catching-all-drawers)
(advice-add
'org-decrypt-entry :around
'my/with-catching-all-drawers)
(org-crypt-use-before-save-magic))
emacs -Q -l /tmp/running-example.el /tmp/test.org
Categorize tasks according to vision
- Create files with category
(setq org-agenda-files "/tmp/BringJoy.org" "/tmp/SynergyWithPlanet.org")
#+TITLE: I want to bring joy to people #+CATEGORY: Bring joy to people
#+TITLE: I want to live in synergy with the planet #+CATEGORY: Synergy with planet
Use tags to prioritize
- Make 5yr, 10yr and 20yr tags to know how effective a task is
Exploit Org Capture
- Make a template for each category and estimate effectiveness on the fly
(setq org-capture-templates
(list
("b" "Joy task"
entry
(file "/tmp/BringJoy.org")
"* TODO %^{Short description} :%(completing-read \"Choose tag\" (list \"0yr\" \"5yr\" \"10yr\" \"20yr\")):\n SCHEDULED:%t\n\n %?"
:empty-lines 2)
("s" "Synergy task"
entry
(file "/tmp/SynergyWithPlanet.org")
"* TODO %^{Short description} :%(completing-read \"Choose tag\" (list \"0yr\" \"5yr\" \"10yr\" \"20yr\")):\n SCHEDULED:%t\n\n %?"
:empty-lines 2)))
Adapt agenda
- Order your agenda by tags priority
(defun my/org-agenda-sort-longterm-tags (el1 el2)
"Prioritize agenda items EL1 and EL2 that contains tags in the form :[0-9]yr: relatively to the number of years."
(let* ((regex ":\\([0-9]+\\)yr:")
(years-el1 (progn
;; this can fail because the regex is not found or there is no tag for :Xyr:, so I am handling with the zeros
(if (string-match regex el1)
(string-to-number (or (match-string 1 el1) "0"))
0)))
(years-el2 (progn
(if (string-match regex el2)
(string-to-number (or (match-string 1 el2) "0"))
0))
))
;; TODO I am sure there is a cleaner way to implement this comparison...
(if (> years-el1 years-el2)
1
(if (> years-el2 years-el1)
-1
nil))))
(setq org-agenda-cmp-user-defined 'my/org-agenda-sort-longterm-tags)
(setq org-agenda-sorting-strategy
'((agenda habit-down time-up user-defined-down priority-down category-keep)
(todo priority-down category-keep)
(tags priority-down category-keep)
(search category-keep)))
How I retrospect weekly on my progress with org-ql
- Review how much progress you have done
- And celebrate progress!
(use-package org-ql
:ensure t)
(defun my/get-stats-tasks (todo-tag from &optional tag to files category)
"Get stats for tasks of last week with TODO-TAG TAG FROM optionally define TO date and source FILES to use."
(let ((tasks
(org-ql-query
:from (or files (org-agenda-files))
:where
`(and (todo ,todo-tag)
(if ,tag (tags ,tag) t)
(if ,category (category ,category) t)
(ts :from ,from :to ,(or to 'today))))))
`((tasks . ,(length tasks))
(tasks-per-day . ,(/ (length tasks) (abs from))))))
(let ((files (org-agenda-files)))
(-->
(my/get-stats-tasks "DONE" -31 nil nil files)
(s-concat
"\n"
(format "Tasks marked DONE last month (today is: %s): %s\n" (current-time-string) it)
(format " Tasks done last month (%s) with category\n\n" (current-time-string))
(format " Bring joy to people %s\n" (my/get-stats-tasks "DONE" -31 nil nil files "Bring joy to people"))
(format " Synergy with planet %s\n" (my/get-stats-tasks "DONE" -31 nil nil files "Synergy with planet"))))
How I form ideas with Org Roam
- Explore ideas and link relevant information for review
(use-package org-roam
:diminish
:pin melpa-stable
;; :hook (org-mode . org-roam-mode)
:custom
(org-roam-link-file-path-type 'noabbrev)
(org-roam-update-db-idle-seconds 30)
(org-roam-rename-file-on-title-change nil)
(org-roam-db-location "/tmp/org-roam.db")
(org-roam-directory "/tmp/notes/")
(org-roam-index-file "/tmp/index.org")
(org-roam-capture-templates
(list '("d" "default" plain (function org-roam--capture-get-point)
"- tags :: %?"
:file-name "%<%Y%m%d%H%M%S>-${slug}"
:head "#+TITLE: ${title}\n\n"
:unnarrowed t
:immediate-finish t)))
:bind (("C-c n i" . org-roam-insert)
("C-c n l" . org-roam)
("C-c n f" . org-roam-find-file)
("C-c n g" . org-roam-graph)
("C-c n j" . org-roam-jump-to-index))
:config
(use-package emacsql-sqlite)
(require 'org-roam-protocol))
Keep things private with Org Crypt
- Keep heading contents readable only by you
(use-package org-crypt
:ensure nil
:after org
:custom
(org-crypt-disable-auto-save 't)
(org-tags-exclude-from-inheritance (list "crypt"))
(org-crypt-key "8197AC77B80C848FA0C99EDA2E4FA6504167C2AC")
:config
(defun my/with-catching-all-drawers (fn)
(let ((org-property-drawer-re
(concat "^[ ]*:[A-Z]*:[ ]*\n"
"[^*]*"
"[ \t]*:END:[ \t]*$")))
(funcall fn)))
(advice-add
'org-encrypt-entry :around
'my/with-catching-all-drawers)
(advice-add
'org-decrypt-entry :around
'my/with-catching-all-drawers)
(org-crypt-use-before-save-magic))