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#64301: 30.0.50; ERC 5.6: Make speaker labels easier to work with Date: Thu, 20 Jul 2023 06:29:13 -0700 Message-ID: <871qh2iudy.fsf__32737.8316478402$1689859820$gmane$org@neverwas.me> References: <87bkh21gfa.fsf@neverwas.me> <87sf9y32q9.fsf@neverwas.me> <87zg3zqlnr.fsf@neverwas.me> <87cz0tnubk.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="20194"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: emacs-erc@gnu.org To: 64301-done@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Jul 20 15:30:12 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 1qMTjP-00051K-7k for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 20 Jul 2023 15:30:11 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qMTjI-0002lK-Mc; Thu, 20 Jul 2023 09:30:04 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qMTjH-0002kr-8J for bug-gnu-emacs@gnu.org; Thu, 20 Jul 2023 09:30:03 -0400 Original-Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qMTjG-0004e8-VU for bug-gnu-emacs@gnu.org; Thu, 20 Jul 2023 09:30:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1qMTjG-0001lr-NK for bug-gnu-emacs@gnu.org; Thu, 20 Jul 2023 09:30:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: "J.P." Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 20 Jul 2023 13:30:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 64301 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 64301-done@debbugs.gnu.org id=D64301.16898597666741 (code D ref 64301); Thu, 20 Jul 2023 13:30:02 +0000 Original-Received: (at 64301-done) by debbugs.gnu.org; 20 Jul 2023 13:29:26 +0000 Original-Received: from localhost ([127.0.0.1]:58032 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qMTid-0001kd-Tg for submit@debbugs.gnu.org; Thu, 20 Jul 2023 09:29:26 -0400 Original-Received: from mail-108-mta22.mxroute.com ([136.175.108.22]:44201) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qMTiZ-0001kS-Ul for 64301-done@debbugs.gnu.org; Thu, 20 Jul 2023 09:29:21 -0400 Original-Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta22.mxroute.com (ZoneMTA) with ESMTPSA id 189737d8ff50004cef.001 for <64301-done@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Thu, 20 Jul 2023 13:29:16 +0000 X-Zone-Loop: 05bb7bad9e52ddb82c55ee6b62d7f5c34775bc010092 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=0HPlW1TEbECReBqV2hjLGIJcdFtNye1A9rSZcDJR9GU=; b=TGihEHeIffCfJwlCfS8/UQXRtf 2IiHSIyeYIQWVPEEYTZNEz/gRXjPSXtQutab+tCpD9Uu+4WMAxGU/Lv46OEIWCAC4wzhJ/X76mcwt Gg1p9QWHlCCHroIrh6/9FMSUKpu9aYIo2MHjGGG8FJUxlGwRmNrLKULNxbKYrshM3n8u8lDAgKBhD ifkWtR/i6factgD0ZuNXiEGzPnCBSeWYebVrW8JCRhhFfmtADdnN2MR/D9s2EZQCZzTpjOheP4Cos gn8kTCNXiMITagtzrreMwsE/kkx/0Bps8xiOI86vWGnzGJ3uhimSSKg42V8hkajcusFk3CSL3jJUm 2BXgPmcA==; In-Reply-To: <87cz0tnubk.fsf@neverwas.me> (J. P.'s message of "Sat, 15 Jul 2023 07:05:51 -0700") X-Authenticated-Id: masked@neverwas.me X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:265597 Archived-At: --=-=-= Content-Type: text/plain v2 (invisibility API). Move primary message-hiding function to erc.el. Invert meaning of invisible-bounds switch and make old non-nil behavior new default. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0000-v1-v2.diff >From 8f0f4c1d424e0a70c6e46b97ca5b75699f80c892 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 20 Jul 2023 05:38:35 -0700 Subject: [PATCH 0/1] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (1): [5.6] Improve ERC's internal invisibility API etc/ERC-NEWS | 9 +++ lisp/erc/erc-fill.el | 16 +++-- lisp/erc/erc-match.el | 46 +++++++------- lisp/erc/erc.el | 32 +++++++++- test/lisp/erc/erc-scenarios-match.el | 95 ++++++++++++++++++++-------- test/lisp/erc/erc-tests.el | 50 +++++++++++++-- 6 files changed, 183 insertions(+), 65 deletions(-) Interdiff: diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 4c881e32ab4..16577c73b2e 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -287,6 +287,15 @@ The 'fill' module is now defined by 'define-erc-module'. The same goes for ERC's imenu integration, which has 'imenu' now appearing in the default value of 'erc-modules'. +*** Hidden messages contain a preceding rather than trailing newline. +ERC has traditionally only offered to hide messages involving fools, +but plans are to make hiding more powerful. Anyone depending on the +existing behavior should be aware that hidden messages now start and +end one character earlier, so that hidden line endings precede rather +than follow accompanying text. However, an escape hatch is available +in the variable 'erc-legacy-invisible-bounds-p'. It reinstates the +old behavior, which is unsupported by newer modules and features. + *** 'erc-display-message' optionally combines faces. Users may notice that ERC now inserts some important error messages in a combination of 'erc-error-face' and 'erc-notice-face'. This is diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el index 2dfae35b871..bb11b03e791 100644 --- a/lisp/erc/erc-fill.el +++ b/lisp/erc/erc-fill.el @@ -288,11 +288,17 @@ erc-fill-wrap-mode-map ;; Not sure if this is problematic because `erc-bol' takes no args. " " #'erc-fill--wrap-beginning-of-line) -(defvar erc-match-mode) (defvar erc-button-mode) -(defvar erc-match--offset-invisible-bounds-p) +(defvar erc-legacy-invisible-bounds-p) (defun erc-fill--wrap-ensure-dependencies () + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (when erc-legacy-invisible-bounds-p + (erc--warn-once-before-connect 'erc-fill-wrap-mode + "Module `fill-wrap' is incompatible with the obsolete compatibility" + " flag `erc-legacy-invisible-bounds-p'. Disabling locally in %s." + (current-buffer)) + (setq-local erc-legacy-invisible-bounds-p nil))) (let (missing-deps) (unless erc-fill-mode (push 'fill missing-deps) @@ -336,11 +342,7 @@ fill-wrap ;; Internal integrations. (add-function :after (local 'erc-stamp--insert-date-function) #'erc-fill--wrap-stamp-insert-prefixed-date) - (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--offset-invisible-bounds-p t)) + (erc-stamp--display-margin-mode +1) (when erc-fill-wrap-merge (add-hook 'erc-button--prev-next-predicate-functions #'erc-fill--wrap-merged-button-p nil t)) diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el index 3fba35536b3..50db8a132ec 100644 --- a/lisp/erc/erc-match.el +++ b/lisp/erc/erc-match.el @@ -655,25 +655,10 @@ erc-go-to-log-matches-buffer (get-buffer (car buffer-cons)))))) (switch-to-buffer buffer-name))) -(defvar-local erc-match--offset-invisible-bounds-p nil - "Whether to hide a message from its preceding newline.") - (defun erc-hide-fools (match-type _nickuserhost _message) "Hide comments from designated fools." (when (eq match-type 'fool) - (erc-match--hide-message 'match-fools))) - -(defun erc-match--hide-message (prop) - (progn ; FIXME raise sexp - (if erc-match--offset-invisible-bounds-p - (let ((beg (point-min)) - (end (point-max))) - (save-restriction - (widen) - (erc--merge-prop (1- beg) (1- end) 'invisible prop))) - ;; Before ERC 5.6, this also used to add an `intangible' - ;; property, but the docs say it's now obsolete. - (erc--merge-prop (point-min) (point-max) 'invisible prop)))) + (erc--hide-message 'match-fools))) (defun erc-beep-on-match (match-type _nickuserhost _message) "Beep when text matches. diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 4192ea9a9e2..972cf8d63f3 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -3007,6 +3007,9 @@ erc-display-line (defvar erc--compose-text-properties nil "Non-nil when `erc-put-text-property' defers to `erc--merge-prop'.") +;; We could maintain a cache of all combined values and dispense +;; archetype singleton objects so all occurrences of some list (a b) +;; across all ERC buffers are actually the same object. (defun erc--merge-prop (from to prop val &optional object) "Compose existing PROP values with VAL between FROM and TO in OBJECT. For spans where PROP is non-nil, cons VAL onto the existing @@ -3028,6 +3031,26 @@ erc--merge-prop old (get-text-property pos prop object) end (next-single-property-change pos prop object to))))) +(defvar erc-legacy-invisible-bounds-p nil + "Whether to hide trailing rather than preceding newlines. +Beginning in ERC 5.6, invisibility extends from a message's +preceding newline to its last non-newline character.") +(make-obsolete-variable 'erc-legacy-invisible-bounds-p + "decremented interval now permanent" "30.1") + +(defun erc--hide-message (value) + "Apply `invisible' text-property with VALUE to current message. +Expect to run in a narrowed buffer during message insertion." + (if erc-legacy-invisible-bounds-p + ;; Before ERC 5.6, this also used to add an `intangible' + ;; property, but the docs say it's now obsolete. + (erc--merge-prop (point-min) (point-max) 'invisible value) + (let ((beg (point-min)) + (end (point-max))) + (save-restriction + (widen) + (erc--merge-prop (1- beg) (1- end) 'invisible value))))) + (defun erc-display-message-highlight (type string) "Highlight STRING according to TYPE, where erc-TYPE-face is an ERC face. diff --git a/test/lisp/erc/erc-scenarios-match.el b/test/lisp/erc/erc-scenarios-match.el index a438635960e..cd899fddb98 100644 --- a/test/lisp/erc/erc-scenarios-match.el +++ b/test/lisp/erc/erc-scenarios-match.el @@ -151,6 +151,12 @@ erc-scenarios-match--stamp-left-fools-invisible (= (next-single-property-change msg-beg 'invisible nil (pos-eol)) (pos-eol)))))))) +(defun erc-scenarios-match--find-bol () + (save-excursion + (should (get-text-property (1- (point)) 'erc-command)) + (goto-char (should (previous-single-property-change (point) 'erc-command))) + (pos-bol))) + (defun erc-scenarios-match--find-eol () (save-excursion (if-let ((next (next-single-property-change (point) 'erc-command))) @@ -160,13 +166,14 @@ erc-scenarios-match--find-eol (pos-eol))) ;; In most cases, `erc-hide-fools' makes line endings invisible. -(ert-deftest erc-scenarios-match--stamp-right-fools-invisible () +(defun erc-scenarios-match--stamp-right-fools-invisible () :tags '(:expensive-test) (let ((erc-insert-timestamp-function #'erc-insert-timestamp-right)) (erc-scenarios-match--invisible-stamp (lambda () - (let ((end (erc-scenarios-match--find-eol))) + (let ((beg (erc-scenarios-match--find-bol)) + (end (erc-scenarios-match--find-eol))) ;; The end of the message is a newline. (should (= ?\n (char-after end))) @@ -178,7 +185,11 @@ erc-scenarios-match--stamp-right-fools-invisible '(timestamp match-fools))) ;; The final newline is hidden by `match', not `stamps' - (should (equal (get-text-property end 'invisible) 'match-fools)) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (if erc-legacy-invisible-bounds-p + (should (eq (get-text-property end 'invisible) 'match-fools)) + (should (eq (get-text-property beg 'invisible) 'match-fools)) + (should-not (get-text-property end 'invisible)))) ;; The message proper has the `invisible' property `match-fools', ;; and it starts after the preceding newline. @@ -204,6 +215,17 @@ erc-scenarios-match--stamp-right-fools-invisible (should (eq (get-text-property inv-beg 'invisible) 'timestamp)))))))) +(ert-deftest erc-scenarios-match--stamp-right-fools-invisible () + :tags '(:expensive-test) + (erc-scenarios-match--stamp-right-fools-invisible)) + +(ert-deftest erc-scenarios-match--stamp-right-fools-invisible--nooffset () + :tags '(:expensive-test) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (should-not erc-legacy-invisible-bounds-p) + (let ((erc-legacy-invisible-bounds-p t)) + (erc-scenarios-match--stamp-right-fools-invisible)))) + ;; This asserts that when `erc-fill-wrap-mode' is enabled, ERC hides ;; the preceding message's line ending. (ert-deftest erc-scenarios-match--stamp-right-invisible-fill-wrap () @@ -249,8 +271,7 @@ erc-scenarios-match--stamp-right-invisible-fill-wrap (let ((inv-beg (next-single-property-change (1- (pos-bol)) 'invisible))) (should (eq (get-text-property inv-beg 'invisible) 'timestamp))))))) -(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static () - :tags '(:expensive-test) +(defun erc-scenarios-match--stamp-both-invisible-fill-static () (should (eq erc-insert-timestamp-function #'erc-insert-timestamp-left-and-right)) @@ -292,7 +313,11 @@ erc-scenarios-match--stamp-both-invisible-fill-static ;; Line ending has the `invisible' property `match-fools'. (should (= (char-after mend) ?\n)) - (should (eq (get-text-property mend'invisible) 'match-fools)))) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (if erc-legacy-invisible-bounds-p + (should (eq (get-text-property mend 'invisible) 'match-fools)) + (should (eq (get-text-property mbeg 'invisible) 'match-fools)) + (should-not (get-text-property mend 'invisible)))))) ;; Only the message right after Alice speaks contains stamps. (when (= 1 bob-utterance-counter) @@ -338,4 +363,15 @@ erc-scenarios-match--stamp-both-invisible-fill-static (should-not (eq (field-at-pos (1- (pos-eol))) 'erc-timestamp)) (should-not (next-single-property-change (pos-bol) 'invisible)))))) +(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static () + :tags '(:expensive-test) + (erc-scenarios-match--stamp-both-invisible-fill-static)) + +(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static--nooffset () + :tags '(:expensive-test) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (should-not erc-legacy-invisible-bounds-p) + (let ((erc-legacy-invisible-bounds-p t)) + (erc-scenarios-match--stamp-both-invisible-fill-static)))) + ;;; erc-scenarios-match.el ends here -- 2.41.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Improve-ERC-s-internal-invisibility-API.patch >From 8f0f4c1d424e0a70c6e46b97ca5b75699f80c892 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 14 Jul 2023 21:08:31 -0700 Subject: [PATCH 1/1] [5.6] Improve ERC's internal invisibility API * etc/ERC-NEWS: Mention that line endings have moved from the end to the beginning of hidden messages. * lisp/erc/erc-fill.el (erc-fill--wrap-ensure-dependencies): Warn when users have `erc-legacy-invisible-bounds-p' enabled, and force it to its default value of nil in the current buffer. (erc-fill-wrap-mode, erc-fill-wrap-enable): Move business involving compat variable for enabling legacy hidden-message behavior to helper. * lisp/erc/erc-match.el (erc-match--hide-fools-offset-bounds): Move internal variable from to main library file and rename to `erc-legacy-invisible-bounds-p'. Also make obsolete and flip semantics so non-nil enables the traditional behavior. (erc-match--hide-message): Move to main library file and rename to `erc--hide-message'. Add a property-value parameter instead of hard-coding to `erc-match'. Also, condition behavior on inverted and renamed compatibility flag `erc-legacy-invisible-bounds-p'. (erc-hide-fools): Call `erc--hide-message' with own value for `invisible' property specifically for fools. That is, use `match-fools' rather than `erc-match' or `erc-match-fools' to save room when visually inspecting. This retains the module name as a prefix to hopefully minimize collisions with invisibility spec members owned by non-ERC minor modes. The `timestamp' spec member owned by erc-stamp likewise lacks a namespace prefix, but its feature/group is already inherent. (erc-match--modify-invisibility-spec): Use toggle command non-interactively for adding and removing invisibility spec member. (erc-match-toggle-hidden-fools): Add explicit override argument and defer to general helper for actually modifying spec. (erc-match--toggle-hidden): New helper for toggling invisibility spec. * lisp/erc/erc.el (erc--merge-prop): If new value is a list, prepend onto existing. Add note about possible space optimization. (erc-legacy-invisible-bounds-p): New obsolete compat variable to enable traditional pre-5.6 invisibility interval on hidden messages. Replaces `erc-match--hide-fools-offset-bounds-p' but has an inverted meaning. The new default value of nil means invisibility covers a shifted interval consisting of the message body plus the line ending immediately preceding it. (erc--hide-message): New function, formerly `erc-match--hide-message' from erc-match.el introduced in ERC 5.6. * test/lisp/erc/erc-scenarios-match.el: (erc-scenarios-match--invisible-stamp): Fix comment and use API function in interactive convenience setup. (erc-scenarios-match--find-bol): New test helper. (erc-scenarios-match--find-eol): Fix bug affecting interactive use. (erc-scenarios-match--stamp-left-fools-invisible, erc-scenarios-match--stamp-right-fools-invisible, erc-scenarios-match--stamp-right-invisible-fill-wrap, erc-scenarios-match--stamp-both-invisible-fill-static): Update `invisible' property from `erc-match' to `match-fools'. (erc-scenarios-match--stamp-right-fools-invisible, erc-scenarios-match--stamp-both-invisible-fill-static): Move test body to function of same name for use in multiple cases. (erc-scenarios-match--stamp-right-fools-invisible--nooffset, erc-scenarios-match--stamp-both-invisible-fill-static--nooffset): New test variants asserting proper hiding with old pre-5.6 invisibility interval. * test/lisp/erc/erc-tests.el (erc-tests--equal-including-properties): Relocate macro in same file. (erc--merge-prop): New test. (Bug#64301) --- etc/ERC-NEWS | 9 +++ lisp/erc/erc-fill.el | 16 +++-- lisp/erc/erc-match.el | 46 +++++++------- lisp/erc/erc.el | 32 +++++++++- test/lisp/erc/erc-scenarios-match.el | 95 ++++++++++++++++++++-------- test/lisp/erc/erc-tests.el | 50 +++++++++++++-- 6 files changed, 183 insertions(+), 65 deletions(-) diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 4c881e32ab4..16577c73b2e 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -287,6 +287,15 @@ The 'fill' module is now defined by 'define-erc-module'. The same goes for ERC's imenu integration, which has 'imenu' now appearing in the default value of 'erc-modules'. +*** Hidden messages contain a preceding rather than trailing newline. +ERC has traditionally only offered to hide messages involving fools, +but plans are to make hiding more powerful. Anyone depending on the +existing behavior should be aware that hidden messages now start and +end one character earlier, so that hidden line endings precede rather +than follow accompanying text. However, an escape hatch is available +in the variable 'erc-legacy-invisible-bounds-p'. It reinstates the +old behavior, which is unsupported by newer modules and features. + *** 'erc-display-message' optionally combines faces. Users may notice that ERC now inserts some important error messages in a combination of 'erc-error-face' and 'erc-notice-face'. This is diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el index a65c95f1d85..bb11b03e791 100644 --- a/lisp/erc/erc-fill.el +++ b/lisp/erc/erc-fill.el @@ -288,11 +288,17 @@ erc-fill-wrap-mode-map ;; Not sure if this is problematic because `erc-bol' takes no args. " " #'erc-fill--wrap-beginning-of-line) -(defvar erc-match-mode) (defvar erc-button-mode) -(defvar erc-match--hide-fools-offset-bounds) +(defvar erc-legacy-invisible-bounds-p) (defun erc-fill--wrap-ensure-dependencies () + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (when erc-legacy-invisible-bounds-p + (erc--warn-once-before-connect 'erc-fill-wrap-mode + "Module `fill-wrap' is incompatible with the obsolete compatibility" + " flag `erc-legacy-invisible-bounds-p'. Disabling locally in %s." + (current-buffer)) + (setq-local erc-legacy-invisible-bounds-p nil))) (let (missing-deps) (unless erc-fill-mode (push 'fill missing-deps) @@ -336,11 +342,7 @@ fill-wrap ;; Internal integrations. (add-function :after (local 'erc-stamp--insert-date-function) #'erc-fill--wrap-stamp-insert-prefixed-date) - (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)) + (erc-stamp--display-margin-mode +1) (when erc-fill-wrap-merge (add-hook 'erc-button--prev-next-predicate-functions #'erc-fill--wrap-merged-button-p nil t)) diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el index a5b0af41b2a..50db8a132ec 100644 --- a/lisp/erc/erc-match.el +++ b/lisp/erc/erc-match.el @@ -655,24 +655,10 @@ erc-go-to-log-matches-buffer (get-buffer (car buffer-cons)))))) (switch-to-buffer buffer-name))) -(defvar-local erc-match--hide-fools-offset-bounds nil) - (defun erc-hide-fools (match-type _nickuserhost _message) "Hide comments from designated fools." (when (eq match-type 'fool) - (erc-match--hide-message))) - -(defun erc-match--hide-message () - (progn ; FIXME raise sexp - (if erc-match--hide-fools-offset-bounds - (let ((beg (point-min)) - (end (point-max))) - (save-restriction - (widen) - (erc--merge-prop (1- beg) (1- end) 'invisible 'erc-match))) - ;; Before ERC 5.6, this also used to add an `intangible' - ;; property, but the docs say it's now obsolete. - (erc--merge-prop (point-min) (point-max) 'invisible 'erc-match)))) + (erc--hide-message 'match-fools))) (defun erc-beep-on-match (match-type _nickuserhost _message) "Beep when text matches. @@ -682,19 +668,31 @@ erc-beep-on-match (defun erc-match--modify-invisibility-spec () "Add an `erc-match' property to the local spec." + ;; Hopefully, this will be extended to do the same for other + ;; invisible properties managed by this module. (if erc-match-mode - (add-to-invisibility-spec 'erc-match) + (erc-match-toggle-hidden-fools +1) (erc-with-all-buffers-of-server nil nil - (remove-from-invisibility-spec 'erc-match)))) + (erc-match-toggle-hidden-fools -1)))) -(defun erc-match-toggle-hidden-fools () +(defun erc-match-toggle-hidden-fools (arg) "Toggle fool visibility. -Expect `erc-hide-fools' or a function that does something similar -to be in `erc-text-matched-hook'." - (interactive) - (if (memq 'erc-match (ensure-list buffer-invisibility-spec)) - (remove-from-invisibility-spec 'erc-match) - (add-to-invisibility-spec 'erc-match))) +Expect the function `erc-hide-fools' or similar to be present in +`erc-text-matched-hook'." + (interactive "P") + (erc-match--toggle-hidden 'match-fools arg)) + +(defun erc-match--toggle-hidden (prop arg) + "Toggle invisibility for spec member PROP. +Treat ARG in a manner similar to mode toggles defined by +`define-minor-mode'." + (when arg + (setq arg (prefix-numeric-value arg))) + (if (memq prop (ensure-list buffer-invisibility-spec)) + (unless (natnump arg) + (remove-from-invisibility-spec prop)) + (when (or (not arg) (natnump arg)) + (add-to-invisibility-spec prop)))) (provide 'erc-match) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index eca6a90d706..972cf8d63f3 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -3007,22 +3007,50 @@ erc-display-line (defvar erc--compose-text-properties nil "Non-nil when `erc-put-text-property' defers to `erc--merge-prop'.") +;; We could maintain a cache of all combined values and dispense +;; archetype singleton objects so all occurrences of some list (a b) +;; across all ERC buffers are actually the same object. (defun erc--merge-prop (from to prop val &optional object) "Compose existing PROP values with VAL between FROM and TO in OBJECT. For spans where PROP is non-nil, cons VAL onto the existing value, ensuring a proper list. Otherwise, just set PROP to VAL. -See also `erc-button-add-face'." +When VAL is itself a list, prepend its members onto an existing +value. See also `erc-button-add-face'." (let ((old (get-text-property from prop object)) (pos from) (end (next-single-property-change from prop object to)) new) (while (< pos to) - (setq new (if old (cons val (ensure-list old)) val)) + (setq new (if old + (if (listp val) + (append val (ensure-list old)) + (cons val (ensure-list old))) + val)) (put-text-property pos end prop new object) (setq pos end old (get-text-property pos prop object) end (next-single-property-change pos prop object to))))) +(defvar erc-legacy-invisible-bounds-p nil + "Whether to hide trailing rather than preceding newlines. +Beginning in ERC 5.6, invisibility extends from a message's +preceding newline to its last non-newline character.") +(make-obsolete-variable 'erc-legacy-invisible-bounds-p + "decremented interval now permanent" "30.1") + +(defun erc--hide-message (value) + "Apply `invisible' text-property with VALUE to current message. +Expect to run in a narrowed buffer during message insertion." + (if erc-legacy-invisible-bounds-p + ;; Before ERC 5.6, this also used to add an `intangible' + ;; property, but the docs say it's now obsolete. + (erc--merge-prop (point-min) (point-max) 'invisible value) + (let ((beg (point-min)) + (end (point-max))) + (save-restriction + (widen) + (erc--merge-prop (1- beg) (1- end) 'invisible value))))) + (defun erc-display-message-highlight (type string) "Highlight STRING according to TYPE, where erc-TYPE-face is an ERC face. diff --git a/test/lisp/erc/erc-scenarios-match.el b/test/lisp/erc/erc-scenarios-match.el index 8a718962c55..cd899fddb98 100644 --- a/test/lisp/erc/erc-scenarios-match.el +++ b/test/lisp/erc/erc-scenarios-match.el @@ -62,11 +62,15 @@ erc-scenarios-match--stamp-left-current-nick 'erc-current-nick-face)))))) ;; When hacking on tests that use this fixture, it's best to run it -;; interactively, and check for wierdness before and after doing -;; M-: (remove-from-invisibility-spec 'erc-match) RET. +;; interactively, and visually inspect the output with various +;; combinations of: +;; +;; M-x erc-match-toggle-hidden-fools RET +;; M-x erc-toggle-timestamps RET +;; (defun erc-scenarios-match--invisible-stamp (hiddenp visiblep) (unless noninteractive - (kill-new "(remove-from-invisibility-spec 'erc-match)")) + (kill-new "erc-match-toggle-hidden-fools")) (erc-scenarios-common-with-cleanup ((erc-scenarios-common-dialog "join/legacy") @@ -128,11 +132,11 @@ erc-scenarios-match--stamp-left-fools-invisible ;; Leading stamp has combined `invisible' property value. (should (equal (get-text-property (pos-bol) 'invisible) - '(timestamp erc-match))) + '(timestamp match-fools))) - ;; Message proper has the `invisible' property `erc-match'. + ;; Message proper has the `invisible' property `match-fools'. (let ((msg-beg (next-single-property-change (pos-bol) 'invisible))) - (should (eq (get-text-property msg-beg 'invisible) 'erc-match)) + (should (eq (get-text-property msg-beg 'invisible) 'match-fools)) (should (>= (next-single-property-change msg-beg 'invisible nil) (pos-eol))))) @@ -147,19 +151,29 @@ erc-scenarios-match--stamp-left-fools-invisible (= (next-single-property-change msg-beg 'invisible nil (pos-eol)) (pos-eol)))))))) +(defun erc-scenarios-match--find-bol () + (save-excursion + (should (get-text-property (1- (point)) 'erc-command)) + (goto-char (should (previous-single-property-change (point) 'erc-command))) + (pos-bol))) + (defun erc-scenarios-match--find-eol () (save-excursion - (goto-char (next-single-property-change (point) 'erc-command)) + (if-let ((next (next-single-property-change (point) 'erc-command))) + (goto-char next) + ;; We're already at the end of the message. + (should (get-text-property (1- (point)) 'erc-command))) (pos-eol))) ;; In most cases, `erc-hide-fools' makes line endings invisible. -(ert-deftest erc-scenarios-match--stamp-right-fools-invisible () +(defun erc-scenarios-match--stamp-right-fools-invisible () :tags '(:expensive-test) (let ((erc-insert-timestamp-function #'erc-insert-timestamp-right)) (erc-scenarios-match--invisible-stamp (lambda () - (let ((end (erc-scenarios-match--find-eol))) + (let ((beg (erc-scenarios-match--find-bol)) + (end (erc-scenarios-match--find-eol))) ;; The end of the message is a newline. (should (= ?\n (char-after end))) @@ -168,19 +182,23 @@ erc-scenarios-match--stamp-right-fools-invisible ;; Stamps have a combined `invisible' property value. (should (equal (get-text-property (1- end) 'invisible) - '(timestamp erc-match))) + '(timestamp match-fools))) ;; The final newline is hidden by `match', not `stamps' - (should (equal (get-text-property end 'invisible) 'erc-match)) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (if erc-legacy-invisible-bounds-p + (should (eq (get-text-property end 'invisible) 'match-fools)) + (should (eq (get-text-property beg 'invisible) 'match-fools)) + (should-not (get-text-property end 'invisible)))) - ;; The message proper has the `invisible' property `erc-match', + ;; The message proper has the `invisible' property `match-fools', ;; and it starts after the preceding newline. - (should (eq (get-text-property (pos-bol) 'invisible) 'erc-match)) + (should (eq (get-text-property (pos-bol) 'invisible) 'match-fools)) ;; It ends just before the timestamp. (let ((msg-end (next-single-property-change (pos-bol) 'invisible))) (should (equal (get-text-property msg-end 'invisible) - '(timestamp erc-match))) + '(timestamp match-fools))) ;; Stamp's `invisible' property extends throughout the stamp ;; and ends before the trailing newline. @@ -197,6 +215,17 @@ erc-scenarios-match--stamp-right-fools-invisible (should (eq (get-text-property inv-beg 'invisible) 'timestamp)))))))) +(ert-deftest erc-scenarios-match--stamp-right-fools-invisible () + :tags '(:expensive-test) + (erc-scenarios-match--stamp-right-fools-invisible)) + +(ert-deftest erc-scenarios-match--stamp-right-fools-invisible--nooffset () + :tags '(:expensive-test) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (should-not erc-legacy-invisible-bounds-p) + (let ((erc-legacy-invisible-bounds-p t)) + (erc-scenarios-match--stamp-right-fools-invisible)))) + ;; This asserts that when `erc-fill-wrap-mode' is enabled, ERC hides ;; the preceding message's line ending. (ert-deftest erc-scenarios-match--stamp-right-invisible-fill-wrap () @@ -215,16 +244,16 @@ erc-scenarios-match--stamp-right-invisible-fill-wrap ;; Stamps have a combined `invisible' property value. (should (equal (get-text-property (1- (pos-eol)) 'invisible) - '(timestamp erc-match))) + '(timestamp match-fools))) - ;; The message proper has the `invisible' property `erc-match', + ;; The message proper has the `invisible' property `match-fools', ;; which starts at the preceding newline... - (should (eq (get-text-property (1- (pos-bol)) 'invisible) 'erc-match)) + (should (eq (get-text-property (1- (pos-bol)) 'invisible) 'match-fools)) ;; ... and ends just before the timestamp. (let ((msgend (next-single-property-change (1- (pos-bol)) 'invisible))) (should (equal (get-text-property msgend 'invisible) - '(timestamp erc-match))) + '(timestamp match-fools))) ;; The newline before `erc-insert-marker' is still visible. (should-not (get-text-property (pos-eol) 'invisible)) @@ -242,8 +271,7 @@ erc-scenarios-match--stamp-right-invisible-fill-wrap (let ((inv-beg (next-single-property-change (1- (pos-bol)) 'invisible))) (should (eq (get-text-property inv-beg 'invisible) 'timestamp))))))) -(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static () - :tags '(:expensive-test) +(defun erc-scenarios-match--stamp-both-invisible-fill-static () (should (eq erc-insert-timestamp-function #'erc-insert-timestamp-left-and-right)) @@ -265,8 +293,8 @@ erc-scenarios-match--stamp-both-invisible-fill-static (search-forward "[23:59]")))) (ert-info ("Line endings in Bob's messages are invisible") - ;; The message proper has the `invisible' property `erc-match'. - (should (eq (get-text-property (pos-bol) 'invisible) 'erc-match)) + ;; The message proper has the `invisible' property `match-fools'. + (should (eq (get-text-property (pos-bol) 'invisible) 'match-fools)) (let* ((mbeg (next-single-property-change (pos-bol) 'erc-command)) (mend (next-single-property-change mbeg 'erc-command))) @@ -283,9 +311,13 @@ erc-scenarios-match--stamp-both-invisible-fill-static (should (= (next-single-property-change (pos-bol) 'erc-timestamp) mend)) - ;; Line ending has the `invisible' property `erc-match'. + ;; Line ending has the `invisible' property `match-fools'. (should (= (char-after mend) ?\n)) - (should (eq (get-text-property mend'invisible) 'erc-match)))) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (if erc-legacy-invisible-bounds-p + (should (eq (get-text-property mend 'invisible) 'match-fools)) + (should (eq (get-text-property mbeg 'invisible) 'match-fools)) + (should-not (get-text-property mend 'invisible)))))) ;; Only the message right after Alice speaks contains stamps. (when (= 1 bob-utterance-counter) @@ -298,7 +330,7 @@ erc-scenarios-match--stamp-both-invisible-fill-static ;; Date stamp has a combined `invisible' property value ;; that extends until the start of the message proper. (should (equal (get-text-property (point) 'invisible) - '(timestamp erc-match))) + '(timestamp match-fools))) (should (= (next-single-property-change (point) 'invisible) (1+ (pos-eol)))))) @@ -314,7 +346,7 @@ erc-scenarios-match--stamp-both-invisible-fill-static (let ((msgend (next-single-property-change (pos-bol) 'invisible))) ;; Stamp has a combined `invisible' property value. (should (equal (get-text-property msgend 'invisible) - '(timestamp erc-match))) + '(timestamp match-fools))) ;; Combined `invisible' property spans entire timestamp. (should (= (next-single-property-change msgend 'invisible) @@ -331,4 +363,15 @@ erc-scenarios-match--stamp-both-invisible-fill-static (should-not (eq (field-at-pos (1- (pos-eol))) 'erc-timestamp)) (should-not (next-single-property-change (pos-bol) 'invisible)))))) +(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static () + :tags '(:expensive-test) + (erc-scenarios-match--stamp-both-invisible-fill-static)) + +(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static--nooffset () + :tags '(:expensive-test) + (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p)) + (should-not erc-legacy-invisible-bounds-p) + (let ((erc-legacy-invisible-bounds-p t)) + (erc-scenarios-match--stamp-both-invisible-fill-static)))) + ;;; erc-scenarios-match.el ends here diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index b5db5fe8764..610fb149a94 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -1272,6 +1272,50 @@ erc-process-input-line (should-not calls)))))) +(defmacro erc-tests--equal-including-properties (a b) + (list (if (< emacs-major-version 29) + 'ert-equal-including-properties + 'equal-including-properties) + a b)) + +(ert-deftest erc--merge-prop () + (with-current-buffer (get-buffer-create "*erc-test*") + ;; Baseline. + (insert "abc\n") + (erc--merge-prop 1 3 'erc-test 'x) + (should (erc-tests--equal-including-properties + (buffer-substring 1 4) #("abc" 0 2 (erc-test x)))) + (erc--merge-prop 1 3 'erc-test 'y) + (should (erc-tests--equal-including-properties + (buffer-substring 1 4) #("abc" 0 2 (erc-test (y x))))) + + ;; Multiple intervals. + (goto-char (point-min)) + (insert "def\n") + (erc--merge-prop 1 2 'erc-test 'x) + (erc--merge-prop 2 3 'erc-test 'y) + (should (erc-tests--equal-including-properties + (buffer-substring 1 4) + #("def" 0 1 (erc-test x) 1 2 (erc-test y)))) + (erc--merge-prop 1 3 'erc-test 'z) + (should (erc-tests--equal-including-properties + (buffer-substring 1 4) + #("def" 0 1 (erc-test (z x)) 1 2 (erc-test (z y))))) + + ;; New val as list. + (goto-char (point-min)) + (insert "ghi\n") + (erc--merge-prop 2 3 'erc-test '(y z)) + (should (erc-tests--equal-including-properties + (buffer-substring 1 4) #("ghi" 1 2 (erc-test (y z))))) + (erc--merge-prop 1 3 'erc-test '(w x)) + (should (erc-tests--equal-including-properties + (buffer-substring 1 4) + #("ghi" 0 1 (erc-test (w x)) 1 2 (erc-test (w x y z))))) + + (when noninteractive + (kill-buffer)))) + (ert-deftest erc--split-string-shell-cmd () ;; Leading and trailing space @@ -1488,12 +1532,6 @@ erc-message (kill-buffer "ExampleNet") (kill-buffer "#chan"))) -(defmacro erc-tests--equal-including-properties (a b) - (list (if (< emacs-major-version 29) - 'ert-equal-including-properties - 'equal-including-properties) - a b)) - (ert-deftest erc-format-privmessage () ;; Basic PRIVMSG (should (erc-tests--equal-including-properties -- 2.41.0 --=-=-=--