I've just discovered :custom and am wondering the advantages of it. A big problem I see is that when using :custom, a call to customize-variable on any configured variable followed by "Save for future session" will add all configured variables to the main custom-set-variables. This makes all these variables configured in 2 places now.
The only advantage I see is that the new syntax is a bit shorter than using setq.
Is there something else?
It's really just an alternative to people calling custom-set-variable in their :init block. It's not for people (like myself) who use the M-x customize interface, which will have the behavior you mentioned.
Note that one thing we could try to do is to make initsplit smart enough to put customizations into use-package blocks that it finds in init.el, rather than into whole files. @dabrahams might have some thoughts on this.
Here is some experimental support code:
;; (use-package abbrev
;; :custom-re "\\`abbrev-" ; this is inferred as such if not specified
;; :custom
;; (abbrev-file-name "~/.emacs.d/abbrevs")
;; :hook
;; (prog-mode . abbrev-mode))
(defcustom use-package-init-files (list user-init-file)
"*List of user files containing use-package declarations."
:type '(repeat (file :must-match t))
:group 'use-package)
(defun use-package-custom-save-all ()
(dolist (file use-package-init-files)
(with-current-buffer (find-file-noselect file t)
(use-package-replace-custom-variables)
(save-buffer))))
(defun use-package-to-string (decl &optional newlined-keywords)
(with-temp-buffer
(let ((standard-output (current-buffer)))
(prin1 decl)
(goto-char (point-min))
(while (re-search-forward "\\(\\s-+\\):\\(\\S-+\\)\\(\\s-+\\)" nil t)
(let ((key (match-string 2)))
(replace-match "\n" nil t nil 1)
(if (member key newlined-keywords)
(replace-match "\n" nil t nil 3)))))
(emacs-lisp-mode)
(indent-region (point-min) (point-max))
(buffer-string)))
(defun use-package-replace-custom-variables ()
(interactive)
(save-excursion
(goto-char (point-min))
(while (re-search-forward
"(use-package\\s-+\\(\\(\\s_\\|\\sw\\)+\\)" nil t)
(let* ((beg (match-beginning 0))
(name (match-string 1))
(end (save-excursion
(goto-char (match-beginning 0))
(forward-sexp)
(point)))
(decl (read (copy-marker beg)))
(regexp (or (plist-get decl :custom-re)
(format "\\`%s-" name))))
(when (or regexp (plist-member decl :custom))
(goto-char beg)
(if (re-search-forward ":custom\\(\\s-+\\|\n\\|(\\)" end t)
(progn
(goto-char (match-beginning 0))
(forward-sexp)
(let ((cus-beg (point)))
(while (not (or (eobp)
(looking-at
"\\()\\|\\(\\s-\\|\n\\)*:[a-z-]+\\)")))
(forward-sexp))
(delete-region cus-beg (point))))
(forward-sexp)
(backward-char))
(insert (use-package--custom-save-variables regexp)))))))
(defun use-package--custom-save-variables (regexp)
"Generate a :custom declaration for variables matching REGEXP."
(with-temp-buffer
(let ((standard-output (current-buffer))
(saved-list (make-list 1 0))
sort-fold-case)
;; First create a sorted list of saved variables.
(mapatoms
(lambda (symbol)
(if (and (get symbol 'saved-value)
;; ignore theme values
(or (null (get symbol 'theme-value))
(eq 'user (caar (get symbol 'theme-value))))
(string-match regexp (symbol-name symbol)))
(nconc saved-list (list symbol)))))
(setq saved-list (sort (cdr saved-list) 'string<))
(princ "\n")
(dolist (symbol saved-list)
(let ((spec (car-safe (get symbol 'theme-value)))
(value (get symbol 'saved-value))
(requests (get symbol 'custom-requests))
(now (and (not (custom-variable-p symbol))
(or (boundp symbol)
(eq (get symbol 'force-value)
'rogue))))
(comment (get symbol 'saved-variable-comment)))
;; Check REQUESTS for validity.
(dolist (request requests)
(when (and (symbolp request) (not (featurep request)))
(message "Unknown requested feature: %s" request)
(setq requests (delq request requests))))
;; Is there anything customized about this variable?
(when (or (and spec (eq (car spec) 'user))
comment
(and (null spec) (get symbol 'saved-value)))
;; Output an element for this variable.
;; It has the form (SYMBOL VALUE-FORM NOW REQUESTS COMMENT).
;; SYMBOL is the variable name.
;; VALUE-FORM is an expression to return the customized value.
;; NOW if non-nil means always set the variable immediately
;; when the customizations are reloaded. This is used
;; for rogue variables
;; REQUESTS is a list of packages to load before setting the
;; variable. Each element of it will be passed to `require'.
;; COMMENT is whatever comment the user has specified
;; with the customize facility.
(unless (bolp)
(princ "\n"))
(princ " (")
(prin1 symbol)
(princ " ")
(let ((val (prin1-to-string (car value))))
(if (< (length val) 60)
(insert val)
(newline-and-indent)
(let ((beginning-of-val (point)))
(insert val)
(save-excursion
(goto-char beginning-of-val)
(indent-pp-sexp 1)))))
(when (or now requests comment)
(princ " ")
(prin1 now)
(when (or requests comment)
(princ " ")
(prin1 requests)
(when comment
(princ " ")
(prin1 comment))))
(princ ")")))))
(buffer-string)))
If you evaluate this code, and then evaluate:
(require 'cus-edit)
(defalias 'custom-save-all 'use-package-custom-save-all)
Then when you save from a customization buffer, all of the :custom blocks in all of your use-package declarations will be updated. CAVEAT: If you customize a variable for a package that is not declared by use-package, or that did not have a :custom or :custom-re block before hand, your setting will not persist after Emacs is restarted.
You can see what effect this will have manually by running M-x use-package-replace-custom-variables in your init.el file.
Note that for the time being I'm not going to install this into use-package.el, because it's highly untested and likely will always do too much or too little, but if you have suggestions on how it might be made more production-worthy, please let me know.
For what it's worth, there are people who used to have custom-set-variables in their :init or :config blocks. I'm one of them and the new :custom keyword is much appreciated.
As for the problem with customization duplicating all of those variables: (1) this isn't just a problem with :custom: even if you didn't use use-package at all you might want to organize your init.el by putting custom-set-variables in different blocks (or even files) for configuring different packages; (2) it has a simple if drastic solution: I use (setq custom-file "~/.emacs.d/garbage.el") and then never load garbage.el. I do use the customize GUI for temporary changes, but instead of "Save for future sessions", I'll open up my init.el and put the variable in the logical place for it.
I think of customize and me just having a fundamental disagreement about organization: customize feels tidy = alphabetical, I fell tidy = "grouped with other related configuration even if that other configuration is not achieved by customize-set-variable".
I haven't tested John's code above, but it's certainly the right idea. Eventually I could get rid of garbage.el!
initsplit.el makes it easy to split customization values across multiple files. I use this to separate Gnus, from Org, from all other variables.
I've been wrestling with the best way forward for exactly this problem for the last week or so. One thing I found interesting was @dabrahams use of elhome (init-split derivative) with use-package.
Here customizations are all kept separate from use-package, which is frustrating, but, I believe, resolves the duplication issue, at the cost of (some) tidiness.
https://github.com/dabrahams/dotemacs
My aim is to try to marry use-package, elhome (https://github.com/demyanrogozhin/elhome), and perhaps a package manager that supports use-package like straight (https://github.com/raxod502/straight.el)
Ideally I want the ability to use custom for some things, and then have other custom settings (properly set using custom functions) set over numerous files without having them duplicated.
If I achieve something stable I'll post a link to the result.
I would also like to find a better way here. I couldn't find a solution., looking through docs and manuals.
Could something be added to the Emacs core, like a special symbol property to say "for these customized variables, skip recording them in the custom file"? Like a custom saving function?
Here's a super-custom solution to work around this issue, indicating there's some interest already: https://www.reddit.com/r/emacs/comments/g46sg2/a_solution_to_the_agony_of_customsetvariables_and/
Most helpful comment
Here is some experimental support code:
If you evaluate this code, and then evaluate:
Then when you save from a customization buffer, all of the
:customblocks in all of youruse-packagedeclarations will be updated. CAVEAT: If you customize a variable for a package that is not declared byuse-package, or that did not have a:customor:custom-reblock before hand, your setting will not persist after Emacs is restarted.