Commits

Anonymous committed d9430f6

- Added new example card types, more useful than 'spanish_verb':
- 'conjugate': retrieves properties VERB_INFINITIVE and VERB_TRANSLATION
from parent item, and uses its own property VERB_TENSE to prompt the
student 'Translate the verb INFINITIVE and conjugate for the TENSE tense'
or 'Give the verb meaning TRANSLATION and conjugate for the TENSE tense'
- 'translate_number': using third party library spell-number.el,
prompt the student to translate a random number to or from a non-English
language (the library can handle numerous languages)
- examples of both in spanish.org
- org-drill-card-type-alist can now take a second function name, for
controlling how the ANSWER is displayed
- items can have weights (DRILL_CARD_WEIGHT). The interval is divided by
the weight when scheduling, so eg an item with a weight of 2.0 will be
tested twice as often as a normal item.
- New command: org-drill-tree. Same as org-drill using 'tree' argument.
- New command: org-drill-strip-data: deletes all scheduling data from
every item in scope. Intended for use if you wish to share your item
library with someone else.
- Fixed bug in simple8 algorithm where items failed on their first review
were not having intervals reset
- Ensure all markers are freed before starting a new drill session.

Comments (0)

Files changed (4)

 <title>Org-Drill</title>
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
 <meta name="generator" content="Org-mode"/>
-<meta name="generated" content="2011-04-22 15:27:38 "/>
+<meta name="generated" content="2011-04-30 16:14:35 "/>
 <meta name="author" content="Paul Sexton"/>
 <meta name="description" content=""/>
 <meta name="keywords" content=""/>
 <li><a href="#sec-4_4">Multi-sided cards </a></li>
 <li><a href="#sec-4_5">Multi-cloze cards </a></li>
 <li><a href="#sec-4_6">User-defined card types </a></li>
+<li><a href="#sec-4_7">Empty cards </a></li>
 </ul>
 </li>
 <li><a href="#sec-5">Running the drill session </a></li>
 will be ignored.
 </p>
 <p>
-You don't need to schedule the topics initially.  However <code>org-drill</code> <b>will</b>
-recognise items that have been scheduled previously with
-<code>org-learn</code>. Unscheduled items are considered to be 'new' and ready for
-memorisation.
+Drill items can have other drill items as children. When a drill item is being
+tested, the contents of any child drill items will be hidden.
+</p>
+<p>
+You don't need to schedule the topics initially.  Unscheduled items are
+considered to be 'new' and ready for memorisation.
 </p>
 <p>
 How should 'drill topics' be structured? Any org topic is a legal drill topic
-&ndash; it will simply be shown with all subheadings collapsed, so thta only the
+&ndash; it will simply be shown with all subheadings collapsed, so that only the
 material beneath the main item heading is visible. After pressing a key, any
 hidden subheadings will be revealed, and you will be asked to rate your
 "recall" of the item.
 Finally, you can write your own emacs lisp functions to define new kinds of
 topics. Any new topic type will need to be added to
 <code>org-drill-card-type-alist</code>, and cards using that topic type will need to have
-it as the value of their <code>DRILL_CARD_TYPE</code> property. For an example, see the
-function <code>org-drill-present-spanish-verb</code>, which defines the new topic type
-<code>spanish_verb</code>, used in 'spanish.org'.
+it as the value of their <code>DRILL_CARD_TYPE</code> property. For examples, see the
+functions at the end of org-drill.el &ndash; these include:
+</p><ul>
+<li><code>org-drill-present-verb-conjugation</code>, which implements the 'conjugate'
+  card type. This asks the user to conjugate a verb in a particular tense. It
+  demonstrates how the appearance of an entry can be completely altered during
+  a drill session, both during testing and during the display fo the answer.
+</li>
+<li><code>org-drill-present-translate-number</code>, which uses a third-party emacs lisp
+  library (<a href="http://www.emacswiki.org/emacs/spell-number.el">spell-number.el</a>) to prompt the user to translate random numbers
+  to and from any language recognised by that library.
+</li>
+<li><code>org-drill-present-spanish-verb</code>, which defines the new topic type
+  <code>spanish_verb</code>. This illustrates how a function can control which of an
+  item's subheadings are visible during the drill session.
+</li>
+</ul>
+
+
+<p>
+See the file <a href="spanish.html">spanish.org</a> for a full set of example material, including examples
+of all the card types discussed above.
+</p>
+
+</div>
+
+</div>
+
+<div id="outline-container-4_7" class="outline-3">
+<h3 id="sec-4_7">Empty cards </h3>
+<div class="outline-text-3" id="text-4_7">
+
+
+
+<p>
+If the body of a drill item is completely empty (ignoring properties and child
+items), then the item will be skipped during drill sessions. The purpose of
+this behaviour is to allow you to paste in 'skeletons' of complex items, then
+fill in missing information later. For example, you may wish to include an
+empty drill item for each tense of a newly learned verb, then paste in the
+actual conjugation later as you learn each tense.
 </p>
 <p>
-See the file <a href="spanish.html">spanish.org</a> for a full set of example material.
+Note that if an item is empty, any child drill items will <b>not</b> be ignored,
+unless they are empty as well.
 </p>
-
+<p>
+If you have an item with an empty body, but still want it to be included in a
+drill session, put a brief comment ('# &hellip;')  in the item body.
+</p>
 
 </div>
 </div>
 sources. Possible values for SCOPE are:
 </p>
 <dl>
