0

I'm trying to edit a mode's font-lock-keywords variable to use defcustom instead of defconst (which it was using previously). Obviously, this is because I want to be able to easily customize the mode's font lock keywords.

Currently it's using a backquote construct to evaluate the list, like so:

(defcustom v-font-lock-keywords
  `(
     ;; builtin
     (,v-builtin-keywords-regexp . font-lock-builtin-face)

     ;; careful
     (,v-careful-keywords-regexp . font-lock-warning-face)

     ;; @ # $
     ;; ("#\\(?:include\\|flag\\)" . 'font-lock-builtin-face)
     ("[@#$][A-Za-z_]*[A-Z-a-z0-9_]*" . 'font-lock-warning-face)

     ;; declaration
     (,v-declaration-keywords-regexp . font-lock-keyword-face)

     ;; ... more of the same

     ;; variable references
     ("\\($?_?[a-z]+[a-z_0-9]*\\)" 1 'font-lock-variable-name-face))
  "An alist mapping regexes to font-lock faces."
  :type '(repeat sexp)
  :group 'v-mode)

The problem is, if I use this backquote / comma structure as it currently exists, I can't properly use the customize interface to change the variable.

For example, when I customize one element of the list to have a different face, now the evaluated value for ,v-builtin-keywords-regexp gets hardcoded into my .emacs:

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 ...
 '(v-font-lock-keywords
   (quote
    (("\\<\\(any\\(?:_\\(?:\\(?:floa\\|in\\)t\\)\\)?\\|b\\(?:ool\\|yte\\(?:ptr\\)?\\)\\|charptr\\|f\\(?:32\\|64\\)\\|i\\(?:1\\(?:28\\|6\\)\\|64\\|nt\\|[8t]\\)\\|rune\\|s\\(?:ize_t\\|tring\\)\\|u\\(?:1\\(?:28\\|6\\)\\|32\\|64\\)\\|voidptr\\)\\>" . font-lock-builtin-face)
     ("\\<\\(C\\|__global\\|as\\(?:sert\\)?\\|break\\|continue\\|defer\\|error\\|go\\(?:to\\)?\\|i\\(?:mport\\|nline\\|[ns]\\)\\|live\\|mut\\|or\\|panic\\|unsafe\\)\\>" . font-lock-warning-face)
     ("[@#$][A-Za-z_]*[A-Z-a-z0-9_]*" quote font-lock-warning-face)
     ("\\<\\(enum\\|fn\\|interface\\|return\\|struct\\|type\\)\\>" . font-lock-keyword-face)
 ...))))

I don't want the current values to get hardcoded into there; I just want it to keep using the backquote instead. Or somehow dynamically embed the variable reference there. But I don't see a way to do this.

Is this even doable with customize? Perhaps I merely need to use defvar to modify the variable instead of defcustom and simply edit it in raw Elisp, just eschewing the customize interface altogether?

For reference, there is some similar code in the Emacs trunk for dns-mode.el:

(defcustom dns-mode-font-lock-keywords
  `((,(concat "^$" (regexp-opt dns-mode-control-entities))
     0 ,dns-mode-control-entity-face)
    ("^$[a-z0-9A-Z]+" 0 ,dns-mode-bad-control-entity-face)
    (,(regexp-opt dns-mode-classes) 0 ,dns-mode-class-face)
    (,(regexp-opt dns-mode-types) 0 ,dns-mode-type-face))
  "Font lock keywords used to highlight text in DNS master file mode."
  :version "26.1"
  :type 'sexp
  :group 'dns-mode)

This variable has the exact same problem which you'll see if you try to edit it in the customize interface via M-x customize-variable dns-mode-font-lock-keywords (you'll have to load dns-mode first).

1

2 Answers 2

1

It's not clear (to me) what you want. The closest you seem to come to it is this, which is also not very clear:

"I just want it to keep using the backquote instead. Or somehow dynamically embed the variable reference there. But I don't see a way to do this."

Can you specify what you want as the value of the option, and how you want to be able to change that value (with or without using Customize)?

It sounds like you maybe want variables, and not their values, in the value of your user option, so that the option value is determined by those variable values. But there's no way (for me at least) to guess what you're really trying to do.


In any case, you probably shouldn't be trying to modify/set font-lock-keywords directly.

From the Elisp manual, node Search-based Fontification:

font-lock-keywords:

Lisp programs should not set this variable directly. Normally, the value is automatically set by Font Lock mode, using the KEYWORDS element in font-lock-defaults. The value can also be altered using the functions font-lock-add-keywords and font-lock-remove-keywords (See Customizing Keywords).

The usual way to modify/set font-lock-keywords is indirectly, by setting font-lock-defaults or using font-lock-add-keywords, as suggested in the manual.

1
  • "It sounds like you maybe want variables, and not their values, in the value of your user option, so that the option value is determined by those variable values. But there's no way (for me at least) to guess what you're really trying to do." - Yes, this is what I want to do. As far as whether the code conforms to standard Lisp conventions, I'm not the developer of the package so I didn't feel like doing a more thorough rewrite for that part of the code.
    – GDP2
    Jun 2, 2021 at 5:15
1

It looks to me like you misunderstand the logic of the backquote. Even though it allows evaluation of code (instead of simply using the quoted value), it is still evaluated at runtime. That is, defcustom does receive a value in the same sense that a quoted list produces a value. Which is why if you look at the stored customizations in your custom.el file, you will always find immutable constants and not, say, a function call.

So the real question is what you want to achieve. You must make sure that your customization functions are evaluated when you need them. Maybe a defcustom is not the right place for that, since it is evaluated at compile time, or when the lisp file is loaded.

1
  • Thanks for the clarification. Maybe I did have a little misunderstanding of the concept in my head, but ultimately I came to the same conclusion that you did in the last paragraph. I decided defcustom wasn't the right tool for the job and I'm simply setting the variable with setq.
    – GDP2
    Jun 2, 2021 at 5:36

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.