diff --git a/doc/emacs/custom.texi b/doc/emacs/custom.texi index b73bf6b929a..77fff25b7b2 100644 --- a/doc/emacs/custom.texi +++ b/doc/emacs/custom.texi @@ -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 diff --git a/doc/lispref/customize.texi b/doc/lispref/customize.texi index 364edf63031..705af15e4e2 100644 --- a/doc/lispref/customize.texi +++ b/doc/lispref/customize.texi @@ -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} diff --git a/etc/NEWS b/etc/NEWS index 335778d248a..af2549b0154 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -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), diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el index 52677f435ee..87d8ecade54 100644 --- a/lisp/cus-edit.el +++ b/lisp/cus-edit.el @@ -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. diff --git a/lisp/subr.el b/lisp/subr.el index 7a5412d3fb7..b0e04bc5f99 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -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)))