.emacs.d/lisp/init-org.el
2024-09-06 11:42:11 +02:00

381 lines
14 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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