branch: externals/doric-themes
commit b2a7eaa8b5c9807a4c4cb9b2c5258b779a004ff9
Author: Protesilaos Stavrou <i...@protesilaos.com>
Commit: Protesilaos Stavrou <i...@protesilaos.com>

    Add commands and relevant functions to load a theme
---
 doric-themes.el | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 183 insertions(+), 1 deletion(-)

diff --git a/doric-themes.el b/doric-themes.el
index e87eb11506..af79f44bf1 100644
--- a/doric-themes.el
+++ b/doric-themes.el
@@ -6,7 +6,7 @@
 ;; Maintainer: Protesilaos Stavrou <i...@protesilaos.com>
 ;; URL: https://github.com/protesilaos/doric-themes
 ;; Version: 0.0.0
-;; Package-Requires: ((emacs "28.1"))
+;; Package-Requires: ((emacs "29.1"))
 ;; Keywords: faces, theme, accessibility
 
 ;; This file is NOT part of GNU Emacs.
@@ -35,6 +35,188 @@
 
 ;;; Code:
 
+(require 'seq)
+(eval-when-compile (require 'subr-x))
+
+(defconst doric-themes-light-themes '(doric-light doric-earth doric-wind)
+  "Light themes.")
+
+(defconst doric-themes-dark-themes '(doric-dark doric-fire doric-water)
+  "Dark themes.")
+
+(defconst doric-themes-collection
+  (append doric-themes-light-themes doric-themes-dark-themes)
+  "Symbols of all the Doric themes.")
+
+(defgroup doric-themes ()
+  "Minimalist themes with few colours and precise typography."
+  :group 'faces
+  :prefix "doric-themes-"
+  :tag "Doric Themes")
+
+;;;; User options
+
+(defcustom doric-themes-to-toggle '(doric-light doric-dark)
+  "Specify two themes for the `doric-themes-toggle' command.
+The variable `doric-themes-collection' contains the symbols of all
+themes that form part of this collection."
+  :type `(choice
+          (const :tag "No toggle (default)" nil)
+          (list :tag "Pick two themes to toggle between"
+                (choice :tag "Theme one of two"
+                        ,@(mapcar (lambda (theme)
+                                    (list 'const theme))
+                                  doric-themes-collection))
+                (choice :tag "Theme two of two"
+                        ,@(mapcar (lambda (theme)
+                                    (list 'const theme))
+                                  doric-themes-collection))))
+  :package-version '(doric-themes . "0.1.0")
+  :group 'doric-themes)
+
+(defcustom doric-themes-to-rotate doric-themes-collection
+  "List of themes to rotate among when using the command 
`doric-themes-rotate'."
+  :type `(repeat (choice
+                  :tag "A theme among the `doric-themes-collection'"
+                  ,@(mapcar (lambda (theme) (list 'const theme)) 
doric-themes-collection)))
+  :package-version '(doric-themes . "0.1.0")
+  :group 'doric-themes)
+
+;;;; Commands and their helper functions
+
+(defun doric-themes--doric-p (theme)
+  "Return non-nil if THEME name has a doric- prefix."
+  (string-prefix-p "doric-" (symbol-name theme)))
+
+(defun doric-themes--list-enabled-themes ()
+  "Return list of `custom-enabled-themes' matching `doric-themes--doric-p'."
+  (seq-filter #'doric-themes--doric-p custom-enabled-themes))
+
+(defun doric-themes--enable-themes ()
+  "Enable the Doric themes."
+  (dolist (theme doric-themes-collection)
+    (unless (memq theme custom-known-themes)
+      (load-theme theme :no-confirm :no-enable))))
+
+(defun doric-themes--list-known-themes ()
+  "Return list of `custom-known-themes' matching `doric-themes--doric-p'."
+  (doric-themes--enable-themes)
+  (seq-filter #'doric-themes--doric-p custom-known-themes))
+
+(defun doric-themes--current-theme ()
+  "Return first enabled Doric theme."
+  (car (or (doric-themes--list-enabled-themes)
+           (doric-themes--list-known-themes))))
+
+(defun doric-themes--annotate-theme (theme)
+  "Return completion annotation for THEME."
+  (when-let* ((symbol (intern-soft theme))
+              (doc-string (get symbol 'theme-documentation)))
+    (format " -- %s" (propertize (car (split-string doc-string "\\.")) 'face 
'completions-annotations))))
+
+(defun doric-themes--completion-table (category candidates)
+  "Pass appropriate metadata CATEGORY to completion CANDIDATES."
+  (lambda (string pred action)
+    (if (eq action 'metadata)
+        `(metadata (category . ,category))
+      (complete-with-action action candidates string pred))))
+
+(defun doric-themes--completion-table-candidates ()
+  "Render `doric-themes--list-known-themes' as completion with theme category."
+  (doric-themes--completion-table 'theme (doric-themes--list-known-themes)))
+
+(defvar doric-themes-select-theme-history nil
+  "Minibuffer history of `doric-themes-select-prompt'.")
+
+(defun doric-themes-select-prompt (&optional prompt)
+  "Minibuffer prompt to select a Doric theme.
+With optional PROMPT string, use it.  Else use a generic prompt."
+  (let ((completion-extra-properties `(:annotation-function 
,#'doric-themes--annotate-theme)))
+    (intern
+     (completing-read
+      (or prompt "Select Doric theme: ")
+      (doric-themes--completion-table-candidates)
+      nil t nil 'doric-themes-select-theme-history))))
+
+(defun doric-themes-load-theme (theme)
+  "Load THEME while disabling other themes.
+Return THEME."
+  (mapc #'disable-theme custom-enabled-themes)
+  (load-theme theme :no-confirm)
+  theme)
+
+;;;###autoload
+(defun doric-themes-select (theme)
+  "Load a Doric THEME using minibuffer completion.
+Run `doric-themes-after-load-theme-hook' after loading the theme.
+Disable other themes per `doric-themes-disable-other-themes'."
+  (interactive (list (doric-themes-select-prompt)))
+  (doric-themes-load-theme theme))
+
+(defun doric-themes--toggle-theme-p ()
+  "Return non-nil if `doric-themes-to-toggle' are valid."
+  (condition-case nil
+      (dolist (theme doric-themes-to-toggle)
+        (or (memq theme doric-themes-collection)
+            (memq theme (doric-themes--list-known-themes))
+            (error "`%s' is not part of `doric-themes-collection'" theme)))
+    (error nil)
+    (:success doric-themes-to-toggle)))
+
+;;;###autoload
+(defun doric-themes-toggle ()
+  "Toggle between the two `doric-themes-to-toggle'.
+If `doric-themes-to-toggle' does not specify two Doric themes, inform
+the user about it while prompting with completion for a theme among our
+collection (this is practically the same as the `doric-themes-select'
+command)."
+  (interactive)
+  (if (doric-themes--toggle-theme-p)
+      (pcase-let ((`(,one ,two) doric-themes-to-toggle))
+        (if (eq (car custom-enabled-themes) one)
+            (doric-themes-load-theme two)
+          (doric-themes-load-theme one)))
+    (doric-themes-load-theme
+     (doric-themes-select-prompt
+      (concat "Set two `doric-themes-to-toggle'; "
+              "switching to theme selection for now: ")))))
+
+(defun doric-themes--rotate (themes)
+  "Rotate THEMES rightward such that the car is moved to the end."
+  (if (proper-list-p themes)
+      (let* ((index (seq-position themes (doric-themes--current-theme)))
+             (offset (1+ index)))
+        (append (nthcdr offset themes) (take offset themes)))
+    (error "The `%s' is not a list" themes)))
+
+(defun doric-themes--rotate-p (themes)
+  "Return a new theme among THEMES if it is possible to rotate to it."
+  (if-let* ((new-theme (car (doric-themes--rotate themes))))
+      (if (eq new-theme (doric-themes--current-theme))
+          (car (doric-themes--rotate-p (doric-themes--rotate themes)))
+        new-theme)
+    (error "Cannot determine a theme among `%s'" themes)))
+
+;;;###autoload
+(defun doric-themes-rotate (themes)
+  "Rotate to the next theme among THEMES.
+When called interactively THEMES is the value of `doric-themes-to-rotate'.
+
+If the current theme is already the next in line, then move to the one
+after.  Perform the rotation rightwards, such that the first element in
+the list becomes the last.  Do not modify THEMES in the process."
+  (interactive (list doric-themes-to-rotate))
+  (unless (proper-list-p themes)
+    "This is not a list of themes: `%s'" themes)
+  (let ((candidate (doric-themes--rotate-p themes)))
+    (if (doric-themes--doric-p candidate)
+        (progn
+          (message "Rotating to `%s'" (propertize (symbol-name candidate) 
'face 'success))
+          (doric-themes-load-theme candidate))
+      (user-error "`%s' is not part of the Doric collection" candidate))))
+
+;;;; Face customisations
+
 (defvar doric-themes-selection-faces
   '(calendar-today
     completions-highlight

Reply via email to