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#60936: 30.0.50; ERC >5.5: Add erc-fill style based on visual-line-mode Date: Wed, 01 Feb 2023 06:27:44 -0800 Message-ID: <87mt5xcvfj.fsf__24753.536765796$1675261731$gmane$org@neverwas.me> References: <87tu0nao77.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="23900"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: emacs-erc@gnu.org To: 60936@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Wed Feb 01 15:28:43 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 1pNE6L-00061F-CI for geb-bug-gnu-emacs@m.gmane-mx.org; Wed, 01 Feb 2023 15:28:41 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNE67-0000Ka-Jc; Wed, 01 Feb 2023 09:28:31 -0500 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 1pNE5i-0000Fe-7h for bug-gnu-emacs@gnu.org; Wed, 01 Feb 2023 09:28:06 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1pNE5h-0002RK-UG for bug-gnu-emacs@gnu.org; Wed, 01 Feb 2023 09:28:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1pNE5h-0007jH-Oo for bug-gnu-emacs@gnu.org; Wed, 01 Feb 2023 09:28:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: "J.P." Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Wed, 01 Feb 2023 14:28:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 60936 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 60936-submit@debbugs.gnu.org id=B60936.167526167829701 (code B ref 60936); Wed, 01 Feb 2023 14:28:01 +0000 Original-Received: (at 60936) by debbugs.gnu.org; 1 Feb 2023 14:27:58 +0000 Original-Received: from localhost ([127.0.0.1]:56936 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pNE5e-0007iz-0c for submit@debbugs.gnu.org; Wed, 01 Feb 2023 09:27:58 -0500 Original-Received: from mail-108-mta91.mxroute.com ([136.175.108.91]:40931) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pNE5d-0007im-0c for 60936@debbugs.gnu.org; Wed, 01 Feb 2023 09:27:57 -0500 Original-Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta91.mxroute.com (ZoneMTA) with ESMTPSA id 1860d606d89000011e.001 for <60936@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Wed, 01 Feb 2023 14:27:48 +0000 X-Zone-Loop: 3eaaa0c75a975981d708ae9087893295e48e9fa99fda 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=5TYvnH2GGCZjzyvR4sX7D83fiAPKJFStpTpRPSqEoGA=; b=TKHHvH/U5vRqifRd1jPa64ld6J ADlIXHgr1sxMC6fyQq7epDb+OLgQsxR5+JiTsT8u/udnjHpNRUj4ra/PIKeEGaebZQOovLiPxscRx n9biwPUxpK34cl8uNKMVGpvfVYaQA3enYwQ9MrRlBkLtSgkSueERqj19k0HCbYaxbp4ewqJU5V9Pp NP65zsg9Re3okGfIEWs4SG05FQIqVitO+BHjKEJukXqHMXtjenzHPK0cSy6/uXfxiavf2wEaC2XvA +6eNzAy4h1jggN2Lumt6uQZ1e/3ZQJ8ZDemZF3OrWrYOso4XgMka92IURVKFqwNxftHNVDfrUH4YY Ltz5iWTg==; In-Reply-To: <87tu0nao77.fsf@neverwas.me> (J. P.'s message of "Wed, 18 Jan 2023 06:53:48 -0800") 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:254582 Archived-At: --=-=-= Content-Type: text/plain v6. Revert addition of leading space for `margin' timestamps. Add `erc-log-filter-function' tailored to new `erc-timestamp-use-align-to' offerings. (For now, this log style resembles those produced by the ZNC bouncer. See attached sample and screenshot of originating buffer.) --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename="#chan!tester@127.0.0.1:6667.txt" [13:48:07] [13:48:07] [Wed Feb 1 2023] [13:48:07] *** You have joined channel #chan [13:48:07] *** Users on #chan: @bob alice tester [13:48:07] tester, welcome! [13:48:07] tester, welcome! [13:48:07] *** #chan modes: +nt [13:48:07] *** #chan was created on 2023-02-01 12:58:04 [13:48:09] alice: An actor too perhaps, if I see cause. [13:48:13] bob: Nay, but the devil take mocking: speak, sad brow and true maid. [13:48:17] alice: My liege, your highness now may do me good. [13:48:22] bob: Farewell! God knows when we shall meet again. [13:48:27] alice: Well said, old mocker: I must needs be friends with thee. --=-=-= Content-Type: image/png Content-Disposition: attachment; filename=fill-wrap-log-compare-chan.png Content-Transfer-Encoding: base64 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 4O/vT2pqKpcvX2bMmDEGWwMbk5tCXfbV1X5dunSRt6F7/PHHefHFFwH91nSm7ld/8OBBPvnkE7p1 68bbb7/NmTNnOHPmDOnp6QAkJSUxZswY4uPjSUlJISEhgTVr1gBNc39OnToVpVJJWloau3fvJiMj o5q8MdevsfrdzftT8BtqtVqq608geNBZu3at1KVLFyk7O7uaLF9VIvUMeF8a1XetJEmSVKGrlPq1 /VjqGfC+pM4vlRLibkk9A96XJg79SpIkSSot0UqRQR9KkUEfSlqtzqj+H7lw4YLk5uYmxcXFmWx/ ZmGmVKbTSpIkSVdyrkrd10ZKY7ZNMEjzQ2q01H1tpLTyp49qzWd34l6p+9pIac3Pa+VzlVKlNGDD EKn3F/2kDPVN+XxFZYVcdve1kdKwLSOlmPRT0qn009KEHROlXl88KpVXlBvkn5ubK5WWVq9zXeTk 5EiOjo7S5cuXpcGDB0udOnWSzp07JwUGBspptm7dKo0YMUI+7t+/vxQdHV2vciRJksLDw6XDhw/X mcbDw0O6cOGCwTkHBwcpJiZGPs7KypJ8fHzk47Fjx0qffvqpfBwQECBdvHjRZP3GUpd/8vLyJHNz cyk/P1+WHzx4UAoJCZGPASknJ0c+XrBggfTSSy/Jxzdu3JAA6ejRow2yLzw8XNq/f798vH79eqlX r141pq3J/8b8ayx/Y3Jj7bNkyRKpXbt2UnR0tKTVaqvZbExujLrsM9Z+//nPf6Rp06ZJOp1O6tOn j9S+fXtJkiSpb9++UmxsrNGyo6OjpY4dO0qSJElvvfWWtHTpUkmSJKlDhw7V7jG1Wi3FxsZK48eP l8aNGyefb8z9qVKpJDMzM0mlUsnnli5dKr344osm1b8p6ne378+/MnFxcVJcXJxkcb8DWYGgsQwc OJBNmzaxYMECVq5ciZ2dXbU0SktzAMzMFXj7OpF0OZv8vFIy0goACAzV95RY2yjx8nXkerKKrFuF 2Nlb1alv7/D7r/K0tDRmz57NsGHDaN26tcn2q0rz+ODUKn6+eZaS8hIAsktyGuCJ6uSV5lOkLcK1 mQst7X+fm2am0A9GZPzWM5lVnM2cqNcNdEvLS7G3+r3nxdnZucF2BAcHk52dzeDBgzEzMxwIGTFi BK+++iq3bt1CoVCQkpJCZGRkg8uqDzk5OajV6mpD9nfWdeDAgaxZs4bHHnuMM2fOUFFRIS8WMkW/ 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 jQGaGwNU7vXylVkZQ9K+9rCPDesqyS+wk18QrX0EuOCisxLqH6Cs1IMlxUjRZOcQHkERERlJpvvv v/87/T3AarV+QUMR+fJJz7BgNBupq/FRWebFbDFy8fwsrl7hxtg9NVw8xYnveBeffNhGKBhhyfV5 scxlD2eamcpyLzl5NhZdnYPZ0jvZMJj2znQLqQ4TR490UlXuxeEwc+WyHOYvGhubBo+n/+amAL/6 2SFmz3Uxc45rqA+jiIh8werr6wEweDyeSH8PTEvT6koRSV4wGOaZf63iaK2fB783lbR0TYSIiJzu SktLAdVIisgw6/B2YTTAvfedrSBSROQMo4ykiIiIiCREGUkRERERGZQB55k2Vbf0+/MlhSqcFxER EfkyUkZSRERERJKiQFJEREREkqJAUkRERESSokBSRERERJISVyD57489QGXpR8M9FhERERE5jcQV SDYcqeG+5Qv5xbqHCPj9wz0mERERETkNxBVIrvnBT7jhH77Baz9/nq8vuoTdO7YN97hEREREZJSL K5C0pFhZufqbPPPGVlzZY3lgxVL2frBjuMcmIiIiIqNYQott6g8fpPFoLVa7HWtq6nCNSURERERO A3EFki0N9Xzv63fw8O03kFdYxIY3t1E0dfpwj01ERERERrEBt0gEeHrNvVSWfsQ/PrGBr15/83CP SUREREROA3EFkrPnL2DV95/DlT12uMcjIiIiIqeJuALJJbf/3XCPQ0REREROM9rZRkRERESSokBS RERERJKiQFJEREREkjJgjeSSQtcXMQ4REREROc0oIykiIiIiSVEgKSIiIiJJUSApIiIiIkn5f6oo TjljaGhAAAAAAElFTkSuQmCC --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0000-v5-v6.diff >From 4dc8b4968313d3e99c680f25693a2a5ef7e301c5 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Wed, 1 Feb 2023 05:59:21 -0800 Subject: [PATCH 0/8] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (8): [5.6] Refactor marker initialization in erc-open [5.6] Adjust some old text properties in ERC buffers [5.6] Expose insertion time as text prop in erc-stamp [5.6] Make some erc-stamp functions more limber [5.6] Put display properties to better use in erc-stamp [5.6] Convert erc-fill minor mode into a proper module [5.6] Add variant for erc-match invisibility spec [5.6] Add erc-fill style based on visual-line-mode lisp/erc/erc-common.el | 1 + lisp/erc/erc-compat.el | 56 +++ lisp/erc/erc-fill.el | 322 ++++++++++++++++-- lisp/erc/erc-match.el | 31 +- lisp/erc/erc-stamp.el | 204 +++++++++-- lisp/erc/erc.el | 136 +++++--- test/lisp/erc/erc-fill-tests.el | 198 +++++++++++ .../erc-scenarios-base-local-module-modes.el | 211 ++++++++++++ .../erc/erc-scenarios-base-local-modules.el | 99 ------ test/lisp/erc/erc-stamp-tests.el | 265 ++++++++++++++ test/lisp/erc/erc-tests.el | 79 ++++- 11 files changed, 1387 insertions(+), 215 deletions(-) create mode 100644 test/lisp/erc/erc-fill-tests.el create mode 100644 test/lisp/erc/erc-scenarios-base-local-module-modes.el create mode 100644 test/lisp/erc/erc-stamp-tests.el Interdiff: diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el index 8862b14b061..d1c2f790bc8 100644 --- a/lisp/erc/erc-stamp.el +++ b/lisp/erc/erc-stamp.el @@ -251,8 +251,14 @@ erc-timestamp-use-align-to right edge. If the value is `margin', the stamp appears in the right margin when visible. -A side effect of enabling this is that there will only be one -space before a right timestamp in any saved logs." +Enabling this option produces a side effect in that stamps aren't +indented in saved logs. When its value is an integer, this +option adds a space after the end of a message if the stamp +doesn't already start with one. And when its value is t, it adds +a single space, unconditionally. And while this option never +adds a space when its value is `margin', ERC does offer a +workaround in `erc-stamp-prefix-log-filter', which strips +trailing stamps from messages and puts them before every line." :type '(choice boolean integer (const margin)) :package-version '(ERC . "5.5")) ; FIXME sync on release @@ -287,6 +293,28 @@ erc-stamp--adjust-right-margin (set-window-margins nil left-margin-width width) (set-window-fringes nil left-fringe-width 0))) +(defun erc-stamp-prefix-log-filter (text) + "Prefix every message in the buffer with a stamp. +Remove trailing stamps as well. For now, hard code the format to +\"ZNC\"-log style, which is [HH:MM:SS]. Expect to be used as a +`erc-log-filter-function' when `erc-timestamp-use-align-to' is +non-nil." + (insert text) + (goto-char (point-min)) + (while + (progn + (when-let* (((< (point) (pos-eol))) + (end (1- (pos-eol))) + ((eq 'erc-timestamp (field-at-pos end))) + (beg (field-beginning end)) + ;; Skip a line that's just a timestamp. + ((> beg (point)))) + (delete-region beg (1+ end))) + (when-let (time (get-text-property (point) 'erc-timestamp)) + (insert (format-time-string "[%H:%M:%S] " time))) + (zerop (forward-line)))) + "") + ;; If people want to use this directly, we can convert it into ;; a local module. (define-minor-mode erc-stamp--display-margin-mode @@ -408,8 +436,6 @@ erc-insert-timestamp-right (put-text-property from (point) 'display `(space :align-to (- right ,s))))) ('margin - (unless (eq ?\s (aref string 0)) - (insert-and-inherit " ")) (put-text-property 0 (length string) 'display `((margin right-margin) ,string) string)) diff --git a/test/lisp/erc/erc-stamp-tests.el b/test/lisp/erc/erc-stamp-tests.el index 73260ff126b..01e71e348e0 100644 --- a/test/lisp/erc/erc-stamp-tests.el +++ b/test/lisp/erc/erc-stamp-tests.el @@ -155,8 +155,8 @@ erc-timestamp-use-align-to--margin (erc-display-message nil nil (current-buffer) msg))) (goto-char (point-min)) ;; Space not added (treated as opaque string). - (should (search-forward "msg one [" nil t)) - ;; Field covers stamp and leading space + (should (search-forward "msg one[" nil t)) + ;; Field covers stamp alone (should (eql ?e (char-before (field-beginning (point))))) ;; Vanity props extended (should (get-text-property (field-beginning (point)) 'wrap-prefix)) @@ -170,9 +170,9 @@ erc-timestamp-use-align-to--margin (let ((msg (erc-format-privmessage "bob" "tttt wwww oooo" nil t))) (erc-display-message nil nil (current-buffer) msg))) ;; No hard wrap - (should (search-forward "oooo [" nil t)) - ;; Field starts at leading space. - (should (eql ?\s (char-after (field-beginning (point))))) + (should (search-forward "oooo[" nil t)) + ;; Field starts at format string (right bracket) + (should (eql ?\[ (char-after (field-beginning (point))))) (should (eql ?\n (char-after (field-end (point))))))))) ;; This concerns a proposed partial reversal of the changes resulting -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Refactor-marker-initialization-in-erc-open.patch >From e22e001fe0dfc53acc229a99ff2a4f761610861a Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 23 Jan 2023 20:48:24 -0800 Subject: [PATCH 1/8] [5.6] Refactor marker initialization in erc-open * lisp/erc/erc.el (erc--initialize-markers): New helper to ensure prompt and its associated markers are set up correctly. (erc-open): When determining whether a session is a logical continuation, leverage the work already performed by the `erc-networks' library to that effect. Its verdicts are based on network context and thus reliable even when a user dials anew from an entry-point, which is not a simple reconnection because the user expects a clean slate for everything except an existing buffer's messages, meaning `erc--server-reconnecting' will be nil and local-module state variables need resetting. Also remove the check for `erc-reuse-buffers' and instead trust that `erc-get-buffer-create' always does the right thing in. Replace all code involving marker and prompt setup by deferring to a new helper, `erc--initialize markers'. * test/lisp/erc/erc-tests.el (erc--initialize-markers): New test. * test/lisp/erc/erc-scenarios-base-local-module-modes.el: New file. * test/lisp/erc/erc-scenarios-base-local-modules.el (erc-scenarios-base-local-modules--mode-persistence): Move test to separate file to help with parallel "-j" runs. --- lisp/erc/erc.el | 79 ++++--- .../erc-scenarios-base-local-module-modes.el | 211 ++++++++++++++++++ .../erc/erc-scenarios-base-local-modules.el | 99 -------- test/lisp/erc/erc-tests.el | 79 ++++++- 4 files changed, 331 insertions(+), 137 deletions(-) create mode 100644 test/lisp/erc/erc-scenarios-base-local-module-modes.el diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index ff1820cfaf2..363fe30ee58 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1966,6 +1966,45 @@ erc--merge-local-modes (cons (nreverse (car out)) (nreverse (cdr out)))) (list new-modes))) +;; This function doubles as a convenient helper for use in unit tests. +;; Prior to 5.6, its contents lived in `erc-open'. + +(defun erc--initialize-markers (old-point continued-session) + "Ensure prompt and its bounding markers have been initialized." + ;; FIXME erase assertions after code review and additional testing. + (setq erc-insert-marker (make-marker) + erc-input-marker (make-marker)) + (if continued-session + (progn + ;; Respect existing multiline input after prompt. Expect any + ;; text preceding it on the same line, including whitespace, + ;; to be part of the prompt itself. + (goto-char (point-max)) + (forward-line 0) + (while (and (not (get-text-property (point) 'erc-prompt)) + (zerop (forward-line -1)))) + (cl-assert (not (= (point) (point-min)))) + (set-marker erc-insert-marker (point)) + ;; If the input area is clean, this search should fail and + ;; return point max. Otherwise, it should return the position + ;; after the last char with the `erc-prompt' property, as per + ;; the doc string for `next-single-property-change'. + (set-marker erc-input-marker + (next-single-property-change (point) 'erc-prompt nil + (point-max))) + (cl-assert (= (field-end) erc-input-marker)) + (goto-char old-point) + (erc--unhide-prompt)) + (cl-assert (not (get-text-property (point) 'erc-prompt))) + ;; In the original version from `erc-open', the snippet that + ;; handled these newline insertions appeared twice close in + ;; proximity, which was probably unintended. Nevertheless, we + ;; preserve the double newlines here for historical reasons. + (insert "\n\n") + (set-marker erc-insert-marker (point)) + (erc-display-prompt) + (cl-assert (= (point) (point-max))))) + (defun erc-open (&optional server port nick full-name connect passwd tgt-list channel process client-certificate user id) @@ -1999,10 +2038,12 @@ erc-open (old-recon-count erc-server-reconnect-count) (old-point nil) (delayed-modules nil) - (continued-session (and erc--server-reconnecting - (with-suppressed-warnings - ((obsolete erc-reuse-buffers)) - erc-reuse-buffers)))) + (continued-session (or erc--server-reconnecting + erc--target-priors + (and-let* (((not target)) + (m (buffer-local-value + 'erc-input-marker buffer)) + ((marker-position m))))))) (when connect (run-hook-with-args 'erc-before-connect server port nick)) (set-buffer buffer) (setq old-point (point)) @@ -2020,21 +2061,6 @@ erc-open (buffer-local-value 'erc-server-announced-name old-buffer))) ;; connection parameters (setq erc-server-process process) - (setq erc-insert-marker (make-marker)) - (setq erc-input-marker (make-marker)) - ;; go to the end of the buffer and open a new line - ;; (the buffer may have existed) - (goto-char (point-max)) - (forward-line 0) - (when (or continued-session (get-text-property (point) 'erc-prompt)) - (setq continued-session t) - (set-marker erc-input-marker - (or (next-single-property-change (point) 'erc-prompt) - (point-max)))) - (unless continued-session - (goto-char (point-max)) - (insert "\n")) - (set-marker erc-insert-marker (point)) ;; stack of default recipients (setq erc-default-recipients tgt-list) (when target @@ -2081,20 +2107,7 @@ erc-open (get-buffer-create (concat "*ERC-DEBUG: " server "*")))) (erc-determine-parameters server port nick full-name user passwd) - - ;; FIXME consolidate this prompt-setup logic with the pass above. - - ;; set up prompt - (unless continued-session - (goto-char (point-max)) - (insert "\n")) - (if continued-session - (progn (goto-char old-point) - (erc--unhide-prompt)) - (set-marker erc-insert-marker (point)) - (erc-display-prompt) - (goto-char (point-max))) - + (erc--initialize-markers old-point continued-session) (save-excursion (run-mode-hooks) (dolist (mod (car delayed-modules)) (funcall mod +1)) (dolist (var (cdr delayed-modules)) (set var nil))) diff --git a/test/lisp/erc/erc-scenarios-base-local-module-modes.el b/test/lisp/erc/erc-scenarios-base-local-module-modes.el new file mode 100644 index 00000000000..7b91e28dc83 --- /dev/null +++ b/test/lisp/erc/erc-scenarios-base-local-module-modes.el @@ -0,0 +1,211 @@ +;;; erc-scenarios-base-local-module-modes.el --- More local-mod ERC tests -*- 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 . + +;;; Commentary: + +;; A local module doubles as a minor mode whose mode variable and +;; associated local data can withstand service disruptions. +;; Unfortunately, the current implementation is too unwieldy to be +;; made public because it doesn't perform any of the boiler plate +;; needed to save and restore buffer-local and "network-local" copies +;; of user options. Ultimately, a user-friendly framework must fill +;; this void if third-party local modules are ever to become +;; practical. +;; +;; The following tests all use `sasl' because, as of ERC 5.5, it's the +;; only local module. + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-sasl) + +;; After quitting a session for which `sasl' is enabled, you +;; disconnect and toggle `erc-sasl-mode' off. You then reconnect +;; using an alternate nickname. You again disconnect and reconnect, +;; this time immediately, and the mode stays disabled. Finally, you +;; once again disconnect, toggle the mode back on, and reconnect. You +;; are authenticated successfully, just like in the initial session. +;; +;; This is meant to show that a user's local mode settings persist +;; between sessions. It also happens to show (in round four, below) +;; that a server renicking a user on 001 after a 903 is handled just +;; like a user-initiated renick, although this is not the main thrust. + +(ert-deftest erc-scenarios-base-local-module-modes--reconnect () + :tags '(:expensive-test) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "base/local-modules") + (erc-server-flood-penalty 0.1) + (dumb-server (erc-d-run "localhost" t 'first 'second 'third 'fourth)) + (port (process-contact dumb-server :service)) + (erc-modules (cons 'sasl erc-modules)) + (expect (erc-d-t-make-expecter)) + (server-buffer-name (format "127.0.0.1:%d" port))) + + (ert-info ("Round one, initial authentication succeeds as expected") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "tester" + :user "tester" + :password "changeme" + :full-name "tester") + (should (string= (buffer-name) server-buffer-name)) + (funcall expect 10 "You are now logged in as tester")) + + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet")) + (funcall expect 10 "This server is in debug mode") + (erc-cmd-JOIN "#chan") + + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (funcall expect 20 "She is Lavinia, therefore must")) + + (erc-cmd-QUIT "") + (funcall expect 10 "finished"))) + + (ert-info ("Round two, nick rejected, alternate granted") + (with-current-buffer "foonet" + + (ert-info ("Toggle mode off, reconnect") + (erc-sasl-mode -1) + (erc-cmd-RECONNECT)) + + (funcall expect 10 "User modes for tester`") + (should-not (cdr (erc-scenarios-common-buflist "foonet"))) + (should (equal (buffer-name) "foonet")) + (should-not (cdr (erc-scenarios-common-buflist "#chan"))) + + (with-current-buffer "#chan" + (funcall expect 10 "Some enigma, some riddle")) + + (erc-cmd-QUIT "") + (funcall expect 10 "finished"))) + + (ert-info ("Round three, send alternate nick initially") + (with-current-buffer "foonet" + + (ert-info ("Keep mode off, reconnect") + (should-not erc-sasl-mode) + (should (local-variable-p 'erc-sasl-mode)) + (erc-cmd-RECONNECT)) + + (funcall expect 10 "User modes for tester`") + (should-not (cdr (erc-scenarios-common-buflist "foonet"))) + (should (equal (buffer-name) "foonet")) + (should-not (cdr (erc-scenarios-common-buflist "#chan"))) + + (with-current-buffer "#chan" + (funcall expect 10 "Let our reciprocal vows be remembered.")) + + (erc-cmd-QUIT "") + (funcall expect 10 "finished"))) + + (ert-info ("Round four, authenticated successfully again") + (with-current-buffer "foonet" + + (ert-info ("Toggle mode on, reconnect") + (should-not erc-sasl-mode) + (should (local-variable-p 'erc-sasl-mode)) + (erc-sasl-mode +1) + (erc-cmd-RECONNECT)) + + (funcall expect 10 "User modes for tester") + (should-not (cdr (erc-scenarios-common-buflist "foonet"))) + (should (equal (buffer-name) "foonet")) + (should-not (cdr (erc-scenarios-common-buflist "#chan"))) + + (with-current-buffer "#chan" + (funcall expect 10 "Well met; good morrow, Titus and Hortensius.")) + + (erc-cmd-QUIT ""))))) + +;; In contrast to the mode-persistence test above, this one +;; demonstrates that a user reinvoking an entry point declares their +;; intention to reset local-module state for the server buffer. +;; Whether a local-module's state variable is also reset in target +;; buffers up to the module. That is, by default, they're left alone. + +(ert-deftest erc-scenarios-base-local-module-modes--entrypoint () + :tags '(:expensive-test) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "base/local-modules") + (erc-server-flood-penalty 0.1) + (dumb-server (erc-d-run "localhost" t 'first 'first)) + (port (process-contact dumb-server :service)) + (erc-modules (cons 'sasl erc-modules)) + (expect (erc-d-t-make-expecter)) + (server-buffer-name (format "127.0.0.1:%d" port))) + + (ert-info ("Round one, initial authentication succeeds as expected") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "tester" + :user "tester" + :password "changeme" + :full-name "tester") + (should (string= (buffer-name) server-buffer-name)) + (funcall expect 10 "You are now logged in as tester")) + + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet")) + (funcall expect 10 "This server is in debug mode") + (erc-cmd-JOIN "#chan") + + (ert-info ("Toggle local-module off in target buffer") + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (funcall expect 20 "She is Lavinia, therefore must") + (erc-sasl-mode -1))) + + (erc-cmd-QUIT "") + (funcall expect 10 "finished") + + (ert-info ("Toggle mode off") + (erc-sasl-mode -1) + (should (local-variable-p 'erc-sasl-mode))))) + + (ert-info ("Reconnecting via entry point discards `erc-sasl-mode' value.") + ;; If you were to /RECONNECT here, no PASS changeme would be + ;; sent instead of CAP SASL, resulting in a failure. + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "tester" + :user "tester" + :password "changeme" + :full-name "tester") + (should (string= (buffer-name) server-buffer-name)) + (funcall expect 10 "You are now logged in as tester") + + (erc-d-t-wait-for 10 (equal (buffer-name) "foonet")) + (funcall expect 10 "User modes for tester") + (should erc-sasl-mode)) ; obviously + + ;; No other foonet buffer exists, e.g., foonet<2> + (should-not (cdr (erc-scenarios-common-buflist "foonet"))) + + (ert-info ("Target buffer retains local-module state") + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (funcall expect 20 "She is Lavinia, therefore must") + (should-not erc-sasl-mode) + (should (local-variable-p 'erc-sasl-mode)) + (erc-cmd-QUIT "")))))) + +;;; erc-scenarios-base-local-module-modes.el ends here diff --git a/test/lisp/erc/erc-scenarios-base-local-modules.el b/test/lisp/erc/erc-scenarios-base-local-modules.el index 1318207a3bf..d6dbd87c8cc 100644 --- a/test/lisp/erc/erc-scenarios-base-local-modules.el +++ b/test/lisp/erc/erc-scenarios-base-local-modules.el @@ -82,105 +82,6 @@ erc-scenarios-base-local-modules--reconnect-let (erc-cmd-QUIT "") (funcall expect 10 "finished"))))) -;; After quitting a session for which `sasl' is enabled, you -;; disconnect and toggle `erc-sasl-mode' off. You then reconnect -;; using an alternate nickname. You again disconnect and reconnect, -;; this time immediately, and the mode stays disabled. Finally, you -;; once again disconnect, toggle the mode back on, and reconnect. You -;; are authenticated successfully, just like in the initial session. -;; -;; This is meant to show that a user's local mode settings persist -;; between sessions. It also happens to show (in round four, below) -;; that a server renicking a user on 001 after a 903 is handled just -;; like a user-initiated renick, although this is not the main thrust. - -(ert-deftest erc-scenarios-base-local-modules--mode-persistence () - :tags '(:expensive-test) - (erc-scenarios-common-with-cleanup - ((erc-scenarios-common-dialog "base/local-modules") - (erc-server-flood-penalty 0.1) - (dumb-server (erc-d-run "localhost" t 'first 'second 'third 'fourth)) - (port (process-contact dumb-server :service)) - (erc-modules (cons 'sasl erc-modules)) - (expect (erc-d-t-make-expecter)) - (server-buffer-name (format "127.0.0.1:%d" port))) - - (ert-info ("Round one, initial authentication succeeds as expected") - (with-current-buffer (erc :server "127.0.0.1" - :port port - :nick "tester" - :user "tester" - :password "changeme" - :full-name "tester") - (should (string= (buffer-name) server-buffer-name)) - (funcall expect 10 "You are now logged in as tester")) - - (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet")) - (funcall expect 10 "This server is in debug mode") - (erc-cmd-JOIN "#chan") - - (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) - (funcall expect 20 "She is Lavinia, therefore must")) - - (erc-cmd-QUIT "") - (funcall expect 10 "finished"))) - - (ert-info ("Round two, nick rejected, alternate granted") - (with-current-buffer "foonet" - - (ert-info ("Toggle mode off, reconnect") - (erc-sasl-mode -1) - (erc-cmd-RECONNECT)) - - (funcall expect 10 "User modes for tester`") - (should-not (cdr (erc-scenarios-common-buflist "foonet"))) - (should (equal (buffer-name) "foonet")) - (should-not (cdr (erc-scenarios-common-buflist "#chan"))) - - (with-current-buffer "#chan" - (funcall expect 10 "Some enigma, some riddle")) - - (erc-cmd-QUIT "") - (funcall expect 10 "finished"))) - - (ert-info ("Round three, send alternate nick initially") - (with-current-buffer "foonet" - - (ert-info ("Keep mode off, reconnect") - (should-not erc-sasl-mode) - (should (local-variable-p 'erc-sasl-mode)) - (erc-cmd-RECONNECT)) - - (funcall expect 10 "User modes for tester`") - (should-not (cdr (erc-scenarios-common-buflist "foonet"))) - (should (equal (buffer-name) "foonet")) - (should-not (cdr (erc-scenarios-common-buflist "#chan"))) - - (with-current-buffer "#chan" - (funcall expect 10 "Let our reciprocal vows be remembered.")) - - (erc-cmd-QUIT "") - (funcall expect 10 "finished"))) - - (ert-info ("Round four, authenticated successfully again") - (with-current-buffer "foonet" - - (ert-info ("Toggle mode on, reconnect") - (should-not erc-sasl-mode) - (should (local-variable-p 'erc-sasl-mode)) - (erc-sasl-mode +1) - (erc-cmd-RECONNECT)) - - (funcall expect 10 "User modes for tester") - (should-not (cdr (erc-scenarios-common-buflist "foonet"))) - (should (equal (buffer-name) "foonet")) - (should-not (cdr (erc-scenarios-common-buflist "#chan"))) - - (with-current-buffer "#chan" - (funcall expect 10 "Well met; good morrow, Titus and Hortensius.")) - - (erc-cmd-QUIT ""))))) - ;; For local modules, the twin toggle commands `erc-FOO-enable' and ;; `erc-FOO-disable' affect all buffers of a connection, whereas ;; `erc-FOO-mode' continues to operate only on the current buffer. diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 40a2d2de657..c5a40d9bc72 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -117,11 +117,7 @@ erc-tests--send-prep ;; Caller should probably shadow `erc-insert-modify-hook' or ;; populate user tables for erc-button. (erc-mode) - (insert "\n\n") - (setq erc-input-marker (make-marker) - erc-insert-marker (make-marker)) - (set-marker erc-insert-marker (point-max)) - (erc-display-prompt) + (erc--initialize-markers (point) nil) (should (= (point) erc-input-marker))) (defun erc-tests--set-fake-server-process (&rest args) @@ -257,6 +253,79 @@ erc-hide-prompt (kill-buffer "bob") (kill-buffer "ServNet")))) +(ert-deftest erc--initialize-markers () + (let ((proc (start-process "true" (current-buffer) "true")) + erc-modules + erc-connect-pre-hook + erc-insert-modify-hook + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) + (set-process-query-on-exit-flag proc nil) + (erc-mode) + (setq erc-server-process proc + erc-networks--id (erc-networks--id-create 'foonet)) + (erc-open "localhost" 6667 "tester" "Tester" nil + "fake" nil "#chan" proc nil "user" nil) + (with-current-buffer (should (get-buffer "#chan")) + (should (= ?\n (char-after 1))) + (should (= ?E (char-after erc-insert-marker))) + (should (= 3 (marker-position erc-insert-marker))) + (should (= 8 (marker-position erc-input-marker))) + (should (= 8 (point-max))) + (should (= 8 (point))) + ;; These prompt properties are a continual source of confusion. + ;; Including the literal defaults here can hopefully serve as a + ;; quick reference for anyone operating in that area. + (should (equal (buffer-string) + #("\n\nERC> " + 2 6 ( font-lock-face erc-prompt-face + rear-nonsticky t + erc-prompt t + field erc-prompt + front-sticky t + read-only t) + 6 7 ( rear-nonsticky t + erc-prompt t + field erc-prompt + front-sticky t + read-only t)))) + + ;; Simulate some activity by inserting some text before and + ;; after the prompt (multiline). + (erc-display-error-notice nil "Welcome") + (goto-char (point-max)) + (insert "Hello\nWorld") + (goto-char 3) + (should (looking-at-p (regexp-quote "*** Welcome")))) + + (ert-info ("Reconnect") + (erc-open "localhost" 6667 "tester" "Tester" nil + "fake" nil "#chan" proc nil "user" nil) + (should-not (get-buffer "#chan<2>"))) + + (ert-info ("Existing prompt respected") + (with-current-buffer (should (get-buffer "#chan")) + (should (= ?\n (char-after 1))) + (should (= ?E (char-after erc-insert-marker))) + (should (= 15 (marker-position erc-insert-marker))) + (should (= 20 (marker-position erc-input-marker))) + (should (= 3 (point))) ; point restored + (should (equal (buffer-string) + #("\n\n*** Welcome\nERC> Hello\nWorld" + 2 13 (font-lock-face erc-error-face) + 14 18 ( font-lock-face erc-prompt-face + rear-nonsticky t + erc-prompt t + field erc-prompt + front-sticky t + read-only t) + 18 19 ( rear-nonsticky t + erc-prompt t + field erc-prompt + front-sticky t + read-only t)))) + (when noninteractive + (kill-buffer)))))) + (ert-deftest erc--switch-to-buffer () (defvar erc-modified-channels-alist) ; lisp/erc/erc-track.el -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Adjust-some-old-text-properties-in-ERC-buffers.patch >From dd598dfae6dd975534ec289c180ff01264fe81e9 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 16 Jun 2022 01:20:49 -0700 Subject: [PATCH 2/8] [5.6] Adjust some old text properties in ERC buffers TODO: mention adjustment in ERC-NEWS for 5.6. * lisp/erc/erc.el (erc-display-message): Replace `rear-sticky' text property, which has been around since 2002, with more useful `erc-message' property. (erc-display-prompt): Make the `field' text property more meaningful to aid in searching, although this makes the `erc-prompt' property somewhat redundant. (erc-put-text-property, erc-list): Alias these to built-in functions. (erc--own-property-names, erc--remove-text-properties) Add internal variable and helper function for filtering values returned by `filter-buffer-substring-function'. (erc-restore-text-properties): Don't forget tags when restoring. (erc--get-eq-comparable-cmd): New function to extract commands for use as easily searchable text-property values. --- lisp/erc/erc.el | 57 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 363fe30ee58..6b3d0b4af2f 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2880,7 +2880,9 @@ erc-display-message (erc-display-line string buffer) (unless (erc-hide-current-message-p parsed) (erc-put-text-property 0 (length string) 'erc-parsed parsed string) - (erc-put-text-property 0 (length string) 'rear-sticky t string) + (put-text-property + 0 (length string) 'erc-message + (erc--get-eq-comparable-cmd (erc-response.command parsed)) string) (when (erc-response.tags parsed) (erc-put-text-property 0 (length string) 'tags (erc-response.tags parsed) string)) @@ -4258,6 +4260,30 @@ erc-ensure-channel-name channel (concat "#" channel))) +(defvar erc--own-property-names + '( tags erc-parsed display ; core + ;; `erc-display-prompt' + rear-nonsticky erc-prompt field front-sticky read-only + ;; stamp + cursor-intangible cursor-sensor-functions isearch-open-invisible + ;; match + invisible intangible + ;; button + erc-callback erc-data mouse-face keymap + ;; fill-wrap + line-prefix wrap-prefix) + "Props added by ERC that should not survive killing. +Among those left behind by default are `font-lock-face' and +`erc-secret'.") + +(defun erc--remove-text-properties (string) + "Remove text properties in STRING added by ERC. +Specifically, remove any that aren't members of +`erc--own-property-names'." + (remove-list-of-text-properties 0 (length string) + erc--own-property-names string) + string) + (defun erc-grab-region (start end) "Copy the region between START and END in a recreatable format. @@ -4309,7 +4335,7 @@ erc-display-prompt (setq prompt (propertize prompt 'rear-nonsticky t 'erc-prompt t - 'field t + 'field 'erc-prompt 'front-sticky t 'read-only t)) (erc-put-text-property 0 (1- (length prompt)) @@ -5681,7 +5707,7 @@ erc-highlight-error (erc-put-text-property 0 (length s) 'font-lock-face 'erc-error-face s) s) -(defun erc-put-text-property (start end property value &optional object) +(defalias 'erc-put-text-property 'put-text-property "Set text-property for an object (usually a string). START and END define the characters covered. PROPERTY is the text-property set, usually the symbol `face'. @@ -5691,14 +5717,9 @@ erc-put-text-property OBJECT is modified without being copied first. You can redefine or `defadvice' this function in order to add -EmacsSpeak support." - (put-text-property start end property value object)) +EmacsSpeak support.") -(defun erc-list (thing) - "Return THING if THING is a list, or a list with THING as its element." - (if (listp thing) - thing - (list thing))) +(defalias 'erc-list 'ensure-list) (defun erc-parse-user (string) "Parse STRING as a user specification (nick!login@host). @@ -7292,10 +7313,11 @@ erc-find-parsed-property (defun erc-restore-text-properties () "Restore the property `erc-parsed' for the region." - (let ((parsed-posn (erc-find-parsed-property))) - (put-text-property - (point-min) (point-max) - 'erc-parsed (when parsed-posn (erc-get-parsed-vector parsed-posn))))) + (when-let* ((parsed-posn (erc-find-parsed-property)) + (found (erc-get-parsed-vector parsed-posn))) + (put-text-property (point-min) (point-max) 'erc-parsed found) + (when-let ((tags (get-text-property parsed-posn 'tags))) + (put-text-property (point-min) (point-max) 'tags tags)))) (defun erc-get-parsed-vector (point) "Return the whole parsed vector on POINT." @@ -7315,6 +7337,13 @@ erc-get-parsed-vector-type (and vect (erc-response.command vect))) +(defun erc--get-eq-comparable-cmd (command) + "Return a symbol or a fixnum representing a message's COMMAND. +See also `erc-message-type'." + ;; IRC numerics are three-digit numbers, possibly with leading 0s. + ;; To invert: (if (numberp o) (format "%03d" o) (symbol-name o)) + (if-let* ((n (string-to-number command)) ((zerop n))) (intern command) n)) + ;; Teach url.el how to open irc:// URLs with ERC. ;; To activate, customize `url-irc-function' to `url-irc-erc'. -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0003-5.6-Expose-insertion-time-as-text-prop-in-erc-stamp.patch >From b23671842178070026b6036e79a4a88848d8759a Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Wed, 24 Nov 2021 03:10:20 -0800 Subject: [PATCH 3/8] [5.6] Expose insertion time as text prop in erc-stamp * lisp/erc/erc-stamp.el (erc-add-timestamp): Add new text property `erc-timestamp' to store lisp time object formerly ensconced in a closure. Instead of creating a new lambda for the cursor-sensor function of each message in a buffer, leave a gap between messages to trip the sensor function. The motivation behind this change is to allow third parties access to valuable timestamp data already stored by ERC anyway. Of secondary importance is discouraging the reliance on those lambdas as a means of detecting message bounds. The gap now serves a similar purpose. Basically, the final character in a message, a newline, will not have a timestamp or a sensor function. When the stamps module isn't loaded, the `erc-message' property can be used instead. Also, instead of looking for the `invisible' text property at point, which is normally `point-max' and thus outside the accessible portion of the buffer, look at the beginning of the inserted message. This allows hook members running before this function to opt out of timestamps by marking a message as invisible. (erc-echo-timestamp): Make interactive and show timestamps even when the variable `erc-echo-timestamps' is nil. (erc--echo-ts-csf): Add new function to serve as value of cursor-sensor function text properties. * test/lisp/erc/erc-stamp-tests.el: New file. --- lisp/erc/erc-stamp.el | 14 ++- test/lisp/erc/erc-stamp-tests.el | 207 +++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 test/lisp/erc/erc-stamp-tests.el diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el index 0aa1590f801..08cdc1c8518 100644 --- a/lisp/erc/erc-stamp.el +++ b/lisp/erc/erc-stamp.el @@ -162,7 +162,7 @@ erc-add-timestamp This function is meant to be called from `erc-insert-modify-hook' or `erc-send-modify-hook'." - (unless (get-text-property (point) 'invisible) + (unless (get-text-property (point-min) 'invisible) (let ((ct (current-time))) (if (fboundp erc-insert-timestamp-function) (funcall erc-insert-timestamp-function @@ -174,12 +174,12 @@ erc-add-timestamp (not erc-timestamp-format)) (funcall erc-insert-away-timestamp-function (erc-format-timestamp ct erc-away-timestamp-format))) - (add-text-properties (point-min) (point-max) + (add-text-properties (point-min) (1- (point-max)) ;; It's important for the function to ;; be different on different entries (bug#22700). (list 'cursor-sensor-functions - (list (lambda (_window _before dir) - (erc-echo-timestamp dir ct)))))))) + ;; Regions are no longer contiguous ^ + '(erc--echo-ts-csf) 'erc-timestamp ct))))) (defvar-local erc-timestamp-last-window-width nil "The width of the last window that showed the current buffer. @@ -400,11 +400,15 @@ erc-toggle-timestamps (defun erc-echo-timestamp (dir stamp) "Print timestamp text-property of an IRC message." - (when (and erc-echo-timestamps (eq 'entered dir)) + (interactive (list 'entered (get-text-property (point) 'erc-timestamp))) + (when (eq 'entered dir) (when stamp (message "%s" (format-time-string erc-echo-timestamp-format stamp))))) +(defun erc--echo-ts-csf (_window _before dir) + (erc-echo-timestamp dir (get-text-property (point) 'erc-timestamp))) + (provide 'erc-stamp) ;;; erc-stamp.el ends here diff --git a/test/lisp/erc/erc-stamp-tests.el b/test/lisp/erc/erc-stamp-tests.el new file mode 100644 index 00000000000..935b9e650b3 --- /dev/null +++ b/test/lisp/erc/erc-stamp-tests.el @@ -0,0 +1,207 @@ +;;; erc-stamp-tests.el --- Tests for erc-stamp. -*- 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 . + +;;; Commentary: + +;;; Code: +(require 'ert-x) +(require 'erc-stamp) +(require 'erc-goodies) ; for `erc-make-read-only' + +;; These display-oriented tests are brittle because many factors +;; influence how text properties are applied. We should just +;; rework these into full scenarios. + +(defun erc-stamp-tests--insert-right (test) + (let ((val (list 0 0)) + (erc-insert-modify-hook '(erc-add-timestamp)) + (erc-insert-post-hook '(erc-make-read-only)) ; see comment above + (erc-timestamp-only-if-changed-flag nil) + ;; + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) + + (advice-add 'erc-format-timestamp :filter-args + (lambda (args) (cons (cl-incf (cadr val) 60) (cdr args))) + '((name . ert-deftest--erc-timestamp-use-align-to))) + + (with-current-buffer (get-buffer-create "*erc-stamp-tests--insert-right*") + (erc-mode) + (erc-munge-invisibility-spec) + (setq erc-server-process (start-process "p" (current-buffer) + "sleep" "1") + erc-input-marker (make-marker) + erc-insert-marker (make-marker)) + (set-process-query-on-exit-flag erc-server-process nil) + (set-marker erc-insert-marker (point-max)) + (erc-display-prompt) + + (funcall test) + + (when noninteractive + (kill-buffer))) + + (advice-remove 'erc-format-timestamp + 'ert-deftest--erc-timestamp-use-align-to))) + +(ert-deftest erc-timestamp-use-align-to--nil () + (erc-stamp-tests--insert-right + (lambda () + + (ert-info ("nil, normal") + (let ((erc-timestamp-use-align-to nil)) + (erc-display-message nil 'notice (current-buffer) "begin")) + (goto-char (point-min)) + (should (search-forward-regexp + (rx "begin" (+ "\t") (* " ") " [") nil t)) + ;; Field includes intervening spaces + (should (eql ?n (char-before (field-beginning (point))))) + ;; Timestamp extends to the end of the line + (should (eql ?\n (char-after (field-end (point)))))) + + ;; The option `erc-timestamp-right-column' is normally nil by + ;; default, but it's a convenient stand in for a sufficiently + ;; small `erc-fill-column' (we can force a line break without + ;; involving that module). + (should-not erc-timestamp-right-column) + + (ert-info ("nil, overlong (hard wrap)") + (let ((erc-timestamp-use-align-to nil) + (erc-timestamp-right-column 20)) + (erc-display-message nil 'notice (current-buffer) + "twenty characters")) + (should (search-forward-regexp (rx bol (+ "\t") (* " ") " [") nil t)) + ;; Field excludes leading whitespace (arguably undesirable). + (should (eql ?\s (char-after (field-beginning (point))))) + ;; Timestamp extends to the end of the line. + (should (eql ?\n (char-after (field-end (point))))))))) + +(ert-deftest erc-timestamp-use-align-to--t () + (erc-stamp-tests--insert-right + (lambda () + + (ert-info ("t, normal") + (let ((erc-timestamp-use-align-to t)) + (let ((msg (erc-format-privmessage "bob" "msg one" nil t))) + (erc-display-message nil nil (current-buffer) msg))) + (goto-char (point-min)) + ;; Exactly two spaces, one from format, one added by erc-stamp. + (should (search-forward "msg one [" nil t)) + ;; Field covers space between. + (should (eql ?e (char-before (field-beginning (point))))) + (should (eql ?\n (char-after (field-end (point)))))) + + (ert-info ("t, overlong (hard wrap)") + (let ((erc-timestamp-use-align-to t) + (erc-timestamp-right-column 20)) + (let ((msg (erc-format-privmessage "bob" "tttt wwww oooo" nil t))) + (erc-display-message nil nil (current-buffer) msg))) + ;; Indented to pos (this is arguably a bug). + (should (search-forward-regexp (rx bol (+ "\t") (* " ") " [") nil t)) + ;; Field starts *after* leading space (arguably bad). + (should (eql ?\[ (char-after (1+ (field-beginning (point)))))) + (should (eql ?\n (char-after (field-end (point))))))))) + +;; This concerns a proposed partial reversal of the changes resulting +;; from: +;; +;; 24.1.50; Wrong behavior of move-end-of-line in ERC (Bug#11706) +;; +;; Perhaps core behavior has changed since this bug was reported, but +;; C-e stopping one char short of EOL no longer seems a problem. +;; However, invoking C-n (`next-line') exhibits a similar effect. +;; When point is in a stamp or near the beginning of a line, issuing a +;; C-n puts point one past the start of the message (i.e., two chars +;; beyond the timestamp's closing "]". Dropping the invisible +;; property when timestamps are hidden does indeed prevent this, but +;; it's also a lasting commitment. The docs mention that it's +;; pointless to pair the old `intangible' property with `invisible' +;; and suggest users look at `cursor-intangible-mode'. Turning off +;; the latter does indeed do the trick as does decrementing the end of +;; the `cursor-intangible' interval so that, in addition to C-n +;; working, a C-f from before the timestamp doesn't overshoot. This +;; appears to be the case whether `erc-hide-timestamps' is enabled or +;; not, but it may be inadvisable for some reason (a hack) and +;; therefore warrants further investigation. +;; +;; Note some striking omissions here: +;; +;; 1. a lack of `fill' module integration (we simulate it by +;; making lines short enough to not wrap) +;; 2. functions like `line-move' behave differently when +;; `noninteractive' +;; 3. no actual test assertions involving `cursor-sensor' movement +;; even though that's a huge ingredient + +(ert-deftest erc-timestamp-intangible--left () + (let ((erc-timestamp-only-if-changed-flag nil) + (erc-timestamp-intangible t) ; default changed to nil in 2014 + (erc-hide-timestamps t) + (erc-insert-timestamp-function 'erc-insert-timestamp-left) + (erc-server-process (start-process "true" (current-buffer) "true")) + (erc-insert-modify-hook '(erc-make-read-only erc-add-timestamp)) + msg + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) + (should (not cursor-sensor-inhibit)) + (set-process-query-on-exit-flag erc-server-process nil) + (erc-mode) + (with-current-buffer (get-buffer-create "*erc-timestamp-intangible*") + (erc-mode) + (erc--initialize-markers (point) nil) + (erc-munge-invisibility-spec) + (erc-display-message nil 'notice (current-buffer) "Welcome") + ;; + ;; Pretend `fill' is active and that these lines are + ;; folded. Otherwise, there's an annoying issue on wrapped lines + ;; (when visual-line-mode is off and stamps are visible) where + ;; C-e sends you to the end of the previous line. + (setq msg "Lorem ipsum dolor sit amet") + (erc-display-message nil nil (current-buffer) + (erc-format-privmessage "alyssa" msg nil t)) + (erc-display-message nil 'notice (current-buffer) "Home") + (goto-char (point-min)) + + ;; EOL is actually EOL (Bug#11706) + + (ert-info ("Notice before stamp, C-e") ; first line/stamp + (should (search-forward "Welcome" nil t)) + (ert-simulate-command '(erc-bol)) + (should (looking-at (rx "["))) + (let ((end (pos-eol))) ; `line-end-position' fails because fields + (ert-simulate-command '(move-end-of-line 1)) + (should (= end (point))))) + + (ert-info ("Privmsg before stamp, C-e") + (should (search-forward "Lorem" nil t)) + (goto-char (pos-bol)) + (should (looking-at (rx "["))) + (let ((end (pos-eol))) + (ert-simulate-command '(move-end-of-line 1)) + (should (= end (point))))) + + (ert-info ("Privmsg first line, C-e") + (goto-char (pos-bol)) + (should (search-forward "ipsum" nil t)) + (let ((end (pos-eol))) + (ert-simulate-command '(move-end-of-line 1)) + (should (= end (point))))) + + (when noninteractive + (kill-buffer))))) + +;;; erc-stamp-tests.el ends here -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0004-5.6-Make-some-erc-stamp-functions-more-limber.patch >From eac909ce56cf8dba87750676e13c37c974f72cd8 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Wed, 24 Nov 2021 05:35:35 -0800 Subject: [PATCH 4/8] [5.6] Make some erc-stamp functions more limber TODO: update ERC-NEWS announcing deprecation. * lisp/erc/erc-stamp.el (erc-timestamp-format-right): Deprecate option and change meaning of its nil value to fall through to `erc-timestamp-format'. Do this to allow modules to predict what the right-hand stamp's final width will be. This also saves `erc-insert-timestamp-left-and-right' from calling `erc-format-timestamp' again for no reason. (erc-stamp--current-time): Add new generic function and method to return current time. Default to calling `current-time'. (erc-stamp--current-time): New internal variable to hold time value used to construct time formatted stamp passed to `erc-insert-timestamp-function'. (erc-add-timestamp): Bind `erc-stamp--current-time' when calling `erc-insert-timestamp-function'. (erc-insert-timestamp-left-and-right): Use STRING parameter and favor it over the now deprecated `erc-timestamp-format-right' to avoid formatting twice. Also extract current time from the variable `erc-stamp--current-time' for similar reasons. --- lisp/erc/erc-stamp.el | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el index 08cdc1c8518..b9ad61aaf3e 100644 --- a/lisp/erc/erc-stamp.el +++ b/lisp/erc/erc-stamp.el @@ -55,6 +55,9 @@ erc-timestamp-format :type '(choice (const nil) (string))) +;; FIXME remove surrounding whitespace from default value and have +;; `erc-insert-timestamp-left-and-right' add it before insertion. + (defcustom erc-timestamp-format-left "\n[%a %b %e %Y]\n" "If set to a string, messages will be timestamped. This string is processed using `format-time-string'. @@ -68,7 +71,7 @@ erc-timestamp-format-left :type '(choice (const nil) (string))) -(defcustom erc-timestamp-format-right " [%H:%M]" +(defcustom erc-timestamp-format-right nil "If set to a string, messages will be timestamped. This string is processed using `format-time-string'. Good examples are \"%T\" and \"%H:%M\". @@ -77,9 +80,14 @@ erc-timestamp-format-right screen when `erc-insert-timestamp-function' is set to `erc-insert-timestamp-left-and-right'. -If nil, timestamping is turned off." +Unlike `erc-timestamp-format' and `erc-timestamp-format-left', if +the value of this option is nil, it falls back to using the value +of `erc-timestamp-format'." + :package-version '(ERC . "5.6") ; FIXME sync on release :type '(choice (const nil) (string))) +(make-obsolete-variable 'erc-timestamp-format-right + 'erc-timestamp-format "30.1") (defcustom erc-insert-timestamp-function 'erc-insert-timestamp-left-and-right "Function to use to insert timestamps. @@ -157,17 +165,31 @@ stamp (remove-hook 'erc-insert-modify-hook #'erc-add-timestamp) (remove-hook 'erc-send-modify-hook #'erc-add-timestamp))) +(defvar erc-stamp--current-time nil + "The current time when calling `erc-insert-timestamp-function'. +Specifically, this is the same lisp time object used to create +the stamp passed to `erc-insert-timestamp-function'.") + +(cl-defgeneric erc-stamp--current-time () + "Return a lisp time object to associate with an IRC message. +This becomes the message's `erc-timestamp' text property, which +may not be unique." + (current-time)) + +(cl-defmethod erc-stamp--current-time :around () + (or erc-stamp--current-time (cl-call-next-method))) + (defun erc-add-timestamp () "Add timestamp and text-properties to message. This function is meant to be called from `erc-insert-modify-hook' or `erc-send-modify-hook'." (unless (get-text-property (point-min) 'invisible) - (let ((ct (current-time))) - (if (fboundp erc-insert-timestamp-function) - (funcall erc-insert-timestamp-function - (erc-format-timestamp ct erc-timestamp-format)) - (error "Timestamp function unbound")) + (let* ((ct (erc-stamp--current-time)) + (erc-stamp--current-time ct)) + (funcall erc-insert-timestamp-function + (erc-format-timestamp ct erc-timestamp-format)) + ;; FIXME this will error when advice has been applied. (when (and (fboundp erc-insert-away-timestamp-function) erc-away-timestamp-format (erc-away-time) -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0005-5.6-Put-display-properties-to-better-use-in-erc-stam.patch >From b88dfe1945b3f13507bdcbc438bf438d0bb2e8b1 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Wed, 24 Nov 2021 05:35:35 -0800 Subject: [PATCH 5/8] [5.6] Put display properties to better use in erc-stamp * lisp/erc/erc-stamp.el (erc-timestamp-use-align-to): Enhance meaning of option to accept numeric value for dynamically aligned right-side stamps. Use `graphic-display-p' to determine default value even though, as stated in the manual, terminal Emacs also supports the "space" display spec. (erc-stamp-right-margin-width): New option to determine width of right margin when `erc-stamp--display-margin-mode' is active or `erc-timestamp-use-align-to' is set to `margin'. (erc-stamp--display-margin-force): Add new helper function for `erc-stamp--display-margin-mode'. (erc-stamp--display-margin-mode): Add internal minor mode to help other modules quickly ensure stamps are showing correctly. (erc-stamp--inherited-props): Add internal const to hold properties that should be inherited from message being inserted. (erc-insert-aligned): Deprecate function and remove from primary client code path. (erc-insert-timestamp-right): Account for new display-related values of `erc-timestamp-use-align-to'. * test/lisp/erc/erc-stamp-tests.el (erc-timestamp-use-align-to--nil, erc-timestamp-use-align-to--t): Adjust spacing for new default right-hand stamp, `erc-format-timestamp', which lacks a leading space. (erc-timestamp-use-align-to--integer, erc-timestamp-use-align-to--margin): New tests. --- lisp/erc/erc-stamp.el | 154 +++++++++++++++++++++++++++---- test/lisp/erc/erc-stamp-tests.el | 70 ++++++++++++-- 2 files changed, 200 insertions(+), 24 deletions(-) diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el index b9ad61aaf3e..d1c2f790bc8 100644 --- a/lisp/erc/erc-stamp.el +++ b/lisp/erc/erc-stamp.el @@ -239,14 +239,107 @@ erc-timestamp-right-column (integer :tag "Column number") (const :tag "Unspecified" nil))) -(defcustom erc-timestamp-use-align-to (eq window-system 'x) +(defcustom erc-timestamp-use-align-to (and (display-graphic-p) t) "If non-nil, use the :align-to display property to align the stamp. This gives better results when variable-width characters (like Asian language characters and math symbols) precede a timestamp. -A side effect of enabling this is that there will only be one -space before a right timestamp in any saved logs." - :type 'boolean) +This option only matters when `erc-insert-timestamp-function' is +set to `erc-insert-timestamp-right' or that option's default, +`erc-insert-timestamp-left-and-right'. If the value is a +positive integer, alignment occurs that many columns from the +right edge. If the value is `margin', the stamp appears in the +right margin when visible. + +Enabling this option produces a side effect in that stamps aren't +indented in saved logs. When its value is an integer, this +option adds a space after the end of a message if the stamp +doesn't already start with one. And when its value is t, it adds +a single space, unconditionally. And while this option never +adds a space when its value is `margin', ERC does offer a +workaround in `erc-stamp-prefix-log-filter', which strips +trailing stamps from messages and puts them before every line." + :type '(choice boolean integer (const margin)) + :package-version '(ERC . "5.5")) ; FIXME sync on release + +(defcustom erc-stamp-right-margin-width nil + "Width in columns of the right margin. +When this option is nil, pretend its value is one column greater +than the `string-width' of the formatted `erc-timestamp-format'. +This option only matters when `erc-timestamp-use-align-to' is set +to `margin'." + :package-version '(ERC . "5.5") ; FIXME sync on release + :type '(choice (const nil) integer)) + +(defun erc-stamp--display-margin-force (orig &rest r) + (let ((erc-timestamp-use-align-to 'margin)) + (apply orig r))) + +(defun erc-stamp--adjust-right-margin (cols) + "Adjust right margin by COLS. +When COLS is zero, reset width to `erc-stamp-right-margin-width' +or one col more than the `string-width' of +`erc-timestamp-format'." + (let ((width + (if (zerop cols) + (or erc-stamp-right-margin-width + (1+ (string-width (or erc-timestamp-last-inserted + (erc-format-timestamp + (current-time) + erc-timestamp-format))))) + (+ right-margin-width cols)))) + (setq right-margin-width width + right-fringe-width 0) + (set-window-margins nil left-margin-width width) + (set-window-fringes nil left-fringe-width 0))) + +(defun erc-stamp-prefix-log-filter (text) + "Prefix every message in the buffer with a stamp. +Remove trailing stamps as well. For now, hard code the format to +\"ZNC\"-log style, which is [HH:MM:SS]. Expect to be used as a +`erc-log-filter-function' when `erc-timestamp-use-align-to' is +non-nil." + (insert text) + (goto-char (point-min)) + (while + (progn + (when-let* (((< (point) (pos-eol))) + (end (1- (pos-eol))) + ((eq 'erc-timestamp (field-at-pos end))) + (beg (field-beginning end)) + ;; Skip a line that's just a timestamp. + ((> beg (point)))) + (delete-region beg (1+ end))) + (when-let (time (get-text-property (point) 'erc-timestamp)) + (insert (format-time-string "[%H:%M:%S] " time))) + (zerop (forward-line)))) + "") + +;; If people want to use this directly, we can convert it into +;; a local module. +(define-minor-mode erc-stamp--display-margin-mode + "Internal minor mode for built-in modules integrating with `stamp'. +It binds `erc-timestamp-use-align-to' to `margin' around calls to +`erc-insert-timestamp-function' in the current buffer, and sets +the right window margin to `erc-stamp-right-margin-width'. It +also arranges to remove most text properties when a user kills +message text so that stamps will be visible when yanked." + :interactive nil + (if erc-stamp--display-margin-mode + (progn + (erc-stamp--adjust-right-margin 0) + (add-function :filter-return (local 'filter-buffer-substring-function) + #'erc--remove-text-properties) + (add-function :around (local 'erc-insert-timestamp-function) + #'erc-stamp--display-margin-force)) + (remove-function (local 'filter-buffer-substring-function) + #'erc--remove-text-properties) + (remove-function (local 'erc-insert-timestamp-function) + #'erc-stamp--display-margin-force) + (kill-local-variable 'right-margin-width) + (kill-local-variable 'right-fringe-width) + (set-window-margins left-margin-width nil) + (set-window-fringes left-fringe-width nil))) (defun erc-insert-timestamp-left (string) "Insert timestamps at the beginning of the line." @@ -265,6 +358,7 @@ erc-insert-aligned If `erc-timestamp-use-align-to' is t, use the :align-to display property to get to the POSth column." + (declare (obsolete "inlined and removed from client code path" "30.1")) (if (not erc-timestamp-use-align-to) (indent-to pos) (insert " ") @@ -275,6 +369,8 @@ erc-insert-aligned ;; Silence byte-compiler (defvar erc-fill-column) +(defvar erc-stamp--inherited-props '(line-prefix wrap-prefix)) + (defun erc-insert-timestamp-right (string) "Insert timestamp on the right side of the screen. STRING is the timestamp to insert. This function is a possible @@ -326,25 +422,47 @@ erc-insert-timestamp-right ;; some margin of error if what is displayed on the line differs ;; from the number of characters on the line. (setq col (+ col (ceiling (/ (- col (- (point) (line-beginning-position))) 1.6)))) - (if (< col pos) - (erc-insert-aligned string pos) - (newline) - (indent-to pos) - (setq from (point)) - (insert string)) + ;; For compatibility reasons, the `erc-timestamp' field includes + ;; intervening white space unless a hard break is warranted. + (pcase erc-timestamp-use-align-to + ((and 't (guard (< col pos))) + (insert " ") + (put-text-property from (point) 'display `(space :align-to ,pos))) + ((pred integerp) ; (cl-type (integer 0 *)) + (insert " ") + (when (eq ?\s (aref string 0)) + (setq string (substring string 1))) + (let ((s (+ erc-timestamp-use-align-to (string-width string)))) + (put-text-property from (point) 'display + `(space :align-to (- right ,s))))) + ('margin + (put-text-property 0 (length string) + 'display `((margin right-margin) ,string) + string)) + ((guard (>= col pos)) (newline) (indent-to pos) (setq from (point))) + (_ (indent-to pos))) + (insert string) + (dolist (p erc-stamp--inherited-props) + (when-let ((v (get-text-property (1- from) p))) + (put-text-property from (point) p v))) (erc-put-text-property from (point) 'field 'erc-timestamp) (erc-put-text-property from (point) 'rear-nonsticky t) (when erc-timestamp-intangible (erc-put-text-property from (1+ (point)) 'cursor-intangible t))))) -(defun erc-insert-timestamp-left-and-right (_string) - "This is another function that can be used with `erc-insert-timestamp-function'. -If the date is changed, it will print a blank line, the date, and -another blank line. If the time is changed, it will then print -it off to the right." - (let* ((ct (current-time)) - (ts-left (erc-format-timestamp ct erc-timestamp-format-left)) - (ts-right (erc-format-timestamp ct erc-timestamp-format-right))) +(defun erc-insert-timestamp-left-and-right (string) + "Insert a stamp on either side when it changes. +When the deprecated option `erc-timestamp-format-right' is nil, +use STRING, which originates from `erc-timestamp-format', for the +right-hand stamp. Use `erc-timestamp-format-left' for the +left-hand stamp and expect it to change less frequently." + (let* ((ct (or erc-stamp--current-time (erc-stamp--current-time))) + (ts-left (erc-format-timestamp ct erc-timestamp-format-left)) + (ts-right (with-suppressed-warnings + ((obsolete erc-timestamp-format-right)) + (if erc-timestamp-format-right + (erc-format-timestamp ct erc-timestamp-format-right) + string)))) ;; insert left timestamp (unless (string-equal ts-left erc-timestamp-last-inserted-left) (goto-char (point-min)) diff --git a/test/lisp/erc/erc-stamp-tests.el b/test/lisp/erc/erc-stamp-tests.el index 935b9e650b3..01e71e348e0 100644 --- a/test/lisp/erc/erc-stamp-tests.el +++ b/test/lisp/erc/erc-stamp-tests.el @@ -68,7 +68,7 @@ erc-timestamp-use-align-to--nil (erc-display-message nil 'notice (current-buffer) "begin")) (goto-char (point-min)) (should (search-forward-regexp - (rx "begin" (+ "\t") (* " ") " [") nil t)) + (rx "begin" (+ "\t") (* " ") "[") nil t)) ;; Field includes intervening spaces (should (eql ?n (char-before (field-beginning (point))))) ;; Timestamp extends to the end of the line @@ -85,9 +85,9 @@ erc-timestamp-use-align-to--nil (erc-timestamp-right-column 20)) (erc-display-message nil 'notice (current-buffer) "twenty characters")) - (should (search-forward-regexp (rx bol (+ "\t") (* " ") " [") nil t)) + (should (search-forward-regexp (rx bol (+ "\t") (* " ") "[") nil t)) ;; Field excludes leading whitespace (arguably undesirable). - (should (eql ?\s (char-after (field-beginning (point))))) + (should (eql ?\[ (char-after (field-beginning (point))))) ;; Timestamp extends to the end of the line. (should (eql ?\n (char-after (field-end (point))))))))) @@ -101,7 +101,7 @@ erc-timestamp-use-align-to--t (erc-display-message nil nil (current-buffer) msg))) (goto-char (point-min)) ;; Exactly two spaces, one from format, one added by erc-stamp. - (should (search-forward "msg one [" nil t)) + (should (search-forward "msg one [" nil t)) ;; Field covers space between. (should (eql ?e (char-before (field-beginning (point))))) (should (eql ?\n (char-after (field-end (point)))))) @@ -112,9 +112,67 @@ erc-timestamp-use-align-to--t (let ((msg (erc-format-privmessage "bob" "tttt wwww oooo" nil t))) (erc-display-message nil nil (current-buffer) msg))) ;; Indented to pos (this is arguably a bug). - (should (search-forward-regexp (rx bol (+ "\t") (* " ") " [") nil t)) + (should (search-forward-regexp (rx bol (+ "\t") (* " ") "[") nil t)) ;; Field starts *after* leading space (arguably bad). - (should (eql ?\[ (char-after (1+ (field-beginning (point)))))) + (should (eql ?\[ (char-after (field-beginning (point))))) + (should (eql ?\n (char-after (field-end (point))))))))) + +(ert-deftest erc-timestamp-use-align-to--integer () + (erc-stamp-tests--insert-right + (lambda () + + (ert-info ("integer, normal") + (let ((erc-timestamp-use-align-to 1)) + (let ((msg (erc-format-privmessage "bob" "msg one" nil t))) + (erc-display-message nil nil (current-buffer) msg))) + (goto-char (point-min)) + ;; Space not added because included in format string. + (should (search-forward "msg one [" nil t)) + ;; Field covers space between. + (should (eql ?e (char-before (field-beginning (point))))) + (should (eql ?\n (char-after (field-end (point)))))) + + (ert-info ("integer, overlong (hard wrap)") + (let ((erc-timestamp-use-align-to 1) + (erc-timestamp-right-column 20)) + (let ((msg (erc-format-privmessage "bob" "tttt wwww oooo" nil t))) + (erc-display-message nil nil (current-buffer) msg))) + ;; No hard wrap + (should (search-forward "oooo [" nil t)) + ;; Field starts at leading space. + (should (eql ?\s (char-after (field-beginning (point))))) + (should (eql ?\n (char-after (field-end (point))))))))) + +(ert-deftest erc-timestamp-use-align-to--margin () + (erc-stamp-tests--insert-right + (lambda () + (erc-stamp--display-margin-mode +1) + + (ert-info ("margin, normal") + (let ((erc-timestamp-use-align-to 'margin)) + (let ((msg (erc-format-privmessage "bob" "msg one" nil t))) + (put-text-property 0 (length msg) 'wrap-prefix 10 msg) + (erc-display-message nil nil (current-buffer) msg))) + (goto-char (point-min)) + ;; Space not added (treated as opaque string). + (should (search-forward "msg one[" nil t)) + ;; Field covers stamp alone + (should (eql ?e (char-before (field-beginning (point))))) + ;; Vanity props extended + (should (get-text-property (field-beginning (point)) 'wrap-prefix)) + (should (get-text-property (1+ (field-beginning (point))) 'wrap-prefix)) + (should (get-text-property (1- (field-end (point))) 'wrap-prefix)) + (should (eql ?\n (char-after (field-end (point)))))) + + (ert-info ("margin, overlong (hard wrap)") + (let ((erc-timestamp-use-align-to 'margin) + (erc-timestamp-right-column 20)) + (let ((msg (erc-format-privmessage "bob" "tttt wwww oooo" nil t))) + (erc-display-message nil nil (current-buffer) msg))) + ;; No hard wrap + (should (search-forward "oooo[" nil t)) + ;; Field starts at format string (right bracket) + (should (eql ?\[ (char-after (field-beginning (point))))) (should (eql ?\n (char-after (field-end (point))))))))) ;; This concerns a proposed partial reversal of the changes resulting -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0006-5.6-Convert-erc-fill-minor-mode-into-a-proper-module.patch >From 4a5909b379c5d0393c6a9f46a41b8d45531e02be Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 24 Apr 2022 02:38:12 -0700 Subject: [PATCH 6/8] [5.6] Convert erc-fill minor mode into a proper module * lisp/erc/erc-fill.el (erc-fill-mode, erc-fill-enable, erc-fill-disable): Use API to create these. (erc-fill-static): Save restriction instead of caller's match data. --- lisp/erc/erc-fill.el | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el index e10b7d790f6..caf401bf222 100644 --- a/lisp/erc/erc-fill.el +++ b/lisp/erc/erc-fill.el @@ -38,30 +38,18 @@ erc-fill :group 'erc) ;;;###autoload(autoload 'erc-fill-mode "erc-fill" nil t) -(define-minor-mode erc-fill-mode - "Toggle ERC fill mode. -With a prefix argument ARG, enable ERC fill mode if ARG is -positive, and disable it otherwise. If called from Lisp, enable -the mode if ARG is omitted or nil. - +(define-erc-module fill nil + "Manage filling in ERC buffers. ERC fill mode is a global minor mode. When enabled, messages in the channel buffers are filled." - :global t - (if erc-fill-mode - (erc-fill-enable) - (erc-fill-disable))) - -(defun erc-fill-enable () - "Setup hooks for `erc-fill-mode'." - (interactive) - (add-hook 'erc-insert-modify-hook #'erc-fill) - (add-hook 'erc-send-modify-hook #'erc-fill)) - -(defun erc-fill-disable () - "Cleanup hooks, disable `erc-fill-mode'." - (interactive) - (remove-hook 'erc-insert-modify-hook #'erc-fill) - (remove-hook 'erc-send-modify-hook #'erc-fill)) + ;; FIXME ensure a consistent ordering relative to hook members from + ;; other modules. Ideally, this module's processing should happen + ;; after "morphological" modifications to a message's text but + ;; before superficial decorations. + ((add-hook 'erc-insert-modify-hook #'erc-fill) + (add-hook 'erc-send-modify-hook #'erc-fill)) + ((remove-hook 'erc-insert-modify-hook #'erc-fill) + (remove-hook 'erc-send-modify-hook #'erc-fill))) (defcustom erc-fill-prefix nil "Values used as `fill-prefix' for `erc-fill-variable'. @@ -130,7 +118,7 @@ erc-fill (defun erc-fill-static () "Fills a text such that messages start at column `erc-fill-static-center'." - (save-match-data + (save-restriction (goto-char (point-min)) (looking-at "^\\(\\S-+\\)") (let ((nick (match-string 1))) -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0007-5.6-Add-variant-for-erc-match-invisibility-spec.patch >From 3e6d4d199863f4c70404b90febc0e66ec9e45885 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 27 Jan 2023 05:34:56 -0800 Subject: [PATCH 7/8] [5.6] Add variant for erc-match invisibility spec * lisp/erc/erc-match.el (erc-match-enable, erc-match-disable): Arrange for possibly adding or removing `erc-match' from `buffer-invisibility-spec'. (erc-match--hide-fools-offset-bounds): Add new variable to serve as switch for activating invisibility on a modified interval that's offset toward `point-min' by one character. (erc-hide-fools): Optionally offset start and end of invisible region by minus one. (erc-match--modify-invisibility-spec): New housekeeping function to set up and tear down offset spec. --- lisp/erc/erc-match.el | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el index 499bcaf5724..87272f0b647 100644 --- a/lisp/erc/erc-match.el +++ b/lisp/erc/erc-match.el @@ -52,8 +52,11 @@ match `erc-current-nick-highlight-type'. For all these highlighting types, you can decide whether the entire message or only the sending nick is highlighted." - ((add-hook 'erc-insert-modify-hook #'erc-match-message 'append)) - ((remove-hook 'erc-insert-modify-hook #'erc-match-message))) + ((add-hook 'erc-insert-modify-hook #'erc-match-message 'append) + (add-hook 'erc-mode-hook #'erc-match--modify-invisibility-spec)) + ((remove-hook 'erc-insert-modify-hook #'erc-match-message) + (remove-hook 'erc-mode-hook #'erc-match--modify-invisibility-spec) + (erc-match--modify-invisibility-spec))) ;; Remaining customizations @@ -649,13 +652,22 @@ erc-go-to-log-matches-buffer (define-key erc-mode-map "\C-c\C-k" #'erc-go-to-log-matches-buffer) +(defvar-local erc-match--hide-fools-offset-bounds nil) + (defun erc-hide-fools (match-type _nickuserhost _message) "Hide foolish comments. This function should be called from `erc-text-matched-hook'." - (when (eq match-type 'fool) - (erc-put-text-properties (point-min) (point-max) - '(invisible intangible) - (current-buffer)))) + (when (eq match-type 'fool) + (if erc-match--hide-fools-offset-bounds + (let ((beg (point-min)) + (end (point-max))) + (save-restriction + (widen) + (put-text-property (1- beg) (1- end) 'invisible 'erc-match))) + ;; The docs say `intangible' is deprecated, but this has been + ;; like this for ages. Should verify unneeded and remove if so. + (erc-put-text-properties (point-min) (point-max) + '(invisible intangible))))) (defun erc-beep-on-match (match-type _nickuserhost _message) "Beep when text matches. @@ -663,6 +675,13 @@ erc-beep-on-match (when (member match-type erc-beep-match-types) (beep))) +(defun erc-match--modify-invisibility-spec () + "Add an ellipsis property to the local spec." + (if erc-match-mode + (add-to-invisibility-spec 'erc-match) + (erc-with-all-buffers-of-server nil nil + (remove-from-invisibility-spec 'erc-match)))) + (provide 'erc-match) ;;; erc-match.el ends here -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0008-5.6-Add-erc-fill-style-based-on-visual-line-mode.patch >From 4dc8b4968313d3e99c680f25693a2a5ef7e301c5 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 13 Jan 2023 00:00:56 -0800 Subject: [PATCH 8/8] [5.6] Add erc-fill style based on visual-line-mode * lisp/erc/erc-common.el (erc--features-to-modules): Add mapping for local module `fill-wrap'. * lisp/erc/erc-compat.el (erc-compat--29-set-transient-map-timer, erc-compat--29-set-transient-map, erc-compat--set-transient-map): Backport `set-transient-map' definition from Emacs 29. * lisp/erc/erc-fill.el (erc-fill-function): Add new value, `erc-fill-wrap'. (erc-fill-static-center): Extend meaning of option to also affect `erc-wrap-mode'. (erc-fill-wrap-mode, erc-fill--wrap-prefix, erc-fill--wrap-value, erc-fill--wrap-movement): New minor mode and variables to support it. (erc-fill-wrap-movement): New option to control how where `visual-line-mode' keys are active. (erc-fill--wrap-kill-line, erc-fill--wrap-beginning-of-line, erc-fill--wrap-end-of-line): New movement commands. (erc-fill-wrap-cycle-visual-movement): New command to cycle local value of `erc-fill-wrap-movement'. (erc-fill-wrap-mode-map): New map based on `visual-line-mode-map'. (erc-fill-wrap): New function implementing `erc-fill-function' (behavioral) interface. (erc-fill-wrap-nudge, erc-fill--wrap-nudge): New command and helper for growing and shrinking visual fill prefix. * test/lisp/erc/erc-fill-tests.el: New file. --- lisp/erc/erc-common.el | 1 + lisp/erc/erc-compat.el | 56 +++++++ lisp/erc/erc-fill.el | 288 +++++++++++++++++++++++++++++++- test/lisp/erc/erc-fill-tests.el | 198 ++++++++++++++++++++++ 4 files changed, 538 insertions(+), 5 deletions(-) create mode 100644 test/lisp/erc/erc-fill-tests.el diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el index 994555acecf..aae8280baa9 100644 --- a/lisp/erc/erc-common.el +++ b/lisp/erc/erc-common.el @@ -95,6 +95,7 @@ erc--features-to-modules (erc-join autojoin) (erc-page page ctcp-page) (erc-sound sound ctcp-sound) + (erc-fill fill-wrap) (erc-stamp stamp timestamp) (erc-services services nickserv)) "Migration alist mapping a library feature to module names. diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el index 5601ede27a5..a4367fe4ba5 100644 --- a/lisp/erc/erc-compat.el +++ b/lisp/erc/erc-compat.el @@ -409,6 +409,62 @@ erc-compat--29-browse-url-irc (cons '("\\`irc6?s?://" . erc-compat--29-browse-url-irc) existing)))))) +(defvar erc-compat--29-set-transient-map-timer nil) + +(defun erc-compat--29-set-transient-map + (map &optional keep-pred on-exit message timeout) + (let* ((message + (when message + (let (keys) + (map-keymap (lambda (key cmd) (and cmd (push key keys))) map) + (format-spec + (if (stringp message) message "Repeat with %k") + `((?k . ,(mapconcat + (lambda (key) + (substitute-command-keys + (format "\\`%s'" (key-description (vector key))))) + keys ", "))))))) + (clearfun (make-symbol "clear-transient-map")) + (exitfun (lambda () + (internal-pop-keymap map 'overriding-terminal-local-map) + (remove-hook 'pre-command-hook clearfun) + (when message (message "")) + (when erc-compat--29-set-transient-map-timer + (cancel-timer erc-compat--29-set-transient-map-timer)) + (when on-exit (funcall on-exit))))) + (fset clearfun + (lambda () + (with-demoted-errors "set-transient-map PCH: %S" + (if (cond + ((null keep-pred) nil) + ((and (not (eq map (cadr overriding-terminal-local-map))) + (memq map (cddr overriding-terminal-local-map))) + t) + ((eq t keep-pred) + (let ((mc (lookup-key map (this-command-keys-vector)))) + (when (and mc (symbolp mc)) + (setq mc (or (command-remapping mc) mc))) + (and mc (eq this-command mc)))) + (t (funcall keep-pred))) + (when message (message "%s" message)) + (funcall exitfun))))) + (add-hook 'pre-command-hook clearfun) + (internal-push-keymap map 'overriding-terminal-local-map) + (when timeout + (when erc-compat--29-set-transient-map-timer + (cancel-timer erc-compat--29-set-transient-map-timer)) + (setq erc-compat--29-set-transient-map-timer + (run-with-idle-timer timeout nil exitfun))) + (when message (message "%s" message)) + exitfun)) + +(defmacro erc-compat--set-transient-map (&rest args) + (cons (if (>= emacs-major-version 29) + 'set-transient-map + 'erc-compat--29-set-transient-map) + args)) + + (provide 'erc-compat) ;;; erc-compat.el ends here diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el index caf401bf222..13e95967bf8 100644 --- a/lisp/erc/erc-fill.el +++ b/lisp/erc/erc-fill.el @@ -28,6 +28,9 @@ ;; `erc-fill-mode' to switch it on. Customize `erc-fill-function' to ;; change the style. +;; TODO: redo `erc-fill-wrap-nudge' using transient after ERC drops +;; support for Emacs 27. + ;;; Code: (require 'erc) @@ -79,16 +82,29 @@ erc-fill-function These two styles are implemented using `erc-fill-variable' and `erc-fill-static'. You can, of course, define your own filling function. Narrowing to the region in question is in effect while your -function is called." +function is called. + +A third style resembles static filling but \"wraps\" instead of +fills, thanks to `visual-line-mode' mode, which ERC automatically +enables when this option is `erc-fill-wrap' or when +`erc-fill-wrap-mode' is active. Set `erc-fill-static-center' to +your preferred initial \"prefix\" width. For adjusting the width +during a session, see the command `erc-fill-wrap-nudge'." :type '(choice (const :tag "Variable Filling" erc-fill-variable) (const :tag "Static Filling" erc-fill-static) + (const :tag "Dynamic word-wrap" erc-fill-wrap) function)) (defcustom erc-fill-static-center 27 - "Column around which all statically filled messages will be centered. -This column denotes the point where the ` ' character between - and the entered text will be put, thus aligning nick -names right and text left." + "Number of columns to \"outdent\" the first line of a message. +During early message handing, ERC prepends a span of +non-whitespace characters to every message, such as a bracketed +\"\" or an `erc-notice-prefix'. The +`erc-fill-function' variants `erc-fill-static' and +`erc-fill-wrap' look to this option to determine the amount of +padding to apply to that portion until the filled (or wrapped) +message content aligns with the indicated column. See also +https://en.wikipedia.org/wiki/Hanging_indent." :type 'integer) (defcustom erc-fill-variable-maximum-indentation 17 @@ -155,6 +171,268 @@ erc-fill-variable (erc-fill-regarding-timestamp)))) (erc-restore-text-properties))) +(defvar-local erc-fill--wrap-prefix nil) +(defvar-local erc-fill--wrap-value nil) +(defvar-local erc-fill--wrap-visual-keys nil) + +(defcustom erc-fill-wrap-use-pixels t + "Whether to calculate padding in pixels when possible. +A value of nil means ERC should use columns, which may happen +regardless, depending on the Emacs version. This option only +matters when `erc-fill-wrap-mode' is enabled." + :package-version '(ERC . "5.5") ; FIXME sync on release + :type 'boolean) + +(defcustom erc-fill-wrap-visual-keys 'non-input + "Whether to retain keys defined by `visual-line-mode'. +A value of t tells ERC to use movement commands defined by +`visual-line-mode' everywhere in an ERC buffer along with visual +editing commands in the input area. A value of nil means to +never do so. A value of `non-input' tells ERC to act like the +value is nil in the input area and t elsewhere. This option only +plays a role when `erc-fill-wrap-mode' is enabled." + :package-version '(ERC . "5.5") ; FIXME sync on release + :type '(choice (const nil) (const t) (const non-input))) + +(defun erc-fill--wrap-move (normal-cmd visual-cmd arg) + (funcall + (pcase erc-fill--wrap-visual-keys + ('non-input (if (>= (point) erc-input-marker) normal-cmd visual-cmd)) + ('t visual-cmd) + (_ normal-cmd)) + arg)) + +(defun erc-fill--wrap-kill-line (arg) + "Defer to `kill-line' or `kill-visual-line'." + (interactive "P") + ;; ERC buffers are read-only outside of the input area, but we run + ;; `kill-line' anyway so that users can see the error. + (erc-fill--wrap-move #'kill-line #'kill-visual-line arg)) + +(defun erc-fill--wrap-beginning-of-line (arg) + "Defer to `move-beginning-of-line' or `beginning-of-visual-line'." + (interactive "^p") + (let ((inhibit-field-text-motion t)) + (erc-fill--wrap-move #'move-beginning-of-line + #'beginning-of-visual-line arg)) + (when (get-text-property (point) 'erc-prompt) + (goto-char erc-input-marker))) + +(defun erc-fill--wrap-end-of-line (arg) + "Defer to `move-end-of-line' or `end-of-visual-line'." + (interactive "^p") + (erc-fill--wrap-move #'move-end-of-line #'end-of-visual-line arg)) + +(defun erc-fill-wrap-cycle-visual-movement (arg) + "Cycle through `erc-fill-wrap-visual-keys' styles ARG times. +Go from nil to t to `non-input' and back around, but set internal +state instead of mutating `erc-fill-wrap-visual-keys'. When ARG +is 0, reset to value of `erc-fill-wrap-visual-keys'." + (interactive "^p") + (when (zerop arg) + (setq erc-fill--wrap-visual-keys erc-fill-wrap-visual-keys)) + (while (not (zerop arg)) + (cl-incf arg (- (abs arg))) + (setq erc-fill--wrap-visual-keys (pcase erc-fill--wrap-visual-keys + ('nil t) + ('t 'non-input) + ('non-input nil)))) + (message "erc-fill-wrap-movement: %S" erc-fill--wrap-visual-keys)) + +(defvar-keymap erc-fill-wrap-mode-map ; Compat 29 + :doc "Keymap for ERC's `fill-wrap' module." + :parent visual-line-mode-map + " " #'erc-fill--wrap-kill-line + " " #'erc-fill--wrap-end-of-line + " " #'erc-fill--wrap-beginning-of-line + "C-c a" #'erc-fill-wrap-cycle-visual-movement + ;; Not sure if this is problematic because `erc-bol' takes no args. + " " #'erc-fill--wrap-beginning-of-line) + +(defvar erc-match-mode) +(defvar erc-match--hide-fools-offset-bounds) + +(define-erc-module fill-wrap nil + "Fill style leveraging `visual-line-mode'. +This local module depends on the global `fill' module. To use +it, either include `fill-wrap' in `erc-modules' or set +`erc-fill-function' to `erc-fill-wrap'. You can also manually +invoke one of the minor-mode toggles. When the option +`erc-insert-timestamp-function' is `erc-insert-timestamp-right' +or `erc-insert-timestamp-left-and-right', it shows timestamps in +the right margin." + ((let (msg) + (unless erc-fill-mode + (unless (memq 'fill erc-modules) + (setq msg + (concat "WARNING: enabling default global module `fill' needed " + " by local module `fill-wrap'. This will impact all" + " ERC sessions. Add `fill' to `erc-modules' to avoid " + " this warning. See Info:\"(erc) Modules\" for more."))) + (erc-fill-mode +1)) + ;; Set local value of user option (can we avoid this somehow?) + (unless (eq erc-fill-function #'erc-fill-wrap) + (setq-local erc-fill-function #'erc-fill-wrap)) + (when-let* ((vars (or erc--server-reconnecting erc--target-priors)) + ((alist-get 'erc-fill-wrap-mode vars))) + (setq erc-fill--wrap-visual-keys (alist-get 'erc-fill--wrap-visual-keys + vars) + erc-fill--wrap-prefix (alist-get 'erc-fill--wrap-prefix vars) + erc-fill--wrap-value (alist-get 'erc-fill--wrap-value vars))) + (when (or erc-stamp-mode (memq 'stamp erc-modules)) + (erc-stamp--display-margin-mode +1)) + (when (or (bound-and-true-p erc-match-mode) (memq 'match erc-modules)) + (require 'erc-match) + (setq erc-match--hide-fools-offset-bounds t)) + (setq erc-fill--wrap-value + (or erc-fill--wrap-value erc-fill-static-center) + ;; + erc-fill--wrap-prefix + (or erc-fill--wrap-prefix + (list 'space :width erc-fill--wrap-value))) + (visual-line-mode +1) + (unless (local-variable-p 'erc-fill--wrap-visual-keys) + (setq erc-fill--wrap-visual-keys erc-fill-wrap-visual-keys)) + (when msg + (erc-display-error-notice nil msg)))) + ((when erc-stamp--display-margin-mode + (erc-stamp--display-margin-mode -1)) + (kill-local-variable 'erc-button--add-nickname-face-function) + (kill-local-variable 'erc-fill--wrap-prefix) + (kill-local-variable 'erc-fill--wrap-value) + (kill-local-variable 'erc-fill-function) + (kill-local-variable 'erc-fill--wrap-visual-keys) + (visual-line-mode -1)) + 'local) + +(defvar-local erc-fill--wrap-length-function nil + "Function to determine length of overhanging characters. +It should return an EXPR as defined by the info node `(elisp) +Pixel Specification'. This value should represent the width of +the overhang with all faces applied, including any enclosing +brackets (which are not normally fontified) and a trailing space. +It can also return nil to tell ERC to fall back to the default +behavior of taking the length from the first \"word\". This +variable can be converted to a public one if needed by third +parties.") + +(defun erc-fill-wrap () + "Use text props to mimic the effect of `erc-fill-static'. +See `erc-fill-wrap-mode' for details." + (unless erc-fill-wrap-mode + (erc-fill-wrap-mode +1)) + (save-excursion + (goto-char (point-min)) + (let* ((len (or (and erc-fill--wrap-length-function + (funcall erc-fill--wrap-length-function)) + (progn + (skip-syntax-forward "^-") + (forward-char) + (if (and erc-fill-wrap-use-pixels + (fboundp 'buffer-text-pixel-size)) + (save-restriction + (narrow-to-region (point-min) (point)) + (list (car (buffer-text-pixel-size)))) + (- (point) (point-min))))))) + ;; Leaving out the final newline doesn't seem to affect anything. + (erc-put-text-properties (point-min) (point-max) + '(line-prefix wrap-prefix) nil + `((space :width (- ,erc-fill--wrap-value ,len)) + ,erc-fill--wrap-prefix))))) + +;; This is an experimental helper for third-party modules. You could, +;; for example, use this to automatically resize the prefix to a +;; fraction of the window's width on some event change. + +(defun erc-fill--wrap-fix (&optional value) + "Re-wrap from `point-min' to `point-max'. +Reset prefix to VALUE, when given." + (save-excursion + (when value + (setq erc-fill--wrap-value value + erc-fill--wrap-prefix (list 'space :width value))) + (let ((inhibit-field-text-motion t) + (inhibit-read-only t)) + (goto-char (point-min)) + (while (and (zerop (forward-line)) + (< (point) (min (point-max) erc-insert-marker))) + (save-restriction + (narrow-to-region (line-beginning-position) (line-end-position)) + (erc-fill-wrap)))))) + +(defun erc-fill--wrap-nudge (arg) + (save-excursion + (save-restriction + (widen) + (let ((inhibit-field-text-motion t) + (inhibit-read-only t) ; necessary? + (p (goto-char (point-min))) + v) + (when (zerop arg) + (setq arg (- erc-fill-static-center erc-fill--wrap-value))) + (cl-incf (caddr erc-fill--wrap-prefix) arg) + (cl-incf erc-fill--wrap-value arg) + (while (setq p (next-single-property-change p 'line-prefix)) + (when-let* ((this-v (get-text-property p 'line-prefix)) + ((not (eq this-v v)))) + (setq v this-v) + (cl-incf (nth 1 (nth 2 v)) arg)))))) ; (space :width (- *i* len)) + arg) + +(defun erc-fill-wrap-nudge (arg) + "Adjust `erc-fill-wrap' by ARG columns. +Offer to repeat command in a manner similar to +`text-scale-adjust'. + + \\`+', \\`=' Increase indentation by one column + \\`-' Decrease indentation by one column + \\`0' Reset indentation to the default + \\`C-+', \\`C-=' Shift right margin rightward (shrink it) + by one column + \\`C--' Shift right margin leftward (grow it) by one + column + \\`C-0' Reset the right margin to the default + +Note that misalignment may occur when messages contain +decorations applied by third-party modules. See +`erc-fill--wrap-fix' for a temporary workaround." + (interactive "p") + (unless erc-fill--wrap-value + (cl-assert (not erc-fill-wrap-mode)) + (user-error "Minor mode `erc-fill-wrap-mode' disabled")) + (unless (get-buffer-window) + (user-error "Command called in an undisplayed buffer")) + (let* ((total (erc-fill--wrap-nudge arg)) + (win-ratio (/ (float (- (window-point) (window-start))) + (- (window-end nil t) (window-start))))) + (when (zerop arg) + (setq arg 1)) + (erc-compat--set-transient-map + (let ((map (make-sparse-keymap))) + (dolist (key '(?+ ?= ?- ?0)) + (let ((a (pcase key + (?0 0) + (?- (- (abs arg))) + (_ (abs arg))))) + (define-key map (vector (list key)) + (lambda () + (interactive) + (cl-incf total (erc-fill--wrap-nudge a)) + (recenter (round (* win-ratio (window-height)))))) + (define-key map (vector (list 'control key)) + (lambda () + (interactive) + (erc-stamp--adjust-right-margin (- a)) + (recenter (round (* win-ratio (window-height)))))))) + map) + t + (lambda () + (message "Fill prefix: %d (%+d col%s)" + erc-fill--wrap-value total (if (> (abs total) 1) "s" ""))) + "Use %k for further adjustment" + 1) + (recenter (round (* win-ratio (window-height)))))) + (defun erc-fill-regarding-timestamp () "Fills a text such that messages start at column `erc-fill-static-center'." (fill-region (point-min) (point-max) t t) diff --git a/test/lisp/erc/erc-fill-tests.el b/test/lisp/erc/erc-fill-tests.el new file mode 100644 index 00000000000..04001ec6524 --- /dev/null +++ b/test/lisp/erc/erc-fill-tests.el @@ -0,0 +1,198 @@ +;;; erc-fill-tests.el --- Tests for erc-fill -*- 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 . + +;;; Commentary: + +;;; Code: +(require 'ert-x) +(require 'erc-fill) + +(defun erc-fill-tests--wrap-populate (test) + (let ((proc (start-process "sleep" (current-buffer) "sleep" "1")) + (id (erc-networks--id-create 'foonet)) + (erc-insert-modify-hook '(erc-fill erc-add-timestamp)) + (erc-server-users (make-hash-table :test 'equal)) + (erc-fill-function 'erc-fill-wrap) + (pre-command-hook pre-command-hook) + (erc-modules '(fill stamp)) + (msg "Hello World") + (inhibit-message noninteractive) + erc-insert-post-hook + extended-command-history + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) + (when (bound-and-true-p erc-button-mode) + (push 'erc-button-add-buttons erc-insert-modify-hook)) + (erc-mode) + (setq erc-server-process proc erc-networks--id id) + (set-process-query-on-exit-flag erc-server-process nil) + + (with-current-buffer (get-buffer-create "#chan") + (erc-mode) + (erc-munge-invisibility-spec) + (setq erc-server-process proc + erc-networks--id id + erc-channel-users (make-hash-table :test 'equal) + erc--target (erc--target-from-string "#chan") + erc-default-recipients (list "#chan")) + (erc--initialize-markers (point) nil) + + (erc-update-channel-member + "#chan" "alice" "alice" t nil nil nil nil nil "fake" "~u" nil nil t) + + (erc-update-channel-member + "#chan" "bob" "bob" t nil nil nil nil nil "fake" "~u" nil nil t) + + (setq msg "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.") + (erc-display-message nil 'notice (current-buffer) msg) + + (setq msg "bob: come, you are a tedious fool: to the purpose.\ + What was done to Elbow's wife, that he hath cause to complain of?\ + Come me to what was done to her.") + (erc-display-message nil nil (current-buffer) + (erc-format-privmessage "alice" msg nil t)) + + ;; Introduce an artificial gap in properties `line-prefix' and + ;; `wrap-prefix' and later ensure they're not incremented twice. + (save-excursion + (forward-line -1) + (search-forward "? ") + (remove-text-properties (1- (point)) (point) + '(line-prefix t wrap-prefix t))) + + (setq msg "alice: Either your unparagoned mistress is dead,\ + or she's outprized by a trifle.") + (erc-display-message nil nil (current-buffer) + (erc-format-privmessage "bob" msg nil t)) + + (let ((original-window-buffer (window-buffer (selected-window)))) + (set-window-buffer (selected-window) (current-buffer)) + ;; Defend against non-local exits from `ert-skip' + (unwind-protect + (funcall test) + (set-window-buffer (selected-window) original-window-buffer) + (when noninteractive + (kill-buffer))))))) + +(defun erc-fill-tests--wrap-check-nudge (expected-width) + (save-excursion + (goto-char (point-min)) + (should (search-forward "*** This server" nil t)) + (should (get-text-property (pos-bol) 'line-prefix)) + (should (get-text-property (pos-eol) 'line-prefix)) + (should (equal (get-text-property (pos-bol) 'wrap-prefix) + `(space :width ,expected-width))) + (should (equal (get-text-property (pos-eol) 'wrap-prefix) + `(space :width ,expected-width))) + + ;; Prefix props are applied properly and faces are accounted + ;; for when determining widths. + (should (search-forward " ")))) + (`(space :width (- ,n ,w)) + (and (= n expected-width) + (= w (length " ")))))) + + ;; Ensure the loop is not visited twice due to the gap. + (should (search-forward " ")))) + (`(space :width (- ,n ,w)) + (and (= n expected-width) + (= w (length " ")))))))) + +(ert-deftest erc-fill-wrap--monospace () + :tags '(:unstable) + + (erc-fill-tests--wrap-populate + + (lambda () + (set-window-buffer (selected-window) (current-buffer)) + (erc-fill-tests--wrap-check-nudge 27) + + (ert-info ("Shift right by one") + (ert-with-message-capture messages + (execute-kbd-macro (kbd "M-x erc-fill-wrap-nudge RET +")) + (should (string-match (rx "for further adjustment") messages))) + (erc-fill-tests--wrap-check-nudge 29)) + + (ert-info ("Shift left by five") + (execute-kbd-macro (kbd "M-x erc-fill-wrap-nudge RET -----")) + (erc-fill-tests--wrap-check-nudge 25)) + + (ert-info ("Reset") + (execute-kbd-macro (kbd "M-x erc-fill-wrap-nudge RET 0")) + (erc-fill-tests--wrap-check-nudge 27))))) + +(ert-deftest erc-fill-wrap--variable-pitch () + :tags '(:unstable) + (unless (and (fboundp 'string-pixel-width) + (not noninteractive) + (display-graphic-p)) + (ert-skip "Test needs interactive graphical Emacs")) + + (with-selected-frame (make-frame '((name . "other"))) + (set-face-attribute 'default (selected-frame) + :family "Sans Serif" + :foundry 'unspecified + :font 'unspecified) + + (erc-fill-tests--wrap-populate + (lambda () + (erc-fill-tests--wrap-check-nudge 27) + (erc-fill--wrap-nudge 2) + (erc-fill-tests--wrap-check-nudge 29) + (erc-fill--wrap-nudge -6) + (erc-fill-tests--wrap-check-nudge 25) + (erc-fill--wrap-nudge 0) + (erc-fill-tests--wrap-check-nudge 27) + + ;; FIXME get rid of this "void variable `erc--results-ewoc'" + ;; error, which seems related to operating in a non-default + ;; frame. + ;; + ;; As a kludge, checking if point made it to the prompt can + ;; serve as visual confirmation that the test passed. + (goto-char (point-max)))))) + +;;; erc-fill-tests.el ends here -- 2.39.1 --=-=-=--