Subject: bug#64230: 30.0.50; Ibuffer reports 1 file too many with ibuffer-auto-mode enabled

From: Stephen Berman <[email-masked]>
Date: Wed, 13 Sep 2023 20:51:47 +0200

On Mon, 11 Sep 2023 16:18:43 +0200 Stephen Berman <[email-masked]> wrote:

> On Sun, 10 Sep 2023 07:11:07 -0700 Stefan Kangas <[email-masked]> wrote:
>
>> Stephen Berman <[email-masked]> writes:
>>
>>> 0. emacs -Q
>>> 1. Type `M-x' and then TAB to pop up the *Completions* buffer, then type
>>>    `C-g'.
>>> 2. Type `M-x ibuffer'.
>>> 3. Type `d' on the lines for the buffers *scratch* and *Completions* to
>>>    flag them for deletion.
>>> 4. Type `C-c C-a' to enable ibuffer-auto-mode.
>>> 5. Type `x' and at the prompt "Really kill 2 buffers? (y or n)" type `y'.
>>> => The *Ibuffer* lines for  *scratch* and *Completions* are deleted and
>>>    the echo area displays this message: "Operation finished; killed 3
>>>    buffers".
>>>
>>> If you change this recipe by omitting step 4, then after the buffer
>>> lines are deleted the message displayed is "Operation finished; killed 2
>>> buffers".
>>>
>>> The unexpected message with ibuffer-auto-mode enabled is displayed in
>>> Emacs 27-30 but not in Emacs 26.  With Emacs 27+, on typing `x' at step
>>> 5, the buffer *Ibuffer confirmation* pops up and a line for this buffer
>>> immediately appears in the *Ibuffer* display, and this is counted by the
>>> function `ibuffer-map-lines', and on typing `y' not only are the two
>>> flagged buffers deleted, but also *Ibuffer confirmation*, hence "killed
>>> 3 buffers".  In contrast, in Emacs 26, the popped up buffer *Ibuffer
>>> confirmation* does not get added to the *Ibuffer* display and thus is
>>> not counted by `ibuffer-map-lines'.
>>>
>>> AFAICT, this difference is not due to any ibuffer code changes after
>>> Emacs 26; rather, there appears to be a timing difference with respect
>>> to when Emacs updates the *Ibuffer* display: when I instrument
>>> `ibuffer-update' for Edebug and then type `x' (step 5 above), what
>>> happens in Emacs 26 is that I can confirm with `y', then the flagged
>>> lines are deleted, and only then does Edebug stop the execution so I can
>>> step into `ibuffer-update'; while in Emacs 27+, as soon as I type `x',
>>> Edebug stops execution, i.e., before the flagged lines are deleted.
>>>
>>> `ibuffer-update' is called in `ibuffer-auto-update-changed', which is
>>> added to post-command-hook in `ibuffer-auto-mode'.  So it seems that in
>>> Emacs 26 post-command-hook runs or takes effect later than in Emacs 27+.
>>> Whether this is really the case, and if so, what change it is due to, I
>>> haven't determined, and I don't know how restore the Emacs 26 execution
>>> order (or if that's even desirable).  But even if the difference is due
>>> to something else, the message displayed in Emacs 27+ after the deletion
>>> of the *Ibuffer* lines is at least misleading, since it clearly is meant
>>> to refer only to the flagged lines, as in Emacs 26.
>>>
>>> In lieu of a real fix, since it is, AFAICS, only the transient buffer
>>> *Ibuffer confirmation* that results in the problematic message, a
>>> workaround is simply to decrement the line count by one when
>>> ibuffer-auto-mode is enabled, as in the the attached patch (which also
>>> takes the opportunity to wrap an overlong line in `ibuffer-map-lines').
>>
>> Your analysis and patch makes sense to me.  Please install, but add a
>> brief comment explaining why we do that decrement there.
>
> Done, and pushed as commit ca95e45f7e8.  Thanks.

Unfortunately, I didn't test that commit adequately before pushing it
and have found two regressions it introduced:

- If you delete exactly one buffer in Ibuffer with ibuffer-auto-mode
  enabled, it now emits the message "Operation finished; killed 0
  buffers".

- If you delete two buffers in Ibuffer with ibuffer-auto-mode enabled
  and with ibuffer-expert non-nil, it emits the message "Operation
  finished; killed 1 buffers" (in general, one less than the number of
  buffers deleted).

