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

From: Sean Whitton <[email-masked]>
Date: Fri, 06 Sep 2024 14:54:58 +0100

Hello,

On Fri 06 Sep 2024 at 02:30pm +03, Eli Zaretskii wrote:

> Thanks, but I see no reason to document this command in the manual,
> certainly not in the ELisp reference (it's a command, not a function).
> IMO it's obscure enough to be documented only in NEWS.

That's quite fine with me.

> Should this be "[:space:]\\n\\r" instead? if not, why not?
>
> Also note that [:space:] depends on mode-specific syntax table, so I
> question the wisdom of using it in a command that's supposed to be
> mode-agnostic.

Both excellent points, thank you.  I looked at the POSIX standard and
came up with a new default value.

>> +This command is like `forward-word' except that words are always
>> +delimited by whitespace, regardless of the buffer's syntax table.
>> +Like `forward-word', this command respects fields.
>> +
>> +This emulates how C-w at the Unix terminal or shell identifies words.
>> +See the `unix-word-rubout' command in Info node `(readline)Commands For
>> +Killing'."
>
> This should try to be more explicit about what "word" means for this
> command.  Since it's so different from any notion of "word" in Emacs,
> I would even seriously consider not to use that word, or maybe quote
> it (in addition to explaining what it means).

This is very helpful.  You're right.  In the attached, I've tried using
"unix-word".  Let me know what you think of that.

> Also, since this is a command, its doc string should clearly separate
> what happens in interactive invocation from what happens when called
> from Lisp.  DELIM belongs to the latter.  (Is it even useful to
> provide that option for Lisp-only calls? what's the use case for
> that?)
>
> And finally, I wonder why we need this command?  AFAIU, the original
> intent was to implement something similar to unix-word-rubout, not a
> new movement command.  If the plan has changed, I think we need to
> discuss the need once again.

I think it was unhelpful of me to send this in without callers.
I'm sorry about that.

The reason for adding this is that it factors out what is in common
between what Philip is doing, and unix-word-rubout.
So in the attached revised patch, I've included unix-word-rubout.

I've also included a second readline command which I have bound in my
own init.  It demonstrates why there is a need for DELIM.

By the way, forward-unix-word could also be a plain function.
I made it a command because, well, why not.  Let me know if you think it
should be switched back to a function.

-- 
Sean Whitton
>From c047e640399bb923728ed9967e8545d53b22ea5c Mon Sep 17 00:00:00 2001
From: Sean Whitton <[email-masked]>
Date: Fri, 6 Sep 2024 11:35:46 +0100
Subject: [PATCH] New commands for moving and killing unix-words

* lisp/simple.el (forward-unix-word, unix-word-rubout)
(unix-filename-rubout): New commands.
* etc/NEWS: Document them.
---
 etc/NEWS       |  7 +++++++
 lisp/simple.el | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+)

diff --git a/etc/NEWS b/etc/NEWS
index f3e719a34d3..54cf0a3df52 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -123,6 +123,13 @@ When using 'visual-wrap-prefix-mode' in buffers with variable-pitch
 fonts, the wrapped text will now be lined up correctly so that it's
 exactly below the text after the prefix on the first line.
 
+---
+** New commands for moving and killing unix-words.
+Unix-words are words separated by whitespace regardless of the buffer's
+syntax table.  In a Unix terminal or shell, C-w kills by unix-word.
+The new commands 'forward-unix-word', 'unix-word-rubout' and
+'unix-filename-rubout' allow you to bind keys to operate more similarly
+to the terminal.
 
 * Changes in Specialized Modes and Packages in Emacs 31.1
 
diff --git a/lisp/simple.el b/lisp/simple.el
index 2453a129d0a..0322ac0cd8c 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -8892,6 +8892,63 @@ current-word
       ;; If we found something nonempty, return it as a string.
       (unless (= start end)
 	(buffer-substring-no-properties start end)))))
+
+(defun forward-unix-word (arg &optional delim)
+  "Move forward ARG unix-words.
+A unix-word is whitespace-delimited.
+Interactively, ARG is the numeric prefix argument, defaulting to 1.
+A negative ARG means go backwards to the beginning of unix-words.
+
+Unix-words differ from Emacs words in that they are always delimited by
+whitespace, regardless of the buffer's syntax table.  This command
+emulates how C-w at the Unix terminal or shell identifies words.
+
+When called from Lisp, DELIM specifies what characters are considered
+whitespace.  It is a string as might be passed to `skip-chars-forward'.
+The default is \" \\f\\n\\r\\t\\v\".  Do not prefix a `^' character."
+  (interactive "^p")
+  (unless (zerop arg)
+    ;; We do skip over newlines by default because `backward-word' does.
+    (let* ((delim (or delim " \f\n\r\t\v"))
+           (ndelim (format "^%s" delim))
+           (start (point))
+           (fun (if (> arg 0)
+                    #'skip-chars-forward
+                  #'skip-chars-backward)))
+      (dotimes (_ (abs arg))
+        (funcall fun delim)
+        (funcall fun ndelim))
+      (constrain-to-field nil start))))
+
+(defun unix-word-rubout (arg)
+  "Kill ARG unix-words backwards.
+A unix-word is whitespace-delimited.
+Interactively, ARG is the numeric prefix argument, defaulting to 1.
+A negative ARG means to kill forwards.
+
+Unix-words differ from Emacs words in that they are always delimited by
+whitespace, regardless of the buffer's syntax table.  Thus, this command
+emulates C-w at the Unix terminal or shell identifies words.
+See also this command's nakesake in Info node
+`(readline)Commands For Killing'."
+  (interactive "^p")
+  (let ((start (point)))
+    (forward-unix-word (- arg))
+    (kill-region start (point))))
+
+(defun unix-filename-rubout (arg)
+  "Kill ARG unix-words backwards, also treating `/' as whitespace.
+A unix-word is whitespace-delimited.
+Interactively, ARG is the numeric prefix argument, defaulting to 1.
+A negative ARG means to kill forwards.
+
+This is like `unix-word-rubout' (which see), but `/' is also considered
+whitespace.  See this command's namesake in Info node
+`(readline)Commands For Killing'."
+  (interactive "^p")
+  (let ((start (point)))
+    (forward-unix-word (- arg) "/ \f\n\r\t\v")
+    (kill-region start (point))))
 
 (defcustom fill-prefix nil
   "String for filling to insert at front of new line, or nil for none."
-- 
2.39.2