1

The mode-line-format can be a list of strings, but it can also include un-evaluated blocks, e.g:

(setq mode-line-format
  (list
    "Hello"
    '(:eval (my-function-call))
    "There"))

I would like to store a list in a similar format which can be converted into a string.

However I don't want to use format-mode-line as I don't want %p etc.. to have special meanings.

Is there a utility function for doing this? Or would I need to implement the logic to extract/evaluate a string from the list myself?

2
  • format-mode-line can be used to store any portion of the mode-line as a string -- saving it to a local/global variable -- it is not just for special mode-line codes .... I use it to store an alphabetically sorted minor-mode-alist so that I can use the stored value when I need to save time; e.g., rapid fire movements ... scrolling up/down, moving cursor left/right, etc. The disadvantage, is that the mouse interaction with a saved string will be limited .... To store a value, you can use (setq my-variable (format-mode-line COMPONENT)) – lawlist Feb 3 at 7:16
  • Is there a way to make it not evaluate %p - and similar characters though? (besides replacing % with %%). I'm wondering if the ability to merge a string and evaluate :eval property is available in a more generic, less mode-line spesific function. – ideasman42 Feb 3 at 8:49
3

If you really just want a list (and not a tree like format-mode-line) that can be evaluated takes element-wise to strings and then concatenated mapconcat and eval are your friends:

(let ((my-list '("Hello, I am " (number-to-string (user-uid)) " and it is " (format-time-string " %T, %F"))))
  (mapconcat #'eval my-list ""))

The output is:

Hello, I am 1000 and it is  11:47:45, 2021-02-03

A more general version can also be implemented quite simple in a recursive way:

(defun tree-to-string (tree &optional lexical)
  "Convert TREE recursively to a string.
TREE can be one of the following:
- lists with car :eval : the cdr is evaluated and the result is passed to `tree-to-string'
- other lists: element-wise processed with `tree-to-string'
- any other element: transformed to string with `prin1-to-string'
The optional argument LEXICAL is passed to `eval'."
  (if (listp tree)
      (if (eq (car tree) :eval)
      (tree-to-string (eval (cons 'progn (cdr tree)) lexical))
    (mapconcat (lambda (item) (tree-to-string item lexical)) tree ""))
    (prin1-to-string tree t)))

(tree-to-string '("Hello, I am " (:eval (user-uid)) (" and it is " (:eval (format-time-string "%T, %F"))) "."))

The output is:

Hello, I am 1000 and it is 11:47:45, 2021-02-03.
3
  • 1
    I suggest calling eval with a non-nil second argument for lexical binding. – Basil Feb 3 at 10:29
  • @Basil Can you provide a practical example where this becomes relevant? I already tried some cases where I thought this would play a role. But the only example where I could get some advantage was with passing a specific lexical environment to eval. – Tobias Feb 5 at 10:45
  • It's relevant wherever Elisp is evaluated. Most such places (*scratch*, --eval, *ielm*, ...) are transitioning or have already transitioned to defaulting to lexical-binding, so it should be used and encouraged in general. You never know when a user might write a form that depends on lexical-binding (e.g. by using a closure) and subsequently be surprised that it doesn't work as expected. – Basil Feb 5 at 11:34
0

Adding an answer based on @Tobias's answer with changes.

  • Support :propertize (as mode-line-format does).
  • Support symbols (which are resolved into strings).
  • Call eval with lexical binding set to t.
  • Raise an error for unsupported values (instead of converting them into strings), this means mistakes are more easily caught.
(defun tree-to-string (tree)
  "Convert TREE recursively to a string.
TREE can be one of the following:
- lists with `car' `:eval'
  - the `cdr' is evaluated and the result is passed to `tree-to-string'
- lists with `car' `:propertize'
  - the `caar' is passed to `tree-to-string'.
  - the `cddr' is passed to as properties to `propertize'.
- other lists: element-wise processed with `tree-to-string'
- a symbol, it's value is transformed to string with `prin1-to-string'.
- any other element must be a string.
"
  (cond
    ((stringp tree)
      tree)
    ((null tree)
      "")
    ((symbolp tree)
      ;; Support non-string symbols, allows numbers etc to be included.
      (prin1-to-string (symbol-value tree) t))
    ((listp tree)
      (let ((tree-type (car tree)))
        (cond
          ((eq tree-type :eval)
            (tree-to-string (eval (cons 'progn (cdr tree)) t)))
          ((eq tree-type :propertize)
            (pcase-let ((`(,item . ,rest) (cdr tree)))
              (apply 'propertize (cons (tree-to-string item) rest))))
          (t
            (mapconcat #'tree-to-string tree "")))))
    (t
      (error
        "Found '%S' (%S), not a string, nil or `:eval' cons or list of these"
        (type-of tree)
        tree))))

Example usage:

;; Define a value that can be evaluated into a string at any time.
(defvar my-custom-format
  (list
    "Hello, User files are in "
    'user-emacs-directory
    " and it is "
    '(:eval (format-time-string "%T, %F "))
    '(:propertize "This text is green" face (:background "green"))
    "."))

;; Evaluate and show the string.
(message "%s" (tree-to-string my-custom-format))

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.