Commits

Anonymous committed 5d12467

- New card types: show2cloze, hide1_firstmore, show1_firstless, show1_lastmore. See docs for details.
- 'conjugate' card type can now show a hint (VERB_INFINITIVE_HINT)
- New command: org-drill-again: run another drill session using leftover items from the last
session, i.e. don't rescan the item collection.
- If org-drill-resume is called and there is nothing to resume, offer to call org-drill-again
- Automatically prompt to save all modified buffers when a drill session finishes (turn off with
'org-drill-save-buffers-after-drill-sessions-p')
- Politely skip cards with unknown card types
- When suspending a drill session, print a message describing the key that runs org-drill-resume
(it it's bound to a key)
- org-drill-merge-buffers: can now optionally be prevented from copying unmatched items from
SRC into DEST
- org-drill-merge-buffers: fixed a bug where it prompted for certain property values if they were
not set in the item being processed
- org-drill-merge-buffers: ensure that *all* items in DEST are stripped of their scheduling data

  • Participants
  • Parent commits 94181db

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-05-12 10:29:29 "/>
+<meta name="generated" content="2011-05-20 23:13:33 NZST"/>
 <meta name="author" content="Paul Sexton"/>
 <meta name="description" content=""/>
 <meta name="keywords" content=""/>
 </ul>
 </li>
 <li><a href="#sec-5">Running the drill session </a></li>
-<li><a href="#sec-6">Cram mode </a></li>
-<li><a href="#sec-7">Leeches </a></li>
-<li><a href="#sec-8">Customisation </a>
+<li><a href="#sec-6">Multiple sequential drill sessions </a></li>
+<li><a href="#sec-7">Cram mode </a></li>
+<li><a href="#sec-8">Leeches </a></li>
+<li><a href="#sec-9">Customisation </a>
 <ul>
-<li><a href="#sec-8_1">Visual appearance of items during drill sessions </a></li>
-<li><a href="#sec-8_2">Duration of drill sessions </a></li>
-<li><a href="#sec-8_3">Sources of items for drill sessions (scope) </a></li>
-<li><a href="#sec-8_4">Definition of old and overdue items </a></li>
-<li><a href="#sec-8_5">Spaced repetition algorithm </a>
+<li><a href="#sec-9_1">Visual appearance of items during drill sessions </a></li>
+<li><a href="#sec-9_2">Duration of drill sessions </a></li>
+<li><a href="#sec-9_3">Saving buffers after drill sessions </a></li>
+<li><a href="#sec-9_4">Sources of items for drill sessions (scope) </a></li>
+<li><a href="#sec-9_5">Definition of old and overdue items </a></li>
+<li><a href="#sec-9_6">Spaced repetition algorithm </a>
 <ul>
-<li><a href="#sec-8_5_1">Choice of algorithm </a></li>
-<li><a href="#sec-8_5_2">Random variation of repetition intervals </a></li>
-<li><a href="#sec-8_5_3">Adjustment for early or late review of items </a></li>
-<li><a href="#sec-8_5_4">Adjusting item difficulty globally </a></li>
+<li><a href="#sec-9_6_1">Choice of algorithm </a></li>
+<li><a href="#sec-9_6_2">Random variation of repetition intervals </a></li>
+<li><a href="#sec-9_6_3">Adjustment for early or late review of items </a></li>
+<li><a href="#sec-9_6_4">Adjusting item difficulty globally </a></li>
 </ul>
 </li>
-<li><a href="#sec-8_6">Per-file customisation settings </a></li>
+<li><a href="#sec-9_7">Per-file customisation settings </a></li>
 </ul>
 </li>
-<li><a href="#sec-9">Coping with large collections </a></li>
-<li><a href="#sec-10">Sharing, merging and synchronising item collections </a></li>
-<li><a href="#sec-11">Incremental reading </a></li>
+<li><a href="#sec-10">Coping with large collections </a></li>
+<li><a href="#sec-11">Sharing, merging and synchronising item collections </a></li>
+<li><a href="#sec-12">Incremental reading </a></li>
 </ul>
 </div>
 </div>
 <p>
 But this card will be difficult to remember. If you get just one of the 4
 hidden facts wrong, you will fail the card. A card like this is likely to
-become a <a href="#sec-7">leech</a>.
+become a <a href="#sec-8">leech</a>.
 </p>
 <p>
 A better way to express all these facts using 'simple' cards is to create
 However, this is really cumbersome. Multicloze card types exist for this
 situation. Multicloze cards behave like 'simple' cards, except that when there
 is more than one area marked as cloze text, some but not all of the areas
-are hidden. There are two types of multicloze card:
+can be hidden. There are several types of predefined multicloze card:
 </p>
 <ol>
 <li><code>hide1cloze</code> &ndash; one of the marked areas is hidden during review; the others
    the others are hidden. The hidden text area is chosen randomly at each
    review.
 </li>
+<li><code>hide2cloze</code> &ndash; like hide1cloze, but 2 marked pieces of text will be hidden,
+   and the rest will be visible.
+</li>
+<li><code>show2cloze</code> &ndash; like show1cloze, but 2 marked pieces of text will be visible,
+   the rest are hidden.
+</li>
+</ol>
+
+
+<p>
+There are also some types of multicloze card where some pieces have an
+increased or decreased chance of being hidden. These are intended for use when
+studying languages: generally it is easy to translate a foreign-language
+sentence into your own language if you have met it before, but it is much
+harder to translate in the other direction. Therefore, you will want to test
+the harder direction more often.
+</p><ol>
+<li><code>hide1_firstmore</code> &ndash; only one of the marked pieces of text will be
+   hidden. 75% of the time (guaranteed), the <i>first</i> piece is hidden; the rest
+   of the time, one of the other pieces is randomly hidden.
+</li>
+<li><code>show1_firstless</code> &ndash; only one of the marked pieces of text will be
+   visible. Only 25% of the time (guaranteed) will the <i>first</i> piece will be
+   visible; the rest of the time, one of the other pieces is randomly visible.
+</li>
+<li><code>show1_lastmore</code> &ndash; only one of the marked pieces of text will be
+   visible. 75% of the time (guaranteed), the <i>last</i> piece will be visible;
+   the rest of the time, one of the other pieces is randomly visible.
+</li>
 </ol>
 
 
 Start a drill session with <code>M-x org-drill</code>. By default, this includes all
 non-hidden topics in the current buffer. <code>org-drill</code> takes an optional
 argument, SCOPE, which allows it to take drill items from other
-sources. See <a href="#sec-8_3">below</a> for details.
+sources. See <a href="#sec-9_4">below</a> for details.
 </p>
 <p>
 During a drill session, you will be presented with each item, then asked to
 </div>
 
 <div id="outline-container-6" class="outline-2">
-<h2 id="sec-6">Cram mode </h2>
+<h2 id="sec-6">Multiple sequential drill sessions </h2>
 <div class="outline-text-2" id="text-6">
 
 
 
 <p>
+Org-Drill has to scan your entire item database each time you start a new drill
+session. This can be slow if you have a large item collection. If you have a
+large number of 'due' items and want to run a second drill session after
+finishing one session, you can use the command <code>org-drill-again</code> to run a new
+drill session that draws from the pool of remaining due items that were not
+tested during the previous session, without re-scanning the item collection.
+</p>
+<p>
+Also note that if you run <code>org-drill-resume</code> and you have actually finished the
+drill session, you will be asked whether you want to start another drill
+session without re-scanning (as if you had run <code>org-drill-again</code>).
+</p>
+
+</div>
+
+</div>
+
+<div id="outline-container-7" class="outline-2">
+<h2 id="sec-7">Cram mode </h2>
+<div class="outline-text-2" id="text-7">
+
+
+
+<p>
 There are some situations, such as before an exam, where you will want to
 revise all of your cards regardless of when they are next due for review.
 </p>
 
 </div>
 
-<div id="outline-container-7" class="outline-2">
-<h2 id="sec-7"><a name="leeches" id="leeches"></a>Leeches </h2>
-<div class="outline-text-2" id="text-7">
+<div id="outline-container-8" class="outline-2">
+<h2 id="sec-8"><a name="leeches" id="leeches"></a>Leeches </h2>
+<div class="outline-text-2" id="text-8">
 
 
 <p>
 
 </div>
 
-<div id="outline-container-8" class="outline-2">
-<h2 id="sec-8">Customisation </h2>
-<div class="outline-text-2" id="text-8">
+<div id="outline-container-9" class="outline-2">
+<h2 id="sec-9">Customisation </h2>
+<div class="outline-text-2" id="text-9">
 
 
 
 
 </div>
 
-<div id="outline-container-8_1" class="outline-3">
-<h3 id="sec-8_1">Visual appearance of items during drill sessions </h3>
-<div class="outline-text-3" id="text-8_1">
+<div id="outline-container-9_1" class="outline-3">
+<h3 id="sec-9_1">Visual appearance of items during drill sessions </h3>
+<div class="outline-text-3" id="text-9_1">
 
 
 
 
 </div>
 
-<div id="outline-container-8_2" class="outline-3">
-<h3 id="sec-8_2">Duration of drill sessions </h3>
-<div class="outline-text-3" id="text-8_2">
+<div id="outline-container-9_2" class="outline-3">
+<h3 id="sec-9_2">Duration of drill sessions </h3>
+<div class="outline-text-3" id="text-9_2">
 
 
 
 
 </div>
 
-<div id="outline-container-8_3" class="outline-3">
-<h3 id="sec-8_3"><a name="scope" id="scope"></a>Sources of items for drill sessions (scope) </h3>
-<div class="outline-text-3" id="text-8_3">
+<div id="outline-container-9_3" class="outline-3">
+<h3 id="sec-9_3">Saving buffers after drill sessions </h3>
+<div class="outline-text-3" id="text-9_3">
+
+
+
+<p>
+By default, you will be prompted to save all unsaved buffers at the end of a
+drill session. If you don't like this behaviour, use the following setting:
+</p>
+
+
+
+<pre class="example">(setq org-drill-save-buffers-after-drill-sessions-p nil)
+</pre>
+
+
+
+
+</div>
+
+</div>
+
+<div id="outline-container-9_4" class="outline-3">
+<h3 id="sec-9_4"><a name="scope" id="scope"></a>Sources of items for drill sessions (scope) </h3>
+<div class="outline-text-3" id="text-9_4">
 
 
 <p>
 
 </div>
 
-<div id="outline-container-8_4" class="outline-3">
-<h3 id="sec-8_4">Definition of old and overdue items </h3>
-<div class="outline-text-3" id="text-8_4">
+<div id="outline-container-9_5" class="outline-3">
+<h3 id="sec-9_5">Definition of old and overdue items </h3>
+<div class="outline-text-3" id="text-9_5">
 
 
 
 
 </div>
 
-<div id="outline-container-8_5" class="outline-3">
-<h3 id="sec-8_5">Spaced repetition algorithm </h3>
-<div class="outline-text-3" id="text-8_5">
+<div id="outline-container-9_6" class="outline-3">
+<h3 id="sec-9_6">Spaced repetition algorithm </h3>
+<div class="outline-text-3" id="text-9_6">
 
 
 
 
 </div>
 
-<div id="outline-container-8_5_1" class="outline-4">
-<h4 id="sec-8_5_1">Choice of algorithm </h4>
-<div class="outline-text-4" id="text-8_5_1">
+<div id="outline-container-9_6_1" class="outline-4">
+<h4 id="sec-9_6_1">Choice of algorithm </h4>
+<div class="outline-text-4" id="text-9_6_1">
 
 
 
 
 </div>
 
-<div id="outline-container-8_5_2" class="outline-4">
-<h4 id="sec-8_5_2">Random variation of repetition intervals </h4>
-<div class="outline-text-4" id="text-8_5_2">
+<div id="outline-container-9_6_2" class="outline-4">
+<h4 id="sec-9_6_2">Random variation of repetition intervals </h4>
+<div class="outline-text-4" id="text-9_6_2">
 
 
 
 
 </div>
 
-<div id="outline-container-8_5_3" class="outline-4">
-<h4 id="sec-8_5_3">Adjustment for early or late review of items </h4>
-<div class="outline-text-4" id="text-8_5_3">
+<div id="outline-container-9_6_3" class="outline-4">
+<h4 id="sec-9_6_3">Adjustment for early or late review of items </h4>
+<div class="outline-text-4" id="text-9_6_3">
 
 
 
 
 </div>
 
-<div id="outline-container-8_5_4" class="outline-4">
-<h4 id="sec-8_5_4">Adjusting item difficulty globally </h4>
-<div class="outline-text-4" id="text-8_5_4">
+<div id="outline-container-9_6_4" class="outline-4">
+<h4 id="sec-9_6_4">Adjusting item difficulty globally </h4>
+<div class="outline-text-4" id="text-9_6_4">
 
 
 
 
 </div>
 
-<div id="outline-container-8_6" class="outline-3">
-<h3 id="sec-8_6"><a name="per-file-settings" id="per-file-settings"></a>Per-file customisation settings </h3>
-<div class="outline-text-3" id="text-8_6">
+<div id="outline-container-9_7" class="outline-3">
+<h3 id="sec-9_7"><a name="per-file-settings" id="per-file-settings"></a>Per-file customisation settings </h3>
+<div class="outline-text-3" id="text-9_7">
 
 
 <p>
 
 </div>
 
-<div id="outline-container-9" class="outline-2">
-<h2 id="sec-9">Coping with large collections </h2>
-<div class="outline-text-2" id="text-9">
+<div id="outline-container-10" class="outline-2">
+<h2 id="sec-10">Coping with large collections </h2>
+<div class="outline-text-2" id="text-10">
 
 
 
 <li>Divide the file into two or more smaller files.
 </li>
 <li>Within each file, set <code>org-drill-scope</code> to 'directory'. See
-   <a href="#sec-8_6">per-file settings</a> above for instructions about how to do this.
+   <a href="#sec-9_7">per-file settings</a> above for instructions about how to do this.
 </li>
 </ol>
 
 
 </div>
 
-<div id="outline-container-10" class="outline-2">
-<h2 id="sec-10">Sharing, merging and synchronising item collections </h2>
-<div class="outline-text-2" id="text-10">
+<div id="outline-container-11" class="outline-2">
+<h2 id="sec-11">Sharing, merging and synchronising item collections </h2>
+<div class="outline-text-2" id="text-11">
 
 
 
 
 </div>
 
-<div id="outline-container-11" class="outline-2">
-<h2 id="sec-11">Incremental reading </h2>
-<div class="outline-text-2" id="text-11">
+<div id="outline-container-12" class="outline-2">
+<h2 id="sec-12">Incremental reading </h2>
+<div class="outline-text-2" id="text-12">
 
 
 
 </div>
 </div>
 <div id="postamble">
-<p class="date">Date: 2011-05-12 10:29:29 </p>
+<p class="date">Date: 2011-05-20 23:13:33 NZST</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>
 However, this is really cumbersome. Multicloze card types exist for this
 situation. Multicloze cards behave like 'simple' cards, except that when there
 is more than one area marked as cloze text, some but not all of the areas
-are hidden. There are two types of multicloze card:
+can be hidden. There are several types of predefined multicloze card:
 
 1. =hide1cloze= -- one of the marked areas is hidden during review; the others
    all remain visible. The hidden text area is chosen randomly at each review.
 2. =show1cloze= -- only one of the marked areas is visible during review; all
    the others are hidden. The hidden text area is chosen randomly at each
    review.
+3. =hide2cloze= -- like hide1cloze, but 2 marked pieces of text will be hidden,
+   and the rest will be visible.
+4. =show2cloze= -- like show1cloze, but 2 marked pieces of text will be visible,
+   the rest are hidden.
+
+There are also some types of multicloze card where some pieces have an
+increased or decreased chance of being hidden. These are intended for use when
+studying languages: generally it is easy to translate a foreign-language
+sentence into your own language if you have met it before, but it is much
+harder to translate in the other direction. Therefore, you will want to test
+the harder direction more often.
+5. =hide1_firstmore= -- only one of the marked pieces of text will be
+   hidden. 75% of the time (guaranteed), the /first/ piece is hidden; the rest
+   of the time, one of the other pieces is randomly hidden.
+6. =show1_firstless= -- only one of the marked pieces of text will be
+   visible. Only 25% of the time (guaranteed) will the /first/ piece will be
+   visible; the rest of the time, one of the other pieces is randomly visible.
+7. =show1_lastmore= -- only one of the marked pieces of text will be
+   visible. 75% of the time (guaranteed), the /last/ piece will be visible;
+   the rest of the time, one of the other pieces is randomly visible.
 
 So, for the above example, we can actually use the original 'bad' simple card,
 but change its card type to 'hide1cloze'. Each time the card is presented for
 session.
 
 
+* Multiple sequential drill sessions
+
+
+Org-Drill has to scan your entire item database each time you start a new drill
+session. This can be slow if you have a large item collection. If you have a
+large number of 'due' items and want to run a second drill session after
+finishing one session, you can use the command =org-drill-again= to run a new
+drill session that draws from the pool of remaining due items that were not
+tested during the previous session, without re-scanning the item collection.
+
+Also note that if you run =org-drill-resume= and you have actually finished the
+drill session, you will be asked whether you want to start another drill
+session without re-scanning (as if you had run =org-drill-again=).
+
+
 * Cram mode
 
 
 session will not end until /all/ outstanding items have been reviewed.
 
 
+** Saving buffers after drill sessions
+
+
+By default, you will be prompted to save all unsaved buffers at the end of a
+drill session. If you don't like this behaviour, use the following setting:
+
+#+BEGIN_EXAMPLE
+(setq org-drill-save-buffers-after-drill-sessions-p nil)
+#+END_EXAMPLE
+
+
 ** Sources of items for drill sessions (scope)
 # <<scope>>
 

File org-drill.el

 ;;; org-drill.el - Self-testing using spaced repetition
 ;;;
 ;;; Author: Paul Sexton <eeeickythump@gmail.com>
-;;; Version: 2.3
+;;; Version: 2.3.2
 ;;; Repository at http://bitbucket.org/eeeickythump/org-drill/
 ;;;
 ;;;
     ("hide1cloze" . org-drill-present-multicloze-hide1)
     ("hide2cloze" . org-drill-present-multicloze-hide2)
     ("show1cloze" . org-drill-present-multicloze-show1)
+    ("show2cloze" . org-drill-present-multicloze-show2)
     ("multicloze" . org-drill-present-multicloze-hide1)
+    ("hide1_firstmore" . org-drill-present-multicloze-hide1-firstmore)
+    ("show1_lastmore" . org-drill-present-multicloze-show1-lastmore)
+    ("show1_firstless" . org-drill-present-multicloze-show1-firstless)
     ("conjugate" org-drill-present-verb-conjugation
      org-drill-show-answer-verb-conjugation)
     ("spanish_verb" . org-drill-present-spanish-verb)
                  list))
 
 
