diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-13 15:25:49 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-13 15:25:49 -0500 |
| commit | 20b251981bf01dbb25f494206de046023648252f (patch) | |
| tree | b77b177db595e4b22357cc14ad640c90ecde47a7 | |
| parent | 22c1e2816a9100e7fcb1e8a205efec727d68fcb9 (diff) | |
| download | dotemacs-20b251981bf01dbb25f494206de046023648252f.tar.gz dotemacs-20b251981bf01dbb25f494206de046023648252f.zip | |
fix(help-config): non-destructive info open, clean cancel, drop dead config
Three audit defects in one file. cj/open-with-info-mode used cl-return-from inside a plain defun, so declining the save prompt threw "No catch for tag" instead of cancelling. The decision is now a pure cj/--info-open-plan and the command routes through it. A dead :hook (info-mode . info-persist-history-mode, which names a non-existent mode on the wrong hook) and an empty :preface are gone. The auto-mode-alist entry that mapped .info to that interactive, buffer-killing command is dropped, so find-file-noselect of a .info no longer destroys buffers. cj/open-with-info-mode stays an M-x command and C-h i still browses info files.
| -rw-r--r-- | modules/help-config.el | 49 | ||||
| -rw-r--r-- | tests/test-help-config.el | 32 | ||||
| -rw-r--r-- | todo.org | 10 |
3 files changed, 68 insertions, 23 deletions
diff --git a/modules/help-config.el b/modules/help-config.el index ce9fd861..df27cbea 100644 --- a/modules/help-config.el +++ b/modules/help-config.el @@ -50,24 +50,34 @@ ;; ------------------------------------ Info ----------------------------------- - (defun cj/open-with-info-mode () - "Open the current buffer's file in Info mode if it's a valid info file. +(defun cj/--info-open-plan (modified-p save-confirmed-p) + "Decide how to open a buffer in Info given its MODIFIED-P state. +SAVE-CONFIRMED-P is the answer to the save prompt, meaningful only when +MODIFIED-P. Returns `open', `save-then-open', or `cancel'." + (cond ((not modified-p) 'open) + (save-confirmed-p 'save-then-open) + (t 'cancel))) + +(defun cj/open-with-info-mode () + "Open the current buffer's file in Info mode if it's a valid info file. Preserves any unsaved changes and checks if the file exists." - (interactive) - (let ((file-name (buffer-file-name))) - (when file-name - (if (and (file-exists-p file-name) - (string-match-p "\\.info\\'" file-name)) - (progn - (when (buffer-modified-p) - (if (y-or-n-p "Buffer has unsaved changes. Save before opening in Info? ") - (save-buffer) - (message "Operation canceled") - (cl-return-from cj/open-with-info-mode))) - (kill-buffer (current-buffer)) - (info file-name)) - (message "Not a valid info file: %s" file-name))))) + (interactive) + (let ((file-name (buffer-file-name))) + (when file-name + (if (and (file-exists-p file-name) + (string-match-p "\\.info\\'" file-name)) + (let ((modified (buffer-modified-p))) + (pcase (cj/--info-open-plan + modified + (and modified + (y-or-n-p "Buffer has unsaved changes. Save before opening in Info? "))) + ('cancel (message "Operation canceled")) + (plan + (when (eq plan 'save-then-open) (save-buffer)) + (kill-buffer (current-buffer)) + (info file-name)))) + (message "Not a valid info file: %s" file-name))))) (defun cj/browse-info-files () "Browse and open .info or .info.gz files from user-emacs-directory." @@ -96,7 +106,6 @@ Preserves any unsaved changes and checks if the file exists." (:map Info-mode-map ("m" . bookmark-set) ;; Rebind 'm' from Info-menu to bookmark-set ("M" . Info-menu)) ;; Move Info-menu to 'M' instead - :preface :init ;; Add personal info files BEFORE Info mode initializes ;; (let ((personal-info-dir (expand-file-name "assets/info" user-emacs-directory))) @@ -104,11 +113,7 @@ Preserves any unsaved changes and checks if the file exists." ;; (setq Info-directory-list (list personal-info-dir)))) ;; the above makes the directory the info list. the below adds it to the default list ;; (add-to-list 'Info-default-directory-list personal-info-dir))) - :hook - (info-mode . info-persist-history-mode) - :config - ;; Make .info files open with our custom function - (add-to-list 'auto-mode-alist '("\\.info\\'" . cj/open-with-info-mode))) + ) (provide 'help-config) ;;; help-config.el ends here. diff --git a/tests/test-help-config.el b/tests/test-help-config.el new file mode 100644 index 00000000..0ba95c41 --- /dev/null +++ b/tests/test-help-config.el @@ -0,0 +1,32 @@ +;;; test-help-config.el --- Tests for the Info-open decision logic -*- lexical-binding: t; -*- + +;;; Commentary: +;; cj/open-with-info-mode opens the current .info buffer in Info, prompting to +;; save first if the buffer is modified. The save/cancel/open decision is +;; factored into the pure helper `cj/--info-open-plan' so it's testable without +;; driving find-file, Info, or the save prompt. Declining the prompt must yield +;; `cancel' -- the original cl-return-from inside a plain defun signalled +;; "No catch for tag" instead of cancelling. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'help-config) + +(ert-deftest test-info-open-plan-unmodified-opens () + "Normal: an unmodified buffer opens in Info directly." + (should (eq (cj/--info-open-plan nil nil) 'open))) + +(ert-deftest test-info-open-plan-modified-confirmed-saves-then-opens () + "Normal: a modified buffer whose save is confirmed saves, then opens." + (should (eq (cj/--info-open-plan t t) 'save-then-open))) + +(ert-deftest test-info-open-plan-modified-declined-cancels () + "Error/edge: a modified buffer whose save is declined cancels -- the path that +used to signal \"No catch for tag\" via cl-return-from in a plain defun." + (should (eq (cj/--info-open-plan t nil) 'cancel))) + +(provide 'test-help-config) +;;; test-help-config.el ends here @@ -948,11 +948,13 @@ Nothing requires =modules/ledger-config.el= (verified by grep), so .dat/.ledger/ ** TODO [#B] eww quick-add bookmarks split the store and break the default file :bug:quick:solo: =modules/eww-config.el:116-126= — quick-add let-binds =eww-bookmarks-directory= to ~/.emacs.d/eww-bookmarks/ (creating a DIRECTORY at the path where the daemon's default store expects a FILE ~/.emacs.d/eww-bookmarks). After one quick-add, B reads an unreadable path and quick-added bookmarks are invisible post-restart. Drop the let-binding or setq the directory once in :config so both commands share one store. From the 2026-06 config audit. -** DOING [#B] help-config: three defects in one small file :bug:quick:solo: +** DONE [#B] help-config: three defects in one small file :bug:quick:solo: +CLOSED: [2026-06-13 Sat] From the 2026-06 config audit, =modules/help-config.el=: - =:67= — =cl-return-from= inside a plain =defun= (no cl-block): declining the save prompt signals "No catch for tag" instead of canceling. =cl-defun= or restructure. - =:108= — =:hook (info-mode . info-persist-history-mode)= is dead twice: Info's hook is =Info-mode-hook= (capital I), and =info-persist-history-mode= doesn't exist anywhere. Implement the intent or delete. - =:111= — auto-mode-alist maps .info to an interactive command that KILLS the buffer mid find-file — programmatic =find-file-noselect= of any .info destroys buffers and pops Info windows. Drop the entry; keep the explicit command. Zero test coverage on this module (the two broken paths are exactly the untested ones). +Fixed 2026-06-13: (1) extracted the save/cancel/open decision into a pure =cj/--info-open-plan= and routed =cj/open-with-info-mode= through it — no more =cl-return-from=, declining cancels cleanly; (2) deleted the dead =:hook= and the empty =:preface=; (3) dropped the destructive =auto-mode-alist= .info entry (kept =cj/open-with-info-mode= as an M-x command and =cj/browse-info-files= on C-h i). New test-help-config.el covers the planner (open / save-then-open / cancel) — 3 green; module loads clean. Stale daemon state (the .info auto-mode entry + the bogus info-mode-hook entry) cleared by hand so the running session is correct without a restart. Interactive Info open + find-file-no-longer-destructive are a VERIFY. ** TODO [#B] modeline runs synchronous git on the redisplay path, unguarded :bug:solo: =modules/modeline-config.el:173,154,145= — the mode-line :eval calls vc-backend/vc-state/vc-working-revision (synchronous git) on TTL expiry; a slow or unmounted filesystem stalls ALL redisplay. The cache key computes =file-truename= on every render (the "one stat per refresh" comment is wrong), and nothing is condition-case-wrapped, so a signal lands inside the mode-line eval. Defer the truename behind the TTL check; wrap the fetch in condition-case caching nil. From the 2026-06 config audit. @@ -4350,6 +4352,12 @@ From the 2026-06-11 messenger-unification brainstorm. Google Voice has no offici ** TODO Manual testing and validation Exercised once the phases above land. +*** VERIFY info-mode open is non-destructive and cancels cleanly +What we're verifying: opening a .info file no longer auto-kills the buffer, and the explicit cj/open-with-info-mode prompt cancels cleanly on decline. Fixed in modules/help-config.el; stale daemon state already cleared, so this also survives a fresh restart. +- find-file a .info file (e.g. one under elpa) — it should open as an ordinary buffer, not vanish into Info +- In that buffer, edit something, then M-x cj/open-with-info-mode; at the save prompt answer no +- Repeat M-x cj/open-with-info-mode on an unmodified .info buffer +Expected: find-file leaves the buffer intact (no auto-kill); declining the save prompt prints "Operation canceled" with no "No catch for tag" error; on an unmodified buffer it opens the file in Info. *** VERIFY dwim-shell zip/backup/menu-key behave What we're verifying: single-file zip makes a valid <name>.zip, the dated backup gets a real timestamp, and the dwim-shell menu is reachable on M-D in plain dired. Fixed in modules/dwim-shell-config.el, reloaded into the daemon. - In dired, mark a single file, run the dwim-shell menu (M-D), pick Zip |
