Re: bug#64855: 30.0.50; ERC 5.6: Make scrolltobottom less erratic

"J.P." <[email protected]>
Newsgroups gmane.emacs.erc.general
Message-ID <[email protected]>
v2. Stash and restore `window-start' for all windows when inserting
messages.
0000-v1-v2.diff (text/x-patch, 6.5 KB)
From 49e66fee68a4ad69418dae329e24e5611ea8de6e Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email protected]>
Date: Wed, 26 Jul 2023 05:33:54 -0700
Subject: [PATCH 0/1] *** NOT A PATCH ***

*** BLURB HERE ***

F. Jason Park (1):
  [5.6] Consider all windows in erc-scrolltobottom-mode

 lisp/erc/erc-goodies.el | 102 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 91 insertions(+), 11 deletions(-)

Interdiff:
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index af44b98e0bf..e82dc73f82f 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -52,22 +52,26 @@ 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-pre-hook #'erc--on-pre-insert)
+   (add-hook 'erc-insert-pre-hook #'erc--scroll-to-bottom-on-pre-insert)
+   (add-hook 'erc-send-pre-functions #'erc--scroll-to-bottom-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-pre-hook #'erc--on-pre-insert)
+   (remove-hook 'erc-insert-pre-hook #'erc--scroll-to-bottom-on-pre-insert)
+   (remove-hook 'erc-send-pre-functions #'erc--scroll-to-bottom-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.")
+  "A cons of some window and the expiration time in seconds.
+The CAR is a window in whose buffer this variable is local.  The
+CDR is a half second after the last attempted refresh of such a
+window.")
 
 (defvar-local erc--scroll-to-bottom-last-window-start nil
-  "A cons of a window and a starting position.")
+  "Alist whose members are 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."
@@ -76,18 +80,36 @@ erc-possibly-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."
+Expect narrowing not to be in effect.  Skip when
+`erc--scroll-to-bottom-debounce-expire' has not yet transpired."
   (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)))))
+    (unless (or (eq this-command 'recenter-top-bottom) (input-pending-p))
+      (let ((now (erc-current-time))
+            (at-prompt-p (>= (point) erc-input-marker)))
+        (unless (and at-prompt-p
+                     erc--scroll-to-bottom-debounce-expire
+                     (eq (car erc--scroll-to-bottom-debounce-expire)
+                         (selected-window))
+                     (< now (cdr erc--scroll-to-bottom-debounce-expire)))
+          (erc--scroll-to-bottom))
+        (setq erc--scroll-to-bottom-debounce-expire
+              (and at-prompt-p (cons (selected-window) (+ now 0.5))))))))
 
 (defun erc--scroll-to-bottom-all (&rest _)
-  "Run `erc-scroll-to-bottom' in all windows showing current buffer."
+  "Maybe put prompt on last line in all windows displaying current buffer.
+Expect to run when narrowing is in effect, such as on insertion
+or send-related hooks.  When recentering has not been performed,
+attempt to restore last `window-start', if known."
   (dolist (window (get-buffer-window-list nil nil 'visible))
     (with-selected-window window
-      (erc--scroll-to-bottom))))
+      (when-let
+          (((not (erc--scroll-to-bottom)))
+           (erc--scroll-to-bottom-last-window-start)
+           (found (assq window erc--scroll-to-bottom-last-window-start)))
+        (setf (window-start window) (cdr found)))))
+  ;; Necessary unless we're sure `erc--scroll-to-bottom-on-pre-insert'
+  ;; always runs between calls to this function.
+  (setq erc--scroll-to-bottom-last-window-start nil))
 
 (defun erc-add-scroll-to-bottom ()
   "Arrange for `scrolltobottom' to refresh on window configuration changes.
@@ -110,31 +132,30 @@ erc-add-scroll-to-bottom
     (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)))))
+(cl-defmethod erc--scroll-to-bottom-on-pre-insert (_input-or-string)
+  "Remember the `window-start' before inserting a message."
+  (setq erc--scroll-to-bottom-last-window-start
+        (mapcar (lambda (w) (cons w (window-start w)))
+                (get-buffer-window-list nil nil 'visible))))
+
+(cl-defmethod erc--scroll-to-bottom-on-pre-insert ((input erc-input))
+  "Remember the `window-start' before inserting a message."
+  (when (erc-input-insertp input)
+    (cl-call-next-method)))
 
 (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."
+one.  Return non-nil when recentering has occurred."
   (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)))))))))
