From b13356487fc3eaf82bfe51bee24ddf70c27c5834 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Fri, 6 May 2022 13:10:45 +0200 Subject: [PATCH] Add new helper macros for minor modes to restore variables * doc/lispref/modes.texi (Defining Minor Modes): Document it. * lisp/emacs-lisp/easy-mmode.el (buffer-local-set-state): New macro. (buffer-local-set-state--get): Helper function. (buffer-local-restore-state): New function. * lisp/textmodes/word-wrap-mode.el (word-wrap-whitespace-mode): Use it to simplify code. --- doc/lispref/modes.texi | 9 +++++++ etc/NEWS | 6 +++++ lisp/emacs-lisp/easy-mmode.el | 33 ++++++++++++++++++++++++ lisp/textmodes/word-wrap-mode.el | 25 +++++------------- test/lisp/emacs-lisp/easy-mmode-tests.el | 12 ++++++++- 5 files changed, 66 insertions(+), 19 deletions(-) diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index ff09a787490..bfd9724173b 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi @@ -1912,6 +1912,15 @@ This means ``use in modes derived from @code{text-mode}, but nowhere else''. (There's an implicit @code{nil} element at the end.) @end defmac +@defmac buffer-local-set-state variable value... +Minor modes often set buffer-local variables that alters some features +in Emacs. When a minor mode is switched off, the mode is expected to +restore the previous state of these variables. This convenience macro +helps with doing that: It works much like @code{setq-local}, but +returns an object that can be used to restore these values back to +their previous values/states (with the +@code{buffer-local-restore-state} function). +@end defmac @node Mode Line Format @section Mode Line Format diff --git a/etc/NEWS b/etc/NEWS index 6637eda00c8..fa7e2c4dcca 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1636,6 +1636,12 @@ functions. * Lisp Changes in Emacs 29.1 ++++ +** New macro 'buffer-local-set-state'. +This is a helper macro to be used by minor modes that wish to restore +buffer-local variables back to their original states when the mode is +switched off. + --- ** New macro 'with-buffer-unmodified-if-unchanged'. If the buffer is marked as unmodified, and code does modifications diff --git a/lisp/emacs-lisp/easy-mmode.el b/lisp/emacs-lisp/easy-mmode.el index 8a76eaf58cf..33c0472ea87 100644 --- a/lisp/emacs-lisp/easy-mmode.el +++ b/lisp/emacs-lisp/easy-mmode.el @@ -825,6 +825,39 @@ Interactively, COUNT is the prefix numeric argument, and defaults to 1." ,@body)) (put ',prev-sym 'definition-name ',base)))) + +(defmacro buffer-local-set-state (&rest pairs) + "Like `setq-local', but return an object that allows restoring previous state. +Use `buffer-local-restore-state' on the returned object to +restore the state. + +\(fn [VARIABLE VALUE]...)" + (declare (debug setq)) + (unless (zerop (mod (length pairs) 2)) + (error "PAIRS must have an even number of variable/value members")) + `(prog1 + (buffer-local-set-state--get ',pairs) + (setq-local ,@pairs))) + +(defun buffer-local-set-state--get (pairs) + (let ((states nil)) + (while pairs + (push (list (car pairs) + (and (boundp (car pairs)) + (local-variable-p (car pairs))) + (and (boundp (car pairs)) + (symbol-value (car pairs)))) + states) + (setq pairs (cddr pairs))) + (nreverse states))) + +(defun buffer-local-restore-state (states) + "Restore buffer local variable values in STATES. +STATES is an object returned by `buffer-local-set-state'." + (pcase-dolist (`(,variable ,local ,value) states) + (if local + (set variable value) + (kill-local-variable variable)))) (provide 'easy-mmode) diff --git a/lisp/textmodes/word-wrap-mode.el b/lisp/textmodes/word-wrap-mode.el index 1459a3395ca..c354fc773a7 100644 --- a/lisp/textmodes/word-wrap-mode.el +++ b/lisp/textmodes/word-wrap-mode.el @@ -60,26 +60,15 @@ The characters to break on are defined by `word-wrap-whitespace-characters'." (if word-wrap-whitespace-mode (progn (setq-local word-wrap-mode--previous-state - (list (category-table) - (local-variable-p 'word-wrap-by-category) - word-wrap-by-category - (local-variable-p 'word-wrap) - word-wrap)) + (cons (category-table) + (buffer-local-set-state + word-wrap-by-category t + word-wrap t))) (set-category-table (copy-category-table)) (dolist (char word-wrap-whitespace-characters) - (modify-category-entry char ?|)) - (setq-local word-wrap-by-category t - word-wrap t)) - (pcase-let ((`(,table ,lby-cat ,by-cat - ,lwrap ,wrap) - word-wrap-mode--previous-state)) - (if lby-cat - (setq-local word-wrap-by-category by-cat) - (kill-local-variable 'word-wrap-by-category)) - (if lwrap - (setq-local word-wrap wrap) - (kill-local-variable 'word-wrap)) - (set-category-table table)))) + (modify-category-entry char ?|))) + (set-category-table (car word-wrap-mode--previous-state)) + (buffer-local-restore-state (cdr word-wrap-mode--previous-state)))) ;;;###autoload (define-globalized-minor-mode global-word-wrap-whitespace-mode diff --git a/test/lisp/emacs-lisp/easy-mmode-tests.el b/test/lisp/emacs-lisp/easy-mmode-tests.el index 0a3bbb189ba..697bf6c2152 100644 --- a/test/lisp/emacs-lisp/easy-mmode-tests.el +++ b/test/lisp/emacs-lisp/easy-mmode-tests.el @@ -60,6 +60,16 @@ (easy-mmode-test-mode 'toggle) (should (eq easy-mmode-test-mode t)))) -(provide 'easy-mmode-tests) +(ert-deftest test-local-set-state () + (setq global 1) + (with-temp-buffer + (setq-local local 2) + (let ((state (buffer-local-set-state global 10 + local 20 + unexist 30))) + (buffer-local-restore-state state) + (should (= global 1)) + (should (= local 2)) + (should-not (boundp 'unexist))))) ;;; easy-mmode-tests.el ends here -- 2.39.5