From e5204b0807c987d4a7b1a9441158b80a3407b675 Mon Sep 17 00:00:00 2001 From: Daniel Mendler Date: Fri, 7 Mar 2025 16:58:10 +0100 Subject: compat-31: New macro with-work-buffer --- NEWS.org | 1 + compat-31.el | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ compat-tests.el | 9 +++++++++ compat.texi | 7 +++++++ 4 files changed, 71 insertions(+) diff --git a/NEWS.org b/NEWS.org index 31b180e..10f4563 100644 --- a/NEWS.org +++ b/NEWS.org @@ -9,6 +9,7 @@ - compat-31: New macros =incf= and =decf=. - compat-31: New function =color-blend=. - compat-31: New function =completion-table-with-metadata=. +- compat-31: New macro =with-work-buffer=. - Drop support for Emacs 24.x. Emacs 25.1 is required now. In case Emacs 24.x support is still needed, Compat 30 can be used. diff --git a/compat-31.el b/compat-31.el index 59ea412..6b23b4c 100644 --- a/compat-31.el +++ b/compat-31.el @@ -94,5 +94,59 @@ METADATA should be an alist of completion metadata. See `(metadata . ,metadata) (complete-with-action action table string pred)))) +;;;; Defined in subr-x.el + +(compat-defvar work-buffer--list nil ;; + "List of work buffers.") + +(compat-defvar work-buffer-limit 10 ;; + "Maximum number of reusable work buffers. +When this limit is exceeded, newly allocated work buffers are +automatically killed, which means that in a such case +`with-work-buffer' becomes equivalent to `with-temp-buffer'.") + +(compat-defun work-buffer--get () ;; + "Get a work buffer." + (let ((buffer (pop work-buffer--list))) + (if (buffer-live-p buffer) + buffer + ;; `generate-new-buffer' and `get-buffer-create' accept an + ;; INHIBIT-BUFFER-HOOKS argument on Emacs 28 and newer. + ;; Unfortunately it is hard or not possible to port this back. See + ;; issue . + (static-if (>= emacs-major-version 28) + (generate-new-buffer " *work*" t) + (generate-new-buffer " *work*"))))) + +(compat-defun work-buffer--release (buffer) ;; + "Release work BUFFER." + (if (buffer-live-p buffer) + (with-current-buffer buffer + (let ((inhibit-read-only t) deactivate-mark) + (erase-buffer)) + (delete-all-overlays) + (let (change-major-mode-hook) + ;; TODO Port back the KILL-PERMANENT argument from Emacs 29 + ;; Right now permanent variables are not killed. + (static-if (>= emacs-major-version 29) + (kill-all-local-variables t) + (kill-all-local-variables))) + (push buffer work-buffer--list))) + (when (> (length work-buffer--list) work-buffer-limit) + (mapc #'kill-buffer (nthcdr work-buffer-limit work-buffer--list)) + (setq work-buffer--list (ntake work-buffer-limit work-buffer--list)))) + +(compat-defmacro with-work-buffer (&rest body) ;; + "Create a work buffer, and evaluate BODY there like `progn'. +Like `with-temp-buffer', but reuse an already created temporary +buffer when possible, instead of creating a new one on each call." + (declare (indent 0) (debug t)) + (let ((work-buffer (make-symbol "work-buffer"))) + `(let ((,work-buffer (work-buffer--get))) + (with-current-buffer ,work-buffer + (unwind-protect + (progn ,@body) + (work-buffer--release ,work-buffer)))))) + (provide 'compat-31) ;;; compat-31.el ends here diff --git a/compat-tests.el b/compat-tests.el index 5b12baf..6705620 100644 --- a/compat-tests.el +++ b/compat-tests.el @@ -1088,6 +1088,15 @@ (with-current-buffer inner (should-not (buffer-modified-p)))))))) +(ert-deftest compat-with-work-buffer () + (with-work-buffer + (should (string-match-p "\\` \\*work\\*" (buffer-name))) + (let ((outer (current-buffer))) + (with-work-buffer + (should (string-match-p "\\` \\*work\\*" (buffer-name))) + (let ((inner (current-buffer))) + (should-not (eq outer inner))))))) + (ert-deftest compat-insert-into-buffer () ;; Without optional compat--arguments (with-temp-buffer diff --git a/compat.texi b/compat.texi index 16ae3a9..c1b7701 100644 --- a/compat.texi +++ b/compat.texi @@ -3427,6 +3427,13 @@ older than 31.1. Note that due to upstream changes, it might happen that there will be the need for changes, so use these functions with care. +@c based on lisp/emacs-lisp/subr-x.el +@defmac with-work-buffer &rest body +Create a work buffer, and evaluate @var{body} there like @code{progn}. +Like @code{with-temp-buffer}, but reuse an already created temporary +buffer when possible, instead of creating a new one on each call. +@end defmac + @c copied from lispref/numbers.texi @defun plusp number This predicate tests whether its argument is positive, and returns -- cgit v1.0