1 ;; -*- coding: utf-8-unix -*-
2 ;;; org-drill.el - Self-testing using spaced repetition
4 ;; Author: Paul Sexton <eeeickythump@gmail.com>
6 ;; Repository at http://bitbucket.org/eeeickythump/org-drill/
8 ;; This file is not part of GNU Emacs.
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 3, or (at your option)
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23 ;;; Commentary and synopsis:
25 ;; Uses the SuperMemo spaced repetition algorithms to conduct interactive
26 ;; "drill sessions", where the material to be remembered is presented to the
27 ;; student in random order. The student rates his or her recall of each item,
28 ;; and this information is used to schedule the item for later revision.
30 ;; Each drill session can be restricted to topics in the current buffer
31 ;; (default), one or several files, all agenda files, or a subtree. A single
32 ;; topic can also be drilled.
34 ;; Different "card types" can be defined, which present their information to
35 ;; the student in different ways.
37 ;; See the file README.org for more detailed documentation.
41 (eval-when-compile (require 'cl
))
42 (eval-when-compile (require 'hi-lock
))
47 (defgroup org-drill nil
48 "Options concerning interactive drill sessions in Org mode (org-drill)."
52 (defcustom org-drill-question-tag
"drill"
53 "Tag which topics must possess in order to be identified as review topics
58 (defcustom org-drill-maximum-items-per-session
30
59 "Each drill session will present at most this many topics for review.
62 :type
'(choice integer
(const nil
)))
64 (defcustom org-drill-maximum-duration
20
65 "Maximum duration of a drill session, in minutes.
68 :type
'(choice integer
(const nil
)))
70 (defcustom org-drill-failure-quality
2
71 "If the quality of recall for an item is this number or lower,
72 it is regarded as an unambiguous failure, and the repetition
73 interval for the card is reset to 0 days. If the quality is higher
74 than this number, it is regarded as successfully recalled, but the
75 time interval to the next repetition will be lowered if the quality
78 By default this is 2, for SuperMemo-like behaviour. For
79 Mnemosyne-like behaviour, set it to 1. Other values are not
82 :type
'(choice (const 2) (const 1)))
84 (defcustom org-drill-forgetting-index
10
85 "What percentage of items do you consider it is 'acceptable' to
86 forget each drill session? The default is 10%. A warning message
87 is displayed at the end of the session if the percentage forgotten
88 climbs above this number."
92 (defcustom org-drill-leech-failure-threshold
15
93 "If an item is forgotten more than this many times, it is tagged
96 :type
'(choice integer
(const nil
)))
98 (defcustom org-drill-leech-method
'skip
99 "How should 'leech items' be handled during drill sessions?
101 - nil :: Leech items are treated the same as normal items.
102 - skip :: Leech items are not included in drill sessions.
103 - warn :: Leech items are still included in drill sessions,
104 but a warning message is printed when each leech item is
107 :type
'(choice (const 'warn
) (const 'skip
) (const nil
)))
109 (defface org-drill-visible-cloze-face
110 '((t (:foreground
"darkseagreen")))
111 "The face used to hide the contents of cloze phrases."
114 (defface org-drill-visible-cloze-hint-face
115 '((t (:foreground
"dark slate blue")))
116 "The face used to hide the contents of cloze phrases."
119 (defface org-drill-hidden-cloze-face
120 '((t (:foreground
"deep sky blue" :background
"blue")))
121 "The face used to hide the contents of cloze phrases."
124 (defcustom org-drill-use-visible-cloze-face-p nil
125 "Use a special face to highlight cloze-deleted text in org mode
130 (defcustom org-drill-hide-item-headings-p nil
131 "Conceal the contents of the main heading of each item during drill
132 sessions? You may want to enable this behaviour if item headings or tags
133 contain information that could 'give away' the answer."
137 (defcustom org-drill-new-count-color
"royal blue"
138 "Foreground colour used to display the count of remaining new items
139 during a drill session."
143 (defcustom org-drill-mature-count-color
"green"
144 "Foreground colour used to display the count of remaining mature items
145 during a drill session. Mature items are due for review, but are not new."
149 (defcustom org-drill-failed-count-color
"red"
150 "Foreground colour used to display the count of remaining failed items
151 during a drill session."
155 (defcustom org-drill-done-count-color
"sienna"
156 "Foreground colour used to display the count of reviewed items
157 during a drill session."
161 (setplist 'org-drill-cloze-overlay-defaults
163 face org-drill-hidden-cloze-face
166 (setplist 'org-drill-hidden-text-overlay
169 (setplist 'org-drill-replaced-text-overlay
170 '(display "Replaced text"
174 (defvar org-drill-hint-separator
"||"
175 "String which, if it occurs within a cloze expression, signifies that the
176 rest of the expression after the string is a `hint', to be displayed instead of
177 the hidden cloze during a test.")
179 (defvar org-drill-cloze-regexp
180 (concat "\\(\\[[[:cntrl:][:graph:][:space:]]+?\\)\\(\\|"
181 (regexp-quote org-drill-hint-separator
)
184 (defvar org-drill-cloze-keywords
185 `((,org-drill-cloze-regexp
186 (1 'org-drill-visible-cloze-face nil
)
187 (2 'org-drill-visible-cloze-hint-face t
)
188 (3 'org-drill-visible-cloze-face nil
))))
190 (defcustom org-drill-card-type-alist
191 '((nil org-drill-present-simple-card
)
192 ("simple" org-drill-present-simple-card
)
193 ("twosided" org-drill-present-two-sided-card nil t
)
194 ("multisided" org-drill-present-multi-sided-card nil t
)
195 ("hide1cloze" org-drill-present-multicloze-hide1
)
196 ("hide2cloze" org-drill-present-multicloze-hide2
)
197 ("show1cloze" org-drill-present-multicloze-show1
)
198 ("show2cloze" org-drill-present-multicloze-show2
)
199 ("multicloze" org-drill-present-multicloze-hide1
)
200 ("hidefirst" org-drill-present-multicloze-hide-first
)
201 ("hidelast" org-drill-present-multicloze-hide-last
)
202 ("hide1_firstmore" org-drill-present-multicloze-hide1-firstmore
)
203 ("show1_lastmore" org-drill-present-multicloze-show1-lastmore
)
204 ("show1_firstless" org-drill-present-multicloze-show1-firstless
)
206 org-drill-present-verb-conjugation
207 org-drill-show-answer-verb-conjugation
)
209 org-drill-present-noun-declension
210 org-drill-show-answer-noun-declension
)
211 ("spanish_verb" org-drill-present-spanish-verb
)
212 ("translate_number" org-drill-present-translate-number
))
213 "Alist associating card types with presentation functions. Each
214 entry in the alist takes the form:
216 ;;; (CARDTYPE QUESTION-FN [ANSWER-FN DRILL-EMPTY-P])
218 Where CARDTYPE is a string or nil (for default), and QUESTION-FN
219 is a function which takes no arguments and returns a boolean
222 When supplied, ANSWER-FN is a function that takes one argument --
223 that argument is a function of no arguments, which when called,
224 prompts the user to rate their recall and performs rescheduling
225 of the drill item. ANSWER-FN is called with the point on the
226 active item's heading, just prior to displaying the item's
227 'answer'. It can therefore be used to modify the appearance of
228 the answer. ANSWER-FN must call its argument before returning.
230 When supplied, DRILL-EMPTY-P is a boolean value, default nil.
231 When non-nil, cards of this type will be presented during tests
232 even if their bodies are empty."
234 :type
'(alist :key-type
(choice string
(const nil
))
235 :value-type function
))
237 (defcustom org-drill-scope
'file
238 "The scope in which to search for drill items when conducting a
239 drill session. This can be any of:
241 file The current buffer, respecting the restriction if any.
243 tree The subtree started with the entry at point
244 file-no-restriction The current buffer, without restriction
245 file-with-archives The current buffer, and any archives associated with it.
246 agenda All agenda files
247 agenda-with-archives All agenda files with any archive files associated
249 directory All files with the extension '.org' in the same
250 directory as the current file (includes the current
251 file if it is an .org file.)
252 (FILE1 FILE2 ...) If this is a list, all files in the list will be scanned.
254 ;; Note -- meanings differ slightly from the argument to org-map-entries:
255 ;; 'file' means current file/buffer, respecting any restriction
256 ;; 'file-no-restriction' means current file/buffer, ignoring restrictions
257 ;; 'directory' means all *.org files in current directory
259 :type
'(choice (const 'file
) (const 'tree
) (const 'file-no-restriction
)
260 (const 'file-with-archives
) (const 'agenda
)
261 (const 'agenda-with-archives
) (const 'directory
)
264 (defcustom org-drill-save-buffers-after-drill-sessions-p t
265 "If non-nil, prompt to save all modified buffers after a drill session
270 (defcustom org-drill-spaced-repetition-algorithm
'sm5
271 "Which SuperMemo spaced repetition algorithm to use for scheduling items.
272 Available choices are:
273 - SM2 :: the SM2 algorithm, used in SuperMemo 2.0
274 - SM5 :: the SM5 algorithm, used in SuperMemo 5.0
275 - Simple8 :: a modified version of the SM8 algorithm. SM8 is used in
276 SuperMemo 98. The version implemented here is simplified in that while it
277 'learns' the difficulty of each item using quality grades and number of
278 failures, it does not modify the matrix of values that
279 governs how fast the inter-repetition intervals increase. A method for
280 adjusting intervals when items are reviewed early or late has been taken
281 from SM11, a later version of the algorithm, and included in Simple8."
283 :type
'(choice (const 'sm2
) (const 'sm5
) (const 'simple8
)))
285 (defcustom org-drill-optimal-factor-matrix nil
286 "DO NOT CHANGE THE VALUE OF THIS VARIABLE.
288 Persistent matrix of optimal factors, used by the SuperMemo SM5 algorithm.
289 The matrix is saved (using the 'customize' facility) at the end of each
292 Over time, values in the matrix will adapt to the individual user's
297 (defcustom org-drill-sm5-initial-interval
4.0
298 "In the SM5 algorithm, the initial interval after the first
299 successful presentation of an item is always 4 days. If you wish to change
300 this, you can do so here."
304 (defcustom org-drill-add-random-noise-to-intervals-p nil
305 "If true, the number of days until an item's next repetition
306 will vary slightly from the interval calculated by the SM2
307 algorithm. The variation is very small when the interval is
308 small, but scales up with the interval."
312 (defcustom org-drill-adjust-intervals-for-early-and-late-repetitions-p nil
313 "If true, when the student successfully reviews an item 1 or more days
314 before or after the scheduled review date, this will affect that date of
315 the item's next scheduled review, according to the algorithm presented at
316 [[http://www.supermemo.com/english/algsm11.htm#Advanced%20repetitions]].
318 Items that were reviewed early will have their next review date brought
319 forward. Those that were reviewed late will have their next review
320 date postponed further.
322 Note that this option currently has no effect if the SM2 algorithm
327 (defcustom org-drill-cloze-text-weight
4
328 "For card types 'hide1_firstmore', 'show1_lastmore' and 'show1_firstless',
329 this number determines how often the 'less favoured' situation
330 should arise. It will occur 1 in every N trials, where N is the
331 value of the variable.
333 For example, with the hide1_firstmore card type, the first piece
334 of clozed text should be hidden more often than the other
335 pieces. If this variable is set to 4 (default), the first item
336 will only be shown 25% of the time (1 in 4 trials). Similarly for
337 show1_lastmore, the last item will be shown 75% of the time, and
338 for show1_firstless, the first item would only be shown 25% of the
341 If the value of this variable is NIL, then weighting is disabled, and
342 all weighted card types are treated as their unweighted equivalents."
344 :type
'(choice integer
(const nil
)))
346 (defcustom org-drill-cram-hours
12
347 "When in cram mode, items are considered due for review if
348 they were reviewed at least this many hours ago."
352 ;;; NEW items have never been presented in a drill session before.
353 ;;; MATURE items HAVE been presented at least once before.
354 ;;; - YOUNG mature items were scheduled no more than
355 ;;; ORG-DRILL-DAYS-BEFORE-OLD days after their last
356 ;;; repetition. These items will have been learned 'recently' and will have a
357 ;;; low repetition count.
358 ;;; - OLD mature items have intervals greater than
359 ;;; ORG-DRILL-DAYS-BEFORE-OLD.
360 ;;; - OVERDUE items are past their scheduled review date by more than
361 ;;; LAST-INTERVAL * (ORG-DRILL-OVERDUE-INTERVAL-FACTOR - 1) days,
362 ;;; regardless of young/old status.
364 (defcustom org-drill-days-before-old
10
365 "When an item's inter-repetition interval rises above this value in days,
366 it is no longer considered a 'young' (recently learned) item."
370 (defcustom org-drill-overdue-interval-factor
1.2
371 "An item is considered overdue if its scheduled review date is
372 more than (ORG-DRILL-OVERDUE-INTERVAL-FACTOR - 1) * LAST-INTERVAL
373 days in the past. For example, a value of 1.2 means an additional
374 20% of the last scheduled interval is allowed to elapse before
375 the item is overdue. A value of 1.0 means no extra time is
376 allowed at all - items are immediately considered overdue if
377 there is even one day's delay in reviewing them. This variable
378 should never be less than 1.0."
382 (defcustom org-drill-learn-fraction
0.5
383 "Fraction between 0 and 1 that governs how quickly the spaces
384 between successive repetitions increase, for all items. The
385 default value is 0.5. Higher values make spaces increase more
386 quickly with each successful repetition. You should only change
387 this in small increments (for example 0.05-0.1) as it has an
388 exponential effect on inter-repetition spacing."
392 (defvar drill-answer nil
393 "Global variable that can be bound to a correct answer when an
394 item is being presented. If this variable is non-nil, the default
395 presentation function will show its value instead of the default
396 behaviour of revealing the contents of the drilled item.
398 This variable is useful for card types that compute their answers
399 -- for example, a card type that asks the student to translate a
400 random number to another language. ")
402 (defvar *org-drill-session-qualities
* nil
)
403 (defvar *org-drill-start-time
* 0)
404 (defvar *org-drill-new-entries
* nil
)
405 (defvar *org-drill-dormant-entry-count
* 0)
406 (defvar *org-drill-due-entry-count
* 0)
407 (defvar *org-drill-overdue-entry-count
* 0)
408 (defvar *org-drill-due-tomorrow-count
* 0)
409 (defvar *org-drill-overdue-entries
* nil
410 "List of markers for items that are considered 'overdue', based on
411 the value of ORG-DRILL-OVERDUE-INTERVAL-FACTOR.")
412 (defvar *org-drill-young-mature-entries
* nil
413 "List of markers for mature entries whose last inter-repetition
414 interval was <= ORG-DRILL-DAYS-BEFORE-OLD days.")
415 (defvar *org-drill-old-mature-entries
* nil
416 "List of markers for mature entries whose last inter-repetition
417 interval was greater than ORG-DRILL-DAYS-BEFORE-OLD days.")
418 (defvar *org-drill-failed-entries
* nil
)
419 (defvar *org-drill-again-entries
* nil
)
420 (defvar *org-drill-done-entries
* nil
)
421 (defvar *org-drill-current-item
* nil
422 "Set to the marker for the item currently being tested.")
423 (defvar *org-drill-cram-mode
* nil
424 "Are we in 'cram mode', where all items are considered due
425 for review unless they were already reviewed in the recent past?")
426 (defvar org-drill-scheduling-properties
427 '("LEARN_DATA" "DRILL_LAST_INTERVAL" "DRILL_REPEATS_SINCE_FAIL"
428 "DRILL_TOTAL_REPEATS" "DRILL_FAILURE_COUNT" "DRILL_AVERAGE_QUALITY"
429 "DRILL_EASE" "DRILL_LAST_QUALITY" "DRILL_LAST_REVIEWED"))
431 ;;; Make the above settings safe as file-local variables.
433 (put 'org-drill-question-tag
'safe-local-variable
'stringp
)
434 (put 'org-drill-maximum-items-per-session
'safe-local-variable
435 '(lambda (val) (or (integerp val
) (null val
))))
436 (put 'org-drill-maximum-duration
'safe-local-variable
437 '(lambda (val) (or (integerp val
) (null val
))))
438 (put 'org-drill-failure-quality
'safe-local-variable
'integerp
)
439 (put 'org-drill-forgetting-index
'safe-local-variable
'integerp
)
440 (put 'org-drill-leech-failure-threshold
'safe-local-variable
'integerp
)
441 (put 'org-drill-leech-method
'safe-local-variable
442 '(lambda (val) (memq val
'(nil skip warn
))))
443 (put 'org-drill-use-visible-cloze-face-p
'safe-local-variable
'booleanp
)
444 (put 'org-drill-hide-item-headings-p
'safe-local-variable
'booleanp
)
445 (put 'org-drill-spaced-repetition-algorithm
'safe-local-variable
446 '(lambda (val) (memq val
'(simple8 sm5 sm2
))))
447 (put 'org-drill-sm5-initial-interval
'safe-local-variable
'floatp
)
448 (put 'org-drill-add-random-noise-to-intervals-p
'safe-local-variable
'booleanp
)
449 (put 'org-drill-adjust-intervals-for-early-and-late-repetitions-p
450 'safe-local-variable
'booleanp
)
451 (put 'org-drill-cram-hours
'safe-local-variable
'integerp
)
452 (put 'org-drill-learn-fraction
'safe-local-variable
'floatp
)
453 (put 'org-drill-days-before-old
'safe-local-variable
'integerp
)
454 (put 'org-drill-overdue-interval-factor
'safe-local-variable
'floatp
)
455 (put 'org-drill-scope
'safe-local-variable
456 '(lambda (val) (or (symbolp val
) (listp val
))))
457 (put 'org-drill-save-buffers-after-drill-sessions-p
'safe-local-variable
'booleanp
)
458 (put 'org-drill-cloze-text-weight
'safe-local-variable
459 '(lambda (val) (or (null val
) (integerp val
))))
461 ;;;; Utilities ================================================================
463 (defun free-marker (m)
466 (defmacro pop-random
(place)
467 (let ((idx (gensym)))
470 (let ((,idx
(random* (length ,place
))))
471 (prog1 (nth ,idx
,place
)
472 (setq ,place
(append (subseq ,place
0 ,idx
)
473 (subseq ,place
(1+ ,idx
)))))))))
475 (defmacro push-end
(val place
)
476 "Add VAL to the end of the sequence stored in PLACE. Return the new
478 `(setq ,place
(append ,place
(list ,val
))))
480 (defun shuffle-list (list)
481 "Randomly permute the elements of LIST (all permutations equally likely)."
482 ;; Adapted from 'shuffle-vector' in cookie1.el
488 (setq j
(+ i
(random* (- len i
))))
489 (setq temp
(nth i list
))
490 (setf (nth i list
) (nth j list
))
491 (setf (nth j list
) temp
)
495 (defun round-float (floatnum fix
)
496 "Round the floating point number FLOATNUM to FIX decimal places.
497 Example: (round-float 3.56755765 3) -> 3.568"
498 (let ((n (expt 10 fix
)))
499 (/ (float (round (* floatnum n
))) n
)))
501 (defun command-keybinding-to-string (cmd)
502 "Return a human-readable description of the key/keys to which the command
503 CMD is bound, or nil if it is not bound to a key."
504 (let ((key (where-is-internal cmd overriding-local-map t
)))
505 (if key
(key-description key
))))
507 (defun time-to-inactive-org-timestamp (time)
509 (concat "[" (substring (cdr org-time-stamp-formats
) 1 -
1) "]")
512 (defun org-map-drill-entries (func &optional scope
&rest skip
)
513 "Like `org-map-entries', but only drill entries are processed."
514 (let ((org-drill-scope (or scope org-drill-scope
)))
515 (apply 'org-map-entries func
516 (concat "+" org-drill-question-tag
)
517 (case org-drill-scope
519 (file-no-restriction 'file
)
521 (directory-files (file-name-directory (buffer-file-name))
526 (defmacro with-hidden-cloze-text
(&rest body
)
528 (org-drill-hide-clozed-text)
532 (org-drill-unhide-clozed-text))))
534 (defmacro with-hidden-cloze-hints
(&rest body
)
536 (org-drill-hide-cloze-hints)
540 (org-drill-unhide-text))))
542 (defmacro with-hidden-comments
(&rest body
)
544 (if org-drill-hide-item-headings-p
545 (org-drill-hide-heading-at-point))
546 (org-drill-hide-comments)
550 (org-drill-unhide-text))))
552 (defun org-drill-days-since-last-review ()
553 "Nil means a last review date has not yet been stored for
555 Zero means it was reviewed today.
556 A positive number means it was reviewed that many days ago.
557 A negative number means the date of last review is in the future --
558 this should never happen."
559 (let ((datestr (org-entry-get (point) "DRILL_LAST_REVIEWED")))
561 (- (time-to-days (current-time))
562 (time-to-days (apply 'encode-time
563 (org-parse-time-string datestr
)))))))
565 (defun org-drill-hours-since-last-review ()
566 "Like `org-drill-days-since-last-review', but return value is
567 in hours rather than days."
568 (let ((datestr (org-entry-get (point) "DRILL_LAST_REVIEWED")))
571 (/ (- (time-to-seconds (current-time))
572 (time-to-seconds (apply 'encode-time
573 (org-parse-time-string datestr
))))
576 (defun org-drill-entry-p (&optional marker
)
577 "Is MARKER, or the point, in a 'drill item'? This will return nil if
578 the point is inside a subheading of a drill item -- to handle that
579 situation use `org-part-of-drill-entry-p'."
582 (org-drill-goto-entry marker
))
583 (member org-drill-question-tag
(org-get-local-tags))))
585 (defun org-drill-goto-entry (marker)
586 (switch-to-buffer (marker-buffer marker
))
589 (defun org-part-of-drill-entry-p ()
590 "Is the current entry either the main heading of a 'drill item',
591 or a subheading within a drill item?"
592 (or (org-drill-entry-p)
593 ;; Does this heading INHERIT the drill tag
594 (member org-drill-question-tag
(org-get-tags-at))))
596 (defun org-drill-goto-drill-entry-heading ()
597 "Move the point to the heading which holds the :drill: tag for this
599 (unless (org-at-heading-p)
600 (org-back-to-heading))
601 (unless (org-part-of-drill-entry-p)
602 (error "Point is not inside a drill entry"))
603 (while (not (org-drill-entry-p))
604 (unless (org-up-heading-safe)
605 (error "Cannot find a parent heading that is marked as a drill entry"))))
607 (defun org-drill-entry-leech-p ()
608 "Is the current entry a 'leech item'?"
609 (and (org-drill-entry-p)
610 (member "leech" (org-get-local-tags))))
612 ;; (defun org-drill-entry-due-p ()
614 ;; (*org-drill-cram-mode*
615 ;; (let ((hours (org-drill-hours-since-last-review)))
616 ;; (and (org-drill-entry-p)
618 ;; (>= hours org-drill-cram-hours)))))
620 ;; (let ((item-time (org-get-scheduled-time (point))))
621 ;; (and (org-drill-entry-p)
622 ;; (or (not (eql 'skip org-drill-leech-method))
623 ;; (not (org-drill-entry-leech-p)))
624 ;; (or (null item-time) ; not scheduled
625 ;; (not (minusp ; scheduled for today/in past
626 ;; (- (time-to-days (current-time))
627 ;; (time-to-days item-time))))))))))
629 (defun org-drill-entry-days-overdue ()
631 - NIL if the item is not to be regarded as scheduled for review at all.
632 This is the case if it is not a drill item, or if it is a leech item
633 that we wish to skip, or if we are in cram mode and have already reviewed
634 the item within the last few hours.
635 - 0 if the item is new, or if it scheduled for review today.
636 - A negative integer - item is scheduled that many days in the future.
637 - A positive integer - item is scheduled that many days in the past."
639 (*org-drill-cram-mode
*
640 (let ((hours (org-drill-hours-since-last-review)))
641 (and (org-drill-entry-p)
643 (>= hours org-drill-cram-hours
))
646 (let ((item-time (org-get-scheduled-time (point))))
648 ((or (not (org-drill-entry-p))
649 (and (eql 'skip org-drill-leech-method
)
650 (org-drill-entry-leech-p)))
652 ((null item-time
) ; not scheduled -> due now
655 (- (time-to-days (current-time))
656 (time-to-days item-time
))))))))
658 (defun org-drill-entry-overdue-p (&optional days-overdue last-interval
)
659 "Returns true if entry that is scheduled DAYS-OVERDUE dasy in the past,
660 and whose last inter-repetition interval was LAST-INTERVAL, should be
661 considered 'overdue'. If the arguments are not given they are extracted
662 from the entry at point."
664 (setq days-overdue
(org-drill-entry-days-overdue)))
665 (unless last-interval
666 (setq last-interval
(org-drill-entry-last-interval 1)))
667 (and (numberp days-overdue
)
668 (> days-overdue
1) ; enforce a sane minimum 'overdue' gap
669 ;;(> due org-drill-days-before-overdue)
670 (> (/ (+ days-overdue last-interval
1.0) last-interval
)
671 org-drill-overdue-interval-factor
)))
673 (defun org-drill-entry-due-p ()
674 (let ((due (org-drill-entry-days-overdue)))
675 (and (not (null due
))
676 (not (minusp due
)))))
678 (defun org-drill-entry-new-p ()
679 (and (org-drill-entry-p)
680 (let ((item-time (org-get-scheduled-time (point))))
683 (defun org-drill-entry-last-quality (&optional default
)
684 (let ((quality (org-entry-get (point) "DRILL_LAST_QUALITY")))
686 (string-to-number quality
)
689 (defun org-drill-entry-failure-count ()
690 (let ((quality (org-entry-get (point) "DRILL_FAILURE_COUNT")))
692 (string-to-number quality
)
695 (defun org-drill-entry-average-quality (&optional default
)
696 (let ((val (org-entry-get (point) "DRILL_AVERAGE_QUALITY")))
698 (string-to-number val
)
701 (defun org-drill-entry-last-interval (&optional default
)
702 (let ((val (org-entry-get (point) "DRILL_LAST_INTERVAL")))
704 (string-to-number val
)
707 (defun org-drill-entry-repeats-since-fail (&optional default
)
708 (let ((val (org-entry-get (point) "DRILL_REPEATS_SINCE_FAIL")))
710 (string-to-number val
)
713 (defun org-drill-entry-total-repeats (&optional default
)
714 (let ((val (org-entry-get (point) "DRILL_TOTAL_REPEATS")))
716 (string-to-number val
)
719 (defun org-drill-entry-ease (&optional default
)
720 (let ((val (org-entry-get (point) "DRILL_EASE")))
722 (string-to-number val
)
725 ;;; From http://www.supermemo.com/english/ol/sm5.htm
726 (defun org-drill-random-dispersal-factor ()
727 "Returns a random number between 0.5 and 1.5."
730 (p (- (random* 1.0) 0.5)))
735 (/ (+ 100 (* (* (/ -
1 b
) (log (- 1 (* (/ b a
) (abs p
)))))
739 (defun pseudonormal (mean variation
)
740 "Random numbers in a pseudo-normal distribution with mean MEAN, range
741 MEAN-VARIATION to MEAN+VARIATION"
742 (+ (random* variation
)
747 (defun org-drill-early-interval-factor (optimal-factor
751 - OPTIMAL-FACTOR: interval-factor if the item had been tested
752 exactly when it was supposed to be.
753 - OPTIMAL-INTERVAL: interval for next repetition (days) if the item had been
754 tested exactly when it was supposed to be.
755 - DAYS-AHEAD: how many days ahead of time the item was reviewed.
757 Returns an adjusted optimal factor which should be used to
758 calculate the next interval, instead of the optimal factor found
760 (let ((delta-ofmax (* (1- optimal-factor
)
761 (/ (+ optimal-interval
762 (* 0.6 optimal-interval
) -
1) (1- optimal-interval
)))))
764 (* delta-ofmax
(/ days-ahead
(+ days-ahead
(* 0.6 optimal-interval
)))))))
766 (defun org-drill-get-item-data ()
767 "Returns a list of 6 items, containing all the stored recall
768 data for the item at point:
769 - LAST-INTERVAL is the interval in days that was used to schedule the item's
771 - REPEATS is the number of items the item has been successfully recalled without
772 without any failures. It is reset to 0 upon failure to recall the item.
773 - FAILURES is the total number of times the user has failed to recall the item.
774 - TOTAL-REPEATS includes both successful and unsuccessful repetitions.
775 - AVERAGE-QUALITY is the mean quality of recall of the item over
776 all its repetitions, successful and unsuccessful.
777 - EASE is a number reflecting how easy the item is to learn. Higher is easier.
779 (let ((learn-str (org-entry-get (point) "LEARN_DATA"))
780 (repeats (org-drill-entry-total-repeats :missing
)))
783 (let ((learn-data (or (and learn-str
785 (copy-list initial-repetition-state
))))
786 (list (nth 0 learn-data
) ; last interval
787 (nth 1 learn-data
) ; repetitions
788 (org-drill-entry-failure-count)
790 (org-drill-entry-last-quality)
791 (nth 2 learn-data
) ; EF
793 ((not (eql :missing repeats
))
794 (list (org-drill-entry-last-interval)
795 (org-drill-entry-repeats-since-fail)
796 (org-drill-entry-failure-count)
797 (org-drill-entry-total-repeats)
798 (org-drill-entry-average-quality)
799 (org-drill-entry-ease)))
801 (list 0 0 0 0 nil nil
)))))
803 (defun org-drill-store-item-data (last-interval repeats failures
806 "Stores the given data in the item at point."
807 (org-entry-delete (point) "LEARN_DATA")
808 (org-set-property "DRILL_LAST_INTERVAL"
809 (number-to-string (round-float last-interval
4)))
810 (org-set-property "DRILL_REPEATS_SINCE_FAIL" (number-to-string repeats
))
811 (org-set-property "DRILL_TOTAL_REPEATS" (number-to-string total-repeats
))
812 (org-set-property "DRILL_FAILURE_COUNT" (number-to-string failures
))
813 (org-set-property "DRILL_AVERAGE_QUALITY"
814 (number-to-string (round-float meanq
3)))
815 (org-set-property "DRILL_EASE"
816 (number-to-string (round-float ease
3))))
818 ;;; SM2 Algorithm =============================================================
820 (defun determine-next-interval-sm2 (last-interval n ef quality
821 failures meanq total-repeats
)
823 - LAST-INTERVAL -- the number of days since the item was last reviewed.
824 - REPEATS -- the number of times the item has been successfully reviewed
825 - EF -- the 'easiness factor'
828 Returns a list: (INTERVAL REPEATS EF FAILURES MEAN TOTAL-REPEATS OFMATRIX), where:
829 - INTERVAL is the number of days until the item should next be reviewed
830 - REPEATS is incremented by 1.
831 - EF is modified based on the recall quality for the item.
832 - OF-MATRIX is not modified."
834 (assert (and (>= quality
0) (<= quality
5)))
835 (if (<= quality org-drill-failure-quality
)
836 ;; When an item is failed, its interval is reset to 0,
837 ;; but its EF is unchanged
838 (list -
1 1 ef
(1+ failures
) meanq
(1+ total-repeats
)
839 org-drill-optimal-factor-matrix
)
841 (let* ((next-ef (modify-e-factor ef quality
))
847 (org-drill-add-random-noise-to-intervals-p
855 (t (* last-interval next-ef
)))))
856 (list (if org-drill-add-random-noise-to-intervals-p
857 (+ last-interval
(* (- interval last-interval
)
858 (org-drill-random-dispersal-factor)))
862 failures meanq
(1+ total-repeats
)
863 org-drill-optimal-factor-matrix
))))
866 ;;; SM5 Algorithm =============================================================
868 (defun initial-optimal-factor-sm5 (n ef
)
870 org-drill-sm5-initial-interval
873 (defun get-optimal-factor-sm5 (n ef of-matrix
)
874 (let ((factors (assoc n of-matrix
)))
876 (let ((ef-of (assoc ef
(cdr factors
))))
877 (and ef-of
(cdr ef-of
))))
878 (initial-optimal-factor-sm5 n ef
))))
880 (defun inter-repetition-interval-sm5 (last-interval n ef
&optional of-matrix
)
881 (let ((of (get-optimal-factor-sm5 n ef
(or of-matrix
882 org-drill-optimal-factor-matrix
))))
885 (* of last-interval
))))
887 (defun determine-next-interval-sm5 (last-interval n ef quality
888 failures meanq total-repeats
889 of-matrix
&optional delta-days
)
890 (if (zerop n
) (setq n
1))
891 (if (null ef
) (setq ef
2.5))
893 (assert (and (>= quality
0) (<= quality
5)))
895 (setq of-matrix org-drill-optimal-factor-matrix
))
896 (setq of-matrix
(cl-copy-tree of-matrix
))
897 (setq meanq
(if meanq
898 (/ (+ quality
(* meanq total-repeats
1.0))
901 (let ((next-ef (modify-e-factor ef quality
))
903 (new-of (modify-of (get-optimal-factor-sm5 n ef of-matrix
)
904 quality org-drill-learn-fraction
))
906 (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
907 delta-days
(minusp delta-days
))
908 (setq new-of
(org-drill-early-interval-factor
909 (get-optimal-factor-sm5 n ef of-matrix
)
910 (inter-repetition-interval-sm5
911 last-interval n ef of-matrix
)
914 (set-optimal-factor n next-ef of-matrix
915 (round-float new-of
3))) ; round OF to 3 d.p.
918 ;; "Failed" -- reset repetitions to 0,
919 ((<= quality org-drill-failure-quality
)
920 (list -
1 1 old-ef
(1+ failures
) meanq
(1+ total-repeats
)
921 of-matrix
)) ; Not clear if OF matrix is supposed to be
923 ;; For a zero-based quality of 4 or 5, don't repeat
924 ;; ((and (>= quality 4)
925 ;; (not org-learn-always-reschedule))
926 ;; (list 0 (1+ n) ef failures meanq
927 ;; (1+ total-repeats) of-matrix)) ; 0 interval = unschedule
929 (setq interval
(inter-repetition-interval-sm5
930 last-interval n ef of-matrix
))
931 (if org-drill-add-random-noise-to-intervals-p
932 (setq interval
(* interval
(org-drill-random-dispersal-factor))))
941 ;;; Simple8 Algorithm =========================================================
943 (defun org-drill-simple8-first-interval (failures)
945 - FAILURES: integer >= 0. The total number of times the item has
946 been forgotten, ever.
948 Returns the optimal FIRST interval for an item which has previously been
949 forgotten on FAILURES occasions."
950 (* 2.4849 (exp (* -
0.057 failures
))))
952 (defun org-drill-simple8-interval-factor (ease repetition
)
954 - EASE: floating point number >= 1.2. Corresponds to `AF' in SM8 algorithm.
955 - REPETITION: the number of times the item has been tested.
956 1 is the first repetition (ie the second trial).
958 The factor by which the last interval should be
959 multiplied to give the next interval. Corresponds to `RF' or `OF'."
960 (+ 1.2 (* (- ease
1.2) (expt org-drill-learn-fraction
(log repetition
2)))))
962 (defun org-drill-simple8-quality->ease
(quality)
963 "Returns the ease (`AF' in the SM8 algorithm) which corresponds
964 to a mean item quality of QUALITY."
965 (+ (* 0.0542 (expt quality
4))
966 (* -
0.4848 (expt quality
3))
967 (* 1.4916 (expt quality
2))
971 (defun determine-next-interval-simple8 (last-interval repeats quality
972 failures meanq totaln
973 &optional delta-days
)
975 - LAST-INTERVAL -- the number of days since the item was last reviewed.
976 - REPEATS -- the number of times the item has been successfully reviewed
977 - EASE -- the 'easiness factor'
979 - DELTA-DAYS -- how many days overdue was the item when it was reviewed.
980 0 = reviewed on the scheduled day. +N = N days overdue.
981 -N = reviewed N days early.
983 Returns the new item data, as a list of 6 values:
990 See the documentation for `org-drill-get-item-data' for a description of these."
991 (assert (>= repeats
0))
992 (assert (and (>= quality
0) (<= quality
5)))
993 (assert (or (null meanq
) (and (>= meanq
0) (<= meanq
5))))
994 (let ((next-interval nil
))
995 (setf meanq
(if meanq
996 (/ (+ quality
(* meanq totaln
1.0)) (1+ totaln
))
999 ((<= quality org-drill-failure-quality
)
1003 ((or (zerop repeats
)
1004 (zerop last-interval
))
1005 (setf next-interval
(org-drill-simple8-first-interval failures
))
1011 org-drill-adjust-intervals-for-early-and-late-repetitions-p
1012 (numberp delta-days
) (plusp delta-days
)
1013 (plusp last-interval
))
1014 (+ repeats
(min 1 (/ delta-days last-interval
1.0)))
1016 (factor (org-drill-simple8-interval-factor
1017 (org-drill-simple8-quality->ease meanq
) use-n
))
1018 (next-int (* last-interval factor
)))
1019 (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
1020 (numberp delta-days
) (minusp delta-days
))
1021 ;; The item was reviewed earlier than scheduled.
1022 (setf factor
(org-drill-early-interval-factor
1023 factor next-int
(abs delta-days
))
1024 next-int
(* last-interval factor
)))
1025 (setf next-interval next-int
)
1029 (if (and org-drill-add-random-noise-to-intervals-p
1030 (plusp next-interval
))
1031 (* next-interval
(org-drill-random-dispersal-factor))
1034 (org-drill-simple8-quality->ease meanq
)
1039 ;;; Essentially copied from `org-learn.el', but modified to
1040 ;;; optionally call the SM2 or simple8 functions.
1041 (defun org-drill-smart-reschedule (quality &optional days-ahead
)
1042 "If DAYS-AHEAD is supplied it must be a positive integer. The
1043 item will be scheduled exactly this many days into the future."
1044 (let ((delta-days (- (time-to-days (current-time))
1045 (time-to-days (or (org-get-scheduled-time (point))
1047 (ofmatrix org-drill-optimal-factor-matrix
)
1048 ;; Entries can have weights, 1 by default. Intervals are divided by the
1049 ;; item's weight, so an item with a weight of 2 will have all intervals
1050 ;; halved, meaning you will end up reviewing it twice as often.
1051 ;; Useful for entries which randomly present any of several facts.
1052 (weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
1053 (if (stringp weight
)
1054 (setq weight
(read weight
)))
1055 (destructuring-bind (last-interval repetitions failures
1056 total-repeats meanq ease
)
1057 (org-drill-get-item-data)
1058 (destructuring-bind (next-interval repetitions ease
1059 failures meanq total-repeats
1060 &optional new-ofmatrix
)
1061 (case org-drill-spaced-repetition-algorithm
1062 (sm5 (determine-next-interval-sm5 last-interval repetitions
1063 ease quality failures
1064 meanq total-repeats ofmatrix
))
1065 (sm2 (determine-next-interval-sm2 last-interval repetitions
1066 ease quality failures
1067 meanq total-repeats
))
1068 (simple8 (determine-next-interval-simple8 last-interval repetitions
1069 quality failures meanq
1072 (if (numberp days-ahead
)
1073 (setq next-interval days-ahead
))
1075 (if (and (null days-ahead
)
1076 (numberp weight
) (plusp weight
)
1077 (not (minusp next-interval
)))
1079 (max 1.0 (+ last-interval
1080 (/ (- next-interval last-interval
) weight
)))))
1082 (org-drill-store-item-data next-interval repetitions failures
1083 total-repeats meanq ease
)
1085 (if (eql 'sm5 org-drill-spaced-repetition-algorithm
)
1086 (setq org-drill-optimal-factor-matrix new-ofmatrix
))
1091 ((minusp days-ahead
)
1092 (org-schedule nil
(current-time)))
1094 (org-schedule nil
(time-add (current-time)
1096 (round next-interval
))))))))))
1099 (defun org-drill-hypothetical-next-review-date (quality)
1100 "Returns an integer representing the number of days into the future
1101 that the current item would be scheduled, based on a recall quality
1103 (let ((weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
1104 (destructuring-bind (last-interval repetitions failures
1105 total-repeats meanq ease
)
1106 (org-drill-get-item-data)
1107 (if (stringp weight
)
1108 (setq weight
(read weight
)))
1109 (destructuring-bind (next-interval repetitions ease
1110 failures meanq total-repeats
1112 (case org-drill-spaced-repetition-algorithm
1113 (sm5 (determine-next-interval-sm5 last-interval repetitions
1114 ease quality failures
1116 org-drill-optimal-factor-matrix
))
1117 (sm2 (determine-next-interval-sm2 last-interval repetitions
1118 ease quality failures
1119 meanq total-repeats
))
1120 (simple8 (determine-next-interval-simple8 last-interval repetitions
1121 quality failures meanq
1124 ((not (plusp next-interval
))
1126 ((and (numberp weight
) (plusp weight
))
1128 (max 1.0 (/ (- next-interval last-interval
) weight
))))
1133 (defun org-drill-hypothetical-next-review-dates ()
1134 (let ((intervals nil
))
1136 (push (max (or (car intervals
) 0)
1137 (org-drill-hypothetical-next-review-date q
))
1139 (reverse intervals
)))
1142 (defun org-drill-reschedule ()
1143 "Returns quality rating (0-5), or nil if the user quit."
1146 (next-review-dates (org-drill-hypothetical-next-review-dates)))
1148 (while (not (memq ch
'(?q ?e ?
0 ?
1 ?
2 ?
3 ?
4 ?
5)))
1149 (setq input
(read-key-sequence
1151 (format "0-2 Means you have forgotten the item.
1152 3-5 Means you have remembered the item.
1154 0 - Completely forgot.
1155 1 - Even after seeing the answer, it still took a bit to sink in.
1156 2 - After seeing the answer, you remembered it.
1157 3 - It took you awhile, but you finally remembered. (+%s days)
1158 4 - After a little bit of thought you remembered. (+%s days)
1159 5 - You remembered the item really easily. (+%s days)
1161 How well did you do? (0-5, ?=help, e=edit, t=tags, q=quit)"
1162 (round (nth 3 next-review-dates
))
1163 (round (nth 4 next-review-dates
))
1164 (round (nth 5 next-review-dates
)))
1165 "How well did you do? (0-5, ?=help, e=edit, t=tags, q=quit)")))
1168 (setq ch
(elt input
0)))
1169 ((and (vectorp input
) (symbolp (elt input
0)))
1171 (up (ignore-errors (forward-line -
1)))
1172 (down (ignore-errors (forward-line 1)))
1173 (left (ignore-errors (backward-char)))
1174 (right (ignore-errors (forward-char)))
1175 (prior (ignore-errors (scroll-down))) ; pgup
1176 (next (ignore-errors (scroll-up))))) ; pgdn
1177 ((and (vectorp input
) (listp (elt input
0))
1178 (eventp (elt input
0)))
1179 (case (car (elt input
0))
1180 (wheel-up (ignore-errors (mwheel-scroll (elt input
0))))
1181 (wheel-down (ignore-errors (mwheel-scroll (elt input
0)))))))
1183 (org-set-tags-command))))
1185 ((and (>= ch ?
0) (<= ch ?
5))
1186 (let ((quality (- ch ?
0))
1187 (failures (org-drill-entry-failure-count)))
1188 (unless *org-drill-cram-mode
*
1190 (org-drill-smart-reschedule quality
1191 (nth quality next-review-dates
)))
1192 (push quality
*org-drill-session-qualities
*)
1194 ((<= quality org-drill-failure-quality
)
1195 (when org-drill-leech-failure-threshold
1196 ;;(setq failures (if failures (string-to-number failures) 0))
1197 ;; (org-set-property "DRILL_FAILURE_COUNT"
1198 ;; (format "%d" (1+ failures)))
1199 (if (> (1+ failures
) org-drill-leech-failure-threshold
)
1200 (org-toggle-tag "leech" 'on
))))
1202 (let ((scheduled-time (org-get-scheduled-time (point))))
1203 (when scheduled-time
1204 (message "Next review in %d days"
1205 (- (time-to-days scheduled-time
)
1206 (time-to-days (current-time))))
1208 (org-set-property "DRILL_LAST_QUALITY" (format "%d" quality
))
1209 (org-set-property "DRILL_LAST_REVIEWED"
1210 (time-to-inactive-org-timestamp (current-time))))
1217 ;; (defun org-drill-hide-all-subheadings-except (heading-list)
1218 ;; "Returns a list containing the position of each immediate subheading of
1219 ;; the current topic."
1220 ;; (let ((drill-entry-level (org-current-level))
1221 ;; (drill-sections nil)
1222 ;; (drill-heading nil))
1223 ;; (org-show-subtree)
1227 ;; (when (and (not (outline-invisible-p))
1228 ;; (> (org-current-level) drill-entry-level))
1229 ;; (setq drill-heading (org-get-heading t))
1230 ;; (unless (and (= (org-current-level) (1+ drill-entry-level))
1231 ;; (member drill-heading heading-list))
1233 ;; (push (point) drill-sections)))
1235 ;; (reverse drill-sections)))
1237 (defun org-drill-hide-subheadings-if (test)
1238 "TEST is a function taking no arguments. TEST will be called for each
1239 of the immediate subheadings of the current drill item, with the point
1240 on the relevant subheading. TEST should return nil if the subheading is
1241 to be revealed, non-nil if it is to be hidden.
1242 Returns a list containing the position of each immediate subheading of
1244 (let ((drill-entry-level (org-current-level))
1245 (drill-sections nil
))
1250 (when (and (not (outline-invisible-p))
1251 (> (org-current-level) drill-entry-level
))
1252 (when (or (/= (org-current-level) (1+ drill-entry-level
))
1255 (push (point) drill-sections
)))
1257 (reverse drill-sections
)))
1259 (defun org-drill-hide-all-subheadings-except (heading-list)
1260 (org-drill-hide-subheadings-if
1261 (lambda () (let ((drill-heading (org-get-heading t
)))
1262 (not (member drill-heading heading-list
))))))
1264 (defun org-drill-presentation-prompt (&rest fmt-and-args
)
1265 (let* ((item-start-time (current-time))
1269 (mature-entry-count (+ (length *org-drill-young-mature-entries
*)
1270 (length *org-drill-old-mature-entries
*)
1271 (length *org-drill-overdue-entries
*)))
1272 (status (first (org-drill-entry-status)))
1276 (first fmt-and-args
)
1277 (rest fmt-and-args
))
1278 (concat "Press key for answer, "
1279 "e=edit, t=tags, s=skip, q=quit."))))
1281 (format "%s %s %s %s %s %s"
1285 ((eql status
:failed
) ?F
)
1286 (*org-drill-cram-mode
* ?C
)
1289 (:new ?N
) (:young ?Y
) (:old ?o
) (:overdue ?
!)
1293 (:new org-drill-new-count-color
)
1294 ((:young
:old
) org-drill-mature-count-color
)
1295 ((:overdue
:failed
) org-drill-failed-count-color
)
1296 (t org-drill-done-count-color
))))
1298 (number-to-string (length *org-drill-done-entries
*))
1299 'face
`(:foreground
,org-drill-done-count-color
)
1300 'help-echo
"The number of items you have reviewed this session.")
1302 (number-to-string (+ (length *org-drill-again-entries
*)
1303 (length *org-drill-failed-entries
*)))
1304 'face
`(:foreground
,org-drill-failed-count-color
)
1305 'help-echo
(concat "The number of items that you failed, "
1306 "and need to review again."))
1308 (number-to-string mature-entry-count
)
1309 'face
`(:foreground
,org-drill-mature-count-color
)
1310 'help-echo
"The number of old items due for review.")
1312 (number-to-string (length *org-drill-new-entries
*))
1313 'face
`(:foreground
,org-drill-new-count-color
)
1314 'help-echo
(concat "The number of new items that you "
1315 "have never reviewed."))
1317 (if (and (eql 'warn org-drill-leech-method
)
1318 (org-drill-entry-leech-p))
1319 (setq prompt
(concat
1320 (propertize "!!! LEECH ITEM !!!
1321 You seem to be having a lot of trouble memorising this item.
1322 Consider reformulating the item to make it easier to remember.\n"
1323 'face
'(:foreground
"red"))
1325 (while (memq ch
'(nil ?t
))
1327 (while (not (input-pending-p))
1328 (let ((elapsed (time-subtract (current-time) item-start-time
)))
1329 (message (concat (if (>= (time-to-seconds elapsed
) (* 60 60))
1331 (format-time-string "%M:%S " elapsed
))
1334 (setq input
(read-key-sequence nil
))
1335 (if (stringp input
) (setq ch
(elt input
0)))
1337 (org-set-tags-command)))
1344 (defun org-pos-in-regexp (pos regexp
&optional nlines
)
1347 (org-in-regexp regexp nlines
)))
1349 (defun org-drill-hide-region (beg end
&optional text
)
1350 "Hide the buffer region between BEG and END with an 'invisible text'
1351 visual overlay, or with the string TEXT if it is supplied."
1352 (let ((ovl (make-overlay beg end
)))
1353 (overlay-put ovl
'category
1354 'org-drill-hidden-text-overlay
)
1355 (when (stringp text
)
1356 (overlay-put ovl
'invisible nil
)
1357 (overlay-put ovl
'face
'default
)
1358 (overlay-put ovl
'display text
))))
1360 (defun org-drill-hide-heading-at-point (&optional text
)
1361 (unless (org-at-heading-p)
1362 (error "Point is not on a heading."))
1364 (let ((beg (point)))
1366 (org-drill-hide-region beg
(point) text
))))
1368 (defun org-drill-hide-comments ()
1370 (while (re-search-forward "^#.*$" nil t
)
1371 (org-drill-hide-region (match-beginning 0) (match-end 0)))))
1373 (defun org-drill-unhide-text ()
1374 ;; This will also unhide the item's heading.
1376 (dolist (ovl (overlays-in (point-min) (point-max)))
1377 (when (eql 'org-drill-hidden-text-overlay
(overlay-get ovl
'category
))
1378 (delete-overlay ovl
)))))
1380 (defun org-drill-hide-clozed-text ()
1382 (while (re-search-forward org-drill-cloze-regexp nil t
)
1383 ;; Don't hide org links, partly because they might contain inline
1384 ;; images which we want to keep visible
1385 (unless (save-match-data
1386 (org-pos-in-regexp (match-beginning 0)
1387 org-bracket-link-regexp
1))
1388 (org-drill-hide-matched-cloze-text)))))
1390 (defun org-drill-hide-matched-cloze-text ()
1391 "Hide the current match with a 'cloze' visual overlay."
1392 (let ((ovl (make-overlay (match-beginning 0) (match-end 0)))
1393 (hint-sep-pos (string-match-p (regexp-quote org-drill-hint-separator
)
1395 (overlay-put ovl
'category
1396 'org-drill-cloze-overlay-defaults
)
1397 (when (and hint-sep-pos
1399 (let ((hint (substring-no-properties
1401 (+ hint-sep-pos
(length org-drill-hint-separator
))
1402 (1- (length (match-string 0))))))
1405 ;; If hint is like `X...' then display [X...]
1406 ;; otherwise display [...X]
1407 (format (if (string-match-p (regexp-quote "...") hint
) "[%s]" "[%s...]")
1410 (defun org-drill-hide-cloze-hints ()
1412 (while (re-search-forward org-drill-cloze-regexp nil t
)
1413 (unless (or (save-match-data
1414 (org-pos-in-regexp (match-beginning 0)
1415 org-bracket-link-regexp
1))
1416 (null (match-beginning 2))) ; hint subexpression matched
1417 (org-drill-hide-region (match-beginning 2) (match-end 2))))))
1419 (defmacro with-replaced-entry-text
(text &rest body
)
1420 "During the execution of BODY, the entire text of the current entry is
1421 concealed by an overlay that displays the string TEXT."
1423 (org-drill-replace-entry-text ,text
)
1427 (org-drill-unreplace-entry-text))))
1429 (defmacro with-replaced-entry-text-multi
(replacements &rest body
)
1430 "During the execution of BODY, the entire text of the current entry is
1431 concealed by an overlay that displays the overlays in REPLACEMENTS."
1433 (org-drill-replace-entry-text ,replacements t
)
1437 (org-drill-unreplace-entry-text))))
1439 (defun org-drill-replace-entry-text (text &optional multi-p
)
1440 "Make an overlay that conceals the entire text of the item, not
1441 including properties or the contents of subheadings. The overlay shows
1443 If MULTI-P is non-nil, TEXT must be a list of values which are legal
1444 for the `display' text property. The text of the item will be temporarily
1445 replaced by all of these items, in the order in which they appear in
1447 Note: does not actually alter the item."
1451 (org-drill-replace-entry-text-multi text
))
1453 (let ((ovl (make-overlay (point-min)
1455 (outline-next-heading)
1457 (overlay-put ovl
'category
1458 'org-drill-replaced-text-overlay
)
1459 (overlay-put ovl
'display text
)))))
1461 (defun org-drill-unreplace-entry-text ()
1463 (dolist (ovl (overlays-in (point-min) (point-max)))
1464 (when (eql 'org-drill-replaced-text-overlay
(overlay-get ovl
'category
))
1465 (delete-overlay ovl
)))))
1467 (defun org-drill-replace-entry-text-multi (replacements)
1468 "Make overlays that conceal the entire text of the item, not
1469 including properties or the contents of subheadings. The overlay shows
1471 Note: does not actually alter the item."
1474 (p-max (save-excursion
1475 (outline-next-heading)
1477 (assert (>= (- p-max p-min
) (length replacements
)))
1478 (dotimes (i (length replacements
))
1479 (setq ovl
(make-overlay (+ p-min
(* 2 i
))
1480 (if (= i
(1- (length replacements
)))
1482 (+ p-min
(* 2 i
) 1))))
1483 (overlay-put ovl
'category
1484 'org-drill-replaced-text-overlay
)
1485 (overlay-put ovl
'display
(nth i replacements
)))))
1487 (defmacro with-replaced-entry-heading
(heading &rest body
)
1489 (org-drill-replace-entry-heading ,heading
)
1493 (org-drill-unhide-text))))
1495 (defun org-drill-replace-entry-heading (heading)
1496 "Make an overlay that conceals the heading of the item. The overlay shows
1498 Note: does not actually alter the item."
1499 (org-drill-hide-heading-at-point heading
))
1501 (defun org-drill-unhide-clozed-text ()
1503 (dolist (ovl (overlays-in (point-min) (point-max)))
1504 (when (eql 'org-drill-cloze-overlay-defaults
(overlay-get ovl
'category
))
1505 (delete-overlay ovl
)))))
1507 (defun org-drill-get-entry-text (&optional keep-properties-p
)
1508 (let ((text (org-agenda-get-some-entry-text (point-marker) 100)))
1509 (if keep-properties-p
1511 (substring-no-properties text
))))
1514 ;; (defun org-entry-empty-p ()
1515 ;; (zerop (length (org-drill-get-entry-text))))
1517 ;; This version is about 5x faster than the old version, above.
1518 (defun org-entry-empty-p ()
1520 (org-back-to-heading t
)
1521 (let ((lim (save-excursion
1522 (outline-next-heading) (point))))
1523 (org-end-of-meta-data-and-drawers)
1524 (or (>= (point) lim
)
1525 (null (re-search-forward "[[:graph:]]" lim t
))))))
1527 (defun org-drill-entry-empty-p () (org-entry-empty-p))
1529 ;;; Presentation functions ====================================================
1531 ;; Each of these is called with point on topic heading. Each needs to show the
1532 ;; topic in the form of a 'question' or with some information 'hidden', as
1533 ;; appropriate for the card type. The user should then be prompted to press a
1534 ;; key. The function should then reveal either the 'answer' or the entire
1535 ;; topic, and should return t if the user chose to see the answer and rate their
1536 ;; recall, nil if they chose to quit.
1538 (defun org-drill-present-simple-card ()
1539 (with-hidden-comments
1540 (with-hidden-cloze-hints
1541 (with-hidden-cloze-text
1542 (org-drill-hide-all-subheadings-except nil
)
1544 (org-display-inline-images t
))
1545 (org-cycle-hide-drawers 'all
)
1546 (prog1 (org-drill-presentation-prompt)
1547 (org-drill-hide-subheadings-if 'org-drill-entry-p
))))))
1549 (defun org-drill-present-default-answer (reschedule-fn)
1552 (with-replaced-entry-text
1553 (format "\nAnswer:\n\n %s\n" drill-answer
)
1555 (funcall reschedule-fn
)
1556 (setq drill-answer nil
))))
1558 (org-drill-hide-subheadings-if 'org-drill-entry-p
)
1559 (org-drill-unhide-clozed-text)
1561 (org-display-inline-images t
))
1562 (org-cycle-hide-drawers 'all
)
1563 (with-hidden-cloze-hints
1564 (funcall reschedule-fn
)))))
1566 (defun org-drill-present-two-sided-card ()
1567 (with-hidden-comments
1568 (with-hidden-cloze-hints
1569 (with-hidden-cloze-text
1570 (let ((drill-sections (org-drill-hide-all-subheadings-except nil
)))
1571 (when drill-sections
1573 (goto-char (nth (random* (min 2 (length drill-sections
)))
1575 (org-show-subtree)))
1577 (org-display-inline-images t
))
1578 (org-cycle-hide-drawers 'all
)
1579 (prog1 (org-drill-presentation-prompt)
1580 (org-drill-hide-subheadings-if 'org-drill-entry-p
)))))))
1582 (defun org-drill-present-multi-sided-card ()
1583 (with-hidden-comments
1584 (with-hidden-cloze-hints
1585 (with-hidden-cloze-text
1586 (let ((drill-sections (org-drill-hide-all-subheadings-except nil
)))
1587 (when drill-sections
1589 (goto-char (nth (random* (length drill-sections
)) drill-sections
))
1590 (org-show-subtree)))
1592 (org-display-inline-images t
))
1593 (org-cycle-hide-drawers 'all
)
1594 (prog1 (org-drill-presentation-prompt)
1595 (org-drill-hide-subheadings-if 'org-drill-entry-p
)))))))
1597 (defun org-drill-present-multicloze-hide-n (number-to-hide
1602 "Hides NUMBER-TO-HIDE pieces of text that are marked for cloze deletion,
1604 If NUMBER-TO-HIDE is negative, show only (ABS NUMBER-TO-HIDE) pieces,
1605 hiding all the rest.
1606 If FORCE-HIDE-FIRST is non-nil, force the first piece of text to be one of
1608 If FORCE-SHOW-FIRST is non-nil, never hide the first piece of text.
1609 If FORCE-SHOW-LAST is non-nil, never hide the last piece of text.
1610 If the number of text pieces in the item is less than
1611 NUMBER-TO-HIDE, then all text pieces will be hidden (except the first or last
1612 items if FORCE-SHOW-FIRST or FORCE-SHOW-LAST is non-nil)."
1613 (with-hidden-comments
1614 (with-hidden-cloze-hints
1615 (let ((item-end nil
)
1617 (body-start (or (cdr (org-get-property-block))
1619 (if (and force-hide-first force-show-first
)
1620 (error "FORCE-HIDE-FIRST and FORCE-SHOW-FIRST are mutually exclusive"))
1621 (org-drill-hide-all-subheadings-except nil
)
1623 (outline-next-heading)
1624 (setq item-end
(point)))
1626 (goto-char body-start
)
1627 (while (re-search-forward org-drill-cloze-regexp item-end t
)
1628 (let ((in-regexp?
(save-match-data
1629 (org-pos-in-regexp (match-beginning 0)
1630 org-bracket-link-regexp
1))))
1632 (incf match-count
)))))
1633 (if (minusp number-to-hide
)
1634 (setq number-to-hide
(+ match-count number-to-hide
)))
1635 (when (plusp match-count
)
1636 (let* ((positions (shuffle-list (loop for i from
1
1641 (if force-hide-first
1642 ;; Force '1' to be in the list, and to be the first item
1644 (setq positions
(cons 1 (remove 1 positions
))))
1645 (if force-show-first
1646 (setq positions
(remove 1 positions
)))
1648 (setq positions
(remove match-count positions
)))
1651 0 (min number-to-hide
(length positions
))))
1652 ;; (dolist (pos-to-hide match-nums)
1654 (goto-char body-start
)
1656 (while (re-search-forward org-drill-cloze-regexp item-end t
)
1657 (unless (save-match-data
1658 (org-pos-in-regexp (match-beginning 0)
1659 org-bracket-link-regexp
1))
1661 (if (memq cnt match-nums
)
1662 (org-drill-hide-matched-cloze-text)))))))
1664 ;; do (re-search-forward org-drill-cloze-regexp
1665 ;; item-end t pos-to-hide)
1666 ;; while (org-pos-in-regexp (match-beginning 0)
1667 ;; org-bracket-link-regexp 1))
1668 ;; (org-drill-hide-matched-cloze-text)))))
1670 (org-display-inline-images t
))
1671 (org-cycle-hide-drawers 'all
)
1672 (prog1 (org-drill-presentation-prompt)
1673 (org-drill-hide-subheadings-if 'org-drill-entry-p
)
1674 (org-drill-unhide-clozed-text))))))
1676 (defun org-drill-present-multicloze-hide-nth (to-hide)
1677 "Hide the TO-HIDE'th piece of clozed text. 1 is the first piece. If
1678 TO-HIDE is negative, count backwards, so -1 means the last item, -2
1679 the second to last, etc."
1680 (with-hidden-comments
1681 (with-hidden-cloze-hints
1682 (let ((item-end nil
)
1684 (body-start (or (cdr (org-get-property-block))
1687 (org-drill-hide-all-subheadings-except nil
)
1689 (outline-next-heading)
1690 (setq item-end
(point)))
1692 (goto-char body-start
)
1693 (while (re-search-forward org-drill-cloze-regexp item-end t
)
1694 (let ((in-regexp?
(save-match-data
1695 (org-pos-in-regexp (match-beginning 0)
1696 org-bracket-link-regexp
1))))
1698 (incf match-count
)))))
1699 (if (minusp to-hide
)
1700 (setq to-hide
(+ 1 to-hide match-count
)))
1702 ((or (not (plusp match-count
))
1703 (> to-hide match-count
))
1707 (goto-char body-start
)
1709 (while (re-search-forward org-drill-cloze-regexp item-end t
)
1710 (unless (save-match-data
1711 (org-pos-in-regexp (match-beginning 0)
1712 org-bracket-link-regexp
1))
1715 (org-drill-hide-matched-cloze-text)))))))
1717 (org-display-inline-images t
))
1718 (org-cycle-hide-drawers 'all
)
1719 (prog1 (org-drill-presentation-prompt)
1720 (org-drill-hide-subheadings-if 'org-drill-entry-p
)
1721 (org-drill-unhide-clozed-text))))))
1723 (defun org-drill-present-multicloze-hide1 ()
1724 "Hides one of the pieces of text that are marked for cloze deletion,
1726 (org-drill-present-multicloze-hide-n 1))
1728 (defun org-drill-present-multicloze-hide2 ()
1729 "Hides two of the pieces of text that are marked for cloze deletion,
1731 (org-drill-present-multicloze-hide-n 2))
1733 (defun org-drill-present-multicloze-hide-first ()
1734 "Hides the first piece of text that is marked for cloze deletion."
1735 (org-drill-present-multicloze-hide-nth 1))
1737 (defun org-drill-present-multicloze-hide-last ()
1738 "Hides the last piece of text that is marked for cloze deletion."
1739 (org-drill-present-multicloze-hide-nth -
1))
1741 (defun org-drill-present-multicloze-hide1-firstmore ()
1742 "Commonly, hides the FIRST piece of text that is marked for
1743 cloze deletion. Uncommonly, hide one of the other pieces of text,
1746 The definitions of 'commonly' and 'uncommonly' are determined by
1747 the value of `org-drill-cloze-text-weight'."
1748 ;; The 'firstmore' and 'lastmore' functions used to randomly choose whether
1749 ;; to hide the 'favoured' piece of text. However even when the chance of
1750 ;; hiding it was set quite high (80%), the outcome was too unpredictable over
1751 ;; the small number of repetitions where most learning takes place for each
1752 ;; item. In other words, the actual frequency during the first 10 repetitions
1753 ;; was often very different from 80%. Hence we use modulo instead.
1755 ((null org-drill-cloze-text-weight
)
1756 ;; Behave as hide1cloze
1757 (org-drill-present-multicloze-hide1))
1758 ((not (and (integerp org-drill-cloze-text-weight
)
1759 (plusp org-drill-cloze-text-weight
)))
1760 (error "Illegal value for org-drill-cloze-text-weight: %S"
1761 org-drill-cloze-text-weight
))
1762 ((zerop (mod (1+ (org-drill-entry-total-repeats 0))
1763 org-drill-cloze-text-weight
))
1764 ;; Uncommonly, hide any item except the first
1765 (org-drill-present-multicloze-hide-n 1 t
))
1767 ;; Commonly, hide first item
1768 (org-drill-present-multicloze-hide-first))))
1770 (defun org-drill-present-multicloze-show1-lastmore ()
1771 "Commonly, hides all pieces except the last. Uncommonly, shows
1772 any random piece. The effect is similar to 'show1cloze' except
1773 that the last item is much less likely to be the item that is
1776 The definitions of 'commonly' and 'uncommonly' are determined by
1777 the value of `org-drill-cloze-text-weight'."
1779 ((null org-drill-cloze-text-weight
)
1780 ;; Behave as show1cloze
1781 (org-drill-present-multicloze-show1))
1782 ((not (and (integerp org-drill-cloze-text-weight
)
1783 (plusp org-drill-cloze-text-weight
)))
1784 (error "Illegal value for org-drill-cloze-text-weight: %S"
1785 org-drill-cloze-text-weight
))
1786 ((zerop (mod (1+ (org-drill-entry-total-repeats 0))
1787 org-drill-cloze-text-weight
))
1788 ;; Uncommonly, show any item except the last
1789 (org-drill-present-multicloze-hide-n -
1 nil nil t
))
1791 ;; Commonly, show the LAST item
1792 (org-drill-present-multicloze-hide-n -
1 nil t
))))
1794 (defun org-drill-present-multicloze-show1-firstless ()
1795 "Commonly, hides all pieces except one, where the shown piece
1796 is guaranteed NOT to be the first piece. Uncommonly, shows any
1797 random piece. The effect is similar to 'show1cloze' except that
1798 the first item is much less likely to be the item that is
1801 The definitions of 'commonly' and 'uncommonly' are determined by
1802 the value of `org-drill-cloze-text-weight'."
1804 ((null org-drill-cloze-text-weight
)
1805 ;; Behave as show1cloze
1806 (org-drill-present-multicloze-show1))
1807 ((not (and (integerp org-drill-cloze-text-weight
)
1808 (plusp org-drill-cloze-text-weight
)))
1809 (error "Illegal value for org-drill-cloze-text-weight: %S"
1810 org-drill-cloze-text-weight
))
1811 ((zerop (mod (1+ (org-drill-entry-total-repeats 0))
1812 org-drill-cloze-text-weight
))
1813 ;; Uncommonly, show the first item
1814 (org-drill-present-multicloze-hide-n -
1 t
))
1816 ;; Commonly, show any item, except the first
1817 (org-drill-present-multicloze-hide-n -
1 nil nil t
))))
1819 (defun org-drill-present-multicloze-show1 ()
1820 "Similar to `org-drill-present-multicloze-hide1', but hides all
1821 the pieces of text that are marked for cloze deletion, except for one
1822 piece which is chosen at random."
1823 (org-drill-present-multicloze-hide-n -
1))
1825 (defun org-drill-present-multicloze-show2 ()
1826 "Similar to `org-drill-present-multicloze-show1', but reveals two
1827 pieces rather than one."
1828 (org-drill-present-multicloze-hide-n -
2))
1830 ;; (defun org-drill-present-multicloze-show1 ()
1831 ;; "Similar to `org-drill-present-multicloze-hide1', but hides all
1832 ;; the pieces of text that are marked for cloze deletion, except for one
1833 ;; piece which is chosen at random."
1834 ;; (with-hidden-comments
1835 ;; (with-hidden-cloze-hints
1836 ;; (let ((item-end nil)
1838 ;; (body-start (or (cdr (org-get-property-block))
1840 ;; (org-drill-hide-all-subheadings-except nil)
1842 ;; (outline-next-heading)
1843 ;; (setq item-end (point)))
1845 ;; (goto-char body-start)
1846 ;; (while (re-search-forward org-drill-cloze-regexp item-end t)
1847 ;; (incf match-count)))
1848 ;; (when (plusp match-count)
1849 ;; (let ((match-to-hide (random* match-count)))
1851 ;; (goto-char body-start)
1852 ;; (dotimes (n match-count)
1853 ;; (re-search-forward org-drill-cloze-regexp
1855 ;; (unless (= n match-to-hide)
1856 ;; (org-drill-hide-matched-cloze-text))))))
1857 ;; (org-display-inline-images t)
1858 ;; (org-cycle-hide-drawers 'all)
1859 ;; (prog1 (org-drill-presentation-prompt)
1860 ;; (org-drill-hide-subheadings-if 'org-drill-entry-p)
1861 ;; (org-drill-unhide-clozed-text))))))
1863 (defun org-drill-present-card-using-text (question &optional answer
)
1864 "Present the string QUESTION as the only visible content of the card.
1865 If ANSWER is supplied, set the global variable `drill-answer' to its value."
1866 (if answer
(setq drill-answer answer
))
1867 (with-hidden-comments
1868 (with-replaced-entry-text
1869 (concat "\n" question
)
1870 (org-drill-hide-all-subheadings-except nil
)
1871 (org-cycle-hide-drawers 'all
)
1873 (org-display-inline-images t
))
1874 (prog1 (org-drill-presentation-prompt)
1875 (org-drill-hide-subheadings-if 'org-drill-entry-p
)))))
1877 (defun org-drill-present-card-using-multiple-overlays (replacements &optional answer
)
1878 "TEXTS is a list of valid values for the 'display' text property.
1879 Present these overlays, in sequence, as the only
1880 visible content of the card.
1881 If ANSWER is supplied, set the global variable `drill-answer' to its value."
1882 (if answer
(setq drill-answer answer
))
1883 (with-hidden-comments
1884 (with-replaced-entry-text-multi
1886 (org-drill-hide-all-subheadings-except nil
)
1887 (org-cycle-hide-drawers 'all
)
1889 (org-display-inline-images t
))
1890 (prog1 (org-drill-presentation-prompt)
1891 (org-drill-hide-subheadings-if 'org-drill-entry-p
)))))
1893 (defun org-drill-entry ()
1894 "Present the current topic for interactive review, as in `org-drill'.
1895 Review will occur regardless of whether the topic is due for review or whether
1896 it meets the definition of a 'review topic' used by `org-drill'.
1898 Returns a quality rating from 0 to 5, or nil if the user quit, or the symbol
1899 EDIT if the user chose to exit the drill and edit the current item. Choosing
1900 the latter option leaves the drill session suspended; it can be resumed
1901 later using `org-drill-resume'.
1903 See `org-drill' for more details."
1905 (org-drill-goto-drill-entry-heading)
1906 ;;(unless (org-part-of-drill-entry-p)
1907 ;; (error "Point is not inside a drill entry"))
1908 ;;(unless (org-at-heading-p)
1909 ;; (org-back-to-heading))
1910 (let ((card-type (org-entry-get (point) "DRILL_CARD_TYPE"))
1911 (answer-fn 'org-drill-present-default-answer
)
1912 (present-empty-cards nil
)
1914 ;; fontification functions in `outline-view-change-hook' can cause big
1915 ;; slowdowns, so we temporarily bind this variable to nil here.
1916 (outline-view-change-hook nil
))
1917 (setq drill-answer nil
)
1918 (org-save-outline-visibility t
1920 (org-narrow-to-subtree)
1922 (org-cycle-hide-drawers 'all
)
1924 (let ((presentation-fn
1925 (cdr (assoc card-type org-drill-card-type-alist
))))
1926 (if (listp presentation-fn
)
1927 (psetq answer-fn
(or (second presentation-fn
)
1928 'org-drill-present-default-answer
)
1929 present-empty-cards
(third presentation-fn
)
1930 presentation-fn
(first presentation-fn
)))
1932 ((null presentation-fn
)
1933 (message "%s:%d: Unrecognised card type '%s', skipping..."
1934 (buffer-name) (point) card-type
)
1938 (setq cont
(funcall presentation-fn
))
1950 (lambda () (org-drill-reschedule)))))))))))))
1952 (defun org-drill-entries-pending-p ()
1953 (or *org-drill-again-entries
*
1954 *org-drill-current-item
*
1955 (and (not (org-drill-maximum-item-count-reached-p))
1956 (not (org-drill-maximum-duration-reached-p))
1957 (or *org-drill-new-entries
*
1958 *org-drill-failed-entries
*
1959 *org-drill-young-mature-entries
*
1960 *org-drill-old-mature-entries
*
1961 *org-drill-overdue-entries
*
1962 *org-drill-again-entries
*))))
1964 (defun org-drill-pending-entry-count ()
1965 (+ (if (markerp *org-drill-current-item
*) 1 0)
1966 (length *org-drill-new-entries
*)
1967 (length *org-drill-failed-entries
*)
1968 (length *org-drill-young-mature-entries
*)
1969 (length *org-drill-old-mature-entries
*)
1970 (length *org-drill-overdue-entries
*)
1971 (length *org-drill-again-entries
*)))
1973 (defun org-drill-maximum-duration-reached-p ()
1974 "Returns true if the current drill session has continued past its
1976 (and org-drill-maximum-duration
1977 (not *org-drill-cram-mode
*)
1978 *org-drill-start-time
*
1979 (> (- (float-time (current-time)) *org-drill-start-time
*)
1980 (* org-drill-maximum-duration
60))))
1982 (defun org-drill-maximum-item-count-reached-p ()
1983 "Returns true if the current drill session has reached the
1984 maximum number of items."
1985 (and org-drill-maximum-items-per-session
1986 (not *org-drill-cram-mode
*)
1987 (>= (length *org-drill-done-entries
*)
1988 org-drill-maximum-items-per-session
)))
1990 (defun org-drill-pop-next-pending-entry ()
1991 (block org-drill-pop-next-pending-entry
1994 (not (org-drill-entry-p m
)))
1998 ;; First priority is items we failed in a prior session.
1999 ((and *org-drill-failed-entries
*
2000 (not (org-drill-maximum-item-count-reached-p))
2001 (not (org-drill-maximum-duration-reached-p)))
2002 (pop-random *org-drill-failed-entries
*))
2003 ;; Next priority is overdue items.
2004 ((and *org-drill-overdue-entries
*
2005 (not (org-drill-maximum-item-count-reached-p))
2006 (not (org-drill-maximum-duration-reached-p)))
2007 ;; We use `pop', not `pop-random', because we have already
2008 ;; sorted overdue items into a random order which takes
2009 ;; number of days overdue into account.
2010 (pop *org-drill-overdue-entries
*))
2011 ;; Next priority is 'young' items.
2012 ((and *org-drill-young-mature-entries
*
2013 (not (org-drill-maximum-item-count-reached-p))
2014 (not (org-drill-maximum-duration-reached-p)))
2015 (pop-random *org-drill-young-mature-entries
*))
2016 ;; Next priority is newly added items, and older entries.
2017 ;; We pool these into a single group.
2018 ((and (or *org-drill-new-entries
*
2019 *org-drill-old-mature-entries
*)
2020 (not (org-drill-maximum-item-count-reached-p))
2021 (not (org-drill-maximum-duration-reached-p)))
2023 ((< (random* (+ (length *org-drill-new-entries
*)
2024 (length *org-drill-old-mature-entries
*)))
2025 (length *org-drill-new-entries
*))
2026 (pop-random *org-drill-new-entries
*))
2028 (pop-random *org-drill-old-mature-entries
*))))
2029 ;; After all the above are done, last priority is items
2030 ;; that were failed earlier THIS SESSION.
2031 (*org-drill-again-entries
*
2032 (pop *org-drill-again-entries
*))
2033 (t ; nothing left -- return nil
2034 (return-from org-drill-pop-next-pending-entry nil
)))))
2037 (defun org-drill-entries (&optional resuming-p
)
2038 "Returns nil, t, or a list of markers representing entries that were
2039 'failed' and need to be presented again before the session ends.
2041 RESUMING-P is true if we are resuming a suspended drill session."
2042 (block org-drill-entries
2043 (while (org-drill-entries-pending-p)
2045 ((or (not resuming-p
)
2046 (null *org-drill-current-item
*)
2047 (not (org-drill-entry-p *org-drill-current-item
*)))
2048 (org-drill-pop-next-pending-entry))
2049 (t ; resuming a suspended session.
2050 (setq resuming-p nil
)
2051 *org-drill-current-item
*))))
2052 (setq *org-drill-current-item
* m
)
2054 (error "Unexpectedly ran out of pending drill items"))
2056 (org-drill-goto-entry m
)
2058 ((not (org-drill-entry-due-p))
2059 ;; The entry is not due anymore. This could arise if the user
2060 ;; suspends a drill session, then drills an individual entry,
2061 ;; then resumes the session.
2062 (message "Entry no longer due, skipping...")
2066 (setq result
(org-drill-entry))
2070 (setq end-pos
:quit
)
2071 (return-from org-drill-entries nil
))
2073 (setq end-pos
(point-marker))
2074 (return-from org-drill-entries nil
))
2076 (setq *org-drill-current-item
* nil
)
2077 nil
) ; skip this item
2080 ((<= result org-drill-failure-quality
)
2081 (if *org-drill-again-entries
*
2082 (setq *org-drill-again-entries
*
2083 (shuffle-list *org-drill-again-entries
*)))
2084 (push-end m
*org-drill-again-entries
*))
2086 (push m
*org-drill-done-entries
*)))
2087 (setq *org-drill-current-item
* nil
))))))))))
2089 (defun org-drill-final-report ()
2091 (round (* 100 (count-if (lambda (qual)
2092 (> qual org-drill-failure-quality
))
2093 *org-drill-session-qualities
*))
2094 (max 1 (length *org-drill-session-qualities
*))))
2096 (max-mini-window-height 0.6))
2099 "%d items reviewed. Session duration %s.
2100 Recall of reviewed items:
2101 Excellent (5): %3d%% | Near miss (2): %3d%%
2102 Good (4): %3d%% | Failure (1): %3d%%
2103 Hard (3): %3d%% | Abject failure (0): %3d%%
2105 You successfully recalled %d%% of reviewed items (quality > %s)
2106 %d/%d items still await review (%s, %s, %s, %s, %s).
2107 Tomorrow, %d more items will become due for review.
2108 Session finished. Press a key to continue..."
2109 (length *org-drill-done-entries
*)
2110 (format-seconds "%h:%.2m:%.2s"
2111 (- (float-time (current-time)) *org-drill-start-time
*))
2112 (round (* 100 (count 5 *org-drill-session-qualities
*))
2113 (max 1 (length *org-drill-session-qualities
*)))
2114 (round (* 100 (count 2 *org-drill-session-qualities
*))
2115 (max 1 (length *org-drill-session-qualities
*)))
2116 (round (* 100 (count 4 *org-drill-session-qualities
*))
2117 (max 1 (length *org-drill-session-qualities
*)))
2118 (round (* 100 (count 1 *org-drill-session-qualities
*))
2119 (max 1 (length *org-drill-session-qualities
*)))
2120 (round (* 100 (count 3 *org-drill-session-qualities
*))
2121 (max 1 (length *org-drill-session-qualities
*)))
2122 (round (* 100 (count 0 *org-drill-session-qualities
*))
2123 (max 1 (length *org-drill-session-qualities
*)))
2125 org-drill-failure-quality
2126 (org-drill-pending-entry-count)
2127 (+ (org-drill-pending-entry-count)
2128 *org-drill-dormant-entry-count
*)
2131 (+ (length *org-drill-failed-entries
*)
2132 (length *org-drill-again-entries
*)))
2133 'face
`(:foreground
,org-drill-failed-count-color
))
2135 (format "%d overdue"
2136 (length *org-drill-overdue-entries
*))
2137 'face
`(:foreground
,org-drill-failed-count-color
))
2140 (length *org-drill-new-entries
*))
2141 'face
`(:foreground
,org-drill-new-count-color
))
2144 (length *org-drill-young-mature-entries
*))
2145 'face
`(:foreground
,org-drill-mature-count-color
))
2148 (length *org-drill-old-mature-entries
*))
2149 'face
`(:foreground
,org-drill-mature-count-color
))
2150 *org-drill-due-tomorrow-count
*
2153 (while (not (input-pending-p))
2154 (message "%s" prompt
)
2156 (read-char-exclusive)
2158 (if (and *org-drill-session-qualities
*
2159 (< pass-percent
(- 100 org-drill-forgetting-index
)))
2160 (read-char-exclusive
2163 You failed %d%% of the items you reviewed during this session.
2164 %d (%d%%) of all items scanned were overdue.
2166 Are you keeping up with your items, and reviewing them
2167 when they are scheduled? If so, you may want to consider
2168 lowering the value of `org-drill-learn-fraction' slightly in
2169 order to make items appear more frequently over time."
2170 (propertize "WARNING!" 'face
'org-warning
)
2171 (- 100 pass-percent
)
2172 *org-drill-overdue-entry-count
*
2173 (round (* 100 *org-drill-overdue-entry-count
*)
2174 (+ *org-drill-dormant-entry-count
*
2175 *org-drill-due-entry-count
*)))))))
2177 (defun org-drill-free-markers (markers)
2178 "MARKERS is a list of markers, all of which will be freed (set to
2179 point nowhere). Alternatively, MARKERS can be 't', in which case
2180 all the markers used by Org-Drill will be freed."
2181 (dolist (m (if (eql t markers
)
2182 (append *org-drill-done-entries
*
2183 *org-drill-new-entries
*
2184 *org-drill-failed-entries
*
2185 *org-drill-again-entries
*
2186 *org-drill-overdue-entries
*
2187 *org-drill-young-mature-entries
*
2188 *org-drill-old-mature-entries
*)
2193 (defun org-drill-order-overdue-entries (overdue-data)
2194 (setq *org-drill-overdue-entries
*
2196 (sort (shuffle-list overdue-data
)
2197 (lambda (a b
) (> (cdr a
) (cdr b
)))))))
2200 (defun org-drill-entry-status ()
2201 "Returns a list (STATUS DUE) where DUE is the number of days overdue,
2202 zero being due today, -1 being scheduled 1 day in the future. STATUS is
2203 one of the following values:
2204 - nil, if the item is not a drill entry, or has an empty body
2214 (unless (org-at-heading-p)
2215 (org-back-to-heading))
2216 (let ((due (org-drill-entry-days-overdue))
2217 (last-int (org-drill-entry-last-interval 1)))
2220 ((not (org-drill-entry-p))
2222 ((and (org-entry-empty-p)
2223 (let* ((card-type (org-entry-get (point) "DRILL_CARD_TYPE" nil
))
2224 (dat (cdr (assoc card-type org-drill-card-type-alist
))))
2225 (or (null card-type
)
2226 (not (third dat
)))))
2227 ;; body is empty, and this is not a card type where empty bodies are
2228 ;; meaningful, so skip it.
2230 ((null due
) ; unscheduled - usually a skipped leech
2234 ((minusp due
) ; scheduled in the future
2236 ;; The rest of the stati all denote 'due' items ==========================
2237 ((<= (org-drill-entry-last-quality 9999)
2238 org-drill-failure-quality
)
2239 ;; Mature entries that were failed last time are
2240 ;; FAILED, regardless of how young, old or overdue
2243 ((org-drill-entry-new-p)
2245 ((org-drill-entry-overdue-p due last-int
)
2246 ;; Overdue status overrides young versus old
2248 ;; Store marker + due, for sorting of overdue entries
2250 ((<= (org-drill-entry-last-interval 9999)
2251 org-drill-days-before-old
)
2258 (defun org-drill-progress-message (collected scanned
)
2259 (when (zerop (% scanned
50))
2260 (let* ((meter-width 40)
2261 (sym1 (if (oddp (floor scanned
(* 50 meter-width
))) ?| ?.
))
2262 (sym2 (if (eql sym1 ?.
) ?| ?.
)))
2263 (message "Collecting due drill items:%4d %s%s"
2265 (make-string (%
(ceiling scanned
50) meter-width
)
2267 (make-string (- meter-width
(%
(ceiling scanned
50) meter-width
))
2271 (defun org-drill (&optional scope resume-p
)
2272 "Begin an interactive 'drill session'. The user is asked to
2273 review a series of topics (headers). Each topic is initially
2274 presented as a 'question', often with part of the topic content
2275 hidden. The user attempts to recall the hidden information or
2276 answer the question, then presses a key to reveal the answer. The
2277 user then rates his or her recall or performance on that
2278 topic. This rating information is used to reschedule the topic
2281 Org-drill proceeds by:
2283 - Finding all topics (headings) in SCOPE which have either been
2284 used and rescheduled before, or which have a tag that matches
2285 `org-drill-question-tag'.
2287 - All matching topics which are either unscheduled, or are
2288 scheduled for the current date or a date in the past, are
2289 considered to be candidates for the drill session.
2291 - If `org-drill-maximum-items-per-session' is set, a random
2292 subset of these topics is presented. Otherwise, all of the
2293 eligible topics will be presented.
2295 SCOPE determines the scope in which to search for
2296 questions. It accepts the same values as `org-drill-scope',
2299 If RESUME-P is non-nil, resume a suspended drill session rather
2300 than starting a new one."
2308 (org-drill-free-markers t
)
2309 (setq *org-drill-current-item
* nil
2310 *org-drill-done-entries
* nil
2311 *org-drill-dormant-entry-count
* 0
2312 *org-drill-due-entry-count
* 0
2313 *org-drill-due-tomorrow-count
* 0
2314 *org-drill-overdue-entry-count
* 0
2315 *org-drill-new-entries
* nil
2316 *org-drill-overdue-entries
* nil
2317 *org-drill-young-mature-entries
* nil
2318 *org-drill-old-mature-entries
* nil
2319 *org-drill-failed-entries
* nil
2320 *org-drill-again-entries
* nil
)
2321 (setq *org-drill-session-qualities
* nil
)
2322 (setq *org-drill-start-time
* (float-time (current-time))))
2323 (setq *random-state
* (make-random-state t
)) ; reseed RNG
2327 (let ((org-trust-scanner-tags t
)
2328 (warned-about-id-creation nil
))
2329 (org-map-drill-entries
2331 (org-drill-progress-message
2332 (+ (length *org-drill-new-entries
*)
2333 (length *org-drill-overdue-entries
*)
2334 (length *org-drill-young-mature-entries
*)
2335 (length *org-drill-old-mature-entries
*)
2336 (length *org-drill-failed-entries
*))
2339 ((not (org-drill-entry-p))
2342 (when (and (not warned-about-id-creation
)
2343 (null (org-id-get)))
2344 (message (concat "Creating unique IDs for items "
2345 "(slow, but only happens once)"))
2347 (setq warned-about-id-creation t
))
2348 (org-id-get-create) ; ensure drill entry has unique ID
2349 (destructuring-bind (status due
) (org-drill-entry-status)
2352 (incf *org-drill-dormant-entry-count
*))
2354 ;; (incf *org-drill-dormant-entry-count*)
2355 ;; (incf *org-drill-due-tomorrow-count*))
2357 (incf *org-drill-dormant-entry-count
*)
2359 (incf *org-drill-due-tomorrow-count
*)))
2361 (push (point-marker) *org-drill-new-entries
*))
2363 (push (point-marker) *org-drill-failed-entries
*))
2365 (push (point-marker) *org-drill-young-mature-entries
*))
2367 (push (cons (point-marker) due
) overdue-data
))
2369 (push (point-marker) *org-drill-old-mature-entries
*))
2372 (org-drill-order-overdue-entries overdue-data
)
2373 (setq *org-drill-overdue-entry-count
*
2374 (length *org-drill-overdue-entries
*))))
2375 (setq *org-drill-due-entry-count
* (org-drill-pending-entry-count))
2377 ((and (null *org-drill-current-item
*)
2378 (null *org-drill-new-entries
*)
2379 (null *org-drill-failed-entries
*)
2380 (null *org-drill-overdue-entries
*)
2381 (null *org-drill-young-mature-entries
*)
2382 (null *org-drill-old-mature-entries
*))
2383 (message "I did not find any pending drill items."))
2385 (org-drill-entries resume-p
)
2386 (message "Drill session finished!"))))
2389 (setq *org-drill-cram-mode
* nil
)
2390 (org-drill-free-markers *org-drill-done-entries
*)))))
2393 (when (markerp end-pos
)
2394 (org-drill-goto-entry end-pos
)
2397 (let ((keystr (command-keybinding-to-string 'org-drill-resume
)))
2399 "You can continue the drill session with the command `org-drill-resume'.%s"
2400 (if keystr
(format "\nYou can run this command by pressing %s." keystr
)
2403 (org-drill-final-report)
2404 (if (eql 'sm5 org-drill-spaced-repetition-algorithm
)
2405 (org-drill-save-optimal-factor-matrix))
2406 (if org-drill-save-buffers-after-drill-sessions-p
2407 (save-some-buffers))
2408 (message "Drill session finished!")))))
2411 (defun org-drill-save-optimal-factor-matrix ()
2412 (message "Saving optimal factor matrix...")
2413 (customize-save-variable 'org-drill-optimal-factor-matrix
2414 org-drill-optimal-factor-matrix
))
2417 (defun org-drill-cram (&optional scope
)
2418 "Run an interactive drill session in 'cram mode'. In cram mode,
2419 all drill items are considered to be due for review, unless they
2420 have been reviewed within the last `org-drill-cram-hours'
2423 (setq *org-drill-cram-mode
* t
)
2427 (defun org-drill-tree ()
2428 "Run an interactive drill session using drill items within the
2434 (defun org-drill-directory ()
2435 "Run an interactive drill session using drill items from all org
2436 files in the same directory as the current file."
2438 (org-drill 'directory
))
2441 (defun org-drill-again (&optional scope
)
2442 "Run a new drill session, but try to use leftover due items that
2443 were not reviewed during the last session, rather than scanning for
2444 unreviewed items. If there are no leftover items in memory, a full
2445 scan will be performed."
2447 (setq *org-drill-cram-mode
* nil
)
2449 ((plusp (org-drill-pending-entry-count))
2450 (org-drill-free-markers *org-drill-done-entries
*)
2451 (if (markerp *org-drill-current-item
*)
2452 (free-marker *org-drill-current-item
*))
2453 (setq *org-drill-start-time
* (float-time (current-time))
2454 *org-drill-done-entries
* nil
2455 *org-drill-current-item
* nil
)
2456 (org-drill scope t
))
2458 (org-drill scope
))))
2462 (defun org-drill-resume ()
2463 "Resume a suspended drill session. Sessions are suspended by
2464 exiting them with the `edit' or `quit' options."
2467 ((org-drill-entries-pending-p)
2469 ((and (plusp (org-drill-pending-entry-count))
2470 ;; Current drill session is finished, but there are still
2471 ;; more items which need to be reviewed.
2473 "You have finished the drill session. However, %d items still
2474 need reviewing. Start a new drill session? "
2475 (org-drill-pending-entry-count))))
2478 (message "You have finished the drill session."))))
2481 (defun org-drill-strip-entry-data ()
2482 (dolist (prop org-drill-scheduling-properties
)
2483 (org-delete-property prop
))
2487 (defun org-drill-strip-all-data (&optional scope
)
2488 "Delete scheduling data from every drill entry in scope. This
2489 function may be useful if you want to give your collection of
2490 entries to someone else. Scope defaults to the current buffer,
2491 and is specified by the argument SCOPE, which accepts the same
2492 values as `org-drill-scope'."
2495 "Delete scheduling data from ALL items in scope: are you sure?")
2498 ;; Scope is the current buffer. This means we can use
2499 ;; `org-delete-property-globally', which is faster.
2500 (dolist (prop org-drill-scheduling-properties
)
2501 (org-delete-property-globally prop
))
2502 (org-map-drill-entries (lambda () (org-schedule t
)) scope
))
2504 (org-map-drill-entries 'org-drill-strip-entry-data scope
)))
2509 (defun org-drill-add-cloze-fontification ()
2510 (when org-drill-use-visible-cloze-face-p
2511 (font-lock-add-keywords 'org-mode
2512 org-drill-cloze-keywords
2515 (add-hook 'org-mode-hook
'org-drill-add-cloze-fontification
)
2517 (org-drill-add-cloze-fontification)
2520 ;;; Synching card collections =================================================
2523 (defvar *org-drill-dest-id-table
* (make-hash-table :test
'equal
))
2526 (defun org-drill-copy-entry-to-other-buffer (dest &optional path
)
2527 "Copy the subtree at point to the buffer DEST. The copy will receive
2528 the tag 'imported'."
2529 (block org-drill-copy-entry-to-other-buffer
2531 (let ((src (current-buffer))
2533 (flet ((paste-tree-here (&optional level
)
2534 (org-paste-subtree level
)
2535 (org-drill-strip-entry-data)
2536 (org-toggle-tag "imported" 'on
)
2537 (org-map-drill-entries
2539 (let ((id (org-id-get)))
2540 (org-drill-strip-entry-data)
2541 (unless (gethash id
*org-drill-dest-id-table
*)
2542 (puthash id
(point-marker)
2543 *org-drill-dest-id-table
*))))
2546 (setq path
(org-get-outline-path)))
2548 (switch-to-buffer dest
)
2551 (org-find-olp path t
)
2552 (error ; path does not exist in DEST
2553 (return-from org-drill-copy-entry-to-other-buffer
2556 (org-drill-copy-entry-to-other-buffer
2557 dest
(butlast path
)))
2559 ;; We've looked all the way up the path
2560 ;; Default to appending to the end of DEST
2561 (goto-char (point-max))
2563 (paste-tree-here)))))))
2565 (outline-next-heading)
2568 (paste-tree-here (1+ (or (org-current-level) 0))))))))
2571 (defun org-drill-merge-buffers (src &optional dest ignore-new-items-p
)
2572 "SRC and DEST are two org mode buffers containing drill items.
2573 For each drill item in DEST that shares an ID with an item in SRC,
2574 overwrite scheduling data in DEST with data taken from the item in SRC.
2575 This is intended for use when two people are sharing a set of drill items,
2576 one person has made some updates to the item set, and the other person
2577 wants to migrate to the updated set without losing their scheduling data.
2579 By default, any drill items in SRC which do not exist in DEST are
2580 copied into DEST. We attempt to place the copied item in the
2581 equivalent location in DEST to its location in SRC, by matching
2582 the heading hierarchy. However if IGNORE-NEW-ITEMS-P is non-nil,
2583 we simply ignore any items that do not exist in DEST, and do not
2585 (interactive "bImport scheduling info from which buffer?")
2587 (setq dest
(current-buffer)))
2588 (setq src
(get-buffer src
)
2589 dest
(get-buffer dest
))
2592 (concat "About to overwrite all scheduling data for drill items in `%s' "
2593 "with information taken from matching items in `%s'. Proceed? ")
2594 (buffer-name dest
) (buffer-name src
)))
2595 ;; Compile list of all IDs in the destination buffer.
2596 (clrhash *org-drill-dest-id-table
*)
2597 (with-current-buffer dest
2598 (org-map-drill-entries
2600 (let ((this-id (org-id-get)))
2602 (puthash this-id
(point-marker) *org-drill-dest-id-table
*))))
2604 ;; Look through all entries in source buffer.
2605 (with-current-buffer src
2606 (org-map-drill-entries
2608 (let ((id (org-id-get))
2609 (last-quality nil
) (last-reviewed nil
)
2610 (scheduled-time nil
))
2613 (not (org-drill-entry-p)))
2615 ((gethash id
*org-drill-dest-id-table
*)
2616 ;; This entry matches an entry in dest. Retrieve all its
2617 ;; scheduling data, then go to the matching location in dest
2618 ;; and write the data.
2619 (let ((marker (gethash id
*org-drill-dest-id-table
*)))
2620 (destructuring-bind (last-interval repetitions failures
2621 total-repeats meanq ease
)
2622 (org-drill-get-item-data)
2623 (setq last-reviewed
(org-entry-get (point) "DRILL_LAST_REVIEWED")
2624 last-quality
(org-entry-get (point) "DRILL_LAST_QUALITY")
2625 scheduled-time
(org-get-scheduled-time (point)))
2627 ;; go to matching entry in destination buffer
2628 (switch-to-buffer (marker-buffer marker
))
2630 (org-drill-strip-entry-data)
2631 (unless (zerop total-repeats
)
2632 (org-drill-store-item-data last-interval repetitions failures
2633 total-repeats meanq ease
)
2635 (org-set-property "LAST_QUALITY" last-quality
)
2636 (org-delete-property "LAST_QUALITY"))
2638 (org-set-property "LAST_REVIEWED" last-reviewed
)
2639 (org-delete-property "LAST_REVIEWED"))
2641 (org-schedule nil scheduled-time
)))))
2642 (remhash id
*org-drill-dest-id-table
*)
2643 (free-marker marker
)))
2645 ;; item in SRC has ID, but no matching ID in DEST.
2646 ;; It must be a new item that does not exist in DEST.
2647 ;; Copy the entire item to the *end* of DEST.
2648 (unless ignore-new-items-p
2649 (org-drill-copy-entry-to-other-buffer dest
))))))
2651 ;; Finally: there may be some items in DEST which are not in SRC, and
2652 ;; which have been scheduled by another user of DEST. Clear out the
2653 ;; scheduling info from all the unmatched items in DEST.
2654 (with-current-buffer dest
2655 (maphash (lambda (id m
)
2657 (org-drill-strip-entry-data)
2659 *org-drill-dest-id-table
*))))
2661 ;;; Card types for learning languages =========================================
2663 ;;; Get spell-number.el from:
2664 ;;; http://www.emacswiki.org/emacs/spell-number.el
2665 (autoload 'spelln-integer-in-words
"spell-number")
2667 ;;; `conjugate' card type =====================================================
2668 ;;; See spanish.org for usage
2670 (defvar org-drill-verb-tense-alist
2671 '(("present" "tomato")
2672 ("simple present" "tomato")
2673 ("present indicative" "tomato")
2676 ("simple past" "purple")
2677 ("preterite" "purple")
2678 ("imperfect" "darkturquoise")
2679 ("present perfect" "royalblue")
2682 ;; moods (backgrounds).
2683 ("indicative" nil
) ; default
2684 ("subjunctive" "medium blue")
2685 ("conditional" "grey30")
2686 ("negative imperative" "red4")
2687 ("positive imperative" "darkgreen")
2689 "Alist where each entry has the form (TENSE COLOUR), where
2690 TENSE is a string naming a tense in which verbs can be
2691 conjugated, and COLOUR is a string specifying a foreground colour
2692 which will be used by `org-drill-present-verb-conjugation' and
2693 `org-drill-show-answer-verb-conjugation' to fontify the verb and
2694 the name of the tense.")
2697 (defun org-drill-get-verb-conjugation-info ()
2698 "Auxiliary function used by `org-drill-present-verb-conjugation' and
2699 `org-drill-show-answer-verb-conjugation'."
2700 (let ((infinitive (org-entry-get (point) "VERB_INFINITIVE" t
))
2701 (inf-hint (org-entry-get (point) "VERB_INFINITIVE_HINT" t
))
2702 (translation (org-entry-get (point) "VERB_TRANSLATION" t
))
2703 (tense (org-entry-get (point) "VERB_TENSE" nil
))
2704 (mood (org-entry-get (point) "VERB_MOOD" nil
))
2705 (highlight-face nil
))
2706 (unless (and infinitive translation
(or tense mood
))
2707 (error "Missing information for verb conjugation card (%s, %s, %s, %s) at %s"
2708 infinitive translation tense mood
(point)))
2709 (setq tense
(if tense
(downcase (car (read-from-string tense
))))
2710 mood
(if mood
(downcase (car (read-from-string mood
))))
2711 infinitive
(car (read-from-string infinitive
))
2712 inf-hint
(if inf-hint
(car (read-from-string inf-hint
)))
2713 translation
(car (read-from-string translation
)))
2714 (setq highlight-face
2716 (or (second (assoc-string tense org-drill-verb-tense-alist t
))
2719 (second (assoc-string mood org-drill-verb-tense-alist t
))))
2720 (setq infinitive
(propertize infinitive
'face highlight-face
))
2721 (setq translation
(propertize translation
'face highlight-face
))
2722 (if tense
(setq tense
(propertize tense
'face highlight-face
)))
2723 (if mood
(setq mood
(propertize mood
'face highlight-face
)))
2724 (list infinitive inf-hint translation tense mood
)))
2727 (defun org-drill-present-verb-conjugation ()
2728 "Present a drill entry whose card type is 'conjugate'."
2729 (flet ((tense-and-mood-to-string
2733 (format "%s tense, %s mood" tense mood
))
2735 (format "%s tense" tense
))
2737 (format "%s mood" mood
)))))
2738 (destructuring-bind (infinitive inf-hint translation tense mood
)
2739 (org-drill-get-verb-conjugation-info)
2740 (org-drill-present-card-using-text
2742 ((zerop (random* 2))
2743 (format "\nTranslate the verb\n\n%s\n\nand conjugate for the %s.\n\n"
2744 infinitive
(tense-and-mood-to-string tense mood
)))
2747 (format "\nGive the verb that means\n\n%s %s\n
2748 and conjugate for the %s.\n\n"
2750 (if inf-hint
(format " [HINT: %s]" inf-hint
) "")
2751 (tense-and-mood-to-string tense mood
))))))))
2754 (defun org-drill-show-answer-verb-conjugation (reschedule-fn)
2755 "Show the answer for a drill item whose card type is 'conjugate'.
2756 RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
2757 returns its return value."
2758 (destructuring-bind (infinitive inf-hint translation tense mood
)
2759 (org-drill-get-verb-conjugation-info)
2760 (with-replaced-entry-heading
2761 (format "%s of %s ==> %s\n\n"
2765 (format "%s tense, %s mood" tense mood
))
2767 (format "%s tense" tense
))
2769 (format "%s mood" mood
))))
2770 infinitive translation
)
2771 (org-cycle-hide-drawers 'all
)
2772 (funcall reschedule-fn
))))
2775 ;;; `decline_noun' card type ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2778 (defvar org-drill-noun-gender-alist
2779 '(("masculine" "dodgerblue")
2780 ("masc" "dodgerblue")
2781 ("male" "dodgerblue")
2783 ("feminine" "orchid")
2794 (defun org-drill-get-noun-info ()
2795 "Auxiliary function used by `org-drill-present-noun-declension' and
2796 `org-drill-show-answer-noun-declension'."
2797 (let ((noun (org-entry-get (point) "NOUN" t
))
2798 (noun-hint (org-entry-get (point) "NOUN_HINT" t
))
2799 (noun-root (org-entry-get (point) "NOUN_ROOT" t
))
2800 (noun-gender (org-entry-get (point) "NOUN_GENDER" t
))
2801 (translation (org-entry-get (point) "NOUN_TRANSLATION" t
))
2802 (highlight-face nil
))
2803 (unless (and noun translation
)
2804 (error "Missing information for `decline_noun' card (%s, %s, %s, %s) at %s"
2805 noun translation noun-hint noun-root
(point)))
2806 (setq noun-root
(if noun-root
(car (read-from-string noun-root
)))
2807 noun
(car (read-from-string noun
))
2808 noun-gender
(downcase (car (read-from-string noun-gender
)))
2809 noun-hint
(if noun-hint
(car (read-from-string noun-hint
)))
2810 translation
(car (read-from-string translation
)))
2811 (setq highlight-face
2813 (or (second (assoc-string noun-gender
2814 org-drill-noun-gender-alist t
))
2816 (setq noun
(propertize noun
'face highlight-face
))
2817 (setq translation
(propertize translation
'face highlight-face
))
2818 (list noun noun-root noun-gender noun-hint translation
)))
2821 (defun org-drill-present-noun-declension ()
2822 "Present a drill entry whose card type is 'decline_noun'."
2823 (destructuring-bind (noun noun-root noun-gender noun-hint translation
)
2824 (org-drill-get-noun-info)
2825 (let* ((props (org-entry-properties (point)))
2828 ((assoc "DECLINE_DEFINITE" props
)
2829 (propertize (if (org-entry-get (point) "DECLINE_DEFINITE")
2830 "definite" "indefinite")
2835 ((assoc "DECLINE_PLURAL" props
)
2836 (propertize (if (org-entry-get (point) "DECLINE_PLURAL")
2837 "plural" "singular")
2840 (org-drill-present-card-using-text
2842 ((zerop (random* 2))
2843 (format "\nTranslate the noun\n\n%s (%s)\n\nand list its declensions%s.\n\n"
2845 (if (or plural definite
)
2846 (format " for the %s %s form" definite plural
)
2849 (format "\nGive the noun that means\n\n%s %s\n
2850 and list its declensions%s.\n\n"
2852 (if noun-hint
(format " [HINT: %s]" noun-hint
) "")
2853 (if (or plural definite
)
2854 (format " for the %s %s form" definite plural
)
2858 (defun org-drill-show-answer-noun-declension (reschedule-fn)
2859 "Show the answer for a drill item whose card type is 'decline_noun'.
2860 RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
2861 returns its return value."
2862 (destructuring-bind (noun noun-root noun-gender noun-hint translation
)
2863 (org-drill-get-noun-info)
2864 (with-replaced-entry-heading
2865 (format "Declensions of %s (%s) ==> %s\n\n"
2866 noun noun-gender translation
)
2867 (org-cycle-hide-drawers 'all
)
2868 (funcall reschedule-fn
))))
2871 ;;; `translate_number' card type ==============================================
2872 ;;; See spanish.org for usage
2875 (defun spelln-integer-in-language (n lang
)
2876 (let ((spelln-language lang
))
2877 (spelln-integer-in-words n
)))
2879 (defun org-drill-present-translate-number ()
2880 (let ((num-min (read (org-entry-get (point) "DRILL_NUMBER_MIN")))
2881 (num-max (read (org-entry-get (point) "DRILL_NUMBER_MAX")))
2882 (language (read (org-entry-get (point) "DRILL_LANGUAGE" t
)))
2884 (drilled-number-direction 'to-english
)
2885 (highlight-face 'font-lock-warning-face
))
2887 ((not (fboundp 'spelln-integer-in-words
))
2888 (message "`spell-number.el' not loaded, skipping 'translate_number' card...")
2891 ((not (and (numberp num-min
) (numberp num-max
) language
))
2892 (error "Missing language or minimum or maximum numbers for number card"))
2894 (if (> num-min num-max
)
2895 (psetf num-min num-max
2897 (setq drilled-number
2898 (+ num-min
(random* (abs (1+ (- num-max num-min
))))))
2899 (setq drilled-number-direction
2900 (if (zerop (random* 2)) 'from-english
'to-english
))
2902 ((eql 'to-english drilled-number-direction
)
2903 (org-drill-present-card-using-text
2904 (format "\nTranslate into English:\n\n%s\n"
2906 (spelln-integer-in-language drilled-number language
)
2907 'face highlight-face
))
2908 (spelln-integer-in-language drilled-number
'english-gb
)))
2910 (org-drill-present-card-using-text
2911 (format "\nTranslate into %s:\n\n%s\n"
2912 (capitalize (format "%s" language
))
2914 (spelln-integer-in-language drilled-number
'english-gb
)
2915 'face highlight-face
))
2916 (spelln-integer-in-language drilled-number language
))))))))
2918 ;; (defun org-drill-show-answer-translate-number (reschedule-fn)
2919 ;; (let* ((language (read (org-entry-get (point) "DRILL_LANGUAGE" t)))
2920 ;; (highlight-face 'font-lock-warning-face)
2922 ;; (let ((spelln-language language))
2923 ;; (propertize (spelln-integer-in-words *drilled-number*)
2924 ;; 'face highlight-face)))
2926 ;; (let ((spelln-language 'english-gb))
2927 ;; (propertize (spelln-integer-in-words *drilled-number*)
2928 ;; 'face 'highlight-face))))
2929 ;; (with-replaced-entry-text
2931 ;; ((eql 'to-english *drilled-number-direction*)
2932 ;; (format "\nThe English translation of %s is:\n\n%s\n"
2933 ;; non-english english))
2935 ;; (format "\nThe %s translation of %s is:\n\n%s\n"
2936 ;; (capitalize (format "%s" language))
2937 ;; english non-english)))
2938 ;; (funcall reschedule-fn))))
2941 ;;; `spanish_verb' card type ==================================================
2942 ;;; Not very interesting, but included to demonstrate how a presentation
2943 ;;; function can manipulate which subheading are hidden versus shown.
2946 (defun org-drill-present-spanish-verb ()
2948 (reveal-headings nil
))
2949 (with-hidden-comments
2950 (with-hidden-cloze-hints
2951 (with-hidden-cloze-text
2954 (org-drill-hide-all-subheadings-except '("Infinitive"))
2956 (concat "Translate this Spanish verb, and conjugate it "
2957 "for the *present* tense.")
2958 reveal-headings
'("English" "Present Tense" "Notes")))
2960 (org-drill-hide-all-subheadings-except '("English"))
2961 (setq prompt
(concat "For the *present* tense, conjugate the "
2962 "Spanish translation of this English verb.")
2963 reveal-headings
'("Infinitive" "Present Tense" "Notes")))
2965 (org-drill-hide-all-subheadings-except '("Infinitive"))
2966 (setq prompt
(concat "Translate this Spanish verb, and "
2967 "conjugate it for the *past* tense.")
2968 reveal-headings
'("English" "Past Tense" "Notes")))
2970 (org-drill-hide-all-subheadings-except '("English"))
2971 (setq prompt
(concat "For the *past* tense, conjugate the "
2972 "Spanish translation of this English verb.")
2973 reveal-headings
'("Infinitive" "Past Tense" "Notes")))
2975 (org-drill-hide-all-subheadings-except '("Infinitive"))
2976 (setq prompt
(concat "Translate this Spanish verb, and "
2977 "conjugate it for the *future perfect* tense.")
2978 reveal-headings
'("English" "Future Perfect Tense" "Notes")))
2980 (org-drill-hide-all-subheadings-except '("English"))
2981 (setq prompt
(concat "For the *future perfect* tense, conjugate the "
2982 "Spanish translation of this English verb.")
2983 reveal-headings
'("Infinitive" "Future Perfect Tense" "Notes"))))
2984 (org-cycle-hide-drawers 'all
)
2985 (prog1 (org-drill-presentation-prompt)
2986 (org-drill-hide-subheadings-if 'org-drill-entry-p
)))))))
2989 (provide 'org-drill
)