GNU bug report logs - #77361
[PATCH] New user option to hide minor mode lighters

Previous Next

Package: emacs;

Reported by: Pengji Zhang <me <at> pengjiz.com>

Date: Sat, 29 Mar 2025 11:09:02 UTC

Severity: normal

Tags: patch

Done: Eli Zaretskii <eliz <at> gnu.org>

To reply to this bug, email your comments to 77361 AT debbugs.gnu.org.
There is no need to reopen the bug first.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sat, 29 Mar 2025 11:09:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Pengji Zhang <me <at> pengjiz.com>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Sat, 29 Mar 2025 11:09:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Pengji Zhang <me <at> pengjiz.com>
To: bug-gnu-emacs <at> gnu.org
Subject: [PATCH] New user option to hide minor mode lighters
Date: Sat, 29 Mar 2025 19:07:40 +0800
[Message part 1 (text/plain, inline)]
Hello,

The attached patch adds a new user option to hide some minor mode
lighters on the mode line (by collapsing them into a menu), which is to
shorten the mode line and prioritize important information when many
minor modes are on.

There exist a few packages that solve the problem. To name a few:

- diminish[1] and delight[2]. Both offer a way to change the minor mode
  lighters by modifying 'minor-mode-alist'. To hide a minor mode
  lighter, one may change it to nil.

- minions[3], which partially inspired this patch. It replaces minor
  mode lighters by a menu to toggle all minor modes.

This patch is different from those solutions in two aspects:

- Unlike diminish or delight, one can still see the lighters by clicking
  the button, instead of hiding them permanently. Besides, this patch is
  compatible with those two packages.

- Unlike minions, this patch focuses on *lighters* for *enabled* minor
  modes. The menu contains only lighters, making it a more space
  efficient replacement for lighters on mode line, instead of a way to
  manage minor modes like minions.

So I hope this patch is still useful given the existing similar
solutions.

Please let me know what you think. Thanks!

Pengji

[1] https://github.com/myrjola/diminish.el
[2] https://elpa.gnu.org/packages/delight.html
[3] https://github.com/tarsius/minions

[0001-New-user-option-to-hide-minor-mode-lighters.patch (text/x-patch, inline)]
From c1e27606247761c31cec3d363714875c74b30277 Mon Sep 17 00:00:00 2001
From: Pengji Zhang <me <at> pengjiz.com>
Date: Sat, 29 Mar 2025 19:04:58 +0800
Subject: [PATCH] New user option to hide minor mode lighters

* lisp/bindings.el (mode-line-collapse-minor-modes): New user
option.
(mode-line-minor-modes): New variable to hold mode line
constructs for minor modes.
(mode-line-modes): Use the new variable 'mode-line-minor-modes',
and adjust the order of elements so the indicator for hidden
minor modes is shown towards the end.
(mode-line--make-lighter-menu): New helper function to generate
the menu for hidden minor modes.
(mode-line--minor-modes): New helper function to computer mode
line constructs for minor mode lighters.

* doc/lispref/modes.texi (Mode Line Basics): Document the new
user option.
* etc/NEWS (Note): Annouce the new user option.
---
 doc/lispref/modes.texi |  12 ++++
 etc/NEWS               |   9 +++
 lisp/bindings.el       | 122 ++++++++++++++++++++++++++++++++++++++---
 3 files changed, 136 insertions(+), 7 deletions(-)

diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 788d98fdf20..23c363be12c 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -2163,6 +2163,18 @@ Mode Line Basics
 variable can be buffer-local to only compress mode-lines in certain
 buffers.
 
+@vindex mode-line-collapse-minor-modes
+  To further ``compress'' the mode line, you may customize the
+@code{mode-line-collapse-minor-modes} option to a non-@code{nil} value,
+and Emacs will hide some minor mode indicators on the mode line by
+collapsing them into a single clickable button.  The option can also be
+a list of symbols to select minor modes indicators to hide or show.  If
+the list starts with the symbol @code{not}, it specifies minor modes to
+show, otherwise it means minor modes to hide.  For example, setting it
+to @code{(not flymake-mode)} makes only the indicator for Flymake mode
+shown, and setting it to @code{(eldoc-mode)} hides only the indicator
+for ElDoc mode.
+
 @node Mode Line Data
 @subsection The Data Structure of the Mode Line
 @cindex mode line construct
diff --git a/etc/NEWS b/etc/NEWS
index 1bd2fd6d486..8b7dfa0ee04 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -316,6 +316,15 @@ This will inhibit implied resizing while a new frame is made and can be
 useful on tiling window managers where the initial frame size should be
 specified by external means.
 
+** Mode Line
+
++++
+*** New user option 'mode-line-collapse-minor-modes'.
+If non-nil, minor mode lighters on the mode line are collapsed into a
+single button.  It could also be a list to specify minor mode lighters
+to hide or show.  The default value is nil, which retains the previous
+behavior of showing all minor mode lighters.
+
 ** Tab Bars and Tab Lines
 
 ---
diff --git a/lisp/bindings.el b/lisp/bindings.el
index 9707ce4b474..a32e3d58dc7 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -429,6 +429,120 @@ bindings--sort-menu-keymap
                            (bindings--menu-item-string (cdr-safe b))))))
     (nconc (make-sparse-keymap prompt) bindings)))
 
