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

From: Philip Kaludercic <[email-masked]>
Date: Fri, 06 Sep 2024 16:32:31 +0000

Sean Whitton <[email-masked]> writes:

> 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.

Right, this is the command that we agreed on being necessary to appease
both people who want C-w to kill the region when no transient
region is active or to kill a word.

> 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.

Should you mention the ^W notation here as well?

> +
> +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."

I think it would be worth checking for a ^ and then throwing a user-error.

> +  (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"))
                                ^
                                I'd use "\s" here (and below).

> +           (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))))

Won't there be an error here if the command is invoked with a negative argument?

> +
> +(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."

-- 
	Philip Kaludercic on siskin