Anonymous avatar Anonymous committed b5d5b9f

- All drill items now receive unique IDs (using the org-id module). This allows
various clever tricks such as 'synching' the item collections of two
people. At the beginning of a drill session, IDs are assigned automatically
to all drill items that do not possess them. This is slow if you have a large
collection, but it only happens once.
- New command 'org-drill-merge-buffers'. Called from buffer A, and given buffer
B, imports all the user-specific scheduling data from B into A, overwriting
any such information in A. Matching items are identified by their ID. Any
items in B that do not exist in A are copied to A. A scenario where this
could be useful:
* Tim decides to learn Swedish using an item collection (org file) made
publically available by Jane. (Before publishing it Jane used
'org-drill-strip-all-data' to remove her personal scheduling data from the
collection.) A few weeks later, Jane updates her collection, adding new
items and revising some old ones. Tim downloads the new collection and
imports his progress from his copy of the old collection, using
'org-drill-merge-buffers'. He can then discard his old copy. Any items HE
added to HIS copy of the old collection will not be lost -- they will be
appended to his copy of the new collection.
- Instead of overdue items being reviewed in a completely random order, they
are now ordered by the number of days overdue, so that the most overdue items
are seen first. When two items are the same number of days overdue, then the
order is random.
- slightly adjusted how 'random noise' is applied to intervals, to give wider
spread
- we now use the port of the Common Lisp random number generator, in cl.el,
instead of emacs' builtin RNG
- Random number generator is now reseeded using system time at the beginning of
each drill session.
- Hints inside clozed text areas are now invisible during drill sessions if the
clozed text is not itself being hidden, ie if your card contains
[Moscow|Russian city] you will only see [Moscow] in the answer.
- The '...' is now shown after the hint text rather than before it,
i.e. '[Russian city...]'. You can override this by actually including '...'
in the hint itself.
- The minibuffer prompt now displays the card 'type' for testing purposes, as a
single letter: N=new, Y=young, o=old, !=overdue, F=failed
- New card type: hide2cloze (hides exactly 2 randomly chosen areas of clozed
text)

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-30 16:14:35 "/>
+<meta name="generated" content="2011-05-10 16:51:44 "/>
 <meta name="author" content="Paul Sexton"/>
 <meta name="description" content=""/>
 <meta name="keywords" content=""/>
 <ul>
 <li><a href="#sec-4_1">Simple topics </a></li>
 <li><a href="#sec-4_2">Cloze deletion </a></li>
-<li><a href="#sec-4_3">Two-sided cards </a></li>
-<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>
+<li><a href="#sec-4_3">Clozed text hints </a></li>
+<li><a href="#sec-4_4">Two-sided cards </a></li>
+<li><a href="#sec-4_5">Multi-sided cards </a></li>
+<li><a href="#sec-4_6">Multi-cloze cards </a></li>
+<li><a href="#sec-4_7">User-defined card types </a></li>
+<li><a href="#sec-4_8">Empty cards </a></li>
 </ul>
 </li>
 <li><a href="#sec-5">Running the drill session </a></li>
 <li><a href="#sec-8_5">Per-file customisation settings </a></li>
 </ul>
 </li>
-<li><a href="#sec-9">Incremental reading </a></li>
+<li><a href="#sec-9">Sharing, merging and synchronising item collections </a></li>
+<li><a href="#sec-10">Incremental reading </a></li>
 </ul>
 </div>
 </div>
 where you have more control over what information is hidden from the user for
 recall purposes. For this reason, some other card types are defined, including:
 </p><ul>
-<li><a href="#sec-4_3">Two-sided cards</a>
+<li><a href="#sec-4_4">Two-sided cards</a>
 </li>
-<li><a href="#sec-4_4">Multi-sided cards</a>
+<li><a href="#sec-4_5">Multi-sided cards</a>
 </li>
-<li><a href="#sec-4_5">Multi-cloze cards</a>
+<li><a href="#sec-4_6">Multi-cloze cards</a>
 </li>
-<li><a href="#sec-4_6">User-defined card types</a>
+<li><a href="#sec-4_7">User-defined card types</a>
 </li>
 </ul>
 
 <p>
 When the user presses a key, the text "Tallinn" will become visible.
 </p>
-<p>
-Clozed text can also contain a "hint" about the answer. If the text
-surrounded by single square brackets contains a `|' character (vertical bar),
-all text after that character is treated as a hint, and will be visible when
-the rest of the text is hidden.
-</p>
-<p>
-Example:
-</p>
-
-
-
-<pre class="example">Type 1 hypersensitivity reactions are mediated by [immunoglobulin E|molecule]
-and [mast cells|cell type].
-</pre>
-
-
-
-<blockquote>
-
-<p>Type 1 hypersensitivity reactions are mediated by
-<font style="background-color: blue;" color="cyan">
-<tt>[&hellip;molecule]</tt></font>
-and <font style="background-color: blue;" color="cyan">
-<tt>[&hellip;cell type]</tt></font>.
-</p>
-</blockquote>
-
-
 
 </div>
 
 </div>
 
 <div id="outline-container-4_3" class="outline-3">
-<h3 id="sec-4_3"><a name="Two-sided-cards" id="Two-sided-cards"></a>Two-sided cards </h3>
+<h3 id="sec-4_3">Clozed text hints </h3>
 <div class="outline-text-3" id="text-4_3">
 
 
+
+<p>
+Clozed text can contain a "hint" about the answer. If the text surrounded
+by single square brackets contains a `|' character (vertical bar), all text
+after that character is treated as a hint. During testing, the hint text will
+be visible when the rest of the text is hidden, and invisible when the rest of
+the text is visible.
+</p>
+<p>
+Example:
+</p>
+
+
+
+<pre class="example">Type 1 hypersensitivity reactions are mediated by [immunoglobulin E|molecule]
+and [mast cells|cell type].
+</pre>
+
+
+
+<blockquote>
+
+<p>Type 1 hypersensitivity reactions are mediated by
+<font style="background-color: blue;" color="cyan">
+<tt>[molecule&hellip;]</tt></font>
+and <font style="background-color: blue;" color="cyan">
+<tt>[cell type&hellip;]</tt></font>.
+</p>
+</blockquote>
+
+
+
+</div>
+
+</div>
+
+<div id="outline-container-4_4" class="outline-3">
+<h3 id="sec-4_4"><a name="Two-sided-cards" id="Two-sided-cards"></a>Two-sided cards </h3>
+<div class="outline-text-3" id="text-4_4">
+
+
 <p>
 The remaining topic types all use the topic property, <code>DRILL_CARD_TYPE</code>. This
 property tells <code>org-drill</code> which function to use to present the topic during
 
 </div>
 
