Subject: bug#75042: `backward-delete-char-untabify': active-region behavior

From: Asher Copeland <[email-masked]>
Date: Wed, 02 Apr 2025 10:59:04 -0700

Tags: patch

I have attached to this e-mail a patch that I believe fixes the bug, by
adding a condition that checks for the same conditions that
`delete-backward-char' uses for the same behavior, and delegates to
`delete-backward-char' if the conditions are met.

I have also found that the bug has another aspect, also fixed by the
patch. I will describe the steps for reproducing it below:

 1) emacs -Q
 2) C-q TAB w o r d
 3) C-SPC M-b
 4) Execute `backward-delete-char-untabify' (bound to DEL)

Notice that "word" was deleted, which is expected, but that the literal
TAB character was untabified, even though it was not necessary to do so.

The expected behavior is to delete "word" without untabifying.

I've looked at my previous e-mail and realized that I didn't leave clear
steps for reproduction for that aspect of the bug, so I will do so here:

 1) emacs -Q
 2) M-: (setq backward-delete-char-untabify-method 'hungry)
 3) SPC SPC w o r d
 4) C-SPC M-b
 5) Execute `backward-delete-char-untabify' (bound to DEL)

The expected behavior for it to delete "word", but instead it deletes
the two spaces to the left of point.

This patch fixes both issues with a single branch that makes it act like
`delete-backward-char' when it is appropriate to kill the region.

>From 3524d169598b97bd7f70bfced8e5d8b24e656df0 Mon Sep 17 00:00:00 2001
From: Asher Copeland <[email-masked]>
Date: Wed, 2 Apr 2025 10:55:06 -0700
Subject: [PATCH] `backward-delete-char-untabify' active-region behavior
 (Bug#75042)

---
 lisp/simple.el | 54 ++++++++++++++++++++++++++++----------------------
 1 file changed, 30 insertions(+), 24 deletions(-)

diff --git a/lisp/simple.el b/lisp/simple.el
index 7037158df8d..ee09a6f1b19 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -6601,30 +6601,36 @@ backward-delete-char-untabify
 Interactively, ARG is the prefix arg (default 1)
 and KILLP is t if a prefix arg was specified."
   (interactive "*p\nP")
-  (when (eq backward-delete-char-untabify-method 'untabify)
-    (let ((count arg))
-      (save-excursion
-	(while (and (> count 0) (not (bobp)))
-	  (if (= (preceding-char) ?\t)
-	      (let ((col (current-column)))
-		(forward-char -1)
-		(setq col (- col (current-column)))
-		(insert-char ?\s col)
-		(delete-char 1)))
-	  (forward-char -1)
-	  (setq count (1- count))))))
-  (let* ((skip (cond ((eq backward-delete-char-untabify-method 'hungry) " \t")
-                     ((eq backward-delete-char-untabify-method 'all)
-                      " \t\n\r")))
-         (n (if skip
-                (let* ((oldpt (point))
-                       (wh (- oldpt (save-excursion
-                                      (skip-chars-backward skip)
-                                      (constrain-to-field nil oldpt)))))
-                  (+ arg (if (zerop wh) 0 (1- wh))))
-              arg)))
-    ;; Avoid warning about delete-backward-char
-    (with-no-warnings (delete-backward-char n killp))))
+  (if (and (use-region-p)
+           delete-active-region
+           (= arg 1))
+      ;; Delete the text of the region and deactivate the mark, in the
+      ;; same way that `delete-backward-char' does.
+      (with-no-warnings (delete-backward-char 1 killp))
+    (when (eq backward-delete-char-untabify-method 'untabify)
+      (let ((count arg))
+        (save-excursion
+	  (while (and (> count 0) (not (bobp)))
+	    (if (= (preceding-char) ?\t)
+	        (let ((col (current-column)))
+		  (forward-char -1)
+		  (setq col (- col (current-column)))
+		  (insert-char ?\s col)
+		  (delete-char 1)))
+	    (forward-char -1)
+	    (setq count (1- count))))))
+    (let* ((skip (cond ((eq backward-delete-char-untabify-method 'hungry) " \t")
+                       ((eq backward-delete-char-untabify-method 'all)
+                        " \t\n\r")))
+           (n (if skip
+                  (let* ((oldpt (point))
+                         (wh (- oldpt (save-excursion
+                                        (skip-chars-backward skip)
+                                        (constrain-to-field nil oldpt)))))
+                    (+ arg (if (zerop wh) 0 (1- wh))))
+                arg)))
+      ;; Avoid warning about delete-backward-char
+      (with-no-warnings (delete-backward-char n killp)))))
 
 (defun char-uppercase-p (char)
   "Return non-nil if CHAR is an upper-case character.
-- 
2.43.0