Subject: 30.0.50; ERC 5.6: Make scrolltobottom less erratic
From: "J.P." <[email-masked]>
To: [email-masked]
Date: Tue, 25 Jul 2023 06:40:24 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

A minor bug fix involving the `scrolltobottom' module (and originally
lumped in with the mostly unrelated bug#63595) eventually materialized
on HEAD as [1]. While it did fix the bug, it didn't really address any
of the related foundational issues that have haunted `scrolltobottom'
since the beginning. The attached patch is meant to make the module more
predictable and consistent overall, while specifically addressing

  - the effect only being applied in the selected window

  - the effect not responding to changes in window size

  - the gradual scrolling of point toward the centermost line when
    away from the prompt (on graphical displays)

People miffed by this module's rough edges are encouraged to try these
changes and give feedback.

Thanks.

[1] commit e51e43b7046b56c58310854182a1d589ee4c770c
    Author: F. Jason Park <[email-masked]>
    Date:   Wed May 17 19:48:02 2023 -0700

      Fix buffer-mismatch bug in erc-scroll-to-bottom

      * lisp/erc/erc-goodies.el (erc-scroll-to-bottom): Only
      `recenter' when the selected window's buffer is current.
      Previously, the module `scrolltobottom' signaled an "Error in
      `post-command-hook'" when a user clicked a channel indicator in
      the mode line from a window showing another ERC buffer.
    [...]
      (Bug#63595)

    lisp/erc/erc-goodies.el | 1 +
    lisp/erc/erc-track.el   | 9 ++++++++-
    2 files changed, 9 insertions(+), 1 deletion(-)

In GNU Emacs 30.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version
 3.24.38, cairo version 1.17.6) of 2023-07-25 built on localhost
Repository revision: 89558533683a100ca7946c4a35bf4ef50463efef
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12014000
System Description: Fedora Linux 37 (Workstation Edition)

Configured using:
 'configure --enable-check-lisp-object-type --enable-checking=yes,glyphs
 'CFLAGS=-O0 -g3'
 PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig'

Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG
JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 M17N_FLT MODULES NOTIFY
INOTIFY PDUMPER PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF
TOOLKIT_SCROLL_BARS WEBP X11 XDBE XIM XINPUT2 XPM GTK3 ZLIB

Important settings:
  value of $LANG: en_US.UTF-8
  value of $XMODIFIERS: @im=ibus
  locale-coding-system: utf-8-unix

Major mode: Lisp Interaction

Minor modes in effect:
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  tool-bar-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  line-number-mode: t
  indent-tabs-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t

Load-path shadows:
None found.

Features:
(shadow sort mail-extr emacsbug message mailcap yank-media puny dired
dired-loaddefs rfc822 mml mml-sec epa derived epg rfc6068 epg-config
gnus-util text-property-search time-date mm-decode mm-bodies mm-encode
mail-parse rfc2231 mailabbrev gmm-utils mailheader sendmail rfc2047
rfc2045 ietf-drums mm-util mail-prsvr mail-utils erc auth-source cl-seq
eieio eieio-core cl-macs password-cache json subr-x map format-spec
cl-loaddefs cl-lib erc-backend erc-networks byte-opt gv bytecomp
byte-compile erc-common erc-compat erc-loaddefs rmc iso-transl tooltip
cconv eldoc paren electric uniquify ediff-hook vc-hooks lisp-float-type
elisp-mode mwheel term/x-win x-win term/common-win x-dnd tool-bar dnd
fontset image regexp-opt fringe tabulated-list replace newcomment
text-mode lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow
isearch easymenu timer select scroll-bar mouse jit-lock font-lock syntax
font-core term/tty-colors frame minibuffer nadvice seq simple cl-generic
indonesian philippine cham georgian utf-8-lang misc-lang vietnamese
tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek
romanian slovak czech european ethiopic indian cyrillic chinese
composite emoji-zwj charscript charprop case-table epa-hook
jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs
theme-loaddefs faces cus-face macroexp files window text-properties
overlay sha1 md5 base64 format env code-pages mule custom widget keymap
hashtable-print-readable backquote threads dbusbind inotify lcms2
dynamic-setting system-font-setting font-render-setting cairo gtk
x-toolkit xinput2 x multi-tty move-toolbar make-network-process emacs)

Memory information:
((conses 16 64796 9162) (symbols 48 8637 0) (strings 32 23386 1972)
 (string-bytes 1 680274) (vectors 16 15042)
 (vector-slots 8 207499 8948) (floats 8 24 41) (intervals 56 225 0)
 (buffers 976 10))

From 5ce39aa8adfefc85208448ff1d254766f15892e1 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Sat, 22 Jul 2023 00:46:44 -0700
Subject: [PATCH] [5.6] Consider all windows in erc-scrolltobottom-mode

* lisp/erc/erc-goodies.el (erc-scrolltobottom-mode,
erc-scrolltobottom-enable, erc-scrolltobottom-disable): Use
`erc--scroll-to-bottom-all' instead of `erc-possibly-scroll-to-bottom'
for `erc-insert-done-hook' and now also `erc-send-completed-hook'.
Call `erc-add-scroll-to-bottom' for teardown as well.
(erc--scroll-to-bottom-debounce-expire): New variable.
(erc-possibly-scroll-to-bottom): Don't bother scrolling when user has
done so recently.
(erc--scroll-to-bottom-all): New function to scroll in all windows of
the current buffer.
(erc-add-scroll-to-bottom): Perform teardown as well when mode is
disabled.  Also run on `window-configuration-changed-hook'.
(erc-scroll-to-bottom): Use `window-point' instead of `point'.
---
 lisp/erc/erc-goodies.el | 81 +++++++++++++++++++++++++++++++++++------
 1 file changed, 70 insertions(+), 11 deletions(-)

diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index d9ededa8e68..af44b98e0bf 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -52,34 +52,93 @@ erc-input-line-position
 (define-erc-module scrolltobottom nil
   "This mode causes the prompt to stay at the end of the window."
   ((add-hook 'erc-mode-hook #'erc-add-scroll-to-bottom)
-   (add-hook 'erc-insert-done-hook #'erc-possibly-scroll-to-bottom)
+   (add-hook 'erc-insert-pre-hook #'erc--on-pre-insert)
+   (add-hook 'erc-insert-done-hook #'erc--scroll-to-bottom-all)
+   (add-hook 'erc-send-completed-hook #'erc--scroll-to-bottom-all)
    (unless erc--updating-modules-p (erc-buffer-do #'erc-add-scroll-to-bottom)))
   ((remove-hook 'erc-mode-hook #'erc-add-scroll-to-bottom)
-   (remove-hook 'erc-insert-done-hook #'erc-possibly-scroll-to-bottom)
-   (dolist (buffer (erc-buffer-list))
-     (with-current-buffer buffer
-       (remove-hook 'post-command-hook #'erc-scroll-to-bottom t)))))
+   (remove-hook 'erc-insert-pre-hook #'erc--on-pre-insert)
+   (remove-hook 'erc-insert-done-hook #'erc--scroll-to-bottom-all)
+   (remove-hook 'erc-send-completed-hook #'erc--scroll-to-bottom-all)
+   (erc-buffer-do #'erc-add-scroll-to-bottom)))
+
+(defvar-local erc--scroll-to-bottom-debounce-expire nil
+  "Time after which `scrolltobottom' is allowed to run.
+Set to a fraction of a second in the future on every refresh.")
+
+(defvar-local erc--scroll-to-bottom-last-window-start nil
+  "A cons of a window and a starting position.")
 
 (defun erc-possibly-scroll-to-bottom ()
   "Like `erc-add-scroll-to-bottom', but only if window is selected."
   (when (eq (selected-window) (get-buffer-window))
     (erc-scroll-to-bottom)))
 
+(defun erc--possibly-scroll-to-bottom ()
+  "Call `erc-scroll-to-bottom' when buffer occupies selected window.
+Skip when `erc--scroll-to-bottom-debounce-expire' has not yet
+arrived."
+  (when (eq (selected-window) (get-buffer-window))
+    (unless (eq this-command 'recenter-top-bottom)
+      (let (erc--scroll-to-bottom-last-window-start)
+        (erc--scroll-to-bottom)))))
+
+(defun erc--scroll-to-bottom-all (&rest _)
+  "Run `erc-scroll-to-bottom' in all windows showing current buffer."
+  (dolist (window (get-buffer-window-list nil nil 'visible))
+    (with-selected-window window
+      (erc--scroll-to-bottom))))
+
 (defun erc-add-scroll-to-bottom ()
-  "A hook function for `erc-mode-hook' to recenter output at bottom of window.
+  "Arrange for `scrolltobottom' to refresh on window configuration changes.
+Undo that arrangement when `erc-scrolltobottom-mode' is disabled.
 
 If you find that ERC hangs when using this function, try customizing
 the value of `erc-input-line-position'.
 
-This works whenever scrolling happens, so it's added to
-`window-scroll-functions' rather than `erc-insert-post-hook'."
-  (add-hook 'post-command-hook #'erc-scroll-to-bottom nil t))
+Note that the prior suggestion comes from a time when this
+function used `window-scroll-functions', which was replaced by
+`post-command-hook' in ERC 5.3."
+  (if erc-scrolltobottom-mode
+      (progn
+        (add-hook 'window-configuration-change-hook
+                  #'erc--possibly-scroll-to-bottom nil t)
+        (add-hook 'post-command-hook
+                  #'erc--possibly-scroll-to-bottom nil t))
+    (remove-hook 'window-configuration-change-hook
+                 #'erc--possibly-scroll-to-bottom t)
+    (remove-hook 'post-command-hook
+                 #'erc--possibly-scroll-to-bottom t)))
+
+(defun erc--on-pre-insert (&rest _)
+  (when (eq (selected-window) (get-buffer-window))
+    (setq erc--scroll-to-bottom-last-window-start
+          (cons (selected-window) (window-start)))))
+
+(defun erc--scroll-to-bottom ()
+  "Like `erc-scroll-to-bottom', but use `window-point'.
+Expect to run in some window, not necessarily the user-selected
+one."
+  (when erc-insert-marker
+    (let ((resize-mini-windows nil))
+      (save-restriction
+        (widen)
+        (if (>= (window-point) erc-input-marker)
+            (save-excursion
+              (goto-char (point-max))
+              (recenter (or erc-input-line-position -1)))
+          (when (and erc--scroll-to-bottom-last-window-start
+                     ;; `selected-window' means the one being visited.
+                     (eq (selected-window)
+                         (car erc--scroll-to-bottom-last-window-start)))
+            (save-excursion
+              (goto-char (cdr erc--scroll-to-bottom-last-window-start))
+              (let ((recenter-positions '(top)))
+                (recenter-top-bottom)))))))))
 
 (defun erc-scroll-to-bottom ()
   "Recenter WINDOW so that `point' is on the last line.
 
-This is added to `window-scroll-functions' by `erc-add-scroll-to-bottom'.
-
 You can control which line is recentered to by customizing the
 variable `erc-input-line-position'."
       ;; Temporarily bind resize-mini-windows to nil so that users who have it
-- 
2.41.0