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, other nicks are tried in the list order. @end defopt -@defopt erc-format-nick-function -A function to format a nickname for message display - -You can set this to @code{erc-format-@@nick} to display user mode prefix +@defopt erc-show-speaker-membership-status +A boolean for including a channel member's @dfn{status prefix} in +their display name when they speak. @end defopt -@example -(setq erc-format-nick-function 'erc-format-@@nick) -@end example - @defopt erc-nick-uniquifier The string to append to the nick if it is already in use. @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 possibly truncated arguments, with the exception of stateful list modes, like "b". +** In-buffer "status messages" are now a thing. +The ancient option 'erc-ensure-target-buffer-on-privmsg' has been +repurposed slightly to express a third state denoted by the symbol +'status'. It tells ERC to revert to the old default behavior in which +separate, "pseudo" target buffers for status-prefixed conversing +co-existed alongside actual target buffers. Instead of this awkward +arrangement, ERC now acts like other clients by default and inserts +so-called "status messages" in situ, right between other messages. +Similar insertion-routing behavior now also applies to CTCP ACTIONs +directed at status-prefixed channels. Unfortunately, outgoing "/msg +@#chan hi" messages aren't yet shown in the same fashion, but the +groundwork has been laid, making such an addition almost trivial. + +** An easier way to see channel-membership prefixes on speakers. +The option 'erc-format-@nick' has been deprecated in favor of the new +boolean option 'erc-show-speaker-membership-status', a simple switch +to enable the displaying of status prefixes on the speaker nicks of +incoming chat messages. Prefixes on your speaker nick for outgoing +chat messages continue to always be present. + ** Miscellaneous UX changes. Some minor quality-of-life niceties have finally made their way to ERC. 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 been deprecated in favor of 'erc-define-message-format-catalog', a new macro for defining template "catalogs" at the top level of libraries. +*** Interface for determining display names renamed. +The option 'erc-format-nick-function' has been renamed to +'erc-speaker-from-channel-member-function' to better reflect its +actual role. So too has the related function 'erc-format-nick', which +is now 'erc-determine-speaker-from user'. + +*** A template-based approach to formatting inserted chat messages. +Predicting and influencing how ERC formats messages containing a +leading "" has never been straightforward. The characters +bracketing the speaker and the faces used for each component have +always been hard-coded, with 'erc-format-query-as-channel-p' being the +only knob of any consequence. With this release, ERC begins its +transition to a unified formatting paradigm that builds upon the +already familiar "language catalog" templating system. Using a +separate "speaker catalog" keyed by contextual symbols, like +'query-privmsg', ERC (and eventually everyone) will more easily be +able to influence how inserted messages take shape in buffers. + +*** New format templates for inserted CTCP ACTION messages. +In 5.5 and earlier, ERC displayed outgoing CTCP ACTION messages in +'erc-input-face' alone (before buttonizing). Incoming ACTION messages +mirrored this, except with 'erc-action-face' throughout. Going +forward, inserted outgoing "/ME" messages will also incorporate +'erc-action-face', only underneath 'erc-input-face', with +'erc-my-nick-face' sitting atop both in the leading "speaker" nickname +portion (again, pre-buttonizing). This new behavior sidesteps the +traditional format template 'erc-message-english-ACTION' from the +default "language catalog" in favor of an entry from the new internal +"speaker catalog". Users needing to access the old behavior can do so +by toggling a provided compatibility switch. See source code around +the function 'erc-send-action' for details. + *** Miscellaneous changes Two helper macros from GNU ELPA's Compat library are now available to third-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 @@ (declare-function erc-display-server-message "erc" (_proc parsed)) (declare-function erc-emacs-time-to-erc-time "erc" (&optional specified-time)) (declare-function erc-format-message "erc" (msg &rest args)) -(declare-function erc-format-privmessage "erc" (nick msg privp msgp)) (declare-function erc-get-buffer "erc" (target &optional proc)) (declare-function erc-handle-login "erc" nil) (declare-function erc-handle-user-status-change "erc" (type nlh &optional l)) @@ -173,6 +172,9 @@ (declare-function erc-update-mode-line-buffer "erc" (buffer)) (declare-function erc-wash-quit-reason "erc" (reason nick login host)) +(declare-function erc--determine-speaker-message-format-args "erc" + (nick target message queryp privmsgp statusmsgp inputp + &optional prefix disp-nick)) (declare-function erc-display-message "erc" (parsed type buffer msg &rest args)) (declare-function erc-get-buffer-create "erc" @@ -1906,6 +1908,66 @@ add things to `%s' instead." ?s (if (/= erc-server-lag 1) "s" ""))) (erc-update-mode-line)))) +(defun erc--statusmsg-target (target) + "Return actual target from given TARGET if it has a leading prefix char." + (and-let* ((erc-ensure-target-buffer-on-privmsg) + ((not (eq erc-ensure-target-buffer-on-privmsg 'status))) + ((not (erc-channel-p target))) + (chars (erc--get-isupport-entry 'STATUSMSG 'single)) + ((string-search (string (aref target 0)) chars)) + (trimmed (substring target 1)) + ((erc-channel-p trimmed))) + trimmed)) + +;; Moved to this file from erc.el in ERC 5.6. +(defvar-local erc-current-message-catalog 'english + "Current language or context catalog for formatting inserted messages. +See `erc-format-message'.") + +;; This variable can be made public if the current design proves +;; sufficient. +(defvar erc--message-speaker-catalog '-speaker + "The \"speaker\" catalog symbol used to format PRIVMSGs and NOTICEs. + +This symbol defines a \"catalog\" of variables and functions +whose names reflect their membership via a corresponding CATALOG +component, as in \"erc-message-CATALOG-KEY\". Here, KEY refers +to a common set of interface members (variables or functions), +that an implementer must define: + +- `statusmsg' and `statusmsg-input': PRIVMSGs whose target is a + status-prefixed channel; the latter is the \"echoed\" version + +- `chan-privmsg', `query-privmsg', `chan-notice', `query-notice': + standard chat messages traditionally prefixed by a + indicating the message's \"speaker\" + +- `input-chan-privmsg', `input-query-privmsg', `input-query-notice', + `input-chan-notice': \"echoed\" versions of the above + +- `ctcp-action', `ctcp-action-input', `ctcp-action-statusmsg', + `ctcp-action-statusmsg-input': \"CTCP ACTION\" versions of the + above + +The other part of this interface is the per-key collection of +`format-spec' parameters members must support. For simplicity, +this catalog currently defines a common set for all keys, some of +which may be assigned the empty string when not applicable: + + %n - nickname + %m - message body + %p - nickname's status prefix (when applicable) + %s - current target's STATUSMSG prefix (when applicable) + +As an added means of communicating with various modules, if this +catalog's symbol has the property `erc--msg-prop-overrides', +consumers calling `erc-display-message' will see the value added +to the `erc--msg-props' \"environment\" in modification hooks, +like `erc-insert-modify-hook'.") + +(defvar erc--speaker-status-prefix-wanted-p (gensym "erc-") + "Sentinel to detect whether `erc-format-@nick' has just run.") + (define-erc-response-handler (PRIVMSG NOTICE) "Handle private messages, including messages in channels." nil (let ((sender-spec (erc-response.sender parsed)) @@ -1927,12 +1989,15 @@ add things to `%s' instead." (msgp (string= cmd "PRIVMSG")) (noticep (string= cmd "NOTICE")) ;; S.B. downcase *both* tgt and current nick - (privp (erc-current-nick-p tgt)) + (medown (erc-downcase (erc-current-nick))) + (inputp (string= medown (erc-downcase nick))) + (privp (string= (erc-downcase tgt) medown)) (erc--display-context `((erc-buffer-display . ,(intern cmd)) ,@erc--display-context)) - (erc--msg-prop-overrides `((erc--msg . msg) - ,@erc--msg-prop-overrides)) - s buffer + (erc--msg-prop-overrides `((erc--tmp) ,@erc--msg-prop-overrides)) + (erc--speaker-status-prefix-wanted-p nil) + (erc-current-message-catalog erc--message-speaker-catalog) + s buffer statusmsg cmem-prefix fnick) (setq buffer (erc-get-buffer (if privp nick tgt) proc)) ;; Even worth checking for empty target here? (invalid anyway) @@ -1950,9 +2015,14 @@ add things to `%s' instead." (push `(erc-receive-query-display . ,(intern cmd)) erc--display-context) (setq buffer (erc--open-target nick))) - ;; A channel buffer has been killed but is still joined. - (when erc-ensure-target-buffer-on-privmsg - (setq buffer (erc--open-target tgt))))) + (cond + ;; Target is a channel and contains leading @+ chars. + ((and-let* ((trimmed(erc--statusmsg-target tgt))) + (setq buffer (erc-get-buffer trimmed proc) + statusmsg (and buffer (substring tgt 0 1))))) + ;; A channel buffer has been killed but is still joined. + (erc-ensure-target-buffer-on-privmsg + (setq buffer (erc--open-target tgt)))))) (when buffer (with-current-buffer buffer (when privp (erc--unhide-prompt)) @@ -1963,36 +2033,46 @@ add things to `%s' instead." privp nil nil nil nil nil host login nil nil t) (defvar erc--cmem-from-nick-function) (defvar erc-format-nick-function) + (defvar erc-show-speaker-membership-status) + (defvar erc-speaker-from-channel-member-function) (let ((cdata (funcall erc--cmem-from-nick-function (erc-downcase nick) sndr parsed))) - (setq fnick (funcall erc-format-nick-function - (car cdata) (cdr cdata)))))) + (setq fnick (funcall erc-speaker-from-channel-member-function + (car cdata) (cdr cdata)) + cmem-prefix (and (or erc--speaker-status-prefix-wanted-p + erc-show-speaker-membership-status + inputp) + (cdr cdata)))))) (cond ((erc-is-message-ctcp-p msg) - (setq s (if msgp + ;; FIXME explain undefined return values being assigned to `s'. + (setq s (if-let ((parsed + (erc--ctcp-response-from-parsed + :parsed parsed :buffer buffer :statusmsg statusmsg + :prefix cmem-prefix :dispname fnick)) + (msgp)) (erc-process-ctcp-query proc parsed nick login host) (erc-process-ctcp-reply proc parsed nick login host (match-string 1 msg))))) (t (setq erc-server-last-peers (cons nick (cdr erc-server-last-peers))) - (defvar erc-format-query-as-channel-p) - (setq s (erc-format-privmessage - (or fnick nick) msg - ;; If buffer is a query buffer, - ;; format the nick as for a channel. - (and (not (and buffer - (erc-query-buffer-p buffer) - erc-format-query-as-channel-p)) - privp) - msgp)))) + (with-current-buffer (or buffer (current-buffer)) + ;; Re-bind in case either buffer has a local value. + (let ((erc-current-message-catalog erc--message-speaker-catalog)) + (setq s (erc--determine-speaker-message-format-args + nick msg privp msgp inputp statusmsg + cmem-prefix fnick)))))) (when s (if (and noticep privp) (progn + (push (cons 'erc--msg (car s)) erc--msg-prop-overrides) + (setq s (apply #'erc-format-message s)) (run-hook-with-args 'erc-echo-notice-always-hook s parsed buffer nick) (run-hook-with-args-until-success 'erc-echo-notice-hook s parsed buffer nick)) - (erc-display-message parsed nil buffer s))))))) + (apply #'erc-display-message parsed nil buffer + (ensure-list s)))))))) (define-erc-response-handler (QUIT) "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 @@ (contents "" :type string) (tags '() :type list)) +(cl-defstruct (erc--ctcp-response + (:include erc-response) + (:constructor + erc--ctcp-response-from-parsed + (&key parsed buffer statusmsg prefix dispname + &aux (unparsed (erc-response.unparsed parsed)) + (sender (erc-response.sender parsed)) + (command (erc-response.command parsed)) + (command-args (erc-response.command-args parsed)) + (contents (erc-response.contents parsed)) + (tags (erc-response.tags parsed))))) + "Data for a processed CTCP query or reply." + (buffer nil :type (or buffer null)) + (statusmsg nil :type (or null string)) + (prefix nil :type (or erc-channel-user null)) + (dispname nil :type (or string null))) + (cl-defstruct erc--isupport-data "Abstract \"class\" for parsed ISUPPORT data. For 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." (defun erc-dcc-chat-parse-output (proc str) (save-match-data (let ((posn 0) + (erc--msg-prop-overrides `((erc--spkr . ,erc-dcc-from))) + (nick (propertize (erc--speakerize-nick erc-dcc-from) + 'font-lock-face 'erc-nick-default-face)) line) (while (string-match "\n" str posn) (setq line (substring str posn (match-beginning 0))) (setq posn (match-end 0)) (erc-display-message nil nil proc - 'dcc-chat-privmsg ?n (propertize erc-dcc-from 'font-lock-face - 'erc-nick-default-face) ?m line)) + 'dcc-chat-privmsg ?n nick ?m line)) (setq erc-dcc-unprocessed-output (substring str posn))))) (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 variable can be converted to a public one if needed by third parties.") -(defvar-local erc-fill--wrap-last-msg nil) -(defvar erc-fill--wrap-max-lull (* 24 60 60)) +(defvar-local erc-fill--wrap-last-msg nil "Marker for merging speakers.") +(defvar erc-fill--wrap-max-lull (* 24 60 60) "Max secs for merging speakers.") (defun erc-fill--wrap-continued-message-p () "Return non-nil when the current speaker hasn't changed. -That is, indicate whether the text just inserted is from the same -sender as that of the previous \"PRIVMSG\". As a side effect, -advance `erc-fill--wrap-last-msg' unless the message has been -marked as being ephemeral." - (and - (not (erc--check-msg-prop 'erc--ephemeral)) - (progn ; preserve blame for now, unprogn on next major change - (prog1 - (and-let* - ((m (or erc-fill--wrap-last-msg - (setq erc-fill--wrap-last-msg (point-min-marker)) - nil)) - ((< (1+ (point-min)) (- (point) 2))) - (props (save-restriction - (widen) - (and-let* - ((speaker (get-text-property m 'erc--spkr)) - ((not (eq (get-text-property m 'erc--ctcp) - 'ACTION))) - ((not (invisible-p m)))) - (cons (get-text-property m 'erc--ts) speaker)))) - (ts (pop props)) - (props) - ((not (time-less-p (erc-stamp--current-time) ts))) - ((time-less-p (time-subtract (erc-stamp--current-time) ts) - erc-fill--wrap-max-lull)) - ;; Assume presence of leading angle bracket or hyphen. - (nick (erc--check-msg-prop 'erc--spkr)) - ((not (erc--check-msg-prop 'erc--ctcp 'ACTION))) - ((erc-nick-equal-p props nick)))) - (set-marker erc-fill--wrap-last-msg (point-min)))))) +But only if the `erc--msg' text property also hasn't. That is, +indicate whether the chat message just inserted is from the same +person as the prior one and is formatted in the same manner. As +a side effect, advance `erc-fill--wrap-last-msg' unless the +message has been marked `erc--ephemeral'." + (and-let* + (((not (erc--check-msg-prop 'erc--ephemeral))) + ;; Always set/move `erc-fill--wrap-last-msg' from here on down. + (m (or (and erc-fill--wrap-last-msg + (prog1 (marker-position erc-fill--wrap-last-msg) + (set-marker erc-fill--wrap-last-msg (point-min)))) + (ignore (setq erc-fill--wrap-last-msg (point-min-marker))))) + ((>= (point) 4)) ; skip the first message + (props (save-restriction + (widen) + (and-let* ((speaker (get-text-property m 'erc--spkr)) + (type (get-text-property m 'erc--msg)) + ((not (invisible-p m)))) + (list (get-text-property m 'erc--ts) type speaker)))) + (ts (nth 0 props)) + (type (nth 1 props)) + (speaker (nth 2 props)) + ((not (time-less-p (erc-stamp--current-time) ts))) + ((time-less-p (time-subtract (erc-stamp--current-time) ts) + erc-fill--wrap-max-lull)) + ((erc--check-msg-prop 'erc--msg type)) + ((erc-nick-equal-p speaker (erc--check-msg-prop 'erc--spkr)))))) (defun erc-fill--wrap-measure (beg end) "Return display spec width for inserted region between BEG and END. @@ -747,8 +743,11 @@ With REPAIRP, destructively fill gaps and re-merge speakers." ((equal "" dval))) (remove-text-properties dbeg (text-property-not-all dbeg end 'display dval) '(display))) - (let* ((pos (if (eq 'date-left (get-text-property beg 'erc-stamp-type)) - (field-beginning beg) + ;; This "should" work w/o `front-sticky' and `rear-nonsticky'. + (let* ((pos (if-let (((eq 'erc-timestamp (field-at-pos beg))) + (b (field-beginning beg)) + ((eq 'datestamp (get-text-property b 'erc--msg)))) + b beg)) (erc--msg-props (map-into (text-properties-at pos) 'hash-table)) (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 value of t means the option's value doesn't require trimming.") (defun erc-stamp--propertize-left-date-stamp () - (add-text-properties (point-min) (1- (point-max)) - '(field erc-timestamp erc-stamp-type date-left)) + (add-text-properties (point-min) (1- (point-max)) '(field erc-timestamp)) (erc--hide-message 'timestamp) (run-hooks 'erc-stamp--insert-date-hook)) 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 their markers accordingly. The following properties have meaning as of ERC 5.6: - - `erc--msg': a symbol, guaranteed present; values include: - `msg', signifying a `PRIVMSG' or an incoming `NOTICE'; - `unknown', a fallback for `erc-display-message'; a catalog - key, such as `s401' or `finished'; an `erc-display-message' - TYPE parameter, like `notice' + - `erc--msg': a symbol, guaranteed present; possible values + include `unknown', a fallback used by `erc-display-message'; a + catalog key, such as `s401' or `finished'; an + `erc-display-message' TYPE parameter, like `notice' - `erc--cmd': a message's associated IRC command, as read by `erc--get-eq-comparable-cmd'; currently either a symbol, like @@ -3017,20 +3016,34 @@ target, and an `erc-server-send' FORCE flag.") "Send STRING to TARGET, possibly immediately, with FORCE." (erc-send-ctcp-message target (format "ACTION %s" string) force)) +(defvar erc--use-language-catalog-for-ctcp-action-p nil + "When non-nil, use `ACTION' entry from language catalog for /ME's. +Otherwise, use `ctcp-action' or `ctcp-action-input' from the +internal `-speaker' catalog. This is an escape hatch to restore +pre-5.6 behavior for the `font-lock-face' property of incoming +and outgoing \"CTCP ACTION\" messages, whose pre-buttonized state +was a single interval of `erc-input-face' or `erc-action-face'. +Newer modules, like `fill-wrap' and `nicks', are incompatible with +this format style. If you use this, please ask ERC to expose it +as a public variable via \\[erc-bug] or similar.") + (defun erc--send-action-display (string) - "Display STRING as an outgoing \"CTCP ACTION\" message." + "Display STRING as an outgoing \"CTCP ACTION\" message. +Propertize the message according to the compatibility flag +`erc--use-language-catalog-for-ctcp-action-p'." ;; Allow hooks acting on inserted PRIVMSG and NOTICES to process us. - (defvar erc--merge-prop-behind-p) - (let* ((nick (erc-current-nick)) - (erc--msg-prop-overrides `((erc--msg . msg) - (erc--ctcp . ACTION) - (erc--spkr . ,nick) - ,@erc--msg-prop-overrides)) - (erc--merge-prop-behind-p t)) - (setq nick (propertize nick 'erc--speaker nick - 'font-lock-face 'erc-my-nick-face)) - (erc-display-message nil '(t input action) (current-buffer) - 'ACTION ?n nick ?a string ?u "" ?h ""))) + (let ((erc--msg-prop-overrides `((erc--ctcp . ACTION) + ,@erc--msg-prop-overrides)) + (nick (erc-current-nick))) + (if erc--use-language-catalog-for-ctcp-action-p + (progn (erc--ensure-spkr-prop nick) + (erc-display-message nil 'input (current-buffer) 'ACTION + ?n (propertize nick 'erc--speaker nick) + ?a string ?u "" ?h "")) + (let ((erc-current-message-catalog erc--message-speaker-catalog)) + (erc-display-message nil nil (current-buffer) 'ctcp-action-input + ?p (erc-get-channel-membership-prefix nick) + ?n (erc--speakerize-nick nick) ?m string))))) (defun erc--send-action (target string force) "Display STRING, then send to TARGET as a \"CTCP ACTION\" message." @@ -3039,11 +3052,20 @@ target, and an `erc-server-send' FORCE flag.") ;; Display interface -(defun erc--ensure-spkr-prop (nick) - "Maybe add NICK to `erc--msg-props' or `erc--msg-prop-overrides'." - (cond (erc--msg-props (puthash 'erc--spkr nick erc--msg-props)) +(defun erc--ensure-spkr-prop (nick &optional overrides) + "Add NICK as `erc--spkr' to the current \"msg props\" environment. +Prefer `erc--msg-props' over `erc--msg-prop-overrides' when both +are available. Also include any members of the alist OVERRIDES, +when present. Assume NICK itself to be free of any text props, +and return it." + (cond (erc--msg-props + (puthash 'erc--spkr nick erc--msg-props) + (dolist (entry overrides) + (puthash (car entry) (cdr entry) erc--msg-props))) (erc--msg-prop-overrides - (push (cons 'erc--spkr nick) erc--msg-prop-overrides)))) + (setq erc--msg-prop-overrides + `((erc--spkr . ,nick) ,@overrides ,@erc--msg-prop-overrides)))) + nick) (defun erc-string-invisible-p (string) "Check whether STRING is invisible or not. @@ -4659,15 +4681,26 @@ See also `erc-message' and `erc-display-line'." (funcall erc--send-message-nested-function line force) (erc--send-message-external line force))) -;; FIXME fully simulate `erc-display-msg'. This doesn't currently add -;; the correct text properties. For example, the LINE should have -;; `erc-default-face'. (defun erc--send-message-external (line force) - (erc-message "PRIVMSG" (concat (erc-default-target) " " line) force) - (erc-display-line - (concat (erc-format-my-nick) line) - (current-buffer)) + "Send a \"PRIVMSG\" to the default target with optional FORCE. +Expect caller to bind `erc-default-recipients' if needing to +specify a status-prefixed target." + ;; Almost like an echoed message, but without the `erc--cmd'. + (let* ((erc-current-message-catalog erc--message-speaker-catalog) + (target (erc-default-target)) + (erc--msg-prop-overrides `((erc--tmp) ,@erc--msg-prop-overrides)) + ;; This util sets the `erc--spkr' property in ^. + (trimmed (erc--statusmsg-target target)) + (stmsgindc (and trimmed (substring target 0 1))) + (queryp (and erc--target (not (erc--target-channel-p erc--target)))) + (args (erc--determine-speaker-message-format-args + (erc-current-nick) line queryp 'privmsgp 'inputp + stmsgindc 'prefix))) + (erc-message "PRIVMSG" (concat target " " line) force) + (push (cons 'erc--msg (car args)) erc--msg-prop-overrides) + (apply #'erc-display-message nil nil (current-buffer) args)) ;; FIXME - treat multiline, run hooks, or remove me? + ;; FIXME explain this ^ in more detail or remove. t) (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." rear-nonsticky erc-prompt field front-sticky read-only ;; stamp cursor-intangible cursor-sensor-functions isearch-open-invisible - erc-stamp-type ;; match invisible intangible ;; button @@ -5626,7 +5658,9 @@ manner implied above, which was lost sometime before ERC 5.4." :package-version '(ERC . "5.6") ; revived :group 'erc-buffers :group 'erc-query - :type 'boolean) + :type '(choice boolean + (choice :tag "Create pseudo queries for STATUSMSGs" + status))) (defcustom erc-format-query-as-channel-p t "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.") 'font-lock-face msg-face str) str)) -(defcustom erc-format-nick-function 'erc-format-nick - "Function to format a nickname for message display." - :group 'erc-display - :type 'function) +;; The format strings in the following `-speaker' catalog shouldn't +;; contain any non-protocol words, so they make sense in any language. -(defun erc-format-nick (&optional user _channel-data) - "Return the nickname of USER. -See also `erc-format-nick-function'." +(defvar erc--message-speaker-statusmsg + #("(%p%n%s) %m" + 0 1 (font-lock-face erc-default-face) + 1 3 (font-lock-face erc-nick-prefix-face) + 3 5 (font-lock-face erc-nick-default-face) + 5 7 (font-lock-face erc-notice-face) + 7 11 (font-lock-face erc-default-face)) + "Message template for in-channel status messages.") + +(defvar erc--message-speaker-statusmsg-input + #("(%p%n%s) %m" + 0 1 (font-lock-face erc-default-face) + 1 3 (font-lock-face erc-my-nick-prefix-face) + 3 5 (font-lock-face erc-my-nick-face) + 5 7 (font-lock-face erc-notice-face) + 7 8 (font-lock-face erc-default-face) + 8 11 (font-lock-face erc-input-face)) + "Message template for echoed status messages.") + +(defvar erc--message-speaker-input-chan-privmsg + #("<%p%n> %m" + 0 1 (font-lock-face erc-default-face) + 1 3 (font-lock-face erc-my-nick-prefix-face) + 3 5 (font-lock-face erc-my-nick-face) + 5 7 (font-lock-face erc-default-face) + 7 9 (font-lock-face erc-input-face)) + "Message template for prompt input or echoed PRIVMSG from own nick.") + +(defvar erc--message-speaker-input-query-privmsg + #("*%n* %m" + 0 1 (font-lock-face erc-direct-msg-face) + 1 3 (font-lock-face erc-my-nick-face) + 3 5 (font-lock-face erc-direct-msg-face) + 5 7 (font-lock-face erc-input-face)) + "Message template for prompt input or echoed PRIVMSG query from own nick.") + +(defvar erc--message-speaker-input-query-notice + #("-%n- %m" + 0 1 (font-lock-face erc-direct-msg-face) + 1 3 (font-lock-face erc-my-nick-face) + 3 5 (font-lock-face erc-direct-msg-face) + 5 7 (font-lock-face erc-input-face)) + "Message template for echoed or spoofed query NOTICE from own nick.") + +(defvar erc--message-speaker-input-chan-notice + #("-%p%n- %m" + 0 1 (font-lock-face erc-default-face) + 1 3 (font-lock-face erc-my-nick-prefix-face) + 3 5 (font-lock-face erc-my-nick-face) + 5 7 (font-lock-face erc-default-face) + 7 9 (font-lock-face erc-input-face)) + "Message template for prompt input or echoed NOTICE from own nick.") + +(defvar erc--message-speaker-chan-privmsg + #("<%p%n> %m" + 0 1 (font-lock-face erc-default-face) + 1 3 (font-lock-face erc-nick-prefix-face) + 3 5 (font-lock-face erc-nick-default-face) + 5 9 (font-lock-face erc-default-face)) + "Message template for a PRIVMSG in a channel.") + +(defvar erc--message-speaker-query-privmsg + #("*%n* %m" + 0 1 (font-lock-face erc-direct-msg-face) + 1 3 (font-lock-face erc-nick-msg-face) + 3 7 (font-lock-face erc-direct-msg-face)) + "Message template for a PRIVMSG in query buffer.") + +(defvar erc--message-speaker-chan-notice + #("-%p%n- %m" + 0 1 (font-lock-face erc-default-face) + 1 3 (font-lock-face erc-nick-prefix-face) + 3 5 (font-lock-face erc-nick-default-face) + 5 9 (font-lock-face erc-default-face)) + "Message template for a NOTICE in a channel.") + +(defvar erc--message-speaker-query-notice + #("-%n- %m" + 0 1 (font-lock-face erc-direct-msg-face) + 1 3 (font-lock-face erc-nick-msg-face) + 3 7 (font-lock-face erc-direct-msg-face)) + "Message template for a NOTICE in a query buffer.") + +(defvar erc--message-speaker-ctcp-action + #("* %p%n %m" + 0 2 (font-lock-face erc-action-face) + 2 4 (font-lock-face (erc-nick-prefix-face erc-action-face)) + 4 9 (font-lock-face erc-action-face)) + "Message template for a CTCP ACTION from another user.") + +(defvar erc--message-speaker-ctcp-action-input + #("* %p%n %m" + 0 2 (font-lock-face #1=(erc-input-face erc-action-face)) + 2 4 (font-lock-face (erc-my-nick-prefix-face . #1#)) + 4 6 (font-lock-face (erc-my-nick-face . #1#)) + 6 9 (font-lock-face #1#)) + "Message template for a CTCP ACTION from current client.") + +(defvar erc--message-speaker-ctcp-action-statusmsg + #("* (%p%n%s) %m" + 0 3 (font-lock-face erc-action-face) + 3 5 (font-lock-face (erc-nick-prefix-face erc-action-face)) + 5 7 (font-lock-face erc-action-face) + 7 9 (font-lock-face (erc-notice-face erc-action-face)) + 9 13 (font-lock-face erc-action-face)) + "Template for a CTCP ACTION status message from another chan op.") + +(defvar erc--message-speaker-ctcp-action-statusmsg-input + #("* (%p%n%s) %m" + 0 3 (font-lock-face #1=(erc-input-face erc-action-face)) + 3 5 (font-lock-face (erc-my-nick-prefix-face . #1#)) + 5 7 (font-lock-face (erc-my-nick-face . #1#)) + 7 9 (font-lock-face (erc-notice-face . #1#)) + 9 13 (font-lock-face #1#)) + "Template for a CTCP ACTION status message from current client.") + +(defun erc--speakerize-nick (nick &optional disp) + "Propertize NICK with `erc--speaker' if not already present. +Do so to DISP instead if it's non-nil. In either case, assign +NICK, sans properties, as the `erc--speaker' value. As a side +effect, pair the latter string (the same `eq'-able object) with +the symbol `erc--spkr' in the \"msg prop\" environment for any +imminent `erc-display-message' invocations. While doing so, +include any overrides defined in `erc--message-speaker-catalog'." + (let ((plain-nick (substring-no-properties nick))) + (erc--ensure-spkr-prop plain-nick (get erc--message-speaker-catalog + 'erc--msg-prop-overrides)) + (if (text-property-not-all 0 (length (or disp nick)) + 'erc--speaker nil (or disp nick)) + (or disp nick) + (propertize (or disp nick) 'erc--speaker plain-nick)))) + +(defun erc--determine-speaker-message-format-args + (nick message queryp privmsgp inputp &optional statusmsg prefix disp-nick) + "Return a list consisting of a \"speaker\"-template key and spec args. +Consider the three flags QUERYP, PRIVMSGP, and INPUTP, as well as +the possibly null STATUSMSG string. (Combined, these describe +the context of a newly arrived \"PRIVMSG\" or, when PRIVMSGP is +nil, a \"NOTICE\"). Interpret QUERYP to mean that MESSAGE is +directed at the ERC client itself (a direct message), and INPUTP +to mean MESSAGE is an outgoing or echoed message originating from +or meant to simulate prompt input. Interpret a non-nil STATUSMSG +to mean MESSAGE should be formatted as a special channel message +intended for privileged members of the same or greater status. + +After deciding on the template key for the current \"speaker\" +catalog, use the remaining arguments, possibly along with +STATUSMSG, to construct the appropriate spec-args plist forming +the returned list's tail. In this plist, pair the char ?n with +NICK, the nickname of the speaker and ?m with MESSAGE, the +message body. When non-nil, assume DISP-NICK to be a possibly +phony display name to take the place of NICK for ?n. When PREFIX +is non-nil, look up NICK's channel-membership status, possibly +using PREFIX itself if it's an `erc-channel-user' object, which +it must be when called outside of a channel buffer. Pair the +result with the ?p specifier. When STATUSMSG is non-nil, pair it +with the ?s specifier. Ensure unused spec values are the empty +string rather than nil." + (when prefix + (setq prefix (erc-get-channel-membership-prefix + (if (erc-channel-user-p prefix) prefix nick)))) + (when (and queryp erc--target erc-format-query-as-channel-p + (not (erc--target-channel-p erc--target))) + (setq queryp nil)) + (list (cond (statusmsg (if inputp 'statusmsg-input 'statusmsg)) + (privmsgp (if queryp + (if inputp 'input-query-privmsg 'query-privmsg) + (if inputp 'input-chan-privmsg 'chan-privmsg))) + (t (if queryp + (if inputp 'input-query-notice 'query-notice) + (if inputp 'input-chan-notice 'chan-notice)))) + ?p (or prefix "") ?n (erc--speakerize-nick nick disp-nick) + ?s (or statusmsg "") ?m message)) + +(defcustom erc-show-speaker-membership-status nil + "Whether to prefix speakers with their channel status. +For example, when this option is non-nil and some nick \"Alice\" +has operator status in the current channel, ERC displays their +leading \"speaker\" label as <@Alice> instead of ." + :package-version '(ERC . "5.6") + :group 'erc-display + :type 'boolean) + +(define-obsolete-variable-alias 'erc-format-nick-function + 'erc-speaker-from-channel-member-function "30.1") +(defcustom erc-speaker-from-channel-member-function + #'erc-determine-speaker-from-user + "Function to determine a message's displayed \"speaker\" label. +Called with an `erc-server-user' object and an `erc-channel-user' +object, both possibly nil. Use this option to do things like +provide localized display names. To ask ERC to prepend +channel-membership \"status\" prefixes, like \"@\", to the +returned name, see `erc-show-speaker-membership-status'." + :package-version '(ERC . "5.6") + :group 'erc-display + :type '(choice (function-item erc-determine-speaker-from-user) function)) + +(define-obsolete-function-alias 'erc-format-nick + #'erc-determine-speaker-from-user "30.1") +(defun erc-determine-speaker-from-user (&optional user _channel-data) + "Return nickname slot of `erc-server-user' USER, when non-nil." (when user (erc-server-user-nickname user))) (define-obsolete-function-alias 'erc-get-user-mode-prefix @@ -5841,14 +6071,17 @@ string nickname, not necessarily downcased." "Format the nickname of USER showing if USER has a voice, is an operator, half-op, admin or owner. Owners have \"~\", admins have \"&\", operators have \"@\" and users with voice have \"+\" as a -prefix. Use CHANNEL-DATA to determine op and voice status. See -also `erc-format-nick-function'." +prefix. Use CHANNEL-DATA to determine op and voice status." + (declare (obsolete "see option `erc-show-speaker-membership-status'" "30.1")) (when user (let ((nick (erc-server-user-nickname user))) - (concat (propertize - (erc-get-channel-membership-prefix channel-data) - 'font-lock-face 'erc-nick-prefix-face) - nick)))) + (if (not erc--speaker-status-prefix-wanted-p) + (prog1 nick + (setq erc--speaker-status-prefix-wanted-p 'erc-format-@nick)) + (concat (propertize + (erc-get-channel-membership-prefix channel-data) + 'font-lock-face 'erc-nick-prefix-face) + nick))))) (defun erc-format-my-nick () "Return the beginning of this user's message, correctly propertized." @@ -5861,11 +6094,37 @@ also `erc-format-nick-function'." (concat (propertize open 'font-lock-face 'erc-default-face) (propertize mode 'font-lock-face 'erc-my-nick-prefix-face) - (propertize nick 'font-lock-face 'erc-my-nick-face 'erc--speaker nick) + (propertize nick 'erc--speaker nick 'font-lock-face 'erc-my-nick-face) (propertize close 'font-lock-face 'erc-default-face))) (let ((prefix "> ")) (propertize prefix 'font-lock-face 'erc-default-face)))) +(defun erc--format-speaker-input-message (message) + "Assemble outgoing MESSAGE entered at the prompt for insertion. +Intend \"input\" to refer to interactive prompt input as well as +the group of associated message-format templates from the +\"speaker\" catalog. Format the speaker portion in a manner +similar to that performed by `erc-format-my-nick', but use either +`erc--message-speaker-input-chan-privmsg' or +`erc--message-speaker-input-query-privmsg' as a formatting +template, with MESSAGE being the actual message body. Return a +copy with possibly shared text-property values." + (if-let ((erc-show-my-nick) + (nick (erc-current-nick)) + (pfx (erc-get-channel-membership-prefix nick)) + (erc-current-message-catalog erc--message-speaker-catalog) + (key (if (or erc-format-query-as-channel-p + (erc--target-channel-p erc--target)) + 'input-chan-privmsg + 'input-query-privmsg))) + (progn + (cond (erc--msg-props (puthash 'erc--msg key erc--msg-props)) + (erc--msg-prop-overrides (push (cons 'erc--msg key) + erc--msg-prop-overrides))) + (erc-format-message key ?p pfx ?n (erc--speakerize-nick nick) + ?m message)) + (propertize (concat "> " message) 'font-lock-face 'erc-input-face))) + (defun erc-echo-notice-in-default-buffer (s parsed buffer _sender) "Echo a private notice in the default buffer, namely the target buffer specified by BUFFER, or there is no target buffer, @@ -6105,8 +6364,7 @@ See also `erc-display-message'." (while queries (let* ((type (upcase (car (split-string (car queries))))) (hook (intern-soft (concat "erc-ctcp-query-" type "-hook"))) - (erc--msg-prop-overrides `((erc--msg . msg) - (erc--ctcp . ,(intern type)) + (erc--msg-prop-overrides `((erc--ctcp . ,(intern type)) ,@erc--msg-prop-overrides))) (if (and hook (boundp hook)) (if (string-equal type "ACTION") @@ -6141,12 +6399,31 @@ See also `erc-display-message'." (let ((s (match-string 1 msg)) (buf (or (erc-get-buffer to proc) (erc-get-buffer nick proc) - (process-buffer proc)))) - (erc--ensure-spkr-prop nick) - (setq nick (propertize nick 'erc--speaker nick)) - (erc-display-message - parsed 'action buf - 'ACTION ?n nick ?u login ?h host ?a s)))) + (process-buffer proc))) + (selfp (erc-current-nick-p nick))) + (if erc--use-language-catalog-for-ctcp-action-p + (progn + (erc--ensure-spkr-prop nick) + (setq nick (propertize nick 'erc--speaker nick)) + (erc-display-message parsed (if selfp 'input 'action) buf + 'ACTION ?n nick ?u login ?h host ?a s)) + (let* ((obj (and (erc--ctcp-response-p parsed) parsed)) + (buffer (and obj (erc--ctcp-response-buffer obj))) + (stsmsg (and obj (erc--ctcp-response-statusmsg obj))) + (prefix (and obj (erc--ctcp-response-prefix obj))) + (dispnm (and obj (erc--ctcp-response-dispname obj))) + (erc-current-message-catalog erc--message-speaker-catalog)) + (erc-display-message + parsed nil (or buffer buf) + (if selfp + (if stsmsg 'ctcp-action-statusmsg-input 'ctcp-action-input) + (if stsmsg 'ctcp-action-statusmsg 'ctcp-action)) + ?s (or stsmsg to) + ?p (or (and (erc-channel-user-p prefix) + (erc-get-channel-membership-prefix prefix)) + "") + ?n (erc--speakerize-nick nick dispnm) + ?m s)))))) (defvar erc-ctcp-query-CLIENTINFO-hook '(erc-ctcp-query-CLIENTINFO)) @@ -7582,15 +7859,11 @@ as outgoing chat messages and echoed slash commands." (erc--assert-input-bounds) (let ((insert-position (marker-position (goto-char erc-insert-marker))) (erc--msg-props (or erc--msg-props - (let ((ovs erc--msg-prop-overrides)) + (let ((ovs (seq-filter + #'cdr erc--msg-prop-overrides))) (map-into `((erc--msg . msg) ,@(reverse ovs)) - 'hash-table)))) - beg) - (insert (erc-format-my-nick)) - (setq beg (point)) - (insert line) - (erc-put-text-property beg (point) 'font-lock-face 'erc-input-face) - (insert "\n") + 'hash-table))))) + (insert (erc--format-speaker-input-message line) "\n") (save-restriction (narrow-to-region insert-position (point)) (run-hooks 'erc-send-modify-hook) @@ -8945,9 +9218,6 @@ functions." (string-replace "%" "%%" reason)) ""))))) - -(defvar-local erc-current-message-catalog 'english) - (defun erc-retrieve-catalog-entry (key &optional catalog) "Retrieve `format-spec' entry for symbol KEY in CATALOG. Without 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 @@ +;;; erc-scenarios-base-statusmsg.el --- statusmsg tests -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(ert-deftest erc-scenarios-base-statusmsg () + + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "base/display-message") + (dumb-server (erc-d-run "localhost" t 'statusmsg)) + (erc-autojoin-channels-alist '((foonet "#mine"))) + (erc-modules (cons 'fill-wrap erc-modules)) + (port (process-contact dumb-server :service)) + (erc-show-speaker-membership-status nil) + (erc-server-flood-penalty 0.1) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "tester" + :user "tester" + :full-name "tester") + (funcall expect 5 "This server is in debug mode"))) + + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#mine")) + + (ert-info ("Receive status messages unprefixed") + (funcall expect 5 "+dummy") + (funcall expect 5 "(dummy+) hello") + (should (eq 'statusmsg (erc--get-inserted-msg-prop 'erc--msg))) + (should (equal "dummy" (erc--get-inserted-msg-prop 'erc--spkr))) + (should (eq (get-text-property (1- (point)) 'font-lock-face) + 'erc-default-face)) + (funcall expect 5 "(dummy+) there") + (should (equal "" (get-text-property (pos-bol) 'display))) + + ;; CTCP ACTION + (funcall expect 5 "* (dummy+) sad") + (should (eq 'ctcp-action-statusmsg + (erc--get-inserted-msg-prop 'erc--msg))) + (should (eq (get-text-property (1- (point)) 'font-lock-face) + 'erc-action-face)) + (funcall expect 5 "* (dummy+) glad") + (should (equal "" (get-text-property (pos-bol) 'display)))) + + (ert-info ("Send status messages") + ;; We don't have `echo-message' yet, so ERC doesn't currently + ;; insert commands like "/msg +#mine foo". + (let ((erc-default-recipients '("+#mine"))) + (erc-send-message "howdy")) + (funcall expect 5 "(@tester+) howdy") + (should (eq 'statusmsg-input (erc--get-inserted-msg-prop 'erc--msg))) + (should (equal "tester" (erc--get-inserted-msg-prop 'erc--spkr))) + (should (eq (get-text-property (1- (point)) 'font-lock-face) + 'erc-input-face)) + (let ((erc-default-recipients '("+#mine"))) + (erc-send-message "tenderfoot")) + (funcall expect 5 "(@tester+) tenderfoot") + (should (equal "" (get-text-property (pos-bol) 'display))) + + ;; Simulate some "echoed" CTCP ACTION messages since we don't + ;; actually support that yet. + (funcall expect 5 "* (@tester+) mad") + (should (eq 'ctcp-action-statusmsg-input + (erc--get-inserted-msg-prop 'erc--msg))) + (should (equal (get-text-property (1- (point)) 'font-lock-face) + '(erc-input-face erc-action-face))) + (funcall expect 5 "* (@tester+) chad") + (should (equal "" (get-text-property (pos-bol) 'display)))) + + (ert-info ("Receive status messages prefixed") + (setq erc-show-speaker-membership-status t) + (erc-scenarios-common-say "/me ready") ; sync + (funcall expect 5 "* @tester ready") + (funcall expect 5 "(+dummy+) okie") + + ;; CTCP ACTION + (funcall expect 5 "* (+dummy+) dokie") + (funcall expect 5 "* +dummy out"))))) + +;;; 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 @@ (ert-info ("Stamps appear in left margin and are invisible") (should (eq 'erc-timestamp (field-at-pos (pos-bol)))) (should (= (pos-bol) (field-beginning (pos-bol)))) - (should (eq 'msg (get-text-property (pos-bol) 'erc--msg))) + (should (eq 'query-notice (get-text-property (pos-bol) 'erc--msg))) (should (eq 'NOTICE (get-text-property (pos-bol) 'erc--cmd))) (should (= ?- (char-after (field-end (pos-bol))))) (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 @@ calls erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) (cl-letf (((symbol-function 'erc-display-message) - (lambda (_ _ _ line) (push line calls))) + (lambda (_ _ _ msg &rest args) + (push (apply #'erc-format-message msg args) calls))) ((symbol-function 'erc-server-send) (lambda (line _) (push line calls))) ((symbol-function 'erc-server-buffer) @@ -2327,7 +2328,7 @@ (should-not erc-server-last-peers) (erc-message "PRIVMSG" ". hi") (should-not erc-server-last-peers) - (should (eq 'no-target (pop calls))) + (should (equal "No target" (pop calls))) (erc-message "PRIVMSG" ", hi") (should-not erc-server-last-peers) (should (string-match "alice :hi" (pop calls))))) @@ -2360,42 +2361,208 @@ (kill-buffer "ExampleNet") (kill-buffer "#chan"))) -(ert-deftest erc-format-privmessage () - ;; Basic PRIVMSG - (should (erc-tests--equal-including-properties - (erc-format-privmessage (copy-sequence "bob") - (copy-sequence "oh my") - nil 'msgp) - #(" oh my" - 0 1 (font-lock-face erc-default-face) - 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) - 4 11 (font-lock-face erc-default-face)))) +;; This is an adapter that uses formatting templates from the +;; `-speaker' catalog to mimic `erc-format-privmessage', for testing +;; purposes. +(defun erc-tests--format-privmessage (nick msg privp msgp &optional inputp pfx) + (let ((erc-current-message-catalog erc--message-speaker-catalog)) + (apply #'erc-format-message + (erc--determine-speaker-message-format-args nick msg privp msgp + inputp nil pfx)))) - ;; Basic NOTICE - (should (erc-tests--equal-including-properties - (erc-format-privmessage (copy-sequence "bob") - (copy-sequence "oh my") - nil nil) - #("-bob- oh my" - 0 1 (font-lock-face erc-default-face) - 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) - 4 11 (font-lock-face erc-default-face)))) +;; This asserts that `erc--determine-speaker-message-format-args' +;; behaves identically to `erc-format-privmessage', the function whose +;; role it basically replaced. +(ert-deftest erc--determine-speaker-message-format-args () + ;; Basic PRIVMSG. + (let ((expect #(" oh my" + 0 1 (font-lock-face erc-default-face) + 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) + 4 11 (font-lock-face erc-default-face))) + (args (list (concat "bob") (concat "oh my") nil 'msgp))) + (should (erc-tests--equal-including-properties + (apply #'erc-format-privmessage args) + expect)) + (should (erc-tests--equal-including-properties + (apply #'erc-tests--format-privmessage args) + expect))) - ;; Prefixed PRIVMSG - (let* ((user (make-erc-server-user :nickname (copy-sequence "Bob"))) + ;; Basic NOTICE. + (let ((expect #("-bob- oh my" + 0 1 (font-lock-face erc-default-face) + 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) + 4 11 (font-lock-face erc-default-face))) + (args (list (copy-sequence "bob") (copy-sequence "oh my") nil nil))) + (should (erc-tests--equal-including-properties + (apply #'erc-format-privmessage args) + expect)) + (should (erc-tests--equal-including-properties + (apply #'erc-tests--format-privmessage args) + expect))) + + ;; Status-prefixed PRIVMSG. + (let* ((expect + #("<@Bob> oh my" + 0 1 (font-lock-face erc-default-face) + 1 2 (font-lock-face erc-nick-prefix-face help-echo "operator") + 2 5 (erc--speaker "Bob" font-lock-face erc-nick-default-face) + 5 12 (font-lock-face erc-default-face))) + (user (make-erc-server-user :nickname (copy-sequence "Bob"))) (cuser (make-erc-channel-user :op t)) (erc-channel-users (make-hash-table :test #'equal))) (puthash "bob" (cons user cuser) erc-channel-users) + (with-suppressed-warnings ((obsolete erc-format-@nick)) + (should (erc-tests--equal-including-properties + (erc-format-privmessage (erc-format-@nick user cuser) + (copy-sequence "oh my") + nil 'msgp) + expect))) + (let ((nick "Bob") + (msg "oh my")) + (should (erc-tests--equal-including-properties + (erc-tests--format-privmessage nick msg nil 'msgp nil cuser) + expect)) ; overloaded on PREFIX arg + (should (erc-tests--equal-including-properties + (erc-tests--format-privmessage nick msg nil 'msgp nil t) + expect)) + ;; The new version makes a copy instead of adding properties to + ;; the input. + (should-not + (text-property-not-all 0 (length nick) 'font-lock-face nil nick)) + (should-not + (text-property-not-all 0 (length msg) 'font-lock-face nil msg))))) + +(ert-deftest erc--determine-speaker-message-format-args/queries-as-channel () + (should erc-format-query-as-channel-p) + + (with-current-buffer (get-buffer-create "bob") + (erc-mode) + (setq erc--target (erc--target-from-string "alice")) + + (insert "PRIVMSG\n" + (erc-tests--format-privmessage "bob" "oh my" 'queryp 'msgp)) (should (erc-tests--equal-including-properties - (erc-format-privmessage (erc-format-@nick user cuser) - (copy-sequence "oh my") - nil 'msgp) - #("<@Bob> oh my" + #(" oh my" 0 1 (font-lock-face erc-default-face) - 1 2 (font-lock-face erc-nick-prefix-face help-echo "operator") - 2 5 (erc--speaker "Bob" font-lock-face erc-nick-default-face) - 5 12 (font-lock-face erc-default-face)))))) + 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) + 4 11 (font-lock-face erc-default-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (insert "\nNOTICE\n" + (erc-tests--format-privmessage "bob" "oh my" 'queryp nil)) + (should (erc-tests--equal-including-properties + #("-bob- oh my" + 0 1 (font-lock-face erc-default-face) + 1 4 (erc--speaker "bob" font-lock-face erc-nick-default-face) + 4 11 (font-lock-face erc-default-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (insert "\nInput PRIVMSG\n" + (erc-tests--format-privmessage "bob" "oh my" + 'queryp 'privmsgp 'inputp)) + (should (erc-tests--equal-including-properties + #(" oh my" + 0 1 (font-lock-face erc-default-face) + 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face) + 4 6 (font-lock-face erc-default-face) + 6 11 (font-lock-face erc-input-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (insert "\nInput NOTICE\n" + (erc-tests--format-privmessage "bob" "oh my" 'queryp nil 'inputp)) + (should (erc-tests--equal-including-properties + #("-bob- oh my" + 0 1 (font-lock-face erc-default-face) + 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face) + 4 6 (font-lock-face erc-default-face) + 6 11 (font-lock-face erc-input-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (when noninteractive (kill-buffer)))) + +(ert-deftest erc--determine-speaker-message-format-args/queries () + (should erc-format-query-as-channel-p) + + (with-current-buffer (get-buffer-create "bob") + (erc-mode) + (setq-local erc-format-query-as-channel-p nil) + (setq erc--target (erc--target-from-string "alice")) + + (insert "PRIVMSG\n" + (erc-tests--format-privmessage "bob" "oh my" 'queryp 'msgp)) + (should (erc-tests--equal-including-properties + #("*bob* oh my" + 0 1 (font-lock-face erc-direct-msg-face) + 1 4 (erc--speaker "bob" font-lock-face erc-nick-msg-face) + 4 11 (font-lock-face erc-direct-msg-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (insert "\nNOTICE\n" + (erc-tests--format-privmessage "bob" "oh my" 'queryp nil)) + (should (erc-tests--equal-including-properties + #("-bob- oh my" + 0 1 (font-lock-face erc-direct-msg-face) + 1 4 (erc--speaker "bob" font-lock-face erc-nick-msg-face) + 4 11 (font-lock-face erc-direct-msg-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (insert "\nInput PRIVMSG\n" + (erc-tests--format-privmessage "bob" "oh my" + 'queryp 'privmsgp 'inputp)) + (should (erc-tests--equal-including-properties + #("*bob* oh my" + 0 1 (font-lock-face erc-direct-msg-face) + 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face) + 4 6 (font-lock-face erc-direct-msg-face) + 6 11 (font-lock-face erc-input-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (insert "\nInput NOTICE\n" + (erc-tests--format-privmessage "bob" "oh my" 'queryp nil 'inputp)) + (should (erc-tests--equal-including-properties + #("-bob- oh my" + 0 1 (font-lock-face erc-direct-msg-face) + 1 4 (erc--speaker "bob" font-lock-face erc-my-nick-face) + 4 6 (font-lock-face erc-direct-msg-face) + 6 11 (font-lock-face erc-input-face)) + (buffer-substring (pos-bol) (pos-eol)))) + + (when noninteractive (kill-buffer)))) + +(defun erc-tests--format-my-nick (message) + (concat (erc-format-my-nick) + (propertize message 'font-lock-face 'erc-input-face))) + +;; This tests that the default behavior of the replacement formatting +;; function for prompt input, `erc--format-speaker-input-message' +;; matches that of the original being replaced, `erc-format-my-nick', +;; though it only handled the speaker portion. +(ert-deftest erc--format-speaker-input-message () + ;; No status prefix. + (let ((erc-server-current-nick "tester") + (expect #(" oh my" + 0 1 (font-lock-face erc-default-face) + 1 7 (font-lock-face erc-my-nick-face erc--speaker "tester") + 7 9 (font-lock-face erc-default-face) + 9 14 (font-lock-face erc-input-face)))) + (should (equal (erc-tests--format-my-nick "oh my") expect)) + (should (equal (erc--format-speaker-input-message "oh my") expect))) + + ;; With channel-operator status prefix. + (let* ((erc-server-current-nick "tester") + (cmem (cons (make-erc-server-user :nickname "tester") + (make-erc-channel-user :op t))) + (erc-channel-users (map-into (list "tester" cmem) + '(hash-table :test equal))) + (expect #("<@tester> oh my" + 0 1 (font-lock-face erc-default-face) + 1 2 (font-lock-face erc-my-nick-prefix-face) + 2 5 (font-lock-face erc-my-nick-face erc--speaker "bob") + 5 7 (font-lock-face erc-default-face) + 7 12 (font-lock-face erc-input-face)))) + (should (equal (erc-tests--format-my-nick "oh my") expect)) + (should (equal (erc--format-speaker-input-message "oh my") expect)))) (ert-deftest erc--route-insertion () (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 @@ +;; -*- mode: lisp-data; -*- +((nick 10 "NICK tester")) +((user 10 "USER tester 0 * :tester") + (0.00 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") + (0.02 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version ergo-v2.11.1") + (0.01 ":irc.foonet.org 003 tester :This server was created Thu, 07 Dec 2023 08:04:35 UTC") + (0.00 ":irc.foonet.org 004 tester irc.foonet.org ergo-v2.11.1 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv") + (0.00 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# CHATHISTORY=1000 ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX :are supported by this server") + (0.01 ":irc.foonet.org 005 tester KICKLEN=390 MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8ONLY WHOX :are supported by this server") + (0.01 ":irc.foonet.org 005 tester draft/CHATHISTORY=1000 :are supported by this server") + (0.00 ":irc.foonet.org 251 tester :There are 0 users and 4 invisible on 1 server(s)") + (0.00 ":irc.foonet.org 252 tester 0 :IRC Operators online") + (0.00 ":irc.foonet.org 253 tester 0 :unregistered connections") + (0.00 ":irc.foonet.org 254 tester 2 :channels formed") + (0.00 ":irc.foonet.org 255 tester :I have 4 clients and 0 servers") + (0.02 ":irc.foonet.org 265 tester 4 5 :Current local users 4, max 5") + (0.00 ":irc.foonet.org 266 tester 4 5 :Current global users 4, max 5") + (0.00 ":irc.foonet.org 422 tester :MOTD File is missing") + (0.00 ":irc.foonet.org 221 tester +i") + (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.")) + +((mode-tester 10 "MODE tester +i")) + +((join-mine 10 "JOIN #mine") + (0.01 ":irc.foonet.org 221 tester +i") + (0.00 ":tester!~u@2jv6nwu4af69s.irc JOIN #mine") + (0.02 ":irc.foonet.org 353 tester = #mine :@tester +dummy") + (0.01 ":irc.foonet.org 366 tester #mine :End of NAMES list")) + +((mode-mine 10 "MODE #mine") + (0.00 ":irc.foonet.org 324 tester #mine +Cnt") + (0.02 ":irc.foonet.org 329 tester #mine 1702026418") + (0.04 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :hello") + (0.03 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :there") + (0.05 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION sad\1") + (0.03 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION glad\1")) + +((privmsg-statusmsg 10 "PRIVMSG +#mine :howdy")) +((privmsg-statusmsg-action 10 "PRIVMSG +#mine :tenderfoot") + ;; These are simulated "echoed messages" + (0.05 ":tester!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION mad\1") + (0.05 ":tester!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION chad\1")) + +((privmsg-prefixed 10 "PRIVMSG #mine :\1ACTION ready\1") + (0.04 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :okie") + (0.05 ":dummy!~u@2jv6nwu4af69s.irc PRIVMSG +#mine :\1ACTION dokie\1") + (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 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 alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n zero.[07:00]\n 0.5\n* bob one.\n two.\n 2.5\n* bob three\n 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 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 alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n zero.[07:00]\n 0.5\n* bob one.\n two.\n 2.5\n* bob three\n 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 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 alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n zero.[07:00]\n 0.5\n* bob one.\n two.\n 2.5\n* bob three\n 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 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 alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n zero.[07:00]\n 0.5\n* bob one.\n two.\n 2.5\n* bob three\n 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 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 alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n zero.[07:00]\n 0.5\n* bob one.\n two.\n 2.5\n* bob three\n 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 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 alice: Either your unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr 1 2023]\n zero.[07:00]\n 0.5\n* bob one.\n two.\n 2.5\n* bob three\n 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