diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
| commit | 092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch) | |
| tree | ea81999b8442246c978b364dd90e8c752af50db5 /modules/ui-theme.el | |
changing repositories
Diffstat (limited to 'modules/ui-theme.el')
| -rw-r--r-- | modules/ui-theme.el | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/modules/ui-theme.el b/modules/ui-theme.el new file mode 100644 index 00000000..d754b554 --- /dev/null +++ b/modules/ui-theme.el @@ -0,0 +1,135 @@ +;;; ui-theme.el --- UI Theme Configuration and Persistence -*- lexical-binding: t; coding: utf-8; -*- +;; author: Craig Jennings <c@cjennings.net> +;; +;;; Commentary: + +;; This module provides a theme management system with persistence across +;; Emacs sessions. +;; +;; - Theme selection via interactive interface (M-L) +;; - Automatic persistence of theme preference to a configurable file +;; - Safe theme loading with fallback mechanism +;; - Support for custom themes in the "themes" subdirectory +;; +;; The persistence mechanism saves theme choices to a file that can be +;; synchronized across machines, ensuring consistent appearance across +;; multiple computers. + +;;; Code: + +(require 'user-constants) ;; For sync-dir + +;; ----------------------------------- Themes ---------------------------------- +;; theme choices and settings + +;; downloaded custom themes go in themes subdirectory +(setq custom-safe-themes t) ;; trust all custom themes +(add-to-list 'custom-theme-load-path + (concat user-emacs-directory "themes")) + +;; ------------------------------- Switch Themes ------------------------------- +;; loads themes in completing read, then persists via the functions below + +(defun cj/switch-themes () + "Function to switch themes and save chosen theme name for persistence. + +Unloads any other applied themes before applying the chosen theme." + (interactive) + (let ((chosentheme "")) + (setq chosentheme + (completing-read "Load custom theme: " + (mapcar #'symbol-name + (custom-available-themes)))) + (mapc #'disable-theme custom-enabled-themes) + (load-theme (intern chosentheme) t)) + (cj/save-theme-to-file)) + +(global-set-key (kbd "M-L") 'cj/switch-themes) + +;; ----------------------------- Theme Persistence ----------------------------- +;; persistence utility functions used by switch themes. + +(defvar theme-file (concat sync-dir "emacs-theme.persist") + "The location of the file to persist the theme name. + +If you want your theme change to persist across instances, put this in a +directory that is sync'd across machines with this configuration.") + +(defvar fallback-theme-name "modus-vivendi" + "The name of the theme to fallback on. + +This is used then there's no file, or the theme name doesn't match +any of the installed themes. This should be a built-in theme. If theme name is +'nil', there will be no theme.") + +(defun cj/read-file-contents (filename) + "Read FILENAME and return its content as a string. + +If FILENAME isn't readable, return nil." + (when (file-readable-p filename) + (with-temp-buffer + (insert-file-contents filename) + (string-trim (buffer-string))))) + +(defun cj/write-file-contents (content filename) + "Write CONTENT to FILENAME. + +If FILENAME isn't writeable, return nil. If successful, return t." + (when (file-writable-p filename) + (condition-case err + (progn + (with-temp-buffer + (insert content) + (write-file filename)) + t) + (error + (message "Error writing to %s: %s" filename (error-message-string err)) + nil)))) + +(defun cj/get-active-theme-name () + "Return the name of the active UI theme as a string. + +Returns fallback-theme-name if no theme is active." + (if custom-enabled-themes + (symbol-name (car custom-enabled-themes)) + fallback-theme-name)) + +(defun cj/save-theme-to-file () + "Save the string representing the current theme to the theme-file." + (if (equal (cj/write-file-contents (cj/get-active-theme-name) theme-file) nil) + (message "Cannot save theme: %s is unwriteable" theme-file) + (message "%s theme saved to %s" (cj/get-active-theme-name) theme-file))) + +(defun cj/load-fallback-theme (msg) + "Display MSG and load ui-theme fallback-theme-name. + +Used to handle errors with loading persisted theme." + (message "%s Loading fallback theme %s" msg fallback-theme-name) + (load-theme (intern fallback-theme-name) t)) + +(defun cj/load-theme-from-file () + "Apply the theme name contained in theme-file as the active UI theme. + +If the theme is nil, it disables all current themes. If an error occurs +loading the file name, the fallback-theme-name is applied and saved." + (let ((theme-name (cj/read-file-contents theme-file))) + ;; if theme-name is nil, unload all themes and load fallback theme + (if (not theme-name) + (progn + (mapc #'disable-theme custom-enabled-themes) + (cj/load-fallback-theme "Theme file not found or empty.")) + ;; Check if theme is 'nil' string + (if (string= theme-name "nil") + (mapc #'disable-theme custom-enabled-themes) + ;; apply theme name or if error, load fallback theme + (condition-case err + (load-theme (intern theme-name) t) + (error + (cj/load-fallback-theme + (format "Error loading theme %s: %s." + theme-name (error-message-string err))))))))) + +(cj/load-theme-from-file) + +(provide 'ui-theme) +;;; ui-theme.el ends here |
