Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cycle through themes #78

Closed
oliverepper opened this issue May 4, 2023 · 9 comments
Closed

Cycle through themes #78

oliverepper opened this issue May 4, 2023 · 9 comments

Comments

@oliverepper
Copy link

oliverepper commented May 4, 2023

Hi @protesilaos,

first of all thanks a lot for these great themes. I am very new to Emacs and your themes help making the switch pleasurable! I enjoyed this video https://youtu.be/kPNMHrF4Lq8 and it even helped me understanding a bit of how things are handled in Emacs.

One thing occurred to me, though. I think that the API modus-themes-toggle is giving up chances:

  • It could return the name of the theme that it did set for an easy logging opportunity
  • I think toggle should be implemented as a special case of cycle (I would like to be able to cycle through more than 2 themes)

Since I am even newer to Lisp than I am to Emacs I have not yet prepared a PR but I tried my best in Lisp so you know what I mean:

(defun oe/rotate-list (list)
  (unless (not list)
    (append (cdr list) (list (car list)))))

(defun oe/cycle-themes (theme-list)
  (load-theme (car theme-list) :no-confirm)
  (funcall (function oe/rotate-list) theme-list))

(set 'all-themes '(
		   modus-operandi
		   modus-vivendi
		   modus-operandi-deuteranopia
		   modus-vivendi-deuteranopia
		   modus-operandi-tinted
		   modus-vivendi-tinted
		   ))

(set 'themes-to-toggle '(
			 modus-operandi
			 modus-vivendi
			 ))

