GNU bug report logs - #57955
29.0.50; Allow session-local ERC modules

Previous Next

Package: emacs;

Reported by: "J.P." <jp <at> neverwas.me>

Date: Tue, 20 Sep 2022 13:06:02 UTC

Severity: wishlist

Tags: patch

Found in version 29.0.50

To reply to this bug, email your comments to 57955 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to emacs-erc <at> gnu.org, bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Tue, 20 Sep 2022 13:06:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to "J.P." <jp <at> neverwas.me>:
New bug report received and forwarded. Copy sent to emacs-erc <at> gnu.org, bug-gnu-emacs <at> gnu.org. (Tue, 20 Sep 2022 13:06:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: bug-gnu-emacs <at> gnu.org
Subject: 29.0.50; Allow session-local ERC modules
Date: Tue, 20 Sep 2022 06:05:38 -0700
[Message part 1 (text/plain, inline)]
Tags: patch

Hi people,

Since its inception, ERC has aimed to support local modules, that is,
modules local to a connection. (If you need convincing of this, take a
look at `define-erc-module'.) This makes sense for a good many reasons,
chief among them simplified semantics when arranging for buffer-local
variables and hooks. Ancillary benefits include let-binding
`erc-modules' around entry-point invocations and selectively disabling
modules for particular sessions (e.g., after capability negotiation).

Unfortunately, this dream of ERC's authors was never fully realized.
Take a look at `erc-open', where you'll find would-be local vars being
set too early and thus clobbered when `erc-mode' (the major mode) is
activated. Various kludges have come along to circumvent this. For
example, the log module uses `erc-connect-pre-hook' to conduct its
buffer-local business. But it shouldn't have to. ERC can do better.

This patch aims to address this problem by partially changing the
purpose of the function `erc-update-modules' in a backward compatible
way. Instead of activating local modules immediately, it now returns
them in a list to be activated at a time and place of the caller's
choosing. The most opportune place for this, in terms of `erc-open', is
after all the core local variables have been determined, which exposes
them to module setup code. As a bonus, the major mode hook is likewise
deferred to this point.

This patch also reworks the module-to-features map and preferred-name
migration logic, which was incomplete. As far as third-party packages
are concerned, it's only been tested with erc-hl-nicks, thus far, but it
"should" work with others too. (Please let me know if that's a lie.)

Thanks,
J.P.

P.S. BTW, if anyone is friendly with the hl-nicks author, please ask
them to reach out regarding an unrelated custom.el issue; attempts to
contact them via GitHub have so far proven unsuccessful.


In GNU Emacs 29.0.50 (build 2, x86_64-pc-linux-gnu, GTK+ Version
 3.24.34, cairo version 1.17.6) of 2022-09-19 built on localhost
Repository revision: 132d5cb0a3ec94afbb49772631861e00160ffffb
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 password-cache epa derived epg rfc6068
epg-config gnus-util text-property-search time-date subr-x mm-decode
mm-bodies mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader
cl-loaddefs cl-lib sendmail rfc2047 rfc2045 ietf-drums mm-util
mail-prsvr mail-utils rmc iso-transl tooltip 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
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 36059 6198)
 (symbols 48 5107 0)
 (strings 32 13115 1641)
 (string-bytes 1 372299)
 (vectors 16 9247)
 (vector-slots 8 146583 10252)
 (floats 8 21 25)
 (intervals 56 220 0)
 (buffers 1000 10))
[0002-Support-local-ERC-modules-in-erc-mode-buffers.patch (text/x-patch, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Tue, 20 Sep 2022 17:44:02 GMT) Full text and rfc822 format available.

Message #8 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: Michael Albinus <michael.albinus <at> gmx.de>
To: "J.P." <jp <at> neverwas.me>
Cc: 57955 <at> debbugs.gnu.org, emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Tue, 20 Sep 2022 19:43:34 +0200
"J.P." <jp <at> neverwas.me> writes:

> Hi people,

Hi,

> Since its inception, ERC has aimed to support local modules, that is,
> modules local to a connection. (If you need convincing of this, take a
> look at `define-erc-module'.) This makes sense for a good many reasons,
> chief among them simplified semantics when arranging for buffer-local
> variables and hooks. Ancillary benefits include let-binding
> `erc-modules' around entry-point invocations and selectively disabling
> modules for particular sessions (e.g., after capability negotiation).

Without knowing erc in general and your patch in detail: this sounds
like you could profit from connection-local variables. Did you check
this?

> Thanks,
> J.P.

Best regards, Michael.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Wed, 21 Sep 2022 13:17:02 GMT) Full text and rfc822 format available.

Message #11 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: Michael Albinus <michael.albinus <at> gmx.de>
Cc: 57955 <at> debbugs.gnu.org, emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Wed, 21 Sep 2022 06:15:48 -0700
Hi Michael,

Michael Albinus <michael.albinus <at> gmx.de> writes:

> Without knowing erc in general and your patch in detail: this sounds
> like you could profit from connection-local variables. Did you check
> this?

Not quite yet (only superficially).

At first glance, I'm not sure they're a perfect fit for this specific
issue, but I'll definitely investigate further. Either way, I'm thinking
they'd be a great solution (or inspiration) for an initiative we have on
the horizon, namely, devising a means of applying user options in a more
granular, contextual manner [1].

Also (if you happen to recall), a few of our recent exchanges ended with
me pledging to follow through on one thing or another. And yet, most of
those promises remain unfulfilled. Please know that I do plan on
addressing them "eventually" and that I very much appreciate your help
(and your patience).

Thanks,
J.P.


[1] In case you're interested, by "context," I'm referring to various
    logical (somewhat overlapping) IRC boundaries, such as

    - message: event-local, i.e., source-wise and message-type-wise
    - channel: target-local
    - network: connection-local

    Basically, I'm looking for something akin to a "context variable"
    facility, except not so much for managing concurrency but instead
    for transparently stashing and restoring message-processing
    environments matched against headers and protocol state. More info:

    https://lists.gnu.org/archive/html/emacs-erc/2021-10/msg00003.html




Severity set to 'wishlist' from 'normal' Request was from Stefan Kangas <stefankangas <at> gmail.com> to control <at> debbugs.gnu.org. (Thu, 13 Oct 2022 13:48:04 GMT) Full text and rfc822 format available.

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Wed, 26 Oct 2022 13:17:02 GMT) Full text and rfc822 format available.

Message #16 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Wed, 26 Oct 2022 06:16:34 -0700
I'd like to propose that these changes be included in ERC 5.5 and Emacs
29. If anyone has any concerns, please speak up. Otherwise, expect this
patch to be installed at some point as part of the larger SASL change
set in bug#29108. Thanks.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Tue, 15 Nov 2022 15:08:02 GMT) Full text and rfc822 format available.

Message #19 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Tue, 15 Nov 2022 07:07:30 -0800
"J.P." <jp <at> neverwas.me> writes:

> I'd like to propose that these changes be included in ERC 5.5 and Emacs
> 29. If anyone has any concerns, please speak up. Otherwise, expect this
> patch to be installed at some point as part of the larger SASL change
> set in bug#29108. Thanks.

Brief update. The scope of this change has shrunk considerably. The
deferred loading and migrations stuff still applies, but the main
user-facing aspect, namely, support for let-binding a module's options
on entry-point invocation, has been abandoned (for now).

After looking into connection-local variables a bit, following Michael's
suggestion up thread, I have come to the opinion that the let-binding
idea was not fully formed and that options granularity is worthy of more
meditation and discussion and thus not a realistic goal for Emacs 5.5.

Thanks.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Mon, 22 May 2023 04:06:02 GMT) Full text and rfc822 format available.

Message #22 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Sun, 21 May 2023 21:05:41 -0700
"J.P." <jp <at> neverwas.me> writes:

> Brief update. The scope of this change has shrunk considerably. The
> deferred loading and migrations stuff still applies, but the main
> user-facing aspect, namely, support for let-binding a module's options
> on entry-point invocation, has been abandoned (for now).
>
> After looking into connection-local variables a bit, following Michael's
> suggestion up thread, I have come to the opinion that the let-binding
> idea was not fully formed and that options granularity is worthy of more
> meditation and discussion and thus not a realistic goal for Emacs 5.5.

To help with managing local modules, I've added some (possibly
temporary) convenience functions and other supporting items as part of
bug#60936. The most useful are:

  * macro `erc--restore-initialize-priors'

    This restores local variables from a previous session on major-mode
    hook or slightly later. The effect is similar to that provided by
    the `permanent-local' property, which we may end up settling for if
    the dream of context-local user options evaporates for good. This
    may also be of interest to global modules intended primarily for
    interactive use, such as the proposed `bufbar' and `nickbar'
    (bug#63595).

  * variable `erc--updating-modules-p'

    This is non-nil when running `erc-update-modules' in `erc-open'. It
    allows global modules to suppress superfluous buffer initialization
    pre-major-mode while still making that same init code available on
    demand for interactive invocations and indirect activation by
    dependent modules.

Please keep in mind that these may change or disappear at any time.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Mon, 09 Oct 2023 04:03:02 GMT) Full text and rfc822 format available.

Message #25 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Sun, 08 Oct 2023 21:02:02 -0700
[Message part 1 (text/plain, inline)]
It turns out these changes removed some behavior involving the loading
of modules that's long been part of ERC's implicit interface. Basically,
when encountering a third-party module `mymod', ERC has always attempted
to `require' the (possibly nonexistent) feature `erc-mymod', and to do
so unconditionally. However, this bug changed that policy to instead
only attempt such loading if the corresponding command `erc-mymod-mode'
is undefined. That was likely unwise because some packages do
questionable things like

  ;;;###autoload
  (eval-after-load 'erc
    '(define-erc-module mymod nil "Doc" () ()))

which fails to provide the vital `symbol-file' association between the
minor-mode command and the library (because the command itself isn't
autoloaded). Such uses ignore the decades-old example in the doc string
of `define-erc-module', which clearly recommends

  ;;;###autoload(autoload 'erc-mymode-mode "erc-mymode")
  (define-erc-module mymod nil "Doc" () ())

as the surefire approach. That said, in cases where the library's name
matches the (prefixed) module name, no autoload cookie is necessary.
Unfortunately, many of these packages are in maintenance mode, with
authors unwilling to respond to suggestions to update such aberrant
uses. Thus, I think it's in ERC's best interest to accommodate them as
it always has. Please see the second patch below. Thanks.

[0001-5.6-Honor-nil-values-in-erc-restore-initialize-prior.patch (text/x-patch, attachment)]
[0002-5.6-Sort-and-dedupe-when-loading-modules-in-erc-open.patch (text/x-patch, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Sat, 14 Oct 2023 00:24:01 GMT) Full text and rfc822 format available.

Message #28 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Fri, 13 Oct 2023 17:23:01 -0700
I've added something similar to the proposed change as

  https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=d46c016f

If you'll recall, I initially left this bug open as a reminder that we
still lack a solution to the glaring problem of configuration scoping.
To summarize, this concerns the pressing need to allow users to specify
different values for new (and hopefully existing) global options based
on context (like network, channel, speaker, etc.). One idea bandied
about has been adapting connection-local variables to be more abstract
and eventually integrating them with Customize and `use-package'. As
thing stand, though, each local module must itself decide whether it's
session-local, buffer-local, or both. And it must contend with stashing
and restoring its configured state on its own.

I'll likely close this bug after opening another to address these
broader configuration concerns. Thanks.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Wed, 18 Oct 2023 13:38:01 GMT) Full text and rfc822 format available.

Message #31 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Wed, 18 Oct 2023 06:36:26 -0700
[Message part 1 (text/plain, inline)]
"J.P." <jp <at> neverwas.me> writes:

> I've added something similar to the proposed change as
>
>   https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=d46c016f

Actually, this has proved insufficient for detecting top-level and
`eval-after-load' calls to `erc-update-modules'. The attached tweak
hopefully addresses that.

[0001-5.6-Warn-about-top-level-erc-update-modules-calls.patch (text/x-patch, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Sat, 10 Feb 2024 20:37:02 GMT) Full text and rfc822 format available.

Message #34 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Sat, 10 Feb 2024 12:36:04 -0800
A new Emacs user recently complained about the lack of an example
showing local modules being `let'-bound around calls to ERC's entry
point functions in lisp code. Perhaps we should add such an example to
the Modules chapter and also possibly update the "Multiple networks"
example in Advanced > SASL to include a non-SASL connection:

  (defun my-erc-up (network)
    (interactive "Snetwork: ")
  
    (pcase network
      ('libera
       (let ((erc-modules (cons 'sasl erc-modules))
             (erc-sasl-mechanism 'external))
         (erc-tls :server "irc.libera.chat" :port 6697
                  :client-certificate t)))
      ('example
       (let ((erc-modules (cons 'sasl erc-modules))
             (erc-sasl-auth-source-function
              #'erc-sasl-auth-source-password-as-host))
         (erc-tls :server "irc.example.net" :port 6697
                  :user "alyssa"
                  :password "Example.Net")))
      (_ (call-interactively #'erc-tls))))




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Fri, 01 Mar 2024 00:26:01 GMT) Full text and rfc822 format available.

Message #37 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Thu, 29 Feb 2024 16:25:08 -0800
"J.P." <jp <at> neverwas.me> writes:

> A new Emacs user recently complained about the lack of an example
> showing local modules being `let'-bound around calls to ERC's entry
> point functions in lisp code. Perhaps we should add such an example to
> the Modules chapter and also possibly update the "Multiple networks"
> example in Advanced > SASL to include a non-SASL connection:
>
>   (defun my-erc-up (network)
>     (interactive "Snetwork: ")
>   
>     (pcase network
>       ('libera
>        (let ((erc-modules (cons 'sasl erc-modules))
>              (erc-sasl-mechanism 'external))
>          (erc-tls :server "irc.libera.chat" :port 6697
>                   :client-certificate t)))
>       ('example
>        (let ((erc-modules (cons 'sasl erc-modules))
>              (erc-sasl-auth-source-function
>               #'erc-sasl-auth-source-password-as-host))
>          (erc-tls :server "irc.example.net" :port 6697
>                   :user "alyssa"
>                   :password "Example.Net")))
>       (_ (call-interactively #'erc-tls))))

I've added an example similar to this one.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Sun, 09 Feb 2025 20:47:01 GMT) Full text and rfc822 format available.

Message #40 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Sun, 09 Feb 2025 12:46:14 -0800
[Message part 1 (text/plain, inline)]
I'd like to reexamine the scope of this bug because it's a partial
blocker for bug#49860 (IRCv3). You'll recall among its original goals
were two overlapping concerns:

  a. Granular configuration of a local module's user options
  b. Persistence of a local module's data across reconnections

One idea bandied about for addressing the first was to recommend and
accommodate buffer-local options, that is, recommend that options in a
local module's Custom group be explicitly declared buffer-local with the
:local `defcustom' keyword and that they be given local bindings on
module activation, thus initializing them with values from the current
environment. While this is technically feasible, a few notable
complications would need sorting out [1].

A complementary aspect addressing the second goal of convenient
persistence was also previously floated and amounted to leveraging the
`permanent-local' symbol property on a local module's own variables of
interest to sustain them across reconnections. With local user options,
this would likely involve the `permanent-only' argument to the :local
`defcustom' keyword explained in (info "(elisp) Variable Definitions").
For the stated purpose of sustaining variables of interest across
reconnection boundaries, this approach remains viable [2], at least for
third party modules that don't know about the internal persistence
mechanism [3].

So, in light of the new proposal for "scoped" configuration now
officially on the table [4], it might behoove us to just pretend the
granularity objective is henceforth solely the domain of that proposal's
bug (bug#76019). That'll allow us, here, in this bug, to focus entirely
on the second objective about persistence and to hopefully arrive at
something worthy of some finality for 5.7. To that end, here's some
related territory possibly worth exploring:

  1. A public utility function to access the prior buffer's local
     variables during reconnection

  2. A managed facility for declaring arbitrary persisted data with
     supporting CRUD operations
  
  3. Optional helpers for an option's :set function that update
     persisted values in affected buffers or inform users to cycle the
     mode or restart the session

  4. Documenting differences in how a local module's mode command
     variants behave with the various flavors of local modules, like
     session-wide, target-only, etc.

  5. An advanced tutorial on how to write a local module using only the
     public API via a fully functional demo

To get started, I've attached a PoC of a possible approach for point 2
(the CRUD thing). It turns out my having explored the idea some has led
me to the opinion that it's probably better to stick to points 4 and 5
only and to let module authors deal with the rest. Basically, I'm not
sure asking anyone to adopt yet another magical abstraction layer just
to persist state is any less mentally taxing than asking them to wrangle
it all themselves using lower level Emacs facilities, so long as we
provide clear guidelines and examples with any necessary boilerplate. Of
course, this observation disregards maintainability concerns, so we'd
need to be pretty certain all related infrastructure is mostly here to
stay (famous last words). More to come on this shortly.

Thanks.


[1] Possible complications with the :local `defcustom' keyword idea:

    . Most users are unfamiliar with buffer-local options. And, AFAICT,
      Customize doesn't itself prescribe how such an option's variable
      should be made buffer local nor how or whether relevant updates
      ought to be propagated across existing local bindings when the
      default value is updated. It would seem such concerns are the
      responsibility of the application. But these are *user* options,
      and users can't be bothered to learn the idiosyncrasies of each
      app just to configure it to behave as expected. They'll either
      move on or risk contending with unwelcome surprises.

    . The buffer-wise "scope" doesn't always align perfectly with
      contexts endemic to IRC, the most important being the
      connection-wise session, which spans multiple buffers (internally,
      those having the same `erc-networks--id'). Users on 29+ might be
      able to use `setopt' to update the value cleanly within a session,
      for example, in a server buffer, and have the change shared with
      all targets as well. But, users on 27 and 28 can't be expected to
      invoke the option's :set function outside of Custom buffers,
      although advanced users can manually apply updates via the
      module's explicit enable/disable command variants.

    . Modules oftentimes ignore the value of an option after
      initialization and instead use something derived from the original
      value and then progressively refined. Local modules also perform
      other initialization tasks based on the value of an option, such
      as subscribe to certain hooks. While buffer-local options may
      agree sufficiently with this pattern, so long as they're bound
      before module setup code runs in a new or reused buffer, the
      pattern dictates that a module capture a "snapshot" of an option's
      value anyway, so there's no reason to prefer buffer-local bindings
      over, say, more ephemeral and arguably easier to reason about
      `let' bindings.

    . Per-target options won't magically work when local in a target
      buffer because ERC often decides on target-related business with
      the server buffer current. Indeed, the target buffer in question
      may not even exist yet, which happens most often in response
      handlers, such as `erc-server-PRIVMSG'. Although this situation
      can be remedied, doing it in a backward compatible way seems a
      chore.

[2] A local module's mode variable itself can't be `permanent-local'
    because the majority of setup it performs won't survive a major-mode
    reset, thus creating an "inconsistent state" during the crucial
    reinitialization period when modules inspect and even modify one
    another. It's then that they also need to possibly recall the
    original value of variables not owned by them or even ERC (and these
    definitely can't be made `permanent-local').

[3] The internal inter-session persistence mechanism consists mainly of
    a crude restoration ritual for transferring values from old buffers
    to new via the variables `erc--server-reconnecting' and
    `erc--target-priors'. These are bound at module initialization time
    to an alist containing the local variables of the "reassociated"
    buffer, if any. Aside from those symbol names not being great and
    there being no public interface, there's also no way to recover if
    something goes awry during (re)initialization: restarting the
    session from scratch won't work so long as module-managed local
    variables are still bound to unusable values in the old buffer.
    Basically, the offending local module must run its "disable body"
    somewhere: either in the old buffer, before reassociating, or in the
    new one upon failure. Clearly, a friendlier and ideally more robust
    user-facing solution is necessary.

[4] https://debbugs.gnu.org/cgi/bugreport.cgi?bug=76019

[0001-5.7-Skip-already-enabled-local-modules-in-erc-open.patch (text/x-patch, attachment)]
[0002-5.7-Provide-API-for-persisting-local-module-state-in.patch (text/x-patch, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#57955; Package emacs. (Wed, 19 Feb 2025 04:19:01 GMT) Full text and rfc822 format available.

Message #43 received at 57955 <at> debbugs.gnu.org (full text, mbox):

From: "J.P." <jp <at> neverwas.me>
To: 57955 <at> debbugs.gnu.org
Cc: emacs-erc <at> gnu.org
Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules
Date: Tue, 18 Feb 2025 20:17:58 -0800
[Message part 1 (text/plain, inline)]
"J.P." <jp <at> neverwas.me> writes:

> So, in light of the new proposal for "scoped" configuration now
> officially on the table [4], it might behoove us to just pretend the
> granularity objective is henceforth solely the domain of that proposal's
> bug (bug#76019). That'll allow us, here, in this bug, to focus entirely
> on the second objective about persistence and to hopefully arrive at
> something worthy of some finality for 5.7. To that end, here's some
> related territory possibly worth exploring:
>
>   1. A public utility function to access the prior buffer's local
>      variables during reconnection
>
>   2. A managed facility for declaring arbitrary persisted data with
>      supporting CRUD operations
>   
>   3. Optional helpers for an option's :set function that update
>      persisted values in affected buffers or inform users to cycle the
>      mode or restart the session
>
>   4. Documenting differences in how a local module's mode command
>      variants behave with the various flavors of local modules, like
>      session-wide, target-only, etc.
>
>   5. An advanced tutorial on how to write a local module using only the
>      public API via a fully functional demo
>
> To get started, I've attached a PoC of a possible approach for point 2
> (the CRUD thing). It turns out my having explored the idea some has led
> me to the opinion that it's probably better to stick to points 4 and 5
> only and to let module authors deal with the rest. Basically, I'm not
> sure asking anyone to adopt yet another magical abstraction layer just
> to persist state is any less mentally taxing than asking them to wrangle
> it all themselves using lower level Emacs facilities, so long as we
> provide clear guidelines and examples with any necessary boilerplate. Of
> course, this observation disregards maintainability concerns, so we'd
> need to be pretty certain all related infrastructure is mostly here to
> stay (famous last words). More to come on this shortly.

Here is an initial draft attempting to address points 4 and 5 in the
list above (patch also attached):


  File: erc.info,  Node: Module Example,  Next: Module Usage,
  Prev: Module Loading,  Up: Modules

  4.3 Example
  ===========

  This is a walk-through of a working module presented in separate chunks.
  If you'd prefer to view it as a whole, you can install it as a
  third-party package through ERC's devel archive:
  <https://emacs-erc.gitlab.io/bugs/archive/erc-view.html>.

       ;;; erc-view.el -- Automatic view-mode for ERC -*- lexical-binding: t; -*-

       ;; Maintainer: The ERC Maintainers <emacs-erc <at> gnu.org>
       ;; Keywords: convenience
       ;; Version: 0.1
       ;; Package-Requires: ((emacs "30.1"))
       ;; URL: https://gitlab.com/emacs-erc/erc-view

       ;;; Commentary:

       ;; This is a demo local module for ERC.  It arranges for automatically
       ;; enabling `view-mode' when leaving the prompt area and automatically
       ;; disabling it when reentering.  It also ensures `view-mode' stays
       ;; enabled or disabled when reconnecting.

       ;;; Code:

     You need to import ERC's main library somehow.  The easiest way is
  directly, via a simple ‘(require 'erc)’, although this module does so
  indirectly because it also uses definitions from ‘erc-goodies’:

       (require 'erc-goodies)
       (require 'view)

     Avoid headaches by aligning the name of your module with its
  containing library and Custom group.  It's best to have one group and
  one module per library.

       (defgroup erc-view nil
         "Automatically enter and exit `view-mode' in ERC."
         :version "0.1"
         :group 'erc)

       (defcustom erc-view-enable-when-exiting-prompt t
         "Whether to enable `view-mode' when exiting the prompt area."
         :type 'boolean)

       (defcustom erc-view-disable-when-entering-prompt t
         "Whether to disable `view-mode' when entering the prompt area."
         :type 'boolean)

       (defcustom erc-view-backspace-at-prompt-scrolls-down t
         "Whether a \\`<backspace>' at the prompt scrolls down to enter `view-mode'."
         :type 'boolean)

     You'll almost always want to define internal variables as
  buffer-local.

       (defvar-local erc-view--enabled-p nil
         "Current reconnect-aware activation state of `view-mode'.")

  In some cases, you may need a variable's value to survive the
  reapplication of ERC's ‘major-mode’ performed in each reassociated
  buffer upon reconnecting.  Do this by leveraging the ‘permanent-local’
  symbol property.  (*note (elisp)Creating Buffer-Local::.)

       (put 'erc-view--enabled-p 'permanent-local t)

  There are a few caveats regarding the durability of permanent values.
  By convention, disabling a module's minor mode kills local bindings.
  Mode commands, like ‘erc-view-mode’, do so in the current buffer only,
  while unidirectional ones, like ‘erc-view-mode-disable’, do so
  connection-wide.

     There are also occasions in which persistence is undefined, most
  notably when “grafting” an old buffer's contents onto a current buffer.
  This occurs in server buffers upon “logical connection” (at ‘MOTD’'s
  end), when a user reconnects with a new invocation of an entry-point
  command, like ‘erc-tls’, instead of via the auto-reconnect facility or
  by issuing a ‘/reconnect’ at the prompt.  Unaffected are entry-point
  invocations that include an ‘:id’ keyword because reassociation happens
  immediately in such cases, before ERC even initializes any modules.
  Grafting can also happen in target buffers, most often after a user
  reconnects under a new nick and conducts business in the same channels
  and queries as before, only to renick _back_ to the previous nick via a
  ‘/nick oldme’ or similar.  As of version 5.7, ERC retains the current
  buffer's permanent value in all such situations, meaning ERC ignores
  permanent values from previous buffers and retains default values
  assigned during module initialization.

     Moving on, if your module needs to bind keys, define its keymap
  _before_ the module itself, and use the standard minor-mode naming
  convention of ‘erc-my-module-mode-map’.

       (defvar-keymap erc-view-mode-map
         :doc "Keymap for `view-mode' in ERC."
         "<remap> <delete-backward-char>" #'erc-view--enable-on-backspace)

       (defvar-keymap erc-view-mode-overriding-map
         :parent view-mode-map
         :doc "Overriding keymap for `view-mode' when `erc-view-mode' is active.
       Hitting \\`<RET>' atop a button prompts for an action by default.  Use
       \\`C-j' or \\`j' for scrolling up by a line."
         "C" nil ; View-kill-and-leave
         "E" #'erc-view--exit-to-bottom ; View-exit-and-edit
         "Q" nil ; View-quit-all
         "k" #'View-scroll-line-backward ; Vim backwards line
         "j" #'View-scroll-line-forward ; Vim forwards line
         "S-<return>" #'View-scroll-line-backward)

     You'll almost always want to define your module as buffer-local.  Do
  this by including a ‘localp’ flag as the final parameter to
  ‘define-erc-module’, after the “disable body”.  If your module only
  operates in one kind of buffer, disable it elsewhere in the “enable
  body”.  For example, if it should only run in server buffers, disable it
  in target buffers by doing something like ‘(if (erc-target)
  (erc-my-module-mode -1) (erc-my-module--setup))’.  And in all cases,
  please remember to mention the module's intended “scope” in the doc
  string.  Some informal adjectives that may help with that are:

     • query-local
     • channel-local
     • target-local (query or channel)
     • server-local
     • session-local (server and target)
     • buffer-local (server or target)

  You may also wish to mention this in the Custom group's doc string.

       (define-erc-module view nil
         "Enable `view-mode' if it was on previously.
       This module is buffer-local.  If you also use the `scrolltobottom'
       module, you probably want to enable the option `erc-scrolltobottom-all'."
         ((add-hook 'view-mode-hook #'erc-view--remember 0 t)
          (add-hook 'post-command-hook #'erc-view--enforce-prompt-boundary 0 t)
          (setf (alist-get 'view-mode minor-mode-overriding-map-alist)
                erc-view-mode-overriding-map)
          (unless (local-variable-p 'erc-view--enabled-p)
            (setq-local erc-view--enabled-p nil))
          (view-mode (if erc-view--enabled-p +1 -1)))
         ((kill-local-variable 'erc-view--enabled-p)
          (remove-hook 'post-command-hook #'erc-view--enforce-prompt-boundary t)
          (remove-hook 'view-mode-hook #'erc-view--remember t)
          (setf (alist-get 'view-mode minor-mode-overriding-map-alist nil 'remove)
                nil))
         localp)

     Always define your module early, before any code that refers to its
  mode command or minor-mode variable.

       (defun erc-view--enable-on-backspace (lines)
         "Enable `view-mode' at the prompt by hitting \\`<backspace>'."
         (interactive "P")
         (if (and erc-view-backspace-at-prompt-scrolls-down (not view-mode)
                  (= (point) erc-input-marker))
             (progn
               (view-mode +1)
               (View-scroll-page-backward lines))
           (call-interactively #'delete-backward-char)))

       (defun erc-view--enforce-prompt-boundary ()
         "Enable or disable `view-mode' when crossing prompt boundary."
         (when-let*
             ((new (if (>= (point) erc-input-marker)
                       (and view-mode erc-view-disable-when-entering-prompt -1)
                     (and (not view-mode) erc-view-enable-when-exiting-prompt +1))))
           (run-at-time 0 nil (lambda (buffer new)
                                (with-current-buffer buffer (view-mode new)))
                        (current-buffer) new)))

       (defun erc-view--exit-to-bottom ()
         "Scroll to prompt, exit `view-mode', and move to EOB."
         (interactive)
         (let (view-no-disable-on-exit)
           (View-scroll-to-buffer-end)
           (View-exit)
           (goto-char (point-max))))

       (defun erc-view--remember ()
         "Remember the value of `view-mode'.
       Disable `erc-move-to-prompt-setup' locally when `view-mode' is enabled."
         (cl-assert (local-variable-p 'erc-view--enabled-p))
         (setq erc-view--enabled-p view-mode)
         (when erc-move-to-prompt-mode
           (if view-mode
               (remove-hook 'pre-command-hook #'erc-move-to-prompt t)
             (erc-move-to-prompt-setup))))

     Don't forget to ‘provide’ your module so that ‘erc-update-modules’
  can find it.

       (provide 'erc-view)

       ;;; erc-view.el ends here

     Mimicking the above should just about cover most use cases.  If your
  module isn't loading correctly, it's likely a naming, layout, or
  packaging issue.  If you _must_ defy the convention recommended earlier
  regarding a library-group-module correspondence or if you've designed
  your module mainly to be toggled interactively rather than added to
  ‘erc-modules’, try placing a line like the following above the module's
  definition.

       ;;;###autoload(autoload 'erc-my-module-mode "erc-my-module" nil t)

  Just remember, doing so means you'll need to (re)generate the autoload
  file when hacking locally (*note (emacs)Fetching Package Sources::).

[0001-5.7-Split-ERC-module-documentation-into-subnodes.patch (text/x-patch, attachment)]
[0002-5.7-Add-module-example-to-ERC-s-documentation.patch (text/x-patch, attachment)]

This bug report was last modified 120 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.