diff --git a/doc/misc/modus-themes.org b/doc/misc/modus-themes.org index d470a9c104b..43983505576 100644 --- a/doc/misc/modus-themes.org +++ b/doc/misc/modus-themes.org @@ -4,9 +4,9 @@ #+language: en #+options: ':t toc:nil author:t email:t num:t #+startup: content -#+macro: stable-version 5.0.0 -#+macro: release-date 2025-11-01 -#+macro: development-version 5.1.0-dev +#+macro: stable-version 5.1.0 +#+macro: release-date 2025-11-07 +#+macro: development-version 5.2.0-dev #+macro: file @@texinfo:@file{@@$1@@texinfo:}@@ #+macro: space @@texinfo:@: @@ #+macro: kbd @@texinfo:@kbd{@@$1@@texinfo:}@@ @@ -4169,11 +4169,13 @@ core Emacs. #+findex: modus-themes-theme #+vindex: modus-themes-registered-items -A new theme can be instantiated with the function ~modus-themes-theme~. -It takes care to (i) declare the theme, (ii) add to it relevant -~theme-properties~, (iii) register it in the ~modus-themes-registered-items~, -(iv) make it work with all the faces and customizations documented -in this manual, and (v) ~provide-theme~ the theme. +A new theme exists in a file whose directory is in the ~custom-theme-load-path~. +The theme file is named =NAME-theme.el=. For example, the ~modus-operandi~ +theme is in the file =modus-operandi-theme.el=. A theme object can be +instantiated with the function ~modus-themes-theme~. This function takes care to +(i) declare the theme, (ii) add to it relevant ~theme-properties~, (iii) register +it in the ~modus-themes-registered-items~, (iv) make it work with all the faces +and customizations documented in this manual, and (v) ~provide-theme~ the theme. Concretely, ~modus-themes-theme~ expects the following mandatory arguments: @@ -5963,39 +5965,40 @@ The Modus themes are a collective effort. Every bit of work matters. Adrian Manea, Aleksei Pirogov, Alex Griffin, Alex Koen, Alex Peitsinis, Alexandr Semenov, Alexey Shmalko, Alok Singh, Anders Johansson, André Alexandre Gomes, Andrew Tropin, Antonio Hernández - Blas, Arif Rezai, Augusto Stoffel, Basil L.{{{space()}}} - Contovounesios, Bernd Rellermeyer, Burgess Chang, Charlotte Van - Petegem, Christian Tietze, Christopher Dimech, Christopher League, - Damien Cassou, Daniel Mendler, Dario Gjorgjevski, David Edmondson, - Davor Rotim, Divan Santana, Eliraz Kedmi, Emanuele Michele Alberto - Monterosso, Eshel Yaron, Farasha Euker, Feng Shu, Filippo Argiolas, - Gautier Ponsinet, Gerry Agbobada, Gianluca Recchia, Gonçalo Marrafa, - Guilherme Semente, Gustavo Barros, Hörmetjan Yiltiz, Ilja Kocken, - Imran Khan, Iris Garcia, Ivan Popovych, Jabir Ali Ouassou, James - Ferguson, Jeremy Friesen, Jerry Zhang, Johannes Grødem, John Haman, - John Sullivan, John Wick, Jonas Collberg, Jorge Morais, Joshua - O'Connor, Julio C. Villasante, Kenta Usami, Kevin Fleming, Kévin Le - Gouguec, Kevin Kainan Li, Kostadin Ninev, Laith Bahodi, Lasse - Lindner, Len Trigg, Lennart C.{{{space()}}} Karssen, Luis Miguel - Castañeda, Magne Hov, Manuel Giraud, Manuel Uberti, Mark Bestley, - Mark Burton, Mark Simpson, Marko Kocic, Markus Beppler, Matt - Armstrong, Matthias Fuchs, Mattias Engdegård, Mauro Aranda, Maxime - Tréca, Michael Goldenberg, Morgan Smith, Morgan Willcock, Murilo - Pereira, Nicky van Foreest, Nicolas De Jaeghere, Nicolas Semrau, - Olaf Meeuwissen, Oliver Epper, Pablo Stafforini, Paul Poloskov, - Pengji Zhang, Pete Kazmier, Peter Wu, Philip Kaludercic, Pierre - Téchoueyres, Przemysław Kryger, Robert Hepple, Roman Rudakov, - Russell Sim, Ryan Phillips, Rytis Paškauskas, Rudolf Adamkovič, Sam - Kleinman, Samuel Culpepper, Saša Janiška, Shreyas Ragavan, Simon - Pugnet, Steve Downey, Tassilo Horn, Thanos Apollo, Thibaut Verron, - Thomas Heartman, Togan Muftuoglu, Tony Zorman, Trey Merkley, Tomasz - Hołubowicz, Toon Claes, Uri Sharf, Utkarsh Singh, Vincent Foley, - Zoltan Kiraly. As well as users: Ben, CsBigDataHub1, Emacs Contrib, - Eugene, Fourchaux, Fredrik, Moesasji, Nick, Summer Emacs, TheBlob42, - TitusMu, Trey, bepolymathe, bit9tream, bangedorrunt, case-lambda, - chainedghost, derek-upham, doolio, fleimgruber, gitrj95, iSeeU, - jixiuf, ltmsyvag, okamsn, pedro-nonfree, pRot0ta1p, shimeike, - shipmints, soaringbird, tumashu, wakamenod. + Blas, Arif Rezai, Ashton Wiersdorf, Augusto Stoffel, Basil + L.{{{space()}}} Contovounesios, Bernd Rellermeyer, Burgess Chang, + Charlotte Van Petegem, Christian Tietze, Christopher Dimech, + Christopher League, Damien Cassou, Daniel Mendler, Dario + Gjorgjevski, David Edmondson, Davor Rotim, Divan Santana, Eliraz + Kedmi, Emanuele Michele Alberto Monterosso, Eshel Yaron, Farasha + Euker, Feng Shu, Filippo Argiolas, Gautier Ponsinet, Gerry Agbobada, + Gianluca Recchia, Gonçalo Marrafa, Guilherme Semente, Gustavo + Barros, Hörmetjan Yiltiz, Ilja Kocken, Imran Khan, Iris Garcia, Ivan + Popovych, Jabir Ali Ouassou, James Ferguson, Jeremy Friesen, Jerry + Zhang, Johannes Grødem, John Haman, John Sullivan, John Wick, Jonas + Collberg, Jorge Morais, Joshua O'Connor, Julio C. Villasante, Kenta + Usami, Kevin Fleming, Kévin Le Gouguec, Kevin Kainan Li, Kostadin + Ninev, Laith Bahodi, Lasse Lindner, Len Trigg, Lennart + C.{{{space()}}} Karssen, Luis Miguel Castañeda, Magne Hov, Manuel + Giraud, Manuel Uberti, Mark Bestley, Mark Burton, Mark Simpson, + Marko Kocic, Markus Beppler, Matt Armstrong, Matthias Fuchs, Mattias + Engdegård, Mauro Aranda, Maxime Tréca, Michael Goldenberg, Morgan + Smith, Morgan Willcock, Murilo Pereira, Nicky van Foreest, Nicolas + De Jaeghere, Nicolas Semrau, Olaf Meeuwissen, Oliver Epper, Pablo + Stafforini, Paul Poloskov, Pengji Zhang, Pete Kazmier, Peter Wu, + Philip Kaludercic, Pierre Téchoueyres, Przemysław Kryger, Robert + Hepple, Roman Rudakov, Russell Sim, Ryan Phillips, Rytis Paškauskas, + Rudolf Adamkovič, Sam Kleinman, Samuel Culpepper, Saša Janiška, + Shreyas Ragavan, Simon Pugnet, Stéphane Marks, Steve Downey, Tassilo + Horn, Thanos Apollo, Thibaut Verron, Thomas Heartman, Togan + Muftuoglu, Tony Zorman, Trey Merkley, Tomasz Hołubowicz, Toon Claes, + Uri Sharf, Utkarsh Singh, Vincent Foley, Zoltan Kiraly. As well as + users: Ben, CsBigDataHub1, Emacs Contrib, Eugene, Fourchaux, + Fredrik, Moesasji, Nick, Summer Emacs, TheBlob42, TitusMu, Trey, + bepolymathe, bit9tream, bangedorrunt, case-lambda, chainedghost, + derek-upham, doolio, fleimgruber, gitrj95, iSeeU, jixiuf, ltmsyvag, + okamsn, pedro-nonfree, pRot0ta1p, shimeike, shipmints, soaringbird, + tumashu, wakamenod. + Packaging :: Basil L.{{{space()}}} Contovounesios, Eli Zaretskii, Glenn Morris, Mauro Aranda, Richard Stallman, Stefan Kangas (core diff --git a/etc/themes/modus-themes.el b/etc/themes/modus-themes.el index 4a57941d238..3bcc190b174 100644 --- a/etc/themes/modus-themes.el +++ b/etc/themes/modus-themes.el @@ -5,7 +5,7 @@ ;; Author: Protesilaos Stavrou ;; Maintainer: Protesilaos Stavrou ;; URL: https://github.com/protesilaos/modus-themes -;; Version: 5.0.0 +;; Version: 5.1.0 ;; Package-Requires: ((emacs "28.1")) ;; Keywords: faces, theme, accessibility @@ -3779,7 +3779,7 @@ Also see `modus-themes-get-themes'.") ;; `custom-known-themes' because loading the theme has the desired ;; side effect of adding the relevant `theme-properties' to it. (unless (and (memq theme modus-themes--activated-themes) - (custom-theme-p theme)) + (custom-theme-p theme)) (load-theme theme t t) (add-to-list 'modus-themes--activated-themes theme))) @@ -3789,18 +3789,21 @@ Also see `modus-themes-get-themes'.") (theme-family (plist-get properties :family))) (eq theme-family family))) -(defun modus-themes-get-all-known-themes (&optional theme-family) +(defun modus-themes-get-all-known-themes (&optional theme-family no-enable) "Return all known Modus themes or derivatives, enabling them if needed. With optional THEME-FAMILY, operate only on the themes whose :family property is that. Else consider the Modus themes as well as all their derivatives. +With optional NO-ENABLE, do not try to enable the themes. + Also see `modus-themes-sort'." (let ((themes (pcase theme-family ('modus-themes modus-themes-items) ((pred (not null)) modus-themes-registered-items) (_ (seq-union modus-themes-items modus-themes-registered-items))))) - (mapc #'modus-themes--activate themes) + (unless no-enable + (mapc #'modus-themes--activate themes)) (if theme-family (seq-filter (lambda (theme) @@ -3833,54 +3836,55 @@ Use `modus-themes-sort' to sort by light and then dark background." sorted-themes modus-themes-items)) -(defun modus-themes-known-p (themes &optional show-error) +(defun modus-themes-known-p (themes) "Return THEMES if they are among `modus-themes-get-themes' else nil. THEMES is either a list of symbols, like `modus-themes-items' or a symbol. With optional SHOW-ERROR, throw an error instead of returning nil." - (condition-case data - (let ((themes (if (listp themes) themes (list themes))) - (known-themes (modus-themes-get-themes))) - (dolist (theme themes) - (or (memq theme known-themes) - (error "`%s' is not part of whant `modus-themes-get-themes' returns" theme)))) - (:success - themes) - (error - (when show-error - (signal (car data) (list (apply #'format-message (cdr data)))))))) + (let ((known-themes (modus-themes-get-all-known-themes))) + (cond + ((symbolp themes) + (memq themes known-themes)) + ((listp themes) + (when (seq-every-p (lambda (theme) (memq theme known-themes)) themes) + themes)) + (t + (error "Themes `%S' is not a symbol or a list of symbols" themes))))) -(defun modus-themes--list-enabled-themes () - "Return list of known `custom-enabled-themes'." - (seq-intersection (modus-themes-get-themes) custom-enabled-themes)) - -(defun modus-themes-get-current-theme () - "Return first enabled Modus theme." - (car (modus-themes--list-enabled-themes))) +(defun modus-themes-get-current-theme (&optional no-enable) + "Return current enabled Modus theme. +With optional NO-ENABLE, do not try to enable any themes." + (let ((current (car custom-enabled-themes))) + (when (memq current (modus-themes-get-all-known-themes nil no-enable)) + current))) (defun modus-themes--get-theme-palette-subr (theme with-overrides with-user-palette) "Get THEME palette without `modus-themes-known-p'. WITH-OVERRIDES and WITH-USER-PALETTE are described in -`modus-themes-get-theme-palette'." - (if-let* ((properties (get theme 'theme-properties)) - (core-palette (symbol-value (plist-get properties :modus-core-palette)))) - (let* ((user-palette (when with-user-palette (symbol-value (plist-get properties :modus-user-palette)))) - (overrides-palette (when with-overrides (symbol-value (plist-get properties :modus-overrides-palette)))) - (all-overrides (when with-overrides - (append overrides-palette modus-themes-common-palette-overrides)))) - (append all-overrides user-palette core-palette)) - (error "The theme must have at least a `:modus-core-palette' property"))) +`modus-themes-get-theme-palette'. + +If THEME does not have at least a `:modus-core-palette' among its +`theme-properties', return nil." + (when-let* ((properties (get theme 'theme-properties)) + (core-palette (symbol-value (plist-get properties :modus-core-palette)))) + (let* ((user-palette (when with-user-palette (symbol-value (plist-get properties :modus-user-palette)))) + (overrides-palette (when with-overrides (symbol-value (plist-get properties :modus-overrides-palette)))) + (all-overrides (when with-overrides (append overrides-palette modus-themes-common-palette-overrides)))) + (append all-overrides user-palette core-palette)))) (defun modus-themes-get-theme-palette (&optional theme with-overrides with-user-palette) "Return palette value of active `modus-themes-get-themes' THEME. If THEME is nil, use the return value of `modus-themes-get-current-theme'. With WITH-OVERRIDES, include all overrides in the combined palette. With WITH-USER-PALETTE do the same for the user-defined palette -extension." - (let ((theme (or theme (modus-themes-get-current-theme)))) - (when (modus-themes-known-p theme :err-if-needed) - (modus-themes--get-theme-palette-subr theme with-overrides with-user-palette)))) +extension. + +If THEME is unknown, return nil." + (modus-themes--get-theme-palette-subr + (or theme (modus-themes-get-current-theme)) + with-overrides + with-user-palette)) (defun modus-themes--disable-themes () "Disable themes per `modus-themes-disable-other-themes'." @@ -5709,7 +5713,7 @@ FG and BG are the main colors." `(jabber-roster-user-dnd ((,c :foreground ,warning))) `(jabber-roster-user-chatty ((,c :foreground ,warning))) `(jabber-roster-user-error ((,c :foreground ,err))) - `(jabber-roster-user-offline ((,c :foreground ,fg-dim :strike-through t))) + `(jabber-roster-user-offline ((,c :foreground ,fg-dim))) `(jabber-roster-user-online ((,c :inherit modus-themes-bold :foreground ,info))) `(jabber-chat-prompt-foreign ((,c :inherit modus-themes-bold :foreground ,err))) `(jabber-chat-prompt-system ((,c :foreground ,warning))) @@ -5967,7 +5971,7 @@ FG and BG are the main colors." `(markdown-header-face-6 ((,c :inherit modus-themes-heading-6))) `(markdown-highlighting-face ((,c :background ,bg-hover-secondary :foreground ,fg-main))) `(markdown-inline-code-face ((,c :inherit modus-themes-fixed-pitch :background ,bg-prose-code :foreground ,fg-prose-code))) - `(markdown-italic-face ((,c :inherit modus-themes-slant))) + `(markdown-italic-face ((,c :inherit italic))) `(markdown-language-keyword-face ((,c :inherit modus-themes-fixed-pitch :background ,bg-prose-block-delimiter :foreground ,fg-prose-block-delimiter))) `(markdown-line-break-face ((,c :foreground ,err :underline t))) `(markdown-link-face ((,c :background ,bg-link :foreground ,fg-link :underline ,underline-link))) @@ -7291,31 +7295,47 @@ Consult the manual for details on how to build a theme on top of the ,@faces) (custom-theme-set-variables ',name - ,@variables)))) + ,@variables)) + :lexical)) (unless theme-exists-p (provide-theme name)))) ;;;; Use theme colors +(defun modus-themes--with-colors-resolve-palette-sort (colors) + "Sort all COLORS in the theme's palette. +Put all named colors before semantic color mappings. A named color is a +symbol whose value is a string. A semantic color mapping is a symbol +whose value is another symbol, which ultimately resolves to a string or +`unspecified'." + (let ((named nil) + (semantic nil)) + (dolist (color colors) + (if (stringp (cadr color)) + (push color named) + (push color semantic))) + (seq-uniq + (nconc (nreverse named) (nreverse semantic)) + (lambda (elt1 elt2) + (eq (car elt1) (car elt2)))))) + +(defun modus-themes-with-colors-subr (expressions) + "Do the work of `modus-themes-with-colors' for EXPRESSIONS." + (condition-case data + (when-let* ((theme (modus-themes-get-current-theme :no-enable))) + (eval + `(let* ((c '((class color) (min-colors 256))) + (unspecified 'unspecified) + ,@(modus-themes--with-colors-resolve-palette-sort + (modus-themes--get-theme-palette-subr theme :with-overrides :with-user-palette))) + ,@expressions) + :lexical)) + (error (message "Error in `modus-themes-with-colors': %s" data)))) + (defmacro modus-themes-with-colors (&rest body) "Evaluate BODY with colors from current palette bound." (declare (indent 0)) - (let* ((sym (gensym)) - (palette (modus-themes-get-theme-palette nil :with-overrides :with-user-palette)) - ;; NOTE 2022-08-23: We just give it a sample palette at this - ;; stage. It only needs to collect each car. Then we - ;; instantiate the actual theme's palette. We have to do this - ;; otherwise the macro does not work properly when called from - ;; inside a function. - (colors (mapcar #'car palette))) - `(let* ((c '((class color) (min-colors 256))) - (,sym (modus-themes-get-theme-palette nil :with-overrides :with-user-palette)) - ,@(mapcar (lambda (color) - (list color - `(modus-themes--retrieve-palette-value ',color ,sym))) - colors)) - (ignore c ,@colors) ; Silence unused variable warnings - ,@body))) + `(modus-themes-with-colors-subr ',body)) ;;;; Declare all the Modus themes