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