Emacs customizable variable with dynamic set of possible values

Viewed 37

I want to define a customizable variable in Emacs Lisp whose possible values are restricted to a certain list, but the list should be dynamically created by a function each time the user opens the customization buffer. Thus, something similar to

(defcustom my-variable
  [...]
  :type '(choice [...])

but choice seems to support only a static set of values. What I want is something like

(defcustom my-variable
  [...]
  :type '(choice my-function)

where my-function is a function that returns the list of possible values, and is evaluated each time the customization buffer is created.

Is that possible?

2 Answers
(defun my-function ()
  "..."
  '(42 "a" b (x y) "c"))

(defcustom foo 42
  "..."
  :group 'convenience
  :type '(restricted-sexp
           :match-alternatives ((lambda (x)
                                  (car (member x (my-function)))))))

Function my-function could provide a different list of possibilities each time (or whenever, based on whatever).

This simple example doesn't provide much in the way of feedback (and no doc strings).

But the idea is this: restricted-sexp is the most general defstruct type-defining construct. This is what the Elisp manual, node Composite Types says:

(restricted-sexp :match-alternatives CRITERIA) This is the most general composite type construct. The value may be any Lisp object that satisfies one of CRITERIA. CRITERIA should be a list, and each element should be one of these possibilities:

  • A predicate—that is, a function of one argument that returns either nil or non-nil according to the argument. Using a predicate in the list says that objects for which the predicate returns non-nil are acceptable.

  • A quoted constant—that is, 'OBJECT. This sort of element in the list says that OBJECT itself is an acceptable value.

    For example,

       (restricted-sexp :match-alternatives
                        (integerp 't 'nil))
    

    allows integers, t and nil as legitimate values.

    The customization buffer shows all legitimate values using their read syntax, and the user edits them textually.

In the example I gave, the list of CRITERIA is a singleton, with just this function:

(lambda (x) (car (member x (my-function))))

That function ensures that the value you use for the option is a member of the list that function my-function returns. (And it makes the value be the car of what member returns, i.e., the particular member of the list that the user enters.

When using M-x customize-option you won't be able to set a new value if it isn't one of the values returned by my-function. Instead of seeing feedback that the value was set to what you entered, you continue to see this help text:

EDITED, shown value does not take effect until you set or save it.

If you provide a valid value then you get confirmation that the value was set.


Admittedly, what you might really want is to provide for completion etc. in the editing field. For that, you would need to use/define an appropriate widget, because Customize, out of the box, provides for completion only for a limited set of defcustom type constructs (files, colors, etc.). You can see which ones by searching for completion starting in node Simple Types.


restricted-sexp is really a wonderful thing. It should be improved by Emacs to be more useful. But even as it is it should be used more, to provide more-specific type checking. Too many users defining defcustoms are a bit lazy, IMO, not bothering to provide a specific type definition even when that might be as simple as using choice. (Too many just use a type of sexp.)

I found a solution: I defined my-variable with type choice and only one default entry, then I wrote a function that updates the choice entries dynamically, and added that function to 'Custom-mode-hook. This hook is called each time Custom mode is entered. Thus, whenever the user is about to customize my-variable, the choice entries are updated before. Here is a sketch of code:

(defcustom my-variable
  "None"
  :type '(choice (const :tag "None" "None")))

(defun my-function ()
  ;; Function that provides the possible values of my-variable
  )

(defun my-update-variable-allowed-values ()
  (let* ( (choices '((const :tag "None" "None"))) )
    (dolist (val (my-function))
      (setq choices (cons (list 'const ':tag val val) choices)))
    (setq choices (cons 'choice choices))
    (put 'my-variable 'custom-type choices)))

(add-hook 'Custom-mode-hook 'my-update-variable-allowed-values)

Related