Subject: bug#76186: 31.0.50; (recenter 0) sometimes does not recenter as expected

From: Markus Triska <[email-masked]>
Date: Tue, 11 Feb 2025 21:45:07 +0100

Eli Zaretskii <[email-masked]> writes:

> I suspect that this has something to do with dimensions of frame
> decorations in your build and the size of the default font on your
> system (which is different from mine).

Yes indeed, this definitely plays an important role. I have now
constructed a case that exhibits the issue within 10 seconds, with an
Emacs commit from today (0e76716c5faa5e91ac3913b02ba4dc690cf5df83).

It shows the issue also on my Debian system if I invoke Emacs with:

    $ emacs -Q -fn "Deja Vu Sans Mono 15"

and then paste the forms below in the *scratch* buffer, and then do:

    M-x eval-buffer RET

and then wait for about 10 seconds.

The interaction between scroll-bar-mode, frame geometry and other
factors also seems to play a role.

> By contrast, a problem that is hard or impossible to reproduce cannot
> be efficiently investigated.

I will try to simplify the instructions further. For now, I hope you can
use them as they are to at least also see the issue on your system.

> It could also be that the reason is the idiosyncratic implementation
> of the various redisplay aspects on macOS, in which case it is
> possible that this problem could be reproduce on no other system.

Indeed I found the problem much easier to reproduce on OSX, where it
consistently happens within a few tries (also with the latest master
commit from today). However, it also happens on other systems, such as:

    In GNU Emacs 31.0.50 (build 1, x86_64-pc-linux-gnu, X toolkit, Xaw
     scroll bars) of 2025-02-11 built on debian
    Repository revision: 0e76716c5faa5e91ac3913b02ba4dc690cf5df83
    Repository branch: master
    Windowing system distributor 'The X.Org Foundation', version 11.0.12004000
    System Description: Debian GNU/Linux 10 (buster)

    Configured using:
     'configure --with-x-toolkit=lucid --with-xpm=ifavailable
     --with-gif=ifavailable --with-tiff=ifavailable
     --with-gnutls=ifavailable'

> Last, but not least: Emacs never promises that every setting of
> window-start will be always obeyed.  The display engine tries to obey
> it, but if doing so violates some of the restrictions, like having the
> line showing cursor fully visible, it will choose a different
> window-start point.  So there's no way to guarantee, in general, that
> setting window-start will be obeyed in 100% of cases, with the current
> Emacs display engine design.

Yes, thank you for pointing this out. Still, the situation I show here
seems consistently to be the same, and recentering sometimes works as
expected and sometimes not, while the buffer contents and positions are
the same between runs.

The updated Lisp forms follow.

Thank you and all the best,
Markus

;; 1) $ emacs -Q -fn "Deja Vu Sans Mono 15"
;; 2) paste the instructions below to the *scratch* buffer
;; 3) M-x eval-buffer RET
;; 4) wait until an exception is thrown

(menu-bar-mode 0)
(tool-bar-mode 0)
(scroll-bar-mode 0)
(set-frame-height nil 27)
(set-frame-width nil 102)

(defvar recentering-test-run 0)

(defun sometimes-not-recentering ()
  (interactive)
  (setq recentering-test-run (1+ recentering-test-run))
  (let ((buf (get-buffer-create "sample")))
    (switch-to-buffer buf)
    (make-local-variable 'face-remapping-alist)
    (setq face-remapping-alist '((default (:family "DejaVu Sans Mono"
                                                   :height 218))))
    (erase-buffer)
    (save-excursion
      (dotimes (i 100)
        (insert (format "line %d\n" i))))
    (let ((frame (make-frame `((parent-frame . ,(selected-frame))
                               (left . 200)
                               (top . 200)
                               (width . (text-pixels . 100))
                               (height . (text-pixels . 100)))))
          (buf (get-buffer-create "buffer-in-frame")))
      (with-selected-frame frame
        (switch-to-buffer buf)
        (erase-buffer)
        (insert "Hello!"))
      (redisplay)
      (delete-frame frame)
      (kill-buffer buf)
      (goto-char (point-max))
      (insert "\n\n")
      (recenter 0)
      (redisplay)
      (unless (= (window-start) (point-max))
        (message "window-start unexpectedly not at point-max (%d != %d) in run %d"
                 (window-start) (point-max) recentering-test-run)
        (setq recentering-test-run 0)
        (error 'not-recentered t)))))

(while t
  (sometimes-not-recentering))