2022-05-19 14:39:52 +00:00
|
|
|
;;; -*- lexical-binding: t; -*-
|
2020-09-30 03:42:59 +00:00
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
(require 's)
|
|
|
|
(require 'dash)
|
|
|
|
(require 'alert)
|
|
|
|
(require 'org-agenda)
|
|
|
|
|
|
|
|
|
|
|
|
(defvar grfn/org-alert-interval 300
|
|
|
|
"Interval in seconds to recheck and display deadlines.")
|
|
|
|
|
|
|
|
|
|
|
|
(defvar grfn/org-alert-notification-title "*org*"
|
|
|
|
"Title to be sent with notify-send.")
|
|
|
|
|
|
|
|
(defvar grfn/org-alert-headline-regexp "\\(Sched.+:.+\\|Deadline:.+\\)"
|
|
|
|
"Regexp for headlines to search in agenda buffer.")
|
|
|
|
|
|
|
|
(defun grfn/org-alert--strip-prefix (headline)
|
|
|
|
"Remove the scheduled/deadline prefix from HEADLINE."
|
|
|
|
(replace-regexp-in-string ".*:\s+" "" headline))
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert--unique-headlines (regexp agenda)
|
|
|
|
"Return unique headlines from the results of REGEXP in AGENDA."
|
|
|
|
(let ((matches (-distinct (-flatten (s-match-strings-all regexp agenda)))))
|
|
|
|
(--map (grfn/org-alert--strip-prefix it) matches)))
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert--get-headlines ()
|
|
|
|
"Return the current org agenda as text only."
|
|
|
|
(with-temp-buffer
|
|
|
|
(let ((org-agenda-sticky nil)
|
|
|
|
(org-agenda-buffer-tmp-name (buffer-name)))
|
|
|
|
(ignore-errors (org-agenda-list nil "TODAY" 1))
|
|
|
|
(grfn/org-alert--unique-headlines
|
|
|
|
grfn/org-alert-headline-regexp
|
|
|
|
(buffer-substring-no-properties (point-min) (point-max))))))
|
|
|
|
|
|
|
|
(defun grfn/parse-range-string (str)
|
|
|
|
(when
|
|
|
|
(string-match (rx (group (repeat 2 (any digit))
|
|
|
|
":"
|
|
|
|
(repeat 2 (any digit)))
|
|
|
|
(optional
|
|
|
|
(and
|
|
|
|
"-"
|
|
|
|
(group (repeat 2 (any digit))
|
|
|
|
":"
|
|
|
|
(repeat 2 (any digit))))))
|
|
|
|
str)
|
|
|
|
(list
|
|
|
|
(org-read-date nil t
|
|
|
|
(match-string 1 str))
|
|
|
|
(when-let ((et (match-string 2 str))) (org-read-date nil t et)))))
|
|
|
|
|
|
|
|
(defun grfn/start-time-from-range-string (str)
|
|
|
|
(pcase-let ((`(,start-time . _) (grfn/parse-range-string str)))
|
|
|
|
start-time))
|
|
|
|
|
|
|
|
(comment
|
|
|
|
(org-agenda-list nil "TODAY" 1)
|
|
|
|
|
|
|
|
(grfn/org-alert--get-headlines)
|
|
|
|
(setq --src
|
|
|
|
(with-temp-buffer
|
|
|
|
(let ((org-agenda-sticky nil)
|
|
|
|
(org-agenda-buffer-tmp-name (buffer-name)))
|
|
|
|
(ignore-errors (org-agenda-list nil "TODAY" 1))
|
|
|
|
(buffer-substring-no-properties (point-min) (point-max)))))
|
|
|
|
|
|
|
|
(setq --entries
|
|
|
|
(with-temp-buffer
|
|
|
|
(let ((inhibit-redisplay t)
|
|
|
|
(org-agenda-sticky nil)
|
|
|
|
(org-agenda-buffer-tmp-name (buffer-name))
|
|
|
|
(org-agenda-buffer-name (buffer-name))
|
|
|
|
(org-agenda-buffer (current-buffer)))
|
|
|
|
(org-agenda-get-day-entries
|
|
|
|
(cadr (org-agenda-files nil 'ifmode))
|
|
|
|
(calendar-gregorian-from-absolute
|
|
|
|
(time-to-days (org-read-date nil t "TODAY")))))))
|
|
|
|
|
|
|
|
(loop for k in (text-properties-at 0 (car --entries))
|
|
|
|
by #'cddr
|
|
|
|
collect k)
|
|
|
|
|
|
|
|
(--map (substring-no-properties (get-text-property 0 'txt it)) --entries)
|
|
|
|
(--map (get-text-property 0 'time it) --entries)
|
|
|
|
(current-time)
|
|
|
|
|
|
|
|
(format-time-string "%R" (org-read-date nil t "10:00-11:00"))
|
|
|
|
|
|
|
|
(grfn/start-time-from-range-string "10:00")
|
|
|
|
|
|
|
|
(current-time-string (org-read-date nil t "10:00-11:00"))
|
|
|
|
|
|
|
|
(todo-state
|
|
|
|
org-habit-p
|
|
|
|
priority
|
|
|
|
warntime
|
|
|
|
ts-date
|
|
|
|
date
|
|
|
|
type
|
|
|
|
org-hd-marker
|
|
|
|
org-marker
|
|
|
|
face
|
|
|
|
undone-face
|
|
|
|
help-echo
|
|
|
|
mouse-face
|
|
|
|
done-face
|
|
|
|
org-complex-heading-regexp
|
|
|
|
org-todo-regexp
|
|
|
|
org-not-done-regexp
|
|
|
|
dotime
|
|
|
|
format
|
|
|
|
extra
|
|
|
|
time
|
|
|
|
level
|
|
|
|
txt
|
|
|
|
breadcrumbs
|
|
|
|
duration
|
|
|
|
time-of-day
|
|
|
|
org-lowest-priority
|
|
|
|
org-highest-priority
|
|
|
|
tags
|
|
|
|
org-category)
|
|
|
|
|
|
|
|
(propertize)
|
|
|
|
|
|
|
|
--src
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert--headline-complete? (headline)
|
|
|
|
"Return whether HEADLINE has been completed."
|
|
|
|
(--any? (s-starts-with? it headline) org-done-keywords-for-agenda))
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert--filter-active (deadlines)
|
|
|
|
"Remove any completed headings from the provided DEADLINES."
|
|
|
|
(-remove 'grfn/org-alert--headline-complete? deadlines))
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert--strip-states (deadlines)
|
|
|
|
"Remove the todo states from DEADLINES."
|
|
|
|
(--map (s-trim (s-chop-prefixes org-todo-keywords-for-agenda it)) deadlines))
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert-check ()
|
|
|
|
"Check for active, due deadlines and initiate notifications."
|
|
|
|
(interactive)
|
|
|
|
;; avoid interrupting current command.
|
|
|
|
(unless (minibufferp)
|
|
|
|
(save-window-excursion
|
|
|
|
(save-excursion
|
|
|
|
(save-restriction
|
|
|
|
(let ((active (grfn/org-alert--filter-active (grfn/org-alert--get-headlines))))
|
|
|
|
(dolist (dl (grfn/org-alert--strip-states active))
|
|
|
|
(alert dl :title grfn/org-alert-notification-title))))))
|
|
|
|
(when (get-buffer org-agenda-buffer-name)
|
|
|
|
(ignore-errors
|
|
|
|
(with-current-buffer org-agenda-buffer-name
|
|
|
|
(org-agenda-redo t))))))
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert-enable ()
|
|
|
|
"Enable the notification timer. Cancels existing timer if running."
|
|
|
|
(interactive)
|
|
|
|
(grfn/org-alert-disable)
|
|
|
|
(run-at-time 0 grfn/org-alert-interval 'grfn/org-alert-check))
|
|
|
|
|
|
|
|
|
|
|
|
(defun grfn/org-alert-disable ()
|
|
|
|
"Cancel the running notification timer."
|
|
|
|
(interactive)
|
|
|
|
(dolist (timer timer-list)
|
|
|
|
(if (eq (elt timer 5) 'grfn/org-alert-check)
|
|
|
|
(cancel-timer timer))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(provide 'grfn/org-alert)
|
|
|
|
;;; grfn/org-alert.el ends here
|