From: "J.P." <jp@neverwas.me>
To: 64855@debbugs.gnu.org
Cc: emacs-erc@gnu.org
Subject: bug#64855: 30.0.50; ERC 5.6: Make scrolltobottom less erratic
Date: Wed, 09 Aug 2023 08:00:07 -0700 [thread overview]
Message-ID: <87zg30l0qw.fsf__46593.5794565425$1691593319$gmane$org@neverwas.me> (raw)
In-Reply-To: <87h6psyurb.fsf@neverwas.me> (J. P.'s message of "Tue, 25 Jul 2023 06:40:24 -0700")
[-- Attachment #1: Type: text/plain, Size: 161 bytes --]
v3. Add new option to forgo forcing prompt to window's bottom. Remove
debouncing mechanism. Prefer setting window-start instead of
`recenter'ing when possible.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0000-v2-v3.diff --]
[-- Type: text/x-patch, Size: 16173 bytes --]
From 81fcee1a241f4efdf0c80fa9ee8a647cfbad794b Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Wed, 9 Aug 2023 06:31:29 -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 | 222 +++++++++++++++++++++++++++++++++++++---
1 file changed, 209 insertions(+), 13 deletions(-)
Interdiff:
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index e82dc73f82f..129e17fb5ec 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -44,58 +44,143 @@ erc-input-line-position
This should be an integer specifying the line of the buffer on which
the input line should stay. A value of \"-1\" would keep the input
line positioned on the last line in the buffer. This is passed as an
-argument to `recenter'."
+argument to `recenter', unless `erc-scrolltobottom-relaxed' is
+non-nil, in which case, ERC interprets it as additional lines to
+scroll down by per message insertion (minus one for the prompt)."
:group 'erc-display
:type '(choice integer (const nil)))
+(defcustom erc-scrolltobottom-relaxed nil
+ "Whether to forgo forcing prompt to the bottom of the window.
+When non-nil, and point is at the prompt, ERC scrolls the window
+up when inserting messages, making the prompt appear stationary.
+Users who find this effect too \"stagnant\" can adjust the option
+`erc-input-line-position', which ERC borrows to express a scroll
+offset when this option is non-nil. Setting that value to zero
+lets the prompt drift toward the bottom by one line per message,
+which is generally slow enough not to distract while composing
+input. Of course, this doesn't apply when receiving a large
+influx of messages, such as after typing \"/msg NickServ help\".
+Note that ERC only considers this option when initializing the
+`scrolltobottom' module and enabling `erc-scrolltobottom-mode'."
+ :group 'erc-display
+ :package-version '(ERC . "5.6") ; FIXME sync on release
+ :type 'boolean)
+
;;;###autoload(autoload 'erc-scrolltobottom-mode "erc-goodies" nil t)
(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--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)
+ (add-hook 'erc-insert-pre-hook #'erc--scrolltobottom-on-pre-insert)
+ (add-hook 'erc-pre-send-functions #'erc--scrolltobottom-on-pre-insert)
+ (add-hook 'erc-insert-done-hook #'erc--scrolltobottom-all)
+ (add-hook 'erc-send-completed-hook #'erc--scrolltobottom-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--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)
+ (remove-hook 'erc-insert-pre-hook #'erc--scrolltobottom-on-pre-insert)
+ (remove-hook 'erc-pre-send-functions #'erc--scrolltobottom-on-pre-insert)
+ (remove-hook 'erc-insert-done-hook #'erc--scrolltobottom-all)
+ (remove-hook 'erc-send-completed-hook #'erc--scrolltobottom-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 _)
+(defvar-local erc--scrolltobottom-relaxed-commands '(end-of-buffer)
+ "Commands triggering a force scroll to prompt.
+Only applies with `erc-scrolltobottom-relaxed' when away from prompt.")
+
+(defvar-local erc--scrolltobottom-window-info nil
+ "Alist with windows as keys and lists of window-related info as values.
+Values are lists containing the last window start position and
+the last \"window line\" of point. The \"window line\", which
+may be nil, is the number of lines between `window-start' and
+`window-point', inclusive.")
+
+(defvar erc--scrolltobottom-post-force-commands
+ '(electric-newline-and-maybe-indent)
+ "Commands that force a scroll after execution at prompt.")
+
+(defvar erc--scrolltobottom-relaxed-skip-commands '(recenter-top-bottom))
+
+(defun erc--scrolltobottom-on-pre-command ()
+ (cl-assert (not erc-scrolltobottom-relaxed)) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (when (> (point) erc-input-marker)
+ (setq erc--scrolltobottom-window-info
+ (list (list (selected-window)
+ (window-start)
+ (count-screen-lines (window-start) (point)))))))
+
+(defun erc--scrolltobottom-on-post-command ()
+ "Restore window start or scroll to prompt and recenter.
+When `erc--scrolltobottom-window-info' is non-nil and its first
+item is associated with the selected window, restore start of
+window so long as prompt hasn't moved. Expect buffer to be
+unnarrowed."
+ (cl-assert (not erc-scrolltobottom-relaxed)) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (if-let (((not (input-pending-p)))
+ (erc--scrolltobottom-window-info)
+ (found (car erc--scrolltobottom-window-info))
+ ((eq (car found) (selected-window)))
+ ((not (memq this-command erc--scrolltobottom-post-force-commands)))
+ ((= (nth 2 found) (count-screen-lines (window-start) (point)))))
+ (set-window-start (selected-window) (nth 1 found))
+ (erc--scrolltobottom-confirm))
+ (setq erc--scrolltobottom-window-info nil))
+
+(defun erc--scrolltobottom-on-pre-command-relaxed ()
+ "Maybe scroll to bottom when away from prompt in an unnarrowed buffer.
+When `erc-scrolltobottom-relaxed' is active, only scroll when
+prompt is past window's end and the command is `end-of-buffer' or
+`self-insert-command' (assuming `move-to-prompt' is active).
+When at prompt and current command is not `recenter-top-bottom',
+stash `erc--scrolltobottom-window-info' for the selected window."
+ (cl-assert erc-scrolltobottom-relaxed) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (when (and (not (input-pending-p))
+ (< (point) erc-input-marker)
+ (memq this-command erc--scrolltobottom-relaxed-commands)
+ (< (window-end nil t) erc-input-marker))
+ (save-excursion
+ (goto-char (point-max))
+ (recenter (or erc-input-line-position -1))))
+ (when (and (> (point) erc-input-marker)
+ (not (memq this-command
+ erc--scrolltobottom-relaxed-skip-commands)))
+ (setq erc--scrolltobottom-window-info
+ (list (list (selected-window)
+ (window-start)
+ (count-screen-lines (window-start) (point-max)))))))
+
+(defun erc--scrolltobottom-on-post-command-relaxed ()
+ "Set window start or scroll when data was captured on pre-command."
+ (cl-assert erc-scrolltobottom-relaxed) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (when-let ((erc--scrolltobottom-window-info)
+ (found (car erc--scrolltobottom-window-info))
+ ((eq (car found) (selected-window))))
+ (if (and (not (memq this-command erc--scrolltobottom-post-force-commands))
+ (= (nth 2 found)
+ (count-screen-lines (window-start) (point-max))))
+ (set-window-start (selected-window) (nth 1 found))
+ (recenter (nth 2 found)))
+ (setq erc--scrolltobottom-window-info nil)))
+
+;; FIXME this is currently of little value because it doesn't restore
+;; the relative position of window point after changing dimensions.
+;; It would be preferable to instead stash the previous ratio of
+;; window line to body height and later recenter proportionally. But
+;; that may not be worth the added bookkeeping.
+(defun erc--scrolltobottom-away-from-prompt ()
+ "Scroll to bottom unless at prompt."
+ (unless (input-pending-p)
+ (erc--scrolltobottom-confirm)))
+
+(defun erc--scrolltobottom-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,
@@ -103,17 +188,17 @@ erc--scroll-to-bottom-all
(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'
+ ((erc--scrolltobottom-window-info)
+ (found (assq window erc--scrolltobottom-window-info))
+ ((not (erc--scrolltobottom-confirm (nth 2 found)))))
+ (setf (window-start window) (cadr found)))))
+ ;; Necessary unless we're sure `erc--scrolltobottom-on-pre-insert'
;; always runs between calls to this function.
- (setq erc--scroll-to-bottom-last-window-start nil))
+ (setq erc--scrolltobottom-window-info nil))
(defun erc-add-scroll-to-bottom ()
- "Arrange for `scrolltobottom' to refresh on window configuration changes.
-Undo that arrangement when `erc-scrolltobottom-mode' is disabled.
+ "Arrange for scrolling to bottom on window configuration changes.
+Undo that arrangement when disabling `erc-scrolltobottom-mode'.
If you find that ERC hangs when using this function, try customizing
the value of `erc-input-line-position'.
@@ -124,29 +209,57 @@ erc-add-scroll-to-bottom
(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))
+ #'erc--scrolltobottom-away-from-prompt nil t)
+ (if erc-scrolltobottom-relaxed
+ (progn
+ (when (or (bound-and-true-p erc-move-to-prompt-mode)
+ (memq 'move-to-prompt erc-modules))
+ (cl-pushnew 'self-insert-command
+ erc--scrolltobottom-relaxed-commands))
+ (add-hook 'post-command-hook
+ #'erc--scrolltobottom-on-post-command-relaxed -90 t)
+ (add-hook 'pre-command-hook ; preempt `move-to-prompt'
+ #'erc--scrolltobottom-on-pre-command-relaxed -90 t))
+ (add-hook 'pre-command-hook
+ #'erc--scrolltobottom-on-pre-command nil t)
+ (add-hook 'post-command-hook
+ #'erc--scrolltobottom-on-post-command nil t)))
(remove-hook 'window-configuration-change-hook
- #'erc--possibly-scroll-to-bottom t)
+ #'erc--scrolltobottom-away-from-prompt t)
+ (remove-hook 'pre-command-hook
+ #'erc--scrolltobottom-on-pre-command t)
+ (remove-hook 'post-command-hook
+ #'erc--scrolltobottom-on-post-command t)
+ (remove-hook 'pre-command-hook
+ #'erc--scrolltobottom-on-pre-command-relaxed t)
(remove-hook 'post-command-hook
- #'erc--possibly-scroll-to-bottom t)))
+ #'erc--scrolltobottom-on-post-command-relaxed t)
+ (kill-local-variable 'erc--scrolltobottom-relaxed-commands)
+ (kill-local-variable 'erc--scrolltobottom-window-info)))
-(cl-defmethod erc--scroll-to-bottom-on-pre-insert (_input-or-string)
+(cl-defmethod erc--scrolltobottom-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)))
+ (setq erc--scrolltobottom-window-info
+ (mapcar (lambda (w)
+ (list w
+ (window-start w)
+ (and-let*
+ ((erc-scrolltobottom-relaxed)
+ (c (count-screen-lines (window-start w)
+ (window-point w) nil w)))
+ (if (= ?\n (char-before (point-max))) (1+ c) c))))
(get-buffer-window-list nil nil 'visible))))
-(cl-defmethod erc--scroll-to-bottom-on-pre-insert ((input erc-input))
+(cl-defmethod erc--scrolltobottom-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 ()
+(defun erc--scrolltobottom-confirm (&optional scroll-to)
"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."
+one. Scroll to SCROLL-TO (or 0) lines from the window's top.
+Return non-nil when recentering has occurred."
(when erc-insert-marker
(let ((resize-mini-windows nil))
(save-restriction
@@ -154,7 +267,7 @@ erc--scroll-to-bottom
(when (>= (window-point) erc-input-marker)
(save-excursion
(goto-char (point-max))
- (recenter (or erc-input-line-position -1))
+ (recenter (+ (or scroll-to 0) (or erc-input-line-position -1)))
t))))))
(defun erc-scroll-to-bottom ()
@@ -293,12 +406,15 @@ erc--keep-place-indicator-setup
(add-hook 'window-configuration-change-hook
#'erc--keep-place-indicator-on-window-configuration-change nil t)
(when-let* (((memq erc-keep-place-indicator-style '(t arrow)))
+ (ov-property (if (zerop (fringe-columns 'left))
+ 'after-string
+ 'before-string))
(display (if (zerop (fringe-columns 'left))
`((margin left-margin) ,overlay-arrow-string)
'(left-fringe right-triangle
erc-keep-place-indicator-arrow)))
(bef (propertize " " 'display display)))
- (overlay-put erc--keep-place-indicator-overlay 'before-string bef))
+ (overlay-put erc--keep-place-indicator-overlay ov-property bef))
(when (memq erc-keep-place-indicator-style '(t face))
(overlay-put erc--keep-place-indicator-overlay 'face
'erc-keep-place-indicator-line)))
--
2.41.0
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0001-5.6-Consider-all-windows-in-erc-scrolltobottom-mode.patch --]
[-- Type: text/x-patch, Size: 15983 bytes --]
From 81fcee1a241f4efdf0c80fa9ee8a647cfbad794b Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
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-input-line-position): Mention secondary
role when new option `erc-scroll-to-bottom-relaxed' is non-nil.
(erc-scrolltobottom-relaxed): New option to leave the prompt
stationary instead of forcing it to the bottom of the window.
(erc-scrolltobottom-mode, erc-scrolltobottom-enable,
erc-scrolltobottom-disable): Use `erc--scrolltobottom-all' instead
of `erc-possibly-scroll-to-bottom' for `erc-insert-done-hook' and now
also `erc-send-completed-hook'. Add
`erc--scrolltobottom-on-pre-insert' to `erc-insert-pre-hook' and
`erc-pre-send-functions'. Call `erc-add-scroll-to-bottom' for
teardown as well.
(erc--scrolltobottom-relaxed-commands,
erc--scrolltobottom-window-info,
erc--scrolltobottom-post-force-commands,
erc--scrolltobottom-relaxed-skip-commands): New internal variables.
(erc--scrolltobottom-on-pre-command
erc--scrolltobottom-on-post-command): New functions resembling
`erc-possibly-scroll-to-bottom' that try to avoid scrolling repeatedly
for no reason.
(erc--scrolltobottom-on-pre-command-relaxed,
erc--scrolltobottom-on-post-command-relaxed): New commands to help
implement `erc-scroll-to-bottom-relaxed'.
(erc--scrolltobottom-away-from-prompt): New function to scroll to
bottom on window configuration changes.
(erc--scrolltobottom-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 scroll on `window-configuration-changed-hook'.
(erc--scrolltobottom-on-pre-insert): New generic function that
remembers the last `window-start' and maybe the current line before
inserting a message in order to restore it afterward.
(erc--scrolltobottom-confirm): New function, a replacement for
`erc-scroll-to-bottom', that returns non-nil when it's actually
recentered the window.
(erc--keep-place-indicator-setup): Add overlay arrow `after-string' in
non-graphical settings in case users have time stamps or other content
occupying the left margin. (Bug#64855)
---
lisp/erc/erc-goodies.el | 222 +++++++++++++++++++++++++++++++++++++---
1 file changed, 209 insertions(+), 13 deletions(-)
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index d9ededa8e68..129e17fb5ec 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -44,42 +44,235 @@ erc-input-line-position
This should be an integer specifying the line of the buffer on which
the input line should stay. A value of \"-1\" would keep the input
line positioned on the last line in the buffer. This is passed as an
-argument to `recenter'."
+argument to `recenter', unless `erc-scrolltobottom-relaxed' is
+non-nil, in which case, ERC interprets it as additional lines to
+scroll down by per message insertion (minus one for the prompt)."
:group 'erc-display
:type '(choice integer (const nil)))
+(defcustom erc-scrolltobottom-relaxed nil
+ "Whether to forgo forcing prompt to the bottom of the window.
+When non-nil, and point is at the prompt, ERC scrolls the window
+up when inserting messages, making the prompt appear stationary.
+Users who find this effect too \"stagnant\" can adjust the option
+`erc-input-line-position', which ERC borrows to express a scroll
+offset when this option is non-nil. Setting that value to zero
+lets the prompt drift toward the bottom by one line per message,
+which is generally slow enough not to distract while composing
+input. Of course, this doesn't apply when receiving a large
+influx of messages, such as after typing \"/msg NickServ help\".
+Note that ERC only considers this option when initializing the
+`scrolltobottom' module and enabling `erc-scrolltobottom-mode'."
+ :group 'erc-display
+ :package-version '(ERC . "5.6") ; FIXME sync on release
+ :type 'boolean)
+
;;;###autoload(autoload 'erc-scrolltobottom-mode "erc-goodies" nil t)
(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--scrolltobottom-on-pre-insert)
+ (add-hook 'erc-pre-send-functions #'erc--scrolltobottom-on-pre-insert)
+ (add-hook 'erc-insert-done-hook #'erc--scrolltobottom-all)
+ (add-hook 'erc-send-completed-hook #'erc--scrolltobottom-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--scrolltobottom-on-pre-insert)
+ (remove-hook 'erc-pre-send-functions #'erc--scrolltobottom-on-pre-insert)
+ (remove-hook 'erc-insert-done-hook #'erc--scrolltobottom-all)
+ (remove-hook 'erc-send-completed-hook #'erc--scrolltobottom-all)
+ (erc-buffer-do #'erc-add-scroll-to-bottom)))
(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)))
+(defvar-local erc--scrolltobottom-relaxed-commands '(end-of-buffer)
+ "Commands triggering a force scroll to prompt.
+Only applies with `erc-scrolltobottom-relaxed' when away from prompt.")
+
+(defvar-local erc--scrolltobottom-window-info nil
+ "Alist with windows as keys and lists of window-related info as values.
+Values are lists containing the last window start position and
+the last \"window line\" of point. The \"window line\", which
+may be nil, is the number of lines between `window-start' and
+`window-point', inclusive.")
+
+(defvar erc--scrolltobottom-post-force-commands
+ '(electric-newline-and-maybe-indent)
+ "Commands that force a scroll after execution at prompt.")
+
+(defvar erc--scrolltobottom-relaxed-skip-commands '(recenter-top-bottom))
+
+(defun erc--scrolltobottom-on-pre-command ()
+ (cl-assert (not erc-scrolltobottom-relaxed)) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (when (> (point) erc-input-marker)
+ (setq erc--scrolltobottom-window-info
+ (list (list (selected-window)
+ (window-start)
+ (count-screen-lines (window-start) (point)))))))
+
+(defun erc--scrolltobottom-on-post-command ()
+ "Restore window start or scroll to prompt and recenter.
+When `erc--scrolltobottom-window-info' is non-nil and its first
+item is associated with the selected window, restore start of
+window so long as prompt hasn't moved. Expect buffer to be
+unnarrowed."
+ (cl-assert (not erc-scrolltobottom-relaxed)) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (if-let (((not (input-pending-p)))
+ (erc--scrolltobottom-window-info)
+ (found (car erc--scrolltobottom-window-info))
+ ((eq (car found) (selected-window)))
+ ((not (memq this-command erc--scrolltobottom-post-force-commands)))
+ ((= (nth 2 found) (count-screen-lines (window-start) (point)))))
+ (set-window-start (selected-window) (nth 1 found))
+ (erc--scrolltobottom-confirm))
+ (setq erc--scrolltobottom-window-info nil))
+
+(defun erc--scrolltobottom-on-pre-command-relaxed ()
+ "Maybe scroll to bottom when away from prompt in an unnarrowed buffer.
+When `erc-scrolltobottom-relaxed' is active, only scroll when
+prompt is past window's end and the command is `end-of-buffer' or
+`self-insert-command' (assuming `move-to-prompt' is active).
+When at prompt and current command is not `recenter-top-bottom',
+stash `erc--scrolltobottom-window-info' for the selected window."
+ (cl-assert erc-scrolltobottom-relaxed) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (when (and (not (input-pending-p))
+ (< (point) erc-input-marker)
+ (memq this-command erc--scrolltobottom-relaxed-commands)
+ (< (window-end nil t) erc-input-marker))
+ (save-excursion
+ (goto-char (point-max))
+ (recenter (or erc-input-line-position -1))))
+ (when (and (> (point) erc-input-marker)
+ (not (memq this-command
+ erc--scrolltobottom-relaxed-skip-commands)))
+ (setq erc--scrolltobottom-window-info
+ (list (list (selected-window)
+ (window-start)
+ (count-screen-lines (window-start) (point-max)))))))
+
+(defun erc--scrolltobottom-on-post-command-relaxed ()
+ "Set window start or scroll when data was captured on pre-command."
+ (cl-assert erc-scrolltobottom-relaxed) ; delete me
+ (cl-assert (eq (selected-window) (get-buffer-window))) ; delete me
+ (when-let ((erc--scrolltobottom-window-info)
+ (found (car erc--scrolltobottom-window-info))
+ ((eq (car found) (selected-window))))
+ (if (and (not (memq this-command erc--scrolltobottom-post-force-commands))
+ (= (nth 2 found)
+ (count-screen-lines (window-start) (point-max))))
+ (set-window-start (selected-window) (nth 1 found))
+ (recenter (nth 2 found)))
+ (setq erc--scrolltobottom-window-info nil)))
+
+;; FIXME this is currently of little value because it doesn't restore
+;; the relative position of window point after changing dimensions.
+;; It would be preferable to instead stash the previous ratio of
+;; window line to body height and later recenter proportionally. But
+;; that may not be worth the added bookkeeping.
+(defun erc--scrolltobottom-away-from-prompt ()
+ "Scroll to bottom unless at prompt."
+ (unless (input-pending-p)
+ (erc--scrolltobottom-confirm)))
+
+(defun erc--scrolltobottom-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
+ ((erc--scrolltobottom-window-info)
+ (found (assq window erc--scrolltobottom-window-info))
+ ((not (erc--scrolltobottom-confirm (nth 2 found)))))
+ (setf (window-start window) (cadr found)))))
+ ;; Necessary unless we're sure `erc--scrolltobottom-on-pre-insert'
+ ;; always runs between calls to this function.
+ (setq erc--scrolltobottom-window-info nil))
+
(defun erc-add-scroll-to-bottom ()
- "A hook function for `erc-mode-hook' to recenter output at bottom of window.
+ "Arrange for scrolling to bottom on window configuration changes.
+Undo that arrangement when disabling `erc-scrolltobottom-mode'.
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--scrolltobottom-away-from-prompt nil t)
+ (if erc-scrolltobottom-relaxed
+ (progn
+ (when (or (bound-and-true-p erc-move-to-prompt-mode)
+ (memq 'move-to-prompt erc-modules))
+ (cl-pushnew 'self-insert-command
+ erc--scrolltobottom-relaxed-commands))
+ (add-hook 'post-command-hook
+ #'erc--scrolltobottom-on-post-command-relaxed -90 t)
+ (add-hook 'pre-command-hook ; preempt `move-to-prompt'
+ #'erc--scrolltobottom-on-pre-command-relaxed -90 t))
+ (add-hook 'pre-command-hook
+ #'erc--scrolltobottom-on-pre-command nil t)
+ (add-hook 'post-command-hook
+ #'erc--scrolltobottom-on-post-command nil t)))
+ (remove-hook 'window-configuration-change-hook
+ #'erc--scrolltobottom-away-from-prompt t)
+ (remove-hook 'pre-command-hook
+ #'erc--scrolltobottom-on-pre-command t)
+ (remove-hook 'post-command-hook
+ #'erc--scrolltobottom-on-post-command t)
+ (remove-hook 'pre-command-hook
+ #'erc--scrolltobottom-on-pre-command-relaxed t)
+ (remove-hook 'post-command-hook
+ #'erc--scrolltobottom-on-post-command-relaxed t)
+ (kill-local-variable 'erc--scrolltobottom-relaxed-commands)
+ (kill-local-variable 'erc--scrolltobottom-window-info)))
+
+(cl-defmethod erc--scrolltobottom-on-pre-insert (_input-or-string)
+ "Remember the `window-start' before inserting a message."
+ (setq erc--scrolltobottom-window-info
+ (mapcar (lambda (w)
+ (list w
+ (window-start w)
+ (and-let*
+ ((erc-scrolltobottom-relaxed)
+ (c (count-screen-lines (window-start w)
+ (window-point w) nil w)))
+ (if (= ?\n (char-before (point-max))) (1+ c) c))))
+ (get-buffer-window-list nil nil 'visible))))
+
+(cl-defmethod erc--scrolltobottom-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--scrolltobottom-confirm (&optional scroll-to)
+ "Like `erc-scroll-to-bottom', but use `window-point'.
+Expect to run in some window, not necessarily the user-selected
+one. Scroll to SCROLL-TO (or 0) lines from the window's top.
+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 scroll-to 0) (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
@@ -213,12 +406,15 @@ erc--keep-place-indicator-setup
(add-hook 'window-configuration-change-hook
#'erc--keep-place-indicator-on-window-configuration-change nil t)
(when-let* (((memq erc-keep-place-indicator-style '(t arrow)))
+ (ov-property (if (zerop (fringe-columns 'left))
+ 'after-string
+ 'before-string))
(display (if (zerop (fringe-columns 'left))
`((margin left-margin) ,overlay-arrow-string)
'(left-fringe right-triangle
erc-keep-place-indicator-arrow)))
(bef (propertize " " 'display display)))
- (overlay-put erc--keep-place-indicator-overlay 'before-string bef))
+ (overlay-put erc--keep-place-indicator-overlay ov-property bef))
(when (memq erc-keep-place-indicator-style '(t face))
(overlay-put erc--keep-place-indicator-overlay 'face
'erc-keep-place-indicator-line)))
--
2.41.0
next prev parent reply other threads:[~2023-08-09 15:00 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-07-25 13:40 bug#64855: 30.0.50; ERC 5.6: Make scrolltobottom less erratic J.P.
2023-07-26 13:27 ` J.P.
2023-08-09 15:00 ` J.P. [this message]
2023-08-18 13:50 ` J.P.
2023-08-24 14:11 ` J.P.
[not found] ` <87il948r8x.fsf@neverwas.me>
2023-09-13 14:05 ` J.P.
[not found] ` <871qf2183j.fsf@neverwas.me>
2023-09-19 13:38 ` J.P.
2023-10-11 2:53 ` J.P.
[not found] ` <87o7h5euo8.fsf@neverwas.me>
2023-10-14 0:29 ` J.P.
[not found] ` <871qdy9hbz.fsf@neverwas.me>
2023-10-25 2:15 ` J.P.
[not found] ` <87r0lja1lw.fsf@neverwas.me>
2023-10-30 13:46 ` J.P.
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='87zg30l0qw.fsf__46593.5794565425$1691593319$gmane$org@neverwas.me' \
--to=jp@neverwas.me \
--cc=64855@debbugs.gnu.org \
--cc=emacs-erc@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.