From unknown Fri Jun 20 05:22:10 2025 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Mailer: MIME-tools 5.509 (Entity 5.509) Content-Type: text/plain; charset=utf-8 From: bug#60933 <60933@debbugs.gnu.org> To: bug#60933 <60933@debbugs.gnu.org> Subject: Status: 30.0.50; ERC >5.5: Make buttonizing more extensible Reply-To: bug#60933 <60933@debbugs.gnu.org> Date: Fri, 20 Jun 2025 12:22:10 +0000 retitle 60933 30.0.50; ERC >5.5: Make buttonizing more extensible reassign 60933 emacs submitter 60933 "J.P." severity 60933 normal tag 60933 patch thanks From debbugs-submit-bounces@debbugs.gnu.org Wed Jan 18 09:39:14 2023 Received: (at submit) by debbugs.gnu.org; 18 Jan 2023 14:39:14 +0000 Received: from localhost ([127.0.0.1]:39943 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pI9aq-0004bf-MC for submit@debbugs.gnu.org; Wed, 18 Jan 2023 09:39:13 -0500 Received: from lists.gnu.org ([209.51.188.17]:46730) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pI9ao-0004bW-Fp for submit@debbugs.gnu.org; Wed, 18 Jan 2023 09:39:11 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pI9an-0000U7-BK for bug-gnu-emacs@gnu.org; Wed, 18 Jan 2023 09:39:09 -0500 Received: from mail-108-mta211.mxroute.com ([136.175.108.211]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1pI9aj-0003CX-TP for bug-gnu-emacs@gnu.org; Wed, 18 Jan 2023 09:39:09 -0500 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta211.mxroute.com (ZoneMTA) with ESMTPSA id 185c5518f84000011e.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Wed, 18 Jan 2023 14:38:54 +0000 X-Zone-Loop: 9464736528e21a03c9f6f2a72c3d75981d9b56193f35 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From:Sender: Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=x8ejFyYV7MR7brDUuXtErWIJV+0Nxv2yAhubSMzsVqo=; b=KDSxkV2TTNBh4neLyIsfDxJAKS 0zoUpxNt6/26fWwiGmARMSSRX1wxXZrw0MFAZQSFsaUBn3BDou1D5XI/fbHmS+3SUyCZNOGet/a8b HWTN6wIdqZiqxWBJkSYqp4P40FIc05ZI+4U/8UCAa0wY/Ibu4EwF3FUoX9POmd5OIJXKjaTgpzcmm 4REjAbkE0gPOvKUFv0Wwo9z7JVrpLxZ02qTC04zSptXz5gF5XDFi6c1xyVQkWGKDw3Ksk444BRS92 nipLKVpermfLZEARfWlYRET2b/1tVGVeOuUZ1DfFES94hXnREBbtvk1HGq1fzGjzjLjQtOragnM3s +gbx/GeQ==; From: "J.P." To: bug-gnu-emacs@gnu.org Subject: 30.0.50; ERC >5.5: Make buttonizing more extensible X-Debbugs-CC: emacs-erc@gnu.org Date: Wed, 18 Jan 2023 06:38:51 -0800 Message-ID: <878rhzc3gk.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me Received-SPF: pass client-ip=136.175.108.211; envelope-from=jp@neverwas.me; helo=mail-108-mta211.mxroute.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: -1.4 (-) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.4 (--) --=-=-= Content-Type: text/plain Tags: patch ERC could really benefit from a more efficient, convenient, and flexible means of modifying how words (mainly nicks) are buttonized in messages. The approach being proposed here involves replacing the preferred type of the third, "form" field in `erc-button-alist' entries. It's currently an arbitrary "guard"-like sexp, which I'd like to deprecate in favor of a function capable of rewriting the button itself. The deprecated form would still be usable but would trigger a warning, going forward. A special accommodation would be made for the constant t as well as for special variables, whose values would be treated as booleans. This would cover all existing default entries as currently used in client code. The idea is for this new "rewriter" function to expect the bounds of the button under consideration as input and to return something similar, or nil, to indicate that the candidate ought to be skipped (as in not buttonized). A separate variant with a different signature will be required for nicknames since they're already treated specially. It'll be passed additional arguments, such as `erc-server-user' and `erc-channel-user' objects and a casemapped nickname, all of which are already present in the caller's environment. Additionally, the values of user options containing faces to be applied, such as `erc-button-nickname-face' and `erc-button-face' will be free for the changing, with all damage limited to the current button alone. As an example of a possible application for this, I have included a helper for displaying messages involving `substitute-command-keys'. Other applications include button colorization and alternate display text. Practical implementations of both are available on request. The second patch contains the actual changes proposed above. The first is only somewhat related but trivial enough to smuggle in with this set (IMO). The last one I threw in on a whim. It adds additional flexibility for third parties but will most likely be dropped or held back because there's no immediate use for it in ERC's client code. Thanks. In GNU Emacs 30.0.50 (build 2, x86_64-pc-linux-gnu, GTK+ Version 3.24.35, cairo version 1.17.6) of 2023-01-17 built on localhost Repository revision: 281f48f19ecad706a639d57cb937afb0b97eded7 Repository branch: master Windowing system distributor 'The X.Org Foundation', version 11.0.12014000 System Description: Fedora Linux 36 (Workstation Edition) Configured using: 'configure --enable-check-lisp-object-type --enable-checking=yes,glyphs 'CFLAGS=-O0 -g3' PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig' Configured features: ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 M17N_FLT MODULES NOTIFY INOTIFY PDUMPER PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS WEBP X11 XDBE XIM XINPUT2 XPM GTK3 ZLIB Important settings: value of $LANG: en_US.UTF-8 value of $XMODIFIERS: @im=ibus locale-coding-system: utf-8-unix Major mode: Lisp Interaction Minor modes in effect: tooltip-mode: t global-eldoc-mode: t eldoc-mode: t show-paren-mode: t electric-indent-mode: t mouse-wheel-mode: t tool-bar-mode: t menu-bar-mode: t file-name-shadow-mode: t global-font-lock-mode: t font-lock-mode: t blink-cursor-mode: t line-number-mode: t indent-tabs-mode: t transient-mark-mode: t auto-composition-mode: t auto-encryption-mode: t auto-compression-mode: t Load-path shadows: None found. Features: (shadow sort mail-extr emacsbug message mailcap yank-media puny dired dired-loaddefs rfc822 mml mml-sec epa derived epg rfc6068 epg-config gnus-util text-property-search mm-decode mm-bodies mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader sendmail rfc2047 rfc2045 ietf-drums mm-util mail-prsvr mail-utils erc iso8601 time-date auth-source cl-seq eieio eieio-core cl-macs password-cache json subr-x map thingatpt pp format-spec cl-loaddefs cl-lib erc-backend erc-goodies erc-networks byte-opt gv bytecomp byte-compile erc-common erc-compat erc-loaddefs rmc iso-transl tooltip cconv eldoc paren electric uniquify ediff-hook vc-hooks lisp-float-type elisp-mode mwheel term/x-win x-win term/common-win x-dnd tool-bar dnd fontset image regexp-opt fringe tabulated-list replace newcomment text-mode lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow isearch easymenu timer select scroll-bar mouse jit-lock font-lock syntax font-core term/tty-colors frame minibuffer nadvice seq simple cl-generic indonesian philippine cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech european ethiopic indian cyrillic chinese composite emoji-zwj charscript charprop case-table epa-hook jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs theme-loaddefs faces cus-face macroexp files window text-properties overlay sha1 md5 base64 format env code-pages mule custom widget keymap hashtable-print-readable backquote threads dbusbind inotify lcms2 dynamic-setting system-font-setting font-render-setting cairo move-toolbar gtk x-toolkit xinput2 x multi-tty make-network-process emacs) Memory information: ((conses 16 64390 6319) (symbols 48 8639 0) (strings 32 23673 1623) (string-bytes 1 685926) (vectors 16 15259) (vector-slots 8 209777 7692) (floats 8 24 35) (intervals 56 232 0) (buffers 976 10)) --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-5.6-Replace-Info-goto-node-with-info-in-erc-button-a.patch Content-Transfer-Encoding: quoted-printable >From 479dc9b345c0e5798505f6699df4f707f8e5ea39 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Dec 2022 19:01:40 -0800 Subject: [PATCH 1/3] [5.6] Replace Info-goto-node with info in erc-button-alist * lisp/erc/erc-button.el (erc-button-alist): Replace `Info-goto-node' with plain `info', which is autoloaded. * lisp/erc/erc-networks.el (erc-networks--set-name, erc-networks--warn-on-connect): Don't require `info'. --- lisp/erc/erc-button.el | 2 +- lisp/erc/erc-networks.el | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 1be47c3e66..979d6e7e94 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -133,7 +133,7 @@ erc-button-alist ("[`=E2=80=98]\\([a-zA-Z][-a-zA-Z_0-9!*<=3D>+]+\\)['=E2=80=99]" 1 t erc-button-describe-symbol 1) ;; pseudo links - ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t Info-goto-node 1) + ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t info 1) ("\\b\\(Ward\\|Wiki\\|WardsWiki\\|TheWiki\\):\\([A-Z][a-z]+\\([A-Z][a-= z]+\\)+\\)" 0 t (lambda (page) (browse-url (concat "http://c2.com/cgi-bin/wiki?" page))) diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 95fd8990c9..4337d633cf 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -1292,7 +1292,6 @@ erc-networks--set-name erc-server-announced-name "\" in `erc-networks-alist'" " or consider calling `erc-tls' with the keyword `:id= '." " See Info:\"(erc) Network Identifier\" for more."))) - (require 'info) (erc-display-error-notice parsed m) (if erc-networks--allow-unknown-network (progn @@ -1514,7 +1513,6 @@ erc-networks--warn-on-connect "Emit warning when the `networks' module hasn't been loaded. Ideally, do so upon opening the network process." (unless (or erc--target erc-networks-mode) - (require 'info nil t) (let ((m (concat "Required module `networks' not loaded. If this " " was unexpected, please add it to `erc-modules'."))) ;; Assume the server buffer has been marked as active. --=20 2.38.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Add-erc-button-helper-for-substituting-command-k.patch >From 5d97ec5342327e03f042cd88ec24609a73bcdd42 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Dec 2022 19:01:40 -0800 Subject: [PATCH 2/3] [5.6] Add erc-button helper for substituting command keys TODO: add ERC-NEWS entry for `erc-button-alist' field-type deprecation once ERC 5.5 is released and a new section for 5.6 is added. * lisp/erc/erc-button.el (erc-button-buttonize-nicks): Change type to include functions. (erc-button-alist): Deprecate arbitrary sexp form for third item of entries and offer more useful bounds-modifying function in its place. (erc-button--maybe-warn-arbitrary-sexp): Add helper for validating third `erc-button-alist' field. (erc-button-add-nickname-buttons): Accommodate function variant for "form" field of `erc-button-alist' entries. Minor optimizations. (erc-button-add-buttons-1): Show warning when arbitrary sexp for third "form" field encountered. Accommodate binary function instead. (erc-button--substitute-command-keys-in-region): Add new function to serve as default key-substitution function item in `erc-button-alist'. (erc-button--display-error-notice-with-keys): Add new helper function for displaying ad hoc warnings that possibly require key substitution. * lisp/erc/erc-networks.el (erc-networks--ensure-announced, erc-networks--on-MOTD-end): Use new key-substitutions helper from erc-button. * test/lisp/erc/erc-tests.el (erc-button--display-error-notice-with-keys): New test. --- lisp/erc/erc-button.el | 94 +++++++++++++++++++++++++++++++++----- lisp/erc/erc-networks.el | 20 ++++---- test/lisp/erc/erc-tests.el | 55 ++++++++++++++++++++++ 3 files changed, 148 insertions(+), 21 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 979d6e7e94..c2fde7c268 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -102,8 +102,15 @@ erc-button-wrap-long-urls :type '(choice integer boolean)) (defcustom erc-button-buttonize-nicks t - "Flag indicating whether nicks should be buttonized or not." - :type 'boolean) + "Flag indicating whether nicks should be buttonized or not. +When the value is a function, it must accept four arguments: the +bounds of the nick in the current message (as a cons), the nick +itself (case-mapped and without text properties), the nick's +`erc-server-users' entry, and a (possibly nil) `erc-channel-user' +object. It must return replacement bounds when buttonizing +should proceed and nil otherwise." + :package-version '(ERC . "5.6") + :type '(choice boolean function)) (defcustom erc-button-rfc-url "https://tools.ietf.org/html/rfc%s" "URL used to browse RFC references. @@ -165,8 +172,16 @@ erc-button-alist BUTTON is the number of the regexp grouping actually matching the button. This is ignored if REGEXP is `nicknames'. -FORM is a Lisp expression which must eval to true for the button to - be added. +FORM is a Lisp symbol for a special variable whose value must be + true for the button to be added. Alternatively, it can be a + function whose arguments are BEG and END, the bounds of the + button in the current buffer. It's expected to return a cons + of (possibly identical) bounds or nil, to deny. For the extent + of the call, all face options defined for the button module are + re-bound, shadowing themselves, so the function is free to + change their values. Important: different arguments are passed + when REGEXP is `nickname'; see `erc-button-buttonize-nicks' for + details. CALLBACK is the function to call when the user push this button. CALLBACK can also be a symbol. Its variable value will be used @@ -176,7 +191,7 @@ erc-button-alist CALLBACK. There can be several PAR arguments. If REGEXP is `nicknames', these are ignored, and CALLBACK will be called with the nickname matched as the argument." - :version "29.1" + :package-version '(ERC . "5.6") ; FIXME sync on release :type '(repeat (list :tag "Button" (choice :tag "Matches" @@ -275,22 +290,47 @@ erc-button-add-buttons (concat "\\<" (regexp-quote (car elem)) "\\>") entry))))))))))) +(defun erc-button--maybe-warn-arbitrary-sexp (form) + (if (and (symbolp form) (special-variable-p form)) + (symbol-value form) + (unless (get 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp) + (put 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp t) + (lwarn 'erc :warning + (concat "Arbitrary sexps for the third FORM" + " slot of `erc-button-alist' entries" + " have been deprecated."))) + (eval form t))) + (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)) bounds word) - (when (or (eq t form) - (eval form t)) + (when (eq 'erc-button-buttonize-nicks form) + (setq form (symbol-value form))) + (when (or (functionp form) + (eq t form) + (and form (erc-button--maybe-warn-arbitrary-sexp 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))) - (when (or (and (erc-server-buffer-p) (erc-get-server-user word)) - (and erc-channel-users (erc-get-channel-user word))) - (erc-button-add-button (car bounds) (cdr bounds) - fun t (list word)))))))) + (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))))) + (when (and user + (or (not (functionp form)) + (setq bounds + (funcall form bounds down user (cdr cuser))))) + (erc-button-add-button (car bounds) (cdr bounds) + fun t (list word))))))))) (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." @@ -302,7 +342,14 @@ erc-button-add-buttons-1 (fun (nth 3 entry)) (data (mapcar #'match-string-no-properties (nthcdr 4 entry)))) (when (or (eq t form) - (eval form t)) + (and (functionp form) + (let* ((erc-button-face erc-button-face) + (erc-button-mouse-face erc-button-mouse-face) + (erc-button-nickname-face erc-button-nickname-face) + (rv (funcall form start end))) + (when rv + (setq end (cdr rv) start (car rv))))) + (erc-button--maybe-warn-arbitrary-sexp form)) (erc-button-add-button start end fun nil data regexp))))) (defun erc-button-remove-old-buttons () @@ -511,6 +558,29 @@ erc-button-beats-to-time (message "@%s is %d:%02d local time" beats hours minutes))) +(defun erc-button--substitute-command-keys-in-region (beg end) + "Replace command in region with keys and return new bounds" + (let* ((o (buffer-substring beg end)) + (s (substitute-command-keys o))) + (unless (equal o s) + (setq erc-button-face nil)) + (delete-region beg end) + (insert s)) + (cons beg (point))) + +(defun erc-button--display-error-notice-with-keys (parsed &rest strings) + "Add help keys to STRING for corner-case admonishments." + (when (stringp parsed) + (push parsed strings) + (setq parsed nil)) + (let ((string (apply #'concat strings)) + (erc-button-alist + `((,(rx "\\[" (group (+ (not "]"))) "]") 0 + erc-button--substitute-command-keys-in-region + erc-button-describe-symbol 1) + ,@erc-button-alist))) + (erc-display-error-notice parsed string))) + (provide 'erc-button) ;;; erc-button.el ends here diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 4337d633cf..dd481032e7 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -67,6 +67,9 @@ erc-session-server (declare-function erc-server-process-alive "erc-backend" (&optional buffer)) (declare-function erc-set-active-buffer "erc" (buffer)) +(declare-function erc-button--display-error-notice-with-keys + (parsed &rest strings)) + ;; Variables (defgroup erc-networks nil @@ -1310,12 +1313,11 @@ erc-networks--ensure-announced Copy source (prefix) from MOTD-ish message as a last resort." ;; The 004 handler never ran; see 2004-03-10 Diane Murray in change log (unless erc-server-announced-name - (setq erc-server-announced-name (erc-response.sender parsed)) - (erc-display-error-notice - parsed (concat "Failed to determine server name. Using \"" - erc-server-announced-name "\" instead." - " If this was unexpected, consider reporting it via " - (substitute-command-keys "\\[erc-bug]") "."))) + (require 'erc-button) + (erc-button--display-error-notice-with-keys + parsed "Failed to determine server name. Using \"" + (setq erc-server-announced-name (erc-response.sender parsed)) "\" instead" + ". If this was unexpected, consider reporting it via \\[erc-bug]" ".")) nil) (defun erc-unset-network-name (_nick _ip _reason) @@ -1493,9 +1495,9 @@ erc-networks-on-MOTD-end (memq (erc--target-symbol erc--target) erc-networks--bouncer-targets))) proc) - (let ((m (concat "Unexpected state detected. Please report via " - (substitute-command-keys "\\[erc-bug]") "."))) - (erc-display-error-notice parsed m)))) + (require 'erc-button) + (erc-button--display-error-notice-with-keys + parsed "Unexpected state detected. Please report via \\[erc-bug]."))) ;; For now, retain compatibility with erc-server-NNN-functions. (or (erc-networks--ensure-announced proc parsed) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 85506c3d27..cbe9d04d05 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -1359,4 +1359,59 @@ define-erc-module--local (put 'erc-mname-enable 'definition-name 'mname) (put 'erc-mname-disable 'definition-name 'mname)))))) + +;; XXX move erc-button tests to new file if more added. +(require 'erc-button) + +;; See also `erc-scenarios-networks-announced-missing' in +;; erc-scenarios-misc.el for a more realistic example. +(ert-deftest erc-button--display-error-notice-with-keys () + (with-current-buffer (get-buffer-create "*fake*") + (let ((mode erc-button-mode) + (inhibit-message noninteractive) + erc-modules + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) + (erc-mode) + (erc-button-mode +1) + (erc-tests--set-fake-server-process "sleep" "1") + (erc-tests--send-prep) + (erc-button--display-error-notice-with-keys + "If \\[erc-bol] fails, " + "see \\[erc-bug] or `erc-mode-map'.") + (goto-char (point-min)) + + (ert-info ("Keymap substitution succeeds") + (erc-button-next) + (should (looking-at "C-a")) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (erc-button-press-button) + (with-current-buffer "*Help*" + (goto-char (point-min)) + (should (search-forward "erc-bol" nil t))) + (erc-button-next) + (erc-button-previous) ; end of interval correct + (should (looking-at "a fails"))) + + (ert-info ("Extended command mapping succeeds") + (erc-button-next) + (should (looking-at "M-x erc-bug")) + (erc-button-press-button) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (with-current-buffer "*Help*" + (goto-char (point-min)) + (should (search-forward "erc-bug" nil t)))) + + (ert-info ("Symbol-description face preserved") ; mutated by d-e-n-w-k + (erc-button-next) + (should (equal (get-text-property (point) 'font-lock-face) + '(erc-button erc-error-face))) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (should (eq erc-button-face 'erc-button))) ; extent evaporates + + (when noninteractive + (unless mode + (erc-button-mode -1)) + (kill-buffer "*Help*") + (kill-buffer))))) + ;;; erc-tests.el ends here -- 2.38.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0003-5.6-Allow-erc-button-add-face-to-take-an-object.patch >From 6df0ae6ab237b72406b5f60ef37679087050916b Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 13 Jan 2023 05:13:06 -0800 Subject: [PATCH 3/3] [5.6] Allow erc-button-add-face to take an object * lisp/erc/erc-button.el (erc-button--add-nickname-face-function): New internal var. (erc-button-add-button): Call `erc-button--add-nickname-face-function' when it's a function for applying `erc-button-nickname-face'. (erc-button-add-face): Add optional `object' param. --- lisp/erc/erc-button.el | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index c2fde7c268..478bbb52da 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -364,6 +364,8 @@ erc-button-remove-old-buttons mouse-face nil keymap nil))) +(defvar erc-button--add-nickname-face-function nil) + (defun erc-button-add-button (from to fun nick-p &optional data regexp) "Create a button between FROM and TO with callback FUN and data DATA. NICK-P specifies if this is a nickname button. @@ -390,7 +392,10 @@ erc-button-add-button (move-marker pos (point)))))) (if nick-p (when erc-button-nickname-face - (erc-button-add-face from to erc-button-nickname-face)) + (if erc-button--add-nickname-face-function + (funcall erc-button--add-nickname-face-function + from to erc-button-nickname-face) + (erc-button-add-face from to erc-button-nickname-face))) (when erc-button-face (erc-button-add-face from to erc-button-face))) (add-text-properties @@ -402,16 +407,16 @@ erc-button-add-button (list 'rear-nonsticky t) (and data (list 'erc-data data))))) -(defun erc-button-add-face (from to face) +(defun erc-button-add-face (from to face &optional object) "Add FACE to the region between FROM and TO." ;; If we just use `add-text-property', then this will overwrite any ;; face text property already used for the button. It will not be ;; merged correctly. If we use overlays, then redisplay will be ;; very slow with lots of buttons. This is why we manually merge ;; face text properties. - (let ((old (erc-list (get-text-property from 'font-lock-face))) + (let ((old (erc-list (get-text-property from 'font-lock-face object))) (pos from) - (end (next-single-property-change from 'font-lock-face nil to)) + (end (next-single-property-change from 'font-lock-face object to)) new) ;; old is the face at pos, in list form. It is nil if there is no ;; face at pos. If nil, the new face is FACE. If not nil, the @@ -419,10 +424,10 @@ erc-button-add-face ;; where this face changes. (while (< pos to) (setq new (if old (cons face old) face)) - (put-text-property pos end 'font-lock-face new) + (put-text-property pos end 'font-lock-face new object) (setq pos end - old (erc-list (get-text-property pos 'font-lock-face)) - end (next-single-property-change pos 'font-lock-face nil to))))) + old (erc-list (get-text-property pos 'font-lock-face object)) + end (next-single-property-change pos 'font-lock-face object to))))) ;; widget-button-click calls with two args, we ignore the first. ;; Since Emacs runs this directly, rather than with -- 2.38.1 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sun Feb 19 10:04:26 2023 Received: (at 60933) by debbugs.gnu.org; 19 Feb 2023 15:04:26 +0000 Received: from localhost ([127.0.0.1]:49459 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pTlEm-0001uY-VZ for submit@debbugs.gnu.org; Sun, 19 Feb 2023 10:04:26 -0500 Received: from mail-108-mta133.mxroute.com ([136.175.108.133]:45663) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pTlEk-0001uH-3r for 60933@debbugs.gnu.org; Sun, 19 Feb 2023 10:04:23 -0500 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta133.mxroute.com (ZoneMTA) with ESMTPSA id 1866a343f55000edb4.001 for <60933@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Sun, 19 Feb 2023 15:04:14 +0000 X-Zone-Loop: 06f9627f374418a2266db100f179e7185f7ff394cc36 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=mZo4XblxpHpUN/81HvxS4id/xoO12Hx9P5LG03mujQ8=; b=ktPEpRoEcKWesajp/tvFf5ME8l dw8aKNneVVEtnqmje3ZzTJUhJKFhDM6cee1359oBpMcJIwrS4FbvD4vNB2vxJNtT2gYvAXGf3h1bl TgiL7xGbz+zsL7zH5/UuCmRt4rAZ7z4xVZEWtfC/Q/xy6XEB7PbCQ47xizduIW5Z//WvftPFPPi/G zRxZ9XZd+PQ1mHuzATDBVlDYwftdYoIUg1r+xNAcnqZIyaOXZixZkBpxoIwr83lNfmdAuZzvf7vY5 e+6Zz/hPSeoS3Dx+GvrXLFP3N7t8HmMh55WgLlRD/caC80Q3kGDKInHAKl8UDX5leWw265b1qLdeE kyKlpNAw==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <878rhzc3gk.fsf@neverwas.me> (J. P.'s message of "Wed, 18 Jan 2023 06:38:51 -0800") References: <878rhzc3gk.fsf@neverwas.me> Date: Sun, 19 Feb 2023 07:04:11 -0800 Message-ID: <87ilfxwvbo.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain v2. Use dedicated internal function as module interface for modifying nick buttonizing (via :around advice). --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0000-v1-v2.diff >From 49c703272cb3d0f4ec035175e94c132fa32eaeba Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 16 Feb 2023 22:40:55 -0800 Subject: [PATCH 0/3] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (3): [5.6] Replace Info-goto-node with info in erc-button-alist [5.6] Add erc-button helper for substituting command keys [5.6] Allow erc-button-add-face to take an object lisp/erc/erc-button.el | 158 ++++++++++++++++++++++++++++++++----- lisp/erc/erc-networks.el | 22 +++--- test/lisp/erc/erc-tests.el | 56 +++++++++++++ 3 files changed, 206 insertions(+), 30 deletions(-) Interdiff: diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 478bbb52daa..49e3caf49a1 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -52,7 +52,8 @@ erc-button ;;;###autoload(autoload 'erc-button-mode "erc-button" nil t) (define-erc-module button nil "This mode buttonizes all messages according to `erc-button-alist'." - ((add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) + ((erc-button--check-nicknames-entry) + (add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) (add-hook 'erc-send-modify-hook #'erc-button-add-buttons 'append) (add-hook 'erc-complete-functions #'erc-button-next-function) (add-hook 'erc-mode-hook #'erc-button-setup)) @@ -102,15 +103,8 @@ erc-button-wrap-long-urls :type '(choice integer boolean)) (defcustom erc-button-buttonize-nicks t - "Flag indicating whether nicks should be buttonized or not. -When the value is a function, it must accept four arguments: the -bounds of the nick in the current message (as a cons), the nick -itself (case-mapped and without text properties), the nick's -`erc-server-users' entry, and a (possibly nil) `erc-channel-user' -object. It must return replacement bounds when buttonizing -should proceed and nil otherwise." - :package-version '(ERC . "5.6") - :type '(choice boolean function)) + "Flag indicating whether nicks should be buttonized or not." + :type 'boolean) (defcustom erc-button-rfc-url "https://tools.ietf.org/html/rfc%s" "URL used to browse RFC references. @@ -173,15 +167,16 @@ erc-button-alist button. This is ignored if REGEXP is `nicknames'. FORM is a Lisp symbol for a special variable whose value must be - true for the button to be added. Alternatively, it can be a - function whose arguments are BEG and END, the bounds of the - button in the current buffer. It's expected to return a cons - of (possibly identical) bounds or nil, to deny. For the extent - of the call, all face options defined for the button module are - re-bound, shadowing themselves, so the function is free to - change their values. Important: different arguments are passed - when REGEXP is `nickname'; see `erc-button-buttonize-nicks' for - details. + true for the button to be added. Alternatively, when REGEXP is + not `nicknames', FORM can be a function whose arguments are BEG + and END, the bounds of the button in the current buffer. It's + expected to return a cons of (possibly identical) bounds or + nil, to deny. For the extent of the call, all face options + defined for the button module are re-bound, shadowing + themselves, so the function is free to change their values. + When regexp is the special symbol `nicknames', FORM must be the + symbol `erc-button-buttonize-nicks'. Specifying anything else + is deprecated. CALLBACK is the function to call when the user push this button. CALLBACK can also be a symbol. Its variable value will be used @@ -301,13 +296,45 @@ erc-button--maybe-warn-arbitrary-sexp " have been deprecated."))) (eval form t))) +(defun erc-button--check-nicknames-entry () + ;; This helper exists because the module is defined after its options. + (when-let (((eq major-mode 'erc-mode)) + (entry (alist-get 'nicknames erc-button-alist))) + (unless (eq 'erc-button-buttonize-nicks (nth 1 entry)) + (erc-button--display-error-notice-with-keys-and-warn + "Values other than `erc-button-buttonize-nicks' in the third slot of " + "the `nicknames' entry of `erc-button-alist' are deprecated.")))) + +(defun erc-button--preserve-bounds (bounds _ _ _) + "Return BOUNDS.\n\n(fn BOUNDS NICKNAME SERVER-USER CHANNEL-USER)" + bounds) + +;; This variable is intended to serve as a "core" to be wrapped by +;; (built-in) modules during setup. It's unclear whether +;; `add-function's practice of removing existing advice before +;; re-adding it is desirable when integrating modules since we're +;; mostly concerned with ensuring one "piece" precedes or follows +;; another (specific piece), which may not yet (or ever) be present. + +(defvar erc-button--modify-nick-function #'erc-button--preserve-bounds + "Function to possibly modify aspects of nick being buttonized. +Called with four args: BOUNDS NICKNAME SERVER-USER CHANNEL-USER. +BOUNDS is a cons of (BEG . END) marking the position of the nick +in the current message, which occupies the whole of the narrowed +buffer. NICKNAME is a case-mapped string without text +properties. SERVER-USER and CHANNEL-USER are the nick's +`erc-server-users' entry and its associated (though possibly nil) +`erc-channel-user' object. The function should return BOUNDS or +a suitable replacement to indicate that buttonizing ought to +proceed, and nil if it should be inhibited.") + (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)) bounds word) - (when (eq 'erc-button-buttonize-nicks form) - (setq form (symbol-value form))) + (when (eq form 'erc-button-buttonize-nicks) + (setq form (and (symbol-value form) erc-button--modify-nick-function))) (when (or (functionp form) (eq t form) (and form (erc-button--maybe-warn-arbitrary-sexp form))) @@ -574,17 +601,35 @@ erc-button--substitute-command-keys-in-region (cons beg (point))) (defun erc-button--display-error-notice-with-keys (parsed &rest strings) - "Add help keys to STRING for corner-case admonishments." + "Add help keys to STRINGS for corner-case admonishments. +Return inserted result." (when (stringp parsed) (push parsed strings) (setq parsed nil)) - (let ((string (apply #'concat strings)) - (erc-button-alist - `((,(rx "\\[" (group (+ (not "]"))) "]") 0 - erc-button--substitute-command-keys-in-region - erc-button-describe-symbol 1) - ,@erc-button-alist))) - (erc-display-error-notice parsed string))) + (let* ((string (apply #'concat strings)) + (erc-insert-post-hook + (cons (lambda () + (setq string (buffer-substring (point-min) (1- (point-max))))) + erc-insert-post-hook)) + (erc-button-alist + `((,(rx "\\[" (group (+ (not "]"))) "]") 0 + erc-button--substitute-command-keys-in-region + erc-button-describe-symbol 1) + ,@erc-button-alist))) + (erc-display-error-notice parsed string) + string)) + +(defun erc-button--display-error-notice-with-keys-and-warn (&rest args) + "Like `erc-button--display-error-notice-with-keys' but also warn." + (let ((string (apply #'erc-button--display-error-notice-with-keys args))) + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (with-syntax-table lisp-mode-syntax-table + (skip-syntax-forward "^-")) + (forward-char) + (display-warning + 'erc (buffer-substring-no-properties (point) (point-max)))))) (provide 'erc-button) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 46bb0c9ba77..4d6fd227518 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -1383,12 +1383,13 @@ erc-button--display-error-notice-with-keys erc-modules erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) (erc-mode) - (erc-button-mode +1) (erc-tests--set-fake-server-process "sleep" "1") - (erc-tests--send-prep) - (erc-button--display-error-notice-with-keys - "If \\[erc-bol] fails, " - "see \\[erc-bug] or `erc-mode-map'.") + (erc--initialize-markers (point) nil) + (erc-button-mode +1) + (should (equal (erc-button--display-error-notice-with-keys + "If \\[erc-bol] fails, " + "see \\[erc-bug] or `erc-mode-map'.") + "*** If C-a fails, see M-x erc-bug or `erc-mode-map'.")) (goto-char (point-min)) (ert-info ("Keymap substitution succeeds") -- 2.39.1 --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-5.6-Replace-Info-goto-node-with-info-in-erc-button-a.patch Content-Transfer-Encoding: quoted-printable >From 3e8a0cb31d727554369d01bbfbf8c0c29d1c2825 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Dec 2022 19:01:40 -0800 Subject: [PATCH 1/3] [5.6] Replace Info-goto-node with info in erc-button-alist * lisp/erc/erc-button.el (erc-button-alist): Replace `Info-goto-node' with plain `info', which is autoloaded. * lisp/erc/erc-networks.el (erc-networks--set-name, erc-networks--warn-on-connect): Don't require `info'. (Bug#60933.) --- lisp/erc/erc-button.el | 2 +- lisp/erc/erc-networks.el | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index c28dddefa0e..986c2d02053 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -133,7 +133,7 @@ erc-button-alist ("[`=E2=80=98]\\([a-zA-Z][-a-zA-Z_0-9!*<=3D>+]+\\)['=E2=80=99]" 1 t erc-button-describe-symbol 1) ;; pseudo links - ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t Info-goto-node 1) + ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t info 1) ("\\b\\(Ward\\|Wiki\\|WardsWiki\\|TheWiki\\):\\([A-Z][a-z]+\\([A-Z][a-= z]+\\)+\\)" 0 t (lambda (page) (browse-url (concat "http://c2.com/cgi-bin/wiki?" page))) diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 95fd8990c99..4337d633cfa 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -1292,7 +1292,6 @@ erc-networks--set-name erc-server-announced-name "\" in `erc-networks-alist'" " or consider calling `erc-tls' with the keyword `:id= '." " See Info:\"(erc) Network Identifier\" for more."))) - (require 'info) (erc-display-error-notice parsed m) (if erc-networks--allow-unknown-network (progn @@ -1514,7 +1513,6 @@ erc-networks--warn-on-connect "Emit warning when the `networks' module hasn't been loaded. Ideally, do so upon opening the network process." (unless (or erc--target erc-networks-mode) - (require 'info nil t) (let ((m (concat "Required module `networks' not loaded. If this " " was unexpected, please add it to `erc-modules'."))) ;; Assume the server buffer has been marked as active. --=20 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Add-erc-button-helper-for-substituting-command-k.patch >From 0fcfbc0b56ce7f0adebdac35ebbcd0c5ef685b87 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Dec 2022 19:01:40 -0800 Subject: [PATCH 2/3] [5.6] Add erc-button helper for substituting command keys TODO: add ERC-NEWS entry for `erc-button-alist' field-type deprecation once ERC 5.5 is released and a new section for 5.6 is added. * lisp/erc/erc-button.el (erc-button-mode, erc-button-enable): Warn if `erc-button-alist' contains deprecated FORM field in `nicknames' entry. (erc-button-alist): Deprecate arbitrary sexp form for third item of entries and offer more useful bounds-modifying function in its place. Mention that anything other than `erc-button-buttonize-nicks' is deprecated as the FORM field in a `nicknames' entry. (erc-button--maybe-warn-arbitrary-sexp): Add helper for validating third `erc-button-alist' field. (erc-button--check-nicknames-entry): Add helper to check for deprecated items in `erc-button-alist'. (erc-button--modify-nick-function): Add new variable to hold a function that filters nickname bounds when buttonizing. (erc-button--preserve-bounds): Add function to serve as default value for `erc-button--modify-nick-function). (erc-button-add-nickname-buttons): Accommodate function variant for "form" field of `erc-button-alist' entries. Minor optimizations. (erc-button-add-buttons-1): Show warning when arbitrary sexp for third "form" field encountered. Accommodate binary function instead. (erc-button--substitute-command-keys-in-region): Add new function to serve as default key-substitution function item in `erc-button-alist'. (erc-button--display-error-notice-with-keys): Add new helper function for displaying ad hoc warnings that possibly require key substitution. * lisp/erc/erc-networks.el (erc-networks--ensure-announced, erc-networks--on-MOTD-end): Use new key-substitutions helper from erc-button. (erc-button--display-error-notice-with-keys-and-warn): Add new function to both display an ERC error message and show a warning. * test/lisp/erc/erc-tests.el (erc-button--display-error-notice-with-keys): New test. (Bug#60933.) --- lisp/erc/erc-button.el | 137 ++++++++++++++++++++++++++++++++++--- lisp/erc/erc-networks.el | 20 +++--- test/lisp/erc/erc-tests.el | 56 +++++++++++++++ 3 files changed, 193 insertions(+), 20 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 986c2d02053..dd40c588eb5 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -52,7 +52,8 @@ erc-button ;;;###autoload(autoload 'erc-button-mode "erc-button" nil t) (define-erc-module button nil "This mode buttonizes all messages according to `erc-button-alist'." - ((add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) + ((erc-button--check-nicknames-entry) + (add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) (add-hook 'erc-send-modify-hook #'erc-button-add-buttons 'append) (add-hook 'erc-complete-functions #'erc-button-next-function) (add-hook 'erc-mode-hook #'erc-button-setup)) @@ -165,8 +166,17 @@ erc-button-alist BUTTON is the number of the regexp grouping actually matching the button. This is ignored if REGEXP is `nicknames'. -FORM is a Lisp expression which must eval to true for the button to - be added. +FORM is a Lisp symbol for a special variable whose value must be + true for the button to be added. Alternatively, when REGEXP is + not `nicknames', FORM can be a function whose arguments are BEG + and END, the bounds of the button in the current buffer. It's + expected to return a cons of (possibly identical) bounds or + nil, to deny. For the extent of the call, all face options + defined for the button module are re-bound, shadowing + themselves, so the function is free to change their values. + When regexp is the special symbol `nicknames', FORM must be the + symbol `erc-button-buttonize-nicks'. Specifying anything else + is deprecated. CALLBACK is the function to call when the user push this button. CALLBACK can also be a symbol. Its variable value will be used @@ -176,7 +186,7 @@ erc-button-alist CALLBACK. There can be several PAR arguments. If REGEXP is `nicknames', these are ignored, and CALLBACK will be called with the nickname matched as the argument." - :package-version '(ERC . "5.5") + :package-version '(ERC . "5.6") ; FIXME sync on release :type '(repeat (list :tag "Button" (choice :tag "Matches" @@ -275,22 +285,79 @@ erc-button-add-buttons (concat "\\<" (regexp-quote (car elem)) "\\>") entry))))))))))) +(defun erc-button--maybe-warn-arbitrary-sexp (form) + (if (and (symbolp form) (special-variable-p form)) + (symbol-value form) + (unless (get 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp) + (put 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp t) + (lwarn 'erc :warning + (concat "Arbitrary sexps for the third FORM" + " slot of `erc-button-alist' entries" + " have been deprecated."))) + (eval form t))) + +(defun erc-button--check-nicknames-entry () + ;; This helper exists because the module is defined after its options. + (when-let (((eq major-mode 'erc-mode)) + (entry (alist-get 'nicknames erc-button-alist))) + (unless (eq 'erc-button-buttonize-nicks (nth 1 entry)) + (erc-button--display-error-notice-with-keys-and-warn + "Values other than `erc-button-buttonize-nicks' in the third slot of " + "the `nicknames' entry of `erc-button-alist' are deprecated.")))) + +(defun erc-button--preserve-bounds (bounds _ _ _) + "Return BOUNDS.\n\n(fn BOUNDS NICKNAME SERVER-USER CHANNEL-USER)" + bounds) + +;; This variable is intended to serve as a "core" to be wrapped by +;; (built-in) modules during setup. It's unclear whether +;; `add-function's practice of removing existing advice before +;; re-adding it is desirable when integrating modules since we're +;; mostly concerned with ensuring one "piece" precedes or follows +;; another (specific piece), which may not yet (or ever) be present. + +(defvar erc-button--modify-nick-function #'erc-button--preserve-bounds + "Function to possibly modify aspects of nick being buttonized. +Called with four args: BOUNDS NICKNAME SERVER-USER CHANNEL-USER. +BOUNDS is a cons of (BEG . END) marking the position of the nick +in the current message, which occupies the whole of the narrowed +buffer. NICKNAME is a case-mapped string without text +properties. SERVER-USER and CHANNEL-USER are the nick's +`erc-server-users' entry and its associated (though possibly nil) +`erc-channel-user' object. The function should return BOUNDS or +a suitable replacement to indicate that buttonizing ought to +proceed, and nil if it should be inhibited.") + (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)) bounds word) - (when (or (eq t form) - (eval form t)) + (when (eq form 'erc-button-buttonize-nicks) + (setq form (and (symbol-value form) erc-button--modify-nick-function))) + (when (or (functionp form) + (eq t form) + (and form (erc-button--maybe-warn-arbitrary-sexp 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))) - (when (or (and (erc-server-buffer-p) (erc-get-server-user word)) - (and erc-channel-users (erc-get-channel-user word))) - (erc-button-add-button (car bounds) (cdr bounds) - fun t (list word)))))))) + (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))))) + (when (and user + (or (not (functionp form)) + (setq bounds + (funcall form bounds down user (cdr cuser))))) + (erc-button-add-button (car bounds) (cdr bounds) + fun t (list word))))))))) (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." @@ -302,7 +369,14 @@ erc-button-add-buttons-1 (fun (nth 3 entry)) (data (mapcar #'match-string-no-properties (nthcdr 4 entry)))) (when (or (eq t form) - (eval form t)) + (and (functionp form) + (let* ((erc-button-face erc-button-face) + (erc-button-mouse-face erc-button-mouse-face) + (erc-button-nickname-face erc-button-nickname-face) + (rv (funcall form start end))) + (when rv + (setq end (cdr rv) start (car rv))))) + (erc-button--maybe-warn-arbitrary-sexp form)) (erc-button-add-button start end fun nil data regexp))))) (defun erc-button-remove-old-buttons () @@ -511,6 +585,47 @@ erc-button-beats-to-time (message "@%s is %d:%02d local time" beats hours minutes))) +(defun erc-button--substitute-command-keys-in-region (beg end) + "Replace command in region with keys and return new bounds" + (let* ((o (buffer-substring beg end)) + (s (substitute-command-keys o))) + (unless (equal o s) + (setq erc-button-face nil)) + (delete-region beg end) + (insert s)) + (cons beg (point))) + +(defun erc-button--display-error-notice-with-keys (parsed &rest strings) + "Add help keys to STRINGS for corner-case admonishments. +Return inserted result." + (when (stringp parsed) + (push parsed strings) + (setq parsed nil)) + (let* ((string (apply #'concat strings)) + (erc-insert-post-hook + (cons (lambda () + (setq string (buffer-substring (point-min) (1- (point-max))))) + erc-insert-post-hook)) + (erc-button-alist + `((,(rx "\\[" (group (+ (not "]"))) "]") 0 + erc-button--substitute-command-keys-in-region + erc-button-describe-symbol 1) + ,@erc-button-alist))) + (erc-display-error-notice parsed string) + string)) + +(defun erc-button--display-error-notice-with-keys-and-warn (&rest args) + "Like `erc-button--display-error-notice-with-keys' but also warn." + (let ((string (apply #'erc-button--display-error-notice-with-keys args))) + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (with-syntax-table lisp-mode-syntax-table + (skip-syntax-forward "^-")) + (forward-char) + (display-warning + 'erc (buffer-substring-no-properties (point) (point-max)))))) + (provide 'erc-button) ;;; erc-button.el ends here diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 4337d633cfa..dd481032e7e 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -67,6 +67,9 @@ erc-session-server (declare-function erc-server-process-alive "erc-backend" (&optional buffer)) (declare-function erc-set-active-buffer "erc" (buffer)) +(declare-function erc-button--display-error-notice-with-keys + (parsed &rest strings)) + ;; Variables (defgroup erc-networks nil @@ -1310,12 +1313,11 @@ erc-networks--ensure-announced Copy source (prefix) from MOTD-ish message as a last resort." ;; The 004 handler never ran; see 2004-03-10 Diane Murray in change log (unless erc-server-announced-name - (setq erc-server-announced-name (erc-response.sender parsed)) - (erc-display-error-notice - parsed (concat "Failed to determine server name. Using \"" - erc-server-announced-name "\" instead." - " If this was unexpected, consider reporting it via " - (substitute-command-keys "\\[erc-bug]") "."))) + (require 'erc-button) + (erc-button--display-error-notice-with-keys + parsed "Failed to determine server name. Using \"" + (setq erc-server-announced-name (erc-response.sender parsed)) "\" instead" + ". If this was unexpected, consider reporting it via \\[erc-bug]" ".")) nil) (defun erc-unset-network-name (_nick _ip _reason) @@ -1493,9 +1495,9 @@ erc-networks-on-MOTD-end (memq (erc--target-symbol erc--target) erc-networks--bouncer-targets))) proc) - (let ((m (concat "Unexpected state detected. Please report via " - (substitute-command-keys "\\[erc-bug]") "."))) - (erc-display-error-notice parsed m)))) + (require 'erc-button) + (erc-button--display-error-notice-with-keys + parsed "Unexpected state detected. Please report via \\[erc-bug]."))) ;; For now, retain compatibility with erc-server-NNN-functions. (or (erc-networks--ensure-announced proc parsed) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 40a2d2de657..4d6fd227518 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -1370,4 +1370,60 @@ define-erc-module--local (put 'erc-mname-enable 'definition-name 'mname) (put 'erc-mname-disable 'definition-name 'mname)))))) + +;; XXX move erc-button tests to new file if more added. +(require 'erc-button) + +;; See also `erc-scenarios-networks-announced-missing' in +;; erc-scenarios-misc.el for a more realistic example. +(ert-deftest erc-button--display-error-notice-with-keys () + (with-current-buffer (get-buffer-create "*fake*") + (let ((mode erc-button-mode) + (inhibit-message noninteractive) + erc-modules + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) + (erc-mode) + (erc-tests--set-fake-server-process "sleep" "1") + (erc--initialize-markers (point) nil) + (erc-button-mode +1) + (should (equal (erc-button--display-error-notice-with-keys + "If \\[erc-bol] fails, " + "see \\[erc-bug] or `erc-mode-map'.") + "*** If C-a fails, see M-x erc-bug or `erc-mode-map'.")) + (goto-char (point-min)) + + (ert-info ("Keymap substitution succeeds") + (erc-button-next) + (should (looking-at "C-a")) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (erc-button-press-button) + (with-current-buffer "*Help*" + (goto-char (point-min)) + (should (search-forward "erc-bol" nil t))) + (erc-button-next) + (erc-button-previous) ; end of interval correct + (should (looking-at "a fails"))) + + (ert-info ("Extended command mapping succeeds") + (erc-button-next) + (should (looking-at "M-x erc-bug")) + (erc-button-press-button) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (with-current-buffer "*Help*" + (goto-char (point-min)) + (should (search-forward "erc-bug" nil t)))) + + (ert-info ("Symbol-description face preserved") ; mutated by d-e-n-w-k + (erc-button-next) + (should (equal (get-text-property (point) 'font-lock-face) + '(erc-button erc-error-face))) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (should (eq erc-button-face 'erc-button))) ; extent evaporates + + (when noninteractive + (unless mode + (erc-button-mode -1)) + (kill-buffer "*Help*") + (kill-buffer))))) + ;;; erc-tests.el ends here -- 2.39.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0003-5.6-Allow-erc-button-add-face-to-take-an-object.patch >From 49c703272cb3d0f4ec035175e94c132fa32eaeba Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 13 Jan 2023 05:13:06 -0800 Subject: [PATCH 3/3] [5.6] Allow erc-button-add-face to take an object * lisp/erc/erc-button.el (erc-button--add-nickname-face-function): New internal var. (erc-button-add-button): Call `erc-button--add-nickname-face-function' when it's a function for applying `erc-button-nickname-face'. (erc-button-add-face): Add optional `object' param. (Bug#60933.) --- lisp/erc/erc-button.el | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index dd40c588eb5..49e3caf49a1 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -391,6 +391,8 @@ erc-button-remove-old-buttons mouse-face nil keymap nil))) +(defvar erc-button--add-nickname-face-function nil) + (defun erc-button-add-button (from to fun nick-p &optional data regexp) "Create a button between FROM and TO with callback FUN and data DATA. NICK-P specifies if this is a nickname button. @@ -417,7 +419,10 @@ erc-button-add-button (move-marker pos (point)))))) (if nick-p (when erc-button-nickname-face - (erc-button-add-face from to erc-button-nickname-face)) + (if erc-button--add-nickname-face-function + (funcall erc-button--add-nickname-face-function + from to erc-button-nickname-face) + (erc-button-add-face from to erc-button-nickname-face))) (when erc-button-face (erc-button-add-face from to erc-button-face))) (add-text-properties @@ -429,16 +434,16 @@ erc-button-add-button (list 'rear-nonsticky t) (and data (list 'erc-data data))))) -(defun erc-button-add-face (from to face) +(defun erc-button-add-face (from to face &optional object) "Add FACE to the region between FROM and TO." ;; If we just use `add-text-property', then this will overwrite any ;; face text property already used for the button. It will not be ;; merged correctly. If we use overlays, then redisplay will be ;; very slow with lots of buttons. This is why we manually merge ;; face text properties. - (let ((old (erc-list (get-text-property from 'font-lock-face))) + (let ((old (erc-list (get-text-property from 'font-lock-face object))) (pos from) - (end (next-single-property-change from 'font-lock-face nil to)) + (end (next-single-property-change from 'font-lock-face object to)) new) ;; old is the face at pos, in list form. It is nil if there is no ;; face at pos. If nil, the new face is FACE. If not nil, the @@ -446,10 +451,10 @@ erc-button-add-face ;; where this face changes. (while (< pos to) (setq new (if old (cons face old) face)) - (put-text-property pos end 'font-lock-face new) + (put-text-property pos end 'font-lock-face new object) (setq pos end - old (erc-list (get-text-property pos 'font-lock-face)) - end (next-single-property-change pos 'font-lock-face nil to))))) + old (erc-list (get-text-property pos 'font-lock-face object)) + end (next-single-property-change pos 'font-lock-face object to))))) ;; widget-button-click calls with two args, we ignore the first. ;; Since Emacs runs this directly, rather than with -- 2.39.1 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Thu Mar 09 09:42:22 2023 Received: (at 60933) by debbugs.gnu.org; 9 Mar 2023 14:42:22 +0000 Received: from localhost ([127.0.0.1]:51264 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1paHTI-0003UL-Km for submit@debbugs.gnu.org; Thu, 09 Mar 2023 09:42:22 -0500 Received: from mail-108-mta182.mxroute.com ([136.175.108.182]:39741) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1paHTF-0003U4-Lv for 60933@debbugs.gnu.org; Thu, 09 Mar 2023 09:42:19 -0500 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta182.mxroute.com (ZoneMTA) with ESMTPSA id 186c6d28648000edb4.001 for <60933@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Thu, 09 Mar 2023 14:42:11 +0000 X-Zone-Loop: 842093f9ed612b5c72c7d8eee83819418b1046db656d X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=IWouYqXHTtMj6NKOZy5gRVP+GjU7KFhVzQC25CerIWQ=; b=j+zqABvaYtMFD9XMay8gIK8csp EA8Ai0+EU1Zme62X/YWtoUpF8BiZNgaBzQZjDMZnGME0dMY007zdaUZP4FBnikVI4fVANiQz9zWNy q3qP1mpePtuLbh6K560ZZk2T0qv/EUlGZKF1vkHoCxNk5btWIO1mADVuGeh9kFyZJVXCRCXa8tScd a3hU8OdVHoPVUuPXZ2ucZkUfJacH9FT73Bq8M9Y3xrY4070Vo8AIs9XvZIEZfAbxc0I2u389v/X/v RIpJZ4MUlwV38nkU265tBjGDyjmQAjN01X/Ie+0dr+0X+LRYxfsOqhEuuXxxiLWTrY2OLSQFS+1Mr wIkloXVw==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <878rhzc3gk.fsf@neverwas.me> (J. P.'s message of "Wed, 18 Jan 2023 06:38:51 -0800") References: <878rhzc3gk.fsf@neverwas.me> Date: Thu, 09 Mar 2023 06:42:07 -0800 Message-ID: <87fsaekmv4.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain v3. Expand `erc-button-alist' pattern to recognize inline (info "...") forms. Autoload button helpers (locally, in ERC's own loaddefs). --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0000-v2-v3.diff Content-Transfer-Encoding: quoted-printable >From 28517cf23b5ed65f8a421dddcffec6a0aecd7fe5 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 9 Mar 2023 06:28:53 -0800 Subject: [PATCH 0/3] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (3): [5.6] Replace Info-goto-node with info in erc-button-alist [5.6] Add erc-button helper for substituting command keys [5.6] Allow erc-button-add-face to take an object lisp/erc/erc-button.el | 172 +++++++++++++++++++++++++++++++++---- lisp/erc/erc-networks.el | 22 ++--- test/lisp/erc/erc-tests.el | 56 ++++++++++++ 3 files changed, 220 insertions(+), 30 deletions(-) Interdiff: diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 49e3caf49a1..c94a412eea8 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -134,7 +134,7 @@ erc-button-alist ("[`=E2=80=98]\\([a-zA-Z][-a-zA-Z_0-9!*<=3D>+]+\\)['=E2=80=99]" 1 t erc-button-describe-symbol 1) ;; pseudo links - ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t info 1) + ("\\(?:\\bInfo: ?\\|(info \\)[\"]\\(([^\"]+\\)[\"])?" 0 t info 1) ("\\b\\(Ward\\|Wiki\\|WardsWiki\\|TheWiki\\):\\([A-Z][a-z]+\\([A-Z][a-= z]+\\)+\\)" 0 t (lambda (page) (browse-url (concat "http://c2.com/cgi-bin/wiki?" page))) @@ -600,25 +600,39 @@ erc-button--substitute-command-keys-in-region (insert s)) (cons beg (point))) =20 -(defun erc-button--display-error-notice-with-keys (parsed &rest strings) - "Add help keys to STRINGS for corner-case admonishments. -Return inserted result." +;;;###autoload +(defun erc-button--display-error-notice-with-keys (&optional parsed buffer + &rest strings) + "Add help keys to STRINGS for configuration-related admonishments. +Return inserted result. PARSED can be an `erc-response' object, +a string, or nil. BUFFER can be a buffer, a string, or nil. As +a special case, PARSED can also be a buffer as long as BUFFER is +a string or nil." + (when (stringp buffer) + (push buffer strings) + (setq buffer nil)) (when (stringp parsed) (push parsed strings) (setq parsed nil)) + (when (bufferp parsed) + (cl-assert (null buffer)) + (setq buffer parsed + parsed nil)) (let* ((string (apply #'concat strings)) (erc-insert-post-hook (cons (lambda () - (setq string (buffer-substring (point-min) (1- (point-ma= x))))) + (setq string (buffer-substring (point-min) + (1- (point-max))))) erc-insert-post-hook)) (erc-button-alist `((,(rx "\\[" (group (+ (not "]"))) "]") 0 erc-button--substitute-command-keys-in-region erc-button-describe-symbol 1) ,@erc-button-alist))) - (erc-display-error-notice parsed string) + (erc-display-message parsed '(notice error) (or buffer 'active) string) string)) =20 +;;;###autoload (defun erc-button--display-error-notice-with-keys-and-warn (&rest args) "Like `erc-button--display-error-notice-with-keys' but also warn." (let ((string (apply #'erc-button--display-error-notice-with-keys args))) --=20 2.39.2 --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-5.6-Replace-Info-goto-node-with-info-in-erc-button-a.patch Content-Transfer-Encoding: quoted-printable >From f61bd6bb6129571327cdb9e68b38e8221b72d91e Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Dec 2022 19:01:40 -0800 Subject: [PATCH 1/3] [5.6] Replace Info-goto-node with info in erc-button-alist * lisp/erc/erc-button.el (erc-button-alist): Replace `Info-goto-node' with plain `info', which is autoloaded. Expand regexp to recognize inline `info' function calls. * lisp/erc/erc-networks.el (erc-networks--set-name, erc-networks--warn-on-connect): Don't require `info'. (Bug#60933.) --- lisp/erc/erc-button.el | 2 +- lisp/erc/erc-networks.el | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index c28dddefa0e..891b453466f 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -133,7 +133,7 @@ erc-button-alist ("[`=E2=80=98]\\([a-zA-Z][-a-zA-Z_0-9!*<=3D>+]+\\)['=E2=80=99]" 1 t erc-button-describe-symbol 1) ;; pseudo links - ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t Info-goto-node 1) + ("\\(?:\\bInfo: ?\\|(info \\)[\"]\\(([^\"]+\\)[\"])?" 0 t info 1) ("\\b\\(Ward\\|Wiki\\|WardsWiki\\|TheWiki\\):\\([A-Z][a-z]+\\([A-Z][a-= z]+\\)+\\)" 0 t (lambda (page) (browse-url (concat "http://c2.com/cgi-bin/wiki?" page))) diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 95fd8990c99..4337d633cfa 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -1292,7 +1292,6 @@ erc-networks--set-name erc-server-announced-name "\" in `erc-networks-alist'" " or consider calling `erc-tls' with the keyword `:id= '." " See Info:\"(erc) Network Identifier\" for more."))) - (require 'info) (erc-display-error-notice parsed m) (if erc-networks--allow-unknown-network (progn @@ -1514,7 +1513,6 @@ erc-networks--warn-on-connect "Emit warning when the `networks' module hasn't been loaded. Ideally, do so upon opening the network process." (unless (or erc--target erc-networks-mode) - (require 'info nil t) (let ((m (concat "Required module `networks' not loaded. If this " " was unexpected, please add it to `erc-modules'."))) ;; Assume the server buffer has been marked as active. --=20 2.39.2 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Add-erc-button-helper-for-substituting-command-k.patch >From 5fb14bbc6535acfabcec5afe89613528e1b405b5 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Dec 2022 19:01:40 -0800 Subject: [PATCH 2/3] [5.6] Add erc-button helper for substituting command keys TODO: add ERC-NEWS entry for `erc-button-alist' field-type deprecation once ERC 5.5 is released and a new section for 5.6 is added. * lisp/erc/erc-button.el (erc-button-mode, erc-button-enable): Warn if `erc-button-alist' contains deprecated FORM field in `nicknames' entry. (erc-button-alist): Deprecate arbitrary sexp form for third item of entries and offer more useful bounds-modifying function in its place. Mention that anything other than `erc-button-buttonize-nicks' is deprecated as the FORM field in a `nicknames' entry. (erc-button--maybe-warn-arbitrary-sexp): Add helper for validating third `erc-button-alist' field. (erc-button--check-nicknames-entry): Add helper to check for deprecated items in `erc-button-alist'. (erc-button--modify-nick-function): Add new variable to hold a function that filters nickname bounds when buttonizing. (erc-button--preserve-bounds): Add function to serve as default value for `erc-button--modify-nick-function). (erc-button-add-nickname-buttons): Accommodate function variant for "form" field of `erc-button-alist' entries. Minor optimizations. (erc-button-add-buttons-1): Show warning when arbitrary sexp for third "form" field encountered. Accommodate binary function instead. (erc-button--substitute-command-keys-in-region): Add new function to serve as default key-substitution function item in `erc-button-alist'. (erc-button--display-error-notice-with-keys): Add new helper function for displaying ad hoc warnings that possibly require key substitution. * lisp/erc/erc-networks.el (erc-networks--ensure-announced, erc-networks--on-MOTD-end): Use new key-substitutions helper from erc-button. (erc-button--display-error-notice-with-keys-and-warn): Add new function to both display an ERC error message and show a warning. * test/lisp/erc/erc-tests.el (erc-button--display-error-notice-with-keys): New test. (Bug#60933.) --- lisp/erc/erc-button.el | 151 ++++++++++++++++++++++++++++++++++--- lisp/erc/erc-networks.el | 20 ++--- test/lisp/erc/erc-tests.el | 56 ++++++++++++++ 3 files changed, 207 insertions(+), 20 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 891b453466f..eca3df44892 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -52,7 +52,8 @@ erc-button ;;;###autoload(autoload 'erc-button-mode "erc-button" nil t) (define-erc-module button nil "This mode buttonizes all messages according to `erc-button-alist'." - ((add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) + ((erc-button--check-nicknames-entry) + (add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) (add-hook 'erc-send-modify-hook #'erc-button-add-buttons 'append) (add-hook 'erc-complete-functions #'erc-button-next-function) (add-hook 'erc-mode-hook #'erc-button-setup)) @@ -165,8 +166,17 @@ erc-button-alist BUTTON is the number of the regexp grouping actually matching the button. This is ignored if REGEXP is `nicknames'. -FORM is a Lisp expression which must eval to true for the button to - be added. +FORM is a Lisp symbol for a special variable whose value must be + true for the button to be added. Alternatively, when REGEXP is + not `nicknames', FORM can be a function whose arguments are BEG + and END, the bounds of the button in the current buffer. It's + expected to return a cons of (possibly identical) bounds or + nil, to deny. For the extent of the call, all face options + defined for the button module are re-bound, shadowing + themselves, so the function is free to change their values. + When regexp is the special symbol `nicknames', FORM must be the + symbol `erc-button-buttonize-nicks'. Specifying anything else + is deprecated. CALLBACK is the function to call when the user push this button. CALLBACK can also be a symbol. Its variable value will be used @@ -176,7 +186,7 @@ erc-button-alist CALLBACK. There can be several PAR arguments. If REGEXP is `nicknames', these are ignored, and CALLBACK will be called with the nickname matched as the argument." - :package-version '(ERC . "5.5") + :package-version '(ERC . "5.6") ; FIXME sync on release :type '(repeat (list :tag "Button" (choice :tag "Matches" @@ -275,22 +285,79 @@ erc-button-add-buttons (concat "\\<" (regexp-quote (car elem)) "\\>") entry))))))))))) +(defun erc-button--maybe-warn-arbitrary-sexp (form) + (if (and (symbolp form) (special-variable-p form)) + (symbol-value form) + (unless (get 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp) + (put 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp t) + (lwarn 'erc :warning + (concat "Arbitrary sexps for the third FORM" + " slot of `erc-button-alist' entries" + " have been deprecated."))) + (eval form t))) + +(defun erc-button--check-nicknames-entry () + ;; This helper exists because the module is defined after its options. + (when-let (((eq major-mode 'erc-mode)) + (entry (alist-get 'nicknames erc-button-alist))) + (unless (eq 'erc-button-buttonize-nicks (nth 1 entry)) + (erc-button--display-error-notice-with-keys-and-warn + "Values other than `erc-button-buttonize-nicks' in the third slot of " + "the `nicknames' entry of `erc-button-alist' are deprecated.")))) + +(defun erc-button--preserve-bounds (bounds _ _ _) + "Return BOUNDS.\n\n(fn BOUNDS NICKNAME SERVER-USER CHANNEL-USER)" + bounds) + +;; This variable is intended to serve as a "core" to be wrapped by +;; (built-in) modules during setup. It's unclear whether +;; `add-function's practice of removing existing advice before +;; re-adding it is desirable when integrating modules since we're +;; mostly concerned with ensuring one "piece" precedes or follows +;; another (specific piece), which may not yet (or ever) be present. + +(defvar erc-button--modify-nick-function #'erc-button--preserve-bounds + "Function to possibly modify aspects of nick being buttonized. +Called with four args: BOUNDS NICKNAME SERVER-USER CHANNEL-USER. +BOUNDS is a cons of (BEG . END) marking the position of the nick +in the current message, which occupies the whole of the narrowed +buffer. NICKNAME is a case-mapped string without text +properties. SERVER-USER and CHANNEL-USER are the nick's +`erc-server-users' entry and its associated (though possibly nil) +`erc-channel-user' object. The function should return BOUNDS or +a suitable replacement to indicate that buttonizing ought to +proceed, and nil if it should be inhibited.") + (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)) bounds word) - (when (or (eq t form) - (eval form t)) + (when (eq form 'erc-button-buttonize-nicks) + (setq form (and (symbol-value form) erc-button--modify-nick-function))) + (when (or (functionp form) + (eq t form) + (and form (erc-button--maybe-warn-arbitrary-sexp 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))) - (when (or (and (erc-server-buffer-p) (erc-get-server-user word)) - (and erc-channel-users (erc-get-channel-user word))) - (erc-button-add-button (car bounds) (cdr bounds) - fun t (list word)))))))) + (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))))) + (when (and user + (or (not (functionp form)) + (setq bounds + (funcall form bounds down user (cdr cuser))))) + (erc-button-add-button (car bounds) (cdr bounds) + fun t (list word))))))))) (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." @@ -302,7 +369,14 @@ erc-button-add-buttons-1 (fun (nth 3 entry)) (data (mapcar #'match-string-no-properties (nthcdr 4 entry)))) (when (or (eq t form) - (eval form t)) + (and (functionp form) + (let* ((erc-button-face erc-button-face) + (erc-button-mouse-face erc-button-mouse-face) + (erc-button-nickname-face erc-button-nickname-face) + (rv (funcall form start end))) + (when rv + (setq end (cdr rv) start (car rv))))) + (erc-button--maybe-warn-arbitrary-sexp form)) (erc-button-add-button start end fun nil data regexp))))) (defun erc-button-remove-old-buttons () @@ -511,6 +585,61 @@ erc-button-beats-to-time (message "@%s is %d:%02d local time" beats hours minutes))) +(defun erc-button--substitute-command-keys-in-region (beg end) + "Replace command in region with keys and return new bounds" + (let* ((o (buffer-substring beg end)) + (s (substitute-command-keys o))) + (unless (equal o s) + (setq erc-button-face nil)) + (delete-region beg end) + (insert s)) + (cons beg (point))) + +;;;###autoload +(defun erc-button--display-error-notice-with-keys (&optional parsed buffer + &rest strings) + "Add help keys to STRINGS for configuration-related admonishments. +Return inserted result. PARSED can be an `erc-response' object, +a string, or nil. BUFFER can be a buffer, a string, or nil. As +a special case, PARSED can also be a buffer as long as BUFFER is +a string or nil." + (when (stringp buffer) + (push buffer strings) + (setq buffer nil)) + (when (stringp parsed) + (push parsed strings) + (setq parsed nil)) + (when (bufferp parsed) + (cl-assert (null buffer)) + (setq buffer parsed + parsed nil)) + (let* ((string (apply #'concat strings)) + (erc-insert-post-hook + (cons (lambda () + (setq string (buffer-substring (point-min) + (1- (point-max))))) + erc-insert-post-hook)) + (erc-button-alist + `((,(rx "\\[" (group (+ (not "]"))) "]") 0 + erc-button--substitute-command-keys-in-region + erc-button-describe-symbol 1) + ,@erc-button-alist))) + (erc-display-message parsed '(notice error) (or buffer 'active) string) + string)) + +;;;###autoload +(defun erc-button--display-error-notice-with-keys-and-warn (&rest args) + "Like `erc-button--display-error-notice-with-keys' but also warn." + (let ((string (apply #'erc-button--display-error-notice-with-keys args))) + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (with-syntax-table lisp-mode-syntax-table + (skip-syntax-forward "^-")) + (forward-char) + (display-warning + 'erc (buffer-substring-no-properties (point) (point-max)))))) + (provide 'erc-button) ;;; erc-button.el ends here diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 4337d633cfa..dd481032e7e 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -67,6 +67,9 @@ erc-session-server (declare-function erc-server-process-alive "erc-backend" (&optional buffer)) (declare-function erc-set-active-buffer "erc" (buffer)) +(declare-function erc-button--display-error-notice-with-keys + (parsed &rest strings)) + ;; Variables (defgroup erc-networks nil @@ -1310,12 +1313,11 @@ erc-networks--ensure-announced Copy source (prefix) from MOTD-ish message as a last resort." ;; The 004 handler never ran; see 2004-03-10 Diane Murray in change log (unless erc-server-announced-name - (setq erc-server-announced-name (erc-response.sender parsed)) - (erc-display-error-notice - parsed (concat "Failed to determine server name. Using \"" - erc-server-announced-name "\" instead." - " If this was unexpected, consider reporting it via " - (substitute-command-keys "\\[erc-bug]") "."))) + (require 'erc-button) + (erc-button--display-error-notice-with-keys + parsed "Failed to determine server name. Using \"" + (setq erc-server-announced-name (erc-response.sender parsed)) "\" instead" + ". If this was unexpected, consider reporting it via \\[erc-bug]" ".")) nil) (defun erc-unset-network-name (_nick _ip _reason) @@ -1493,9 +1495,9 @@ erc-networks-on-MOTD-end (memq (erc--target-symbol erc--target) erc-networks--bouncer-targets))) proc) - (let ((m (concat "Unexpected state detected. Please report via " - (substitute-command-keys "\\[erc-bug]") "."))) - (erc-display-error-notice parsed m)))) + (require 'erc-button) + (erc-button--display-error-notice-with-keys + parsed "Unexpected state detected. Please report via \\[erc-bug]."))) ;; For now, retain compatibility with erc-server-NNN-functions. (or (erc-networks--ensure-announced proc parsed) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index d6c63934163..05f0de6b195 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -1370,4 +1370,60 @@ define-erc-module--local (put 'erc-mname-enable 'definition-name 'mname) (put 'erc-mname-disable 'definition-name 'mname)))))) + +;; XXX move erc-button tests to new file if more added. +(require 'erc-button) + +;; See also `erc-scenarios-networks-announced-missing' in +;; erc-scenarios-misc.el for a more realistic example. +(ert-deftest erc-button--display-error-notice-with-keys () + (with-current-buffer (get-buffer-create "*fake*") + (let ((mode erc-button-mode) + (inhibit-message noninteractive) + erc-modules + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) + (erc-mode) + (erc-tests--set-fake-server-process "sleep" "1") + (erc--initialize-markers (point) nil) + (erc-button-mode +1) + (should (equal (erc-button--display-error-notice-with-keys + "If \\[erc-bol] fails, " + "see \\[erc-bug] or `erc-mode-map'.") + "*** If C-a fails, see M-x erc-bug or `erc-mode-map'.")) + (goto-char (point-min)) + + (ert-info ("Keymap substitution succeeds") + (erc-button-next) + (should (looking-at "C-a")) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (erc-button-press-button) + (with-current-buffer "*Help*" + (goto-char (point-min)) + (should (search-forward "erc-bol" nil t))) + (erc-button-next) + (erc-button-previous) ; end of interval correct + (should (looking-at "a fails"))) + + (ert-info ("Extended command mapping succeeds") + (erc-button-next) + (should (looking-at "M-x erc-bug")) + (erc-button-press-button) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (with-current-buffer "*Help*" + (goto-char (point-min)) + (should (search-forward "erc-bug" nil t)))) + + (ert-info ("Symbol-description face preserved") ; mutated by d-e-n-w-k + (erc-button-next) + (should (equal (get-text-property (point) 'font-lock-face) + '(erc-button erc-error-face))) + (should (eq (get-text-property (point) 'mouse-face) 'highlight)) + (should (eq erc-button-face 'erc-button))) ; extent evaporates + + (when noninteractive + (unless mode + (erc-button-mode -1)) + (kill-buffer "*Help*") + (kill-buffer))))) + ;;; erc-tests.el ends here -- 2.39.2 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0003-5.6-Allow-erc-button-add-face-to-take-an-object.patch >From 28517cf23b5ed65f8a421dddcffec6a0aecd7fe5 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 13 Jan 2023 05:13:06 -0800 Subject: [PATCH 3/3] [5.6] Allow erc-button-add-face to take an object * lisp/erc/erc-button.el (erc-button--add-nickname-face-function): New internal var. (erc-button-add-button): Call `erc-button--add-nickname-face-function' when it's a function for applying `erc-button-nickname-face'. (erc-button-add-face): Add optional `object' param. (Bug#60933.) --- lisp/erc/erc-button.el | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index eca3df44892..c94a412eea8 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -391,6 +391,8 @@ erc-button-remove-old-buttons mouse-face nil keymap nil))) +(defvar erc-button--add-nickname-face-function nil) + (defun erc-button-add-button (from to fun nick-p &optional data regexp) "Create a button between FROM and TO with callback FUN and data DATA. NICK-P specifies if this is a nickname button. @@ -417,7 +419,10 @@ erc-button-add-button (move-marker pos (point)))))) (if nick-p (when erc-button-nickname-face - (erc-button-add-face from to erc-button-nickname-face)) + (if erc-button--add-nickname-face-function + (funcall erc-button--add-nickname-face-function + from to erc-button-nickname-face) + (erc-button-add-face from to erc-button-nickname-face))) (when erc-button-face (erc-button-add-face from to erc-button-face))) (add-text-properties @@ -429,16 +434,16 @@ erc-button-add-button (list 'rear-nonsticky t) (and data (list 'erc-data data))))) -(defun erc-button-add-face (from to face) +(defun erc-button-add-face (from to face &optional object) "Add FACE to the region between FROM and TO." ;; If we just use `add-text-property', then this will overwrite any ;; face text property already used for the button. It will not be ;; merged correctly. If we use overlays, then redisplay will be ;; very slow with lots of buttons. This is why we manually merge ;; face text properties. - (let ((old (erc-list (get-text-property from 'font-lock-face))) + (let ((old (erc-list (get-text-property from 'font-lock-face object))) (pos from) - (end (next-single-property-change from 'font-lock-face nil to)) + (end (next-single-property-change from 'font-lock-face object to)) new) ;; old is the face at pos, in list form. It is nil if there is no ;; face at pos. If nil, the new face is FACE. If not nil, the @@ -446,10 +451,10 @@ erc-button-add-face ;; where this face changes. (while (< pos to) (setq new (if old (cons face old) face)) - (put-text-property pos end 'font-lock-face new) + (put-text-property pos end 'font-lock-face new object) (setq pos end - old (erc-list (get-text-property pos 'font-lock-face)) - end (next-single-property-change pos 'font-lock-face nil to))))) + old (erc-list (get-text-property pos 'font-lock-face object)) + end (next-single-property-change pos 'font-lock-face object to))))) ;; widget-button-click calls with two args, we ignore the first. ;; Since Emacs runs this directly, rather than with -- 2.39.2 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Tue Apr 18 10:12:18 2023 Received: (at 60933) by debbugs.gnu.org; 18 Apr 2023 14:12:18 +0000 Received: from localhost ([127.0.0.1]:60121 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pom49-0005KV-Ee for submit@debbugs.gnu.org; Tue, 18 Apr 2023 10:12:18 -0400 Received: from mail-108-mta191.mxroute.com ([136.175.108.191]:36535) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pom45-0005KB-6P for 60933@debbugs.gnu.org; Tue, 18 Apr 2023 10:12:15 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta191.mxroute.com (ZoneMTA) with ESMTPSA id 18794b55279000becb.001 for <60933@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Tue, 18 Apr 2023 14:12:03 +0000 X-Zone-Loop: 655e08193d6507b0f61f005840386590766907e97c5f X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=zU95+CqWzizeiz+jJbzrWYO7nmigl2GzeCH/qKKW1l8=; b=ZbEJcBc1693IzldMGo2NegO3Z0 8Zs8YL+rlT5h3tFYWNsi55mVpYxwXk+lq9VxS7IsYebTAr2FGe5XfV6FYH2Qbquo9BNuvsJ+O6Dmf J4MU5PZ/knj04AcvQsWhWolkSxuGCipU6Q6EyVefuUBBLvTKs9J6loq7i28jsAiLaXuraG+STsOV6 o/hnyAV4ZMMnMv7SwhshPtzw3VuOInlBrZ9qp7YwaPKyuY3N1fiWq9uv+k5HZ63Fqvmjp0+6zOfVW s9YJmDl0Ju7Z1cmf5v7867a3rC9YruA4QdK579mKzDvLKxBkIG9mLjI38dUj7ZE1ml8An+dD4HVMK uaW1qCwg==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <87fsaekmv4.fsf@neverwas.me> (J. P.'s message of "Thu, 09 Mar 2023 06:42:07 -0800") References: <878rhzc3gk.fsf@neverwas.me> <87fsaekmv4.fsf@neverwas.me> Date: Tue, 18 Apr 2023 07:11:59 -0700 Message-ID: <877cu9qnyo.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain This bug's main contribution has been the introduction of a function variant for the FORM field of `erc-button-alist' entries. It was meant to offer a more natural way to influence buttonizing than the boolean sexps it was displacing while still retaining enough structure to encourage maintainability (for example, by highlighting which elements invite modification, like a button's faces and its bounds, and which are off limits, like certain required text properties). However, in the short term, I feel that locking this down too cleverly will only hamstring us as we inch closer to adopting various protocol extensions for ERC 5.7. As requirements shift beneath us, the current design will always end up seeming overly restrictive or permissive. As such, I think it's best to partially revert these changes and instead look to an existing interface we already know (but don't love), namely `erc-button-add-button'. From now on, I think we should just interpret any function occupying the FORM field as a replacement "buttonizer" to be called in its place. The same general thinking applies to the nicks-specific buttonizer as well, though it being intrinsically special and, for now, internal means we can take more liberties in inconveniencing its consumers (which are all built-in modules). Thus, I'm proposing we replace the slightly unwieldy set of positional params with a single passed-around struct, which members of the interface's "advice stack" can modify at will. See implementation for details. Note that these changes currently require those from "bug#62834: Make erc-button navigation more flexible". Thanks. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Improve-erc-button-modify-nick-function-interfac.patch >From 190a77266d27e490dad7bda84d6790827f9e3953 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sat, 15 Apr 2023 09:52:05 -0700 Subject: [PATCH 1/2] [5.6] Improve erc-button--modify-nick-function interface * lisp/erc/erc-button.el (erc-button--check-nicknames-entry): Remove unused let binding. (erc-button--nick): New struct. (erc-button--preserve-bounds): Rework to expect `erc-button--nick' object. (erc-button--modify-nick-function): Reexplain interface base on `erc-button--nick' object. (erc-button--add-phantom-speaker): Redo to expect `erc-button--nick' object. (erc-button-add-nickname-buttons): Rework slightly to use `erc-button--nick' when calling `erc-button--modify-nick-function'. (Bug#60933.) --- lisp/erc/erc-button.el | 91 +++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index e2447deecde..5d8fd03615b 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -300,16 +300,42 @@ erc-button--maybe-warn-arbitrary-sexp (defun erc-button--check-nicknames-entry () ;; This helper exists because the module is defined after its options. - (when-let (((eq major-mode 'erc-mode)) - (entry (alist-get 'nicknames erc-button-alist))) - (unless (eq 'erc-button-buttonize-nicks (nth 1 entry)) + (when (eq major-mode 'erc-mode) + (unless (eq (nth 1 (alist-get 'nicknames erc-button-alist)) + 'erc-button-buttonize-nicks) (erc-button--display-error-notice-with-keys-and-warn "Values other than `erc-button-buttonize-nicks' in the third slot of " "the `nicknames' entry of `erc-button-alist' are deprecated.")))) -(defun erc-button--preserve-bounds (bounds _ server-user _) - "Return BOUNDS.\n\n(fn BOUNDS NICKNAME SERVER-USER CHANNEL-USER)" - (and server-user bounds)) +(cl-defstruct erc-button--nick + ;; Indicates the nick's position in the current message. BEG is + ;; normally also point. + ( bounds nil :type cons + :documentation "A cons of (BEG . END).") + ;; NICK is the original, non-casemapped nickname and REST is a + ;; possibly empty list of opaque objects. If non-nil, the entire + ;; cons should be mutated rather than replaced because it's used as + ;; a key in hash tables and text-property searches. + ( data nil :type (or null cons) + :documentation "A unique cons of (NICK . REST).") + ( downcased nil :type (or null string) + :documentation "The case-mapped nickname sans text properties.") + ;; Not necessarily present in `erc-server-users'. + ( user nil :type (or null erc-server-user) + :documentation "A possibly nil or spoofed `erc-server-user'.") + ;; The CDR of a value from an `erc-channel-users' table. + ( cuser nil :type (or null erc-channel-user) + :documentation "A possibly nil `erc-channel-user'.") + ( erc-button-face erc-button-face :type symbol + :documentation "Temp `erc-button-face' while buttonizing.") + ( erc-button-nickname-face erc-button-nickname-face :type symbol + :documentation "Temp `erc-button-nickname-face' while buttonizing.") + ( erc-button-mouse-face erc-button-mouse-face :type symbol + :documentation "Temp `erc-button-mouse-face' while buttonizing.")) + +(defun erc-button--preserve-bounds (nick-object) + "Return NICK-OBJECT when its user slot is non-empty." + (and (erc-button--nick-user nick-object) nick-object)) ;; This variable is intended to serve as a "core" to be wrapped by ;; (built-in) modules during setup. It's unclear whether @@ -320,29 +346,27 @@ erc-button--preserve-bounds (defvar erc-button--modify-nick-function #'erc-button--preserve-bounds "Function to possibly modify aspects of nick being buttonized. -Called with four args: BOUNDS NICKNAME SERVER-USER CHANNEL-USER. -BOUNDS is a cons of (BEG . END) marking the position of the nick -in the current message, which occupies the whole of the narrowed -buffer. BEG is normally also point. NICKNAME is a case-mapped -string without text properties. SERVER-USER and CHANNEL-USER are -the nick's `erc-server-users' entry and its associated (though -possibly nil) `erc-channel-user' object. The function should -return BOUNDS or a suitable replacement to indicate that -buttonizing ought to proceed, and nil if it should be inhibited.") +Called with one argument, an `erc-button--nick' object, or nil. +The function should return the same (or similar) object when +buttonizing ought to proceed and nil otherwise. While running, +all faces defined in `erc-button' are bound temporarily and can +be updated at will.") (defvar-local erc-button--phantom-users nil) (defun erc-button--add-phantom-speaker (args) "Maybe substitute fake `server-user' for speaker at point." - (pcase args - (`(,bounds ,downcased-nick nil ,channel-user) - (list bounds downcased-nick - ;; Like `with-memoization' but don't cache when value is nil. - (or (gethash downcased-nick erc-button--phantom-users) - (and-let* ((user (erc-button--get-user-from-speaker-naive - (car bounds)))) - (puthash downcased-nick user erc-button--phantom-users))) - channel-user)) + (pcase (car args) + ((and obj (cl-struct erc-button--nick bounds downcased (user 'nil))) + ;; Like `with-memoization' but don't cache when value is nil. + (when-let ((user (or (gethash downcased erc-button--phantom-users) + (erc-button--get-user-from-speaker-naive + (car bounds))))) + (cl-assert (null (erc-button--nick-data obj))) + (puthash downcased user erc-button--phantom-users) + (setf (erc-button--nick-data obj) (list (erc-server-user-nickname user)) + (erc-button--nick-user obj) user)) + (list obj)) (_ args))) (define-minor-mode erc-button--phantom-users-mode @@ -402,12 +426,23 @@ erc-button-add-nickname-buttons (gethash down erc-channel-users))) (user (or (and cuser (car cuser)) (and erc-server-users - (gethash down erc-server-users))))) + (gethash down erc-server-users)))) + (data (list word))) (when (or (not (functionp form)) - (setq bounds - (funcall form bounds down user (cdr cuser)))) + (and-let* ((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-erc-button-mouse-face obj) + erc-button-nickname-face + (erc-button--nick-erc-button-nickname-face obj) + erc-button-face + (erc-button--nick-erc-button-face obj)))) (erc-button-add-button (car bounds) (cdr bounds) - fun t (list word))))))))) + fun t data)))))))) (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." -- 2.39.2 --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0002-5.6-Revise-FORM-as-function-interface-in-erc-button-.patch Content-Transfer-Encoding: quoted-printable >From 513ea7d6034d6bc0c44955dcda4c7724699b52b9 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sat, 15 Apr 2023 09:52:05 -0700 Subject: [PATCH 2/2] [5.6] Revise FORM-as-function interface in erc-button-alist * lisp/erc/erc-button.el (erc-button-alist): Remove redundant "" entry, which adds nothing beyond highlighting the surrounding bookends at the expense of doubling up on face properties for no reason. Revise the FORM-as-function interface by removing the dynamic binding of face options and weird bounds-as-a-cons parameter. Instead, just treat any such function, when present, as a replacement for `erc-button-add-button'. (erc-button--maybe-warn-arbitrary-sexp): Make more robust by having it handle all accepted FORM types other than booleans. (erc-button-add-buttons-1): Rework to only check FORM field once. (erc-button--substitute-command-keys-in-region, erc-button--display-error-with-buttons): Rename former as latter and change signature to conform to new `erc-button-add-buttons' interface. (erc-button--display-error-notice-with-keys): Call renamed helper. * test/lisp/erc/erc-button-tests.el (erc-button-alist--url, erc-button-tests--form, erc-button-tests--some-var, erc-button-tests--erc-button-alist--function-as-form, erc-button-alist--function-as-form, erc-button-tests--erc-button-alist--nil-form, erc-button-alist---nil-form): Add tests and helpers. (Bug#60933.) --- lisp/erc/erc-button.el | 91 +++++++++++++------------ test/lisp/erc/erc-button-tests.el | 106 ++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 46 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 5d8fd03615b..4829e8b7be2 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -128,7 +128,6 @@ erc-button-alist ;; things hard to maintain. '((nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0) (erc-button-url-regexp 0 t browse-url-button-open-url 0) - (" ]+\\) *>" 0 t browse-url-button-open-url 1) ;;; ("(\\(\\([^~\n \t@][^\n \t@]*\\)@\\([a-zA-Z0-9.:-]+\\)\\)" 1 t finger = 2 3) ;; emacs internal ("[`=E2=80=98]\\([a-zA-Z][-a-zA-Z_0-9!*<=3D>+]+\\)['=E2=80=99]" @@ -166,17 +165,14 @@ erc-button-alist BUTTON is the number of the regexp grouping actually matching the button. This is ignored if REGEXP is `nicknames'. =20 -FORM is a Lisp symbol for a special variable whose value must be - true for the button to be added. Alternatively, when REGEXP is - not `nicknames', FORM can be a function whose arguments are BEG - and END, the bounds of the button in the current buffer. It's - expected to return a cons of (possibly identical) bounds or - nil, to deny. For the extent of the call, all face options - defined for the button module are re-bound, shadowing - themselves, so the function is free to change their values. - When regexp is the special symbol `nicknames', FORM must be the - symbol `erc-button-buttonize-nicks'. Specifying anything else - is deprecated. +FORM is either a boolean or a special variable whose value must + be non-nil for the button to be added. When REGEXP is the + special symbol `nicknames', FORM must be the symbol + `erc-button-buttonize-nicks'. Anything else is deprecated. + For all other entries, FORM can also be a function to call in + place of `erc-button-add-button' with the exact same arguments. + When FORM is also a special variable, ERC disregards the + variable and calls the function. =20 CALLBACK is the function to call when the user push this button. CALLBACK can also be a symbol. Its variable value will be used @@ -288,15 +284,18 @@ erc-button-add-buttons entry))))))))))) =20 (defun erc-button--maybe-warn-arbitrary-sexp (form) - (if (and (symbolp form) (special-variable-p form)) - (symbol-value form) - (unless (get 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-= sexp) - (put 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp t) - (lwarn 'erc :warning - (concat "Arbitrary sexps for the third FORM" - " slot of `erc-button-alist' entries" - " have been deprecated."))) - (eval form t))) + (cl-assert (not (booleanp form))) ; covered by caller + ;; If a special-variable is also a function, favor the function. + (cond ((functionp form) form) + ((and (symbolp form) (special-variable-p form)) (symbol-value form= )) + (t (unless (get 'erc-button--maybe-warn-arbitrary-sexp + 'warned-arbitrary-sexp) + (put 'erc-button--maybe-warn-arbitrary-sexp + 'warned-arbitrary-sexp t) + (lwarn 'erc :warning (concat "Arbitrary sexps for the third F= ORM" + " slot of `erc-button-alist' ent= ries" + " have been deprecated."))) + (eval form t)))) =20 (defun erc-button--check-nicknames-entry () ;; This helper exists because the module is defined after its options. @@ -447,22 +446,22 @@ erc-button-add-nickname-buttons (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (let ((start (match-beginning (nth 1 entry))) - (end (match-end (nth 1 entry))) - (form (nth 2 entry)) - (fun (nth 3 entry)) - (data (mapcar #'match-string-no-properties (nthcdr 4 entry)))) - (when (or (eq t form) - (and (functionp form) - (let* ((erc-button-face erc-button-face) - (erc-button-mouse-face erc-button-mouse-face) - (erc-button-nickname-face erc-button-nickname-= face) - (rv (funcall form start end))) - (when rv - (setq end (cdr rv) start (car rv))))) - (erc-button--maybe-warn-arbitrary-sexp form)) - (erc-button-add-button start end fun nil data regexp))))) + (let (buttonizer) + (while + (and (re-search-forward regexp nil t) + (or buttonizer + (setq buttonizer + (and-let* + ((raw-form (nth 2 entry)) + (res (or (eq t raw-form) + (erc-button--maybe-warn-arbitrary-sexp + raw-form)))) + (if (functionp res) res #'erc-button-add-button))= ))) + (let ((start (match-beginning (nth 1 entry))) + (end (match-end (nth 1 entry))) + (fun (nth 3 entry)) + (data (mapcar #'match-string-no-properties (nthcdr 4 entry)))) + (funcall buttonizer start end fun nil data regexp))))) =20 (defun erc-button-remove-old-buttons () "Remove all existing buttons. @@ -717,15 +716,15 @@ erc-button-beats-to-time (message "@%s is %d:%02d local time" beats hours minutes))) =20 -(defun erc-button--substitute-command-keys-in-region (beg end) +(defun erc-button--display-error-with-buttons + (from to fun nick-p &optional data regexp) "Replace command in region with keys and return new bounds" - (let* ((o (buffer-substring beg end)) - (s (substitute-command-keys o))) - (unless (equal o s) - (setq erc-button-face nil)) - (delete-region beg end) - (insert s)) - (cons beg (point))) + (let* ((o (buffer-substring from to)) + (s (substitute-command-keys o)) + (erc-button-face (and (equal o s) erc-button-face))) + (delete-region from to) + (insert s) + (erc-button-add-button from (point) fun nick-p data regexp))) =20 ;;;###autoload (defun erc-button--display-error-notice-with-keys (&optional parsed buffer @@ -762,7 +761,7 @@ erc-button--display-error-notice-with-keys erc-insert-post-hook)) (erc-button-alist `((,(rx "\\[" (group (+ (not "]"))) "]") 0 - erc-button--substitute-command-keys-in-region + erc-button--display-error-with-buttons erc-button-describe-symbol 1) ,@erc-button-alist))) (erc-display-message parsed '(notice error) (or buffer 'active) string) diff --git a/test/lisp/erc/erc-button-tests.el b/test/lisp/erc/erc-button-t= ests.el index ced08d117bc..6a6f6934389 100644 --- a/test/lisp/erc/erc-button-tests.el +++ b/test/lisp/erc/erc-button-tests.el @@ -23,6 +23,112 @@ =20 (require 'erc-button) =20 +(ert-deftest erc-button-alist--url () + (setq erc-server-process + (start-process "sleep" (current-buffer) "sleep" "1")) + (set-process-query-on-exit-flag erc-server-process nil) + (with-current-buffer (erc--open-target "#chan") + (let ((verify + (lambda (p url) + (should (equal (get-text-property p 'erc-data) (list url))) + (should (equal (get-text-property p 'mouse-face) 'highlight)) + (should (eq (get-text-property p 'font-lock-face) 'erc-button= )) + (should (eq (get-text-property p 'erc-callback) + 'browse-url-button-open-url))))) + (goto-char (point-min)) + + ;; Most common (unbracketed) + (erc-display-message nil nil (current-buffer) + "Foo https://example.com bar.") + (search-forward "https") + (funcall verify (point) "https://example.com") + + ;; The still works despite being removed in ERC 5.6. + (erc-display-message nil nil (current-buffer) + "Foo bar.") + (search-forward "https") + (funcall verify (point) "https://gnu.org") + + ;; Bracketed + (erc-display-message nil nil (current-buffer) "Foo b= ar.") + (search-forward "ftp") + (funcall verify (point) "ftp://gnu.org")) + + (when noninteractive + (kill-buffer)))) + +(defvar erc-button-tests--form nil) +(defvar erc-button-tests--some-var nil) + +(defun erc-button-tests--form (&rest rest) + (push rest erc-button-tests--form) + (apply #'erc-button-add-button rest)) + +(defun erc-button-tests--erc-button-alist--function-as-form (func) + (setq erc-server-process + (start-process "sleep" (current-buffer) "sleep" "1")) + (set-process-query-on-exit-flag erc-server-process nil) + + (with-current-buffer (erc--open-target "#chan") + (let* ((erc-button-tests--form nil) + (entry (list (rx "+1") 0 func #'ignore 0)) + (erc-button-alist (cons entry erc-button-alist))) + + (erc-display-message nil 'notice (current-buffer) "Foo bar baz") + (erc-display-message nil nil (current-buffer) "+1") + (erc-display-message nil 'notice (current-buffer) "Spam") + (should (equal (pop erc-button-tests--form) + '(53 55 ignore nil ("+1") "\\+1"))) + (should-not erc-button-tests--form) + (goto-char (point-min)) + (search-forward "+") + (should (equal (get-text-property (point) 'erc-data) '("+1"))) + (should (equal (get-text-property (point) 'mouse-face) 'highlight)) + (should (eq (get-text-property (point) 'font-lock-face) 'erc-button)) + (should (eq (get-text-property (point) 'erc-callback) 'ignore))) + + (when noninteractive + (kill-buffer)))) + +(ert-deftest erc-button-alist--function-as-form () + (erc-button-tests--erc-button-alist--function-as-form + #'erc-button-tests--form) + + (erc-button-tests--erc-button-alist--function-as-form + (symbol-function #'erc-button-tests--form)) + + (erc-button-tests--erc-button-alist--function-as-form + (lambda (&rest r) (push r erc-button-tests--form) + (apply #'erc-button-add-button r)))) + +(defun erc-button-tests--erc-button-alist--nil-form (form) + (setq erc-server-process + (start-process "sleep" (current-buffer) "sleep" "1")) + (set-process-query-on-exit-flag erc-server-process nil) + + (with-current-buffer (erc--open-target "#chan") + (let* ((erc-button-tests--form nil) + (entry (list (rx "+1") 0 form #'ignore 0)) + (erc-button-alist (cons entry erc-button-alist))) + + (erc-display-message nil 'notice (current-buffer) "Foo bar baz") + (erc-display-message nil nil (current-buffer) "+1") + (erc-display-message nil 'notice (current-buffer) "Spam") + (should-not erc-button-tests--form) + (goto-char (point-min)) + (search-forward "+") + (should-not (get-text-property (point) 'erc-data)) + (should-not (get-text-property (point) 'mouse-face)) + (should-not (get-text-property (point) 'font-lock-face)) + (should-not (get-text-property (point) 'erc-callback))) + + (when noninteractive + (kill-buffer)))) + +(ert-deftest erc-button-alist--nil-form () + (erc-button-tests--erc-button-alist--nil-form nil) + (erc-button-tests--erc-button-alist--nil-form 'erc-button-tests--some-va= r)) + (defun erc-button-tests--insert-privmsg (speaker &rest msg-parts) (declare (indent 1)) (let ((msg (erc-format-privmessage speaker --=20 2.39.2 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sat Apr 29 11:56:28 2023 Received: (at 60933) by debbugs.gnu.org; 29 Apr 2023 15:56:28 +0000 Received: from localhost ([127.0.0.1]:36411 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1psmvx-000639-VC for submit@debbugs.gnu.org; Sat, 29 Apr 2023 11:56:27 -0400 Received: from mail-108-mta82.mxroute.com ([136.175.108.82]:41005) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1psmvt-00062r-PG for 60933@debbugs.gnu.org; Sat, 29 Apr 2023 11:56:24 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta82.mxroute.com (ZoneMTA) with ESMTPSA id 187cdba98d6000becb.001 for <60933@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Sat, 29 Apr 2023 15:56:10 +0000 X-Zone-Loop: 10adfeddce07621a524bdf5fe210838c3375ec61b15a X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=2fKBn83sAO/jdSJUGx9QeJwWZK0i3AWT5vw0AndF9Ys=; b=iXPOEYD3JNdjD4BLVpmubrvxxw e9Hnc43YOlyLQOugx1zSxPksh+BWIF2EDdPXVC2ZlSJiRIIKqRI7NxTBJI+jkAq3pmrZxfQeTd0dT fTmqBPHT3BtvDP3UvNUJm3Pqbt6qVHg2txEj/RCAxiuTpANnqDeMcHMidFSbgDmF3SfxnqeSlWORO cDd8yJu7CJUgjG+jjIFAOhHk8jTV0a65e6xbIvxPDSrhJYW7f3O0JJe3gmGGl+dTcp4qXf7zX9/63 qU+3RPxvp3CYey5ri2kAz/WKVLkxccKU+iuUJMT7Ktacd+u5z17H8McNjTcMFyLXI2OlJva0/Wj0P XK2UUnZw==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <877cu9qnyo.fsf@neverwas.me> (J. P.'s message of "Tue, 18 Apr 2023 07:11:59 -0700") References: <878rhzc3gk.fsf@neverwas.me> <87fsaekmv4.fsf@neverwas.me> <877cu9qnyo.fsf@neverwas.me> Date: Sat, 29 Apr 2023 08:56:06 -0700 Message-ID: <87v8he65t5.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain "J.P." writes: > The same general thinking applies to the nicks-specific buttonizer as > well, though it being intrinsically special and, for now, internal means > we can take more liberties in inconveniencing its consumers (which are > all built-in modules). Thus, I'm proposing we replace the slightly > unwieldy set of positional params with a single passed-around struct, > which members of the interface's "advice stack" can modify at will. See > implementation for details. Previously, consumers of the new nick-buttonizer interface were given a look at every single word in a message. But they should only really care about those with an associated `erc-server-user' object, meaning known nicks. And while it's true that some might want to create these associations on the fly, I think they're better off doing so earlier on, both to help separate concerns and to skip the hassle of determining whether a candidate is a speaker or a mention. To that end, I've carved out a couple more access points to influence how nick buttonizing happens. Both use the same pattern of "local advice around a function-interface variable," which I've come to regard as the most predictable and flexible for building new internal APIs. The first lives in `erc-server-PRIVMSG' and integrates with the old `erc-format-nick-function', which takes the user object it spits out. The second runs right before the nick buttonizer but only as a fallback when the usual means of finding an `erc-server-user' object from a candidate fails. It's set to `ignore' by default. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0000-v1-v2.diff >From 63440ff3f23ef6c3d67fea598c748723ee5f32ac Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sat, 29 Apr 2023 08:18:09 -0700 Subject: [PATCH 0/3] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (3): [5.6] Revise FORM-as-function interface in erc-button-alist [5.6] Improve erc-button--modify-nick-function interface [5.6] Use getter for finding users in erc-server-PRIVMSG lisp/erc/erc-backend.el | 4 +- lisp/erc/erc-button.el | 240 +++++++++++++++++------------- lisp/erc/erc.el | 39 ++++- test/lisp/erc/erc-button-tests.el | 106 +++++++++++++ 4 files changed, 280 insertions(+), 109 deletions(-) Interdiff: diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index 4a394a10d44..f52cc1aaeaf 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -102,6 +102,7 @@ (require 'erc-common) (defvar erc--target) +(defvar erc--user-from-nick-function) (defvar erc-auto-query) (defvar erc-channel-list) (defvar erc-channel-users) @@ -1881,7 +1882,8 @@ define-erc-response-handler ;; at this point. (erc-update-channel-member (if privp nick tgt) nick nick privp nil nil nil nil nil host login nil nil t) - (let ((cdata (erc-get-channel-user nick))) + (let ((cdata (funcall erc--user-from-nick-function + (erc-downcase nick) sndr parsed))) (setq fnick (funcall erc-format-nick-function (car cdata) (cdr cdata)))))) (cond diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 4829e8b7be2..638a2b20239 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -353,55 +353,56 @@ erc-button--modify-nick-function (defvar-local erc-button--phantom-users nil) -(defun erc-button--add-phantom-speaker (args) - "Maybe substitute fake `server-user' for speaker at point." - (pcase (car args) - ((and obj (cl-struct erc-button--nick bounds downcased (user 'nil))) - ;; Like `with-memoization' but don't cache when value is nil. - (when-let ((user (or (gethash downcased erc-button--phantom-users) - (erc-button--get-user-from-speaker-naive - (car bounds))))) - (cl-assert (null (erc-button--nick-data obj))) - (puthash downcased user erc-button--phantom-users) - (setf (erc-button--nick-data obj) (list (erc-server-user-nickname user)) - (erc-button--nick-user obj) user)) - (list obj)) - (_ args))) - +(defvar erc-button--fallback-user-function #'ignore + "Function to determine `erc-server-user' if not found in the usual places. +Called with DOWNCASED-NICK, NICK, and NICK-BOUNDS when +`erc-button-add-nickname-buttons' cannot find a user object for +DOWNCASED-NICK in `erc-channel-users' or `erc-server-users'.") + +(defun erc-button--add-phantom-speaker (downcased nuh _parsed) + "Stash fictitious `erc-server-user' while processing \"PRIVMSG\". +Expect DOWNCASED to be the downcased nickname, NUH to be a triple +of (NICK LOGIN HOST), and parsed to be an `erc-response' object." + (pcase-let* ((`(,nick ,login ,host) nuh) + (user (or (gethash downcased erc-button--phantom-users) + (make-erc-server-user + :nickname nick + :host (and (not (string-empty-p host)) host) + :login (and (not (string-empty-p login)) login))))) + (list (puthash downcased user erc-button--phantom-users)))) + +(defun erc-button--get-phantom-user (down _word _bounds) + (gethash down erc-button--phantom-users)) + +;; In the future, we'll most likely create temporary +;; `erc-channel-users' tables during BATCH chathistory playback, thus +;; obviating the need for this mode entirely. (define-minor-mode erc-button--phantom-users-mode "Minor mode to recognize unknown speakers. Expect to be used by module setup code for creating placeholder users on the fly during history playback. Treat an unknown -PRIVMSG speaker, like , as if they were present in a 353 and -are thus a member of the channel. However, don't bother creating -an actual `erc-channel-user' object because their status prefix -is unknown. Instead, just spoof an `erc-server-user' by applying -early (outer), args-filtering advice wrapping -`erc-button--modify-nick-function'." +\"PRIVMSG\" speaker, like \"\", as if they previously +appeared in a prior \"353\" message and are thus a known member +of the channel. However, don't bother creating an actual +`erc-channel-user' object because their status prefix is unknown. +Instead, just spoof an `erc-server-user' and stash it during +\"PRIVMSG\" handling via `erc--user-from-nick-function' and +retrieve it during buttonizing via +`erc-button--fallback-user-function'." :interactive nil (if erc-button--phantom-users-mode (progn - (add-function :filter-args (local 'erc-button--modify-nick-function) - #'erc-button--add-phantom-speaker '((depth . -90))) + (add-function :after-until (local 'erc--user-from-nick-function) + #'erc-button--add-phantom-speaker '((depth . -50))) + (add-function :after-until (local 'erc-button--fallback-user-function) + #'erc-button--get-phantom-user '((depth . 50))) (setq erc-button--phantom-users (make-hash-table :test #'equal))) - (remove-function (local 'erc-button--modify-nick-function) + (remove-function (local 'erc--user-from-nick-function) #'erc-button--add-phantom-speaker) + (remove-function (local 'erc-button--fallback-user-function) + #'erc-button--get-phantom-user) (kill-local-variable 'erc-nicks--phantom-users))) -;; FIXME replace this after making ERC account-aware. -(defun erc-button--get-user-from-speaker-naive (point) - "Return `erc-server-user' object for nick at POINT." - (when-let* - (((eql ?< (char-before point))) - ((eq (get-text-property point 'font-lock-face) 'erc-nick-default-face)) - (parsed (erc-get-parsed-vector point))) - (pcase-let* ((`(,nick ,login ,host) - (erc-parse-user (erc-response.sender parsed)))) - (make-erc-server-user - :nickname nick - :host (and (not (string-empty-p host)) host) - :login (and (not (string-empty-p login)) login))))) - (defun erc-button-add-nickname-buttons (entry) "Search through the buffer for nicknames, and add buttons." (let ((form (nth 2 entry)) @@ -425,10 +426,13 @@ erc-button-add-nickname-buttons (gethash down erc-channel-users))) (user (or (and cuser (car cuser)) (and erc-server-users - (gethash down 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* ((obj (funcall form (make-erc-button--nick + (and-let* ((user) + (obj (funcall form (make-erc-button--nick :bounds bounds :data data :downcased down :user user :cuser (cdr cuser))))) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 071bef649b3..56f36a758b8 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -4947,20 +4947,45 @@ erc-is-message-ctcp-and-not-action-p (and (erc-is-message-ctcp-p message) (not (string-match "^\C-aACTION.*\C-a$" message)))) +(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 +possibly nil `erc-channel-user' for formatting a server user's +nick. Called in the appropriate buffer with the downcased nick, +the parsed NUH, and the original `erc-response' object.") + +(defun erc--examine-nick (downcased _nuh _parsed) + (and erc-channel-users (gethash downcased erc-channel-users))) + +(defvar erc--format-speaker-functions nil + "Abnormal hook for formatting the speaker of a PRIVMSG or NOTICE. +Called in a temp buffer narrowed to the nick and its surrounding +adornments, typically angle brackets. Called with two args, BEG +and END, indicating the bounds of the nick portion, which will +already have a `font-lock-face' applied.") + (defun erc-format-privmessage (nick msg privp msgp) "Format a PRIVMSG in an insertable fashion." (let* ((mark-s (if msgp (if privp "*" "<") "-")) (mark-e (if msgp (if privp "*" ">") "-")) - (str (format "%s%s%s %s" mark-s nick mark-e msg)) (nick-face (if privp 'erc-nick-msg-face 'erc-nick-default-face)) (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 - (erc-put-text-property 0 (length mark-s) 'font-lock-face msg-face str) - (erc-put-text-property (length mark-s) (+ (length mark-s) (length nick)) - 'font-lock-face nick-face str) - (erc-put-text-property (+ (length mark-s) (length nick)) (length str) - 'font-lock-face msg-face str) - str)) + (with-temp-buffer + (insert mark-s) ; pretend `mark-s' and `mark-e' might be > length 1 + (let ((beg (point)) end rest) + (insert nick) + (setq end (point)) + (insert mark-e) + (setq rest (point)) + ;; Insert before hook so members can widen to see entire msg. + (insert " " msg) + (put-text-property 1 (point) 'font-lock-face msg-face) + (put-text-property beg end 'font-lock-face nick-face) + (save-restriction + (narrow-to-region 1 rest) + (run-hook-with-args 'erc--format-speaker-functions beg end)) + (buffer-string))))) (defcustom erc-format-nick-function 'erc-format-nick "Function to format a nickname for message display." -- 2.40.0 --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-5.6-Revise-FORM-as-function-interface-in-erc-button-.patch Content-Transfer-Encoding: quoted-printable >From 4542ad9bf3776ba92489acf226a70f314b0c1413 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sat, 15 Apr 2023 09:52:05 -0700 Subject: [PATCH 1/3] [5.6] Revise FORM-as-function interface in erc-button-alist * lisp/erc/erc-button.el (erc-button-alist): Remove redundant "" entry, which adds nothing beyond highlighting the surrounding bookends at the expense of doubling up on face properties for no reason. Revise the FORM-as-function interface by removing the dynamic binding of face options and weird bounds-as-a-cons parameter. Instead, just treat any such function, when present, as a replacement for `erc-button-add-button'. (erc-button--maybe-warn-arbitrary-sexp): Make more robust by having it handle all accepted FORM types other than booleans. (erc-button-add-buttons-1): Rework to only check FORM field once. (erc-button--substitute-command-keys-in-region, erc-button--display-error-with-buttons): Rename former as latter and change signature to conform to new `erc-button-add-buttons' interface. (erc-button--display-error-notice-with-keys): Call renamed helper. * test/lisp/erc/erc-button-tests.el (erc-button-alist--url, erc-button-tests--form, erc-button-tests--some-var, erc-button-tests--erc-button-alist--function-as-form, erc-button-alist--function-as-form, erc-button-tests--erc-button-alist--nil-form, erc-button-alist---nil-form): Add tests and helpers. (Bug#60933) --- lisp/erc/erc-button.el | 91 +++++++++++++------------ test/lisp/erc/erc-button-tests.el | 106 ++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 46 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index e2447deecde..7376c18ad4c 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -128,7 +128,6 @@ erc-button-alist ;; things hard to maintain. '((nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0) (erc-button-url-regexp 0 t browse-url-button-open-url 0) - (" ]+\\) *>" 0 t browse-url-button-open-url 1) ;;; ("(\\(\\([^~\n \t@][^\n \t@]*\\)@\\([a-zA-Z0-9.:-]+\\)\\)" 1 t finger = 2 3) ;; emacs internal ("[`=E2=80=98]\\([a-zA-Z][-a-zA-Z_0-9!*<=3D>+]+\\)['=E2=80=99]" @@ -166,17 +165,14 @@ erc-button-alist BUTTON is the number of the regexp grouping actually matching the button. This is ignored if REGEXP is `nicknames'. =20 -FORM is a Lisp symbol for a special variable whose value must be - true for the button to be added. Alternatively, when REGEXP is - not `nicknames', FORM can be a function whose arguments are BEG - and END, the bounds of the button in the current buffer. It's - expected to return a cons of (possibly identical) bounds or - nil, to deny. For the extent of the call, all face options - defined for the button module are re-bound, shadowing - themselves, so the function is free to change their values. - When regexp is the special symbol `nicknames', FORM must be the - symbol `erc-button-buttonize-nicks'. Specifying anything else - is deprecated. +FORM is either a boolean or a special variable whose value must + be non-nil for the button to be added. When REGEXP is the + special symbol `nicknames', FORM must be the symbol + `erc-button-buttonize-nicks'. Anything else is deprecated. + For all other entries, FORM can also be a function to call in + place of `erc-button-add-button' with the exact same arguments. + When FORM is also a special variable, ERC disregards the + variable and calls the function. =20 CALLBACK is the function to call when the user push this button. CALLBACK can also be a symbol. Its variable value will be used @@ -288,15 +284,18 @@ erc-button-add-buttons entry))))))))))) =20 (defun erc-button--maybe-warn-arbitrary-sexp (form) - (if (and (symbolp form) (special-variable-p form)) - (symbol-value form) - (unless (get 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-= sexp) - (put 'erc-button--maybe-warn-arbitrary-sexp 'warned-arbitrary-sexp t) - (lwarn 'erc :warning - (concat "Arbitrary sexps for the third FORM" - " slot of `erc-button-alist' entries" - " have been deprecated."))) - (eval form t))) + (cl-assert (not (booleanp form))) ; covered by caller + ;; If a special-variable is also a function, favor the function. + (cond ((functionp form) form) + ((and (symbolp form) (special-variable-p form)) (symbol-value form= )) + (t (unless (get 'erc-button--maybe-warn-arbitrary-sexp + 'warned-arbitrary-sexp) + (put 'erc-button--maybe-warn-arbitrary-sexp + 'warned-arbitrary-sexp t) + (lwarn 'erc :warning (concat "Arbitrary sexps for the third F= ORM" + " slot of `erc-button-alist' ent= ries" + " have been deprecated."))) + (eval form t)))) =20 (defun erc-button--check-nicknames-entry () ;; This helper exists because the module is defined after its options. @@ -412,22 +411,22 @@ erc-button-add-nickname-buttons (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (let ((start (match-beginning (nth 1 entry))) - (end (match-end (nth 1 entry))) - (form (nth 2 entry)) - (fun (nth 3 entry)) - (data (mapcar #'match-string-no-properties (nthcdr 4 entry)))) - (when (or (eq t form) - (and (functionp form) - (let* ((erc-button-face erc-button-face) - (erc-button-mouse-face erc-button-mouse-face) - (erc-button-nickname-face erc-button-nickname-= face) - (rv (funcall form start end))) - (when rv - (setq end (cdr rv) start (car rv))))) - (erc-button--maybe-warn-arbitrary-sexp form)) - (erc-button-add-button start end fun nil data regexp))))) + (let (buttonizer) + (while + (and (re-search-forward regexp nil t) + (or buttonizer + (setq buttonizer + (and-let* + ((raw-form (nth 2 entry)) + (res (or (eq t raw-form) + (erc-button--maybe-warn-arbitrary-sexp + raw-form)))) + (if (functionp res) res #'erc-button-add-button))= ))) + (let ((start (match-beginning (nth 1 entry))) + (end (match-end (nth 1 entry))) + (fun (nth 3 entry)) + (data (mapcar #'match-string-no-properties (nthcdr 4 entry)))) + (funcall buttonizer start end fun nil data regexp))))) =20 (defun erc-button-remove-old-buttons () "Remove all existing buttons. @@ -682,15 +681,15 @@ erc-button-beats-to-time (message "@%s is %d:%02d local time" beats hours minutes))) =20 -(defun erc-button--substitute-command-keys-in-region (beg end) +(defun erc-button--display-error-with-buttons + (from to fun nick-p &optional data regexp) "Replace command in region with keys and return new bounds" - (let* ((o (buffer-substring beg end)) - (s (substitute-command-keys o))) - (unless (equal o s) - (setq erc-button-face nil)) - (delete-region beg end) - (insert s)) - (cons beg (point))) + (let* ((o (buffer-substring from to)) + (s (substitute-command-keys o)) + (erc-button-face (and (equal o s) erc-button-face))) + (delete-region from to) + (insert s) + (erc-button-add-button from (point) fun nick-p data regexp))) =20 ;;;###autoload (defun erc-button--display-error-notice-with-keys (&optional parsed buffer @@ -727,7 +726,7 @@ erc-button--display-error-notice-with-keys erc-insert-post-hook)) (erc-button-alist `((,(rx "\\[" (group (+ (not "]"))) "]") 0 - erc-button--substitute-command-keys-in-region + erc-button--display-error-with-buttons erc-button-describe-symbol 1) ,@erc-button-alist))) (erc-display-message parsed '(notice error) (or buffer 'active) string) diff --git a/test/lisp/erc/erc-button-tests.el b/test/lisp/erc/erc-button-t= ests.el index ced08d117bc..6a6f6934389 100644 --- a/test/lisp/erc/erc-button-tests.el +++ b/test/lisp/erc/erc-button-tests.el @@ -23,6 +23,112 @@ =20 (require 'erc-button) =20 +(ert-deftest erc-button-alist--url () + (setq erc-server-process + (start-process "sleep" (current-buffer) "sleep" "1")) + (set-process-query-on-exit-flag erc-server-process nil) + (with-current-buffer (erc--open-target "#chan") + (let ((verify + (lambda (p url) + (should (equal (get-text-property p 'erc-data) (list url))) + (should (equal (get-text-property p 'mouse-face) 'highlight)) + (should (eq (get-text-property p 'font-lock-face) 'erc-button= )) + (should (eq (get-text-property p 'erc-callback) + 'browse-url-button-open-url))))) + (goto-char (point-min)) + + ;; Most common (unbracketed) + (erc-display-message nil nil (current-buffer) + "Foo https://example.com bar.") + (search-forward "https") + (funcall verify (point) "https://example.com") + + ;; The still works despite being removed in ERC 5.6. + (erc-display-message nil nil (current-buffer) + "Foo bar.") + (search-forward "https") + (funcall verify (point) "https://gnu.org") + + ;; Bracketed + (erc-display-message nil nil (current-buffer) "Foo b= ar.") + (search-forward "ftp") + (funcall verify (point) "ftp://gnu.org")) + + (when noninteractive + (kill-buffer)))) + +(defvar erc-button-tests--form nil) +(defvar erc-button-tests--some-var nil) + +(defun erc-button-tests--form (&rest rest) + (push rest erc-button-tests--form) + (apply #'erc-button-add-button rest)) + +(defun erc-button-tests--erc-button-alist--function-as-form (func) + (setq erc-server-process + (start-process "sleep" (current-buffer) "sleep" "1")) + (set-process-query-on-exit-flag erc-server-process nil) + + (with-current-buffer (erc--open-target "#chan") + (let* ((erc-button-tests--form nil) + (entry (list (rx "+1") 0 func #'ignore 0)) + (erc-button-alist (cons entry erc-button-alist))) + + (erc-display-message nil 'notice (current-buffer) "Foo bar baz") + (erc-display-message nil nil (current-buffer) "+1") + (erc-display-message nil 'notice (current-buffer) "Spam") + (should (equal (pop erc-button-tests--form) + '(53 55 ignore nil ("+1") "\\+1"))) + (should-not erc-button-tests--form) + (goto-char (point-min)) + (search-forward "+") + (should (equal (get-text-property (point) 'erc-data) '("+1"))) + (should (equal (get-text-property (point) 'mouse-face) 'highlight)) + (should (eq (get-text-property (point) 'font-lock-face) 'erc-button)) + (should (eq (get-text-property (point) 'erc-callback) 'ignore))) + + (when noninteractive + (kill-buffer)))) + +(ert-deftest erc-button-alist--function-as-form () + (erc-button-tests--erc-button-alist--function-as-form + #'erc-button-tests--form) + + (erc-button-tests--erc-button-alist--function-as-form + (symbol-function #'erc-button-tests--form)) + + (erc-button-tests--erc-button-alist--function-as-form + (lambda (&rest r) (push r erc-button-tests--form) + (apply #'erc-button-add-button r)))) + +(defun erc-button-tests--erc-button-alist--nil-form (form) + (setq erc-server-process + (start-process "sleep" (current-buffer) "sleep" "1")) + (set-process-query-on-exit-flag erc-server-process nil) + + (with-current-buffer (erc--open-target "#chan") + (let* ((erc-button-tests--form nil) + (entry (list (rx "+1") 0 form #'ignore 0)) + (erc-button-alist (cons entry erc-button-alist))) + + (erc-display-message nil 'notice (current-buffer) "Foo bar baz") + (erc-display-message nil nil (current-buffer) "+1") + (erc-display-message nil 'notice (current-buffer) "Spam") + (should-not erc-button-tests--form) + (goto-char (point-min)) + (search-forward "+") + (should-not (get-text-property (point) 'erc-data)) + (should-not (get-text-property (point) 'mouse-face)) + (should-not (get-text-property (point) 'font-lock-face)) + (should-not (get-text-property (point) 'erc-callback))) + + (when noninteractive + (kill-buffer)))) + +(ert-deftest erc-button-alist--nil-form () + (erc-button-tests--erc-button-alist--nil-form nil) + (erc-button-tests--erc-button-alist--nil-form 'erc-button-tests--some-va= r)) + (defun erc-button-tests--insert-privmsg (speaker &rest msg-parts) (declare (indent 1)) (let ((msg (erc-format-privmessage speaker --=20 2.40.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Improve-erc-button-modify-nick-function-interfac.patch >From 9a3f8710e5aabd0975ea242142600a51bdcc9be7 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sat, 15 Apr 2023 09:52:05 -0700 Subject: [PATCH 2/3] [5.6] Improve erc-button--modify-nick-function interface * lisp/erc/erc-button.el (erc-button--check-nicknames-entry): Remove unused let binding. (erc-button--nick): New struct. (erc-button--preserve-bounds): Rework to expect `erc-button--nick' object. (erc-button--modify-nick-function): Reexplain interface base on `erc-button--nick' object. (erc-button--add-phantom-speaker): Redo to expect `erc-button--nick' object. (erc-button-add-nickname-buttons): Rework slightly to use `erc-button--nick' when calling `erc-button--modify-nick-function'. (Bug#60933) --- lisp/erc/erc-button.el | 92 +++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 7376c18ad4c..b427b72ee5d 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -299,16 +299,42 @@ erc-button--maybe-warn-arbitrary-sexp (defun erc-button--check-nicknames-entry () ;; This helper exists because the module is defined after its options. - (when-let (((eq major-mode 'erc-mode)) - (entry (alist-get 'nicknames erc-button-alist))) - (unless (eq 'erc-button-buttonize-nicks (nth 1 entry)) + (when (eq major-mode 'erc-mode) + (unless (eq (nth 1 (alist-get 'nicknames erc-button-alist)) + 'erc-button-buttonize-nicks) (erc-button--display-error-notice-with-keys-and-warn "Values other than `erc-button-buttonize-nicks' in the third slot of " "the `nicknames' entry of `erc-button-alist' are deprecated.")))) -(defun erc-button--preserve-bounds (bounds _ server-user _) - "Return BOUNDS.\n\n(fn BOUNDS NICKNAME SERVER-USER CHANNEL-USER)" - (and server-user bounds)) +(cl-defstruct erc-button--nick + ;; Indicates the nick's position in the current message. BEG is + ;; normally also point. + ( bounds nil :type cons + :documentation "A cons of (BEG . END).") + ;; NICK is the original, non-casemapped nickname and REST is a + ;; possibly empty list of opaque objects. If non-nil, the entire + ;; cons should be mutated rather than replaced because it's used as + ;; a key in hash tables and text-property searches. + ( data nil :type (or null cons) + :documentation "A unique cons of (NICK . REST).") + ( downcased nil :type (or null string) + :documentation "The case-mapped nickname sans text properties.") + ;; Not necessarily present in `erc-server-users'. + ( user nil :type (or null erc-server-user) + :documentation "A possibly nil or spoofed `erc-server-user'.") + ;; The CDR of a value from an `erc-channel-users' table. + ( cuser nil :type (or null erc-channel-user) + :documentation "A possibly nil `erc-channel-user'.") + ( erc-button-face erc-button-face :type symbol + :documentation "Temp `erc-button-face' while buttonizing.") + ( erc-button-nickname-face erc-button-nickname-face :type symbol + :documentation "Temp `erc-button-nickname-face' while buttonizing.") + ( erc-button-mouse-face erc-button-mouse-face :type symbol + :documentation "Temp `erc-button-mouse-face' while buttonizing.")) + +(defun erc-button--preserve-bounds (nick-object) + "Return NICK-OBJECT when its user slot is non-empty." + (and (erc-button--nick-user nick-object) nick-object)) ;; This variable is intended to serve as a "core" to be wrapped by ;; (built-in) modules during setup. It's unclear whether @@ -319,29 +345,27 @@ erc-button--preserve-bounds (defvar erc-button--modify-nick-function #'erc-button--preserve-bounds "Function to possibly modify aspects of nick being buttonized. -Called with four args: BOUNDS NICKNAME SERVER-USER CHANNEL-USER. -BOUNDS is a cons of (BEG . END) marking the position of the nick -in the current message, which occupies the whole of the narrowed -buffer. BEG is normally also point. NICKNAME is a case-mapped -string without text properties. SERVER-USER and CHANNEL-USER are -the nick's `erc-server-users' entry and its associated (though -possibly nil) `erc-channel-user' object. The function should -return BOUNDS or a suitable replacement to indicate that -buttonizing ought to proceed, and nil if it should be inhibited.") +Called with one argument, an `erc-button--nick' object, or nil. +The function should return the same (or similar) object when +buttonizing ought to proceed and nil otherwise. While running, +all faces defined in `erc-button' are bound temporarily and can +be updated at will.") (defvar-local erc-button--phantom-users nil) (defun erc-button--add-phantom-speaker (args) "Maybe substitute fake `server-user' for speaker at point." - (pcase args - (`(,bounds ,downcased-nick nil ,channel-user) - (list bounds downcased-nick - ;; Like `with-memoization' but don't cache when value is nil. - (or (gethash downcased-nick erc-button--phantom-users) - (and-let* ((user (erc-button--get-user-from-speaker-naive - (car bounds)))) - (puthash downcased-nick user erc-button--phantom-users))) - channel-user)) + (pcase (car args) + ((and obj (cl-struct erc-button--nick bounds downcased (user 'nil))) + ;; Like `with-memoization' but don't cache when value is nil. + (when-let ((user (or (gethash downcased erc-button--phantom-users) + (erc-button--get-user-from-speaker-naive + (car bounds))))) + (cl-assert (null (erc-button--nick-data obj))) + (puthash downcased user erc-button--phantom-users) + (setf (erc-button--nick-data obj) (list (erc-server-user-nickname user)) + (erc-button--nick-user obj) user)) + (list obj)) (_ args))) (define-minor-mode erc-button--phantom-users-mode @@ -401,12 +425,24 @@ erc-button-add-nickname-buttons (gethash down erc-channel-users))) (user (or (and cuser (car cuser)) (and erc-server-users - (gethash down erc-server-users))))) + (gethash down erc-server-users)))) + (data (list word))) (when (or (not (functionp form)) - (setq bounds - (funcall form bounds down user (cdr cuser)))) + (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-erc-button-mouse-face obj) + erc-button-nickname-face + (erc-button--nick-erc-button-nickname-face obj) + erc-button-face + (erc-button--nick-erc-button-face obj)))) (erc-button-add-button (car bounds) (cdr bounds) - fun t (list word))))))))) + fun t data)))))))) (defun erc-button-add-buttons-1 (regexp entry) "Search through the buffer for matches to ENTRY and add buttons." -- 2.40.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0003-5.6-Use-getter-for-finding-users-in-erc-server-PRIVM.patch >From 63440ff3f23ef6c3d67fea598c748723ee5f32ac Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 28 Apr 2023 06:34:09 -0700 Subject: [PATCH 3/3] [5.6] Use getter for finding users in erc-server-PRIVMSG * lisp/erc/erc-backend.el (erc-server-PRIVMSG): Call new hook `erc--user-from-nick-function' for turning the sender's nick into a channel user, if any. * lisp/erc/erc-button.el (erc-button--add-phantom-speaker): Redo completely using simplified API. (erc-button--fallback-user-function): Add internal function-interface variable for finding an `erc-server-user' object when the usual places disappoint. (erc-button--get-phantom-user): Add new function, a getter for `erc-button--phantom-users'. (erc-button--phantom-users-mode): Replace advice subscription for `erc-button--modify-nick-function' with one for `erc-button--user-from-nick-function' and one for `erc-button--fallback-user-function'. (erc-button--get-user-from-speaker-naive): Remove unused function. (erc-button--add-nickname-buttons): Call `erc-button--fallback-user-function' when a user can't be found in `erc-server-users' or `erc-channel-users'. * lisp/erc/erc.el (erc--user-from-nick-function): New function-interface variable for determining an `erc-server-user' `erc-channel-user' pair from the sender's nick. (erc--examine-nick): Add new function to serve as default value for `erc--user-from-nick-function'. (erc--format-speaker-functions): Add new internal hook to adjust formatted speaker of a private message. (erc-format-privmessage): Run hook `erc--format-speaker-functions' in temporary buffer narrowed to speaker's formatted nick. This and the hook will likely be removed unless an immediate use case arises. In the long term, this may be useful for offering alternate styling for speaker names, e.g., other than "". (Bug#60933) --- lisp/erc/erc-backend.el | 4 +- lisp/erc/erc-button.el | 81 +++++++++++++++++++++-------------------- lisp/erc/erc.el | 39 ++++++++++++++++---- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index 4a394a10d44..f52cc1aaeaf 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -102,6 +102,7 @@ (require 'erc-common) (defvar erc--target) +(defvar erc--user-from-nick-function) (defvar erc-auto-query) (defvar erc-channel-list) (defvar erc-channel-users) @@ -1881,7 +1882,8 @@ define-erc-response-handler ;; at this point. (erc-update-channel-member (if privp nick tgt) nick nick privp nil nil nil nil nil host login nil nil t) - (let ((cdata (erc-get-channel-user nick))) + (let ((cdata (funcall erc--user-from-nick-function + (erc-downcase nick) sndr parsed))) (setq fnick (funcall erc-format-nick-function (car cdata) (cdr cdata)))))) (cond diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index b427b72ee5d..638a2b20239 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -353,55 +353,56 @@ erc-button--modify-nick-function (defvar-local erc-button--phantom-users nil) -(defun erc-button--add-phantom-speaker (args) - "Maybe substitute fake `server-user' for speaker at point." - (pcase (car args) - ((and obj (cl-struct erc-button--nick bounds downcased (user 'nil))) - ;; Like `with-memoization' but don't cache when value is nil. - (when-let ((user (or (gethash downcased erc-button--phantom-users) - (erc-button--get-user-from-speaker-naive - (car bounds))))) - (cl-assert (null (erc-button--nick-data obj))) - (puthash downcased user erc-button--phantom-users) - (setf (erc-button--nick-data obj) (list (erc-server-user-nickname user)) - (erc-button--nick-user obj) user)) - (list obj)) - (_ args))) - +(defvar erc-button--fallback-user-function #'ignore + "Function to determine `erc-server-user' if not found in the usual places. +Called with DOWNCASED-NICK, NICK, and NICK-BOUNDS when +`erc-button-add-nickname-buttons' cannot find a user object for +DOWNCASED-NICK in `erc-channel-users' or `erc-server-users'.") + +(defun erc-button--add-phantom-speaker (downcased nuh _parsed) + "Stash fictitious `erc-server-user' while processing \"PRIVMSG\". +Expect DOWNCASED to be the downcased nickname, NUH to be a triple +of (NICK LOGIN HOST), and parsed to be an `erc-response' object." + (pcase-let* ((`(,nick ,login ,host) nuh) + (user (or (gethash downcased erc-button--phantom-users) + (make-erc-server-user + :nickname nick + :host (and (not (string-empty-p host)) host) + :login (and (not (string-empty-p login)) login))))) + (list (puthash downcased user erc-button--phantom-users)))) + +(defun erc-button--get-phantom-user (down _word _bounds) + (gethash down erc-button--phantom-users)) + +;; In the future, we'll most likely create temporary +;; `erc-channel-users' tables during BATCH chathistory playback, thus +;; obviating the need for this mode entirely. (define-minor-mode erc-button--phantom-users-mode "Minor mode to recognize unknown speakers. Expect to be used by module setup code for creating placeholder users on the fly during history playback. Treat an unknown -PRIVMSG speaker, like , as if they were present in a 353 and -are thus a member of the channel. However, don't bother creating -an actual `erc-channel-user' object because their status prefix -is unknown. Instead, just spoof an `erc-server-user' by applying -early (outer), args-filtering advice wrapping -`erc-button--modify-nick-function'." +\"PRIVMSG\" speaker, like \"\", as if they previously +appeared in a prior \"353\" message and are thus a known member +of the channel. However, don't bother creating an actual +`erc-channel-user' object because their status prefix is unknown. +Instead, just spoof an `erc-server-user' and stash it during +\"PRIVMSG\" handling via `erc--user-from-nick-function' and +retrieve it during buttonizing via +`erc-button--fallback-user-function'." :interactive nil (if erc-button--phantom-users-mode (progn - (add-function :filter-args (local 'erc-button--modify-nick-function) - #'erc-button--add-phantom-speaker '((depth . -90))) + (add-function :after-until (local 'erc--user-from-nick-function) + #'erc-button--add-phantom-speaker '((depth . -50))) + (add-function :after-until (local 'erc-button--fallback-user-function) + #'erc-button--get-phantom-user '((depth . 50))) (setq erc-button--phantom-users (make-hash-table :test #'equal))) - (remove-function (local 'erc-button--modify-nick-function) + (remove-function (local 'erc--user-from-nick-function) #'erc-button--add-phantom-speaker) + (remove-function (local 'erc-button--fallback-user-function) + #'erc-button--get-phantom-user) (kill-local-variable 'erc-nicks--phantom-users))) -;; FIXME replace this after making ERC account-aware. -(defun erc-button--get-user-from-speaker-naive (point) - "Return `erc-server-user' object for nick at POINT." - (when-let* - (((eql ?< (char-before point))) - ((eq (get-text-property point 'font-lock-face) 'erc-nick-default-face)) - (parsed (erc-get-parsed-vector point))) - (pcase-let* ((`(,nick ,login ,host) - (erc-parse-user (erc-response.sender parsed)))) - (make-erc-server-user - :nickname nick - :host (and (not (string-empty-p host)) host) - :login (and (not (string-empty-p login)) login))))) - (defun erc-button-add-nickname-buttons (entry) "Search through the buffer for nicknames, and add buttons." (let ((form (nth 2 entry)) @@ -425,7 +426,9 @@ erc-button-add-nickname-buttons (gethash down erc-channel-users))) (user (or (and cuser (car cuser)) (and erc-server-users - (gethash down 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) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 071bef649b3..56f36a758b8 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -4947,20 +4947,45 @@ erc-is-message-ctcp-and-not-action-p (and (erc-is-message-ctcp-p message) (not (string-match "^\C-aACTION.*\C-a$" message)))) +(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 +possibly nil `erc-channel-user' for formatting a server user's +nick. Called in the appropriate buffer with the downcased nick, +the parsed NUH, and the original `erc-response' object.") + +(defun erc--examine-nick (downcased _nuh _parsed) + (and erc-channel-users (gethash downcased erc-channel-users))) + +(defvar erc--format-speaker-functions nil + "Abnormal hook for formatting the speaker of a PRIVMSG or NOTICE. +Called in a temp buffer narrowed to the nick and its surrounding +adornments, typically angle brackets. Called with two args, BEG +and END, indicating the bounds of the nick portion, which will +already have a `font-lock-face' applied.") + (defun erc-format-privmessage (nick msg privp msgp) "Format a PRIVMSG in an insertable fashion." (let* ((mark-s (if msgp (if privp "*" "<") "-")) (mark-e (if msgp (if privp "*" ">") "-")) - (str (format "%s%s%s %s" mark-s nick mark-e msg)) (nick-face (if privp 'erc-nick-msg-face 'erc-nick-default-face)) (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 - (erc-put-text-property 0 (length mark-s) 'font-lock-face msg-face str) - (erc-put-text-property (length mark-s) (+ (length mark-s) (length nick)) - 'font-lock-face nick-face str) - (erc-put-text-property (+ (length mark-s) (length nick)) (length str) - 'font-lock-face msg-face str) - str)) + (with-temp-buffer + (insert mark-s) ; pretend `mark-s' and `mark-e' might be > length 1 + (let ((beg (point)) end rest) + (insert nick) + (setq end (point)) + (insert mark-e) + (setq rest (point)) + ;; Insert before hook so members can widen to see entire msg. + (insert " " msg) + (put-text-property 1 (point) 'font-lock-face msg-face) + (put-text-property beg end 'font-lock-face nick-face) + (save-restriction + (narrow-to-region 1 rest) + (run-hook-with-args 'erc--format-speaker-functions beg end)) + (buffer-string))))) (defcustom erc-format-nick-function 'erc-format-nick "Function to format a nickname for message display." -- 2.40.0 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Fri May 05 20:50:15 2023 Received: (at control) by debbugs.gnu.org; 6 May 2023 00:50:15 +0000 Received: from localhost ([127.0.0.1]:57820 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pv67q-0006KT-Jf for submit@debbugs.gnu.org; Fri, 05 May 2023 20:50:14 -0400 Received: from mail-108-mta32.mxroute.com ([136.175.108.32]:41167) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pv67l-0006KA-JG for control@debbugs.gnu.org; Fri, 05 May 2023 20:50:13 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta32.mxroute.com (ZoneMTA) with ESMTPSA id 187ee897ce7000becb.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Sat, 06 May 2023 00:50:00 +0000 X-Zone-Loop: 9efff7777f88c95718e46395ed35693c33f3b5004fd3 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From:Sender: Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=zGCjplaXh8gKwxAtdQeIzYIIwzDk00RcUuYhoj90kdc=; b=Jp2hWVuLFRJGdnKUurBynKA7cr zBZp7sHdvAeSKDHq1ts3OkuEZx4WW5ULRpUx4eiif8vUpCUtGLUGp5+cBe3WEGy3xZIfQYUuS0yHH fWa3c6HvTdN8UqVt9V3RTNhX72Vh4WOUxSOfKLMU3iUY4oRE2gU6zxICiL/ZM04qU3VBW9JKHj+CW +rFko1IdqAq4veMUAe3a+9G2TRx9yxvEaGtEHv2lg6+WxC2SdDCsh6Vcyt9qlYO3f5Sz0IpyCkkPU ljdYpA1D8PPRdtLJLlM8fG4V40rXHm1bUZ3btH86S9iMB09VcKut6xyswGvpeHbI0tHHQ9VFffJeq 82ygN1ww==; From: "J.P." To: control@debbugs.gnu.org Subject: control message for bug #60933 Date: Fri, 05 May 2023 17:49:57 -0700 Message-ID: <87lei2wafe.fsf@neverwas.me> MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: control X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) close 60933 30.1 quit From debbugs-submit-bounces@debbugs.gnu.org Tue May 23 09:35:20 2023 Received: (at 60933) by debbugs.gnu.org; 23 May 2023 13:35:20 +0000 Received: from localhost ([127.0.0.1]:38676 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1q1SAZ-0001FZ-JQ for submit@debbugs.gnu.org; Tue, 23 May 2023 09:35:20 -0400 Received: from mail-108-mta155.mxroute.com ([136.175.108.155]:35699) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1q1SAX-0001FH-Gf for 60933@debbugs.gnu.org; Tue, 23 May 2023 09:35:18 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta155.mxroute.com (ZoneMTA) with ESMTPSA id 18848d2196100074ee.001 for <60933@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Tue, 23 May 2023 13:35:08 +0000 X-Zone-Loop: 2f3128ee4bcb1113778c8f0e2406885e6773eca1a845 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=atI0IrBOEhNgojSci+niocnBjkDOvWx/7dmVBCH31Es=; b=bVn1+r8RXjDvhyuj5zgkDB4IgC SNUui8ZRbt9eMlhCyhaNzk18n8UBXTpFuQbqnRxw+c8p4ZRZ8xr3sGvt9co626ZjXiDIdXX4OOQQL Nf2hbDy0pnUczETFgg+A4uCAChq9yFLbtzd0byDJxgDac7tJQ8qaK0CbFZ31IZCK/WTnE0Gs+9s4s PNVllqKtny3qKHzGRsnzDZitaNEUB/LwrKGgaIxiJrUQXVl75tcgfaixOyT36ve7HmmWVI9oxLpI+ wvUdU7WnfnNr1P+0Lw4H0+egwPuuFO2b3ckygEFAtC4ZTgqyHuDIjxhbwJSUpXgjFpZnPdX7QOJZF uUa1Gapg==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <878rhzc3gk.fsf@neverwas.me> (J. P.'s message of "Wed, 18 Jan 2023 06:38:51 -0800") References: <878rhzc3gk.fsf@neverwas.me> Date: Tue, 23 May 2023 06:35:05 -0700 Message-ID: <87y1lfrx12.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain The new DWIM `erc-tab' command defers to pcomplete inside the prompt area and runs a hook otherwise. But the hook's interface demands that implementing members handle a raw prefix arg for no compelling reason. And most of these members are movement-focused commands whose natural interactive spec tends to be numeric, making for some roundabout boilerplate awkwardness. This patch switches to numeric prefixes everywhere, which is how things likely should have been originally. --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-5.6-Simplify-erc-tab-interface.patch Content-Transfer-Encoding: quoted-printable >From 3ad4da1dc674226339af1437de8c0552316d5b89 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 22 May 2023 23:42:11 -0700 Subject: [PATCH] [5.6] Simplify erc-tab interface * etc/ERC-NEWS: Improve section mentioning `erc-tab'. * lisp/erc/erc-button.el (erc-button-alist): Restore a commented out version of the "" entry, which was dropped in 5adda2f4683 "Revise FORM-as-function interface in erc-button-alist" for being redundant and hence wasteful. This may help any unlikely objectors better adapt to the churn. (erc-button-next, erc-button-previous): Don't bother accommodating raw prefix arguments; expect numeric arguments only. * lisp/erc/erc.el (erc-tab): Change interactive spec to lowercase "p". (Bug#60933) --- etc/ERC-NEWS | 12 ++++++------ lisp/erc/erc-button.el | 7 +++---- lisp/erc/erc.el | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index d257bdcbf51..3d110ca43c6 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -215,12 +215,12 @@ changes are encouraged to voice their concerns on the= bug list. *** 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'. -In the area of buttons, 'Info-goto-node' has been supplanted by plain -old 'info' in 'erc-button-alist', and the bracketed "" -pattern entry has been removed because it was more or less redundant. -And the "TAB" key is now bound to a new command, 'erc-tab', that only -calls 'completion-at-point' when point is in the input area and -module-specific commands, like 'erc-button-next', otherwise. +In 'erc-button-alist', 'Info-goto-node' has been supplanted by plain +old 'info', and the "" entry has been removed because it was +more or less redundant. In all ERC buffers, the "" key is now +bound to a new command, 'erc-tab', that calls 'completion-at-point' +inside the input area and otherwise dispatches module-specific +commands, like 'erc-button-next'. =20 * Changes in ERC 5.5 diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 4307dc3b860..33b93ff6744 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -128,6 +128,7 @@ erc-button-alist ;; things hard to maintain. '((nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0) (erc-button-url-regexp 0 t browse-url-button-open-url 0) + ;; (" ]+\\) *>" 0 t browse-url-button-open-url 1) ;;; ("(\\(\\([^~\n \t@][^\n \t@]*\\)@\\([a-zA-Z0-9.:-]+\\)\\)" 1 t finger = 2 3) ;; emacs internal ("[`=E2=80=98]\\([a-zA-Z][-a-zA-Z_0-9!*<=3D>+]+\\)['=E2=80=99]" @@ -629,15 +630,13 @@ erc-button-next "Go to the ARGth next button." (declare (advertised-calling-convention (arg) "30.1")) (interactive "p") - (setq arg (pcase arg ((pred listp) (prefix-numeric-value arg)) (_ arg))) - (erc--button-next arg)) + (erc--button-next (or arg 1))) =20 (defun erc-button-previous (&optional arg) "Go to ARGth previous button." (declare (advertised-calling-convention (arg) "30.1")) (interactive "p") - (setq arg (pcase arg ((pred listp) (prefix-numeric-value arg)) (_ arg))) - (erc--button-next (- arg))) + (erc--button-next (- (or arg 1)))) =20 (defun erc-button-previous-of-nick (arg) "Go to ARGth previous button for nick at point." diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 0be9eb69432..b8ad37a55a0 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -4773,11 +4773,11 @@ erc--tab-functions "Functions to try when user hits \\`TAB' outside of input area. Called with a numeric prefix arg.") =20 -(defun erc-tab (&optional arg) +(defun erc-tab (arg) "Call `completion-at-point' when typing in the input area. -Otherwise call members of `erc--tab-functions' with raw prefix -ARG until one of them returns non-nil." - (interactive "P") +Otherwise call members of `erc--tab-functions' with a numeric +prefix ARG until one of them returns non-nil." + (interactive "p") (if (>=3D (point) erc-input-marker) (completion-at-point) (run-hook-with-args-until-success 'erc--tab-functions arg))) --=20 2.40.0 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Fri Jun 02 10:07:32 2023 Received: (at 60933) by debbugs.gnu.org; 2 Jun 2023 14:07:32 +0000 Received: from localhost ([127.0.0.1]:40549 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1q55RD-0002Y7-28 for submit@debbugs.gnu.org; Fri, 02 Jun 2023 10:07:32 -0400 Received: from mail-108-mta72.mxroute.com ([136.175.108.72]:42821) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1q55R6-0002Xm-SX for 60933@debbugs.gnu.org; Fri, 02 Jun 2023 10:07:29 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta72.mxroute.com (ZoneMTA) with ESMTPSA id 1887c6f23c400074ee.001 for <60933@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 02 Jun 2023 14:07:18 +0000 X-Zone-Loop: 1e699115acb25b0cd92d2166cd5e310021163bbc9198 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=gxCYUEphks3YgeF9Vs0aqupytggr/06WzKqW/nEr1ug=; b=D7jZCaT24KNu1YDRhOiHxv6Wvy 0s8TNFQDVv05vxAsyOQwDq+zevmnURDKsf3mcf/33Hd7s3h96l0L0UxZbw/0GGaPORm469Zy3/kCb Q3WFkpiefM/Bi5Zuy0AJ6a41mmU6rQbRwWhcqMvDbJjpXmdPmPzL4PnthFdZeZAZGGSOT3IS5IFWB +OCkoZhHHPbJ4iCjTuXlZ7nyAFv8AZHhvP8OnIgEXeWbl26/+5TAMdSFSp7nJLnwn2/L7YjVtzU59 xvtuB7JgyC4i4GgPP3mDYIANFR5yBqWUQz8MFiYG2pVon9CwoIT4VjTGLWqTydk5Yti79TMxEsu/2 9578zf/A==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <878rhzc3gk.fsf@neverwas.me> (J. P.'s message of "Wed, 18 Jan 2023 06:38:51 -0800") References: <878rhzc3gk.fsf@neverwas.me> Date: Fri, 02 Jun 2023 07:07:15 -0700 Message-ID: <87o7lylzzg.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain Some of the recent changes to this module were perhaps not handled as sensibly as they could've been. The broad strokes seem sane enough, but a few key aspects affecting the user experience were left too rough around the edges for my taste. My main gripe involves the partial deprecation of the `nicknames' entry for the option `erc-button-alist'. After some reflection, I'm now of the opinion that imposing a specific constraint on a specific slot in a specific default entry that may not even be present after customization is just too messy a notion to foist on users or to try and communicate in a doc string. A better approach, I now feel, is to remove the troublesome `nicknames' entry altogether and take special care behind the scenes to gracefully accommodate users who still have it present in their configs. The attached changes attempt something like this and also tidy up other odds and ends in erc-button.el. Note that the second patch also appears in bug#63569 and is only included here for its ERC-NEWS changes, which touch on some of the above. Thanks. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Deprecate-nicknames-entry-in-erc-button-alist.patch >From 627670ff54fbbd0b5278e538cea34c275ffc249d Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 1 Jun 2023 22:07:03 -0700 Subject: [PATCH 1/2] [5.6] Deprecate nicknames entry in erc-button-alist * lisp/erc/erc-button.el (erc-button-mode, erc-button-enable, erc-button-disable): Restore running `erc-button-setup' on `erc-mode-hook' and also immediately in all ERC buffers. Do this instead of calling `erc-button--check-nicknames-entry. (erc-button-buttonize-nicks): Mention that this must be non-nil for all but the most basic client functionality. (erc-button-alist): Remove `nicknames' entry. Describe deprecation, replacement behavior, and escape hatches in doc string. Update and improve custom type definition. (erc-button-keys-added): Deprecate because unused and misleading: keys are bound during module init. (erc-button--has-nickname-entry): New variable to indicate whether to follow legacy code path when a `nicknames' entry exists in `erc-button-alist'. (erc-button-setup): Rewrite to provide warnings about deprecated values for `erc-button-alist'. (erc-button-nickname-callback-function): Add escape hatch for those needing a custom callback for what was the default `nickname' entry in `erc-button-alist'. (erc-button-add-buttons): Always run `erc-button-add-nickname-buttons' unless `erc-button--has-nickname-entry' is non-nil. (erc-button--maybe-warn-arbitrary-sexp, erc-button--extract-form): Rename former to latter and abstain from emitting a warning. (erc-button--check-nicknames-entry): Remove unused function. (erc-button-add-nickname-buttons): Defer to `erc-button--extract-form' for determining value of third FORM slot of entry. (erc-button-add-buttons-1): Call renamed version of `erc-button--maybe-warn-arbitrary-sexp'. (Bug#60933) --- lisp/erc/erc-button.el | 134 ++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 56 deletions(-) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 33b93ff6744..22ef3dc4846 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -52,13 +52,15 @@ erc-button ;;;###autoload(autoload 'erc-button-mode "erc-button" nil t) (define-erc-module button nil "This mode buttonizes all messages according to `erc-button-alist'." - ((erc-button--check-nicknames-entry) - (add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) + ((add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 'append) (add-hook 'erc-send-modify-hook #'erc-button-add-buttons 'append) + (add-hook 'erc-mode-hook #'erc-button-setup) + (unless erc--updating-modules-p (erc-buffer-filter #'erc-button-setup)) (add-hook 'erc--tab-functions #'erc-button-next) (erc--modify-local-map t "" #'erc-button-previous)) ((remove-hook 'erc-insert-modify-hook #'erc-button-add-buttons) (remove-hook 'erc-send-modify-hook #'erc-button-add-buttons) + (remove-hook 'erc-mode-hook #'erc-button-setup) (remove-hook 'erc--tab-functions #'erc-button-next) (erc--modify-local-map nil "" #'erc-button-previous))) @@ -103,7 +105,10 @@ erc-button-wrap-long-urls :type '(choice integer boolean)) (defcustom erc-button-buttonize-nicks t - "Flag indicating whether nicks should be buttonized or not." + "Flag indicating whether nicks should be buttonized. +Note that beginning in ERC 5.6, some functionality provided by +other modules, such as `fill-wrap', may depend on this option +being non-nil." :type 'boolean) (defcustom erc-button-rfc-url "https://tools.ietf.org/html/rfc%s" @@ -126,8 +131,7 @@ erc-button-alist ;; a button, it makes no sense to optimize performance by ;; bytecompiling lambdas in this alist. On the other hand, it makes ;; things hard to maintain. - '((nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0) - (erc-button-url-regexp 0 t browse-url-button-open-url 0) + '((erc-button-url-regexp 0 t browse-url-button-open-url 0) ;; (" ]+\\) *>" 0 t browse-url-button-open-url 1) ;;; ("(\\(\\([^~\n \t@][^\n \t@]*\\)@\\([a-zA-Z0-9.:-]+\\)\\)" 1 t finger 2 3) ;; emacs internal @@ -159,41 +163,45 @@ erc-button-alist strings, or an alist with the strings in the car. Note that entries in lists or alists are considered to be nicks or other complete words. Therefore they are enclosed in \\< and \\> - while searching. REGEXP can also be the symbol - `nicknames', which matches the nickname of any user on the - current server. + while searching. Also, use of the special symbol `nicknames' + for this slot was deprecated in ERC 5.6, but users can still + use `erc-button-buttonize-nicks' to control whether nicks get + buttonized. And because customizing a corresponding CALLBACK + is no longer possible, an escape hatch has been provided via + the variable `erc-button-nickname-callback-function'. BUTTON is the number of the regexp grouping actually matching the - button. This is ignored if REGEXP is `nicknames'. + button. FORM is either a boolean or a special variable whose value must - be non-nil for the button to be added. When REGEXP is the - special symbol `nicknames', FORM must be the symbol - `erc-button-buttonize-nicks'. Anything else is deprecated. - For all other entries, FORM can also be a function to call in - place of `erc-button-add-button' with the exact same arguments. - When FORM is also a special variable, ERC disregards the - variable and calls the function. + be non-nil for the button to be added. It can also be a + function to call in place of `erc-button-add-button' with the + exact same arguments. When FORM is also a special variable, + ERC disregards the variable and calls the function. Note that + arbitrary s-expressions were deprecated in ERC 5.6 and may not + be respected in the future. If necessary, users can instead + supply a function that calls `erc-button-add-button' after + first checking some condition. CALLBACK is the function to call when the user push this button. CALLBACK can also be a symbol. Its variable value will be used as the callback function. PAR is a number of a regexp grouping whose text will be passed to - CALLBACK. There can be several PAR arguments. If REGEXP is - `nicknames', these are ignored, and CALLBACK will be called with - the nickname matched as the argument." + CALLBACK. There can be several PAR arguments." :package-version '(ERC . "5.6") ; FIXME sync on release :type '(repeat (list :tag "Button" (choice :tag "Matches" regexp (variable :tag "Variable containing regexp") - (const :tag "Nicknames" nicknames)) + (repeat :tag "List of words" string) + (alist :key-type string :value-type sexp)) (integer :tag "Number of the regexp section that matches") (choice :tag "When to buttonize" (const :tag "Always" t) - (sexp :tag "Only when this evaluates to non-nil")) + (function :tag "Alternative buttonizing function") + (variable :tag "Variable containing boolean")) (function :tag "Function to call when button is pressed") (repeat :tag "Sections of regexp to send to the function" :inline t @@ -239,15 +247,37 @@ erc-button-syntax-table (defvar erc-button-keys-added nil "Internal variable used to keep track of whether we've added the global-level ERC button keys yet.") +(make-obsolete-variable 'erc-button-keys-added "no longer relevant" "30.1") + +(defvar-local erc-button--has-nickname-entry nil + "Whether `erc-button-alist' contains a legacy `nicknames' entry.") -;; Maybe deprecate this function and `erc-button-keys-added' if they -;; continue to go unused for a another version (currently 5.6). (defun erc-button-setup () - "Add ERC mode-level button movement keys. This is only done once." - ;; Add keys. - (unless erc-button-keys-added - (define-key erc-mode-map (kbd "") #'erc-button-previous) - (setq erc-button-keys-added t))) + "Perform major-mode setup for ERC's button module. +Note that prior to ERC 5.6, this function used to modify +`erc-mode-map', but that's now handled by the mode toggles +themselves." + (setq erc-button-keys-added t) + (cl-assert (derived-mode-p 'erc-mode)) + (dolist (entry erc-button-alist) + (pcase entry + ((or `(nicknames ,_ ,sym . ,_) `('nicknames ,_ ,sym . ,_)) + (setq erc-button--has-nickname-entry t) + (unless (eq sym 'erc-button-buttonize-nicks) + (erc--warn-once-before-connect 'erc-button-mode + "The legacy `nicknames' entry in `erc-button-alist'" + " is deprecated. See doc string for details."))) + ((and `(,_ ,_ ,form . ,_) + (guard (not (or (and (symbolp form) + (special-variable-p form)) + (functionp form))))) + (erc--warn-once-before-connect 'erc-button-mode + "Arbitrary sexps for the third, FORM slot of `erc-button-alist'" + " entries are deprecated. Either use a variable or a function" + " that conditionally calls `erc-button-add-button'."))))) + +(defvar erc-button-nickname-callback-function #'erc-nick-popup + "Escape hatch for those needing a different nickname callback.") (defun erc-button-add-buttons () "Find external references in the current buffer and make buttons of them. @@ -261,6 +291,11 @@ erc-button-add-buttons (alist erc-button-alist) regexp) (erc-button-remove-old-buttons) + (unless (or erc-button--has-nickname-entry + (not erc-button-buttonize-nicks)) + (erc-button-add-nickname-buttons + `(_ _ erc-button--modify-nick-function + ,erc-button-nickname-callback-function))) (dolist (entry alist) (if (or (eq (car entry) 'nicknames) ;; Old form retained for backward compatibility. @@ -284,28 +319,18 @@ erc-button-add-buttons (concat "\\<" (regexp-quote (car elem)) "\\>") entry))))))))))) -(defun erc-button--maybe-warn-arbitrary-sexp (form) - (cl-assert (not (booleanp form))) ; covered by caller +(defun erc-button--extract-form (form) ;; If a special-variable is also a function, favor the function. - (cond ((functionp form) form) - ((and (symbolp form) (special-variable-p form)) (symbol-value form)) - (t (unless (get 'erc-button--maybe-warn-arbitrary-sexp - 'warned-arbitrary-sexp) - (put 'erc-button--maybe-warn-arbitrary-sexp - 'warned-arbitrary-sexp t) - (lwarn 'erc :warning (concat "Arbitrary sexps for the third FORM" - " slot of `erc-button-alist' entries" - " have been deprecated."))) - (eval form t)))) - -(defun erc-button--check-nicknames-entry () - ;; This helper exists because the module is defined after its options. - (when (eq major-mode 'erc-mode) - (unless (eq (nth 1 (alist-get 'nicknames erc-button-alist)) - 'erc-button-buttonize-nicks) - (erc-button--display-error-notice-with-keys-and-warn - "Values other than `erc-button-buttonize-nicks' in the third slot of " - "the `nicknames' entry of `erc-button-alist' are deprecated.")))) + (cond ((eq t form) t) + ((functionp form) form) + ((and (symbolp form) (special-variable-p form)) + (while (let ((val (symbol-value form))) + (prog1 (and (not (eq val form)) + (symbolp val) + (special-variable-p val)) + (setq form val)))) + form) + (t (eval form t)))) (cl-defstruct erc-button--nick ( bounds nil :type cons @@ -405,12 +430,10 @@ erc-button-add-nickname-buttons "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 (eq form 'erc-button-buttonize-nicks) - (setq form (and (symbol-value form) erc-button--modify-nick-function))) - (when (or (functionp form) - (eq t form) - (and form (erc-button--maybe-warn-arbitrary-sexp form))) + (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)) @@ -456,8 +479,7 @@ erc-button-add-buttons-1 (and-let* ((raw-form (nth 2 entry)) (res (or (eq t raw-form) - (erc-button--maybe-warn-arbitrary-sexp - raw-form)))) + (erc-button--extract-form raw-form)))) (if (functionp res) res #'erc-button-add-button))))) (let ((start (match-beginning (nth 1 entry))) (end (match-end (nth 1 entry))) -- 2.40.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Allow-ERC-modules-to-extend-erc-nick-popup-alist.patch >From 1c194ad4237d3544df9c6af7567d1764e1ba8411 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Dec 2022 19:01:40 -0800 Subject: [PATCH 2/2] [5.6] Allow ERC modules to extend erc-nick-popup-alist * etc/ERC-NEWS: Mention superficial changes to `erc-nick-popup-alist'. * lisp/erc/erc-button.el (erc-nick-popup-alist): Accept alternate shape for type with strings associated with functions instead of arbitrary sexps. (erc-button-cmd-KICK, erc-button-cmd-MSG): New functions to serve as wrappers for `erc-cmd-KICK' and `erc-cmd-MSG', respectively. The first also fixes a bug in which all but the first token of a given "reason" would be omitted from the ":trailing" portion of an outgoing KICK message. (erc-button--nick-popup-alist): New variable to help built-in modules expose special actions to `erc-nick-popup' without touching `erc-nick-popup-alist'. (erc-nick-popup): Present both `erc--nick-popup-alist' and `erc-nick-popup-alist' to the invoking user. Accommodate functions as well as arbitrary sexps. (bug#63569) --- etc/ERC-NEWS | 18 ++++++++---- lisp/erc/erc-button.el | 64 +++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index e9ec9e2caab..836c6ff8ee8 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -116,13 +116,19 @@ asking users who've customized this option to switch to that some other solution, like automatic migration, is justified, please make that known on the bug list. -** The 'nicknames' entry in 'erc-button-alist' is officially exceptional. +** 'erc-button-alist' and 'erc-nick-popup-alist' have evolved slightly. It's no secret that the 'buttons' module treats potential nicknames -specially. To simplify ERC's move to next-gen "rich UI" extensions, -this special treatment is being canonized. From now on, all values -other than the symbol 'erc-button-buttonize-nicks' appearing in the -"FORM" field (third element) of this entry are considered deprecated -and will incur a warning. +specially. This is perhaps most evident in its treatment of the +'nicknames' entry in 'erc-button-alist'. Indeed, to simplify ERC's +move to next-gen "rich UI" extensions, this special treatment is being +canonized. From here on out, this entry will no longer appear in the +option's default value but will instead be applied implicitly so long +as the option 'erc-button-buttonize-nicks' is non-nil, which it is by +default. Relatedly, the option 'erc-nick-popup-alist' now favors +functions, which ERC calls non-interactively, over arbitrary +s-expressions, which ERC will continue to honor. Although the default +lineup remains functionally equivalent, its members have all been +updated accordingly. ** Option 'erc-query-on-unjoined-chan-privmsg' restored and renamed. This option was accidentally removed from the default client in ERC diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 22ef3dc4846..374d3fd0201 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -682,20 +682,20 @@ erc-browse-emacswiki-lisp ;;; Nickname buttons: (defcustom erc-nick-popup-alist - '(("DeOp" . (erc-cmd-DEOP nick)) - ("Kick" . (erc-cmd-KICK (concat nick " " - (read-from-minibuffer - (concat "Kick " nick ", reason: "))))) - ("Msg" . (erc-cmd-MSG (concat nick " " - (read-from-minibuffer - (concat "Message to " nick ": "))))) - ("Op" . (erc-cmd-OP nick)) - ("Query" . (erc-cmd-QUERY nick)) - ("Whois" . (erc-cmd-WHOIS nick)) - ("Lastlog" . (erc-cmd-LASTLOG nick))) + '(("DeOp" . erc-cmd-DEOP) + ("Kick" . erc-button-cmd-KICK) + ("Msg" . erc-button-cmd-MSG) + ("Op" . erc-cmd-OP) + ("Query" . erc-cmd-QUERY) + ("Whois" . erc-cmd-WHOIS) + ("Lastlog" . erc-cmd-LASTLOG)) "An alist of possible actions to take on a nickname. -An entry looks like (\"Action\" . SEXP) where SEXP is evaluated with -the variable `nick' bound to the nick in question. +For all entries (ACTION . FUNC), ERC offers ACTION as a possible +completion item and calls the selected entry's FUNC with the +buttonized nickname at point as the only argument. For +historical reasons, FUNC can also be an arbitrary sexp, in which +case, ERC binds the nick in question to the variable `nick' and +evaluates the expression. Examples: (\"DebianDB\" . @@ -703,18 +703,48 @@ erc-nick-popup-alist (format \"ldapsearch -x -P 2 -h db.debian.org -b dc=debian,dc=org ircnick=%s\" nick)))" + :package-version '(ERC . "5.6") ; FIXME sync on release :type '(repeat (cons (string :tag "Op") - sexp))) + (choice function sexp)))) + +(defun erc-button-cmd-KICK (nick) + "Prompt for a reason, then kick NICK via `erc-cmd-KICK'. +In server buffers, also prompt for a channel." + (erc-cmd-KICK + (or (and erc--target (erc-default-target)) + (let ((targets (mapcar (lambda (b) + (cons (erc--target-string + (buffer-local-value 'erc--target b)) + b)) + (erc-channel-list erc-server-process)))) + (completing-read (format "Channel (%s): " (caar targets)) + targets (pcase-lambda (`(,_ . ,buf)) + (with-current-buffer buf + (erc-get-channel-user nick))) + t nil t (caar targets)))) + nick + (read-from-minibuffer "Reason: "))) + +(defun erc-button-cmd-MSG (nick) + "Prompt for a message to NICK, and send it via `erc-cmd-MSG'." + (let ((msg (read-from-minibuffer (concat "Message to " nick ": ")))) + (erc-cmd-MSG (concat nick " " msg)))) + +(defvar-local erc-button--nick-popup-alist nil + "Internally controlled items for `erc-nick-popup-alist'.") (defun erc-nick-popup (nick) (let* ((completion-ignore-case t) + (alist (append erc-nick-popup-alist erc-button--nick-popup-alist)) (action (completing-read (format-message "What action to take on `%s'? " nick) - erc-nick-popup-alist)) - (code (cdr (assoc action erc-nick-popup-alist)))) + alist)) + (code (cdr (assoc action alist)))) (when code (erc-set-active-buffer (current-buffer)) - (eval code `((nick . ,nick)))))) + (if (functionp code) + (funcall code nick) + (eval code `((nick . ,nick))))))) ;;; Callback functions (defun erc-button-describe-symbol (symbol-name) -- 2.40.1 --=-=-=-- From unknown Fri Jun 20 05:22:10 2025 Received: (at fakecontrol) by fakecontrolmessage; To: internal_control@debbugs.gnu.org From: Debbugs Internal Request Subject: Internal Control Message-Id: bug archived. Date: Sat, 01 Jul 2023 11:24:06 +0000 User-Agent: Fakemail v42.6.9 # This is a fake control message. # # The action: # bug archived. thanks # This fakemail brought to you by your local debbugs # administrator From debbugs-submit-bounces@debbugs.gnu.org Wed Sep 13 09:39:32 2023 Received: (at control) by debbugs.gnu.org; 13 Sep 2023 13:39:32 +0000 Received: from localhost ([127.0.0.1]:32967 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qgQ5c-0002fT-4V for submit@debbugs.gnu.org; Wed, 13 Sep 2023 09:39:32 -0400 Received: from mail-108-mta164.mxroute.com ([136.175.108.164]:35031) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qgQ5W-0002fF-1x for control@debbugs.gnu.org; Wed, 13 Sep 2023 09:39:30 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta164.mxroute.com (ZoneMTA) with ESMTPSA id 18a8ec486ee001d7b6.001 for (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Wed, 13 Sep 2023 13:39:18 +0000 X-Zone-Loop: 6f9d2fc942e48200d13f666fc4fadf426ebc270292b7 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From:Sender: Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=X5Q3fl1Xl9r28VEui1jNZVAHi4LSaF12PfigY38n/6I=; b=EcbXPFR9fpOoGGAaIBn0gOvQYR 22hrSBF0s3tijl1IMzzpBThO72X/Woh8RSCr7Aj7fBw51Qn7ECqNc3L67vadjRaVzo3A8BS2LNACy f2MowLpF9JENu/FpJ4Ju7+Nc3Ukhq6RtgaRpY/h0S3lPhLJD8Cyr1ztpRI5dRL6yLzBZPzY+Mrcoz vFoi3ZbkiN5oJ29JnjrrUC1Vcqkolk5VOIYsBejCOZC936xywbwnueXyRbrpzFtkcmSy3pR0W9Lw5 D5xNypiuBbGaIP+JusuLNvZM92qMICGFD85o25ktbwVXRcwzacRe8bxHvGgucCKFNpw1gEQMEi/AS 4WgjwVtw==; From: "J.P." To: control@debbugs.gnu.org Subject: control message for bug #60933 Date: Wed, 13 Sep 2023 06:39:15 -0700 Message-ID: <87a5tq2nwc.fsf@neverwas.me> MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: control X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) unarchive 60933 quit From debbugs-submit-bounces@debbugs.gnu.org Wed Sep 13 10:10:12 2023 Received: (at 60933) by debbugs.gnu.org; 13 Sep 2023 14:10:12 +0000 Received: from localhost ([127.0.0.1]:35499 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qgQZH-0000x8-BY for submit@debbugs.gnu.org; Wed, 13 Sep 2023 10:10:12 -0400 Received: from mail-108-mta21.mxroute.com ([136.175.108.21]:43405) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qgQZE-0000wx-Fu for 60933@debbugs.gnu.org; Wed, 13 Sep 2023 10:10:10 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta21.mxroute.com (ZoneMTA) with ESMTPSA id 18a8ee09657000d7b6.001 for <60933@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Wed, 13 Sep 2023 14:09:57 +0000 X-Zone-Loop: dcfe0074152d0cd4d2cb8d70e29839096eb605daed51 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=6SrOvsjYq3aS4kQMOZhwjyUJM5W44mVAFn1cvnRzPP0=; b=IPoXNo3HXIjtjMfiTVyKlYBuyd E4WcrD/41i5bcMc3GyZP7GFdhHlt35l82MwYsZpEBEqyUWZcMK647lvL23KHWq0P+rNzX/irVlXOh AK8IIY3MOSIV5oyikDQrGOelw2lnRMp0LF9MKcPTV1JWjCWgafCRuL3YPAnAKd+v/kfbtTs39mlKr tZ0oF1ELdG3E5M/GFNzdkPx7FBfcG2bwr2TF8LbXG7SstuehQtxqwFaLb1Bv9d64MiZ+5Lgyzljhh hsnBS2LzLacJwwzEC+2IYbistQb318znh4eGdLKzksoLrWQ9klv8V8gkVJSJxL3EDf5yhv5apMHGP 94fCp14Q==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <878rhzc3gk.fsf@neverwas.me> (J. P.'s message of "Wed, 18 Jan 2023 06:38:51 -0800") References: <878rhzc3gk.fsf@neverwas.me> Date: Wed, 13 Sep 2023 07:09:54 -0700 Message-ID: <87wmwuyxjh.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain This feature added an internal interface that allows for creating ephemeral "phantom" users, which help make nickname buttonizing possible with protocol bridges and chat-history playback. Unfortunately, the original design cut a major corner that it shouldn't have. Essentially, I wanted to avoid adding an `erc-channel-user' to accompany every phantom `erc-server-user' being spoofed, primarily because it's a waste of space. However, I've come to believe this shortcut won't be worth the added maintenance burden of having to check for missing objects when performing related operations. Attached is a patch to fix this. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Spoof-channel-users-in-erc-button-phantom-users-.patch >From 784cdaeee6d9b5ca7138b0cde0e251b475414201 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Wed, 6 Sep 2023 19:40:11 -0700 Subject: [PATCH] [5.6] Spoof channel users in erc-button--phantom-users-mode * lisp/erc/erc-backend.el (erc--cmem-from-nick-function): Update forward declaration. (erc-server-PRIVMSG): Use new name for `erc--user-from-nick-function', now `erc--cmem-from-nick-function'. * lisp/erc/erc-button.el (erc-button--phantom-users, erc-button--phantom-cmems): Rename former to latter. (erc-button--fallback-user-function, erc-button--fallback-cmem-function): Rename former to latter. (erc--phantom-channel-user, erc--phantom-server-user): New superficial `cl-struct' objects subclassing `erc-channel-user' and `erc-server-user', respectively. (erc-button--add-phantom-speaker): Look for channel member instead of server user, creating one if necessary. Return a made-up `erc-channel-user' along with the made-up `erc-server-user'. (erc-button--get-phantom-user, erc-button--get-phantom-cmem): Rename former to latter. (erc-button--phantom-users-mode, erc-button--phantom-users-enable, erc-button--phantom-users-disable): Use updated names for function-valued interface vars and their implementing functions. Remove obsolete comment. (erc-button-add-nickname-buttons): Attempt to query fallback function, if non-nil, while populating channel member instead of server user. * lisp/erc/erc.el (erc--user-from-nick-function, erc--cmem-from-nick-function): Rename former to latter. (erc--examine-nick, erc--get-existing-channel-member): Rename former to latter. (Bug#60933) --- lisp/erc/erc-backend.el | 4 +-- lisp/erc/erc-button.el | 66 +++++++++++++++++++++++------------------ lisp/erc/erc.el | 13 ++++---- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index 9e121ec1e92..fb10ee31c78 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -104,7 +104,7 @@ (defvar erc--called-as-input-p) (defvar erc--display-context) (defvar erc--target) -(defvar erc--user-from-nick-function) +(defvar erc--cmem-from-nick-function) (defvar erc-channel-list) (defvar erc-channel-users) (defvar erc-default-nicks) @@ -1944,7 +1944,7 @@ erc--server-determine-join-display-context ;; at this point. (erc-update-channel-member (if privp nick tgt) nick nick privp nil nil nil nil nil host login nil nil t) - (let ((cdata (funcall erc--user-from-nick-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)))))) diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 8c1188e64a2..620ee63fa39 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -380,32 +380,39 @@ erc-button--modify-nick-function all faces defined in `erc-button' are bound temporarily and can be updated at will.") -(defvar-local erc-button--phantom-users nil) +(defvar-local erc-button--phantom-cmems nil) -(defvar erc-button--fallback-user-function #'ignore - "Function to determine `erc-server-user' if not found in the usual places. +(defvar erc-button--fallback-cmem-function #'ignore + "Function to determine channel member if not found in the usual places. Called with DOWNCASED-NICK, NICK, and NICK-BOUNDS when `erc-button-add-nickname-buttons' cannot find a user object for DOWNCASED-NICK in `erc-channel-users' or `erc-server-users'.") +;; Historical or fictitious users. As long as these two structs +;; remain superficial subclasses with the exact same slots and +;; defaults, they can live here instead of in erc-common.el. Modules +;; can use the named getters for the superclasses when doing `setf' +;; modifications without having to `require' this library. +(cl-defstruct (erc--phantom-channel-user (:include erc-channel-user))) +(cl-defstruct (erc--phantom-server-user (:include erc-server-user))) + (defun erc-button--add-phantom-speaker (downcased nuh _parsed) - "Stash fictitious `erc-server-user' while processing \"PRIVMSG\". -Expect DOWNCASED to be the downcased nickname, NUH to be a triple -of (NICK LOGIN HOST), and parsed to be an `erc-response' object." (pcase-let* ((`(,nick ,login ,host) nuh) - (user (or (gethash downcased erc-button--phantom-users) - (make-erc-server-user + (cmem (gethash downcased erc-button--phantom-cmems)) + (user (or (car cmem) + (make-erc--phantom-server-user :nickname nick :host (and (not (string-empty-p host)) host) - :login (and (not (string-empty-p login)) login))))) - (list (puthash downcased user erc-button--phantom-users)))) + :login (and (not (string-empty-p login)) login)))) + (cuser (or (cdr cmem) + (make-erc--phantom-channel-user + :last-message-time (current-time))))) + (puthash downcased (cons user cuser) erc-button--phantom-cmems) + (cons user cuser))) -(defun erc-button--get-phantom-user (down _word _bounds) - (gethash down erc-button--phantom-users)) +(defun erc-button--get-phantom-cmem (down _word _bounds) + (gethash down erc-button--phantom-cmems)) -;; In the future, we'll most likely create temporary -;; `erc-channel-users' tables during BATCH chathistory playback, thus -;; obviating the need for this mode entirely. (define-minor-mode erc-button--phantom-users-mode "Minor mode to recognize unknown speakers. Expect to be used by module setup code for creating placeholder @@ -415,22 +422,22 @@ erc-button--phantom-users-mode of the channel. However, don't bother creating an actual `erc-channel-user' object because their status prefix is unknown. Instead, just spoof an `erc-server-user' and stash it during -\"PRIVMSG\" handling via `erc--user-from-nick-function' and +\"PRIVMSG\" handling via `erc--cmem-from-nick-function' and retrieve it during buttonizing via `erc-button--fallback-user-function'." :interactive nil (if erc-button--phantom-users-mode (progn - (add-function :after-until (local 'erc--user-from-nick-function) - #'erc-button--add-phantom-speaker '((depth . -50))) - (add-function :after-until (local 'erc-button--fallback-user-function) - #'erc-button--get-phantom-user '((depth . 50))) - (setq erc-button--phantom-users (make-hash-table :test #'equal))) - (remove-function (local 'erc--user-from-nick-function) + (add-function :after-until (local 'erc--cmem-from-nick-function) + #'erc-button--add-phantom-speaker '((depth . 30))) + (add-function :after-until (local 'erc-button--fallback-cmem-function) + #'erc-button--get-phantom-cmem '((depth . 50))) + (setq erc-button--phantom-cmems (make-hash-table :test #'equal))) + (remove-function (local 'erc--cmem-from-nick-function) #'erc-button--add-phantom-speaker) - (remove-function (local 'erc-button--fallback-user-function) - #'erc-button--get-phantom-user) - (kill-local-variable 'erc-nicks--phantom-users))) + (remove-function (local 'erc-button--fallback-cmem-function) + #'erc-button--get-phantom-cmem) + (kill-local-variable 'erc-button--phantom-cmems))) (defun erc-button-add-nickname-buttons (entry) "Search through the buffer for nicknames, and add buttons." @@ -451,11 +458,12 @@ erc-button-add-nickname-buttons (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))) + (cuser (and erc-channel-users + (or (gethash down erc-channel-users) + (funcall erc-button--fallback-cmem-function + down word bounds)))) (user (or (and cuser (car cuser)) - (and erc-server-users (gethash down erc-server-users)) - (funcall erc-button--fallback-user-function - down word bounds))) + (and erc-server-users (gethash down erc-server-users)))) (data (list word))) (when (or (not (functionp form)) (and-let* ((user) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 07ba32d1cca..ba0733f0ee5 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -5262,14 +5262,15 @@ erc--get-speaker-bounds (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. +(defvar erc--cmem-from-nick-function #'erc--get-existing-channel-member + "Function returning an `erc-channel-members' object from a nick. Must return either nil or a cons of an `erc-server-user' and a -possibly nil `erc-channel-user' for formatting a server user's -nick. Called in the appropriate buffer with the downcased nick, -the parsed NUH, and the original `erc-response' object.") +`erc-channel-user' object for formatting a user's nick for +insertion. Called in the appropriate target buffer with the +downcased nick, the parsed NUH, and the current `erc-response' +object.") -(defun erc--examine-nick (downcased _nuh _parsed) +(defun erc--get-existing-channel-member (downcased _nuh _parsed) (and erc-channel-users (gethash downcased erc-channel-users))) (defun erc-format-privmessage (nick msg privp msgp) -- 2.41.0 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Tue Sep 19 09:28:57 2023 Received: (at 60933) by debbugs.gnu.org; 19 Sep 2023 13:28:57 +0000 Received: from localhost ([127.0.0.1]:55628 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qiamf-0006VZ-1w for submit@debbugs.gnu.org; Tue, 19 Sep 2023 09:28:57 -0400 Received: from mail-108-mta131.mxroute.com ([136.175.108.131]:42595) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qiamb-0006VL-FF for 60933@debbugs.gnu.org; Tue, 19 Sep 2023 09:28:56 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta131.mxroute.com (ZoneMTA) with ESMTPSA id 18aada0fcca000d7b6.001 for <60933@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Tue, 19 Sep 2023 13:28:43 +0000 X-Zone-Loop: bbc60fc69401a1ea6284eadf0cac49a04985d90cecd7 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=UnJMx/r0PAAQFgtjS9WKeTWe7nElYG6OGQSp0RDb8HE=; b=bpXCFzfNzjCrUNo86Re2eixohx xtvVIAZXvE+n2DbXcOs0n7+ivTR4yyTKqjCLWYxf9Ff9Lm+ryZoqT4GST71YGaW3Nx/4IOvMKjGtF 7El6pdnNaXF+1BfSUptPvD0vPDbXH/k+LXQoHVgFceIhZo7n1FIKt0Um+nhTgOJvVUxTIZLYaISd2 GzPL/dVAN9y85yqgRsn0M9Q8svBpM5sIHYxG30IHU/ZaI7k35jRV0IyA2jdtnWWR1sNHd+20vpLW1 GmF/EHO+NgNnvIUxCbgNF96Ho0HBxlTSd9dPeU9AgdQ0BpbISktMoU/MburTiMmxrPhy7e0CWY4ol NZf3NshA==; From: "J.P." To: 60933@debbugs.gnu.org Subject: Re: bug#60933: 30.0.50; ERC >5.5: Make buttonizing more extensible In-Reply-To: <87wmwuyxjh.fsf@neverwas.me> (J. P.'s message of "Wed, 13 Sep 2023 07:09:54 -0700") References: <878rhzc3gk.fsf@neverwas.me> <87wmwuyxjh.fsf@neverwas.me> Date: Tue, 19 Sep 2023 06:28:40 -0700 Message-ID: <87y1h2e1h3.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 60933 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) "J.P." writes: > This feature added an internal interface that allows for creating > ephemeral "phantom" users, which help make nickname buttonizing possible > with protocol bridges and chat-history playback. Unfortunately, the > original design cut a major corner that it shouldn't have. Essentially, > I wanted to avoid adding an `erc-channel-user' to accompany every > phantom `erc-server-user' being spoofed, primarily because it's a waste > of space. However, I've come to believe this shortcut won't be worth the > added maintenance burden of having to check for missing objects when > performing related operations. Attached is a patch to fix this. A version of this was installed as a0ed463baba Spoof channel users in erc-button--phantom-users-mode From unknown Fri Jun 20 05:22:10 2025 Received: (at fakecontrol) by fakecontrolmessage; To: internal_control@debbugs.gnu.org From: Debbugs Internal Request Subject: Internal Control Message-Id: bug archived. Date: Wed, 18 Oct 2023 11:24:04 +0000 User-Agent: Fakemail v42.6.9 # This is a fake control message. # # The action: # bug archived. thanks # This fakemail brought to you by your local debbugs # administrator