diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-01 14:02:33 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-01 14:02:33 -0400 |
| commit | 35cbde39ffd459593589b36318a6bc2962703599 (patch) | |
| tree | e7d3c8baebc9f19f3f8ca3b1067d48f22213e80f | |
| parent | e46376a2d9263ba9c8ef26891de44bd5920bd4a9 (diff) | |
| download | dotemacs-35cbde39ffd459593589b36318a6bc2962703599.tar.gz dotemacs-35cbde39ffd459593589b36318a6bc2962703599.zip | |
fix(eat): guard against a nil charset wedging the terminal
EAT 0.9.4's parser accepts more charset-designation final bytes than its store step maps. A designation like ESC ( A (UK) isn't one of the two it handles ("0" and "B"), so it stores nil as that slot's charset. The next character written then fails (cl-assert charset) in eat--t-write. Since writes run off the output-queue timer, it repeats once per output chunk. An agent terminal that emits one of these bytes throws "cl-assertion-failed (charset)" hundreds of times and stops rendering.
I added filter-args advice on eat--t-set-charset that coerces a nil charset to us-ascii before it's stored, so an unmapped designation falls back to plain ASCII instead of wedging. Patching the vendored pcase would be cleaner, but a package update reverts it. The advice loads with eat, since the target is an internal function.
| -rw-r--r-- | modules/eat-config.el | 27 | ||||
| -rw-r--r-- | tests/test-eat-config--charset-never-nil.el | 43 |
2 files changed, 70 insertions, 0 deletions
diff --git a/modules/eat-config.el b/modules/eat-config.el index 1de24dc4..d66507c4 100644 --- a/modules/eat-config.el +++ b/modules/eat-config.el @@ -83,6 +83,33 @@ ARGS is (TERMINAL OUTPUT)." (advice-add 'eat-term-process-output :filter-args #'cj/--eat-reset-sgr-at-newline) +;; EAT 0.9.4 charset-designation bug. Its parser (eat--t-handle-output) accepts +;; a wide set of final bytes for an `ESC ( x' charset designation, but the store +;; step (eat--t-set-charset) only maps two of them: "0" -> dec-line-drawing and +;; "B" -> us-ascii. Any other accepted byte (e.g. ESC ( A, the UK set) falls +;; through the `pcase' to nil, and nil gets stored as that slot's charset. The +;; next character written then trips (cl-assert charset) in eat--t-write, and +;; because writes are driven by the eat--process-output-queue timer it errors +;; once per output chunk -- the "cl-assertion-failed (charset) [N times]" storm. +;; We can't patch the vendored pcase without a fork the next package update would +;; revert, so guard the one function that injects the nil: coerce a nil charset +;; to us-ascii (the safe default the byte would have mapped to) before it lands. + +(declare-function eat--t-set-charset "eat") + +(defun cj/--eat-charset-never-nil (args) + "`:filter-args' advice for `eat--t-set-charset'. +ARGS is (SLOT CHARSET). Return it with a nil CHARSET replaced by +`us-ascii', so EAT never stores nil for a charset designation it does +not recognize (which would later trip (cl-assert charset) on write)." + (list (car args) (or (cadr args) 'us-ascii))) + +;; eat--t-set-charset is an internal function defined only once eat.el loads +;; (the package is deferred), so add the advice after load rather than at top +;; level. +(with-eval-after-load 'eat + (advice-add 'eat--t-set-charset :filter-args #'cj/--eat-charset-never-nil)) + ;; ------------------------------- eat package --------------------------------- (use-package eat diff --git a/tests/test-eat-config--charset-never-nil.el b/tests/test-eat-config--charset-never-nil.el new file mode 100644 index 00000000..dfd38cb5 --- /dev/null +++ b/tests/test-eat-config--charset-never-nil.el @@ -0,0 +1,43 @@ +;;; test-eat-config--charset-never-nil.el --- Tests for the EAT charset nil-guard -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for `cj/--eat-charset-never-nil', the `:filter-args' guard on +;; `eat--t-set-charset'. eat 0.9.4 accepts more charset-designation final +;; bytes in its parser than its store step maps, so an unmapped designation +;; (e.g. ESC ( A) stores nil as that slot's charset; the next character write +;; then trips (cl-assert charset) in `eat--t-write', repeatedly, via the +;; output-queue timer. The guard coerces a nil charset to `us-ascii' before +;; it is ever stored, so the assertion can't fire. + +;;; Code: + +(require 'ert) + +;; Stub keymap dep before loading the module (matches the other module tests). +(defvar cj/custom-keymap (make-sparse-keymap) + "Stub keymap for testing.") + +(require 'eat-config) + +(ert-deftest test-eat-config-charset-never-nil-normal-real-charset-unchanged () + "Normal: a real charset value passes through unchanged." + (should (equal (cj/--eat-charset-never-nil '(g0 us-ascii)) + '(g0 us-ascii))) + (should (equal (cj/--eat-charset-never-nil '(g1 dec-line-drawing)) + '(g1 dec-line-drawing)))) + +(ert-deftest test-eat-config-charset-never-nil-error-nil-becomes-us-ascii () + "Error: a nil charset (unmapped designation) is coerced to `us-ascii'." + (should (equal (cj/--eat-charset-never-nil '(g0 nil)) + '(g0 us-ascii))) + (should (equal (cj/--eat-charset-never-nil '(g3 nil)) + '(g3 us-ascii)))) + +(ert-deftest test-eat-config-charset-never-nil-boundary-preserves-slot () + "Boundary: the slot symbol is preserved for every slot, coerced or not." + (dolist (slot '(g0 g1 g2 g3)) + (should (eq (car (cj/--eat-charset-never-nil (list slot nil))) slot)) + (should (eq (cadr (cj/--eat-charset-never-nil (list slot nil))) 'us-ascii)))) + +(provide 'test-eat-config--charset-never-nil) +;;; test-eat-config--charset-never-nil.el ends here |
