summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Cunningham <cc@localhost>2024-06-13 16:28:55 -0700
committerChristian Cunningham <cc@localhost>2024-06-13 16:28:55 -0700
commita9e5f06b03ffb9cc28ac2537ce98dc17bf08f66a (patch)
tree65f4c4e05f9631bdef746b50aaec8c01cf5b63c9
parent6780fd6b56e943e0b3476a35d8fd28cb5d511c0d (diff)
Agenda in Dashboard
Display the agenda in the dashboard: 1. Breakdown of tasks in their categories 2. Overdue Tasks 3. Upcoming Tasks
-rw-r--r--elchemy-dashboard.el136
-rw-r--r--elchemy-functions.el38
-rw-r--r--elchemy-package-org.el3
-rw-r--r--elchemy-user.el5
4 files changed, 180 insertions, 2 deletions
diff --git a/elchemy-dashboard.el b/elchemy-dashboard.el
index 5116807..522c546 100644
--- a/elchemy-dashboard.el
+++ b/elchemy-dashboard.el
@@ -34,6 +34,33 @@
(insert (buttonize name (lambda (y) (elchemy/find-file y)) path) "\n")))
(elchemy/read-alist-file (concat elchemy/elchemy-root elchemy/elchemy-projects-file)))
(insert "\n"))
+ (ignore-errors
+ (setq agenda-items (mapcar #'(lambda (x) (elchemy/get-agenda-items x 3)) elchemy/dashboard-agenda-titles)
+ agenda-max-count (apply 'max (mapcar #'(lambda (x) (length x)) agenda-items))
+ agenda-max-length (apply 'max (mapcar #'(lambda (x) (apply 'max (mapcar #'(lambda (y) (length y)) x))) agenda-items)))
+ (let ((start (point)))
+ (insert "Agenda")
+ (add-text-properties start (point)
+ '(face (:height 1.5))))
+ (insert "\n")
+ (let ((start (point)))
+ (insert (apply 'format (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s" "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s" "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s") elchemy/dashboard-agenda-titles))
+ (add-text-properties start (point)
+ '(face (:weight bold :slant italic :foreground "red"))))
+ (insert "\n")
+ (dotimes (i agenda-max-count)
+ (insert (format
+ (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s")
+ (elchemy/replace-nil (nth i (nth 0 agenda-items)))))
+ (insert (format
+ (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s")
+ (elchemy/replace-nil (nth i (nth 1 agenda-items)))))
+ (insert (format
+ (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s")
+ (elchemy/replace-nil (nth i (nth 2 agenda-items)))))
+ (insert "\n")
+ ))
+ (insert "\n")
(let ((start (point)))
(insert "Command Reference")
(add-text-properties start (point)
@@ -56,8 +83,115 @@
(insert "C-x C-u ~ Uppercase Region\n")
(insert "C-M-n ~ Move forward one balanced expression\n")
(insert "C-M-p ~ Move forward one balanced expression\n")
- (setq header-line-format nil)
+ (insert "\n")
+ (insert (buttonize "Refresh" '(lambda (x) (elchemy/recreate-dashboard))))
+ (insert "\n")
(button-mode +1)
(read-only-mode +1))))
+(defun elchemy/recreate-dashboard ()
+ "Create the user dashboard"
+ (interactive)
+ (let ((buffer (get-buffer-create "*Dashboard*")))
+ (switch-to-buffer buffer)
+ (read-only-mode -1)
+ (erase-buffer)
+ (when (file-exists-p (concat elchemy/elchemy-root elchemy/dashboard-splash))
+ (insert-image (create-image (concat elchemy/elchemy-root elchemy/dashboard-splash) nil nil :scale 0.25))
+ (insert "\n"))
+ (let ((start (point)))
+ (insert "Elchemy Dashboard")
+ (add-text-properties start (point)
+ '(face (:height 4.0))))
+ (insert "\n\n")
+ (dotimes (i (length elchemy/dashboard/heading-buttons))
+ (let* ((button (nth i elchemy/dashboard/heading-buttons))
+ (title (car button))
+ (callback (cdr button)))
+ (insert (buttonize title callback))
+ (if (eq (% (+ i 1) elchemy/dashboard/heading-columns) 0)
+ (insert "\n")
+ (insert (format (concat "%-" (format "%d" (+ (- elchemy/dashboard/heading-max-length (length title)) elchemy/dashboard/heading-padding)) "s") " ")))))
+ (unless (eq (% (length elchemy/dashboard/heading-buttons) elchemy/dashboard/heading-columns) 0)
+ (insert "\n"))
+ (insert "\n\n")
+ (when (file-exists-p (concat elchemy/elchemy-root elchemy/elchemy-projects-file))
+ (let ((start (point)))
+ (insert "Projects")
+ (add-text-properties start (point)
+ '(face (:height 1.5))))
+ (insert "\n")
+ (mapcar #'(lambda (x) (let ((name (car x))
+ (path (cdr x)))
+ (insert (buttonize name (lambda (y) (elchemy/find-file y)) path) "\n")))
+ (elchemy/read-alist-file (concat elchemy/elchemy-root elchemy/elchemy-projects-file)))
+ (insert "\n"))
+ (let ((start (point)))
+ (insert "Agenda")
+ (add-text-properties start (point)
+ '(face (:height 1.5))))
+ (insert "\n")
+ (setq agenda-items (mapcar #'(lambda (x) (elchemy/get-agenda-items x 3)) elchemy/dashboard-agenda-titles)
+ agenda-max-count (apply 'max (mapcar #'(lambda (x) (length x)) agenda-items))
+ agenda-max-length (apply 'max (mapcar #'(lambda (x) (apply 'max (mapcar #'(lambda (y) (length y)) x))) agenda-items)))
+ (let ((start (point)))
+ (insert (apply 'format (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s" "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s" "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s") elchemy/dashboard-agenda-titles))
+ (add-text-properties start (point)
+ '(face (:weight bold :slant italic :foreground "red"))))
+ (insert "\n")
+ (dotimes (i agenda-max-count)
+ (insert (format
+ (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s")
+ (elchemy/replace-nil (nth i (nth 0 agenda-items)))))
+ (insert (format
+ (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s")
+ (elchemy/replace-nil (nth i (nth 1 agenda-items)))))
+ (insert (format
+ (concat "%-" (format "%d" (+ agenda-max-length elchemy/dashboard-agenda-padding)) "s")
+ (elchemy/replace-nil (nth i (nth 2 agenda-items)))))
+ (insert "\n")
+ )
+ (insert "\n")
+ (let ((start (point)))
+ (insert "Overdue")
+ (add-text-properties start (point)
+ '(face (:height 1.5))))
+ (insert "\n")
+ (insert (elchemy/format-processed-agenda (mapcar 'elchemy/process-agenda-heading (org-ql-select (org-agenda-files) `(and (todo "STRT" "WAIT" "TODO") (ts :from ,(- elchemy/schedule-lookahead) :to today))))))
+ (insert "\n")
+ (let ((start (point)))
+ (insert "Upcoming")
+ (add-text-properties start (point)
+ '(face (:height 1.5))))
+ (insert "\n")
+ (insert (elchemy/format-processed-agenda (mapcar 'elchemy/process-agenda-heading (org-ql-select (org-agenda-files) `(and (todo "STRT" "WAIT" "TODO") (ts :from today :to ,elchemy/schedule-lookahead))))))
+ (insert "\n")
+ (let ((start (point)))
+ (insert "Command Reference")
+ (add-text-properties start (point)
+ '(face (:height 1.5))))
+ (insert "\n")
+ (insert "C-/ ~ Undo\n")
+ (insert "C-x n n ~ Narrow\n")
+ (insert "C-x n w ~ Widen\n")
+ (insert "M-% ~ Query Replace\n")
+ (insert "C-M-s ~ Regex Search\n")
+ (insert "F3 ~ Record Macro\n")
+ (insert "F4 ~ Play Macro\n")
+ (insert "M-0 F4 ~ Play Macro until failure\n")
+ (insert "M-l ~ Lowercase following word\n")
+ (insert "M-u ~ Uppercase following word\n")
+ (insert "M-c ~ Capitalize following word\n")
+ (insert "M-g w ~ Jump to word\n")
+ (insert "M-g l ~ Jump to line\n")
+ (insert "C-x C-l ~ Lowercase Region\n")
+ (insert "C-x C-u ~ Uppercase Region\n")
+ (insert "C-M-n ~ Move forward one balanced expression\n")
+ (insert "C-M-p ~ Move forward one balanced expression\n")
+ (insert "\n")
+ (insert (buttonize "Refresh" '(lambda (x) (elchemy/recreate-dashboard))))
+ (insert "\n")
+ (button-mode +1)
+ (read-only-mode +1)))
+
(provide 'elchemy-dashboard)
diff --git a/elchemy-functions.el b/elchemy-functions.el
index a5919d7..0c36ad8 100644
--- a/elchemy-functions.el
+++ b/elchemy-functions.el
@@ -56,4 +56,42 @@
(other-window 1)
(term TERM))
+(defun elchemy/replace-nil (STRING &optional REPLACEMENT)
+ "Replace a nil value with an empty string"
+ (unless REPLACEMENT
+ (setq REPLACEMENT " "))
+ (if STRING
+ STRING
+ REPLACEMENT))
+
+(defun elchemy/get-agenda-items (TODO LEVEL)
+ "Get Agenda Items"
+ (let* ((agenda-items-raw (org-ql-select
+ (org-agenda-files)
+ `(todo ,TODO)
+ :sort '(date todo priority)))
+ (agenda-items-plist (mapcar #'(lambda (x) (cl--plist-to-alist (car (cdr x)))) agenda-items-raw))
+ (agenda-items-filtered (seq-filter (lambda (x) (eq (cdr (assoc :level x)) LEVEL)) agenda-items-plist)))
+ (mapcar #'(lambda (x) (cdr (assoc :raw-value x))) agenda-items-filtered)))
+
+(defun elchemy/process-agenda-heading (heading)
+ "Process agenda headings
+
+Returns '((:raw-value . <Heading Text>) (:level <Org Heading Level>) (:priority . <Org Priority>) (:todo-keyword . <Org Todo Keyword>) (:scheduled . <Date Scheduled as Text>))"
+ (let ((heading-alist (cl--plist-to-alist (car (cdr heading)))))
+ `(,(assoc :raw-value heading-alist) ,(assoc :level heading-alist) ,(assoc :priority heading-alist) (:todo-keyword . ,(substring-no-properties (cdr (assoc :todo-keyword heading-alist)))) (:scheduled . ,(cdr (assoc :raw-value (cl--plist-to-alist (nth 2 (assoc :scheduled heading-alist)))))))))
+
+(defun elchemy/format-priority (priority)
+ "Format a priority to the letter or '-' if no priority"
+ (if priority
+ priority
+ 45))
+
+(defun elchemy/format-processed-agenda (agenda-list)
+ "Format a processed agenda to a string:
+[PRIORITY] TODO-KEYWORD: HEADING ~ SCHEDULED"
+ (apply #'concat (mapcar #'(lambda (x)
+ (concat (format "[%c] " (elchemy/format-priority (cdr (assoc :priority x)))) (cdr (assoc :todo-keyword x)) ": " (cdr (assoc :raw-value x)) " ~ " (cdr (assoc :scheduled x)) "\n"))
+ agenda-list)))
+
(provide 'elchemy-functions)
diff --git a/elchemy-package-org.el b/elchemy-package-org.el
index 1aa5941..0bc3949 100644
--- a/elchemy-package-org.el
+++ b/elchemy-package-org.el
@@ -11,6 +11,9 @@
(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-c c") 'org-capture))
+(use-package org-ql
+ :ensure t)
+
(defun elchemy/hide-org-mode-stars ()
"Hides the section stars"
(font-lock-add-keywords
diff --git a/elchemy-user.el b/elchemy-user.el
index fdccba8..a557804 100644
--- a/elchemy-user.el
+++ b/elchemy-user.el
@@ -38,7 +38,10 @@
elchemy/user/org-notes-file-name "notes.org"
elchemy/user/org-hideaway t
elchemy/elchemy-projects-file "projects"
- elchemy/dashboard-splash "assets/splash.png")
+ elchemy/dashboard-splash "assets/splash.png"
+ elchemy/dashboard-agenda-titles '("TODO" "STRT" "WAIT")
+ elchemy/dashboard-agenda-padding 4
+ elchemy/schedule-lookahead 7)
;; Modeline
(setq-default mode-line-format '((:propertize "♥︎" face (:foreground "red"))