-<dt>tree</dt><dd>The subtree starting with the entry at the cursor.
+<dt>tree</dt><dd>The subtree starting with the entry at the cursor. (Alternatively you
+          can use <code>M-x org=drill-tree</code> to run the drill session &ndash; this will
+          behave the same as <code>org-drill</code> if 'tree' was used as the value of
+          SCOPE.)
 </dd>
 <dt>file</dt><dd>The current buffer, including both hidden and non-hidden items.
 </dd>
 </div>
 </div>
 <div id="postamble">
-<p class="date">Date: 2011-04-22 15:27:38 </p>
+<p class="date">Date: 2011-04-30 16:14:35 </p>
 <p class="author">Author: Paul Sexton</p>
 <p class="creator">Org version 7.5 with Emacs version 23</p>
 <a href="http://validator.w3.org/check?uri=referer">Validate XHTML 1.0</a>
 # -*- mode: org; coding: utf-8 -*-
 #+STARTUP: showall
 #+OPTIONS: num:nil
-# Make absolutely sure the emacs lisp examples below don't get spuriously evaluated
-#+BABEL: :exports code
 #+TITLE: Org-Drill
 #+AUTHOR: Paul Sexton
 
 =org-drill-question-tag=. This is =:drill:= by default. Any other org topics
 will be ignored.
 
-You don't need to schedule the topics initially.  However =org-drill= *will*
-recognise items that have been scheduled previously with
-=org-learn=. Unscheduled items are considered to be 'new' and ready for
-memorisation.
+Drill items can have other drill items as children. When a drill item is being
+tested, the contents of any child drill items will be hidden.
+
+You don't need to schedule the topics initially.  Unscheduled items are
+considered to be 'new' and ready for memorisation.
 
 How should 'drill topics' be structured? Any org topic is a legal drill topic
--- it will simply be shown with all subheadings collapsed, so thta only the
+-- it will simply be shown with all subheadings collapsed, so that only the
 material beneath the main item heading is visible. After pressing a key, any
 hidden subheadings will be revealed, and you will be asked to rate your
 "recall" of the item.
 Finally, you can write your own emacs lisp functions to define new kinds of
 topics. Any new topic type will need to be added to
 =org-drill-card-type-alist=, and cards using that topic type will need to have
