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