aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-13 15:25:49 -0500
committerCraig Jennings <c@cjennings.net>2026-06-13 15:25:49 -0500
commitdba2e66ff74bee5cc3170ce096da92a73b762ddb (patch)
treecc42cefb1b3192675caf75ab1cd76438e664f424
parent75a94dae562a7e5ab7b93e9cd1f66146ab47daaa (diff)
downloaddotemacs-dba2e66ff74bee5cc3170ce096da92a73b762ddb.tar.gz
dotemacs-dba2e66ff74bee5cc3170ce096da92a73b762ddb.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.el49
-rw-r--r--tests/test-help-config.el32
2 files changed, 59 insertions, 22 deletions
diff --git a/modules/help-config.el b/modules/help-config.el
index ce9fd8614..df27cbea9 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 000000000..0ba95c410
--- /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