Package: emacs;
Reported by: "J.P." <jp <at> neverwas.me>
Date: Mon, 14 Oct 2024 02:25:02 UTC
Severity: wishlist
Tags: patch
Found in version 31.0.50
View this message in rfc822 format
From: "J.P." <jp <at> neverwas.me> To: 73798 <at> debbugs.gnu.org Cc: emacs-erc <at> gnu.org Subject: bug#73798: 31.0.50; ERC 5.7: New extensibility focused match API Date: Thu, 31 Oct 2024 22:22:41 -0700
"J.P." <jp <at> neverwas.me> writes: > For a detailed example showing how to use this API for more involved > matching that doesn't involve highlighting, see the ‘notifications’ > module, which lives in ‘erc-desktop-notifications.el’. Ignore the parts > that involve adapting the global setup (and teardown) business to a > buffer-local context. Since your module is declared ‘local’, as per the > modern convention, you won't be needing such code, so feel free to use > utility functions like ‘erc-match-add-local-type’ directly in your > module's definition. Actually, I'm not so sure it's wise to hold up the `notifications' module as a shining example of how to use this API. That's because it currently suffers from some potentially confusing quirks, some of which I'd like to address using internal functions that aren't really meant for third parties. > Here, the user leverages a handy subtype of ‘erc-match’, called > ‘erc-match-opt-keyword’, which actually descends directly from another, > intermediate ‘erc-match’ type: > > -- Struct: erc-match-traditional category face data part > > Use this type or one of its descendants (see below) if you want > ‘erc-text-matched-hook’ to run alongside (after) the ‘handler’ > slot's default highlighter, ‘erc-match-highlight’, on every match > for which the ‘category’ slot's value is non-‘nil’ (it becomes the > argument provided for the hook's MATCH-TYPE parameter). > > Much more important, however, is ‘part’. This slot determines what > portion of the message is being highlighted or otherwise operated > on. It can be any symbol, but the ones with predefined methods are > ‘nick’, ‘message’, ‘all’, ‘keyword’, ‘nick-or-keyword’, and > ‘nick-or-mention’. This should probably also mention what the `data' slot does because it features in both examples. > And, finally, here's a more elaborate, module-like example demoing > highlighting based on the ‘erc-match-traditional’ type: > > ;; -*- lexical-binding: t; -*- > > (require 'erc-match) > (require 'erc-button) > > (defvar my-keywords > `((foonet ("#chan" ,(rx bow (or "foo" "bar" "baz") eow))))) > > (defface my-keyword '((t (:underline (:color "tomato" :style wave)))) > "My face.") > > (defun my-get-keyword () > (and-let* ((chans (alist-get (erc-network) my-keywords)) > ((cdr (assoc (erc-target) chans)))))) > > (cl-defstruct (my-match (:include erc-match-opt-keyword > (part 'keyword) There's no need to override `part' here because `keyword' is already the default. > (data (my-get-keyword)) > (face 'my-keyword)) > (:constructor my-match))) > > (setopt erc-match-types (add-to-list 'erc-match-types #'my-match)) > > (cl-defmethod erc-match-highlight-by-part ((instance my-match) > (_ (eql keyword))) > "Highlight keywords by merging instead of clobbering." > (dolist (pat (my-match-data instance)) > (goto-char (my-match-body-beg instance)) > (while (re-search-forward pat nil t) > (erc-button-add-face (match-beginning 0) (match-end 0) > (my-match-face instance))))) > One thing that's possibly unclear about this example is why the `my-match' definition overrides `face' and `data' only to use their accessors in the body of the method. IOW, readers may wonder why it doesn't just use these init forms directly inline. Moreover, while `data' holds an opaque object that users are technically invited to repurpose as needed, doing so only really makes sense if they're subclassing a traditional options-based type. Indeed, for novel purposes, it's much saner for users to just define their own slot and use it for the usual reasons, e.g., to share processed data in various stages of refinement. This example would do well to incorporate such usage. In any case, it's become clear to me that a practical demonstration of this API might be necessary to fully grasp its facets and trade offs. To that end, I offer the following full-featured demo module: https://emacs-erc.gitlab.io/bugs/archive/jabbycat.html To try it out, you can start Emacs like $ HOME=$(mktemp -d) ./src/emacs and then eval (require 'package) (push '("erc-bugs" . "https://emacs-erc.gitlab.io/bugs/archive/") package-archives) (package-install 'erc-73798) (sit-for 1) ;; see [1] below (package-install 'jabbycat) (setopt jabbycat-server "xmpp.myvps.example.org:5222" jabbycat-recipient "me <at> xmpp.myvps.example.org" jabbycat-username "jabbycat <at> xmpp.myvps.example.org" jabbycat-password "changeme" erc-jabbycat-match-patterns '((Libera.Chat ("#test" . "."))) erc-modules (add-to-list 'erc-modules 'jabbycat)) (erc-tls) If not already obvious, you either need to apply the latest patch set from this bug or (as shown above) run the bug-specific version of ERC with those changes pre-applied. The source code is currently hosted at https://gitlab.com/emacs-erc/jabbycat (though a possibly less objectionable mirror may be provided eventually). Thanks. [1] For some reason, without the pause, `url-insert-file-contents' gives signal(file-error ("https://fake.example.org/foo.tar" "No Data")) package--with-response-buffer-1("https://fake.example.org/" #f(compiled-function () ...>) :file "foo.tar" :async nil :error-function #<subr ...> :noerror nil) package-install-from-archive(#s(package-desc :name foo ... :signed nil)) package-download-transaction((#s(package-desc ... :signed nil))) package-install(foo)
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.