diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-15 15:10:19 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-15 15:10:19 -0500 |
| commit | 74cffcac0c9bb8e4a7ee33d51ff1f2cd9a2cd7f0 (patch) | |
| tree | 9f15fd69ddac57a6ec7476fac08938d6495a693a | |
| parent | 45cdc8f41fdf337dfd8dd82f278c4c55654cf83e (diff) | |
| download | dotemacs-74cffcac0c9bb8e4a7ee33d51ff1f2cd9a2cd7f0.tar.gz dotemacs-74cffcac0c9bb8e4a7ee33d51ff1f2cd9a2cd7f0.zip | |
feat(yas): activate yasnippet globally with fundamental-mode extras
The yasnippet use-package block switches from `:hook (prog-mode . yas-minor-mode)` to `:demand t` + `(yas-global-mode 1)`. That makes yas-minor-mode active in every buffer, not just prog-mode-derived ones.
I added a small helper, `cj/--yas-activate-fundamental-extras`, attached as `:hook (yas-minor-mode . ...)`. It calls `(yas-activate-extra-mode 'fundamental-mode)` so the snippet table at `snippets/fundamental-mode/` is consulted in every buffer regardless of the buffer's own major mode. That's what makes universal triggers like `<cj` work everywhere.
The new `tests/test-prog-general-yas-activation.el` covers both wiring (yas-global-mode on, fundamental-mode in yas-extra-modes, yas-minor-mode active in org/text buffers) and end-to-end expansion (the marker snippet expands correctly in fundamental, text, org, emacs-lisp, and python-ts modes). 9 tests, all green; full unit suite green with no regressions.
| -rw-r--r-- | modules/prog-general.el | 14 | ||||
| -rw-r--r-- | tests/test-prog-general-yas-activation.el | 130 |
2 files changed, 141 insertions, 3 deletions
diff --git a/modules/prog-general.el b/modules/prog-general.el index 45b83ca7..e7dd4989 100644 --- a/modules/prog-general.el +++ b/modules/prog-general.el @@ -248,15 +248,23 @@ If no such file exists there, display a message." ;; ---------------------------------- Snippets --------------------------------- ;; reusable code and text +(defun cj/--yas-activate-fundamental-extras () + "Activate `fundamental-mode' as an extra yasnippet mode in this buffer. +Hooked onto `yas-minor-mode-hook' so every buffer also consults +`snippets/fundamental-mode/' regardless of the buffer's own major mode. +This is what makes universal snippets like =<cj= work in any buffer." + (yas-activate-extra-mode 'fundamental-mode)) + (use-package yasnippet - :commands (yas-new-snippet yas-visit-snippet-file yas-global-mode) - :hook (prog-mode . yas-minor-mode) + :demand t :bind ("C-c s n" . yas-new-snippet) ("C-c s e" . yas-visit-snippet-file) + :hook (yas-minor-mode . cj/--yas-activate-fundamental-extras) :config (setq yas-snippet-dirs (list snippets-dir)) - (yas-reload-all)) + (yas-reload-all) + (yas-global-mode 1)) ;; --------------------- Display Color On Color Declaration -------------------- ;; display the actual color as highlight to color hex code diff --git a/tests/test-prog-general-yas-activation.el b/tests/test-prog-general-yas-activation.el new file mode 100644 index 00000000..d6ea42cd --- /dev/null +++ b/tests/test-prog-general-yas-activation.el @@ -0,0 +1,130 @@ +;;; test-prog-general-yas-activation.el --- Tests for universal yasnippet activation -*- lexical-binding: t; -*- + +;;; Commentary: +;; Covers the universal-yasnippet wiring in prog-general.el: +;; +;; Wiring: +;; - yas-global-mode is enabled at module-load time (not only in prog-mode). +;; - yas-snippet-dirs points at the repo's snippets/ directory. +;; - yas-minor-mode-hook activates fundamental-mode as an extra mode in +;; every buffer so the universal snippet table is always consulted. +;; +;; Expansion (the user-visible verification): +;; - The <cj snippet from snippets/fundamental-mode/ expands to the literal +;; marker block "#+begin_src cj: comment\n\n#+end_src" in any major mode, +;; verified across fundamental, text, org, emacs-lisp, and python-ts. + +;;; Code: + +(when noninteractive + (package-initialize)) + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'user-constants) +(require 'prog-general) + +(require 'yasnippet) + +;;; Wiring tests + +(ert-deftest test-prog-general-yas-global-mode-enabled () + "Normal: yas-global-mode is on after prog-general loads." + (should (bound-and-true-p yas-global-mode))) + +(ert-deftest test-prog-general-yas-snippet-dirs-set () + "Normal: yas-snippet-dirs contains the repo's snippets/ directory." + (should (cl-some (lambda (d) + (string= (file-name-as-directory (expand-file-name d)) + (file-name-as-directory (expand-file-name snippets-dir)))) + yas-snippet-dirs))) + +(defmacro test-prog-general--with-named-buffer (mode-form &rest body) + "Run BODY in a fresh, non-temp buffer set to the major mode of MODE-FORM. +Uses a generated name without a leading space so `yas-global-mode' doesn't +skip the buffer the way it skips `with-temp-buffer' buffers (which start +with a space and are excluded by globalized-minor-mode defaults). The +buffer is killed in `unwind-protect' regardless of BODY outcome." + (declare (indent 1)) + `(let ((buf (generate-new-buffer "yas-activation-test"))) + (unwind-protect + (with-current-buffer buf + ,mode-form + ,@body) + (kill-buffer buf)))) + +(ert-deftest test-prog-general-yas-extra-mode-fundamental-in-text-buffer () + "Normal: a fresh text-mode buffer with yas-minor-mode on has +fundamental-mode in yas-extra-modes (proves the hook fired)." + (test-prog-general--with-named-buffer + (dlet ((text-mode-hook nil)) (text-mode)) + (should (bound-and-true-p yas-minor-mode)) + (should (memq 'fundamental-mode yas-extra-modes)))) + +(ert-deftest test-prog-general-yas-minor-mode-in-org-buffer () + "Boundary: yas-minor-mode is active in an org-mode buffer +(not just in prog-mode-derived buffers)." + (test-prog-general--with-named-buffer + (dlet ((org-mode-hook nil)) (org-mode)) + (should (bound-and-true-p yas-minor-mode)))) + +;;; Expansion tests — the user-visible verification + +(defconst test-prog-general--cj-expected + "#+begin_src cj: comment\n\n#+end_src" + "Exact expansion of the <cj snippet from snippets/fundamental-mode/.") + +(defun test-prog-general--expand-cj-in-mode (mode-fn) + "Insert <cj into a buffer of MODE-FN, expand via yas, return the buffer string. +Silences mode hooks to keep test load light. `dlet' is used in place of +`let' because some of these hook vars are declared lexical elsewhere +under `lexical-binding: t', and a plain `let' would error with +\"Defining as dynamic an already lexical var\"." + (with-temp-buffer + (dlet ((emacs-lisp-mode-hook nil) + (org-mode-hook nil) + (text-mode-hook nil) + (python-mode-hook nil) + (python-ts-mode-hook nil) + (prog-mode-hook nil)) + (funcall mode-fn)) + (unless (bound-and-true-p yas-minor-mode) + (yas-minor-mode 1)) + (insert "<cj") + (yas-expand) + (buffer-substring-no-properties (point-min) (point-max)))) + +(ert-deftest test-prog-general-cj-expands-in-fundamental-mode () + "Normal: <cj + expand in fundamental-mode produces the marker block." + (should (string= (test-prog-general--expand-cj-in-mode #'fundamental-mode) + test-prog-general--cj-expected))) + +(ert-deftest test-prog-general-cj-expands-in-text-mode () + "Normal: <cj + expand in text-mode produces the marker block." + (should (string= (test-prog-general--expand-cj-in-mode #'text-mode) + test-prog-general--cj-expected))) + +(ert-deftest test-prog-general-cj-expands-in-org-mode () + "Boundary: <cj + expand in org-mode produces the marker block. +Regression test for replacing the org-tempo entry with the yasnippet." + (should (string= (test-prog-general--expand-cj-in-mode #'org-mode) + test-prog-general--cj-expected))) + +(ert-deftest test-prog-general-cj-expands-in-emacs-lisp-mode () + "Normal: <cj + expand in emacs-lisp-mode (a prog-mode-derived mode) +produces the marker block." + (should (string= (test-prog-general--expand-cj-in-mode #'emacs-lisp-mode) + test-prog-general--cj-expected))) + +(ert-deftest test-prog-general-cj-expands-in-python-ts-mode () + "Boundary: <cj + expand in python-ts-mode (a tree-sitter prog-mode-derived +mode) produces the marker block. Verifies the snippet reaches modern +tree-sitter modes through fundamental-mode inheritance." + (skip-unless (fboundp 'python-ts-mode)) + (should (string= (test-prog-general--expand-cj-in-mode #'python-ts-mode) + test-prog-general--cj-expected))) + +(provide 'test-prog-general-yas-activation) +;;; test-prog-general-yas-activation.el ends here |
