From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: "J.P." Newsgroups: gmane.emacs.bugs Subject: bug#64855: 30.0.50; ERC 5.6: Make scrolltobottom less erratic Date: Thu, 24 Aug 2023 07:11:26 -0700 Message-ID: <87il948r8x.fsf__38029.0148475737$1692886354$gmane$org@neverwas.me> References: <87h6psyurb.fsf@neverwas.me> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="38114"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: emacs-erc@gnu.org To: 64855@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Aug 24 16:12:24 2023 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1qZB4R-0009dh-By for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 24 Aug 2023 16:12:23 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qZB44-0004jA-BY; Thu, 24 Aug 2023 10:12:00 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qZB42-0004hr-8H for bug-gnu-emacs@gnu.org; Thu, 24 Aug 2023 10:11:58 -0400 Original-Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qZB42-0004Wr-02 for bug-gnu-emacs@gnu.org; Thu, 24 Aug 2023 10:11:58 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1qZB45-0007SB-M8 for bug-gnu-emacs@gnu.org; Thu, 24 Aug 2023 10:12:01 -0400 X-Loop: help-debbugs@gnu.org Resent-From: "J.P." Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 24 Aug 2023 14:12:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 64855 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 64855-submit@debbugs.gnu.org id=B64855.169288630728630 (code B ref 64855); Thu, 24 Aug 2023 14:12:01 +0000 Original-Received: (at 64855) by debbugs.gnu.org; 24 Aug 2023 14:11:47 +0000 Original-Received: from localhost ([127.0.0.1]:38354 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qZB3r-0007Rh-M8 for submit@debbugs.gnu.org; Thu, 24 Aug 2023 10:11:47 -0400 Original-Received: from mail-108-mta158.mxroute.com ([136.175.108.158]:42039) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qZB3n-0007RQ-E3 for 64855@debbugs.gnu.org; Thu, 24 Aug 2023 10:11:44 -0400 Original-Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta158.mxroute.com (ZoneMTA) with ESMTPSA id 18a27e2de67000d7b6.001 for <64855@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Thu, 24 Aug 2023 14:11:34 +0000 X-Zone-Loop: c1b0e6ecca1d408880e84f66afbe3acdd61f821f8a89 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=Xfx2plXf4cfhnEjGIDtviG54GHuqFTYL2/zCsKUgzoc=; b=G3Kbp8Z26PYbBlnWHYj2o8ON18 v+rmwcm8VDntzu2pL75mkvyKq4suT1dXojbrCPjS8nIdnExXJYgAhvZ6busYAl2YxvfdmOOS6/MF5 NFUY+/giMkeJe/5w2abEjsCmdGkonbOHjWfXpxmEw1ZgKM04mpTWx/uFJXIuJQ/KI6sxps1AJrLGP 1swlfyGN+VOUitliEw7ptRn3oZ7VCPb8eAkbSdbtwF3SV8UnY6EA7o1jlUWdmP46r8jBZLOte2plw zBTleX6vuvYPCCP5FvCk0U0nJHB2KOJ910wmZMGLNqXq+RXYdtN5gP91iDyGGs1lokyD8vcC/V5Sr r7STZKOg==; In-Reply-To: <87h6psyurb.fsf@neverwas.me> (J. P.'s message of "Tue, 25 Jul 2023 06:40:24 -0700") X-Authenticated-Id: masked@neverwas.me X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:268342 Archived-At: --=-=-= Content-Type: text/plain v5. Make new behavior hinge on new option, disabled by default. Assign some module functions explicit hook depths. Add news and tests. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0000-v4-v5.diff >From 3ed5a748d76989ec2ed79311e3550da5c6d1aaa1 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 24 Aug 2023 06:49:46 -0700 Subject: [PATCH 0/1] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (1): [5.6] Consider all windows in erc-scrolltobottom-mode etc/ERC-NEWS | 19 +- lisp/erc/erc-backend.el | 2 +- lisp/erc/erc-goodies.el | 267 ++++++++++++++++-- .../erc/erc-scenarios-scrolltobottom-all.el | 48 ++++ .../erc-scenarios-scrolltobottom-relaxed.el | 140 +++++++++ test/lisp/erc/erc-scenarios-scrolltobottom.el | 44 +++ .../erc/resources/base/assoc/bumped/again.eld | 10 +- .../resources/base/assoc/bumped/foisted.eld | 10 +- .../resources/base/assoc/bumped/refoisted.eld | 8 +- .../resources/base/netid/bouncer/barnet.eld | 2 +- .../resources/base/netid/bouncer/foonet.eld | 2 +- test/lisp/erc/resources/erc-d/erc-d-t.el | 7 +- .../erc/resources/erc-scenarios-common.el | 196 +++++++++++++ .../erc/resources/scrolltobottom/help.eld | 46 +++ 14 files changed, 761 insertions(+), 40 deletions(-) create mode 100644 test/lisp/erc/erc-scenarios-scrolltobottom-all.el create mode 100644 test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el create mode 100644 test/lisp/erc/erc-scenarios-scrolltobottom.el create mode 100644 test/lisp/erc/resources/scrolltobottom/help.eld Interdiff: diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 94452514e6d..72b768b02ec 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -178,6 +178,15 @@ been restored with a slightly revised role contingent on a few assumptions explained in its doc string. For clarity, it has been renamed 'erc-ensure-target-buffer-on-privmsg'. +** Module 'scrolltobottom' attempts to behave more sensibly. +Enabling the experimental option 'erc-scrolltobottom-all' tells +'scrolltobottom' to be more vigilant about recentering and to do so in +all ERC windows. The dependent option 'erc-scrolltobottom-relaxed', +likewise experimental, makes ERC's prompt stationary wherever it +resides instead of forcing it to the bottom of the window. That is, +new input appears above the prompt, scrolling existing messages upward +to compensate. + ** Subtle changes in two fundamental faces. Users of the default theme may notice that 'erc-action-face' and 'erc-notice-face' now appear slightly less bold on systems supporting @@ -232,9 +241,9 @@ property of the same name has been retained and now has a value of Built-in and third-party modules rely on certain hooks for adjusting incoming and outgoing messages upon insertion. And some modules only want to do so after others have done their damage. Traditionally, -this required various hacks and finagling to achieve. And while this -release makes an effort to load modules in a more consistent order, -that alone isn't enough to ensure similar predictability among +this has required various hacks and finagling to achieve. And while +this release makes an effort to load modules in a more consistent +order, that alone isn't enough to ensure similar predictability among essential members of important hooks. Luckily, ERC now leverages a feature introduced in Emacs 27, "hook @@ -248,6 +257,10 @@ the first two, 'erc-button-add-buttons' and 'erc-fill', which have been swapped with respect to their previous places in recent ERC versions. +The same depth interval is now also provisionally reserved for +'erc-insert-pre-hook' and for the non-ERC hooks 'pre-command-hook' and +'post-command-hook', but only locally, in ERC buffers. + *** ERC now manages timestamp-related properties a bit differently. For starters, the 'cursor-sensor-functions' property no longer contains unique closures and thus no longer proves effective for diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index eb3ec39fedd..9e121ec1e92 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -1089,7 +1089,7 @@ erc--hide-prompt (put-text-property erc-insert-marker (1- erc-input-marker) 'erc-prompt 'hidden) (erc--conceal-prompt)) - (add-hook 'pre-command-hook #'erc--unhide-prompt-on-self-insert 91 t)))) + (add-hook 'pre-command-hook #'erc--unhide-prompt-on-self-insert 80 t)))) (defun erc-process-sentinel (cproc event) "Sentinel function for ERC process." diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el index 30089915c19..07a93b06f2c 100644 --- a/lisp/erc/erc-goodies.el +++ b/lisp/erc/erc-goodies.el @@ -50,19 +50,31 @@ erc-input-line-position :group 'erc-display :type '(choice integer (const nil))) +(defcustom erc-scrolltobottom-all nil + "Whether to scroll all windows or just the selected one. +A value of nil preserves pre-5.6 behavior, in which scrolling +only affects the selected window. Users should consider its +non-nil behavior experimental for the time being. Note also that +ERC expects this option to be configured before module +initialization." + :group 'erc-display + :package-version '(ERC . "5.6") ; FIXME sync on release + :type 'boolean) + (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'." +step 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 the +experimental companion option `erc-scrolltobottom-all' is enabled +and, only then, during module setup." :group 'erc-display :package-version '(ERC . "5.6") ; FIXME sync on release :type 'boolean) @@ -70,18 +82,25 @@ erc-scrolltobottom-relaxed ;;;###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--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--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))) + ((add-hook 'erc-mode-hook #'erc--scrolltobottom-setup) + (unless erc--updating-modules-p (erc-buffer-do #'erc--scrolltobottom-setup)) + (if erc-scrolltobottom-all + (progn + (add-hook 'erc-insert-pre-hook #'erc--scrolltobottom-on-pre-insert 25) + (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)) + (add-hook 'erc-insert-done-hook #'erc-possibly-scroll-to-bottom))) + ((remove-hook 'erc-mode-hook #'erc--scrolltobottom-setup) + (erc-buffer-do #'erc--scrolltobottom-setup) + (if erc-scrolltobottom-all + (progn + (remove-hook 'erc-insert-pre-hook #'erc--scrolltobottom-on-pre-insert) + (remove-hook 'erc-send-completed-hook #'erc--scrolltobottom-all) + (remove-hook 'erc-insert-done-hook #'erc--scrolltobottom-all) + (remove-hook 'erc-pre-send-functions + #'erc--scrolltobottom-on-pre-insert)) + (remove-hook 'erc-insert-done-hook #'erc-possibly-scroll-to-bottom)))) (defun erc-possibly-scroll-to-bottom () "Like `erc-add-scroll-to-bottom', but only if window is selected." @@ -100,8 +119,12 @@ erc--scrolltobottom-window-info `window-point', inclusive.") (defvar erc--scrolltobottom-post-force-commands - '(electric-newline-and-maybe-indent default-indent-new-line) - "Commands that force a scroll after execution at prompt.") + '(beginning-of-buffer + electric-newline-and-maybe-indent + default-indent-new-line) + "Commands that force a scroll after execution at prompt. +That is, ERC recalculates the window's start instead of blindly +restoring it.") (defvar erc--scrolltobottom-relaxed-skip-commands '(recenter-top-bottom scroll-down-command)) @@ -112,7 +135,7 @@ erc--scrolltobottom-on-pre-command (setq erc--scrolltobottom-window-info (list (list (selected-window) (window-start) - (count-screen-lines (window-start) (point))))))) + (count-screen-lines (window-start) (point-max))))))) (defun erc--scrolltobottom-on-post-command () "Restore window start or scroll to prompt and recenter. @@ -127,7 +150,8 @@ erc--scrolltobottom-on-post-command ((eq (car found) (selected-window))) ((not (memq this-command erc--scrolltobottom-post-force-commands))) - ((= (nth 2 found) (count-screen-lines (window-start) (point))))) + ((= (nth 2 found) + (count-screen-lines (window-start) (point-max))))) (set-window-start (selected-window) (nth 1 found)) (erc--scrolltobottom-confirm)) (setq erc--scrolltobottom-window-info nil))) @@ -137,8 +161,9 @@ erc--scrolltobottom-on-pre-command-relaxed 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." +When at prompt and current command does not appear in +`erc--scrolltobottom-relaxed-skip-commands', stash +`erc--scrolltobottom-window-info' for the selected window." (when (eq (selected-window) (get-buffer-window)) (when (and (not (input-pending-p)) (< (point) erc-input-marker) @@ -175,7 +200,9 @@ erc--scrolltobottom-on-post-command-relaxed ;; that may not be worth the added bookkeeping. (defun erc--scrolltobottom-away-from-prompt () "Scroll to bottom unless at prompt." - (unless (input-pending-p) + (unless (or (input-pending-p) + (minibuffer-window-active-p (minibuffer-window)) + (eq (old-selected-window) (minibuffer-window))) (erc--scrolltobottom-confirm))) (defun erc--scrolltobottom-all (&rest _) @@ -195,8 +222,7 @@ erc--scrolltobottom-all (setq erc--scrolltobottom-window-info nil)) (defun erc-add-scroll-to-bottom () - "Arrange for scrolling to bottom on window configuration changes. -Undo that arrangement when disabling `erc-scrolltobottom-mode'. + "A hook function for `erc-mode-hook' to recenter output at bottom of window. If you find that ERC hangs when using this function, try customizing the value of `erc-input-line-position'. @@ -204,24 +230,35 @@ erc-add-scroll-to-bottom 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." + (declare (obsolete erc--scrolltobottom-setup "30.1")) + (add-hook 'post-command-hook #'erc-scroll-to-bottom nil t)) + +(cl-defgeneric erc--scrolltobottom-setup () + "Arrange for scrolling to bottom on window configuration changes. +Undo that arrangement when disabling `erc-scrolltobottom-mode'." (if erc-scrolltobottom-mode - (progn + (add-hook 'post-command-hook #'erc-scroll-to-bottom nil t) + (remove-hook 'post-command-hook #'erc-scroll-to-bottom t))) + +(cl-defmethod erc--scrolltobottom-setup (&context + (erc-scrolltobottom-all (eql t))) + (if erc-scrolltobottom-mode + (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 60 t) + (add-hook 'pre-command-hook ; preempt `move-to-prompt' + #'erc--scrolltobottom-on-pre-command-relaxed 60 t)) (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 60 t) - (add-hook 'pre-command-hook ; preempt `move-to-prompt' - #'erc--scrolltobottom-on-pre-command-relaxed 60 t)) - (add-hook 'pre-command-hook - #'erc--scrolltobottom-on-pre-command 60 t) - (add-hook 'post-command-hook - #'erc--scrolltobottom-on-post-command 60 t))) + (add-hook 'pre-command-hook + #'erc--scrolltobottom-on-pre-command 60 t) + (add-hook 'post-command-hook + #'erc--scrolltobottom-on-post-command 60 t)) (remove-hook 'window-configuration-change-hook #'erc--scrolltobottom-away-from-prompt t) (remove-hook 'pre-command-hook @@ -255,9 +292,9 @@ erc--scrolltobottom-on-pre-insert (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." +Position current line (with `recenter') SCROLL-TO lines below +window's top. Return nil if point is not in prompt area or if +prompt isn't ready." (when erc-insert-marker (let ((resize-mini-windows nil)) (save-restriction @@ -332,7 +369,7 @@ erc-move-to-prompt-setup ;;;###autoload(autoload 'erc-keep-place-mode "erc-goodies" nil t) (define-erc-module keep-place nil "Leave point above un-viewed text in other channels." - ((add-hook 'erc-insert-pre-hook #'erc-keep-place)) + ((add-hook 'erc-insert-pre-hook #'erc-keep-place 85)) ((remove-hook 'erc-insert-pre-hook #'erc-keep-place))) (defcustom erc-keep-place-indicator-style t @@ -427,7 +464,7 @@ keep-place-indicator ((memq 'keep-place erc-modules) (erc-keep-place-mode +1)) ;; Enable a local version of `keep-place-mode'. - (t (add-hook 'erc-insert-pre-hook #'erc-keep-place 90 t))) + (t (add-hook 'erc-insert-pre-hook #'erc-keep-place 85 t))) (if (pcase erc-keep-place-indicator-buffer-type ('target erc--target) ('server (not erc--target)) @@ -450,7 +487,7 @@ erc--keep-place-indicator-on-global-module global one." (if erc-keep-place-mode (remove-hook 'erc-insert-pre-hook #'erc-keep-place t) - (add-hook 'erc-insert-pre-hook #'erc-keep-place 90 t))) + (add-hook 'erc-insert-pre-hook #'erc-keep-place 85 t))) (defun erc-keep-place-move (pos) "Move keep-place indicator to current line or POS. diff --git a/test/lisp/erc/erc-scenarios-scrolltobottom-all.el b/test/lisp/erc/erc-scenarios-scrolltobottom-all.el new file mode 100644 index 00000000000..33f232e64d9 --- /dev/null +++ b/test/lisp/erc/erc-scenarios-scrolltobottom-all.el @@ -0,0 +1,48 @@ +;;; erc-scenarios-scrolltobottom-all.el --- erc-scrolltobottom-all test -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-goodies) + +(ert-deftest erc-scenarios-scrolltobottom--all () + :tags '(:expensive-test) + (when (version< emacs-version "29") (ert-skip "Times out")) + + (should-not erc-scrolltobottom-all) + + (let ((erc-scrolltobottom-all t)) + + (erc-scenarios-scrolltobottom--normal + (lambda () + (ert-info ("New insertion anchors prompt in other window") + (let ((w (next-window))) + ;; We're at prompt and aligned to bottom. + (should (>= (window-point w) erc-input-marker)) + (erc-d-t-wait-for 10 + (erc-scenarios-common--at-win-end-p w)) + (erc-d-t-ensure-for 0.5 + (erc-scenarios-common--at-win-end-p w)))))))) + +;;; erc-scenarios-scrolltobottom-all.el ends here diff --git a/test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el b/test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el new file mode 100644 index 00000000000..7d256bf711b --- /dev/null +++ b/test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el @@ -0,0 +1,140 @@ +;;; erc-scenarios-scrolltobottom-relaxed.el --- erc-scrolltobottom-relaxed -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;; TODO assert behavior of prompt input spanning multiple lines, with +;; and without line endings. + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-goodies) + +(ert-deftest erc-scenarios-scrolltobottom--relaxed () + :tags '(:expensive-test) + (when (version< emacs-version "29") (ert-skip "Times out")) + + (should-not erc-scrolltobottom-all) + + (erc-scenarios-common-with-noninteractive-in-term + ((erc-scenarios-common-dialog "scrolltobottom") + (dumb-server (erc-d-run "localhost" t 'help)) + (port (process-contact dumb-server :service)) + (erc-modules `(scrolltobottom fill-wrap ,@erc-modules)) + (erc-scrolltobottom-all t) + (erc-scrolltobottom-relaxed t) + (erc-server-flood-penalty 0.1) + (expect (erc-d-t-make-expecter)) + lower upper) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :full-name "tester" + :nick "tester") + (funcall expect 10 "debug mode"))) + + (with-current-buffer "foonet" + (should (looking-at " and")) + (set-window-buffer nil (current-buffer)) + (delete-other-windows) + (split-window-below 15) + (recenter 0) + + (ert-info ("Moving into prompt does not trigger scroll") + (with-selected-window (next-window) + (should-not (erc-scenarios-common--at-win-end-p)) + (recenter 0) + (goto-char (1- erc-insert-marker)) + (execute-kbd-macro "\C-n") + (should-not (erc-scenarios-common--at-win-end-p)) + (should (= (point) (point-max))) + (setq lower (count-screen-lines (window-start) (window-point))))) + + (ert-info ("Module `move-to-prompt' still works") + ;; Prompt is somewhere in the middle of the window. + (should (erc-scenarios-common--above-win-end-p)) + (should-not (= (point-max) (point))) + ;; Hitting a self-insert key triggers `move-to-prompt' but not + ;; a scroll (to bottom). + (execute-kbd-macro "hi") + ;; Prompt and input appear on same line. + (should (= (point-max) (point))) + (setq upper (count-screen-lines (window-start) (window-point))) + (should-not (= upper (window-body-height)))) + + (ert-info ("Command `recenter-top-bottom' allowed at prompt") + ;; Hitting C-l recenters the window. + (should (= upper (count-screen-lines (window-start) (window-point)))) + (let ((lines (list upper))) + (erc-scenarios-common--recenter-top-bottom) + (push (count-screen-lines (window-start) (window-point)) lines) + (erc-scenarios-common--recenter-top-bottom) + (push (count-screen-lines (window-start) (window-point)) lines) + (erc-scenarios-common--recenter-top-bottom) + (push (count-screen-lines (window-start) (window-point)) lines) + (setq lines (delete-dups lines)) + (should (= (length lines) 4)))) + + (ert-info ("Command `beginning-of-buffer' allowed at prompt") + ;; Hitting C-< goes to beginning of buffer. + (execute-kbd-macro "\M-<") + (should (= 1 (point))) + (redisplay) + (should (zerop (count-screen-lines (window-start) (window-point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (ert-info ("New message doesn't trigger scroll when away from prompt") + ;; Arriving insertions don't trigger a scroll when away from the + ;; prompt. New output not seen. + (erc-cmd-MSG "NickServ help register") + (save-excursion (erc-d-t-search-for 10 "End of NickServ")) + (should (= 1 (point))) + (should (zerop (count-screen-lines (window-start) (window-point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (ert-info ("New insertion keeps prompt stationary in other window") + (let ((w (next-window))) + ;; We're at prompt and completely stationary. + (should (>= (window-point w) erc-input-marker)) + (erc-d-t-wait-for 10 + (= lower (count-screen-lines (window-start w) (window-point w)))) + (erc-d-t-ensure-for 0.5 + (= lower (count-screen-lines (window-start w) + (window-point w)))))) + + (should (= 2 (length (window-list)))) + (ert-info ("New message does not trigger a scroll when at prompt") + ;; Recenter so prompt is above rather than at window's end. + (funcall expect 10 "End of NickServ HELP") + (recenter 0) + (set-window-point nil (point-max)) + (setq upper (count-screen-lines (window-start) (window-point))) + ;; Prompt is somewhere in the middle of the window. + (erc-d-t-wait-for 10 (erc-scenarios-common--above-win-end-p)) + (erc-scenarios-common-say "/msg NickServ help identify") + ;; New arriving messages don't move prompt. + (erc-d-t-ensure-for 1 + (= upper (count-screen-lines (window-start) (window-point)))) + (funcall expect 10 "IDENTIFY lets you login"))))) + +;;; erc-scenarios-scrolltobottom-relaxed.el ends here diff --git a/test/lisp/erc/erc-scenarios-scrolltobottom.el b/test/lisp/erc/erc-scenarios-scrolltobottom.el new file mode 100644 index 00000000000..44e64204fb1 --- /dev/null +++ b/test/lisp/erc/erc-scenarios-scrolltobottom.el @@ -0,0 +1,44 @@ +;;; erc-scenarios-scrolltobottom.el --- erc-scrolltobottom-mode -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-goodies) + +(ert-deftest erc-scenarios-scrolltobottom--normal () + :tags '(:expensive-test) + (when (version< emacs-version "29") (ert-skip "Times out")) + + (should-not erc-scrolltobottom-all) + + (erc-scenarios-scrolltobottom--normal + (lambda () + (ert-info ("New insertion doesn't anchor prompt in other window") + (let ((w (next-window))) + ;; We're at prompt but not aligned to bottom. + (should (>= (window-point w) erc-input-marker)) + (erc-d-t-wait-for 10 + (not (erc-scenarios-common--at-win-end-p w)))))))) + +;;; erc-scenarios-scrolltobottom.el ends here diff --git a/test/lisp/erc/resources/base/assoc/bumped/again.eld b/test/lisp/erc/resources/base/assoc/bumped/again.eld index ab3c7b06214..aef164b6237 100644 --- a/test/lisp/erc/resources/base/assoc/bumped/again.eld +++ b/test/lisp/erc/resources/base/assoc/bumped/again.eld @@ -1,10 +1,10 @@ ;; -*- mode: lisp-data; -*- -((nick 1 "NICK tester")) -((user 1 "USER user 0 * :tester") +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") (0.0 ":irc.foonet.org 433 * tester :Nickname is reserved by a different account") (0.0 ":irc.foonet.org FAIL NICK NICKNAME_RESERVED tester :Nickname is reserved by a different account")) -((nick 3 "NICK tester`") +((nick 10 "NICK tester`") (0.1 ":irc.foonet.org 001 tester` :Welcome to the foonet IRC Network tester`") (0.0 ":irc.foonet.org 002 tester` :Your host is irc.foonet.org, running version oragono-2.6.1-937b9b02368748e5") (0.0 ":irc.foonet.org 003 tester` :This server was created Fri, 24 Sep 2021 01:38:36 UTC") @@ -21,10 +21,10 @@ (0.2 ":irc.foonet.org 266 tester` 3 3 :Current global users 3, max 3") (0.0 ":irc.foonet.org 422 tester` :MOTD File is missing")) -((mode-user 3.2 "MODE tester` +i") +((mode-user 10 "MODE tester` +i") (0.0 ":irc.foonet.org 221 tester` +i") (0.0 ":irc.foonet.org NOTICE tester` :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) -((privmsg 42.6 "PRIVMSG NickServ :IDENTIFY tester changeme") +((privmsg 10 "PRIVMSG NickServ :IDENTIFY tester changeme") (0.01 ":tester`!~u@rpaau95je67ci.irc NICK tester") (0.0 ":NickServ!NickServ@localhost NOTICE tester :You're now logged in as tester")) diff --git a/test/lisp/erc/resources/base/assoc/bumped/foisted.eld b/test/lisp/erc/resources/base/assoc/bumped/foisted.eld index 5c36e58d9d3..0f7aadac564 100644 --- a/test/lisp/erc/resources/base/assoc/bumped/foisted.eld +++ b/test/lisp/erc/resources/base/assoc/bumped/foisted.eld @@ -1,6 +1,6 @@ ;; -*- mode: lisp-data; -*- -((nick 1 "NICK tester")) -((user 1 "USER user 0 * :tester") +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") (0.0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") (0.0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version oragono-2.6.1-937b9b02368748e5") (0.0 ":irc.foonet.org 003 tester :This server was created Fri, 24 Sep 2021 01:38:36 UTC") @@ -17,14 +17,14 @@ (0.0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3") (0.0 ":irc.foonet.org 422 tester :MOTD File is missing")) -((mode-user 1.2 "MODE tester +i") +((mode-user 10 "MODE tester +i") (0.0 ":irc.foonet.org 221 tester +i") (0.0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) -((privmsg 17.21 "PRIVMSG bob :hi") +((privmsg 10 "PRIVMSG bob :hi") (0.02 ":bob!~u@ecnnh95wr67pv.net PRIVMSG tester :hola") (0.01 ":bob!~u@ecnnh95wr67pv.net PRIVMSG tester :how r u?")) -((quit 18.19 "QUIT :" quit) +((quit 10 "QUIT :" quit) (0.01 ":tester!~u@rpaau95je67ci.irc QUIT :Quit: " quit)) ((drop 1 DROP)) diff --git a/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld b/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld index 33e4168ac46..63366d3f576 100644 --- a/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld +++ b/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld @@ -1,6 +1,6 @@ ;; -*- mode: lisp-data; -*- -((nick 1 "NICK tester")) -((user 1 "USER user 0 * :tester") +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") (0.1 ":irc.foonet.org 001 dummy :Welcome to the foonet IRC Network dummy") (0.0 ":irc.foonet.org 002 dummy :Your host is irc.foonet.org, running version oragono-2.6.1-937b9b02368748e5") (0.0 ":irc.foonet.org 003 dummy :This server was created Fri, 24 Sep 2021 01:38:36 UTC") @@ -22,10 +22,10 @@ (0.01 ":bob!~u@ecnnh95wr67pv.net PRIVMSG dummy :back?") ) -((mode-user 1.2 "MODE dummy +i") +((mode-user 10 "MODE dummy +i") (0.0 ":irc.foonet.org 221 dummy +i") (0.0 ":irc.foonet.org NOTICE dummy :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) -((renick 42.6 "NICK tester") +((renick 10 "NICK tester") (0.01 ":dummy!~u@rpaau95je67ci.irc NICK tester") (0.0 ":NickServ!NickServ@localhost NOTICE dummy :You're now logged in as tester")) diff --git a/test/lisp/erc/resources/base/netid/bouncer/barnet.eld b/test/lisp/erc/resources/base/netid/bouncer/barnet.eld index 204d01fef77..596383c2699 100644 --- a/test/lisp/erc/resources/base/netid/bouncer/barnet.eld +++ b/test/lisp/erc/resources/base/netid/bouncer/barnet.eld @@ -38,4 +38,4 @@ (0.05 ":joe!~u@awyxgybtkx7uq.irc PRIVMSG #chan :mike: As he regards his aged father's life.") (0.05 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :joe: It is a rupture that you may easily heal; and the cure of it not only saves your brother, but keeps you from dishonor in doing it.")) -((linger 1 LINGER)) +((linger 2 LINGER)) diff --git a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld index 4445350ca0c..2e1a3ac27da 100644 --- a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld +++ b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld @@ -43,4 +43,4 @@ (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Orlando, my liege; the youngest son of Sir Rowland de Boys.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: The ape is dead, and I must conjure him.")) -((linger 1 LINGER)) +((linger 2 LINGER)) diff --git a/test/lisp/erc/resources/erc-d/erc-d-t.el b/test/lisp/erc/resources/erc-d/erc-d-t.el index 7b2adf4f07b..cf869fb3c70 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-t.el +++ b/test/lisp/erc/resources/erc-d/erc-d-t.el @@ -83,6 +83,8 @@ erc-d-t-with-cleanup (ignore-errors (kill-buffer buf))))) (sleep-for erc-d-t-cleanup-sleep-secs))))) +(defvar erc-d-t--wait-message-prefix "Awaiting: ") + (defmacro erc-d-t-wait-for (max-secs msg &rest body) "Wait for BODY to become non-nil. Or signal error with MSG after MAX-SECS. When MAX-SECS is negative, @@ -99,7 +101,7 @@ erc-d-t-wait-for (let ((inverted (make-symbol "inverted")) (time-out (make-symbol "time-out")) (result (make-symbol "result"))) - `(ert-info ((concat "Awaiting: " ,msg)) + `(ert-info ((concat erc-d-t--wait-message-prefix ,msg)) (let ((,time-out (abs ,max-secs)) (,inverted (< ,max-secs 0)) (,result ',result)) @@ -120,7 +122,8 @@ erc-d-t-ensure-for (unless (or (stringp msg) (memq (car-safe msg) '(format concat))) (push msg body) (setq msg (prin1-to-string body))) - `(erc-d-t-wait-for (- (abs ,max-secs)) ,msg (not (progn ,@body)))) + `(let ((erc-d-t--wait-message-prefix "Sustaining: ")) + (erc-d-t-wait-for (- (abs ,max-secs)) ,msg (not (progn ,@body))))) (defun erc-d-t-search-for (timeout text &optional from on-success) "Wait for TEXT to appear in current buffer before TIMEOUT secs. diff --git a/test/lisp/erc/resources/erc-scenarios-common.el b/test/lisp/erc/resources/erc-scenarios-common.el index 972faa5c73f..b92acdd81e8 100644 --- a/test/lisp/erc/resources/erc-scenarios-common.el +++ b/test/lisp/erc/resources/erc-scenarios-common.el @@ -183,6 +183,103 @@ erc-scenarios-common-with-cleanup ,@body))) +(defvar erc-scenarios-common--term-size '(34 . 80)) +(declare-function term-char-mode "term" nil) +(declare-function term-line-mode "term" nil) + +(defun erc-scenarios-common--run-in-term (&optional debug) + (require 'term) + (let* ((default-directory (getenv "EMACS_TEST_DIRECTORY")) + (emacs (expand-file-name invocation-name invocation-directory)) + (process-environment (cons "HOME=/nonexistent" + (cons "ERC_TESTS_SUBPROCESS=1" + process-environment))) + (name (ert-test-name (ert-running-test))) + (temp-file (make-temp-file "erc-term-test-")) + (cmd `(let ((stats 1)) + (setq enable-dir-local-variables nil) + (unwind-protect + (setq stats (ert-run-tests-batch ',name)) + (unless ',debug + (let ((buf (with-current-buffer (messages-buffer) + (buffer-string)))) + (with-temp-file ,temp-file + (insert buf))) + (kill-emacs (ert-stats-completed-unexpected stats)))))) + ;; The `ert-test' object in Emacs 29 has a `file-name' field. + (file-name (symbol-file name 'ert--test)) + (default-directory (expand-file-name (file-name-directory file-name))) + (package (if-let* ((found (getenv "ERC_PACKAGE_NAME")) + ((string-prefix-p "erc-" found))) + (intern found) + 'erc)) + ;; This is for testing ERC's ELPA-package on older Emacsen we + ;; still support. It won't run inside the emacs.git tree. + (setup (and (featurep 'compat) + `(progn + (require 'package) + (let ((package-load-list '((compat t) (,package t)))) + (package-initialize))))) + ;; Make subprocess terminal bigger than controlling. + (buf (cl-letf (((symbol-function 'window-screen-lines) + (lambda () (car erc-scenarios-common--term-size))) + ((symbol-function 'window-max-chars-per-line) + (lambda () (cdr erc-scenarios-common--term-size)))) + (make-term (symbol-name name) emacs nil "-Q" "-nw" + "-eval" (prin1-to-string setup) + "-l" file-name "-eval" (format "%S" cmd)))) + (proc (get-buffer-process buf)) + (err (lambda () + (with-temp-buffer + (insert-file-contents temp-file) + (message "Subprocess: %s" (buffer-string)) + (delete-file temp-file))))) + (set-window-buffer (selected-window) buf) + (delete-other-windows) + (with-current-buffer buf + (set-process-query-on-exit-flag proc nil) + (unless noninteractive (term-char-mode)) + (with-timeout (30 (funcall err) (error "Timed out awaiting result")) + (while (process-live-p proc) + (accept-process-output proc 0.1) + (unless noninteractive + (redisplay)))) + (while (accept-process-output proc)) + (term-line-mode) + (goto-char (point-min)) + ;; Otherwise gives process exited abnormally with exit-code >0 + (unless (search-forward (format "Process %s finished" name) nil t) + (funcall err) + (ert-fail (when (search-forward "exited" nil t) + (buffer-substring-no-properties (line-beginning-position) + (line-end-position))))) + (delete-file temp-file) + (when noninteractive + (kill-buffer))))) + +(defvar erc-scenarios-common-interactive-debug-term-p nil + "When non-nil, run interactive ") + +(defmacro erc-scenarios-common-with-noninteractive-in-term (&rest body) + "Run BODY via `erc-scenarios-common-with-cleanup' in a `term' subprocess. +Also do so when `erc-scenarios-common-interactive-debug-term-p' +is non-nil. When debugging, leave the `term-mode' buffer around +for inspection and name it after the test, bounded by asterisks. +When debugging, ensure the test always fails, as a reminder to +disable `erc-scenarios-common-interactive-debug-term-p'. + +See Info node `(emacs) Term Mode' for the various commands." + (declare (indent 1)) + `(if (and (or erc-scenarios-common-interactive-debug-term-p + noninteractive) + (not (getenv "ERC_TESTS_SUBPROCESS"))) + (progn + (when (memq system-type '(windows-nt ms-dos)) + (ert-skip "System must be UNIX")) + (erc-scenarios-common--run-in-term + erc-scenarios-common-interactive-debug-term-p)) + (erc-scenarios-common-with-cleanup ,@body))) + (defun erc-scenarios-common-assert-initial-buf-name (id port) ;; Assert no limbo period when explicit ID given (should (string= (if id @@ -209,9 +306,108 @@ erc-scenarios-common-say (insert str) (erc-send-current-line))) +(defun erc-scenarios-common--at-win-end-p (&optional window) + (= (window-body-height window) + (count-screen-lines (window-start window) (point-max) nil window))) + +(defun erc-scenarios-common--above-win-end-p (&optional window) + (> (window-body-height window) + (count-screen-lines (window-start window) (point-max)))) + +(defun erc-scenarios-common--prompt-past-win-end-p (&optional window) + (< (window-body-height window) + (count-screen-lines (window-start window) (point-max)))) + +(defun erc-scenarios-common--recenter-top-bottom-around (orig &rest args) + (let (this-command last-command) (apply orig args))) + +(defun erc-scenarios-common--recenter-top-bottom () + (advice-add 'recenter-top-bottom + :around #'erc-scenarios-common--recenter-top-bottom-around) + (execute-kbd-macro "\C-l") + (advice-remove 'recenter-top-bottom + #'erc-scenarios-common--recenter-top-bottom-around)) + ;;;; Fixtures +(defun erc-scenarios-scrolltobottom--normal (test) + (erc-scenarios-common-with-noninteractive-in-term + ((erc-scenarios-common-dialog "scrolltobottom") + (dumb-server (erc-d-run "localhost" t 'help)) + (port (process-contact dumb-server :service)) + (erc-modules `(scrolltobottom fill-wrap ,@erc-modules)) + (erc-server-flood-penalty 0.1) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :full-name "tester" + :nick "tester") + (funcall expect 10 "debug mode"))) + + (with-current-buffer "foonet" + (should (looking-at " and")) + (set-window-buffer nil (current-buffer)) + (delete-other-windows) + (split-window-below 15) + (recenter 0) + + (ert-info ("Moving into prompt in other window triggers scroll") + (with-selected-window (next-window) + (should-not (erc-scenarios-common--at-win-end-p)) + (goto-char (1- erc-insert-marker)) + (execute-kbd-macro "\C-n") + ;; Ensure point is at prompt and aligned to bottom. + (should (erc-scenarios-common--at-win-end-p)))) + + (ert-info ("Module `move-to-prompt' still works") + ;; Prompt is somewhere in the middle of the window. + (should (erc-scenarios-common--above-win-end-p)) + ;; Hitting a self-insert key triggers `move-to-prompt' as well + ;; as a scroll (to bottom). + (execute-kbd-macro "hi") + ;; Prompt and input appear on last line of window. + (should (erc-scenarios-common--at-win-end-p))) + + (ert-info ("Command `recenter-top-bottom' disallowed at prompt") + ;; Hitting C-l does not recenter the window. + (erc-scenarios-common--recenter-top-bottom) + (should (erc-scenarios-common--at-win-end-p)) + (erc-scenarios-common--recenter-top-bottom) + (should (erc-scenarios-common--at-win-end-p))) + + (ert-info ("Command `beginning-of-buffer' allowed at prompt") + ;; Hitting C-< goes to beginning of buffer. + (call-interactively #'beginning-of-buffer) + (should (= 1 (point))) + (redisplay) + (should (zerop (count-screen-lines (window-start) (point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (ert-info ("New message doesn't trigger scroll when away from prompt") + ;; Arriving insertions don't trigger a scroll when away from the + ;; prompt. New output not seen. + (erc-cmd-MSG "NickServ help register") + (save-excursion (erc-d-t-search-for 10 "End of NickServ")) + (should (= 1 (point))) + (should (zerop (count-screen-lines (window-start) (window-point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (funcall test) + + (ert-info ("New message does trigger a scroll when at prompt") + ;; Recenter so prompt is above rather than at window's end. + (funcall expect 10 "If you are currently logged in") + (recenter 0) + ;; Prompt is somewhere in the middle of the window. + (erc-d-t-wait-for 10 (erc-scenarios-common--above-win-end-p)) + (erc-scenarios-common-say "/msg NickServ help identify") + ;; New arriving messages trigger a snap when inserted. + (erc-d-t-wait-for 10 (erc-scenarios-common--at-win-end-p)) + (funcall expect 10 "IDENTIFY lets you login"))))) + (cl-defun erc-scenarios-common--base-network-id-bouncer ((&key autop foo-id bar-id after &aux diff --git a/test/lisp/erc/resources/scrolltobottom/help.eld b/test/lisp/erc/resources/scrolltobottom/help.eld new file mode 100644 index 00000000000..ba44a0def39 --- /dev/null +++ b/test/lisp/erc/resources/scrolltobottom/help.eld @@ -0,0 +1,46 @@ +;; -*- mode: lisp-data; -*- +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") + (0.00 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") + (0.01 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version ergo-v2.11.1") + (0.01 ":irc.foonet.org 003 tester :This server was created Mon, 21 Aug 2023 06:18:36 UTC") + (0.02 ":irc.foonet.org 004 tester irc.foonet.org ergo-v2.11.1 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv") + (0.00 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# CHATHISTORY=1000 ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX :are supported by this server") + (0.01 ":irc.foonet.org 005 tester KICKLEN=390 MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8ONLY WHOX :are supported by this server") + (0.01 ":irc.foonet.org 005 tester draft/CHATHISTORY=1000 :are supported by this server") + (0.01 ":irc.foonet.org 251 tester :There are 0 users and 4 invisible on 1 server(s)") + (0.01 ":irc.foonet.org 252 tester 0 :IRC Operators online") + (0.01 ":irc.foonet.org 253 tester 0 :unregistered connections") + (0.01 ":irc.foonet.org 254 tester 2 :channels formed") + (0.01 ":irc.foonet.org 255 tester :I have 4 clients and 0 servers") + (0.01 ":irc.foonet.org 265 tester 4 4 :Current local users 4, max 4") + (0.01 ":irc.foonet.org 266 tester 4 4 :Current global users 4, max 4") + (0.01 ":irc.foonet.org 422 tester :MOTD File is missing")) + +((mode 10 "MODE tester +i") + (0.00 ":irc.foonet.org 221 tester +i") + (0.01 ":irc.foonet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.") + (0.02 ":irc.foonet.org 221 tester +i")) + +((privmsg-help-register 10 "PRIVMSG NickServ :help register") + (0.05 ":NickServ!NickServ@localhost NOTICE tester :*** \2NickServ HELP\2 ***") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :Syntax: \2REGISTER [email]\2") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :REGISTER lets you register your current nickname as a user account. If the") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :server allows anonymous registration, you can omit the e-mail address.") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :If you are currently logged in with a TLS client certificate and wish to use") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :it instead of a password to log in, send * as the password.") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :*** \2End of NickServ HELP\2 ***")) + +((privmsg-help-identify 20 "PRIVMSG NickServ :help identify") + (0.06 ":NickServ!NickServ@localhost NOTICE tester :*** \2NickServ HELP\2 ***") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :Syntax: \2IDENTIFY [password]\2") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :IDENTIFY lets you login to the given username using either password auth, or") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :certfp (your client certificate) if a password is not given.") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :*** \2End of NickServ HELP\2 ***")) + +((quit 10 "QUIT :\2ERC\2 ") + (0.07 ":tester!~u@26axz8nh8zaag.irc QUIT :Quit: \2ERC\2") + (0.02 "ERROR :Quit: \2ERC\2")) -- 2.41.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Consider-all-windows-in-erc-scrolltobottom-mode.patch >From 3ed5a748d76989ec2ed79311e3550da5c6d1aaa1 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sat, 22 Jul 2023 00:46:44 -0700 Subject: [PATCH 1/1] [5.6] Consider all windows in erc-scrolltobottom-mode * etc/ERC-NEWS: Add entry for option `erc-scrolltobottom-all'. Also mention explicit hook-depth intervals reserved by ERC. * lisp/erc/erc-backend.el (erc--hide-prompt): Change hook depth on `pre-command-hook' from 91 to 80. * 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-all): New option that decides whether module `scrolltobottom' affects all windows or just the selected one, as it always has. (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-setup' instead of `erc-add-scroll-to-bottom' for adding and removing local hooks, instead of ranging over buffers when removing them. Also add and remove new hook members when `erc-scrolltobottom-all' is non-nil. (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): Deprecate this function because it is now unused. (erc--scrolltobottom-setup): New generic function to perform teardown as well as setup, depending on module mode var. Add scrolling on `window-configuration-changed-hook' when `erc-scrolltobottom-all' is non-nil and `erc-scrolltobottom-relaxed' is nil. (erc--scrolltobottom-on-pre-insert): New generic function that remembers the last `window-start' and maybe the current screen 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-move-to-prompt-setup): Add `erc-move-to-prompt' to `pre-command-hook' at a depth of 70. (erc-keep-place-mode, erc-keep-place-enable): Change hook depth from 0 to 85. (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. (erc-keep-place-indicator-mode, erc-keep-place-indicator-enable): Change hook depth from 90 to 85 so as not to conflict with t. (erc--keep-place-indicator-on-global-module): Change hook depth from 90 to 85. * test/lisp/erc/erc-scenarios-scrolltobottom.el: New file. * test/lisp/erc/erc-scenarios-scrolltobottom-all.el: New file. * test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el: New file. * test/lisp/erc/resources/base/assoc/bumped/again.eld: Adjust timeouts. * test/lisp/erc/resources/base/assoc/bumped/foisted.eld: Adjust timeouts. * test/lisp/erc/resources/base/assoc/bumped/refoisted.eld: Adjust timeouts. * test/lisp/erc/resources/base/netid/bouncer/barnet.eld: Adjust timeouts. * test/lisp/erc/resources/base/netid/bouncer/foonet.eld: Adjust timeouts. * test/lisp/erc/resources/erc-d/erc-d-t.el (erc-d-t--wait-message-prefix, erc-d-t-wait-for, erc-d-t-ensure-for): Add and use new variable to make `ert-info' message prefix adjustable. * test/lisp/erc/resources/erc-scenarios-common.el (erc-scenarios-common--term-size, erc-scenarios-common--run-in-term, erc-scenarios-common-interactive-debug-term-p, erc-scenarios-common-with-noninteractive-in-term): New test macro and supporting helper function and variables to facilitate running scenario-based tests in a subprocess. (erc-scenarios-common--at-win-end-p, erc-scenarios-common--above-win-end-p, erc-scenarios-common--prompt-past-win-end-p, erc-scenarios-common--recenter-top-bottom-around, erc-scenarios-common--recenter-top-bottom, erc-scenarios-scrolltobottom--normal): New test fixture and assertion helper functions. * test/lisp/erc/resources/scrolltobottom/help.eld: New file. (Bug#64855) --- etc/ERC-NEWS | 19 +- lisp/erc/erc-backend.el | 2 +- lisp/erc/erc-goodies.el | 267 ++++++++++++++++-- .../erc/erc-scenarios-scrolltobottom-all.el | 48 ++++ .../erc-scenarios-scrolltobottom-relaxed.el | 140 +++++++++ test/lisp/erc/erc-scenarios-scrolltobottom.el | 44 +++ .../erc/resources/base/assoc/bumped/again.eld | 10 +- .../resources/base/assoc/bumped/foisted.eld | 10 +- .../resources/base/assoc/bumped/refoisted.eld | 8 +- .../resources/base/netid/bouncer/barnet.eld | 2 +- .../resources/base/netid/bouncer/foonet.eld | 2 +- test/lisp/erc/resources/erc-d/erc-d-t.el | 7 +- .../erc/resources/erc-scenarios-common.el | 196 +++++++++++++ .../erc/resources/scrolltobottom/help.eld | 46 +++ 14 files changed, 761 insertions(+), 40 deletions(-) create mode 100644 test/lisp/erc/erc-scenarios-scrolltobottom-all.el create mode 100644 test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el create mode 100644 test/lisp/erc/erc-scenarios-scrolltobottom.el create mode 100644 test/lisp/erc/resources/scrolltobottom/help.eld diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 94452514e6d..72b768b02ec 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -178,6 +178,15 @@ been restored with a slightly revised role contingent on a few assumptions explained in its doc string. For clarity, it has been renamed 'erc-ensure-target-buffer-on-privmsg'. +** Module 'scrolltobottom' attempts to behave more sensibly. +Enabling the experimental option 'erc-scrolltobottom-all' tells +'scrolltobottom' to be more vigilant about recentering and to do so in +all ERC windows. The dependent option 'erc-scrolltobottom-relaxed', +likewise experimental, makes ERC's prompt stationary wherever it +resides instead of forcing it to the bottom of the window. That is, +new input appears above the prompt, scrolling existing messages upward +to compensate. + ** Subtle changes in two fundamental faces. Users of the default theme may notice that 'erc-action-face' and 'erc-notice-face' now appear slightly less bold on systems supporting @@ -232,9 +241,9 @@ property of the same name has been retained and now has a value of Built-in and third-party modules rely on certain hooks for adjusting incoming and outgoing messages upon insertion. And some modules only want to do so after others have done their damage. Traditionally, -this required various hacks and finagling to achieve. And while this -release makes an effort to load modules in a more consistent order, -that alone isn't enough to ensure similar predictability among +this has required various hacks and finagling to achieve. And while +this release makes an effort to load modules in a more consistent +order, that alone isn't enough to ensure similar predictability among essential members of important hooks. Luckily, ERC now leverages a feature introduced in Emacs 27, "hook @@ -248,6 +257,10 @@ the first two, 'erc-button-add-buttons' and 'erc-fill', which have been swapped with respect to their previous places in recent ERC versions. +The same depth interval is now also provisionally reserved for +'erc-insert-pre-hook' and for the non-ERC hooks 'pre-command-hook' and +'post-command-hook', but only locally, in ERC buffers. + *** ERC now manages timestamp-related properties a bit differently. For starters, the 'cursor-sensor-functions' property no longer contains unique closures and thus no longer proves effective for diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index eb3ec39fedd..9e121ec1e92 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -1089,7 +1089,7 @@ erc--hide-prompt (put-text-property erc-insert-marker (1- erc-input-marker) 'erc-prompt 'hidden) (erc--conceal-prompt)) - (add-hook 'pre-command-hook #'erc--unhide-prompt-on-self-insert 91 t)))) + (add-hook 'pre-command-hook #'erc--unhide-prompt-on-self-insert 80 t)))) (defun erc-process-sentinel (cproc event) "Sentinel function for ERC process." diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el index b37855cbecc..07a93b06f2c 100644 --- a/lisp/erc/erc-goodies.el +++ b/lisp/erc/erc-goodies.el @@ -44,42 +44,270 @@ 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-all nil + "Whether to scroll all windows or just the selected one. +A value of nil preserves pre-5.6 behavior, in which scrolling +only affects the selected window. Users should consider its +non-nil behavior experimental for the time being. Note also that +ERC expects this option to be configured before module +initialization." + :group 'erc-display + :package-version '(ERC . "5.6") ; FIXME sync on release + :type 'boolean) + +(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 +step 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 the +experimental companion option `erc-scrolltobottom-all' is enabled +and, only then, during module setup." + :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) - (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))))) + ((add-hook 'erc-mode-hook #'erc--scrolltobottom-setup) + (unless erc--updating-modules-p (erc-buffer-do #'erc--scrolltobottom-setup)) + (if erc-scrolltobottom-all + (progn + (add-hook 'erc-insert-pre-hook #'erc--scrolltobottom-on-pre-insert 25) + (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)) + (add-hook 'erc-insert-done-hook #'erc-possibly-scroll-to-bottom))) + ((remove-hook 'erc-mode-hook #'erc--scrolltobottom-setup) + (erc-buffer-do #'erc--scrolltobottom-setup) + (if erc-scrolltobottom-all + (progn + (remove-hook 'erc-insert-pre-hook #'erc--scrolltobottom-on-pre-insert) + (remove-hook 'erc-send-completed-hook #'erc--scrolltobottom-all) + (remove-hook 'erc-insert-done-hook #'erc--scrolltobottom-all) + (remove-hook 'erc-pre-send-functions + #'erc--scrolltobottom-on-pre-insert)) + (remove-hook 'erc-insert-done-hook #'erc-possibly-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 + '(beginning-of-buffer + electric-newline-and-maybe-indent + default-indent-new-line) + "Commands that force a scroll after execution at prompt. +That is, ERC recalculates the window's start instead of blindly +restoring it.") + +(defvar erc--scrolltobottom-relaxed-skip-commands + '(recenter-top-bottom scroll-down-command)) + +(defun erc--scrolltobottom-on-pre-command () + (when (and (eq (selected-window) (get-buffer-window)) + (>= (point) erc-input-marker)) + (setq erc--scrolltobottom-window-info + (list (list (selected-window) + (window-start) + (count-screen-lines (window-start) (point-max))))))) + +(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." + (when (eq (selected-window) (get-buffer-window)) + (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-max))))) + (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 does not appear in +`erc--scrolltobottom-relaxed-skip-commands', stash +`erc--scrolltobottom-window-info' for the selected window." + (when (eq (selected-window) (get-buffer-window)) + (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." + (when-let (((eq (selected-window) (get-buffer-window))) + (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 (or (input-pending-p) + (minibuffer-window-active-p (minibuffer-window)) + (eq (old-selected-window) (minibuffer-window))) + (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. 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'." +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." + (declare (obsolete erc--scrolltobottom-setup "30.1")) (add-hook 'post-command-hook #'erc-scroll-to-bottom nil t)) +(cl-defgeneric erc--scrolltobottom-setup () + "Arrange for scrolling to bottom on window configuration changes. +Undo that arrangement when disabling `erc-scrolltobottom-mode'." + (if erc-scrolltobottom-mode + (add-hook 'post-command-hook #'erc-scroll-to-bottom nil t) + (remove-hook 'post-command-hook #'erc-scroll-to-bottom t))) + +(cl-defmethod erc--scrolltobottom-setup (&context + (erc-scrolltobottom-all (eql t))) + (if erc-scrolltobottom-mode + (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 60 t) + (add-hook 'pre-command-hook ; preempt `move-to-prompt' + #'erc--scrolltobottom-on-pre-command-relaxed 60 t)) + (add-hook 'window-configuration-change-hook + #'erc--scrolltobottom-away-from-prompt nil t) + (add-hook 'pre-command-hook + #'erc--scrolltobottom-on-pre-command 60 t) + (add-hook 'post-command-hook + #'erc--scrolltobottom-on-post-command 60 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) + (point-max) 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'. +Position current line (with `recenter') SCROLL-TO lines below +window's top. Return nil if point is not in prompt area or if +prompt isn't ready." + (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 @@ -135,13 +363,13 @@ erc-move-to-prompt (defun erc-move-to-prompt-setup () "Initialize the move-to-prompt module." - (add-hook 'pre-command-hook #'erc-move-to-prompt nil t)) + (add-hook 'pre-command-hook #'erc-move-to-prompt 70 t)) ;;; Keep place in unvisited channels ;;;###autoload(autoload 'erc-keep-place-mode "erc-goodies" nil t) (define-erc-module keep-place nil "Leave point above un-viewed text in other channels." - ((add-hook 'erc-insert-pre-hook #'erc-keep-place)) + ((add-hook 'erc-insert-pre-hook #'erc-keep-place 85)) ((remove-hook 'erc-insert-pre-hook #'erc-keep-place))) (defcustom erc-keep-place-indicator-style t @@ -213,12 +441,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))) @@ -233,7 +464,7 @@ keep-place-indicator ((memq 'keep-place erc-modules) (erc-keep-place-mode +1)) ;; Enable a local version of `keep-place-mode'. - (t (add-hook 'erc-insert-pre-hook #'erc-keep-place 90 t))) + (t (add-hook 'erc-insert-pre-hook #'erc-keep-place 85 t))) (if (pcase erc-keep-place-indicator-buffer-type ('target erc--target) ('server (not erc--target)) @@ -256,7 +487,7 @@ erc--keep-place-indicator-on-global-module global one." (if erc-keep-place-mode (remove-hook 'erc-insert-pre-hook #'erc-keep-place t) - (add-hook 'erc-insert-pre-hook #'erc-keep-place 90 t))) + (add-hook 'erc-insert-pre-hook #'erc-keep-place 85 t))) (defun erc-keep-place-move (pos) "Move keep-place indicator to current line or POS. diff --git a/test/lisp/erc/erc-scenarios-scrolltobottom-all.el b/test/lisp/erc/erc-scenarios-scrolltobottom-all.el new file mode 100644 index 00000000000..33f232e64d9 --- /dev/null +++ b/test/lisp/erc/erc-scenarios-scrolltobottom-all.el @@ -0,0 +1,48 @@ +;;; erc-scenarios-scrolltobottom-all.el --- erc-scrolltobottom-all test -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-goodies) + +(ert-deftest erc-scenarios-scrolltobottom--all () + :tags '(:expensive-test) + (when (version< emacs-version "29") (ert-skip "Times out")) + + (should-not erc-scrolltobottom-all) + + (let ((erc-scrolltobottom-all t)) + + (erc-scenarios-scrolltobottom--normal + (lambda () + (ert-info ("New insertion anchors prompt in other window") + (let ((w (next-window))) + ;; We're at prompt and aligned to bottom. + (should (>= (window-point w) erc-input-marker)) + (erc-d-t-wait-for 10 + (erc-scenarios-common--at-win-end-p w)) + (erc-d-t-ensure-for 0.5 + (erc-scenarios-common--at-win-end-p w)))))))) + +;;; erc-scenarios-scrolltobottom-all.el ends here diff --git a/test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el b/test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el new file mode 100644 index 00000000000..7d256bf711b --- /dev/null +++ b/test/lisp/erc/erc-scenarios-scrolltobottom-relaxed.el @@ -0,0 +1,140 @@ +;;; erc-scenarios-scrolltobottom-relaxed.el --- erc-scrolltobottom-relaxed -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;; TODO assert behavior of prompt input spanning multiple lines, with +;; and without line endings. + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-goodies) + +(ert-deftest erc-scenarios-scrolltobottom--relaxed () + :tags '(:expensive-test) + (when (version< emacs-version "29") (ert-skip "Times out")) + + (should-not erc-scrolltobottom-all) + + (erc-scenarios-common-with-noninteractive-in-term + ((erc-scenarios-common-dialog "scrolltobottom") + (dumb-server (erc-d-run "localhost" t 'help)) + (port (process-contact dumb-server :service)) + (erc-modules `(scrolltobottom fill-wrap ,@erc-modules)) + (erc-scrolltobottom-all t) + (erc-scrolltobottom-relaxed t) + (erc-server-flood-penalty 0.1) + (expect (erc-d-t-make-expecter)) + lower upper) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :full-name "tester" + :nick "tester") + (funcall expect 10 "debug mode"))) + + (with-current-buffer "foonet" + (should (looking-at " and")) + (set-window-buffer nil (current-buffer)) + (delete-other-windows) + (split-window-below 15) + (recenter 0) + + (ert-info ("Moving into prompt does not trigger scroll") + (with-selected-window (next-window) + (should-not (erc-scenarios-common--at-win-end-p)) + (recenter 0) + (goto-char (1- erc-insert-marker)) + (execute-kbd-macro "\C-n") + (should-not (erc-scenarios-common--at-win-end-p)) + (should (= (point) (point-max))) + (setq lower (count-screen-lines (window-start) (window-point))))) + + (ert-info ("Module `move-to-prompt' still works") + ;; Prompt is somewhere in the middle of the window. + (should (erc-scenarios-common--above-win-end-p)) + (should-not (= (point-max) (point))) + ;; Hitting a self-insert key triggers `move-to-prompt' but not + ;; a scroll (to bottom). + (execute-kbd-macro "hi") + ;; Prompt and input appear on same line. + (should (= (point-max) (point))) + (setq upper (count-screen-lines (window-start) (window-point))) + (should-not (= upper (window-body-height)))) + + (ert-info ("Command `recenter-top-bottom' allowed at prompt") + ;; Hitting C-l recenters the window. + (should (= upper (count-screen-lines (window-start) (window-point)))) + (let ((lines (list upper))) + (erc-scenarios-common--recenter-top-bottom) + (push (count-screen-lines (window-start) (window-point)) lines) + (erc-scenarios-common--recenter-top-bottom) + (push (count-screen-lines (window-start) (window-point)) lines) + (erc-scenarios-common--recenter-top-bottom) + (push (count-screen-lines (window-start) (window-point)) lines) + (setq lines (delete-dups lines)) + (should (= (length lines) 4)))) + + (ert-info ("Command `beginning-of-buffer' allowed at prompt") + ;; Hitting C-< goes to beginning of buffer. + (execute-kbd-macro "\M-<") + (should (= 1 (point))) + (redisplay) + (should (zerop (count-screen-lines (window-start) (window-point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (ert-info ("New message doesn't trigger scroll when away from prompt") + ;; Arriving insertions don't trigger a scroll when away from the + ;; prompt. New output not seen. + (erc-cmd-MSG "NickServ help register") + (save-excursion (erc-d-t-search-for 10 "End of NickServ")) + (should (= 1 (point))) + (should (zerop (count-screen-lines (window-start) (window-point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (ert-info ("New insertion keeps prompt stationary in other window") + (let ((w (next-window))) + ;; We're at prompt and completely stationary. + (should (>= (window-point w) erc-input-marker)) + (erc-d-t-wait-for 10 + (= lower (count-screen-lines (window-start w) (window-point w)))) + (erc-d-t-ensure-for 0.5 + (= lower (count-screen-lines (window-start w) + (window-point w)))))) + + (should (= 2 (length (window-list)))) + (ert-info ("New message does not trigger a scroll when at prompt") + ;; Recenter so prompt is above rather than at window's end. + (funcall expect 10 "End of NickServ HELP") + (recenter 0) + (set-window-point nil (point-max)) + (setq upper (count-screen-lines (window-start) (window-point))) + ;; Prompt is somewhere in the middle of the window. + (erc-d-t-wait-for 10 (erc-scenarios-common--above-win-end-p)) + (erc-scenarios-common-say "/msg NickServ help identify") + ;; New arriving messages don't move prompt. + (erc-d-t-ensure-for 1 + (= upper (count-screen-lines (window-start) (window-point)))) + (funcall expect 10 "IDENTIFY lets you login"))))) + +;;; erc-scenarios-scrolltobottom-relaxed.el ends here diff --git a/test/lisp/erc/erc-scenarios-scrolltobottom.el b/test/lisp/erc/erc-scenarios-scrolltobottom.el new file mode 100644 index 00000000000..44e64204fb1 --- /dev/null +++ b/test/lisp/erc/erc-scenarios-scrolltobottom.el @@ -0,0 +1,44 @@ +;;; erc-scenarios-scrolltobottom.el --- erc-scrolltobottom-mode -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-goodies) + +(ert-deftest erc-scenarios-scrolltobottom--normal () + :tags '(:expensive-test) + (when (version< emacs-version "29") (ert-skip "Times out")) + + (should-not erc-scrolltobottom-all) + + (erc-scenarios-scrolltobottom--normal + (lambda () + (ert-info ("New insertion doesn't anchor prompt in other window") + (let ((w (next-window))) + ;; We're at prompt but not aligned to bottom. + (should (>= (window-point w) erc-input-marker)) + (erc-d-t-wait-for 10 + (not (erc-scenarios-common--at-win-end-p w)))))))) + +;;; erc-scenarios-scrolltobottom.el ends here diff --git a/test/lisp/erc/resources/base/assoc/bumped/again.eld b/test/lisp/erc/resources/base/assoc/bumped/again.eld index ab3c7b06214..aef164b6237 100644 --- a/test/lisp/erc/resources/base/assoc/bumped/again.eld +++ b/test/lisp/erc/resources/base/assoc/bumped/again.eld @@ -1,10 +1,10 @@ ;; -*- mode: lisp-data; -*- -((nick 1 "NICK tester")) -((user 1 "USER user 0 * :tester") +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") (0.0 ":irc.foonet.org 433 * tester :Nickname is reserved by a different account") (0.0 ":irc.foonet.org FAIL NICK NICKNAME_RESERVED tester :Nickname is reserved by a different account")) -((nick 3 "NICK tester`") +((nick 10 "NICK tester`") (0.1 ":irc.foonet.org 001 tester` :Welcome to the foonet IRC Network tester`") (0.0 ":irc.foonet.org 002 tester` :Your host is irc.foonet.org, running version oragono-2.6.1-937b9b02368748e5") (0.0 ":irc.foonet.org 003 tester` :This server was created Fri, 24 Sep 2021 01:38:36 UTC") @@ -21,10 +21,10 @@ (0.2 ":irc.foonet.org 266 tester` 3 3 :Current global users 3, max 3") (0.0 ":irc.foonet.org 422 tester` :MOTD File is missing")) -((mode-user 3.2 "MODE tester` +i") +((mode-user 10 "MODE tester` +i") (0.0 ":irc.foonet.org 221 tester` +i") (0.0 ":irc.foonet.org NOTICE tester` :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) -((privmsg 42.6 "PRIVMSG NickServ :IDENTIFY tester changeme") +((privmsg 10 "PRIVMSG NickServ :IDENTIFY tester changeme") (0.01 ":tester`!~u@rpaau95je67ci.irc NICK tester") (0.0 ":NickServ!NickServ@localhost NOTICE tester :You're now logged in as tester")) diff --git a/test/lisp/erc/resources/base/assoc/bumped/foisted.eld b/test/lisp/erc/resources/base/assoc/bumped/foisted.eld index 5c36e58d9d3..0f7aadac564 100644 --- a/test/lisp/erc/resources/base/assoc/bumped/foisted.eld +++ b/test/lisp/erc/resources/base/assoc/bumped/foisted.eld @@ -1,6 +1,6 @@ ;; -*- mode: lisp-data; -*- -((nick 1 "NICK tester")) -((user 1 "USER user 0 * :tester") +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") (0.0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") (0.0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version oragono-2.6.1-937b9b02368748e5") (0.0 ":irc.foonet.org 003 tester :This server was created Fri, 24 Sep 2021 01:38:36 UTC") @@ -17,14 +17,14 @@ (0.0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3") (0.0 ":irc.foonet.org 422 tester :MOTD File is missing")) -((mode-user 1.2 "MODE tester +i") +((mode-user 10 "MODE tester +i") (0.0 ":irc.foonet.org 221 tester +i") (0.0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) -((privmsg 17.21 "PRIVMSG bob :hi") +((privmsg 10 "PRIVMSG bob :hi") (0.02 ":bob!~u@ecnnh95wr67pv.net PRIVMSG tester :hola") (0.01 ":bob!~u@ecnnh95wr67pv.net PRIVMSG tester :how r u?")) -((quit 18.19 "QUIT :" quit) +((quit 10 "QUIT :" quit) (0.01 ":tester!~u@rpaau95je67ci.irc QUIT :Quit: " quit)) ((drop 1 DROP)) diff --git a/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld b/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld index 33e4168ac46..63366d3f576 100644 --- a/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld +++ b/test/lisp/erc/resources/base/assoc/bumped/refoisted.eld @@ -1,6 +1,6 @@ ;; -*- mode: lisp-data; -*- -((nick 1 "NICK tester")) -((user 1 "USER user 0 * :tester") +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") (0.1 ":irc.foonet.org 001 dummy :Welcome to the foonet IRC Network dummy") (0.0 ":irc.foonet.org 002 dummy :Your host is irc.foonet.org, running version oragono-2.6.1-937b9b02368748e5") (0.0 ":irc.foonet.org 003 dummy :This server was created Fri, 24 Sep 2021 01:38:36 UTC") @@ -22,10 +22,10 @@ (0.01 ":bob!~u@ecnnh95wr67pv.net PRIVMSG dummy :back?") ) -((mode-user 1.2 "MODE dummy +i") +((mode-user 10 "MODE dummy +i") (0.0 ":irc.foonet.org 221 dummy +i") (0.0 ":irc.foonet.org NOTICE dummy :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) -((renick 42.6 "NICK tester") +((renick 10 "NICK tester") (0.01 ":dummy!~u@rpaau95je67ci.irc NICK tester") (0.0 ":NickServ!NickServ@localhost NOTICE dummy :You're now logged in as tester")) diff --git a/test/lisp/erc/resources/base/netid/bouncer/barnet.eld b/test/lisp/erc/resources/base/netid/bouncer/barnet.eld index 204d01fef77..596383c2699 100644 --- a/test/lisp/erc/resources/base/netid/bouncer/barnet.eld +++ b/test/lisp/erc/resources/base/netid/bouncer/barnet.eld @@ -38,4 +38,4 @@ (0.05 ":joe!~u@awyxgybtkx7uq.irc PRIVMSG #chan :mike: As he regards his aged father's life.") (0.05 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :joe: It is a rupture that you may easily heal; and the cure of it not only saves your brother, but keeps you from dishonor in doing it.")) -((linger 1 LINGER)) +((linger 2 LINGER)) diff --git a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld index 4445350ca0c..2e1a3ac27da 100644 --- a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld +++ b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld @@ -43,4 +43,4 @@ (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Orlando, my liege; the youngest son of Sir Rowland de Boys.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: The ape is dead, and I must conjure him.")) -((linger 1 LINGER)) +((linger 2 LINGER)) diff --git a/test/lisp/erc/resources/erc-d/erc-d-t.el b/test/lisp/erc/resources/erc-d/erc-d-t.el index 7b2adf4f07b..cf869fb3c70 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-t.el +++ b/test/lisp/erc/resources/erc-d/erc-d-t.el @@ -83,6 +83,8 @@ erc-d-t-with-cleanup (ignore-errors (kill-buffer buf))))) (sleep-for erc-d-t-cleanup-sleep-secs))))) +(defvar erc-d-t--wait-message-prefix "Awaiting: ") + (defmacro erc-d-t-wait-for (max-secs msg &rest body) "Wait for BODY to become non-nil. Or signal error with MSG after MAX-SECS. When MAX-SECS is negative, @@ -99,7 +101,7 @@ erc-d-t-wait-for (let ((inverted (make-symbol "inverted")) (time-out (make-symbol "time-out")) (result (make-symbol "result"))) - `(ert-info ((concat "Awaiting: " ,msg)) + `(ert-info ((concat erc-d-t--wait-message-prefix ,msg)) (let ((,time-out (abs ,max-secs)) (,inverted (< ,max-secs 0)) (,result ',result)) @@ -120,7 +122,8 @@ erc-d-t-ensure-for (unless (or (stringp msg) (memq (car-safe msg) '(format concat))) (push msg body) (setq msg (prin1-to-string body))) - `(erc-d-t-wait-for (- (abs ,max-secs)) ,msg (not (progn ,@body)))) + `(let ((erc-d-t--wait-message-prefix "Sustaining: ")) + (erc-d-t-wait-for (- (abs ,max-secs)) ,msg (not (progn ,@body))))) (defun erc-d-t-search-for (timeout text &optional from on-success) "Wait for TEXT to appear in current buffer before TIMEOUT secs. diff --git a/test/lisp/erc/resources/erc-scenarios-common.el b/test/lisp/erc/resources/erc-scenarios-common.el index 972faa5c73f..b92acdd81e8 100644 --- a/test/lisp/erc/resources/erc-scenarios-common.el +++ b/test/lisp/erc/resources/erc-scenarios-common.el @@ -183,6 +183,103 @@ erc-scenarios-common-with-cleanup ,@body))) +(defvar erc-scenarios-common--term-size '(34 . 80)) +(declare-function term-char-mode "term" nil) +(declare-function term-line-mode "term" nil) + +(defun erc-scenarios-common--run-in-term (&optional debug) + (require 'term) + (let* ((default-directory (getenv "EMACS_TEST_DIRECTORY")) + (emacs (expand-file-name invocation-name invocation-directory)) + (process-environment (cons "HOME=/nonexistent" + (cons "ERC_TESTS_SUBPROCESS=1" + process-environment))) + (name (ert-test-name (ert-running-test))) + (temp-file (make-temp-file "erc-term-test-")) + (cmd `(let ((stats 1)) + (setq enable-dir-local-variables nil) + (unwind-protect + (setq stats (ert-run-tests-batch ',name)) + (unless ',debug + (let ((buf (with-current-buffer (messages-buffer) + (buffer-string)))) + (with-temp-file ,temp-file + (insert buf))) + (kill-emacs (ert-stats-completed-unexpected stats)))))) + ;; The `ert-test' object in Emacs 29 has a `file-name' field. + (file-name (symbol-file name 'ert--test)) + (default-directory (expand-file-name (file-name-directory file-name))) + (package (if-let* ((found (getenv "ERC_PACKAGE_NAME")) + ((string-prefix-p "erc-" found))) + (intern found) + 'erc)) + ;; This is for testing ERC's ELPA-package on older Emacsen we + ;; still support. It won't run inside the emacs.git tree. + (setup (and (featurep 'compat) + `(progn + (require 'package) + (let ((package-load-list '((compat t) (,package t)))) + (package-initialize))))) + ;; Make subprocess terminal bigger than controlling. + (buf (cl-letf (((symbol-function 'window-screen-lines) + (lambda () (car erc-scenarios-common--term-size))) + ((symbol-function 'window-max-chars-per-line) + (lambda () (cdr erc-scenarios-common--term-size)))) + (make-term (symbol-name name) emacs nil "-Q" "-nw" + "-eval" (prin1-to-string setup) + "-l" file-name "-eval" (format "%S" cmd)))) + (proc (get-buffer-process buf)) + (err (lambda () + (with-temp-buffer + (insert-file-contents temp-file) + (message "Subprocess: %s" (buffer-string)) + (delete-file temp-file))))) + (set-window-buffer (selected-window) buf) + (delete-other-windows) + (with-current-buffer buf + (set-process-query-on-exit-flag proc nil) + (unless noninteractive (term-char-mode)) + (with-timeout (30 (funcall err) (error "Timed out awaiting result")) + (while (process-live-p proc) + (accept-process-output proc 0.1) + (unless noninteractive + (redisplay)))) + (while (accept-process-output proc)) + (term-line-mode) + (goto-char (point-min)) + ;; Otherwise gives process exited abnormally with exit-code >0 + (unless (search-forward (format "Process %s finished" name) nil t) + (funcall err) + (ert-fail (when (search-forward "exited" nil t) + (buffer-substring-no-properties (line-beginning-position) + (line-end-position))))) + (delete-file temp-file) + (when noninteractive + (kill-buffer))))) + +(defvar erc-scenarios-common-interactive-debug-term-p nil + "When non-nil, run interactive ") + +(defmacro erc-scenarios-common-with-noninteractive-in-term (&rest body) + "Run BODY via `erc-scenarios-common-with-cleanup' in a `term' subprocess. +Also do so when `erc-scenarios-common-interactive-debug-term-p' +is non-nil. When debugging, leave the `term-mode' buffer around +for inspection and name it after the test, bounded by asterisks. +When debugging, ensure the test always fails, as a reminder to +disable `erc-scenarios-common-interactive-debug-term-p'. + +See Info node `(emacs) Term Mode' for the various commands." + (declare (indent 1)) + `(if (and (or erc-scenarios-common-interactive-debug-term-p + noninteractive) + (not (getenv "ERC_TESTS_SUBPROCESS"))) + (progn + (when (memq system-type '(windows-nt ms-dos)) + (ert-skip "System must be UNIX")) + (erc-scenarios-common--run-in-term + erc-scenarios-common-interactive-debug-term-p)) + (erc-scenarios-common-with-cleanup ,@body))) + (defun erc-scenarios-common-assert-initial-buf-name (id port) ;; Assert no limbo period when explicit ID given (should (string= (if id @@ -209,9 +306,108 @@ erc-scenarios-common-say (insert str) (erc-send-current-line))) +(defun erc-scenarios-common--at-win-end-p (&optional window) + (= (window-body-height window) + (count-screen-lines (window-start window) (point-max) nil window))) + +(defun erc-scenarios-common--above-win-end-p (&optional window) + (> (window-body-height window) + (count-screen-lines (window-start window) (point-max)))) + +(defun erc-scenarios-common--prompt-past-win-end-p (&optional window) + (< (window-body-height window) + (count-screen-lines (window-start window) (point-max)))) + +(defun erc-scenarios-common--recenter-top-bottom-around (orig &rest args) + (let (this-command last-command) (apply orig args))) + +(defun erc-scenarios-common--recenter-top-bottom () + (advice-add 'recenter-top-bottom + :around #'erc-scenarios-common--recenter-top-bottom-around) + (execute-kbd-macro "\C-l") + (advice-remove 'recenter-top-bottom + #'erc-scenarios-common--recenter-top-bottom-around)) + ;;;; Fixtures +(defun erc-scenarios-scrolltobottom--normal (test) + (erc-scenarios-common-with-noninteractive-in-term + ((erc-scenarios-common-dialog "scrolltobottom") + (dumb-server (erc-d-run "localhost" t 'help)) + (port (process-contact dumb-server :service)) + (erc-modules `(scrolltobottom fill-wrap ,@erc-modules)) + (erc-server-flood-penalty 0.1) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :full-name "tester" + :nick "tester") + (funcall expect 10 "debug mode"))) + + (with-current-buffer "foonet" + (should (looking-at " and")) + (set-window-buffer nil (current-buffer)) + (delete-other-windows) + (split-window-below 15) + (recenter 0) + + (ert-info ("Moving into prompt in other window triggers scroll") + (with-selected-window (next-window) + (should-not (erc-scenarios-common--at-win-end-p)) + (goto-char (1- erc-insert-marker)) + (execute-kbd-macro "\C-n") + ;; Ensure point is at prompt and aligned to bottom. + (should (erc-scenarios-common--at-win-end-p)))) + + (ert-info ("Module `move-to-prompt' still works") + ;; Prompt is somewhere in the middle of the window. + (should (erc-scenarios-common--above-win-end-p)) + ;; Hitting a self-insert key triggers `move-to-prompt' as well + ;; as a scroll (to bottom). + (execute-kbd-macro "hi") + ;; Prompt and input appear on last line of window. + (should (erc-scenarios-common--at-win-end-p))) + + (ert-info ("Command `recenter-top-bottom' disallowed at prompt") + ;; Hitting C-l does not recenter the window. + (erc-scenarios-common--recenter-top-bottom) + (should (erc-scenarios-common--at-win-end-p)) + (erc-scenarios-common--recenter-top-bottom) + (should (erc-scenarios-common--at-win-end-p))) + + (ert-info ("Command `beginning-of-buffer' allowed at prompt") + ;; Hitting C-< goes to beginning of buffer. + (call-interactively #'beginning-of-buffer) + (should (= 1 (point))) + (redisplay) + (should (zerop (count-screen-lines (window-start) (point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (ert-info ("New message doesn't trigger scroll when away from prompt") + ;; Arriving insertions don't trigger a scroll when away from the + ;; prompt. New output not seen. + (erc-cmd-MSG "NickServ help register") + (save-excursion (erc-d-t-search-for 10 "End of NickServ")) + (should (= 1 (point))) + (should (zerop (count-screen-lines (window-start) (window-point)))) + (should (erc-scenarios-common--prompt-past-win-end-p))) + + (funcall test) + + (ert-info ("New message does trigger a scroll when at prompt") + ;; Recenter so prompt is above rather than at window's end. + (funcall expect 10 "If you are currently logged in") + (recenter 0) + ;; Prompt is somewhere in the middle of the window. + (erc-d-t-wait-for 10 (erc-scenarios-common--above-win-end-p)) + (erc-scenarios-common-say "/msg NickServ help identify") + ;; New arriving messages trigger a snap when inserted. + (erc-d-t-wait-for 10 (erc-scenarios-common--at-win-end-p)) + (funcall expect 10 "IDENTIFY lets you login"))))) + (cl-defun erc-scenarios-common--base-network-id-bouncer ((&key autop foo-id bar-id after &aux diff --git a/test/lisp/erc/resources/scrolltobottom/help.eld b/test/lisp/erc/resources/scrolltobottom/help.eld new file mode 100644 index 00000000000..ba44a0def39 --- /dev/null +++ b/test/lisp/erc/resources/scrolltobottom/help.eld @@ -0,0 +1,46 @@ +;; -*- mode: lisp-data; -*- +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") + (0.00 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") + (0.01 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version ergo-v2.11.1") + (0.01 ":irc.foonet.org 003 tester :This server was created Mon, 21 Aug 2023 06:18:36 UTC") + (0.02 ":irc.foonet.org 004 tester irc.foonet.org ergo-v2.11.1 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv") + (0.00 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# CHATHISTORY=1000 ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX :are supported by this server") + (0.01 ":irc.foonet.org 005 tester KICKLEN=390 MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8ONLY WHOX :are supported by this server") + (0.01 ":irc.foonet.org 005 tester draft/CHATHISTORY=1000 :are supported by this server") + (0.01 ":irc.foonet.org 251 tester :There are 0 users and 4 invisible on 1 server(s)") + (0.01 ":irc.foonet.org 252 tester 0 :IRC Operators online") + (0.01 ":irc.foonet.org 253 tester 0 :unregistered connections") + (0.01 ":irc.foonet.org 254 tester 2 :channels formed") + (0.01 ":irc.foonet.org 255 tester :I have 4 clients and 0 servers") + (0.01 ":irc.foonet.org 265 tester 4 4 :Current local users 4, max 4") + (0.01 ":irc.foonet.org 266 tester 4 4 :Current global users 4, max 4") + (0.01 ":irc.foonet.org 422 tester :MOTD File is missing")) + +((mode 10 "MODE tester +i") + (0.00 ":irc.foonet.org 221 tester +i") + (0.01 ":irc.foonet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.") + (0.02 ":irc.foonet.org 221 tester +i")) + +((privmsg-help-register 10 "PRIVMSG NickServ :help register") + (0.05 ":NickServ!NickServ@localhost NOTICE tester :*** \2NickServ HELP\2 ***") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :Syntax: \2REGISTER [email]\2") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :REGISTER lets you register your current nickname as a user account. If the") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :server allows anonymous registration, you can omit the e-mail address.") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :") + (0.01 ":NickServ!NickServ@localhost NOTICE tester :If you are currently logged in with a TLS client certificate and wish to use") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :it instead of a password to log in, send * as the password.") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :*** \2End of NickServ HELP\2 ***")) + +((privmsg-help-identify 20 "PRIVMSG NickServ :help identify") + (0.06 ":NickServ!NickServ@localhost NOTICE tester :*** \2NickServ HELP\2 ***") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :Syntax: \2IDENTIFY [password]\2") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :IDENTIFY lets you login to the given username using either password auth, or") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :certfp (your client certificate) if a password is not given.") + (0.02 ":NickServ!NickServ@localhost NOTICE tester :*** \2End of NickServ HELP\2 ***")) + +((quit 10 "QUIT :\2ERC\2 ") + (0.07 ":tester!~u@26axz8nh8zaag.irc QUIT :Quit: \2ERC\2") + (0.02 "ERROR :Quit: \2ERC\2")) -- 2.41.0 --=-=-=--