在 Emacs 随机切换主题
Emacs 里我目前最喜欢的主题是 modus-themes 和 ef-themes,它们都是出自 protesilaos。
后续可能还会加入 doric-themes,也是 Prot 的写的主题,这个主题的颜色比较单一,写作的时候用感觉不错。
modus-themes 相对来说更成熟,覆盖了大部分的包和使用场景; ef-themes 则提供了更多的色彩选择。
一个主题用久了就会觉得有些厌倦,想有一些新鲜感,就总是会找新主题,换新主题。
平时主要用 consult-theme (consult 提供的一个方法)去切换主题,前阵子看了 emacs-更改配色方案,受到文章的启发,我找 LLM 帮我写了一些用于定时切换主题的方法。
主题切换相关 Elisp
(defvar spike-leung/candidate-themes '(modus-operandi modus-operandi-tinted modus-vivendi modus-vivendi-tinted ef-arbutus ef-autumn ef-bio ef-cherie ef-cyprus ef-dark ef-deuteranopia-dark ef-deuteranopia-light ef-dream ef-duo-dark ef-duo-light ef-eagle ef-elea-dark ef-elea-light ef-frost ef-kassio ef-light ef-maris-dark ef-maris-light ;; ef-melissa-dark ef-melissa-light ef-night ef-owl ef-reverie ef-rosa ef-spring ef-summer ef-symbiosis ef-trio-dark ef-trio-light ef-tritanopia-dark ef-tritanopia-light ef-winter) "A list of themes to randomly cycle through.") ;;; theme related ;; @see: https://emacsredux.com/blog/2025/02/03/clean-unloading-of-emacs-themes/ (defun spike-leung/disable-all-active-themes () "Disable all currently active themes." (interactive) (dolist (theme custom-enabled-themes) (disable-theme theme))) (defun spike-leung/apply-random-theme () "Disable current themes and apply a random theme from `spike-leung/candidate-themes`." (interactive) (when (boundp 'spike-leung/candidate-themes) (spike-leung/disable-all-active-themes) (let ((theme (nth (random (length spike-leung/candidate-themes)) spike-leung/candidate-themes))) (condition-case err (progn ;; Load theme to ensure its specific settings/customizations are applied (load-theme theme :no-confirm) ;; Enable the theme (this actually applies it and adds to custom-enabled-themes) (enable-theme theme) (message "Applied random theme: %s" theme)) (error (message "Error applying theme %s: %s" theme err)))))) (run-with-timer (* 15 60) (* 15 60) 'spike-leung/apply-random-theme)
这段代码主要做了这几个事:
- 定义一个变量,存储所有我需要随机的主题
- 定义一个函数,随机从变量中获取一个主题然后应用
- 重置当前的主题设置
- 随机获取一个主题
- 加载获取的主题
- 设置一个定时器,每隔一定时间调用函数切换主题
用了一阵子,它很好地满足了我的新鲜感,如果你感兴趣,不妨试试~
へ(゜∇、°)へ へ(゜∇、°)へ
说起来奇怪,如果是我自己主动切换的主题,可能切换后我马上就想换了。
但是随机切换的主题,我反而有种顺其自然的心境,更愿意先用用看。
更新
现在主要使用 modus-themes ,通过 light 和 dark 两个函数切换明暗主题。
代码
(use-package modus-themes :ensure :init (defun spike-leung/modus-themes-load-based-on-time () "根据当前时间加载主题:08:00-18:00 使用浅色主题,其他时间使用深色主题。" (let ((hour (string-to-number (format-time-string "%H")))) (if (and (>= hour 8) (< hour 18)) (modus-themes-load-random 'light) (modus-themes-load-random 'dark)))) ;; 因为 desktop 会保存 face 相关内容,可能会和主题冲突,因此主题加载需要在恢复 desktop 之后 :hook (desktop-after-read . spike-leung/modus-themes-load-based-on-time) :config (setq modus-themes-italic-constructs t modus-themes-bold-constructs t modus-themes-disable-other-themes t) ;; remove fill-column-indicator background (modus-themes-with-colors (custom-set-faces `(fill-column-indicator ((,c :background unspecified :foreground ,bg-dim :height 1.0))))) (defun light() "Toggle light theme." (interactive) (modus-themes-load-random 'light)) (defun dark() "Toggle dark theme." (interactive) (modus-themes-load-random 'dark)))
更新
看到 Prot 一直在更新 doric-themes 和 ef-themes ,于是又心痒痒。
ef-themes 颜色最鲜艳, modus-themes 次之, doric-themes 最单调,周三前觉得周末很远,比较煎熬,所以周一到周三使用 ef-themes ,让色彩丰富明亮一些;周四周五快到周末了,心情渐佳,改用 modus-themes ;周末应该好好休息,就用单调一些的 doric-themes ,避免过分沉浸在 Emacs 中。白天用亮色主题,晚上用暗色主题。
代码
;; theme (use-package modus-themes :config (setq modus-themes-italic-constructs t modus-themes-bold-constructs t modus-themes-disable-other-themes t) ;; remove fill-column-indicator background (modus-themes-with-colors (custom-set-faces `(fill-column-indicator ((,c :background unspecified :foreground ,bg-dim :height 1.0)))))) (use-package ef-themes) (use-package doric-themes) (defun spike-leung/load-theme-by-time (light-fn dark-fn) "Call LIGHT-FN to load light themes from 8:00a.m to 6:00p.m. otherwise, call DARK-FN to load dark themes." (let ((hour (string-to-number (format-time-string "%H")))) (funcall (if (and (>= hour 8) (< hour 18)) light-fn dark-fn)))) (defun spike-leung/themes-load-random (&optional background-mode) "Random load themes. Use `ef-themes' for Monday,Tuesday,Wednesday. Use `modus-themes' on Thursday, Friday. Use `doric-themes' on Saturday, Sunday. Use light themes at day, use dark themes at night. With optional BACKGROUND-MODE as a prefix argument, prompt to limit the set of themes to either dark or light variants." (require 'modus-themes) (require 'ef-themes) (require 'doric-themes) (let ((week (string-to-number (format-time-string "%u")))) (cond ((<= week 3) (cond ((eq background-mode 'light) (ef-themes-load-random-light)) ((eq background-mode 'dark) (ef-themes-load-random-dark)) (t (spike-leung/load-theme-by-time #'ef-themes-load-random-light #'ef-themes-load-random-dark)))) ((<= week 5) (if background-mode (modus-themes-load-random background-mode) (spike-leung/load-theme-by-time (lambda () (modus-themes-load-random 'light)) (lambda () (modus-themes-load-random 'dark))))) (t (if background-mode (doric-themes-load-random background-mode) (spike-leung/load-theme-by-time (lambda () (doric-themes-load-random 'light)) (lambda () (doric-themes-load-random 'dark)))))))) (defun light() "Load random light themes." (interactive) (setq spike-leung/current-theme-background-mode 'light) (spike-leung/themes-load-random 'light) (modify-frame-parameters nil `((alpha . 100)))) (defun dark() "Load random dark themes." (interactive) (setq spike-leung/current-theme-background-mode 'dark) (spike-leung/themes-load-random 'dark) ;; 暗色主题适当降低透明度,看起来更好看。不同的主题适合不同的透明度。 (let ((theme (format "%s" custom-enabled-themes))) (cond ((string-match-p (rx (or "modus-" "ef-")) theme) (modify-frame-parameters nil `((alpha . 80)))) ((string-match-p "doric-" theme) (modify-frame-parameters nil `((alpha . 95))))))) ;; 要在 desktop 加载后再执行,避免被 desktop 记录的主题覆盖,导致混乱 (add-hook 'desktop-after-read-hook #'spike-leung/themes-load-random)
另外,我平时写博客会用到 Olivetti,ef-themes 和 doric-themes 没有设置 fringe 的颜色,导致 Olivetti 主体和 fringe 无法区分。所以我写了个函数,在切换主题之后,获取一个和 Olivetti 主体接近的颜色作为 olivetti-fringe 的颜色。
代码
;; olivetti 是一款打字机的名字,这个模式会模仿打字机的纸张大小,写作时用 (use-package olivetti :diminish :custom (olivetti-style 'fancy) (olivetti-body-width 100) (olivetti-margin-width 5) :hook (olivetti-mode-on . spike-leung/set-olivetti-fringe-face) :config (defun spike-leung/set-olivetti-fringe-face (&rest _) "Set `olivetti-fringe' to `fringe''s background, fallback to `org-block''s background." (let ((bg-default (face-attribute 'default :background nil)) (bg-fringe (face-attribute 'fringe :background nil)) (bg-fallback (face-attribute 'org-block :background nil))) (if (eq bg-default bg-fringe) (set-face-attribute 'olivetti-fringe nil :background bg-fallback) (set-face-attribute 'olivetti-fringe nil :background bg-fringe)))) (advice-remove 'spike-leung/themes-load-random #'spike-leung/set-olivetti-fringe-face) (advice-add 'spike-leung/themes-load-random :after #'spike-leung/set-olivetti-fringe-face))
其他参考
- Picking an emacs colour theme by gds
- guidoschmidt/circadian.el Theme-switching for Emacs based on daytime
- Setting Emacs Theme Based on Ambient Light by Matt Bilyeu
- Darkman for Emacs