-it as the value of their =DRILL_CARD_TYPE= property. For an example, see the
-function =org-drill-present-spanish-verb=, which defines the new topic type
-=spanish_verb=, used in 'spanish.org'.
+it as the value of their =DRILL_CARD_TYPE= property. For examples, see the
+functions at the end of org-drill.el -- these include:
+- =org-drill-present-verb-conjugation=, which implements the 'conjugate'
+  card type. This asks the user to conjugate a verb in a particular tense. It
+  demonstrates how the appearance of an entry can be completely altered during
+  a drill session, both during testing and during the display fo the answer.
+- =org-drill-present-translate-number=, which uses a third-party emacs lisp
+  library ([[http://www.emacswiki.org/emacs/spell-number.el][spell-number.el]]) to prompt the user to translate random numbers
+  to and from any language recognised by that library.
+- =org-drill-present-spanish-verb=, which defines the new topic type
+  =spanish_verb=. This illustrates how a function can control which of an
+  item's subheadings are visible during the drill session.
 
-See the file [[file:spanish.org][spanish.org]] for a full set of example material.
+See the file [[file:spanish.org][spanish.org]] for a full set of example material, including examples
+of all the card types discussed above.
 
 
+** Empty cards
+
+
+If the body of a drill item is completely empty (ignoring properties and child
+items), then the item will be skipped during drill sessions. The purpose of
+this behaviour is to allow you to paste in 'skeletons' of complex items, then
+fill in missing information later. For example, you may wish to include an
+empty drill item for each tense of a newly learned verb, then paste in the
+actual conjugation later as you learn each tense.
+
+Note that if an item is empty, any child drill items will *not* be ignored,
+unless they are empty as well.
+
+If you have an item with an empty body, but still want it to be included in a
+drill session, put a brief comment ('# ...')  in the item body.
+
 
 * Running the drill session
 
 argument, SCOPE, which allows it to take drill items from other
 sources. Possible values for SCOPE are:
 
-- tree :: The subtree starting with the entry at the cursor.
+- tree :: The subtree starting with the entry at the cursor. (Alternatively you
+          can use =M-x org=drill-tree= to run the drill session -- this will
+          behave the same as =org-drill= if 'tree' was used as the value of
+          SCOPE.)
 - file :: The current buffer, including both hidden and non-hidden items.
 - file-with-archives :: The current buffer, and any archives associated with it.
 - agenda :: All agenda files.
 ;;; org-drill.el - Self-testing using spaced repetition
 ;;;
 ;;; Author: Paul Sexton <eeeickythump@gmail.com>
-;;; Version: 2.1.1
+;;; Version: 2.2
 ;;; Repository at http://bitbucket.org/eeeickythump/org-drill/
 ;;;
 ;;;
 (setplist 'org-drill-hidden-text-overlay
           '(invisible t))
 
+(setplist 'org-drill-replaced-text-overlay
+          '(display "Replaced text"
+                    face default
+                    window t))
+
 
 (defvar org-drill-cloze-regexp
   ;; ver 1   "[^][]\\(\\[[^][][^]]*\\]\\)"
     ("hide1cloze" . org-drill-present-multicloze-hide1)
     ("show1cloze" . org-drill-present-multicloze-show1)
     ("multicloze" . org-drill-present-multicloze-hide1)
-    ("spanish_verb" . org-drill-present-spanish-verb))
+    ("conjugate" org-drill-present-verb-conjugation
+     org-drill-show-answer-verb-conjugation)
+    ("spanish_verb" . org-drill-present-spanish-verb)
+    ("translate_number" org-drill-present-translate-number
+     org-drill-show-answer-translate-number))
   "Alist associating card types with presentation functions. Each entry in the
-alist takes the form (CARDTYPE . FUNCTION), where CARDTYPE is a string
-or nil, and FUNCTION is a function which takes no arguments and returns a
-boolean value."
+alist takes one of two forms:
+1. (CARDTYPE . QUESTION-FN), where CARDTYPE is a string or nil (for default),
+   and QUESTION-FN is a function which takes no arguments and returns a boolean
+   value.
+2. (CARDTYPE QUESTION-FN ANSWER-FN), where ANSWER-FN is a function that takes
+   one argument -- the argument is a function that itself takes no arguments.
+   ANSWER-FN is called with the point on the active item's
+   heading, just prior to displaying the item's 'answer'. It can therefore be
+   used to modify the appearance of the answer. ANSWER-FN must call its argument
+   before returning. (Its argument is a function that prompts the user and
+   performs rescheduling)."
   :group 'org-drill
   :type '(alist :key-type (choice string (const nil)) :value-type function))
 
                     (/ (+ quality (* meanq totaln 1.0)) (1+ totaln))
                   quality))
     (cond
+     ((<= quality org-drill-failure-quality)
+      (incf failures)
+      (setf repeats 0
+            next-interval -1))
      ((or (zerop repeats)
           (zerop last-interval))
       (setf next-interval (org-drill-simple8-first-interval failures))
       (incf repeats)
       (incf totaln))
      (t
-      (cond
-       ((<= quality org-drill-failure-quality)
-        (incf failures)
-        (setf repeats 0
-              next-interval -1))
-       (t
-        (let* ((use-n
-                (if (and
-                     org-drill-adjust-intervals-for-early-and-late-repetitions-p
-                     (numberp delta-days) (plusp delta-days)
-                     (plusp last-interval))
-                    (+ repeats (min 1 (/ delta-days last-interval 1.0)))
-                  repeats))
-               (factor (org-drill-simple8-interval-factor
-                        (org-drill-simple8-quality->ease meanq) use-n))
-               (next-int (* last-interval factor)))
-          (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
-                     (numberp delta-days) (minusp delta-days))
-            ;; The item was reviewed earlier than scheduled.
-            (setf factor (org-drill-early-interval-factor
-                          factor next-int (abs delta-days))
-                  next-int (* last-interval factor)))
-          (setf next-interval next-int)
-          (incf repeats)
-          (incf totaln))))))
+      (let* ((use-n
+              (if (and
+                   org-drill-adjust-intervals-for-early-and-late-repetitions-p
+                   (numberp delta-days) (plusp delta-days)
+                   (plusp last-interval))
+                  (+ repeats (min 1 (/ delta-days last-interval 1.0)))
+                repeats))
+             (factor (org-drill-simple8-interval-factor
+                      (org-drill-simple8-quality->ease meanq) use-n))
+             (next-int (* last-interval factor)))
+        (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
+                   (numberp delta-days) (minusp delta-days))
+          ;; The item was reviewed earlier than scheduled.
+          (setf factor (org-drill-early-interval-factor
+                        factor next-int (abs delta-days))
+                next-int (* last-interval factor)))
+        (setf next-interval next-int)
+        (incf repeats)
+        (incf totaln))))
     (list
      (if (and org-drill-add-random-noise-to-intervals-p
               (plusp next-interval))
 
 
 ;;; Essentially copied from `org-learn.el', but modified to
-;;; optionally call the SM2 function above.
+;;; optionally call the SM2 or simple8 functions.
 (defun org-drill-smart-reschedule (quality &optional days-ahead)
   "If DAYS-AHEAD is supplied it must be a positive integer. The
 item will be scheduled exactly this many days into the future."
   (let ((delta-days (- (time-to-days (current-time))
                    (time-to-days (or (org-get-scheduled-time (point))
                                      (current-time)))))
-        (ofmatrix org-drill-optimal-factor-matrix))
+        (ofmatrix org-drill-optimal-factor-matrix)
+        ;; Entries can have weights, 1 by default. Intervals are divided by the
+        ;; item's weight, so an item with a weight of 2 will have all intervals
+        ;; halved, meaning you will end up reviewing it twice as often.
+        ;; Useful for entries which randomly present any of several facts.
+        (weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
+    (if (stringp weight)
+        (setq weight (read weight)))
     (destructuring-bind (last-interval repetitions failures
                                        total-repeats meanq ease)
         (org-drill-get-item-data)
                                                       quality failures meanq
                                                       total-repeats
                                                       delta-days)))
-        (if (integerp days-ahead)
-            (setf next-interval days-ahead))
+        (if (numberp days-ahead)
+            (setq next-interval days-ahead))
+
         (org-drill-store-item-data next-interval repetitions failures
                                    total-repeats meanq ease)
+        (if (and (null days-ahead)
+                 (numberp weight) (plusp weight)
+                 (not (minusp next-interval)))
+            (setq next-interval (max 1.0 (/ next-interval weight))))
+
         (if (eql 'sm5 org-drill-spaced-repetition-algorithm)
             (setq org-drill-optimal-factor-matrix new-ofmatrix))
 
                                        (round next-interval))))))))))
 
 
-
 (defun org-drill-hypothetical-next-review-date (quality)
   "Returns an integer representing the number of days into the future
 that the current item would be scheduled, based on a recall quality
 of QUALITY."
-  (destructuring-bind (last-interval repetitions failures
-                                     total-repeats meanq ease)
-      (org-drill-get-item-data)
-    (destructuring-bind (next-interval repetitions ease
-                                       failures meanq total-repeats
-                                       &optional ofmatrix)
-        (case org-drill-spaced-repetition-algorithm
-          (sm5 (determine-next-interval-sm5 last-interval repetitions
-                                            ease quality failures
-                                            meanq total-repeats
-                                            org-drill-optimal-factor-matrix))
-          (sm2 (determine-next-interval-sm2 last-interval repetitions
-                                            ease quality failures
-                                            meanq total-repeats))
-          (simple8 (determine-next-interval-simple8 last-interval repetitions
-                                                    quality failures meanq
-                                                    total-repeats)))
-      (cond
-       ((not (plusp next-interval))
-        0)
-       (t
-        next-interval)))))
-
+  (let ((weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
+    (destructuring-bind (last-interval repetitions failures
+                                       total-repeats meanq ease)
+        (org-drill-get-item-data)
+      (if (stringp weight)
+          (setq weight (read weight)))
+      (destructuring-bind (next-interval repetitions ease
+                                         failures meanq total-repeats
+                                         &optional ofmatrix)
+          (case org-drill-spaced-repetition-algorithm
+            (sm5 (determine-next-interval-sm5 last-interval repetitions
+                                              ease quality failures
+                                              meanq total-repeats
+                                              org-drill-optimal-factor-matrix))
+            (sm2 (determine-next-interval-sm2 last-interval repetitions
+                                              ease quality failures
+                                              meanq total-repeats))
+            (simple8 (determine-next-interval-simple8 last-interval repetitions
+                                                      quality failures meanq
+                                                      total-repeats)))
+        (cond
+         ((not (plusp next-interval))
+          0)
+         ((and (numberp weight) (plusp weight))
+          (max 1.0 (/ next-interval weight)))
+         (t
+          next-interval))))))
 
 
 (defun org-drill-hypothetical-next-review-dates ()
     (org-in-regexp regexp nlines)))
 
 
-(defun org-drill-hide-region (beg end)
+(defun org-drill-hide-region (beg end &optional text)
   "Hide the buffer region between BEG and END with an 'invisible text'
-visual overlay."
+visual overlay, or with the string TEXT if it is supplied."
   (let ((ovl (make-overlay beg end)))
     (overlay-put ovl 'category
-                 'org-drill-hidden-text-overlay)))
+                 'org-drill-hidden-text-overlay)
+    (when (stringp text)
+      (overlay-put ovl 'invisible nil)
+      (overlay-put ovl 'face 'default)
+      (overlay-put ovl 'display text))))
 
 
-(defun org-drill-hide-heading-at-point ()
+(defun org-drill-hide-heading-at-point (&optional text)
   (unless (org-at-heading-p)
     (error "Point is not on a heading."))
   (save-excursion
     (let ((beg (point)))
       (end-of-line)
-      (org-drill-hide-region beg (point)))))
+      (org-drill-hide-region beg (point) text))))
 
 
 (defun org-drill-hide-comments ()
                             (1- (length (match-string 0)))))))))
 
 
+(defmacro with-replaced-entry-text (text &rest body)
+  "During the execution of BODY, the entire text of the current entry is
+concealed by an overlay that displays the string TEXT."
+  `(progn
+     (org-drill-replace-entry-text ,text)
+     (unwind-protect
+         (progn
+           ,@body)
+       (org-drill-unreplace-entry-text))))
+
+
+(defun org-drill-replace-entry-text (text)
+  "Make an overlay that conceals the entire text of the item, not
+including properties or the contents of subheadings. The overlay shows
+the string TEXT.
+Note: does not actually alter the item."
+  (let ((ovl (make-overlay (point-min)
+                           (save-excursion
+                             (outline-next-heading)
+                             (point)))))
+    (overlay-put ovl 'category
+                 'org-drill-replaced-text-overlay)
+    (overlay-put ovl 'display text)))
+
+
+(defun org-drill-unreplace-entry-text ()
+  (save-excursion
+    (dolist (ovl (overlays-in (point-min) (point-max)))
+      (when (eql 'org-drill-replaced-text-overlay (overlay-get ovl 'category))
+        (delete-overlay ovl)))))
+
+
+(defmacro with-replaced-entry-heading (heading &rest body)
+  `(progn
+     (org-drill-replace-entry-heading ,heading)
+     (unwind-protect
+         (progn
+           ,@body)
+       (org-drill-unhide-comments))))
+
+
+(defun org-drill-replace-entry-heading (heading)
+  "Make an overlay that conceals the heading of the item. The overlay shows
+the string TEXT.
+Note: does not actually alter the item."
+  (org-drill-hide-heading-at-point heading))
+
+
 (defun org-drill-unhide-clozed-text ()
   (save-excursion
     (dolist (ovl (overlays-in (point-min) (point-max)))
         (delete-overlay ovl)))))
 
 
+(defun org-drill-get-entry-text ()
+  (substring-no-properties
+   (org-agenda-get-some-entry-text (point-marker) 100)))
+
+
+(defun org-drill-entry-empty-p ()
+  (zerop (length (org-drill-get-entry-text))))
+
+
 
 ;;; Presentation functions ====================================================
 
        (org-drill-unhide-clozed-text)))))
 
 
-(defun org-drill-present-spanish-verb ()
-  (let ((prompt nil)
-        (reveal-headings nil))
-    (with-hidden-comments
-     (with-hidden-cloze-text
-      (case (random 6)
-        (0
-         (org-drill-hide-all-subheadings-except '("Infinitive"))
-         (setq prompt
-               (concat "Translate this Spanish verb, and conjugate it "
-                       "for the *present* tense.")
-               reveal-headings '("English" "Present Tense" "Notes")))
-        (1
-         (org-drill-hide-all-subheadings-except '("English"))
-         (setq prompt (concat "For the *present* tense, conjugate the "
-                              "Spanish translation of this English verb.")
-               reveal-headings '("Infinitive" "Present Tense" "Notes")))
-        (2
-         (org-drill-hide-all-subheadings-except '("Infinitive"))
-         (setq prompt (concat "Translate this Spanish verb, and "
-                              "conjugate it for the *past* tense.")
-               reveal-headings '("English" "Past Tense" "Notes")))
-        (3
-         (org-drill-hide-all-subheadings-except '("English"))
-         (setq prompt (concat "For the *past* tense, conjugate the "
-                              "Spanish translation of this English verb.")
-               reveal-headings '("Infinitive" "Past Tense" "Notes")))
-        (4
-         (org-drill-hide-all-subheadings-except '("Infinitive"))
-         (setq prompt (concat "Translate this Spanish verb, and "
-                              "conjugate it for the *future perfect* tense.")
-               reveal-headings '("English" "Future Perfect Tense" "Notes")))
-        (5
-         (org-drill-hide-all-subheadings-except '("English"))
-         (setq prompt (concat "For the *future perfect* tense, conjugate the "
-                              "Spanish translation of this English verb.")
-               reveal-headings '("Infinitive" "Future Perfect Tense" "Notes"))))
-      (org-cycle-hide-drawers 'all)
-      (prog1
-          (org-drill-presentation-prompt prompt)
-        (org-drill-hide-all-subheadings-except reveal-headings))))))
+(defun org-drill-present-card-using-text (question &optional answer)
+  "Present the string QUESTION as the only visible content of the card."
+  (with-hidden-comments
+   (with-replaced-entry-text
+    question
+    (org-drill-hide-all-subheadings-except nil)
+    (org-cycle-hide-drawers 'all)
+    (prog1 (org-drill-presentation-prompt)
+      (org-drill-hide-subheadings-if 'org-drill-entry-p)))))
 
 
 ;;; The following macro is necessary because `org-save-outline-visibility'
   ;;(unless (org-at-heading-p)
   ;;  (org-back-to-heading))
   (let ((card-type (org-entry-get (point) "DRILL_CARD_TYPE"))
-        (cont nil))
+        (cont nil)
+        (answer-fn nil))
     (org-drill-save-visibility
      (save-restriction
        (org-narrow-to-subtree)
        (org-cycle-hide-drawers 'all)
 
        (let ((presentation-fn (cdr (assoc card-type org-drill-card-type-alist))))
+         (if (listp presentation-fn)
+             (psetq answer-fn (second presentation-fn)
+                    presentation-fn (first presentation-fn)))
          (cond
           (presentation-fn
            (setq cont (funcall presentation-fn)))
          'skip)
         (t
          (save-excursion
-           (org-drill-reschedule))))))))
+           (cond
+            (answer-fn
+             (funcall answer-fn (lambda () (org-drill-reschedule))))
+            (t
+             (org-drill-reschedule))))))))))
 
 
 (defun org-drill-entries-pending-p ()
          ))))
 
 