+(defcustom mode-line-collapse-minor-modes nil
+  "Minor modes for which mode line lighters are hidden.
+Hidden lighters are collapsed into one.
+
+The value could be a list (MODES ...) which means to collapse lighters
+only for MODES, or a list (not MODES ...) which means to collapse all
+lighters for minor modes not in MODES.  Other non-nil values make all
+lighters hidden."
+  :type '(choice (const :tag "No modes" nil)
+                 (repeat :tag "Modes" symbol)
+                 (cons :tag "All modes except"
+                       (const not) (repeat symbol))
+                 (const :tag "All modes" t))
+  :group 'mode-line)
+
+(defvar mode-line-minor-modes '(:eval (mode-line--minor-modes))
+  "Mode line construct for minor mode lighters.")
+;;;###autoload
+(put 'mode-line-minor-modes 'risky-local-variable t)
+
+(defun mode-line--make-lighter-menu (alist)
+  "Return a menu keymap for minor mode lighters in ALIST.
+ALIST should be in the same format as `minor-mode-alist'.
+
+Return nil if no lighters in ALIST should be visible, for example, there
+are no active minor modes or non-empty lighters."
+  (let ((menu (make-sparse-keymap "Minor Modes"))
+        (empty t))
+    (dolist (item alist)
+      (when-let* ((variable (car item))
+                  ((and (boundp variable)
+                        (symbol-value variable)))
+                  (lighter (format-mode-line `("" ,@(cdr-safe item))))
+                  ((not (string= lighter "")))
+                  (toggle (or (get variable :minor-mode-function) variable))
+                  ;; Follow the format in `mouse-minor-mode-menu'
+                  (name (format "%s - %s" lighter
+                                (capitalize
+                                 (string-replace
+                                  "-" " " (symbol-name toggle))))))
+        (when (eq ?  (aref name 0))
+          (setq name (substring name 1)))
+        (let* ((map (cdr-safe (assq variable minor-mode-map-alist)))
+               (mm-menu (and (keymapp map)
+                             (keymap-lookup map "<menu-bar>"))))
+          (setq mm-menu
+                (cond (mm-menu (mouse-menu-non-singleton mm-menu))
+                      ((fboundp toggle)
+                       (define-keymap :name name
+                         "<help>" (list 'menu-item
+                                        "Help for minor mode"
+                                        (lambda () (interactive)
+                                          (describe-function toggle)))
+                         "<turn-off>" (list 'menu-item
+                                            "Turn off minor mode"
+                                            toggle)))
+                      ;; No menu and not a minor mode function, so just
+                      ;; display the label without a sub-menu.
+                      (t nil)))
+          (keymap-set menu (format "<%s>" toggle)
+                      (list 'menu-item name mm-menu))
+          (setq empty nil))))
+    (and (not empty) menu)))
+
+(defun mode-line--minor-modes ()
+  "Compute mode line constructs for minor mode lighters."
+  (let (visible hidden)
+    (cond
+     ((not mode-line-collapse-minor-modes)
+      (setq visible minor-mode-alist
+            hidden nil))
+     ((eq 'not (car-safe mode-line-collapse-minor-modes))
+      (let ((modes (cdr mode-line-collapse-minor-modes)))
+        (dolist (item minor-mode-alist)
+          (if (memq (car item) modes)
+              (push item visible)
+            (push item hidden)))
+        (setq visible (nreverse visible)
+              hidden (nreverse hidden))))
+     ((listp mode-line-collapse-minor-modes)
+      (let ((modes mode-line-collapse-minor-modes))
+        (dolist (item minor-mode-alist)
+          (if (memq (car item) modes)
+              (push item hidden)
+            (push item visible)))
+        (setq visible (nreverse visible)
+              hidden (nreverse hidden))))
+     (t (setq visible nil
+              hidden minor-mode-alist)))
+    (list ""
+          `(:propertize ("" ,visible)
+                        mouse-face mode-line-highlight
+                        help-echo "Minor mode\n\
+mouse-1: Display minor mode menu\n\
+mouse-2: Show help for minor mode\n\
+mouse-3: Toggle minor modes"
+                        local-map ,mode-line-minor-mode-keymap)
+          (when-let* ((menu (mode-line--make-lighter-menu hidden))
+                      (menu-binding (list 'menu-item "Display" menu
+                                          :filter #'bindings--sort-menu-keymap))
+                      (toggle-binding (list 'menu-item "Toggle" mode-line-mode-menu
+                                            :fitler #'bindings--sort-menu-keymap))
+                      (keymap (define-keymap
+                                "<mode-line> <down-mouse-1>" menu-binding
+                                "<mode-line> <mouse-2>" #'describe-mode
+                                "<mode-line> <down-mouse-3>" toggle-binding)))
+            `(:propertize ,(if (char-displayable-p ?…) " …" " ...")
+                          mouse-face mode-line-highlight
+                          help-echo "Hidden minor modes\n\
+mouse-1: Display hidden minor modes\n\
+mouse-2: Show help for enabled minor modes\n\
+mouse-3: Toggle minor modes"
+                          local-map ,keymap)))))
+
 (defvar mode-line-major-mode-keymap
   (let ((map (make-sparse-keymap)))
     (define-key map [mode-line down-mouse-1]
@@ -466,17 +580,11 @@ mode-line-modes
 			mouse-face mode-line-highlight
 			local-map ,mode-line-major-mode-keymap)
 	  '("" mode-line-process)
-	  `(:propertize ("" minor-mode-alist)
-			mouse-face mode-line-highlight
-			help-echo "Minor mode\n\
-mouse-1: Display minor mode menu\n\
-mouse-2: Show help for minor mode\n\
-mouse-3: Toggle minor modes"
-			local-map ,mode-line-minor-mode-keymap)
 	  (propertize "%n" 'help-echo "mouse-2: Remove narrowing from buffer"
 		      'mouse-face 'mode-line-highlight
 		      'local-map (make-mode-line-mouse-map
 				  'mouse-2 #'mode-line-widen))
+	  '("" mode-line-minor-modes)
 	  ")"
 	  (propertize "%]" 'help-echo recursive-edit-help-echo)
 	  " "))
-- 
2.49.0


Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sat, 05 Apr 2025 09:05:02 GMT) Full text and rfc822 format available.

Message #8 received at 77361 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Pengji Zhang <me <at> pengjiz.com>
Cc: 77361 <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters
Date: Sat, 05 Apr 2025 12:03:43 +0300
> From: Pengji Zhang <me <at> pengjiz.com>
> Date: Sat, 29 Mar 2025 19:07:40 +0800
> 
> The attached patch adds a new user option to hide some minor mode
> lighters on the mode line (by collapsing them into a menu), which is to
> shorten the mode line and prioritize important information when many
> minor modes are on.
> 
> There exist a few packages that solve the problem. To name a few:
> 
> - diminish[1] and delight[2]. Both offer a way to change the minor mode
>   lighters by modifying 'minor-mode-alist'. To hide a minor mode
>   lighter, one may change it to nil.
> 
> - minions[3], which partially inspired this patch. It replaces minor
>   mode lighters by a menu to toggle all minor modes.
> 
> This patch is different from those solutions in two aspects:
> 
> - Unlike diminish or delight, one can still see the lighters by clicking
>   the button, instead of hiding them permanently. Besides, this patch is
>   compatible with those two packages.
> 
> - Unlike minions, this patch focuses on *lighters* for *enabled* minor
>   modes. The menu contains only lighters, making it a more space
>   efficient replacement for lighters on mode line, instead of a way to
>   manage minor modes like minions.
> 
> So I hope this patch is still useful given the existing similar
> solutions.
> 
> Please let me know what you think. Thanks!

Thanks, some comments below.

> >From c1e27606247761c31cec3d363714875c74b30277 Mon Sep 17 00:00:00 2001
> From: Pengji Zhang <me <at> pengjiz.com>
> Date: Sat, 29 Mar 2025 19:04:58 +0800
> Subject: [PATCH] New user option to hide minor mode lighters
> 
> * lisp/bindings.el (mode-line-collapse-minor-modes): New user
> option.
> (mode-line-minor-modes): New variable to hold mode line
> constructs for minor modes.
> (mode-line-modes): Use the new variable 'mode-line-minor-modes',
> and adjust the order of elements so the indicator for hidden
> minor modes is shown towards the end.
> (mode-line--make-lighter-menu): New helper function to generate
> the menu for hidden minor modes.
> (mode-line--minor-modes): New helper function to computer mode
> line constructs for minor mode lighters.
> 
> * doc/lispref/modes.texi (Mode Line Basics): Document the new
> user option.
> * etc/NEWS (Note): Annouce the new user option.

Please in future submissions of the patch mention the bug number in
the log message.

> --- a/doc/lispref/modes.texi
> +++ b/doc/lispref/modes.texi
> @@ -2163,6 +2163,18 @@ Mode Line Basics
>  variable can be buffer-local to only compress mode-lines in certain
>  buffers.
>  
> +@vindex mode-line-collapse-minor-modes
> +  To further ``compress'' the mode line, you may customize the
> +@code{mode-line-collapse-minor-modes} option to a non-@code{nil} value,
> +and Emacs will hide some minor mode indicators on the mode line by
> +collapsing them into a single clickable button.  The option can also be
> +a list of symbols to select minor modes indicators to hide or show.  If
> +the list starts with the symbol @code{not}, it specifies minor modes to
> +show, otherwise it means minor modes to hide.  For example, setting it
> +to @code{(not flymake-mode)} makes only the indicator for Flymake mode
> +shown, and setting it to @code{(eldoc-mode)} hides only the indicator
> +for ElDoc mode.

I think this text and the preceding paragraph should be moved to the
Emacs user manual, into the "Optional Mode Line" node.  These are
user-facing features, so their place is not in the ELisp Reference
manual.

For the user manual, the above description of the possible values of
mode-line-collapse-minor-modes is too detailed.  I suggest to have
there only the first sentence, and then refer to the doc string for
the various alternative forms of the value.

> ++++
> +*** New user option 'mode-line-collapse-minor-modes'.
> +If non-nil, minor mode lighters on the mode line are collapsed into a
> +single button.  It could also be a list to specify minor mode lighters
                   ^^^^^^^^^^^^^^^^^^^^^^^
"The value could also be a list..."

> +(defcustom mode-line-collapse-minor-modes nil
> +  "Minor modes for which mode line lighters are hidden.
> +Hidden lighters are collapsed into one.
> +
> +The value could be a list (MODES ...) which means to collapse lighters
> +only for MODES, or a list (not MODES ...) which means to collapse all
> +lighters for minor modes not in MODES.  Other non-nil values make all
> +lighters hidden."
> +  :type '(choice (const :tag "No modes" nil)
> +                 (repeat :tag "Modes" symbol)
> +                 (cons :tag "All modes except"
> +                       (const not) (repeat symbol))
> +                 (const :tag "All modes" t))
> +  :group 'mode-line)

The :version tag is missing.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sat, 05 Apr 2025 11:46:02 GMT) Full text and rfc822 format available.

Message #11 received at 77361 <at> debbugs.gnu.org (full text, mbox):

From: Daniel Mendler <mail <at> daniel-mendler.de>
To: Pengji Zhang <me <at> pengjiz.com>
Cc: 77361 <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters,
 [PATCH] New user option to hide minor mode lighters
Date: Sat, 05 Apr 2025 13:45:10 +0200
Hello Pengji!

Pengji Zhang <me <at> pengjiz.com> writes:
> The attached patch adds a new user option to hide some minor mode
> lighters on the mode line (by collapsing them into a menu), which is to
> shorten the mode line and prioritize important information when many
> minor modes are on.
>
> There exist a few packages that solve the problem. To name a few:
>
> - diminish[1] and delight[2]. Both offer a way to change the minor mode
>   lighters by modifying 'minor-mode-alist'. To hide a minor mode
>   lighter, one may change it to nil.
>
> - minions[3], which partially inspired this patch. It replaces minor
>   mode lighters by a menu to toggle all minor modes.
>
> This patch is different from those solutions in two aspects:
>
> - Unlike diminish or delight, one can still see the lighters by clicking
>   the button, instead of hiding them permanently. Besides, this patch is
>   compatible with those two packages.
>
> - Unlike minions, this patch focuses on *lighters* for *enabled* minor
>   modes. The menu contains only lighters, making it a more space
>   efficient replacement for lighters on mode line, instead of a way to
>   manage minor modes like minions.
>
> So I hope this patch is still useful given the existing similar
> solutions.
>
> Please let me know what you think. Thanks!

This is a good idea. I've used both minions and diminish and it is nice
to have this functionality ootb. I have some concerns however regarding
the frequent mode line recomputation during redisplay. Do you see a
possibility to cache the computed minor mode menu and collapsed lighter?
In any case, the computation should be as efficient as possible. For
profiling I suggest to enable your new user option and then scroll
repeatedly in order to force redisplays and mode line recomputations. If
certain mode line functions appear prominently in the profile they are
candidates for optimization, in order to reduce redisplay latency.

Daniel




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sun, 06 Apr 2025 08:06:02 GMT) Full text and rfc822 format available.