+(defcustom org-drill-save-buffers-after-drill-sessions-p
+  t
+  "If non-nil, prompt to save all modified buffers after a drill session
+finishes."
+  :group 'org-drill
+  :type 'boolean)
+
+
 (defcustom org-drill-spaced-repetition-algorithm
   'sm5
   "Which SuperMemo spaced repetition algorithm to use for scheduling items.
 (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.")
 (put 'org-drill-overdue-interval-factor 'safe-local-variable 'floatp)
 (put 'org-drill-scope 'safe-local-variable
      '(lambda (val) (or (symbolp val) (listp val))))
+(put 'org-drill-save-buffers-after-drill-sessions-p 'safe-local-variable 'booleanp)
 
 
 ;;;; Utilities ================================================================
     (/ (float (round (* floatnum n))) n)))
 
 
+(defun command-keybinding-to-string (cmd)
+  "Return a human-readable description of the key/keys to which the command
+CMD is bound, or nil if it is not bound to a key."
+  (let ((key (where-is-internal cmd overriding-local-map t)))
+    (if key (key-description key))))
+
+
 (defun time-to-inactive-org-timestamp (time)
   (format-time-string
    (concat "[" (substring (cdr org-time-stamp-formats) 1 -1) "]")
          (mature-entry-count (+ (length *org-drill-young-mature-entries*)
                                 (length *org-drill-old-mature-entries*)
                                 (length *org-drill-overdue-entries*)))
+         (status (first (org-drill-entry-status)))
          (prompt
           (if fmt-and-args
               (apply 'format
           (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 ??)))
+                    (case status
+                      (: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)
+                           ,(case status
+                              (: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*))
          (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
 
 
-(defun org-drill-present-multicloze-hide-n (number-to-hide)
+(defun org-drill-present-multicloze-hide-n (number-to-hide
+                                            &optional
+                                            force-show-first
+                                            force-show-last
+                                            force-hide-first)
   "Hides NUMBER-TO-HIDE pieces of text that are marked for cloze deletion,
+chosen at random.
+If NUMBER-TO-HIDE is negative, show only (ABS NUMBER-TO-HIDE) pieces,
+hiding all the rest.
+If FORCE-HIDE-FIRST is non-nil, force the first piece of text to be one of
+the hidden items.
+If FORCE-SHOW-FIRST is non-nil, never hide the first piece of text.
+If FORCE-SHOW-LAST is non-nil, never hide the last piece of text.
+If the number of text pieces in the item is less than
+NUMBER-TO-HIDE, then all text pieces will be hidden (except the first or last
+items if FORCE-SHOW-FIRST or FORCE-SHOW-LAST is non-nil)."
+  (with-hidden-comments
+   (with-hidden-cloze-hints
+    (let ((item-end nil)
+          (match-count 0)
+          (body-start (or (cdr (org-get-property-block))
+                          (point))))
+      (if (and force-hide-first force-show-first)
+          (error "FORCE-HIDE-FIRST and FORCE-SHOW-FIRST are mutually exclusive"))
+      (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)))
+      (if (minusp number-to-hide)
+          (setq number-to-hide (+ match-count number-to-hide)))
+      (when (plusp match-count)
+        (let* ((positions (shuffle-list (loop for i from 1
+                                              to match-count
+                                              collect i)))
+               (match-nums nil))
+          (if force-hide-first
+              ;; Force '1' to be in the list, and to be the first item
+              ;; in the list.
+              (setq positions (cons 1 (remove 1 positions))))
+          (if force-show-first
+              (setq positions (remove 1 positions)))
+          (if force-show-last
+              (setq positions (remove match-count positions)))
+          (setq match-nums
+                (subseq positions
+                        0 (min number-to-hide (length positions))))
+          (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)
+        (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."
+  (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-hide-nth (cnt)
+  "Hide the CNT'th piece of clozed text. 1 is the first piece. If
+CNT is negative, count backwards, so -1 means the last item, -2
+the second to last, etc."
   (with-hidden-comments
    (with-hidden-cloze-hints
     (let ((item-end nil)
         (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)))))
+      (cond
+       ((or (not (plusp match-count))
+            (> cnt match-count)
+            (and (minusp cnt) (> (abs cnt) match-count)))
+        nil)
+       (t
+        (save-excursion
+          (goto-char body-start)
+          (re-search-forward org-drill-cloze-regexp
+                             item-end t (if (minusp cnt) (+ 1 cnt match-count) cnt))
+          (org-drill-hide-matched-cloze-text))))
       (org-display-inline-images t)
       (org-cycle-hide-drawers 'all)
       (prog1 (org-drill-presentation-prompt)
         (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."
-  (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-hide-first ()
+  "Hides the first piece of text that is marked for cloze deletion."
+  (org-drill-present-multicloze-hide-nth 1))
+
+
+(defun org-drill-present-multicloze-hide-last ()
+  "Hides the last piece of text that is marked for cloze deletion."
+  (org-drill-present-multicloze-hide-nth -1))
+
+
+(defun org-drill-present-multicloze-hide1-firstmore ()
+  "Three out of every four repetitions, hides the FIRST piece of
+text that is marked for cloze deletion. One out of every four
+repetitions, hide one of the other pieces of text, chosen at
+random."
+  ;; The 'firstmore' and 'lastmore' functions used to randomly choose whether
+  ;; to hide the 'favoured' piece of text. However even when the chance of
+  ;; hiding it was set quite high (80%), the outcome was too unpredictable over
+  ;; the small number of repetitions where most learning takes place for each
+  ;; item. In other words, the actual frequency during the first 10 repetitions
+  ;; was often very different from 80%. Hence we use modulo instead.
+  (if (zerop (mod (1+ (org-drill-entry-total-repeats 0)) 4))
+      ;; 25% of time, hide any item except the first
+      (org-drill-present-multicloze-hide-n 1 t)
+    ;; 75% of time, hide first item
+    (org-drill-present-multicloze-hide-first)))
+
+
+(defun org-drill-present-multicloze-show1-lastmore ()
+  "Three out of every four repetitions, hides all pieces except
+the last. One out of every four repetitions, shows any random
+piece. The effect is similar to 'show1cloze' except that the last
+item is much less likely to be the item that is visible."
+  (if (zerop (mod (1+ (org-drill-entry-total-repeats 0)) 4))
+      ;; 25% of time, show any item except the last
+      (org-drill-present-multicloze-hide-n -1 nil t)
+    ;; 75% of time, show the LAST item
+    (org-drill-present-multicloze-hide-n -1 nil t)))
+
+
+(defun org-drill-present-multicloze-show1-firstless ()
+  "Three out of every four repetitions, hides all pieces except
+one, where the shown piece is guaranteed NOT to be the first
+piece. One out of every four repetitions, shows any random
+piece. The effect is similar to 'show1cloze' except that the
+first item is much less likely to be the item that is visible."
+  (if (zerop (mod (1+ (org-drill-entry-total-repeats 0)) 4))
+      ;; 25% of time, show the first item
+      (org-drill-present-multicloze-hide-n -1 t)
+    ;; 75% of time, show any item, except the first
+    (org-drill-present-multicloze-hide-n -1 nil nil t)))
 
 
 (defun org-drill-present-multicloze-show1 ()
   "Similar to `org-drill-present-multicloze-hide1', but hides all
 the pieces of text that are marked for cloze deletion, except for one
 piece which is 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-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))))))
+  (org-drill-present-multicloze-hide-n -1))
+
+
+(defun org-drill-present-multicloze-show2 ()
+  "Similar to `org-drill-present-multicloze-show1', but reveals two
+pieces rather than one."
+  (org-drill-present-multicloze-hide-n -2))
+
+
+;; (defun org-drill-present-multicloze-show1 ()
+;;   "Similar to `org-drill-present-multicloze-hide1', but hides all
+;; the pieces of text that are marked for cloze deletion, except for one
+;; piece which is 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-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)
                                   'org-drill-present-default-answer)
                     presentation-fn (first presentation-fn)))
          (cond
-          (presentation-fn
-           (setq cont (funcall presentation-fn)))
+          ((null presentation-fn)
+           (message "%s:%d: Unrecognised card type '%s', skipping..."
+                    (buffer-name) (point) card-type)
+           (sit-for 0.5)
+           'skip)
           (t
-           (error "Unknown card type: '%s'" card-type))))
-
-       (cond
-        ((not cont)
-         (message "Quit")
-         nil)
-        ((eql cont 'edit)
-         'edit)
-        ((eql cont 'skip)
-         'skip)
-        (t
-         (save-excursion
-           (funcall answer-fn
-                    (lambda () (org-drill-reschedule))))))))))
+           (setq cont (funcall presentation-fn))
+           (cond
+            ((not cont)
+             (message "Quit")
+             nil)
+            ((eql cont 'edit)
+             'edit)
+            ((eql cont 'skip)
+             'skip)
+            (t
+             (save-excursion
+               (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*
            ;; 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.
             ((< (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)))))
           (error "Unexpectedly ran out of pending drill items"))
         (save-excursion
           (org-drill-goto-entry m)
-          (setq result (org-drill-entry))
           (cond
-           ((null result)
-            (message "Quit")
-            (setq end-pos :quit)
-            (return-from org-drill-entries nil))
-           ((eql result 'edit)
-            (setq end-pos (point-marker))
-            (return-from org-drill-entries nil))
-           ((eql result 'skip)
-            nil)                        ; skip this item
+           ((not (org-drill-entry-due-p))
+            ;; The entry is not due anymore. This could arise if the user
+            ;; suspends a drill session, then drills an individual entry,
+            ;; then resumes the session.
+            (message "Entry no longer due, skipping...")
+            (sit-for 0.3)
+            nil)
            (t
+            (setq result (org-drill-entry))
             (cond
-             ((<= result org-drill-failure-quality)
-              (if *org-drill-again-entries*
-                  (setq *org-drill-again-entries*
-                        (shuffle-list *org-drill-again-entries*)))
-              (push-end m *org-drill-again-entries*))
+             ((null result)
+              (message "Quit")
+              (setq end-pos :quit)
+              (return-from org-drill-entries nil))
+             ((eql result 'edit)
+              (setq end-pos (point-marker))
+              (return-from org-drill-entries nil))
+             ((eql result 'skip)
+              nil)                      ; skip this item
              (t
-              (push m *org-drill-done-entries*))))))))))
+              (cond
+               ((<= result org-drill-failure-quality)
+                (if *org-drill-again-entries*
+                    (setq *org-drill-again-entries*
+                          (shuffle-list *org-drill-again-entries*)))
+                (push-end m *org-drill-again-entries*))
+               (t
+                (push m *org-drill-done-entries*))))))))))))
 
 
 
 
 
 
-(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*))
+(defun org-drill-free-markers (markers)
+  "MARKERS is a list of markers, all of which will be freed (set to
+point nowhere). Alternatively, MARKERS can be 't', in which case
+all the markers used by Org-Drill will be freed."
+  (dolist (m (if (eql t markers)
+                 (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*)
+               markers))
     (free-marker m)))
 
 
                       (lambda (a b) (> (cdr a) (cdr b)))))))
 
 
+(defun org-drill-entry-status ()
+  "Returns a list (STATUS DUE) where DUE is the number of days overdue,
+zero being due today, -1 being scheduled 1 day in the future. STATUS is
+one of the following values:
+- nil, if the item is not a drill entry, or has an empty body
+- :unscheduled
+- :future
+- :new
+- :failed
+- :overdue
+- :young
+- :old
+"
+  (save-excursion
+    (unless (org-at-heading-p)
+      (org-back-to-heading))
+    (let ((due (org-drill-entry-days-overdue))
+          (last-int (org-drill-entry-last-interval 1)))
+      (list
+       (cond
+        ((not (org-drill-entry-p))
+         nil)
+        ((org-drill-entry-empty-p)
+         nil)                           ; skip -- item body is empty
+        ((null due)                     ; unscheduled - usually a skipped leech
+         :unscheduled)
+        ;; ((eql -1 due)
+        ;;  :tomorrow)
+        ((minusp due)                   ; scheduled in the future
+         :future)
+        ;; The rest of the stati all denote 'due' items ==========================
+        ((<= (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.
+         :failed)
+        ((org-drill-entry-new-p)
+         :new)
+        ((org-drill-entry-overdue-p due last-int)
+         ;; Overdue status overrides young versus old
+         ;; distinction.
+         ;; Store marker + due, for sorting of overdue entries
+         :overdue)
+        ((<= (org-drill-entry-last-interval 9999)
+             org-drill-days-before-old)
+         :young)
+        (t
+         :old))
+       due))))
+
+
 (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)
+        (org-drill-free-markers t)
         (setq *org-drill-current-item* nil
               *org-drill-done-entries* nil
               *org-drill-dormant-entry-count* 0
                        (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*)))))))