The first attached patch fixes these regressions while retaining the
improvement in ca95e45f7e8.

While debugging I noticed two unrelated infelicities in the Ibuffer
feedback:

- The message reporting deletion of one buffer is grammatically
  incorrect: "killed 1 buffers".

- If you type `x' in an Ibuffer buffer containing no marked buffer lines
  and with point not on one of the buffer lines (e.g. at (point-min) or
  (point-max)), you are prompted with "Really kill buffer *Ibuffer*? (y
  or n)" and if you type `y', the resulting message is "Operation
  finished; killed 0 buffers".  This statement is correct, since no
  buffer was killed (without the first patch, the message is
  nonsensical: "killed -1 buffers"), but then Ibuffer appears to be
  ignoring the user's response to its prompt.  However, I think the
  prompt itself is a mistake, and instead, Ibuffer should point out that
  there's no buffer on the current line and do nothing else (but again,
  only when there are no marked buffer lines.)

The second attached patch fixes these problems (to see the effect I had
to bootstrap; just regenerating ibuffer-loaddefs.el and loaddefs.el was
insufficient).

Should I install both patches?  I've tested all combinations of deleting
just one or more than buffer with ibuffer-auto-mode disabled and enabled
and ibuffer-expert nil and non-nil, but perhaps I've again overlooked
something, so I'll wait for a go-ahead.  Also, since the second patch is
strictly unrelated to the original bug report, a new bug report for it
might be preferred.

Steve Berman

diff --git a/lisp/ibuffer.el b/lisp/ibuffer.el
index b5a7f2d04e0..c30c38a90fd 100644
--- a/lisp/ibuffer.el
+++ b/lisp/ibuffer.el
@@ -1898,14 +1898,17 @@ ibuffer-map-lines
 		    (t
 		     (cl-incf ibuffer-map-lines-count)
 		     (forward-line 1)))))
-	  ;; With `ibuffer-auto-mode' enabled, the preceding loop
-	  ;; counts the automatically popped up (and hence not
-	  ;; user-marked) buffer "*Ibuffer confirmation*".  Since
-	  ;; Ibuffer reports how many user-marked buffers were acted
-	  ;; upon, and in this case the reported count would be too
-	  ;; high by one, we decrement the count to avoid the
+	  ;; With `ibuffer-auto-mode' enabled, `ibuffer-expert'
+	  ;; non-nil and more than one marked buffer lines, the
+	  ;; preceding loop counts the automatically popped up (and
+	  ;; hence not user-marked) buffer "*Ibuffer confirmation*".
+	  ;; Since Ibuffer reports how many marked buffers lines were
+	  ;; acted upon, and in this case the reported count would be
+	  ;; too high by one, we decrement the count to avoid the
 	  ;; confusing message (see bug#64230).
-          (if (and (featurep 'ibuf-ext) ibuffer-auto-mode)
+          (if (and (featurep 'ibuf-ext) ibuffer-auto-mode
+                   (> ibuffer-map-lines-count 1)
+                   (not ibuffer-expert))
               (1- ibuffer-map-lines-count)
             ibuffer-map-lines-count))
       (progn
diff --git a/lisp/ibuf-macs.el b/lisp/ibuf-macs.el
index c38dfefe0c5..36616389f99 100644
--- a/lisp/ibuf-macs.el
+++ b/lisp/ibuf-macs.el
@@ -230,6 +230,9 @@ define-ibuffer-op
 				(_
 				 'ibuffer-marked-buffer-names)))))
 	 (when (null marked-names)
+           (cl-assert (get-text-property (line-beginning-position)
+                                         'ibuffer-properties)
+                      nil "No buffer on this line")
 	   (setq marked-names (list (buffer-name (ibuffer-current-buffer))))
 	   (ibuffer-set-mark ,(pcase mark
 				(:deletion
@@ -243,7 +246,9 @@ define-ibuffer-op
 			    ())
                           (and after `(,after)) ; post-operation form.
 			  `((ibuffer-redisplay t)
-			    (message ,(concat "Operation finished; " opstring " %s buffers") count))))
+			    (message ,(concat "Operation finished; " opstring
+                                              " %s %s")
+                                     count (ngettext "buffer" "buffers" count)))))
 		 (inner-body (if complex
 				 `(progn ,@body)
 			       `(progn