>From 171dbaefbdc47154b21aa7f7e8c980958f983313 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Tue, 4 Jul 2023 23:21:25 -0700 Subject: [PATCH 0/4] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (4): [5.6] Respect existing invisibility props in erc-stamp [5.6] Simplify erc-button-add-nickname-buttons [5.6] Add text props for CTCPs and speakers in ERC [5.6] Handle composite faces better in erc-display-message etc/ERC-NEWS | 15 +++ lisp/erc/erc-backend.el | 39 +++---- lisp/erc/erc-button.el | 78 +++++++------ lisp/erc/erc-dcc.el | 16 +-- lisp/erc/erc-fill.el | 25 +++-- lisp/erc/erc-match.el | 14 +-- lisp/erc/erc-sasl.el | 8 +- lisp/erc/erc-stamp.el | 20 +++- lisp/erc/erc-track.el | 12 +- lisp/erc/erc.el | 99 +++++++++++++---- test/lisp/erc/erc-button-tests.el | 2 +- test/lisp/erc/erc-fill-tests.el | 5 +- test/lisp/erc/erc-scenarios-match.el | 160 +++++++++++++++++++++++---- 13 files changed, 347 insertions(+), 146 deletions(-) Interdiff: diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 5665b760ea9..40bcd934772 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -224,6 +224,21 @@ The 'fill' module is now defined by 'define-erc-module'. The same goes for ERC's imenu integration, which has 'imenu' now appearing in the default value of 'erc-modules'. +*** 'erc-display-message' combines faces when 'type' is a list. +Users may notice that ERC now renders messages passed to the +convenience function 'erc-display-error-notice' in a combination of +'erc-error-face' and 'erc-notice-face'. This is merely a consequence +of that function being a wrapper around 'erc-display-message', which +has gotten smarter about how it treats face properties when its 'type' +parameter is a list. Originally, ERC's authors intended to display +both server-originating and ERC-generated errors in this style, but +due to various complications, that intent was never realized until +this release, and even now only partially so. Indeed, to minimize +churn, the effect has been limited to internal and usage errors. For +third-party code, the key take away is that more 'font-lock-face' +properties encountered in the wild may be combinations of faces rather +than simple ones. + *** Prompt input is split before 'erc-pre-send-functions' has a say. Hook members are now treated to input whose lines have already been adjusted to fall within the allowed length limit. For convenience, diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index f1b51f9234a..bf21ec96225 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -148,7 +148,6 @@ erc-whowas-on-nosuchnick (declare-function erc-current-time "erc" (&optional specified-time)) (declare-function erc-default-target "erc" nil) (declare-function erc-delete-default-channel "erc" (channel &optional buffer)) -(declare-function erc-display-error-notice "erc" (parsed string)) (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)) @@ -2411,47 +2410,47 @@ erc-server-322-message (when erc-whowas-on-nosuchnick (erc-log (format "cmd: WHOWAS: %s" nick/channel)) (erc-server-send (format "WHOWAS %s 1" nick/channel))) - (erc-display-message parsed '(notice error) 'active + (erc-display-message parsed 'error 'active 's401 ?n nick/channel))) (define-erc-response-handler (402) "No such server." nil - (erc-display-message parsed '(notice error) 'active + (erc-display-message parsed 'error 'active 's402 ?c (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (403) "No such channel." nil - (erc-display-message parsed '(notice error) 'active + (erc-display-message parsed 'error 'active 's403 ?c (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (404) "Cannot send to channel." nil - (erc-display-message parsed '(notice error) 'active + (erc-display-message parsed 'error 'active 's404 ?c (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (405) "Can't join that many channels." nil - (erc-display-message parsed '(notice error) 'active + (erc-display-message parsed 'error 'active 's405 ?c (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (406) "No such nick." nil - (erc-display-message parsed '(notice error) 'active + (erc-display-message parsed 'error 'active 's406 ?n (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (412) "No text to send." nil - (erc-display-message parsed '(notice error) 'active 's412)) + (erc-display-message parsed 'error 'active 's412)) (define-erc-response-handler (421) "Unknown command." nil - (erc-display-message parsed '(notice error) 'active 's421 + (erc-display-message parsed 'error 'active 's421 ?c (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (432) "Bad nick." nil - (erc-display-message parsed '(notice error) 'active 's432 + (erc-display-message parsed 'error 'active 's432 ?n (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (433) @@ -2469,12 +2468,12 @@ erc-server-322-message (define-erc-response-handler (442) "Not on channel." nil - (erc-display-message parsed '(notice error) 'active 's442 + (erc-display-message parsed 'error 'active 's442 ?c (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (461) "Not enough parameters for command." nil - (erc-display-message parsed '(notice error) 'active 's461 + (erc-display-message parsed 'error 'active 's461 ?c (cadr (erc-response.command-args parsed)) ?m (erc-response.contents parsed))) @@ -2482,20 +2481,19 @@ erc-server-322-message "You are banned from this server." nil (setq erc-server-banned t) ;; show the server's message, as a reason might be provided - (erc-display-error-notice - parsed + (erc-display-message parsed 'error 'active (erc-response.contents parsed))) (define-erc-response-handler (474) "Banned from channel errors." nil - (erc-display-message parsed '(notice error) nil + (erc-display-message parsed 'error nil (intern (format "s%s" (erc-response.command parsed))) ?c (cadr (erc-response.command-args parsed)))) (define-erc-response-handler (475) "Channel key needed." nil - (erc-display-message parsed '(notice error) nil 's475 + (erc-display-message parsed 'error nil 's475 ?c (cadr (erc-response.command-args parsed))) (when erc-prompt-for-channel-key (let ((channel (cadr (erc-response.command-args parsed))) @@ -2516,7 +2514,7 @@ erc-server-322-message "You need to be a channel operator to do that." nil (let ((channel (cadr (erc-response.command-args parsed))) (message (erc-response.contents parsed))) - (erc-display-message parsed '(notice error) 'active 's482 + (erc-display-message parsed 'error 'active 's482 ?c channel ?m message))) (define-erc-response-handler (671) @@ -2551,11 +2549,8 @@ erc-server-322-message ;; 491 - No O-lines for your host ;; 501 - Unknown MODE flag ;; 502 - Cannot change mode for other users - "Generic display of server error messages. - -See `erc-display-error-notice'." nil - (erc-display-error-notice - parsed + "Display error message as given from server." nil + (erc-display-message parsed 'error 'active (intern (format "s%s" (erc-response.command parsed))))) ;; FIXME: These are yet to be implemented, they're just stubs for now diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 0c616a6026d..c30f7c10ca6 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -355,8 +355,6 @@ erc-button--nick ( cuser nil :type (or null erc-channel-user) ;; The CDR of a value from an `erc-channel-users' table. :documentation "A possibly nil `erc-channel-user'.") - ( face erc-button-face :type symbol - :documentation "Temp `erc-button-face' while buttonizing.") ( nickname-face erc-button-nickname-face :type symbol :documentation "Temp `erc-button-nickname-face' while buttonizing.") ( mouse-face erc-button-mouse-face :type symbol @@ -431,45 +429,43 @@ erc-button--phantom-users-mode (defun erc-button-add-nickname-buttons (entry) "Search through the buffer for nicknames, and add buttons." - (let ((form (nth 2 entry)) - (fun (nth 3 entry)) - (erc-button-buttonize-nicks (and erc-button-buttonize-nicks - erc-button--modify-nick-function)) - bounds word) - (when (and form (setq form (erc-button--extract-form form))) - (goto-char (point-min)) - (while (erc-forward-word) - (when (setq bounds (erc-bounds-of-word-at-point)) - (setq word (buffer-substring-no-properties - (car bounds) (cdr bounds))) - (let* ((erc-button-face erc-button-face) - (erc-button-mouse-face erc-button-mouse-face) - (erc-button-nickname-face erc-button-nickname-face) - (down (erc-downcase word)) - (cuser (and erc-channel-users - (gethash down erc-channel-users))) - (user (or (and cuser (car cuser)) - (and erc-server-users - (gethash down erc-server-users)) - (funcall erc-button--fallback-user-function - down word bounds))) - (data (list word))) - (when (or (not (functionp form)) - (and-let* ((user) - (obj (funcall form (make-erc-button--nick - :bounds bounds :data data - :downcased down :user user - :cuser (cdr cuser))))) - (setq bounds (erc-button--nick-bounds obj) - data (erc-button--nick-data obj) - erc-button-mouse-face - (erc-button--nick-mouse-face obj) - erc-button-nickname-face - (erc-button--nick-nickname-face obj) - erc-button-face - (erc-button--nick-face obj)))) - (erc-button-add-button (car bounds) (cdr bounds) - fun t data)))))))) + (when-let ((form (nth 2 entry)) + ;; Spoof `form' slot of default legacy `nicknames' entry + ;; so `erc-button--extract-form' sees a function value. + (form (let ((erc-button-buttonize-nicks + (and erc-button-buttonize-nicks + erc-button--modify-nick-function))) + (erc-button--extract-form form))) + (seen 0)) + (goto-char (point-min)) + (while-let + (((erc-forward-word)) + (bounds (or (and (= 1 (cl-incf seen)) (erc--get-speaker-bounds)) + (erc-bounds-of-word-at-point))) + (word (buffer-substring-no-properties (car bounds) (cdr bounds))) + (down (erc-downcase word))) + (let* ((erc-button-mouse-face erc-button-mouse-face) + (erc-button-nickname-face erc-button-nickname-face) + (cuser (and erc-channel-users (gethash down erc-channel-users))) + (user (or (and cuser (car cuser)) + (and erc-server-users (gethash down erc-server-users)) + (funcall erc-button--fallback-user-function + down word bounds))) + (data (list word))) + (when (or (not (functionp form)) + (and-let* ((user) + (obj (funcall form (make-erc-button--nick + :bounds bounds :data data + :downcased down :user user + :cuser (cdr cuser))))) + (setq erc-button-mouse-face ; might be null + (erc-button--nick-mouse-face obj) + erc-button-nickname-face ; might be null + (erc-button--nick-nickname-face obj) + data (erc-button--nick-data obj) + bounds (erc-button--nick-bounds obj)))) + (erc-button-add-button (car bounds) (cdr bounds) (nth 3 entry) + 'nickp data)))))) (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." diff --git a/lisp/erc/erc-dcc.el b/lisp/erc/erc-dcc.el index cc2dcc9a788..8968295a83c 100644 --- a/lisp/erc/erc-dcc.el +++ b/lisp/erc/erc-dcc.el @@ -566,7 +566,7 @@ erc-dcc-do-GET-command file)) (erc-dcc-get-file elt file proc) (erc-display-message - nil '(notice error) proc + nil 'error proc 'dcc-get-cmd-aborted ?n nick ?f filename))) (t @@ -578,7 +578,7 @@ erc-dcc-do-GET-command (setq erc-dcc-list (cons (plist-put elt :turbo t) (delq elt erc-dcc-list))))) (erc-display-message - nil '(notice error) 'active + nil 'error 'active 'dcc-get-notfound ?n nick ?f filename)))) (defvar-local erc-dcc-byte-count nil) @@ -648,7 +648,7 @@ erc-dcc-do-SEND-command nil 'notice 'active 'dcc-send-offer ?n nick ?f file) (erc-dcc-send-file nick file) t) - (erc-display-message nil '(notice error) proc "File not found") t)) + (erc-display-message nil 'error proc "File not found") t)) ;;; Server message handling (i.e. messages from remote users) @@ -675,7 +675,7 @@ erc-ctcp-query-DCC (funcall handler proc query nick login host to) ;; FIXME: Send a ctcp error notice to the remote end? (erc-display-message - nil '(notice error) proc + nil 'error proc 'dcc-ctcp-unknown ?q query ?n nick ?u login ?h host)))) @@ -771,7 +771,7 @@ erc-dcc-handle-ctcp-chat (;; DCC CHAT requests must be sent to you, and you alone. (not (erc-current-nick-p to)) (erc-display-message - nil '(notice error) proc + nil 'error proc 'dcc-request-bogus ?r "CHAT" ?n nick ?u login ?h host)) ((string-match erc-dcc-ctcp-query-chat-regexp query) ;; We need to use let* here, since erc-dcc-member might clutter @@ -805,7 +805,7 @@ erc-dcc-handle-ctcp-chat proc)))) (t (erc-display-message - nil '(notice error) proc + nil 'error proc 'dcc-malformed ?n nick ?u login ?h host ?q query))))) @@ -1053,7 +1053,7 @@ erc-dcc-get-filter ((and (> (plist-get erc-dcc-entry-data :size) 0) (> received-bytes (plist-get erc-dcc-entry-data :size))) (erc-display-message - nil '(notice error) 'active + nil 'error 'active 'dcc-get-file-too-long ?f (file-name-nondirectory (buffer-name))) (delete-process proc)) @@ -1085,7 +1085,7 @@ erc-dcc-get-sentinel (erc-dcc-append-contents (current-buffer) erc-dcc-file-name)) (let ((done (= erc-dcc-byte-count (plist-get erc-dcc-entry-data :size)))) (erc-display-message - nil (if done 'notice '(notice error)) erc-server-process + nil (if done 'notice 'error) erc-server-process (if done 'dcc-get-complete 'dcc-get-failed) ?v (plist-get erc-dcc-entry-data :size) ?f erc-dcc-file-name diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el index 35289910d0a..a65c95f1d85 100644 --- a/lisp/erc/erc-fill.el +++ b/lisp/erc/erc-fill.el @@ -124,11 +124,9 @@ erc-fill-line-spacing :package-version '(ERC . "5.6") ; FIXME sync on release :type '(choice (const nil) number)) -(defcustom erc-fill-spaced-commands '(PRIVMSG NOTICE) +(defvar erc-fill--spaced-commands '(PRIVMSG NOTICE) "Types of messages to add space between on graphical displays. -Only considered when `erc-fill-line-spacing' is non-nil." - :package-version '(ERC . "5.6") ; FIXME sync on release - :type '(repeat (choice integer symbol))) +Only considered when `erc-fill-line-spacing' is non-nil.") (defvar-local erc-fill--function nil "Internal copy of `erc-fill-function'. @@ -153,12 +151,12 @@ erc-fill (p (point-min))) (widen) (when (or (and-let* ((cmd (get-text-property p 'erc-command))) - (memq cmd erc-fill-spaced-commands)) + (memq cmd erc-fill--spaced-commands)) (and-let* ((cmd (save-excursion (forward-line -1) (get-text-property (point) 'erc-command)))) - (memq cmd erc-fill-spaced-commands))) + (memq cmd erc-fill--spaced-commands))) (put-text-property (1- p) p 'line-spacing erc-fill-line-spacing)))))))) @@ -433,13 +431,8 @@ erc-fill-wrap (let ((len (or (and erc-fill--wrap-length-function (funcall erc-fill--wrap-length-function)) (progn - (when-let ((b (next-single-property-change - (point) 'erc-speaker nil (pos-eol))) - ((/= (pos-eol) b)) - ;; String vals `eq' along same stretch - (e (text-property-not-all - b (pos-eol) 'erc-speaker - (get-text-property b 'erc-speaker))) + (when-let ((e (erc--get-speaker-bounds)) + (b (pop e)) ((or erc-fill--wrap-action-dedent-p (not (eq (get-text-property b 'erc-ctcp) 'ACTION))))) diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el index 2b7fff87ff0..549de4feeb8 100644 --- a/lisp/erc/erc-match.el +++ b/lisp/erc/erc-match.el @@ -657,22 +657,18 @@ erc-go-to-log-matches-buffer (defvar-local erc-match--hide-fools-offset-bounds nil) -;; FIXME this should merge with instead of overwrite existing -;; `invisible' values. (defun erc-hide-fools (match-type _nickuserhost _message) - "Hide foolish comments. -This function should be called from `erc-text-matched-hook'." + "Hide comments from designated fools." (when (eq match-type 'fool) (if erc-match--hide-fools-offset-bounds (let ((beg (point-min)) (end (point-max))) (save-restriction (widen) - (put-text-property (1- beg) (1- end) 'invisible 'erc-match))) - ;; The docs say `intangible' is deprecated, but this has been - ;; like this for ages. Should verify unneeded and remove if so. - (erc-put-text-properties (point-min) (point-max) - '(invisible intangible))))) + (erc--merge-prop (1- beg) (1- end) 'invisible 'erc-match))) + ;; Before ERC 5.6, this also used to add an `intangible' + ;; property, but the docs say it's now obsolete. + (erc--merge-prop (point-min) (point-max) 'invisible 'erc-match)))) (defun erc-beep-on-match (match-type _nickuserhost _message) "Beep when text matches. diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 482d6d901ab..dd481032e7e 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -29,6 +29,8 @@ ;; ;; This is the "networks" module. ;; +;; M-x erc-server-select provides an alternative way to connect to servers by +;; choosing networks. ;; You can use (eq (erc-network) 'Network) if you'd like to set variables or do ;; certain actions according to which network you're connected to. ;; If a network you use is not listed in `erc-networks-alist', you can put @@ -485,8 +487,6 @@ erc-server-alist (choice (integer :tag "Port number") (list :tag "Port range" integer integer))))))) -(make-obsolete-variable 'erc-server-alist - "specify `:server' with `erc-tls'." "30.1") (defcustom erc-networks-alist '((4-irc "4-irc.com") @@ -1544,9 +1544,9 @@ erc-ports-list result))))) (nreverse result))) +;;;###autoload (defun erc-server-select () "Interactively select a server to connect to using `erc-server-alist'." - (declare (obsolete erc-tls "30.1")) (interactive) (let* ((completion-ignore-case t) (net (intern diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el index c6922b1b26b..73d318fd4fd 100644 --- a/lisp/erc/erc-sasl.el +++ b/lisp/erc/erc-sasl.el @@ -377,7 +377,7 @@ erc-sasl--destroy (define-erc-response-handler (902) "Handle an ERR_NICKLOCKED response." nil - (erc-display-message parsed '(notice error) 'active 's902 + (erc-display-message parsed 'error 'active 's902 ?n (car (erc-response.command-args parsed)) ?s (erc-response.contents parsed)) (erc-sasl--destroy proc)) @@ -391,19 +391,19 @@ erc-sasl--destroy (define-erc-response-handler (907) "Handle a RPL_SASLALREADY response." nil - (erc-display-message parsed '(notice error) 'active 's907 + (erc-display-message parsed 'error 'active 's907 ?s (erc-response.contents parsed))) (define-erc-response-handler (904 905 906) "Handle various SASL-related error responses." nil - (erc-display-message parsed '(notice error) 'active + (erc-display-message parsed 'error 'active (intern (format "s%s" (erc-response.command parsed))) ?s (erc-response.contents parsed)) (erc-sasl--destroy proc)) (define-erc-response-handler (908) "Handle a RPL_SASLMECHS response." nil - (erc-display-message parsed '(notice error) 'active 's908 + (erc-display-message parsed 'error 'active 's908 ?m (alist-get 'mechanism erc-sasl--options) ?s (string-join (cdr (erc-response.command-args parsed)) " ")) diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el index 5035e60a87d..2f52d78d42b 100644 --- a/lisp/erc/erc-stamp.el +++ b/lisp/erc/erc-stamp.el @@ -179,6 +179,12 @@ stamp (kill-local-variable 'erc-timestamp-last-inserted-left) (kill-local-variable 'erc-timestamp-last-inserted-right)))) +(defvar erc-stamp--invisible-property nil + "Existing `invisible' property value and/or symbol `timestamp'.") + +(defvar erc-stamp--skip-when-invisible nil + "Escape hatch for omitting stamps when first char is invisible.") + (defun erc-stamp--recover-on-reconnect () (when-let ((priors (or erc--server-reconnecting erc--target-priors))) (dolist (var '(erc-timestamp-last-inserted @@ -209,8 +215,11 @@ erc-add-timestamp (progn ; remove this `progn' on next major refactor (let* ((ct (erc-stamp--current-time)) (invisible (get-text-property (point-min) 'invisible)) + (erc-stamp--invisible-property + ;; FIXME on major version bump, make this `erc-' prefixed. + (if invisible `(timestamp ,@(ensure-list invisible)) 'timestamp)) (erc-stamp--current-time ct)) - (unless invisible + (unless (setq invisible (and erc-stamp--skip-when-invisible invisible)) (funcall erc-insert-timestamp-function (erc-format-timestamp ct erc-timestamp-format))) ;; FIXME this will error when advice has been applied. @@ -380,7 +389,7 @@ erc-insert-timestamp-left (s (if ignore-p (make-string len ? ) string))) (unless ignore-p (setq erc-timestamp-last-inserted string)) (erc-put-text-property 0 len 'field 'erc-timestamp s) - (erc-put-text-property 0 len 'invisible 'timestamp s) + (erc-put-text-property 0 len 'invisible erc-stamp--invisible-property s) (insert s))) (defun erc-insert-aligned (string pos) @@ -477,6 +486,8 @@ erc-insert-timestamp-right (put-text-property from (point) p v))) (erc-put-text-property from (point) 'field 'erc-timestamp) (erc-put-text-property from (point) 'rear-nonsticky t) + (erc-put-text-property from (point) 'invisible + erc-stamp--invisible-property) (when erc-timestamp-intangible (erc-put-text-property from (1+ (point)) 'cursor-intangible t))))) @@ -520,9 +531,8 @@ erc-format-timestamp (let ((ts (format-time-string format time erc-stamp--tz))) (erc-put-text-property 0 (length ts) 'font-lock-face 'erc-timestamp-face ts) - (erc-put-text-property 0 (length ts) 'invisible 'timestamp ts) - (erc-put-text-property 0 (length ts) - 'isearch-open-invisible 'timestamp ts) + (erc-put-text-property 0 (length ts) 'invisible + erc-stamp--invisible-property ts) ;; N.B. Later use categories instead of this harmless, but ;; inelegant, hack. -- BPT (and erc-timestamp-intangible diff --git a/lisp/erc/erc-track.el b/lisp/erc/erc-track.el index ef064c6a4ee..bc09c5d87fb 100644 --- a/lisp/erc/erc-track.el +++ b/lisp/erc/erc-track.el @@ -164,7 +164,6 @@ erc-track-use-faces (defcustom erc-track-faces-priority-list '(erc-error-face - (erc-error-face erc-notice-face) (erc-nick-default-face erc-current-nick-face) erc-current-nick-face erc-keyword-face @@ -311,6 +310,8 @@ erc-track-switch-direction (const leastactive) (const mostactive))) +(defconst erc-track--attn-faces '((erc-error-face erc-notice-face)) + "Faces whose presence always trigger mode-line inclusion.") (defun erc-track-remove-from-mode-line () "Remove `erc-track-modified-channels' from the mode-line." @@ -738,6 +739,9 @@ erc-track-find-face (declare (obsolete erc-track-select-mode-line-face "28.1")) (erc-track-select-mode-line-face (car faces) (cdr faces))) +;; Note that unless called by `erc-track-modified-channels', +;; `erc-track-faces-priority-list' will not begin with +;; `erc-track--attn-faces'. (defun erc-track-select-mode-line-face (cur-face new-faces) "Return the face to use in the mode line. @@ -804,7 +808,9 @@ erc-track-modified-channels ;; (in the car), change its face attribute (in the cddr) if ;; necessary. See `erc-modified-channels-alist' for the ;; exact data structure used. - (let ((faces (erc-faces-in (buffer-string)))) + (let ((faces (erc-faces-in (buffer-string))) + (erc-track-faces-priority-list + `(,@erc-track--attn-faces ,@erc-track-faces-priority-list))) (unless (and (or (eq erc-track-priority-faces-only 'all) (member this-channel erc-track-priority-faces-only)) @@ -875,7 +881,7 @@ erc-track-face-priority higher number than any other face in that list." (let ((count 0)) (catch 'done - (dolist (item erc-track-faces-priority-list) + (dolist (item `(,@erc-track--attn-faces ,@erc-track-faces-priority-list)) (if (equal item face) (throw 'done t) (setq count (1+ count))))) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index a7d3f7d0ed5..98127697815 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1309,7 +1309,8 @@ erc-notice-face "ERC face for notices." :group 'erc-faces) -(defface erc-action-face '((t :weight bold)) +(defface erc-action-face '((((supports :weight semi-bold)) :weight semi-bold) + (t :weight bold)) "ERC face for actions generated by /ME." :group 'erc-faces) @@ -4962,17 +4963,14 @@ erc--nickname-in-use-make-request (erc-cmd-NICK temp)) (defun erc-nickname-in-use (nick reason) - "If NICK is unavailable, tell the user the REASON. - -See also `erc-display-error-notice'." + "Explain REASON NICK is taken and maybe ask for alternate." (if (or (not erc-try-new-nick-p) ;; how many default-nicks are left + one more try... (eq erc-nick-change-attempt-count (if (consp erc-nick) (+ (length erc-nick) 1) 1))) - (erc-display-error-notice - nil + (erc-display-message nil 'error 'active (format "Nickname %s is %s, try another." nick reason)) (setq erc-nick-change-attempt-count (+ erc-nick-change-attempt-count 1)) (let ((newnick (nth 1 erc-default-nicks)) @@ -4995,8 +4993,7 @@ erc-nickname-in-use (- 9 (length erc-nick-uniquifier)))) erc-nick-uniquifier))) (erc--nickname-in-use-make-request nick newnick) - (erc-display-error-notice - nil + (erc-display-message nil 'error 'active (format "Nickname %s is %s, trying %s" nick reason newnick))))) @@ -5052,6 +5049,16 @@ erc-is-message-ctcp-and-not-action-p (and (erc-is-message-ctcp-p message) (not (string-match "^\C-aACTION.*\C-a$" message)))) +(define-inline erc--get-speaker-bounds () + "Return the bounds of `erc-speaker' property when present. +Assume buffer is narrowed to the confines of an inserted message." + (inline-quote + (and-let* + (((memq (get-text-property (point) 'erc-command) '(PRIVMSG NOTICE))) + (beg (or (and (get-text-property (point-min) 'erc-speaker) (point-min)) + (next-single-property-change (point-min) 'erc-speaker)))) + (cons beg (next-single-property-change beg 'erc-speaker))))) + (defvar erc--user-from-nick-function #'erc--examine-nick "Function to possibly consider unknown user. Must return either nil or a cons of an `erc-server-user' and a @@ -5069,8 +5076,9 @@ erc-format-privmessage (str (format "%s%s%s %s" mark-s nick mark-e msg)) (nick-face (if privp 'erc-nick-msg-face 'erc-nick-default-face)) (nick-prefix-face (get-text-property 0 'font-lock-face nick)) - (prefix-len (or (text-property-not-all 0 (length nick) 'font-lock-face - nick-prefix-face nick) + (prefix-len (or (and nick-prefix-face (text-property-not-all + 0 (length nick) 'font-lock-face + nick-prefix-face nick)) 0)) (msg-face (if privp 'erc-direct-msg-face 'erc-default-face))) ;; add text properties to text before the nick, the nick and after the nick diff --git a/test/lisp/erc/erc-fill-tests.el b/test/lisp/erc/erc-fill-tests.el index 15a8087f848..99ec4a9635e 100644 --- a/test/lisp/erc/erc-fill-tests.el +++ b/test/lisp/erc/erc-fill-tests.el @@ -153,7 +153,10 @@ erc-fill-tests--compare (with-temp-file expect-file (insert repr)) (if (file-exists-p expect-file) - ;; Compare set-equal over intervals + ;; Compare set-equal over intervals. This comparison is + ;; less useful for messages treated by other modules because + ;; it doesn't compare "nested" props belonging to + ;; string-valued properties, like timestamps. (should (equal-including-properties (read repr) (read (with-temp-buffer diff --git a/test/lisp/erc/erc-scenarios-match.el b/test/lisp/erc/erc-scenarios-match.el index 782907bfc30..edc1749cdd2 100644 --- a/test/lisp/erc/erc-scenarios-match.el +++ b/test/lisp/erc/erc-scenarios-match.el @@ -26,6 +26,7 @@ (require 'erc-stamp) (require 'erc-match) +(require 'erc-fill) ;; This defends against a regression in which all matching by the ;; `erc-match-message' fails when `erc-add-timestamp' precedes it in @@ -57,28 +58,20 @@ erc-scenarios-match--stamp-left-current-nick (should (eq (get-text-property (1- (point)) 'font-lock-face) 'erc-current-nick-face)))))) -;; This asserts that when stamps appear before a message, -;; some non-nil invisibility property spans the entire message. -(ert-deftest erc-scenarios-match--stamp-left-fools-invisible () - :tags '(:expensive-test) - (ert-skip "WIP: fix included in bug#64301") +;; When hacking on tests that use this fixture, it's best to run it +;; interactively, and check for wierdness before and after doing +;; M-: (remove-from-invisibility-spec 'erc-match) RET. +(defun erc-scenarios-match--invisible-stamp (hiddenp visiblep) (erc-scenarios-common-with-cleanup ((erc-scenarios-common-dialog "join/legacy") (dumb-server (erc-d-run "localhost" t 'foonet)) (port (process-contact dumb-server :service)) (erc-server-flood-penalty 0.1) - (erc-insert-timestamp-function 'erc-insert-timestamp-left) (erc-timestamp-only-if-changed-flag nil) (erc-fools '("bob")) (erc-text-matched-hook '(erc-hide-fools)) (erc-autojoin-channels-alist '((FooNet "#chan"))) - (expect (erc-d-t-make-expecter)) - (hiddenp (lambda () - (and (eq (field-at-pos (pos-bol)) 'erc-timestamp) - (get-text-property (pos-bol) 'invisible) - (>= (next-single-property-change (pos-bol) - 'invisible nil) - (pos-eol)))))) + (expect (erc-d-t-make-expecter))) (ert-info ("Connect") (with-current-buffer (erc :server "127.0.0.1" @@ -94,30 +87,155 @@ erc-scenarios-match--stamp-left-fools-invisible (ert-info ("Ensure lines featuring \"bob\" are invisible") (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) (should (funcall expect 10 " tester, welcome!")) - (should (funcall hiddenp)) + (ert-info (" tester, welcome!") (funcall hiddenp)) ;; Alice's is the only one visible. (should (funcall expect 10 " tester, welcome!")) - (should (eq (field-at-pos (pos-bol)) 'erc-timestamp)) - (should (get-text-property (pos-bol) 'invisible)) - (should-not (get-text-property (point) 'invisible)) + (ert-info (" tester, welcome!") (funcall visiblep)) (should (funcall expect 10 " alice: But, as it seems")) - (should (funcall hiddenp)) + (ert-info (" alice: But, as it seems") (funcall hiddenp)) (should (funcall expect 10 " bob: Well, this is the forest")) - (should (funcall hiddenp)) + (ert-info (" bob: Well, this is the forest") (funcall hiddenp)) (should (funcall expect 10 " bob: And will you")) - (should (funcall hiddenp)) + (ert-info (" bob: And will you") (funcall hiddenp)) (should (funcall expect 10 " alice: Live, and be prosperous")) - (should (funcall hiddenp)) + (ert-info (" alice: Live, and be prosperous") (funcall hiddenp)) (should (funcall expect 10 "ERC>")) (should-not (get-text-property (pos-bol) 'invisible)) (should-not (get-text-property (point) 'invisible)))))) +;; This asserts that when stamps appear before a message, registered +;; invisibility properties owned by modules span the entire message. +(ert-deftest erc-scenarios-match--stamp-left-fools-invisible () + :tags '(:expensive-test) + (let ((erc-insert-timestamp-function #'erc-insert-timestamp-left)) + (erc-scenarios-match--invisible-stamp + + (lambda () + ;; This is a time-stamped message. + (should (eq (field-at-pos (pos-bol)) 'erc-timestamp)) + + ;; Leading stamp has combined `invisible' property value. + (should (equal (get-text-property (pos-bol) 'invisible) + '(timestamp erc-match))) + + ;; Message proper has the `invisible' property `erc-match'. + (let ((msg-beg (next-single-property-change (pos-bol) 'invisible))) + (should (eq (get-text-property msg-beg 'invisible) 'erc-match)) + (should (>= (next-single-property-change msg-beg 'invisible nil) + (pos-eol))))) + + (lambda () + ;; This is a time-stamped message. + (should (eq (field-at-pos (pos-bol)) 'erc-timestamp)) + (should (get-text-property (pos-bol) 'invisible)) + + ;; The entire message proper is visible. + (let ((msg-beg (next-single-property-change (pos-bol) 'invisible))) + (should + (= (next-single-property-change msg-beg 'invisible nil (pos-eol)) + (pos-eol)))))))) + +(defun erc-scenarios-match--find-eol () + (save-excursion + (goto-char (next-single-property-change (point) 'erc-command)) + (pos-eol))) + +;; In most cases, `erc-hide-fools' makes line endings invisible. +(ert-deftest erc-scenarios-match--stamp-right-fools-invisible () + :tags '(:expensive-test) + (let ((erc-insert-timestamp-function #'erc-insert-timestamp-right)) + (erc-scenarios-match--invisible-stamp + + (lambda () + (let ((end (erc-scenarios-match--find-eol))) + ;; The end of the message is a newline. + (should (= ?\n (char-after end))) + + ;; Every message has a trailing time stamp. + (should (eq (field-at-pos (1- end)) 'erc-timestamp)) + + ;; Stamps have a combined `invisible' property value. + (should (equal (get-text-property (1- end) 'invisible) + '(timestamp erc-match))) + + ;; The final newline is hidden by `match', not `stamps' + (should (equal (get-text-property end 'invisible) 'erc-match)) + + ;; The message proper has the `invisible' property `erc-match', + ;; and it starts after the preceding newline. + (should (eq (get-text-property (pos-bol) 'invisible) 'erc-match)) + + ;; It ends just before the timestamp. + (let ((msg-end (next-single-property-change (pos-bol) 'invisible))) + (should (equal (get-text-property msg-end 'invisible) + '(timestamp erc-match))) + + ;; Stamp's `invisible' property extends throughout the stamp + ;; and ends before the trailing newline. + (should (= (next-single-property-change msg-end 'invisible) end))))) + + (lambda () + (let ((end (erc-scenarios-match--find-eol))) + ;; This message has a time stamp like all the others. + (should (eq (field-at-pos (1- end)) 'erc-timestamp)) + + ;; The entire message proper is visible. + (should-not (get-text-property (pos-bol) 'invisible)) + (let ((inv-beg (next-single-property-change (pos-bol) 'invisible))) + (should (eq (get-text-property inv-beg 'invisible) + 'timestamp)))))))) + +;; This asserts that when `erc-fill-wrap-mode' is enabled, ERC hides +;; the preceding message's line ending. +(ert-deftest erc-scenarios-match--stamp-right-invisible-fill-wrap () + :tags '(:expensive-test) + (let ((erc-insert-timestamp-function #'erc-insert-timestamp-right) + (erc-fill-function #'erc-fill-wrap)) + (erc-scenarios-match--invisible-stamp + + (lambda () + ;; Every message has a trailing time stamp. + (should (eq (field-at-pos (1- (pos-eol))) 'erc-timestamp)) + + ;; Stamps appear in the right margin. + (should (equal (car (get-text-property (1- (pos-eol)) 'display)) + '(margin right-margin))) + + ;; Stamps have a combined `invisible' property value. + (should (equal (get-text-property (1- (pos-eol)) 'invisible) + '(timestamp erc-match))) + + ;; The message proper has the `invisible' property `erc-match', + ;; which starts at the preceding newline... + (should (eq (get-text-property (1- (pos-bol)) 'invisible) 'erc-match)) + + ;; ... and ends just before the timestamp. + (let ((msgend (next-single-property-change (1- (pos-bol)) 'invisible))) + (should (equal (get-text-property msgend 'invisible) + '(timestamp erc-match))) + + ;; The newline before `erc-insert-marker' is still visible. + (should-not (get-text-property (pos-eol) 'invisible)) + (should (= (next-single-property-change msgend 'invisible) + (pos-eol))))) + + (lambda () + ;; This message has a time stamp like all the others. + (should (eq (field-at-pos (1- (pos-eol))) 'erc-timestamp)) + + ;; Unlike hidden messages, the preceding newline is visible. + (should-not (get-text-property (1- (pos-bol)) 'invisible)) + + ;; The entire message proper is visible. + (let ((inv-beg (next-single-property-change (1- (pos-bol)) 'invisible))) + (should (eq (get-text-property inv-beg 'invisible) 'timestamp))))))) + (eval-when-compile (require 'erc-join)) ;;; erc-scenarios-match.el ends here -- 2.41.0