+                     (destructuring-bind (status due) (org-drill-entry-status)
+                       (case status
+                         (:unscheduled
+                          (incf *org-drill-dormant-entry-count*))
+                         ;; (:tomorrow
+                         ;;  (incf *org-drill-dormant-entry-count*)
+                         ;;  (incf *org-drill-due-tomorrow-count*))
+                         (:future
+                          (incf *org-drill-dormant-entry-count*)
+                          (if (eq -1 due)
+                              (incf *org-drill-due-tomorrow-count*)))
+                         (:new
+                          (push (point-marker) *org-drill-new-entries*))
+                         (:failed
+                          (push (point-marker) *org-drill-failed-entries*))
+                         (:young
+                          (push (point-marker) *org-drill-young-mature-entries*))
+                         (:overdue
+                          (push (cons (point-marker) due) overdue-data))
+                         (:old
+                          (push (point-marker) *org-drill-old-mature-entries*)))))))
                  scope)
+                ;; (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*))))
                 ;; 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)
               (message "Drill session finished!"))))
         (progn
           (unless end-pos
-            (org-drill-free-all-markers)))))
+            (org-drill-free-markers *org-drill-done-entries*)))))
     (cond
      (end-pos
       (when (markerp end-pos)
         (org-drill-goto-entry end-pos))
-      (message
-       "You can continue the drill session with `M-x org-drill-resume'."))
+      (let ((keystr (command-keybinding-to-string 'org-drill-resume)))
+        (message
+         "You can continue the drill session with the command `org-drill-resume'.%s"
+         (if keystr (format "\nYou can run this command by pressing %s." keystr)
+           ""))))
      (t
       (org-drill-final-report)
       (if (eql 'sm5 org-drill-spaced-repetition-algorithm)
           (org-drill-save-optimal-factor-matrix))
+      (if org-drill-save-buffers-after-drill-sessions-p
+          (save-some-buffers))
       ))))
 
 
-
 (defun org-drill-save-optimal-factor-matrix ()
   (message "Saving optimal factor matrix...")
   (customize-save-variable 'org-drill-optimal-factor-matrix
   (org-drill 'directory))
 
 
+(defun org-drill-again (&optional scope)
+  "Run a new drill session, but try to use leftover due items that
+were not reviewed during the last session, rather than scanning for
+unreviewed items. If there are no leftover items in memory, a full
+scan will be performed."
+  (interactive)
+  (cond
+   ((plusp (org-drill-pending-entry-count))
+    (org-drill-free-markers *org-drill-done-entries*)
+    (if (markerp *org-drill-current-item*)
+        (free-marker *org-drill-current-item*))
+    (setq *org-drill-start-time* (float-time (current-time))
+          *org-drill-done-entries* nil
+          *org-drill-current-item* nil)
+    (org-drill scope t))
+   (t
+    (org-drill scope))))
+
+
+
 (defun org-drill-resume ()
   "Resume a suspended drill session. Sessions are suspended by
-exiting them with the `edit' option."
+exiting them with the `edit' or `quit' options."
   (interactive)
-  (org-drill nil t))
+  (cond
+   ((org-drill-entries-pending-p)
+    (org-drill nil t))
+   ((and (plusp (org-drill-pending-entry-count))
+         ;; Current drill session is finished, but there are still
+         ;; more items which need to be reviewed.
+         (y-or-n-p (format
+                    "You have finished the drill session. However, %d items still
+need reviewing. Start a new drill session? "
+                    (org-drill-pending-entry-count))))
+    (org-drill-again))
+   (t
+    (message "You have finished the drill session."))))
 
 
 (defun org-drill-strip-entry-data ()
 
 
 
-(defun org-drill-merge-buffers (src &optional dest)
+(defun org-drill-merge-buffers (src &optional dest ignore-new-items-p)
   "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."
+wants to migrate to the updated set without losing their scheduling data.
+
+By default, any drill items in SRC which do not exist in DEST are
+copied into DEST. We attempt to place the copied item in the
+equivalent location in DEST to its location in SRC, by matching
+the heading hierarchy. However if IGNORE-NEW-ITEMS-P is non-nil,
+we simply ignore any items that do not exist in DEST, and do not
+copy them across."
   ;; 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
                    (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 last-quality
+                         (org-set-property "LAST_QUALITY" last-quality)
+                       (org-delete-property "LAST_QUALITY"))
+                     (if last-reviewed
+                         (org-set-property "LAST_REVIEWED" last-reviewed)
+                       (org-delete-property "LAST_REVIEWED"))
                      (if scheduled-time
                          (org-schedule nil scheduled-time)))))
+               (remhash id *org-drill-dest-id-table*)
                (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)))))
