(defun org-cycle-hide-all-drawers ()
"Hide all drawers"
(interactive)
(org-cycle-hide-drawers 'all))
(defun org-cycle-hide-drawers (state)
"Re-hide all drawers after a visibility state change."
(when (and (derived-mode-p 'org-mode)
(not (memq state '(overview folded contents))))
(save-excursion
(let* ((globalp (memq state '(contents all)))
(beg (if globalp
(point-min)
(point)))
(end (if globalp
(point-max)
(if (eq state 'children)
(save-excursion
(outline-next-heading)
(point))
(org-end-of-subtree t)))))
(goto-char beg)
(while (re-search-forward org-drawer-regexp end t)
(save-excursion
(beginning-of-line 1)
(when (looking-at org-drawer-regexp)
(let* ((start (1- (match-beginning 0)))
(limit
(save-excursion
(outline-next-heading)
(point)))
(msg (format
(concat
"org-cycle-hide-drawers: "
"`:END:`"
" line missing at position %s")
(1+ start))))
(if (re-search-forward "^[ \t]*:END:" limit t)
(outline-flag-region start (point-at-eol) t)
(user-error msg))))))))))
(defun nm/task-is-active-proj ()
"Checks if task is a Project with child subtask"
(and (bh/is-project-p)
(nm/has-subtask-active-p)))
(defun nm/task-has-next-condition ()
"Checks task to see if it meets NEXT state critera and returns t."
(interactive)
(save-excursion
(and (bh/is-task-p)
(or (nm/checkbox-active-exist-p) (nm/is-scheduled-p) (nm/exist-context-tag-p))
(and (not (member "WAIT" (org-get-tags))) (not (equal (org-get-todo-state) "DONE"))))))
(defun nm/task-has-todo-condition ()
"Checks to see if task conditions meet TODO crtieria, and returns t if so."
(interactive)
(save-excursion
(and (bh/is-task-p)
(and (not (nm/checkbox-active-exist-p)) (not (nm/is-scheduled-p)) (not (nm/exist-context-tag-p)))
(and (not (member "WAIT" (org-get-tags))) (not (equal (org-get-todo-state) "DONE"))))))
(defun nm/task-has-done-condition ()
"Checks if task is considered DONE, and returns t."
(interactive)
(save-excursion
(and (bh/is-task-p)
(and (not (nm/checkbox-active-exist-p)) (not (nm/is-scheduled-p)) (not (nm/exist-context-tag-p)))
(nm/checkbox-done-exist-p))))
(defun nm/task-has-wait-condition ()
"Checks if task has conditions for WAIT state, retunrs t."
(interactive)
(and (bh/is-task-p)
(member "WAIT" (org-get-tags))
(not (equal (org-get-todo-state) "DONE"))
(not (member "SOMEDAY" (org-get-tags)))))
(defun nm/checkbox-active-exist-p ()
"Checks if a checkbox that's not marked DONE exist in the tree."
(interactive)
(save-excursion
(org-back-to-heading)
(let ((end (save-excursion (org-end-of-subtree t))))
(search-forward-regexp "^[-+] \\[\\W].+\\|^[1-9].\\W\\[\\W]" end t))))
(defun nm/checkbox-done-exist-p ()
"Checks if a checkbox that's not marked DONE exist in the tree."
(interactive)
(save-excursion
(org-back-to-heading)
(let ((end (save-excursion (org-end-of-subtree t))))
(search-forward-regexp "^[-+] \\[X].+\\|^[1-9].\\W\\[X]" end t))))
(defun nm/has-subtask-done-p ()
"Returns t for any heading that has a subtask is DONE state."
(interactive)
(org-back-to-heading t)
(let ((end (save-excursion (org-end-of-subtree t))))
(outline-end-of-heading)
(save-excursion
(re-search-forward (concat "^\*+ " "\\(DONE\\|KILL\\)") nil end))))
(defun nm/has-subtask-active-p ()
"Returns t for any heading that has subtasks."
(save-restriction
(widen)
(org-back-to-heading t)
(let ((end (save-excursion (org-end-of-subtree t))))
(outline-end-of-heading)
(save-excursion
(re-search-forward (concat "^\*+ " "\\(NEXT\\|WAIT\\|TODO\\)") end t)))))
(defun nm/exist-tag-p (arg)
"If headline has ARG tag keyword assigned, return t."
(interactive)
(let ((end (save-excursion (end-of-line))))
(save-excursion
(member arg (org-get-tags)))))
(defconst nm/context-tags ".+\s:@\\w.+:\\|.+:@\\w.+:")
(defun nm/exist-context-tag-p (&optional arg)
"If headline has context tag keyword assigned, return t."
(interactive)
(goto-char (org-entry-beginning-position))
(let ((end (save-excursion (line-end-position))))
(re-search-forward nm/context-tags end t)))
(defun nm/is-scheduled-p ()
"Checks task for SCHEDULE and if found, return t."
(save-excursion
(org-back-to-heading)
(let ((end (save-excursion (org-end-of-subtree t))))
(re-search-forward org-scheduled-regexp end t))))
(defun nm/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)
((oh/is-scheduled-p) subtree-end)
((org-is-habit-p) subtree-end)
((bh/is-project-subtree-p) subtree-end)
(t nil)))))
(defun nm/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)
((nm/is-scheduled-p) next-headline)
((bh/is-project-p) next-headline)
((and (bh/is-task-p) (not (bh/is-project-subtree-p))) next-headline)
(t nil)))))
(defun nm/skip-scheduled ()
"Skip headlines that are scheduled."
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(cond
((nm/is-scheduled-p) next-headline)
(t nil)))))
(add-hook! 'org-checkbox-statistics-hook #'nm/statistics-update-task)
(add-hook 'before-save-hook #'nm/update-task-conditions)
(defvar org-tasks-properties-metadata "SOURCE" "List of property values used to clarify task items.")
(defun nm/statistics-update-task ()
"Update task state when statistics checker runs"
(when (and (bh/is-task-p) (nm/checkbox-active-exist-p)) (org-todo "NEXT"))
(when (and (bh/is-task-p) (not (nm/checkbox-active-exist-p)) (nm/checkbox-done-exist-p)) (org-todo "DONE")))
; TODO Write interactive menu selection to ask user what value they want to update (tags, schedule, checkboxes).
(defun nm/update-task-tags ()
"Update all child tasks in buffer that are missing a TAG value."
(interactive)
(org-map-entries (lambda ()
(message (nm/org-get-headline-title))
(when (and (bh/is-task-p) (null (org-get-tags)))
(org-set-tags-command))) t 'file))
(defun nm/update-task-conditions ()
"Update task states depending on their conditions."
(interactive)
(org-map-entries (lambda ()
(when (nm/task-is-active-proj) (org-todo "ACTIVE"))) t))
We use Jethro’s function to process bulk agenda items…
- [ ] Write a new function to process bulk agenda items
(defun jethro/org-process-inbox ()
"Called in org-agenda-mode, processes all inbox items."
(interactive)
(org-agenda-bulk-mark-regexp "inbox:")
(jethro/bulk-process-entries))
(defvar jethro/org-current-effort "1:00"
"Current effort for agenda items.")
Set our effort to “…”
(defun jethro/my-org-agenda-set-effort (effort)
"Set the effort property for the current headline."
(interactive
(list (read-string (format "Effort [%s]: " jethro/org-current-effort) nil nil jethro/org-current-effort)))
(setq jethro/org-current-effort effort)
(org-agenda-check-no-diary)
(let* ((hdmarker (or (org-get-at-bol 'org-hd-marker)
(org-agenda-error)))
(buffer (marker-buffer hdmarker))
(pos (marker-position hdmarker))
(inhibit-read-only t)
newhead)
(org-with-remote-undo buffer
(with-current-buffer buffer
(widen)
(goto-char pos)
(org-show-context 'agenda)
(funcall-interactively 'org-set-effort nil jethro/org-current-effort)
(end-of-line 1)
(setq newhead (org-get-heading)))
(org-agenda-change-all-lines newhead hdmarker))))
Function to process a single item in our inbox
(defun jethro/org-agenda-process-inbox-item ()
"Process a single item in the org-agenda."
(org-with-wide-buffer
(org-agenda-set-tags)
(org-agenda-set-property)
(org-agenda-priority)
(call-interactively 'org-agenda-schedule)
(call-interactively 'jethro/my-org-agenda-set-effort)
(org-agenda-refile nil nil t)))
Bulk process entries
(defun jethro/bulk-process-entries ()
(if (not (null org-agenda-bulk-marked-entries))
(let ((entries (reverse org-agenda-bulk-marked-entries))
(processed 0)
(skipped 0))
(dolist (e entries)
(let ((pos (text-property-any (point-min) (point-max) 'org-hd-marker e)))
(if (not pos)
(progn (message "Skipping removed entry at %s" e)
(cl-incf skipped))
(goto-char pos)
(let (org-loop-over-headlines-in-active-region) (funcall 'jethro/org-agenda-process-inbox-item))
;; `post-command-hook' is not run yet. We make sure any
;; pending log note is processed.
(when (or (memq 'org-add-log-note (default-value 'post-command-hook))
(memq 'org-add-log-note post-command-hook))
(org-add-log-note))
(cl-incf processed))))
(org-agenda-redo)
(unless org-agenda-persistent-marks (org-agenda-bulk-unmark-all))
(message "Acted on %d entries%s%s"
processed
(if (= skipped 0)
""
(format ", skipped %d (disappeared before their turn)"
skipped))
(if (not org-agenda-persistent-marks) "" " (kept marked)")))))
Initiate capture from agenda
(defun jethro/org-inbox-capture ()
(interactive)
"Capture a task in agenda mode."
(org-capture nil "i"))
;;; ~/.doom.d/customs.el -*- lexical-binding: t; -*-
;(add-hook 'org-archive-hook (lambda ()
; "Set tags to archived headline"
; (counsel-org-tag)))
(defun zyro/move-line-after-meta-data ()
"Takes the point and moves it to the line after the Properties drawer"
(interactive)
(when (org-at-heading-p) (org-end-of-meta-data) (evil-insert 1) (beginning-of-line))
(when (looking-at "^[ ]*:LOGBOOK:[ ]*\n\\(?:.*\n\\)*?[ ]*:END:[ ]*$")
(re-search-forward "^[ \t]*:END:[ \t]*$" nil t) (end-of-line) (newline))
(when (looking-at-p "^[ ]*:\\(\\(?:\\w\\|[-_]\\)+\\):[ ]*$")
(re-search-forward "^[ \t]*:END:[ \t]*$" nil t) (end-of-line) (newline)))
(defun zyro/capture-daily-notes ()
"Capture template for ORG-ROAM daily notes."
(interactive)
(expand-file-name (format "%s.org" (format-time-string "%Y-%m-%d")) org-roam-directory))
(defun zyro/add-note ()
"Add note to headline"
(interactive)
(or
(org-next-visible-heading 1)
(goto-char (point-max)))
(forward-line -1)
(when (org-at-item-p) (forward-line -1))
(newline)
(insert (format "- %s" (read-string "Task name: "))))
(defun zyro/capture-file-name ()
"Generate filename at time of capture"
(setq zyro/capture-headline (read-string "Document Title: "))
(expand-file-name (concat (doom-project-root) "diary/" (format "(%s)%s.org" (format-time-string "%b-%d-%Y") zyro/capture-headline))))
;(defun zyro/capture-file-name ()
; "Generate filename at time of capture"
; (setq zyro/capture-headline (read-string "Document Title: "))
; (let ((dirname (read-directory-name "Pick directory: " (concat (doom-project-root) "notes/")))
; (zyro/capture-headline (read-string "Document Title: ")))
; (expand-file-name (concat dirname
; (format "(%s)%s.org" (format-time-string "%b-%d-%Y") zyro/capture-headline)))))
;(defun zyro/diary--capture-to-folder ()
; "Diary capture captured to destination folder"
; (interactive)
; (let ((dirname (directory-files (concat (doom-project-root) "notes/") nil "^\\w+$"))
; (dirfold (concat (doom-project-root) "notes/")))
; (defvar zyro/capture-headline (read-string "Document Title: "))
; (expand-file-name (concat dirfold (completing-read "Select directory: " dirname) "/" (format "(%s)%s.org" (format-time-string "%b-%d-%Y") zyro/capture-headline)))))
(defun zyro/select-task-type ()
"Select task file from a list defined by '+org-capture-task-files'"
(list (format "%stasks/%s" (doom-project-root) (ivy-completing-read "Select task file: " +org-capture-task-files))))
(defun zyro/capture--existing-file ()
"Test"
(interactive)
(let ((filecandid (read-file-name "Select directory: " (concat (doom-project-root) "tasks/"))))
(let ((org-agenda-files (list filecandid)))
(if (null (car org-agenda-files))
(error "%s is not visiting a file" (buffer-name (current-buffer)))
(counsel-org-agenda-headlines)
(org-next-visible-heading 1)
(next-line -1)))))
(defvar +org-capture-next-file "next.org")
(defvar +org-capture-refs-file "refs.org")
(defvar +org-capture-someday-file "someday.org")
(defvar +org-capture-task-files '("breakfix.org" "sustaining.org" "coding.org" "inquiries.org" "escalations.org" "defects.org"))
(defun zyro/create-new-task ()
"Add task in buffer"
(interactive)
(+org/insert-item-below 1)
(org-metaright)
(insert (format "TODO %s" (read-string "Task name: ")))
(newline)
(insert (format "[[%s][Link to case]]" (read-string "URL: "))))
(defun counsel-narrow ()
"Narrow with counsel"
(interactive)
(counsel-imenu)
(org-narrow-to-subtree))
(defun +org-move-next-headline-and-narrow ()
"Move to NEXT headline on same level and narrow"
(interactive)
(widen)
(outline-forward-same-level 1)
(org-narrow-to-subtree))
(defun +org-gtd-references ()
"GTD References file"
(interactive)
(find-file (read-file-name "Choose: " +org-gtd-refs-project)))
(defun helm-org-rifle-project-files ()
"Rifle projects files"
(interactive)
(helm-org-rifle-directories (doom-project-root)))
(defun my-agenda-prefix ()
(format "%s" (my-agenda-indent-string (org-current-level))))
(defun my-agenda-indent-string (level)
(if (= level 1)
""
(let ((str ""))
(while (> level 2)
(setq level (1- level)
str (concat str "─")))
(concat str "➤"))))
(defvar org-archive-directory "~/.org/archives/")
(defun org-archive-file ()
"Moves the current buffer to the archived folder"
(interactive)
(let ((old (or (buffer-file-name) (user-error "Not visiting a file")))
(dir (read-directory-name "Move to: " org-archive-directory)))
(write-file (expand-file-name (file-name-nondirectory old) dir) t)
(delete-file old)))
(provide 'org-archive-file)
(defun org-capture-headline-finder (&optional arg)
"Like `org-todo-list', but using only the current buffer's file."
(interactive "P")
(let ((org-agenda-files (list (buffer-file-name (current-buffer)))))
(if (null (car org-agenda-files))
(error "%s is not visiting a file" (buffer-name (current-buffer)))
(counsel-org-agenda-headlines)))
(goto-char (org-end-of-subtree)))
(defun +org-find-headline-narrow ()
"Find a headline and narrow to it"
(interactive)
(widen)
(let ((org-agenda-files (list (buffer-file-name (current-buffer)))))
(if (null (car org-agenda-files))
(error "%s is not visiting a file" (buffer-name (current-buffer)))
(counsel-org-agenda-headlines)))
(org-narrow-to-subtree))
;(defun org-capture-refile-hook ()
; "Refile before finalizing capture to project TOOD file"
; (interactive)
; (let ((org-agenda-files (list (+org-capture-project-todo-file))))
; (counsel-org-agenda-headlines)))
;(defun eos/org-custom-id-get (&optional pom create prefix)
; "Get the CUSTOM_ID property of the entry at point-or-marker POM.
; If POM is nil, refer to the entry at point. If the entry does
; not have an CUSTOM_ID, the function returns nil. However, when
; CREATE is non nil, create a CUSTOM_ID if none is present
; already. PREFIX will be passed through to `org-id-new'. In any
; case, the CUSTOM_ID of the entry is returned."
; (interactive)
; (org-with-point-at pom
; (let ((id (org-entry-get nil "CUSTOM_ID")))
; (cond
; ((and id (stringp id) (string-match "\\S-" id))
; id)
; (create
; (org-entry-put pom "CUSTOM_ID" (read-string (format "Set CUSTOM_ID for %s: " (substring-no-properties (org-format-outline-path (org-get-outline-path t nil))))
; (helm-org-rifle--make-default-custom-id (nth 4 (org-heading-components)))))
; (org-id-add-location id (buffer-file-name (buffer-base-buffer)))
; id)))))
;(defun eos/org-add-ids-to-headlines-in-file ()
; "Add CUSTOM_ID properties to all headlines in the
; current file which do not already have one."
; (interactive)
; (org-map-entries (lambda () (eos/org-custom-id-get (point) 'create))))
;(add-hook 'org-capture-before-finalize-hook
; (lambda () (eos/org-custom-id-get (point) '(create))))
(defun org-update-cookies-after-save()
(interactive)
(let ((current-prefix-arg '(4)))
(org-update-statistics-cookies "ALL")))
(add-hook 'org-mode-hook
(lambda ()
(add-hook 'before-save-hook 'org-update-cookies-after-save nil 'make-it-local)))
(provide 'org-update-cookies-after-save)
(setq-default truncate-lines t)
(defun jethro/truncate-lines-hook ()
(setq truncate-lines nil))
(add-hook 'text-mode-hook 'jethro/truncate-lines-hook)
;;; Hide all drawers
;(defun org-cycle-hide-all-drawers ()
; "Hide all drawers"
; (interactive)
; (org-cycle-hide-drawers 'all))
;(defun org-cycle-hide-drawers (state)
; "Re-hide all drawers after a visibility state change."
; (when (and (derived-mode-p 'org-mode)
; (not (memq state '(overview folded contents))))
; (save-excursion
; (let* ((globalp (memq state '(contents all)))
; (beg (if globalp
; (point-min)
; (point)))
; (end (if globalp
; (point-max)
; (if (eq state 'children)
; (save-excursion
; (outline-next-heading)
; (point))
; (org-end-of-subtree t)))))
; (goto-char beg)
; (while (re-search-forward org-drawer-regexp end t)
; (save-excursion
; (beginning-of-line 1)
; (when (looking-at org-drawer-regexp)
; (let* ((start (1- (match-beginning 0)))
; (limit
; (save-excursion
; (outline-next-heading)
; (point)))
; (msg (format
; (concat
; "org-cycle-hide-drawers: "
; "`:END:`"
; " line missing at position %s")
; (1+ start))))
; (if (re-search-forward "^[ \t]*:END:" limit t)
; (outline-flag-region start (point-at-eol) t)
; (user-error msg))))))))))
;------------ Show calendar busy state
(defface busy-1 '((t :foreground "black" :background "#eceff1")) "")
(defface busy-2 '((t :foreground "black" :background "#cfd8dc")) "")
(defface busy-3 '((t :foreground "black" :background "#b0bec5")) "")
(defface busy-4 '((t :foreground "black" :background "#90a4ae")) "")
(defface busy-5 '((t :foreground "white" :background "#78909c")) "")
(defface busy-6 '((t :foreground "white" :background "#607d8b")) "")
(defface busy-7 '((t :foreground "white" :background "#546e7a")) "")
(defface busy-8 '((t :foreground "white" :background "#455a64")) "")
(defface busy-9 '((t :foreground "white" :background "#37474f")) "")
(defface busy-10 '((t :foreground "white" :background "#263238")) "")
;(defadvice calendar-generate-month
; (after highlight-weekend-days (month year indent) activate)
; "Highlight weekend days"
; (dotimes (i 31)
; (let ((date (list month (1+ i) year))
; (count (length (org-agenda-get-day-entries
; (list (org-agenda-files)) (list month (1+ i) year)))))
; (cond ((= count 0) ())
; ((= count 1) (calendar-mark-visible-date date 'busy-1))
; ((= count 2) (calendar-mark-visible-date date 'busy-2))
; ((= count 3) (calendar-mark-visible-date date 'busy-3))
; ((= count 4) (calendar-mark-visible-date date 'busy-4))
; ((= count 5) (calendar-mark-visible-date date 'busy-5))
; ((= count 6) (calendar-mark-visible-date date 'busy-6))
; ((= count 7) (calendar-mark-visible-date date 'busy-7))
; ((= count 8) (calendar-mark-visible-date date 'busy-8))
; ((= count 9) (calendar-mark-visible-date date 'busy-9))
; (t (calendar-mark-visible-date date 'busy-10))))))
;; Set default column view headings: Task Effort Clock_Summary
(setq org-columns-default-format "%TODO %40ITEM(Task) %40SUMMARY(Summary)")
(defvar yant/last-note-taken ""
"Text of the last note taken.")
(define-advice org-store-log-note (:before (&rest args) yant/org-store-last-note)
"Store the last saved note into `yant/last-note-taken'."
(let ((txt (buffer-string)))
(while (string-match "\\`# .*\n[ \t\n]*" txt)
(setq txt (replace-match "" t t txt)))
(when (string-match "\\s-+\\'" txt)
(setq txt (replace-match " " t t txt)))
(when (string-match "\n" txt)
(setq txt (replace-match " " t t txt)))
(if (not (seq-empty-p txt))
(setq yant/last-note-taken txt))))
(defmacro org-with-point-at-org-buffer (&rest body)
"If in agenda, put the point into the corresponding org buffer."
`(cond ((eq major-mode 'org-agenda-mode)
(when-let ((marker (org-get-at-bol 'org-hd-marker)))
(org-with-point-at marker
,@body)))
((eq major-mode 'org-mode)
(org-with-wide-buffer
,@body))
(t (display-warning :warning "Trying to call org function in non-org buffer."))))
(define-advice org-store-log-note (:after (&rest args) yant/org-save-last-note-into-summary-prop)
"Save the last saved note into SUMMARY property."
(when (and (not org-note-abort) (not (seq-empty-p yant/last-note-taken)))
(if (eq major-mode 'org-agenda-mode)
(org-with-point-at-org-buffer
(org-set-property "SUMMARY" (or yant/last-note-taken "")))
(org-set-property "SUMMARY" (or yant/last-note-taken "")))
(setq yant/last-note-taken nil)))