;;; init-org.el --- Org-mode config -*- lexical-binding: t -*- ;;; Commentary: ;; Among settings for many aspects of `org-mode', this code includes ;; an opinionated setup for the Getting Things Done (GTD) system based ;; around the Org Agenda. I have an "inbox.org" file with a header ;; including ;; #+CATEGORY: Inbox ;; #+FILETAGS: INBOX ;; and then set this file as `org-default-notes-file'. Captured org ;; items will then go into this file with the file-level tag, and can ;; be refiled to other locations as necessary. ;; Those other locations are generally other org files, which should ;; be added to `org-agenda-files-list' (along with "inbox.org" org). ;; With that done, there's then an agenda view, accessible via the ;; `org-agenda' command, which gives a convenient overview. ;; `org-todo-keywords' is customised here to provide corresponding ;; TODO states, which should make sense to GTD adherents. ;;; Code: (when *is-a-mac* (maybe-require-package 'grab-mac-link)) (maybe-require-package 'org-cliplink) (define-key global-map (kbd "C-c l") 'org-store-link) (define-key global-map (kbd "C-c a") 'org-agenda) (defvar sanityinc/org-global-prefix-map (make-sparse-keymap) "A keymap for handy global access to org helpers, particularly clocking.") (define-key sanityinc/org-global-prefix-map (kbd "j") 'org-clock-goto) (define-key sanityinc/org-global-prefix-map (kbd "l") 'org-clock-in-last) (define-key sanityinc/org-global-prefix-map (kbd "i") 'org-clock-in) (define-key sanityinc/org-global-prefix-map (kbd "o") 'org-clock-out) (define-key global-map (kbd "C-c o") sanityinc/org-global-prefix-map) ;; Various preferences (setq org-log-done t org-edit-timestamp-down-means-later t org-hide-emphasis-markers t org-catch-invisible-edits 'show org-export-coding-system 'utf-8 org-fast-tag-selection-single-key 'expert org-html-validation-link nil org-export-kill-product-buffer-when-displayed t org-tags-column 80) ;; Lots of stuff from http://doc.norang.ca/org-mode.html ;; Re-align tags when window shape changes (with-eval-after-load 'org-agenda (add-hook 'org-agenda-mode-hook (lambda () (add-hook 'window-configuration-change-hook 'org-agenda-align-tags nil t)))) (maybe-require-package 'writeroom-mode) (define-minor-mode prose-mode "Set up a buffer for prose editing. This enables or modifies a number of settings so that the experience of editing prose is a little more like that of a typical word processor." :init-value nil :lighter " Prose" :keymap nil (if prose-mode (progn (when (fboundp 'writeroom-mode) (writeroom-mode 1)) (setq truncate-lines nil) (setq word-wrap t) (setq cursor-type 'bar) (when (eq major-mode 'org) (kill-local-variable 'buffer-face-mode-face)) (buffer-face-mode 1) ;;(delete-selection-mode 1) (setq-local blink-cursor-interval 0.6) (setq-local show-trailing-whitespace nil) (setq-local line-spacing 0.2) (setq-local electric-pair-mode nil) (ignore-errors (flyspell-mode 1)) (visual-line-mode 1)) (kill-local-variable 'truncate-lines) (kill-local-variable 'word-wrap) (kill-local-variable 'cursor-type) (kill-local-variable 'blink-cursor-interval) (kill-local-variable 'show-trailing-whitespace) (kill-local-variable 'line-spacing) (kill-local-variable 'electric-pair-mode) (buffer-face-mode -1) ;; (delete-selection-mode -1) (flyspell-mode -1) (visual-line-mode -1) (when (fboundp 'writeroom-mode) (writeroom-mode 0)))) ;;(add-hook 'org-mode-hook 'buffer-face-mode) (setq org-support-shift-select t) ;;; Capturing (global-set-key (kbd "C-c c") 'org-capture) (setq org-capture-templates `(("t" "todo" entry (file "") ; "" => `org-default-notes-file' "* NEXT %?\n%U\n" :clock-resume t) ("n" "note" entry (file "") "* %? :NOTE:\n%U\n%a\n" :clock-resume t) )) ;;; Refiling (setq org-refile-use-cache nil) ;; Targets include this file and any file contributing to the agenda - up to 5 levels deep (setq org-refile-targets '((nil :maxlevel . 5) (org-agenda-files :maxlevel . 5))) (with-eval-after-load 'org-agenda (add-to-list 'org-agenda-after-show-hook 'org-show-entry)) (advice-add 'org-refile :after (lambda (&rest _) (org-save-all-org-buffers))) ;; Exclude DONE state tasks from refile targets (defun sanityinc/verify-refile-target () "Exclude todo keywords with a done state from refile targets." (not (member (nth 2 (org-heading-components)) org-done-keywords))) (setq org-refile-target-verify-function 'sanityinc/verify-refile-target) (defun sanityinc/org-refile-anywhere (&optional goto default-buffer rfloc msg) "A version of `org-refile' which allows refiling to any subtree." (interactive "P") (let ((org-refile-target-verify-function)) (org-refile goto default-buffer rfloc msg))) (defun sanityinc/org-agenda-refile-anywhere (&optional goto rfloc no-update) "A version of `org-agenda-refile' which allows refiling to any subtree." (interactive "P") (let ((org-refile-target-verify-function)) (org-agenda-refile goto rfloc no-update))) ;; Targets start with the file name - allows creating level 1 tasks ;;(setq org-refile-use-outline-path (quote file)) (setq org-refile-use-outline-path t) (setq org-outline-path-complete-in-steps nil) ;; Allow refile to create parent tasks with confirmation (setq org-refile-allow-creating-parent-nodes 'confirm) ;;; To-do settings (setq org-todo-keywords (quote ((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!/!)") (sequence "PROJECT(p)" "|" "DONE(d!/!)" "CANCELLED(c@/!)") (sequence "WAITING(w@/!)" "DELEGATED(e!)" "HOLD(h)" "|" "CANCELLED(c@/!)"))) org-todo-repeat-to-state "NEXT") (setq org-todo-keyword-faces (quote (("NEXT" :inherit warning) ("PROJECT" :inherit font-lock-string-face)))) ;;; Agenda views (setq-default org-agenda-clockreport-parameter-plist '(:link t :maxlevel 3)) (let ((active-project-match "-INBOX/PROJECT")) (setq org-stuck-projects `(,active-project-match ("NEXT"))) (setq org-agenda-compact-blocks t org-agenda-sticky t org-agenda-start-on-weekday nil org-agenda-span 'day org-agenda-include-diary nil org-agenda-sorting-strategy '((agenda habit-down time-up user-defined-up effort-up category-keep) (todo category-up effort-up) (tags category-up effort-up) (search category-up)) org-agenda-window-setup 'current-window org-agenda-custom-commands `(("N" "Notes" tags "NOTE" ((org-agenda-overriding-header "Notes") (org-tags-match-list-sublevels t))) ("g" "GTD" ((agenda "" nil) (tags "INBOX" ((org-agenda-overriding-header "Inbox") (org-tags-match-list-sublevels nil))) (stuck "" ((org-agenda-overriding-header "Stuck Projects") (org-agenda-tags-todo-honor-ignore-options t) (org-tags-match-list-sublevels t) (org-agenda-todo-ignore-scheduled 'future))) (tags-todo "-INBOX" ((org-agenda-overriding-header "Next Actions") (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-todo-ignore-scheduled 'future) (org-agenda-skip-function '(lambda () (or (org-agenda-skip-subtree-if 'todo '("HOLD" "WAITING")) (org-agenda-skip-entry-if 'nottodo '("NEXT"))))) (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy '(todo-state-down effort-up category-keep)))) (tags-todo ,active-project-match ((org-agenda-overriding-header "Projects") (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy '(category-keep)))) (tags-todo "-INBOX/-NEXT" ((org-agenda-overriding-header "Orphaned Tasks") (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-todo-ignore-scheduled 'future) (org-agenda-skip-function '(lambda () (or (org-agenda-skip-subtree-if 'todo '("PROJECT" "HOLD" "WAITING" "DELEGATED")) (org-agenda-skip-subtree-if 'nottododo '("TODO"))))) (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy '(category-keep)))) (tags-todo "/WAITING" ((org-agenda-overriding-header "Waiting") (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-todo-ignore-scheduled 'future) (org-agenda-sorting-strategy '(category-keep)))) (tags-todo "/DELEGATED" ((org-agenda-overriding-header "Delegated") (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-todo-ignore-scheduled 'future) (org-agenda-sorting-strategy '(category-keep)))) (tags-todo "-INBOX" ((org-agenda-overriding-header "On Hold") (org-agenda-skip-function '(lambda () (or (org-agenda-skip-subtree-if 'todo '("WAITING")) (org-agenda-skip-entry-if 'nottodo '("HOLD"))))) (org-tags-match-list-sublevels nil) (org-agenda-sorting-strategy '(category-keep)))) ;; (tags-todo "-NEXT" ;; ((org-agenda-overriding-header "All other TODOs") ;; (org-match-list-sublevels t))) ))))) (add-hook 'org-agenda-mode-hook 'hl-line-mode) ;;; Org clock ;; Save the running clock and all clock history when exiting Emacs, load it on startup (with-eval-after-load 'org (org-clock-persistence-insinuate)) (setq org-clock-persist t) (setq org-clock-in-resume t) ;; Save clock data and notes in the LOGBOOK drawer (setq org-clock-into-drawer t) ;; Save state changes in the LOGBOOK drawer (setq org-log-into-drawer t) ;; Removes clocked tasks with 0:00 duration (setq org-clock-out-remove-zero-time-clocks t) ;; Show clock sums as hours and minutes, not "n days" etc. (setq org-time-clocksum-format '(:hours "%d" :require-hours t :minutes ":%02d" :require-minutes t)) ;;; Show the clocked-in task - if any - in the header line (defun sanityinc/show-org-clock-in-header-line () (setq-default header-line-format '((" " org-mode-line-string " ")))) (defun sanityinc/hide-org-clock-from-header-line () (setq-default header-line-format nil)) (add-hook 'org-clock-in-hook 'sanityinc/show-org-clock-in-header-line) (add-hook 'org-clock-out-hook 'sanityinc/hide-org-clock-from-header-line) (add-hook 'org-clock-cancel-hook 'sanityinc/hide-org-clock-from-header-line) (with-eval-after-load 'org-clock (define-key org-clock-mode-line-map [header-line mouse-2] 'org-clock-goto) (define-key org-clock-mode-line-map [header-line mouse-1] 'org-clock-menu)) (when (and *is-a-mac* (file-directory-p "/Applications/org-clock-statusbar.app")) (add-hook 'org-clock-in-hook (lambda () (call-process "/usr/bin/osascript" nil 0 nil "-e" (concat "tell application \"org-clock-statusbar\" to clock in \"" org-clock-current-task "\"")))) (add-hook 'org-clock-out-hook (lambda () (call-process "/usr/bin/osascript" nil 0 nil "-e" "tell application \"org-clock-statusbar\" to clock out")))) ;; TODO: warn about inconsistent items, e.g. TODO inside non-PROJECT ;; TODO: nested projects! ;;; Archiving (setq org-archive-mark-done nil) (setq org-archive-location "%s_archive::* Archive") (require-package 'org-pomodoro) (setq org-pomodoro-keep-killed-pomodoro-time t) (with-eval-after-load 'org-agenda (define-key org-agenda-mode-map (kbd "P") 'org-pomodoro)) ;; ;; Show iCal calendars in the org agenda ;; (when (and *is-a-mac* (require 'org-mac-iCal nil t)) ;; (setq org-agenda-include-diary t ;; org-agenda-custom-commands ;; '(("I" "Import diary from iCal" agenda "" ;; ((org-agenda-mode-hook #'org-mac-iCal))))) ;; (add-hook 'org-agenda-cleanup-fancy-diary-hook ;; (lambda () ;; (goto-char (point-min)) ;; (save-excursion ;; (while (re-search-forward "^[a-z]" nil t) ;; (goto-char (match-beginning 0)) ;; (insert "0:00-24:00 "))) ;; (while (re-search-forward "^ [a-z]" nil t) ;; (goto-char (match-beginning 0)) ;; (save-excursion ;; (re-search-backward "^[0-9]+:[0-9]+-[0-9]+:[0-9]+ " nil t)) ;; (insert (match-string 0)))))) (with-eval-after-load 'org (define-key org-mode-map (kbd "C-M-") 'org-up-element) (when *is-a-mac* (define-key org-mode-map (kbd "M-h") nil) (define-key org-mode-map (kbd "C-c g") 'grab-mac-link))) (with-eval-after-load 'org (org-babel-do-load-languages 'org-babel-load-languages (seq-filter (lambda (pair) (locate-library (concat "ob-" (symbol-name (car pair))))) '((R . t) (ditaa . t) (dot . t) (emacs-lisp . t) (gnuplot . t) (haskell . nil) (latex . t) (ledger . t) (ocaml . nil) (octave . t) (plantuml . t) (python . t) (ruby . t) (screen . nil) (sh . t) ;; obsolete (shell . t) (sql . t) (sqlite . t))))) (provide 'init-org) ;;; init-org.el ends here