-       'file))))
+             (unless ignore-new-items-p
+               (org-drill-copy-entry-to-other-buffer dest))))))
+       'file))
+    ;; Finally: there may be some items in DEST which are not in SRC, and
+    ;; which have been scheduled by another user of DEST. Clear out the
+    ;; scheduling info from all the unmatched items in DEST.
+    (with-current-buffer dest
+      (maphash (lambda (id m)
+                 (goto-char m)
+                 (org-drill-strip-entry-data)
+                 (free-marker m))
+               *org-drill-dest-id-table*))))
 
 
 
   "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))
+        (inf-hint (org-entry-get (point) "VERB_INFINITIVE_HINT" t))
         (translation (org-entry-get (point) "VERB_TRANSLATION" t))
         (tense (org-entry-get (point) "VERB_TENSE" nil))
         (highlight-face nil))
              infinitive translation tense (point)))
     (setq tense (downcase (car (read-from-string tense)))
           infinitive (car (read-from-string infinitive))
+          inf-hint (if inf-hint (car (read-from-string inf-hint)))
           translation (car (read-from-string translation)))
     (setq highlight-face
           (list :foreground
     (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)))
+    (list infinitive inf-hint translation tense)))
 
 
 (defun org-drill-present-verb-conjugation ()
   "Present a drill entry whose card type is 'conjugate'."
-  (destructuring-bind (infinitive translation tense)
+  (destructuring-bind (infinitive inf-hint translation tense)
       (org-drill-get-verb-conjugation-info)
     (org-drill-present-card-using-text
      (cond
        (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))))))
