3

Should I use custom-set-variables (as opposed to setq) when setting a variable that returns non-nil for custom-variable-p but was not defined by defcustom?

For example, echo-keystrokes is defined in keyboard.c via defvar_lisp, but the following returns non-nil:

(custom-variable-p 'echo-keystrokes)

The documentation for custom-variable-p states:

A customizable variable is either (i) a variable whose property list contains a non-nil ‘standard-value’ or ‘custom-autoload’ property, or (ii) an alias for another customizable variable.

This leaves me wondering if the conventional advice to prefer custom-set-variables over setq when dealing with custom variables (since they may have special setters and/or initialization code) applies to such variables, or whether it's exclusive to variables defined via defcustom.

If I should only bother with custom-set-variables when dealing with those defined by defcustom, how can I quickly make that determination? I was hoping to use custom-variable-p to programmatically check which variables I should set with custom-set-variables instead of setq, as I'd rather not have to dig through library source code for every variable I set.

  • 1
    You need not use custom-set-variables. Do you also consider it more of a bother to use customize-set-variable' or customize-set-value` than to use setq? (If so, why?) – Drew Apr 15 '17 at 0:19
  • @Drew In addition to its conciseness, I find setq to be more straightforward, especially when eval-when-compile starts getting involved. If I want to set multiple variables in one go, I need to construct quoted lists around my variables and values (which get eval'd). I haven't tried customize-set-variable, partly because it can't set multiple variables in one call. I didn't know about customize-set-value. I don't think I understand the difference between that and customize-set-variable, even after reading the docs. – ivan Apr 15 '17 at 0:42
  • I guess another question on my mind as I navigate the ins and outs of configuring Emacs is how reasonable is it to selectively ignore the best-practice and just use setq when dealing with custom variables that don't make use of special setters or initialization code (which seems to be most of them)? – ivan Apr 15 '17 at 0:47
  • 2
    It's generally fine to use setq if there are no setters etc. Most abuses of setq for options, which can get people into trouble, come from complete ignorance of the possibility of setters etc., not from knowing about them and not worrying about them. IOW, people just get in the habit of using setq, and most of the time they are not surprised. – Drew Apr 15 '17 at 3:52
  • @Drew It is fine that you discussed the depths of initialization of variables. But, now it is time for an accepted answer. I think your discussion of getters and setters is quite suitable for that. – Tobias Mar 20 '18 at 21:42
5

Use customize-set-variable or custom-set-variables, depending on just what you want/need.

Both of those make use of any initialization or setter functions (and so on) with which the user option might have been defined. See (elisp) Variable Definitions for information about these things.

If a given user option is defined without specifying any particular initialization, setter function then it is typically OK to simply use setq (or setf) to set its value.

But if the option has a setter function then do not use setq, as it will not do all that is needed.

Most abuses of setq for options, which can get people into trouble, come from complete ignorance of the possibility of setters etc., not from knowing about them and not worrying about them. IOW, people just get in the habit of using setq, and most of the time they are not surprised.

A setter function does something in addition to setting the option value. It may set the value in a special way, or it may also change other things in Emacs (e.g., the values of other variables) - things that need to go hand in hand with changing the option value.

| improve this answer | |
  • I don't believe customize-set-value makes use of setter functions. Called as (customize-set-value VARIABLE VALUE) it's no different to (set VARIABLE VALUE). – phils Mar 21 '18 at 1:52
  • ...which is unfortunate, to my mind. IIRC using customize-set-variable will result in updates to your custom-file, the same as using the customize UI would do. I feel there ought to be a setq analog for user options which doesn't affect the custom-file, but otherwise plays nicely with customize. (I recall thinking that customize-set-value was exactly that, until I looked at the code.) – phils Mar 21 '18 at 2:06
  • @phils: Yeah, I kinda remembered it like that too, but was too lazy to confirm (almost hesitated to do so, but was in a hurry). I've made that correction, so as not to lead people astray. Thx. – Drew Mar 21 '18 at 3:40
0

I've recently also been dealing with this problem and have come up with a working solution.

I initially uploaded it to reddit, read custom-set-variables and custom file trampling for all the details.

I've got the solution in place in my emacs configuration and it is working perfectly. Simply installing a package from melpa no longer clobbers my custom file with the custom-set-variable declarations from my config file.

It is "a" solution, not "the" solution. It involves 2 vars and 2 pieces of advice. But the solution is, actually, rather "simple" and maybe can point the way for emacs maintainers to provide a similar mechanism out of the box in the future.

A brief summary of solution:

First, I summarize here the 3 ways to set custom variables properly, with a note on their state as regards this solution to the problem:

custom-set-variables: good
customize-set-variable: bad (explained later)
use-package :custom section: bad (this uses customize-set-variable)

My solution involves 2 variables (1 is optional, simply used to wrap advices in "if's" if you want to), and 2 pieces of advice.

The problem is that the custom system is actually quite good and works entirely as expected EXCEPT they give the user no option to tell emacs to "hey, don't write this out to the custom-file, I'm taking care of it in my config file."

Everything boils down to the following "when" statement (in the "custom-save-variables" function in cus-edit.el):

     ;; Is there anything customized about this variable?
 (when (or (and spec (eq (car spec) 'user))
    comment
    (and (null spec) (get symbol 'saved-value)))

If this evaluates to true then this custom var is written/dumped to custom-file. We can hitchhike on comment and construct a boolean with it. And so my solution uses comment to fix the entire problem.

Here are the two variable declarations, the first is optional, the second required. The first is used to construct an "if" in the advice if you so desire:

      (defvar juus/customize-pinned-use t
    "Do =NOT= write values of custom variables using `juus/customize-pinned-string'.

        When this value is t and `juus/customize-pinned-string' is a prefix or suffix of 
        the customization variables' comment then it will not be written out to `custom-file'.

        If you set this to nil, values will be written to the `custom-file' and will
        then override your settings in your configuration files since the `custom-file' is 
        loaded after your configuration settings. This can be quite confusing and annoying.

        So I recommend to not set this to nil unless you are testing or want to copy a formatted
        variable for use in your config file. In that case I highly recommend that you temporarily
        setq your `custom-file' to a dump file.")


  (defvar juus/customize-pinned-string ":PINNED:"
    "The string used to control writing to `custom-file'.

        See `juus/customize-pinned-use'.")

Now, if we send in a custom variable that has a comment that looks like:

":PINNED:" or ":PINNED: my comment" or "my comment :PINNED:"

...we have a boolean we can use.

To construct the "around" advice one must copy the entire custom-save-variables function from cus-edit.el. Wrap the advice in an "if" if you want to use the boolean var given above. Then change the "when" from above to:

   ;; Is there anything customized about this variable?
     (when (and
            (or (and spec (eq (car spec) 'user))
                comment
                (and (null spec) (get symbol 'saved-value)))
            (not
             (or
              (string-prefix-p juus/customize-pinned-string comment t)
              (string-suffix-p juus/customize-pinned-string comment t))))

and now add the advice: (advice-add 'custom-save-variables :around #'custom-save-variables--whatever-you-named-it)

Now in your config file use this construction for custom-set-variables (the 3rd nil parameter is problematic [see later] but can be set to t instead, and sometimes needs to be):

(custom-set-variables '(cust-var cust-val nil nil ":PINNED: my comment") )

You can construct a "helper" function to help automate this (an example in link above) but then you will need to use backtick and comma grammar.

This works completely for custom-set-variables, but not for customize-set-variable which is what use-package :custom uses. The reason it doesn't work is the "state" that it saves it in will not pass on the comment to the system. Without a comment we cannot filter it.

So another piece of advice is needed for use-package, which tells it to use custom-set-variables instead, and it will automate the :PINNED: for us, meaning that there will be no need to edit :custom sections.

The original controlling line in use-package-handler/:custom function in use-package is this:

 `(customize-set-variable (quote ,variable) ,value ,comment)))

Replaced with this fixes the issue and automatically prepends ":PINNED:" to the comment:

 (setq comment (concat juus/customize-pinned-string " " comment))
 `(custom-set-variables '(,variable ,value ,t ,nil ,comment))))

You'll see that I've used ,t as the 3rd param. This is because due to some internal logic in cus-edit.el involving "rogue" variables the state is set to something other than "Saved and set" which is required for the comment to be passed on, as far as I can see. The ,t was required on my computer to fix a small portion of :custom's that weren't responding correctly.

Here is the optional use-package advice. I've used "nth"'s a lot, but "let"'s could be used to make it more readable, the original params the function expects are in the document line:

(defun use-package-handler/:custom-juus/advice (orig-fun &rest args)
"ADVICE for `use-package-handler/:custom'.
  orig-fun expects: name _keyword args rest state"
(if (not juus/customize-pinned-use)
    (apply orig-fun args)
  (use-package-concat
   (mapcar
    #'(lambda (def)
        (let ((variable (nth 0 def))
              (value (nth 1 def))
              (comment (nth 2 def)))
          (unless (and comment (stringp comment))
            (setq comment (format "set by use-package '%s'" (nth 0 args))))
          (setq comment (concat juus/customize-pinned-string " " comment))
          `(custom-set-variables '(,variable ,value ,t ,nil ,comment))))
    (nth 2 args))
   (use-package-process-keywords (nth 0 args) (nth 3 args) (nth 4 args)))))

Then add the advice to the system: (advice-add 'use-package-handler/:custom :around #'use-package-handler/:custom-juus/advice)

CAVEATS:

The functions I copied come from emacs 26.3, so don't use my constructions literally maybe unless you are using 26.3, copy from your source code! As you can see this is a real "hands on" fix.

I have not (yet) looked at the custom-set-faces side of the custom-file because I've not used it yet. Maybe someone out there is interested to let me know if a similar problem exists for custom-set-faces and, if so, if a similar solution works???

Don't use customize-set-variable, it simply won't work in this system. Convert them to custom-set-variable declarations.

| improve this answer | |
  • This is a link-only answer (it just says that one can find an answer at that URL), so it risks being deleted. Please at least summarize the info at that URL. – Drew Apr 22 at 14:51
  • Oh, thank you. I will upload it here then, but it's a lot of information...? – juus Apr 22 at 18:35
  • You need not upload all of the info, or even all of the relevant info. The point is to try to provide an answer. It's fine to link elsewhere for details, but your "answer" here needs to at least summarize what the solution is. We're not trying to be pedantic. The point is to make Q&A here help users. – Drew Apr 22 at 18:39
  • I will be uploading a summary of the solution in the next few minutes. – juus Apr 22 at 19:28

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.