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