381 lines
14 KiB
EmacsLisp
381 lines
14 KiB
EmacsLisp
;;; 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:
|
||
|
||
(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))))
|
||
|
||
(add-hook 'org-mode-hook 'visual-line-mode)
|
||
|
||
|
||
(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)
|
||
("i" "Inbox" entry (file "inbox.org")
|
||
,(concat "* TODO %?\n"
|
||
"/Entered on/ %U"))
|
||
))
|
||
|
||
|
||
|
||
;;; 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-hide-tags-regexp "."
|
||
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))
|
||
|
||
|
||
|
||
|
||
;; 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
|
||
(when (maybe-require-package 'org-bullets)
|
||
(add-hook 'org-mode 'org-bullets-mode)))
|
||
|
||
(with-eval-after-load 'org
|
||
(define-key org-mode-map (kbd "C-M-<up>") 'org-up-element)
|
||
)
|
||
|
||
(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
|