New macro setopt-local and function set-local (bug#80709)

'setopt-local' is the buffer local equivalent of 'setopt'.

Unify 'setopt', 'setopt-local', 'setq-local',
'buffer-local-set-state' with 'setq' to signal
'wrong-number-of-arguments'.

* lisp/cus-edit.el (setopt): Change error signal to
'wrong-number-of-arguments'.
(setopt-local): New macro.
(setopt--set-local): New function.
* lisp/subr.el (set-local): New function.
(setq-local, buffer-local-set-state): Signal
'wrong-number-of-arguments' rather than 'error'.
* doc/emacs/custom.texi (Examining): Document 'setopt-local'.
* etc/NEWS: Announce the new macro and function.
This commit is contained in:
Stéphane Marks
2026-04-01 06:58:18 -04:00
committed by Stefan Monnier
parent 5032b2167d
commit faf1932875
5 changed files with 98 additions and 10 deletions

View File

@@ -890,6 +890,34 @@ special setter functions, they will be run automatically when using
non-customizable variables, but this is less efficient than using
@code{setq}.
@findex setopt-local
There is also a buffer-local version of @code{setopt}, called
@code{setopt-local}, that you can use to set buffer specific values for
customizable options, for example, in mode hooks (@pxref{Hooks}).
This works the same as @code{setq-local}, but if the variable has any
special setter functions, they will be run automatically when using
@code{setopt-local}. You can also use @code{setopt-local} on other,
non-customizable variables, but this is less efficient than using
@code{setq-local}.
If you want to change the value of a customizable variable only in
your current buffer, you can use the @code{setopt-local} macro. For
instance:
@example
M-: (setopt-local fill-column 75) @key{RET}
@end example
@noindent
Or, if you want to do this in your initialization file, use the
following inside a mode hook so this variable will be automatically
customized in buffers of that mode (@pxref{Hooks}):
@example
(setopt-local fill-column 75)
@end example
@node Hooks
@subsection Hooks
@cindex hook

View File

@@ -372,12 +372,15 @@ added by calls to @code{custom-add-frequent-value} (see below).
@item :set @var{setfunction}
Specify @var{setfunction} as the way to change the value of this
option when using the Customize interface. The function
@var{setfunction} should take two arguments, a symbol (the option
name) and the new value, and should do whatever is necessary to update
@var{setfunction} should take two or three arguments, a symbol (the option
name), the new value, and an optional @var{buffer-local} indicator.
@var{setfunction} should do whatever is necessary to update
the value properly for this option (which may not mean simply setting
the option as a Lisp variable); preferably, though, it should not
modify its value argument destructively. The default for
@var{setfunction} is @code{set-default-toplevel-value}.
modify its value argument destructively. If optional @var{buffer-local}
is non-nil, the new value should be set buffer locally and not affect its
global or default values. The default for @var{setfunction} is
@code{set-default-toplevel-value}.
If defined, @var{setfunction} will also be called when evaluating a
@code{defcustom} form with @kbd{C-M-x} in Emacs Lisp mode and when the
@@ -387,7 +390,7 @@ If defined, @var{setfunction} will also be called when evaluating a
If you specify this keyword, the variable's documentation string
should describe how to do the same job in hand-written Lisp code,
either by invoking @var{setfunction} directly or by using
@code{setopt}.
@code{setopt} or @code{setopt-local}.
@kindex get@r{, @code{defcustom} keyword}
@item :get @var{getfunction}

View File

@@ -4152,6 +4152,17 @@ change it globally with:
---
*** Loading a file displays a warning if there is no 'lexical-binding' cookie.
---
** New function 'set-local'.
This is the buffer local equivalent of the function 'set'.
+++
** New macro 'setopt-local'.
This is the buffer local version of 'setopt' for user options rather
than plain variables and uses 'custom-set'/'set-local' to set variable
values. A new argument, BUFFER-LOCAL, is passed to 'custom-set'
functions to indicate the buffer local context.
+++
** New macros 'incf' and 'decf'.
They increment or decrement the value stored in a variable (a symbol),

View File

@@ -1084,7 +1084,7 @@ even if it doesn't match the type.)
\(fn [VARIABLE VALUE]...)"
(declare (debug setq))
(unless (evenp (length pairs))
(error "PAIRS must have an even number of variable/value members"))
(signal 'wrong-number-of-arguments (list 'setopt (length pairs))))
(let ((expr nil))
(while pairs
(unless (symbolp (car pairs))
@@ -1100,11 +1100,53 @@ even if it doesn't match the type.)
;; Check that the type is correct.
(when-let* ((type (get variable 'custom-type)))
(unless (widget-apply (widget-convert type) :match value)
(warn "Value `%S' for variable `%s' does not match its type \"%s\""
value variable type)))
(warn "Value does not match %S's type `%S': %S" variable type value)))
(put variable 'custom-check-value (list value))
(funcall (or (get variable 'custom-set) #'set-default) variable value))
;;;###autoload
(defmacro setopt-local (&rest pairs)
"Set buffer local VARIABLE/VALUE pairs, and return the final VALUE.
This is like `setq-local', but is meant for user options instead of
plain variables. This means that `setopt-local' will execute any
`custom-set' form associated with VARIABLE. Unlike `setopt',
`setopt-local' does not affect a user option's global value.
Note that `setopt-local' will emit a warning if the type of a VALUE does
not match the type of the corresponding VARIABLE as declared by
`defcustom'. (VARIABLE will be assigned the value even if it doesn't
match the type.)
Signal an error if a `custom-set' form does not support the
`buffer-local' argument.
\(fn [VARIABLE VALUE]...)"
(declare (debug setq))
(unless (evenp (length pairs))
(signal 'wrong-number-of-arguments (list 'setopt-local (length pairs))))
(let ((expr nil))
(while pairs
(unless (symbolp (car pairs))
(error "Attempting to set a non-symbol: %s" (car pairs)))
(push `(setopt--set-local ',(car pairs) ,(cadr pairs))
expr)
(setq pairs (cddr pairs)))
(macroexp-progn (nreverse expr))))
;;;###autoload
(defun setopt--set-local (variable value)
(custom-load-symbol variable)
;; Check that the type is correct.
(when-let* ((type (get variable 'custom-type)))
(unless (widget-apply (widget-convert type) :match value)
(warn "Value does not match %S's type `%S': %S" variable type value)))
(condition-case _
(funcall (or (get variable 'custom-set)
(lambda (x v &optional _) (set-local x v)))
variable value 'buffer-local)
(wrong-number-of-arguments
(error "The setter of %S does not support setopt-local" variable))))
;;;###autoload
(defun customize-save-variable (variable value &optional comment)
"Set the default for VARIABLE to VALUE, and save it for future sessions.

View File

@@ -160,6 +160,10 @@ of previous VARs.
(push `(set-default ',(pop args) ,(pop args)) exps))
`(progn . ,(nreverse exps))))
(defun set-local (variable value)
"Make VARIABLE buffer local and set it to VALUE."
(set (make-local-variable variable) value))
(defmacro setq-local (&rest pairs)
"Make each VARIABLE local to current buffer and set it to corresponding VALUE.
@@ -181,7 +185,7 @@ In some corner cases you may need to resort to
\(fn [VARIABLE VALUE]...)"
(declare (debug setq))
(unless (evenp (length pairs))
(error "PAIRS must have an even number of variable/value members"))
(signal 'wrong-number-of-arguments (list 'setq-local (length pairs))))
(let ((expr nil))
(while pairs
(unless (symbolp (car pairs))
@@ -229,7 +233,7 @@ in order to restore the state of the local variables set via this macro.
\(fn [VARIABLE VALUE]...)"
(declare (debug setq))
(unless (evenp (length pairs))
(error "PAIRS must have an even number of variable/value members"))
(signal 'wrong-number-of-arguments (list 'buffer-local-set-state (length pairs))))
(let ((vars nil)
(tmp pairs))
(while tmp (push (car tmp) vars) (setq tmp (cddr tmp)))