;;; init-org.el --- Sean's Org-mode configuration ;;; Code: (require 'org-agenda) (require 'org-checklist) (require 'org-inlinetask) (require 'ol-notmuch) ;;;; Preferences (setq ;; use real indentation. ensures buffer text doesn't go beyond the 80 char ;; mark. my agenda files conform to this; other Org files in ~/doc/ have not ;; yet been indented. sometimes set `org-adapt-indentation' to nil in ;; .dir-locals.el (e.g. in ~/doc/newpapers) org-startup-indented nil org-adapt-indentation t org-startup-folded t org-hide-emphasis-markers nil org-cycle-separator-lines 0 org-show-following-heading t org-show-siblings t org-show-hierarchy-above t org-blank-before-new-entry '((heading . t) (plain-list-item . auto)) org-return-follows-link nil org-agenda-files (expand-file-name "~/doc/emacs-org-agenda-files") org-agenda-sticky t ;; org-agenda-dim-blocked-tasks nil org-deadline-warning-days 60 org-agenda-skip-deadline-prewarning-if-scheduled 3 ;; we just use a completely custom agenda view ;; org-agenda-todo-ignore-with-date nil ;; org-agenda-todo-ignore-deadlines nil ;; org-agenda-todo-ignore-scheduled 'future ;; org-agenda-todo-list-sublevels nil org-agenda-skip-deadline-if-done t org-agenda-skip-scheduled-if-done t org-agenda-skip-scheduled-if-deadline-is-shown 'not-today ;; org-agenda-skip-additional-timestamps-same-entry nil org-agenda-skip-timestamp-if-done t org-agenda-start-on-weekday nil org-agenda-persistent-filter t org-agenda-window-setup 'reorganize-frame org-agenda-restore-windows-after-quit t org-agenda-entry-text-maxlines 3 org-goto-auto-isearch t org-goto-interface 'outline org-archive-mark-done nil org-archive-save-context-info '(time file olpath) org-archive-location (concat (expand-file-name "~/doc/org/archive/archive.org") "::* From %s") ;; inline tasks ;; prefix arg can be used to override this setting org-inlinetask-default-state "TODO" ;; we don't actually use Org's built-in stuck project support, ;; instead generating our own review agenda from scratch which ;; includes the right tasks. See the view assigned to the '#' key org-stuck-projects '("TODO" '("NEXT") nil "") org-use-fast-todo-selection 'expert org-treat-S-cursor-todo-selection-as-state-change nil org-treat-insert-todo-heading-as-state-change t org-fast-tag-selection-include-todo nil org-enforce-todo-dependencies t org-insert-heading-respect-content nil org-refile-targets '((org-agenda-files . (:maxlevel . 5)) (nil . (:maxlevel . 5))) org-refile-use-outline-path 'file org-refile-allow-creating-parent-nodes 'confirm ;; wanted with `fido-mode' org-outline-path-complete-in-steps nil ;; org-yank-adjusted-subtrees t ;; org-yank-folded-subtrees nil org-log-into-drawer "LOGBOOK" org-log-states-order-reversed nil org-reverse-note-order nil org-log-done 'time org-log-redeadline nil org-log-reschedule nil org-log-refile nil ;; not needed, and cluttering, because the information is probably in git org-log-repeat nil org-special-ctrl-a/e t org-special-ctrl-k t org-read-date-prefer-future 'time org-list-demote-modify-bullet '(("-" . "+") ("+" . "*") ("*" . "-") ("1." . "-") ("1)" . "-")) org-list-use-circular-motion t org-tags-match-list-sublevels 'indented org-tag-alist '((:startgroup) ("@Tucson" . ?t) ("@Sheffield" . ?s) ("@LaAldea" . ?h) ("@Office" . ?o) (:endgroup) ("@iPad" . ?i) ;; following are needed when at times when I'm regularly accessing ;; my Org-mode agenda over SSH ;; (:startgroup) ;; ("@Emacs" . ?e) ; SSH Emacs only ;; ("@workstation" . ?m) ; on my fully set-up personal (m)achine ;; (:endgroup) ("UA" . ?w) ; academic work ("Debian" . ?d) ("FLOSS" . ?f) ;; these two probably don't need to be in the list; can remove to ;; reclaim the shortcut keys ("NOARCHIVE" . ?N) ("NOAGENDA" . ?A)) org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)") (sequence "WAITING(w@)" "SOMEDAY(s)" "|" "CANCELLED(c)")) org-todo-keyword-faces '(("SOMEDAY" . (:foreground "#0000FF" :weight bold)) ("NEXT" . (:foreground "#DD0000" :weight bold))) org-default-notes-file (concat org-directory "/refile.org") ;; don't generate bookmarks as causes git merge conflicts org-bookmark-names-plist nil org-archive-subtree-save-file-p t) ;;;; Export ;; Org-mode's export engine is great for producing versions of arbitrary Org ;; files which are more easily shareable with people who don't use Emacs. For ;; this purpose, exporting to PDF via .odt and LibreOffice's headless mode is ;; less complex than going via LaTeX, and additionally produces a .docx which ;; looks the same as the .pdf, which is often wanted for sending to others. ;; Keep export engine config simple so that exporting works robustly. ;; ;; For longer term projects where (i) the goal is to produce an output file ;; distinct from what we edit, rather than simply wanting to export something ;; for the benefit of non-Emacs users, and/or (ii) for whatever reason we want ;; to produce PS/PDF with LaTeX, possibly via Pandoc, it is preferable to have ;; build scripts and/or Makefiles alongside the source files such that the ;; output files can be rebuilt noninteractively and the external dependencies ;; are clearly defined -- so, we don't want our document build to rely on ;; Org-mode export config in this init file, but it would be okay to rely on ;; Org-mode export config in a separate .el file loaded into Emacs batch mode. ;; ;; (Experience suggests that just authoring in plain LaTeX is probably most ;; robust, except where we want to produce .docx files, in which case probably ;; Pandoc with Org-mode source (as is done in ~/doc/newpapers)) ;; with the new exporter in Org version 8, must explicitly require the ;; exporters I want to use (require 'ox-odt) (require 'ox-ascii) (require 'ox-beamer) ;; setting this means if we type C-c C-e o O then the PDF opens for inspection (setq org-odt-preferred-output-format "pdf") (add-to-list 'org-file-apps '(system . "xdg-open %s")) ;; ... but also ensure we get a .docx (would be better to make ;; `org-odt-preferred-output-format' accept a list) (defun spw/org-odt-export-docx (&rest ignore) (let ((org-input (concat (file-name-sans-extension (buffer-file-name)) ".odt"))) (org-odt-convert org-input "docx"))) (advice-add 'org-odt-export-to-odt :after #'spw/org-odt-export-docx) ;;;; Links ;; the default value for `org-notmuch-open-function' is ;; `org-notmuch-follow-link', but that function is broken: it calls ;; `notmuch-show' with a search query rather than a thread ID. This ;; causes `notmuch-show-thread-id' to be populated with a value ;; which is not a thread ID, which breaks various other things ;; ;; so use a custom function instead (defun spw/org-notmuch-follow-link (search) (let ((thread-id (car (process-lines notmuch-command "search" "--output=threads" "--limit=1" "--format=text" "--format-version=4" search)))) (notmuch-show thread-id nil nil search (concat "*notmuch-" search "*")))) (setq org-notmuch-open-function #'spw/org-notmuch-follow-link) ;;;; Agenda (setq org-agenda-custom-commands '(;; Previously, I had a minimal agenda on 'a' and a fuller agenda ;; on 'A'; that fuller agenda is now on 'a'. The idea was to open ;; the 'A' agenda, schedule tasks to be done today, and then ;; reference 'a' throughout the day. But I don't think this fits ;; grad school, and referring only to the fuller agenda is more useful ;; ("a" "Primary agenda view" ;; ((agenda "day" ((org-agenda-span 'day) ;; (org-agenda-overriding-header ;; "Tasks, appointments and waiting tasks to be chased today") ;; (org-agenda-include-deadlines nil) ;; (org-agenda-time-grid nil)))) ;; ((org-agenda-start-with-log-mode nil) ;; ;; (org-agenda-tag-filter-preset '("-Sariul")) ;; ;; (org-agenda-start-with-entry-text-mode t) ;; (org-agenda-start-with-follow-mode nil))) ;; ("A" "Daily planning view" ("a" "Primary agenda view" ((agenda "day" ((org-agenda-span 'day) (org-agenda-time-grid nil) (org-agenda-overriding-header "Plan for today & upcoming deadlines"))) (todo "TODO|NEXT" ((org-agenda-todo-ignore-scheduled t) (org-agenda-todo-ignore-deadlines 'far) (org-agenda-overriding-header' "Unscheduled standalone tasks & project next actions") (org-agenda-skip-function #'spw/skip-non-actionable))) ;; commented out as not using Org as an appointments diary at present ;; (agenda "" ((org-agenda-span 3) ;; (org-agenda-start-day "+1d") ;; (org-agenda-time-grid nil) ;; (org-agenda-repeating-timestamp-show-all t) ;; (org-agenda-entry-types '(:timestamp :sexp)) ;; (org-agenda-show-all-dates nil) ;; (org-agenda-overriding-header "Coming up") ;; (org-agenda-files (quote ("~/doc/org/diary.org"))))) )) ("#" "Weekly review view" ((todo "WAITING" ((org-agenda-todo-ignore-scheduled t) (org-agenda-todo-ignore-deadlines nil) (org-agenda-todo-ignore-with-date nil) (org-agenda-overriding-header "Waiting on others & not scheduled to chase up"))) (todo "TODO|NEXT" ((org-agenda-todo-ignore-with-date t) (org-agenda-overriding-header "Stuck projects") (org-agenda-skip-function #'spw/skip-non-stuck-projects))) (tags "LEVEL=1+REFILE" ((org-agenda-todo-ignore-with-date nil) (org-agenda-todo-ignore-deadlines nil) (org-agenda-todo-ignore-scheduled nil) (org-agenda-overriding-header "Items to add context tag and priority, and refile"))) ;; This view shows *only top-level* TODOs (i.e. projects) that ;; are complete (and that, for safety, contain no incomplete ;; (sub)projects or tasks). Sometimes I want to archive complete ;; subprojects of very large projects that are not yet complete, ;; but I don't want to have to make that decision when looking at ;; my review agenda. I can archive these as required. ;; ;; Add the NOARCHIVE tag if want to stop something from appearing ;; in this list, because for whatever reason don't want to ;; archive it (e.g. tasks which are in top-level headings ;; labelled by semester in Arizona.org (e.g. "* Fall 2019"), ;; which I archive all at once after that semester) (todo "DONE|CANCELLED" ((org-agenda-overriding-header "Tasks to be archived") (org-agenda-todo-ignore-scheduled nil) (org-agenda-todo-ignore-deadlines nil) (org-agenda-todo-ignore-with-date nil) (org-agenda-skip-function #'spw/skip-incomplete-projects-and-all-subprojects-and-NOARCHIVE))) ;; to find files which were mistakenly not added to ;; `org-agenda-files'. to exclude whole files from this list, ;; when they contains TODOs for state tracking but I don't need ;; to worry about those TODOs except when visiting the file, just ;; add #+FILETAGS: NOAGENDA (todo "TODO|NEXT|WAITING" ((org-agenda-overriding-header "Tasks from outside of org-agenda-files") (org-agenda-files (spw/org-non-agenda-files)) (org-agenda-skip-function #'spw/skip-subprojects-and-NOAGENDA))))) ;; don't have diary.org anymore; all moved into iCal ;; ("d" "Six-month diary" agenda "" ;; ((org-agenda-span 180) ;; ;; (org-agenda-start-on-weekday 1) ;; (org-agenda-time-grid nil) ;; (org-agenda-repeating-timestamp-show-all t) ;; (org-agenda-entry-types '(:timestamp :sexp)) ;; (org-agenda-show-all-dates nil) ;; (org-agenda-overriding-header "Sean's diary for the next six months") ;; (org-agenda-files '("~/doc/org/diary.org")))) )) (defun spw/org-auto-exclude-function (tag) (let ((hour-of-day ;; (info "(elisp) Time of Day") suggests you really are meant to use ;; `substring' to get at the hour of the day (string-to-number (substring (current-time-string) 11 13)))) (and (cond ;; tags passed to org-agenda-auto-exclude-function always ;; lower case per Org version 6.34 changelog ;; ;; only show La Aldea tasks when on hephaestus ;; ((string= tag "@laaldea") ;; (not (string= (system-name) "hephaestus"))) ;; always hide FLOSS, since I tend to to a tag filter to look at ;; those on their own ((string= tag "floss") t) ;; determine whether to hide work or home tasks depending on the ;; time of day ((string= tag (if (< hour-of-day 16) "@laaldea" "ua")) t) ;; hide campus tasks in evening ((and (string= tag "@campus") (> hour-of-day 16)) t) ((and (string= tag "@office") (> hour-of-day 16)) t) ;; ;; hide office tasks when at home ;; ((string= tag "@office") ;; (string= (system-name) "hephaestus")) ;; ((string= tag "@campus") ;; (string= (system-name) "athena")) ;; ((string= tag "@workstation") ;; (not (or (string= (system-name) "iris") ;; (string= (system-name) "zephyr") ;; (string= (system-name) "hephaestus")))) ;; ((string= tag "ua") ;; (= (calendar-day-of-week (calendar-current-date)) 6)) ) (concat "-" tag)))) (setq org-agenda-auto-exclude-function #'spw/org-auto-exclude-function) ;;; agenda skipping functions. Many of these are adapted from Bernt ;;; Hansen's http://doc.norang.ca/org-mode.html (defmacro spw/has-subheading-such-that (pred) `(let (matches) (save-excursion (save-restriction (org-narrow-to-subtree) (while (and (not matches) (ignore-errors (outline-next-heading))) (when ,pred (setq matches t))))) matches)) (defmacro spw/skip-when (condition) "Skip trees where CONDITION is false when evaluated when point is on the headline of the tree." `(let ((next-headline (save-excursion (outline-next-heading)))) (when ,condition (or next-headline ;; if there is no next headline, skip by going to the end ;; of the buffer. An alternative would be (save-excursion ;; (forward-line 1) (point)) (point-max))))) (defmacro spw/org-task-first-line-such-that (&rest forms) `(when (spw/is-task-or-project-p) (save-excursion ;; ignore errors in case we are before the first headline (not sure why ;; this is needed since before the first headline, ;; `spw/is-task-or-project-p' returns nil) (ignore-errors (outline-show-subtree)) (forward-line 1) (beginning-of-line-text) ,@forms))) (defun spw/is-task-or-project-p () ;; alternatively: (member (org-get-todo-state) org-todo-keywords-1) (member (nth 2 (org-heading-components)) org-todo-keywords-1)) (defun spw/is-project-p () "Any task with a todo keyword subtask" (and (spw/is-task-or-project-p) (spw/has-subheading-such-that (spw/is-task-or-project-p)))) (defun spw/is-subproject-p () "Any task which is a subtask of another project" (when (spw/is-task-or-project-p) (let ((is-subproject)) (save-excursion (while (and (not is-subproject) (org-up-heading-safe)) (when (spw/is-task-or-project-p) (setq is-subproject t)))) is-subproject))) (defun spw/is-task-p () "Any task with a todo keyword and no subtask" (and (spw/is-task-or-project-p) (not (spw/has-subheading-such-that (spw/is-task-or-project-p))))) (defun spw/skip-subprojects-and-NOAGENDA () "Skip trees that are subprojects, and trees with (possibly inherited) NOAGENDA tag" (spw/skip-when (or (spw/is-subproject-p) (member "NOAGENDA" (org-get-tags))))) (defun spw/skip-projects-with-scheduled-or-deadlined-subprojects () "Skip projects that have subtasks, where at least one of those is scheduled or deadlined" (spw/skip-when (spw/has-scheduled-or-deadlined-subproject-p))) (defun spw/skip-subprojects-and-projects-with-scheduled-or-deadlined-subprojects () "Skip subprojects projects that have subtasks, where at least one of those is scheduled or deadlined." (spw/skip-when (or (spw/is-subproject-p) (spw/has-scheduled-or-deadlined-subproject-p)))) (defun spw/skip-incomplete-projects-and-all-subprojects-and-NOARCHIVE () "Skip all subprojects and projects with subprojects not yet completed, and trees with (possibly inherited) NOARCHIVE tag" (spw/skip-when (or (spw/is-subproject-p) (spw/has-incomplete-subproject-or-task-p) (member "NOARCHIVE" (org-get-tags))))) (defun spw/skip-non-stuck-projects () (spw/skip-when (or (spw/is-task-p) (spw/has-scheduled-or-deadlined-subproject-p) (spw/has-next-action-p)))) (defun spw/skip-non-actionable () "Skip: - anything tagged @Sheffield when I'm in Tucson - anything tagged @Tucson when I'm in Sheffield - projects (i.e. has subtasks) - subtasks of projects that are not NEXT actions - subtasks of SOMEDAY projects - subtasks of WAITING projects - subtasks of scheduled projects In the last case, the idea is that if I've scheduled the project then I intend to tackle all the NEXT actions on that date (or at least the next chunk of them); I've broken the project down into NEXT actions but not for the purpose of handling them on different occasions." (spw/skip-when (or ;; #1 ;; melete is a laptop, but usually it's not in Sheffield (and (or (string= (system-name) "melete") (string= (system-name) "hephaestus")) (member "@Sheffield" (org-get-tags))) ;; #2 (and (string= (system-name) "zephyr") (member "@Tucson" (org-get-tags))) ;; #3 (spw/is-project-p) ;; we used to skip deadlined standalone tasks but actually those ;; are actionable in general ;; (and (spw/is-task-p) ;; (spw/org-has-deadline-p)) ;; #4--#7 (and (spw/is-subproject-p) (or ;; #4 (not (string= (nth 2 (org-heading-components)) "NEXT")) (save-excursion (and (org-up-heading-safe) (or ;; # 5 (string= (nth 2 (org-heading-components)) "SOMEDAY") ;; # 6 (string= (nth 2 (org-heading-components)) "WAITING") ;; # 7 (spw/org-is-scheduled-p))))))))) (defun spw/org-is-scheduled-p () "A task that is scheduled" (spw/org-task-first-line-such-that (when (looking-at (org-re-timestamp 'deadline)) (forward-sexp 2) (forward-char)) (looking-at (org-re-timestamp 'scheduled)))) (defun spw/org-has-deadline-p () "A task that has a deadline" (spw/org-task-first-line-such-that (when (looking-at (org-re-timestamp 'scheduled)) (forward-sexp 2) (forward-char)) (looking-at (org-re-timestamp 'deadline)))) (defun spw/org-is-scheduled-or-deadlined-p () "A task that is scheduled or deadlined" (spw/org-task-first-line-such-that (looking-at (org-re-timestamp 'scheduled-or-deadline)))) (defun spw/has-scheduled-or-deadlined-subproject-p () "A task that has a scheduled or deadlined subproject" (spw/has-subheading-such-that (spw/org-is-scheduled-or-deadlined-p))) (defun spw/has-next-action-p () "A task that has a NEXT subproject" (spw/has-subheading-such-that (string= (nth 2 (org-heading-components)) "NEXT"))) (defun spw/has-incomplete-subproject-or-task-p () "A task that has an incomplete subproject or task." (spw/has-subheading-such-that (not (member (nth 2 (org-heading-components)) (list "DONE" "CANCELLED"))))) (defun spw/org-non-agenda-files () "Return a list of all Org files which are not normally part of my agenda" (let ((agenda-files (org-agenda-files)) (ignore-dirs (mapcar (lambda (dir) (expand-file-name (concat org-directory "/" dir))) '("archive" "philos")))) (seq-filter (lambda (file) (not (member file agenda-files))) (directory-files-recursively (expand-file-name org-directory) "\\.org\\'" nil (lambda (dir) (not (member dir ignore-dirs))))))) ;;;; Capture (setq org-capture-templates-contexts '(("t" "m" ((in-mode . "notmuch-show-mode"))) ("t" ((not-in-mode . "notmuch-show-mode"))) ("T" ((in-mode . "notmuch-show-mode"))) ("m" ((in-mode . "notmuch-show-mode"))) ("f" ((in-mode . "notmuch-show-mode")))) org-capture-templates '(("t" "Task to be refiled" entry (file org-default-notes-file) "* TODO %^{Title}\n%?") ("T" "Task to be refiled" entry (file org-default-notes-file) "* TODO %^{Title}\n%?") ("n" "Information to be refiled" entry (file org-default-notes-file) "* %^{Title}\n%?") ("m" "Task from mail to be refiled" entry (file org-default-notes-file) ;; Lisp is to filter square brackets out of the subject as these mean that ;; the Org-mode link does not properly form. In Org 9.3, the escaping ;; syntax for links has changed, so might be able to do something smarter ;; than this "* TODO [[notmuch:id:%:message-id][%^{Title|\"%(replace-regexp-in-string \"\\\\\\[\\\\\\|\\\\\\]\" \"\" \"%:subject\")\" from %:fromname}]]\n%?") ;; This will show a thread with only flagged messages expanded. ;; ;; The purpose of this is for cases where there are multiple actionable ;; messages in a single thread, such that I want to view them all in a ;; single buffer. I flag those, and create a link to the thread using ;; this snippet. Creating Org links to individual messages would not ;; achieve this. And having an 'inbox' tag which represents actionable ;; but read messages would add overhead as I'd have to get used to ;; removing that tag from messages, and sort out syncing the tag. The ;; case comes up too rarely for it to be worth doing that. ("f" "All flagged messages in current thread" entry (file org-default-notes-file) "* TODO [[notmuch:thread:{id:%:message-id} and tag:flagged][%^{Title|Flagged messages in thread \"%(replace-regexp-in-string \"\\\\\\[\\\\\\|\\\\\\]\" \"\" \"%:subject\")\"}]]\n%?") ;; ("a" "Appointment" entry (file+datetree "~/doc/org/diary.org") ;; "* %^{Time} %^{Title & location} ;; %^t" :immediate-finish t) ;; ("A" "Appointment (untimed)" entry (file+datetree "~/doc/org/diary.org") ;; "* %^{Title & location} ;; %^t" :immediate-finish t) ("s" "Task for the future to be refiled" entry (file org-default-notes-file) "* SOMEDAY %^{Title}\n%?") ("d" "Diary entry" entry (file+datetree "~/.labbook.gpg") "* %^{Title}\n%U\n\n%?") ("u" "URI on clipboard" entry (file org-default-notes-file) "* SOMEDAY [[%^{URI|%x}][%^{Title}]]" :immediate-finish t))) ;;;; Hooks and bindings (add-hook 'org-agenda-mode-hook #'hl-line-mode) ;; for cyling remote visibility (define-key org-agenda-mode-map " " #'org-agenda-cycle-show) ;; this binding seems to have dropped out of upstream, so define it again ;; In fact, see if I can get used to using C-c @ ;;(define-key org-mode-map (kbd "C-c C-SPC") #'org-mark-subtree) ;; this works well whether or not `org-adapt-indentation' is set for a buffer (define-key org-mode-map (kbd "RET") #'org-return-indent) ;; `org-forward-paragraph', `org-backward-paragraph' and `org-mark-element' do ;; not leave point where someone who uses `forward-paragraph', ;; `backward-paragraph', `mark-paragraph' very regularly would expect, so ;; allow M-h, M-{ and M-} to have their global bindings. (dolist (key '([remap backward-paragraph] [remap forward-paragraph] [remap mark-paragraph] "\M-{" "\M-}" "\M-h")) (define-key org-mode-map key nil)) ;; with recent Org we need to unset these variables, too, to have the keys ;; behave as normal (defun spw/restore-standard-paragraphs () (kill-local-variable 'paragraph-start) (kill-local-variable 'paragraph-separate)) (add-hook 'org-mode-hook #'spw/restore-standard-paragraphs) ;; `org-forward-element', `org-backward-element' are already on C-M-a and ;; C-M-e, so for consistency, put `org-mark-element' on C-M-h (define-key org-mode-map (kbd "C-M-h") #'org-mark-element) ;;;; Miscellaneous functions (defun spw/org-reformat-subtree () (interactive) ;; we have to set the mark, rather than just narrowing to the subtree, or ;; just using `outline-back-to-heading'/`outline-next-heading', because of ;; how `org-fill-paragraph' works (save-mark-and-excursion ;; widen, because otherwise it is trickier to ensure just one line at end ;; of subtree (save-restriction (widen) ;; basic reformatting of the text (let ((transient-mark-mode t)) (org-mark-subtree) (forward-line 1) (call-interactively 'org-fill-paragraph) (call-interactively 'indent-region) (beginning-of-line 0)) ;; ensure a newline before headline unless first line of buffer (unless (or (equal (point) (point-min)) (looking-back "\n\n" nil)) (open-line 1) (forward-line 1)) ;; ensure no newline before metadata (delete-blank-lines) ;; ensure a single newline after all metadata (org-end-of-meta-data t) (open-line 2) (delete-blank-lines) ;; ensure a single newline at end of subtree (exchange-point-and-mark) (open-line 2) (delete-blank-lines)))) (global-set-key "\C-co\M-q" #'spw/org-reformat-subtree) (defun spw/org-agenda-priority-filter (arg) "Hide low-priority items. If ARG, hide slightly fewer." (interactive "P") (push (if arg "\[#A\]\\|Appt" "\[#[AB]\]\\|Appt") org-agenda-regexp-filter) (org-agenda-filter-apply org-agenda-regexp-filter 'regexp)) (define-key org-agenda-mode-map "\C-cgf" #'spw/org-agenda-priority-filter) ;;; the default C-c [ and C-c ] expand the directory ~/doc/org in the ;;; org-agenda-files variable using the local path, ;;; e.g. /meta/s/spw/doc/org, which is not good when init-custom.el is ;;; held in git. So use alternative behaviour of storing the agenda ;;; paths in a file (see documentation for `org-agenda-files'). Two ;;; functions to do the work (defun spw/org-agenda-file-to-front () (interactive) (let ((path (abbreviate-file-name buffer-file-name))) (with-current-buffer (find-file-noselect org-agenda-files) (save-excursion (goto-char (point-min)) (unless (search-forward path nil t) (goto-char (point-max)) (insert path) (save-buffer) (message "Added")))))) (defun spw/org-remove-file () (interactive) (let ((path (abbreviate-file-name buffer-file-name))) (with-current-buffer (find-file-noselect org-agenda-files) (save-excursion (goto-char (point-min)) (flush-lines (concat "^" path "$")) (save-buffer) (message "Removed"))))) ;;; init-org.el ends here