Message #14 received at 77361 <at> debbugs.gnu.org (full text, mbox):

From: Pengji Zhang <me <at> pengjiz.com>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: 77361 <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters
Date: Sun, 06 Apr 2025 16:05:00 +0800
[Message part 1 (text/plain, inline)]
Eli Zaretskii <eliz <at> gnu.org> writes:

> Please in future submissions of the patch mention the bug number in
> the log message.

Thanks for the review! Bug number added in the updated patch.

>> --- a/doc/lispref/modes.texi
>> +++ b/doc/lispref/modes.texi
>> @@ -2163,6 +2163,18 @@ Mode Line Basics
>>  variable can be buffer-local to only compress mode-lines in certain
>>  buffers.
>>  
>> +@vindex mode-line-collapse-minor-modes
>> +  To further ``compress'' the mode line, you may customize the
>> +@code{mode-line-collapse-minor-modes} option to a non-@code{nil} value,
>> +and Emacs will hide some minor mode indicators on the mode line by
>> +collapsing them into a single clickable button.  The option can also be
>> +a list of symbols to select minor modes indicators to hide or show.  If
>> +the list starts with the symbol @code{not}, it specifies minor modes to
>> +show, otherwise it means minor modes to hide.  For example, setting it
>> +to @code{(not flymake-mode)} makes only the indicator for Flymake mode
>> +shown, and setting it to @code{(eldoc-mode)} hides only the indicator
>> +for ElDoc mode.
>
> I think this text and the preceding paragraph should be moved to the
> Emacs user manual, into the "Optional Mode Line" node.  These are
> user-facing features, so their place is not in the ELisp Reference
> manual.
>
> For the user manual, the above description of the possible values of
> mode-line-collapse-minor-modes is too detailed.  I suggest to have
> there only the first sentence, and then refer to the doc string for
> the various alternative forms of the value.