(defun oe/cycle-themes-and-print-log-msg ()
  (set 'all-themes (funcall (function oe/cycle-themes) all-themes))
  (message "Theme: %s selected" (car (reverse all-themes))))

(defun oe/toggle-themes-and-print-log-msg ()
  (set 'themes-to-toggle (funcall (function oe/cycle-themes) themes-to-toggle))
  (message "Theme: %s selected" (cadr themes-to-toggle)))

(oe/cycle-themes-and-print-log-msg)
(oe/toggle-themes-and-print-log-msg)

Please let me know if you like the idea. I might give it a shot but this is literally the first Lisp code I ever wrote.

@protesilaos
Copy link
Owner

this is literally the first Lisp code I ever wrote.

Wow, that is good!

The idea is promising! Though let's not interfere with the toggle for
the time being.

I made some minor tweaks to your code:

(defun oe/rotate-list (list)
  (when list
    (append (cdr list) (list (car list)))))

(defun oe/cycle-themes (theme-list)
  (load-theme (car theme-list) :no-confirm)
  (oe/rotate-list theme-list))

(defvar modus-themes--cycle-themes modus-themes-items)

(defun oe/cycle-themes-and-print-log-msg ()
  (setq modus-themes--cycle-themes (oe/cycle-themes modus-themes--cycle-themes))
  (message "Theme: %s selected" (car (reverse modus-themes--cycle-themes))))

(defvar modus-themes--toggle-themes modus-themes-to-toggle)

(defun oe/toggle-themes-and-print-log-msg ()
  (setq modus-themes--toggle-themes (oe/cycle-themes modus-themes--toggle-themes))
  (message "Theme: %s selected" (cadr modus-themes--toggle-themes)))

(oe/cycle-themes-and-print-log-msg)
(oe/toggle-themes-and-print-log-msg)

The main difference is to set "private" variables (denoted by the --
in their name), so that we do not change global/public variables.

Below is my attempt at a "cycle" command:

(defun modus-themes--rotate-list-of-symbol (symbol)
  "Rotate list value of SYMBOL by moving its car to the end.
Return the first element before performing the rotation.

This means that if `sample-list' has an initial value of `(one
two three)', this function will first return `one' and update the
value of `sample-list' to `(two three one)'.  Subsequent calls
will continue rotating accordingly."
  (unless (symbolp symbol)
    (user-error "%s is not a symbol" symbol))
  (when-let* ((value (symbol-value symbol))
              (list (and (listp value) value))
              (first (car list)))
    (set symbol (append (cdr list) (list first)))
    first))

(defvar modus-themes--cycle-themes modus-themes-items)

(defun modus-themes-cycle ()
  "Cycle through the `modus-themes-items'."
  (interactive)
  (let ((theme (modus-themes--rotate-list-of-symbol
                'modus-themes--cycle-themes)))
    (modus-themes-load-theme theme)
    (message "Loaded %s theme" theme)))

Those granted, we probably want to have a modus-themes-to-cycle as a
companion to such a command. Its default value could be modus-themes-items.

@oliverepper
Copy link
Author

Hey Prot,

I made a few adjustments on my cycle-themes idea, so that it saves my last choosen theme and loads it again when I start Emacs. This works super nice for my cycle-function but since your toggle function does not return the selected theme it's hard to advice it with the oe/save-selected-theme-function.

(use-package modus-themes
  :demand
  :custom
  (modus-themes-to-toggle		'(modus-operandi modus-vivendi))
  (modus-themes-disable-other-themes	t)

  (modus-themes-italic-constructs	t)
  (modus-themes-variable-pitch-ui	t)
  (modus-themes-mixed-fonts		t)

  (modus-themes-prompts		'(bold))
  (modus-themes-org-blocks		'tinted-background)

  (modus-themes-headings
   '(
     (1			. (1.215))
     (2			. (1.138))
     (3			. (1.076))
     (4			. (1.0))
     (5			. (0.937))

     (agenda-date		. (variable-pitch italic 1.138))
     (agenda-structure	. (variable-pitch light 1.8))
     (t . (medium))
     ))

  (modus-themes-common-palette-overrides
   '(
     (border-mode-line-active		bg-mode-line-active)
     (border-mode-line-inactive	bg-mode-line-inactive)
     ))
  :bind ("<f5>" . modus-themes-toggle)
  :config
  (let ((last (expand-file-name "selected-theme.el" user-emacs-directory)))
    (if (file-exists-p last)
	(load last)
      (load-theme (car modus-themes-to-toggle) :no-confirm))))

(defun oe/save-selected-theme (theme)
  (with-temp-file (expand-file-name "selected-theme.el" user-emacs-directory)
    (insert (format "(load-theme '%s :no-confirm)\n" theme))))

(defun oe/rotate-list (list)
  (when list
    (append (cdr list) (list (car list)))))

(defun oe/cycle-themes (theme-list)
  (load-theme (car theme-list) :no-confirm)
  (oe/rotate-list theme-list))

(defun oe/cycle-themes-and-print-log-msg ()
  (interactive)
  (defvar modus-themes--cycle modus-themes-items)
  (setq modus-themes--cycle (oe/cycle-themes modus-themes--cycle))
  (let ((selected-theme (car (reverse modus-themes--cycle))))
    (message "Theme: %s selected" (car (reverse modus-themes--cycle)))
    (oe/save-selected-theme selected-theme)))

(global-set-key (kbd "<f4>") #'oe/cycle-themes-and-print-log-msg)

Keep in mind that I am still new to Emacs/elisp. I'd glady take any idea to make f4 and f5 save the choosen theme.

@protesilaos
Copy link
Owner

Very well!

(defun oe/cycle-themes-and-print-log-msg ()
  (interactive)
  (defvar modus-themes--cycle modus-themes-items)
  (setq modus-themes--cycle (oe/cycle-themes modus-themes--cycle))
  (let ((selected-theme (car (reverse modus-themes--cycle))))
    (message "Theme: %s selected" (car (reverse modus-themes--cycle)))
    (oe/save-selected-theme selected-theme)))

The defvar declares the variable. This usually is a top-level form because then the variable does not depend on the call to a specific function (if you expect the variable elsewhere, it will fail). Put the defvar outside the function and in the function use the setq you already have.

Maybe I am misreading this now, but in this let form the second (car (reverse ... could be replaced by selected-theme.

About the choice of key, you can always use any key you want, though note that the default for f4 is to either conclude an in-progress keyboard macro or execute the last defined keyboard macro (kmacro definitions are done with f3). I find those very useful.

@oliverepper
Copy link
Author

Yes. You're right the second let should read selected-theme. That was an oversight. Regarding the defvar I used it like one would use the static keyword in C (the var belongs to the function but storage needs to be outside of the function). Do you have an idea how I can save the last selected theme that is choosen by modus' own toggle function?

@protesilaos
Copy link
Owner

Do you have an idea how I can save the last selected theme that is choosen by modus' own toggle function?

Would this do what you need?

 modus-themes.el | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/modus-themes.el b/modus-themes.el
index 3d4c1b4..c854002 100644
--- a/modus-themes.el
+++ b/modus-themes.el
@@ -1257,7 +1257,8 @@ (defun modus-themes-load-theme (theme)
 after loading the THEME."
   (modus-themes--disable-themes)
   (load-theme theme :no-confirm)
-  (run-hooks 'modus-themes-after-load-theme-hook))
+  (run-hooks 'modus-themes-after-load-theme-hook)
+  theme)
 
 (defun modus-themes--retrieve-palette-value (color palette)
   "Return COLOR from PALETTE.

@oliverepper
Copy link
Author

Yes! And for what's it worth I think it's a better function, now. It has a side effect (setting up the theme) and now it tells the world not only that it did that side-effect but even how it parametrized this side-effect. ❤️

protesilaos added a commit that referenced this issue May 23, 2023
The intent is to allow other functions that call this one to capture
the return value for their purposes.

Thank to Oliver Epper for the feedback in issue 78 on the GitHub
mirror: <#78>.
@protesilaos
Copy link
Owner

Very well! I just made the change.

@oliverepper
Copy link
Author

This is very nice:

:bind ("<f5>" . (lambda () (interactive) (oe/save-selected-theme (modus-themes-toggle))))

@protesilaos
Copy link
Owner

Hello again! I just added a new modus-themes-rotate command and the user option modus-themes-to-toggle. It expands on the idea discussed here, though the code is a bit different so that (i) it will continue rotating from the position right after the currently loaded theme and (ii) it will skip to the next item on the list if the one it finds is the same as the currently loaded theme.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants