diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-05 05:29:05 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-05 05:29:05 -0500 |
| commit | 718775cdf2baf7b6a2ed09edaa07d5684d47c4a9 (patch) | |
| tree | c82a1a923ba003877df676370515bfc724e20f12 | |
| parent | fd658b166a8dcf716c82cca28f370990a9df83af (diff) | |
| download | org-drill-718775cdf2baf7b6a2ed09edaa07d5684d47c4a9.tar.gz org-drill-718775cdf2baf7b6a2ed09edaa07d5684d47c4a9.zip | |
fix: keep cloze regex within a single line (upstream #38)
The inner match was [[:cntrl:][:graph:][:space:]]+?, which silently
includes newline. A stray [ could match all the way to a ]
several lines later, covering org headings in between with the
visible-cloze face. Reporter saw lines 4 and 5 of test.org lose
their org-level-N face and use default instead.
Switched the inner class to [^\n]+?. Clozes now stay within a
single line, which matches the design intent and stops the face
bleed. Three new tests cover the regression.
| -rw-r--r-- | org-drill.el | 11 | ||||
| -rw-r--r-- | tests/test-org-drill-cloze-regex-single-line.el | 57 |
2 files changed, 66 insertions, 2 deletions
diff --git a/org-drill.el b/org-drill.el index 6885791..b628695 100644 --- a/org-drill.el +++ b/org-drill.el @@ -231,10 +231,17 @@ Mature items are due for review, but are not new." "Delimiter in cloze expression for hints.") (defun org-drill--compute-cloze-regexp () - "Return a regexp that detects clozes." + "Return a regexp that detects clozes. + +The inner match is constrained to non-newline characters so a cloze +stays within one line. An older version used +`[[:cntrl:][:graph:][:space:]]' which silently included newline, +letting a stray `[' match all the way to a `]' several lines later +and bleeding the cloze face onto intervening org headings (upstream +issue #38)." (concat "\\(" (regexp-quote org-drill-left-cloze-delimiter) - "[[:cntrl:][:graph:][:space:]]+?\\)\\(\\|" + "[^\n]+?\\)\\(\\|" (regexp-quote org-drill-hint-separator) ".+?\\)\\(" (regexp-quote org-drill-right-cloze-delimiter) diff --git a/tests/test-org-drill-cloze-regex-single-line.el b/tests/test-org-drill-cloze-regex-single-line.el new file mode 100644 index 0000000..bd13269 --- /dev/null +++ b/tests/test-org-drill-cloze-regex-single-line.el @@ -0,0 +1,57 @@ +;;; test-org-drill-cloze-regex-single-line.el --- Regression for #38 -*- lexical-binding: t; -*- + +;;; Commentary: +;; Upstream issue #38 (2021-01). When `org-drill-use-visible-cloze-face-p' +;; was non-nil, the cloze face leaked onto unrelated org headings. +;; Root cause: the cloze regex's inner character class included +;; `[:cntrl:]' / `[:space:]' which contain newline. A stray `[' could +;; match all the way to a `]' several lines later, covering org +;; headings in between with the visible-cloze face. +;; +;; Fix: restrict the inner match to non-newline characters so a cloze +;; stays within a single line — the intended scope. + +;;; Code: + +(require 'ert) +(require 'org-drill) + +;;;; Regression — #38 + +(ert-deftest test-cloze-regex-doesnt-span-newlines () + "A `[' on one line followed by `]' two lines later should NOT match +as a single cloze. Pre-fix this matched the entire span, which is +what caused the heading-face bleed." + (let ((re (org-drill--compute-cloze-regexp))) + (with-temp-buffer + (insert "Line one [some text\nLine two\nLine three] more") + (goto-char (point-min)) + (should-not (re-search-forward re nil t))))) + +(ert-deftest test-cloze-regex-still-matches-single-line-cloze () + "Single-line clozes still match — the fix doesn't break the happy path." + (let ((re (org-drill--compute-cloze-regexp))) + (with-temp-buffer + (insert "Capital of France is [Paris].") + (goto-char (point-min)) + (should (re-search-forward re nil t)) + (should (equal "[Paris]" (match-string 0)))))) + +(ert-deftest test-cloze-regex-multiline-buffer-finds-only-line-bounded-cloze () + "In a buffer with multiple lines and a single-line cloze on each, +each cloze is matched independently — no spans across lines." + (let ((re (org-drill--compute-cloze-regexp))) + (with-temp-buffer + (insert "Line A: [foo]\n* Heading\nLine B: [bar]\n") + (goto-char (point-min)) + (let ((matches nil)) + (while (re-search-forward re nil t) + (push (match-string 0) matches)) + (should (member "[foo]" matches)) + (should (member "[bar]" matches)) + ;; And exactly two matches — no spurious span across the heading. + (should (= 2 (length matches))))))) + +(provide 'test-org-drill-cloze-regex-single-line) + +;;; test-org-drill-cloze-regex-single-line.el ends here |
