aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorF. Jason Park2023-11-26 18:24:48 -0800
committerF. Jason Park2023-12-17 20:17:55 -0800
commit49bfea4386fd8d1a0885ebfd1f81cc8fee6ef136 (patch)
tree5d3905a4ac4ea43b10d5aebc438f79a2c9f6515c
parent236a416be76cbb0b79ad46c06652f6cbf8788fcb (diff)
downloademacs-49bfea4386fd8d1a0885ebfd1f81cc8fee6ef136.tar.gz
emacs-49bfea4386fd8d1a0885ebfd1f81cc8fee6ef136.zip
Use templates for formatting chat messages in ERC
* doc/misc/erc.texi: Replace option `erc-format-nick-function' with `erc-show-speaker-membership-status'. * etc/ERC-NEWS: Mention shift to template-based speaker formatting. Also mention in-buffer STATUSMSG support and various name changes and new formatting-related options. * lisp/erc/erc-backend.el (erc-format-privmessage): Remove forward declaration. (erc--determine-speaker-message-format-args): Add forward declaration. (erc--statusmsg-target): New utility function for detecting whether the current target is status-prefixed. (erc-current-message-catalog): Move here from lisp/erc/erc.el. (erc--message-speaker-catalog): New variable. (erc--speaker-status-prefix-wanted-p): New variable specifically for the function `erc-format-@nick' to signal it wants status-prefixes prepended to the displayed nick. (erc-server-PRIVMSG): Initialize `let'-bound value of `erc--msg-prop-overrides' to a dummy `erc--tmp' property with a null value that `erc-display-message' will "strip" before calling its hooks. Move away from the rather blunt symbol `msg' as a useful value for `erc--msg'. Instead, allow `erc-display-message' to assign the most appropriate value based on context. Also, bind the variable `erc-current-message-catalog' to whatever the buffer's `erc--message-speaker-catalog' happens to be. Future internal modules can set this to alternative catalogs as needed. Additionally, detect STATUSMSG prefixes on targets and inform the formatting logic of the verdict. Lastly, and most importantly, use the function `erc--determine-speaker-message-format-args' instead of `erc-format-privmessage' for message formatting. Pass along the returned "catalog key" and spec parameters to `erc-display-message'. However, for NOTICEs, continue to render the string, as before, for the two "echo notice" hooks. * lisp/erc/erc-common.el (erc--ctcp-response): New "subsclass" of `erc-response' for smuggling extra information to CTCP query handlers in a mostly backwards-compatible way. The same approach could be taken with the "echo notice" hooks mentioned above. * lisp/erc/erc-dcc.el (erc-dcc-chat-filter): Add `erc--spkr' and `erc--speaker' properties even though these chat buffers are not `erc-mode' buffers. * lisp/erc/erc-fill.el (erc-fill--wrap-last-msg, erc-fill--wrap-max-lull): Add doc strings. (erc-fill--wrap-continued-message-p): Rework to look for `erc--spkr'- `erc--msg' combinations as indicators of speaker continuity. (erc-fill--wrap-rejigger-region): Remove reference to the no longer relevant `erc-stamp-type'. Instead, use the `erc--msg' property combined with the `erc-timestamp' field to detect date stamps because all are currently left-sided. * lisp/erc/erc-stamp.el (erc-stamp--propertize-left-date-stamp): Don't add superfluous `erc-stamp-type' property. * lisp/erc/erc.el (erc--msg-props): Revise purpose and meaning of `erc--msg' by removing possible value `msg', which was previously meant to indicate that a message had a "speaker". Instead, rely on the separate `erc--spkr' property to convey this information, with `erc--msg' now expressing a "type" or "role". (erc--use-language-catalog-for-ctcp-action-p): New variable, a compatibility switch to help transition from the `ACTION' entry of the language catalog to the `ctcp-action' family of entries in the new `-speaker' catalog. (erc--ensure-spkr-prop): Update to include any passed-in environmental overrides. (erc--send-action-display): Restore pre-5.6 behavior when compatibility flag enabled. Otherwise, use new `-speaker' catalog for formatting inserted message. (erc--send-message-external): Overhaul to behave more faithfully in mimicking a line submitted at the prompt of the current target buffer. (erc--own-property-names): Remove `erc-stamp-type'. (erc-ensure-target-buffer-on-privmsg): Add new choice variant for old default behavior and change meaning of default to mean "except for STATUSMSGs". This option is newly revived for ERC 5.6. (erc--message-speaker-statusmsg, erc--message-speaker-statusmsg-input, erc--message-speaker-input, erc--message-speaker-input-chan-privmsg, erc--message-speaker-input-chan-notice, erc--message-speaker-input-query-privmsg, erc--message-speaker-input-query-notice, erc--message-speaker-chan-privmsg, erc--message-speaker-query-privmsg, erc--message-speaker-chan-notice, erc--message-speaker-query-notice, erc--message-speaker-ctcp-action, erc--message-speaker-ctcp-action-input, erc--message-speaker-ctcp-action-statusmsg, erc--message-speaker-ctcp-action-statusmsg-input): New variables for new `speaker' format-template catalog. (erc--speakerize-nick): New helper function. (erc--determine-speaker-message-format-args): New function to find the appropriate format key from various contextual parameters. Could become the default of a function-valued variable for internal use. (erc-show-speaker-membership-status): New option. (erc-format-nick-function, erc-speaker-from-channel-member-function): Declare former as an obsolete alias for the latter, and redefine purpose slightly. (erc-format-nick-function, erc-determine-speaker-from-user): Rename former to latter and obsolete the old name. (erc-format-nick, erc-determine-speaker-from-user): Rename former to latter and obsolete old name. (erc-format-@nick): Deprecate and adapt for use with new template-based formatting paradigm. (erc-format-my-nick): Move `erc-speaker' text prop toward head of list, meaning it will end up beneath `font-lock-face' in the final output. (erc--format-speaker-input-message): New function to replace `erc-format-my-nick' in-tree. (erc-process-ctcp-query): Don't bind `erc--msg' to `msg'. Instead, rely on `erc-display-message' to set it to the current template key. (erc-ctcp-query-ACTION): Prefer using formatting template, but attempt to simulate pre-5.6 behavior when compatibility flag enabled. (erc-display-msg): Use `erc--format-speaker-input-message' instead of `erc-format-my-nick'. Ignore `erc--msg-prop-overrides' with null values. (erc-current-message-catalog): Move to erc-backend.el. * test/lisp/erc/erc-scenarios-base-statusmsg.el: New file. * test/lisp/erc/erc-scenarios-stamp.el (erc-scenarios-stamp--left/display-margin-mode): Expect format catalog key instead of unhelpful `msg' as value of `erc--msg' prop. * test/lisp/erc/erc-tests.el (erc-message): Render format template in mock function and expect string in assertions. (erc-tests--format-privmessage): New function, a helper for the following test. (erc-format-privmessage, erc--determine-speaker-message-format-args): Rename former to latter and suppress deprecation warning. (erc--determine-speaker-message-format-args/queries, erc--determine-speaker-message-format-args/queries-as-channel): New tests. (erc-tests--format-my-nick): New helper function for the following test. (erc--format-speaker-input-message): New test. * test/lisp/erc/resources/base/display-message/statusmsg.eld: New file. (Bug#67677) ; * test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld: Update. ; * test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-post-01.eld: ; Update. ; * test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-pre-01.eld: ; Update.
-rw-r--r--doc/misc/erc.texi11
-rw-r--r--etc/ERC-NEWS52
-rw-r--r--lisp/erc/erc-backend.el124
-rw-r--r--lisp/erc/erc-common.el17
-rw-r--r--lisp/erc/erc-dcc.el6
-rw-r--r--lisp/erc/erc-fill.el69
-rw-r--r--lisp/erc/erc-stamp.el3
-rw-r--r--lisp/erc/erc.el394
-rw-r--r--test/lisp/erc/erc-scenarios-base-statusmsg.el103
-rw-r--r--test/lisp/erc/erc-scenarios-stamp.el2
-rw-r--r--test/lisp/erc/erc-tests.el231
-rw-r--r--test/lisp/erc/resources/base/display-message/statusmsg.eld47
-rw-r--r--test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld2
-rw-r--r--test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-post-01.eld2
-rw-r--r--test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-pre-01.eld2
15 files changed, 898 insertions, 167 deletions
diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi
index 94081b79099..131e02555d1 100644
--- a/doc/misc/erc.texi
+++ b/doc/misc/erc.texi
@@ -918,16 +918,11 @@ In the latter case, if the first nick in the list is already in use,
918other nicks are tried in the list order. 918other nicks are tried in the list order.
919@end defopt 919@end defopt
920 920
921@defopt erc-format-nick-function 921@defopt erc-show-speaker-membership-status
922A function to format a nickname for message display 922A boolean for including a channel member's @dfn{status prefix} in
923 923their display name when they speak.
924You can set this to @code{erc-format-@@nick} to display user mode prefix
925@end defopt 924@end defopt
926 925
927@example
928(setq erc-format-nick-function 'erc-format-@@nick)
929@end example
930
931@defopt erc-nick-uniquifier 926@defopt erc-nick-uniquifier
932The string to append to the nick if it is already in use. 927The string to append to the nick if it is already in use.
933@end defopt 928@end defopt
diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index 146f6690c5e..ed3634614a0 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -275,6 +275,26 @@ buffers. In channels, it's grown to include all letters and their
275possibly truncated arguments, with the exception of stateful list 275possibly truncated arguments, with the exception of stateful list
276modes, like "b". 276modes, like "b".
277 277
278** In-buffer "status messages" are now a thing.
279The ancient option 'erc-ensure-target-buffer-on-privmsg' has been
280repurposed slightly to express a third state denoted by the symbol
281'status'. It tells ERC to revert to the old default behavior in which
282separate, "pseudo" target buffers for status-prefixed conversing
283co-existed alongside actual target buffers. Instead of this awkward
284arrangement, ERC now acts like other clients by default and inserts
285so-called "status messages" in situ, right between other messages.
286Similar insertion-routing behavior now also applies to CTCP ACTIONs
287directed at status-prefixed channels. Unfortunately, outgoing "/msg
288@#chan hi" messages aren't yet shown in the same fashion, but the
289groundwork has been laid, making such an addition almost trivial.
290
291** An easier way to see channel-membership prefixes on speakers.
292The option 'erc-format-@nick' has been deprecated in favor of the new
293boolean option 'erc-show-speaker-membership-status', a simple switch
294to enable the displaying of status prefixes on the speaker nicks of
295incoming chat messages. Prefixes on your speaker nick for outgoing
296chat messages continue to always be present.
297
278** Miscellaneous UX changes. 298** Miscellaneous UX changes.
279Some minor quality-of-life niceties have finally made their way to 299Some minor quality-of-life niceties have finally made their way to
280ERC. For example, fool visibility has become togglable with the new 300ERC. For example, fool visibility has become togglable with the new
@@ -533,6 +553,38 @@ The functions 'erc-define-catalog-entry' and 'erc-define-catalog' have
533been deprecated in favor of 'erc-define-message-format-catalog', a new 553been deprecated in favor of 'erc-define-message-format-catalog', a new
534macro for defining template "catalogs" at the top level of libraries. 554macro for defining template "catalogs" at the top level of libraries.
535 555
556*** Interface for determining display names renamed.
557The option 'erc-format-nick-function' has been renamed to
558'erc-speaker-from-channel-member-function' to better reflect its
559actual role. So too has the related function 'erc-format-nick', which
560is now 'erc-determine-speaker-from user'.
561
562*** A template-based approach to formatting inserted chat messages.
563Predicting and influencing how ERC formats messages containing a
564leading "<speaker>" has never been straightforward. The characters
565bracketing the speaker and the faces used for each component have
566always been hard-coded, with 'erc-format-query-as-channel-p' being the
567only knob of any consequence. With this release, ERC begins its
568transition to a unified formatting paradigm that builds upon the
569already familiar "language catalog" templating system. Using a
570separate "speaker catalog" keyed by contextual symbols, like
571'query-privmsg', ERC (and eventually everyone) will more easily be
572able to influence how inserted messages take shape in buffers.
573
574*** New format templates for inserted CTCP ACTION messages.
575In 5.5 and earlier, ERC displayed outgoing CTCP ACTION messages in
576'erc-input-face' alone (before buttonizing). Incoming ACTION messages
577mirrored this, except with 'erc-action-face' throughout. Going
578forward, inserted outgoing "/ME" messages will also incorporate
579'erc-action-face', only underneath 'erc-input-face', with
580'erc-my-nick-face' sitting atop both in the leading "speaker" nickname
581portion (again, pre-buttonizing). This new behavior sidesteps the
582traditional format template 'erc-message-english-ACTION' from the
583default "language catalog" in favor of an entry from the new internal
584"speaker catalog". Users needing to access the old behavior can do so
585by toggling a provided compatibility switch. See source code around
586the function 'erc-send-action' for details.
587
536*** Miscellaneous changes 588*** Miscellaneous changes
537Two helper macros from GNU ELPA's Compat library are now available to 589Two helper macros from GNU ELPA's Compat library are now available to
538third-party modules as 'erc-compat-call' and 'erc-compat-function'. 590third-party modules as 'erc-compat-call' and 'erc-compat-function'.
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 0f6f7e2d4c3..1aee8cff345 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -142,7 +142,6 @@
142(declare-function erc-display-server-message "erc" (_proc parsed)) 142(declare-function erc-display-server-message "erc" (_proc parsed))
143(declare-function erc-emacs-time-to-erc-time "erc" (&optional specified-time)) 143(declare-function erc-emacs-time-to-erc-time "erc" (&optional specified-time))
144(declare-function erc-format-message "erc" (msg &rest args)) 144(declare-function erc-format-message "erc" (msg &rest args))
145(declare-function erc-format-privmessage "erc" (nick msg privp msgp))
146(declare-function erc-get-buffer "erc" (target &optional proc)) 145(declare-function erc-get-buffer "erc" (target &optional proc))
147(declare-function erc-handle-login "erc" nil) 146(declare-function erc-handle-login "erc" nil)
148(declare-function erc-handle-user-status-change "erc" (type nlh &optional l)) 147(declare-function erc-handle-user-status-change "erc" (type nlh &optional l))
@@ -173,6 +172,9 @@
173(declare-function erc-update-mode-line-buffer "erc" (buffer)) 172(declare-function erc-update-mode-line-buffer "erc" (buffer))
174(declare-function erc-wash-quit-reason "erc" (reason nick login host)) 173(declare-function erc-wash-quit-reason "erc" (reason nick login host))
175 174
175(declare-function erc--determine-speaker-message-format-args "erc"
176 (nick target message queryp privmsgp statusmsgp inputp
177 &optional prefix disp-nick))
176(declare-function erc-display-message "erc" 178(declare-function erc-display-message "erc"
177 (parsed type buffer msg &rest args)) 179 (parsed type buffer msg &rest args))
178(declare-function erc-get-buffer-create "erc" 180(declare-function erc-get-buffer-create "erc"
@@ -1906,6 +1908,66 @@ add things to `%s' instead."
1906 ?s (if (/= erc-server-lag 1) "s" ""))) 1908 ?s (if (/= erc-server-lag 1) "s" "")))
1907 (erc-update-mode-line)))) 1909 (erc-update-mode-line))))
1908 1910
1911(defun erc--statusmsg-target (target)
1912 "Return actual target from given TARGET if it has a leading prefix char."
1913 (and-let* ((erc-ensure-target-buffer-on-privmsg)
1914 ((not (eq erc-ensure-target-buffer-on-privmsg 'status)))
1915 ((not (erc-channel-p target)))
1916 (chars (erc--get-isupport-entry 'STATUSMSG 'single))
1917 ((string-search (string (aref target 0)) chars))
1918 (trimmed (substring target 1))
1919 ((erc-channel-p trimmed)))
1920 trimmed))
1921
1922;; Moved to this file from erc.el in ERC 5.6.
1923(defvar-local erc-current-message-catalog 'english
1924 "Current language or context catalog for formatting inserted messages.
1925See `erc-format-message'.")
1926
1927;; This variable can be made public if the current design proves
1928;; sufficient.
1929(defvar erc--message-speaker-catalog '-speaker
1930 "The \"speaker\" catalog symbol used to format PRIVMSGs and NOTICEs.
1931
1932This symbol defines a \"catalog\" of variables and functions
1933whose names reflect their membership via a corresponding CATALOG
1934component, as in \"erc-message-CATALOG-KEY\". Here, KEY refers
1935to a common set of interface members (variables or functions),
1936that an implementer must define:
1937
1938- `statusmsg' and `statusmsg-input': PRIVMSGs whose target is a
1939 status-prefixed channel; the latter is the \"echoed\" version
1940
1941- `chan-privmsg', `query-privmsg', `chan-notice', `query-notice':
1942 standard chat messages traditionally prefixed by a <nickname>
1943 indicating the message's \"speaker\"
1944
1945- `input-chan-privmsg', `input-query-privmsg', `input-query-notice',
1946 `input-chan-notice': \"echoed\" versions of the above
1947
1948- `ctcp-action', `ctcp-action-input', `ctcp-action-statusmsg',
1949 `ctcp-action-statusmsg-input': \"CTCP ACTION\" versions of the
1950 above
1951
1952The other part of this interface is the per-key collection of
1953`format-spec' parameters members must support. For simplicity,
1954this catalog currently defines a common set for all keys, some of
1955which may be assigned the empty string when not applicable:
1956
1957 %n - nickname
1958 %m - message body
1959 %p - nickname's status prefix (when applicable)
1960 %s - current target's STATUSMSG prefix (when applicable)
1961
1962As an added means of communicating with various modules, if this
1963catalog's symbol has the property `erc--msg-prop-overrides',
1964consumers calling `erc-display-message' will see the value added
1965to the `erc--msg-props' \"environment\" in modification hooks,
1966like `erc-insert-modify-hook'.")
1967
1968(defvar erc--speaker-status-prefix-wanted-p (gensym "erc-")
1969 "Sentinel to detect whether `erc-format-@nick' has just run.")
1970
1909(define-erc-response-handler (PRIVMSG NOTICE) 1971(define-erc-response-handler (PRIVMSG NOTICE)
1910 "Handle private messages, including messages in channels." nil 1972 "Handle private messages, including messages in channels." nil
1911 (let ((sender-spec (erc-response.sender parsed)) 1973 (let ((sender-spec (erc-response.sender parsed))
@@ -1927,12 +1989,15 @@ add things to `%s' instead."
1927 (msgp (string= cmd "PRIVMSG")) 1989 (msgp (string= cmd "PRIVMSG"))
1928 (noticep (string= cmd "NOTICE")) 1990 (noticep (string= cmd "NOTICE"))
1929 ;; S.B. downcase *both* tgt and current nick 1991 ;; S.B. downcase *both* tgt and current nick
1930 (privp (erc-current-nick-p tgt)) 1992 (medown (erc-downcase (erc-current-nick)))
1993 (inputp (string= medown (erc-downcase nick)))
1994 (privp (string= (erc-downcase tgt) medown))
1931 (erc--display-context `((erc-buffer-display . ,(intern cmd)) 1995 (erc--display-context `((erc-buffer-display . ,(intern cmd))
1932 ,@erc--display-context)) 1996 ,@erc--display-context))
1933 (erc--msg-prop-overrides `((erc--msg . msg) 1997 (erc--msg-prop-overrides `((erc--tmp) ,@erc--msg-prop-overrides))
1934 ,@erc--msg-prop-overrides)) 1998 (erc--speaker-status-prefix-wanted-p nil)
1935 s buffer 1999 (erc-current-message-catalog erc--message-speaker-catalog)
2000 s buffer statusmsg cmem-prefix
1936 fnick) 2001 fnick)
1937 (setq buffer (erc-get-buffer (if privp nick tgt) proc)) 2002 (setq buffer (erc-get-buffer (if privp nick tgt) proc))
1938 ;; Even worth checking for empty target here? (invalid anyway) 2003 ;; Even worth checking for empty target here? (invalid anyway)
@@ -1950,9 +2015,14 @@ add things to `%s' instead."
1950 (push `(erc-receive-query-display . ,(intern cmd)) 2015 (push `(erc-receive-query-display . ,(intern cmd))
1951 erc--display-context) 2016 erc--display-context)
1952 (setq buffer (erc--open-target nick))) 2017 (setq buffer (erc--open-target nick)))
1953 ;; A channel buffer has been killed but is still joined. 2018 (cond
1954 (when erc-ensure-target-buffer-on-privmsg 2019 ;; Target is a channel and contains leading @+ chars.
1955 (setq buffer (erc--open-target tgt))))) 2020 ((and-let* ((trimmed(erc--statusmsg-target tgt)))
2021 (setq buffer (erc-get-buffer trimmed proc)
2022 statusmsg (and buffer (substring tgt 0 1)))))
2023 ;; A channel buffer has been killed but is still joined.
2024 (erc-ensure-target-buffer-on-privmsg
2025 (setq buffer (erc--open-target tgt))))))
1956 (when buffer 2026 (when buffer
1957 (with-current-buffer buffer 2027 (with-current-buffer buffer
1958 (when privp (erc--unhide-prompt)) 2028 (when privp (erc--unhide-prompt))
@@ -1963,36 +2033,46 @@ add things to `%s' instead."
1963 privp nil nil nil nil nil host login nil nil t) 2033 privp nil nil nil nil nil host login nil nil t)
1964 (defvar erc--cmem-from-nick-function) 2034 (defvar erc--cmem-from-nick-function)
1965 (defvar erc-format-nick-function) 2035 (defvar erc-format-nick-function)
2036 (defvar erc-show-speaker-membership-status)
2037 (defvar erc-speaker-from-channel-member-function)
1966 (let ((cdata (funcall erc--cmem-from-nick-function 2038 (let ((cdata (funcall erc--cmem-from-nick-function
1967 (erc-downcase nick) sndr parsed))) 2039 (erc-downcase nick) sndr parsed)))
1968 (setq fnick (funcall erc-format-nick-function 2040 (setq fnick (funcall erc-speaker-from-channel-member-function
1969 (car cdata) (cdr cdata)))))) 2041 (car cdata) (cdr cdata))
2042 cmem-prefix (and (or erc--speaker-status-prefix-wanted-p
2043 erc-show-speaker-membership-status
2044 inputp)
2045 (cdr cdata))))))
1970 (cond 2046 (cond
1971 ((erc-is-message-ctcp-p msg) 2047 ((erc-is-message-ctcp-p msg)
1972 (setq s (if msgp 2048 ;; FIXME explain undefined return values being assigned to `s'.
2049 (setq s (if-let ((parsed
2050 (erc--ctcp-response-from-parsed
2051 :parsed parsed :buffer buffer :statusmsg statusmsg
2052 :prefix cmem-prefix :dispname fnick))
2053 (msgp))
1973 (erc-process-ctcp-query proc parsed nick login host) 2054 (erc-process-ctcp-query proc parsed nick login host)
1974 (erc-process-ctcp-reply proc parsed nick login host 2055 (erc-process-ctcp-reply proc parsed nick login host
1975 (match-string 1 msg))))) 2056 (match-string 1 msg)))))
1976 (t 2057 (t
1977 (setq erc-server-last-peers (cons nick (cdr erc-server-last-peers))) 2058 (setq erc-server-last-peers (cons nick (cdr erc-server-last-peers)))
1978 (defvar erc-format-query-as-channel-p) 2059 (with-current-buffer (or buffer (current-buffer))
1979 (setq s (erc-format-privmessage 2060 ;; Re-bind in case either buffer has a local value.
1980 (or fnick nick) msg 2061 (let ((erc-current-message-catalog erc--message-speaker-catalog))
1981 ;; If buffer is a query buffer, 2062 (setq s (erc--determine-speaker-message-format-args
1982 ;; format the nick as for a channel. 2063 nick msg privp msgp inputp statusmsg
1983 (and (not (and buffer 2064 cmem-prefix fnick))))))
1984 (erc-query-buffer-p buffer)
1985 erc-format-query-as-channel-p))
1986 privp)
1987 msgp))))
1988 (when s 2065 (when s
1989 (if (and noticep privp) 2066 (if (and noticep privp)
1990 (progn 2067 (progn
2068 (push (cons 'erc--msg (car s)) erc--msg-prop-overrides)
2069 (setq s (apply #'erc-format-message s))
1991 (run-hook-with-args 'erc-echo-notice-always-hook 2070 (run-hook-with-args 'erc-echo-notice-always-hook
1992 s parsed buffer nick) 2071 s parsed buffer nick)
1993 (run-hook-with-args-until-success 2072 (run-hook-with-args-until-success
1994 'erc-echo-notice-hook s parsed buffer nick)) 2073 'erc-echo-notice-hook s parsed buffer nick))
1995 (erc-display-message parsed nil buffer s))))))) 2074 (apply #'erc-display-message parsed nil buffer
2075 (ensure-list s))))))))
1996 2076
1997(define-erc-response-handler (QUIT) 2077(define-erc-response-handler (QUIT)
1998 "Another user has quit IRC." nil 2078 "Another user has quit IRC." nil
diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el
index 90112ab9126..0b865387671 100644
--- a/lisp/erc/erc-common.el
+++ b/lisp/erc/erc-common.el
@@ -100,6 +100,23 @@
100 (contents "" :type string) 100 (contents "" :type string)
101 (tags '() :type list)) 101 (tags '() :type list))
102 102
103(cl-defstruct (erc--ctcp-response
104 (:include erc-response)
105 (:constructor
106 erc--ctcp-response-from-parsed
107 (&key parsed buffer statusmsg prefix dispname
108 &aux (unparsed (erc-response.unparsed parsed))
109 (sender (erc-response.sender parsed))
110 (command (erc-response.command parsed))
111 (command-args (erc-response.command-args parsed))
112 (contents (erc-response.contents parsed))
113 (tags (erc-response.tags parsed)))))
114 "Data for a processed CTCP query or reply."
115 (buffer nil :type (or buffer null))
116 (statusmsg nil :type (or null string))
117 (prefix nil :type (or erc-channel-user null))
118 (dispname nil :type (or string null)))
119
103(cl-defstruct erc--isupport-data 120(cl-defstruct erc--isupport-data
104 "Abstract \"class\" for parsed ISUPPORT data. 121 "Abstract \"class\" for parsed ISUPPORT data.
105For use with the macro `erc--with-isupport-data'." 122For use with the macro `erc--with-isupport-data'."
diff --git a/lisp/erc/erc-dcc.el b/lisp/erc/erc-dcc.el
index ac7fc817cb9..d12ebd33a86 100644
--- a/lisp/erc/erc-dcc.el
+++ b/lisp/erc/erc-dcc.el
@@ -1251,14 +1251,16 @@ other client."
1251(defun erc-dcc-chat-parse-output (proc str) 1251(defun erc-dcc-chat-parse-output (proc str)
1252 (save-match-data 1252 (save-match-data
1253 (let ((posn 0) 1253 (let ((posn 0)
1254 (erc--msg-prop-overrides `((erc--spkr . ,erc-dcc-from)))
1255 (nick (propertize (erc--speakerize-nick erc-dcc-from)
1256 'font-lock-face 'erc-nick-default-face))
1254 line) 1257 line)
1255 (while (string-match "\n" str posn) 1258 (while (string-match "\n" str posn)
1256 (setq line (substring str posn (match-beginning 0))) 1259 (setq line (substring str posn (match-beginning 0)))
1257 (setq posn (match-end 0)) 1260 (setq posn (match-end 0))
1258 (erc-display-message 1261 (erc-display-message
1259 nil nil proc 1262 nil nil proc
1260 'dcc-chat-privmsg ?n (propertize erc-dcc-from 'font-lock-face 1263 'dcc-chat-privmsg ?n nick ?m line))
1261 'erc-nick-default-face) ?m line))
1262 (setq erc-dcc-unprocessed-output (substring str posn))))) 1264 (setq erc-dcc-unprocessed-output (substring str posn)))))
1263 1265
1264(defun erc-dcc-chat-buffer-killed () 1266(defun erc-dcc-chat-buffer-killed ()
diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el
index 0c2be4b5bc9..b17f571d3c0 100644
--- a/lisp/erc/erc-fill.el
+++ b/lisp/erc/erc-fill.el
@@ -546,42 +546,38 @@ behavior of taking the length from the first \"word\". This
546variable can be converted to a public one if needed by third 546variable can be converted to a public one if needed by third
547parties.") 547parties.")
548 548
549(defvar-local erc-fill--wrap-last-msg nil) 549(defvar-local erc-fill--wrap-last-msg nil "Marker for merging speakers.")
550(defvar erc-fill--wrap-max-lull (* 24 60 60)) 550(defvar erc-fill--wrap-max-lull (* 24 60 60) "Max secs for merging speakers.")
551 551
552(defun erc-fill--wrap-continued-message-p () 552(defun erc-fill--wrap-continued-message-p ()
553 "Return non-nil when the current speaker hasn't changed. 553 "Return non-nil when the current speaker hasn't changed.
554That is, indicate whether the text just inserted is from the same 554But only if the `erc--msg' text property also hasn't. That is,
555sender as that of the previous \"PRIVMSG\". As a side effect, 555indicate whether the chat message just inserted is from the same
556advance `erc-fill--wrap-last-msg' unless the message has been 556person as the prior one and is formatted in the same manner. As
557marked as being ephemeral." 557a side effect, advance `erc-fill--wrap-last-msg' unless the
558 (and 558message has been marked `erc--ephemeral'."
559 (not (erc--check-msg-prop 'erc--ephemeral)) 559 (and-let*
560 (progn ; preserve blame for now, unprogn on next major change 560 (((not (erc--check-msg-prop 'erc--ephemeral)))
561 (prog1 561 ;; Always set/move `erc-fill--wrap-last-msg' from here on down.
562 (and-let* 562 (m (or (and erc-fill--wrap-last-msg
563 ((m (or erc-fill--wrap-last-msg 563 (prog1 (marker-position erc-fill--wrap-last-msg)
564 (setq erc-fill--wrap-last-msg (point-min-marker)) 564 (set-marker erc-fill--wrap-last-msg (point-min))))
565 nil)) 565 (ignore (setq erc-fill--wrap-last-msg (point-min-marker)))))
566 ((< (1+ (point-min)) (- (point) 2))) 566 ((>= (point) 4)) ; skip the first message
567 (props (save-restriction 567 (props (save-restriction
568 (widen) 568 (widen)
569 (and-let* 569 (and-let* ((speaker (get-text-property m 'erc--spkr))
570 ((speaker (get-text-property m 'erc--spkr)) 570 (type (get-text-property m 'erc--msg))
571 ((not (eq (get-text-property m 'erc--ctcp) 571 ((not (invisible-p m))))
572 'ACTION))) 572 (list (get-text-property m 'erc--ts) type speaker))))
573 ((not (invisible-p m)))) 573 (ts (nth 0 props))
574 (cons (get-text-property m 'erc--ts) speaker)))) 574 (type (nth 1 props))
575 (ts (pop props)) 575 (speaker (nth 2 props))
576 (props) 576 ((not (time-less-p (erc-stamp--current-time) ts)))
577 ((not (time-less-p (erc-stamp--current-time) ts))) 577 ((time-less-p (time-subtract (erc-stamp--current-time) ts)
578 ((time-less-p (time-subtract (erc-stamp--current-time) ts) 578 erc-fill--wrap-max-lull))
579 erc-fill--wrap-max-lull)) 579 ((erc--check-msg-prop 'erc--msg type))
580 ;; Assume presence of leading angle bracket or hyphen. 580 ((erc-nick-equal-p speaker (erc--check-msg-prop 'erc--spkr))))))
581 (nick (erc--check-msg-prop 'erc--spkr))
582 ((not (erc--check-msg-prop 'erc--ctcp 'ACTION)))
583 ((erc-nick-equal-p props nick))))
584 (set-marker erc-fill--wrap-last-msg (point-min))))))
585 581
586(defun erc-fill--wrap-measure (beg end) 582(defun erc-fill--wrap-measure (beg end)
587 "Return display spec width for inserted region between BEG and END. 583 "Return display spec width for inserted region between BEG and END.
@@ -747,8 +743,11 @@ With REPAIRP, destructively fill gaps and re-merge speakers."
747 ((equal "" dval))) 743 ((equal "" dval)))
748 (remove-text-properties 744 (remove-text-properties
749 dbeg (text-property-not-all dbeg end 'display dval) '(display))) 745 dbeg (text-property-not-all dbeg end 'display dval) '(display)))
750 (let* ((pos (if (eq 'date-left (get-text-property beg 'erc-stamp-type)) 746 ;; This "should" work w/o `front-sticky' and `rear-nonsticky'.
751 (field-beginning beg) 747 (let* ((pos (if-let (((eq 'erc-timestamp (field-at-pos beg)))
748 (b (field-beginning beg))
749 ((eq 'datestamp (get-text-property b 'erc--msg))))
750 b
752 beg)) 751 beg))
753 (erc--msg-props (map-into (text-properties-at pos) 'hash-table)) 752 (erc--msg-props (map-into (text-properties-at pos) 'hash-table))
754 (erc-stamp--current-time (gethash 'erc--ts erc--msg-props))) 753 (erc-stamp--current-time (gethash 'erc--ts erc--msg-props)))
diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el
index a6efa3b5151..9ca3ea320a0 100644
--- a/lisp/erc/erc-stamp.el
+++ b/lisp/erc/erc-stamp.el
@@ -660,8 +660,7 @@ truncating `erc-timestamp-format-left' prior to rendering. A
660value of t means the option's value doesn't require trimming.") 660value of t means the option's value doesn't require trimming.")
661 661
662(defun erc-stamp--propertize-left-date-stamp () 662(defun erc-stamp--propertize-left-date-stamp ()
663 (add-text-properties (point-min) (1- (point-max)) 663 (add-text-properties (point-min) (1- (point-max)) '(field erc-timestamp))
664 '(field erc-timestamp erc-stamp-type date-left))
665 (erc--hide-message 'timestamp) 664 (erc--hide-message 'timestamp)
666 (run-hooks 'erc-stamp--insert-date-hook)) 665 (run-hooks 'erc-stamp--insert-date-hook))
667 666
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 57194ed439e..759907b7618 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -154,11 +154,10 @@ visiting and editing inserted messages. Modules should align
154their markers accordingly. The following properties have meaning 154their markers accordingly. The following properties have meaning
155as of ERC 5.6: 155as of ERC 5.6:
156 156
157 - `erc--msg': a symbol, guaranteed present; values include: 157 - `erc--msg': a symbol, guaranteed present; possible values
158 `msg', signifying a `PRIVMSG' or an incoming `NOTICE'; 158 include `unknown', a fallback used by `erc-display-message'; a
159 `unknown', a fallback for `erc-display-message'; a catalog 159 catalog key, such as `s401' or `finished'; an
160 key, such as `s401' or `finished'; an `erc-display-message' 160 `erc-display-message' TYPE parameter, like `notice'
161 TYPE parameter, like `notice'
162 161
163 - `erc--cmd': a message's associated IRC command, as read by 162 - `erc--cmd': a message's associated IRC command, as read by
164 `erc--get-eq-comparable-cmd'; currently either a symbol, like 163 `erc--get-eq-comparable-cmd'; currently either a symbol, like
@@ -3017,20 +3016,34 @@ target, and an `erc-server-send' FORCE flag.")
3017 "Send STRING to TARGET, possibly immediately, with FORCE." 3016 "Send STRING to TARGET, possibly immediately, with FORCE."
3018 (erc-send-ctcp-message target (format "ACTION %s" string) force)) 3017 (erc-send-ctcp-message target (format "ACTION %s" string) force))
3019 3018
3019(defvar erc--use-language-catalog-for-ctcp-action-p nil
3020 "When non-nil, use `ACTION' entry from language catalog for /ME's.
3021Otherwise, use `ctcp-action' or `ctcp-action-input' from the
3022internal `-speaker' catalog. This is an escape hatch to restore
3023pre-5.6 behavior for the `font-lock-face' property of incoming
3024and outgoing \"CTCP ACTION\" messages, whose pre-buttonized state
3025was a single interval of `erc-input-face' or `erc-action-face'.
3026Newer modules, like `fill-wrap' and `nicks', are incompatible with
3027this format style. If you use this, please ask ERC to expose it
3028as a public variable via \\[erc-bug] or similar.")
3029
3020(defun erc--send-action-display (string) 3030(defun erc--send-action-display (string)
3021 "Display STRING as an outgoing \"CTCP ACTION\" message." 3031 "Display STRING as an outgoing \"CTCP ACTION\" message.
3032Propertize the message according to the compatibility flag
3033`erc--use-language-catalog-for-ctcp-action-p'."
3022 ;; Allow hooks acting on inserted PRIVMSG and NOTICES to process us. 3034 ;; Allow hooks acting on inserted PRIVMSG and NOTICES to process us.
3023 (defvar erc--merge-prop-behind-p) 3035 (let ((erc--msg-prop-overrides `((erc--ctcp . ACTION)
3024 (let* ((nick (erc-current-nick)) 3036 ,@erc--msg-prop-overrides))
3025 (erc--msg-prop-overrides `((erc--msg . msg) 3037 (nick (erc-current-nick)))
3026 (erc--ctcp . ACTION) 3038 (if erc--use-language-catalog-for-ctcp-action-p
3027 (erc--spkr . ,nick) 3039 (progn (erc--ensure-spkr-prop nick)
3028 ,@erc--msg-prop-overrides)) 3040 (erc-display-message nil 'input (current-buffer) 'ACTION
3029 (erc--merge-prop-behind-p t)) 3041 ?n (propertize nick 'erc--speaker nick)
3030 (setq nick (propertize nick 'erc--speaker nick 3042 ?a string ?u "" ?h ""))
3031 'font-lock-face 'erc-my-nick-face)) 3043 (let ((erc-current-message-catalog erc--message-speaker-catalog))
3032 (erc-display-message nil '(t input action) (current-buffer) 3044 (erc-display-message nil nil (current-buffer) 'ctcp-action-input
3033 'ACTION ?n nick ?a string ?u "" ?h ""))) 3045 ?p (erc-get-channel-membership-prefix nick)
3046 ?n (erc--speakerize-nick nick) ?m string)))))
3034 3047
3035(defun erc--send-action (target string force) 3048(defun erc--send-action (target string force)
3036 "Display STRING, then send to TARGET as a \"CTCP ACTION\" message." 3049 "Display STRING, then send to TARGET as a \"CTCP ACTION\" message."
@@ -3039,11 +3052,20 @@ target, and an `erc-server-send' FORCE flag.")
3039 3052
3040;; Display interface 3053;; Display interface
3041 3054
3042(defun erc--ensure-spkr-prop (nick) 3055(defun erc--ensure-spkr-prop (nick &optional overrides)
3043 "Maybe add NICK to `erc--msg-props' or `erc--msg-prop-overrides'." 3056 "Add NICK as `erc--spkr' to the current \"msg props\" environment.
3044 (cond (erc--msg-props (puthash 'erc--spkr nick erc--msg-props)) 3057Prefer `erc--msg-props' over `erc--msg-prop-overrides' when both
3058are available. Also include any members of the alist OVERRIDES,
3059when present. Assume NICK itself to be free of any text props,
3060and return it."
3061 (cond (erc--msg-props
3062 (puthash 'erc--spkr nick erc--msg-props)
3063 (dolist (entry overrides)
3064 (puthash (car entry) (cdr entry) erc--msg-props)))
3045 (erc--msg-prop-overrides 3065 (erc--msg-prop-overrides
3046 (push (cons 'erc--spkr nick) erc--msg-prop-overrides)))) 3066 (setq erc--msg-prop-overrides
3067 `((erc--spkr . ,nick) ,@overrides ,@erc--msg-prop-overrides))))
3068 nick)
3047 3069
3048(defun erc-string-invisible-p (string) 3070(defun erc-string-invisible-p (string)
3049 "Check whether STRING is invisible or not. 3071 "Check whether STRING is invisible or not.
@@ -4659,15 +4681,26 @@ See also `erc-message' and `erc-display-line'."
4659 (funcall erc--send-message-nested-function line force) 4681 (funcall erc--send-message-nested-function line force)
4660 (erc--send-message-external line force))) 4682 (erc--send-message-external line force)))
4661 4683
4662;; FIXME fully simulate `erc-display-msg'. This doesn't currently add
4663;; the correct text properties. For example, the LINE should have
4664;; `erc-default-face'.
4665(defun erc--send-message-external (line force) 4684(defun erc--send-message-external (line force)
4666 (erc-message "PRIVMSG" (concat (erc-default-target) " " line) force) 4685 "Send a \"PRIVMSG\" to the default target with optional FORCE.
4667 (erc-display-line 4686Expect caller to bind `erc-default-recipients' if needing to
4668 (concat (erc-format-my-nick) line) 4687specify a status-prefixed target."
4669 (current-buffer)) 4688 ;; Almost like an echoed message, but without the `erc--cmd'.
4689 (let* ((erc-current-message-catalog erc--message-speaker-catalog)
4690 (target (erc-default-target))
4691 (erc--msg-prop-overrides `((erc--tmp) ,@erc--msg-prop-overrides))
4692 ;; This util sets the `erc--spkr' property in ^.
4693 (trimmed (erc--statusmsg-target target))
4694 (stmsgindc (and trimmed (substring target 0 1)))
4695 (queryp (and erc--target (not (erc--target-channel-p erc--target))))
4696 (args (erc--determine-speaker-message-format-args
4697 (erc-current-nick) line queryp 'privmsgp 'inputp
4698 stmsgindc 'prefix)))
4699 (erc-message "PRIVMSG" (concat target " " line) force)
4700 (push (cons 'erc--msg (car args)) erc--msg-prop-overrides)
4701 (apply #'erc-display-message nil nil (current-buffer) args))
4670 ;; FIXME - treat multiline, run hooks, or remove me? 4702 ;; FIXME - treat multiline, run hooks, or remove me?
4703 ;; FIXME explain this ^ in more detail or remove.
4671 t) 4704 t)
4672 4705
4673(defun erc--send-message-nested (input-line force) 4706(defun erc--send-message-nested (input-line force)
@@ -5282,7 +5315,6 @@ Eventually add a # in front of it, if that turns it into a valid channel name."
5282 rear-nonsticky erc-prompt field front-sticky read-only 5315 rear-nonsticky erc-prompt field front-sticky read-only
5283 ;; stamp 5316 ;; stamp
5284 cursor-intangible cursor-sensor-functions isearch-open-invisible 5317 cursor-intangible cursor-sensor-functions isearch-open-invisible
5285 erc-stamp-type
5286 ;; match 5318 ;; match
5287 invisible intangible 5319 invisible intangible
5288 ;; button 5320 ;; button
@@ -5626,7 +5658,9 @@ manner implied above, which was lost sometime before ERC 5.4."
5626 :package-version '(ERC . "5.6") ; revived 5658 :package-version '(ERC . "5.6") ; revived
5627 :group 'erc-buffers 5659 :group 'erc-buffers
5628 :group 'erc-query 5660 :group 'erc-query
5629 :type 'boolean) 5661 :type '(choice boolean
5662 (choice :tag "Create pseudo queries for STATUSMSGs"
5663 status)))
5630 5664
5631(defcustom erc-format-query-as-channel-p t 5665(defcustom erc-format-query-as-channel-p t
5632 "If non-nil, format text from others in a query buffer like in a channel. 5666 "If non-nil, format text from others in a query buffer like in a channel.
@@ -5803,14 +5837,210 @@ NUH, and the current `erc-response' object.")
5803 'font-lock-face msg-face str) 5837 'font-lock-face msg-face str)
5804 str)) 5838 str))
5805 5839
5806(defcustom erc-format-nick-function 'erc-format-nick 5840;; The format strings in the following `-speaker' catalog shouldn't
5807 "Function to format a nickname for message display." 5841;; contain any non-protocol words, so they make sense in any language.
5842
5843(defvar erc--message-speaker-statusmsg
5844 #("(%p%n%s) %m"
5845 0 1 (font-lock-face erc-default-face)
5846 1 3 (font-lock-face erc-nick-prefix-face)
5847 3 5 (font-lock-face erc-nick-default-face)
5848 5 7 (font-lock-face erc-notice-face)
5849 7 11 (font-lock-face erc-default-face))
5850 "Message template for in-channel status messages.")
5851
5852(defvar erc--message-speaker-statusmsg-input
5853 #("(%p%n%s) %m"
5854 0 1 (font-lock-face erc-default-face)
5855 1 3 (font-lock-face erc-my-nick-prefix-face)
5856 3 5 (font-lock-face erc-my-nick-face)
5857 5 7 (font-lock-face erc-notice-face)
5858 7 8 (font-lock-face erc-default-face)
5859 8 11 (font-lock-face erc-input-face))
5860 "Message template for echoed status messages.")
5861
5862(defvar erc--message-speaker-input-chan-privmsg
5863 #("<%p%n> %m"
5864 0 1 (font-lock-face erc-default-face)
5865 1 3 (font-lock-face erc-my-nick-prefix-face)
5866 3 5 (font-lock-face erc-my-nick-face)
5867 5 7 (font-lock-face erc-default-face)
5868 7 9 (font-lock-face erc-input-face))
5869 "Message template for prompt input or echoed PRIVMSG from own nick.")
5870
5871(defvar erc--message-speaker-input-query-privmsg
5872 #("*%n* %m"
5873 0 1 (font-lock-face erc-direct-msg-face)
5874 1 3 (font-lock-face erc-my-nick-face)
5875 3 5 (font-lock-face erc-direct-msg-face)
5876 5 7 (font-lock-face erc-input-face))
5877 "Message template for prompt input or echoed PRIVMSG query from own nick.")
5878
5879(defvar erc--message-speaker-input-query-notice
5880 #("-%n- %m"
5881 0 1 (font-lock-face erc-direct-msg-face)
5882 1 3 (font-lock-face erc-my-nick-face)
5883 3 5 (font-lock-face erc-direct-msg-face)
5884 5 7 (font-lock-face erc-input-face))
5885 "Message template for echoed or spoofed query NOTICE from own nick.")
5886
5887(defvar erc--message-speaker-input-chan-notice
5888 #("-%p%n- %m"
5889 0 1 (font-lock-face erc-default-face)
5890 1 3 (font-lock-face erc-my-nick-prefix-face)
5891 3 5 (font-lock-face erc-my-nick-face)
5892 5 7 (font-lock-face erc-default-face)
5893 7 9 (font-lock-face erc-input-face))
5894 "Message template for prompt input or echoed NOTICE from own nick.")
5895
5896(defvar erc--message-speaker-chan-privmsg
5897 #("<%p%n> %m"
5898 0 1 (font-lock-face erc-default-face)
5899 1 3 (font-lock-face erc-nick-prefix-face)
5900 3 5 (font-lock-face erc-nick-default-face)
5901 5 9 (font-lock-face erc-default-face))
5902 "Message template for a PRIVMSG in a channel.")
5903
5904(defvar erc--message-speaker-query-privmsg
5905 #("*%n* %m"
5906 0 1 (font-lock-face erc-direct-msg-face)
5907 1 3 (font-lock-face erc-nick-msg-face)
5908 3 7 (font-lock-face erc-direct-msg-face))
5909 "Message template for a PRIVMSG in query buffer.")
5910
5911(defvar erc--message-speaker-chan-notice
5912 #("-%p%n- %m"
5913 0 1 (font-lock-face erc-default-face)
5914 1 3 (font-lock-face erc-nick-prefix-face)
5915 3 5 (font-lock-face erc-nick-default-face)
5916 5 9 (font-lock-face erc-default-face))
5917 "Message template for a NOTICE in a channel.")
5918
5919(defvar erc--message-speaker-query-notice
5920 #("-%n- %m"
5921 0 1 (font-lock-face erc-direct-msg-face)
5922 1 3 (font-lock-face erc-nick-msg-face)
5923 3 7 (font-lock-face erc-direct-msg-face))
5924 "Message template for a NOTICE in a query buffer.")
5925
5926(defvar erc--message-speaker-ctcp-action
5927 #("* %p%n %m"
5928 0 2 (font-lock-face erc-action-face)
5929 2 4 (font-lock-face (erc-nick-prefix-face erc-action-face))
5930 4 9 (font-lock-face erc-action-face))
5931 "Message template for a CTCP ACTION from another user.")
5932
5933(defvar erc--message-speaker-ctcp-action-input
5934 #("* %p%n %m"
5935 0 2 (font-lock-face #1=(erc-input-face erc-action-face))
5936 2 4 (font-lock-face (erc-my-nick-prefix-face . #1#))
5937 4 6 (font-lock-face (erc-my-nick-face . #1#))
5938 6 9 (font-lock-face #1#))
5939 "Message template for a CTCP ACTION from current client.")
5940
5941(defvar erc--message-speaker-ctcp-action-statusmsg
5942 #("* (%p%n%s) %m"
5943 0 3 (font-lock-face erc-action-face)
5944 3 5 (font-lock-face (erc-nick-prefix-face erc-action-face))
5945 5 7 (font-lock-face erc-action-face)
5946 7 9 (font-lock-face (erc-notice-face erc-action-face))
5947 9 13 (font-lock-face erc-action-face))
5948 "Template for a CTCP ACTION status message from another chan op.")
5949
5950(defvar erc--message-speaker-ctcp-action-statusmsg-input
5951 #("* (%p%n%s) %m"
5952 0 3 (font-lock-face #1=(erc-input-face erc-action-face))
5953 3 5 (font-lock-face (erc-my-nick-prefix-face . #1#))
5954 5 7 (font-lock-face (erc-my-nick-face . #1#))
5955 7 9 (font-lock-face (erc-notice-face . #1#))
5956 9 13 (font-lock-face #1#))
5957 "Template for a CTCP ACTION status message from current client.")
5958
5959(defun erc--speakerize-nick (nick &optional disp)
5960 "Propertize NICK with `erc--speaker' if not already present.
5961Do so to DISP instead if it's non-nil. In either case, assign
5962NICK, sans properties, as the `erc--speaker' value. As a side
5963effect, pair the latter string (the same `eq'-able object) with
5964the symbol `erc--spkr' in the \"msg prop\" environment for any
5965imminent `erc-display-message' invocations. While doing so,
5966include any overrides defined in `erc--message-speaker-catalog'."
5967 (let ((plain-nick (substring-no-properties nick)))
5968 (erc--ensure-spkr-prop plain-nick (get erc--message-speaker-catalog
5969 'erc--msg-prop-overrides))
5970 (if (text-property-not-all 0 (length (or disp nick))
5971 'erc--speaker nil (or disp nick))
5972 (or disp nick)
5973 (propertize (or disp nick) 'erc--speaker plain-nick))))
5974
5975(defun erc--determine-speaker-message-format-args
5976 (nick message queryp privmsgp inputp &optional statusmsg prefix disp-nick)
5977 "Return a list consisting of a \"speaker\"-template key and spec args.
5978Consider the three flags QUERYP, PRIVMSGP, and INPUTP, as well as
5979the possibly null STATUSMSG string. (Combined, these describe
5980the context of a newly arrived \"PRIVMSG\" or, when PRIVMSGP is
5981nil, a \"NOTICE\"). Interpret QUERYP to mean that MESSAGE is
5982directed at the ERC client itself (a direct message), and INPUTP
5983to mean MESSAGE is an outgoing or echoed message originating from
5984or meant to simulate prompt input. Interpret a non-nil STATUSMSG
5985to mean MESSAGE should be formatted as a special channel message
5986intended for privileged members of the same or greater status.
5987
5988After deciding on the template key for the current \"speaker\"
5989catalog, use the remaining arguments, possibly along with
5990STATUSMSG, to construct the appropriate spec-args plist forming
5991the returned list's tail. In this plist, pair the char ?n with
5992NICK, the nickname of the speaker and ?m with MESSAGE, the
5993message body. When non-nil, assume DISP-NICK to be a possibly
5994phony display name to take the place of NICK for ?n. When PREFIX
5995is non-nil, look up NICK's channel-membership status, possibly
5996using PREFIX itself if it's an `erc-channel-user' object, which
5997it must be when called outside of a channel buffer. Pair the
5998result with the ?p specifier. When STATUSMSG is non-nil, pair it
5999with the ?s specifier. Ensure unused spec values are the empty
6000string rather than nil."
6001 (when prefix
6002 (setq prefix (erc-get-channel-membership-prefix
6003 (if (erc-channel-user-p prefix) prefix nick))))
6004 (when (and queryp erc--target erc-format-query-as-channel-p
6005 (not (erc--target-channel-p erc--target)))
6006 (setq queryp nil))
6007 (list (cond (statusmsg (if inputp 'statusmsg-input 'statusmsg))
6008 (privmsgp (if queryp
6009 (if inputp 'input-query-privmsg 'query-privmsg)
6010 (if inputp 'input-chan-privmsg 'chan-privmsg)))
6011 (t (if queryp
6012 (if inputp 'input-query-notice 'query-notice)
6013 (if inputp 'input-chan-notice 'chan-notice))))
6014 ?p (or prefix "") ?n (erc--speakerize-nick nick disp-nick)
6015 ?s (or statusmsg "") ?m message))
6016
6017(defcustom erc-show-speaker-membership-status nil
6018 "Whether to prefix speakers with their channel status.
6019For example, when this option is non-nil and some nick \"Alice\"
6020has operator status in the current channel, ERC displays their
6021leading \"speaker\" label as <@Alice> instead of <Alice>."
6022 :package-version '(ERC . "5.6")
6023 :group 'erc-display
6024 :type 'boolean)
6025
6026(define-obsolete-variable-alias 'erc-format-nick-function
6027 'erc-speaker-from-channel-member-function "30.1")
6028(defcustom erc-speaker-from-channel-member-function
6029 #'erc-determine-speaker-from-user
6030 "Function to determine a message's displayed \"speaker\" label.
6031Called with an `erc-server-user' object and an `erc-channel-user'
6032object, both possibly nil. Use this option to do things like
6033provide localized display names. To ask ERC to prepend
6034channel-membership \"status\" prefixes, like \"@\", to the
6035returned name, see `erc-show-speaker-membership-status'."
6036 :package-version '(ERC . "5.6")
5808 :group 'erc-display 6037 :group 'erc-display
5809 :type 'function) 6038 :type '(choice (function-item erc-determine-speaker-from-user) function))
5810 6039
5811(defun erc-format-nick (&optional user _channel-data) 6040(define-obsolete-function-alias 'erc-format-nick
5812 "Return the nickname of USER. 6041 #'erc-determine-speaker-from-user "30.1")
5813See also `erc-format-nick-function'." 6042(defun erc-determine-speaker-from-user (&optional user _channel-data)
6043 "Return nickname slot of `erc-server-user' USER, when non-nil."
5814 (when user (erc-server-user-nickname user))) 6044 (when user (erc-server-user-nickname user)))
5815 6045
5816(define-obsolete-function-alias 'erc-get-user-mode-prefix 6046(define-obsolete-function-alias 'erc-get-user-mode-prefix
@@ -5841,14 +6071,17 @@ string nickname, not necessarily downcased."
5841 "Format the nickname of USER showing if USER has a voice, is an 6071 "Format the nickname of USER showing if USER has a voice, is an
5842operator, half-op, admin or owner. Owners have \"~\", admins have 6072operator, half-op, admin or owner. Owners have \"~\", admins have
5843\"&\", operators have \"@\" and users with voice have \"+\" as a 6073\"&\", operators have \"@\" and users with voice have \"+\" as a
5844prefix. Use CHANNEL-DATA to determine op and voice status. See 6074prefix. Use CHANNEL-DATA to determine op and voice status."
5845also `erc-format-nick-function'." 6075 (declare (obsolete "see option `erc-show-speaker-membership-status'" "30.1"))
5846 (when user 6076 (when user
5847 (let ((nick (erc-server-user-nickname user))) 6077 (let ((nick (erc-server-user-nickname user)))
5848 (concat (propertize 6078 (if (not erc--speaker-status-prefix-wanted-p)
5849 (erc-get-channel-membership-prefix channel-data) 6079 (prog1 nick
5850 'font-lock-face 'erc-nick-prefix-face) 6080 (setq erc--speaker-status-prefix-wanted-p 'erc-format-@nick))
5851 nick)))) 6081 (concat (propertize
6082 (erc-get-channel-membership-prefix channel-data)
6083 'font-lock-face 'erc-nick-prefix-face)
6084 nick)))))
5852 6085
5853(defun erc-format-my-nick () 6086(defun erc-format-my-nick ()
5854 "Return the beginning of this user's message, correctly propertized." 6087 "Return the beginning of this user's message, correctly propertized."
@@ -5861,11 +6094,37 @@ also `erc-format-nick-function'."
5861 (concat 6094 (concat
5862 (propertize open 'font-lock-face 'erc-default-face) 6095 (propertize open 'font-lock-face 'erc-default-face)
5863 (propertize mode 'font-lock-face 'erc-my-nick-prefix-face) 6096 (propertize mode 'font-lock-face 'erc-my-nick-prefix-face)
5864 (propertize nick 'font-lock-face 'erc-my-nick-face 'erc--speaker nick) 6097 (propertize nick 'erc--speaker nick 'font-lock-face 'erc-my-nick-face)
5865 (propertize close 'font-lock-face 'erc-default-face))) 6098 (propertize close 'font-lock-face 'erc-default-face)))
5866 (let ((prefix "> ")) 6099 (let ((prefix "> "))
5867 (propertize prefix 'font-lock-face 'erc-default-face)))) 6100 (propertize prefix 'font-lock-face 'erc-default-face))))
5868 6101
6102(defun erc--format-speaker-input-message (message)
6103 "Assemble outgoing MESSAGE entered at the prompt for insertion.
6104Intend \"input\" to refer to interactive prompt input as well as
6105the group of associated message-format templates from the
6106\"speaker\" catalog. Format the speaker portion in a manner
6107similar to that performed by `erc-format-my-nick', but use either
6108`erc--message-speaker-input-chan-privmsg' or
6109`erc--message-speaker-input-query-privmsg' as a formatting
6110template, with MESSAGE being the actual message body. Return a
6111copy with possibly shared text-property values."
6112 (if-let ((erc-show-my-nick)
6113 (nick (erc-current-nick))
6114 (pfx (erc-get-channel-membership-prefix nick))
6115 (erc-current-message-catalog erc--message-speaker-catalog)
6116 (key (if (or erc-format-query-as-channel-p
6117 (erc--target-channel-p erc--target))
6118 'input-chan-privmsg
6119 'input-query-privmsg)))
6120 (progn
6121 (cond (erc--msg-props (puthash 'erc--msg key erc--msg-props))
6122 (erc--msg-prop-overrides (push (cons 'erc--msg key)
6123 erc--msg-prop-overrides)))
6124 (erc-format-message key ?p pfx ?n (erc--speakerize-nick nick)
6125 ?m message))
6126 (propertize (concat "> " message) 'font-lock-face 'erc-input-face)))
6127
5869(defun erc-echo-notice-in-default-buffer (s parsed buffer _sender) 6128(defun erc-echo-notice-in-default-buffer (s parsed buffer _sender)
5870 "Echo a private notice in the default buffer, namely the 6129 "Echo a private notice in the default buffer, namely the
5871target buffer specified by BUFFER, or there is no target buffer, 6130target buffer specified by BUFFER, or there is no target buffer,
@@ -6105,8 +6364,7 @@ See also `erc-display-message'."
6105 (while queries 6364 (while queries
6106 (let* ((type (upcase (car (split-string (car queries))))) 6365 (let* ((type (upcase (car (split-string (car queries)))))
6107 (hook (intern-soft (concat "erc-ctcp-query-" type "-hook"))) 6366 (hook (intern-soft (concat "erc-ctcp-query-" type "-hook")))
6108 (erc--msg-prop-overrides `((erc--msg . msg) 6367 (erc--msg-prop-overrides `((erc--ctcp . ,(intern type))
6109 (erc--ctcp . ,(intern type))
6110 ,@erc--msg-prop-overrides))) 6368 ,@erc--msg-prop-overrides)))
6111 (if (and hook (boundp hook)) 6369 (if (and hook (boundp hook))
6112 (if (string-equal type "ACTION") 6370 (if (string-equal type "ACTION")
@@ -6141,12 +6399,31 @@ See also `erc-display-message'."
6141 (let ((s (match-string 1 msg)) 6399 (let ((s (match-string 1 msg))
6142 (buf (or (erc-get-buffer to proc) 6400 (buf (or (erc-get-buffer to proc)
6143 (erc-get-buffer nick proc) 6401 (erc-get-buffer nick proc)
6144 (process-buffer proc)))) 6402 (process-buffer proc)))
6145 (erc--ensure-spkr-prop nick) 6403 (selfp (erc-current-nick-p nick)))
6146 (setq nick (propertize nick 'erc--speaker nick)) 6404 (if erc--use-language-catalog-for-ctcp-action-p
6147 (erc-display-message 6405 (progn
6148 parsed 'action buf 6406 (erc--ensure-spkr-prop nick)
6149 'ACTION ?n nick ?u login ?h host ?a s)))) 6407 (setq nick (propertize nick 'erc--speaker nick))
6408 (erc-display-message parsed (if selfp 'input 'action) buf
6409 'ACTION ?n nick ?u login ?h host ?a s))
6410 (let* ((obj (and (erc--ctcp-response-p parsed) parsed))
6411 (buffer (and obj (erc--ctcp-response-buffer obj)))
6412 (stsmsg (and obj (erc--ctcp-response-statusmsg obj)))
6413 (prefix (and obj (erc--ctcp-response-prefix obj)))
6414 (dispnm (and obj (erc--ctcp-response-dispname obj)))
6415 (erc-current-message-catalog erc--message-speaker-catalog))
6416 (erc-display-message
6417 parsed nil (or buffer buf)
6418 (if selfp
6419 (if stsmsg 'ctcp-action-statusmsg-input 'ctcp-action-input)
6420 (if stsmsg 'ctcp-action-statusmsg 'ctcp-action))
6421 ?s (or stsmsg to)
6422 ?p (or (and (erc-channel-user-p prefix)
6423 (erc-get-channel-membership-prefix prefix))
6424 "")
6425 ?n (erc--speakerize-nick nick dispnm)
6426 ?m s))))))
6150 6427
6151(defvar erc-ctcp-query-CLIENTINFO-hook '(erc-ctcp-query-CLIENTINFO)) 6428(defvar erc-ctcp-query-CLIENTINFO-hook '(erc-ctcp-query-CLIENTINFO))
6152 6429
@@ -7582,15 +7859,11 @@ as outgoing chat messages and echoed slash commands."
7582 (erc--assert-input-bounds) 7859 (erc--assert-input-bounds)
7583 (let ((insert-position (marker-position (goto-char erc-insert-marker))) 7860 (let ((insert-position (marker-position (goto-char erc-insert-marker)))
7584 (erc--msg-props (or erc--msg-props 7861 (erc--msg-props (or erc--msg-props
7585 (let ((ovs erc--msg-prop-overrides)) 7862 (let ((ovs (seq-filter
7863 #'cdr erc--msg-prop-overrides)))
7586 (map-into `((erc--msg . msg) ,@(reverse ovs)) 7864 (map-into `((erc--msg . msg) ,@(reverse ovs))
7587 'hash-table)))) 7865 'hash-table)))))
7588 beg) 7866 (insert (erc--format-speaker-input-message line) "\n")
7589 (insert (erc-format-my-nick))
7590 (setq beg (point))
7591 (insert line)
7592 (erc-put-text-property beg (point) 'font-lock-face 'erc-input-face)
7593 (insert "\n")
7594 (save-restriction 7867 (save-restriction
7595 (narrow-to-region insert-position (point)) 7868 (narrow-to-region insert-position (point))
7596 (run-hooks 'erc-send-modify-hook) 7869 (run-hooks 'erc-send-modify-hook)
@@ -8945,9 +9218,6 @@ functions."
8945 (string-replace "%" "%%" reason)) 9218 (string-replace "%" "%%" reason))
8946 ""))))) 9219 "")))))
8947 9220
8948
8949(defvar-local erc-current-message-catalog 'english)
8950
8951(defun erc-retrieve-catalog-entry (key &optional catalog) 9221(defun erc-retrieve-catalog-entry (key &optional catalog)
8952 "Retrieve `format-spec' entry for symbol KEY in CATALOG. 9222 "Retrieve `format-spec' entry for symbol KEY in CATALOG.
8953Without symbol CATALOG, use `erc-current-message-catalog'. If 9223Without symbol CATALOG, use `erc-current-message-catalog'. If
diff --git a/test/lisp/erc/erc-scenarios-base-statusmsg.el b/test/lisp/erc/erc-scenarios-base-statusmsg.el
new file mode 100644
index 00000000000..80582e0cf80
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios-base-statusmsg.el
@@ -0,0 +1,103 @@
1;;; erc-scenarios-base-statusmsg.el --- statusmsg tests -*- lexical-binding: t -*-
2
3;; Copyright (C) 2023 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Code:
21
22(require 'ert-x)
23(eval-and-compile
24 (let ((load-path (cons (ert-resource-directory) load-path)))
25 (require 'erc-scenarios-common)))
26
27(ert-deftest erc-scenarios-base-statusmsg ()
28
29 (erc-scenarios-common-with-cleanup
30 ((erc-scenarios-common-dialog "base/display-message")
31 (dumb-server (erc-d-run "localhost" t 'statusmsg))
32 (erc-autojoin-channels-alist '((foonet "#mine")))
33 (erc-modules (cons 'fill-wrap erc-modules))
34 (port (process-contact dumb-server :service))
35 (erc-show-speaker-membership-status nil)
36 (erc-server-flood-penalty 0.1)
37 (expect (erc-d-t-make-expecter)))
38
39 (ert-info ("Connect")
40 (with-current-buffer (erc :server "127.0.0.1"
41 :port port
42 :nick "tester"
43 :user "tester"
44 :full-name "tester")
45 (funcall expect 5 "This server is in debug mode")))
46
47 (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#mine"))
48
49 (ert-info ("Receive status messages unprefixed")
50 (funcall expect 5 "+dummy")
51 (funcall expect 5 "(dummy+) hello")
52 (should (eq 'statusmsg (erc--get-inserted-msg-prop 'erc--msg)))
53 (should (equal "dummy" (erc--get-inserted-msg-prop 'erc--spkr)))
54 (should (eq (get-text-property (1- (point)) 'font-lock-face)
55 'erc-default-face))
56 (funcall expect 5 "(dummy+) there")
57 (should (equal "" (get-text-property (pos-bol) 'display)))
58
59 ;; CTCP ACTION
60 (funcall expect 5 "* (dummy+) sad")
61 (should (eq 'ctcp-action-statusmsg
62 (erc--get-inserted-msg-prop 'erc--msg)))
63 (should (eq (get-text-property (1- (point)) 'font-lock-face)
64 'erc-action-face))
65 (funcall expect 5 "* (dummy+) glad")
66 (should (equal "" (get-text-property (pos-bol) 'display))))
67
68 (ert-info ("Send status messages")
69 ;; We don't have `echo-message' yet, so ERC doesn't currently
70 ;; insert commands like "/msg +#mine foo".
71 (let ((erc-default-recipients '("+#mine")))
72 (erc-send-message "howdy"))
73 (funcall expect 5 "(@tester+) howdy")
74 (should (eq 'statusmsg-input (erc--get-inserted-msg-prop 'erc--msg)))
75 (should (equal "tester" (erc--get-inserted-msg-prop 'erc--spkr)))
76 (should (eq (get-text-property (1- (point)) 'font-lock-face)
77 'erc-input-face))
78 (let ((erc-default-recipients '("+#mine")))
79 (erc-send-message "tenderfoot"))
80 (funcall expect 5 "(@tester+) tenderfoot")
81 (should (equal "" (get-text-property (pos-bol) 'display)))
82
83 ;; Simulate some "echoed" CTCP ACTION messages since we don't
84 ;; actually support that yet.
85 (funcall expect 5 "* (@tester+) mad")
86 (should (eq 'ctcp-action-statusmsg-input
87 (erc--get-inserted-msg-prop 'erc--msg)))
88 (should (equal (get-text-property (1- (point)) 'font-lock-face)
89 '(erc-input-face erc-action-face)))
90 (funcall expect 5 "* (@tester+) chad")
91 (should (equal "" (get-text-property (pos-bol) 'display))))
92
93 (ert-info ("Receive status messages prefixed")
94 (setq erc-show-speaker-membership-status t)
95 (erc-scenarios-common-say "/me ready") ; sync
96 (funcall expect 5 "* @tester ready")
97 (funcall expect 5 "(+dummy+) okie")
98
99 ;; CTCP ACTION
100 (funcall expect 5 "* (+dummy+) dokie")
101 (funcall expect 5 "* +dummy out")))))
102
103;;; erc-scenarios-base-statusmsg.el ends here
diff --git a/test/lisp/erc/erc-scenarios-stamp.el b/test/lisp/erc/erc-scenarios-stamp.el
index bb3a4195e0d..e4788f78654 100644
--- a/test/lisp/erc/erc-scenarios-stamp.el
+++ b/test/lisp/erc/erc-scenarios-stamp.el
@@ -68,7 +68,7 @@
68 (ert-info ("Stamps appear in left margin and are invisible") 68 (ert-info ("Stamps appear in left margin and are invisible")
69 (should (eq 'erc-timestamp (field-at-pos (pos-bol)))) 69 (should (eq 'erc-timestamp (field-at-pos (pos-bol))))
70 (should (= (pos-bol) (field-beginning (pos-bol)))) 70 (should (= (pos-bol) (field-beginning (pos-bol))))
71 (should (eq 'msg (get-text-property (pos-bol) 'erc--msg))) 71 (should (eq 'query-notice (get-text-property (pos-bol) 'erc--msg)))
72 (should (eq 'NOTICE (get-text-property (pos-bol) 'erc--cmd))) 72 (should (eq 'NOTICE (get-text-property (pos-bol) 'erc--cmd)))
73 (should (= ?- (char-after (field-end (pos-bol))))) 73 (should (= ?- (char-after (field-end (pos-bol)))))
74 (should (equal (get-text-property (1+ (field-end (pos-bol))) 74 (should (equal (get-text-property (1+ (field-end (pos-bol)))
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 94ba724ac43..b7e0cdcaa21 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -2285,7 +2285,8 @@
2285 calls 2285 calls
2286 erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) 2286 erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
2287 (cl-letf (((symbol-function 'erc-display-message) 2287 (cl-letf (((symbol-function 'erc-display-message)
2288 (lambda (_ _ _ line) (push line calls))) 2288 (lambda (_ _ _ msg &rest args)
2289 (push (apply #'erc-format-message msg args) calls)))
2289 ((symbol-function 'erc-server-send) 2290 ((symbol-function 'erc-server-send)
2290 (lambda (line _) (push line calls))) 2291 (lambda (line _) (push line calls)))
2291 ((symbol-function 'erc-server-buffer) 2292 ((symbol-function 'erc-server-buffer)
@@ -2327,7 +2328,7 @@
2327 (should-not erc-server-last-peers) 2328 (should-not erc-server-last-peers)
2328 (erc-message "PRIVMSG" ". hi") 2329 (erc-message "PRIVMSG" ". hi")
2329 (should-not erc-server-last-peers) 2330 (should-not erc-server-last-peers)
2330 (should (eq 'no-target (pop calls))) 2331 (should (equal "No target" (pop calls)))
2331 (erc-message "PRIVMSG" ", hi") 2332 (erc-message "PRIVMSG" ", hi")
2332 (should-not erc-server-last-peers) 2333 (should-not erc-server-last-peers)
2333 (should (string-match "alice :hi" (pop calls))))) 2334 (should (string-match "alice :hi" (pop calls)))))
@@ -2360,42 +2361,208 @@
2360 (kill-buffer "ExampleNet") 2361 (kill-buffer "ExampleNet")
2361 (kill-buffer "#chan"))) 2362 (kill-buffer "#chan")))
2362 2363
2363(ert-deftest erc-format-privmessage () 2364;; This is an adapter that uses formatting templates from the
2364 ;; Basic PRIVMSG 2365;; `-speaker' catalog to mimic `erc-format-privmessage', for testing
2365 (should (erc-tests--equal-including-properties 2366;; purposes.
2366 (erc-format-privmessage (copy-sequence "bob") 2367(defun erc-tests--format-privmessage (nick msg privp msgp &optional inputp pfx)
2367 (copy-sequence "oh my") 2368 (let ((erc-current-message-catalog erc--message-speaker-catalog))
2368 nil 'msgp) 2369 (apply #'erc-format-message
2369 #("<bob> oh my" 2370 (erc--determine-speaker-message-format-args nick msg privp msgp
2370 0 1 (font-lock-face erc-default-face) 2371 inputp nil pfx))))
2371 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) 2372
2372 4 11 (font-lock-face erc-default-face)))) 2373;; This asserts that `erc--determine-speaker-message-format-args'
2373 2374;; behaves identically to `erc-format-privmessage', the function whose
2374 ;; Basic NOTICE 2375;; role it basically replaced.
2375 (should (erc-tests--equal-including-properties 2376(ert-deftest erc--determine-speaker-message-format-args ()
2376 (erc-format-privmessage (copy-sequence "bob") 2377 ;; Basic PRIVMSG.
2377 (copy-sequence "oh my") 2378 (let ((expect #("<bob> oh my"
2378 nil nil) 2379 0 1 (font-lock-face erc-default-face)
2379 #("-bob- oh my" 2380 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face)
2380 0 1 (font-lock-face erc-default-face) 2381 4 11 (font-lock-face erc-default-face)))
2381 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) 2382 (args (list (concat "bob") (concat "oh my") nil 'msgp)))
2382 4 11 (font-lock-face erc-default-face)))) 2383 (should (erc-tests--equal-including-properties
2383 2384 (apply #'erc-format-privmessage args)
2384 ;; Prefixed PRIVMSG 2385 expect))
2385 (let* ((user (make-erc-server-user :nickname (copy-sequence "Bob"))) 2386 (should (erc-tests--equal-including-properties
2387 (apply #'erc-tests--format-privmessage args)
2388 expect)))
2389
2390 ;; Basic NOTICE.
2391 (let ((expect #("-bob- oh my"
2392 0 1 (font-lock-face erc-default-face)
2393 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face)
2394 4 11 (font-lock-face erc-default-face)))
2395 (args (list (copy-sequence "bob") (copy-sequence "oh my") nil nil)))
2396 (should (erc-tests--equal-including-properties
2397 (apply #'erc-format-privmessage args)
2398 expect))
2399 (should (erc-tests--equal-including-properties
2400 (apply #'erc-tests--format-privmessage args)
2401 expect)))
2402
2403 ;; Status-prefixed PRIVMSG.
2404 (let* ((expect
2405 #("<@Bob> oh my"
2406 0 1 (font-lock-face erc-default-face)
2407 1 2 (font-lock-face erc-nick-prefix-face help-echo "operator")
2408 2 5 (erc--speaker "Bob" font-lock-face erc-nick-default-face)
2409 5 12 (font-lock-face erc-default-face)))
2410 (user (make-erc-server-user :nickname (copy-sequence "Bob")))
2386 (cuser (make-erc-channel-user :op t)) 2411 (cuser (make-erc-channel-user :op t))
2387 (erc-channel-users (make-hash-table :test #'equal))) 2412 (erc-channel-users (make-hash-table :test #'equal)))
2388 (puthash "bob" (cons user cuser) erc-channel-users) 2413 (puthash "bob" (cons user cuser) erc-channel-users)
2389 2414
2415 (with-suppressed-warnings ((obsolete erc-format-@nick))
2416 (should (erc-tests--equal-including-properties
2417 (erc-format-privmessage (erc-format-@nick user cuser)
2418 (copy-sequence "oh my")
2419 nil 'msgp)
2420 expect)))
2421 (let ((nick "Bob")
2422 (msg "oh my"))
2423 (should (erc-tests--equal-including-properties
2424 (erc-tests--format-privmessage nick msg nil 'msgp nil cuser)
2425 expect)) ; overloaded on PREFIX arg
2426 (should (erc-tests--equal-including-properties
2427 (erc-tests--format-privmessage nick msg nil 'msgp nil t)
2428 expect))
2429 ;; The new version makes a copy instead of adding properties to
2430 ;; the input.
2431 (should-not
2432 (text-property-not-all 0 (length nick) 'font-lock-face nil nick))
2433 (should-not
2434 (text-property-not-all 0 (length msg) 'font-lock-face nil msg)))))
2435
2436(ert-deftest erc--determine-speaker-message-format-args/queries-as-channel ()
2437 (should erc-format-query-as-channel-p)
2438
2439 (with-current-buffer (get-buffer-create "bob")
2440 (erc-mode)
2441 (setq erc--target (erc--target-from-string "alice"))
2442
2443 (insert "PRIVMSG\n"
2444 (erc-tests--format-privmessage "bob" "oh my" 'queryp 'msgp))
2445 (should (erc-tests--equal-including-properties
2446 #("<bob> oh my"
2447 0 1 (font-lock-face erc-default-face)
2448 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face)
2449 4 11 (font-lock-face erc-default-face))
2450 (buffer-substring (pos-bol) (pos-eol))))
2451
2452 (insert "\nNOTICE\n"
2453 (erc-tests--format-privmessage "bob" "oh my" 'queryp nil))
2454 (should (erc-tests--equal-including-properties
2455 #("-bob- oh my"
2456 0 1 (font-lock-face erc-default-face)
2457 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face)
2458 4 11 (font-lock-face erc-default-face))
2459 (buffer-substring (pos-bol) (pos-eol))))
2460
2461 (insert "\nInput PRIVMSG\n"
2462 (erc-tests--format-privmessage "bob" "oh my"
2463 'queryp 'privmsgp 'inputp))
2464 (should (erc-tests--equal-including-properties
2465 #("<bob> oh my"
2466 0 1 (font-lock-face erc-default-face)
2467 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face)
2468 4 6 (font-lock-face erc-default-face)
2469 6 11 (font-lock-face erc-input-face))
2470 (buffer-substring (pos-bol) (pos-eol))))
2471
2472 (insert "\nInput NOTICE\n"
2473 (erc-tests--format-privmessage "bob" "oh my" 'queryp nil 'inputp))
2390 (should (erc-tests--equal-including-properties 2474 (should (erc-tests--equal-including-properties
2391 (erc-format-privmessage (erc-format-@nick user cuser) 2475 #("-bob- oh my"
2392 (copy-sequence "oh my")
2393 nil 'msgp)
2394 #("<@Bob> oh my"
2395 0 1 (font-lock-face erc-default-face) 2476 0 1 (font-lock-face erc-default-face)
2396 1 2 (font-lock-face erc-nick-prefix-face help-echo "operator") 2477 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face)
2397 2 5 (erc--speaker "Bob" font-lock-face erc-nick-default-face) 2478 4 6 (font-lock-face erc-default-face)
2398 5 12 (font-lock-face erc-default-face)))))) 2479 6 11 (font-lock-face erc-input-face))
2480 (buffer-substring (pos-bol) (pos-eol))))
2481
2482 (when noninteractive (kill-buffer))))
2483
2484(ert-deftest erc--determine-speaker-message-format-args/queries ()
2485 (should erc-format-query-as-channel-p)
2486
2487 (with-current-buffer (get-buffer-create "bob")
2488 (erc-mode)
2489 (setq-local erc-format-query-as-channel-p nil)
2490 (setq erc--target (erc--target-from-string "alice"))
2491
2492 (insert "PRIVMSG\n"
2493 (erc-tests--format-privmessage "bob" "oh my" 'queryp 'msgp))
2494 (should (erc-tests--equal-including-properties
2495 #("*bob* oh my"
2496 0 1 (font-lock-face erc-direct-msg-face)
2497 1 4 (erc--speaker "bob" font-lock-face erc-nick-msg-face)
2498 4 11 (font-lock-face erc-direct-msg-face))
2499 (buffer-substring (pos-bol) (pos-eol))))
2500
2501 (insert "\nNOTICE\n"
2502 (erc-tests--format-privmessage "bob" "oh my" 'queryp nil))
2503 (should (erc-tests--equal-including-properties
2504 #("-bob- oh my"
2505 0 1 (font-lock-face erc-direct-msg-face)
2506 1 4 (erc--speaker "bob" font-lock-face erc-nick-msg-face)
2507 4 11 (font-lock-face erc-direct-msg-face))
2508 (buffer-substring (pos-bol) (pos-eol))))
2509
2510 (insert "\nInput PRIVMSG\n"
2511 (erc-tests--format-privmessage "bob" "oh my"
2512 'queryp 'privmsgp 'inputp))
2513 (should (erc-tests--equal-including-properties
2514 #("*bob* oh my"
2515 0 1 (font-lock-face erc-direct-msg-face)
2516 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face)
2517 4 6 (font-lock-face erc-direct-msg-face)
2518 6 11 (font-lock-face erc-input-face))
2519 (buffer-substring (pos-bol) (pos-eol))))
2520
2521 (insert "\nInput NOTICE\n"
2522 (erc-tests--format-privmessage "bob" "oh my" 'queryp nil 'inputp))
2523 (should (erc-tests--equal-including-properties
2524 #("-bob- oh my"
2525 0 1 (font-lock-face erc-direct-msg-face)
2526 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face)
2527 4 6 (font-lock-face erc-direct-msg-face)
2528 6 11 (font-lock-face erc-input-face))
2529 (buffer-substring (pos-bol) (pos-eol))))
2530
2531 (when noninteractive (kill-buffer))))
2532
2533(defun erc-tests--format-my-nick (message)
2534 (concat (erc-format-my-nick)
2535 (propertize message 'font-lock-face 'erc-input-face)))
2536
2537;; This tests that the default behavior of the replacement formatting
2538;; function for prompt input, `erc--format-speaker-input-message'
2539;; matches that of the original being replaced, `erc-format-my-nick',
2540;; though it only handled the speaker portion.
2541(ert-deftest erc--format-speaker-input-message ()
2542 ;; No status prefix.
2543 (let ((erc-server-current-nick "tester")
2544 (expect #("<tester> oh my"
2545 0 1 (font-lock-face erc-default-face)
2546 1 7 (font-lock-face erc-my-nick-face erc--speaker "tester")
2547 7 9 (font-lock-face erc-default-face)
2548 9 14 (font-lock-face erc-input-face))))
2549 (should (equal (erc-tests--format-my-nick "oh my") expect))
2550 (should (equal (erc--format-speaker-input-message "oh my") expect)))
2551
2552 ;; With channel-operator status prefix.
2553 (let* ((erc-server-current-nick "tester")
2554 (cmem (cons (make-erc-server-user :nickname "tester")
2555 (make-erc-channel-user :op t)))
2556 (erc-channel-users (map-into (list "tester" cmem)
2557 '(hash-table :test equal)))
2558 (expect #("<@tester> oh my"
2559 0 1 (font-lock-face erc-default-face)
2560 1 2 (font-lock-face erc-my-nick-prefix-face)
2561 2 5 (font-lock-face erc-my-nick-face erc--speaker "bob")
2562 5 7 (font-lock-face erc-default-face)
2563 7 12 (font-lock-face erc-input-face))))
2564 (should (equal (erc-tests--format-my-nick "oh my") expect))
2565 (should (equal (erc--format-speaker-input-message "oh my") expect))))
2399 2566
2400(ert-deftest erc--route-insertion () 2567(ert-deftest erc--route-insertion ()
2401 (erc-tests--send-prep) 2568 (erc-tests--send-prep)
diff --git a/test/lisp/erc/resources/base/display-message/statusmsg.eld b/test/lisp/erc/resources/base/display-message/statusmsg.eld
new file mode 100644
index 00000000000..7c42117080c
--- /dev/null
+++ b/test/lisp/erc/resources/base/display-message/statusmsg.eld
@@ -0,0 +1,47 @@
1;; -*- mode: lisp-data; -*-
2((nick 10 "NICK tester"))
3((user 10 "USER tester 0 * :tester")
4 (0.00 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
5 (0.02 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version ergo-v2.11.1")
6 (0.01 ":irc.foonet.org 003 tester :This server was created Thu, 07 Dec 2023 08:04:35 UTC")
7 (0.00 ":irc.foonet.org 004 tester irc.foonet.org ergo-v2.11.1 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv")
8 (0.00 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# CHATHISTORY=1000 ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX :are supported by this server")
9 (0.01 ":irc.foonet.org 005 tester KICKLEN=390 MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8ONLY WHOX :are supported by this server")
10 (0.01 ":irc.foonet.org 005 tester draft/CHATHISTORY=1000 :are supported by this server")
11 (0.00 ":irc.foonet.org 251 tester :There are 0 users and 4 invisible on 1 server(s)")
12 (0.00 ":irc.foonet.org 252 tester 0 :IRC Operators online")
13 (0.00 ":irc.foonet.org 253 tester 0 :unregistered connections")
14 (0.00 ":irc.foonet.org 254 tester 2 :channels formed")
15 (0.00 ":irc.foonet.org 255 tester :I have 4 clients and 0 servers")
16 (0.02 ":irc.foonet.org 265 tester 4 5 :Current local users 4, max 5")
17 (0.00 ":irc.foonet.org 266 tester 4 5 :Current global users 4, max 5")
18 (0.00 ":irc.foonet.org 422 tester :MOTD File is missing")
19 (0.00 ":irc.foonet.org 221 tester +i")
20 (0.00 ":irc.foonet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
21
22((mode-tester 10 "MODE tester +i"))
23
24((join-mine 10 "JOIN #mine")
25 (0.01 ":irc.foonet.org 221 tester +i")
26 (0.00 ":tester!~u@2jv6nwu4af69s.irc JOIN #mine")
27 (0.02 ":irc.foonet.org 353 tester = #mine :@tester +dummy")
28 (0.01 ":irc.foonet.org 366 tester #mine :End of NAMES list"))
29
30((mode-mine 10 "MODE #mine")
31 (0.00 ":irc.foonet.org 324 tester #mine +Cnt")
32 (0.02 ":irc.foonet.org 329 tester #mine 1702026418")
33 (0.04 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :hello")
34 (0.03 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :there")
35 (0.05 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION sad\1")
36 (0.03 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION glad\1"))
37
38((privmsg-statusmsg 10 "PRIVMSG +#mine :howdy"))
39((privmsg-statusmsg-action 10 "PRIVMSG +#mine :tenderfoot")
40 ;; These are simulated "echoed messages"
41 (0.05 ":tester!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION mad\1")
42 (0.05 ":tester!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION chad\1"))
43
44((privmsg-prefixed 10 "PRIVMSG #mine :\1ACTION ready\1")
45 (0.04 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :okie")
46 (0.05 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION dokie\1")
47 (0.04 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG #mine :\1ACTION out\1"))
diff --git a/test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld b/test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld
index 9f648915d5c..feaba85ec90 100644
--- a/test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld
+++ b/test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld
@@ -1 +1 @@
#("\n\n\n[Thu Jan 1 1970]\n*** This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.[00:00]\n<alice> bob: come, you are a tedious fool: to the purpose. What was done to Elbow's wife, that he hath cause to complain of? Come me to what was done to her.\n<bob> alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n<bob> zero.[07:00]\n<bob> 0.5\n* bob one.\n<bob> two.\n<bob> 2.5\n* bob three\n<bob> four.\n" 2 3 (erc--msg datestamp erc--ts 0 field erc-timestamp) 3 20 (field erc-timestamp wrap-prefix #1=(space :width 27) line-prefix (space :width (- 27 (18)))) 21 22 (erc--msg notice erc--ts 0 wrap-prefix #1# line-prefix #2=(space :width (- 27 (4)))) 22 183 (wrap-prefix #1# line-prefix #2#) 183 190 (field erc-timestamp wrap-prefix #1# line-prefix #2# display (#5=(margin right-margin) #("[00:00]" 0 7 (invisible timestamp)))) 191 192 (erc--msg msg erc--ts 0 erc--spkr "alice" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #3=(space :width (- 27 (8)))) 192 197 (wrap-prefix #1# line-prefix #3#) 197 199 (wrap-prefix #1# line-prefix #3#) 199 202 (wrap-prefix #1# line-prefix #3#) 202 315 (wrap-prefix #1# line-prefix #3#) 316 348 (wrap-prefix #1# line-prefix #3#) 349 350 (erc--msg msg erc--ts 0 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #4=(space :width (- 27 (6)))) 350 353 (wrap-prefix #1# line-prefix #4#) 353 355 (wrap-prefix #1# line-prefix #4#) 355 360 (wrap-prefix #1# line-prefix #4#) 360 435 (wrap-prefix #1# line-prefix #4#) 436 437 (erc--msg datestamp erc--ts 1680307200 field erc-timestamp) 437 454 (field erc-timestamp wrap-prefix #1# line-prefix (space :width (- 27 (18)))) 455 456 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #6=(space :width (- 27 (6)))) 456 459 (wrap-prefix #1# line-prefix #6#) 459 466 (wrap-prefix #1# line-prefix #6#) 466 473 (field erc-timestamp wrap-prefix #1# line-prefix #6# display (#5# #("[07:00]" 0 7 (invisible timestamp)))) 474 475 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #7=(space :width (- 27 0)) display #8="") 475 478 (wrap-prefix #1# line-prefix #7# display #8#) 478 480 (wrap-prefix #1# line-prefix #7# display #8#) 480 483 (wrap-prefix #1# line-prefix #7#) 484 485 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #9=(space :width (- 27 (6)))) 485 486 (wrap-prefix #1# line-prefix #9#) 486 489 (wrap-prefix #1# line-prefix #9#) 489 494 (wrap-prefix #1# line-prefix #9#) 495 496 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #10=(space :width (- 27 (6)))) 496 499 (wrap-prefix #1# line-prefix #10#) 499 505 (wrap-prefix #1# line-prefix #10#) 506 507 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #11=(space :width (- 27 0)) display #8#) 507 510 (wrap-prefix #1# line-prefix #11# display #8#) 510 512 (wrap-prefix #1# line-prefix #11# display #8#) 512 515 (wrap-prefix #1# line-prefix #11#) 516 517 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #12=(space :width (- 27 (2)))) 517 518 (wrap-prefix #1# line-prefix #12#) 518 521 (wrap-prefix #1# line-prefix #12#) 521 527 (wrap-prefix #1# line-prefix #12#) 528 529 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #13=(space :width (- 27 (6)))) 529 532 (wrap-prefix #1# line-prefix #13#) 532 539 (wrap-prefix #1# line-prefix #13#)) \ No newline at end of file #("\n\n\n[Thu Jan 1 1970]\n*** This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.[00:00]\n<alice> bob: come, you are a tedious fool: to the purpose. What was done to Elbow's wife, that he hath cause to complain of? Come me to what was done to her.\n<bob> alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n<bob> zero.[07:00]\n<bob> 0.5\n* bob one.\n<bob> two.\n<bob> 2.5\n* bob three\n<bob> four.\n" 2 3 (erc--msg datestamp erc--ts 0 field erc-timestamp) 3 20 (field erc-timestamp wrap-prefix #1=(space :width 27) line-prefix (space :width (- 27 (18)))) 21 22 (erc--msg notice erc--ts 0 wrap-prefix #1# line-prefix #2=(space :width (- 27 (4)))) 22 183 (wrap-prefix #1# line-prefix #2#) 183 190 (field erc-timestamp wrap-prefix #1# line-prefix #2# display (#5=(margin right-margin) #("[00:00]" 0 7 (invisible timestamp)))) 191 192 (erc--msg msg erc--spkr "alice" erc--ts 0 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #3=(space :width (- 27 (8)))) 192 197 (wrap-prefix #1# line-prefix #3#) 197 199 (wrap-prefix #1# line-prefix #3#) 199 202 (wrap-prefix #1# line-prefix #3#) 202 315 (wrap-prefix #1# line-prefix #3#) 316 348 (wrap-prefix #1# line-prefix #3#) 349 350 (erc--msg msg erc--spkr "bob" erc--ts 0 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #4=(space :width (- 27 (6)))) 350 353 (wrap-prefix #1# line-prefix #4#) 353 355 (wrap-prefix #1# line-prefix #4#) 355 360 (wrap-prefix #1# line-prefix #4#) 360 435 (wrap-prefix #1# line-prefix #4#) 436 437 (erc--msg datestamp erc--ts 1680307200 field erc-timestamp) 437 454 (field erc-timestamp wrap-prefix #1# line-prefix (space :width (- 27 (18)))) 455 456 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #6=(space :width (- 27 (6)))) 456 459 (wrap-prefix #1# line-prefix #6#) 459 466 (wrap-prefix #1# line-prefix #6#) 466 473 (field erc-timestamp wrap-prefix #1# line-prefix #6# display (#5# #("[07:00]" 0 7 (invisible timestamp)))) 474 475 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #7=(space :width (- 27 0)) display #8="") 475 478 (wrap-prefix #1# line-prefix #7# display #8#) 478 480 (wrap-prefix #1# line-prefix #7# display #8#) 480 483 (wrap-prefix #1# line-prefix #7#) 484 485 (erc--msg ctcp-action erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #9=(space :width (- 27 (6)))) 485 486 (wrap-prefix #1# line-prefix #9#) 486 489 (wrap-prefix #1# line-prefix #9#) 489 494 (wrap-prefix #1# line-prefix #9#) 495 496 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #10=(space :width (- 27 (6)))) 496 499 (wrap-prefix #1# line-prefix #10#) 499 505 (wrap-prefix #1# line-prefix #10#) 506 507 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #11=(space :width (- 27 0)) display #8#) 507 510 (wrap-prefix #1# line-prefix #11# display #8#) 510 512 (wrap-prefix #1# line-prefix #11# display #8#) 512 515 (wrap-prefix #1# line-prefix #11#) 516 517 (erc--msg ctcp-action erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #12=(space :width (- 27 (2)))) 517 518 (wrap-prefix #1# line-prefix #12#) 518 521 (wrap-prefix #1# line-prefix #12#) 521 527 (wrap-prefix #1# line-prefix #12#) 528 529 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #13=(space :width (- 27 (6)))) 529 532 (wrap-prefix #1# line-prefix #13#) 532 539 (wrap-prefix #1# line-prefix #13#)) \ No newline at end of file
diff --git a/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-post-01.eld b/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-post-01.eld
index a63fcad3d38..ed1488c8595 100644
--- a/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-post-01.eld
+++ b/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-post-01.eld
@@ -1 +1 @@
#("\n\n\n[Thu Jan 1 1970]\n*** This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.[00:00]\n<alice> bob: come, you are a tedious fool: to the purpose. What was done to Elbow's wife, that he hath cause to complain of? Come me to what was done to her.\n<bob> alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n<bob> zero.[07:00]\n<bob> 0.5\n* bob one.\n<bob> two.\n<bob> 2.5\n* bob three\n<bob> four.\n" 2 3 (erc--msg datestamp erc--ts 0 field erc-timestamp) 3 20 (field erc-timestamp wrap-prefix #1=(space :width 27) line-prefix (space :width (- 27 (18)))) 21 22 (erc--msg notice erc--ts 0 wrap-prefix #1# line-prefix #2=(space :width (- 27 (4)))) 22 183 (wrap-prefix #1# line-prefix #2#) 183 190 (field erc-timestamp wrap-prefix #1# line-prefix #2# display (#5=(margin right-margin) #("[00:00]" 0 7 (invisible timestamp)))) 191 192 (erc--msg msg erc--ts 0 erc--spkr "alice" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #3=(space :width (- 27 (8)))) 192 197 (wrap-prefix #1# line-prefix #3#) 197 199 (wrap-prefix #1# line-prefix #3#) 199 202 (wrap-prefix #1# line-prefix #3#) 202 315 (wrap-prefix #1# line-prefix #3#) 316 348 (wrap-prefix #1# line-prefix #3#) 349 350 (erc--msg msg erc--ts 0 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #4=(space :width (- 27 (6)))) 350 353 (wrap-prefix #1# line-prefix #4#) 353 355 (wrap-prefix #1# line-prefix #4#) 355 360 (wrap-prefix #1# line-prefix #4#) 360 435 (wrap-prefix #1# line-prefix #4#) 436 437 (erc--msg datestamp erc--ts 1680307200 field erc-timestamp) 437 454 (field erc-timestamp wrap-prefix #1# line-prefix (space :width (- 27 (18)))) 455 456 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #6=(space :width (- 27 (6)))) 456 459 (wrap-prefix #1# line-prefix #6#) 459 466 (wrap-prefix #1# line-prefix #6#) 466 473 (field erc-timestamp wrap-prefix #1# line-prefix #6# display (#5# #("[07:00]" 0 7 (invisible timestamp)))) 474 475 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #7=(space :width (- 27 0)) display #8="") 475 478 (wrap-prefix #1# line-prefix #7# display #8#) 478 480 (wrap-prefix #1# line-prefix #7# display #8#) 480 483 (wrap-prefix #1# line-prefix #7#) 484 485 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #9=(space :width (- 27 (6)))) 485 486 (wrap-prefix #1# line-prefix #9#) 486 489 (wrap-prefix #1# line-prefix #9#) 489 494 (wrap-prefix #1# line-prefix #9#) 495 496 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #10=(space :width (- 27 (6)))) 496 499 (wrap-prefix #1# line-prefix #10#) 499 505 (wrap-prefix #1# line-prefix #10#) 505 506 (display #("~\n" 0 2 (font-lock-face shadow))) 506 507 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #11=(space :width (- 27 0)) display #8#) 507 510 (wrap-prefix #1# line-prefix #11# display #8#) 510 512 (wrap-prefix #1# line-prefix #11# display #8#) 512 515 (wrap-prefix #1# line-prefix #11#) 516 517 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #12=(space :width (- 27 (2)))) 517 518 (wrap-prefix #1# line-prefix #12#) 518 521 (wrap-prefix #1# line-prefix #12#) 521 527 (wrap-prefix #1# line-prefix #12#) 528 529 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #13=(space :width (- 27 (6)))) 529 532 (wrap-prefix #1# line-prefix #13#) 532 539 (wrap-prefix #1# line-prefix #13#)) \ No newline at end of file #("\n\n\n[Thu Jan 1 1970]\n*** This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.[00:00]\n<alice> bob: come, you are a tedious fool: to the purpose. What was done to Elbow's wife, that he hath cause to complain of? Come me to what was done to her.\n<bob> alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n<bob> zero.[07:00]\n<bob> 0.5\n* bob one.\n<bob> two.\n<bob> 2.5\n* bob three\n<bob> four.\n" 2 3 (erc--msg datestamp erc--ts 0 field erc-timestamp) 3 20 (field erc-timestamp wrap-prefix #1=(space :width 27) line-prefix (space :width (- 27 (18)))) 21 22 (erc--msg notice erc--ts 0 wrap-prefix #1# line-prefix #2=(space :width (- 27 (4)))) 22 183 (wrap-prefix #1# line-prefix #2#) 183 190 (field erc-timestamp wrap-prefix #1# line-prefix #2# display (#5=(margin right-margin) #("[00:00]" 0 7 (invisible timestamp)))) 191 192 (erc--msg msg erc--spkr "alice" erc--ts 0 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #3=(space :width (- 27 (8)))) 192 197 (wrap-prefix #1# line-prefix #3#) 197 199 (wrap-prefix #1# line-prefix #3#) 199 202 (wrap-prefix #1# line-prefix #3#) 202 315 (wrap-prefix #1# line-prefix #3#) 316 348 (wrap-prefix #1# line-prefix #3#) 349 350 (erc--msg msg erc--spkr "bob" erc--ts 0 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #4=(space :width (- 27 (6)))) 350 353 (wrap-prefix #1# line-prefix #4#) 353 355 (wrap-prefix #1# line-prefix #4#) 355 360 (wrap-prefix #1# line-prefix #4#) 360 435 (wrap-prefix #1# line-prefix #4#) 436 437 (erc--msg datestamp erc--ts 1680307200 field erc-timestamp) 437 454 (field erc-timestamp wrap-prefix #1# line-prefix (space :width (- 27 (18)))) 455 456 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #6=(space :width (- 27 (6)))) 456 459 (wrap-prefix #1# line-prefix #6#) 459 466 (wrap-prefix #1# line-prefix #6#) 466 473 (field erc-timestamp wrap-prefix #1# line-prefix #6# display (#5# #("[07:00]" 0 7 (invisible timestamp)))) 474 475 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #7=(space :width (- 27 0)) display #8="") 475 478 (wrap-prefix #1# line-prefix #7# display #8#) 478 480 (wrap-prefix #1# line-prefix #7# display #8#) 480 483 (wrap-prefix #1# line-prefix #7#) 484 485 (erc--msg ctcp-action erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #9=(space :width (- 27 (6)))) 485 486 (wrap-prefix #1# line-prefix #9#) 486 489 (wrap-prefix #1# line-prefix #9#) 489 494 (wrap-prefix #1# line-prefix #9#) 495 496 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #10=(space :width (- 27 (6)))) 496 499 (wrap-prefix #1# line-prefix #10#) 499 505 (wrap-prefix #1# line-prefix #10#) 505 506 (display #("~\n" 0 2 (font-lock-face shadow))) 506 507 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #11=(space :width (- 27 0)) display #8#) 507 510 (wrap-prefix #1# line-prefix #11# display #8#) 510 512 (wrap-prefix #1# line-prefix #11# display #8#) 512 515 (wrap-prefix #1# line-prefix #11#) 516 517 (erc--msg ctcp-action erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #12=(space :width (- 27 (2)))) 517 518 (wrap-prefix #1# line-prefix #12#) 518 521 (wrap-prefix #1# line-prefix #12#) 521 527 (wrap-prefix #1# line-prefix #12#) 528 529 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #13=(space :width (- 27 (6)))) 529 532 (wrap-prefix #1# line-prefix #13#) 532 539 (wrap-prefix #1# line-prefix #13#)) \ No newline at end of file
diff --git a/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-pre-01.eld b/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-pre-01.eld
index 7cbabfd0581..a3530a6c44d 100644
--- a/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-pre-01.eld
+++ b/test/lisp/erc/resources/fill/snapshots/merge-wrap-indicator-pre-01.eld
@@ -1 +1 @@
#("\n\n\n[Thu Jan 1 1970]\n*** This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.[00:00]\n<alice> bob: come, you are a tedious fool: to the purpose. What was done to Elbow's wife, that he hath cause to complain of? Come me to what was done to her.\n<bob> alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n<bob> zero.[07:00]\n<bob> 0.5\n* bob one.\n<bob> two.\n<bob> 2.5\n* bob three\n<bob> four.\n" 2 3 (erc--msg datestamp erc--ts 0 field erc-timestamp) 3 20 (field erc-timestamp wrap-prefix #1=(space :width 27) line-prefix (space :width (- 27 (18)))) 21 22 (erc--msg notice erc--ts 0 wrap-prefix #1# line-prefix #2=(space :width (- 27 (4)))) 22 183 (wrap-prefix #1# line-prefix #2#) 183 190 (field erc-timestamp wrap-prefix #1# line-prefix #2# display (#5=(margin right-margin) #("[00:00]" 0 7 (invisible timestamp)))) 191 192 (erc--msg msg erc--ts 0 erc--spkr "alice" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #3=(space :width (- 27 (8)))) 192 197 (wrap-prefix #1# line-prefix #3#) 197 199 (wrap-prefix #1# line-prefix #3#) 199 202 (wrap-prefix #1# line-prefix #3#) 202 315 (wrap-prefix #1# line-prefix #3#) 316 348 (wrap-prefix #1# line-prefix #3#) 349 350 (erc--msg msg erc--ts 0 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #4=(space :width (- 27 (6)))) 350 353 (wrap-prefix #1# line-prefix #4#) 353 355 (wrap-prefix #1# line-prefix #4#) 355 360 (wrap-prefix #1# line-prefix #4#) 360 435 (wrap-prefix #1# line-prefix #4#) 436 437 (erc--msg datestamp erc--ts 1680307200 field erc-timestamp) 437 454 (field erc-timestamp wrap-prefix #1# line-prefix (space :width (- 27 (18)))) 455 456 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #6=(space :width (- 27 (6)))) 456 459 (wrap-prefix #1# line-prefix #6#) 459 466 (wrap-prefix #1# line-prefix #6#) 466 473 (field erc-timestamp wrap-prefix #1# line-prefix #6# display (#5# #("[07:00]" 0 7 (invisible timestamp)))) 474 475 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #7=(space :width (- 27 #10=(2))) display #8=#("> " 0 1 (font-lock-face shadow))) 475 478 (wrap-prefix #1# line-prefix #7# display #8#) 478 480 (wrap-prefix #1# line-prefix #7# display #8#) 480 483 (wrap-prefix #1# line-prefix #7#) 484 485 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #9=(space :width (- 27 (6)))) 485 486 (wrap-prefix #1# line-prefix #9#) 486 489 (wrap-prefix #1# line-prefix #9#) 489 494 (wrap-prefix #1# line-prefix #9#) 495 496 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #11=(space :width (- 27 (6)))) 496 499 (wrap-prefix #1# line-prefix #11#) 499 505 (wrap-prefix #1# line-prefix #11#) 506 507 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #12=(space :width (- 27 #10#)) display #8#) 507 510 (wrap-prefix #1# line-prefix #12# display #8#) 510 512 (wrap-prefix #1# line-prefix #12# display #8#) 512 515 (wrap-prefix #1# line-prefix #12#) 516 517 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #13=(space :width (- 27 (2)))) 517 518 (wrap-prefix #1# line-prefix #13#) 518 521 (wrap-prefix #1# line-prefix #13#) 521 527 (wrap-prefix #1# line-prefix #13#) 528 529 (erc--msg msg erc--ts 1680332400 erc--spkr "bob" erc--cmd PRIVMSG wrap-prefix #1# line-prefix #14=(space :width (- 27 (6)))) 529 532 (wrap-prefix #1# line-prefix #14#) 532 539 (wrap-prefix #1# line-prefix #14#)) \ No newline at end of file #("\n\n\n[Thu Jan 1 1970]\n*** This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.[00:00]\n<alice> bob: come, you are a tedious fool: to the purpose. What was done to Elbow's wife, that he hath cause to complain of? Come me to what was done to her.\n<bob> alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n<bob> zero.[07:00]\n<bob> 0.5\n* bob one.\n<bob> two.\n<bob> 2.5\n* bob three\n<bob> four.\n" 2 3 (erc--msg datestamp erc--ts 0 field erc-timestamp) 3 20 (field erc-timestamp wrap-prefix #1=(space :width 27) line-prefix (space :width (- 27 (18)))) 21 22 (erc--msg notice erc--ts 0 wrap-prefix #1# line-prefix #2=(space :width (- 27 (4)))) 22 183 (wrap-prefix #1# line-prefix #2#) 183 190 (field erc-timestamp wrap-prefix #1# line-prefix #2# display (#5=(margin right-margin) #("[00:00]" 0 7 (invisible timestamp)))) 191 192 (erc--msg msg erc--spkr "alice" erc--ts 0 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #3=(space :width (- 27 (8)))) 192 197 (wrap-prefix #1# line-prefix #3#) 197 199 (wrap-prefix #1# line-prefix #3#) 199 202 (wrap-prefix #1# line-prefix #3#) 202 315 (wrap-prefix #1# line-prefix #3#) 316 348 (wrap-prefix #1# line-prefix #3#) 349 350 (erc--msg msg erc--spkr "bob" erc--ts 0 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #4=(space :width (- 27 (6)))) 350 353 (wrap-prefix #1# line-prefix #4#) 353 355 (wrap-prefix #1# line-prefix #4#) 355 360 (wrap-prefix #1# line-prefix #4#) 360 435 (wrap-prefix #1# line-prefix #4#) 436 437 (erc--msg datestamp erc--ts 1680307200 field erc-timestamp) 437 454 (field erc-timestamp wrap-prefix #1# line-prefix (space :width (- 27 (18)))) 455 456 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #6=(space :width (- 27 (6)))) 456 459 (wrap-prefix #1# line-prefix #6#) 459 466 (wrap-prefix #1# line-prefix #6#) 466 473 (field erc-timestamp wrap-prefix #1# line-prefix #6# display (#5# #("[07:00]" 0 7 (invisible timestamp)))) 474 475 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #7=(space :width (- 27 #10=(2))) display #8=#("> " 0 1 (font-lock-face shadow))) 475 478 (wrap-prefix #1# line-prefix #7# display #8#) 478 480 (wrap-prefix #1# line-prefix #7# display #8#) 480 483 (wrap-prefix #1# line-prefix #7#) 484 485 (erc--msg ctcp-action erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #9=(space :width (- 27 (6)))) 485 486 (wrap-prefix #1# line-prefix #9#) 486 489 (wrap-prefix #1# line-prefix #9#) 489 494 (wrap-prefix #1# line-prefix #9#) 495 496 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #11=(space :width (- 27 (6)))) 496 499 (wrap-prefix #1# line-prefix #11#) 499 505 (wrap-prefix #1# line-prefix #11#) 506 507 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #12=(space :width (- 27 #10#)) display #8#) 507 510 (wrap-prefix #1# line-prefix #12# display #8#) 510 512 (wrap-prefix #1# line-prefix #12# display #8#) 512 515 (wrap-prefix #1# line-prefix #12#) 516 517 (erc--msg ctcp-action erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG erc--ctcp ACTION wrap-prefix #1# line-prefix #13=(space :width (- 27 (2)))) 517 518 (wrap-prefix #1# line-prefix #13#) 518 521 (wrap-prefix #1# line-prefix #13#) 521 527 (wrap-prefix #1# line-prefix #13#) 528 529 (erc--msg msg erc--spkr "bob" erc--ts 1680332400 erc--cmd PRIVMSG wrap-prefix #1# line-prefix #14=(space :width (- 27 (6)))) 529 532 (wrap-prefix #1# line-prefix #14#) 532 539 (wrap-prefix #1# line-prefix #14#)) \ No newline at end of file