From b9dfeb4e8f2c19a6218aaec12aafccc99a964676 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 12 Jul 2021 03:44:28 -0700 Subject: [PATCH 2/4] Support local ERC modules in erc-mode buffers * lisp/erc/erc.el (erc-migrate-modules): add some missing mappings. (erc--module-name-migrations, erc--features-to-modules, erc--modules-to-features): add alists to support simplified module-name migrations. (erc-update-modules): Change return value to a list of minor-mode commands for local modules that need deferred activation, if any. Use `custom-variable-p' to detect flavor. Currently, all modules are global, meaning so are their accompanying minor modes. (erc-open): Defer enabling of local modules via `erc-update-modules' until after buffer is initialized with other local vars. Also defer major mode hooks so they can detect things like whether the buffer is a server or target buffer. * lisp/erc/erc-common.el (define-erc-modules): Don't enable local modules (minor modes) unless `erc-mode' is the major mode. And don't disable them unless the minor mode is actually active. Also, don't mutate `erc-modules' when dealing with a local module. It's believed that the original authors wanted this functionality. (erc--normalize-module-symbol): Add helper for `erc-migrate-modules'. --- lisp/erc/erc.el | 83 +++++++++++++++++++++++++------------- test/lisp/erc/erc-tests.el | 47 +++++++++++++++++++++ 2 files changed, 101 insertions(+), 29 deletions(-) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index db39e341b2..2601ebfc70 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2030,14 +2030,36 @@ erc-default-nicks (defvar-local erc-nick-change-attempt-count 0 "Used to keep track of how many times an attempt at changing nick is made.") +(defconst erc--features-to-modules + '((erc-pcomplete completion pcomplete) + (erc-capab capab-identify) + (erc-join autojoin) + (erc-page page ctcp-page) + (erc-sound sound ctcp-sound) + (erc-stamp stamp timestamp) + (erc-services services nickserv)) + "Migration alist mapping a library feature to module names. +Keys need not be unique: a library may define more than one +module.") + +(defconst erc--modules-to-features + (cl-loop for (feature . names) in erc--features-to-modules + append (mapcar (lambda (name) (cons name feature)) names)) + "Migration alist mapping a module's name to library feature.") + +(defconst erc--module-name-migrations + (let (pairs) + (pcase-dolist (`(,_ ,canonical . ,rest) erc--features-to-modules) + (dolist (obsolete rest) + (push (cons obsolete canonical) pairs))) + pairs) + "Association list of obsolete module names to canonical names.") + (defun erc-migrate-modules (mods) "Migrate old names of ERC modules to new ones." ;; modify `transforms' to specify what needs to be changed ;; each item is in the format '(old . new) - (let ((transforms '((pcomplete . completion)))) - (delete-dups - (mapcar (lambda (m) (or (cdr (assoc m transforms)) m)) - mods)))) + (delete-dups (mapcar #'erc--normalize-module-symbol mods))) (defcustom erc-modules '(netsplit fill button match track completion readonly networks ring autojoin noncommands irccontrols @@ -2116,27 +2138,22 @@ erc-modules :group 'erc) (defun erc-update-modules () - "Run this to enable erc-foo-mode for all modules in `erc-modules'." - (let (req) + "Enable global minor mode for all global modules in `erc-modules'. +Return minor-mode commands for all local modules, possibly for +deferred invocation, as done by `erc-open' whenever a new ERC +buffer is created. Local modules were introduced in ERC 5.6." + (let (local-modules) (dolist (mod erc-modules) - (setq req (concat "erc-" (symbol-name mod))) - (cond - ;; yuck. perhaps we should bring the filenames into sync? - ((string= req "erc-capab-identify") - (setq req "erc-capab")) - ((string= req "erc-completion") - (setq req "erc-pcomplete")) - ((string= req "erc-pcomplete") - (setq mod 'completion)) - ((string= req "erc-autojoin") - (setq req "erc-join"))) - (condition-case nil - (require (intern req)) - (error nil)) + (require (or (alist-get mod erc--modules-to-features) + (intern (concat "erc-" (symbol-name mod)))) + nil 'noerror) ; some modules don't have a corresponding feature (let ((sym (intern-soft (concat "erc-" (symbol-name mod) "-mode")))) - (if (fboundp sym) + (unless (and sym (fboundp sym)) + (error "`%s' is not a known ERC module" mod)) + (if (custom-variable-p sym) (funcall sym 1) - (error "`%s' is not a known ERC module" mod)))))) + (push sym local-modules)))) + local-modules)) (defun erc-setup-buffer (buffer) "Consults `erc-join-buffer' to find out how to display `BUFFER'." @@ -2192,18 +2209,22 @@ erc-open (let* ((target (and channel (erc--target-from-string channel))) (buffer (erc-get-buffer-create server port nil target id)) (old-buffer (current-buffer)) - old-point + (old-recon-count erc-server-reconnect-count) + (old-point nil) + (delayed-modules nil) (continued-session (and erc--server-reconnecting (with-suppressed-warnings ((obsolete erc-reuse-buffers)) erc-reuse-buffers)))) (when connect (run-hook-with-args 'erc-before-connect server port nick)) - (erc-update-modules) (set-buffer buffer) (setq old-point (point)) - (let ((old-recon-count erc-server-reconnect-count)) - (erc-mode) - (setq erc-server-reconnect-count old-recon-count)) + (setq delayed-modules (erc-update-modules)) + + (delay-mode-hooks (erc-mode)) + + (setq erc-server-reconnect-count old-recon-count) + (when (setq erc-server-connected (not connect)) (setq erc-server-announced-name (buffer-local-value 'erc-server-announced-name old-buffer))) @@ -2266,6 +2287,12 @@ erc-open (setq erc-dbuf (when erc-log-p (get-buffer-create (concat "*ERC-DEBUG: " server "*")))) + + (erc-determine-parameters server port nick full-name user passwd) + + (save-excursion (run-mode-hooks)) + (dolist (mod delayed-modules) (funcall mod +1)) + ;; set up prompt (unless continued-session (goto-char (point-max)) @@ -2277,8 +2304,6 @@ erc-open (erc-display-prompt) (goto-char (point-max))) - (erc-determine-parameters server port nick full-name user passwd) - ;; Saving log file on exit (run-hook-with-args 'erc-connect-pre-hook buffer) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index b2ed29e80e..d3d319ab22 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -975,4 +975,51 @@ erc-message (kill-buffer "ExampleNet") (kill-buffer "#chan"))) +(ert-deftest erc-migrate-modules () + (should (equal (erc-migrate-modules '(autojoin timestamp button)) + '(autojoin stamp button))) + ;; Default unchanged + (should (equal (erc-migrate-modules erc-modules) erc-modules))) + +(ert-deftest erc-update-modules () + (let* (calls + (erc-modules '(fake-foo fake-bar))) + (cl-letf (((symbol-function 'require) + (lambda (s &rest _) (push s calls))) + ((symbol-function 'erc-fake-foo-mode) + (lambda (n) (push (cons 'fake-foo n) calls))) + ;; Here, foo is a global module (minor mode) + ((get 'erc-fake-foo-mode 'standard-value) #'ignore) + ((symbol-function 'erc-fake-bar-mode) + (lambda (n) (push (cons 'fake-bar n) calls))) + ((symbol-function 'erc-autojoin-mode) + (lambda (n) (push (cons 'autojoin n) calls))) + ((get 'erc-autojoin-mode 'standard-value) #'ignore) + ((symbol-function 'erc-networks-mode) + (lambda (n) (push (cons 'networks n) calls))) + ((symbol-function 'erc-completion-mode) + (lambda (n) (push (cons 'completion n) calls))) + ((get 'erc-completion-mode 'standard-value) #'ignore)) + + (ert-info ("Locals") + (should (equal (erc-update-modules) + '(erc-fake-bar-mode))) + ;; Bar still required + (should (equal (nreverse calls) '(erc-fake-foo + (fake-foo . 1) + erc-fake-bar))) + (setq calls nil)) + + (ert-info ("Module name overrides") + (setq erc-modules '(completion autojoin networks)) + (should-not (erc-update-modules)) ; no locals + (should (equal (nreverse calls) + '(erc-pcomplete + (completion . 1) + erc-join + (autojoin . 1) + erc-networks + (networks . 1)))) + (setq calls nil))))) + ;;; erc-tests.el ends here -- 2.37.3