-<div id="outline-container-4_4" class="outline-3">
-<h3 id="sec-4_4"><a name="Multi-sided-cards" id="Multi-sided-cards"></a>Multi-sided cards </h3>
-<div class="outline-text-3" id="text-4_4">
+<div id="outline-container-4_5" class="outline-3">
+<h3 id="sec-4_5"><a name="Multi-sided-cards" id="Multi-sided-cards"></a>Multi-sided cards </h3>
+<div class="outline-text-3" id="text-4_5">
 
 
 
 
 </div>
 
-<div id="outline-container-4_5" class="outline-3">
-<h3 id="sec-4_5"><a name="Multi-cloze-cards" id="Multi-cloze-cards"></a>Multi-cloze cards </h3>
-<div class="outline-text-3" id="text-4_5">
+<div id="outline-container-4_6" class="outline-3">
+<h3 id="sec-4_6"><a name="Multi-cloze-cards" id="Multi-cloze-cards"></a>Multi-cloze cards </h3>
+<div class="outline-text-3" id="text-4_6">
 
 
 
 
 
 <pre class="example">The capital city of New Zealand is Wellington, which is located in the
-South Island and has a population of about 400,000.
+North Island and has a population of about 400,000.
 </pre>
 
 
 
 <pre class="example">* Fact
   :PROPERTIES:
-  :DRILL_CARD_TYPE: multicloze
+  :DRILL_CARD_TYPE: hide1cloze
   :END:
 
 The capital city of [New Zealand] is [Wellington], which is located in
 
 </div>
 
-<div id="outline-container-4_6" class="outline-3">
-<h3 id="sec-4_6"><a name="User-defined-card-types" id="User-defined-card-types"></a>User-defined card types </h3>
-<div class="outline-text-3" id="text-4_6">
+<div id="outline-container-4_7" class="outline-3">
+<h3 id="sec-4_7"><a name="User-defined-card-types" id="User-defined-card-types"></a>User-defined card types </h3>
+<div class="outline-text-3" id="text-4_7">
 
 
 
 
 </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">
+<div id="outline-container-4_8" class="outline-3">
+<h3 id="sec-4_8">Empty cards </h3>
+<div class="outline-text-3" id="text-4_8">
 
 
 
 </div>
 
 <div id="outline-container-9" class="outline-2">
-<h2 id="sec-9">Incremental reading </h2>
+<h2 id="sec-9">Sharing, merging and synchronising item collections </h2>
 <div class="outline-text-2" id="text-9">
 
 
 
 <p>
