unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: "J.P." <jp@neverwas.me>
To: 60954@debbugs.gnu.org
Cc: emacs-erc@gnu.org
Subject: bug#60954: 30.0.50; ERC >5.5: Stop requiring erc-goodies in erc.el
Date: Thu, 09 Mar 2023 06:43:26 -0800	[thread overview]
Message-ID: <87cz5ikmsx.fsf__41530.8781013422$1678373057$gmane$org@neverwas.me> (raw)
In-Reply-To: <87pmb9wyz6.fsf@neverwas.me> (J. P.'s message of "Thu, 19 Jan 2023 21:34:37 -0800")

[-- Attachment #1: Type: text/plain, Size: 281 bytes --]

v6. Autoload `erc-controls-strip' in erc-loaddefs.el and also `require'
the latter in erc.el when compiling. Remove unneeded forward
declarations. Remove barely used `require's atop file (possibly
controversial). Use dedicated "spoilers" face to avoid appearance of
gaps in text.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0000-v5-v6.diff --]
[-- Type: text/x-patch, Size: 6594 bytes --]

From 45e083eb4212cb77130d917a27f5f9985b08bdf7 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Thu, 9 Mar 2023 06:13:56 -0800
Subject: [PATCH 0/7] *** NOT A PATCH ***

*** BLURB HERE ***

F. Jason Park (7):
  [5.6] Honor arbitrary CHANTYPES in ERC
  [5.6] Copy over upstream Compat macros to erc-compat
  [5.6] Leverage loaddefs for migrating ERC modules
  [5.6] Don't require erc-goodies in erc.el
  [5.6] Modify erc-mode-map in module definitions
  [5.6] Add missing colors to erc-irccontrols-mode
  [5.6] Convert ERC's Imenu integration into proper module

 lisp/erc/erc-backend.el            |   2 +-
 lisp/erc/erc-button.el             |   6 +-
 lisp/erc/erc-common.el             |  39 +----
 lisp/erc/erc-compat.el             |  52 +++++-
 lisp/erc/erc-goodies.el            | 117 ++++++++-----
 lisp/erc/erc-ibuffer.el            |   1 +
 lisp/erc/erc-imenu.el              |  23 ++-
 lisp/erc/erc-log.el                |   8 +-
 lisp/erc/erc-match.el              |   8 +-
 lisp/erc/erc-page.el               |   4 +
 lisp/erc/erc-pcomplete.el          |   2 +
 lisp/erc/erc-services.el           |   1 +
 lisp/erc/erc-sound.el              |   1 +
 lisp/erc/erc-speedbar.el           |   1 +
 lisp/erc/erc-stamp.el              |   4 +
 lisp/erc/erc.el                    | 105 ++++++++----
 test/lisp/erc/erc-goodies-tests.el | 253 +++++++++++++++++++++++++++++
 test/lisp/erc/erc-tests.el         | 187 +++++++++++++++++++--
 18 files changed, 672 insertions(+), 142 deletions(-)
 create mode 100644 test/lisp/erc/erc-goodies-tests.el

Interdiff:
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index 3d018bb11e0..7ea6c42ec65 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -235,6 +235,12 @@ erc-inverse-face
   "ERC inverse face."
   :group 'erc-faces)
 
+(defface erc-spoiler-face
+  '((((background light)) :foreground "DimGray" :background "DimGray")
+    (((background dark)) :foreground "LightGray" :background "LightGray"))
+  "ERC spoiler face."
+  :group 'erc-faces)
+
 (defface erc-underline-face '((t :underline t))
   "ERC underline face."
   :group 'erc-faces)
@@ -467,6 +473,7 @@ erc-controls-interpret
                 s))
              (t s)))))
 
+;;;###autoload
 (defun erc-controls-strip (str)
   "Return a copy of STR with all IRC control characters removed."
   (when str
@@ -531,6 +538,13 @@ erc-controls-propertize
   "Prepend properties from IRC control characters between FROM and TO.
 If optional argument STR is provided, apply to STR, otherwise prepend properties
 to a region in the current buffer."
+  (if (and fg bg (equal fg bg))
+      (progn
+        (setq fg 'erc-spoiler-face
+              bg nil)
+        (put-text-property from to 'mouse-face 'erc-inverse-face str))
+    (when fg (setq fg (erc-get-fg-color-face fg)))
+    (when bg (setq bg (erc-get-bg-color-face bg))))
   (font-lock-prepend-text-property
    from
    to
@@ -548,14 +562,12 @@ erc-controls-propertize
                '(erc-underline-face)
              nil)
            (if fg
-               (list (erc-get-fg-color-face fg))
+               (list fg)
              nil)
            (if bg
-               (list (erc-get-bg-color-face bg))
+               (list bg)
              nil))
    str)
-  (when (and fg bg (equal fg bg))
-    (put-text-property from to 'mouse-face 'erc-inverse-face str))
   str)
 
 (defun erc-toggle-interpret-controls (&optional arg)
@@ -632,3 +644,7 @@ erc-occur
 (provide 'erc-goodies)
 
 ;;; erc-goodies.el ends here
+
+;; Local Variables:
+;; generated-autoload-file: "erc-loaddefs.el"
+;; End:
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index b8f8263f9c3..1315bce2e54 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -58,17 +58,13 @@
 
 ;;; Code:
 
-(load "erc-loaddefs" 'noerror 'nomessage)
+(eval-and-compile (load "erc-loaddefs" 'noerror 'nomessage))
 
 (require 'erc-networks)
 (require 'erc-backend)
 (require 'cl-lib)
 (require 'format-spec)
-(require 'pp)
-(require 'thingatpt)
 (require 'auth-source)
-(require 'time-date)
-(require 'iso8601)
 (eval-when-compile (require 'subr-x) (require 'url-parse))
 
 (defconst erc-version "5.5"
@@ -135,13 +131,14 @@ erc-scripts
 
 ;; Forward declarations
 (defvar erc-message-parsed)
-(defvar erc-irccontrols-mode)
 
 (defvar tabbar--local-hlf)
 (defvar motif-version-string)
 (defvar gtk-version-string)
 
-(declare-function erc-controls-strip "erc-goodies" (str))
+(declare-function decoded-time-period "time-date" (time))
+(declare-function iso8601-parse-duration "iso8601" (string))
+(declare-function word-at-point "thingatpt" (&optional no-properties))
 
 ;; tunable connection and authentication parameters
 
@@ -3099,6 +3096,8 @@ erc--read-time-period
       (string-to-number period))
      ;; Parse as a time spec.
      (t
+      (require 'time-date)
+      (require 'iso8601)
       (let ((time (condition-case nil
                       (iso8601-parse-duration
                        (concat (cond
@@ -6891,8 +6890,7 @@ erc-update-mode-line-buffer
                   (?m . ,(erc-format-channel-modes))
                   (?n . ,(or (erc-current-nick) ""))
                   (?N . ,(erc-format-network))
-                  (?o . ,(or (and (bound-and-true-p erc-irccontrols-mode)
-                                  (erc-controls-strip erc-channel-topic))
+                  (?o . ,(or (erc-controls-strip erc-channel-topic)
                              ""))
                   (?p . ,(erc-port-to-string erc-session-port))
                   (?s . ,(erc-format-target-and/or-server))
diff --git a/test/lisp/erc/erc-goodies-tests.el b/test/lisp/erc/erc-goodies-tests.el
index 8cab1dd0857..46fcf82401b 100644
--- a/test/lisp/erc/erc-goodies-tests.el
+++ b/test/lisp/erc/erc-goodies-tests.el
@@ -156,11 +156,13 @@ erc-controls-highlight--inverse
          0 "Spoiler: " 'erc-default-face
          '(fg:erc-color-face0 bg:erc-color-face0))
         (erc-goodies-tests--assert-face
-         9 "Hello" '(fg:erc-color-face0 bg:erc-color-face0)
-         '(fg:erc-color-face1 bg:erc-color-face1))
+         9 "Hello" '(erc-spoiler-face)
+         '( fg:erc-color-face0 bg:erc-color-face0
+            fg:erc-color-face1 bg:erc-color-face1))
         (erc-goodies-tests--assert-face
-         18 " World" '(fg:erc-color-face1 bg:erc-color-face1)
-         '(fg:erc-color-face0 bg:erc-color-face0)))
+         18 " World" '(erc-spoiler-face)
+         '( fg:erc-color-face0 bg:erc-color-face0
+            fg:erc-color-face1 bg:erc-color-face1 )))
       (when noninteractive
         (kill-buffer)))))
 
-- 
2.39.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0001-5.6-Honor-arbitrary-CHANTYPES-in-ERC.patch --]
[-- Type: text/x-patch, Size: 2684 bytes --]

From 2be509ddf594767a29ea3b16a31874e4078c3255 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Sat, 18 Feb 2023 19:32:36 -0800
Subject: [PATCH 1/7] [5.6] Honor arbitrary CHANTYPES in ERC

* lisp/erc/erc.el (erc-channel-p): Favor "CHANTYPES" ISUPPORT item
before falling back to well known prefixes.
* test/lisp/erc/erc-tests.el (erc-channel-p): Add test.
---
 lisp/erc/erc.el            | 12 ++++++++----
 test/lisp/erc/erc-tests.el | 21 +++++++++++++++++++++
 2 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 69bdb5d71b1..dd3697ac50b 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1530,10 +1530,14 @@ erc-reuse-frames
 (defun erc-channel-p (channel)
   "Return non-nil if CHANNEL seems to be an IRC channel name."
   (cond ((stringp channel)
-         (memq (aref channel 0) '(?# ?& ?+ ?!)))
-        ((and (bufferp channel) (buffer-live-p channel))
-         (with-current-buffer channel
-           (erc-channel-p (erc-default-target))))
+         (memq (aref channel 0)
+               (if-let ((types (erc--get-isupport-entry 'CHANTYPES 'single)))
+                   (append types nil)
+                 '(?# ?& ?+ ?!))))
+        ((and-let* (((bufferp channel))
+                    ((buffer-live-p channel))
+                    (target (buffer-local-value 'erc--target channel)))
+           (erc-channel-p (erc--target-string target))))
         (t nil)))
 
 ;; For the sake of compatibility, a historical quirk concerning this
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index d6c63934163..bbf3269161d 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -447,6 +447,27 @@ erc-downcase
       (should (equal (erc-downcase "Tilde~") "tilde~" ))
       (should (equal (erc-downcase "\\O/") "|o/" )))))
 
+(ert-deftest erc-channel-p ()
+  (let ((erc--isupport-params (make-hash-table))
+        erc-server-parameters)
+
+    (should (erc-channel-p "#chan"))
+    (should (erc-channel-p "##chan"))
+    (should (erc-channel-p "&chan"))
+    (should (erc-channel-p "+chan"))
+    (should (erc-channel-p "!chan"))
+    (should-not (erc-channel-p "@chan"))
+
+    (push '("CHANTYPES" . "#&@+!") erc-server-parameters)
+
+    (should (erc-channel-p "!chan"))
+    (should (erc-channel-p "#chan"))
+
+    (with-current-buffer (get-buffer-create "#chan")
+      (setq erc--target (erc--target-from-string "#chan")))
+    (should (erc-channel-p (get-buffer "#chan"))))
+  (kill-buffer "#chan"))
+
 (ert-deftest erc--valid-local-channel-p ()
   (ert-info ("Local channels not supported")
     (let ((erc--isupport-params (make-hash-table)))
-- 
2.39.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0002-5.6-Copy-over-upstream-Compat-macros-to-erc-compat.patch --]
[-- Type: text/x-patch, Size: 4112 bytes --]

From 9f0c66057b527845f6a81204fb1fd2f00de5029f Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Thu, 19 Jan 2023 20:52:47 -0800
Subject: [PATCH 2/7] [5.6] Copy over upstream Compat macros to erc-compat

* lisp/erc/erc-backend: (erc--get-isupport-entry): Replace call to
`erc-compat--with-memoization' with the built-in `with-memoization'.
* lisp/erc/erc-compat.el: (erc-compat-function, erc-compat-call): Add
new macros from Compat 29.1.2.0.
(erc-compat--with-memoization): Remove because it's now provided by
Compat.  (Bug#60954.)
---
 lisp/erc/erc-backend.el |  2 +-
 lisp/erc/erc-compat.el  | 52 ++++++++++++++++++++++++++++++++++-------
 2 files changed, 44 insertions(+), 10 deletions(-)

diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 567443f5329..8c90e8eebb9 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -1876,7 +1876,7 @@ erc--get-isupport-entry
 primitive value."
   (if-let* ((table (or erc--isupport-params
                        (erc-with-server-buffer erc--isupport-params)))
-            (value (erc-compat--with-memoization (gethash key table)
+            (value (with-memoization (gethash key table)
                      (when-let ((v (assoc (symbol-name key)
                                           erc-server-parameters)))
                        (if (cdr v)
diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el
index 5601ede27a5..605ee701850 100644
--- a/lisp/erc/erc-compat.el
+++ b/lisp/erc/erc-compat.el
@@ -34,6 +34,49 @@
 (require 'compat nil 'noerror)
 (eval-when-compile (require 'cl-lib) (require 'url-parse))
 
+;; Except for the "erc-" namespacing, these two definitions should be
+;; continuously updated to match the latest upstream ones verbatim.
+;; Although they're pretty simple, it's likely not worth checking for
+;; and possibly deferring to the non-prefixed versions.
+;;
+;; BEGIN Compat macros
+
+;;;; Macros for extended compatibility function calls
+
+(defmacro erc-compat-function (fun)
+  "Return compatibility function symbol for FUN.
+
+If the Emacs version provides a sufficiently recent version of
+FUN, the symbol FUN is returned itself.  Otherwise the macro
+returns the symbol of a compatibility function which supports the
+behavior and calling convention of the current stable Emacs
+version.  For example Compat 29.1 will provide compatibility
+functions which implement the behavior and calling convention of
+Emacs 29.1.
+
+See also `compat-call' to directly call compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `#',(if (fboundp compat) compat fun)))
+
+(defmacro erc-compat-call (fun &rest args)
+  "Call compatibility function or macro FUN with ARGS.
+
+A good example function is `plist-get' which was extended with an
+additional predicate argument in Emacs 29.1.  The compatibility
+function, which supports this additional argument, can be
+obtained via (compat-function plist-get) and called
+via (compat-call plist-get plist prop predicate).  It is not
+possible to directly call (plist-get plist prop predicate) on
+Emacs older than 29.1, since the original `plist-get' function
+does not yet support the predicate argument.  Note that the
+Compat library never overrides existing functions.
+
+See also `compat-function' to lookup compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `(,(if (fboundp compat) compat fun) ,@args)))
+
+;; END Compat macros
+
 ;;;###autoload(autoload 'erc-define-minor-mode "erc-compat")
 (define-obsolete-function-alias 'erc-define-minor-mode
   #'define-minor-mode "28.1")
@@ -368,15 +411,6 @@ erc-compat--29-sasl-scram--client-final-message
 
 ;;;; Misc 29.1
 
-(defmacro erc-compat--with-memoization (table &rest forms)
-  (declare (indent defun))
-  (cond
-   ((fboundp 'with-memoization)
-    `(with-memoization ,table ,@forms)) ; 29.1
-   ((fboundp 'cl--generic-with-memoization)
-    `(cl--generic-with-memoization ,table ,@forms))
-   (t `(progn ,@forms))))
-
 (defvar url-irc-function)
 
 (defun erc-compat--29-browse-url-irc (string &rest _)
-- 
2.39.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: 0003-5.6-Leverage-loaddefs-for-migrating-ERC-modules.patch --]
[-- Type: text/x-patch, Size: 19348 bytes --]

From e5092e3ee953bff28e5f391b647585e5f60cb0cd Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Sat, 4 Feb 2023 06:24:59 -0800
Subject: [PATCH 3/7] [5.6] Leverage loaddefs for migrating ERC modules

* lisp/erc/erc-common.el (erc--features-to-modules,
erc--modules-to-features, erc--module-name-migrations): Remove unused
internal functions.
(erc--normalize-module-symbol): Make aware of new symbol-prop-based
migration scheme.
* lisp/erc/erc-page.el: Add autoload cookie for module migration.
* lisp/erc/erc-services.el: Add autoload cookie for module migration.
* lisp/erc/erc-sound.el: Add autoload cookie for module migration.
* lisp/erc/erc-stamp.el: Add autoload cookie for module migration.
* lisp/erc/erc.el (erc-modules): Remove non-existent module
`hecomplete' from lineup.  Swap a couple more to maintain sorted
order.  Change `:initialize' function to tag all symbols for built-in
modules with an `erc--module' property.  Likewise, in the `:set'
function, ensure third-party modules appear after the sorted and
normalized built-ins, but in user-defined order. Do this to prevent
all modules, built-ins included, from ending up as populated form
fields for the "other" checkbox in the Customize interface.
(erc--find-mode): Add helper function for `erc--update-modules'.
(erc--update-modules): Always resolve module names and only
conditionally attempt to require corresponding features.
* test/lisp/erc/erc-tests.el
(erc-tests--module): Add manifest for asserting built-in modules and
features.  This is easier to verify visually than looking at the
custom-type set for `erc-modules'.
(erc-modules--internal-property): Add test.
(erc--normalize-module-symbol): Make more comprehensive.
(erc--find-mode): New test.  (Bug#60954.)
---
 lisp/erc/erc-common.el     |  39 ++----------
 lisp/erc/erc-page.el       |   1 +
 lisp/erc/erc-pcomplete.el  |   2 +
 lisp/erc/erc-services.el   |   1 +
 lisp/erc/erc-sound.el      |   1 +
 lisp/erc/erc-stamp.el      |   4 ++
 lisp/erc/erc.el            |  56 ++++++++++++-----
 test/lisp/erc/erc-tests.el | 122 ++++++++++++++++++++++++++++++++-----
 8 files changed, 161 insertions(+), 65 deletions(-)

diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el
index 0279b0a0bc4..f01db9d8bbc 100644
--- a/lisp/erc/erc-common.el
+++ b/lisp/erc/erc-common.el
@@ -85,40 +85,13 @@ erc--target
   (contents "" :type string)
   (tags '() :type list))
 
-;; TODO move goodies modules here after 29 is released.
-(defconst erc--features-to-modules
-  '((erc-pcomplete completion pcomplete)
-    (erc-capab capab-identify)
-    (erc-join autojoin)
-    (erc-page page ctcp-page)
-    (erc-sound sound ctcp-sound)
-    (erc-stamp stamp timestamp)
-    (erc-services services nickserv))
-  "Migration alist mapping a library feature to module names.
-Keys need not be unique: a library may define more than one
-module.  Sometimes a module's downcased alias will be its
-canonical name.")
-
-(defconst erc--modules-to-features
-  (let (pairs)
-    (pcase-dolist (`(,feature . ,names) erc--features-to-modules)
-      (dolist (name names)
-        (push (cons name feature) pairs)))
-    (nreverse pairs))
-  "Migration alist mapping a module's name to its home library feature.")
-
-(defconst erc--module-name-migrations
-  (let (pairs)
-    (pcase-dolist (`(,_ ,canonical . ,rest) erc--features-to-modules)
-      (dolist (obsolete rest)
-        (push (cons obsolete canonical) pairs)))
-    pairs)
-  "Association list of obsolete module names to canonical names.")
-
+;; After deprecating 28, we can use prefixed "erc-autoload" cookies.
 (defun erc--normalize-module-symbol (symbol)
-  "Return preferred SYMBOL for `erc-modules'."
-  (setq symbol (intern (downcase (symbol-name symbol))))
-  (or (cdr (assq symbol erc--module-name-migrations)) symbol))
+  "Return preferred SYMBOL for `erc--modules'."
+  (while-let ((canonical (get symbol 'erc--module))
+              ((not (eq canonical symbol))))
+    (setq symbol canonical))
+  symbol)
 
 (defun erc--assemble-toggle (localp name ablsym mode val body)
   (let ((arg (make-symbol "arg")))
diff --git a/lisp/erc/erc-page.el b/lisp/erc/erc-page.el
index 308b3784ca5..6cba59c6946 100644
--- a/lisp/erc/erc-page.el
+++ b/lisp/erc/erc-page.el
@@ -34,6 +34,7 @@ erc-page
   "React to CTCP PAGE messages."
   :group 'erc)
 
+;;;###autoload(put 'ctcp-page 'erc--module 'page)
 ;;;###autoload(autoload 'erc-page-mode "erc-page")
 (define-erc-module page ctcp-page
   "Process CTCP PAGE requests from IRC."
diff --git a/lisp/erc/erc-pcomplete.el b/lisp/erc/erc-pcomplete.el
index 0bce856018c..7eb7431fb91 100644
--- a/lisp/erc/erc-pcomplete.el
+++ b/lisp/erc/erc-pcomplete.el
@@ -56,6 +56,8 @@ erc-pcomplete-order-nickname-completions
   "If t, order nickname completions with the most recent speakers first."
   :type 'boolean)
 
+;;;###autoload(put 'Completion 'erc--module 'completion)
+;;;###autoload(put 'pcomplete 'erc--module 'completion)
 ;;;###autoload(autoload 'erc-completion-mode "erc-pcomplete" nil t)
 (define-erc-module pcomplete Completion
   "In ERC Completion mode, the TAB key does completion whenever possible."
diff --git a/lisp/erc/erc-services.el b/lisp/erc/erc-services.el
index 2e6959cc3f0..5408ba405db 100644
--- a/lisp/erc/erc-services.el
+++ b/lisp/erc/erc-services.el
@@ -102,6 +102,7 @@ erc-nickserv-identify-mode
 	 (when (featurep 'erc-services)
 	   (erc-nickserv-identify-mode val))))
 
+;;;###autoload(put 'nickserv 'erc--module 'services)
 ;;;###autoload(autoload 'erc-services-mode "erc-services" nil t)
 (define-erc-module services nickserv
   "This mode automates communication with services."
diff --git a/lisp/erc/erc-sound.el b/lisp/erc/erc-sound.el
index 0abdbfd959c..9da9202f0cf 100644
--- a/lisp/erc/erc-sound.el
+++ b/lisp/erc/erc-sound.el
@@ -47,6 +47,7 @@
 
 (require 'erc)
 
+;;;###autoload(put 'ctcp-sound 'erc--module 'sound)
 ;;;###autoload(autoload 'erc-sound-mode "erc-sound")
 (define-erc-module sound ctcp-sound
   "In ERC sound mode, the client will respond to CTCP SOUND requests
diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el
index 0aa1590f801..d1a1507f700 100644
--- a/lisp/erc/erc-stamp.el
+++ b/lisp/erc/erc-stamp.el
@@ -147,6 +147,10 @@ erc-timestamp-face
   "ERC timestamp face."
   :group 'erc-faces)
 
+;; New libraries should only autoload the minor mode for a module's
+;; preferred name (rather than its alias).
+
+;;;###autoload(put 'timestamp 'erc--module 'stamp)
 ;;;###autoload(autoload 'erc-timestamp-mode "erc-stamp" nil t)
 (define-erc-module stamp timestamp
   "This mode timestamps messages in the channel buffers."
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index dd3697ac50b..6520e57ff3a 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1830,12 +1830,20 @@ erc-modules
   :get (lambda (sym)
          ;; replace outdated names with their newer equivalents
          (erc-migrate-modules (symbol-value sym)))
-  :initialize #'custom-initialize-default
+  ;; Expect every built-in module to have the symbol property
+  ;; `erc--module' set to its canonical symbol (often itself).
+  :initialize (lambda (symbol exp)
+                ;; Use `cdddr' because (set :greedy t . ,entries)
+                (dolist (entry (cdddr (get 'erc-modules 'custom-type)))
+                  (when-let* (((eq (car entry) 'const))
+                              (s (cadddr entry))) ; (const :tag "..." ,s)
+                    (put s 'erc--module s)))
+                (custom-initialize-default symbol exp))
   :set (lambda (sym val)
          ;; disable modules which have just been removed
          (when (and (boundp 'erc-modules) erc-modules val)
            (dolist (module erc-modules)
-             (unless (member module val)
+             (unless (memq module val)
                (let ((f (intern-soft (format "erc-%s-mode" module))))
                  (when (and (fboundp f) (boundp f))
                    (when (symbol-value f)
@@ -1847,7 +1855,14 @@ erc-modules
                                           (when (symbol-value f)
                                             (funcall f 0))
                                           (kill-local-variable f)))))))))
-         (set sym val)
+         (let (built-in third-party)
+           (dolist (v val)
+             (setq v (erc--normalize-module-symbol v))
+             (if (get v 'erc--module)
+                 (push v built-in)
+               (push v third-party)))
+           (set sym (append (sort built-in #'string-lessp)
+                            (nreverse third-party))))
          ;; this test is for the case where erc hasn't been loaded yet
          (when (fboundp 'erc-update-modules)
            (erc-update-modules)))
@@ -1861,7 +1876,6 @@ erc-modules
            capab-identify)
     (const :tag "completion: Complete nicknames and commands (programmable)"
            completion)
-    (const :tag "hecomplete: Complete nicknames and commands (obsolete, use \"completion\")" hecomplete)
     (const :tag "dcc: Provide Direct Client-to-Client support" dcc)
     (const :tag "fill: Wrap long lines" fill)
     (const :tag "identd: Launch an identd server on port 8113" identd)
@@ -1878,11 +1892,11 @@ erc-modules
     (const :tag "networks: Provide data about IRC networks" networks)
     (const :tag "noncommands: Don't display non-IRC commands after evaluation"
            noncommands)
+    (const :tag "notifications: Desktop alerts on PRIVMSG or mentions"
+           notifications)
     (const :tag
            "notify: Notify when the online status of certain users changes"
            notify)
-    (const :tag "notifications: Send notifications on PRIVMSG or nickname mentions"
-           notifications)
     (const :tag "page: Process CTCP PAGE requests from IRC" page)
     (const :tag "readonly: Make displayed lines read-only" readonly)
     (const :tag "replace: Replace text in messages" replace)
@@ -1895,8 +1909,8 @@ erc-modules
     (const :tag "smiley: Convert smileys to pretty icons" smiley)
     (const :tag "sound: Play sounds when you receive CTCP SOUND requests"
            sound)
-    (const :tag "stamp: Add timestamps to messages" stamp)
     (const :tag "spelling: Check spelling" spelling)
+    (const :tag "stamp: Add timestamps to messages" stamp)
     (const :tag "track: Track channel activity in the mode-line" track)
     (const :tag "truncate: Truncate buffers to a certain size" truncate)
     (const :tag "unmorse: Translate morse code in messages" unmorse)
@@ -1910,18 +1924,28 @@ erc-update-modules
   (erc--update-modules)
   nil)
 
+(defun erc--find-mode (sym)
+  (setq sym (erc--normalize-module-symbol sym))
+  (if-let* ((mode (intern-soft (concat "erc-" (symbol-name sym) "-mode")))
+            ((or (boundp mode)
+                 (and (fboundp mode)
+                      (autoload-do-load (symbol-function mode) mode)))))
+      mode
+    (and (require (or (get sym 'erc--feature)
+                      (intern (concat "erc-" (symbol-name sym))))
+                  nil 'noerror)
+         (setq mode (intern-soft (concat "erc-" (symbol-name sym) "-mode")))
+         (fboundp mode)
+         mode)))
+
 (defun erc--update-modules ()
   (let (local-modes)
     (dolist (module erc-modules local-modes)
-      (require (or (alist-get module erc--modules-to-features)
-                   (intern (concat "erc-" (symbol-name module))))
-               nil 'noerror) ; some modules don't have a corresponding feature
-      (let ((mode (intern-soft (concat "erc-" (symbol-name module) "-mode"))))
-        (unless (and mode (fboundp mode))
-          (error "`%s' is not a known ERC module" module))
-        (if (custom-variable-p mode)
-            (funcall mode 1)
-          (push mode local-modes))))))
+      (if-let ((mode (erc--find-mode module)))
+          (if (custom-variable-p mode)
+              (funcall mode 1)
+            (push mode local-modes))
+        (error "`%s' is not a known ERC module" module)))))
 
 (defun erc-setup-buffer (buffer)
   "Consults `erc-join-buffer' to find out how to display `BUFFER'."
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index bbf3269161d..6ab6d9ee40f 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -1224,6 +1224,77 @@ erc-handle-irc-url
     (kill-buffer "baznet")
     (kill-buffer "#chan")))
 
+(defconst erc-tests--modules
+  '( autoaway autojoin button capab-identify completion dcc fill identd
+     irccontrols keep-place list log match menu move-to-prompt netsplit
+     networks noncommands notifications notify page readonly
+     replace ring sasl scrolltobottom services smiley sound
+     spelling stamp track truncate unmorse xdcc))
+
+;; Ensure the `:initialize' function for `erc-modules' successfully
+;; tags all built-in modules with the internal property `erc--module'.
+
+(ert-deftest erc-modules--internal-property ()
+  (let (ours)
+    (mapatoms (lambda (s)
+                (when-let ((v (get s 'erc--module))
+                           ((eq v s)))
+                  (push s ours))))
+    (should (equal (sort ours #'string-lessp) erc-tests--modules))))
+
+(ert-deftest erc--normalize-module-symbol ()
+  (dolist (mod erc-tests--modules)
+    (should (eq (erc--normalize-module-symbol mod) mod)))
+  (should (eq (erc--normalize-module-symbol 'pcomplete) 'completion))
+  (should (eq (erc--normalize-module-symbol 'Completion) 'completion))
+  (should (eq (erc--normalize-module-symbol 'ctcp-page) 'page))
+  (should (eq (erc--normalize-module-symbol 'ctcp-sound) 'sound))
+  (should (eq (erc--normalize-module-symbol 'timestamp) 'stamp))
+  (should (eq (erc--normalize-module-symbol 'nickserv) 'services)))
+
+;; Worrying about which library a module comes from is mostly not
+;; worth the hassle so long as ERC can find its minor mode.  However,
+;; bugs involving multiple modules living in the same library may slip
+;; by because a module's loading problems may remain hidden on account
+;; of its place in the default ordering.
+
+(ert-deftest erc--find-mode ()
+  (let* ((package (if-let* ((found (getenv "ERC_PACKAGE_NAME"))
+                            ((string-prefix-p "erc-" found)))
+                      (intern found)
+                    'erc))
+         (prog
+          `(,@(and (featurep 'compat)
+                   `((progn
+                       (require 'package)
+                       (let ((package-load-list '((compat t) (,package t))))
+                         (package-initialize)))))
+            (require 'erc)
+            (let ((mods (mapcar #'cadddr
+                                (cdddr (get 'erc-modules 'custom-type))))
+                  moded)
+              (setq mods
+                    (sort mods (lambda (a b) (if (zerop (random 2)) a b))))
+              (dolist (mod mods)
+                (unless (keywordp mod)
+                  (push (if-let ((mode (erc--find-mode mod)))
+                            mod
+                          (list :missing mod))
+                        moded)))
+              (message "%S"
+                       (sort moded
+                             (lambda (a b)
+                               (string< (symbol-name a) (symbol-name b))))))))
+         (proc (start-process "erc--module-mode-autoloads"
+                              (current-buffer)
+                              (concat invocation-directory invocation-name)
+                              "-batch" "-Q"
+                              "-eval" (format "%S" (cons 'progn prog)))))
+    (set-process-query-on-exit-flag proc t)
+    (while (accept-process-output proc 10))
+    (goto-char (point-min))
+    (should (equal (read (current-buffer)) erc-tests--modules))))
+
 (ert-deftest erc-migrate-modules ()
   (should (equal (erc-migrate-modules '(autojoin timestamp button))
                  '(autojoin stamp button)))
@@ -1234,17 +1305,28 @@ erc--update-modules
   (let (calls
         erc-modules
         erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
+
+    ;; This `lbaz' module is unknown, so ERC looks for it via the
+    ;; symbol proerty `erc--feature' and, failing that, by
+    ;; `require'ing its "erc-" prefixed symbol.
+    (should-not (intern-soft "erc-lbaz-mode"))
+
     (cl-letf (((symbol-function 'require)
-               (lambda (s &rest _) (push s calls)))
+               (lambda (s &rest _)
+                 (when (eq s 'erc--lbaz-feature)
+                   (fset (intern "erc-lbaz-mode") ; local module
+                         (lambda (n) (push (cons 'lbaz n) calls))))
+                 (push s calls)))
 
               ;; Local modules
-              ((symbol-function 'erc-fake-bar-mode)
-               (lambda (n) (push (cons 'fake-bar n) calls)))
+              ((symbol-function 'erc-lbar-mode)
+               (lambda (n) (push (cons 'lbar n) calls)))
+              ((get 'lbaz 'erc--feature) 'erc--lbaz-feature)
 
               ;; Global modules
-              ((symbol-function 'erc-fake-foo-mode)
-               (lambda (n) (push (cons 'fake-foo n) calls)))
-              ((get 'erc-fake-foo-mode 'standard-value) 'ignore)
+              ((symbol-function 'erc-gfoo-mode)
+               (lambda (n) (push (cons 'gfoo n) calls)))
+              ((get 'erc-gfoo-mode 'standard-value) 'ignore)
               ((symbol-function 'erc-autojoin-mode)
                (lambda (n) (push (cons 'autojoin n) calls)))
               ((get 'erc-autojoin-mode 'standard-value) 'ignore)
@@ -1255,20 +1337,28 @@ erc--update-modules
                (lambda (n) (push (cons 'completion n) calls)))
               ((get 'erc-completion-mode 'standard-value) 'ignore))
 
+      (ert-info ("Unknown module")
+        (setq erc-modules '(lfoo))
+        (should-error (erc--update-modules))
+        (should (equal (pop calls) 'erc-lfoo))
+        (should-not calls))
+
       (ert-info ("Local modules")
-        (setq erc-modules '(fake-foo fake-bar))
-        (should (equal (erc--update-modules) '(erc-fake-bar-mode)))
-        ;; Bar the feature is still required but the mode is not activated
-        (should (equal (nreverse calls)
-                       '(erc-fake-foo (fake-foo . 1) erc-fake-bar)))
+        (setq erc-modules '(gfoo lbar lbaz))
+        ;; Don't expose the mode here
+        (should (equal (mapcar #'symbol-name (erc--update-modules))
+                       '("erc-lbaz-mode" "erc-lbar-mode")))
+        ;; Lbaz required because unknown.
+        (should (equal (nreverse calls) '((gfoo . 1) erc--lbaz-feature)))
+        (fmakunbound (intern "erc-lbaz-mode"))
+        (unintern (intern "erc-lbaz-mode") obarray)
         (setq calls nil))
 
-      (ert-info ("Module name overrides")
-        (setq erc-modules '(completion autojoin networks))
+      (ert-info ("Global modules") ; `pcomplete' resolved to `completion'
+        (setq erc-modules '(pcomplete autojoin networks))
         (should-not (erc--update-modules)) ; no locals
-        (should (equal (nreverse calls) '( erc-pcomplete (completion . 1)
-                                           erc-join (autojoin . 1)
-                                           erc-networks (networks . 1))))
+        (should (equal (nreverse calls)
+                       '((completion . 1) (autojoin . 1) (networks . 1))))
         (setq calls nil)))))
 
 (ert-deftest erc--merge-local-modes ()
-- 
2.39.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #6: 0004-5.6-Don-t-require-erc-goodies-in-erc.el.patch --]
[-- Type: text/x-patch, Size: 9911 bytes --]

From 8a3956c735952256ea9812a2fe15d39be4185223 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Thu, 19 Jan 2023 21:07:27 -0800
Subject: [PATCH 4/7] [5.6] Don't require erc-goodies in erc.el

* lisp/erc/erc-common.el: (erc--features-to-modules): Add mappings
from erc-goodies.
* lisp/erc/erc-goodies.el: Remove forward declarations.  Add
minor-mode autoloads for `scrolltobottom', `readonly',
`move-to-prompt', `keep-place', `noncommands', `irccontrols',
`smiley', and `unmorse'.  Add Local variables footer with
`generated-autoload-file'.
(erc-controls-strip): Autoload this function.
(erc-irccontrols-mode, erc-irccontrols-enable,
erc-irccontrols-disable): Add and remove key for
`erc-toggle-interpret-controls' to `erc-mode-map'.
(erc--irccontrols-on-major-mode): New helper to add or remove toggle
command from local map.
* lisp/erc/erc-ibuffer.el: Require `erc-goodies' for
`erc-control-interpret'.  The justification for the blanket `require'
is this module isn't a member of `erc-modules' by default.
* lisp/erc/erc-page.el: (erc-ctcp-query-PAGE): Require `erc-goodies'
and put forward declaration for `erc-control-interpret' atop file.
* lisp/erc/erc-speedbar.el: Require `erc-goodies' for the same reason
in erc-ibuffer.el.
* lisp/erc/erc.el: Remove `require' for `erc-goodies' at end of file
and `pp' at top of file because `pp-to-string' is autoloaded on Emacs
27.  Also remove `require's for `thingatpt', `time-date', and
`iso8601'.  They're all used sparingly and the latter two have only
been around for one major release, so their removal likely won't cause
much churn.  And `thingatpt' already has a call-site `require', so the
top-level one was redundant.  Also wrap local loaddefs `require' call
in `eval-and-compile'.
(erc-update-mode-line-buffer): Only strip control chars when
`erc-irccontrols-mode' is active.  This is arguably a minor breaking
change perhaps deserving of a NEWS entry.  (Bug#60954.)
---
 lisp/erc/erc-goodies.el  | 28 ++++++++++++++--------------
 lisp/erc/erc-ibuffer.el  |  1 +
 lisp/erc/erc-page.el     |  3 +++
 lisp/erc/erc-speedbar.el |  1 +
 lisp/erc/erc.el          | 19 +++++++++----------
 5 files changed, 28 insertions(+), 24 deletions(-)

diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index 05a21019042..7ca155ef9d0 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -29,23 +29,10 @@
 
 ;;; Code:
 
-;;; Imenu support
-
 (eval-when-compile (require 'cl-lib))
-(require 'erc-common)
-
 (defvar erc-controls-highlight-regexp)
 (defvar erc-controls-remove-regexp)
-(defvar erc-input-marker)
-(defvar erc-insert-marker)
-(defvar erc-server-process)
-(defvar erc-modules)
-(defvar erc-log-p)
-
-(declare-function erc-buffer-list "erc" (&optional predicate proc))
-(declare-function erc-error "erc" (&rest args))
-(declare-function erc-extract-command-from-line "erc" (line))
-(declare-function erc-beg-of-input-line "erc" nil)
+(require 'erc)
 
 (defun erc-imenu-setup ()
   "Setup Imenu support in an ERC buffer."
@@ -65,6 +52,7 @@ erc-input-line-position
   :group 'erc-display
   :type '(choice integer (const nil)))
 
+;;;###autoload(autoload 'erc-scrolltobottom-mode "erc-goodies" nil t)
 (define-erc-module scrolltobottom nil
   "This mode causes the prompt to stay at the end of the window."
   ((add-hook 'erc-mode-hook #'erc-add-scroll-to-bottom)
@@ -116,6 +104,7 @@ erc-scroll-to-bottom
 	  (recenter (or erc-input-line-position -1)))))))
 
 ;;; Make read only
+;;;###autoload(autoload 'erc-readonly-mode "erc-goodies" nil t)
 (define-erc-module readonly nil
   "This mode causes all inserted text to be read-only."
   ((add-hook 'erc-insert-post-hook #'erc-make-read-only)
@@ -131,6 +120,7 @@ erc-make-read-only
   (put-text-property (point-min) (point-max) 'rear-nonsticky t))
 
 ;;; Move to prompt when typing text
+;;;###autoload(autoload 'erc-move-to-prompt-mode "erc-goodies" nil t)
 (define-erc-module move-to-prompt nil
   "This mode causes the point to be moved to the prompt when typing text."
   ((add-hook 'erc-mode-hook #'erc-move-to-prompt-setup)
@@ -155,6 +145,7 @@ erc-move-to-prompt-setup
   (add-hook 'pre-command-hook #'erc-move-to-prompt nil t))
 
 ;;; Keep place in unvisited channels
+;;;###autoload(autoload 'erc-keep-place-mode "erc-goodies" nil t)
 (define-erc-module keep-place nil
   "Leave point above un-viewed text in other channels."
   ((add-hook 'erc-insert-pre-hook  #'erc-keep-place))
@@ -193,6 +184,7 @@ erc-noncommands-list
 If a command's function symbol is in this list, the typed command
 does not appear in the ERC buffer after the user presses ENTER.")
 
+;;;###autoload(autoload 'erc-noncommands-mode "erc-goodies" nil t)
 (define-erc-module noncommands nil
   "This mode distinguishes non-commands.
 Commands listed in `erc-insert-this' know how to display
@@ -381,6 +373,7 @@ erc-get-fg-color-face
       (intern (concat "fg:erc-color-face" (number-to-string n))))
      (t (erc-log (format "   Wrong color: %s" n)) 'default))))
 
+;;;###autoload(autoload 'erc-irccontrols-mode "erc-goodies" nil t)
 (define-erc-module irccontrols nil
   "This mode enables the interpretation of IRC control chars."
   ((add-hook 'erc-insert-modify-hook #'erc-controls-highlight)
@@ -440,6 +433,7 @@ erc-controls-interpret
                 s))
              (t s)))))
 
+;;;###autoload
 (defun erc-controls-strip (str)
   "Return a copy of STR with all IRC control characters removed."
   (when str
@@ -553,6 +547,7 @@ erc-toggle-interpret-controls
            (if erc-interpret-controls-p "ON" "OFF")))
 
 ;; Smiley
+;;;###autoload(autoload 'erc-smiley-mode "erc-goodies" nil t)
 (define-erc-module smiley nil
   "This mode translates text-smileys such as :-) into pictures.
 This requires the function `smiley-region', which is defined in
@@ -569,6 +564,7 @@ erc-smiley
     (smiley-region (point-min) (point-max))))
 
 ;; Unmorse
+;;;###autoload(autoload 'erc-unmorse-mode "erc-goodies" nil t)
 (define-erc-module unmorse nil
   "This mode causes morse code in the current channel to be unmorsed."
   ((add-hook 'erc-insert-modify-hook #'erc-unmorse))
@@ -611,3 +607,7 @@ erc-occur
 (provide 'erc-goodies)
 
 ;;; erc-goodies.el ends here
+
+;; Local Variables:
+;; generated-autoload-file: "erc-loaddefs.el"
+;; End:
diff --git a/lisp/erc/erc-ibuffer.el b/lisp/erc/erc-ibuffer.el
index 6699afe36a0..612814ac6da 100644
--- a/lisp/erc/erc-ibuffer.el
+++ b/lisp/erc/erc-ibuffer.el
@@ -32,6 +32,7 @@
 (require 'ibuffer)
 (require 'ibuf-ext)
 (require 'erc)
+(require 'erc-goodies) ; `erc-controls-interpret'
 
 (defgroup erc-ibuffer nil
   "The Ibuffer group for ERC."
diff --git a/lisp/erc/erc-page.el b/lisp/erc/erc-page.el
index 6cba59c6946..a94678e5132 100644
--- a/lisp/erc/erc-page.el
+++ b/lisp/erc/erc-page.el
@@ -30,6 +30,8 @@
 
 (require 'erc)
 
+(declare-function erc-controls-interpret "erc-goodies" (str))
+
 (defgroup erc-page nil
   "React to CTCP PAGE messages."
   :group 'erc)
@@ -70,6 +72,7 @@ erc-ctcp-query-PAGE
 This will call `erc-page-function', if defined, or it will just print
 a message and `beep'.  In addition to that, the page message is also
 inserted into the server buffer."
+  (require 'erc-goodies) ; for `erc-controls-interpret'
   (when (and erc-page-mode
 	     (string-match "PAGE\\(\\s-+.*\\)?$" msg))
     (let* ((m (match-string 1 msg))
diff --git a/lisp/erc/erc-speedbar.el b/lisp/erc/erc-speedbar.el
index 5fca14e2365..a9443e0ea17 100644
--- a/lisp/erc/erc-speedbar.el
+++ b/lisp/erc/erc-speedbar.el
@@ -36,6 +36,7 @@
 ;;; Code:
 
 (require 'erc)
+(require 'erc-goodies)
 (require 'speedbar)
 (condition-case nil (require 'dframe) (error nil))
 
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 6520e57ff3a..6acb3e0ab97 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -58,17 +58,13 @@
 
 ;;; Code:
 
-(load "erc-loaddefs" 'noerror 'nomessage)
+(eval-and-compile (load "erc-loaddefs" 'noerror 'nomessage))
 
 (require 'erc-networks)
 (require 'erc-backend)
 (require 'cl-lib)
 (require 'format-spec)
-(require 'pp)
-(require 'thingatpt)
 (require 'auth-source)
-(require 'time-date)
-(require 'iso8601)
 (eval-when-compile (require 'subr-x) (require 'url-parse))
 
 (defconst erc-version "5.5"
@@ -140,6 +136,10 @@ tabbar--local-hlf
 (defvar motif-version-string)
 (defvar gtk-version-string)
 
+(declare-function decoded-time-period "time-date" (time))
+(declare-function iso8601-parse-duration "iso8601" (string))
+(declare-function word-at-point "thingatpt" (&optional no-properties))
+
 ;; tunable connection and authentication parameters
 
 (defcustom erc-server nil
@@ -3082,6 +3082,8 @@ erc--read-time-period
       (string-to-number period))
      ;; Parse as a time spec.
      (t
+      (require 'time-date)
+      (require 'iso8601)
       (let ((time (condition-case nil
                       (iso8601-parse-duration
                        (concat (cond
@@ -6864,8 +6866,6 @@ erc-format-lag-time
     (cond (lag (format "lag:%.0f" lag))
           (t ""))))
 
-;; erc-goodies is required at end of this file.
-
 ;; TODO when ERC drops Emacs 28, replace the expressions in the format
 ;; spec below with functions.
 (defun erc-update-mode-line-buffer (buffer)
@@ -6876,7 +6876,8 @@ erc-update-mode-line-buffer
                   (?m . ,(erc-format-channel-modes))
                   (?n . ,(or (erc-current-nick) ""))
                   (?N . ,(erc-format-network))
-                  (?o . ,(or (erc-controls-strip erc-channel-topic) ""))
+                  (?o . ,(or (erc-controls-strip erc-channel-topic)
+                             ""))
                   (?p . ,(erc-port-to-string erc-session-port))
                   (?s . ,(erc-format-target-and/or-server))
                   (?S . ,(erc-format-target-and/or-network))
@@ -7414,6 +7415,4 @@ erc-handle-irc-url
 
 (provide 'erc)
 
-;; FIXME this is a temporary stopgap for Emacs 29.
-(require 'erc-goodies)
 ;;; erc.el ends here
-- 
2.39.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #7: 0005-5.6-Modify-erc-mode-map-in-module-definitions.patch --]
[-- Type: text/x-patch, Size: 9617 bytes --]

From 3c5d56edd89c4aa10f164c1e470fb49d88dcb3dd Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Thu, 19 Jan 2023 21:07:27 -0800
Subject: [PATCH 5/7] [5.6] Modify erc-mode-map in module definitions

* lisp/erc/erc-button.el (erc-button-mode, erc-button-enable,
erc-button-disable): Replace call to `erc-button-setup' with one to
`erc--modify-local-map'.  This means `erc-button-setup' is now dead
from a client perspective.
* lisp/erc/erc-goodies.el (erc-irccontrols-enable,
erc-irccontrols-disable, erc-irccontrols-mode): Bind
`erc-toggle-interpret-controls' in module definition so it's only
available when the module is active.
* lisp/erc/erc-log.el (erc-log-mode, erc-log-enable, erc-log-disable):
Move top-level `define-key' into module definition.
* lisp/erc/erc-match.el (erc-match-mode, erc-match-enable,
erc-match-disable): Move top-level `define-key' into module
definition.
* lisp/erc/erc.el (erc-mode-map): Remove C-c C-c binding for
`erc-toggle-interpret-controls'.
(erc--modify-local-map): Add helper for global modules to use when
modifying `erc-mode-map'.
* test/lisp/erc/erc-tests.el (erc--modify-local-map): Add test.
Ensure modifications to `erc-mode-map' on loading `erc' and via
`erc-mode-hook' still work.  (Bug#60954.)
---
 lisp/erc/erc-button.el     |  6 ++++--
 lisp/erc/erc-goodies.el    |  6 ++++--
 lisp/erc/erc-log.el        |  8 +++----
 lisp/erc/erc-match.el      |  8 +++----
 lisp/erc/erc.el            | 14 +++++++++++-
 test/lisp/erc/erc-tests.el | 44 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 73 insertions(+), 13 deletions(-)

diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el
index c28dddefa0e..1be56f5dc21 100644
--- a/lisp/erc/erc-button.el
+++ b/lisp/erc/erc-button.el
@@ -55,11 +55,11 @@ button
   ((add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append)
    (add-hook 'erc-send-modify-hook #'erc-button-add-buttons 'append)
    (add-hook 'erc-complete-functions #'erc-button-next-function)
-   (add-hook 'erc-mode-hook #'erc-button-setup))
+   (erc--modify-local-map t "<backtab>" #'erc-button-previous))
   ((remove-hook 'erc-insert-modify-hook #'erc-button-add-buttons)
    (remove-hook 'erc-send-modify-hook #'erc-button-add-buttons)
    (remove-hook 'erc-complete-functions #'erc-button-next-function)
-   (remove-hook 'erc-mode-hook #'erc-button-setup)))
+   (erc--modify-local-map nil "<backtab>" #'erc-button-previous)))
 
 ;;; Variables
 
@@ -233,6 +233,8 @@ erc-button-keys-added
   "Internal variable used to keep track of whether we've added the
 global-level ERC button keys yet.")
 
+;; Maybe deprecate this function and `erc-button-keys-added' if they
+;; continue to go unused for a another version (currently 5.6).
 (defun erc-button-setup ()
   "Add ERC mode-level button movement keys.  This is only done once."
   ;; Add keys.
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index 7ca155ef9d0..7ff5b1aecdf 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -377,9 +377,11 @@ erc-get-fg-color-face
 (define-erc-module irccontrols nil
   "This mode enables the interpretation of IRC control chars."
   ((add-hook 'erc-insert-modify-hook #'erc-controls-highlight)
-   (add-hook 'erc-send-modify-hook #'erc-controls-highlight))
+   (add-hook 'erc-send-modify-hook #'erc-controls-highlight)
+   (erc--modify-local-map t "C-c C-c" #'erc-toggle-interpret-controls))
   ((remove-hook 'erc-insert-modify-hook #'erc-controls-highlight)
-   (remove-hook 'erc-send-modify-hook #'erc-controls-highlight)))
+   (remove-hook 'erc-send-modify-hook #'erc-controls-highlight)
+   (erc--modify-local-map nil "C-c C-c" #'erc-toggle-interpret-controls)))
 
 (defun erc-controls-interpret (str)
    "Return a copy of STR after dealing with IRC control characters.
diff --git a/lisp/erc/erc-log.el b/lisp/erc/erc-log.el
index 2cb9031640d..a44437ddcf7 100644
--- a/lisp/erc/erc-log.el
+++ b/lisp/erc/erc-log.el
@@ -230,7 +230,8 @@ log
    ;; append, so that 'erc-initialize-log-marker runs first
    (add-hook 'erc-connect-pre-hook #'erc-log-setup-logging 'append)
    (dolist (buffer (erc-buffer-list))
-     (erc-log-setup-logging buffer)))
+     (erc-log-setup-logging buffer))
+   (erc--modify-local-map t "C-c C-l" #'erc-save-buffer-in-logs))
   ;; disable
   ((remove-hook 'erc-insert-post-hook #'erc-save-buffer-in-logs)
    (remove-hook 'erc-send-post-hook #'erc-save-buffer-in-logs)
@@ -241,9 +242,8 @@ log
    (remove-hook 'erc-part-hook #'erc-conditional-save-buffer)
    (remove-hook 'erc-connect-pre-hook #'erc-log-setup-logging)
    (dolist (buffer (erc-buffer-list))
-     (erc-log-disable-logging buffer))))
-
-(define-key erc-mode-map "\C-c\C-l" #'erc-save-buffer-in-logs)
+     (erc-log-disable-logging buffer))
+   (erc--modify-local-map nil "C-c C-l" #'erc-save-buffer-in-logs)))
 
 ;;; functionality referenced from erc.el
 (defun erc-log-setup-logging (buffer)
diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el
index 52ee5c855f3..7ec9078d493 100644
--- a/lisp/erc/erc-match.el
+++ b/lisp/erc/erc-match.el
@@ -52,8 +52,10 @@ 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)
+   (erc--modify-local-map t "C-c C-k" #'erc-go-to-log-matches-buffer))
+  ((remove-hook 'erc-insert-modify-hook #'erc-match-message)
+   (erc--modify-local-map nil "C-c C-k" #'erc-go-to-log-matches-buffer)))
 
 ;; Remaining customizations
 
@@ -647,8 +649,6 @@ erc-go-to-log-matches-buffer
 					(get-buffer (car buffer-cons))))))
     (switch-to-buffer buffer-name)))
 
-(define-key erc-mode-map "\C-c\C-k" #'erc-go-to-log-matches-buffer)
-
 (defun erc-hide-fools (match-type _nickuserhost _message)
  "Hide foolish comments.
 This function should be called from `erc-text-matched-hook'."
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 6acb3e0ab97..03bab52ac07 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1189,7 +1189,6 @@ erc-mode-map
     (define-key map [home] #'erc-bol)
     (define-key map "\C-c\C-a" #'erc-bol)
     (define-key map "\C-c\C-b" #'erc-switch-to-buffer)
-    (define-key map "\C-c\C-c" #'erc-toggle-interpret-controls)
     (define-key map "\C-c\C-d" #'erc-input-action)
     (define-key map "\C-c\C-e" #'erc-toggle-ctcp-autoresponse)
     (define-key map "\C-c\C-f" #'erc-toggle-flood-control)
@@ -1213,6 +1212,19 @@ erc-mode-map
     map)
   "ERC keymap.")
 
+(defun erc--modify-local-map (mode &rest bindings)
+  "Modify `erc-mode-map' on behalf of a global module.
+Add or remove `key-valid-p' BINDINGS when toggling MODE."
+  (declare (indent 1))
+  (while (pcase-let* ((`(,key ,def . ,rest) bindings)
+                      (existing (keymap-lookup erc-mode-map key)))
+           (if mode
+               (when (or (not existing) (eq existing #'undefined))
+                 (keymap-set erc-mode-map key def))
+             (when (eq existing def)
+               (keymap-unset erc-mode-map key t)))
+           (setq bindings rest))))
+
 ;; Faces
 
 ; Honestly, I have a horrible sense of color and the "defaults" below
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 6ab6d9ee40f..2c5c2541bca 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -492,6 +492,50 @@ erc--target-from-string
     (should (equal (erc--target-from-string "&Bitlbee")
                    #s(erc--target-channel-local "&Bitlbee" &bitlbee)))))
 
+(ert-deftest erc--modify-local-map ()
+  (when (and (bound-and-true-p erc-irccontrols-mode)
+             (fboundp 'erc-irccontrols-mode))
+    (erc-irccontrols-mode -1))
+  (when (and (bound-and-true-p erc-match-mode)
+             (fboundp 'erc-match-mode))
+    (erc-match-mode -1))
+  (let* (calls
+         (inhibit-message noninteractive)
+         (cmd-foo (lambda () (interactive) (push 'foo calls)))
+         (cmd-bar (lambda () (interactive) (push 'bar calls))))
+
+    (ert-info ("Add non-existing")
+      (erc--modify-local-map t "C-c C-c" cmd-foo "C-c C-k" cmd-bar)
+      (with-temp-buffer
+        (set-window-buffer (selected-window) (current-buffer))
+        (use-local-map erc-mode-map)
+        (execute-kbd-macro "\C-c\C-c")
+        (execute-kbd-macro "\C-c\C-k"))
+      (should (equal calls '(bar foo))))
+    (setq calls nil)
+
+    (ert-info ("Add existing") ; Attempt to swap definitions fails
+      (erc--modify-local-map t "C-c C-c" cmd-bar "C-c C-k" cmd-foo)
+      (with-temp-buffer
+        (set-window-buffer (selected-window) (current-buffer))
+        (use-local-map erc-mode-map)
+        (execute-kbd-macro "\C-c\C-c")
+        (execute-kbd-macro "\C-c\C-k"))
+      (should (equal calls '(bar foo))))
+    (setq calls nil)
+
+    (ert-info ("Remove existing")
+      (ert-with-message-capture messages
+        (erc--modify-local-map nil "C-c C-c" cmd-foo "C-c C-k" cmd-bar)
+        (with-temp-buffer
+          (set-window-buffer (selected-window) (current-buffer))
+          (use-local-map erc-mode-map)
+          (execute-kbd-macro "\C-c\C-c")
+          (execute-kbd-macro "\C-c\C-k"))
+        (should (string-search "C-c C-c is undefined" messages))
+        (should (string-search "C-c C-k is undefined" messages))
+        (should-not calls)))))
+
 (ert-deftest erc-ring-previous-command-base-case ()
   (ert-info ("Create ring when nonexistent and do nothing")
     (let (erc-input-ring
-- 
2.39.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #8: 0006-5.6-Add-missing-colors-to-erc-irccontrols-mode.patch --]
[-- Type: text/x-patch, Size: 18371 bytes --]

From a6ac60a30293b445e3080972899f7b04f26f79ae Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Fri, 9 Jul 2021 20:03:51 -0700
Subject: [PATCH 6/7] [5.6] Add missing colors to erc-irccontrols-mode

* lisp/erc/erc-goodies.el (erc-spoiler-face): Add new face.
(erc--controls-additional-colors): Add
remaining 16-99 colors.
(erc-get-bg-color-face, erc-get-fg-color-face): Look up colors in
table.
(erc-controls-remove-regexp, erc-controls-highlight-regexp): Convert
to `rx' forms and move above first use to eliminate intra-file forward
declarations.
(erc-controls-propertize): Support spoilers.
* test/lisp/erc/erc-goodies-tests.el: New file.  (Bug#60954.)
---
 lisp/erc/erc-goodies.el            |  77 +++++++--
 test/lisp/erc/erc-goodies-tests.el | 253 +++++++++++++++++++++++++++++
 2 files changed, 312 insertions(+), 18 deletions(-)
 create mode 100644 test/lisp/erc/erc-goodies-tests.el

diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index 7ff5b1aecdf..5ddacb643fd 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -30,8 +30,6 @@
 ;;; Code:
 
 (eval-when-compile (require 'cl-lib))
-(defvar erc-controls-highlight-regexp)
-(defvar erc-controls-remove-regexp)
 (require 'erc)
 
 (defun erc-imenu-setup ()
@@ -243,6 +241,12 @@ erc-inverse-face
   "ERC inverse face."
   :group 'erc-faces)
 
+(defface erc-spoiler-face
+  '((((background light)) :foreground "DimGray" :background "DimGray")
+    (((background dark)) :foreground "LightGray" :background "LightGray"))
+  "ERC spoiler face."
+  :group 'erc-faces)
+
 (defface erc-underline-face '((t :underline t))
   "ERC underline face."
   :group 'erc-faces)
@@ -345,19 +349,38 @@ bg:erc-color-face15
   "ERC face."
   :group 'erc-faces)
 
+;; https://lists.gnu.org/archive/html/emacs-erc/2021-07/msg00005.html
+(defvar erc--controls-additional-colors
+  ["#470000" "#472100" "#474700" "#324700" "#004700" "#00472c"
+   "#004747" "#002747" "#000047" "#2e0047" "#470047" "#47002a"
+   "#740000" "#743a00" "#747400" "#517400" "#007400" "#007449"
+   "#007474" "#004074" "#000074" "#4b0074" "#740074" "#740045"
+   "#b50000" "#b56300" "#b5b500" "#7db500" "#00b500" "#00b571"
+   "#00b5b5" "#0063b5" "#0000b5" "#7500b5" "#b500b5" "#b5006b"
+   "#ff0000" "#ff8c00" "#ffff00" "#b2ff00" "#00ff00" "#00ffa0"
+   "#00ffff" "#008cff" "#0000ff" "#a500ff" "#ff00ff" "#ff0098"
+   "#ff5959" "#ffb459" "#ffff71" "#cfff60" "#6fff6f" "#65ffc9"
+   "#6dffff" "#59b4ff" "#5959ff" "#c459ff" "#ff66ff" "#ff59bc"
+   "#ff9c9c" "#ffd39c" "#ffff9c" "#e2ff9c" "#9cff9c" "#9cffdb"
+   "#9cffff" "#9cd3ff" "#9c9cff" "#dc9cff" "#ff9cff" "#ff94d3"
+   "#000000" "#131313" "#282828" "#363636" "#4d4d4d" "#656565"
+   "#818181" "#9f9f9f" "#bcbcbc" "#e2e2e2" "#ffffff"])
+
 (defun erc-get-bg-color-face (n)
   "Fetches the right face for background color N (0-15)."
   (if (stringp n) (setq n (string-to-number n)))
   (if (not (numberp n))
       (prog1 'default
         (erc-error "erc-get-bg-color-face: n is NaN: %S" n))
-    (when (> n 16)
+    (when (> n 99)
       (erc-log (format "   Wrong color: %s" n))
       (setq n (mod n 16)))
     (cond
      ((and (>= n 0) (< n 16))
       (intern (concat "bg:erc-color-face" (number-to-string n))))
-     (t (erc-log (format "   Wrong color: %s" n)) 'default))))
+     ((< 15 n 99)
+      (list :background (aref erc--controls-additional-colors (- n 16))))
+     (t (erc-log (format "   Wrong color: %s" n)) '(default)))))
 
 (defun erc-get-fg-color-face (n)
   "Fetches the right face for foreground color N (0-15)."
@@ -365,13 +388,15 @@ erc-get-fg-color-face
   (if (not (numberp n))
       (prog1 'default
         (erc-error "erc-get-fg-color-face: n is NaN: %S" n))
-    (when (> n 16)
+    (when (> n 99)
       (erc-log (format "   Wrong color: %s" n))
       (setq n (mod n 16)))
     (cond
      ((and (>= n 0) (< n 16))
       (intern (concat "fg:erc-color-face" (number-to-string n))))
-     (t (erc-log (format "   Wrong color: %s" n)) 'default))))
+     ((< 15 n 99)
+      (list :foreground (aref erc--controls-additional-colors (- n 16))))
+     (t (erc-log (format "   Wrong color: %s" n)) '(default)))))
 
 ;;;###autoload(autoload 'erc-irccontrols-mode "erc-goodies" nil t)
 (define-erc-module irccontrols nil
@@ -383,6 +408,25 @@ irccontrols
    (remove-hook 'erc-send-modify-hook #'erc-controls-highlight)
    (erc--modify-local-map nil "C-c C-c" #'erc-toggle-interpret-controls)))
 
+;; These patterns were moved here to circumvent compiler warnings but
+;; otherwise translated verbatim from their original string-literal
+;; definitions (minus a small bug fix to satisfy newly added tests).
+(defvar erc-controls-remove-regexp
+  (rx (or ?\C-b ?\C-\] ?\C-_ ?\C-v ?\C-g ?\C-o
+          (: ?\C-c (? (any "0-9")) (? (any "0-9"))
+             (? (group ?, (any "0-9") (? (any "0-9")))))))
+  "Regular expression matching control characters to remove.")
+
+;; Before the change to `rx', group 3 used to be a sibling of group 2.
+;; This was assumed to be a bug.  A few minor simplifications were
+;; also performed.  If incorrect, please admonish.
+(defvar erc-controls-highlight-regexp
+  (rx (group (or ?\C-b ?\C-\] ?\C-v ?\C-_ ?\C-g ?\C-o
+                 (: ?\C-c (? (group (** 1 2 (any "0-9")))
+                             (? (group ?, (group (** 1 2 (any "0-9")))))))))
+      (group (* (not (any ?\C-b ?\C-c ?\C-g ?\n ?\C-o ?\C-v ?\C-\] ?\C-_)))))
+  "Regular expression matching control chars to highlight.")
+
 (defun erc-controls-interpret (str)
    "Return a copy of STR after dealing with IRC control characters.
 See `erc-interpret-controls-p' and `erc-interpret-mirc-color' for options."
@@ -444,16 +488,6 @@ erc-controls-strip
         (setq s (replace-match "" nil nil s)))
       s)))
 
-(defvar erc-controls-remove-regexp
-  "\C-b\\|\C-]\\|\C-_\\|\C-v\\|\C-g\\|\C-o\\|\C-c[0-9]?[0-9]?\\(,[0-9][0-9]?\\)?"
-  "Regular expression which matches control characters to remove.")
-
-(defvar erc-controls-highlight-regexp
-  (concat "\\(\C-b\\|\C-]\\|\C-v\\|\C-_\\|\C-g\\|\C-o\\|"
-          "\C-c\\([0-9][0-9]?\\)?\\(,\\([0-9][0-9]?\\)\\)?\\)"
-          "\\([^\C-b\C-]\C-v\C-_\C-c\C-g\C-o\n]*\\)")
-  "Regular expression which matches control chars and the text to highlight.")
-
 (defun erc-controls-highlight ()
   "Highlight IRC control chars in the buffer.
 This is useful for `erc-insert-modify-hook' and `erc-send-modify-hook'.
@@ -510,6 +544,13 @@ erc-controls-propertize
   "Prepend properties from IRC control characters between FROM and TO.
 If optional argument STR is provided, apply to STR, otherwise prepend properties
 to a region in the current buffer."
+  (if (and fg bg (equal fg bg))
+      (progn
+        (setq fg 'erc-spoiler-face
+              bg nil)
+        (put-text-property from to 'mouse-face 'erc-inverse-face str))
+    (when fg (setq fg (erc-get-fg-color-face fg)))
+    (when bg (setq bg (erc-get-bg-color-face bg))))
   (font-lock-prepend-text-property
    from
    to
@@ -527,10 +568,10 @@ erc-controls-propertize
                '(erc-underline-face)
              nil)
            (if fg
-               (list (erc-get-fg-color-face fg))
+               (list fg)
              nil)
            (if bg
-               (list (erc-get-bg-color-face bg))
+               (list bg)
              nil))
    str)
   str)
diff --git a/test/lisp/erc/erc-goodies-tests.el b/test/lisp/erc/erc-goodies-tests.el
new file mode 100644
index 00000000000..46fcf82401b
--- /dev/null
+++ b/test/lisp/erc/erc-goodies-tests.el
@@ -0,0 +1,253 @@
+;;; erc-goodies-tests.el --- Tests for erc-goodies  -*- 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 <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;; Code:
+(require 'ert-x)
+(require 'erc-goodies)
+(declare-function erc--initialize-markers "erc" (old-point continued) t)
+
+(defun erc-goodies-tests--assert-face (beg end-str present &optional absent)
+  (setq beg (+ beg (point-min)))
+  (let ((end (+ beg (1- (length end-str)))))
+    (while (and beg (< beg end))
+      (let* ((val (get-text-property beg 'font-lock-face))
+             (ft (flatten-tree (ensure-list val))))
+        (dolist (p (ensure-list present))
+          (if (consp p)
+              (should (member p val))
+            (should (memq p ft))))
+        (dolist (a (ensure-list absent))
+          (if (consp a)
+              (should-not (member a val))
+            (should-not (memq a ft))))
+        (setq beg (text-property-not-all beg (point-max)
+                                         'font-lock-face val))))))
+
+;; These are from the "Examples" section of
+;; https://modern.ircdocs.horse/formatting.html
+
+(ert-deftest erc-controls-highlight--examples ()
+  ;; FIXME remove after adding
+  (unless (fboundp 'erc--initialize-markers)
+    (ert-skip "Missing required function"))
+  (should (eq t erc-interpret-controls-p))
+  (let ((erc-insert-modify-hook '(erc-controls-highlight))
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
+    (with-current-buffer (get-buffer-create "#chan")
+      (erc-mode)
+      (setq-local erc-interpret-mirc-color t)
+      (erc--initialize-markers (point) nil)
+
+      (let* ((m "I love \C-c3IRC!\C-c It is the \C-c7best protocol ever!")
+             (msg (erc-format-privmessage "bob" m nil t)))
+        (erc-display-message nil nil (current-buffer) msg))
+      (forward-line -1)
+      (should (search-forward "<bob> " nil t))
+      (save-restriction
+        (narrow-to-region (point) (pos-eol))
+        (erc-goodies-tests--assert-face
+         0 "I love" 'erc-default-face 'fg:erc-color-face3)
+        (erc-goodies-tests--assert-face
+         7 " IRC!" 'fg:erc-color-face3)
+        (erc-goodies-tests--assert-face
+         11 " It is the " 'erc-default-face 'fg:erc-color-face7)
+        (erc-goodies-tests--assert-face
+         22 "best protocol ever!" 'fg:erc-color-face7))
+
+      (let* ((m "This is a \C-]\C-c13,9cool \C-cmessage")
+             (msg (erc-format-privmessage "alice" m nil t)))
+        (erc-display-message nil nil (current-buffer) msg))
+      (should (search-forward "<alice> " nil t))
+      (save-restriction
+        (narrow-to-region (point) (pos-eol))
+        (erc-goodies-tests--assert-face
+         0 "this is a " 'erc-default-face 'erc-italic-face)
+        (erc-goodies-tests--assert-face
+         10 "cool " '(erc-italic-face fg:erc-color-face13 bg:erc-color-face9))
+        (erc-goodies-tests--assert-face
+         15 "message" 'erc-italic-face
+         '(fg:erc-color-face13 bg:erc-color-face9)))
+
+      (let* ((m "IRC \C-bis \C-c4,12so \C-cgreat\C-o!")
+             (msg (erc-format-privmessage "bob" m nil t)))
+        (erc-display-message nil nil (current-buffer) msg))
+      (should (search-forward "<bob> " nil t))
+      (save-restriction
+        (narrow-to-region (point) (pos-eol))
+        (erc-goodies-tests--assert-face
+         0 "IRC " 'erc-default-face 'erc-bold-face)
+        (erc-goodies-tests--assert-face
+         4 "is " 'erc-bold-face '(fg:erc-color-face4 bg:erc-color-face12))
+        (erc-goodies-tests--assert-face
+         7 "so " '(erc-bold-face fg:erc-color-face4 bg:erc-color-face12))
+        (erc-goodies-tests--assert-face
+         10 "great" 'erc-bold-face '(fg:erc-color-face4 bg:erc-color-face12))
+        (erc-goodies-tests--assert-face
+         15 "!" 'erc-default-face 'erc-bold-face))
+
+      (let* ((m (concat "Rules: Don't spam 5\C-c13,8,6\C-c,7,8, "
+                        "and especially not \C-b9\C-b\C-]!"))
+             (msg (erc-format-privmessage "alice" m nil t)))
+        (erc-display-message nil nil (current-buffer) msg))
+      (should (search-forward "<alice> " nil t))
+      (save-restriction
+        (narrow-to-region (point) (pos-eol))
+        (erc-goodies-tests--assert-face
+         0 "Rules: Don't spam 5" 'erc-default-face
+         '(fg:erc-color-face13 bg:erc-color-face8))
+        (erc-goodies-tests--assert-face
+         19 ",6" '(fg:erc-color-face13 bg:erc-color-face8))
+        (erc-goodies-tests--assert-face
+         21 ",7,8, and especially not " 'erc-default-face
+         '(fg:erc-color-face13 bg:erc-color-face8 erc-bold-face))
+        (erc-goodies-tests--assert-face
+         44 "9" 'erc-bold-face 'erc-italic-face)
+        (erc-goodies-tests--assert-face
+         45 "!" 'erc-italic-face 'erc-bold-face))
+
+      (when noninteractive
+        (kill-buffer)))))
+
+;; Like the test above, this is most intuitive when run interactively.
+;; Hovering over the redacted area should reveal its underlying text
+;; in a high-contrast face.
+
+(ert-deftest erc-controls-highlight--inverse ()
+  ;; FIXME remove after adding
+  (unless (fboundp 'erc--initialize-markers)
+    (ert-skip "Missing required function"))
+  (should (eq t erc-interpret-controls-p))
+  (let ((erc-insert-modify-hook '(erc-controls-highlight))
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
+    (with-current-buffer (get-buffer-create "#chan")
+      (erc-mode)
+      (setq-local erc-interpret-mirc-color t)
+      (erc--initialize-markers (point) nil)
+
+      (let* ((m "Spoiler: \C-c0,0Hello\C-c1,1World!")
+             (msg (erc-format-privmessage "bob" m nil t)))
+        (erc-display-message nil nil (current-buffer) msg))
+      (forward-line -1)
+      (should (search-forward "<bob> " nil t))
+      (save-restriction
+        (narrow-to-region (point) (pos-eol))
+        (should (eq (get-text-property (+ 9 (point)) 'mouse-face)
+                    'erc-inverse-face))
+        (should (eq (get-text-property (1- (pos-eol)) 'mouse-face)
+                    'erc-inverse-face))
+        (erc-goodies-tests--assert-face
+         0 "Spoiler: " 'erc-default-face
+         '(fg:erc-color-face0 bg:erc-color-face0))
+        (erc-goodies-tests--assert-face
+         9 "Hello" '(erc-spoiler-face)
+         '( fg:erc-color-face0 bg:erc-color-face0
+            fg:erc-color-face1 bg:erc-color-face1))
+        (erc-goodies-tests--assert-face
+         18 " World" '(erc-spoiler-face)
+         '( fg:erc-color-face0 bg:erc-color-face0
+            fg:erc-color-face1 bg:erc-color-face1 )))
+      (when noninteractive
+        (kill-buffer)))))
+
+(defvar erc-goodies-tests--motd
+  ;; This is from ergo's MOTD
+  '((":- - this is \2bold text\17.")
+    (":- - this is \35italics text\17.")
+    (":- - this is \0034red\3 and \0032blue\3 text.")
+    (":- - this is \0034,12red text with a light blue background\3.")
+    (":- - this is a normal escaped dollarsign: $")
+    (":- ")
+    (":- "
+     "\0031,0 00 \0030,1 01 \0030,2 02 \0030,3 03 "
+     "\0031,4 04 \0030,5 05 \0030,6 06 \0031,7 07 ")
+    (":- "
+     "\0031,8 08 \0031,9 09 \0030,10 10 \0031,11 11 "
+     "\0030,12 12 \0031,13 13 \0031,14 14 \0031,15 15 ")
+    (":- ")
+    (":- "
+     "\0030,16 16 \0030,17 17 \0030,18 18 \0030,19 19 "
+     "\0030,20 20 \0030,21 21 \0030,22 22 \0030,23 23 "
+     "\0030,24 24 \0030,25 25 \0030,26 26 \0030,27 27 ")
+    (":- "
+     "\0030,28 28 \0030,29 29 \0030,30 30 \0030,31 31 "
+     "\0030,32 32 \0030,33 33 \0030,34 34 \0030,35 35 "
+     "\0030,36 36 \0030,37 37 \0030,38 38 \0030,39 39 ")
+    (":- "
+     "\0030,40 40 \0030,41 41 \0030,42 42 \0030,43 43 "
+     "\0030,44 44 \0030,45 45 \0030,46 46 \0030,47 47 "
+     "\0030,48 48 \0030,49 49 \0030,50 50 \0030,51 51 ")
+    (":- "
+     "\0030,52 52 \0030,53 53 \0031,54 54 \0031,55 55 "
+     "\0031,56 56 \0031,57 57 \0031,58 58 \0030,59 59 "
+     "\0030,60 60 \0030,61 61 \0030,62 62 \0030,63 63 ")
+    (":- "
+     "\0030,64 64 \0031,65 65 \0031,66 66 \0031,67 67 "
+     "\0031,68 68 \0031,69 69 \0031,70 70 \0031,71 71 "
+     "\0030,72 72 \0030,73 73 \0030,74 74 \0030,75 75 ")
+    (":- "
+     "\0031,76 76 \0031,77 77 \0031,78 78 \0031,79 79 "
+     "\0031,80 80 \0031,81 81 \0031,82 82 \0031,83 83 "
+     "\0031,84 84 \0031,85 85 \0031,86 86 \0031,87 87 ")
+    (":- "
+     "\0030,88 88 \0030,89 89 \0030,90 90 \0030,91 91 "
+     "\0030,92 92 \0030,93 93 \0030,94 94 \0030,95 95 "
+     "\0031,96 96 \0031,97 97 \0031,98 98 \399,99 99 ")
+    (":- ")))
+
+(ert-deftest erc-controls-highlight--motd ()
+  ;; FIXME remove after adding
+  (unless (fboundp 'erc--initialize-markers)
+    (ert-skip "Missing required function"))
+  (should (eq t erc-interpret-controls-p))
+  (let ((erc-insert-modify-hook '(erc-controls-highlight))
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
+    (with-current-buffer (get-buffer-create "#chan")
+      (erc-mode)
+      (setq-local erc-interpret-mirc-color t)
+      (erc--initialize-markers (point) nil)
+
+      (dolist (parts erc-goodies-tests--motd)
+        (erc-display-message nil 'notice (current-buffer) (string-join parts)))
+
+      ;; Spot check
+      (goto-char (point-min))
+      (should (search-forward " 16 " nil t))
+      (save-restriction
+        (narrow-to-region (point) (pos-eol))
+        (erc-goodies-tests--assert-face
+         0 " 17 " '(fg:erc-color-face0 (:background "#472100")))
+        (erc-goodies-tests--assert-face
+         4 " 18 " '(fg:erc-color-face0 (:background "#474700"))
+         '((:background "#472100"))))
+
+      (should (search-forward " 71 " nil t))
+      (save-restriction
+        (narrow-to-region (point) (pos-eol))
+        (erc-goodies-tests--assert-face
+         0 " 72 " '(fg:erc-color-face0 (:background "#5959ff")))
+        (erc-goodies-tests--assert-face
+         4 " 73 " '(fg:erc-color-face0 (:background "#c459ff"))
+         '((:background "#5959ff"))))
+
+      (goto-char (point-min))
+      (when noninteractive
+        (kill-buffer)))))
+
+;;; erc-goodies-tests.el ends here
-- 
2.39.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #9: 0007-5.6-Convert-ERC-s-Imenu-integration-into-proper-modu.patch --]
[-- Type: text/x-patch, Size: 5341 bytes --]

From 45e083eb4212cb77130d917a27f5f9985b08bdf7 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Thu, 19 Jan 2023 21:07:27 -0800
Subject: [PATCH 7/7] [5.6] Convert ERC's Imenu integration into proper module

TODO: add news item once a section for 5.6 has been added.

* lisp/erc/erc-goodies.el: Don't add Imenu hooks to `erc-mode-hook' at
top level. Remove autoload for `erc-create-imenu-index' because it
already exists in the `erc-imenu' library.
(erc-imenu-setup) Move to erc-imenu.
* lisp/erc/erc-imenu.el (erc-unfill-notice): Allow modifications to
read-only text.  Thanks to Yusef Aslam for reporting this bug.
(erc-imenu-setup): Move here from goodies.
(erc-imenu-mode, erc-imenu-enable, erc-imenu-disable): Create new
ERC module for Imenu.
* lisp/erc/erc.el (erc-modules): Add `imenu' to default value and
create menu item.  Update package-version.
* test/lisp/erc/erc-tests.el (erc-tests--modules): Add
`imenu'.  (Bug#60954.)
---
 lisp/erc/erc-goodies.el    |  6 ------
 lisp/erc/erc-imenu.el      | 23 ++++++++++++++++++++++-
 lisp/erc/erc.el            |  4 +++-
 test/lisp/erc/erc-tests.el |  2 +-
 4 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index 5ddacb643fd..7ea6c42ec65 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -32,12 +32,6 @@
 (eval-when-compile (require 'cl-lib))
 (require 'erc)
 
-(defun erc-imenu-setup ()
-  "Setup Imenu support in an ERC buffer."
-  (setq-local imenu-create-index-function #'erc-create-imenu-index))
-
-(add-hook 'erc-mode-hook #'erc-imenu-setup)
-(autoload 'erc-create-imenu-index "erc-imenu" "Imenu index creation function")
 
 ;;; Automatically scroll to bottom
 (defcustom erc-input-line-position nil
diff --git a/lisp/erc/erc-imenu.el b/lisp/erc/erc-imenu.el
index 6223cd3d06f..526afd32249 100644
--- a/lisp/erc/erc-imenu.el
+++ b/lisp/erc/erc-imenu.el
@@ -52,7 +52,8 @@ erc-unfill-notice
 			 (forward-line 1)
 			 (looking-at "    "))
 		  (forward-line 1))
-		(end-of-line) (point)))))
+		(end-of-line) (point))))
+	(inhibit-read-only t))
     (with-temp-buffer
       (insert str)
       (goto-char (point-min))
@@ -124,6 +125,26 @@ erc-create-imenu-index
 				  index-alist))
     index-alist))
 
+(defvar-local erc-imenu--create-index-function nil
+  "Previous local value of `imenu-create-index-function', if any.")
+
+(defun erc-imenu-setup ()
+  "Wire up support for Imenu in an ERC buffer."
+  (when (and (local-variable-p 'imenu-create-index-function)
+             imenu-create-index-function)
+    (setq erc-imenu--create-index-function imenu-create-index-function))
+  (setq imenu-create-index-function #'erc-create-imenu-index))
+
+;;;###autoload(autoload 'erc-imenu-mode "erc-imenu" nil t)
+(define-erc-module imenu nil
+  "Simple Imenu integration for ERC."
+  ((add-hook 'erc-mode-hook #'erc-imenu-setup))
+  ((remove-hook 'erc-mode-hook #'erc-imenu-setup)
+   (erc-with-all-buffers-of-server erc-server-process nil
+     (when erc-imenu--create-index-function
+       (setq imenu-create-index-function erc-imenu--create-index-function)
+       (kill-local-variable 'erc-imenu--create-index-function)))))
+
 (provide 'erc-imenu)
 
 ;;; erc-imenu.el ends here
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 03bab52ac07..1315bce2e54 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1834,7 +1834,7 @@ erc-migrate-modules
 
 (defcustom erc-modules '(netsplit fill button match track completion readonly
                                   networks ring autojoin noncommands irccontrols
-                                  move-to-prompt stamp menu list)
+                                  move-to-prompt stamp menu list imenu)
   "A list of modules which ERC should enable.
 If you set the value of this without using `customize' remember to call
 \(erc-update-modules) after you change it.  When using `customize', modules
@@ -1891,6 +1891,7 @@ erc-modules
     (const :tag "dcc: Provide Direct Client-to-Client support" dcc)
     (const :tag "fill: Wrap long lines" fill)
     (const :tag "identd: Launch an identd server on port 8113" identd)
+    (const :tag "imenu: A simple Imenu integration" imenu)
     (const :tag "irccontrols: Highlight or remove IRC control characters"
            irccontrols)
     (const :tag "keep-place: Leave point above un-viewed text" keep-place)
@@ -1928,6 +1929,7 @@ erc-modules
     (const :tag "unmorse: Translate morse code in messages" unmorse)
     (const :tag "xdcc: Act as an XDCC file-server" xdcc)
     (repeat :tag "Others" :inline t symbol))
+  :package-version '(ERC . "5.6") ; FIXME sync on release
   :group 'erc)
 
 (defun erc-update-modules ()
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 2c5c2541bca..3e844f45f1a 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -1270,7 +1270,7 @@ erc-handle-irc-url
 
 (defconst erc-tests--modules
   '( autoaway autojoin button capab-identify completion dcc fill identd
-     irccontrols keep-place list log match menu move-to-prompt netsplit
+     imenu irccontrols keep-place list log match menu move-to-prompt netsplit
      networks noncommands notifications notify page readonly
      replace ring sasl scrolltobottom services smiley sound
      spelling stamp track truncate unmorse xdcc))
-- 
2.39.2


  parent reply	other threads:[~2023-03-09 14:43 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <87pmb9wyz6.fsf@neverwas.me>
2023-01-20  7:16 ` bug#60954: 29.0.60; ERC 5.4.1: loading ERC clobbers customizations to erc-mode-hook Eli Zaretskii
     [not found] ` <83cz79oeu7.fsf@gnu.org>
2023-01-20 14:14   ` J.P.
2023-01-20 14:15 ` J.P.
2023-01-21 15:03 ` bug#60954: 30.0.50; ERC >5.5: Stop requiring erc-goodies in erc.el J.P.
2023-01-31 15:27 ` J.P.
2023-02-07 15:22 ` J.P.
2023-02-19 15:07 ` J.P.
2023-03-09 14:43 ` J.P. [this message]
2023-03-14 13:32 ` J.P.
2023-03-15 14:04 ` J.P.

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='87cz5ikmsx.fsf__41530.8781013422$1678373057$gmane$org@neverwas.me' \
    --to=jp@neverwas.me \
    --cc=60954@debbugs.gnu.org \
    --cc=emacs-erc@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).