2

Q: How to sort the minor-mode-alist alphabetically (in the mode-line) using the :lighter as the criteria for alphabetization.


Rough draft that does not work:

(setq mode-line-format '(
  (:eval
    (let* (
        (copy-of-list (copy-list minor-mode-alist))
        (sorted-list
          (sort copy-of-list #'(lambda (x y) 
            (when
                (and
                  (stringp (car (cdr x)))
                  (stringp (car (cdr y))))
              (when (eq ?\s (aref (car (cdr x)) 0))
                (setq x (replace-regexp-in-string "^\s" "" (car (cdr x)))))
              (when (eq ?\s (aref (car (cdr y)) 0))
                (setq y (replace-regexp-in-string "^\s" "" (car (cdr y)))))
              (when (and (stringp x) (stringp y))
                (string< x y )))))) )
      (list
        `(:propertize ("" sorted-list)
          face bold
          mouse-face highlight
          help-echo "This is a mouseover help message."
          local-map ,mode-line-minor-mode-keymap))))))

EDIT: The following is a revised working draft based upon the guidance of @Drew in his answer below -- it appears to be working correctly for :lighter in the form of either strings or symbols, and I'll be testing it out over the next day or so. The draft will need to be revised to include the situations mentioned by @Drew -- e.g., (text-scale-mode (" " text-scale-mode-lighter)) and other possibilities described in the Elisp manual at node Mode Line Data

(require 'cl)

(defmacro non-empty-name-p (name)       ; Error if NAME is neither nil nor a string.
  "NAME if non-nil and not \"\", else nil."
  `(and ,name (stringp ,name) (not (string= "" ,name)) ,name))

(setq mode-line-format '(
  (:eval
    (let* (
        (copied-list
          (delq nil
            (mapcar
              (lambda (x)
                (when
                    (or
                      (and
                        (symbolp (cadr x))
                        (non-empty-name-p (symbol-value (cadr x))))
                      (stringp (cadr x)))
                  x))
              (copy-list minor-mode-alist))))
        (sorted-list
          (sort
            copied-list
            (lambda (x y)
              (let* (
                  (xname (cadr x))
                  (yname (cadr y)) )
                (when (symbolp xname) (setq xname (symbol-value xname)))
                (when (symbolp yname) (setq yname (symbol-value yname)))
                (when (and (stringp xname) (non-empty-name-p xname)
                           (stringp yname) (non-empty-name-p yname))
                  (when (eq ?\s (aref xname 0))
                    (setq xname (replace-regexp-in-string "^\s" "" xname)))
                  (when (eq ?\s (aref yname 0))
                    (setq yname (replace-regexp-in-string "^\s" "" yname)))
                  (string< xname yname)))))) )
      (list
    `(:propertize ("" mode-name)
      help-echo "Major mode help message."
      face '(:foreground "red")
      mouse-face '(:foreground "blue")
      local-map ,mode-line-major-mode-keymap)
    `(:propertize ("" ,sorted-list)
      face '(:foreground "purple")
      mouse-face '(:foreground "blue")
      help-echo "Minor mode help message."
      local-map ,mode-line-minor-mode-keymap))))))
  • You may want to use format-mode-line on the cadr in order to turn them into strings. – politza Jun 27 '15 at 18:25
1

There are several things wrong with that code. Here is an improvement, but it still does not serve the general case.

(setq mode-line-format
      '((:eval
         (let* ((copy-of-list (copy-list minor-mode-alist))
                (sorted-list
                 (sort copy-of-list
                       (lambda (x y)
                         (let ((xname  (cadr x))
                               (yname  (cadr y)))
                           (when (symbolp xname) (setq xname  (symbol-value xname)))
                           (when (symbolp yname) (setq yname  (symbol-value yname)))
                           (when (and (stringp xname) (non-empty-name-p xname)
                                      (stringp yname) (non-empty-name-p yname))
                             (when (eq ?\s  (aref xname 0))
                               (setq x  (replace-regexp-in-string "^\s" "" xname)))
                             (when (eq ?\s  (aref yname 0))
                               (setq y  (replace-regexp-in-string "^\s" "" yname)))
                             (and (stringp x) (stringp y) (string< x y))))))))
           (list `(:propertize ("" ,sorted-list) face bold mouse-face highlight
                               help-echo "This is a mouseover help message."
                               local-map ,mode-line-minor-mode-keymap))))))

(defmacro non-empty-name-p (name)       ; Error if NAME is neither nil nor a string.
  "NAME if non-nil and not \"\", else nil."
  `(and ,name (not (string= "" ,name)) ,name))

The general problem is that you are not handling the various cases of what the cadr of x and y can be. The above code handles the case where such a cadr can be a symbol whose value is a string, e.g. (eldoc-mode eldoc-minor-mode-string). But it does not yet handle the case where such a cadr can be a list, such as (text-scale-mode (" " text-scale-mode-lighter)). You will need to tighten up such things.

See the Elisp manual, node Mode Line Data for the various possibilities.

You also forgot to put a comma in front of sorted-list, so that symbol was being used instead of its value. And you forgot to handle the "" case for a string, which won't work with aref...0.

For testing this kind of thing, evaluate the let form that is passed to :eval. In that context you can insert calls to debug or message etc., to see what is going on.

HTH.

  • Thank you for the working draft -- I'll spend some more time with this over the weekend and see if I can figure out why at least one of the :lighter is not being alphabetized -- most of them are alphabetized, but not all. [Perhaps the discrepancy is due to one ore more gaps in the list where neither x nor y are strings -- maybe deleting anything that is not a string from the list before sorting will fix it . . .] – lawlist Jun 27 '15 at 18:18
  • 1
    It is no doubt due to exactly that. Note that if you remove the (when (symbolp...)...) code then you will see that the Eldoc highlighter is also sorted wrong. You need to treat each possible value form, including, as I said, a list such as (text-scale-mode (" " text-scale-mode-lighter)). Anything that does not sort normally (i.e., explicitly) by your code gets pushed to the end (the sort test is nil). – Drew Jun 27 '15 at 19:10
  • Based on your help, I put together a working draft and added that as an edit to the original question. I believe the test should be with (string< xname yname) instead of (string< x y); and, the strings where we remove the leading space should be set to xname and yname instead of x and y. The other change I made was to utilize your macro at the outset to essentially remove anything from the list where (cadr x) is not a string or a symbol that passes your test. It appears to be working okay, and I'll test it out over the next day or so. – lawlist Jun 27 '15 at 23:25
  • Good to hear. Glad I could be of some help. The main message, I think, is the suggestion of how you can debug/test this kind of thing. The mode line is updated during redisplay, so it is not amenable to use of the debugger or calls to message etc. But you can use those tools on pieces of the code you use for the mode line. In this case, you can add them to your outermost let* sexp. – Drew Jun 28 '15 at 0:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.