Skip to content

Latest commit

 

History

History
3010 lines (2696 loc) · 118 KB

jmm-org-config.org

File metadata and controls

3010 lines (2696 loc) · 118 KB

Josh Moller-Mara’s Org-mode config

I make heavy use of Org-mode, for organizing my TODOs, keeping track of ideas, references, and links, as well as for literate programming.

Since I use a lot of the contributed modules, we make sure we’ve loaded org-plus-contrib

(use-package org
  ;; :ensure org-plus-contrib
  :defer t				;Deferring doesn't matter since we needed org to tangle a file
  :bind (:map org-mode-map
	 ("M-T" . josh/org-convert-added-to-created-property)
	 ("M-N" . josh/org-convert-added-or-add-created)
	 ("M-M" . josh/org-toggle-marked-tag)
	 ("M-H" . jmm/org-hide-visible-properties-drawer)
	 ("C-x n p" . jmm/org-narrow-to-parent-project)
	 ("C-c M-w" . jmm/org-refile-to-register) ;; I don't really use org-refile-copy enough for its own key
	 ;; I find it really annoying having to skip over SCHEDULED,
	 ;; PROPERTIES, and LOGBOOK entries just to starting writing
	 ;; text under a heading.
	 ("C-c C-j" . jmm/org-goto-text-content))
  :init
  (setq org-indirect-buffer-display 'current-window)
  (setq org-catch-invisible-edits 'smart)
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
  (setq org-clock-out-remove-zero-time-clocks t)
  (setq org-clock-history-length 30)
  (setq org-highest-priority ?A
	org-lowest-priority ?F
	org-default-priority ?D)
  (setq org-agenda-dim-blocked-tasks nil)
  (setq org-agenda-todo-ignore-scheduled 'future
	org-agenda-tags-todo-honor-ignore-options t)
  :config
  (unbind-key "C-'" org-mode-map)
  (add-to-list 'org-tags-exclude-from-inheritance "MARKED")
  (add-to-list 'org-tag-faces '("MARKED" :foreground "MediumPurple1" :weight bold))
  (add-to-list 'org-tag-faces '("vague" :foreground "DodgerBlue" :weight bold))
  (add-to-list 'org-tag-faces '("breakdown" :foreground "RoyalBlue" :weight bold))
  (add-to-list 'org-tag-faces '("project" :foreground "DeepPink" :weight bold))
  (add-to-list 'org-tag-faces '("blocked" :foreground "yellow" :weight bold))
  (add-to-list 'org-tag-faces '("stuck" :foreground "yellow" :weight bold))
  (add-to-list 'org-tag-faces '("unsched" :foreground "gold"))
  (advice-add 'org-open-at-point :around #'jmm/org-open-link-alternate-browser)
  (advice-add 'org-open-at-point-global :around #'jmm/org-open-link-alternate-browser)
  ;; (require 'org-screenshot)
  ;; (require 'org-depend) 		;Replace with org-edna
  (require 'org-expiry)
  ;; (require 'org-inlinetask)
  ;; (require 'org-collector)
  (require 'ox)
  (require 'oc)	;; For citations. Requires a pretty recent version of org.
  (require 'oc-basic)
  (require 'oc-biblatex))

(use-package org-agenda
  :after org
  :defer t
  :bind (:map org-agenda-mode-map
	      ("M-P" . josh/org-agenda-lock-to-parent-project)
	      ("C-c M-w" . jmm/org-refile-to-register))
  :hook ((org-agenda-mode . hl-line-mode))
  :init
  (setq org-agenda-tags-column 0)
  :config
  (require 'org-clock))

Set up org-crypt

org-crypt is handy if your todo list sometimes contains sensitive information, like information about your taxes, etc.

(use-package org-crypt
  :after org
  :init
  (add-to-list 'org-tags-exclude-from-inheritance "crypt")
  (setq org-crypt-disable-auto-save t)
  :config
  (progn
    (org-crypt-use-before-save-magic)))

Setup my agenda file

Read from “org-agendas” where the agenda files are. Set the default span to a day view. I also have a script periodically export agendas. So to keep from destroying an agenda I’m currently viewing, I turn on sticky agendas.

I find that time grids can get in my way if there are too many of them, so we’ll just show a few times instead of the default of every two hours.

(setq org-agenda-files (concat (file-name-as-directory org-directory) "org-agendas.txt"))
(setq org-agenda-span 3)
(setq org-agenda-sticky t)
;; (setcar (nthcdr 2 org-agenda-time-grid) '(900 1200 1700))

Don’t open encrypted files

You can have encrypted files in your org-agenda. The only problem is that every time you want to generate an agenda, you either need to have the encrypted file open or you need to enter your password. I’d rather only display encrypted org files in my agenda if they’re already open, otherwise I just skip them.

(defun jmm/org-agenda-skip-unopened-encrypted (orig-fun &rest args)
  "Don't try to open encrypted org files that aren't already open."
  (let ((res (apply orig-fun args)))
    (-filter (lambda (file)
	       (if (s-equals? (f-ext file) "gpg")
		   (get-file-buffer file)
		 t))
	     res)))

(advice-add 'org-agenda-files :around #'jmm/org-agenda-skip-unopened-encrypted)

(defun jmm/org-id-skip-unopened-encrypted (orig-fun &rest args)
  "Don't try to open encrypted org files that aren't already open when updating the org-id database."
  (let ((org-id-files (-filter (lambda (file)
				       (if (s-equals? (f-ext file) "gpg")
					   (get-file-buffer file)
					 t))
			       org-id-files)))
    (apply orig-fun args)))
;; TODO: Maybe we should push the encrypted files back into the id
;; database afterward, though. This currently just removes them from
;; the db completely.

(advice-add 'org-id-update-id-locations :around #'jmm/org-id-skip-unopened-encrypted)

Also worth noting, if you have encrypted .org.gpg files and you archive tasks in a separate _archive file, the archive file will be in cleartext. You probably don’t want this. The solution here is to set .org.gpg_archive files to be encrypted by epa.

;; It's better to actually set this through Custom, so you don't have
;; to call `epa-file-name-regexp-update'.
;; (setq epa-file-name-regexp "\\.gpg\\(~\\|\\.~[0-9]+~\\|_archive\\)?\\'")
;; (epa-file-name-regexp-update)

Default notes file?

Note to self, figure out what the difference between this and agenda is.

(setq org-default-notes-file (concat (file-name-as-directory org-directory) "gtd-test.org"))

Capture templates

Need to make this more portable across different systems. I think it defaults to org-directory.

(setq org-capture-templates
      '(("t" "Todo" entry (id "28d41dfc-bff4-428b-939d-63cb4fcea958")
         "* TODO %i%?\n  Added: %U")
        ("r" "Refile" entry (file "refile.org")
         "* %i%?\n  Added: %U")
        ("a" "Today" entry (id "28d41dfc-bff4-428b-939d-63cb4fcea958")
         "* TODO %?\n  SCHEDULED: <%<%Y-%m-%d %a>>\n  Added: %U")
        ("d" "Date" entry (file+datetree+prompt "~/org/journal.org")
         "* %?\n%t\n")
	("w" "Calendar" entry (id "42a740a4-8483-40a0-9c53-ad846e9a7def")
         "* %?\n%^t\n")
	("E" "Email capture")
	("EE" "Event (from email)" entry (id "42a740a4-8483-40a0-9c53-ad846e9a7def")
	 "* %:subject%?\n%^T\n%i\n%a%^{LOCATION}p")
	("Et" "Todo (from email)" entry (id "28d41dfc-bff4-428b-939d-63cb4fcea958")
	 "* TODO %:subject%?  :email:\n%i\n%a")
	("Ea" "Todo today (from email)" entry (id "28d41dfc-bff4-428b-939d-63cb4fcea958")
	 "* TODO %:subject%?   :email:\n  SCHEDULED: <%<%Y-%m-%d %a>>\n%a\n%i")
	("Er" "Todo reply to email" entry (id "28d41dfc-bff4-428b-939d-63cb4fcea958")
	 "* TODO Reply to %(car (s-split \" \" \"%:from\")): \"%:subject\"%?   :email:\n%a\n%i")
        ("l" "Lookup stuff")
        ("ll" "Lookup" entry (file+headline "lookup.org" "Lookup")
         "* %?\n  Added: %U")
        ("lp" "Lookup paper" entry (file+headline "lookup.org" "Paper lookup")
         "* TODO %?")
	("lP" "Lookup paper (link)" entry (file+headline "lookup.org" "Paper lookup")
	 "* [[%c][%(www-get-page-title (current-kill 0))]]\n  Added: %U\n  - %c")
        ("L" "Lookup link" entry (file+headline "lookup.org" "Lookup")
         "* [[%c][%(www-get-page-title (current-kill 0))]]\n  Added: %U\n  - %c")
        ("N" "Neuroecon" entry (file+headline "neuroecon.org" "Neuroecon")
         "* %?\n%i\n  Added: %U")
	("n" "NYU")
	("ne" "NYU Events" entry (file+headline "nyu.org" "NYU Events Calendar") "* %?\n%^t\n")
	("nc" "CNS Events" entry (file+headline "nyu.org" "CNS Events") "* %?\n%^t\n")
	("nd" "Service disruption" entry (file+headline "nyu.org" "Service disruption") "* %?\n%^t\n")
	("ns" "NYU Shanghai Events" entry (file+headline "nyu.org" "NYU Shanghai Calendar") "* %?\n%^t\n")
        ("M" "Someday/Maybe" entry (file+headline "someday-maybe.org" "Someday/Maybe")
         "* %?\n  Added: %U")
        ("m" "Someday/Maybe lists")
        ("mm" "Someday/Maybe" entry (file+headline "someday-maybe.org" "Someday/Maybe")
         "* %?\n  Added: %U")
        ("mc" "Computer maybe" entry (file+headline "someday-maybe.org" "Computer Maybe")
         "* %?\n  Added: %U")
        ("ml" "Learn" entry (file+headline "someday-maybe.org" "Learn")
         "* %?\n  Added: %U")
        ("mb" "Books/Reading" entry (file+headline "movies-books-media.org" "Books/Reading")
         "* %?\n  Added: %U")
        ("mv" "Movies" entry (file+headline "movies-books-media.org" "Movies")
         "* %?\n  Added: %U")
        ("ma" "Audio/Music" entry (file+headline "movies-books-media.org" "Music/Audio")
         "* %?\n  Added: %U")
        ("ms" "Shanghai goals" entry (file+headline "someday-maybe.org" "Shanghai Goals")
         "* %?\n  Added: %U")
        ("o" "thoughts" plain (file "thoughts-misc.txt")
         "\n\n%U -\n\n %?\n" :empty-lines 1)
        ("e" "Erlich stuff")
        ("et" "Erlich tasks" entry (file+headline "erlich.org" "Erlich tasks")
         "* TODO %i%?\n  Added: %U")
        ("ea" "Erlich today" entry (file+headline "erlich.org" "Erlich tasks")
         "* TODO %?\n  SCHEDULED: <%<%Y-%m-%d %a>>\n  Added: %U")
        ("eo" "Erlich thoughts" entry (file+headline "erlich.org" "Erlich thoughts")
         "* %i%?\n  Added: %U")
        ("em" "Erlich maybe" entry (file+headline "erlich.org" "Erlich maybe")
         "* %?\n  Added: %U")
        ("j" "Journal Stuff")
	("ja" "Journal task today" entry (file+datetree "journal.org")
         "* TODO %?  :task:\n  SCHEDULED: <%<%Y-%m-%d %a>>\n  Added: %U")
	("je" "Journal event today" entry (file+datetree "journal.org")
         "* %?\n%^t\n")
        ("jr" "Journal Resume" entry (file+datetree "~/org/journal.org")
         "* %?\n%U\n" :clock-in t :clock-resume t)
        ("jc" "Journal Clock-In" entry (file+datetree "~/org/journal.org")
         "* %?\n%U\n" :clock-in t :clock-keep t)
        ("ji" "Journal Clock-In Immediate" entry (file+datetree "~/org/journal.org")
         "* %c %u\n%U\n" :clock-in t :clock-keep t :immediate-finish t)
	("jw" "Journal Weight table" table-line (id "ffb6e5d6-fdfe-47cf-ad1c-a6e4ea7900dc")
         "| %u | %? |")
	("jW" "Journal Wake table" table-line (id "3bca8376-bfdc-40af-bf0a-c130fd677c33")
         "| %U | %u | %? |")
        ("J" "Jokes" plain (file "jokes.txt")
         "\n\n%U -\n\n %?\n" :empty-lines 1)
        ("v" "Vocabulary" entry
         (file+headline "~/reading/words-i-learned.org" "Vocabulary")
         "* %^{The word} :drill:\n Added: %U\n %^{Extended word (may be empty)|%\\1}\n** Answer \n%^{The definition}")
        ("V" "Two-sided Vocabulary" entry
         (file+headline "~/reading/words-i-learned.org" "Vocabulary")
         "* <[%^{The word}]> :drill:\n Added: %U\n    :PROPERTIES:\n    :DRILL_CARD_TYPE: twosided\n    :END:\n** Word\n%^{Extended word (may be empty)|%\\1}\n** Definition\n%^{Definition}\n** Examples\n%^{Examples}\n")
        ("c" "Chinese Word" entry
         (file+headline "~/reading/skip/chinese.org" "Words")
         "* <[%(josh/chinese-prompt)]> :drill:\n Added: %U\nDefinition:\n%(josh/chinese-get-definition (josh/chinese-dict-find josh/chinese-word))\n** Characters\n%(josh/chinese-get-word josh/chinese-word-dict)\n** Pronunciation\n%(josh/chinese-get-pronunciation josh/chinese-word-dict)\n** Cangjie\n%(josh/chinese-cangjie-codes josh/chinese-words)\n")
        ("C" "Chinese Word (Read)" entry
         (file+headline "~/reading/skip/chinese.org" "Words")
         "* <[%(josh/chinese-prompt)]> :drill:\n Added: %U\n%(josh/chinese-get-word (josh/chinese-dict-find josh/chinese-word))\n** Pronunciation\n%(josh/chinese-get-pronunciation josh/chinese-word-dict)\n** Cangjie\n%(josh/chinese-cangjie-codes josh/chinese-words)\n** Definition\n%(josh/chinese-get-definition josh/chinese-word-dict)\n")
        ("R" "reading" plain
         (file "~/org/data/reading.csv")
         "%(format-time-string \"%s\"),\"%(format-time-string \"%Y-%m-%d\")\",\"%(josh/prompt-book)\",%^{Start},%^{End}")
	("x" "Miscellaneous")
	("xt" "Tweet" entry (file+headline "ideas.org" "Tweets")
         "* %i%?\n  Added: %U")))

Fix a bug causing org-capture to mess up line numbers. This can make loading things with long lines slow, though.

(setq-default cache-long-scans nil)
(setq org-element-use-cache nil)

A a function to make testing capture templates easier.

(defun jmm/add-to-org-capture (template)
  "Add a template, or a list of templates, to `org-capture-templates'.
When template doesn't have a cdr, delete it."
  (if (listp (car template))		;We're dealing with a list of templates
      (-map 'jmm/add-to-org-capture template)
    (let* ((key (car template))
	   (parentkey (when (> (length key) 1) (substring key 0 -1))))
      (-if-let (template-index (--find-index (equal (car it) key) org-capture-templates)) ;It already exists in templates, just replace
	  (setq org-capture-templates (if (cdr template)
					  (-replace-at template-index template org-capture-templates)
					(-remove-at template-index org-capture-templates)))
	(when (and parentkey (not (--find-index (equal (car it) parentkey) org-capture-templates)))
	  (jmm/add-to-org-capture (list parentkey (format "Dummy entry for %s" parentkey))))
	(setq org-capture-templates
	      (-insert-at (-if-let (parent-index (and parentkey (--find-index (equal (car it) parentkey) org-capture-templates)))
			      (1+ parent-index)
			    0)
			  template org-capture-templates))))))

Get the title of a URL

Used for a capture template. I want my links to also have a sort of description

(defun html-entities-to-unicode (string)
  "Convert html entities. Modified from konr's answer on https://stackoverflow.com/a/8483409"
  (let* ((plist '(Aacute "Á" aacute "á" Acirc "Â" acirc "â" acute "´" AElig "Æ" aelig "æ" Agrave "À" agrave "à" alefsym "ℵ" Alpha "Α" alpha "α" amp "&" and "∧" ang "∠" apos "'" aring "å" Aring "Å" asymp "≈" atilde "ã" Atilde "Ã" auml "ä" Auml "Ä" bdquo "„" Beta "Β" beta "β" brvbar "¦" bull "•" cap "∩" ccedil "ç" Ccedil "Ç" cedil "¸" cent "¢" Chi "Χ" chi "χ" circ "ˆ" clubs "♣" cong "≅" copy "©" crarr "↵" cup "∪" curren "¤" Dagger "‡" dagger "†" darr "↓" dArr "⇓" deg "°" Delta "Δ" delta "δ" diams "♦" divide "÷" eacute "é" Eacute "É" ecirc "ê" Ecirc "Ê" egrave "è" Egrave "È" empty "∅" emsp " " ensp " " Epsilon "Ε" epsilon "ε" equiv "≡" Eta "Η" eta "η" eth "ð" ETH "Ð" euml "ë" Euml "Ë" euro "€" exist "∃" fnof "ƒ" forall "∀" frac12 "½" frac14 "¼" frac34 "¾" frasl "⁄" Gamma "Γ" gamma "γ" ge "≥" gt ">" harr "↔" hArr "⇔" hearts "♥" hellip "…" iacute "í" Iacute "Í" icirc "î" Icirc "Î" iexcl "¡" igrave "ì" Igrave "Ì" image "ℑ" infin "∞" int "∫" Iota "Ι" iota "ι" iquest "¿" isin "∈" iuml "ï" Iuml "Ï" Kappa "Κ" kappa "κ" Lambda "Λ" lambda "λ" lang "〈" laquo "«" larr "←" lArr "⇐" lceil "⌈" ldquo "“" le "≤" lfloor "⌊" lowast "∗" loz "◊" lrm "" lsaquo "‹" lsquo "‘" lt "<" macr "¯" mdash "—" micro "µ" middot "·" minus "−" Mu "Μ" mu "μ" nabla "∇" nbsp "" ndash "–" ne "≠" ni "∋" not "¬" notin "∉" nsub "⊄" ntilde "ñ" Ntilde "Ñ" Nu "Ν" nu "ν" oacute "ó" Oacute "Ó" ocirc "ô" Ocirc "Ô" OElig "Œ" oelig "œ" ograve "ò" Ograve "Ò" oline "‾" omega "ω" Omega "Ω" Omicron "Ο" omicron "ο" oplus "⊕" or "∨" ordf "ª" ordm "º" oslash "ø" Oslash "Ø" otilde "õ" Otilde "Õ" otimes "⊗" ouml "ö" Ouml "Ö" para "¶" part "∂" permil "‰" perp "⊥" Phi "Φ" phi "φ" Pi "Π" pi "π" piv "ϖ" plusmn "±" pound "£" Prime "″" prime "′" prod "∏" prop "∝" Psi "Ψ" psi "ψ" quot "\"" radic "√" rang "〉" raquo "»" rarr "→" rArr "⇒" rceil "⌉" rdquo "”" real "ℜ" reg "®" rfloor "⌋" Rho "Ρ" rho "ρ" rlm "" rsaquo "›" rsquo "’" sbquo "‚" scaron "š" Scaron "Š" sdot "⋅" sect "§" shy "" Sigma "Σ" sigma "σ" sigmaf "ς" sim "∼" spades "♠" sub "⊂" sube "⊆" sum "∑" sup "⊃" sup1 "¹" sup2 "²" sup3 "³" supe "⊇" szlig "ß" Tau "Τ" tau "τ" there4 "∴" Theta "Θ" theta "θ" thetasym "ϑ" thinsp " " thorn "þ" THORN "Þ" tilde "˜" times "×" trade "™" uacute "ú" Uacute "Ú" uarr "↑" uArr "⇑" ucirc "û" Ucirc "Û" ugrave "ù" Ugrave "Ù" uml "¨" upsih "ϒ" Upsilon "Υ" upsilon "υ" uuml "ü" Uuml "Ü" weierp "℘" Xi "Ξ" xi "ξ" yacute "ý" Yacute "Ý" yen "¥" yuml "ÿ" Yuml "Ÿ" Zeta "Ζ" zeta "ζ" zwj "" zwnj ""))
	 (get-numeric-function (lambda (s)
				 (char-to-string (string-to-number (cadr (s-match "&#\\([0-9]+\\);" s))))))
         (get-function (lambda (s) (or (plist-get plist (intern (substring s 1 -1))) s))))
    (--> string
	 (replace-regexp-in-string "&#\\([0-9]+\\);" get-numeric-function it)
	 (replace-regexp-in-string "&[^; ]*;" get-function it))))

(defun www-get-page-title (url)
  "Modified from https://lists.gnu.org/archive/html/help-gnu-emacs/2010-07/msg00291.html"
  (html-entities-to-unicode
   (let ((title))
    (with-current-buffer (url-retrieve-synchronously url)
      (let* ((title (progn (goto-char (point-min))
			   (when (re-search-forward "<title>\\([^<]*\\)</title>" nil t 1)
			     (match-string 1))))
	     (coding (progn (goto-char (point-min))
			    (when (re-search-forward "charset=\"?\\([-0-9a-zA-Z]*\\)\"?" nil t 1)
			      (match-string 1)))))
	(if (and coding (not (string= "" coding)))
	    (decode-coding-string title (intern (downcase coding)))
	  title))))))

Org agenda listings

(setq org-agenda-custom-commands
      (quote
       (("w" todo "WAITING")
        ("W" todo-tree "WAITING")
        ("b" "Things to do if bored"
         tags "IFBORED"
         ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done)))
         ("~/org/blockreddit/ifbored.html"))
	("o" "Old done tasks" todo "DONE|CANCELLED"
	 ((org-agenda-skip-function (lambda () (josh/org-skip-old 14)))
	  (org-agenda-overriding-header "Old DONE tasks: ")))
        ("D" "Daily Action List"
         ((agenda "" ((org-agenda-ndays 1)
                      (org-agenda-sorting-strategy
                       (quote ((agenda time-up priority-down tag-up) )))
                      (org-deadline-warning-days 0)))))
	;; Agenda of tasks that are labeled "TODO" but don't have any schedule or deadline.
	("u" "Unscheduled" todo "TODO"
	 ((org-agenda-skip-function (lambda () (or (zin/org-agenda-skip-tag "task" t)
						   (org-agenda-skip-entry-if 'scheduled 'deadline))))
	  (org-agenda-overriding-header "Unscheduled tasks: ")))
        ("U" "Unscheduled NoDeadline" alltodo ""
         ((org-agenda-skip-function
           '(org-agenda-skip-entry-if 'scheduled 'deadline)))))))

An agenda which shows which papers I should read.

;; Note: josh/plist-get is defined elsewhere in this file
(defun jmm/org-get-raw-scheduled ()
  "Raw scheduled date for element at point."
  (concat				;If it's nil, don't display anything
   (josh/plist-get (org-element-at-point) 'headline :scheduled 'timestamp :raw-value)))

(defun jmm/org-get-raw-created ()
  "Raw created date for element at point."
  (concat				;If it's nil, don't display anything
   (org-entry-get (point) org-expiry-created-property-name)))

(defun jmm/org-get-created-set-property (agendastr)
  "Takes in a line AGENDASTR.
   If it has \"created-time\" set, return it.
   Otherwise looks for the `org-expiry-created-property-name' and sets \"created-time\""
  (let* ((has-ct-prop (plist-member (text-properties-at 0 agendastr) 'created-time))
	 (createdtime (cadr has-ct-prop)))
    (if has-ct-prop
	createdtime
      (let* ((createdprop (org-entry-get (get-text-property 0 'org-hd-marker agendastr) org-expiry-created-property-name))
	     (createts (if createdprop (org-time-string-to-absolute createdprop))))
	(org-add-props agendastr nil
	  'created-time createts)
	createts))))

(defun jmm/org-agenda-sort-created-time (a b)
  "To be set as `org-agenda-cmp-user-defined'.
   Very similar to `org-cmp-ts'"
  (let* ((def (if org-sort-agenda-notime-is-late most-positive-fixnum -1))
	 (ta (or (jmm/org-get-created-set-property a) def))
	 (tb (or (jmm/org-get-created-set-property b) def)))
    (cond ((< ta tb) -1)
	  ((< tb ta) +1))))

;; Any use of org-add-agenda-custom-command requires org-agenda
;; It's simple enough that I could change it to a call to "add-to-list"
;; Oh well
(require 'org-agenda)
(org-add-agenda-custom-command
 '("j" "Journal articles"
   ((tags-todo "+paper"
	      ((org-agenda-overriding-header "Scheduled articles")
	       (org-agenda-skip-function '(lambda () (or (org-agenda-skip-entry-if 'todo 'done) (org-agenda-skip-entry-if 'notscheduled))))
	       (org-agenda-prefix-format " %i %-5:c %-17(jmm/org-get-raw-scheduled) ")
	       (org-agenda-sorting-strategy '(scheduled-up))))
    (tags-todo "+paper"
	      ((org-agenda-overriding-header "Unscheduled articles")
	       (org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled 'todo 'done))
	       (org-agenda-prefix-format " %i %-5:c %-22(jmm/org-get-raw-created) ")
	       (org-agenda-cmp-user-defined 'jmm/org-agenda-sort-created-time)
	       (org-sort-agenda-notime-is-late nil)
	       (org-agenda-sorting-strategy '(priority-down user-defined-down))))
    )
   ((org-agenda-hide-tags-regexp "paper"))))

A projects-related agenda. View next tasks, waiting, and stuck projects.

(org-add-agenda-custom-command
 '("P" "Projects and Next Tasks"
   ((tags-todo "-CANCELLED/!NEXT"
               ((org-agenda-overriding-header "Next tasks")
                (org-agenda-skip-function 'bh/skip-projects-and-habits-and-single-tasks)))
    (tags-todo "-CANCELLED+WAITING|HOLD/!"
               ((org-agenda-overriding-header "Waiting tasks")
                (org-agenda-skip-function 'bh/skip-non-tasks)))
    (tags-todo "-CANCELLED/!"
               ((org-agenda-overriding-header "Stuck Projects")
                (org-agenda-skip-function 'bh/skip-non-stuck-projects))))))

Same thing as above, but also include the next three days agenda.

(defvar jmm/agenda-two-span
  '(agenda "" ((org-agenda-prefix-format " %i %-12:c%?-12t% s%(josh/org-show-effort-and-clocked) ")
                (org-agenda-skip-scheduled-if-done t)
                (org-agenda-span 2)))
  "An agenda for the next couple days that shows effort and clocked time.")

(defvar jmm/agenda-unscheduled-next
  '(tags-todo "-CANCELLED-HOLD/!NEXT"
          ((org-agenda-overriding-header "Unscheduled next tasks")
           (org-agenda-prefix-format " %i %-12:c%?-12t% s%(josh/org-format-next-time) ")
	   (org-agenda-cmp-user-defined 'jmm/org-agenda-sort-next-time)
    	   (org-sort-agenda-notime-is-late nil)
    	   (org-agenda-sorting-strategy '(priority-down user-defined-down))
           (org-agenda-skip-function
            (lambda () (or (org-agenda-skip-entry-if 'scheduled 'deadline)
                           (bh/skip-projects-and-habits-and-single-tasks))))))
  "An agenda that shows unscheduled NEXT tasks.")

(defvar jmm/agenda-unscheduled-waiting
  '(tags-todo "-CANCELLED+WAITING/!"
              ((org-agenda-overriding-header "Unscheduled waiting tasks")
               (org-agenda-skip-function
                (lambda () (org-agenda-skip-entry-if 'scheduled 'deadline)))
               (org-agenda-prefix-format " %i %-12:c%?-12t% s%(josh/org-format-waiting-time) ")))
  "An agenda that shows waiting tasks, and how long they've been waiting for.")

(defvar jmm/agenda-unscheduled-events
  '(tags-todo "+event/!"
              ((org-agenda-overriding-header "Unscheduled waiting tasks")
               (org-agenda-skip-function
                (lambda () (org-agenda-skip-entry-if 'scheduled 'deadline)))))
  "An agenda that shows events that are weirdly not scheduled.")

(defvar jmm/agenda-non-stuck-projects
  '(tags-todo "-CANCELLED/!"
              ((org-agenda-overriding-header "\"Non stuck\" Projects")
               (org-agenda-skip-function 'bh/skip-non-projects)
	       (org-agenda-prefix-format " %i %-12:c%?-12t% s%(josh/org-format-max-clock-time) "))))

(defvar jmm/agenda-stuck-projects
  '(tags-todo "-CANCELLED-HOLD/!"
               ((org-agenda-overriding-header "Stuck Projects")
                (org-agenda-skip-function 'bh/skip-non-stuck-projects)
                (org-agenda-prefix-format " %i %-12:c%?-12t% s%(josh/org-format-max-clock-time) ")))
  "An agenda that shows stuck projects")

(defvar jmm/agenda-refile-stuff
  '(tags "REFILE"
          ((org-agenda-hide-tags-regexp "REFILE")
	   (org-agenda-overriding-header "Refile:")))
  "Agenda that shows things to refile.")

(defvar jmm/agenda-unscheduled-tasks
  '(todo "TODO"
         ((org-agenda-skip-function (lambda () (or (zin/org-agenda-skip-tag "task" t)
                                                   ;; (bh/skip-projects-and-habits)
                                                   (josh/skip-project-to-next-heading)
                                                   (org-agenda-skip-entry-if 'scheduled 'deadline))))
          (org-agenda-overriding-header "Unscheduled tasks: ")
	  (org-agenda-cmp-user-defined 'jmm/org-agenda-sort-created-time)
	  (org-sort-agenda-notime-is-late nil)
	  (org-agenda-sorting-strategy '(priority-down user-defined-down))
          (org-agenda-prefix-format " %i %-12:c%?-12t% s%(josh/org-format-age-from-added) ")))
  "An agenda that shows unscheduled tasks and how old they are.")

(org-add-agenda-custom-command
 `("  " "Default agenda"
   (,jmm/agenda-two-span
    ,jmm/agenda-unscheduled-next
    ,jmm/agenda-unscheduled-waiting
    ,jmm/agenda-stuck-projects
    ,jmm/agenda-refile-stuff
    ,jmm/agenda-unscheduled-tasks)))

(org-add-agenda-custom-command `(" d" "Scheduled agenda" (,jmm/agenda-two-span)))
(org-add-agenda-custom-command `(" u" "Unscheduled things" (,jmm/agenda-unscheduled-next ,jmm/agenda-unscheduled-waiting ,jmm/agenda-unscheduled-events ,jmm/agenda-unscheduled-tasks)))
(org-add-agenda-custom-command `(" s" "Stuck projects" (,jmm/agenda-stuck-projects ,jmm/agenda-unscheduled-waiting)))
(org-add-agenda-custom-command `(" p" "Projects" (,jmm/agenda-non-stuck-projects ,jmm/agenda-stuck-projects)))
(org-add-agenda-custom-command `(" r" "Refile things" (,jmm/agenda-refile-stuff)))

(defun jmm/org-default-agenda ()
  "Display my default org agenda"
  (interactive)
  (org-agenda nil " d"))

Other agendas. Like movies to see, things I need to buy, and so on.

(org-add-agenda-custom-command
   '("1" "Shopping" tags "+SHOPPING-TODO=\"DONE\"-TODO=\"CANCELLED\""
     ((org-agenda-hide-tags-regexp "SHOPPING")
      (org-agenda-overriding-header "Shopping stuff: "))))

(org-add-agenda-custom-command
   '("v" "Movies" tags-todo "+movie"
     ((org-agenda-hide-tags-regexp "movie")
      (org-agenda-overriding-header "Movies to see: "))))

An agenda for unscheduled tasks where we’ve set a deadline, but never scheduled it. Show earlier due entries first.

(defun josh/plist-get (plist prop &rest rest-props)
  "Recursively apply `plist-get' to plist"
  (let ((got (plist-get plist prop)))
    (if (and got rest-props)
	(apply 'josh/plist-get got rest-props)
      got)))

(defun josh/org-get-raw-deadline ()
  "Raw raw deadline for element at point."
  (josh/plist-get (org-element-at-point) 'headline :deadline 'timestamp :raw-value))

(add-to-list 'org-agenda-custom-commands
   '("u" "Unscheduled Deadline" alltodo ""
     ((org-agenda-overriding-header "Unscheduled TODOs with deadlines")
      (org-agenda-prefix-format " %i %-12:c%?-12t% s%-22(josh/org-get-raw-deadline) ")
      (org-agenda-sorting-strategy '(deadline-up))
      (org-agenda-skip-function
       '(or (org-agenda-skip-entry-if 'scheduled 'notdeadline)
	    (and (bh/is-project-p) (bh/skip-non-stuck-projects)))))))

A basic agenda for goals. In the future I should make this more nuanced.

  • Which goals have I started?
  • Which goals are deferred?
  • What are the different categories of goals, and in what time range do I plan to have them done?
    • Do they have deadlines, or are they just lofty ideas?
  • Some of these can have org-agenda-overriding-columns-format set to view it automatically
  • Sort by deadlines or importance?
;; TODO: Eventually just make this more like Sacha Chua's evil plans
(org-add-agenda-custom-command
   `("g" "Goals"
     ((tags "goal"
	    ((org-agenda-overriding-header "Goals")
	     (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))))
      (tags "lifegoal"
	    ((org-agenda-overriding-header "Life Goals")
	     )))
     ((org-agenda-overriding-columns-format "%50ITEM(Goal) %5Effort(Time){:} %6CLOCKSUM{Total}")
      (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
      ;; (org-agenda-view-columns-initially t)
      (org-agenda-hide-tags-regexp ,(rx (or "lifegoal" "goal"))))))

Sorting timestamps in the agenda

I’d like to sort my NEXT actions by their age. This lets me know which NEXT actions have been sitting around and not getting done, which is a sign that the action needs to be either better specified or further broken down.

Sorting in the agenda is pretty slow. Here are some macros and functions that try to use memoization to speed up sorting.

(defmacro jmm/org-agenda-memoize (funcname key ifnotmemoized)
  "Make a function that memoizes some stuff in org-agenda properties. Use symbol KEY as the text property"
  (let ((hasprop (gensym))
	(newval (gensym)))
    `(defun ,funcname (agendastr)
       (let* ((,hasprop (plist-member (text-properties-at 0 agendastr) ,key)))
	 (if ,hasprop
	     (cadr ,hasprop)		;get the actual value
	   (let* ((,newval (,ifnotmemoized (get-text-property 0 'org-hd-marker agendastr))))
	     (org-add-props agendastr nil
	       ,key ,newval)
	     ,newval))))))

;; FIXME. Needs to be some other number when neither defined
(jmm/org-agenda-memoize
 jmm/org-agenda-get-next-time 'nexttime
 (lambda (orgmarker)
   (with-current-buffer (marker-buffer orgmarker)
     (save-excursion
       (goto-char (marker-position orgmarker))
       (max (josh/absolute-time-or-0 (josh/org-get-next-time))
	    (josh/absolute-time-or-0 (josh/org-get-added-time)))))))

(defun jmm/org-agenda-sort-next-time (a b)
  "To be set as `org-agenda-cmp-user-defined'.
   Very similar to `org-cmp-ts'"
  (let* ((def (if org-sort-agenda-notime-is-late most-positive-fixnum -1))
	 (ta (or (jmm/org-agenda-get-next-time a) def))
	 (tb (or (jmm/org-agenda-get-next-time b) def)))
    (cond ((< ta tb) -1)
	  ((< tb ta) +1))))

Setting a restriction lock to a parent project

Sometimes I want to narrow my org agenda view to just look at one project. Setting a restriction lock for the project does this, and also makes agenda generation much faster. This function finds the parent project of a task and sets a restriction lock to the project.

(defun jmm/org-goto-parent-project ()
  "Go to parent project using `bh/is-project-p' and `bh/is-subproject-p'."
  (save-restriction
    (widen)
    (let ((start (point))
	  (found))
      (while (progn (and (not found)
			 (or (and (bh/is-project-p)
				  (not (bh/is-subproject-p))
				  (setq found t))
			     (org-up-heading-safe)))))
      (unless found
	(goto-char start))
      found)))

(defun jmm/org-agenda-lock-to-parent-project ()
  "In the org mode agenda, lock the restriction to the current project."
  (interactive)
  (save-window-excursion
    (org-agenda-goto)
    (if (jmm/org-goto-parent-project)
	(org-agenda-set-restriction-lock)
      (user-error "No parent project found.")))
  (org-agenda-redo-all))

Also, here’s a handy function like org-narrow-to-subtree but for narrowing to the current project.

(defun jmm/org-narrow-to-parent-project ()
  "Like `org-narrow-to-subtree' but for the current project tree."
  (interactive)
  (save-excursion
    (if (jmm/org-goto-parent-project)
	(progn
	  (outline-show-all)
	  (org-narrow-to-subtree))
      (user-error "No parent project found."))))

Org persistent tags

Some tags that I might use a lot. (Or maybe I don’t, but I just don’t want to have the hotkeys for each of these tags repeatedly in each file.)

(setq org-tag-persistent-alist '(("task" . ?t) ("drill" . ?d)
				 ("IGNORE" . ?I)
                                 ("breakdown" . ?b) ("blocked" . ?B)
				 ("CANCELLED" . ?C)
                                 ("WAITING" . ?w) ("home" . ?h)
                                 ("REWARD" . ?R) ("SHOPPING" . ?s)
                                 ("paper" . ?p) ("erlich" . ?e) ("@net" . ?n) ("nyu" . ?y)
				 ("project" . ?P)
				 ("event" . ?E)
                                 ("vague" . ?v) ("lookup" . ?l)
                                 ("CODING" . ?c)
                                 ("goal" . ?g)))

(add-to-list 'org-tags-exclude-from-inheritance "IGNORE")

Define a stuck project

Stuck projects are projects that don’t have a next action or a TODO. Also, make sure the “PROJECT” tag isn’t inherited.

(setq org-stuck-projects
           '("+PROJECT/-MAYBE-DONE" ("NEXT" "TODO") ("@SHOP")
             "\\<IGNORE\\>"))

(add-to-list 'org-tags-exclude-from-inheritance "PROJECT")
(add-to-list 'org-tags-exclude-from-inheritance "project")
(add-to-list 'org-tags-exclude-from-inheritance "stuck")
(add-to-list 'org-tags-exclude-from-inheritance "unsched")

If I didn’t want it to interfere with windmove

;; (setq org-replace-disputed-keys t)

Writing my current task to a file

I have a conky script that displays my current task. That way, even when I’m not in Emacs, I can see what task I’m supposed to be working on, and how long I’ve been clocked into it.

(setq josh/clock-current-task-file "~/.currenttask")

(defun josh/org-clock-in-conky ()
  (interactive)
  "Creates a file `josh/clock-current-task-file' with the current task and the time started.
To be used with a script in conky to display what I'm working on."
  (if org-clock-current-task
      (with-temp-file josh/clock-current-task-file
          (progn
            (insert org-clock-current-task)
            (newline)
            (insert (format-time-string "%s" org-clock-start-time))
            (newline)))))

(defun josh/org-clock-out-conky ()
  (interactive)
  "When I clock out, remove `josh/clock-current-task-file'"
  (if (file-exists-p josh/clock-current-task-file)
          (delete-file josh/clock-current-task-file)))

;; (add-hook 'org-clock-in-hook 'josh/org-clock-in-conky)
;; (add-hook 'org-clock-out-hook 'josh/org-clock-out-conky)

Here’s another hook that works with my “ceftoolbar” in sawfish.

The ceftoolbar is a Chromium embedded framework toolbar that displays CPU usage, network usage, as well as my current task

;; (defun josh/org-clock-2 ()
;;   (interactive)
;;   "When I clock in or out, call a script that updates the ceftoolbar"
;;   (start-process "LogTime"
;;                  (get-buffer-create " *josh-clock-buffer*")
;;                  "~/.sawfish/scripts/clock-in.sh"))

;; (defun josh/org-clock-in-conky2 ()
;;   (josh/org-clock-in-conky)
;;   (josh/org-clock-2))

;; (defun josh/org-clock-out-conky2 ()
;;   (josh/org-clock-out-conky)
;;   (josh/org-clock-2))

;; (add-hook 'org-clock-in-hook 'josh/org-clock-in-conky2)
;; (add-hook 'org-clock-out-hook 'josh/org-clock-out-conky2)

Org-drill

Require org-drill. Add random noise to the due dates of cards, so they’re not always clumped together. Also, change the default cloze delimiters, as the defaults weren’t working well for me.

;; (add-to-list 'load-path "~/elisp/org-mode/contrib/lisp/")
(use-package org-drill
  :disabled 				;copy-list not defined for some reason?
  :after org
  :config (progn
	    (add-to-list 'org-modules 'org-drill)
	    (setq org-drill-add-random-noise-to-intervals-p t)
	    (setq org-drill-hint-separator "||")
	    (setq org-drill-left-cloze-delimiter "<[")
	    (setq org-drill-right-cloze-delimiter "]>")
	    (setq org-drill-learn-fraction 0.25)
	    (setq org-drill--lapse-very-overdue-entries-p t)))

org-preview-latex-fragment

The function “org-preview-latex-fragment” was deprecated a while back, but org-drill still depends on it. So here’s a quick hack that will display the LaTeX in org-drill.

(defun org-preview-latex-fragment ()
  (interactive)
  (org-remove-latex-fragment-image-overlays)
  (org-toggle-latex-fragment '(4)))

Chinese word definition library

Require the library that gets Chinese word definitions. I use this to make org-drill flashcards fairly quickly with a capture template.

(require 'josh-chinese)

Org-habit

(use-package org-habit
  :after org
  :defer t
  :config
  (add-to-list 'org-modules 'org-habit))

Exporting

Org-mode has a bunch of great tools for exporting into HTML, pdf, icalendar, and so forth.

Twitter bootstrap HTML

The base HTML can look a little plain. This package uses bootstrap to theme HTML exports.

(use-package ox-twbs
  :defer t)

For exporting latex

http://blog.karssen.org/2013/08/22/using-bibtex-from-org-mode/

(setq org-latex-pdf-process '("latexmk -g -f -pdf -bibtex %f"))

Exporting calendar files

Setting up some variables for .ics export.

(setq org-icalendar-include-todo nil ;; I don't currently have any applications that support VTODO
      org-icalendar-alarm-time 0 ;; Don't display alarms for now.
      org-icalendar-store-UID t ;; Save the same UID
      org-icalendar-timezone "America/Los_Angeles"
      org-icalendar-exclude-tags '("nyuical" "habit" "jmmical" "CANCELLED")
      org-icalendar-include-body 500
      org-icalendar-use-deadline '(event-if-todo-not-done todo-due)
      org-icalendar-use-scheduled '(event-if-todo-not-done todo-start)
      org-icalendar-combined-description "Josh's org-mode icalendar"
      org-icalendar-combined-name "JoshOrgMode"
      )

Instead of always using org-agenda, I like viewing my events and to-dos in a calendar format. Org-mode has a pretty decent icalendar exporter, but I find I frequently need to export updated .ics files.

To not block emacs, I’d like a function to export my calendar files asynchronously. And so we don’t constantly perform redundant exports, let’s only export org-mode agenda files that are newer than their .ics counterparts.

(defun jmm/org-should-export-new-ics ()
  "Should we export a new icalendar .ics file for the current buffer?
We do this if either
- The export file doesn't exist
- The export file is older than the current buffer file.

This function needs to be run in the context of the org file
we're considering exporting."
  (let ((file (buffer-file-name (buffer-base-buffer)))
	(export-file (org-export-output-file-name ".ics")))
    (or (not (file-exists-p export-file))
	(file-newer-than-file-p file export-file))))

(defun jmm/org-export-ical-stuff ()
  "Export icalendar stuff asynchronously. Only export newly modified files."
  (interactive)
  (let ((files (cl-remove-if-not #'file-exists-p (org-agenda-files t)))
	files-to-export)
    (dolist (file files files-to-export)
      (with-current-buffer (org-get-agenda-file-buffer file)
	(when (jmm/org-should-export-new-ics)
	  (push file files-to-export))))
    (setq the-files-to-export files-to-export)
    ;; TODO: Export all files, not just files that were changed?
    (if files-to-export
	(org-export-async-start
	    (lambda (results)
	      (message "Updated %d calendar files" (seq-length results))
	      (setq blah2 results)
	      (apply 'start-process "upload-ics-process" " *upload-ics-process*" "~/code/sh/upload-ical.sh" results)
	      (dolist (f results) (org-export-add-to-stack f 'icalendar)))
	  `(let (output-files)
	     (dolist (file ',files-to-export output-files)
	       (with-current-buffer (org-get-agenda-file-buffer file)
		 (push (expand-file-name (org-icalendar-export-to-ics))
		       output-files)))))
      (message "All icalendar files are already up to date"))))

Add our org-export-async-init-file, without which exporting doesn’t work so good :/

(setq org-export-async-init-file "~/.emacs.d/org-async-init.el")

Clocking

Easier method to clock into some frequent habits

Some habits occur quite frequently, and it’s kind of a pain to have to find them in my GTD org file before clocking in. This simplifies clocking into frequent tasks. (Mostly helps me track bad habits.)

(require 'helm-adaptive)
(defun josh/org-helm-candidates ()
  (interactive)
  (org-map-entries
   (lambda () (let* ((title (nth 4 (org-heading-components))))
                (cons title (cons title (current-buffer)))))
   nil
   'agenda))

(setq josh/helm-source-org-clock
  '((name . "Clock in to what")
    (candidates . josh/org-helm-candidates)
    (case-fold-search . t)
    (filtered-candidate-transformer
     helm-adaptive-sort)
    (action . (("Clock in"
                . josh/org-clock-in)))))

(defun josh/org-clock-in (candidate)
  "Clock into taskname in gtd-test"
  (interactive)
  (save-excursion
    (let* ((taskname (car candidate))
           (taskbuffer (cdr candidate))
           (place (org-find-exact-headline-in-buffer taskname taskbuffer)))
      (with-current-buffer (marker-buffer place)
        (goto-char place)
        (org-clock-in)))))

(defun josh/helm-org-clock-in ()
  "Use helm to clock into a task"
  (interactive)
  (helm-other-buffer 'josh/helm-source-org-clock
                     "*Helm Clock-in*"))

(defun josh/helm-org-jump-candidate (candidate)
  "Jump to a candidate with org"
  (interactive)
  (let* ((taskname (car candidate))
         (taskbuffer (cdr candidate))
         (place (org-find-exact-headline-in-buffer taskname taskbuffer)))
    (switch-to-buffer (marker-buffer place))
    (goto-char place)
    (org-show-context)))

(setq josh/helm-jump-org
  '((name . "Jump to org")
    (candidates . josh/org-helm-candidates)
    (case-fold-search . t)
    (filtered-candidate-transformer
     helm-adaptive-sort)
    (action . (("Jump to"
                . josh/helm-org-jump-candidate)))))

(defun josh/helm-org-jump ()
  "Use helm to clock into a task"
  (interactive)
  (helm-other-buffer 'josh/helm-jump-org
                     "*Org Jump*"))

These functions clock into a task if it exists and creates it using org-capture if it doesn’t.

(defun josh/org-clock-in2 (candidate)
  "Clock into taskname, creating it if it doesn't exist."
  (interactive)
  (if (stringp candidate)
      (progn
        (kill-new candidate)
        (org-capture nil "ji"))         ;Creates a task in datetree from kill ring
    (save-excursion
      (let* ((taskname (car candidate))
             (taskbuffer (cdr candidate))
             (place (org-find-exact-headline-in-buffer taskname taskbuffer)))
        (with-current-buffer (marker-buffer place)
          (goto-char place)
          (org-clock-in))))))

(defun josh/helm-org-clock-in2 ()
  "Use helm to clock into a task, creating it if it doesn't exist."
  (interactive)
  (josh/org-clock-in2 (helm-comp-read "Clock in to: " (josh/org-helm-candidates))))

Setting a timer on the current task

I use <f9> z to set the current task. When I want to set a timer, for instance in a pomodoro-type fashion, I’ll use this function which I have bound to <f9> p. It’s the same thing as org-timer-set-timer, but I don’t have to switch buffers to find the task I’m already clocked into.

(defun josh/org-current-task-timer (&optional opt)
  "Find the current clocking task and set a timer on it."
  (interactive "P")
  (when (org-clocking-p)
    (save-excursion
      (org-no-warnings (set-buffer (org-clocking-buffer)))
      (save-restriction
        (widen)
        (goto-char org-clock-marker)
        (beginning-of-line 1)
        (org-timer-set-timer opt)))))

(bind-key "<f9> p" 'josh/org-current-task-timer)

Quick key for clocking into current task

As well as clocking into previous tasks.

(bind-key "<f11>" 'org-clock-goto)
(bind-key "C-<f11>" 'org-clock-in-last)
(bind-key "M-<f11>" 'org-clock-out)

Inserting a link to the currently clocked task

When I’m capturing tasks or other org headlines, many times it’s related to the task I’m currently clocking.

I like to have contexts for why I captured certain items, so it’s nice to have a function that inserts a link to the currently clocked task.

(defun jmm/org-current-clock-link ()
  "Get the link of the currently clocked item."
  (save-window-excursion
    (let ((org-id-link-to-org-use-id t)	;Make a global ID
	  (clock (cons org-clock-marker
		       org-clock-start-time)))
    (unless (marker-buffer (car clock))
      (error "No clock is currently running"))
    (org-with-clock clock (org-clock-goto))
    (with-current-buffer (marker-buffer (car clock))
      (save-excursion
	(goto-char (car clock))
	(org-back-to-heading t)
	(org-store-link t))))))

(defun jmm/insert-org-current-clock-link ()
  "Insert a link of the currently clocked item"
  (interactive)
  (insert (jmm/org-current-clock-link)))

(bind-key "<S-f11>" 'jmm/insert-org-current-clock-link)

Navigating

Jump to an org project with helm

I like using helm-org-rifle for a lot of jumping stuff. But sometimes I want to jump to something that I know is a project, and I don’t want to see a bunch of extra headlines. These functions show org projects in helm and let me (relatively) quickly jump to them.

(defun jmm/skip-non-projects ()
  "Same as `bh/skip-non-projects', but doesn't skip stuck projects"
  (if (or (save-excursion (bh/skip-non-stuck-projects))
	  (save-excursion (bh/skip-stuck-projects)))
      (save-restriction
        (widen)
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
          (cond
           ((bh/is-project-p)
            nil)
           ((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
            nil)
           (t
            subtree-end))))
    (save-excursion (org-end-of-subtree t))))

(defvar jmm/org-projects-cache '())
(defun jmm/org-helm-project-candidates ()
  (interactive)
  (or jmm/org-projects-cache
      (setq jmm/org-projects-cache
	    (org-map-entries
	     (lambda ()
	       (cons (format "%s: %s"
			     (s-left 13 (s-pad-left 13 " " (buffer-name)))
			     (buffer-substring (line-beginning-position) (line-end-position)))
		     (point-marker)))
	     "/!"
	     'agenda
	     'jmm/skip-non-projects))))

(defun jmm/org-jump-to-project-marker (place)
  (switch-to-buffer (marker-buffer place))
  (goto-char place)
  (org-show-context)
  (org-show-subtree))

(setq jmm/helm-jump-org-project
    '((name . "Jump to Org project")
      (candidates . jmm/org-helm-project-candidates)
      (case-fold-search . t)
      (filtered-candidate-transformer
       helm-adaptive-sort)
      (action . (("Jump to"
                  . jmm/org-jump-to-project-marker)))))

(defun jmm/helm-org-jump-project (&optional arg)
  "Use helm to jump to a project. With optional ARG, clear the cache."
  (interactive "P")
  (when arg
    (setq jmm/org-projects-cache nil))
  (helm-other-buffer 'jmm/helm-jump-org-project
		     "*Org Jump*"))

Org-recent-headings

alphapapa’s org-recent-headings package makes it pretty easy to jump to recently used headings.

;; Might not be working with a newer version of helm. Or it might be
;; an issue with some function/variable I messed with elsewhere.
;; (use-package org-recent-headings
;;   :ensure t
;;   :config (org-recent-headings-mode)
;;   :bind (("<f9> h" . org-recent-headings-helm))
;;   :after org
;;   :demand t)

Refiling to other places

This is so we’re able to refile to other files. You also might be interested in my blog post ”Fast refiling in org-mode with hydras”.

(setq org-refile-targets (quote ((nil :maxlevel . 9)
                                 (org-agenda-files :maxlevel . 9)
                                 (("~/org/lookup.org") :maxlevel . 1))))

Just to make things a bit easier, I only want to refile to headlines with children. Also, cache this to make getting targets faster.

If I need to refile to a headline without a child, I’ll generally just cut the subtree, find the new parent with helm-org-rifle, and paste it there. Most easy/quick refiling is explained in my blog post though.

(defun jmm/org-is-valid-refile-target ()
  "Is this headline a valid refile target?
I normally don't refile to \"DONE\" headlines, or headlines without
  children.
Return t if it's a valid target, otherwise return nil."
  ;; Skip returns the opposite of what we want.
  ;; Is it not DONE?
  (and (not (org-agenda-skip-entry-if 'todo 'done))
      ;; And does it have a child?
      (save-excursion
	(save-match-data
	  (org-goto-first-child)))))

(setq org-refile-target-verify-function #'jmm/org-is-valid-refile-target)
(setq org-refile-use-cache t)

Refiling to specific IDs

Sometimes headlines move around between files. Having a UUID in the ID property of a headline allows you to keep track of it, even as the headline title changes and moves.

These functions allow you to refile to a target specified by its ID.

(defun jmm/org-refile-to-buffer-pos (buffer pos &optional arg)
  "Refile to a position given by a buffer and a point."
  (let* ((headline (with-current-buffer buffer
		     (org-with-wide-buffer
		      (goto-char pos)
		      (josh/plist-get (org-element-at-point) 'headline :title))))
	 (filepath (buffer-file-name buffer))
	 ;; TODO: Link or figure out the definition of rfloc
	 (rfloc (list headline filepath nil pos)))
    (if (and (eq major-mode 'org-agenda-mode) (not (and arg (listp arg)))) ;Don't use org-agenda-refile if we're just jumping
	(org-agenda-refile nil rfloc)
      (org-refile arg nil rfloc))))

(defun jmm/org-capture-refile-to-buffer-pos (buffer pos &optional arg)
  "Copied from `org-capture-refile' since it doesn't allow passing arguments. This does.

See also `jmm/org-refile-to-buffer-pos'"
  (unless (eq (org-capture-get :type 'local) 'entry)
    (error
     "Refiling from a capture buffer makes only sense for `entry'-type templates"))
  (let ((mypos (point))
	(base (buffer-base-buffer (current-buffer)))
	(org-capture-is-refiling t)
	(kill-buffer (org-capture-get :kill-buffer 'local)))
    (org-capture-put :kill-buffer nil)
    (org-capture-finalize)
    (save-window-excursion
      (with-current-buffer (or base (current-buffer))
	(org-with-wide-buffer
	 (goto-char mypos)
	 (jmm/org-refile-to-buffer-pos buffer pos arg))))
    (when kill-buffer (kill-buffer base))))


(defun jmm/org-refile-to-id (id &optional arg)
  "Refile to an org-mode headline with a specific ID.

With a `C-u` ARG, just jump to the headline."
  (interactive "P")
  (-let ((is-capturing (and (boundp 'org-capture-mode) org-capture-mode))
	 (is-jumping (and arg (listp arg)))
	 ((buffer . pos) (save-window-excursion
			   (save-excursion
			     (org-id-goto id)
			     (cons (current-buffer) (point))))))
    (cond
     (is-jumping
      (jmm/org-refile-to-buffer-pos buffer pos arg))
     (is-capturing
      (jmm/org-capture-refile-to-buffer-pos buffer pos arg))
     (t
      (jmm/org-refile-to-buffer-pos buffer pos arg)))))

Refiling to registers

A way quicker way of storing a few refile targets.

(defun jmm/org-refile-to-register (register &optional arg)
  "Refile the current tree to the heading at the point stored in REGISTER.

ARG gets passed to `jmm/org-refile-to-buffer-pos' which passes it to `org-refile'.
It's probably not necessary."
  (interactive (list (register-read-with-preview "Org refile to register: ")
		     current-prefix-arg))
  (-let (((buffer . pos) (save-window-excursion
			   (save-excursion
			     (jump-to-register register)
			     (org-back-to-heading)
			     (cons (current-buffer) (point))))))
    (jmm/org-refile-to-buffer-pos buffer pos arg)))

Better task states

From http://doc.norang.ca/org-mode.html

(setq org-todo-keywords
       (quote ((sequence "TODO(t)" "PROJECT(p)" "NEXT(n!)" "|" "DONE(d)")
               (sequence "WAITING(w!)" "HOLD(h!)" "MAYBE(m!)" "|" "CANCELLED(c/!)" "DEFERRED(f/!)" "NOGO(N!)"))))

(setq org-todo-keyword-faces
      (quote (("TODO" :foreground "red" :weight bold)
              ("NEXT" :foreground "blue" :weight bold)
              ("DONE" :foreground "forest green" :weight bold)
              ("WAITING" :foreground "orange" :weight bold)
              ("HOLD" :foreground "magenta" :weight bold)
	      ("MAYBE" :foreground "DarkGoldenrod1" :weight bold)
              ("CANCELLED" :foreground "forest green" :weight bold)
              ("DEFERRED" :foreground "tomato" :weight bold))))

(setq org-todo-state-tags-triggers
      (quote (("CANCELLED" ("CANCELLED" . t))
              ("WAITING" ("WAITING" . t))
	      ("PROJECT" ("project" . t))
              ("HOLD" ("WAITING") ("HOLD" . t))
              (done ("WAITING") ("HOLD"))
              ("TODO" ("WAITING") ("CANCELLED") ("HOLD"))
              ("NEXT" ("WAITING") ("CANCELLED") ("HOLD"))
              ("DONE" ("WAITING") ("CANCELLED") ("HOLD") ("IFBORED")))))

Babel

Org babel is a fantastic library for literate programming. Probably the best, really. There are so many language integrations.

Install some commonly used packages

Here I install some packages that provide code support for languages I commonly use with babel.

(use-package ob-ipython
  :ensure t
  :defer t
  :commands
  (org-babel-execute:ipython
   org-babel-expand-body:ipython))

Babel languages and settings

(org-babel-do-load-languages
 (quote org-babel-load-languages)
 (quote ((emacs-lisp . t)
	 (ditaa . t)
	 (R . t)
	 (python . t)
	 (ledger . t)
	 (org . t)
	 (latex . t)
	 (shell . t)
	 (dot . t)
	 (plantuml . t)
	 (sql . t)
	 (clojure . t))))

(setq org-edit-src-content-indentation 0
      org-src-tab-acts-natively t
      org-src-window-setup 'current-window)

Ditaa

(setq org-ditaa-jar-path "/usr/bin/ditaa")

Org Mobile Setup

In order to sync to MobileOrg, you need to set org-mobile-directory

(setq org-mobile-directory "~/org-mobile/")

Org agenda filtering functions

Here are a few org-agenda filtering functions for creating custom agendas. These do things like skip entries by tag, etc.

(defun zin/org-agenda-skip-tag (tag &optional others)
  "Skip all entries that correspond to TAG.

If OTHERS is true, skip all entries that do not correspond to TAG."
  (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
        (current-headline (or (and (org-at-heading-p)
                                   (point))
                              (save-excursion (org-back-to-heading)))))
    (if others
        (if (not (member tag (org-get-tags-at current-headline)))
            next-headline
          nil)
      (if (member tag (org-get-tags-at current-headline))
          next-headline
        nil))))

Bernt Hansen’s org functions

This page has a really great org mode setup. Here I steal a few of his functions for filtering agenda views.

(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))

(defun bh/is-project-subtree-p ()
  "Any task with a todo keyword that is in a project subtree.
Callers of this function already widen the buffer view."
  (let ((task (save-excursion (org-back-to-heading 'invisible-ok)
                              (point))))
    (save-excursion
      (bh/find-project-task)
      (if (equal (point) task)
          nil
        t))))

(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))

(defun bh/is-subproject-p ()
  "Any task which is a subtask of another project"
  (let ((is-subproject)
        (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
    (save-excursion
      (while (and (not is-subproject) (org-up-heading-safe))
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq is-subproject t))))
    (and is-a-task is-subproject)))

(defun bh/list-sublevels-for-projects-indented ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels 'indented)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defun bh/list-sublevels-for-projects ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels t)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defvar bh/hide-scheduled-and-waiting-next-tasks t)

(defun bh/toggle-next-task-display ()
  (interactive)
  (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks))
  (when  (equal major-mode 'org-agenda-mode)
    (org-agenda-redo))
  (message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show")))

(defun bh/skip-stuck-projects ()
  "Skip trees that are not stuck projects"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                nil
              next-headline)) ; a stuck project, has subtasks but no next task
        nil))))

(defun bh/skip-non-stuck-projects ()
  "Skip trees that are not stuck projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                next-headline
              nil)) ; a stuck project, has subtasks but no next task
        next-headline))))

(defun bh/skip-non-projects ()
  "Skip trees that are not projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (if (save-excursion (bh/skip-non-stuck-projects))
      (save-restriction
        (widen)
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
          (cond
           ((bh/is-project-p)
            nil)
           ((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
            nil)
           (t
            subtree-end))))
    (save-excursion (org-end-of-subtree t))))

(defun bh/skip-project-trees-and-habits ()
  "Skip trees that are projects"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-projects-and-habits-and-single-tasks ()
  "Skip trees that are projects, tasks that are habits, single non-project tasks"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((org-is-habit-p)
        next-headline)
       ((and bh/hide-scheduled-and-waiting-next-tasks
             (member "WAITING" (org-get-tags-at)))
        next-headline)
       ((bh/is-project-p)
        next-headline)
       ((and (bh/is-task-p) (not (bh/is-project-subtree-p)))
        next-headline)
       (t
        nil)))))

(defun bh/skip-project-tasks-maybe ()
  "Show tasks related to the current restriction.
When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks.
When not restricted, skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max))))
           (limit-to-project (marker-buffer org-agenda-restrict-begin)))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (not limit-to-project)
             (bh/is-project-subtree-p))
        subtree-end)
       ((and limit-to-project
             (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       (t
        nil)))))

(defun bh/skip-project-tasks ()
  "Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       ((bh/is-project-subtree-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-non-project-tasks ()
  "Show project tasks.
Skip project and sub-project tasks, habits, and loose non-project tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       ((not (bh/is-project-subtree-p))
        subtree-end)
       (t
        nil)))))

(defun bh/skip-projects-and-habits ()
  "Skip trees that are projects and tasks that are habits"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-non-subprojects ()
  "Skip trees that are not projects"
  (let ((next-headline (save-excursion (outline-next-heading))))
    (if (bh/is-subproject-p)
        nil
      next-headline)))

(defun bh/find-project-task ()
  "Move point to the parent (project) task if any"
  (save-restriction
    (widen)
    (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
      (while (org-up-heading-safe)
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq parent-task (point))))
      (goto-char parent-task)
      parent-task)))

(defun josh/skip-project-to-next-heading ()
  "Skip project tasks, but instead of going to the end of the
subtree, just go to the next headline"
  (save-restriction
    (widen)
    (let* ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((bh/is-project-p)
        next-headline)
       (t
        nil)))))

Find old closed entries

My org files seem to now be accumulating a bunch of “DONE” entries that have been closed a long time ago. These functions and agenda help me find these old entries so I can archive them

(defun josh/org-closed-days-old ()
  "Get how many days ago this entry was closed."
  (josh/org-timestamp-days-old
   (org-element-property :closed (org-element-at-point))))

(defun josh/org-timestamp-days-old (timestamp)
  (- (calendar-absolute-from-gregorian (calendar-current-date))
     (josh/org-timestamp-to-absolute-date timestamp)))

(defun josh/org-timestamp-to-absolute-date (timestamp)
  "Get an integer date from timestamp. Used for date differences"
  (calendar-absolute-from-gregorian
   (if timestamp
       (mapcar (lambda (x) (plist-get (cadr timestamp) x)) '(:month-start :day-start :year-start))
     (calendar-current-date))))

(defun josh/org-skip-old (age)
  "Skip all entries that were closed more than AGE days ago."
  (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
    (if (> (josh/org-closed-days-old) age)
        nil
      next-headline)))

Show effort and clocked time

If you modify org-agenda-prefix-format, you can get some extra details in your agenda view. Here’s how I view effort and clocked time.

(use-package org-clock
  :after org-agenda
  :defer t)

(defun josh/minutes-to-hhmm (min)
    (let* ((h (floor (/ min 60)))
           (m (- min (* 60 h))))
      (format "%01d:%02d" h m)))

(defun josh/org-show-effort-and-clocked (&optional noparens)
  "Show how much effort or clocked time there is.
  If no effort is set, show \"+\" clocked
  If there's no effort and no clocked time, show nothing
  If there's effort but no clocked time, show effort
  If there effort and clocked time, show \"-\" remaining effort
  If done, show clocked time.

  With optional parameter NOPARENS, don't include square brackets in output"
  (if (not (outline-on-heading-p t))
      ""
    (format (if noparens "%s" "[%s]")
            (let ((effort (org-get-at-eol 'effort-minutes 1))
                  (clocked (org-clock-sum-current-item (org-clock-get-sum-start))))
              (if (org-entry-is-todo-p)
                  (if effort
                      (if (> clocked 0)
                          (format "-% 3d" (- effort clocked))
                        (josh/minutes-to-hhmm effort))
                    (if (> clocked 0)
                        (format "+% 3d" clocked)
                      "    "))
                (format "+% 3d" clocked))))))

Helm Org Buffer

This command makes it easy to quickly switch to an org-mode buffer.

(require 'helm-types)
(require 'helm-buffers)
(defvar helm-org-buffers-list-cache nil)

(defclass helm-source-org-buffer (helm-source-sync helm-type-buffer)
  ((init :initform (lambda ()
                     (setq helm-org-buffers-list-cache
                           (mapcar (lambda (b)
                                     (with-current-buffer b (buffer-name)))
                                   (-filter (lambda (b)
                                              (with-current-buffer b
                                                (and (eq major-mode 'org-mode)
                                                     (buffer-name))))
                                            (buffer-list))))
                     (let ((result (cl-loop for b in helm-org-buffers-list-cache
                                            maximize (length b) into len-buf
                                            maximize (length (with-current-buffer b
                                                               (symbol-name major-mode)))
                                            into len-mode
                                            finally return (cons len-buf len-mode))))
                       (unless helm-buffer-max-length
                         (setq helm-buffer-max-length (car result)))
                       (unless helm-buffer-max-len-mode
                         (setq helm-buffer-max-len-mode (cdr result))))))
   (candidates :initform helm-org-buffers-list-cache)
   (matchplugin :initform nil)
   (match :initform 'helm-buffers-match-function)
   (persistent-action :initform 'helm-buffers-list-persistent-action)
   (keymap :initform helm-buffer-map)
   (volatile :initform t)
   (persistent-help
    :initform
    "Show this buffer / C-u \\[helm-execute-persistent-action]: Kill this buffer")))

(defvar helm-source-org-buffers-list (helm-make-source "Org-mode buffers" 'helm-source-org-buffer))

(defun helm-org-buffer ()
  (interactive)
  (helm :sources helm-source-org-buffers-list
        :buffer "*helm projectile*"
        :prompt "Switch to Org buffer:"))

;; (bind-key "C-c o" 'helm-org-buffer)
;; This is also a good key just for swooping
(bind-key "C-c O" 'helm-multi-swoop-org)

Show how old an entry is

I usually have “Added: [inactive timestamp]” added to most of my entries when captured with org-capture. Sometimes, I have unscheduled tasks around for a while, so these functions let me see how old they are. You could also use something like org-expiry for something this.

(Lately I’ve moved to using the “CREATED” property from org-expiry)

(defun josh/org-get-added-time ()
  "Get the time an entry was added"
  (or
   (org-entry-get (point) org-expiry-created-property-name)
   (save-excursion
     (org-back-to-heading t)
     (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
       (if (re-search-forward "Added: \\(\\[.*\\]\\)" subtree-end t)
	   (match-string 1))))))


(defun josh/org-format-age-from-added ()
  "Get age from the added date"
  (format "[%s|%s]"
          (let ((josh-added-time (josh/org-get-added-time)))
            (if josh-added-time
                (format "%3dd" (- (calendar-absolute-from-gregorian (calendar-current-date))
                                  (org-time-string-to-absolute josh-added-time)))
              "????"))
          (josh/org-show-effort-and-clocked t)))

Show how long I’ve been waiting for something

I have a section for “Waiting” tasks in my org agenda. I’d also like to see how long I’ve been waiting for them, to remind me if I should follow up.

(defun josh/org-get-waiting-time ()
  "Get the time we started waiting for a task"
  (save-excursion
    (org-back-to-heading t)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (if (re-search-forward "State \"WAITING\".*\\(\\[.*\\]\\)" subtree-end t)
          (match-string 1)))))

(defun josh/org-format-waiting-time ()
  "Get age from the added date"
  (format "[%s]"
          (let ((josh-waiting-time (josh/org-get-waiting-time)))
            (if josh-waiting-time
                (format "%3dd" (- (calendar-absolute-from-gregorian (calendar-current-date))
                                  (org-time-string-to-absolute josh-waiting-time)))
              "??"))))

Show how long a task has been in the “Next” state

How old is this task from when it was changed to a “next” task? Or when was it added? Take the more recent of the two.

(defun josh/org-get-next-time ()
  "Get the time we turned this task into a 'next' task"
  (save-excursion
    (org-back-to-heading t)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (if (re-search-forward "State \"NEXT\".*\\(\\[.*\\]\\)" subtree-end t)
          (match-string 1)))))

(defun josh/absolute-time-or-0 (x)
  (if x (org-time-string-to-absolute x) 0))

(defun josh/org-format-next-time ()
  "How long has an unscheduled 'next' task been waiting? Take the more recent of the added or changed-to-next date.
Also show amount of effort"
  (format "[%s|%s]"
          (let* ((josh-added-time (josh/org-get-added-time))
                 (josh-next-time  (josh/org-get-next-time))
                 (josh-waiting-time (when (or josh-added-time josh-next-time)
                                      (max (josh/absolute-time-or-0 josh-added-time)
                                           (josh/absolute-time-or-0 josh-next-time)))))
            (if josh-waiting-time
                (format "%3dd" (- (calendar-absolute-from-gregorian (calendar-current-date))
                                  josh-waiting-time))
              "??"))
	  (let ((effort (org-get-at-eol 'effort-minutes 1)))
            (if effort
                (josh/minutes-to-hhmm effort)
	      "    "))))

Show when the last time I’ve made progress on a project

I’ve got a list of stuck projects on my agenda. I’d like to know how long they’ve been stuck for. These functions show how many days it’s been since I’ve clocked into a task in the project.

(defun josh/org-get-end-clock-times ()
  "Get the last times we clocked out of a task. Return as a list."
  (save-excursion
    (org-back-to-heading t)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (matches nil))
      (while (re-search-forward "CLOCK: .*--\\(\\[.*\\]\\)" subtree-end t)
        (setq matches (cons (match-string-no-properties 1) matches)))
      matches)))

(defun josh/org-get-closed-times ()
  "Get the times we closed a task. Return as a list."
  (save-excursion
    (org-back-to-heading t)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (matches nil))
      (while (re-search-forward "CLOSED: \\(\\[.*\\]\\)" subtree-end t)
        (setq matches (cons (match-string-no-properties 1) matches)))
      matches)))

(defun josh/org-get-max-time (mytimes)
  "Get the largest day of a list of times.."
  (let ((times (mapcar #'org-time-string-to-absolute mytimes)))
    (when times
      (apply 'max times))))

(defun josh/org-format-max-clock-time ()
  "Format how many days ago we clocked out of a task. Used for projects."
  (format "[%s/%s]"
          (let ((josh-last-clock-time (josh/org-get-max-time (josh/org-get-end-clock-times))))
            (if josh-last-clock-time
                (format "%3dd" (- (calendar-absolute-from-gregorian (calendar-current-date))
                                  josh-last-clock-time))
              "  ??"))
          (let ((josh-last-closed-time (josh/org-get-max-time (josh/org-get-closed-times))))
            (if josh-last-closed-time
                (format "%3dd" (- (calendar-absolute-from-gregorian (calendar-current-date))
                                  josh-last-closed-time))
              "  ??"))))

Use speed keys

Speed keys make it really quick to do things like sorting headlines.

(setq org-use-speed-commands t)
(setq org-speed-commands-user
      '(("d" . jmm/org-edna-hydra/body)
	("m" . (progn (josh/org-toggle-marked-tag)
		      (org-speed-move-safe 'org-next-visible-heading)))))

Org expiry

Use org-expiry to make a “created” property.

(setq org-expiry-created-property-name "CREATED")
(setq org-expiry-inactive-timestamps t)
;; (use-package org
;;   :bind (:map org-mode-map
;; 	      ("M-N" . org-expiry-insert-created)))

Actually I’m gonna shadow that with a function that will either convert the “Added: ” field I usually have, or insert a timestamp

(defun josh/org-convert-added-to-created-property ()
  "Convert the \"Added: [timestamp]\" I've used in the past to using
  the CREATED property set by org-expiry.

  Return t if we found and deleted it."
  (interactive)
  (save-excursion
    (org-back-to-heading t)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (when (re-search-forward "Added: \\(\\[.*\\]\\)" subtree-end t)
	(org-entry-put (point) org-expiry-created-property-name (match-string 1))
	(delete-region (progn (forward-line 0) (point)) ;Delete the line
		       (progn (forward-line 1) (point)))
	t))))

(defun josh/org-convert-added-or-add-created ()
  "Convert the \"Added:\" style lines I have in capture
  templates, otherwise add the CREATED property (or whichever
  property is defined by `org-expiry-created-property-name') to
  the heading using `org-expiry-insert-created'"
  (interactive)
  (unless (josh/org-convert-added-to-created-property)
    (org-expiry-insert-created)))

Add CREATED property to captured headlines

I’d like to know when I captured headlines. Here I’ll add a hook to org-capture-mode that adds the CREATED property if we’re capturing an org-mode heading.

(defun jmm/org-capture-add-created-time ()
  "Add the CREATED property among when capturing a headline"
  (when (and (eq major-mode 'org-mode)
	     (eq (org-capture-get :type) 'entry))
    (josh/org-convert-added-or-add-created)))

(add-hook 'org-capture-mode-hook 'jmm/org-capture-add-created-time)

Other org tag stuff

“vague since” property

I use a “vague” tag to mark tasks that are too general, or don’t have a specific measurable outcome.

Ideally, I should go through my “vague” tasks and clean them up, either by adding specifics, or possibly just deleting the task

I’d like to know how long a task has been “vague” for, so this function adds a timestamp when something is marked “vague” or “breakdown”.

(defun jmm/org-add-tag-added-time-property ()
  "Run during `org-after-tags-change-hook'.
For example, if we have a \"vague\" tag and no \"VAGUE_SINCE\" property, add one.
If we don't have a \"vague\" tag but do have a \"VAGUE_SINCE\" property, delete it."
  (let* ((current-headline (or (and (org-at-heading-p)
                                   (point))
                               (save-excursion (org-back-to-heading))))
	 (current-tags (org-get-tags-at current-headline t)))
    (cl-loop for (tag property) in '(("vague" "VAGUE_SINCE") ("breakdown" "BREAKDOWN_SINCE") ("stuck" "STUCK_SINCE"))
	     do (if (member tag current-tags)
		    (unless (org-entry-get current-headline property)
		      (org-entry-put current-headline property (jmm/org-current-inactive-timestamp)))
		  (org-entry-delete current-headline property)))))

(defun jmm/org-current-inactive-timestamp ()
  "From `org-expiry-insert-created'"
  (let ((timestr (format-time-string (cdr org-time-stamp-formats))))
    (concat "[" (substring timestr 1 -1) "]")))

(add-hook 'org-after-tags-change-hook 'jmm/org-add-tag-added-time-property)

Marking headings with a tag

Sometimes I want to perform an action in bulk on a bunch of entries. For example, I might want to refile a bunch of headings or add a lot of tags for similar headings. This can be pretty tedious to do manually, especially since there’s a org-agenda-bulk-action command (usually bound to B in org-agenda) that helps us out.

In order to get a bunch of “marked” entries in an agenda, though, we want a quick way to add something like a “MARKED” tag. The code below does that.

(Note, I probably moved it above in the use-package for org)

(defun josh/org-toggle-marked-tag ()
  "Add a \"MARKED\" tag to a headline"
  (interactive)
  (org-toggle-tag "MARKED"))

Org-ref

I’m starting to learn to use org-ref.

(use-package org-ref
  :defer t
  :init
  (setq org-ref-bibtex-hydra-key-binding (kbd "C-c C-j"))
  (setq reftex-default-bibliography '("~/org-ref/references.bib"))
  (setq org-ref-show-citation-on-enter nil)
  ;; see org-ref for use of these variables
  (setq org-ref-bibliography-notes "~/org-ref/notes.org"
	org-ref-default-bibliography (file-expand-wildcards "~/org-ref/*.bib")
	org-ref-pdf-directory "~/org-ref/bibtex-pdfs/")
  (setq bibtex-completion-bibliography org-ref-default-bibliography
	bibtex-completion-library-path '("~/org-ref/bibtex-pdfs" "~/org-ref/zotfile")
	bibtex-completion-pdf-field "file"
	bibtex-completion-additional-search-fields '(keywords journal)
	bibtex-completion-notes-path "~/org-ref/helm-bibtex-notes")
  (setq bibtex-completion-display-formats
	'((t . "${author:36} ${title:*} ${year:4} ${journal:7} ${=has-pdf=:1}${=has-note=:1} ${keywords:12}")))
  (bind-key "C-c z" 'helm-bibtex)
  ;; Use helm-bibtex-notes file for notes
  (setq org-ref-notes-function
	(lambda (thekey)
	  (let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
	    (bibtex-completion-edit-notes
	     (list (car (org-ref-get-bibtex-key-and-file thekey))))))))

(use-package org-ref-bibtex
  :defer t
  :init
  (setq org-ref-bibtex-hydra-key-binding (kbd "C-c C-j")))

Associate most recent PDF with bibtex entry

Here’s a function for associating the most recently downloaded PDF with a bibtex entry

(defun josh/org-ref-bibtex-assoc-most-recent-pdf-with-entry (&optional prefix)
  "Associate the most recent PDF file in ~/Downloads with the current bibtex entry.
This is basically a copy of `org-ref-bibtex-assoc-pdf-with-entry'. Optional PREFIX argument
toggles between `rename-file' and `copy-file'"
  (interactive "P")
  (save-excursion
    (bibtex-beginning-of-entry)
    (let* ((file (josh/latest-file "~/Downloads" ".*\.[pP][dD][fF]$"))
	   (bibtex-expand-strings t)
           (entry (bibtex-parse-entry t))
           (key (reftex-get-bib-field "=key=" entry))
           (pdf (concat org-ref-pdf-directory (concat key ".pdf")))
	   (file-move-func (org-ref-bibtex-get-file-move-func prefix)))
      (if (file-exists-p pdf)
	  (message (format "A file named %s already exists" pdf))
	(progn
	  (funcall file-move-func file pdf)
	  (message (format "Created file %s from %s" pdf file)))))))

Find zotero files

I use Zotfile to move PDFs from a bunch of disparate folders in Zotero to one shared folder. Zotfile moves PDFs and stores them as links in entries. When Better BibTeX exports .bib files, these files show up as being a /zotfile/ folder. Helm-bibtex interprets this as an absolute path (which it kind of should), and isn’t able to find my PDF files. This tries to fix that.

I assume that files are stored in a “zotfile” directory.

Also, this advice removes HTML snapshots, since I generally don’t want to look at them anyway.

(defun jmm/helm-bibtex-replace-zotfile-with-relative-path (orig-fun &rest args)
  "Replace \"/zotfile/\" with \"zotfile\". Also remove any snapshots."
  (let ((res (apply orig-fun args)))
    (if (stringp res)
	(replace-regexp-in-string "\\(;[a-zA-Z0-9 ]*?Snapshot:.*?text/html\\|/zotfile/\\)" "" res)
      res)))

(advice-add 'bibtex-completion-get-value :around #'jmm/helm-bibtex-replace-zotfile-with-relative-path)

Org Edna

Org Edna looks like a cool package for managing dependencies and actions in org-mode. It basically builds off of org-depend.el, which was made as a proof-of-concept for dependencies. Org Edna adds more sophisticated ways of finding dependencies and triggering actions, but one of the coolest things is that it’s extensible. You can add your own functions for dependencies and actions.

(use-package org-edna
  :after org
  :ensure t
  :defer t
  :config
  (org-edna-load))

Use links instead of bare UUIDs

One of the great features of Org Edna compared to org-depend is the ability to use global dependencies. This means you can depend on headlines in different files. One way to depend on external headlines is to use the ”ids” keyword with a UUID. The problem is that UUIDs don’t give you an idea of what you’re depending on (or triggering).

This function gives you the finder link-ids, which works like ids but instead takes quoted org mode links to headlines (using the id: form). This lets you quickly add dependencies with org-store-link / org-insert-link, and allows you to see dependencies’ headlines.

(defun org-edna-finder/link-ids (&rest ids)
  "Find a list of headlines with given IDs.

Unlike `org-edna-finder/ids', IDS here can be links of the form \"[[id:UUID][Headline]]\" (in quotes).
This allows for easier readability of targets."
  (mapcar (lambda (id) (save-window-excursion
			 (org-open-link-from-string id)
			 (point-marker)))
	  ids))

Add and remove tags without obliterating already set tags

Org Edna’s tag! action sets tags but doesn’t keep the ones already set. This adds the ability to add multiple tags while keeping the tags you already have.

(defun org-edna-action/add-tags! (last-entry tags)
  "Add TAGS without deleting already set tags.
TAGS is a string with multiple tags separated by a colon."
  (ignore last-entry)
  (mapc (lambda (tag) (org-toggle-tag tag 'on))
	(if (stringp tags) (split-string tags ":" t) tags)))

(defun org-edna-action/remove-tags! (last-entry tags)
  "Remove TAGS without deleting already set tags.
TAGS is a string with multiple tags separated by a colon."
  (ignore last-entry)
  (mapc (lambda (tag) (org-toggle-tag tag 'off))
	(if (stringp tags) (split-string tags ":" t) tags)))

In a similar vein, this function works like chain! but doesn’t obliterate a property if it’s already set.

(defun org-edna-action/chain-maybe! (last-entry property)
  "Like `org-edna-action/chain!' but don't replace the property if it exists."
  (when-let* ((old-prop (org-entry-get last-entry property))
	      (current-entry-has-no-prop (not (org-entry-get nil property))))
    (org-entry-put nil property old-prop)))

A hydra for setting some common triggers

It’s a bit of a pain to set Org Edna triggers with org-set-property. I have a few common triggers I use that I’d like to be able to set quickly. They are:

  • Trigger the last stored link
  • Set the next sibling to “NEXT”
  • Mark the parent as “DONE”

Here I make a function for setting the TRIGGER property and moving the point to it, so we can easily change the trigger after setting it. Then, I make a hydra for setting some of the triggers I mentioned earlier

(defun jmm/org-edna-set-trigger-and-point (triggervalue)
  "Set the TRIGGER property to TRIGGERVALUE. Move the point to
the newly set value. Open the PROPERTIES drawer."
  (let ((property "TRIGGER"))
    (org-entry-put (point) property triggervalue)
    (org-back-to-heading t)
    (let* ((beg (point))
	   (range (org-get-property-block beg 'force))
	   (end (cdr range))
	   (case-fold-search t))
      (goto-char (1- (car range)))	;Need to go one character back to get property-drawer element
      (let ((element (org-element-at-point)))
	(when (eq (org-element-type element) 'property-drawer)
	  (org-flag-drawer nil element)))
      (goto-char (car range))
      (re-search-forward (org-re-property property nil t) end t))))

(defun jmm/org-edna-chain-next ()
  "Set TRIGGER to chain next"
  (interactive)
  (jmm/org-edna-set-trigger-and-point "next-sibling todo!(NEXT) chain!(\"TRIGGER\")"))

(defun jmm/org-pop-stored-link ()
  "Get the string for the previously stored link, then remove it from `org-stored-links'"
  (let* ((firstlink (car org-stored-links))
       (link (car firstlink))
       (desc (cadr firstlink)))
    (setq org-stored-links (delq (assoc link org-stored-links)
				   org-stored-links))
    (org-make-link-string link desc)))

(defun jmm/org-edna-link (&optional rest)
  "Set TRIGGER to chain next. With option"
  (interactive)
  (jmm/org-edna-set-trigger-and-point
   (format "link-ids(\"%s\")%s" (jmm/org-pop-stored-link) (if rest (concat " " rest) ""))))

(defhydra jmm/org-edna-hydra (:color blue)
  "Org Edna"
  ("l" jmm/org-edna-link "Link")
  ("L" (jmm/org-edna-link "todo!(NEXT)") "Link NEXT")
  ("n" (jmm/org-edna-set-trigger-and-point "next-sibling todo!(NEXT)") "Next sibling NEXT")
  ("N" (jmm/org-edna-set-trigger-and-point "next-sibling todo!(NEXT) chain!(\"TRIGGER\")") "Chain next-sibling NEXT")
  ("p" (jmm/org-edna-set-trigger-and-point "parent todo!(DONE)") "Parent DONE")
  ("q" nil "cancel"))

(bind-key "<f9> d" 'jmm/org-edna-hydra/body)

Infinitely clone subtrees with Org Edna

Often I have recurring tasks of the form “Do 30 minutes of X”. Rather than just have this as a single repeating item, I use subtree clones so I can shift the scheduled time of each individual instance. When I get to the end of the list of clones, I usually need to make more. These functions allow Org Edna to automatically make more clones and schedule them.

You’d set a TRIGGER property to something like self clone!(5 "+1d") rest-of-siblings todo!(TODO) refresh-created! next-n-siblings(4) delete-property!("TRIGGER"), which clones 5 subtrees. Yeah, it’s a bit complicated but it seems to work okay.

(defun org-edna-action/clone! (last-entry n &optional shift)
  "Clone an entry N times with optional SHIFT.
See `org-clone-subtree-with-time-shift'."
  (ignore last-entry)
  (org-clone-subtree-with-time-shift n shift))

(defun org-edna-action/refresh-created! (last-entry)
  (ignore last-entry)
  (org-expiry-insert-created t))

(defun org-edna-finder/next-n-siblings (n)
  (org-with-wide-buffer
   (let ((self (and (ignore-errors (org-back-to-heading t)) (point)))
         (markers)
	 (i 0))
     ;; Go from this heading forward n times
     (while (and (org-get-next-sibling)
		 (< i n))
       (unless (equal (point) self)
         (push (point-marker) markers))
       (setq i (1+ i)))
     (nreverse markers))))

org-ql

org-ql provides a great way to define searches that can be used to make an agenda view.

Not only are searches easier to define than their default agenda counterparts, they’re also way faster because org-ql caches searches for each buffer. Agenda views that take 7 seconds normally for me end up taking a fraction of a second using org-ql.

With org-ql I find I no longer have to write complex org-agenda-skip-function functions.

(use-package org-ql
  :after org
  :ensure t
  :bind (("C-c A !" . jmm/org-ql-basic-agenda-1)
	 ("C-c A 1" . jmm/org-ql-basic-agenda-1-all)
	 ("C-c A 2" . jmm/org-ql-basic-agenda-2)
	 ("C-c A 3" . jmm/org-ql-show-sparse-tree-unscheduled-tasks)
	 ("C-c A @" . jmm/org-ql-agenda-tomorrow)
	 ("C-c A e" . jmm/org-ql-erlich-agenda)
	 ("C-c A E 1" . jmm/org-ql-erlich-agenda)
	 ("C-c A E 2" . jmm/org-ql-erlich-agenda-tomorrow)
	 ("C-c A E w" . jmm/org-ql-erlich-agenda-week)
	 ("C-c A E p" . jmm/org-ql-erlich-projects)
	 ("C-c A n" . jmm/org-ql-next-todos-agenda)
	 ("C-c A o" . jmm/org-ql-old-completed-tasks-agenda)
	 ("C-c A p" . jmm/org-ql-projects)
	 ("C-c A r" . jmm/org-ql-recent-items)
	 ("C-c A u" . jmm/org-ql-next-unscheduled-tasks)
	 ("C-c A w" . jmm/org-ql-unscheduled-waiting-tasks))
  :defer t)

Some org-ql agenda views

Here are some org-ql views that I sometimes use.

(defvar jmm/org-ql-basic-super-groups
  '((:priority "A")
    (:priority "B")
    (:priority "C")
    (:priority "D" :order 7)
    (:name "Blocked tasks" :tag "blocked" :order 7)
    (:name "PhD Thesis" :tag "thesis")
    (:name "Personal" :tag "personal")
    (:name "Home" :tag "home")
    (:name "Habit" :tag "habit")
    (:name "Net stuff" :tag "@net")
    (:name "Quiz" :tag "quiz")
    (:name "Email" :tag "email")
    (:name "Erlich" :tag "erlich")
    (:name "Shopping" :tag "SHOPPING")
    (:todo "WAITING" :order 6)
    (:name "Other" :anything))
  "Super groups for some basic agendas I use.")

(defun jmm/org-ql-basic-agenda-1 ()
  "A basic non-work-related agenda for the day."
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (and (not (done))
	       (or (deadline auto)
		   (scheduled :to today)
		   (ts-active :on today)))
	  (not (tags "erlich"))
	  (not (tags "ARCHIVE")))
    :sort '(date priority todo)
    :title "Basic Agenda 1 (Today)"
    :super-groups jmm/org-ql-basic-super-groups))

(defun jmm/org-ql-basic-agenda-1-all ()
  "A basic agenda for the day, including work stuff."
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (and (not (done))
	       (or (deadline auto)
		   (scheduled :to today)
		   (ts-active :on today)))
	  (not (tags "ARCHIVE")))
    :sort 'jmm/org-ql--tag-priority>
    :title "Basic Agenda 1 (Today, all)"
    :super-groups jmm/org-ql-basic-super-groups))

(defun jmm/org-ql-basic-agenda-2 ()
  "A basic non-work-related agenda for *just* today."
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (and (not (done))
	       (or (deadline auto)
		   (scheduled :on today)
		   (ts-active :on today)))
	  (not (tags "erlich"))
	  (not (tags "ARCHIVE")))
    :sort '(date priority todo)
    :title "Basic Agenda 2 (Only today)"
    :super-groups jmm/org-ql-basic-super-groups))

(defun jmm/org-ql-basic-agenda-2-tomorrow ()
  "A basic non-work-related agenda for *just* tomorrow."
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (and (not (done))
	       (or (deadline auto)
		   (scheduled :on +1)
		   (ts-active :on +1)))
	  (not (tags "erlich"))
	  (not (tags "ARCHIVE")))
    :sort '(date priority todo)
    :title "Basic Agenda 2 (tomorrow)"
    :super-groups jmm/org-ql-basic-super-groups))

(defun jmm/org-ql-agenda-tomorrow ()
  "An agenda for *just* tomorrow."
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (not (done))
	 (or (deadline auto)
	     (scheduled :on +1)
	     (ts-active :on +1))
	 (not (tags "ARCHIVE")))
    :sort '(date priority todo)
    :title "Tomorrow Agenda"
    :super-groups jmm/org-ql-basic-super-groups))
(defvar jmm/org-super-groups-erlich
  '((:priority "A" :order 1)
    (:priority "B" :order 2)
    (:priority "C" :order 3)
    (:name "WAITING tasks" :todo "WAITING")
    (:name "Risk" :tag "risk")
    (:name "Infrastructure" :tag "infrastructure")
    (:name "Emails" :tag "email")
    (:name "Habits" :tag "habit"))
  "A super group layout for some Erlich agendas")

(defun jmm/org-ql-erlich-agenda ()
  "Make an agenda for things I need to do today for the Erlich lab"
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (tags "erlich")
	  (not (tags "ARCHIVE"))
	  (and (not (done))
	       (or (deadline auto)
		   (scheduled :to today))))
    :sort '(date priority todo)
    :title "Erlich Agenda View"
    :super-groups jmm/org-super-groups-erlich))


(defun jmm/org-ql-erlich-agenda-week ()
  "Erlich lab agenda for the week"
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (tags "erlich")
	  (not (tags "ARCHIVE"))
	  (and (not (done))
	       (or (deadline auto)
		   (scheduled :from today :to +7)
		   (ts-active :on today))))
    :sort '(date priority todo)
    :title "Erlich Agenda View"
    :super-groups jmm/org-super-groups-erlich))


(defun jmm/org-ql-erlich-agenda-tomorrow ()
  "Make an agenda for things I need to do tomorrow for the Erlich lab"
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (and (not (done))
	       (scheduled :on +1))
	  (tags "erlich")
	  (not (tags "ARCHIVE")))
    :sort '(date priority todo)
    :title "Erlich Agenda View (tomorrow)"
    :super-groups jmm/org-super-groups-erlich))

(defun jmm/org-ql-erlich-projects ()
  "Make a view of outstanding projects for the Erlich lab"
  (interactive)
  (jmm/org-ql-update-projects)
  (org-ql-search (org-agenda-files)
    '(and (tags "erlich")
	  (not (tags "ARCHIVE"))
	  (and (not (done))
	       (tags-local "project")))
    :sort '(date priority todo)
    :title "Erlich Project View"
    :super-groups '((:priority "A")
		    (:priority "B")
		    (:priority "D" :order 6)
		    (:name "HOLD" :tag "HOLD" :order 7)
		    (:name "Gitlab" :tag "gitlab")
		    (:name "Infrastructure" :tag "infrastructure" :order 3)
		    (:name "Emails" :tag "email")
		    (:name "Other" :anything))))
;; FIXME: Some items scheduled in the future show up.
(defun jmm/org-ql-recent-items ()
  "A view that shows items with a timestamp from a week ago to today."
  (interactive)
  (org-ql-search (org-agenda-files)
    '(ts :from -7 :to today)
    :title "Recent Items"
    :sort '(date priority todo)
    :super-groups '((:auto-ts t))))
(defun jmm/org-ql-next-todos-agenda ()
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (todo)
	  (ts :from today :to +7))
    :title "Next Items"
    :sort '(date priority todo)
    :super-groups '((:auto-ts t))))
(defun jmm/org-ql-old-completed-tasks-agenda ()
  (interactive)
  (org-ql-search (org-agenda-files)
  '(and (done)
	(level 2)
	(category "Tasks")
	(not (tags "reference"))
	(closed :to -14))
    :sort '(date)
    :title "Old completed tasks"))

Project-related views

Some of these views require the ancestors and parent predicates, which should be included in a future version of org-ql.

(defun jmm/org-ql-stuck-projects ()
  "Find stuck projects in an agenda view.
Taken from the org-ql examples.org, which was shared by reddit user \"emptymatrix\". "
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (tags-local "project")
	  (not (done))
	  (not (descendants (todo "NEXT")))
	  (not (descendants (scheduled))))
    :title "Stuck projects"
    :sort '(date priority)
    :super-groups '((:name "Erlich" :tag "erlich")
		    (:priority "A" :order 1)
		    (:priority "B" :order 2)
		    (:priority "C" :order 2))))

Mark stuck projects with a “stuck” tag. Unmark unstuck projects. Create a project view.

(defun jmm/org-ql-update-projects ()
  "Update stuck and unscheduled project tags."
  (org-ql-select (org-agenda-files)
    '(and (tags-local "project")
	  (not (tags-local "stuck"))
	  (not (done))
	  (not (descendants (todo "NEXT")))
	  (not (descendants (and (todo) (scheduled)))))
    :action '(org-toggle-tag "stuck" 'on))
  (org-ql-select (org-agenda-files)
    '(and (tags-local "stuck")
	  (tags-local "project")
	  (or
	   (done)
	   (descendants (todo "NEXT"))
	   (descendants (and (todo) (scheduled)))))
    :action '(org-toggle-tag "stuck" 'off))
  (org-ql-select (org-agenda-files)
    '(and (tags-local "project")
	  (not (tags-local "stuck"))
	  (not (done))
	  (not (descendants (and (todo) (scheduled)))))
    :action '(org-toggle-tag "unsched" 'on))
  (org-ql-select (org-agenda-files)
    '(and (tags-local "project")
	  (tags-local "unsched")
	  (or
	   (done)
	   (descendants (and (todo) (scheduled)))))
    :action '(org-toggle-tag "unsched" 'off)))

(defun jmm/org-ql-projects ()
  "Find stuck projects in an agenda view.
Taken from the org-ql examples.org, which was shared by reddit user \"emptymatrix\". "
  (interactive)
  (jmm/org-ql-update-projects)
  (org-ql-search (org-agenda-files)
    '(and (tags-local "project")
	  (not (done)))
    :title "Projects"
    :sort '(date priority)
    :super-groups '((:name "Held projects" :tag "HOLD" :order 8)
		    (:priority "A" :order 1)
		    (:priority "B" :order 2)
		    (:priority "C" :order 3)
		    (:name "Unscheduled projects" :tag "unsched" :order 6)
		    (:name "Stuck projects" :tag "stuck" :order 7)
		    (:name "Erlich" :tag "erlich" :order 5)
		    (:name "Other" :order 4 :anything))))
(defun jmm/org-ql-next-unscheduled-tasks ()
  "Find unscheduled \"next\" tasks that are part of stuck projects."
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (todo "NEXT")
	  (not (tags "HOLD"))
	  (not (scheduled))
	  (ancestors (and (tags-local "project")
			  (todo)
			  (not (descendants (and (todo)
						 (scheduled)))))))
    ;; TODO: This might need a bug fix
    :sort #'jmm/org-ql--created<
    :title "Next tasks that should be scheduled"))
(defun jmm/org-ql-unscheduled-waiting-tasks ()
  "Find unscheduled \"waiting\" tasks in projects"
  (interactive)
  (org-ql-search (org-agenda-files)
    '(and (todo "WAITING")
	  (not (scheduled))
	  (ancestors (tags-local "project")))
    :title "Unscheduled WAITING tasks in projects"))

Sparse tree views

Sometimes sparse trees make a better view than agendas, especially if you want to see the hierarchical layout of things.

(defun jmm/org-ql-show-sparse-tree-unscheduled-tasks ()
  "Show a sparse tree of unscheduled tasks (not part of a project)."
  (interactive)
  (org-ql-sparse-tree
   '(and (todo "TODO")
	 (not (scheduled))
	 (not (deadline))
	 (not (tags "ARCHIVE"))
	 (not (ancestors (tags-local "project"))))
   :buffer (get-buffer "gtd-test.org")))

Sorting

Sorting by time

Sometimes I want to see entries sorted from oldest to newest.

(defun jmm/org-get-created-ts (elem)
  "Get the timestamp of headline at point for created property."
  (or (-some-> (plist-get elem 'headline)
		(plist-get :CREATED)
		(org-time-string-to-seconds))
      (if org-sort-agenda-notime-is-late most-positive-fixnum -1)))

(defun jmm/org-ql--created< (a b)
  "Return non-nil if A's created property is less than B's."
  (let ((a-ts (jmm/org-get-created-ts a))
	 (b-ts (jmm/org-get-created-ts b)))
    (< a-ts b-ts)))

(defun jmm/org-ql--created> (a b)
  "Return non-nil if A's created property is greater than B's."
  (let ((a-ts (jmm/org-get-created-ts a))
	 (b-ts (jmm/org-get-created-ts b)))
    (> a-ts b-ts)))

Sorting by tags

Org-mode’s default “priority” feature is sometimes useful, but not always super granular or modular. A headline can only have one priority, even though you may want to sort on different features. Does priority mean importance or how urgent something is?

Sometimes I’d like to “bump” things higher in my org-ql agenda, without saying anything about its importance. For example, it’s useful for me to create a “queue” of tasks I’ll work on for the day.

Here I create some tags that add or subtract a “weight” to a headline. Tags with higher weights get bumped higher. Tags with lower weights get bumped lower.

I still need to add functions for better bumping from the agenda, as well as add the ability to compose/tiebreak different sorting mechanisms.

(defvar jmm/org-tagged-priorities
  '("p1" 4
    "p2" 3
    "p3" 2
    "p4" 1
    "n1" -1
    "n2" -2
    "n3" -3
    "n4" -4)
  "Some org-mode tags and associated priorities.
Higher priorities are bumped higher. Negative priorities are buried.
The normal priority is 0.")

(defun jmm/org-get-tagged-priority (elem)
  "Get the maximum priority number for an org-element.
See `jmm/org-tagged-priorities'"
  (or (-some-> (plist-get elem 'headline)
      (plist-get :tags)
      (->> (-map (lambda (prop) (lax-plist-get jmm/org-tagged-priorities prop))))
      -non-nil
      -min)
    0))

(defun jmm/org-ql--tag-priority> (a b)
  "Return non-nil if a A has a higher tagged-priority than B.

This is different than normal org-mode priorities (which are in
the properties drawer). Tag priorities are tags, so they don't
conflict with the normal priority property. This should allow you
to have multiple different types of \"priorities\" in the
future. (Maybe like \"importance\" or \"urgent\", etc.)

A and B are org elements, which you get with something like
`org-element-at-point'.
"
  (let ((a-priority (jmm/org-get-tagged-priority a))
	(b-priority (jmm/org-get-tagged-priority b)))
    (> a-priority b-priority)))

An example of searching through plain TODOs with Ivy

This is just a toy example of jumping to various “TODO” tasks, sorted by most-recently-created TODOs first.

It’s a bit slow. Maybe this is because it returns a lot of items and formatting is done separately each time.

(defun jmm/org-ql-goto-todo ()
  "Have ivy prompt for todo items. Jump to it."
  (interactive)
  (ivy-read "TODO:"
	    (->> (let ((org-sort-agenda-notime-is-late nil))
		   (org-ql-select (org-agenda-files)
		     '(and (todo)
			   (not (tags "ARCHIVE"))
			   (not (habit))
			   (not (tags-local "project"))
			   (not (todo "MAYBE"))
			   (not (ancestors (tags-local "project"))))
		     :sort #'jmm/org-ql--created>
		     :action 'element-with-markers))
		 (-map #'org-ql-view--format-element))
	    :action (lambda (x)
		      (let* ((marker (get-text-property 0 'org-hd-marker x))
			     (buffer (marker-buffer marker))
			     (pos (marker-position marker)))
			(xref-push-marker-stack)
			(pop-to-buffer-same-window buffer)
			(widen)
			(goto-char pos)
			(org-reveal)))))

Other useful functions

Org agenda current subtree or region

alphapapa has a good function for creating an agenda for just the current subtree or region

(defun ap/org-agenda-current-subtree-or-region (prefix)
  "Display an agenda view for the current subtree or region.
With prefix, display only TODO-keyword items."
  ;; BUG: This doesn't work properly in indirect or narrowed (both?)
  ;; buffers: it acts upon the whole buffer instead. It works in
  ;; direct buffers.
  (interactive "p")
  (let (header)
    (if (use-region-p)
	(progn
	  (setq header "Region")
	  (put 'org-agenda-files 'org-restrict (list (buffer-file-name (current-buffer))))
	  (setq org-agenda-restrict (current-buffer))
	  (move-marker org-agenda-restrict-begin (region-beginning))
	  (move-marker org-agenda-restrict-end
		       (save-excursion
			 ;; If point is at beginning of line, include heading on that line by moving point forward 1 char
			 (goto-char (1+ (region-end)))
			 (org-end-of-subtree))))
      (progn
	;; No region; restrict to subtree
	(setq header "Subtree")
	(org-agenda-set-restriction-lock 'subtree)))

    ;; Sorting doesn't seem to be working, but the header is
    (let ((org-agenda-sorting-strategy '(priority-down timestamp-up))
	  (org-agenda-sticky nil)		;Force regeneration
	  (org-agenda-overriding-header header))
      (org-search-view (if (>= prefix 4) t nil) "*"))
    (org-agenda-remove-restriction-lock t)
    (message nil)))

(bind-key "<f9> o r" 'ap/org-agenda-current-subtree-or-region)

Hide planning information

Most of my headings in org-mode have a PROPERTIES drawer. Many of them also have LOGBOOK drawers and scheduling information. Sometimes, when I want an overview of an org-mode file, all these planning, properties, and logbook lines visually clutter my screen. These functions are my first attempt at hiding them when using global cycling.

jmm/org-hide-visible-properties-drawer is my second attempt at hiding planning information. It only hides PROPERTIES and LOGBOOK and by default only hides visible drawers on the screen. This makes it much faster than jmm/org-cycle-hide-scheduled-properties-logbook which doesn’t check whether elements are visible.

(require 'cl-lib)
(defun jmm/org-end-planning-stuff ()
  "Return the point where planning, properties, and drawers end."
  (save-excursion
    (let (already-jumped-heading
	  (post-blank 0))
      (while (let* ((element (org-element-at-point))
		    (type (car element))
		    (jumpto (cl-case type
			      (headline (unless already-jumped-heading
					  (or (org-element-property :contents-begin element)
					      (org-element-property :end element))))
			      (planning (org-element-property :end element))
			      (property-drawer (org-element-property :end element))
			      (drawer (org-element-property :end element))
			      (t nil))))
	       (setq already-jumped-heading t)
	       (when jumpto
		 (goto-char jumpto)
		 (setq post-blank (org-element-property :post-blank element))
		 (< jumpto (point-max))))
	t)
      ;; The org-element-property :end includes blank lines
      ;; Go back over them.
      ;; TODO: Maybe look at org-cycle-separator-lines for hiding
      (when (< 0 post-blank)
	(forward-line (- post-blank)))
      (point))))

(defun jmm/org-goto-text-content ()
  "Jump to the content of a heading, skipping any planning,
properties or logbook drawers."
  (interactive)
  (goto-char (jmm/org-end-planning-stuff))
  (when (org-at-heading-p)
    (org-open-line 1)
    ;; Just hit tab
    (org-cycle))
  (back-to-indentation))

(defun jmm/org-hide-planning-stuff ()
  "When at a heading, hide planning info"
  (when (org-at-heading-p)
      (outline-flag-region (line-end-position) (- (jmm/org-end-planning-stuff) 1) t)))

(defun jmm/org-hide-visible-properties-drawer (&optional wholebuffer)
  "Hide visible \"properties\" and \"logbook\" information in the current window.
With optional argument WHOLEBUFFER, hide properties for the entire
buffer, not just the visible part in the window.

Unlike `jmm/org-hide-planning-stuff' this doesn't hide planning
stuff like scheduled and closed times.

This also assumes that a logbook drawer doesn't exist without a
properties drawer, which is usually the case for me.
"
  (interactive "P")
  (save-excursion
    (goto-char (if wholebuffer (point-min) (window-start)))
    (while (re-search-forward org-property-drawer-re
			      ;; We re-evaluate window-end every time because it can change
			      (if wholebuffer (point-max) (window-end (selected-window) t))
			      t)
      (if (get-char-property (1- (match-beginning 0)) 'invisible)
	  (while (and (not (eobp)) (invisible-p (point)))
	    (goto-char (next-property-change (point))))
	(let ((planstart (match-beginning 0))
	      (planend (jmm/org-end-planning-stuff)))
	  (outline-flag-region (1- planstart) (1- planend) t)
	  (goto-char planend))))))

(defun jmm/org-cycle-hide-scheduled-properties-logbook (state)
  "Re-hide all planning information and drawers after a visibility state change.

STATE should be one of the symbols listed in the docstring of
`org-cycle-hook'."
  (when (and (derived-mode-p 'org-mode)
	     (memq state '(all)))
    (org-map-tree 'jmm/org-hide-planning-stuff)))

(defun jmm/org-cycle-hide-properties-logbook (state)
  "Re-hide all drawers after a visibility state change.
STATE should be one of the symbols listed in the docstring of
`org-cycle-hook'."
  (when (and (derived-mode-p 'org-mode)
	     (memq state '(all)))
    (jmm/org-hide-visible-properties-drawer)))

;; Not sure if this works correctly
;; (add-hook 'org-cycle-hook 'jmm/org-cycle-hide-properties-logbook)

(defun jmm/org-hide-planning-parent-project ()
  "Hide planning info for project."
  (interactive)
  (save-excursion
    (when (or (jmm/org-goto-parent-project) (org-up-heading-safe))
      (org-map-tree 'jmm/org-hide-planning-stuff))))

Expand org-mode when using ediff

I use orgzly a lot these days, so I often need to sync org-mode changes between my phone and computer. For this I use ediff-directories.

Normally my org-mode files are started folded, but when ediffing I want to see everything. This adds that. (Based off this answer in Stack Overflow)

(defun jmm/ediff-expand-all ()
  "Show everything in ediff when opening org files."
  (when (eq major-mode 'org-mode)
    (outline-show-all)))

(add-hook 'ediff-prepare-buffer-hook #'jmm/ediff-expand-all)

Rescaling org-latex-preview fragments

org-latex-preview fragments unfortunately don’t scale with text-scale-mode. Probably the obvious reason for this is that text-scale-mode-amount doesn’t give a percent size increase (e.g. a text-scale-mode-amount from 0 to 1 isn’t doubling the size).

But, sometimes the previews are a bit small for me, so this changes the LaTeX scale depending on the font size. In the future I might add this to text-scale-mode-hook as describe here.

(defun jmm/org-latex-preview-fragment-size (orig-fun &rest args)
  "Advice to change the scale of `org-latex-preview' fragments based on `text-scale-mode' size."
  (plist-put org-format-latex-options :scale
	     (if text-scale-mode
		 (1+ text-scale-mode-amount)
	       1))
  (apply orig-fun args))

(advice-add 'org-latex-preview  :around #'jmm/org-latex-preview-fragment-size)

Fixing org-read-date’s burying of the *Calendar* buffer

When I’m scheduling org tasks in emails in notmuch, one annoying thing is that the *Calendar* buffer just keeps popping up, even though it’s supposed to be buried.

I think what’s happening is that org-read-date is trying to call bury-buffer in the wrong window.

This is my hack for fixing that.

Another approach would be to set switch-to-prev-buffer-skip to always skip the calendar.

(defun jmm/org-read-date-bury-calendar (orig-fun &rest args)
   "It seems like `org-read-date' doesn't properly `bury-buffer' the calendar.
This tries to fix that."
   ;; Can't call this, as it's now done in the wrong window.
   ;; (select-window (get-buffer-window "*Calendar*" 'visible))
   ;; I'm not even sure if it's an issue with org or with the way notmuch is burying its own buffers.
   ;; I think it is that `org-read-date' is trying to bury the buffer in the wrong window.
   ;; I think it's an issue with a second "bury-buffer" calling `switch-to-prev-buffer', which is not what I want.
   ;; From `switch-to-prev-buffer'
   (save-selected-window
     (when-let ((old-buffer (get-buffer "*Calendar*")))
       (dolist (window (window-list))
	 ;; (select-window window)
	 ;; (bury-buffer old-buffer)
	 ;; From `switch-to-prev-buffer'. Can't rebury the buffer when it's too late.
	 (set-window-prev-buffers window
				  (assq-delete-all old-buffer (window-prev-buffers window)))))))

(advice-add 'org-read-date :after #'jmm/org-read-date-bury-calendar)

Links

PDFView links

This adds ways to store and jump to links for PDF files viewed with PDF tools.

By default when using `C-c l` to store links, we’ll just save the file and page number. If the region is active in the PDF view, we’ll store that instead. If it’s a text region, store the text as a description. If it’s a rectangle region, don’t.

We can also store links to annotations (See jmm-emacs.org), but they’re not as stable right now. Especially if you delete annotations, it may affect other links.

(org-link-set-parameters "pdfview"
			 :follow #'jmm/org-pdfview-follow-link
			 :store #'jmm/org-pdfview-store-link)

(defun jmm/org-pdfview-store-link ()
  "Store a link to a pdfview page"
  (when (eq major-mode 'pdf-view-mode)
    (let* ((page (pdf-view-current-page))
	   (file (bookmark-buffer-file-name))
	   desc link)
      (if (pdf-view-active-region-p)
	  ;; Store the region
	  (let ((region (pdf-view-active-region))
		(regiontext (jmm/pdf-view-unfill-text))
		(isrect pdf-view--have-rectangle-region))
	    (org-store-link-props :type "pdfview" :page page :region region :file file :text regiontext)
	    (setq desc (if isrect
			   (format "%s rectangle on page %s" (s-chop-suffix ".pdf" (buffer-name)) page)
			 regiontext))
	    (setq link (format "pdfview:%s::%s" file (if isrect
							 (list :page page :region region :rect t)
							 (list :page page :region region)))))

	;; Store the page
	  (progn
	    (org-store-link-props :type "pdfview" :page page :file file)
	    (setq desc (format "%s page %s" (s-chop-suffix ".pdf" (buffer-name)) page))
	    (setq link (format "pdfview:%s::%s" file page))))
      (org-add-link-props :link link :description desc)
      link)))

(defun jmm/org-pdfview-follow-link (link)
  "Follow a pdfview link."
  (-let* (((file rawloc) (s-split "::" link))
	  (loc (car (read-from-string rawloc))))
    (find-file-other-window file)
    (unless (derived-mode-p 'pdf-view-mode)
      (pdf-view-mode))
    (cond
     ((numberp loc) (pdf-view-goto-page loc))
     ((plist-get loc :region) (progn (pdf-view-goto-page (plist-get loc :page))
				     (setq pdf-view-active-region (plist-get loc :region))
				     (pdf-view-display-region pdf-view-active-region (plist-get loc :rect))
				     (pdf-util-scroll-to-edges (pdf-util-scale-relative-to-pixel (car pdf-view-active-region)))))
     ((plist-get loc :annot) (pdf-annot-show-annotation (pdf-info-getannot (plist-get loc :annot)) t)))))

Opening links with other browsers

Web URLs with org-web-tools

org-web-tools is handy for getting the titles for links.

(use-package org-web-tools
  :bind (:map org-mode-map
	      ("C-x i" . org-web-tools-insert-link-for-url)))

Attachments

Attaching the last downloaded file

Easy function for attaching the last download to the current headline.

;; TODO: Require or autoload org-attach-attach
(defun jmm/org-attach-link-last-downloaded ()
  "Attach the last downloaded file by hardlinking."
  (interactive)
  (let ((org-attach-method 'ln))
    (org-attach-attach (josh/latest-file jmm/downloads-directory "[:alpha:]"))))