Subject: bug#40896: 27.0.91; Moving point fails sometimes in shell-command

From: Michael Albinus <[email-masked]>
Date: Thu, 30 Apr 2020 15:45:38 +0200

Michael Albinus <[email-masked]> writes:

Hi Eli,

> Maybe all of this is irrelevant in real life. But I'm fighting with
> Tramp, in order to let its shell-command implementation behave like in
> the local case.

In order to understand the situation better, I have enhanced my test
code to cover all different values of shell-command-dont-erase-buffer,
and whether the OUTPUT-BUFFER argument of shell-command is the current
buffer, or not. Here's the code:

--8<---------------cut here---------------start------------->8---
(progn
  (message "   s-c-d-e-b current-buffer buffer-string point")
  (message "===============================================")

  (let ((default-directory temporary-file-directory)
	buffer)
    ;; These are the possible values of `shell-command-dont-erase-buffer'.
    ;; `random' is taken as non-nil value without special meaning.
    (dolist (shell-command-dont-erase-buffer
	     '(nil erase beg-last-out end-last-out save-point random))
      ;; `shell-command' might work over the current buffer, or not.
      (dolist (current '(t nil))
	(with-temp-buffer
	  ;; We insert the string "foobar" into an empty buffer.
	  ;; Point is set between "foo" and "bar".
	  (setq buffer (current-buffer))
	  (insert "foobar")
	  (goto-char (- (point) 3))

	  ;; Apply `shell-command'.  It shall output the string
	  ;; "bazz".  Messages in the *Messages* buffer are
	  ;; suppressed.
          (let (message-log-max)
	    (if current
		(shell-command "echo -n bazz" (current-buffer))
	      (with-temp-buffer (shell-command "echo -n bazz" buffer))))

	  ;; Print result.
	  (message
	   "%12s %14s %13s %5d"
	   shell-command-dont-erase-buffer current (buffer-string) (point))))
      (message "-----------------------------------------------"))))
--8<---------------cut here---------------end--------------->8---

In a temporary buffer, the string "foobar" is inserted. Point is set
between "foo" and "bar". Then, shell-command is called, which outputs
the string "bazz".

The result can be seen in the *Messages* buffer. Honestly, I cannot say
whether this is conform to the docstring of shell-command (I have read
it several times), because it describes too many cases, and it is not
always clear how the combination of the cases is meant. But at least, I
would say the results look inconsistent.

A first observation is, that the output is inserted at (point) if
OUTPUT-BUFFER is the current buffer, and it is appended at (point-max)
if OUTPUT-BUFFER is not the current buffer. The fact, that the output is
appended at the end of the buffer, isn't mentioned anywhere in the doc AFAICT.

Now, let's see the details:

--8<---------------cut here---------------start------------->8---
   s-c-d-e-b current-buffer buffer-string point
===============================================
         nil              t    foobazzbar     4
         nil            nil          bazz     5
--8<---------------cut here---------------end--------------->8---

shell-command-dont-erase-buffer is nil. In case OUTPUT-BUFFER is the
current one, "bazz" is inserted at (point), and (point) is
preserved. Fine. In case OUTPUT-BUFFER is not the current one, the
buffer is erased, which as OK. However, (point) is NOT preserved, but
moved to the end of the shell command output. That doesn't sound right.

--8<---------------cut here---------------start------------->8---
       erase              t          bazz     1
       erase            nil          bazz     5
--8<---------------cut here---------------end--------------->8---

shell-command-dont-erase-buffer is erase. In both cases of
OUTPUT-BUFFER, the buffer is erased (as expected), but again, in case
OUTPUT-BUFFER is not the current one, (point) is moved after the shell
command output.

--8<---------------cut here---------------start------------->8---
beg-last-out              t    foobazzbar     4
beg-last-out            nil    foobarbazz     7
--8<---------------cut here---------------end--------------->8---

shell-command-dont-erase-buffer is beg-last-out. Shell command output is
inserted in case OUTPUT-BUFFER is the current one, and it is appended
otherwise. (point) is moved in both cases to the begin of the shell
command output, which is consistent, but I don't still see why the
output is either inserted, or appended.

--8<---------------cut here---------------start------------->8---
end-last-out              t    foobazzbar     4
end-last-out            nil    foobazzbar    11
--8<---------------cut here---------------end--------------->8---

shell-command-dont-erase-buffer is end-last-out. In case OUTPUT-BUFFER
is the current one everything looks as expected. But in the other case,
OUTPUT-BUFFER is not the current buffer, the result is irritating. Shell
command is inserted in this case at (point), and (point) is not moved to the
end of the shell command output, but to the end of the buffer. Strange!

--8<---------------cut here---------------start------------->8---
  save-point              t    foobazzbar     4
  save-point            nil    foobarbazz     4
--8<---------------cut here---------------end--------------->8---

shell-command-dont-erase-buffer is save-point. And indeed, (point) is
saved in both cases. But again, shell command output is appended at the
end of the buffer, instead of being inserted at (point).

--8<---------------cut here---------------start------------->8---
      random              t    foobazzbar     4
      random            nil    foobazzbar    11
--8<---------------cut here---------------end--------------->8---

shell-command-dont-erase-buffer is random, which stands for a non-nil
value which has no special meaning. It behaves like end-last-out. I
don't know whether this is intended; if yes, it shall be documented.

Best regards, Michael.