zk / Calendar View

This should derive from Task View .

Dependencies

(require 'subr-x)
(require 'org-zk-repeat)

Configuration

(defvar org-zk-calendar-view-n-days 14)

Faces

(defface org-zk-calendar-view-today-face
  '((t . (:inherit font-lock-keyword-face)))
  "Face to highlight entries for the current day"
  :group 'org-zk-calendar-view)

TODO Entries

;; TODO: Implement as headline hook
(defun org-zk-calendar-view--time-entries ()
  (org-el-cache-mapcan-headlines
   (lambda (_cached-file headline)
     (let ((entries nil))
       (if-let ((deadline (plist-get headline :deadline)))
           (push (plist-put deadline :headline headline)
                 entries))
       (if-let ((scheduled (plist-get headline :scheduled)))
           (push (plist-put scheduled :headline headline)
                 entries))
       (setq entries
             (nconc entries
                    (mapcar (lambda (timestamp)
                              (plist-put timestamp :headline headline))
                            (plist-get headline :timestamps))))
       entries))))


(defun org-zk-calendar-view--repeated-time-entries (n-days)
  "Generate a list of all entries with a timestamp,
including repetitions of timestamps.
Returns a list of elements (headline ts type)."
  (mapcan
   (lambda (entry)
     (if (equal (plist-get (plist-get entry :headline) :style) "habit")
         (if-let ((next (org-zk-repeat-repetition-next entry)))
             (list (plist-put entry :repetition next)))
       (mapcar
        (lambda (repetition) (plist-put entry :repetition repetition))
        (org-zk-repeat-repetitions-next-n-days
         entry
         n-days))))
   (org-zk-calendar-view--time-entries)))

Tabulation

(setq org-zk-calendar-view-format
      (vector
       (list "Date" 20 t)
       (list "Type" 10 t)
       (list "File" 20 t)
       (list "Title" 20 t)))

(defun org-zk-calendar-view--ts-format (ts)
  (if ts
      (if (and (ts-hour ts) (ts-minute ts))
          (ts-format "%Y-%m-%d %H:%M" ts)
        (ts-format "%Y-%m-%d" ts))
    "----"))

(defun org-zk-calendar-view-tabulate (entries)
  (mapcar
   (lambda (entry)
     (let* ((headline (plist-get entry :headline))
            (file (plist-get headline :file)))
       (list
        entry
        (vector
         (org-zk-calendar-view--ts-format (plist-get entry :repetition))
         (symbol-name (plist-get entry :type))
         ;; TODO: Find title
         (or (org-el-cache-file-keyword file "TITLE")
             file)
         (plist-get headline :title)))))
   entries))

Commands

(defun org-zk-calendar-view-open ()
  (interactive)
  (let* ((headline (plist-get (tabulated-list-get-id) :headline)))
    (find-file (plist-get headline :file))
    (goto-char (plist-get headline :begin))))

Mode

(setq org-zk-calendar-view-mode-map
      (let ((map (make-sparse-keymap)))
        (set-keymap-parent map tabulated-list-mode-map)
        (define-key map (kbd "RET") 'org-zk-calendar-view-open)
        map))

(define-derived-mode org-zk-calendar-view-mode tabulated-list-mode "org-zk Calendar"
  "Major mode for listing org calendar entries"
  (hl-line-mode))

Printing Entries

(defun org-zk-calendar-view--today-p (ts)
  (let ((now (ts-now)))
    (and
     (eq (ts-year ts) (ts-year now))
     (eq (ts-month ts) (ts-month now))
     (eq (ts-day ts) (ts-day now)))))

(defun org-zk-calendar-view--face (entry)
  (let ((ts (plist-get entry :repetition)))
    (if (org-zk-calendar-view--today-p ts) 'bold 'default)))

;; TODO: Move this to the tabulate function
(defun org-zk-calendar-view-print-entry (id cols)
  "Insert a Tabulated List entry at point.
This is the default `tabulated-list-printer' function.  ID is a
Lisp object identifying the entry to print, and COLS is a vector
of column descriptors."
  (let ((beg   (point))
        (x     (max tabulated-list-padding 0))
        (ncols (length tabulated-list-format))
        (inhibit-read-only t))
    (if (> tabulated-list-padding 0)
        (insert (make-string x ?\s)))
    (let ((tabulated-list--near-rows ; Bind it if not bound yet (Bug#25506).
           (or (bound-and-true-p tabulated-list--near-rows)
               (list (or (tabulated-list-get-entry (point-at-bol 0))
                         cols)
                     cols))))
      (dotimes (n ncols)
        (setq x (tabulated-list-print-col n (aref cols n) x))))
    (insert ?\n)
    ;; Ever so slightly faster than calling `put-text-property' twice.
    (add-text-properties
     beg (point)
     `(tabulated-list-id ,id tabulated-list-entry ,cols))
    (put-text-property
     beg (point)
     'face
     (org-zk-calendar-view--face id))))

View

(defun org-zk-calendar-view-buffer ()
  (get-buffer-create "*org-zettelkasten Calendar*"))

(defun org-zk-calendar-view ()
  (interactive)
  (let ((entries (org-zk-calendar-view--repeated-time-entries org-zk-calendar-view-n-days)))
    (with-current-buffer (org-zk-calendar-view-buffer)
      (setq tabulated-list-format org-zk-calendar-view-format)
      (org-zk-calendar-view-mode)
      (tabulated-list-init-header)
      (setq tabulated-list-entries (org-zk-calendar-view-tabulate entries))
      (setq tabulated-list-sort-key (cons "Date" nil))
      (setq tabulated-list-printer #'org-zk-calendar-view-print-entry)
      (tabulated-list-print)
      (switch-to-buffer (current-buffer)))))

Footer

(provide 'org-zk-calendar-view)

If you have an idea how this page could be improved or a comment send me a mail.