aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-25 15:11:04 -0500
committerCraig Jennings <c@cjennings.net>2026-05-25 15:11:04 -0500
commitb007a9b808d3cf360d03f3d7597119c2fe2cd6e8 (patch)
tree0a890f19804f7e9ed01d682d4855689432c2a147
parentfda029f8f96d9ce7ac8276ceb3475cd9a3bbe797 (diff)
downloaddotemacs-b007a9b808d3cf360d03f3d7597119c2fe2cd6e8.tar.gz
dotemacs-b007a9b808d3cf360d03f3d7597119c2fe2cd6e8.zip
fix(latex): make PDF-viewer selection idempotent
cj/--latex-select-pdf-viewer runs on every LaTeX-mode buffer and was blindly pushing an (output-pdf VIEWER) entry onto TeX-view-program-selection, so each LaTeX buffer opened in a session stacked another duplicate. The head still won, so the viewer worked, but the list grew and the docstring's idempotency claim was false. I drop any existing output-pdf entry before consing the chosen viewer, which also makes "wins over any default" actually true. Added a test file (the module had none) covering selection, preference order, the PDF-Tools fallback, idempotency, and default override, with executable-find mocked so the run doesn't depend on which viewers are installed.
-rw-r--r--modules/latex-config.el13
-rw-r--r--tests/test-latex-config.el84
2 files changed, 92 insertions, 5 deletions
diff --git a/modules/latex-config.el b/modules/latex-config.el
index 8636e2cd..0db21f2f 100644
--- a/modules/latex-config.el
+++ b/modules/latex-config.el
@@ -44,15 +44,18 @@
(declare-function pdf-view-mode "pdf-view")
(defun cj/--latex-select-pdf-viewer ()
- "Push the first available external PDF viewer onto `TeX-view-program-selection'.
-Falls back to PDF Tools when no external viewer is on PATH. The new
-selection is consed onto the head of the alist so it wins over any
-default. Idempotent: re-running picks the same viewer."
+ "Select the first available external PDF viewer for `output-pdf'.
+Falls back to PDF Tools when no external viewer is on PATH. Any existing
+`output-pdf' entry is dropped first, then the chosen viewer is consed onto
+the head so it wins over any default. Idempotent: re-running leaves a
+single entry."
(let* ((found (cl-find-if (lambda (entry)
(executable-find (car entry)))
cj/--latex-pdf-viewer-candidates))
(viewer-name (if found (cdr found) "PDF Tools")))
- (push (list 'output-pdf viewer-name) TeX-view-program-selection)))
+ (setq TeX-view-program-selection
+ (cons (list 'output-pdf viewer-name)
+ (assq-delete-all 'output-pdf TeX-view-program-selection)))))
;; ----------------------------- Auctex And Related ----------------------------
diff --git a/tests/test-latex-config.el b/tests/test-latex-config.el
new file mode 100644
index 00000000..cb8aaeca
--- /dev/null
+++ b/tests/test-latex-config.el
@@ -0,0 +1,84 @@
+;;; test-latex-config.el --- Tests for latex-config PDF viewer selection -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; latex-config picks the first available external PDF viewer and pushes it
+;; onto `TeX-view-program-selection' for output-pdf. These tests exercise
+;; `cj/--latex-select-pdf-viewer' with `executable-find' mocked, so they don't
+;; depend on which viewers happen to be installed on the test machine.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Declare special so the let-bindings below are dynamic, matching the global
+;; valued `defvar' AUCTeX's tex.el provides at runtime. Without it the module's
+;; file-local `(defvar TeX-view-program-selection)' does not reach a test's let.
+(defvar TeX-view-program-selection nil)
+
+(require 'latex-config)
+
+(defun test-latex--with-available (available thunk)
+ "Run THUNK with `executable-find' returning non-nil only for AVAILABLE names."
+ (cl-letf (((symbol-function 'executable-find)
+ (lambda (cmd &rest _)
+ (and (member cmd available) (concat "/usr/bin/" cmd)))))
+ (funcall thunk)))
+
+(ert-deftest test-latex-config-select-pdf-viewer-picks-available ()
+ "Normal: selects the available external viewer for output-pdf."
+ (let ((TeX-view-program-selection nil))
+ (test-latex--with-available
+ '("zathura")
+ (lambda () (cj/--latex-select-pdf-viewer)))
+ (should (equal (assq 'output-pdf TeX-view-program-selection)
+ '(output-pdf "Zathura")))))
+
+(ert-deftest test-latex-config-select-pdf-viewer-honors-preference-order ()
+ "Normal: picks the first candidate in preference order when several exist."
+ (let ((TeX-view-program-selection nil))
+ ;; both available; evince precedes okular in the candidate list
+ (test-latex--with-available
+ '("okular" "evince")
+ (lambda () (cj/--latex-select-pdf-viewer)))
+ (should (equal (assq 'output-pdf TeX-view-program-selection)
+ '(output-pdf "Evince")))))
+
+(ert-deftest test-latex-config-select-pdf-viewer-falls-back-to-pdf-tools ()
+ "Boundary: no external viewer on PATH falls back to PDF Tools."
+ (let ((TeX-view-program-selection nil))
+ (test-latex--with-available
+ '()
+ (lambda () (cj/--latex-select-pdf-viewer)))
+ (should (equal (assq 'output-pdf TeX-view-program-selection)
+ '(output-pdf "PDF Tools")))))
+
+(ert-deftest test-latex-config-select-pdf-viewer-idempotent ()
+ "Boundary: re-running leaves exactly one output-pdf entry."
+ (let ((TeX-view-program-selection nil))
+ (test-latex--with-available
+ '("zathura")
+ (lambda ()
+ (cj/--latex-select-pdf-viewer)
+ (cj/--latex-select-pdf-viewer)
+ (cj/--latex-select-pdf-viewer)))
+ (should (= 1 (cl-count 'output-pdf TeX-view-program-selection :key #'car)))
+ (should (equal (assq 'output-pdf TeX-view-program-selection)
+ '(output-pdf "Zathura")))))
+
+(ert-deftest test-latex-config-select-pdf-viewer-overrides-existing-default ()
+ "Boundary: an existing output-pdf default is replaced, not stacked."
+ (let ((TeX-view-program-selection '((output-pdf "Evince") (output-dvi "xdvi"))))
+ (test-latex--with-available
+ '("zathura")
+ (lambda () (cj/--latex-select-pdf-viewer)))
+ (should (= 1 (cl-count 'output-pdf TeX-view-program-selection :key #'car)))
+ (should (equal (assq 'output-pdf TeX-view-program-selection)
+ '(output-pdf "Zathura")))
+ ;; an unrelated output type survives the replacement
+ (should (equal (assq 'output-dvi TeX-view-program-selection)
+ '(output-dvi "xdvi")))))
+
+(provide 'test-latex-config)
+;;; test-latex-config.el ends here