1

I'd like to locally remap a face to other custom face. The docstring reads:

(face-remap-set-base FACE &rest SPECS)

Set the base remapping of FACE in the current buffer to SPECS. This causes the remappings specified by ‘face-remap-add-relative’ to apply on top of the face specification given by SPECS.

The remaining arguments, SPECS, should form a list of faces. Each list element should be either a face name or a property list of face attribute/value pairs, like in a ‘face’ text property.

Armed with this knowledge, I tried:

(defface my/exceptional-face
  '((t (:background "red")))
  "My exceptional face.")
    
(face-remap-set-base 'font-lock-keyword-face '(my/exceptional-face))

In a Lisp buffer, M-x eval-buffer RET, I'd expect the keywords to show up with a red background. However, this fails with:

(wrong-type-argument listp my/exceptional-face)

Indeed, the face-remap-set-base function reads:

 (defun face-remap-set-base (face &rest specs)
  (while (and (consp specs) (not (null (car specs))) (null (cdr specs)))
    (setq specs (car specs)))
  (if (or (null specs)
      (and (eq (car specs) face) (null (cdr specs)))) ; Error raised here
      ;; Set entry back to default
      (face-remap-reset-base face)
    ;; Set the base remapping
    ...snip...))

It thus seems that there's no way I can pass a single face to this function. Using:

(face-remap-set-base 'font-lock-keyword-face '(my/exceptional-face nil))

…seems to work though. Is that the expected behavior, or is this a bug?

1 Answer 1

1

Remove the quoted list from around its elements. Just provide the elements (as an implicit list) as additional args to face-remap-set-base:

(face-remap-set-base 'font-lock-keyword-face 'my/exceptional-face)

The code defining function face-remap-set-base binds its single &rest parameter SPECS to the list (my/exceptional-face).

The doc says:

The remaining arguments, SPECS, should form a list of faces.

What that means is what &rest means: provide faces individually as separate additional arguments. Don't provide an explicit list of faces as a single argument.

You provide any number of faces as additional actual arguments, and the function receives, as its single &rest argument SPECS, a list of those actual args that you passed.

See the Elisp manual, node Argument List, for an explanation of &rest.

In the code you provided the list (my/exceptional-face) that you passed resulted in the &rest parameter SPECS being bound to this list: ((my/exceptional-face)). Function face-remap-set-base figured that this was a list of one face, that face (or face spec) being the list (my/exceptional-face). A face spec that's a list needs a certain form, and (my/exceptional-face) doesn't have that form -- hence the error:

(wrong-type-argument listp my/exceptional-face)

See node Defining Faces of the Elisp manual for the proper list form of a face spec.


Oddly enough, I don't find anywhere in the Elisp manual or the Eintr manual (An Introduction to Programming in Emacs Lisp) where it is explained that passing multiple optional args to a function results in a single &rest list argument being received. But it's fundamental to Lisp (pretty much any Lisp, not just Elisp).

The Common Lisp doc tells you this about a &rest parameter:

all remaining arguments are made into a list for the &rest parameter


However, though all I said is I think true, the code I suggested that you use also fails.

I think there's a bug in the code of face-remap-set-base. I submitted bug report #45264. We'll find out....

I note that there are zero uses of face-remap-set-base in the Emacs Lisp code. Perhaps it was never tested...

(Or perhaps I'm mistaken, and someone else will provide a correct answer.)

2
  • Thanks for the investigation Drew, appreciated! I was aware of &rest arguments being passed as a list, although it seems that on SE, most people use these functions with a single list (e.g., here). Similarly, I've seen here people using (face-remap-set-base 'f1 (face-all-attributes 'f2)), but I couldn't make this to work—is it because face-remaps expect a list rather than an alist?
    – Michaël
    Dec 16, 2020 at 0:44
  • It seems to me that (elisp)Argument List explains "that passing multiple optional args to a function results in a single &rest list argument being received" clearly enough (since 26.3 at minimum). Specifically the example which states "any arguments after the first four are collected into a list and ‘e’ is bound to that list".
    – phils
    Sep 7, 2022 at 11:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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