Subject: [PATCH] simple.el (delete-trailing-whitespace-mode): New minor mode
From: Stefan Monnier <[email-masked]>
To: [email-masked]
Date: Mon, 05 May 2025 12:01:22 -0400
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

While

    (add-hook 'before-save-hook #'delete-trailing-whitespace nil t)

is simple enough to put into one's init file, it's a bit rough around
the edges (e.g. for read-only buffers, or interaction with
editorconfig).  So I suggest we add a simple
`delete-trailing-whitespace-mode` minor mode.

Comments?  Objections?

        Stefan

 In GNU Emacs 31.0.50 (build 1, i686-pc-linux-gnu, GTK+ Version 3.24.49,
 cairo version 1.18.4) of 2025-05-01 built on alfajor
Repository revision: 3555aa795435c6c150e9291386cb776acd35e564
Repository branch: work
Windowing system distributor 'The X.Org Foundation', version 11.0.12101016
System Description: Debian GNU/Linux trixie/sid

Configured using:
 'configure -C --enable-checking --enable-check-lisp-object-type --with-modules --with-cairo --with-tiff=ifavailable
 'CFLAGS=-Wall -g3 -Og -Wno-pointer-sign'
 PKG_CONFIG_PATH=/home/monnier/lib/pkgconfig'

From 5740c9b27883de86888df8d201e5daed97d7dd0b Mon Sep 17 00:00:00 2001
From: Stefan Monnier <[email-masked]>
Date: Mon, 5 May 2025 11:55:29 -0400
Subject: [PATCH] simple.el (delete-trailing-whitespace-mode): New minor mode

Partly motivated by bug#78097.

* lisp/simple.el (delete-trailing-whitespace-if-possible): New function.
(delete-trailing-whitespace-mode): New minor mode.

* lisp/editorconfig.el (editorconfig-trim-whitespaces-mode):
Change default to `delete-trailing-whitespace-mode`.
(editorconfig--get-trailing-ws): Simplify accordingly.
(editorconfig--add-hook-safe-p): Delete function.  Don't touch
`safe-local-eval-function` any more.
---
 etc/NEWS             |  4 ++++
 lisp/editorconfig.el | 38 +++++++++++---------------------------
 lisp/simple.el       | 15 +++++++++++++++
 3 files changed, 30 insertions(+), 27 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 146cdedaa43..8efb6e6ae07 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2034,6 +2034,10 @@ the file name, using the minibuffer's "future history".
 
 * New Modes and Packages in Emacs 31.1
 
+** New minor mode 'delete-trailing-whitespace-mode'.
+A simple buffer-local mode that runs 'delete-trailing-whitespace'
+before saving the buffer.
+
 ** New major mode 'conf-npmrc-mode'.
 A major mode based on 'conf-mode' for editing ".npmrc" files.
 
diff --git a/lisp/editorconfig.el b/lisp/editorconfig.el
index 7cdd79fa3b0..bfc8ef46ed3 100644
--- a/lisp/editorconfig.el
+++ b/lisp/editorconfig.el
@@ -277,13 +277,13 @@ editorconfig-indentation-alist
   :version "30.1"
   :risky t)
 
-(defcustom editorconfig-trim-whitespaces-mode nil
+(defcustom editorconfig-trim-whitespaces-mode #'delete-trailing-whitespace-mode
   "Buffer local minor-mode to use to trim trailing whitespaces.
 
 If set, enable that mode when `trim_trailing_whitespace` is set to true.
 Otherwise, use `delete-trailing-whitespace'."
   :version "30.1"
-  :type 'symbol)
+  :type 'function)
 
 (defvar-local editorconfig-properties-hash nil
   "Hash object of EditorConfig properties that was enabled for current buffer.
@@ -542,33 +542,17 @@ editorconfig--delete-trailing-whitespace
   "Call `delete-trailing-whitespace' unless the buffer is read-only."
   (unless buffer-read-only (delete-trailing-whitespace)))
 
-;; Arrange for our (eval . (add-hook ...)) "local var" to be considered safe.
-(defun editorconfig--add-hook-safe-p (exp)
-  (equal exp '(add-hook 'before-save-hook
-                        #'editorconfig--delete-trailing-whitespace nil t)))
-(let ((predicates (get 'add-hook 'safe-local-eval-function)))
-  (when (functionp predicates)
-    (setq predicates (list predicates)))
-  (unless (memq #'editorconfig--add-hook-safe-p predicates)
-    (put 'add-hook 'safe-local-eval-function #'editorconfig--add-hook-safe-p)))
-
 (defun editorconfig--get-trailing-ws (props)
   "Get vars to trim of trailing whitespace according to PROPS."
-  (pcase (gethash 'trim_trailing_whitespace props)
-    ("true"
-     `((eval
-        . ,(if editorconfig-trim-whitespaces-mode
-               `(,editorconfig-trim-whitespaces-mode 1)
-             '(add-hook 'before-save-hook
-                        #'editorconfig--delete-trailing-whitespace nil t)))))
-    ("false"
-     ;; Just do it right away rather than return a (VAR . VAL), which
-     ;; would be probably more trouble than it's worth.
-     (when editorconfig-trim-whitespaces-mode
-       (funcall editorconfig-trim-whitespaces-mode 0))
-     (remove-hook 'before-save-hook
-                  #'editorconfig--delete-trailing-whitespace t)
-     nil)))
+  (let ((fun (or editorconfig-trim-whitespaces-mode
+                 #'delete-trailing-whitespace-mode)))
+    (pcase (gethash 'trim_trailing_whitespace props)
+      ("true" `((eval . (,fun 1))))
+      ("false"
+       ;; Just do it right away rather than return a (VAR . VAL), which
+       ;; would be probably more trouble than it's worth.
+       (funcall fun 0)
+       nil))))
 
 (defun editorconfig--get-line-length (props)
   "Get the max line length (`fill-column') to PROPS."
diff --git a/lisp/simple.el b/lisp/simple.el
index 486092de2c8..f686907ad68 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -888,6 +888,21 @@ delete-trailing-whitespace
   ;; Return nil for the benefit of `write-file-functions'.
   nil)
 
+(defun delete-trailing-whitespace-if-possible ()
+  "Call `delete-trailing-whitespace' unless the buffer is read-only."
+  (unless buffer-read-only (delete-trailing-whitespace)))
+
+(define-minor-mode delete-trailing-whitespace-mode
+  "Delete trailing whitespace before saving the current buffer."
+  :global nil
+  (cond
+   (delete-trailing-whitespace-mode
+    (add-hook 'before-save-hook
+              #'delete-trailing-whitespace-if-possible nil t))
+   (t
+    (remove-hook 'before-save-hook
+                 #'delete-trailing-whitespace-if-possible t))))
+
 (defun newline-and-indent (&optional arg)
   "Insert a newline, then indent according to major mode.
 Indentation is done using the value of `indent-line-function'.
-- 
2.47.2