aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-30 22:12:23 -0500
committerCraig Jennings <c@cjennings.net>2026-05-30 22:12:23 -0500
commit4fae47ee8c268a4f530f60996741a7871d096b13 (patch)
tree368bbe482c5f0dbf6d8ea16455bec7b1ba52dd16
parentc7da717d0bbe7fd3dc976c9519d135b0ea75b00e (diff)
downloadorg-drill-4fae47ee8c268a4f530f60996741a7871d096b13.tar.gz
org-drill-4fae47ee8c268a4f530f60996741a7871d096b13.zip
fix: detect #+FILETAGS decks so org-drill-mode auto-enables
org-drill-buffer-has-cards-p only scanned for a per-heading :drill:/:leitner: tag, so a deck tagged through #+FILETAGS: had no match and org-drill-mode never auto-enabled. Those files opened without cloze highlighting. I extended the predicate to also scan #+FILETAGS: lines, handling both the space-separated and colon-delimited syntaxes, with [: \t] boundaries so a value like drilldown can't false-match drill. The inherited-top-level-tag case already worked, since the ancestor heading line carries the literal tag and the per-heading scan catches it. Tests cover filetag-only decks (space, colon, leitner), the inherited-top-level lock, the substring boundary, and auto-enable on a filetag-only buffer.
-rw-r--r--org-drill.el37
-rw-r--r--tests/test-org-drill-mode.el52
2 files changed, 79 insertions, 10 deletions
diff --git a/org-drill.el b/org-drill.el
index 2680113..2ad00f0 100644
--- a/org-drill.el
+++ b/org-drill.el
@@ -3782,19 +3782,36 @@ non-nil; otherwise the mode is a no-op for fontification."
(defun org-drill-buffer-has-cards-p ()
"Return non-nil if the current buffer contains a drill card — a heading
-tagged with `org-drill-question-tag' or `org-drill-leitner-tag'."
+tagged with `org-drill-question-tag' or `org-drill-leitner-tag'.
+
+The tag may sit on the heading itself, on an ancestor heading (the
+ancestor line carries the literal tag, so the per-heading scan still
+matches), or be applied file-wide via `#+FILETAGS:'. The file-tag case
+is checked separately because the tag never appears on a heading line
+there — the upstream bug where filetag-only decks failed to auto-enable
+`org-drill-mode'."
(save-excursion
(save-restriction
(widen)
- (goto-char (point-min))
- (let ((case-fold-search t))
- (re-search-forward
- (concat "^\\*+ .*:\\(?:"
- (regexp-quote org-drill-question-tag)
- "\\|"
- (regexp-quote org-drill-leitner-tag)
- "\\):")
- nil t)))))
+ (let ((case-fold-search t)
+ (tags (concat "\\(?:"
+ (regexp-quote org-drill-question-tag)
+ "\\|"
+ (regexp-quote org-drill-leitner-tag)
+ "\\)")))
+ (or
+ ;; Per-heading tag (also catches inheritance from a tagged ancestor).
+ (progn
+ (goto-char (point-min))
+ (re-search-forward (concat "^\\*+ .*:" tags ":") nil t))
+ ;; File-wide tag via #+FILETAGS:, in either the space-separated or
+ ;; colon-delimited syntax. The [: \t] boundaries keep a value like
+ ;; `drilldown' from matching `drill'.
+ (progn
+ (goto-char (point-min))
+ (re-search-forward
+ (concat "^#\\+FILETAGS:.*[: \t]" tags "\\(?:[: \t]\\|$\\)")
+ nil t)))))))
(defun org-drill-maybe-enable-mode ()
"Enable `org-drill-mode' when appropriate for the current buffer.
diff --git a/tests/test-org-drill-mode.el b/tests/test-org-drill-mode.el
index 2050feb..2760b31 100644
--- a/tests/test-org-drill-mode.el
+++ b/tests/test-org-drill-mode.el
@@ -75,6 +75,48 @@ character of the first occurrence of literal string S."
(org-mode)
(should-not (org-drill-buffer-has-cards-p))))
+;;;; Drill-buffer predicate — file-level tags (#+FILETAGS), upstream bug
+
+(ert-deftest test-org-drill-buffer-has-cards-p-true-for-filetags-space ()
+ "A deck that tags its cards via `#+FILETAGS: drill' (space syntax, no
+per-heading tag) counts as a drill buffer."
+ (with-temp-buffer
+ (insert "#+FILETAGS: drill\n* A card\nQ [answer] A\n")
+ (org-mode)
+ (should (org-drill-buffer-has-cards-p))))
+
+(ert-deftest test-org-drill-buffer-has-cards-p-true-for-filetags-colon ()
+ "A deck tagged via the colon syntax `#+FILETAGS: :spanish:drill:verbs:'
+counts as a drill buffer even when the tag sits among others."
+ (with-temp-buffer
+ (insert "#+FILETAGS: :spanish:drill:verbs:\n* A card\nQ [answer] A\n")
+ (org-mode)
+ (should (org-drill-buffer-has-cards-p))))
+
+(ert-deftest test-org-drill-buffer-has-cards-p-true-for-filetags-leitner ()
+ "A deck tagged via `#+FILETAGS: leitner' counts as a drill buffer."
+ (with-temp-buffer
+ (insert "#+FILETAGS: leitner\n* A card\nQ [answer] A\n")
+ (org-mode)
+ (should (org-drill-buffer-has-cards-p))))
+
+(ert-deftest test-org-drill-buffer-has-cards-p-true-for-inherited-top-level-tag ()
+ "A deck whose cards inherit the tag from a tagged top-level heading counts
+as a drill buffer. The ancestor heading line carries the literal tag, so the
+per-heading scan already matches it — this locks that in."
+ (with-temp-buffer
+ (insert "* Deck :drill:\n** Card 1\nQ [answer] A\n** Card 2\nQ2 [answer2] A2\n")
+ (org-mode)
+ (should (org-drill-buffer-has-cards-p))))
+
+(ert-deftest test-org-drill-buffer-has-cards-p-false-for-filetags-substring ()
+ "A `#+FILETAGS:' value that merely contains the tag as a substring
+\(e.g. `drilldown') must NOT count as a drill buffer."
+ (with-temp-buffer
+ (insert "#+FILETAGS: drilldown\n* Notes\nbody\n")
+ (org-mode)
+ (should-not (org-drill-buffer-has-cards-p))))
+
;;;; Auto-enable on org-mode-hook
(ert-deftest test-org-drill-mode-auto-enables-in-drill-buffer ()
@@ -85,6 +127,16 @@ character of the first occurrence of literal string S."
(org-mode)
(should org-drill-mode))))
+(ert-deftest test-org-drill-mode-auto-enables-in-filetags-buffer ()
+ "With auto-enable on, opening a deck tagged only via `#+FILETAGS: drill'
+turns the mode on. This is the user-facing bug: filetag-only decks were
+left without cloze highlighting."
+ (let ((org-drill-auto-enable-mode t))
+ (with-temp-buffer
+ (insert "#+FILETAGS: drill\n* A card\nQ [answer] A\n")
+ (org-mode)
+ (should org-drill-mode))))
+
(ert-deftest test-org-drill-mode-does-not-auto-enable-in-plain-buffer ()
"With auto-enable on, a plain org buffer does NOT turn the mode on."
(let ((org-drill-auto-enable-mode t))