+        (when (>= (window-point) erc-input-marker)
+          (save-excursion
+            (goto-char (point-max))
+            (recenter (or erc-input-line-position -1))
+            t))))))
 
 (defun erc-scroll-to-bottom ()
   "Recenter WINDOW so that `point' is on the last line.
-- 
2.41.0
0001-5.6-Consider-all-windows-in-erc-scrolltobottom-mode.patch (text/x-patch, 8 KB)
From 49e66fee68a4ad69418dae329e24e5611ea8de6e Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email protected]>
Date: Sat, 22 Jul 2023 00:46:44 -0700
Subject: [PATCH 1/1] [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'.
Add `erc--scroll-to-bottom-on-pre-insert' to `erc-insert-pre-hook' and
`erc-send-pre-functions'.
Call `erc-add-scroll-to-bottom' for teardown as well.
(erc--scroll-to-bottom-debounce-expire): New variable to help ERC
refrain from calling `recenter' when the user is typing at the prompt.
(erc--scroll-to-bottom-last-window-start): New variable used by
`erc--scroll-to-bottom-on-pre-insert' and `erc--scroll-to-bottom' for
stashing and restoring the `window-start' of all windows displaying
the buffer.
(erc--possibly-scroll-to-bottom): New function resembling
`erc-possibly-scroll-to-bottom' that tries to avoid scrolling again
when user has done so recently.
(erc--scroll-to-bottom-all): New function to scroll all windows
displaying 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'.
(erc--scroll-to-bottom-on-pre-insert): New generic function that
remembers the `window-start' before inserting a message in order to
restore it afterward.
(erc--scroll-to-bottom): New function, a replacement for
`erc-scroll-to-bottom', that takes returns non-nil when it's actually
performed a recentering.  (Bug#64855)
---
 lisp/erc/erc-goodies.el | 102 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 91 insertions(+), 11 deletions(-)

diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index d9ededa8e68..e82dc73f82f 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -52,34 +52,114 @@ 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--scroll-to-bottom-on-pre-insert)
+   (add-hook 'erc-send-pre-functions #'erc--scroll-to-bottom-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--scroll-to-bottom-on-pre-insert)
+   (remove-hook 'erc-send-pre-functions #'erc--scroll-to-bottom-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
+  "A cons of some window and the expiration time in seconds.
+The CAR is a window in whose buffer this variable is local.  The
+CDR is a half second after the last attempted refresh of such a
+window.")
+
+(defvar-local erc--scroll-to-bottom-last-window-start nil
+  "Alist whose members are 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.
+Expect narrowing not to be in effect.  Skip when
+`erc--scroll-to-bottom-debounce-expire' has not yet transpired."
+  (when (eq (selected-window) (get-buffer-window))
+    (unless (or (eq this-command 'recenter-top-bottom) (input-pending-p))
+      (let ((now (erc-current-time))
+            (at-prompt-p (>= (point) erc-input-marker)))
+        (unless (and at-prompt-p
+                     erc--scroll-to-bottom-debounce-expire
+                     (eq (car erc--scroll-to-bottom-debounce-expire)
+                         (selected-window))
+                     (< now (cdr erc--scroll-to-bottom-debounce-expire)))
+          (erc--scroll-to-bottom))
+        (setq erc--scroll-to-bottom-debounce-expire
+              (and at-prompt-p (cons (selected-window) (+ now 0.5))))))))
+
+(defun erc--scroll-to-bottom-all (&rest _)
+  "Maybe put prompt on last line in all windows displaying current buffer.
+Expect to run when narrowing is in effect, such as on insertion
+or send-related hooks.  When recentering has not been performed,
+attempt to restore last `window-start', if known."
+  (dolist (window (get-buffer-window-list nil nil 'visible))
+    (with-selected-window window
+      (when-let
+          (((not (erc--scroll-to-bottom)))
+           (erc--scroll-to-bottom-last-window-start)
+           (found (assq window erc--scroll-to-bottom-last-window-start)))
+        (setf (window-start window) (cdr found)))))
+  ;; Necessary unless we're sure `erc--scroll-to-bottom-on-pre-insert'
+  ;; always runs between calls to this function.
+  (setq erc--scroll-to-bottom-last-window-start nil))
+
 (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)))
+
+(cl-defmethod erc--scroll-to-bottom-on-pre-insert (_input-or-string)
+  "Remember the `window-start' before inserting a message."
+  (setq erc--scroll-to-bottom-last-window-start
+        (mapcar (lambda (w) (cons w (window-start w)))
+                (get-buffer-window-list nil nil 'visible))))
+
+(cl-defmethod erc--scroll-to-bottom-on-pre-insert ((input erc-input))
+  "Remember the `window-start' before inserting a message."
+  (when (erc-input-insertp input)
+    (cl-call-next-method)))
+
+(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.  Return non-nil when recentering has occurred."
+  (when erc-insert-marker
+    (let ((resize-mini-windows nil))
+      (save-restriction
+        (widen)
+        (when (>= (window-point) erc-input-marker)
+          (save-excursion
+            (goto-char (point-max))
+            (recenter (or erc-input-line-position -1))
+            t))))))
 
 (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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.