I have moved the paragraph for 'mode-line-compact' to the "Optional Mode
Line" node, and added the first sentence for
'mode-line-collapse-minor-modes' to that paragraph.

>> ++++
>> +*** New user option 'mode-line-collapse-minor-modes'.
>> +If non-nil, minor mode lighters on the mode line are collapsed into a
>> +single button.  It could also be a list to specify minor mode lighters
>                    ^^^^^^^^^^^^^^^^^^^^^^^
> "The value could also be a list..."

Fixed.

>> +(defcustom mode-line-collapse-minor-modes nil
>> +  "Minor modes for which mode line lighters are hidden.
>> +Hidden lighters are collapsed into one.
>> +
>> +The value could be a list (MODES ...) which means to collapse lighters
>> +only for MODES, or a list (not MODES ...) which means to collapse all
>> +lighters for minor modes not in MODES.  Other non-nil values make all
>> +lighters hidden."
>> +  :type '(choice (const :tag "No modes" nil)
>> +                 (repeat :tag "Modes" symbol)
>> +                 (cons :tag "All modes except"
>> +                       (const not) (repeat symbol))
>> +                 (const :tag "All modes" t))
>> +  :group 'mode-line)
>
> The :version tag is missing.

Added.

[0001-New-user-option-to-hide-minor-mode-lighters-bug-7736.patch (text/x-patch, inline)]
From ff653f559b9cd984f584756741b7b52815befe8d Mon Sep 17 00:00:00 2001
From: Pengji Zhang <me <at> pengjiz.com>
Date: Sat, 29 Mar 2025 19:04:58 +0800
Subject: [PATCH] New user option to hide minor mode lighters (bug#77361)

* lisp/bindings.el (mode-line-collapse-minor-modes): New user
option.
(mode-line-minor-modes): New variable to hold mode line
constructs for minor modes.
(mode-line--make-lighter-menu): New helper function to generate
the menu for hidden minor modes.
(mode-line--minor-modes): New helper function to computer mode
line constructs for minor mode lighters.
(mode-line-modes): Use the new variable 'mode-line-minor-modes',
and adjust the order of elements so the indicator for hidden
minor modes is shown towards the end.

* doc/lispref/modes.texi (Mode Line Basics): Move the paragraph
for 'mode-line-compact' from here...
* doc/emacs/display.texi (Optional Mode Line): ...to here, and
document the new user option.
* etc/NEWS: Annouce the new user option.
---
 doc/emacs/display.texi |  15 +++++
 doc/lispref/modes.texi |  11 ----
 etc/NEWS               |   9 +++
 lisp/bindings.el       | 128 ++++++++++++++++++++++++++++++++++++++---
 4 files changed, 145 insertions(+), 18 deletions(-)

diff --git a/doc/emacs/display.texi b/doc/emacs/display.texi
index 520d3289f2d..ad496b5b1cd 100644
--- a/doc/emacs/display.texi
+++ b/doc/emacs/display.texi
@@ -1811,6 +1811,21 @@ Optional Mode Line
 @code{eol-mnemonic-dos}, @code{eol-mnemonic-mac}, and
 @code{eol-mnemonic-undecided} to the strings you prefer.
 
+@vindex mode-line-compact
+@vindex mode-line-collapse-minor-modes
+  Some modes put a lot of data in the mode line, pushing elements at the
+end of the mode line off to the right.  Emacs can ``compress'' the mode
+line if the @code{mode-line-compact} variable is non-@code{nil} by
+turning stretches of spaces into a single space.  If this variable is
+@code{long}, this is only done when the mode line is wider than the
+currently selected window.  (This computation is approximate, based on
+the number of characters, and not their displayed width.)  This variable
+can be buffer-local to only compress mode-lines in certain buffers.  To
+further ``compress'' the mode line, you may customize the
+@code{mode-line-collapse-minor-modes} option to a non-@code{nil} value,
+and Emacs will hide some minor mode indicators on the mode line by
+collapsing them into a single clickable button.
+
 @node Text Display
 @section How Text Is Displayed
 @cindex characters (in text)
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 788d98fdf20..0dd73fe17a8 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -2152,17 +2152,6 @@ Mode Line Basics
 @end lisp
 @end defun
 
-@vindex mode-line-compact
-  Some modes put a lot of data in the mode line, pushing elements at
-the end of the mode line off to the right.  Emacs can ``compress'' the
-mode line if the @code{mode-line-compact} variable is non-@code{nil}
-by turning stretches of spaces into a single space.  If this variable
-is @code{long}, this is only done when the mode line is wider than the
-currently selected window.  (This computation is approximate, based on
-the number of characters, and not their displayed width.)  This
-variable can be buffer-local to only compress mode-lines in certain
-buffers.
-
 @node Mode Line Data
 @subsection The Data Structure of the Mode Line
 @cindex mode line construct
diff --git a/etc/NEWS b/etc/NEWS
index 35e6edcd712..939dc748f54 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -322,6 +322,15 @@ This will inhibit implied resizing while a new frame is made and can be
 useful on tiling window managers where the initial frame size should be
 specified by external means.
 
+** Mode Line
+
++++
+*** New user option 'mode-line-collapse-minor-modes'.
+If non-nil, minor mode lighters on the mode line are collapsed into a
+single button.  The value could also be a list to specify minor mode
+lighters to hide or show.  The default value is nil, which retains the
+previous behavior of showing all minor mode lighters.
+
 ** Tab Bars and Tab Lines
 
 ---
diff --git a/lisp/bindings.el b/lisp/bindings.el
index 9707ce4b474..2d6e1579e10 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -429,6 +429,126 @@ bindings--sort-menu-keymap
                            (bindings--menu-item-string (cdr-safe b))))))
     (nconc (make-sparse-keymap prompt) bindings)))
 
