(defgroup remind nil
"Calendar remind extensions."
:prefix "calendar-remind-"
:group 'calendar)
(defcustom calendar-remind-reminders-file-name
(expand-file-name "~/.reminders")
"Main reminders file name to use."
:type 'file
:group 'remind)
(defcustom calendar-remind-buffer-name
"*Remind*"
"Buffer name to use for remind output."
:type 'string
:group 'remind)
(defconst calendar-remind-print-buffer-name
"*calendar-remind-print-buffer*"
"Buffer name to use for temporary calendar remind print output.")
(defconst calendar-remind-text-property
"remind-source"
"Name of text property to store file name and line number.")
(defconst calendar-remind-blank-regexp
"^$"
"Regular expression that matches a blank line.")
(defconst calendar-remind-fileinfo-regexp
"^# fileinfo "
"Regular expression that matches a fileinfo line.")
(defun calendar-remind-date-regexp (date)
"Regular expression that matches a dated remind line."
(concat "^" date " [^ ]+ [^ ]+ [^ ]+ [^ ]+ "))
(defvar calendar-remind-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "\r" 'calendar-remind-visit-position)
(define-key map "v" 'calendar-remind-visit-position)
(define-key map "r" 'calendar-remind-lookup)
map))
(defun calendar-remind-mode ()
"Major mode for displaying calendar reminders.
\n\\{calendar-remind-mode-map}"
(kill-all-local-variables)
(setq major-mode 'calendar-remind-mode)
(setq mode-name "Reminders")
(use-local-map calendar-remind-mode-map)
(setq buffer-read-only t)
(run-hooks 'calendar-remind-mode-hook))
(defun calendar-remind-date (&optional day mon year)
"Return the given date in various formats.
If any date parameters are nil, the selected calendar date is
used to populate them.
Return an association list containing the following elements:
((:numeric (D M YYYY))
(:standard \"YYYY-MM-DD\")
(:internal \"YYYY/MM/DD\")
(:string (\"D\" \"Mon\" \"2000\"))
(:remind \"Mon D YYYY\"))
Example:
((:numeric (1 1 2000))
(:standard \"2000-01-01\")
(:internal \"2000/01/01\")
(:string (\"1\" \"Jan\" \"2000\"))
(:remind \"Jan 1 2000\"))"
(interactive)
(let ((month-map '((1 "Jan") (2 "Feb") (3 "Mar") (4 "Apr") (5 "May")
(6 "Jun") (7 "Jul") (8 "Aug") (9 "Sep") (10 "Oct")
(11 "Nov") (12 "Dec"))) dates)
(save-excursion
(save-current-buffer
(when (not (equal (buffer-name) calendar-buffer))
(when (not (bufferp (get-buffer calendar-buffer)))
(calendar))
(set-buffer (get-buffer calendar-buffer)))
(unless day
(setq day (extract-calendar-day (calendar-cursor-to-date))))
(unless mon
(setq mon (extract-calendar-month (calendar-cursor-to-date))))
(unless year
(setq year (extract-calendar-year (calendar-cursor-to-date))))))
(push (list :numeric (list day mon year)) dates)
(push (list :standard (format "%04d-%02d-%02d" year mon day)) dates)
(push (list :internal (format "%04d/%02d/%02d" year mon day)) dates)
(when (numberp day)
(setq day (number-to-string day)))
(when (numberp mon)
(setq mon (cadr (assq mon month-map))))
(when (numberp year)
(setq year (number-to-string year)))
(push (list :string (list day mon year)) dates)
(push (list :remind (format "%s %s %s" mon day year)) dates)
(nreverse dates)))
(defun calendar-remind-print (&optional day mon year)
"Execute the `remind' program and print the results into the current buffer.
\nUse function `calendar-remind-date' to determine the date for
any parameters not given."
(interactive "*")
(let (buffer reminders dates)
(setq dates (calendar-remind-date day mon year))
(setq day (first (cadr (assoc :string dates)))
mon (second (cadr (assoc :string dates)))
year (third (cadr (assoc :string dates))))
(when (not (point-at-bol))
(goto-char (point-at-eol))
(newline))
(insert (concat "Reminders for " (cadr (assoc :standard dates)) ":"))
(newline)
(newline)
(setq buffer (generate-new-buffer calendar-remind-print-buffer-name))
(save-current-buffer
(set-buffer buffer)
(call-process "remind" nil t nil "-s+1l"
(expand-file-name calendar-remind-reminders-file-name)
day mon year)
(setq reminders (buffer-string))
(kill-buffer buffer))
(setq reminders (split-string reminders "\n"))
(cl-loop while reminders
for fi = (split-string (pop reminders))
for rem = (pop reminders)
if (and (equal (cadr fi) "fileinfo")
(equal (substring rem 0 10) (cadr (assoc :internal dates))))
do (let ((beg (point)))
(insert rem)
(save-excursion
(goto-char beg)
(search-forward-regexp (calendar-remind-date-regexp (cadr (assoc :internal dates))))
(kill-region beg (point)))
(add-text-properties beg (point)
(list calendar-remind-text-property
(list (fourth fi) (third fi))))
(newline)))))
(defun calendar-remind-lookup (&optional day mon year)
"Execute the `remind' program and output the results.
\nUse function `calendar-remind-print' to output the remind information.
Use variable `calendar-remind-buffer-name' for the buffer name."
(interactive)
(let (dates cal)
(setq dates (calendar-remind-date day mon year))
(setq day (first (cadr (assoc :numeric dates)))
mon (second (cadr (assoc :numeric dates)))
year (third (cadr (assoc :numeric dates))))
(when (equal (buffer-name) calendar-buffer)
(setq cal t))
(get-buffer-create calendar-remind-buffer-name)
(set-buffer calendar-remind-buffer-name)
(setq buffer-read-only nil)
(erase-buffer)
(calendar-remind-print day mon year)
(setq buffer-read-only t)
(when cal
(other-window 1))
(switch-to-buffer calendar-remind-buffer-name)
(goto-char (point-min))
(calendar-remind-mode)
(forward-line 2)
(when cal
(other-window 1))))
(defun calendar-remind-visit (&optional file-name)
"Edit file FILE-NAME using function `find-file'.
\nFILE-NAME is the file to edit (defaults to variable
`calendar-remind-reminders-file-name')."
(interactive)
(when (equal (buffer-name) calendar-buffer)
(other-window 1))
(if file-name
(find-file file-name)
(find-file calendar-remind-reminders-file-name))
(goto-char (point-max)))
(defun calendar-remind-visit-insert (&optional file-name)
"Call function `calendar-remind-visit' then insert a new entry with the selected date."
(interactive)
(let ((dates (calendar-remind-date))) (calendar-remind-visit file-name)
(insert "REM " (cadr (assoc :remind dates)) " AT : DURATION : MSG ")
(newline)
(forward-line -1)))
(defun calendar-remind-visit-position ()
"Edit file stored in `remind-source' property at current position.
\nUse on an entry in `calendar-remind-buffer-name' buffer."
(interactive)
(when (equal (buffer-name) calendar-remind-buffer-name)
(let ((file-name (first (get-text-property (point) calendar-remind-text-property)))
(line (string-to-number (second (get-text-property (point) calendar-remind-text-property)))))
(when (and (stringp file-name)
(file-exists-p file-name)
(integerp line))
(find-file file-name)
(goto-char (point-min))
(forward-line (1- line))))))
(provide 'calendar-remind)