+       (format "\nGive the verb that means\n\n%s %s\n
+and conjugate for the %s tense.\n\n"
+               translation
+               (if inf-hint (format "  [HINT: %s]" inf-hint) "")
+               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)
+  (destructuring-bind (infinitive inf-hint translation tense)
       (org-drill-get-verb-conjugation-info)
     (with-replaced-entry-heading
      (format "%s tense of %s ==> %s\n\n"
 
 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.
+all of 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=, =VERB_TRANSLATION= and =VERB_INFINITIVE_HINT= properties.
 
 Some of the conjugation items are empty -- this allows the user to past in
-conjugations as they are learned.
+conjugations as they are learned. They will automatically be excluded from
+drill sessions as long as their bodies remain empty.
 
-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.
+Following this item is an [[Old Style Verb][example]] of the older "spanish_verb" card type. This
+is not as sophisticated or useful as the first 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:
   :VERB_TRANSLATION: "to dance"
   :DRILL_CARD_TYPE: hide1cloze
   :DATE_ADDED: [2011-04-30 Sat]
+  :VERB_INFINITIVE_HINT: "b..."
   :END:
 
 Sp:  [bailar]
 Regular verb.
 
 
+** Sentences
+
+
+It is generally a lot harder for language students to translate /to/ the
+foreign language, than to translate /from/ it. This is because when you see a
+sentence in the foreign language, you can often get the sense of the sentence
+by recognising the nouns and verbs; once this is achieved, combining them into
+a grammatically correct sentence in your native tongue is automatic and
+effortless. However, in the other direction, not only do you have to recall the
+correct nouns, verbs and so on, but you also have to put the words in the right
+order and get all the grammar and "in-between words" correct.
+
+Therefore, if you are learning a language you should generally test your
+ability to translate into the language, more often than you test your ability
+in the other direction.
+
+The following is an example of the card type =hide1_firstmore=. This card type
+works like =hide1cloze= but the /first/ clozed text area is guaranteed to be
+hidden 75% of the time.
+
+The second example is of a similar card type, =show1_firstless=. Here only 1
+clozed text area is visible during testing. 75% of the time, the /first/ area
+is guaranteed to be one of the hidden areas.
+
+
+*** Sentence                                                             :drill:
+    :PROPERTIES:
+    :DRILL_CARD_TYPE: hide1_firstmore
+    :END:
+
+Sp:  [La mujer cuyo perro estamos buscando es mi jefe.]
+En:  [The woman whose dog we’re seeking is my boss.]
+
+*** Adverb                                                               :drill:
+    :PROPERTIES:
+    :DRILL_CARD_TYPE: show1_firstless
+    :END:
+
+Sp:  [entre]
+En:  [between] or [among]
+
+
 ** Random Numbers
 
 Below is an example of a card that tests the user's ability to translate random