>From 7b09a1403c3f7d554d21340307ba4b13bd19b098 Mon Sep 17 00:00:00 2001 From: "J.P. Neverwas" Date: Wed, 10 Jan 2024 06:47:49 -0800 Subject: [PATCH 3/3] [POC] Use proposed msgfspec API ;; Note that these changes exist for demonstration purposes and are intended ;; to be viewed as a series. Please pretend that library symbols beginning ;; with "erc--" are actually "erc-", meaning *not* internal. This demos the proposed "msgfspec" API, which allows third parties to more easily influence the content and appearance of inserted chat messages ("speaker messages") without having to fiddle with the more involved templating framework shown in the last commit (and introduced as a feature by bug#67677). However, that framework is still useful for dictating the overall look of messages in a buffer and, as such, will probably be kept internal. Modules wishing to interoperate with a specific catalog's formatting features can leverage the "msgfspec" hook to apply smaller tweaks without having to override catalog entries entirely. Less boilerplate makes for easier reading and means less surface area to maintain. --- erc-crypt.el | 103 +++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 61 deletions(-) diff --git a/erc-crypt.el b/erc-crypt.el index 4ab0a18..7b6e3ce 100644 --- a/erc-crypt.el +++ b/erc-crypt.el @@ -187,24 +187,26 @@ Must be string.") (add-hook 'erc-pre-send-functions #'erc-crypt-maybe-send nil t) (add-hook 'erc-insert-pre-hook #'erc-crypt-pre-insert nil t) (add-hook 'erc-insert-done-hook #'erc-crypt-dh-save nil t) + ;; Add indicator and replace message when receiving. + (add-hook 'erc--msgfspec-speaker-hook + #'erc-crypt--on-msgfspec-speaker nil t) ;; Reset buffer locals (setq erc-crypt--insert-queue nil) ;; Don't bother splitting lines, since the sub protocol already does ;; that for transmission purposes - (setq-local erc-split-line-length 0 - ;; FIXME use public API when it becomes available. - erc--message-speaker-catalog 'crypt-speaker)) + (setq-local erc-split-line-length 0)) (erc-crypt-mode -1))) ;; Disabled ( (remove-hook 'erc-pre-send-functions #'erc-crypt-maybe-send t) (remove-hook 'erc-insert-pre-hook #'erc-crypt-pre-insert t) (remove-hook 'erc-insert-done-hook #'erc-crypt-dh-save t) + (remove-hook 'erc--msgfspec-speaker-hook + #'erc-crypt--on-msgfspec-speaker t) (mapc #'kill-local-variable '(erc-crypt-key-file erc-crypt--insert-queue erc-crypt--post-insert - erc-split-line-length - erc--message-speaker-catalog))) + erc-split-line-length))) 'local) (unless (assq 'erc-crypt-mode minor-mode-alist) @@ -514,74 +516,53 @@ Does not display message and does not trigger `erc-insert-modify-hook'." (push (cons :error nil) erc-crypt--insert-queue)))) erc-crypt--insert-queue) +;; Maybe optionize this or similar. +(defvar erc-crypt-indicator-style 'after-speaker) + (defface erc-crypt-success-face `((t :foreground ,erc-crypt-success-color)) "Encrypted indicator face on success.") (defface erc-crypt-failure-face `((t :foreground ,erc-crypt-failure-color)) "Encrypted indicator face on failure.") -(defvar erc-crypt--message-speaker-input-chan-privmsg-success - (concat (propertize "<" 'font-lock-face 'erc-default-face) - (propertize "%p" 'font-lock-face 'erc-my-nick-prefix-face) - (propertize "%n" 'font-lock-face 'erc-my-nick-face) - (propertize "%i" 'font-lock-face 'erc-crypt-success-face) - (propertize "> " 'font-lock-face 'erc-default-face) - (propertize "%m" 'font-lock-face 'erc-input-face)) - "Message template for encrypted chat input from own nick.") - -(defvar erc-crypt--message-speaker-chan-privmsg-success - (concat (propertize "<" 'font-lock-face 'erc-default-face) - (propertize "%p" 'font-lock-face 'erc-nick-prefix-face) - (propertize "%n" 'font-lock-face 'erc-nick-default-face) - (propertize "%i" 'font-lock-face 'erc-crypt-success-face) - (propertize "> %m" 'font-lock-face 'erc-default-face)) - "Message template for a successfully encrypted PRIVMSG in a channel.") - -(defvar erc-crypt--message-speaker-chan-privmsg-failure - (concat (propertize "<" 'font-lock-face 'erc-default-face) - (propertize "%p" 'font-lock-face 'erc-nick-prefix-face) - (propertize "%n" 'font-lock-face 'erc-nick-default-face) - (propertize "%i" 'font-lock-face 'erc-crypt-failure-face) - (propertize "> %m" 'font-lock-face 'erc-default-face)) - "Message template for a failed encrypted PRIVMSG in a channel.") - -;; FIXME use public name instead of `-speaker' when made available upstream. -(erc-define-message-format-catalog crypt-speaker - :parent erc--message-speaker-catalog - (input-chan-privmsg . #'erc-crypt--format-speaker-input-chan-privmsg) - (input-query-privmsg . #'erc-crypt--format-speaker-input-chan-privmsg) - (query-privmsg . #'erc-crypt--format-speaker-chan-privmsg) - (chan-privmsg . #'erc-crypt--format-speaker-chan-privmsg)) +(defun erc-crypt--add-indicator-to-msgfmt (spec-obj &optional errorp) + "Modify `fmt' slot of `erc--msgfspec' SPEC-OBJ. +With ERRORP, use `erc-crypt-failure-face'." + (let ((rep (if errorp + #("%i" 0 2 (font-lock-face erc-crypt-failure-face)) + #("%i" 0 2 (font-lock-face erc-crypt-success-face))))) + (if (eq erc-crypt-indicator-style 'after-speaker) + (erc--msgfspec-insert-spec-after spec-obj ?n ?i rep) + (erc--msgfspec-insert-spec-before spec-obj ?n ?i rep)))) ;; An unfortunate aspect of ERC's current API is the lack of symmetry between ;; incoming and outgoing insertion hooks. For example, `erc-crypt-maybe-send' ;; actually runs *before* this function via the abnormal hook ;; `erc-pre-send-functions', whereas `erc-crypt-pre-insert' runs *after ;; `erc-crypt--format-speaker-chan-privmsg'. -(defun erc-crypt--format-speaker-input-chan-privmsg (&rest plist) - "Return prompt-input string for insertion from `format-spec' PLIST." - (format-spec erc-crypt--message-speaker-input-chan-privmsg-success - (cons `(?i . ,erc-crypt-indicator) - (apply #'format-spec-make plist)))) - -(defun erc-crypt--format-speaker-chan-privmsg (&rest plist) - "Return formatted string for insertion from `format-spec' PLIST." - (let* ((string (plist-get plist ?m)) - (queue (erc-crypt--handle-received-msg string)) - (errorp (eq (eq 1 (caar queue)) :error)) - (msg (and (erc-crypt--crypted-message-p string) - (not (eq 1 (cdar queue))) - (erc-crypt--merge-fragments queue))) - (specs (apply #'format-spec-make plist))) - (when msg - (setf (alist-get ?m specs) (decode-coding-string msg 'utf-8) - specs (cons `(?i . ,erc-crypt-indicator) specs))) - ;; FIXME use public API to access default speaker format spec when - ;; exposed by upstream. - (format-spec (cond ((null msg) erc--message-speaker-chan-privmsg) - (errorp erc-crypt--message-speaker-chan-privmsg-failure) - (t erc-crypt--message-speaker-chan-privmsg-success)) - specs 'ignore-missing))) +(defun erc-crypt--on-msgfspec-speaker (spec-obj) + "Update `erc--msgfspec' SPEC-OBJ for normal chat messages." + (and + (pcase spec-obj + ;; This is an outgoing encrypted message. + ((cl-struct erc--msgfspec-speaker + (key (or 'input-chan-privmsg 'input-query-privmsg))) + (erc-crypt--add-indicator-to-msgfmt spec-obj)) + ;; This is an incoming encrypted message. + ((cl-struct erc--msgfspec-speaker + (key (or 'chan-privmsg 'query-privmsg)) + (\?m string)) + ;; Modify spec when STRING is an error or a terminal message. + (let* ((queue (erc-crypt--handle-received-msg string)) + (errorp (eq (eq 1 (caar queue)) :error)) + (msg (and (erc-crypt--crypted-message-p string) + (not (eq 1 (cdar queue))) + (erc-crypt--merge-fragments queue)))) + (when msg + (setf (erc--msgfspec-speaker-?m spec-obj) + (decode-coding-string msg 'utf-8)) + (erc-crypt--add-indicator-to-msgfmt spec-obj errorp))))) + (push `(?i . ,erc-crypt-indicator) (erc--msgfspec-alist spec-obj)))) (defun erc-crypt--merge-fragments (queue) "Return reconstituted and decrypted message from fragments in QUEUE." -- 2.42.0