branch: externals/eev
commit 35f33e17914b524c8127e713138a079d91401d57
Author: Eduardo Ochs <[email protected]>
Commit: Eduardo Ochs <[email protected]>
Finished the code and the documentation for `eepitch-b'.
---
ChangeLog | 82 ++++++++++++++
VERSION | 4 +-
eepitch.el | 260 ++++++++++++++++++++++++++++---------------
eev-elinks.el | 3 +-
eev-intro.el | 321 ++++++++++++++++++++++++++++++++++++++++++++++--------
eev-load.el | 9 +-
eev-testblocks.el | 195 ++++++++++++++++++++++++++-------
eev-tlinks.el | 13 ++-
eev.el | 2 +-
9 files changed, 707 insertions(+), 182 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 9f2ff967b0..13f0d73a95 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,85 @@
+2025-11-23 Eduardo Ochs <[email protected]>
+
+ * eepitch.el (eepitch-b-show-source, eepitch-b-set-source)
+ (eepitch-b-show-target, eepitch-b-set-target)
+ (eepitch-b-show-source-and-target, eepitch-b)
+ (eepitch-b-set-target-and-show, ee-buffers-in-modes)
+ (ee-buffers-in-mode, ee-buffers-with-minor-mode)
+ (ee-buffers-with-name-matching, ee-kill-buffers)
+ (ee-kill-buffers-in-modes, ee-kill-buffers-in-mode)
+ (ee-kill-buffers-with-minor-mode)
+ (ee-kill-buffers-with-name-matching, find-ebuffer-in-mode)
+ (eepitch-gdb-kill, eepitch-gdb-select)
+ (ee-slime-inferior-lisp-buffers, ee-slime-buffers)
+ (eepitch-slime-kill, eepitch-slime-select)
+ (eepitch-slime-pkgbuffers, eepitch-slime-set-pkgbuffers)
+ (eepitch-slime-get-pkgbuffer, eepitch-slime-select-pkgbuffer)
+ (eepitch-sly-kill, eepitch-sly-select, eepitch-sly-pkgbuffers)
+ (eepitch-sly-set-pkgbuffers, eepitch-sly-get-pkgbuffer)
+ (eepitch-sly-select-pkgbuffer): these are all the functions in the
+ section "badly-behaved targets" of eepitch.el - I rewrote the code
+ and the comments for most of them.
+
+ * eev-testblocks.el (ee-insert-test-lisp-mode)
+ (ee-insert-test-lisp-mode-nil, ee-insert-test-lisp-mode-1)
+ (ee-insert-test-lisp-mode-2, ee-insert-test-lisp-mode-3)
+ (ee-insert-test-lisp-mode-4, ee-insert-test-lisp-mode-5):
+ rewritten.
+
+2025-11-21 Eduardo Ochs <[email protected]>
+
+ * eev-intro.el (find-eepitch-intro): rewrote the sections about
+ badly-behaved targets (sections 4 to 4.7).
+
+2025-11-16 Eduardo Ochs <[email protected]>
+
+ * eepitch.el (eepitch-slime-pkgbuffers): new variable.
+ (eepitch-slime-pkgbuffers, eepitch-slime-set-pkgbuffers)
+ (eepitch-slime-get-pkgbuffer, eepitch-slime-select-pkgbuffer): new
+ functions.
+
+2025-10-22 Eduardo Ochs <[email protected]>
+
+ * eepitch.el (ee-buffers-with-name-matching)
+ (ee-kill-buffers-with-name-matching): new functions.
+ (eepitch-sly-kill): use `ee-kill-buffers-with-name-matching'.
+ (eepitch-b): new function.
+ (eepitch-b-set-target): FIX?
+
+ * eev-tlinks.el (ee-dot-emacs-eepitchb): new function.
+
+2025-10-08 Eduardo Ochs <[email protected]>
+
+ * eepitch.el (ee-buffers-with-minor-mode, ee-kill-buffers)
+ (ee-kill-buffers-with-minor-mode): new functions.
+ (eepitch-sly-kill, eepitch-sly-start, eepitch-sly-select)
+ (eepitch-sly-connect): new functions.
+ (eepitch-sly-pkgbuffers): new variable.
+ (eepitch-sly-pkgbuffers, eepitch-sly-set-pkgbuffers)
+ (eepitch-sly-get-pkgbuffer, eepitch-sly-select-pkgbuffer): new
+ functions.
+
+2025-10-05 Eduardo Ochs <[email protected]>
+
+ * eev-elinks.el (find-pdflike-page-links): new argument: target.
+
+2025-09-27 Eduardo Ochs <[email protected]>
+
+ * eepitch.el (eepitch-gdb-start, eepitch-slime-start): made the
+ arguments optional.
+
+ * eev-testblocks.el (ee-insert-test-lisp-mode-2): new function.
+ (ee-insert-test-lisp-mode-3): new function.
+ (ee-insert-test-lisp-mode-4): new function.
+ (ee-insert-test-lisp-mode-5): new function.
+
+ * eev-tlinks.el: added a link like `(find-eev "eev-testblocks.el"
+ "lua-mode")'.
+
+2025-09-16 Eduardo Ochs <[email protected]>
+
+ * eepitch.el (eepitch-gdb-select): fixed a typo.
+
2025-09-14 Eduardo Ochs <[email protected]>
* eev-mode.el (ee-kill-this-buffer): use `kill-current-buffer'
diff --git a/VERSION b/VERSION
index db68a0ca01..205f3664de 100644
--- a/VERSION
+++ b/VERSION
@@ -1,2 +1,2 @@
-Sun Sep 14 22:12:31 GMT 2025
-Sun Sep 14 19:12:31 -03 2025
+Sun Nov 23 23:05:39 GMT 2025
+Sun Nov 23 20:05:39 -03 2025
diff --git a/eepitch.el b/eepitch.el
index 33f876e152..c3fa594a70 100644
--- a/eepitch.el
+++ b/eepitch.el
@@ -19,7 +19,7 @@
;;
;; Author: Eduardo Ochs <[email protected]>
;; Maintainer: Eduardo Ochs <[email protected]>
-;; Version: 20250914
+;; Version: 20251123
;; Keywords: e-scripts
;;
;; Latest version: <http://anggtwu.net/eev-current/eepitch.el>
@@ -54,9 +54,11 @@
;; «.eepitch-sly» (to "eepitch-sly")
;; «.badly-behaved» (to "badly-behaved")
;; «.eepitch-b» (to "eepitch-b")
-;; «.ee-buffers-in-mode» (to "ee-buffers-in-mode")
-;; «.eepitch-gdb» (to "eepitch-gdb")
-;; «.eepitch-slime» (to "eepitch-slime")
+;; «.ee-kill-buffers» (to "ee-kill-buffers")
+;; «.find-ebuffer-in-mode» (to "find-ebuffer-in-mode")
+;; «.eepitch-b-gdb» (to "eepitch-b-gdb")
+;; «.eepitch-b-slime» (to "eepitch-b-slime")
+;; «.eepitch-b-sly» (to "eepitch-b-sly")
;;
;; «.eepitch-langs» (to "eepitch-langs")
;; «.eepitch-langs-vterm» (to "eepitch-langs-vterm")
@@ -1082,51 +1084,32 @@ If the mrepl doesn't start in 30 seconds this function
yields an error."
;;; |___/
;;
;; «badly-behaved» (to ".badly-behaved")
+;; WARNING: EXPERIMENTAL! ADVANCED! NEEDS CONFIGURATION!
;; See: (find-eepitch-intro "4. Badly-behaved targets")
;; (find-eepitch-intro "4. Badly-behaved targets" "For example")
-;; Warning: EXPERIMENTAL! BADLY DOCUMENTED! BADLY TESTED!
-;;
-;; Sometimes a target is so badly behaved - for example: Slime - that
-;; I don't know how to write a sexp like this for it,
-;;
-;; (eepitch CODE)
-;;
-;; that would pitch to the right target buffer... in the case of Slime
-;; the target buffer is a buffer with a name like "*slime-repl sbcl*"
-;; or "*slime-repl sbcl*<4>", that is in `slime-repl-mode', and that
-;; has an active `slime-buffer-connection', but there may be several
-;; buffers like that, and writing a trick with hooks that would select
-;; the right buffer - as `find-slyprocess' does, above - turned out to
-;; be too hard...
-;;
-;; In cases like that the best solution is to treat the `eepitch' in
-;; `(eepitch CODE)' as a black box that needs to be opened, that needs
-;; to have its components run step by step by hand, and that the user
-;; needs to select the right target buffer by running `M-x b' on it.
-;; For example, in...
-;;
-;; [I need to rewrite the rest!]
-
+;; (find-eepitch-intro "4.2. `eepitch-b'")
+;; (find-eepitch-intro "4.3. Configuring `eepitch-b'")
;; «eepitch-b» (to ".eepitch-b")
;; The basic functions for the support for badly-behaved targets.
+;; See: (find-eepitch-intro "4.2. `eepitch-b'")
+;; Tests: (eepitch-b-set-source)
+;; (find-2a nil '(eepitch-b-show-source))
+;; (eepitch-b-set-source 2)
+;; (find-2a nil '(eepitch-b-show-source))
+;; (eepitch-to-buffer "TODO")
+;; (eepitch-b-set-source 3)
+;; (delete-other-windows)
+;; (eepitch-b-show-source-and-target)
;;
(defvar eepitch-b-source-buffer "")
(defvar eepitch-b-source-marker nil)
+;; Low-level functions
(defun eepitch-b-show-source ()
(interactive)
(find-emarker eepitch-b-source-marker))
-(defun eepitch-b-show-target ()
- (interactive)
- (find-ebuffer eepitch-buffer-name))
-
-;; Test:
-;; (eepitch-b-set-source)
-;; (find-2a nil '(eepitch-b-show-source))
-;; (eepitch-b-set-source 2)
-;; (find-2a nil '(eepitch-b-show-source))
(defun eepitch-b-set-source (&optional n)
(interactive)
(setq eepitch-b-source-buffer (buffer-name))
@@ -1135,60 +1118,99 @@ If the mrepl doesn't start in 30 seconds this function
yields an error."
(if n (forward-line n))
(point-marker))))
-;; Test:
-;; (eepitch-to-buffer "TODO")
-;; (eepitch-b-set-source 3)
-;; (eek "C-x 1")
-;; (eepitch-b-show-source-and-target)
-(defun eepitch-b-show-source-and-target ()
+(defun eepitch-b-show-target ()
(interactive)
- (find-2a '(eepitch-b-show-source) '(eepitch-b-show-target))
- (message "%S -> %S" eepitch-b-source-buffer eepitch-buffer-name)
- (format "`%s' -> `%s'" eepitch-b-source-buffer eepitch-buffer-name))
+ (find-ebuffer eepitch-buffer-name))
(defun eepitch-b-set-target ()
- "An internal function used by `eepitch-set-source-and-M-x-b'."
(interactive)
(setq eepitch-buffer-name (buffer-name))
- (eepitch-b-show-source-and-target))
+ (setq eepitch-code `(find-ebuffer ,(buffer-name))))
-;; See: (find-eepitch-intro "4. Badly-behaved targets" "For example")
-(defun eepitch-set-source-and-M-x-b (&optional n)
+(defun eepitch-b-show-source-and-target ()
(interactive)
- (eepitch-b-set-source n)
- (defalias 'b 'eepitch-b-set-target)
- (format "`M-x b' will set the eepitch target and return to `%s'"
- eepitch-b-source-buffer))
-
-;; Unused at the moment!
-(defun eepitch-b-insert ()
+ (find-2a '(eepitch-b-show-source) '(eepitch-b-show-target))
+ (message "%S -> %S" eepitch-b-source-buffer eepitch-buffer-name)
+ (format "`%s' -> `%s'" eepitch-b-source-buffer eepitch-buffer-name))
+
+;; High-level functions.
+;; See: (find-eepitch-intro "4.2. `eepitch-b'")
+;; (find-eepitch-intro "4.3. Configuring `eepitch-b'")
+;; (find-eepitch-intro "4.3. Configuring `eepitch-b'" "<f9>")
+;;
+(defun eepitch-b (sexp)
+ "This is like `eepitch', but SEXP may be a badly-behaved target.
+This function sets the source, runs SEXP, and it waits until the user
+types <f9> to set the target; then it creates a two-window setting with
+the source at the left and the target at the right."
+ (eepitch-b-set-source 1)
+ (eval sexp)
+ "Type <f9> to tell to `eepitch-b' that we're on the target buffer.")
+
+(defun eepitch-b-set-target-and-show ()
+ "Run this to signal to `eepitch-b' that we're on the target buffer.
+Read the comments in the source to see how to configure this."
(interactive)
- (let* ((sexp `(eepitch-to-buffer ,eepitch-buffer-name))
- (line (format " %s\n" (ee-S sexp))))
- (move-beginning-of-line nil)
- (insert line)))
+ (eepitch-b-set-target)
+ (eepitch-b-show-source-and-target))
+
-;; «ee-buffers-in-mode» (to ".ee-buffers-in-mode")
-;; These functions are used by some badly behaved targets - like gdb.
-;;
-(defun ee-buffers-in-mode (majormode)
- (ee-buffers-in-modes (list majormode)))
+
+;;; _ ___ _ _ _ __ __
+;;; | |/ (_) | | | |__ _ _ / _|/ _| ___ _ __ ___
+;;; | ' /| | | | | '_ \| | | | |_| |_ / _ \ '__/ __|
+;;; | . \| | | | | |_) | |_| | _| _| __/ | \__ \
+;;; |_|\_\_|_|_| |_.__/ \__,_|_| |_| \___|_| |___/
+;;;
+;; «ee-kill-buffers» (to ".ee-kill-buffers")
+;; Some badly-behaved targets use the functions in this block to kill
+;; the buffers associated to a target. See this for an example:
+;; (find-eepitch-intro "4.1. Killing Slime")
(defun ee-buffers-in-modes (majormodes)
(sort (cl-loop for b in (buffer-list)
if (member (with-current-buffer b major-mode) majormodes)
collect (buffer-name b))))
-(defun ee-kill-buffers-in-mode (majormode)
- (ee-kill-buffers-in-modes (list majormode)))
+(defun ee-buffers-in-mode (majormode)
+ (ee-buffers-in-modes (list majormode)))
+
+(defun ee-buffers-with-minor-mode (minormode)
+ (sort (cl-loop for b in (buffer-list)
+ if (with-current-buffer b (symbol-value minormode))
+ collect (buffer-name b))))
-(defun ee-kill-buffers-in-modes (majormodes)
- (let* ((bufs (ee-buffers-in-modes majormodes)))
- (cl-loop for b in bufs
+(defun ee-buffers-with-name-matching (regexp)
+ (sort (cl-loop for b in (buffer-list)
+ if (string-match regexp (buffer-name b))
+ collect (buffer-name b))))
+
+(defun ee-kill-buffers (buffernames &optional show-only)
+ (if show-only
+ `(Buffers that will be killed: ,(or buffernames 'none))
+ (cl-loop for b in buffernames
do (ee-kill-buffer b))
- `(Buffers killed: ,(or bufs 'none))))
+ `(Buffers killed: ,(or buffernames 'none))))
+
+(defun ee-kill-buffers-in-modes (majormodes &optional show-only)
+ (ee-kill-buffers (ee-buffers-in-modes majormodes) show-only))
+
+(defun ee-kill-buffers-in-mode (majormode &optional show-only)
+ (ee-kill-buffers-in-modes (list majormode) show-only))
+
+(defun ee-kill-buffers-with-minor-mode (minormode &optional show-only)
+ (ee-kill-buffers (ee-buffers-with-minor-mode minormode) show-only))
+(defun ee-kill-buffers-with-name-matching (regexp &optional show-only)
+ (ee-kill-buffers (ee-buffers-with-name-matching regexp) show-only))
+
+
+;; «find-ebuffer-in-mode» (to ".find-ebuffer-in-mode")
+;; Some badly-behaved targets use this to select a target buffer.
+;; See the explanation for `eepitch-slime-select' in:
+;; (find-eepitch-intro "4.4. `eepitch-slime-select'")
+;;
(defun find-ebuffer-in-mode (majormode &rest pos-spec-list)
"Similar to `find-ebuffer', but goes to the only buffer in MAJORMODE.
If the number of buffers with major mode MAJORMODE is not exactly one,
@@ -1200,33 +1222,101 @@ raise an error."
majormode (or buffers 'none)))))
-;; «eepitch-gdb» (to ".eepitch-gdb")
-;; See: (find-eepitch-intro "4. Badly-behaved targets" "For example")
+;; «eepitch-b-gdb» (to ".eepitch-b-gdb")
+;; GDB is a simple badly-behaved target.
+;; A typical eepitch block for GDB looks like this:
+;;
+;; (eepitch-gdb-kill)
+;; (eepitch-b '(gdb "gdb -i=mi"))
+;; (eepitch-gdb-select)
+;;
+;; Compare that with the simpler example of eepitching to slime, in:
+;; (find-eepitch-intro "4. Badly-behaved targets")
+;;
(defun eepitch-gdb-kill ()
(ee-kill-buffers-in-mode 'gud-mode))
-(defun eepitch-gdb-start (command-line)
- (eepitch-set-source-and-M-x-b 1)
- (gdb command-line))
-
(defun eepitch-gdb-select ()
- (eepitch '(find-buffer-in-mode 'gud-mode)))
+ (eepitch '(find-ebuffer-in-mode 'gud-mode)))
+
-;; «eepitch-slime» (to ".eepitch-slime")
-;; See: (find-eev "eev-testblocks.el" "slime")
+;; «eepitch-b-slime» (to ".eepitch-b-slime")
+;; Slime is a very badly-behaved target.
+;; These are the functions specific to it.
+;; See: (find-eepitch-intro "4.1. Killing Slime")
+;; (find-eepitch-intro "4.4. `eepitch-slime-select'")
+;; (find-eev "eev-testblocks.el" "lisp-mode-slime")
+;; (find-eev "eev-testblocks.el" "lisp-mode-maxima-slime")
;;
-(defun eepitch-slime-kill ()
- (ee-kill-buffers-in-mode 'slime-repl-mode))
+(defun ee-slime-inferior-lisp-buffers ()
+ (cl-loop for b in (ee-buffers-with-name-matching "^\\*inferior-lisp")
+ if (with-current-buffer b slime-inferior-lisp-args)
+ collect b))
-(defun eepitch-slime-start (command)
- (eepitch-set-source-and-M-x-b 1)
- (slime command))
+(defun ee-slime-buffers ()
+ (append (ee-buffers-with-name-matching "^\\*slime-")
+ (ee-slime-inferior-lisp-buffers)))
+
+(defun eepitch-slime-kill (&optional show-only)
+ (ee-kill-buffers (ee-slime-buffers) show-only))
(defun eepitch-slime-select ()
(eepitch '(find-ebuffer-in-mode 'slime-repl-mode)))
+;; This is for when we have several buffers in `slime-repl-mode'.
+;; See: (find-eepitch-intro "4.5. Slime and Maxima")
+;; (find-eepitch-intro "4.6. The pkgbuffers")
+;; (find-eev "eev-testblocks.el" "lisp-mode-maxima-slime")
+;;
+(defvar eepitch-slime-pkgbuffers nil)
+
+(defun eepitch-slime-pkgbuffers ()
+ (cl-loop for b in (ee-buffers-in-mode 'slime-repl-mode)
+ for p = (with-current-buffer b (slime-current-package))
+ collect (cons p b)))
+
+(defun eepitch-slime-set-pkgbuffers ()
+ (setq eepitch-slime-pkgbuffers (eepitch-slime-pkgbuffers)))
+
+(defun eepitch-slime-get-pkgbuffer (pkg)
+ (alist-get pkg eepitch-slime-pkgbuffers nil nil 'equal))
+
+(defun eepitch-slime-select-pkgbuffer (pkg)
+ (eepitch-to-buffer (eepitch-slime-get-pkgbuffer pkg)))
+
+
+;; «eepitch-b-sly» (to ".eepitch-b-sly")
+;; This is similar to `eepitch-b-slime', for for Sly.
+;; See: (find-eev "eev-testblocks.el" "lisp-mode-sly")
+;;
+(defun eepitch-sly-kill (&optional show-only)
+ (ee-kill-buffers-with-name-matching "^\\*sly-" show-only))
+
+(defun eepitch-sly-select ()
+ (eepitch '(find-ebuffer-in-mode 'sly-mrepl-mode)))
+
+(defvar eepitch-sly-pkgbuffers nil)
+
+(defun eepitch-sly-pkgbuffers ()
+ (cl-loop for b in (ee-buffers-in-mode 'sly-mrepl-mode)
+ for p = (with-current-buffer b (sly-current-package))
+ collect (cons p b)))
+
+(defun eepitch-sly-set-pkgbuffers ()
+ (setq eepitch-sly-pkgbuffers (eepitch-sly-pkgbuffers)))
+
+(defun eepitch-sly-get-pkgbuffer (pkg)
+ (alist-get pkg eepitch-sly-pkgbuffers nil nil 'equal))
+
+(defun eepitch-sly-select-pkgbuffer (pkg)
+ (eepitch-to-buffer (eepitch-sly-get-pkgbuffer pkg)))
+
+
+
+
+
diff --git a/eev-elinks.el b/eev-elinks.el
index be8d97a2a6..d7c30a9783 100644
--- a/eev-elinks.el
+++ b/eev-elinks.el
@@ -1169,11 +1169,12 @@ when this is true remove the prefix D from FNAME, and
put the sexp
;;
;; Skel: (find-find-links-links-new "pdflike-page" "page bufname offset" "")
;;
-(defun find-pdflike-page-links (&optional page bufname offset &rest
pos-spec-list)
+(defun find-pdflike-page-links (&optional page bufname offset target &rest
pos-spec-list)
"Visit a temporary buffer containing hyperlinks to a pdf-like document.
See: (find-pdf-like-intro)
(find-pdf-like-intro \"refining hyperlinks to pages\")"
(interactive)
+ (if target (setq offset (- page target)))
(setq page (or page (ee-current-page)))
(setq bufname (or bufname (buffer-name)))
(setq offset (or offset ee-page-offset))
diff --git a/eev-intro.el b/eev-intro.el
index 70d22b92e7..d1a9f5ca08 100644
--- a/eev-intro.el
+++ b/eev-intro.el
@@ -19,7 +19,7 @@
;;
;; Author: Eduardo Ochs <[email protected]>
;; Maintainer: Eduardo Ochs <[email protected]>
-;; Version: 20250913
+;; Version: 20251121
;; Keywords: e-scripts
;;
;; Latest version: <http://anggtwu.net/eev-current/eev-intro.el>
@@ -6510,8 +6510,8 @@ scripts etc\]
;; (find-eev "eepitch.readme")
(defun find-eepitch-intro (&rest rest) (interactive)
- (let ((ee-buffer-name "*(find-eepitch-intro)*"))
- (apply 'find-eintro "\
+ (let ((ee-buffer-name "*(find-eepitch-intro)*"))
+ (apply 'find-eintro "\
\(Re)generate: (find-eepitch-intro)
Source code: (find-eev \"eev-intro.el\" \"find-eepitch-intro\")
More intros: (find-eev-quick-intro)
@@ -7157,65 +7157,294 @@ In an eepitch block like this one
the first two red star lines are typically only used when we want to kill
a current shell target - if it exists - and then create a new one.
-For \"badly-behaved targets\" - I will explain the term precisely in the
-next section - it is hard to define a function `eepitch-BBT' that would
-work well enough in an eepitch block like this one,
+For \"badly-behaved targets\" we may need eepitch blocks that are much
+more complex than that. For example, for Slime we need this:
- (eepitch-BBT)
- (eepitch-kill)
- (eepitch-BBT)
+ (eepitch-slime-kill 'show-only)
+ (eepitch-slime-kill)
+ (eepitch-b '(slime \"sbcl\"))
+ (eepitch-slime-select)
-and it is more practical to have a eepitch block with functions specific
-for the target BBT, like this one:
+I will explain its parts in the following subsections.
- (eepitch-BBT-kill)
- (eepitch-BBT-start)
- (eepitch-BBT-select)
-but sometimes it is better to replace the middle red star line by
-several lines, and make them remind us how to go back to our source
-buffer. For example, here,
- (eepitch-gdb-kill)
- To restart gdb:
- (eepitch-set-source-and-M-x-b 2)
- (gdb \"gdb -i=mi\")
- (eepitch-gdb-select)
+4.1. Killing Slime
+------------------
+Slime uses several buffers, and the best way to kill a previous instance
+of Slime is with:
-the sexp `(gdb \"gdb -i=mi\")' asks some questions, messes up our window
-configuration, and only leaves us at the target buffer after too many
-keystrokes.
+ (eepitch-slime-kill 'show-only)
+ (eepitch-slime-kill)
-Note that running the sexp `(eepitch-set-source-and-M-x-b 2)' prints
-instructions in the echo area - it says:
+The first `eepitch-slime-kill' above only shows the buffers that will,
+or would, be killed, by the second \"kill\". It shows something like
+this in the echo area:
- \"`M-x b' will set the eepitch target and return to
`*(find-eepitch-intro)*'\"
+ (Buffers that will be killed: (\"*slime-events*\"
+ \"*slime-repl sbcl*\" \"*inferior-lisp*\"))
-So after the `(gdb \"gdb -i=mi\")' finishes we need to run `M-x b'. The
-sexp `(eepitch-set-source-and-M-x-b 2)' has saved the source buffer -
-\"*(find-eepitch-intro)*\" - and the line that we need to return to,
-that is 2 lines below the `(eepitch-set-source-and-M-x-b 2)' itself;
-when we type `M-x b' Emacs interprets that as: this is the target
-<b>uffer - go <b>ack to the source <b>uffer and use this window setup:
+The second `eepitch-slime-kill' kills those buffers, but some of them
+issue messages when they die that take over the echo area. These
+messages are shown,
- _____________________
- | | |
- | source | target |
- | buffer | buffer |
- | | |
- |__________|__________|
+ (Buffers killed: (\"*slime-events*\" \"*slime-repl sbcl*\"
+ \"*slime-repl sbcl<2>*\" \"*inferior-lisp*\"))
+ Lisp connection closed unexpectedly: connection broken by remote peer
+but the user only sees the last one, with \"Lisp connection closed
+unexpectedly\". Adding this line
+ (eepitch-slime-kill 'show-only)
+makes things less mysterious, and less blackbox-ish.
-4.1. What are badly-behaved targets?
-------------------------------------
-(Examples: gdb, slime)
-(Compare with Sly)
-UNFINISHED!!!
-See: (find-eev \"eepitch.el\" \"badly-behaved\")
+4.2. `eepitch-b'
+----------------
+The third line of
+
+ (eepitch-slime-kill 'show-only)
+ (eepitch-slime-kill)
+ (eepitch-b '(slime \"sbcl\"))
+ (eepitch-slime-select)
+
+starts with an `eepitch-b', that is a variant of `eepitch' that we can
+use in cases in which it is so hard to detect automatically when the
+target is ready that it is better to leave that task to the user. In
+short...
+
+ `eepitch-b' considers that the target is ready
+ when the user types <f9>
+ to signal that the target is ready.
+
+`eepitch-b' was inspired by the difficulties of writing a function based
+on `eepitch-sly' that would work for Slime (Sly is a fork of Slime).
+
+Writing a `eepitch-sly' was very hard, because the plain `eepitch'
+expects \"shell-like programs\", and to make Sly behave like a
+shell-like program I had to find a way to \"wait for hooks\" in exactly
+the right way. The technical details are explained in these places:
+
+ (find-eev \"eepitch.el\" \"eepitch\" \"(defun eepitch \")
+ (find-eev \"eepitch.el\" \"wait-for-hooks\")
+
+Slime was much more badly-behaved than Sly. At some point I discovered -
+with a LOT of help - that I could consider that Sly \"was ready\" when
+it ran the hook `sly-mrepl-hook'... but with Slime I decided to delegate
+that to the user.
+
+The line
+
+ (eepitch-b '(slime \"sbcl\"))
+
+works like this: it saves the current position in these variables,
+
+ (find-evariable 'eepitch-b-source-buffer)
+ (find-evariable 'eepitch-b-source-marker)
+
+it runs `(slime \"sbcl\")', and it waits until the user types <f9>.
+When the user types <f9> this means:
+
+ 1) the user is saying that Slime is ready,
+ 2) the point is at the target buffer for eepitch, and
+ 3) now please rearrange the windows like this:
+
+ ____________________
+ | | |
+ | source | slime |
+ | buffer | mrepl |
+ | | |
+ |__________|_________|
+
+
+
+4.3. Configuring `eepitch-b'
+----------------------------
+At this moment eev doesn't bind <f9> by default.
+To bind it correctly you need the last block of:
+
+ (find-dot-emacs-links \"eev angges melpa epl eepitchb\")
+
+that contains these lines:
+
+ ;; See: (find-eepitch-intro \"4.3. Configuring `eepitch-b'\")
+ (define-key eev-mode-map (kbd \"<f9>\") 'eepitch-b-set-target-and-show)
+
+
+
+
+4.4. `eepitch-slime-select'
+---------------------------
+The last line of
+
+ (eepitch-slime-kill 'show-only)
+ (eepitch-slime-kill)
+ (eepitch-b '(slime \"sbcl\"))
+ (eepitch-slime-select)
+
+calls `eepitch-slime-select', whose definition is:
+
+ (defun eepitch-slime-select ()
+ (eepitch '(find-ebuffer-in-mode 'slime-repl-mode)))
+
+In most cases when we use Slime we are only interested in using one of
+its buffers as a target for eepitch. The name of that buffer is
+something like \"*slime-repl sbcl*\" or \"*slime-repl sbcl<n>*\", and it
+will be the only buffer whose major mode is `slime-repl-mode'. The
+
+ (find-ebuffer-in-mode 'slime-repl-mode)
+
+switches to that buffer if there is exactly one buffer with that major
+mode, and yields an error if there zero or two or more.
+
+
+
+
+4.5. Slime and Maxima
+---------------------
+My current favorite way of debugging Maxima with _Sly_ is with this,
+
+ (find-try-sly-intro \"8. Inspect Maxima with Sly\")
+
+that may need some updates, but it works, or used to work.
+
+My current favorite way of debugging Maxima with _Slime_ is with this -
+but I haven't documented its full setup yet:
+
+ (eepitch-slime-kill 'show-only)
+ (eepitch-slime-kill)
+ (eepitch-b '(slime \"sbcl\"))
+ (eepitch-slime-select)
+ (eepitch-maxima)
+ (eepitch-kill)
+ (eepitch-maxima)
+ load(\"startslime\");
+ (eepitch-b '(slime-connect \"localhost\" 4005))
+ (eepitch-slime-set-pkgbuffers)
+ (eepitch-slime-select-pkgbuffer \"MAXIMA\")
+ (eepitch-maxima)
+
+We understood its first four lines in the previous subsections. The
+following four lines,
+
+ (eepitch-maxima)
+ (eepitch-kill)
+ (eepitch-maxima)
+ load(\"startslime\");
+
+are easy to understand - it deletes a previous Maxima if there was one
+running, it creates a new Maxima, and it makes it load \"startslime\",
+that is this file:
+
+ (find-angg \".maxima/startslime.lisp\")
+
+Loading \"startslime\" takes several seconds, and we need to wait until
+it finishes before typing more <f8>s. At that point we have two
+interesting targets, and we can switch between them with:
+
+ (eepitch-slime-select)
+ (eepitch-maxima)
+
+The next line is
+
+ (eepitch-b '(slime-connect \"localhost\" 4005))
+
+and it will only work correctly if it is run after the
+
+ load(\"startslime\");
+
+has finished executing, because \"startslime\" makes Maxima listen on a
+certain port, and the `slime-connect' makes Slime connect to that port.
+
+When Slime connects to Maxima this won't work anymore,
+
+ (eepitch-slime-select)
+
+because now there are two buffers in `slime-repl-mode'. It aborts with
+this message:
+
+ Error - buffers with major mode slime-repl-mode:
+ (\"*slime-repl sbcl*\" \"*slime-repl sbcl<2>*\")
+
+
+
+
+4.6. The pkgbuffers
+-------------------
+These lines
+
+ (eepitch-slime-set-pkgbuffers)
+ (eepitch-slime-select-pkgbuffer \"MAXIMA\")
+
+let us handle several buffers in `slime-repl-mode'. The first line sets
+the variable `eepitch-slime-pkgbuffers' and shows its value in the echo
+area - it will be something like this:
+
+ ((\"COMMON-LISP-USER\" . \"*slime-repl sbcl*\")
+ (\"MAXIMA\" . \"*slime-repl sbcl<2>*\"))
+
+This variable lets us convert between the name of a Common Lisp package
+and the name of the Slime REPL buffer that was \"in that package\" when
+`eepitch-slime-set-pkgbuffers' was run. If you have everything working,
+you can try this
+
+ (eepitch-slime-select-pkgbuffer \"COMMON-LISP-USER\")
+*package*
+ (eepitch-slime-select-pkgbuffer \"MAXIMA\")
+*package*
+
+to start to understand the precise meaning of being \"in a certain
+package\".
+
+At that point we have three targets, and we can select between them
+with:
+
+ (eepitch-slime-select-pkgbuffer \"COMMON-LISP-USER\")
+ (eepitch-slime-select-pkgbuffer \"MAXIMA\")
+ (eepitch-maxima)
+
+
+
+
+4.7. Attention
+--------------
+My main motivation for writing the tools for creating eepitch blocks
+like this one
+
+ (eepitch-slime-kill 'show-only)
+ (eepitch-slime-kill)
+ (eepitch-b '(slime \"sbcl\"))
+ (eepitch-slime-select)
+ (eepitch-maxima)
+ (eepitch-kill)
+ (eepitch-maxima)
+ load(\"startslime\");
+ (eepitch-b '(slime-connect \"localhost\" 4005))
+ (eepitch-slime-set-pkgbuffers)
+ (eepitch-slime-select-pkgbuffer \"MAXIMA\")
+ (eepitch-maxima)
+
+is that I needed to have a way to restart some eepitch targets without
+paying much attention to the \"restarting\" part; I wanted to be able to
+keep my mind on, say, the Maxima functions that I am trying to
+understand and the tests that I am writing for them - and \"restarting
+Maxima and Slime\" should take very little of my attention.
+
+In the block above I only need to pay attention when I'm on the lines
+with `eepitch-b' and on the load(\"startslime\"); those lines require
+waiting until something is finished, and some of them require an <f9> or
+answering a yes-or-no prompt. The other lines only require <f8>s, and I
+can type those <f8>s at any speed.
+
+Before `eepitch-b' and the other techniques above a badly-behaved
+target, like Sly, would only become \"usable\" to me - in the sense of:
+\"restarting it takes very little attention\" - after hours or days of
+work, and, for some very badly-behaved targets, like, Slime, they would
+be unusable for me for years. With the techniques above they become
+usable, but with eepitch blocks that take _some_ attention - and I can
+start with that, and optimize their eepitch blocks gradually.
diff --git a/eev-load.el b/eev-load.el
index 10eed7fafd..8fa580c8d7 100644
--- a/eev-load.el
+++ b/eev-load.el
@@ -1,7 +1,7 @@
;;; eev-load.el -- load all the main modules of eev. -*- lexical-binding:
nil; -*-
;;; This can also be used as an index to the main source files.
-;; Copyright (C) 2019-2024 Free Software Foundation, Inc.
+;; Copyright (C) 2019-2025 Free Software Foundation, Inc.
;;
;; This file is part of GNU eev.
;;
@@ -20,7 +20,7 @@
;;
;; Author: Eduardo Ochs <[email protected]>
;; Maintainer: Eduardo Ochs <[email protected]>
-;; Version: 20240619
+;; Version: 20251122
;; Keywords: e-scripts
;;
;; Supersedes: (find-eev "eev-all.el")
@@ -195,14 +195,13 @@
;; See: (find-eev-levels-intro)
' (require 'eev-aliases) ; (find-eev "eev-aliases.el")
-;; This is experimental and very incomplete.
+;; These things are experimental and very incomplete.
;; See: (find-lean4-intro)
' (require 'eev-lean4) ; (find-eev "eev-lean4.el")
-
;; Make `M-x eev-beginner' work in the "expert setups" too.
;; See: (find-efunctiondescr 'autoload "If FUNCTION is already defined")
-(autoload 'eev-beginner "eev-beginner"
+(autoload 'eev-beginner "eev-beginner"
"Load all basic modules of eev, turn eev-mode on, and open a tutorial."
'interactive) ; (find-eev "eev-beginner.el")
diff --git a/eev-testblocks.el b/eev-testblocks.el
index 347b0961c9..0110d11c23 100644
--- a/eev-testblocks.el
+++ b/eev-testblocks.el
@@ -19,7 +19,7 @@
;;
;; Author: Eduardo Ochs <[email protected]>
;; Maintainer: Eduardo Ochs <[email protected]>
-;; Version: 20250913
+;; Version: 20251123
;; Keywords: e-scripts
;;
;; Latest version: <http://anggtwu.net/eev-current/eev-testblocks.el>
@@ -40,40 +40,43 @@
;; http://anggtwu.net/emacsconf2021.html
;; http://anggtwu.net/LATEX/2021emacsconf.pdf
-;; «.eeit» (to "eeit")
-;; «.ee-insert-test» (to "ee-insert-test")
-;; «.ee-insert-test-block» (to "ee-insert-test-block")
-;; «.examples» (to "examples")
-;; «.c-mode» (to "c-mode")
-;; «.elixir-mode» (to "elixir-mode")
-;; «.fennel-mode» (to "fennel-mode")
-;; «.f90-mode» (to "f90-mode")
-;; «.gnuplot-mode» (to "gnuplot-mode")
-;; «.haskell-mode» (to "haskell-mode")
-;; «.js-mode» (to "js-mode")
-;; «.julia-mode» (to "julia-mode")
-;; «.latex-mode» (to "latex-mode")
-;; «.lisp» (to "lisp")
-;; «.lisp-mode» (to "lisp-mode")
-;; «.slime» (to "slime")
-;; «.lua-mode» (to "lua-mode")
-;; «.makefile-gmake» (to "makefile-gmake")
-;; «.makefile-mode» (to "makefile-mode")
-;; «.maxima-mode» (to "maxima-mode")
-;; «.octave-mode» (to "octave-mode")
-;; «.org-mode» (to "org-mode")
-;; «.php-mode» (to "php-mode")
-;; «.python-mode» (to "python-mode")
-;; «.racket-mode» (to "racket-mode")
-;; «.raku-mode» (to "raku-mode")
-;; «.ruby-mode» (to "ruby-mode")
-;; «.scheme-mode» (to "scheme-mode")
-;; «.sml-mode» (to "sml-mode")
-;; «.sh-mode» (to "sh-mode")
-;; «.sql-mode» (to "sql-mode")
-;; «.subed-vtt-mode» (to "subed-vtt-mode")
-;; «.tcl-mode» (to "tcl-mode")
-;; «.tuareg-mode» (to "tuareg-mode")
+;; «.eeit» (to "eeit")
+;; «.ee-insert-test» (to "ee-insert-test")
+;; «.ee-insert-test-block» (to "ee-insert-test-block")
+;; «.examples» (to "examples")
+;; «.c-mode» (to "c-mode")
+;; «.elixir-mode» (to "elixir-mode")
+;; «.fennel-mode» (to "fennel-mode")
+;; «.f90-mode» (to "f90-mode")
+;; «.gnuplot-mode» (to "gnuplot-mode")
+;; «.haskell-mode» (to "haskell-mode")
+;; «.js-mode» (to "js-mode")
+;; «.julia-mode» (to "julia-mode")
+;; «.latex-mode» (to "latex-mode")
+;; «.lisp-mode» (to "lisp-mode")
+;; «.lisp-mode-slime» (to "lisp-mode-slime")
+;; «.lisp-mode-sly» (to "lisp-mode-sly")
+;; «.lisp-mode-maxima» (to "lisp-mode-maxima")
+;; «.lisp-mode-maxima-sly» (to "lisp-mode-maxima-sly")
+;; «.lisp-mode-maxima-slime» (to "lisp-mode-maxima-slime")
+;; «.lua-mode» (to "lua-mode")
+;; «.makefile-gmake» (to "makefile-gmake")
+;; «.makefile-mode» (to "makefile-mode")
+;; «.maxima-mode» (to "maxima-mode")
+;; «.octave-mode» (to "octave-mode")
+;; «.org-mode» (to "org-mode")
+;; «.php-mode» (to "php-mode")
+;; «.python-mode» (to "python-mode")
+;; «.racket-mode» (to "racket-mode")
+;; «.raku-mode» (to "raku-mode")
+;; «.ruby-mode» (to "ruby-mode")
+;; «.scheme-mode» (to "scheme-mode")
+;; «.sml-mode» (to "sml-mode")
+;; «.sh-mode» (to "sh-mode")
+;; «.sql-mode» (to "sql-mode")
+;; «.subed-vtt-mode» (to "subed-vtt-mode")
+;; «.tcl-mode» (to "tcl-mode")
+;; «.tuareg-mode» (to "tuareg-mode")
@@ -263,11 +266,22 @@ include(\"%s\")
))))
+
+
+
;; «lisp-mode» (to ".lisp-mode")
(defun ee-insert-test-lisp-mode ()
- (funcall (ee-intern "ee-insert-test-lisp-mode-%s" current-prefix-arg)))
+ "Insert a test block for Lisp. Use a numeric prefix to select a variant.
+With just `M-x eeit' run `ee-insert-test-lisp-mode-nil', that calls SBCL.
+With an invalid prefix, like `M-9 M-x eeit', display a help message."
+ (let ((f (ee-intern "ee-insert-test-lisp-mode-%s" current-prefix-arg)))
+ (if (fboundp f)
+ (funcall f)
+ (error "Valid prefixes: 1.slime, 2.sly, 3.maxima, 4.maxima+slime,
5.maxima+sly"))))
+
(defun ee-insert-test-lisp-mode-nil ()
+ "Use `M-x eeit' on a Lisp mode buffer to run this."
(interactive)
(insert (ee-adjust-red-stars (format "
#|
@@ -280,16 +294,16 @@ include(\"%s\")
" (buffer-name)))))
-;; «slime» (to ".slime")
-;; See: (find-eev "eepitch.el" "eepitch-slime")
+;; «lisp-mode-slime» (to ".lisp-mode-slime")
+;; See: (find-eev "eepitch.el" "eepitch-b-slime")
(defun ee-insert-test-lisp-mode-1 ()
+ "Use `M-1 M-x eeit' on a Lisp mode buffer to run this."
(interactive)
(insert (ee-adjust-red-stars (format "
#|
+ (eepitch-slime-kill 'show-only)
(eepitch-slime-kill)
- To restart Slime:
- (eepitch-set-source-and-M-x-b 2)
- (slime \"sbcl\")
+ (eepitch-b '(slime \"sbcl\"))
(eepitch-slime-select)
(load \"%s\")
@@ -297,8 +311,107 @@ include(\"%s\")
" (buffer-name)))))
+;; «lisp-mode-sly» (to ".lisp-mode-sly")
+;; See: (find-eev "eepitch.el" "eepitch-b-sly")
+(defun ee-insert-test-lisp-mode-2 ()
+ "Use `M-2 M-x eeit' on a Lisp mode buffer to run this."
+ (interactive)
+ (insert (ee-adjust-red-stars (format "
+#|
+ (eepitch-sly-kill 'show-only)
+ (eepitch-sly-kill)
+ (eepitch-sly)
+(load \"%s\")
+
+|#
+" (buffer-name)))))
+
+
+;; «lisp-mode-maxima» (to ".lisp-mode-maxima")
+(defun ee-insert-test-lisp-mode-3 ()
+ "Use `M-3 M-x eeit' on a Lisp mode buffer to run this."
+ (interactive)
+ (insert (ee-adjust-red-stars (format "
+#|
+ (eepitch-maxima)
+ (eepitch-kill)
+ (eepitch-maxima)
+load(\"%s\");
+to_lisp();
+ (load \"%s\")
+ (to-maxima)
+
+|#
+" (buffer-name) (buffer-name)))))
+
+
+;; «lisp-mode-maxima-slime» (to ".lisp-mode-maxima-slime")
+;; See: (find-eev "eepitch.el" "eepitch-b-slime")
+;; (find-angg ".maxima/startslime.lisp")
+(defun ee-insert-test-lisp-mode-4 ()
+ "Use `M-4 M-x eeit' on a Lisp mode buffer to run this."
+ (interactive)
+ (insert (ee-adjust-red-stars (format "
+#|
+ (eepitch-slime-kill 'show-only)
+ (eepitch-slime-kill)
+ (eepitch-b '(slime \"sbcl\"))
+ (eepitch-slime-select)
+ (eepitch-maxima)
+ (eepitch-kill)
+ (eepitch-maxima)
+ load(\"startslime\");
+ (eepitch-b '(slime-connect \"localhost\" 4005))
+ (eepitch-slime-set-pkgbuffers)
+ (eepitch-slime-select-pkgbuffer \"COMMON-LISP-USER\")
+ (eepitch-slime-select-pkgbuffer \"MAXIMA\")
+(load \"%s\")
+ (eepitch-maxima)
+load(\"%s\");
+
+|#
+" (buffer-name) (buffer-name)))))
+
+
+;; «lisp-mode-maxima-sly» (to ".lisp-mode-maxima-sly")
+;; See: (find-eev "eepitch.el" "eepitch-b-sly")
+;; (find-angg ".maxima/startsly.lisp")
+(defun ee-insert-test-lisp-mode-5 ()
+ "Use `M-5 M-x eeit' on a Lisp mode buffer to run this."
+ (interactive)
+ (insert (ee-adjust-red-stars (format "
+#|
+ (eepitch-sly-kill 'show-only)
+ (eepitch-sly-kill)
+ (eepitch-sly)
+ (eepitch-maxima)
+ (eepitch-kill)
+ (eepitch-maxima)
+ load(\"startsly\");
+ (eepitch-b '(sly-connect \"localhost\" 56789))
+ (eepitch-sly-set-pkgbuffers)
+ (eepitch-sly-select-pkgbuffer \"common-lisp-user\")
+ (eepitch-sly-select-pkgbuffer \"maxima\")
+(load \"%s\")
+ (eepitch-maxima)
+load(\"%s\");
+
+|#
+" (buffer-name) (buffer-name)))))
+
+
+
+
+
+
+
+
+
+
+
;; «lua-mode» (to ".lua-mode")
(defun ee-insert-test-lua-mode ()
+ "Insert a test block for Lua. With a numeric prefix N, use N `='s."
(interactive)
(let ((equals (make-string (or current-prefix-arg 0) ?=)))
(insert (ee-adjust-red-stars (format "
diff --git a/eev-tlinks.el b/eev-tlinks.el
index 320cfe8350..142f7e9595 100644
--- a/eev-tlinks.el
+++ b/eev-tlinks.el
@@ -19,7 +19,7 @@
;;
;; Author: Eduardo Ochs <[email protected]>
;; Maintainer: Eduardo Ochs <[email protected]>
-;; Version: 20250425
+;; Version: 20251022
;; Keywords: e-scripts
;;
;; Latest version: <http://anggtwu.net/eev-current/eev-tlinks.el>
@@ -3000,6 +3000,7 @@ wget -nc -O {yyyy}-{mm}-{dd}-emacs-news.org \\
;; (find-eepitch-intro \"3. Test blocks\")
;; (find-eepitch-intro \"3.1. `find-eeit-links'\")
;; (find-eev \"eev-testblocks.el\" \"examples\")
+;; (find-eev \"eev-testblocks.el\" \"{majormodestr}\")
;; Current definition:
;; (find-efunction '{eeitfun})
@@ -3969,6 +3970,7 @@ is nil, use the result of (ee-1stclassvideos)."
(find-dot-emacs-links "eev angges")
(find-dot-emacs-links "eev angges melpa edrxmaxima mfms")
(find-dot-emacs-links "eev angges melpa lean4 edrxmaxima mfms")
+ (find-dot-emacs-links "eev angges melpa epl eepitchb")
;; Convention: the first sexp always regenerates the buffer.
(find-efunction 'find-dot-emacs-links)
";;"
@@ -4056,6 +4058,12 @@ is nil, use the result of (ee-1stclassvideos)."
(replace-regexp-in-string eepitch-preprocess-regexp \"\" line))
")
+;; Test: (find-estring-elisp (ee-dot-emacs-eepitchb))
+(defun ee-dot-emacs-eepitchb (&rest rest) "\
+;; See: (find-eepitch-intro \"4.3. Configuring `eepitch-b'\")
+(define-key eev-mode-map (kbd \"<f9>\") 'eepitch-b-set-target-and-show)
+")
+
;; Test: (find-estring-elisp (ee-dot-emacs-lean4))
(defun ee-dot-emacs-lean4 (&rest rest) "\
;; See: (find-lean4-intro)
@@ -4379,6 +4387,9 @@ import {pkg_}
dist = importlib.metadata.distribution('{distr}')
dist = importlib.metadata.distribution('{pkg}')
pprint.pprint(dist.files)
+dist.files[1]
+dist.files[1].locate()
+
md = dist.metadata
print('\\n'.join(md.keys()))
md['Name']
diff --git a/eev.el b/eev.el
index 9d76d1b912..01d3169e82 100644
--- a/eev.el
+++ b/eev.el
@@ -6,7 +6,7 @@
;; Package-Requires: ((emacs "25.1"))
;; Keywords: lisp e-scripts
;; URL: http://anggtwu.net/#eev
-;; Version: 20250914
+;; Version: 20251123
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by