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

From: Philip Kaludercic <[email-masked]>
Date: Mon, 02 Sep 2024 21:12:01 +0000

Sean Whitton <[email-masked]> writes:

> Hello,
>
> On Mon 02 Sep 2024 at 09:42pm +01, Sean Whitton wrote:
>
>> Hello,
>>
>> On Mon 02 Sep 2024 at 08:39pm GMT, Philip Kaludercic wrote:
>>
>>> How about this suggestion:  We add a generic kill-region-or-word
>>> command, and a user option for a function (set to either
>>> `backward-kill-word' or Sean's `unix-word-rubout').  I can prepare a
>>> patch with the simpler version, and then Sean can add his behaviour in a
>>> second patch so that the attribution remains correct.
>>
>> I think a tristate option is preferable to this.
>>
>> If the user wants something of their own they can just write their own
>> command.
>
> Sorry, I wrote this too quickly, I don't think I actually understand
> your new proposal.
>
> Could you sketch it out?
>
> Thank you for your patience on this one.

I had misremembered the last state of this patch.  It is easier to just
have a tristate option.  Here is the updated proposal:

diff --git a/lisp/simple.el b/lisp/simple.el
index eedc5768fe2..9ce108089e7 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -5817,6 +5817,17 @@ kill-read-only-ok
   :type 'boolean
   :group 'killing)
 
+(defcustom kill-word-if-no-region nil
+  "Behaviour when `kill-region' is invoked without an active region.
+If set to nil (default), then an error occurs and nothing is killed.  If
+set to `emacs-word', then kill a the last word as defined by the current
+major mode.  If set to `unix-word', then kill the last word in the style
+of a shell like Bash, disregarding the major mode."
+  :type '(choice (const :tag "Kill a word like `backward-kill-word'" emacs-word)
+                 (const :tag "Kill a word like Bash would" unix-word)
+                 (const :tag "Do not kill anything" nil))
+  :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.
@@ -5839,25 +5850,47 @@ 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-if-no-region))
+                    ((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 'emacs-word)
+                      (let ((end (point)))
+                        (save-excursion
+                          (forward-word -1)
+                          (filter-buffer-substring (point) end 'delete))))
+                     ((eq region 'unix-word)
+                      (let ((end (point)))
+                        (save-excursion
+                          (skip-chars-backward "[:space:]")
+                          (skip-chars-backward "^[:space:]")
+                          (filter-buffer-substring
+                           (if (get-char-property (point) 'read-only)
+                               (next-single-char-property-change
+                                (point) 'read-only nil end)
+                             (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)

-- 
	Philip Kaludercic on peregrine