+Every drill item is automatically given a persistent unique "ID" the first time
+it is seen by Org-Drill. This means that if two different people subsequently
+edit or reschedule that item, Org-Drill can still tell that it is the same
+item. This in turn means that collections of items can be shared and edited in
+a collaborative manner.
+</p>
+<p>
+There are two commands that are useful in this regard:
+</p><ol>
+<li><code>org-drill-strip-all-data</code> - this command deletes all user-specific
+   scheduling data from every item in the current collection. (It takes the
+   same optional 'scope' argument as <code>org-drill</code> to define which items will
+   be processed by the command). User-specific data includes scheduling dates,
+   ease factors, number of failures and repetitions, and so on. All items are
+   reset to 'new' status. This command is useful if you want to share your
+   item collection with someone else.
+</li>
+<li><code>org-drill-merge-buffers</code> - When called from buffer A, it prompts you for
+   another buffer (B), which must also be loaded into Emacs. This command
+   imports all the user-specific scheduling data from buffer B into buffer A,
+   and deletes any such information in A. Matching items are identified by
+   their ID. Any items in B that do not exist in A are copied to A, in
+   the same hierarchical location if all the parent headings exist, otherwise
+   at the end of the buffer.
+</li>
+</ol>
+
+
+<p>
+An example scenario:
+</p>
+<p>
+Tim decides to learn Swedish using an item collection (<code>.org</code> file) made
+publically available by Jane.  (Before publishing it Jane used
+'org-drill-strip-all-data' to remove her personal scheduling data from the
+collection.)  A few weeks later, Jane updates her collection, adding new items
+and revising some old ones. Tim downloads the new collection and imports his
+progress from his copy of the old collection, using 'org-drill-merge-buffers',
+using the new collection as buffer A and the old one as buffer B. He can then
+discard the old copy. Any items HE added to HIS copy of the old collection
+(buffer B) will not be lost &ndash; they will be appended to his copy of the new
+collection.
+</p>
+<p>
+Of course the sharing does not need to be 'public'. You and a friend might be
+learning a language or some other topic together. You each maintain a card
+collection. Periodically your friend sends you a copy of their collection --
+you run <code>org-drill-merge-buffers</code> on it, always using your own collection as
+buffer B so that your own scheduling progress is carried over. Other times you
+send your friend a copy of your collection, and he or she follows the same
+procedure.
+</p>
+
+</div>
+
+</div>
+
+<div id="outline-container-10" class="outline-2">
+<h2 id="sec-10">Incremental reading </h2>
+<div class="outline-text-2" id="text-10">
+
+
+
+<p>
 An innovative feature of the program SuperMemo is so-called "incremental
 reading". This refers to the ability to quickly and easily make drill items
 from selected portions of text as you read an article (a web page for
 </div>
 </div>
 <div id="postamble">
-<p class="date">Date: 2011-04-30 16:14:35 </p>
+<p class="date">Date: 2011-05-10 16:51:44 </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 -*-
+# -*- mode: org; coding: utf-8-unix -*-
 #+STARTUP: showall
 #+OPTIONS: num:nil
 #+TITLE: Org-Drill
 
 When the user presses a key, the text "Tallinn" will become visible.
 
-Clozed text can also contain a "hint" about the answer. If the text
-surrounded by single square brackets contains a `|' character (vertical bar),
-all text after that character is treated as a hint, and will be visible when
-the rest of the text is hidden.
+
+** Clozed text hints
+
+
+Clozed text can contain a "hint" about the answer. If the text surrounded
+by single square brackets contains a `|' character (vertical bar), all text
+after that character is treated as a hint. During testing, the hint text will
+be visible when the rest of the text is hidden, and invisible when the rest of
+the text is visible.
 
 Example:
 
 #+BEGIN_QUOTE
 Type 1 hypersensitivity reactions are mediated by
 @<font style="background-color: blue;" color="cyan">
-@<tt>[...molecule]@</tt>@</font>
+@<tt>[molecule...]@</tt>@</font>
 and @<font style="background-color: blue;" color="cyan">
-@<tt>[...cell type]@</tt>@</font>.
+@<tt>[cell type...]@</tt>@</font>.
 #+END_QUOTE
 
 
 
 #+BEGIN_EXAMPLE
 The capital city of New Zealand is Wellington, which is located in the
-South Island and has a population of about 400,000.
+North Island and has a population of about 400,000.
 #+END_EXAMPLE
 
 There is more than one fact in this statement -- you could create a single
 #+BEGIN_EXAMPLE
 * Fact
   :PROPERTIES:
-  :DRILL_CARD_TYPE: multicloze
+  :DRILL_CARD_TYPE: hide1cloze
   :END:
 
 The capital city of [New Zealand] is [Wellington], which is located in
 #+END_EXAMPLE
 
 
+* Sharing, merging and synchronising item collections
+
+
+Every drill item is automatically given a persistent unique "ID" the first time
+it is seen by Org-Drill. This means that if two different people subsequently
+edit or reschedule that item, Org-Drill can still tell that it is the same
+item. This in turn means that collections of items can be shared and edited in
+a collaborative manner.
+
+There are two commands that are useful in this regard:
+1. =org-drill-strip-all-data= - this command deletes all user-specific
+   scheduling data from every item in the current collection. (It takes the
+   same optional 'scope' argument as =org-drill= to define which items will
+   be processed by the command). User-specific data includes scheduling dates,
+   ease factors, number of failures and repetitions, and so on. All items are
+   reset to 'new' status. This command is useful if you want to share your
+   item collection with someone else.
+2. =org-drill-merge-buffers= - When called from buffer A, it prompts you for
+   another buffer (B), which must also be loaded into Emacs. This command
+   imports all the user-specific scheduling data from buffer B into buffer A,
+   and deletes any such information in A. Matching items are identified by
+   their ID. Any items in B that do not exist in A are copied to A, in
+   the same hierarchical location if all the parent headings exist, otherwise
+   at the end of the buffer.
+
+An example scenario:
+
+Tim decides to learn Swedish using an item collection (=.org= file) made
+publically available by Jane.  (Before publishing it Jane used
+'org-drill-strip-all-data' to remove her personal scheduling data from the
+collection.)  A few weeks later, Jane updates her collection, adding new items
+and revising some old ones. Tim downloads the new collection and imports his
+progress from his copy of the old collection, using 'org-drill-merge-buffers',
+using the new collection as buffer A and the old one as buffer B. He can then
+discard the old copy. Any items HE added to HIS copy of the old collection
+(buffer B) will not be lost -- they will be appended to his copy of the new
+collection.
+
+Of course the sharing does not need to be 'public'. You and a friend might be
+learning a language or some other topic together. You each maintain a card
+collection. Periodically your friend sends you a copy of their collection --
+you run =org-drill-merge-buffers= on it, always using your own collection as
+buffer B so that your own scheduling progress is carried over. Other times you
+send your friend a copy of your collection, and he or she follows the same
+procedure.
+
+
 * Incremental reading
 
 
 or give it different tags or properties, for example.
 
 
-* Still to do                                                         :noexport:
-
-
-- =org-drill-question-tag= should use a tag match string, rather than a
-  single tag? Can use =org-make-tag-matcher=.
-- perhaps take account of item priorities, showing high priority items first
-- get tooltips to work for old/new/etc counts during review?
 ;;; org-drill.el - Self-testing using spaced repetition
 ;;;
 ;;; Author: Paul Sexton <eeeickythump@gmail.com>
-;;; Version: 2.2
+;;; Version: 2.3
 ;;; Repository at http://bitbucket.org/eeeickythump/org-drill/
 ;;;
 ;;;
 (eval-when-compile (require 'cl))
 (eval-when-compile (require 'hi-lock))
 (require 'org)
+(require 'org-id)
 (require 'org-learn)
 
 
     ("twosided" . org-drill-present-two-sided-card)
     ("multisided" . org-drill-present-multi-sided-card)
     ("hide1cloze" . org-drill-present-multicloze-hide1)
+    ("hide2cloze" . org-drill-present-multicloze-hide2)
     ("show1cloze" . org-drill-present-multicloze-show1)
     ("multicloze" . org-drill-present-multicloze-hide1)
     ("conjugate" org-drill-present-verb-conjugation
 (defvar *org-drill-due-entry-count* 0)
 (defvar *org-drill-overdue-entry-count* 0)
 (defvar *org-drill-due-tomorrow-count* 0)
+(defvar *org-drill-current-entry-schedule-type* nil)
 (defvar *org-drill-overdue-entries* nil
   "List of markers for items that are considered 'overdue', based on
 the value of ORG-DRILL-OVERDUE-INTERVAL-FACTOR.")
   (let ((idx (gensym)))
     `(if (null ,place)
          nil
-       (let ((,idx (random (length ,place))))
+       (let ((,idx (random* (length ,place))))
          (prog1 (nth ,idx ,place)
            (setq ,place (append (subseq ,place 0 ,idx)
                                 (subseq ,place (1+ ,idx)))))))))
 	temp
 	(len (length list)))
     (while (< i len)
-      (setq j (+ i (random (- len i))))
+      (setq j (+ i (random* (- len i))))
       (setq temp (nth i list))
       (setf (nth i list) (nth j list))
       (setf (nth j list) temp)
        (org-drill-unhide-clozed-text))))
 
 
+(defmacro with-hidden-cloze-hints (&rest body)
+  `(progn
+     (org-drill-hide-cloze-hints)
+     (unwind-protect
+         (progn
+           ,@body)
+       (org-drill-unhide-text))))
+
+
 (defmacro with-hidden-comments (&rest body)
   `(progn
      (if org-drill-hide-item-headings-p
      (unwind-protect
          (progn
            ,@body)
-       (org-drill-unhide-comments))))
+       (org-drill-unhide-text))))
 
 
 (defun org-drill-days-since-last-review ()
 
 ;;; From http://www.supermemo.com/english/ol/sm5.htm
 (defun org-drill-random-dispersal-factor ()
+  "Returns a random number between 0.5 and 1.5."
   (let ((a 0.047)
         (b 0.092)
         (p (- (random* 1.0) 0.5)))
                    (sign p)))
          100.0))))
 
+(defun pseudonormal (mean variation)
+  "Random numbers in a pseudo-normal distribution with mean MEAN, range
+    MEAN-VARIATION to MEAN+VARIATION"
+  (+  (random* variation)
+      (random* variation)
+      (- variation)
+      mean))
+
 
 (defun org-drill-early-interval-factor (optimal-factor
                                                 optimal-interval
       (setq interval (inter-repetition-interval-sm5
                       last-interval n ef of-matrix))
       (if org-drill-add-random-noise-to-intervals-p
-          (setq interval (+ last-interval
-                            (* (- interval last-interval)
-                               (org-drill-random-dispersal-factor)))))
+          (setq interval (* interval (org-drill-random-dispersal-factor))))
       (list interval
             (1+ n)
             ef
     (list
      (if (and org-drill-add-random-noise-to-intervals-p
               (plusp next-interval))
-         (+ last-interval (* (- next-interval last-interval)
-                             (org-drill-random-dispersal-factor)))
+         (* next-interval (org-drill-random-dispersal-factor))
        next-interval)
      repeats
      (org-drill-simple8-quality->ease meanq)
             (concat "Press key for answer, "
                     "e=edit, t=tags, s=skip, q=quit."))))
     (setq prompt
-          (format "%s %s %s %s %s"
+          (format "%s %s %s %s %s %s"
+                  (propertize
+                   (char-to-string
+                    (case *org-drill-current-entry-schedule-type*
+                      (new ?N) (young ?Y) (old ?o) (overdue ?!) (failed ?F) (t ??)))
+                   'face `(:foreground
+                           ,(case *org-drill-current-entry-schedule-type*
+                              (new org-drill-new-count-color)
+                              ((young old) org-drill-mature-count-color)
+                              ((overdue failed) org-drill-failed-count-color)
+                              (t org-drill-done-count-color))))
                   (propertize
                    (number-to-string (length *org-drill-done-entries*))
                    'face `(:foreground ,org-drill-done-count-color)
       (org-drill-hide-region (match-beginning 0) (match-end 0)))))
 
 
-(defun org-drill-unhide-comments ()
+(defun org-drill-unhide-text ()
   ;; This will also unhide the item's heading.
   (save-excursion
     (dolist (ovl (overlays-in (point-min) (point-max)))
     (overlay-put ovl 'category
                  'org-drill-cloze-overlay-defaults)
     (when (find ?| (match-string 0))
-      (overlay-put ovl
-                   'display
-                   (format "[...%s]"
-                           (substring-no-properties
-                            (match-string 0)
-                            (1+ (position ?| (match-string 0)))
-                            (1- (length (match-string 0)))))))))
+      (let ((hint (substring-no-properties
+                   (match-string 0)
+                   (1+ (position ?| (match-string 0)))
+                   (1- (length (match-string 0))))))
+        (overlay-put
+         ovl 'display
+         ;; If hint is like `X...' then display [X...]
+         ;; otherwise display [...X]
+         (format (if (string-match-p "\\.\\.\\." hint) "[%s]" "[%s...]")
+                 hint))))))
+
+
+(defun org-drill-hide-cloze-hints ()
+  (save-excursion
+    (while (re-search-forward org-drill-cloze-regexp nil t)
+      (unless (or (org-pos-in-regexp (match-beginning 0)
+                                     org-bracket-link-regexp 1)
+                  (null (match-beginning 2))) ; hint subexpression matched
+        (org-drill-hide-region (match-beginning 2) (match-end 2))))))
 
 
 (defmacro with-replaced-entry-text (text &rest body)
      (unwind-protect
          (progn
            ,@body)
-       (org-drill-unhide-comments))))
+       (org-drill-unhide-text))))
 
 
 (defun org-drill-replace-entry-heading (heading)
         (delete-overlay ovl)))))
 
 
-(defun org-drill-get-entry-text ()
-  (substring-no-properties
-   (org-agenda-get-some-entry-text (point-marker) 100)))
+(defun org-drill-get-entry-text (&optional keep-properties-p)
+  (let ((text (org-agenda-get-some-entry-text (point-marker) 100)))
+    (if keep-properties-p
+        text
+      (substring-no-properties text))))
 
 
 (defun org-drill-entry-empty-p ()
 
 (defun org-drill-present-simple-card ()
   (with-hidden-comments
-   (with-hidden-cloze-text
-    (org-drill-hide-all-subheadings-except nil)
-    (org-display-inline-images t)
-    (org-cycle-hide-drawers 'all)
-    (prog1 (org-drill-presentation-prompt)
-      (org-drill-hide-subheadings-if 'org-drill-entry-p)))))
+   (with-hidden-cloze-hints
+    (with-hidden-cloze-text
+     (org-drill-hide-all-subheadings-except nil)
+     (org-display-inline-images t)
+     (org-cycle-hide-drawers 'all)
+     (prog1 (org-drill-presentation-prompt)
+       (org-drill-hide-subheadings-if 'org-drill-entry-p))))))
+
+
+(defun org-drill-present-default-answer (reschedule-fn)
+  (org-drill-hide-subheadings-if 'org-drill-entry-p)
+  (org-drill-unhide-clozed-text)
+  (with-hidden-cloze-hints
+   (funcall reschedule-fn)))
 
 
 (defun org-drill-present-two-sided-card ()
   (with-hidden-comments
-   (with-hidden-cloze-text
-    (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
-      (when drill-sections
-        (save-excursion
-          (goto-char (nth (random (min 2 (length drill-sections)))
-                          drill-sections))
-          (org-show-subtree)))
-      (org-display-inline-images t)
-      (org-cycle-hide-drawers 'all)
-      (prog1
-          (org-drill-presentation-prompt)
-        (org-drill-hide-subheadings-if 'org-drill-entry-p))))))
+   (with-hidden-cloze-hints
+    (with-hidden-cloze-text
+     (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
+       (when drill-sections
+         (save-excursion
+           (goto-char (nth (random* (min 2 (length drill-sections)))
+                           drill-sections))
+           (org-show-subtree)))
+       (org-display-inline-images t)
+       (org-cycle-hide-drawers 'all)
+       (prog1 (org-drill-presentation-prompt)
+         (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
 
 
 
 (defun org-drill-present-multi-sided-card ()
   (with-hidden-comments
-   (with-hidden-cloze-text
-    (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
-      (when drill-sections
-        (save-excursion
-          (goto-char (nth (random (length drill-sections)) drill-sections))
-          (org-show-subtree)))
+   (with-hidden-cloze-hints
+    (with-hidden-cloze-text
+     (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
+       (when drill-sections
+         (save-excursion
+           (goto-char (nth (random* (length drill-sections)) drill-sections))
+           (org-show-subtree)))
+       (org-display-inline-images t)
+       (org-cycle-hide-drawers 'all)
+       (prog1 (org-drill-presentation-prompt)
+         (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
+
+
+(defun org-drill-present-multicloze-hide-n (number-to-hide)
+  "Hides NUMBER-TO-HIDE pieces of text that are marked for cloze deletion,
+chosen at random."
+  (with-hidden-comments
+   (with-hidden-cloze-hints
+    (let ((item-end nil)
+          (match-count 0)
+          (body-start (or (cdr (org-get-property-block))
+                          (point))))
+      (org-drill-hide-all-subheadings-except nil)
+      (save-excursion
+        (outline-next-heading)
+        (setq item-end (point)))
+      (save-excursion
+        (goto-char body-start)
+        (while (re-search-forward org-drill-cloze-regexp item-end t)
+          (incf match-count)))
+      (when (plusp match-count)
+        (let ((match-nums (subseq (shuffle-list (loop for i from 1 to match-count
+                                                      collect i))
+                                  0 number-to-hide)))
+          (dolist (pos-to-hide match-nums)
+            (save-excursion
+              (goto-char body-start)
+              (re-search-forward org-drill-cloze-regexp
+                                 item-end t pos-to-hide)
+              (org-drill-hide-matched-cloze-text)))))
       (org-display-inline-images t)
       (org-cycle-hide-drawers 'all)
-      (prog1
-          (org-drill-presentation-prompt)
-        (org-drill-hide-subheadings-if 'org-drill-entry-p))))))
+      (prog1 (org-drill-presentation-prompt)
+        (org-drill-hide-subheadings-if 'org-drill-entry-p)
+        (org-drill-unhide-clozed-text))))))
 
 
 (defun org-drill-present-multicloze-hide1 ()
   "Hides one of the pieces of text that are marked for cloze deletion,
 chosen at random."
-  (with-hidden-comments
-   (let ((item-end nil)
-         (match-count 0)
-         (body-start (or (cdr (org-get-property-block))
-                         (point))))
-     (org-drill-hide-all-subheadings-except nil)
-     (save-excursion
-       (outline-next-heading)
-       (setq item-end (point)))
-     (save-excursion
-       (goto-char body-start)
-       (while (re-search-forward org-drill-cloze-regexp item-end t)
-         (incf match-count)))
-     (when (plusp match-count)
-       (save-excursion
-         (goto-char body-start)
-         (re-search-forward org-drill-cloze-regexp
-                            item-end t (1+ (random match-count)))
-         (org-drill-hide-matched-cloze-text)))
-     (org-display-inline-images t)
-     (org-cycle-hide-drawers 'all)
-     (prog1 (org-drill-presentation-prompt)
-       (org-drill-hide-subheadings-if 'org-drill-entry-p)
-       (org-drill-unhide-clozed-text)))))
+  (org-drill-present-multicloze-hide-n 1))
+
+
+(defun org-drill-present-multicloze-hide2 ()
+  "Hides two of the pieces of text that are marked for cloze deletion,
+chosen at random."
+  (org-drill-present-multicloze-hide-n 2))
+
+
+;; (defun org-drill-present-multicloze-hide1 ()
+;;   "Hides one of the pieces of text that are marked for cloze deletion,
+;; chosen at random."
+;;   (with-hidden-comments
+;;    (let ((item-end nil)
+;;          (match-count 0)
+;;          (body-start (or (cdr (org-get-property-block))
+;;                          (point))))
+;;      (org-drill-hide-all-subheadings-except nil)
+;;      (save-excursion
+;;        (outline-next-heading)
+;;        (setq item-end (point)))
+;;      (save-excursion
+;;        (goto-char body-start)
+;;        (while (re-search-forward org-drill-cloze-regexp item-end t)
+;;          (incf match-count)))
+;;      (when (plusp match-count)
+;;        (save-excursion
+;;          (goto-char body-start)
+;;          (re-search-forward org-drill-cloze-regexp
+;;                             item-end t (1+ (random match-count)))
+;;          (org-drill-hide-matched-cloze-text)))
+;;      (org-display-inline-images t)
+;;      (org-cycle-hide-drawers 'all)
+;;      (prog1 (org-drill-presentation-prompt)
+;;        (org-drill-hide-subheadings-if 'org-drill-entry-p)
+;;        (org-drill-unhide-clozed-text)))))
 
 
 (defun org-drill-present-multicloze-show1 ()
 the pieces of text that are marked for cloze deletion, except for one
 piece which is chosen at random."
   (with-hidden-comments
-   (let ((item-end nil)
-         (match-count 0)
-         (body-start (or (cdr (org-get-property-block))
-                         (point))))
-     (org-drill-hide-all-subheadings-except nil)
-     (save-excursion
-       (outline-next-heading)
-       (setq item-end (point)))
-     (save-excursion
-       (goto-char body-start)
-       (while (re-search-forward org-drill-cloze-regexp item-end t)
-         (incf match-count)))
-     (when (plusp match-count)
-       (let ((match-to-hide (random match-count)))
-         (save-excursion
-           (goto-char body-start)
-           (dotimes (n match-count)
-             (re-search-forward org-drill-cloze-regexp
-                                item-end t)
-             (unless (= n match-to-hide)
-               (org-drill-hide-matched-cloze-text))))))
-     (org-display-inline-images t)
-     (org-cycle-hide-drawers 'all)
-     (prog1 (org-drill-presentation-prompt)
-       (org-drill-hide-subheadings-if 'org-drill-entry-p)
-       (org-drill-unhide-clozed-text)))))
+   (with-hidden-cloze-hints
+    (let ((item-end nil)
+          (match-count 0)
+          (body-start (or (cdr (org-get-property-block))
+                          (point))))
+      (org-drill-hide-all-subheadings-except nil)
+      (save-excursion
+        (outline-next-heading)
+        (setq item-end (point)))
+      (save-excursion
+        (goto-char body-start)
+        (while (re-search-forward org-drill-cloze-regexp item-end t)
+          (incf match-count)))
+      (when (plusp match-count)
+        (let ((match-to-hide (random* match-count)))
+          (save-excursion
+            (goto-char body-start)
+            (dotimes (n match-count)
+              (re-search-forward org-drill-cloze-regexp
+                                 item-end t)
+              (unless (= n match-to-hide)
+                (org-drill-hide-matched-cloze-text))))))
+      (org-display-inline-images t)
+      (org-cycle-hide-drawers 'all)
+      (prog1 (org-drill-presentation-prompt)
+        (org-drill-hide-subheadings-if 'org-drill-entry-p)
+        (org-drill-unhide-clozed-text))))))
 
 
 (defun org-drill-present-card-using-text (question &optional answer)
   ;;(unless (org-at-heading-p)
   ;;  (org-back-to-heading))
   (let ((card-type (org-entry-get (point) "DRILL_CARD_TYPE"))
-        (cont nil)
-        (answer-fn nil))
+        (answer-fn 'org-drill-present-default-answer)
+        (cont nil))
     (org-drill-save-visibility
      (save-restriction
        (org-narrow-to-subtree)
 
        (let ((presentation-fn (cdr (assoc card-type org-drill-card-type-alist))))
          (if (listp presentation-fn)
-             (psetq answer-fn (second presentation-fn)
+             (psetq answer-fn (or (second presentation-fn)
+                                  'org-drill-present-default-answer)
                     presentation-fn (first presentation-fn)))
          (cond
           (presentation-fn
          'skip)
         (t
          (save-excursion
-           (cond
-            (answer-fn
-             (funcall answer-fn (lambda () (org-drill-reschedule))))
-            (t
-             (org-drill-reschedule))))))))))
+           (funcall answer-fn
+                    (lambda () (org-drill-reschedule))))))))))
 
 
 (defun org-drill-entries-pending-p ()
           ((and *org-drill-failed-entries*
                 (not (org-drill-maximum-item-count-reached-p))
                 (not (org-drill-maximum-duration-reached-p)))
+           (setq *org-drill-current-entry-schedule-type* 'failed)
            (pop-random *org-drill-failed-entries*))
           ;; Next priority is overdue items.
           ((and *org-drill-overdue-entries*
                 (not (org-drill-maximum-item-count-reached-p))
                 (not (org-drill-maximum-duration-reached-p)))
-           (pop-random *org-drill-overdue-entries*))
+           ;; We use `pop', not `pop-random', because we have already
+           ;; sorted overdue items into a random order which takes
+           ;; number of days overdue into account.
+           (setq *org-drill-current-entry-schedule-type* 'overdue)
+           (pop *org-drill-overdue-entries*))
           ;; Next priority is 'young' items.
           ((and *org-drill-young-mature-entries*
                 (not (org-drill-maximum-item-count-reached-p))
                 (not (org-drill-maximum-duration-reached-p)))
+           (setq *org-drill-current-entry-schedule-type* 'young)
            (pop-random *org-drill-young-mature-entries*))
           ;; Next priority is newly added items, and older entries.
           ;; We pool these into a single group.
                     *org-drill-old-mature-entries*)
                 (not (org-drill-maximum-item-count-reached-p))
                 (not (org-drill-maximum-duration-reached-p)))
-           (if (< (random (+ (length *org-drill-new-entries*)
-                             (length *org-drill-old-mature-entries*)))
-                  (length *org-drill-new-entries*))
-               (pop-random *org-drill-new-entries*)
-             ;; else
-             (pop-random *org-drill-old-mature-entries*)))
+           (cond
+            ((< (random* (+ (length *org-drill-new-entries*)
+                            (length *org-drill-old-mature-entries*)))
+                (length *org-drill-new-entries*))
+             (setq *org-drill-current-entry-schedule-type* 'new)
+             (pop-random *org-drill-new-entries*))
+            (t
+             (setq *org-drill-current-entry-schedule-type* 'old)
+             (pop-random *org-drill-old-mature-entries*))))
           ;; After all the above are done, last priority is items
           ;; that were failed earlier THIS SESSION.
           (*org-drill-again-entries*
+           (setq *org-drill-current-entry-schedule-type* 'failed)
            (pop *org-drill-again-entries*))
           (t                            ; nothing left -- return nil
            (return-from org-drill-pop-next-pending-entry nil)))))
           (format
            "%d items reviewed. Session duration %s.
 %d/%d items awaiting review (%s, %s, %s, %s, %s).
-Tomorrow, %d more items will become due for review.
 
 Recall of reviewed items:
  Excellent (5):     %3d%%   |   Near miss (2):      %3d%%
  Hard (3):          %3d%%   |   Abject failure (0): %3d%%
 
 You successfully recalled %d%% of reviewed items (quality > %s)
+Tomorrow, %d more items will become due for review.
 Session finished. Press a key to continue..."
            (length *org-drill-done-entries*)
            (format-seconds "%h:%.2m:%.2s"
             (format "%d old"
                     (length *org-drill-old-mature-entries*))
             'face `(:foreground ,org-drill-mature-count-color))
-           *org-drill-due-tomorrow-count*
            (round (* 100 (count 5 *org-drill-session-qualities*))
                   (max 1 (length *org-drill-session-qualities*)))
            (round (* 100 (count 2 *org-drill-session-qualities*))
                   (max 1 (length *org-drill-session-qualities*)))
            pass-percent
            org-drill-failure-quality
+           *org-drill-due-tomorrow-count*
            ))
 
     (while (not (input-pending-p))
     (free-marker m)))
 
 
+(defun org-drill-order-overdue-entries (overdue-data)
+  (setq *org-drill-overdue-entries*
+        (mapcar 'car
+                (sort (shuffle-list overdue-data)
+                      (lambda (a b) (> (cdr a) (cdr b)))))))
+
 
 (defun org-drill (&optional scope resume-p)
   "Begin an interactive 'drill session'. The user is asked to
 
   (interactive)
   (let ((end-pos nil)
+        (overdue-data nil)
         (cnt 0))
     (block org-drill
       (unless resume-p
               *org-drill-again-entries* nil)
         (setq *org-drill-session-qualities* nil)
         (setq *org-drill-start-time* (float-time (current-time))))
+      (setq *random-state* (make-random-state t)) ; reseed RNG
       (unwind-protect
           (save-excursion
             (unless resume-p
-              (let ((org-trust-scanner-tags t))
+              (let ((org-trust-scanner-tags t)
+                    (warned-about-id-creation nil))
                 (org-map-entries
                  (lambda ()
                    (when (zerop (% (incf cnt) 50))
                                  (length *org-drill-old-mature-entries*)
                                  (length *org-drill-failed-entries*))
                               (make-string (ceiling cnt 50) ?.)))
-                   (let ((due (org-drill-entry-days-overdue))
-                         (last-int (org-drill-entry-last-interval 1)))
-                     (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*)
-                       (if (eq -1 due)
-                           (incf *org-drill-due-tomorrow-count*)))
-                      ((org-drill-entry-new-p)
-                       (push (point-marker) *org-drill-new-entries*))
-                      ((<= (org-drill-entry-last-quality 9999)
-                           org-drill-failure-quality)
-                       ;; Mature entries that were failed last time are FAILED,
-                       ;; regardless of how young, old or overdue they are.
-                       (push (point-marker) *org-drill-failed-entries*))
-                      ((org-drill-entry-overdue-p due last-int)
-                       ;; Overdue status overrides young versus old distinction.
-                       (push (point-marker) *org-drill-overdue-entries*))
-                      ((<= (org-drill-entry-last-interval 9999)
-                           org-drill-days-before-old)
-                       ;; Item is 'young'.
-                       (push (point-marker) *org-drill-young-mature-entries*))
-                      (t
-                       (push (point-marker) *org-drill-old-mature-entries*)))))
-                 (concat "+" org-drill-question-tag) scope)))
+                   (cond
+                    ((not (org-drill-entry-p))
+                     nil)               ; skip
+                    (t
+                     (when (and (not warned-about-id-creation)
+                                (null (org-id-get)))
+                       (message (concat "Creating unique IDs for items "
+                                        "(slow, but only happens once)"))
+                       (sit-for 0.5)
+                       (setq warned-about-id-creation t))
+                     (org-id-get-create) ; ensure drill entry has unique ID
+                     (let ((due (org-drill-entry-days-overdue))
+                           (last-int (org-drill-entry-last-interval 1)))
+                       (cond
+                        ((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*)
+                         (if (eq -1 due)
+                             (incf *org-drill-due-tomorrow-count*)))
+                        ((org-drill-entry-new-p)
+                         (push (point-marker) *org-drill-new-entries*))
+                        ((<= (org-drill-entry-last-quality 9999)
+                             org-drill-failure-quality)
+                         ;; Mature entries that were failed last time are
+                         ;; FAILED, regardless of how young, old or overdue
+                         ;; they are.
+                         (push (point-marker) *org-drill-failed-entries*))
+                        ((org-drill-entry-overdue-p due last-int)
+                         ;; Overdue status overrides young versus old
+                         ;; distinction.
+                         ;; Store marker + due, for sorting of overdue entries
+                         (push (cons (point-marker) due) overdue-data))
+                        ((<= (org-drill-entry-last-interval 9999)
+                             org-drill-days-before-old)
+                         ;; Item is 'young'.
+                         (push (point-marker)
+                               *org-drill-young-mature-entries*))
+                        (t
+                         (push (point-marker)
+                               *org-drill-old-mature-entries*)))))))
+                 (concat "+" org-drill-question-tag) scope)
+                ;; Order 'overdue' items so that the most overdue will tend to
+                ;; come up for review first, while keeping exact order random
+                (org-drill-order-overdue-entries overdue-data)
+                (setq *org-drill-overdue-entry-count*
+                      (length *org-drill-overdue-entries*))))
             (setq *org-drill-due-entry-count* (org-drill-pending-entry-count))
-            (setq *org-drill-overdue-entry-count*
-                  (length *org-drill-overdue-entries*))
             (cond
              ((and (null *org-drill-new-entries*)
                    (null *org-drill-failed-entries*)
   (org-drill nil t))
 
 
-(defun org-drill-strip-data (&optional scope)
+(defun org-drill-strip-entry-data ()
+  (org-delete-property "LEARN_DATA")
+  (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))
+
+
+(defun org-drill-strip-all-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,
   (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))
+    (org-map-entries 'org-drill-strip-entry-data
                      "" scope)
     (message "Done.")))
 
 
 (add-hook 'org-mode-hook
           (lambda ()
-            (if org-drill-use-visible-cloze-face-p
-                (font-lock-add-keywords
-                 'org-mode
-                 org-drill-cloze-keywords
-                 nil))))
+            (when org-drill-use-visible-cloze-face-p
+              (font-lock-add-keywords 'org-mode
+                                      org-drill-cloze-keywords
+                                      nil))))
 
 
-(provide 'org-drill)
+;;; Synching card collections =================================================
+
+
+(defvar *org-drill-dest-id-table* (make-hash-table :test 'equal))
+
+
+(defun org-drill-copy-entry-to-other-buffer (dest &optional path)
+  "Copy the subtree at point to the buffer DEST. The copy will receive
+the tag 'imported'."
+  (block org-drill-copy-entry-to-other-buffer
+    (save-excursion
+      (let ((src (current-buffer))
+            (m nil))
+        (flet ((paste-tree-here (&optional level)
+                                (org-paste-subtree level)
+                                (org-toggle-tag "imported" 'on)
+                                (org-map-entries
+                                 (lambda ()
+                                   (let ((id (org-id-get)))
+                                     (unless (gethash id *org-drill-dest-id-table*)
+                                       (puthash id (point-marker)
+                                                *org-drill-dest-id-table*))))
+                                 (concat "+" org-drill-question-tag) 'tree)))
+          (unless path
+            (setq path (org-get-outline-path)))
+          (switch-to-buffer dest)
+          (setq m
+                (condition-case nil
+                    (org-find-olp path t)
+                  (error                ; path does not exist in DEST
+                   (return-from org-drill-copy-entry-to-other-buffer
+                     (cond
+                      ((cdr path)
+                       (org-drill-copy-entry-to-other-buffer
+                        dest (butlast path)))
+                      (t
+                       ;; We've looked all the way up the path
+                       ;; Default to appending to the end of DEST
+                       (goto-char (point-max))
+                       (newline)
+                       (paste-tree-here)))))))
+          (goto-char m)
+          (org-forward-same-level)
+          (newline)
+          (forward-line -1)
+          (paste-tree-here (1+ (or (org-current-level) 0)))
+          )))))
+
+
+
+(defun org-drill-merge-buffers (src &optional dest)
+  "SRC and DEST are two org mode buffers containing drill items.
+For each drill item in DEST that shares an ID with an item in SRC,
+overwrite scheduling data in DEST with data taken from the item in SRC.
+This is intended for use when two people are sharing a set of drill items,
+one person has made some updates to the item set, and the other person
+wants to migrate to the updated set without losing their scheduling data."
+  ;; In future could look at what to do if we find an item in SRC whose ID
+  ;; is not present in DEST -- copy the whole item to DEST?
+  ;; org-copy-subtree --> org-paste-subtree
+  ;; could try to put it "near" the closest marker
+  (interactive "bImport scheduling info from which buffer?")
+  (unless dest
+    (setq dest (current-buffer)))
+  (setq src (get-buffer src)
+        dest (get-buffer dest))
+  (when (yes-or-no-p
+         (format
+          (concat "About to overwrite all scheduling data for drill items in `%s' "
+                  "with information taken from matching items in `%s'. Proceed? ")
+          (buffer-name dest) (buffer-name src)))
+    ;; Compile list of all IDs in the destination buffer.
+    (clrhash *org-drill-dest-id-table*)
+    (with-current-buffer dest
+      (org-map-entries
+       (lambda ()
+         (let ((this-id (org-id-get)))
+           (when this-id
+             (puthash this-id (point-marker) *org-drill-dest-id-table*))))
+       (concat "+" org-drill-question-tag)))
+    ;; Look through all entries in source buffer.
+    (with-current-buffer src
+      (org-map-entries
+       (lambda ()
+         (let ((id (org-id-get))
+               (last-quality nil) (last-reviewed nil)
+               (scheduled-time nil))
+           (cond
+            ((or (null id)
+                 (not (org-drill-entry-p)))
+             nil)
+            ((gethash id *org-drill-dest-id-table*)
+             ;; This entry matches an entry in dest. Retrieve all its
+             ;; scheduling data, then go to the matching location in dest
+             ;; and write the data.
+             (let ((marker (gethash id *org-drill-dest-id-table*)))
+               (destructuring-bind (last-interval repetitions failures
+                                                  total-repeats meanq ease)
+                   (org-drill-get-item-data)
+                 (setq last-reviewed (org-entry-get (point) "DRILL_LAST_REVIEWED")
+                       last-quality (org-entry-get (point) "DRILL_LAST_QUALITY")
+                       scheduled-time (org-get-scheduled-time (point)))
+                 (save-excursion
+                   ;; go to matching entry in destination buffer
+                   (switch-to-buffer (marker-buffer marker))
+                   (goto-char marker)
+                   (org-drill-strip-entry-data)
+                   (unless (zerop total-repeats)
+                     (org-drill-store-item-data last-interval repetitions failures
+                                                total-repeats meanq ease)
+                     (org-set-property "LAST_QUALITY" last-quality)
+                     (org-set-property "LAST_REVIEWED" last-reviewed)
+                     (if scheduled-time
+                         (org-schedule nil scheduled-time)))))
+               (free-marker marker)))
+            (t
+             ;; item in SRC has ID, but no matching ID in DEST.
+             ;; It must be a new item that does not exist in DEST.
+             ;; Copy the entire item to the *end* of DEST.
+             (org-drill-copy-entry-to-other-buffer dest)))))
+       ;; (org-copy-subtree)
+       ;; (save-excursion
+       ;;   (with-current-buffer dest
+       ;;     (goto-char (point-max))
+       ;;     (newline)
+       ;;     (org-paste-subtree)
+       ;;     ;; Check if item has any child drill items. If it does,
+       ;;     ;; store their IDs in the hashtable, to signify that they
+       ;;     ;; now exist in DEST.
+       ;;     (org-map-entries
+       ;;      (lambda ()
+       ;;        (let ((id (org-id-get)))
+       ;;          (unless (gethash id *org-drill-dest-id-table*)
+       ;;            (puthash id (point-marker) *org-drill-dest-id-table*))))
+       ;;      (concat "+" org-drill-question-tag) 'tree)
+       ;;     ))))))
+       (concat "+" org-drill-question-tag)))))
+
+
 
 ;;; Card types for learning languages =========================================
 
       (org-drill-get-verb-conjugation-info)
     (org-drill-present-card-using-text
      (cond
-      ((zerop (random 2))
+      ((zerop (random* 2))
        (format "\nTranslate the verb\n\n%s\n\nand conjugate for the %s tense.\n\n"
                infinitive tense))
       (t
           (psetf num-min num-max
                  num-max num-min))
       (setq *drilled-number*
-            (+ num-min (random (abs (1+ (- num-max num-min))))))
+            (+ num-min (random* (abs (1+ (- num-max num-min))))))
       (setq *drilled-number-direction*
-            (if (zerop (random 2)) 'from-english 'to-english))
+            (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 ((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))))))
+     (with-hidden-cloze-hints
+      (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)
+         (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
 
 
+(provide 'org-drill)
+
 # 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.
 
+# This card also illustrates the use of hints inside clozed text. Note how
+# during testing, the hint text `gender' is invisible unless its clozed text
+# area is being hidden, in which case that text is replaced by `[gender...]'
+
 *** Grammar Rule                                     :drill:
     :PROPERTIES:
     :DRILL_CARD_TYPE: hide1cloze
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.