+
+(defun org-drill-free-all-markers ()
+  (dolist (m (append  *org-drill-done-entries*
+                      *org-drill-new-entries*
+                      *org-drill-failed-entries*
+                      *org-drill-again-entries*
+                      *org-drill-overdue-entries*
+                      *org-drill-young-mature-entries*
+                      *org-drill-old-mature-entries*))
+    (free-marker m)))
+
+
+
 (defun org-drill (&optional scope resume-p)
   "Begin an interactive 'drill session'. The user is asked to
 review a series of topics (headers). Each topic is initially
         (cnt 0))
     (block org-drill
       (unless resume-p
+        (org-drill-free-all-markers)
         (setq *org-drill-current-item* nil
               *org-drill-done-entries* nil
               *org-drill-dormant-entry-count* 0
                      (cond
                       ((not (org-drill-entry-p))
                        nil)             ; skip
+                      ((org-drill-entry-empty-p)
+                       nil)             ; skip -- item body is empty
                       ((or (null due)   ; unscheduled - usually a skipped leech
                            (minusp due)) ; scheduled in the future
                        (incf *org-drill-dormant-entry-count*)
               (message "Drill session finished!"))))
         (progn
           (unless end-pos
-            (dolist (m (append  *org-drill-done-entries*
-                                *org-drill-new-entries*
-                                *org-drill-failed-entries*
-                                *org-drill-again-entries*
-                                *org-drill-overdue-entries*
-                                *org-drill-young-mature-entries*
-                                *org-drill-old-mature-entries*))
-              (free-marker m))))))
+            (org-drill-free-all-markers)))))
     (cond
      (end-pos
       (when (markerp end-pos)
       ))))
 
 