+(defcustom mode-line-collapse-minor-modes nil
+  "Minor modes for which mode line lighters are hidden.
+Hidden lighters are collapsed into one.
+
+The value could be a list (MODES ...) which means to collapse lighters
+only for MODES, or a list (not MODES ...) which means to collapse all
+lighters for minor modes not in MODES.  Other non-nil values make all
+lighters hidden."
+  :type '(choice (const :tag "No modes" nil)
+                 (repeat :tag "Modes" symbol)
+                 (cons :tag "All modes except"
+                       (const not) (repeat symbol))
+                 (const :tag "All modes" t))
+  :group 'mode-line
+  :version "31.1")
+
+(defvar mode-line-minor-modes '(:eval (mode-line--minor-modes))
+  "Mode line construct for minor mode lighters.")
+;;;###autoload
+(put 'mode-line-minor-modes 'risky-local-variable t)
+
+(defun mode-line--make-lighter-menu (alist)
+  "Return a menu keymap for minor mode lighters in ALIST.
+ALIST should be in the same format as `minor-mode-alist'.
+
+Return nil if no lighters in ALIST should be visible, for example, there
+are no active minor modes or non-empty lighters."
+  (let ((menu (make-sparse-keymap "Minor Modes"))
+        (empty t))
+    (dolist (item alist)
+      (when-let* ((variable (car item))
+                  ((and (boundp variable)
+                        (symbol-value variable)))
+                  (lighter (format-mode-line `("" ,@(cdr-safe item))))
+                  ((not (string= lighter "")))
+                  (toggle (or (get variable :minor-mode-function) variable))
+                  ;; Follow the format in `mouse-minor-mode-menu'
+                  (name (format "%s - %s" lighter
+                                (capitalize
+                                 (string-replace
+                                  "-" " " (symbol-name toggle))))))
+        (when (eq ?  (aref name 0))
+          (setq name (substring name 1)))
+        (let* ((map (cdr-safe (assq variable minor-mode-map-alist)))
+               (mm-menu (and (keymapp map)
+                             (keymap-lookup map "<menu-bar>"))))
+          (setq mm-menu
+                (cond (mm-menu (mouse-menu-non-singleton mm-menu))
+                      ((fboundp toggle)
+                       (define-keymap :name name
+                         "<help>" (list 'menu-item
+                                        "Help for minor mode"
+                                        (lambda () (interactive)
+                                          (describe-function toggle)))
+                         "<turn-off>" (list 'menu-item
+                                            "Turn off minor mode"
+                                            toggle)))
+                      ;; No menu and not a minor mode function, so just
+                      ;; display the label without a sub-menu.
+                      (t nil)))
+          (keymap-set menu (format "<%s>" toggle)
+                      (list 'menu-item name mm-menu))
+          (setq empty nil))))
+    (and (not empty) menu)))
+
+(defun mode-line--minor-modes ()
+  "Compute mode line constructs for minor mode lighters."
+  (let (visible hidden)
+    (cond
+     ((not mode-line-collapse-minor-modes)
+      (setq visible minor-mode-alist
+            hidden nil))
+     ((eq 'not (car-safe mode-line-collapse-minor-modes))
+      (let ((modes (cdr mode-line-collapse-minor-modes)))
+        (dolist (item minor-mode-alist)
+          (if (memq (car item) modes)
+              (push item visible)
+            (push item hidden)))
+        (setq visible (nreverse visible)
+              hidden (nreverse hidden))))
+     ((listp mode-line-collapse-minor-modes)
+      (let ((modes mode-line-collapse-minor-modes))
+        (dolist (item minor-mode-alist)
+          (if (memq (car item) modes)
+              (push item hidden)
+            (push item visible)))
+        (setq visible (nreverse visible)
+              hidden (nreverse hidden))))
+     (t (setq visible nil
+              hidden minor-mode-alist)))
+    (list ""
+          `(:propertize ("" ,visible)
+                        mouse-face mode-line-highlight
+                        help-echo "Minor mode\n\
+mouse-1: Display minor mode menu\n\
+mouse-2: Show help for minor mode\n\
+mouse-3: Toggle minor modes"
+                        local-map ,mode-line-minor-mode-keymap)
+          (unless (string= "" (format-mode-line `("" ,hidden)))
+            (let* ((menu
+                    ;; FIXME: This is to defer the computation of the
+                    ;; menu, but may not play well with touchscreen.
+                    (lambda (e)
+                      (interactive "@e")
+                      (if-let* ((m (mode-line--make-lighter-menu hidden)))
+                          (popup-menu m e)
+                        (message "No menu available"))))
+                   (keymap
+                    (define-keymap
+                      :parent mode-line-minor-mode-keymap
+                      "<mode-line> <down-mouse-1>" menu
+                      "<mode-line> <mouse-2>" #'describe-mode)))
+              `(:propertize ,(if (char-displayable-p ?…) " …" " ...")
+                            mouse-face mode-line-highlight
+                            help-echo "Hidden minor modes\n\
+mouse-1: Display hidden minor modes\n\
+mouse-2: Show help for enabled minor modes\n\
+mouse-3: Toggle minor modes"
+                            local-map ,keymap))))))
+
 (defvar mode-line-major-mode-keymap
   (let ((map (make-sparse-keymap)))
     (define-key map [mode-line down-mouse-1]
@@ -466,17 +586,11 @@ mode-line-modes
 			mouse-face mode-line-highlight
 			local-map ,mode-line-major-mode-keymap)
 	  '("" mode-line-process)
-	  `(:propertize ("" minor-mode-alist)
-			mouse-face mode-line-highlight
-			help-echo "Minor mode\n\
-mouse-1: Display minor mode menu\n\
-mouse-2: Show help for minor mode\n\
-mouse-3: Toggle minor modes"
-			local-map ,mode-line-minor-mode-keymap)
 	  (propertize "%n" 'help-echo "mouse-2: Remove narrowing from buffer"
 		      'mouse-face 'mode-line-highlight
 		      'local-map (make-mode-line-mouse-map
 				  'mouse-2 #'mode-line-widen))
+	  '("" mode-line-minor-modes)
 	  ")"
 	  (propertize "%]" 'help-echo recursive-edit-help-echo)
 	  " "))
-- 
2.49.0


Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sun, 06 Apr 2025 08:19:01 GMT) Full text and rfc822 format available.

Message #17 received at 77361 <at> debbugs.gnu.org (full text, mbox):

From: Pengji Zhang <me <at> pengjiz.com>
To: Daniel Mendler <mail <at> daniel-mendler.de>
Cc: 77361 <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters
Date: Sun, 06 Apr 2025 16:17:54 +0800
Daniel Mendler <mail <at> daniel-mendler.de> writes:

> Hello Pengji!

Hi Daniel!

> This is a good idea. I've used both minions and diminish and it is nice
> to have this functionality ootb. I have some concerns however regarding
> the frequent mode line recomputation during redisplay. Do you see a
> possibility to cache the computed minor mode menu and collapsed lighter?
> In any case, the computation should be as efficient as possible. For
> profiling I suggest to enable your new user option and then scroll
> repeatedly in order to force redisplays and mode line recomputations. If
> certain mode line functions appear prominently in the profile they are
> candidates for optimization, in order to reduce redisplay latency.

Thanks! I did not consider the performance issue much. How about
deferring the computation of the minor mode menu, the most costly part?

+          (unless (string= "" (format-mode-line `("" ,hidden)))
+            (let* ((menu
+                    ;; FIXME: This is to defer the computation of the
+                    ;; menu, but may not play well with touchscreen.
+                    (lambda (e)
+                      (interactive "@e")
+                      (if-let* ((m (mode-line--make-lighter-menu hidden)))
+                          (popup-menu m e)
+                        (message "No menu available"))))
+                   (keymap
+                    (define-keymap
+                      :parent mode-line-minor-mode-keymap
+                      "<mode-line> <down-mouse-1>" menu
+                      "<mode-line> <mouse-2>" #'describe-mode)))
+              `(:propertize ,(if (char-displayable-p ?…) " …" " ...")
+                            mouse-face mode-line-highlight
+                            help-echo "Hidden minor modes\n\
+mouse-1: Display hidden minor modes\n\
+mouse-2: Show help for enabled minor modes\n\
+mouse-3: Toggle minor modes"
+                            local-map ,keymap)))

Still we need to split 'minor-mode-alist' and do some other work, but my
profiling showed that there was little impact to the performance.

Pengji




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sun, 06 Apr 2025 08:41:02 GMT) Full text and rfc822 format available.

Message #20 received at 77361 <at> debbugs.gnu.org (full text, mbox):

From: Daniel Mendler <mail <at> daniel-mendler.de>
To: Pengji Zhang <me <at> pengjiz.com>
Cc: 77361 <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters
Date: Sun, 06 Apr 2025 10:40:32 +0200
Pengji Zhang <me <at> pengjiz.com> writes:

> Daniel Mendler <mail <at> daniel-mendler.de> writes:
>
>> Hello Pengji!
>
> Hi Daniel!
>
>> This is a good idea. I've used both minions and diminish and it is nice
>> to have this functionality ootb. I have some concerns however regarding
>> the frequent mode line recomputation during redisplay. Do you see a
>> possibility to cache the computed minor mode menu and collapsed lighter?
>> In any case, the computation should be as efficient as possible. For
>> profiling I suggest to enable your new user option and then scroll
>> repeatedly in order to force redisplays and mode line recomputations. If
>> certain mode line functions appear prominently in the profile they are
>> candidates for optimization, in order to reduce redisplay latency.
>
> Thanks! I did not consider the performance issue much. How about
> deferring the computation of the minor mode menu, the most costly part?

Deferring the menu computation sounds like a good idea, in particular
since the menu is only accessed rarely. The repeated recomputation of
the mode line elements itself should be fine.

> Still we need to split 'minor-mode-alist' and do some other work, but my
> profiling showed that there was little impact to the performance.

Thanks for checking.

Daniel




Reply sent to Eli Zaretskii <eliz <at> gnu.org>:
You have taken responsibility. (Sun, 13 Apr 2025 08:48:02 GMT) Full text and rfc822 format available.

Notification sent to Pengji Zhang <me <at> pengjiz.com>:
bug acknowledged by developer. (Sun, 13 Apr 2025 08:48:02 GMT) Full text and rfc822 format available.

Message #25 received at 77361-done <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Pengji Zhang <me <at> pengjiz.com>
Cc: 77361-done <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters
Date: Sun, 13 Apr 2025 11:46:33 +0300
> From: Pengji Zhang <me <at> pengjiz.com>
> Cc: 77361 <at> debbugs.gnu.org
> Date: Sun, 06 Apr 2025 16:05:00 +0800
> 
> Eli Zaretskii <eliz <at> gnu.org> writes:
> 
> > Please in future submissions of the patch mention the bug number in
> > the log message.
> 
> Thanks for the review! Bug number added in the updated patch.
> 
> >> --- a/doc/lispref/modes.texi
> >> +++ b/doc/lispref/modes.texi
> >> @@ -2163,6 +2163,18 @@ Mode Line Basics
> >>  variable can be buffer-local to only compress mode-lines in certain
> >>  buffers.
> >>  
> >> +@vindex mode-line-collapse-minor-modes
> >> +  To further ``compress'' the mode line, you may customize the
> >> +@code{mode-line-collapse-minor-modes} option to a non-@code{nil} value,
> >> +and Emacs will hide some minor mode indicators on the mode line by
> >> +collapsing them into a single clickable button.  The option can also be
> >> +a list of symbols to select minor modes indicators to hide or show.  If
> >> +the list starts with the symbol @code{not}, it specifies minor modes to
> >> +show, otherwise it means minor modes to hide.  For example, setting it
> >> +to @code{(not flymake-mode)} makes only the indicator for Flymake mode
> >> +shown, and setting it to @code{(eldoc-mode)} hides only the indicator
> >> +for ElDoc mode.
> >
> > I think this text and the preceding paragraph should be moved to the
> > Emacs user manual, into the "Optional Mode Line" node.  These are
> > user-facing features, so their place is not in the ELisp Reference
> > manual.
> >
> > For the user manual, the above description of the possible values of
> > mode-line-collapse-minor-modes is too detailed.  I suggest to have
> > there only the first sentence, and then refer to the doc string for
> > the various alternative forms of the value.
> 
> I have moved the paragraph for 'mode-line-compact' to the "Optional Mode
> Line" node, and added the first sentence for
> 'mode-line-collapse-minor-modes' to that paragraph.
> 
> >> ++++
> >> +*** New user option 'mode-line-collapse-minor-modes'.
> >> +If non-nil, minor mode lighters on the mode line are collapsed into a
> >> +single button.  It could also be a list to specify minor mode lighters
> >                    ^^^^^^^^^^^^^^^^^^^^^^^
> > "The value could also be a list..."
> 
> Fixed.
> 
> >> +(defcustom mode-line-collapse-minor-modes nil
> >> +  "Minor modes for which mode line lighters are hidden.
> >> +Hidden lighters are collapsed into one.
> >> +
> >> +The value could be a list (MODES ...) which means to collapse lighters
> >> +only for MODES, or a list (not MODES ...) which means to collapse all
> >> +lighters for minor modes not in MODES.  Other non-nil values make all
> >> +lighters hidden."
> >> +  :type '(choice (const :tag "No modes" nil)
> >> +                 (repeat :tag "Modes" symbol)
> >> +                 (cons :tag "All modes except"
> >> +                       (const not) (repeat symbol))
> >> +                 (const :tag "All modes" t))
> >> +  :group 'mode-line)
> >
> > The :version tag is missing.
> 
> Added.

Thanks, now installed on the master branch, and closing the bug.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sat, 19 Apr 2025 04:32:02 GMT) Full text and rfc822 format available.

Message #28 received at 77361 <at> debbugs.gnu.org (full text, mbox):

From: Pengji Zhang <me <at> pengjiz.com>
To: Jonas Bernoulli <jonas <at> bernoul.li>
Cc: 77361 <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters
Date: Sat, 19 Apr 2025 12:30:42 +0800
[Message part 1 (text/plain, inline)]
(Cc'ing the bug tracker thread in case others would like to chime in.)

Hello Jonas,

Jonas Bernoulli <jonas <at> bernoul.li> writes:

> Pengji Zhang <me <at> pengjiz.com> writes:
>
>> - Unlike minions, this patch focuses on *lighters* for *enabled* minor
>>   modes. The menu contains only lighters, making it a more space
>>   efficient replacement for lighters on mode line, instead of a way to
>>   manage minor modes like minions.
>
> As the author of minions I am going to stick with that, but even then
> the addition of `mode-line-minor-modes' is beneficial.  Minions can
> now just modify this trivial element instead of the much more complex
> `mode-line-modes', which lead to a lot of duplication (or alternatively
> fragile patching of the built-in value).

Thanks! Good to know it is useful to minions as well.

> Unfortunately one still has to modify `mode-line-modes' to remove the
> parens around the modes.  IMO these are just unnecessary noise if one
> uses minions or the new mode-line-collapse-minor-modes.
>
>   Messages …
>
> is good enough for me, I can see how one might want parens if showing
> enabled minor-modes directly in the mode-line
>
>   (Messages foobar schalala other modes here)
>
> So I would like to suggest the addition of something like:
>
> (defcustom mode-line-modes-delimiters '("(" . ")")
>   "Strings placed near the edges of `mode-line-modes'."
>   :type '(choice (const :tag "No delimiters")
>                  (cons (string :tag "Opening string")
>                        (string :tag "Closing string")))
>   :group 'mode-line
>   :version "31.1")
>
> used like so
>
>  (defvar mode-line-modes
> ...
> -         "("
> +         '(:eval (car mode-line-modes-delimiters))
> ...
> -         ")"
> +         '(:eval (cdr mode-line-modes-delimiters))

Just my two cents -- I do think that it is good to make the delimiters
customizable.

> Additionally you might want to consider making the menu string
> customizable instead of using
>
> (if (char-displayable-p ?…) " …" " ...")

That makes sense. Please find the attached patch.

>      Cheers,
>      Jonas

Regards,
Pengji

[0001-Make-lighter-customizable-for-collapsed-minor-modes-.patch (text/x-patch, inline)]
From 656f03f48b2af0afab1e20ea3f13b2a007bc2bb8 Mon Sep 17 00:00:00 2001
From: Pengji Zhang <me <at> pengjiz.com>
Date: Sat, 19 Apr 2025 12:04:36 +0800
Subject: [PATCH] Make lighter customizable for collapsed minor modes
 (bug#77361)

* lisp/bindings.el (mode-line-collapsed-minor-modes-lighter):
New option for the collapsed lighter of minor modes.
(mode-line-collapse-minor-modes): Mention that the collapsed
lighter could be customized.
(mode-line--minor-modes): Use the new option.
* etc/NEWS: Announce the new option.
---
 etc/NEWS         |  5 +++++
 lisp/bindings.el | 14 ++++++++++++--
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 98f8e703013..ac06b214499 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -334,6 +334,11 @@ single button.  The value could also be a list to specify minor mode
 lighters to hide or show.  The default value is nil, which retains the
 previous behavior of showing all minor mode lighters.
 
+---
+*** New user option 'mode-line-collapsed-minor-modes-lighter'.
+The value should be a string to speficify the mode line lighter for
+collapsed minor modes.
+
 ** Tab Bars and Tab Lines
 
 ---
diff --git a/lisp/bindings.el b/lisp/bindings.el
index 2d6e1579e10..9be467a421e 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -431,7 +431,8 @@ bindings--sort-menu-keymap
 
 (defcustom mode-line-collapse-minor-modes nil
   "Minor modes for which mode line lighters are hidden.
-Hidden lighters are collapsed into one.
+Hidden lighters are collapsed into one, which is customizable via option
+`mode-line-collapsed-minor-modes-lighter'.
 
 The value could be a list (MODES ...) which means to collapse lighters
 only for MODES, or a list (not MODES ...) which means to collapse all
@@ -445,6 +446,15 @@ mode-line-collapse-minor-modes
   :group 'mode-line
   :version "31.1")
 
+(defcustom mode-line-collapsed-minor-modes-lighter
+  (if (char-displayable-p ?…) " …" " ...")
+  "Lighter for collapsed minor modes.
+This is effective only when `mode-line-collapse-minor-modes' is non-nil."
+  :type 'string
+  :initialize #'custom-initialize-delay
+  :group 'mode-line
+  :version "31.1")
+
 (defvar mode-line-minor-modes '(:eval (mode-line--minor-modes))
   "Mode line construct for minor mode lighters.")
 ;;;###autoload
@@ -541,7 +551,7 @@ mode-line--minor-modes
                       :parent mode-line-minor-mode-keymap
                       "<mode-line> <down-mouse-1>" menu
                       "<mode-line> <mouse-2>" #'describe-mode)))
-              `(:propertize ,(if (char-displayable-p ?…) " …" " ...")
+              `(:propertize mode-line-collapsed-minor-modes-lighter
                             mouse-face mode-line-highlight
                             help-echo "Hidden minor modes\n\
 mouse-1: Display hidden minor modes\n\
-- 
2.49.0


Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#77361; Package emacs. (Sat, 19 Apr 2025 10:10:01 GMT) Full text and rfc822 format available.

Message #31 received at 77361 <at> debbugs.gnu.org (full text, mbox):

From: Sean Whitton <spwhitton <at> spwhitton.name>
To: Pengji Zhang <me <at> pengjiz.com>
Cc: Jonas Bernoulli <jonas <at> bernoul.li>, 77361 <at> debbugs.gnu.org
Subject: Re: bug#77361: [PATCH] New user option to hide minor mode lighters
Date: Sat, 19 Apr 2025 18:08:44 +0800
Hello Pengji,

Thanks.  Making this customizable sounds good.

A couple of comments on your patch:

- It doesn't make sense to add an additional NEWS entry because the
  whole feature will be new in this release.  So you can delete that
  part of your patch.

- Ideally the name of the variable would start with mode-line-collapse-
  instead of mode-line-collapsed- in order to match the existing one.

  May I suggest mode-line-collapse-minor-modes-to ?
  Or mode-line-collapse-minor-modes-to-char ?

  Or could we get away with just one variable?
  mode-line-collapse-major-modes can be the string to use; a value of
  't' means to use ….
  Possibly renaming it to mode-line-collapse-major-modes-to-char.

-- 
Sean Whitton




This bug report was last modified 5 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.