Subject: bug#69097: [PATCH] Add 'kill-region-or-word' command

From: Philip Kaludercic <[email-masked]>
Date: Fri, 03 May 2024 19:41:43 +0000

Eli Zaretskii <[email-masked]> writes:

>> From: Philip Kaludercic <[email-masked]>
>> Cc: [email-masked],  [email-masked]
>> Date: Fri, 03 May 2024 17:32:35 +0000
>> 
>> Eli Zaretskii <[email-masked]> writes:
>> 
>> >> Cc: [email-masked], [email-masked]
>> >> Date: Fri, 03 May 2024 13:59:00 +0300
>> >> From: Eli Zaretskii <[email-masked]>
>> >> 
>> >> > From: Philip Kaludercic <[email-masked]>
>> >> > Cc: [email-masked],  Richard Stallman <[email-masked]>
>> >> > Date: Fri, 03 May 2024 10:48:13 +0000
>> >> > 
>> >> > > More to the point, can you explain why we need this, given the
>> >> > > existence of kill-word?
>> >> > >
>> >> > > Also, this kills backward, something that is not evident, neither from
>> >> > > the name of the command nor from the doc string (and the obscure
>> >> > > reference to Unix doesn't help, IMO).
>> >> > >
>> >> > > Finally, having another command that kills the region doesn't seem
>> >> > > justified, or is it?
>> >> > 
>> >> > The motivation is sort of the same as with generalising `upcase-word' to
>> >> > `upcase-dwim'.  If there is no active region, it behaves like
>> >> > `backward-kill-word' (which is what C-w does in a terminal as well), but
>> >> > if there is an active region it reverts to `kill-ring-save'.
>> >> 
>> >> If so, then (assuming we decide to accept this change), the name of
>> >> the command should be something like kill-word-dwim, and the doc
>> >> string should mention the two commands you refer to above.
>> >> 
>> >> > I have this in my personal configuration for ages, and it is always
>> >> > one of the first things I re-implement when using an Emacs without a
>> >> > custom init.el.
>> >> 
>> >> Any reason why you need this command and cannot settle for the two
>> >> commands it replaces heuristically?  Are you using Emacs with
>> >> transient-mark-mode on or off?
>> >
>> > And one more question: currently C-w signals an error if there's no
>> > region.  So another idea is to extend C-w to delete the word at point
>> > if there's no region (where "no region" means "no active region" if
>> > transient-mark-mode is ON, otherwise it means "no mark set").
>> 
>> That is basically what my command does (with the difference that I don't
>> just check if (mark) is non-nil but also if (use-region-p) is non-nil),
>> just not part of kill-region.  I don't know if this is too invasive, if
>> users are used to this error being signalled.  Perhaps it can be
>> protected by a user option?
>
> I don't think anyone wants the error, so doing something useful in
> that case should be a no-brainer.

How does this look like:

>From c64fa2065e88c4e4848bf8a15161c82ae6d1c2bd Mon Sep 17 00:00:00 2001
From: Philip Kaludercic <[email-masked]>
Date: Fri, 3 May 2024 21:38:51 +0200
Subject: [PATCH] Allow 'kill-region' to delete the last word

* lisp/simple.el (kill-word-if-no-region): Add new user option
to allow 'kill-region' to kill the last word if no region is
active.  The intention is to mirror the conventional C-w binding
found in many Unix shells.
(kill-region): Respect 'kill-word-if-no-region'.
* etc/NEWS: Mention the change.  (Bug#69097)
---
 etc/NEWS       |  6 ++++++
 lisp/simple.el | 43 ++++++++++++++++++++++++++++++-------------
 2 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index d4177d759f3..a0f2472ab67 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -416,6 +416,12 @@ When visiting a script that invokes 'env -S INTERPRETER ARGS...' in
 its shebang line, Emacs will now skip over 'env -S' and deduce the
 major mode based on the interpreter after 'env -S'.
 
+---
+*** New user option 'kill-word-if-no-region'.
+This option will modify the fall-back behaviour of 'kill-region' if no
+region is active, and will kill the last word instead of raising an
+error.
+
 ** Emacs Server and Client
 
 ---
diff --git a/lisp/simple.el b/lisp/simple.el
index a459f6ecfd2..7fade562909 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -5790,6 +5790,11 @@ kill-read-only-ok
   :type 'boolean
   :group 'killing)
 
+(defcustom kill-word-if-no-region nil
+  "Non-nil means that `kill-region' without a region will kill the last word."
+  :type 'boolean
+  :group 'killing)
+
 (defun kill-region (beg end &optional region)
   "Kill (\"cut\") text between point and mark.
 This deletes the text from the buffer and saves it in the kill ring.
@@ -5812,25 +5817,36 @@ kill-region
 the text, but put the text in the kill ring anyway.  This means that
 you can use the killing commands to copy text from a read-only buffer.
 
-Lisp programs should use this function for killing text.
- (To delete text, use `delete-region'.)
-Supply two arguments, character positions BEG and END indicating the
- stretch of text to be killed.  If the optional argument REGION is
- non-nil, the function ignores BEG and END, and kills the current
- region instead.  Interactively, REGION is always non-nil, and so
- this command always kills the current region."
+Lisp programs should use this function for killing text.  (To delete
+text, use `delete-region'.)  Supply two arguments, character positions
+BEG and END indicating the stretch of text to be killed.  If the
+optional argument REGION is non-nil, the function ignores BEG and END,
+and kills the current region instead.  If REGION has the special value
+`kill-word', then it will kill the previous word, as with
+`backward-kill-word'.  Interactively, REGION is always non-nil, and so
+this command always kills the current region."
   ;; Pass mark first, then point, because the order matters when
   ;; calling `kill-append'.
   (interactive (progn
                  (let ((beg (mark))
                        (end (point)))
-                   (unless (and beg end)
-                     (user-error "The mark is not set now, so there is no region"))
-                   (list beg end 'region))))
+                   (cond
+                    ((and beg end (use-region-p))
+                     (list beg end 'region))
+                    (kill-word-if-no-region
+                     (list beg end 'kill-word))
+                    ((user-error "The mark is not set now, so there is no region"))))))
+
   (condition-case nil
-      (let ((string (if region
-                        (funcall region-extract-function 'delete)
-                      (filter-buffer-substring beg end 'delete))))
+      (let ((string (cond
+                     ((eq region 'kill-word)
+                      (let ((end (point)))
+                        (save-excursion
+                          (forward-word -1)
+                          (filter-buffer-substring (point) end 'delete))))
+                     (region
+                      (funcall region-extract-function 'delete))
+                     ((filter-buffer-substring beg end 'delete)))))
 	(when string			;STRING is nil if BEG = END
 	  ;; Add that string to the kill ring, one way or another.
 	  (if (eq last-command 'kill-region)
@@ -5857,6 +5873,7 @@ kill-region
        ;; If the buffer isn't read-only, the text is.
        (signal 'text-read-only (list (current-buffer)))))))
 
+
 ;; copy-region-as-kill no longer sets this-command, because it's confusing
 ;; to get two copies of the text when the user accidentally types M-w and
 ;; then corrects it with the intended C-w.
-- 
2.44.0

-- 
	Philip Kaludercic on peregrine