+
 (defun org-drill-save-optimal-factor-matrix ()
   (message "Saving optimal factor matrix...")
   (customize-save-variable 'org-drill-optimal-factor-matrix
     (org-drill scope)))
 
 
+(defun org-drill-tree ()
+  "Run an interactive drill session using drill items within the
+subtree at point."
+  (interactive)
+  (org-drill 'tree))
+
+
 (defun org-drill-resume ()
   "Resume a suspended drill session. Sessions are suspended by
 exiting them with the `edit' option."
   (org-drill nil t))
 
 
+(defun org-drill-strip-data (&optional scope)
+  "Delete scheduling data from every drill entry in scope. This
+function may be useful if you want to give your collection of
+entries to someone else.  Scope defaults to the current buffer,
+and is specified by the argument SCOPE, which accepts the same
+values as `org-drill'."
+  (interactive)
+  (when (yes-or-no-p
+         "Delete scheduling data from ALL items in scope: are you sure?")
+    (org-map-entries (lambda ()
+                       (org-delete-property "DRILL_LAST_INTERVAL")
+                       (org-delete-property "DRILL_REPEATS_SINCE_FAIL")
+                       (org-delete-property "DRILL_TOTAL_REPEATS")
+                       (org-delete-property "DRILL_FAILURE_COUNT")
+                       (org-delete-property "DRILL_AVERAGE_QUALITY")
+                       (org-delete-property "DRILL_EASE")
+                       (org-delete-property "DRILL_LAST_QUALITY")
+                       (org-delete-property "DRILL_LAST_REVIEWED")
+                       (org-schedule t))
+                     "" scope)
+    (message "Done.")))
+
+
+
 (add-hook 'org-mode-hook
           (lambda ()
             (if org-drill-use-visible-cloze-face-p
                  nil))))
 
 
+(provide 'org-drill)
 
-(provide 'org-drill)
+;;; Card types for learning languages =========================================
+
+;;; Get spell-number.el from:
+;;; http://www.emacswiki.org/emacs/spell-number.el
+(autoload 'spelln-integer-in-words "spell-number")
+
+
+;;; `conjugate' card type =====================================================
+;;; See spanish.org for usage
+
+(defvar org-drill-verb-tense-alist
+  '(("present" "tomato")
+    ("simple present" "tomato")
+    ("present indicative" "tomato")
+    ;; past tenses
+    ("past" "purple")
+    ("simple past" "purple")
+    ("preterite" "purple")
+    ("imperfect" "darkturquoise")
+    ("present perfect" "royalblue")
+    ;; future tenses
+    ("future" "green"))
+  "Alist where each entry has the form (TENSE COLOUR), where
+TENSE is a string naming a tense in which verbs can be
+conjugated, and COLOUR is a string specifying a foreground colour
+which will be used by `org-drill-present-verb-conjugation' and
+`org-drill-show-answer-verb-conjugation' to fontify the verb and
+the name of the tense.")
+
+
+(defun org-drill-get-verb-conjugation-info ()
+  "Auxiliary function used by `org-drill-present-verb-conjugation' and
+`org-drill-show-answer-verb-conjugation'."
+  (let ((infinitive (org-entry-get (point) "VERB_INFINITIVE" t))
+        (translation (org-entry-get (point) "VERB_TRANSLATION" t))
+        (tense (org-entry-get (point) "VERB_TENSE" nil))
+        (highlight-face nil))
+    (unless (and infinitive translation tense)
+      (error "Missing information for verb conjugation card (%s, %s, %s) at %s"
+             infinitive translation tense (point)))
+    (setq tense (downcase (car (read-from-string tense)))
+          infinitive (car (read-from-string infinitive))
+          translation (car (read-from-string translation)))
+    (setq highlight-face
+          (list :foreground
+                (or (second (assoc-string tense org-drill-verb-tense-alist t))
+                    "red")))
+    (setq infinitive (propertize infinitive 'face highlight-face))
+    (setq translation (propertize translation 'face highlight-face))
+    (setq tense (propertize tense 'face highlight-face))
+    (list infinitive translation tense)))
+
+
+(defun org-drill-present-verb-conjugation ()
+  "Present a drill entry whose card type is 'conjugate'."
+  (destructuring-bind (infinitive translation tense)
+      (org-drill-get-verb-conjugation-info)
+    (org-drill-present-card-using-text
+     (cond
+      ((zerop (random 2))
+       (format "\nTranslate the verb\n\n%s\n\nand conjugate for the %s tense.\n\n"
+               infinitive tense))
+      (t
+       (format "\nGive the verb that means\n\n%s\n\nand conjugate for the %s tense.\n\n"
+               translation tense))))))
+
+
+(defun org-drill-show-answer-verb-conjugation (reschedule-fn)
+  "Show the answer for a drill item whose card type is 'conjugate'.
+RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
+returns its return value."
+  (destructuring-bind (infinitive translation tense)
+      (org-drill-get-verb-conjugation-info)
+    (with-replaced-entry-heading
+     (format "%s tense of %s ==> %s\n\n"
+             (capitalize tense)
+             infinitive translation)
+     (funcall reschedule-fn))))
+
+
+;;; `translate_number' card type ==============================================
+;;; See spanish.org for usage
+
+(defvar *drilled-number* 0)
+(defvar *drilled-number-direction* 'to-english)
+
+(defun org-drill-present-translate-number ()
+  (let ((num-min (read (org-entry-get (point) "DRILL_NUMBER_MIN")))
+        (num-max (read (org-entry-get (point) "DRILL_NUMBER_MAX")))
+        (language (read (org-entry-get (point) "DRILL_LANGUAGE" t)))
+        (highlight-face 'font-lock-warning-face))
+    (cond
+     ((not (fboundp 'spelln-integer-in-words))
+      (message "`spell-number.el' not loaded, skipping 'translate_number' card...")
+      (sit-for 0.5)
+      'skip)
+     ((not (and (numberp num-min) (numberp num-max) language))
+      (error "Missing language or minimum or maximum numbers for number card"))
+     (t
+      (if (> num-min num-max)
+          (psetf num-min num-max
+                 num-max num-min))
+      (setq *drilled-number*
+            (+ num-min (random (abs (1+ (- num-max num-min))))))
+      (setq *drilled-number-direction*
+            (if (zerop (random 2)) 'from-english 'to-english))
+      (org-drill-present-card-using-text
+       (if (eql 'to-english *drilled-number-direction*)
+           (format "\nTranslate into English:\n\n%s\n"
+                   (let ((spelln-language language))
+                     (propertize
+                      (spelln-integer-in-words *drilled-number*)
+                      'face highlight-face)))
+         (format "\nTranslate into %s:\n\n%s\n"
+                 (capitalize (format "%s" language))
+                 (let ((spelln-language 'english-gb))
+                   (propertize
+                    (spelln-integer-in-words *drilled-number*)
+                    'face highlight-face)))))))))
+
+
+(defun org-drill-show-answer-translate-number (reschedule-fn)
+  (let* ((language (read (org-entry-get (point) "DRILL_LANGUAGE" t)))
+         (highlight-face 'font-lock-warning-face)
+         (non-english
+          (let ((spelln-language language))
+            (propertize (spelln-integer-in-words *drilled-number*)
+                        'face highlight-face)))
+         (english
+          (let ((spelln-language 'english-gb))
+            (propertize (spelln-integer-in-words *drilled-number*)
+                        'face 'highlight-face))))
+    (with-replaced-entry-text
+     (cond
+      ((eql 'to-english *drilled-number-direction*)
+       (format "\nThe English translation of %s is:\n\n%s\n"
+               non-english english))
+      (t
+       (format "\nThe %s translation of %s is:\n\n%s\n"
+               (capitalize (format "%s" language))
+               english non-english)))
+     (funcall reschedule-fn))))
+
+
+;;; `spanish_verb' card type ==================================================
+;;; Not very interesting, but included to demonstrate how a presentation
+;;; function can manipulate which subheading are hidden versus shown.
+
+
+(defun org-drill-present-spanish-verb ()
+  (let ((prompt nil)
+        (reveal-headings nil))
+    (with-hidden-comments
+     (with-hidden-cloze-text
+      (case (random 6)
+        (0
+         (org-drill-hide-all-subheadings-except '("Infinitive"))
+         (setq prompt
+               (concat "Translate this Spanish verb, and conjugate it "
+                       "for the *present* tense.")
+               reveal-headings '("English" "Present Tense" "Notes")))
+        (1
+         (org-drill-hide-all-subheadings-except '("English"))
+         (setq prompt (concat "For the *present* tense, conjugate the "
+                              "Spanish translation of this English verb.")
+               reveal-headings '("Infinitive" "Present Tense" "Notes")))
+        (2
+         (org-drill-hide-all-subheadings-except '("Infinitive"))
+         (setq prompt (concat "Translate this Spanish verb, and "
+                              "conjugate it for the *past* tense.")
+               reveal-headings '("English" "Past Tense" "Notes")))
+        (3
+         (org-drill-hide-all-subheadings-except '("English"))
+         (setq prompt (concat "For the *past* tense, conjugate the "
+                              "Spanish translation of this English verb.")
+               reveal-headings '("Infinitive" "Past Tense" "Notes")))
+        (4
+         (org-drill-hide-all-subheadings-except '("Infinitive"))
+         (setq prompt (concat "Translate this Spanish verb, and "
+                              "conjugate it for the *future perfect* tense.")
+               reveal-headings '("English" "Future Perfect Tense" "Notes")))
+        (5
+         (org-drill-hide-all-subheadings-except '("English"))
+         (setq prompt (concat "For the *future perfect* tense, conjugate the "
+                              "Spanish translation of this English verb.")
+               reveal-headings '("Infinitive" "Future Perfect Tense" "Notes"))))
+      (org-cycle-hide-drawers 'all)
+      (prog1
+          (org-drill-presentation-prompt prompt)
+        (org-drill-hide-all-subheadings-except reveal-headings))))))
+
+
 # that character is considered to be a `hint', and will remain visible when the
 # rest of the clozed text is hidden.
 
-# Set the variable `org-drill-use-visible-cloze-face-p' to `t' if you want 
+# Set the variable `org-drill-use-visible-cloze-face-p' to `t' if you want
 # cloze-deleted text to be shown in a special face when you are editing org
 # mode buffers.
 
 
 *** Grammar Rule                                     :drill:
 
-To make the plural of an adjective ending in [a stressed vowel or a consonant 
+To make the plural of an adjective ending in [a stressed vowel or a consonant
 other than -z], add /-es/.
 
 ** Grammar rules 2
 
-# An example of a 'multicloze' card. One of the areas marked with square
+# An example of a 'hide1cloze' card. One of the areas marked with square
 # brackets will be hidden (chosen at random), the others will remain visible.
 
 *** Grammar Rule                                     :drill:
     :PROPERTIES:
-    :DRILL_CARD_TYPE: multicloze
+    :DRILL_CARD_TYPE: hide1cloze
     :END:
 
-To form [an adverb] from an adjective, add [-mente] to the [feminine|gender] 
+To form [an adverb] from an adjective, add [-mente] to the [feminine|gender]
 form of the adjective.
 
 ** Vocabulary
 
 # Examples of 'twosided' cards. These are 'flip cards' where one of the
 # first 2 'sides' (subheadings) is presented at random, while all others stay
-# hidden. 
+# hidden.
 
 # There is another builtin card type called 'multisided'. These are like
 # 'twosided' cards, but can have any number of sides. So we could extend the
 
 *** Noun                                             :drill:
     :PROPERTIES:
-    :DRILL_CARD_TYPE: twosided
+    :DRILL_CARD_TYPE: hide1cloze
     :END:
 
-Translate this word.
-
-**** Spanish
-
-el perro
-
-**** English
-
-the dog
+Sp: [el perro]
+En: [the dog]
 
 **** Example sentence
 
-Cuidado con *el perro*. 
+Cuidado con *el perro*.
 Beware of *the dog*.
 
 
 
 caliente
 
-**** English 
+**** English
 
 hot
 
 
 ** Verbs
 
-# An example of a special card type. The information in "spanish_verb" topics
-# can be presented in any of several different ways -- see the function
-# `org-drill-present-spanish-verb'.
+[[Regular Verb: bailar][Below]] is an example of a complex drill item. The main item is itself a drill
+item which tests your ability to translate 'bailar' to and from English (which
+direction is chosen at random).
 
-*** Verb                                             :drill:
+The item has several child items, some of which contain notes about the verb,
+others of which are separate drill items relating to the verb. In this example,
+all the child drill items test verb conjugation, and have the 'conjugate' card
+type. Which tense to test is specified by the =VERB_TENSE= property in each item,
+and the information about the verb is retrieved from the parent's
+=VERB_INFINITIVE= and =VERB_TRANSLATION= properties.
+
+Some of the conjugation items are empty -- this allows the user to past in
+conjugations as they are learned.
+
+Following this item is an [[Old Style Verb][example]] of the "spanish_verb" card type. This is not
+as sophisticated or useful as the above example, but is intended to demonstrate
+how a function can control which subheadings are visible when an item is
+tested.
+
+
+*** Regular Verb: bailar                                            :verb:drill:
+  :PROPERTIES:
+  :VERB_INFINITIVE: "bailar"
+  :VERB_TRANSLATION: "to dance"
+  :DRILL_CARD_TYPE: hide1cloze
+  :DATE_ADDED: [2011-04-30 Sat]
+  :END:
+
+Sp:  [bailar]
+En:  [to dance] (verb)
+
+**** Notes
+
+This is a regular verb.
+
+**** Examples
+
+Bailé con mi novia.
+I danced with my girlfriend.
+
+**** Present Indicative tense                                       :verb:drill:
+     :PROPERTIES:
+     :VERB_TENSE: "present indicative"
+     :DRILL_CARD_TYPE: conjugate
+     :END:
+
+| yo            | bailo    |
+| tú            | bailas   |
+| él/usted      | baila    |
+| nosotros      | bailamos |
+| vosotros      | bailáis  |
+| ellos/ustedes | bailan   |
+
+**** Participles                                                    :verb:drill:
+Present participle of bailar:  [bailando]
+Past participle of bailar:     [bailado]
+
+**** Preterite tense                                                :verb:drill:
+     :PROPERTIES:
+     :VERB_TENSE: "preterite"
+     :DRILL_CARD_TYPE: conjugate
+     :END:
+
+| yo            | bailé      |
+| tú            | bailaste   |
+| él/usted      | bailó      |
+| nosotros      | bailamos   |
+| vosotros      | bailasteis |
+| ellos/ustedes | bailaron   |
+
+**** Imperfect tense                                                :verb:drill:
+     :PROPERTIES:
+     :VERB_TENSE: "imperfect"
+     :DRILL_CARD_TYPE: conjugate
+     :END:
+
+**** Future tense                                                   :verb:drill:
+    :PROPERTIES:
+    :VERB_TENSE: "future"
+    :DRILL_CARD_TYPE: conjugate
+    :END:
+
+
+*** Old Style Verb                                                       :drill:
     :PROPERTIES:
     :DRILL_CARD_TYPE: spanish_verb
     :END:
 
 Regular verb.
 
+
+** Random Numbers
+
+Below is an example of a card that tests the user's ability to translate random
+whole numbers to and from a non-English language. For it to work correctly, you
+must have the third party library [[http://www.emacswiki.org/emacs/spell-number.el][spell-number.el]] installed and loaded.
+
+The meaning of the item's properties is as follows:
+- =DRILL_LANGUAGE=: any language recognised by spell-number.el. At the time of
+  writing these include: catalan, danish, dutch, english-eur, english-gb,
+  english-us, esperanto, finnish, french-fr, french-ch, german, italian,
+  japanese, norwegian, portuguese-br, portuguese-pt, spanish and swedish.
+- =DRILL_NUMBER_MIN= and =DRILL_NUMBER_MAX=: the range between which the random
+  number will fall.
+
+
+*** Random Number 20-99                                                  :drill:
+  :PROPERTIES:
+  :DRILL_NUMBER_MIN: 20
+  :DRILL_NUMBER_MAX: 99
+  :DRILL_LANGUAGE: spanish
+  :DRILL_CARD_TYPE: translate_number
+  :END:
+
+# This comment is included so that the item body is non-empty. Items with
+# empty bodies are skipped during drill sessions.
+