ox-texinfo: Handle tables with captions and list of tables
[org-mode/org-tableheadings.git] / contrib / lisp / org-drill.el
blob81012e1d1bcb09a8e0095059e7fa2c395a5ae015
1 ;;; -*- coding: utf-8-unix -*-
2 ;;; org-drill.el - Self-testing using spaced repetition
3 ;;;
4 ;;; Author: Paul Sexton <eeeickythump@gmail.com>
5 ;;; Version: 2.4.5
6 ;;; Repository at http://bitbucket.org/eeeickythump/org-drill/
7 ;;;
8 ;;;
9 ;;; Synopsis
10 ;;; ========
11 ;;;
12 ;;; Uses the SuperMemo spaced repetition algorithms to conduct interactive
13 ;;; "drill sessions", where the material to be remembered is presented to the
14 ;;; student in random order. The student rates his or her recall of each item,
15 ;;; and this information is used to schedule the item for later revision.
16 ;;;
17 ;;; Each drill session can be restricted to topics in the current buffer
18 ;;; (default), one or several files, all agenda files, or a subtree. A single
19 ;;; topic can also be drilled.
20 ;;;
21 ;;; Different "card types" can be defined, which present their information to
22 ;;; the student in different ways.
23 ;;;
24 ;;; See the file README.org for more detailed documentation.
27 (eval-when-compile (require 'cl))
28 (eval-when-compile (require 'hi-lock))
29 (require 'cl-lib)
30 (require 'hi-lock)
31 (require 'org)
32 (require 'org-id)
33 (require 'org-learn)
36 (defgroup org-drill nil
37 "Options concerning interactive drill sessions in Org mode (org-drill)."
38 :tag "Org-Drill"
39 :group 'org-link)
43 (defcustom org-drill-question-tag
44 "drill"
45 "Tag which topics must possess in order to be identified as review topics
46 by `org-drill'."
47 :group 'org-drill
48 :type 'string)
51 (defcustom org-drill-maximum-items-per-session
53 "Each drill session will present at most this many topics for review.
54 Nil means unlimited."
55 :group 'org-drill
56 :type '(choice integer (const nil)))
60 (defcustom org-drill-maximum-duration
62 "Maximum duration of a drill session, in minutes.
63 Nil means unlimited."
64 :group 'org-drill
65 :type '(choice integer (const nil)))
68 (defcustom org-drill-failure-quality
70 "If the quality of recall for an item is this number or lower,
71 it is regarded as an unambiguous failure, and the repetition
72 interval for the card is reset to 0 days. If the quality is higher
73 than this number, it is regarded as successfully recalled, but the
74 time interval to the next repetition will be lowered if the quality
75 was near to a fail.
77 By default this is 2, for SuperMemo-like behaviour. For
78 Mnemosyne-like behaviour, set it to 1. Other values are not
79 really sensible."
80 :group 'org-drill
81 :type '(choice (const 2) (const 1)))
84 (defcustom org-drill-forgetting-index
86 "What percentage of items do you consider it is 'acceptable' to
87 forget each drill session? The default is 10%. A warning message
88 is displayed at the end of the session if the percentage forgotten
89 climbs above this number."
90 :group 'org-drill
91 :type 'integer)
94 (defcustom org-drill-leech-failure-threshold
96 "If an item is forgotten more than this many times, it is tagged
97 as a 'leech' item."
98 :group 'org-drill
99 :type '(choice integer (const nil)))
102 (defcustom org-drill-leech-method
103 'skip
104 "How should 'leech items' be handled during drill sessions?
105 Possible values:
106 - nil :: Leech items are treated the same as normal items.
107 - skip :: Leech items are not included in drill sessions.
108 - warn :: Leech items are still included in drill sessions,
109 but a warning message is printed when each leech item is
110 presented."
111 :group 'org-drill
112 :type '(choice (const warn) (const skip) (const nil)))
115 (defface org-drill-visible-cloze-face
116 '((t (:foreground "darkseagreen")))
117 "The face used to hide the contents of cloze phrases."
118 :group 'org-drill)
121 (defface org-drill-visible-cloze-hint-face
122 '((t (:foreground "dark slate blue")))
123 "The face used to hide the contents of cloze phrases."
124 :group 'org-drill)
127 (defface org-drill-hidden-cloze-face
128 '((t (:foreground "deep sky blue" :background "blue")))
129 "The face used to hide the contents of cloze phrases."
130 :group 'org-drill)
133 (defcustom org-drill-use-visible-cloze-face-p
135 "Use a special face to highlight cloze-deleted text in org mode
136 buffers?"
137 :group 'org-drill
138 :type 'boolean)
141 (defcustom org-drill-hide-item-headings-p
143 "Conceal the contents of the main heading of each item during drill
144 sessions? You may want to enable this behaviour if item headings or tags
145 contain information that could 'give away' the answer."
146 :group 'org-drill
147 :type 'boolean)
150 (defcustom org-drill-new-count-color
151 "royal blue"
152 "Foreground colour used to display the count of remaining new items
153 during a drill session."
154 :group 'org-drill
155 :type 'color)
157 (defcustom org-drill-mature-count-color
158 "green"
159 "Foreground colour used to display the count of remaining mature items
160 during a drill session. Mature items are due for review, but are not new."
161 :group 'org-drill
162 :type 'color)
164 (defcustom org-drill-failed-count-color
165 "red"
166 "Foreground colour used to display the count of remaining failed items
167 during a drill session."
168 :group 'org-drill
169 :type 'color)
171 (defcustom org-drill-done-count-color
172 "sienna"
173 "Foreground colour used to display the count of reviewed items
174 during a drill session."
175 :group 'org-drill
176 :type 'color)
178 (defcustom org-drill-left-cloze-delimiter
180 "String used within org buffers to delimit cloze deletions."
181 :group 'org-drill
182 :type 'string)
184 (defcustom org-drill-right-cloze-delimiter
186 "String used within org buffers to delimit cloze deletions."
187 :group 'org-drill
188 :type 'string)
191 (setplist 'org-drill-cloze-overlay-defaults
192 `(display ,(format "%s...%s"
193 org-drill-left-cloze-delimiter
194 org-drill-right-cloze-delimiter)
195 face org-drill-hidden-cloze-face
196 window t))
198 (setplist 'org-drill-hidden-text-overlay
199 '(invisible t))
201 (setplist 'org-drill-replaced-text-overlay
202 '(display "Replaced text"
203 face default
204 window t))
207 (defvar org-drill-hint-separator "||"
208 "String which, if it occurs within a cloze expression, signifies that the
209 rest of the expression after the string is a `hint', to be displayed instead of
210 the hidden cloze during a test.")
212 (defun org-drill--compute-cloze-regexp ()
213 (concat "\\("
214 (regexp-quote org-drill-left-cloze-delimiter)
215 "[[:cntrl:][:graph:][:space:]]+?\\)\\(\\|"
216 (regexp-quote org-drill-hint-separator)
217 ".+?\\)\\("
218 (regexp-quote org-drill-right-cloze-delimiter)
219 "\\)"))
221 (defun org-drill--compute-cloze-keywords ()
222 (list (list (org-drill--compute-cloze-regexp)
223 (copy-list '(1 'org-drill-visible-cloze-face nil))
224 (copy-list '(2 'org-drill-visible-cloze-hint-face t))
225 (copy-list '(3 'org-drill-visible-cloze-face nil))
228 (defvar-local org-drill-cloze-regexp
229 (org-drill--compute-cloze-regexp))
232 (defvar-local org-drill-cloze-keywords
233 (org-drill--compute-cloze-keywords))
236 (defcustom org-drill-card-type-alist
237 '((nil org-drill-present-simple-card)
238 ("simple" org-drill-present-simple-card)
239 ("twosided" org-drill-present-two-sided-card nil t)
240 ("multisided" org-drill-present-multi-sided-card nil t)
241 ("hide1cloze" org-drill-present-multicloze-hide1)
242 ("hide2cloze" org-drill-present-multicloze-hide2)
243 ("show1cloze" org-drill-present-multicloze-show1)
244 ("show2cloze" org-drill-present-multicloze-show2)
245 ("multicloze" org-drill-present-multicloze-hide1)
246 ("hidefirst" org-drill-present-multicloze-hide-first)
247 ("hidelast" org-drill-present-multicloze-hide-last)
248 ("hide1_firstmore" org-drill-present-multicloze-hide1-firstmore)
249 ("show1_lastmore" org-drill-present-multicloze-show1-lastmore)
250 ("show1_firstless" org-drill-present-multicloze-show1-firstless)
251 ("conjugate"
252 org-drill-present-verb-conjugation
253 org-drill-show-answer-verb-conjugation)
254 ("decline_noun"
255 org-drill-present-noun-declension
256 org-drill-show-answer-noun-declension)
257 ("spanish_verb" org-drill-present-spanish-verb)
258 ("translate_number" org-drill-present-translate-number))
259 "Alist associating card types with presentation functions. Each
260 entry in the alist takes the form:
262 ;;; (CARDTYPE QUESTION-FN [ANSWER-FN DRILL-EMPTY-P])
264 Where CARDTYPE is a string or nil (for default), and QUESTION-FN
265 is a function which takes no arguments and returns a boolean
266 value.
268 When supplied, ANSWER-FN is a function that takes one argument --
269 that argument is a function of no arguments, which when called,
270 prompts the user to rate their recall and performs rescheduling
271 of the drill item. ANSWER-FN is called with the point on the
272 active item's heading, just prior to displaying the item's
273 'answer'. It can therefore be used to modify the appearance of
274 the answer. ANSWER-FN must call its argument before returning.
276 When supplied, DRILL-EMPTY-P is a boolean value, default nil.
277 When non-nil, cards of this type will be presented during tests
278 even if their bodies are empty."
279 :group 'org-drill
280 :type '(alist :key-type (choice string (const nil))
281 :value-type function))
284 (defcustom org-drill-scope
285 'file
286 "The scope in which to search for drill items when conducting a
287 drill session. This can be any of:
289 file The current buffer, respecting the restriction if any.
290 This is the default.
291 tree The subtree started with the entry at point
292 file-no-restriction The current buffer, without restriction
293 file-with-archives The current buffer, and any archives associated with it.
294 agenda All agenda files
295 agenda-with-archives All agenda files with any archive files associated
296 with them.
297 directory All files with the extension '.org' in the same
298 directory as the current file (includes the current
299 file if it is an .org file.)
300 (FILE1 FILE2 ...) If this is a list, all files in the list will be scanned.
302 ;; Note -- meanings differ slightly from the argument to org-map-entries:
303 ;; 'file' means current file/buffer, respecting any restriction
304 ;; 'file-no-restriction' means current file/buffer, ignoring restrictions
305 ;; 'directory' means all *.org files in current directory
306 :group 'org-drill
307 :type '(choice (const :tag "The current buffer, respecting the restriction if any." file)
308 (const :tag "The subtree started with the entry at point" tree)
309 (const :tag "The current buffer, without restriction" file-no-restriction)
310 (const :tag "The current buffer, and any archives associated with it." file-with-archives)
311 (const :tag "All agenda files" agenda)
312 (const :tag "All agenda files with any archive files associated with them." agenda-with-archives)
313 (const :tag "All files with the extension '.org' in the same directory as the current file (includes the current file if it is an .org file.)" directory)
314 (repeat :tag "List of files to scan for drill items." file)))
317 (defcustom org-drill-match
319 "If non-nil, a string specifying a tags/property/TODO query. During
320 drill sessions, only items that match this query will be considered."
321 :group 'org-drill
322 :type '(choice (const nil) string))
325 (defcustom org-drill-save-buffers-after-drill-sessions-p
327 "If non-nil, prompt to save all modified buffers after a drill session
328 finishes."
329 :group 'org-drill
330 :type 'boolean)
333 (defcustom org-drill-spaced-repetition-algorithm
334 'sm5
335 "Which SuperMemo spaced repetition algorithm to use for scheduling items.
336 Available choices are:
337 - SM2 :: the SM2 algorithm, used in SuperMemo 2.0
338 - SM5 :: the SM5 algorithm, used in SuperMemo 5.0
339 - Simple8 :: a modified version of the SM8 algorithm. SM8 is used in
340 SuperMemo 98. The version implemented here is simplified in that while it
341 'learns' the difficulty of each item using quality grades and number of
342 failures, it does not modify the matrix of values that
343 governs how fast the inter-repetition intervals increase. A method for
344 adjusting intervals when items are reviewed early or late has been taken
345 from SM11, a later version of the algorithm, and included in Simple8."
346 :group 'org-drill
347 :type '(choice (const sm2) (const sm5) (const simple8)))
350 (defcustom org-drill-optimal-factor-matrix
352 "DO NOT CHANGE THE VALUE OF THIS VARIABLE.
354 Persistent matrix of optimal factors, used by the SuperMemo SM5 algorithm.
355 The matrix is saved (using the 'customize' facility) at the end of each
356 drill session.
358 Over time, values in the matrix will adapt to the individual user's
359 pace of learning."
360 :group 'org-drill
361 :type 'sexp)
364 (defcustom org-drill-sm5-initial-interval
366 "In the SM5 algorithm, the initial interval after the first
367 successful presentation of an item is always 4 days. If you wish to change
368 this, you can do so here."
369 :group 'org-drill
370 :type 'float)
373 (defcustom org-drill-add-random-noise-to-intervals-p
375 "If true, the number of days until an item's next repetition
376 will vary slightly from the interval calculated by the SM2
377 algorithm. The variation is very small when the interval is
378 small, but scales up with the interval."
379 :group 'org-drill
380 :type 'boolean)
383 (defcustom org-drill-adjust-intervals-for-early-and-late-repetitions-p
385 "If true, when the student successfully reviews an item 1 or more days
386 before or after the scheduled review date, this will affect that date of
387 the item's next scheduled review, according to the algorithm presented at
388 [[http://www.supermemo.com/english/algsm11.htm#Advanced%20repetitions]].
390 Items that were reviewed early will have their next review date brought
391 forward. Those that were reviewed late will have their next review
392 date postponed further.
394 Note that this option currently has no effect if the SM2 algorithm
395 is used."
396 :group 'org-drill
397 :type 'boolean)
400 (defcustom org-drill-cloze-text-weight
402 "For card types 'hide1_firstmore', 'show1_lastmore' and 'show1_firstless',
403 this number determines how often the 'less favoured' situation
404 should arise. It will occur 1 in every N trials, where N is the
405 value of the variable.
407 For example, with the hide1_firstmore card type, the first piece
408 of clozed text should be hidden more often than the other
409 pieces. If this variable is set to 4 (default), the first item
410 will only be shown 25% of the time (1 in 4 trials). Similarly for
411 show1_lastmore, the last item will be shown 75% of the time, and
412 for show1_firstless, the first item would only be shown 25% of the
413 time.
415 If the value of this variable is NIL, then weighting is disabled, and
416 all weighted card types are treated as their unweighted equivalents."
417 :group 'org-drill
418 :type '(choice integer (const nil)))
421 (defcustom org-drill-cram-hours
423 "When in cram mode, items are considered due for review if
424 they were reviewed at least this many hours ago."
425 :group 'org-drill
426 :type 'integer)
429 ;;; NEW items have never been presented in a drill session before.
430 ;;; MATURE items HAVE been presented at least once before.
431 ;;; - YOUNG mature items were scheduled no more than
432 ;;; ORG-DRILL-DAYS-BEFORE-OLD days after their last
433 ;;; repetition. These items will have been learned 'recently' and will have a
434 ;;; low repetition count.
435 ;;; - OLD mature items have intervals greater than
436 ;;; ORG-DRILL-DAYS-BEFORE-OLD.
437 ;;; - OVERDUE items are past their scheduled review date by more than
438 ;;; LAST-INTERVAL * (ORG-DRILL-OVERDUE-INTERVAL-FACTOR - 1) days,
439 ;;; regardless of young/old status.
442 (defcustom org-drill-days-before-old
444 "When an item's inter-repetition interval rises above this value in days,
445 it is no longer considered a 'young' (recently learned) item."
446 :group 'org-drill
447 :type 'integer)
450 (defcustom org-drill-overdue-interval-factor
452 "An item is considered overdue if its scheduled review date is
453 more than (ORG-DRILL-OVERDUE-INTERVAL-FACTOR - 1) * LAST-INTERVAL
454 days in the past. For example, a value of 1.2 means an additional
455 20% of the last scheduled interval is allowed to elapse before
456 the item is overdue. A value of 1.0 means no extra time is
457 allowed at all - items are immediately considered overdue if
458 there is even one day's delay in reviewing them. This variable
459 should never be less than 1.0."
460 :group 'org-drill
461 :type 'float)
464 (defcustom org-drill-learn-fraction
466 "Fraction between 0 and 1 that governs how quickly the spaces
467 between successive repetitions increase, for all items. The
468 default value is 0.5. Higher values make spaces increase more
469 quickly with each successful repetition. You should only change
470 this in small increments (for example 0.05-0.1) as it has an
471 exponential effect on inter-repetition spacing."
472 :group 'org-drill
473 :type 'float)
476 (defvar drill-answer nil
477 "Global variable that can be bound to a correct answer when an
478 item is being presented. If this variable is non-nil, the default
479 presentation function will show its value instead of the default
480 behaviour of revealing the contents of the drilled item.
482 This variable is useful for card types that compute their answers
483 -- for example, a card type that asks the student to translate a
484 random number to another language. ")
487 (defvar *org-drill-session-qualities* nil)
488 (defvar *org-drill-start-time* 0)
489 (defvar *org-drill-new-entries* nil)
490 (defvar *org-drill-dormant-entry-count* 0)
491 (defvar *org-drill-due-entry-count* 0)
492 (defvar *org-drill-overdue-entry-count* 0)
493 (defvar *org-drill-due-tomorrow-count* 0)
494 (defvar *org-drill-overdue-entries* nil
495 "List of markers for items that are considered 'overdue', based on
496 the value of ORG-DRILL-OVERDUE-INTERVAL-FACTOR.")
497 (defvar *org-drill-young-mature-entries* nil
498 "List of markers for mature entries whose last inter-repetition
499 interval was <= ORG-DRILL-DAYS-BEFORE-OLD days.")
500 (defvar *org-drill-old-mature-entries* nil
501 "List of markers for mature entries whose last inter-repetition
502 interval was greater than ORG-DRILL-DAYS-BEFORE-OLD days.")
503 (defvar *org-drill-failed-entries* nil)
504 (defvar *org-drill-again-entries* nil)
505 (defvar *org-drill-done-entries* nil)
506 (defvar *org-drill-current-item* nil
507 "Set to the marker for the item currently being tested.")
508 (defvar *org-drill-cram-mode* nil
509 "Are we in 'cram mode', where all items are considered due
510 for review unless they were already reviewed in the recent past?")
511 (defvar org-drill-scheduling-properties
512 '("LEARN_DATA" "DRILL_LAST_INTERVAL" "DRILL_REPEATS_SINCE_FAIL"
513 "DRILL_TOTAL_REPEATS" "DRILL_FAILURE_COUNT" "DRILL_AVERAGE_QUALITY"
514 "DRILL_EASE" "DRILL_LAST_QUALITY" "DRILL_LAST_REVIEWED"))
515 (defvar org-drill--lapse-very-overdue-entries-p nil
516 "If non-nil, entries more than 90 days overdue are regarded as 'lapsed'.
517 This means that when the item is eventually re-tested it will be
518 treated as 'failed' (quality 2) for rescheduling purposes,
519 regardless of whether the test was successful.")
522 ;;; Make the above settings safe as file-local variables.
525 (put 'org-drill-question-tag 'safe-local-variable 'stringp)
526 (put 'org-drill-maximum-items-per-session 'safe-local-variable
527 '(lambda (val) (or (integerp val) (null val))))
528 (put 'org-drill-maximum-duration 'safe-local-variable
529 '(lambda (val) (or (integerp val) (null val))))
530 (put 'org-drill-failure-quality 'safe-local-variable 'integerp)
531 (put 'org-drill-forgetting-index 'safe-local-variable 'integerp)
532 (put 'org-drill-leech-failure-threshold 'safe-local-variable 'integerp)
533 (put 'org-drill-leech-method 'safe-local-variable
534 '(lambda (val) (memq val '(nil skip warn))))
535 (put 'org-drill-use-visible-cloze-face-p 'safe-local-variable 'booleanp)
536 (put 'org-drill-hide-item-headings-p 'safe-local-variable 'booleanp)
537 (put 'org-drill-spaced-repetition-algorithm 'safe-local-variable
538 '(lambda (val) (memq val '(simple8 sm5 sm2))))
539 (put 'org-drill-sm5-initial-interval 'safe-local-variable 'floatp)
540 (put 'org-drill-add-random-noise-to-intervals-p 'safe-local-variable 'booleanp)
541 (put 'org-drill-adjust-intervals-for-early-and-late-repetitions-p
542 'safe-local-variable 'booleanp)
543 (put 'org-drill-cram-hours 'safe-local-variable 'integerp)
544 (put 'org-drill-learn-fraction 'safe-local-variable 'floatp)
545 (put 'org-drill-days-before-old 'safe-local-variable 'integerp)
546 (put 'org-drill-overdue-interval-factor 'safe-local-variable 'floatp)
547 (put 'org-drill-scope 'safe-local-variable
548 '(lambda (val) (or (symbolp val) (listp val))))
549 (put 'org-drill-match 'safe-local-variable
550 '(lambda (val) (or (stringp val) (null val))))
551 (put 'org-drill-save-buffers-after-drill-sessions-p 'safe-local-variable 'booleanp)
552 (put 'org-drill-cloze-text-weight 'safe-local-variable
553 '(lambda (val) (or (null val) (integerp val))))
554 (put 'org-drill-left-cloze-delimiter 'safe-local-variable 'stringp)
555 (put 'org-drill-right-cloze-delimiter 'safe-local-variable 'stringp)
558 ;;;; Utilities ================================================================
561 (defun free-marker (m)
562 (set-marker m nil))
565 (defmacro pop-random (place)
566 (let ((idx (gensym)))
567 `(if (null ,place)
569 (let ((,idx (random* (length ,place))))
570 (prog1 (nth ,idx ,place)
571 (setq ,place (append (subseq ,place 0 ,idx)
572 (subseq ,place (1+ ,idx)))))))))
575 (defmacro push-end (val place)
576 "Add VAL to the end of the sequence stored in PLACE. Return the new
577 value."
578 `(setq ,place (append ,place (list ,val))))
581 (defun shuffle-list (list)
582 "Randomly permute the elements of LIST (all permutations equally likely)."
583 ;; Adapted from 'shuffle-vector' in cookie1.el
584 (let ((i 0)
586 temp
587 (len (length list)))
588 (while (< i len)
589 (setq j (+ i (random* (- len i))))
590 (setq temp (nth i list))
591 (setf (nth i list) (nth j list))
592 (setf (nth j list) temp)
593 (setq i (1+ i))))
594 list)
597 (defun round-float (floatnum fix)
598 "Round the floating point number FLOATNUM to FIX decimal places.
599 Example: (round-float 3.56755765 3) -> 3.568"
600 (let ((n (expt 10 fix)))
601 (/ (float (round (* floatnum n))) n)))
604 (defun command-keybinding-to-string (cmd)
605 "Return a human-readable description of the key/keys to which the command
606 CMD is bound, or nil if it is not bound to a key."
607 (let ((key (where-is-internal cmd overriding-local-map t)))
608 (if key (key-description key))))
611 (defun time-to-inactive-org-timestamp (time)
612 (format-time-string
613 (concat "[" (substring (cdr org-time-stamp-formats) 1 -1) "]")
614 time))
617 (defun time-to-active-org-timestamp (time)
618 (format-time-string
619 (concat "<" (substring (cdr org-time-stamp-formats) 1 -1) ">")
620 time))
623 (defun org-map-drill-entries (func &optional scope drill-match &rest skip)
624 "Like `org-map-entries', but only drill entries are processed."
625 (let ((org-drill-scope (or scope org-drill-scope))
626 (org-drill-match (or drill-match org-drill-match)))
627 (apply 'org-map-entries func
628 (concat "+" org-drill-question-tag
629 (if (and (stringp org-drill-match)
630 (not (member '(?+ ?- ?|) (elt org-drill-match 0))))
631 "+" "")
632 (or org-drill-match ""))
633 (case org-drill-scope
634 (file nil)
635 (file-no-restriction 'file)
636 (directory
637 (directory-files (file-name-directory (buffer-file-name))
638 t "\\.org$"))
639 (t org-drill-scope))
640 skip)))
643 (defmacro with-hidden-cloze-text (&rest body)
644 `(progn
645 (org-drill-hide-clozed-text)
646 (unwind-protect
647 (progn
648 ,@body)
649 (org-drill-unhide-clozed-text))))
652 (defmacro with-hidden-cloze-hints (&rest body)
653 `(progn
654 (org-drill-hide-cloze-hints)
655 (unwind-protect
656 (progn
657 ,@body)
658 (org-drill-unhide-text))))
661 (defmacro with-hidden-comments (&rest body)
662 `(progn
663 (if org-drill-hide-item-headings-p
664 (org-drill-hide-heading-at-point))
665 (org-drill-hide-comments)
666 (unwind-protect
667 (progn
668 ,@body)
669 (org-drill-unhide-text))))
672 (defun org-drill-days-since-last-review ()
673 "Nil means a last review date has not yet been stored for
674 the item.
675 Zero means it was reviewed today.
676 A positive number means it was reviewed that many days ago.
677 A negative number means the date of last review is in the future --
678 this should never happen."
679 (let ((datestr (org-entry-get (point) "DRILL_LAST_REVIEWED")))
680 (when datestr
681 (- (time-to-days (current-time))
682 (time-to-days (apply 'encode-time
683 (org-parse-time-string datestr)))))))
686 (defun org-drill-hours-since-last-review ()
687 "Like `org-drill-days-since-last-review', but return value is
688 in hours rather than days."
689 (let ((datestr (org-entry-get (point) "DRILL_LAST_REVIEWED")))
690 (when datestr
691 (floor
692 (/ (- (time-to-seconds (current-time))
693 (time-to-seconds (apply 'encode-time
694 (org-parse-time-string datestr))))
695 (* 60 60))))))
698 (defun org-drill-entry-p (&optional marker)
699 "Is MARKER, or the point, in a 'drill item'? This will return nil if
700 the point is inside a subheading of a drill item -- to handle that
701 situation use `org-part-of-drill-entry-p'."
702 (save-excursion
703 (when marker
704 (org-drill-goto-entry marker))
705 (member org-drill-question-tag (org-get-local-tags))))
708 (defun org-drill-goto-entry (marker)
709 (switch-to-buffer (marker-buffer marker))
710 (goto-char marker))
713 (defun org-part-of-drill-entry-p ()
714 "Is the current entry either the main heading of a 'drill item',
715 or a subheading within a drill item?"
716 (or (org-drill-entry-p)
717 ;; Does this heading INHERIT the drill tag
718 (member org-drill-question-tag (org-get-tags-at))))
721 (defun org-drill-goto-drill-entry-heading ()
722 "Move the point to the heading which holds the :drill: tag for this
723 drill entry."
724 (unless (org-at-heading-p)
725 (org-back-to-heading))
726 (unless (org-part-of-drill-entry-p)
727 (error "Point is not inside a drill entry"))
728 (while (not (org-drill-entry-p))
729 (unless (org-up-heading-safe)
730 (error "Cannot find a parent heading that is marked as a drill entry"))))
734 (defun org-drill-entry-leech-p ()
735 "Is the current entry a 'leech item'?"
736 (and (org-drill-entry-p)
737 (member "leech" (org-get-local-tags))))
740 ;; (defun org-drill-entry-due-p ()
741 ;; (cond
742 ;; (*org-drill-cram-mode*
743 ;; (let ((hours (org-drill-hours-since-last-review)))
744 ;; (and (org-drill-entry-p)
745 ;; (or (null hours)
746 ;; (>= hours org-drill-cram-hours)))))
747 ;; (t
748 ;; (let ((item-time (org-get-scheduled-time (point))))
749 ;; (and (org-drill-entry-p)
750 ;; (or (not (eql 'skip org-drill-leech-method))
751 ;; (not (org-drill-entry-leech-p)))
752 ;; (or (null item-time) ; not scheduled
753 ;; (not (minusp ; scheduled for today/in past
754 ;; (- (time-to-days (current-time))
755 ;; (time-to-days item-time))))))))))
758 (defun org-drill-entry-days-overdue ()
759 "Returns:
760 - NIL if the item is not to be regarded as scheduled for review at all.
761 This is the case if it is not a drill item, or if it is a leech item
762 that we wish to skip, or if we are in cram mode and have already reviewed
763 the item within the last few hours.
764 - 0 if the item is new, or if it scheduled for review today.
765 - A negative integer - item is scheduled that many days in the future.
766 - A positive integer - item is scheduled that many days in the past."
767 (cond
768 (*org-drill-cram-mode*
769 (let ((hours (org-drill-hours-since-last-review)))
770 (and (org-drill-entry-p)
771 (or (null hours)
772 (>= hours org-drill-cram-hours))
773 0)))
775 (let ((item-time (org-get-scheduled-time (point))))
776 (cond
777 ((or (not (org-drill-entry-p))
778 (and (eql 'skip org-drill-leech-method)
779 (org-drill-entry-leech-p)))
780 nil)
781 ((null item-time) ; not scheduled -> due now
784 (- (time-to-days (current-time))
785 (time-to-days item-time))))))))
788 (defun org-drill-entry-overdue-p (&optional days-overdue last-interval)
789 "Returns true if entry that is scheduled DAYS-OVERDUE dasy in the past,
790 and whose last inter-repetition interval was LAST-INTERVAL, should be
791 considered 'overdue'. If the arguments are not given they are extracted
792 from the entry at point."
793 (unless days-overdue
794 (setq days-overdue (org-drill-entry-days-overdue)))
795 (unless last-interval
796 (setq last-interval (org-drill-entry-last-interval 1)))
797 (and (numberp days-overdue)
798 (> days-overdue 1) ; enforce a sane minimum 'overdue' gap
799 ;;(> due org-drill-days-before-overdue)
800 (> (/ (+ days-overdue last-interval 1.0) last-interval)
801 org-drill-overdue-interval-factor)))
805 (defun org-drill-entry-due-p ()
806 (let ((due (org-drill-entry-days-overdue)))
807 (and (not (null due))
808 (not (minusp due)))))
811 (defun org-drill-entry-new-p ()
812 (and (org-drill-entry-p)
813 (let ((item-time (org-get-scheduled-time (point))))
814 (null item-time))))
817 (defun org-drill-entry-last-quality (&optional default)
818 (let ((quality (org-entry-get (point) "DRILL_LAST_QUALITY")))
819 (if quality
820 (string-to-number quality)
821 default)))
824 (defun org-drill-entry-failure-count ()
825 (let ((quality (org-entry-get (point) "DRILL_FAILURE_COUNT")))
826 (if quality
827 (string-to-number quality)
828 0)))
831 (defun org-drill-entry-average-quality (&optional default)
832 (let ((val (org-entry-get (point) "DRILL_AVERAGE_QUALITY")))
833 (if val
834 (string-to-number val)
835 (or default nil))))
837 (defun org-drill-entry-last-interval (&optional default)
838 (let ((val (org-entry-get (point) "DRILL_LAST_INTERVAL")))
839 (if val
840 (string-to-number val)
841 (or default 0))))
843 (defun org-drill-entry-repeats-since-fail (&optional default)
844 (let ((val (org-entry-get (point) "DRILL_REPEATS_SINCE_FAIL")))
845 (if val
846 (string-to-number val)
847 (or default 0))))
849 (defun org-drill-entry-total-repeats (&optional default)
850 (let ((val (org-entry-get (point) "DRILL_TOTAL_REPEATS")))
851 (if val
852 (string-to-number val)
853 (or default 0))))
855 (defun org-drill-entry-ease (&optional default)
856 (let ((val (org-entry-get (point) "DRILL_EASE")))
857 (if val
858 (string-to-number val)
859 default)))
862 ;;; From http://www.supermemo.com/english/ol/sm5.htm
863 (defun org-drill-random-dispersal-factor ()
864 "Returns a random number between 0.5 and 1.5."
865 (let ((a 0.047)
866 (b 0.092)
867 (p (- (random* 1.0) 0.5)))
868 (cl-flet ((sign (n)
869 (cond ((zerop n) 0)
870 ((plusp n) 1)
871 (t -1))))
872 (/ (+ 100 (* (* (/ -1 b) (log (- 1 (* (/ b a ) (abs p)))))
873 (sign p)))
874 100.0))))
876 (defun pseudonormal (mean variation)
877 "Random numbers in a pseudo-normal distribution with mean MEAN, range
878 MEAN-VARIATION to MEAN+VARIATION"
879 (+ (random* variation)
880 (random* variation)
881 (- variation)
882 mean))
885 (defun org-drill-early-interval-factor (optimal-factor
886 optimal-interval
887 days-ahead)
888 "Arguments:
889 - OPTIMAL-FACTOR: interval-factor if the item had been tested
890 exactly when it was supposed to be.
891 - OPTIMAL-INTERVAL: interval for next repetition (days) if the item had been
892 tested exactly when it was supposed to be.
893 - DAYS-AHEAD: how many days ahead of time the item was reviewed.
895 Returns an adjusted optimal factor which should be used to
896 calculate the next interval, instead of the optimal factor found
897 in the matrix."
898 (let ((delta-ofmax (* (1- optimal-factor)
899 (/ (+ optimal-interval
900 (* 0.6 optimal-interval) -1) (1- optimal-interval)))))
901 (- optimal-factor
902 (* delta-ofmax (/ days-ahead (+ days-ahead (* 0.6 optimal-interval)))))))
905 (defun org-drill-get-item-data ()
906 "Returns a list of 6 items, containing all the stored recall
907 data for the item at point:
908 - LAST-INTERVAL is the interval in days that was used to schedule the item's
909 current review date.
910 - REPEATS is the number of items the item has been successfully recalled without
911 without any failures. It is reset to 0 upon failure to recall the item.
912 - FAILURES is the total number of times the user has failed to recall the item.
913 - TOTAL-REPEATS includes both successful and unsuccessful repetitions.
914 - AVERAGE-QUALITY is the mean quality of recall of the item over
915 all its repetitions, successful and unsuccessful.
916 - EASE is a number reflecting how easy the item is to learn. Higher is easier.
918 (let ((learn-str (org-entry-get (point) "LEARN_DATA"))
919 (repeats (org-drill-entry-total-repeats :missing)))
920 (cond
921 (learn-str
922 (let ((learn-data (or (and learn-str
923 (read learn-str))
924 (copy-list initial-repetition-state))))
925 (list (nth 0 learn-data) ; last interval
926 (nth 1 learn-data) ; repetitions
927 (org-drill-entry-failure-count)
928 (nth 1 learn-data)
929 (org-drill-entry-last-quality)
930 (nth 2 learn-data) ; EF
932 ((not (eql :missing repeats))
933 (list (org-drill-entry-last-interval)
934 (org-drill-entry-repeats-since-fail)
935 (org-drill-entry-failure-count)
936 (org-drill-entry-total-repeats)
937 (org-drill-entry-average-quality)
938 (org-drill-entry-ease)))
939 (t ; virgin item
940 (list 0 0 0 0 nil nil)))))
943 (defun org-drill-store-item-data (last-interval repeats failures
944 total-repeats meanq
945 ease)
946 "Stores the given data in the item at point."
947 (org-entry-delete (point) "LEARN_DATA")
948 (org-set-property "DRILL_LAST_INTERVAL"
949 (number-to-string (round-float last-interval 4)))
950 (org-set-property "DRILL_REPEATS_SINCE_FAIL" (number-to-string repeats))
951 (org-set-property "DRILL_TOTAL_REPEATS" (number-to-string total-repeats))
952 (org-set-property "DRILL_FAILURE_COUNT" (number-to-string failures))
953 (org-set-property "DRILL_AVERAGE_QUALITY"
954 (number-to-string (round-float meanq 3)))
955 (org-set-property "DRILL_EASE"
956 (number-to-string (round-float ease 3))))
960 ;;; SM2 Algorithm =============================================================
963 (defun determine-next-interval-sm2 (last-interval n ef quality
964 failures meanq total-repeats)
965 "Arguments:
966 - LAST-INTERVAL -- the number of days since the item was last reviewed.
967 - REPEATS -- the number of times the item has been successfully reviewed
968 - EF -- the 'easiness factor'
969 - QUALITY -- 0 to 5
971 Returns a list: (INTERVAL REPEATS EF FAILURES MEAN TOTAL-REPEATS OFMATRIX), where:
972 - INTERVAL is the number of days until the item should next be reviewed
973 - REPEATS is incremented by 1.
974 - EF is modified based on the recall quality for the item.
975 - OF-MATRIX is not modified."
976 (assert (> n 0))
977 (assert (and (>= quality 0) (<= quality 5)))
978 (if (<= quality org-drill-failure-quality)
979 ;; When an item is failed, its interval is reset to 0,
980 ;; but its EF is unchanged
981 (list -1 1 ef (1+ failures) meanq (1+ total-repeats)
982 org-drill-optimal-factor-matrix)
983 ;; else:
984 (let* ((next-ef (modify-e-factor ef quality))
985 (interval
986 (cond
987 ((<= n 1) 1)
988 ((= n 2)
989 (cond
990 (org-drill-add-random-noise-to-intervals-p
991 (case quality
992 (5 6)
993 (4 4)
994 (3 3)
995 (2 1)
996 (t -1)))
997 (t 6)))
998 (t (* last-interval next-ef)))))
999 (list (if org-drill-add-random-noise-to-intervals-p
1000 (+ last-interval (* (- interval last-interval)
1001 (org-drill-random-dispersal-factor)))
1002 interval)
1003 (1+ n)
1004 next-ef
1005 failures meanq (1+ total-repeats)
1006 org-drill-optimal-factor-matrix))))
1009 ;;; SM5 Algorithm =============================================================
1013 (defun initial-optimal-factor-sm5 (n ef)
1014 (if (= 1 n)
1015 org-drill-sm5-initial-interval
1016 ef))
1018 (defun get-optimal-factor-sm5 (n ef of-matrix)
1019 (let ((factors (assoc n of-matrix)))
1020 (or (and factors
1021 (let ((ef-of (assoc ef (cdr factors))))
1022 (and ef-of (cdr ef-of))))
1023 (initial-optimal-factor-sm5 n ef))))
1026 (defun inter-repetition-interval-sm5 (last-interval n ef &optional of-matrix)
1027 (let ((of (get-optimal-factor-sm5 n ef (or of-matrix
1028 org-drill-optimal-factor-matrix))))
1029 (if (= 1 n)
1031 (* of last-interval))))
1034 (defun determine-next-interval-sm5 (last-interval n ef quality
1035 failures meanq total-repeats
1036 of-matrix &optional delta-days)
1037 (if (zerop n) (setq n 1))
1038 (if (null ef) (setq ef 2.5))
1039 (assert (> n 0))
1040 (assert (and (>= quality 0) (<= quality 5)))
1041 (unless of-matrix
1042 (setq of-matrix org-drill-optimal-factor-matrix))
1043 (setq of-matrix (cl-copy-tree of-matrix))
1045 (setq meanq (if meanq
1046 (/ (+ quality (* meanq total-repeats 1.0))
1047 (1+ total-repeats))
1048 quality))
1050 (let ((next-ef (modify-e-factor ef quality))
1051 (old-ef ef)
1052 (new-of (modify-of (get-optimal-factor-sm5 n ef of-matrix)
1053 quality org-drill-learn-fraction))
1054 (interval nil))
1055 (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
1056 delta-days (minusp delta-days))
1057 (setq new-of (org-drill-early-interval-factor
1058 (get-optimal-factor-sm5 n ef of-matrix)
1059 (inter-repetition-interval-sm5
1060 last-interval n ef of-matrix)
1061 delta-days)))
1063 (setq of-matrix
1064 (set-optimal-factor n next-ef of-matrix
1065 (round-float new-of 3))) ; round OF to 3 d.p.
1067 (setq ef next-ef)
1069 (cond
1070 ;; "Failed" -- reset repetitions to 0,
1071 ((<= quality org-drill-failure-quality)
1072 (list -1 1 old-ef (1+ failures) meanq (1+ total-repeats)
1073 of-matrix)) ; Not clear if OF matrix is supposed to be
1074 ; preserved
1075 ;; For a zero-based quality of 4 or 5, don't repeat
1076 ;; ((and (>= quality 4)
1077 ;; (not org-learn-always-reschedule))
1078 ;; (list 0 (1+ n) ef failures meanq
1079 ;; (1+ total-repeats) of-matrix)) ; 0 interval = unschedule
1081 (setq interval (inter-repetition-interval-sm5
1082 last-interval n ef of-matrix))
1083 (if org-drill-add-random-noise-to-intervals-p
1084 (setq interval (* interval (org-drill-random-dispersal-factor))))
1085 (list interval
1086 (1+ n)
1088 failures
1089 meanq
1090 (1+ total-repeats)
1091 of-matrix)))))
1094 ;;; Simple8 Algorithm =========================================================
1097 (defun org-drill-simple8-first-interval (failures)
1098 "Arguments:
1099 - FAILURES: integer >= 0. The total number of times the item has
1100 been forgotten, ever.
1102 Returns the optimal FIRST interval for an item which has previously been
1103 forgotten on FAILURES occasions."
1104 (* 2.4849 (exp (* -0.057 failures))))
1107 (defun org-drill-simple8-interval-factor (ease repetition)
1108 "Arguments:
1109 - EASE: floating point number >= 1.2. Corresponds to `AF' in SM8 algorithm.
1110 - REPETITION: the number of times the item has been tested.
1111 1 is the first repetition (ie the second trial).
1112 Returns:
1113 The factor by which the last interval should be
1114 multiplied to give the next interval. Corresponds to `RF' or `OF'."
1115 (+ 1.2 (* (- ease 1.2) (expt org-drill-learn-fraction (log repetition 2)))))
1118 (defun org-drill-simple8-quality->ease (quality)
1119 "Returns the ease (`AF' in the SM8 algorithm) which corresponds
1120 to a mean item quality of QUALITY."
1121 (+ (* 0.0542 (expt quality 4))
1122 (* -0.4848 (expt quality 3))
1123 (* 1.4916 (expt quality 2))
1124 (* -1.2403 quality)
1125 1.4515))
1128 (defun determine-next-interval-simple8 (last-interval repeats quality
1129 failures meanq totaln
1130 &optional delta-days)
1131 "Arguments:
1132 - LAST-INTERVAL -- the number of days since the item was last reviewed.
1133 - REPEATS -- the number of times the item has been successfully reviewed
1134 - EASE -- the 'easiness factor'
1135 - QUALITY -- 0 to 5
1136 - DELTA-DAYS -- how many days overdue was the item when it was reviewed.
1137 0 = reviewed on the scheduled day. +N = N days overdue.
1138 -N = reviewed N days early.
1140 Returns the new item data, as a list of 6 values:
1141 - NEXT-INTERVAL
1142 - REPEATS
1143 - EASE
1144 - FAILURES
1145 - AVERAGE-QUALITY
1146 - TOTAL-REPEATS.
1147 See the documentation for `org-drill-get-item-data' for a description of these."
1148 (assert (>= repeats 0))
1149 (assert (and (>= quality 0) (<= quality 5)))
1150 (assert (or (null meanq) (and (>= meanq 0) (<= meanq 5))))
1151 (let ((next-interval nil))
1152 (setf meanq (if meanq
1153 (/ (+ quality (* meanq totaln 1.0)) (1+ totaln))
1154 quality))
1155 (cond
1156 ((<= quality org-drill-failure-quality)
1157 (incf failures)
1158 (setf repeats 0
1159 next-interval -1))
1160 ((or (zerop repeats)
1161 (zerop last-interval))
1162 (setf next-interval (org-drill-simple8-first-interval failures))
1163 (incf repeats)
1164 (incf totaln))
1166 (let* ((use-n
1167 (if (and
1168 org-drill-adjust-intervals-for-early-and-late-repetitions-p
1169 (numberp delta-days) (plusp delta-days)
1170 (plusp last-interval))
1171 (+ repeats (min 1 (/ delta-days last-interval 1.0)))
1172 repeats))
1173 (factor (org-drill-simple8-interval-factor
1174 (org-drill-simple8-quality->ease meanq) use-n))
1175 (next-int (* last-interval factor)))
1176 (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
1177 (numberp delta-days) (minusp delta-days))
1178 ;; The item was reviewed earlier than scheduled.
1179 (setf factor (org-drill-early-interval-factor
1180 factor next-int (abs delta-days))
1181 next-int (* last-interval factor)))
1182 (setf next-interval next-int)
1183 (incf repeats)
1184 (incf totaln))))
1185 (list
1186 (if (and org-drill-add-random-noise-to-intervals-p
1187 (plusp next-interval))
1188 (* next-interval (org-drill-random-dispersal-factor))
1189 next-interval)
1190 repeats
1191 (org-drill-simple8-quality->ease meanq)
1192 failures
1193 meanq
1194 totaln
1200 ;;; Essentially copied from `org-learn.el', but modified to
1201 ;;; optionally call the SM2 or simple8 functions.
1202 (defun org-drill-smart-reschedule (quality &optional days-ahead)
1203 "If DAYS-AHEAD is supplied it must be a positive integer. The
1204 item will be scheduled exactly this many days into the future."
1205 (let ((delta-days (- (time-to-days (current-time))
1206 (time-to-days (or (org-get-scheduled-time (point))
1207 (current-time)))))
1208 (ofmatrix org-drill-optimal-factor-matrix)
1209 ;; Entries can have weights, 1 by default. Intervals are divided by the
1210 ;; item's weight, so an item with a weight of 2 will have all intervals
1211 ;; halved, meaning you will end up reviewing it twice as often.
1212 ;; Useful for entries which randomly present any of several facts.
1213 (weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
1214 (if (stringp weight)
1215 (setq weight (read weight)))
1216 (destructuring-bind (last-interval repetitions failures
1217 total-repeats meanq ease)
1218 (org-drill-get-item-data)
1219 (destructuring-bind (next-interval repetitions ease
1220 failures meanq total-repeats
1221 &optional new-ofmatrix)
1222 (case org-drill-spaced-repetition-algorithm
1223 (sm5 (determine-next-interval-sm5 last-interval repetitions
1224 ease quality failures
1225 meanq total-repeats ofmatrix))
1226 (sm2 (determine-next-interval-sm2 last-interval repetitions
1227 ease quality failures
1228 meanq total-repeats))
1229 (simple8 (determine-next-interval-simple8 last-interval repetitions
1230 quality failures meanq
1231 total-repeats
1232 delta-days)))
1233 (if (numberp days-ahead)
1234 (setq next-interval days-ahead))
1236 (if (and (null days-ahead)
1237 (numberp weight) (plusp weight)
1238 (not (minusp next-interval)))
1239 (setq next-interval
1240 (max 1.0 (+ last-interval
1241 (/ (- next-interval last-interval) weight)))))
1243 (org-drill-store-item-data next-interval repetitions failures
1244 total-repeats meanq ease)
1246 (if (eql 'sm5 org-drill-spaced-repetition-algorithm)
1247 (setq org-drill-optimal-factor-matrix new-ofmatrix))
1249 (cond
1250 ((= 0 days-ahead)
1251 (org-schedule '(4)))
1252 ((minusp days-ahead)
1253 (org-schedule nil (current-time)))
1255 (org-schedule nil (time-add (current-time)
1256 (days-to-time
1257 (round next-interval))))))))))
1260 (defun org-drill-hypothetical-next-review-date (quality)
1261 "Returns an integer representing the number of days into the future
1262 that the current item would be scheduled, based on a recall quality
1263 of QUALITY."
1264 (let ((weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
1265 (destructuring-bind (last-interval repetitions failures
1266 total-repeats meanq ease)
1267 (org-drill-get-item-data)
1268 (if (stringp weight)
1269 (setq weight (read weight)))
1270 (destructuring-bind (next-interval repetitions ease
1271 failures meanq total-repeats
1272 &optional ofmatrix)
1273 (case org-drill-spaced-repetition-algorithm
1274 (sm5 (determine-next-interval-sm5 last-interval repetitions
1275 ease quality failures
1276 meanq total-repeats
1277 org-drill-optimal-factor-matrix))
1278 (sm2 (determine-next-interval-sm2 last-interval repetitions
1279 ease quality failures
1280 meanq total-repeats))
1281 (simple8 (determine-next-interval-simple8 last-interval repetitions
1282 quality failures meanq
1283 total-repeats)))
1284 (cond
1285 ((not (plusp next-interval))
1287 ((and (numberp weight) (plusp weight))
1288 (+ last-interval
1289 (max 1.0 (/ (- next-interval last-interval) weight))))
1291 next-interval))))))
1294 (defun org-drill-hypothetical-next-review-dates ()
1295 (let ((intervals nil))
1296 (dotimes (q 6)
1297 (push (max (or (car intervals) 0)
1298 (org-drill-hypothetical-next-review-date q))
1299 intervals))
1300 (reverse intervals)))
1303 (defun org-drill-reschedule ()
1304 "Returns quality rating (0-5), or nil if the user quit."
1305 (let ((ch nil)
1306 (input nil)
1307 (next-review-dates (org-drill-hypothetical-next-review-dates)))
1308 (save-excursion
1309 (while (not (memq ch '(?q ?e ?0 ?1 ?2 ?3 ?4 ?5)))
1310 (setq input (read-key-sequence
1311 (if (eq ch ??)
1312 (format "0-2 Means you have forgotten the item.
1313 3-5 Means you have remembered the item.
1315 0 - Completely forgot.
1316 1 - Even after seeing the answer, it still took a bit to sink in.
1317 2 - After seeing the answer, you remembered it.
1318 3 - It took you awhile, but you finally remembered. (+%s days)
1319 4 - After a little bit of thought you remembered. (+%s days)
1320 5 - You remembered the item really easily. (+%s days)
1322 How well did you do? (0-5, ?=help, e=edit, t=tags, q=quit)"
1323 (round (nth 3 next-review-dates))
1324 (round (nth 4 next-review-dates))
1325 (round (nth 5 next-review-dates)))
1326 "How well did you do? (0-5, ?=help, e=edit, t=tags, q=quit)")))
1327 (cond
1328 ((stringp input)
1329 (setq ch (elt input 0)))
1330 ((and (vectorp input) (symbolp (elt input 0)))
1331 (case (elt input 0)
1332 (up (ignore-errors (forward-line -1)))
1333 (down (ignore-errors (forward-line 1)))
1334 (left (ignore-errors (backward-char)))
1335 (right (ignore-errors (forward-char)))
1336 (prior (ignore-errors (scroll-down))) ; pgup
1337 (next (ignore-errors (scroll-up))))) ; pgdn
1338 ((and (vectorp input) (listp (elt input 0))
1339 (eventp (elt input 0)))
1340 (case (car (elt input 0))
1341 (wheel-up (ignore-errors (mwheel-scroll (elt input 0))))
1342 (wheel-down (ignore-errors (mwheel-scroll (elt input 0)))))))
1343 (if (eql ch ?t)
1344 (org-set-tags-command))))
1345 (cond
1346 ((and (>= ch ?0) (<= ch ?5))
1347 (let ((quality (- ch ?0))
1348 (failures (org-drill-entry-failure-count)))
1349 (unless *org-drill-cram-mode*
1350 (save-excursion
1351 (let ((quality (if (org-drill--entry-lapsed-p) 2 quality)))
1352 (org-drill-smart-reschedule quality
1353 (nth quality next-review-dates))))
1354 (push quality *org-drill-session-qualities*)
1355 (cond
1356 ((<= quality org-drill-failure-quality)
1357 (when org-drill-leech-failure-threshold
1358 ;;(setq failures (if failures (string-to-number failures) 0))
1359 ;; (org-set-property "DRILL_FAILURE_COUNT"
1360 ;; (format "%d" (1+ failures)))
1361 (if (> (1+ failures) org-drill-leech-failure-threshold)
1362 (org-toggle-tag "leech" 'on))))
1364 (let ((scheduled-time (org-get-scheduled-time (point))))
1365 (when scheduled-time
1366 (message "Next review in %d days"
1367 (- (time-to-days scheduled-time)
1368 (time-to-days (current-time))))
1369 (sit-for 0.5)))))
1370 (org-set-property "DRILL_LAST_QUALITY" (format "%d" quality))
1371 (org-set-property "DRILL_LAST_REVIEWED"
1372 (time-to-inactive-org-timestamp (current-time))))
1373 quality))
1374 ((= ch ?e)
1375 'edit)
1377 nil))))
1380 ;; (defun org-drill-hide-all-subheadings-except (heading-list)
1381 ;; "Returns a list containing the position of each immediate subheading of
1382 ;; the current topic."
1383 ;; (let ((drill-entry-level (org-current-level))
1384 ;; (drill-sections nil)
1385 ;; (drill-heading nil))
1386 ;; (org-show-subtree)
1387 ;; (save-excursion
1388 ;; (org-map-entries
1389 ;; (lambda ()
1390 ;; (when (and (not (outline-invisible-p))
1391 ;; (> (org-current-level) drill-entry-level))
1392 ;; (setq drill-heading (org-get-heading t))
1393 ;; (unless (and (= (org-current-level) (1+ drill-entry-level))
1394 ;; (member drill-heading heading-list))
1395 ;; (hide-subtree))
1396 ;; (push (point) drill-sections)))
1397 ;; "" 'tree))
1398 ;; (reverse drill-sections)))
1402 (defun org-drill-hide-subheadings-if (test)
1403 "TEST is a function taking no arguments. TEST will be called for each
1404 of the immediate subheadings of the current drill item, with the point
1405 on the relevant subheading. TEST should return nil if the subheading is
1406 to be revealed, non-nil if it is to be hidden.
1407 Returns a list containing the position of each immediate subheading of
1408 the current topic."
1409 (let ((drill-entry-level (org-current-level))
1410 (drill-sections nil))
1411 (org-show-subtree)
1412 (save-excursion
1413 (org-map-entries
1414 (lambda ()
1415 (when (and (not (outline-invisible-p))
1416 (> (org-current-level) drill-entry-level))
1417 (when (or (/= (org-current-level) (1+ drill-entry-level))
1418 (funcall test))
1419 (hide-subtree))
1420 (push (point) drill-sections)))
1421 "" 'tree))
1422 (reverse drill-sections)))
1425 (defun org-drill-hide-all-subheadings-except (heading-list)
1426 (org-drill-hide-subheadings-if
1427 (lambda () (let ((drill-heading (org-get-heading t)))
1428 (not (member drill-heading heading-list))))))
1431 (defun org-drill-presentation-prompt (&rest fmt-and-args)
1432 (let* ((item-start-time (current-time))
1433 (input nil)
1434 (ch nil)
1435 (last-second 0)
1436 (mature-entry-count (+ (length *org-drill-young-mature-entries*)
1437 (length *org-drill-old-mature-entries*)
1438 (length *org-drill-overdue-entries*)))
1439 (status (first (org-drill-entry-status)))
1440 (prompt
1441 (if fmt-and-args
1442 (apply 'format
1443 (first fmt-and-args)
1444 (rest fmt-and-args))
1445 (concat "Press key for answer, "
1446 "e=edit, t=tags, s=skip, q=quit."))))
1447 (setq prompt
1448 (format "%s %s %s %s %s %s"
1449 (propertize
1450 (char-to-string
1451 (cond
1452 ((eql status :failed) ?F)
1453 (*org-drill-cram-mode* ?C)
1455 (case status
1456 (:new ?N) (:young ?Y) (:old ?o) (:overdue ?!)
1457 (t ??)))))
1458 'face `(:foreground
1459 ,(case status
1460 (:new org-drill-new-count-color)
1461 ((:young :old) org-drill-mature-count-color)
1462 ((:overdue :failed) org-drill-failed-count-color)
1463 (t org-drill-done-count-color))))
1464 (propertize
1465 (number-to-string (length *org-drill-done-entries*))
1466 'face `(:foreground ,org-drill-done-count-color)
1467 'help-echo "The number of items you have reviewed this session.")
1468 (propertize
1469 (number-to-string (+ (length *org-drill-again-entries*)
1470 (length *org-drill-failed-entries*)))
1471 'face `(:foreground ,org-drill-failed-count-color)
1472 'help-echo (concat "The number of items that you failed, "
1473 "and need to review again."))
1474 (propertize
1475 (number-to-string mature-entry-count)
1476 'face `(:foreground ,org-drill-mature-count-color)
1477 'help-echo "The number of old items due for review.")
1478 (propertize
1479 (number-to-string (length *org-drill-new-entries*))
1480 'face `(:foreground ,org-drill-new-count-color)
1481 'help-echo (concat "The number of new items that you "
1482 "have never reviewed."))
1483 prompt))
1484 (if (and (eql 'warn org-drill-leech-method)
1485 (org-drill-entry-leech-p))
1486 (setq prompt (concat
1487 (propertize "!!! LEECH ITEM !!!
1488 You seem to be having a lot of trouble memorising this item.
1489 Consider reformulating the item to make it easier to remember.\n"
1490 'face '(:foreground "red"))
1491 prompt)))
1492 (while (memq ch '(nil ?t))
1493 (setq ch nil)
1494 (while (not (input-pending-p))
1495 (let ((elapsed (time-subtract (current-time) item-start-time)))
1496 (message (concat (if (>= (time-to-seconds elapsed) (* 60 60))
1497 "++:++ "
1498 (format-time-string "%M:%S " elapsed))
1499 prompt))
1500 (sit-for 1)))
1501 (setq input (read-key-sequence nil))
1502 (if (stringp input) (setq ch (elt input 0)))
1503 (if (eql ch ?t)
1504 (org-set-tags-command)))
1505 (case ch
1506 (?q nil)
1507 (?e 'edit)
1508 (?s 'skip)
1509 (otherwise t))))
1512 (defun org-pos-in-regexp (pos regexp &optional nlines)
1513 (save-excursion
1514 (goto-char pos)
1515 (org-in-regexp regexp nlines)))
1518 (defun org-drill-hide-region (beg end &optional text)
1519 "Hide the buffer region between BEG and END with an 'invisible text'
1520 visual overlay, or with the string TEXT if it is supplied."
1521 (let ((ovl (make-overlay beg end)))
1522 (overlay-put ovl 'category
1523 'org-drill-hidden-text-overlay)
1524 (overlay-put ovl 'priority 9999)
1525 (when (stringp text)
1526 (overlay-put ovl 'invisible nil)
1527 (overlay-put ovl 'face 'default)
1528 (overlay-put ovl 'display text))))
1531 (defun org-drill-hide-heading-at-point (&optional text)
1532 (unless (org-at-heading-p)
1533 (error "Point is not on a heading."))
1534 (save-excursion
1535 (let ((beg (point)))
1536 (end-of-line)
1537 (org-drill-hide-region beg (point) text))))
1540 (defun org-drill-hide-comments ()
1541 (save-excursion
1542 (while (re-search-forward "^#.*$" nil t)
1543 (org-drill-hide-region (match-beginning 0) (match-end 0)))))
1546 (defun org-drill-unhide-text ()
1547 ;; This will also unhide the item's heading.
1548 (save-excursion
1549 (dolist (ovl (overlays-in (point-min) (point-max)))
1550 (when (eql 'org-drill-hidden-text-overlay (overlay-get ovl 'category))
1551 (delete-overlay ovl)))))
1554 (defun org-drill-hide-clozed-text ()
1555 (save-excursion
1556 (while (re-search-forward org-drill-cloze-regexp nil t)
1557 ;; Don't hide:
1558 ;; - org links, partly because they might contain inline
1559 ;; images which we want to keep visible.
1560 ;; - LaTeX math fragments
1561 ;; - the contents of SRC blocks
1562 (unless (save-match-data
1563 (or (org-pos-in-regexp (match-beginning 0)
1564 org-bracket-link-regexp 1)
1565 (org-in-src-block-p)
1566 (org-inside-LaTeX-fragment-p)))
1567 (org-drill-hide-matched-cloze-text)))))
1570 (defun org-drill-hide-matched-cloze-text ()
1571 "Hide the current match with a 'cloze' visual overlay."
1572 (let ((ovl (make-overlay (match-beginning 0) (match-end 0)))
1573 (hint-sep-pos (string-match-p (regexp-quote org-drill-hint-separator)
1574 (match-string 0))))
1575 (overlay-put ovl 'category
1576 'org-drill-cloze-overlay-defaults)
1577 (overlay-put ovl 'priority 9999)
1578 (when (and hint-sep-pos
1579 (> hint-sep-pos 1))
1580 (let ((hint (substring-no-properties
1581 (match-string 0)
1582 (+ hint-sep-pos (length org-drill-hint-separator))
1583 (1- (length (match-string 0))))))
1584 (overlay-put
1585 ovl 'display
1586 ;; If hint is like `X...' then display [X...]
1587 ;; otherwise display [...X]
1588 (format (if (string-match-p (regexp-quote "...") hint) "[%s]" "[%s...]")
1589 hint))))))
1592 (defun org-drill-hide-cloze-hints ()
1593 (save-excursion
1594 (while (re-search-forward org-drill-cloze-regexp nil t)
1595 (unless (or (save-match-data
1596 (org-pos-in-regexp (match-beginning 0)
1597 org-bracket-link-regexp 1))
1598 (null (match-beginning 2))) ; hint subexpression matched
1599 (org-drill-hide-region (match-beginning 2) (match-end 2))))))
1602 (defmacro with-replaced-entry-text (text &rest body)
1603 "During the execution of BODY, the entire text of the current entry is
1604 concealed by an overlay that displays the string TEXT."
1605 `(progn
1606 (org-drill-replace-entry-text ,text)
1607 (unwind-protect
1608 (progn
1609 ,@body)
1610 (org-drill-unreplace-entry-text))))
1613 (defmacro with-replaced-entry-text-multi (replacements &rest body)
1614 "During the execution of BODY, the entire text of the current entry is
1615 concealed by an overlay that displays the overlays in REPLACEMENTS."
1616 `(progn
1617 (org-drill-replace-entry-text ,replacements t)
1618 (unwind-protect
1619 (progn
1620 ,@body)
1621 (org-drill-unreplace-entry-text))))
1624 (defun org-drill-replace-entry-text (text &optional multi-p)
1625 "Make an overlay that conceals the entire text of the item, not
1626 including properties or the contents of subheadings. The overlay shows
1627 the string TEXT.
1628 If MULTI-P is non-nil, TEXT must be a list of values which are legal
1629 for the `display' text property. The text of the item will be temporarily
1630 replaced by all of these items, in the order in which they appear in
1631 the list.
1632 Note: does not actually alter the item."
1633 (cond
1634 ((and multi-p
1635 (listp text))
1636 (org-drill-replace-entry-text-multi text))
1638 (let ((ovl (make-overlay (point-min)
1639 (save-excursion
1640 (outline-next-heading)
1641 (point)))))
1642 (overlay-put ovl 'priority 9999)
1643 (overlay-put ovl 'category
1644 'org-drill-replaced-text-overlay)
1645 (overlay-put ovl 'display text)))))
1648 (defun org-drill-unreplace-entry-text ()
1649 (save-excursion
1650 (dolist (ovl (overlays-in (point-min) (point-max)))
1651 (when (eql 'org-drill-replaced-text-overlay (overlay-get ovl 'category))
1652 (delete-overlay ovl)))))
1655 (defun org-drill-replace-entry-text-multi (replacements)
1656 "Make overlays that conceal the entire text of the item, not
1657 including properties or the contents of subheadings. The overlay shows
1658 the string TEXT.
1659 Note: does not actually alter the item."
1660 (let ((ovl nil)
1661 (p-min (point-min))
1662 (p-max (save-excursion
1663 (outline-next-heading)
1664 (point))))
1665 (assert (>= (- p-max p-min) (length replacements)))
1666 (dotimes (i (length replacements))
1667 (setq ovl (make-overlay (+ p-min (* 2 i))
1668 (if (= i (1- (length replacements)))
1669 p-max
1670 (+ p-min (* 2 i) 1))))
1671 (overlay-put ovl 'priority 9999)
1672 (overlay-put ovl 'category
1673 'org-drill-replaced-text-overlay)
1674 (overlay-put ovl 'display (nth i replacements)))))
1677 (defmacro with-replaced-entry-heading (heading &rest body)
1678 `(progn
1679 (org-drill-replace-entry-heading ,heading)
1680 (unwind-protect
1681 (progn
1682 ,@body)
1683 (org-drill-unhide-text))))
1686 (defun org-drill-replace-entry-heading (heading)
1687 "Make an overlay that conceals the heading of the item. The overlay shows
1688 the string TEXT.
1689 Note: does not actually alter the item."
1690 (org-drill-hide-heading-at-point heading))
1693 (defun org-drill-unhide-clozed-text ()
1694 (save-excursion
1695 (dolist (ovl (overlays-in (point-min) (point-max)))
1696 (when (eql 'org-drill-cloze-overlay-defaults (overlay-get ovl 'category))
1697 (delete-overlay ovl)))))
1700 (defun org-drill-get-entry-text (&optional keep-properties-p)
1701 (let ((text (org-agenda-get-some-entry-text (point-marker) 100)))
1702 (if keep-properties-p
1703 text
1704 (substring-no-properties text))))
1707 ;; (defun org-entry-empty-p ()
1708 ;; (zerop (length (org-drill-get-entry-text))))
1710 ;; This version is about 5x faster than the old version, above.
1711 (defun org-entry-empty-p ()
1712 (save-excursion
1713 (org-back-to-heading t)
1714 (let ((lim (save-excursion
1715 (outline-next-heading) (point))))
1716 (if (fboundp 'org-end-of-meta-data-and-drawers)
1717 (org-end-of-meta-data-and-drawers) ; function removed Feb 2015
1718 (org-end-of-meta-data t))
1719 (or (>= (point) lim)
1720 (null (re-search-forward "[[:graph:]]" lim t))))))
1722 (defun org-drill-entry-empty-p () (org-entry-empty-p))
1725 ;;; Presentation functions ====================================================
1727 ;; Each of these is called with point on topic heading. Each needs to show the
1728 ;; topic in the form of a 'question' or with some information 'hidden', as
1729 ;; appropriate for the card type. The user should then be prompted to press a
1730 ;; key. The function should then reveal either the 'answer' or the entire
1731 ;; topic, and should return t if the user chose to see the answer and rate their
1732 ;; recall, nil if they chose to quit.
1735 (defun org-drill-present-simple-card ()
1736 (with-hidden-comments
1737 (with-hidden-cloze-hints
1738 (with-hidden-cloze-text
1739 (org-drill-hide-all-subheadings-except nil)
1740 (org-drill--show-latex-fragments) ; overlay all LaTeX fragments with images
1741 (ignore-errors
1742 (org-display-inline-images t))
1743 (org-cycle-hide-drawers 'all)
1744 (prog1 (org-drill-presentation-prompt)
1745 (org-drill-hide-subheadings-if 'org-drill-entry-p))))))
1748 (defun org-drill-present-default-answer (reschedule-fn)
1749 (cond
1750 (drill-answer
1751 (with-replaced-entry-text
1752 (format "\nAnswer:\n\n %s\n" drill-answer)
1753 (prog1
1754 (funcall reschedule-fn)
1755 (setq drill-answer nil))))
1757 (org-drill-hide-subheadings-if 'org-drill-entry-p)
1758 (org-drill-unhide-clozed-text)
1759 (org-drill--show-latex-fragments)
1760 (ignore-errors
1761 (org-display-inline-images t))
1762 (org-cycle-hide-drawers 'all)
1763 (with-hidden-cloze-hints
1764 (funcall reschedule-fn)))))
1767 (defun org-drill--show-latex-fragments ()
1768 (org-remove-latex-fragment-image-overlays)
1769 (if (fboundp 'org-toggle-latex-fragment)
1770 (org-toggle-latex-fragment '(4))
1771 (org-preview-latex-fragment '(4))))
1774 (defun org-drill-present-two-sided-card ()
1775 (with-hidden-comments
1776 (with-hidden-cloze-hints
1777 (with-hidden-cloze-text
1778 (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
1779 (when drill-sections
1780 (save-excursion
1781 (goto-char (nth (random* (min 2 (length drill-sections)))
1782 drill-sections))
1783 (org-show-subtree)))
1784 (org-drill--show-latex-fragments)
1785 (ignore-errors
1786 (org-display-inline-images t))
1787 (org-cycle-hide-drawers 'all)
1788 (prog1 (org-drill-presentation-prompt)
1789 (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
1793 (defun org-drill-present-multi-sided-card ()
1794 (with-hidden-comments
1795 (with-hidden-cloze-hints
1796 (with-hidden-cloze-text
1797 (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
1798 (when drill-sections
1799 (save-excursion
1800 (goto-char (nth (random* (length drill-sections)) drill-sections))
1801 (org-show-subtree)))
1802 (org-drill--show-latex-fragments)
1803 (ignore-errors
1804 (org-display-inline-images t))
1805 (org-cycle-hide-drawers 'all)
1806 (prog1 (org-drill-presentation-prompt)
1807 (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
1810 (defun org-drill-present-multicloze-hide-n (number-to-hide
1811 &optional
1812 force-show-first
1813 force-show-last
1814 force-hide-first)
1815 "Hides NUMBER-TO-HIDE pieces of text that are marked for cloze deletion,
1816 chosen at random.
1817 If NUMBER-TO-HIDE is negative, show only (ABS NUMBER-TO-HIDE) pieces,
1818 hiding all the rest.
1819 If FORCE-HIDE-FIRST is non-nil, force the first piece of text to be one of
1820 the hidden items.
1821 If FORCE-SHOW-FIRST is non-nil, never hide the first piece of text.
1822 If FORCE-SHOW-LAST is non-nil, never hide the last piece of text.
1823 If the number of text pieces in the item is less than
1824 NUMBER-TO-HIDE, then all text pieces will be hidden (except the first or last
1825 items if FORCE-SHOW-FIRST or FORCE-SHOW-LAST is non-nil)."
1826 (with-hidden-comments
1827 (with-hidden-cloze-hints
1828 (let ((item-end nil)
1829 (match-count 0)
1830 (body-start (or (cdr (org-get-property-block))
1831 (point))))
1832 (if (and force-hide-first force-show-first)
1833 (error "FORCE-HIDE-FIRST and FORCE-SHOW-FIRST are mutually exclusive"))
1834 (org-drill-hide-all-subheadings-except nil)
1835 (save-excursion
1836 (outline-next-heading)
1837 (setq item-end (point)))
1838 (save-excursion
1839 (goto-char body-start)
1840 (while (re-search-forward org-drill-cloze-regexp item-end t)
1841 (let ((in-regexp? (save-match-data
1842 (org-pos-in-regexp (match-beginning 0)
1843 org-bracket-link-regexp 1))))
1844 (unless (or in-regexp?
1845 (org-inside-LaTeX-fragment-p))
1846 (incf match-count)))))
1847 (if (minusp number-to-hide)
1848 (setq number-to-hide (+ match-count number-to-hide)))
1849 (when (plusp match-count)
1850 (let* ((positions (shuffle-list (loop for i from 1
1851 to match-count
1852 collect i)))
1853 (match-nums nil)
1854 (cnt nil))
1855 (if force-hide-first
1856 ;; Force '1' to be in the list, and to be the first item
1857 ;; in the list.
1858 (setq positions (cons 1 (remove 1 positions))))
1859 (if force-show-first
1860 (setq positions (remove 1 positions)))
1861 (if force-show-last
1862 (setq positions (remove match-count positions)))
1863 (setq match-nums
1864 (subseq positions
1865 0 (min number-to-hide (length positions))))
1866 ;; (dolist (pos-to-hide match-nums)
1867 (save-excursion
1868 (goto-char body-start)
1869 (setq cnt 0)
1870 (while (re-search-forward org-drill-cloze-regexp item-end t)
1871 (unless (save-match-data
1872 (or (org-pos-in-regexp (match-beginning 0)
1873 org-bracket-link-regexp 1)
1874 (org-inside-LaTeX-fragment-p)))
1875 (incf cnt)
1876 (if (memq cnt match-nums)
1877 (org-drill-hide-matched-cloze-text)))))))
1878 ;; (loop
1879 ;; do (re-search-forward org-drill-cloze-regexp
1880 ;; item-end t pos-to-hide)
1881 ;; while (org-pos-in-regexp (match-beginning 0)
1882 ;; org-bracket-link-regexp 1))
1883 ;; (org-drill-hide-matched-cloze-text)))))
1884 (org-drill--show-latex-fragments)
1885 (ignore-errors
1886 (org-display-inline-images t))
1887 (org-cycle-hide-drawers 'all)
1888 (prog1 (org-drill-presentation-prompt)
1889 (org-drill-hide-subheadings-if 'org-drill-entry-p)
1890 (org-drill-unhide-clozed-text))))))
1893 (defun org-drill-present-multicloze-hide-nth (to-hide)
1894 "Hide the TO-HIDE'th piece of clozed text. 1 is the first piece. If
1895 TO-HIDE is negative, count backwards, so -1 means the last item, -2
1896 the second to last, etc."
1897 (with-hidden-comments
1898 (with-hidden-cloze-hints
1899 (let ((item-end nil)
1900 (match-count 0)
1901 (body-start (or (cdr (org-get-property-block))
1902 (point)))
1903 (cnt 0))
1904 (org-drill-hide-all-subheadings-except nil)
1905 (save-excursion
1906 (outline-next-heading)
1907 (setq item-end (point)))
1908 (save-excursion
1909 (goto-char body-start)
1910 (while (re-search-forward org-drill-cloze-regexp item-end t)
1911 (let ((in-regexp? (save-match-data
1912 (org-pos-in-regexp (match-beginning 0)
1913 org-bracket-link-regexp 1))))
1914 (unless (or in-regexp?
1915 (org-inside-LaTeX-fragment-p))
1916 (incf match-count)))))
1917 (if (minusp to-hide)
1918 (setq to-hide (+ 1 to-hide match-count)))
1919 (cond
1920 ((or (not (plusp match-count))
1921 (> to-hide match-count))
1922 nil)
1924 (save-excursion
1925 (goto-char body-start)
1926 (setq cnt 0)
1927 (while (re-search-forward org-drill-cloze-regexp item-end t)
1928 (unless (save-match-data
1929 ;; Don't consider this a cloze region if it is part of an
1930 ;; org link, or if it occurs inside a LaTeX math
1931 ;; fragment
1932 (or (org-pos-in-regexp (match-beginning 0)
1933 org-bracket-link-regexp 1)
1934 (org-inside-LaTeX-fragment-p)))
1935 (incf cnt)
1936 (if (= cnt to-hide)
1937 (org-drill-hide-matched-cloze-text)))))))
1938 (org-drill--show-latex-fragments)
1939 (ignore-errors
1940 (org-display-inline-images t))
1941 (org-cycle-hide-drawers 'all)
1942 (prog1 (org-drill-presentation-prompt)
1943 (org-drill-hide-subheadings-if 'org-drill-entry-p)
1944 (org-drill-unhide-clozed-text))))))
1947 (defun org-drill-present-multicloze-hide1 ()
1948 "Hides one of the pieces of text that are marked for cloze deletion,
1949 chosen at random."
1950 (org-drill-present-multicloze-hide-n 1))
1953 (defun org-drill-present-multicloze-hide2 ()
1954 "Hides two of the pieces of text that are marked for cloze deletion,
1955 chosen at random."
1956 (org-drill-present-multicloze-hide-n 2))
1959 (defun org-drill-present-multicloze-hide-first ()
1960 "Hides the first piece of text that is marked for cloze deletion."
1961 (org-drill-present-multicloze-hide-nth 1))
1964 (defun org-drill-present-multicloze-hide-last ()
1965 "Hides the last piece of text that is marked for cloze deletion."
1966 (org-drill-present-multicloze-hide-nth -1))
1969 (defun org-drill-present-multicloze-hide1-firstmore ()
1970 "Commonly, hides the FIRST piece of text that is marked for
1971 cloze deletion. Uncommonly, hide one of the other pieces of text,
1972 chosen at random.
1974 The definitions of 'commonly' and 'uncommonly' are determined by
1975 the value of `org-drill-cloze-text-weight'."
1976 ;; The 'firstmore' and 'lastmore' functions used to randomly choose whether
1977 ;; to hide the 'favoured' piece of text. However even when the chance of
1978 ;; hiding it was set quite high (80%), the outcome was too unpredictable over
1979 ;; the small number of repetitions where most learning takes place for each
1980 ;; item. In other words, the actual frequency during the first 10 repetitions
1981 ;; was often very different from 80%. Hence we use modulo instead.
1982 (cond
1983 ((null org-drill-cloze-text-weight)
1984 ;; Behave as hide1cloze
1985 (org-drill-present-multicloze-hide1))
1986 ((not (and (integerp org-drill-cloze-text-weight)
1987 (plusp org-drill-cloze-text-weight)))
1988 (error "Illegal value for org-drill-cloze-text-weight: %S"
1989 org-drill-cloze-text-weight))
1990 ((zerop (mod (1+ (org-drill-entry-total-repeats 0))
1991 org-drill-cloze-text-weight))
1992 ;; Uncommonly, hide any item except the first
1993 (org-drill-present-multicloze-hide-n 1 t))
1995 ;; Commonly, hide first item
1996 (org-drill-present-multicloze-hide-first))))
1999 (defun org-drill-present-multicloze-show1-lastmore ()
2000 "Commonly, hides all pieces except the last. Uncommonly, shows
2001 any random piece. The effect is similar to 'show1cloze' except
2002 that the last item is much less likely to be the item that is
2003 visible.
2005 The definitions of 'commonly' and 'uncommonly' are determined by
2006 the value of `org-drill-cloze-text-weight'."
2007 (cond
2008 ((null org-drill-cloze-text-weight)
2009 ;; Behave as show1cloze
2010 (org-drill-present-multicloze-show1))
2011 ((not (and (integerp org-drill-cloze-text-weight)
2012 (plusp org-drill-cloze-text-weight)))
2013 (error "Illegal value for org-drill-cloze-text-weight: %S"
2014 org-drill-cloze-text-weight))
2015 ((zerop (mod (1+ (org-drill-entry-total-repeats 0))
2016 org-drill-cloze-text-weight))
2017 ;; Uncommonly, show any item except the last
2018 (org-drill-present-multicloze-hide-n -1 nil nil t))
2020 ;; Commonly, show the LAST item
2021 (org-drill-present-multicloze-hide-n -1 nil t))))
2024 (defun org-drill-present-multicloze-show1-firstless ()
2025 "Commonly, hides all pieces except one, where the shown piece
2026 is guaranteed NOT to be the first piece. Uncommonly, shows any
2027 random piece. The effect is similar to 'show1cloze' except that
2028 the first item is much less likely to be the item that is
2029 visible.
2031 The definitions of 'commonly' and 'uncommonly' are determined by
2032 the value of `org-drill-cloze-text-weight'."
2033 (cond
2034 ((null org-drill-cloze-text-weight)
2035 ;; Behave as show1cloze
2036 (org-drill-present-multicloze-show1))
2037 ((not (and (integerp org-drill-cloze-text-weight)
2038 (plusp org-drill-cloze-text-weight)))
2039 (error "Illegal value for org-drill-cloze-text-weight: %S"
2040 org-drill-cloze-text-weight))
2041 ((zerop (mod (1+ (org-drill-entry-total-repeats 0))
2042 org-drill-cloze-text-weight))
2043 ;; Uncommonly, show the first item
2044 (org-drill-present-multicloze-hide-n -1 t))
2046 ;; Commonly, show any item, except the first
2047 (org-drill-present-multicloze-hide-n -1 nil nil t))))
2050 (defun org-drill-present-multicloze-show1 ()
2051 "Similar to `org-drill-present-multicloze-hide1', but hides all
2052 the pieces of text that are marked for cloze deletion, except for one
2053 piece which is chosen at random."
2054 (org-drill-present-multicloze-hide-n -1))
2057 (defun org-drill-present-multicloze-show2 ()
2058 "Similar to `org-drill-present-multicloze-show1', but reveals two
2059 pieces rather than one."
2060 (org-drill-present-multicloze-hide-n -2))
2063 (defun org-drill-present-card-using-text (question &optional answer)
2064 "Present the string QUESTION as the only visible content of the card.
2065 If ANSWER is supplied, set the global variable `drill-answer' to its value."
2066 (if answer (setq drill-answer answer))
2067 (with-hidden-comments
2068 (with-replaced-entry-text
2069 (concat "\n" question)
2070 (org-drill-hide-all-subheadings-except nil)
2071 (org-cycle-hide-drawers 'all)
2072 (ignore-errors
2073 (org-display-inline-images t))
2074 (prog1 (org-drill-presentation-prompt)
2075 (org-drill-hide-subheadings-if 'org-drill-entry-p)))))
2078 (defun org-drill-present-card-using-multiple-overlays (replacements &optional answer)
2079 "TEXTS is a list of valid values for the 'display' text property.
2080 Present these overlays, in sequence, as the only
2081 visible content of the card.
2082 If ANSWER is supplied, set the global variable `drill-answer' to its value."
2083 (if answer (setq drill-answer answer))
2084 (with-hidden-comments
2085 (with-replaced-entry-text-multi
2086 replacements
2087 (org-drill-hide-all-subheadings-except nil)
2088 (org-cycle-hide-drawers 'all)
2089 (ignore-errors
2090 (org-display-inline-images t))
2091 (prog1 (org-drill-presentation-prompt)
2092 (org-drill-hide-subheadings-if 'org-drill-entry-p)))))
2095 (defun org-drill-entry ()
2096 "Present the current topic for interactive review, as in `org-drill'.
2097 Review will occur regardless of whether the topic is due for review or whether
2098 it meets the definition of a 'review topic' used by `org-drill'.
2100 Returns a quality rating from 0 to 5, or nil if the user quit, or the symbol
2101 EDIT if the user chose to exit the drill and edit the current item. Choosing
2102 the latter option leaves the drill session suspended; it can be resumed
2103 later using `org-drill-resume'.
2105 See `org-drill' for more details."
2106 (interactive)
2107 (org-drill-goto-drill-entry-heading)
2108 ;;(unless (org-part-of-drill-entry-p)
2109 ;; (error "Point is not inside a drill entry"))
2110 ;;(unless (org-at-heading-p)
2111 ;; (org-back-to-heading))
2112 (let ((card-type (org-entry-get (point) "DRILL_CARD_TYPE" t))
2113 (answer-fn 'org-drill-present-default-answer)
2114 (present-empty-cards nil)
2115 (cont nil)
2116 ;; fontification functions in `outline-view-change-hook' can cause big
2117 ;; slowdowns, so we temporarily bind this variable to nil here.
2118 (outline-view-change-hook nil))
2119 (setq drill-answer nil)
2120 (org-save-outline-visibility t
2121 (save-restriction
2122 (org-narrow-to-subtree)
2123 (org-show-subtree)
2124 (org-cycle-hide-drawers 'all)
2126 (let ((presentation-fn
2127 (cdr (assoc card-type org-drill-card-type-alist))))
2128 (if (listp presentation-fn)
2129 (psetq answer-fn (or (second presentation-fn)
2130 'org-drill-present-default-answer)
2131 present-empty-cards (third presentation-fn)
2132 presentation-fn (first presentation-fn)))
2133 (prog1
2134 (cond
2135 ((null presentation-fn)
2136 (message "%s:%d: Unrecognised card type '%s', skipping..."
2137 (buffer-name) (point) card-type)
2138 (sit-for 0.5)
2139 'skip)
2141 (setq cont (funcall presentation-fn))
2142 (cond
2143 ((not cont)
2144 (message "Quit")
2145 nil)
2146 ((eql cont 'edit)
2147 'edit)
2148 ((eql cont 'skip)
2149 'skip)
2151 (save-excursion
2152 (funcall answer-fn
2153 (lambda () (org-drill-reschedule))))))))
2154 (org-remove-latex-fragment-image-overlays)))))))
2157 (defun org-drill-entries-pending-p ()
2158 (or *org-drill-again-entries*
2159 *org-drill-current-item*
2160 (and (not (org-drill-maximum-item-count-reached-p))
2161 (not (org-drill-maximum-duration-reached-p))
2162 (or *org-drill-new-entries*
2163 *org-drill-failed-entries*
2164 *org-drill-young-mature-entries*
2165 *org-drill-old-mature-entries*
2166 *org-drill-overdue-entries*
2167 *org-drill-again-entries*))))
2170 (defun org-drill-pending-entry-count ()
2171 (+ (if (markerp *org-drill-current-item*) 1 0)
2172 (length *org-drill-new-entries*)
2173 (length *org-drill-failed-entries*)
2174 (length *org-drill-young-mature-entries*)
2175 (length *org-drill-old-mature-entries*)
2176 (length *org-drill-overdue-entries*)
2177 (length *org-drill-again-entries*)))
2180 (defun org-drill-maximum-duration-reached-p ()
2181 "Returns true if the current drill session has continued past its
2182 maximum duration."
2183 (and org-drill-maximum-duration
2184 (not *org-drill-cram-mode*)
2185 *org-drill-start-time*
2186 (> (- (float-time (current-time)) *org-drill-start-time*)
2187 (* org-drill-maximum-duration 60))))
2190 (defun org-drill-maximum-item-count-reached-p ()
2191 "Returns true if the current drill session has reached the
2192 maximum number of items."
2193 (and org-drill-maximum-items-per-session
2194 (not *org-drill-cram-mode*)
2195 (>= (length *org-drill-done-entries*)
2196 org-drill-maximum-items-per-session)))
2199 (defun org-drill-pop-next-pending-entry ()
2200 (block org-drill-pop-next-pending-entry
2201 (let ((m nil))
2202 (while (or (null m)
2203 (not (org-drill-entry-p m)))
2204 (setq
2206 (cond
2207 ;; First priority is items we failed in a prior session.
2208 ((and *org-drill-failed-entries*
2209 (not (org-drill-maximum-item-count-reached-p))
2210 (not (org-drill-maximum-duration-reached-p)))
2211 (pop-random *org-drill-failed-entries*))
2212 ;; Next priority is overdue items.
2213 ((and *org-drill-overdue-entries*
2214 (not (org-drill-maximum-item-count-reached-p))
2215 (not (org-drill-maximum-duration-reached-p)))
2216 ;; We use `pop', not `pop-random', because we have already
2217 ;; sorted overdue items into a random order which takes
2218 ;; number of days overdue into account.
2219 (pop *org-drill-overdue-entries*))
2220 ;; Next priority is 'young' items.
2221 ((and *org-drill-young-mature-entries*
2222 (not (org-drill-maximum-item-count-reached-p))
2223 (not (org-drill-maximum-duration-reached-p)))
2224 (pop-random *org-drill-young-mature-entries*))
2225 ;; Next priority is newly added items, and older entries.
2226 ;; We pool these into a single group.
2227 ((and (or *org-drill-new-entries*
2228 *org-drill-old-mature-entries*)
2229 (not (org-drill-maximum-item-count-reached-p))
2230 (not (org-drill-maximum-duration-reached-p)))
2231 (cond
2232 ((< (random* (+ (length *org-drill-new-entries*)
2233 (length *org-drill-old-mature-entries*)))
2234 (length *org-drill-new-entries*))
2235 (pop-random *org-drill-new-entries*))
2237 (pop-random *org-drill-old-mature-entries*))))
2238 ;; After all the above are done, last priority is items
2239 ;; that were failed earlier THIS SESSION.
2240 (*org-drill-again-entries*
2241 (pop *org-drill-again-entries*))
2242 (t ; nothing left -- return nil
2243 (return-from org-drill-pop-next-pending-entry nil)))))
2244 m)))
2247 (defun org-drill-entries (&optional resuming-p)
2248 "Returns nil, t, or a list of markers representing entries that were
2249 'failed' and need to be presented again before the session ends.
2251 RESUMING-P is true if we are resuming a suspended drill session."
2252 (block org-drill-entries
2253 (while (org-drill-entries-pending-p)
2254 (let ((m (cond
2255 ((or (not resuming-p)
2256 (null *org-drill-current-item*)
2257 (not (org-drill-entry-p *org-drill-current-item*)))
2258 (org-drill-pop-next-pending-entry))
2259 (t ; resuming a suspended session.
2260 (setq resuming-p nil)
2261 *org-drill-current-item*))))
2262 (setq *org-drill-current-item* m)
2263 (unless m
2264 (error "Unexpectedly ran out of pending drill items"))
2265 (save-excursion
2266 (org-drill-goto-entry m)
2267 (cond
2268 ((not (org-drill-entry-due-p))
2269 ;; The entry is not due anymore. This could arise if the user
2270 ;; suspends a drill session, then drills an individual entry,
2271 ;; then resumes the session.
2272 (message "Entry no longer due, skipping...")
2273 (sit-for 0.3)
2274 nil)
2276 (setq result (org-drill-entry))
2277 (cond
2278 ((null result)
2279 (message "Quit")
2280 (setq end-pos :quit)
2281 (return-from org-drill-entries nil))
2282 ((eql result 'edit)
2283 (setq end-pos (point-marker))
2284 (return-from org-drill-entries nil))
2285 ((eql result 'skip)
2286 (setq *org-drill-current-item* nil)
2287 nil) ; skip this item
2289 (cond
2290 ((<= result org-drill-failure-quality)
2291 (if *org-drill-again-entries*
2292 (setq *org-drill-again-entries*
2293 (shuffle-list *org-drill-again-entries*)))
2294 (push-end m *org-drill-again-entries*))
2296 (push m *org-drill-done-entries*)))
2297 (setq *org-drill-current-item* nil))))))))))
2301 (defun org-drill-final-report ()
2302 (let ((pass-percent
2303 (round (* 100 (count-if (lambda (qual)
2304 (> qual org-drill-failure-quality))
2305 *org-drill-session-qualities*))
2306 (max 1 (length *org-drill-session-qualities*))))
2307 (prompt nil)
2308 (max-mini-window-height 0.6))
2309 (setq prompt
2310 (format
2311 "%d items reviewed. Session duration %s.
2312 Recall of reviewed items:
2313 Excellent (5): %3d%% | Near miss (2): %3d%%
2314 Good (4): %3d%% | Failure (1): %3d%%
2315 Hard (3): %3d%% | Abject failure (0): %3d%%
2317 You successfully recalled %d%% of reviewed items (quality > %s)
2318 %d/%d items still await review (%s, %s, %s, %s, %s).
2319 Tomorrow, %d more items will become due for review.
2320 Session finished. Press a key to continue..."
2321 (length *org-drill-done-entries*)
2322 (format-seconds "%h:%.2m:%.2s"
2323 (- (float-time (current-time)) *org-drill-start-time*))
2324 (round (* 100 (count 5 *org-drill-session-qualities*))
2325 (max 1 (length *org-drill-session-qualities*)))
2326 (round (* 100 (count 2 *org-drill-session-qualities*))
2327 (max 1 (length *org-drill-session-qualities*)))
2328 (round (* 100 (count 4 *org-drill-session-qualities*))
2329 (max 1 (length *org-drill-session-qualities*)))
2330 (round (* 100 (count 1 *org-drill-session-qualities*))
2331 (max 1 (length *org-drill-session-qualities*)))
2332 (round (* 100 (count 3 *org-drill-session-qualities*))
2333 (max 1 (length *org-drill-session-qualities*)))
2334 (round (* 100 (count 0 *org-drill-session-qualities*))
2335 (max 1 (length *org-drill-session-qualities*)))
2336 pass-percent
2337 org-drill-failure-quality
2338 (org-drill-pending-entry-count)
2339 (+ (org-drill-pending-entry-count)
2340 *org-drill-dormant-entry-count*)
2341 (propertize
2342 (format "%d failed"
2343 (+ (length *org-drill-failed-entries*)
2344 (length *org-drill-again-entries*)))
2345 'face `(:foreground ,org-drill-failed-count-color))
2346 (propertize
2347 (format "%d overdue"
2348 (length *org-drill-overdue-entries*))
2349 'face `(:foreground ,org-drill-failed-count-color))
2350 (propertize
2351 (format "%d new"
2352 (length *org-drill-new-entries*))
2353 'face `(:foreground ,org-drill-new-count-color))
2354 (propertize
2355 (format "%d young"
2356 (length *org-drill-young-mature-entries*))
2357 'face `(:foreground ,org-drill-mature-count-color))
2358 (propertize
2359 (format "%d old"
2360 (length *org-drill-old-mature-entries*))
2361 'face `(:foreground ,org-drill-mature-count-color))
2362 *org-drill-due-tomorrow-count*
2365 (while (not (input-pending-p))
2366 (message "%s" prompt)
2367 (sit-for 0.5))
2368 (read-char-exclusive)
2370 (if (and *org-drill-session-qualities*
2371 (< pass-percent (- 100 org-drill-forgetting-index)))
2372 (read-char-exclusive
2373 (format
2375 You failed %d%% of the items you reviewed during this session.
2376 %d (%d%%) of all items scanned were overdue.
2378 Are you keeping up with your items, and reviewing them
2379 when they are scheduled? If so, you may want to consider
2380 lowering the value of `org-drill-learn-fraction' slightly in
2381 order to make items appear more frequently over time."
2382 (propertize "WARNING!" 'face 'org-warning)
2383 (- 100 pass-percent)
2384 *org-drill-overdue-entry-count*
2385 (round (* 100 *org-drill-overdue-entry-count*)
2386 (+ *org-drill-dormant-entry-count*
2387 *org-drill-due-entry-count*)))
2388 ))))
2392 (defun org-drill-free-markers (markers)
2393 "MARKERS is a list of markers, all of which will be freed (set to
2394 point nowhere). Alternatively, MARKERS can be 't', in which case
2395 all the markers used by Org-Drill will be freed."
2396 (dolist (m (if (eql t markers)
2397 (append *org-drill-done-entries*
2398 *org-drill-new-entries*
2399 *org-drill-failed-entries*
2400 *org-drill-again-entries*
2401 *org-drill-overdue-entries*
2402 *org-drill-young-mature-entries*
2403 *org-drill-old-mature-entries*)
2404 markers))
2405 (free-marker m)))
2408 ;;; overdue-data is a list of entries, each entry has the form (POS DUE AGE)
2409 ;;; where POS is a marker pointing to the start of the entry, and
2410 ;;; DUE is a number indicating how many days ago the entry was due.
2411 ;;; AGE is the number of days elapsed since item creation (nil if unknown).
2412 ;;; if age > lapse threshold (default 90), sort by age (oldest first)
2413 ;;; if age < lapse threshold, sort by due (biggest first)
2416 (defun org-drill-order-overdue-entries (overdue-data)
2417 (let* ((lapsed-days (if org-drill--lapse-very-overdue-entries-p
2418 90 most-positive-fixnum))
2419 (not-lapsed (remove-if (lambda (a) (> (or (second a) 0) lapsed-days))
2420 overdue-data))
2421 (lapsed (remove-if-not (lambda (a) (> (or (second a) 0)
2422 lapsed-days)) overdue-data)))
2423 (setq *org-drill-overdue-entries*
2424 (mapcar 'first
2425 (append
2426 (sort (shuffle-list not-lapsed)
2427 (lambda (a b) (> (second a) (second b))))
2428 (sort lapsed
2429 (lambda (a b) (> (third a) (third b)))))))))
2432 (defun org-drill--entry-lapsed-p ()
2433 (let ((lapsed-days 90))
2434 (and org-drill--lapse-very-overdue-entries-p
2435 (> (or (org-drill-entry-days-overdue) 0) lapsed-days))))
2440 (defun org-drill-entry-days-since-creation (&optional use-last-interval-p)
2441 "If USE-LAST-INTERVAL-P is non-nil, and DATE_ADDED is missing, use the
2442 value of DRILL_LAST_INTERVAL instead (as the item's age must be at least
2443 that many days)."
2444 (let ((timestamp (org-entry-get (point) "DATE_ADDED")))
2445 (cond
2446 (timestamp
2447 (- (org-time-stamp-to-now timestamp)))
2448 (use-last-interval-p
2449 (+ (or (org-drill-entry-days-overdue) 0)
2450 (read (or (org-entry-get (point) "DRILL_LAST_INTERVAL") "0"))))
2451 (t nil))))
2454 (defun org-drill-entry-status ()
2455 "Returns a list (STATUS DUE AGE) where DUE is the number of days overdue,
2456 zero being due today, -1 being scheduled 1 day in the future.
2457 AGE is the number of days elapsed since the item was created (nil if unknown).
2458 STATUS is one of the following values:
2459 - nil, if the item is not a drill entry, or has an empty body
2460 - :unscheduled
2461 - :future
2462 - :new
2463 - :failed
2464 - :overdue
2465 - :young
2466 - :old
2468 (save-excursion
2469 (unless (org-at-heading-p)
2470 (org-back-to-heading))
2471 (let ((due (org-drill-entry-days-overdue))
2472 (age (org-drill-entry-days-since-creation t))
2473 (last-int (org-drill-entry-last-interval 1)))
2474 (list
2475 (cond
2476 ((not (org-drill-entry-p))
2477 nil)
2478 ((and (org-entry-empty-p)
2479 (let* ((card-type (org-entry-get (point) "DRILL_CARD_TYPE" nil))
2480 (dat (cdr (assoc card-type org-drill-card-type-alist))))
2481 (or (null card-type)
2482 (not (third dat)))))
2483 ;; body is empty, and this is not a card type where empty bodies are
2484 ;; meaningful, so skip it.
2485 nil)
2486 ((null due) ; unscheduled - usually a skipped leech
2487 :unscheduled)
2488 ;; ((eql -1 due)
2489 ;; :tomorrow)
2490 ((minusp due) ; scheduled in the future
2491 :future)
2492 ;; The rest of the stati all denote 'due' items ==========================
2493 ((<= (org-drill-entry-last-quality 9999)
2494 org-drill-failure-quality)
2495 ;; Mature entries that were failed last time are
2496 ;; FAILED, regardless of how young, old or overdue
2497 ;; they are.
2498 :failed)
2499 ((org-drill-entry-new-p)
2500 :new)
2501 ((org-drill-entry-overdue-p due last-int)
2502 ;; Overdue status overrides young versus old
2503 ;; distinction.
2504 ;; Store marker + due, for sorting of overdue entries
2505 :overdue)
2506 ((<= (org-drill-entry-last-interval 9999)
2507 org-drill-days-before-old)
2508 :young)
2510 :old))
2511 due age))))
2514 (defun org-drill-progress-message (collected scanned)
2515 (when (zerop (% scanned 50))
2516 (let* ((meter-width 40)
2517 (sym1 (if (oddp (floor scanned (* 50 meter-width))) ?| ?.))
2518 (sym2 (if (eql sym1 ?.) ?| ?.)))
2519 (message "Collecting due drill items:%4d %s%s"
2520 collected
2521 (make-string (% (ceiling scanned 50) meter-width)
2522 sym2)
2523 (make-string (- meter-width (% (ceiling scanned 50) meter-width))
2524 sym1)))))
2527 (defun org-drill (&optional scope drill-match resume-p)
2528 "Begin an interactive 'drill session'. The user is asked to
2529 review a series of topics (headers). Each topic is initially
2530 presented as a 'question', often with part of the topic content
2531 hidden. The user attempts to recall the hidden information or
2532 answer the question, then presses a key to reveal the answer. The
2533 user then rates his or her recall or performance on that
2534 topic. This rating information is used to reschedule the topic
2535 for future review.
2537 Org-drill proceeds by:
2539 - Finding all topics (headings) in SCOPE which have either been
2540 used and rescheduled before, or which have a tag that matches
2541 `org-drill-question-tag'.
2543 - All matching topics which are either unscheduled, or are
2544 scheduled for the current date or a date in the past, are
2545 considered to be candidates for the drill session.
2547 - If `org-drill-maximum-items-per-session' is set, a random
2548 subset of these topics is presented. Otherwise, all of the
2549 eligible topics will be presented.
2551 SCOPE determines the scope in which to search for
2552 questions. It accepts the same values as `org-drill-scope',
2553 which see.
2555 DRILL-MATCH, if supplied, is a string specifying a tags/property/
2556 todo query. Only items matching the query will be considered.
2557 It accepts the same values as `org-drill-match', which see.
2559 If RESUME-P is non-nil, resume a suspended drill session rather
2560 than starting a new one."
2562 (interactive)
2563 ;; Check org version. Org 7.9.3f introduced a backwards-incompatible change
2564 ;; to the arguments accepted by `org-schedule'. At the time of writing there
2565 ;; are still lots of people using versions of org older than this.
2566 (let ((majorv (first (mapcar 'string-to-number (split-string (org-release) "[.]")))))
2567 (if (and (< majorv 8)
2568 (not (string-match-p "universal prefix argument" (documentation 'org-schedule))))
2569 (read-char-exclusive
2570 (format "Warning: org-drill requires org mode 7.9.3f or newer. Scheduling of failed cards will not
2571 work correctly with older versions of org mode. Your org mode version (%s) appears to be older than
2572 7.9.3f. Please consider installing a more recent version of org mode." (org-release)))))
2573 (let ((end-pos nil)
2574 (overdue-data nil)
2575 (cnt 0))
2576 (block org-drill
2577 (unless resume-p
2578 (org-drill-free-markers t)
2579 (setq *org-drill-current-item* nil
2580 *org-drill-done-entries* nil
2581 *org-drill-dormant-entry-count* 0
2582 *org-drill-due-entry-count* 0
2583 *org-drill-due-tomorrow-count* 0
2584 *org-drill-overdue-entry-count* 0
2585 *org-drill-new-entries* nil
2586 *org-drill-overdue-entries* nil
2587 *org-drill-young-mature-entries* nil
2588 *org-drill-old-mature-entries* nil
2589 *org-drill-failed-entries* nil
2590 *org-drill-again-entries* nil)
2591 (setq *org-drill-session-qualities* nil)
2592 (setq *org-drill-start-time* (float-time (current-time))))
2593 (setq *random-state* (make-random-state t)) ; reseed RNG
2594 (unwind-protect
2595 (save-excursion
2596 (unless resume-p
2597 (let ((org-trust-scanner-tags t)
2598 (warned-about-id-creation nil))
2599 (org-map-drill-entries
2600 (lambda ()
2601 (org-drill-progress-message
2602 (+ (length *org-drill-new-entries*)
2603 (length *org-drill-overdue-entries*)
2604 (length *org-drill-young-mature-entries*)
2605 (length *org-drill-old-mature-entries*)
2606 (length *org-drill-failed-entries*))
2607 (incf cnt))
2608 (cond
2609 ((not (org-drill-entry-p))
2610 nil) ; skip
2612 (when (and (not warned-about-id-creation)
2613 (null (org-id-get)))
2614 (message (concat "Creating unique IDs for items "
2615 "(slow, but only happens once)"))
2616 (sit-for 0.5)
2617 (setq warned-about-id-creation t))
2618 (org-id-get-create) ; ensure drill entry has unique ID
2619 (destructuring-bind (status due age)
2620 (org-drill-entry-status)
2621 (case status
2622 (:unscheduled
2623 (incf *org-drill-dormant-entry-count*))
2624 ;; (:tomorrow
2625 ;; (incf *org-drill-dormant-entry-count*)
2626 ;; (incf *org-drill-due-tomorrow-count*))
2627 (:future
2628 (incf *org-drill-dormant-entry-count*)
2629 (if (eq -1 due)
2630 (incf *org-drill-due-tomorrow-count*)))
2631 (:new
2632 (push (point-marker) *org-drill-new-entries*))
2633 (:failed
2634 (push (point-marker) *org-drill-failed-entries*))
2635 (:young
2636 (push (point-marker) *org-drill-young-mature-entries*))
2637 (:overdue
2638 (push (list (point-marker) due age) overdue-data))
2639 (:old
2640 (push (point-marker) *org-drill-old-mature-entries*))
2641 )))))
2642 scope drill-match)
2643 (org-drill-order-overdue-entries overdue-data)
2644 (setq *org-drill-overdue-entry-count*
2645 (length *org-drill-overdue-entries*))))
2646 (setq *org-drill-due-entry-count* (org-drill-pending-entry-count))
2647 (cond
2648 ((and (null *org-drill-current-item*)
2649 (null *org-drill-new-entries*)
2650 (null *org-drill-failed-entries*)
2651 (null *org-drill-overdue-entries*)
2652 (null *org-drill-young-mature-entries*)
2653 (null *org-drill-old-mature-entries*))
2654 (message "I did not find any pending drill items."))
2656 (org-drill-entries resume-p)
2657 (message "Drill session finished!"))))
2658 (progn
2659 (unless end-pos
2660 (setq *org-drill-cram-mode* nil)
2661 (org-drill-free-markers *org-drill-done-entries*)))))
2662 (cond
2663 (end-pos
2664 (when (markerp end-pos)
2665 (org-drill-goto-entry end-pos)
2666 (org-reveal)
2667 (org-show-entry))
2668 (let ((keystr (command-keybinding-to-string 'org-drill-resume)))
2669 (message
2670 "You can continue the drill session with the command `org-drill-resume'.%s"
2671 (if keystr (format "\nYou can run this command by pressing %s." keystr)
2672 ""))))
2674 (org-drill-final-report)
2675 (if (eql 'sm5 org-drill-spaced-repetition-algorithm)
2676 (org-drill-save-optimal-factor-matrix))
2677 (if org-drill-save-buffers-after-drill-sessions-p
2678 (save-some-buffers))
2679 (message "Drill session finished!")
2680 ))))
2683 (defun org-drill-save-optimal-factor-matrix ()
2684 (message "Saving optimal factor matrix...")
2685 (customize-save-variable 'org-drill-optimal-factor-matrix
2686 org-drill-optimal-factor-matrix))
2689 (defun org-drill-cram (&optional scope drill-match)
2690 "Run an interactive drill session in 'cram mode'. In cram mode,
2691 all drill items are considered to be due for review, unless they
2692 have been reviewed within the last `org-drill-cram-hours'
2693 hours."
2694 (interactive)
2695 (setq *org-drill-cram-mode* t)
2696 (org-drill scope drill-match))
2699 (defun org-drill-tree ()
2700 "Run an interactive drill session using drill items within the
2701 subtree at point."
2702 (interactive)
2703 (org-drill 'tree))
2706 (defun org-drill-directory ()
2707 "Run an interactive drill session using drill items from all org
2708 files in the same directory as the current file."
2709 (interactive)
2710 (org-drill 'directory))
2713 (defun org-drill-again (&optional scope drill-match)
2714 "Run a new drill session, but try to use leftover due items that
2715 were not reviewed during the last session, rather than scanning for
2716 unreviewed items. If there are no leftover items in memory, a full
2717 scan will be performed."
2718 (interactive)
2719 (setq *org-drill-cram-mode* nil)
2720 (cond
2721 ((plusp (org-drill-pending-entry-count))
2722 (org-drill-free-markers *org-drill-done-entries*)
2723 (if (markerp *org-drill-current-item*)
2724 (free-marker *org-drill-current-item*))
2725 (setq *org-drill-start-time* (float-time (current-time))
2726 *org-drill-done-entries* nil
2727 *org-drill-current-item* nil)
2728 (org-drill scope drill-match t))
2730 (org-drill scope drill-match))))
2734 (defun org-drill-resume ()
2735 "Resume a suspended drill session. Sessions are suspended by
2736 exiting them with the `edit' or `quit' options."
2737 (interactive)
2738 (cond
2739 ((org-drill-entries-pending-p)
2740 (org-drill nil nil t))
2741 ((and (plusp (org-drill-pending-entry-count))
2742 ;; Current drill session is finished, but there are still
2743 ;; more items which need to be reviewed.
2744 (y-or-n-p (format
2745 "You have finished the drill session. However, %d items still
2746 need reviewing. Start a new drill session? "
2747 (org-drill-pending-entry-count))))
2748 (org-drill-again))
2750 (message "You have finished the drill session."))))
2753 (defun org-drill-relearn-item ()
2754 "Make the current item due for revision, and set its last interval to 0.
2755 Makes the item behave as if it has been failed, without actually recording a
2756 failure. This command can be used to 'reset' repetitions for an item."
2757 (interactive)
2758 (org-drill-smart-reschedule 4 0))
2761 (defun org-drill-strip-entry-data ()
2762 (dolist (prop org-drill-scheduling-properties)
2763 (org-delete-property prop))
2764 (org-schedule '(4)))
2767 (defun org-drill-strip-all-data (&optional scope)
2768 "Delete scheduling data from every drill entry in scope. This
2769 function may be useful if you want to give your collection of
2770 entries to someone else. Scope defaults to the current buffer,
2771 and is specified by the argument SCOPE, which accepts the same
2772 values as `org-drill-scope'."
2773 (interactive)
2774 (when (yes-or-no-p
2775 "Delete scheduling data from ALL items in scope: are you sure?")
2776 (cond
2777 ((null scope)
2778 ;; Scope is the current buffer. This means we can use
2779 ;; `org-delete-property-globally', which is faster.
2780 (dolist (prop org-drill-scheduling-properties)
2781 (org-delete-property-globally prop))
2782 (org-map-drill-entries (lambda () (org-schedule '(4))) scope))
2784 (org-map-drill-entries 'org-drill-strip-entry-data scope)))
2785 (message "Done.")))
2788 (defun org-drill-add-cloze-fontification ()
2789 ;; Compute local versions of the regexp for cloze deletions, in case
2790 ;; the left and right delimiters are redefined locally.
2791 (setq-local org-drill-cloze-regexp (org-drill--compute-cloze-regexp))
2792 (setq-local org-drill-cloze-keywords (org-drill--compute-cloze-keywords))
2793 (when org-drill-use-visible-cloze-face-p
2794 (add-to-list 'org-font-lock-extra-keywords
2795 (first org-drill-cloze-keywords))))
2797 (add-hook 'org-font-lock-set-keywords-hook 'org-drill-add-cloze-fontification)
2799 ;; Can't add to org-mode-hook, because local variables won't have been loaded
2800 ;; yet.
2802 ;; (defun org-drill-add-cloze-fontification ()
2803 ;; (when (eql major-mode 'org-mode)
2804 ;; ;; Compute local versions of the regexp for cloze deletions, in case
2805 ;; ;; the left and right delimiters are redefined locally.
2806 ;; (setq-local org-drill-cloze-regexp (org-drill--compute-cloze-regexp))
2807 ;; (setq-local org-drill-cloze-keywords (org-drill--compute-cloze-keywords))
2808 ;; (when org-drill-use-visible-cloze-face-p
2809 ;; (font-lock-add-keywords nil ;'org-mode
2810 ;; org-drill-cloze-keywords
2811 ;; nil))))
2813 ;; XXX
2814 ;; (add-hook 'hack-local-variables-hook
2815 ;; 'org-drill-add-cloze-fontification)
2817 ;; (org-drill-add-cloze-fontification)
2820 ;;; Synching card collections =================================================
2823 (defvar *org-drill-dest-id-table* (make-hash-table :test 'equal))
2826 (defun org-drill-copy-entry-to-other-buffer (dest &optional path)
2827 "Copy the subtree at point to the buffer DEST. The copy will receive
2828 the tag 'imported'."
2829 (block org-drill-copy-entry-to-other-buffer
2830 (save-excursion
2831 (let ((src (current-buffer))
2832 (m nil))
2833 (cl-flet ((paste-tree-here (&optional level)
2834 (org-paste-subtree level)
2835 (org-drill-strip-entry-data)
2836 (org-toggle-tag "imported" 'on)
2837 (org-map-drill-entries
2838 (lambda ()
2839 (let ((id (org-id-get)))
2840 (org-drill-strip-entry-data)
2841 (unless (gethash id *org-drill-dest-id-table*)
2842 (puthash id (point-marker)
2843 *org-drill-dest-id-table*))))
2844 'tree)))
2845 (unless path
2846 (setq path (org-get-outline-path)))
2847 (org-copy-subtree)
2848 (switch-to-buffer dest)
2849 (setq m
2850 (condition-case nil
2851 (org-find-olp path t)
2852 (error ; path does not exist in DEST
2853 (return-from org-drill-copy-entry-to-other-buffer
2854 (cond
2855 ((cdr path)
2856 (org-drill-copy-entry-to-other-buffer
2857 dest (butlast path)))
2859 ;; We've looked all the way up the path
2860 ;; Default to appending to the end of DEST
2861 (goto-char (point-max))
2862 (newline)
2863 (paste-tree-here)))))))
2864 (goto-char m)
2865 (outline-next-heading)
2866 (newline)
2867 (forward-line -1)
2868 (paste-tree-here (1+ (or (org-current-level) 0)))
2869 )))))
2873 (defun org-drill-merge-buffers (src &optional dest ignore-new-items-p)
2874 "SRC and DEST are two org mode buffers containing drill items.
2875 For each drill item in DEST that shares an ID with an item in SRC,
2876 overwrite scheduling data in DEST with data taken from the item in SRC.
2877 This is intended for use when two people are sharing a set of drill items,
2878 one person has made some updates to the item set, and the other person
2879 wants to migrate to the updated set without losing their scheduling data.
2881 By default, any drill items in SRC which do not exist in DEST are
2882 copied into DEST. We attempt to place the copied item in the
2883 equivalent location in DEST to its location in SRC, by matching
2884 the heading hierarchy. However if IGNORE-NEW-ITEMS-P is non-nil,
2885 we simply ignore any items that do not exist in DEST, and do not
2886 copy them across."
2887 (interactive "bImport scheduling info from which buffer?")
2888 (unless dest
2889 (setq dest (current-buffer)))
2890 (setq src (get-buffer src)
2891 dest (get-buffer dest))
2892 (when (yes-or-no-p
2893 (format
2894 (concat "About to overwrite all scheduling data for drill items in `%s' "
2895 "with information taken from matching items in `%s'. Proceed? ")
2896 (buffer-name dest) (buffer-name src)))
2897 ;; Compile list of all IDs in the destination buffer.
2898 (clrhash *org-drill-dest-id-table*)
2899 (with-current-buffer dest
2900 (org-map-drill-entries
2901 (lambda ()
2902 (let ((this-id (org-id-get)))
2903 (when this-id
2904 (puthash this-id (point-marker) *org-drill-dest-id-table*))))
2905 'file))
2906 ;; Look through all entries in source buffer.
2907 (with-current-buffer src
2908 (org-map-drill-entries
2909 (lambda ()
2910 (let ((id (org-id-get))
2911 (last-quality nil) (last-reviewed nil)
2912 (scheduled-time nil))
2913 (cond
2914 ((or (null id)
2915 (not (org-drill-entry-p)))
2916 nil)
2917 ((gethash id *org-drill-dest-id-table*)
2918 ;; This entry matches an entry in dest. Retrieve all its
2919 ;; scheduling data, then go to the matching location in dest
2920 ;; and write the data.
2921 (let ((marker (gethash id *org-drill-dest-id-table*)))
2922 (destructuring-bind (last-interval repetitions failures
2923 total-repeats meanq ease)
2924 (org-drill-get-item-data)
2925 (setq last-reviewed (org-entry-get (point) "DRILL_LAST_REVIEWED")
2926 last-quality (org-entry-get (point) "DRILL_LAST_QUALITY")
2927 scheduled-time (org-get-scheduled-time (point)))
2928 (save-excursion
2929 ;; go to matching entry in destination buffer
2930 (switch-to-buffer (marker-buffer marker))
2931 (goto-char marker)
2932 (org-drill-strip-entry-data)
2933 (unless (zerop total-repeats)
2934 (org-drill-store-item-data last-interval repetitions failures
2935 total-repeats meanq ease)
2936 (if last-quality
2937 (org-set-property "LAST_QUALITY" last-quality)
2938 (org-delete-property "LAST_QUALITY"))
2939 (if last-reviewed
2940 (org-set-property "LAST_REVIEWED" last-reviewed)
2941 (org-delete-property "LAST_REVIEWED"))
2942 (if scheduled-time
2943 (org-schedule nil scheduled-time)))))
2944 (remhash id *org-drill-dest-id-table*)
2945 (free-marker marker)))
2947 ;; item in SRC has ID, but no matching ID in DEST.
2948 ;; It must be a new item that does not exist in DEST.
2949 ;; Copy the entire item to the *end* of DEST.
2950 (unless ignore-new-items-p
2951 (org-drill-copy-entry-to-other-buffer dest))))))
2952 'file))
2953 ;; Finally: there may be some items in DEST which are not in SRC, and
2954 ;; which have been scheduled by another user of DEST. Clear out the
2955 ;; scheduling info from all the unmatched items in DEST.
2956 (with-current-buffer dest
2957 (maphash (lambda (id m)
2958 (goto-char m)
2959 (org-drill-strip-entry-data)
2960 (free-marker m))
2961 *org-drill-dest-id-table*))))
2965 ;;; Card types for learning languages =========================================
2967 ;;; Get spell-number.el from:
2968 ;;; http://www.emacswiki.org/emacs/spell-number.el
2969 (autoload 'spelln-integer-in-words "spell-number")
2972 ;;; `conjugate' card type =====================================================
2973 ;;; See spanish.org for usage
2975 (defvar org-drill-verb-tense-alist
2976 '(("present" "tomato")
2977 ("simple present" "tomato")
2978 ("present indicative" "tomato")
2979 ;; past tenses
2980 ("past" "purple")
2981 ("simple past" "purple")
2982 ("preterite" "purple")
2983 ("imperfect" "darkturquoise")
2984 ("present perfect" "royalblue")
2985 ;; future tenses
2986 ("future" "green")
2987 ;; moods (backgrounds).
2988 ("indicative" nil) ; default
2989 ("subjunctive" "medium blue")
2990 ("conditional" "grey30")
2991 ("negative imperative" "red4")
2992 ("positive imperative" "darkgreen")
2994 "Alist where each entry has the form (TENSE COLOUR), where
2995 TENSE is a string naming a tense in which verbs can be
2996 conjugated, and COLOUR is a string specifying a foreground colour
2997 which will be used by `org-drill-present-verb-conjugation' and
2998 `org-drill-show-answer-verb-conjugation' to fontify the verb and
2999 the name of the tense.")
3002 (defun org-drill-get-verb-conjugation-info ()
3003 "Auxiliary function used by `org-drill-present-verb-conjugation' and
3004 `org-drill-show-answer-verb-conjugation'."
3005 (let ((infinitive (org-entry-get (point) "VERB_INFINITIVE" t))
3006 (inf-hint (org-entry-get (point) "VERB_INFINITIVE_HINT" t))
3007 (translation (org-entry-get (point) "VERB_TRANSLATION" t))
3008 (tense (org-entry-get (point) "VERB_TENSE" nil))
3009 (mood (org-entry-get (point) "VERB_MOOD" nil))
3010 (highlight-face nil))
3011 (unless (and infinitive translation (or tense mood))
3012 (error "Missing information for verb conjugation card (%s, %s, %s, %s) at %s"
3013 infinitive translation tense mood (point)))
3014 (setq tense (if tense (downcase (car (read-from-string tense))))
3015 mood (if mood (downcase (car (read-from-string mood))))
3016 infinitive (car (read-from-string infinitive))
3017 inf-hint (if inf-hint (car (read-from-string inf-hint)))
3018 translation (car (read-from-string translation)))
3019 (setq highlight-face
3020 (list :foreground
3021 (or (second (assoc-string tense org-drill-verb-tense-alist t))
3022 "hotpink")
3023 :background
3024 (second (assoc-string mood org-drill-verb-tense-alist t))))
3025 (setq infinitive (propertize infinitive 'face highlight-face))
3026 (setq translation (propertize translation 'face highlight-face))
3027 (if tense (setq tense (propertize tense 'face highlight-face)))
3028 (if mood (setq mood (propertize mood 'face highlight-face)))
3029 (list infinitive inf-hint translation tense mood)))
3032 (defun org-drill-present-verb-conjugation ()
3033 "Present a drill entry whose card type is 'conjugate'."
3034 (cl-flet ((tense-and-mood-to-string
3035 (tense mood)
3036 (cond
3037 ((and tense mood)
3038 (format "%s tense, %s mood" tense mood))
3039 (tense
3040 (format "%s tense" tense))
3041 (mood
3042 (format "%s mood" mood)))))
3043 (destructuring-bind (infinitive inf-hint translation tense mood)
3044 (org-drill-get-verb-conjugation-info)
3045 (org-drill-present-card-using-text
3046 (cond
3047 ((zerop (random* 2))
3048 (format "\nTranslate the verb\n\n%s\n\nand conjugate for the %s.\n\n"
3049 infinitive (tense-and-mood-to-string tense mood)))
3052 (format "\nGive the verb that means\n\n%s %s\n
3053 and conjugate for the %s.\n\n"
3054 translation
3055 (if inf-hint (format " [HINT: %s]" inf-hint) "")
3056 (tense-and-mood-to-string tense mood))))))))
3059 (defun org-drill-show-answer-verb-conjugation (reschedule-fn)
3060 "Show the answer for a drill item whose card type is 'conjugate'.
3061 RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
3062 returns its return value."
3063 (destructuring-bind (infinitive inf-hint translation tense mood)
3064 (org-drill-get-verb-conjugation-info)
3065 (with-replaced-entry-heading
3066 (format "%s of %s ==> %s\n\n"
3067 (capitalize
3068 (cond
3069 ((and tense mood)
3070 (format "%s tense, %s mood" tense mood))
3071 (tense
3072 (format "%s tense" tense))
3073 (mood
3074 (format "%s mood" mood))))
3075 infinitive translation)
3076 (org-cycle-hide-drawers 'all)
3077 (funcall reschedule-fn))))
3080 ;;; `decline_noun' card type ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3083 (defvar org-drill-noun-gender-alist
3084 '(("masculine" "dodgerblue")
3085 ("masc" "dodgerblue")
3086 ("male" "dodgerblue")
3087 ("m" "dodgerblue")
3088 ("feminine" "orchid")
3089 ("fem" "orchid")
3090 ("female" "orchid")
3091 ("f" "orchid")
3092 ("neuter" "green")
3093 ("neutral" "green")
3094 ("neut" "green")
3095 ("n" "green")
3099 (defun org-drill-get-noun-info ()
3100 "Auxiliary function used by `org-drill-present-noun-declension' and
3101 `org-drill-show-answer-noun-declension'."
3102 (let ((noun (org-entry-get (point) "NOUN" t))
3103 (noun-hint (org-entry-get (point) "NOUN_HINT" t))
3104 (noun-root (org-entry-get (point) "NOUN_ROOT" t))
3105 (noun-gender (org-entry-get (point) "NOUN_GENDER" t))
3106 (translation (org-entry-get (point) "NOUN_TRANSLATION" t))
3107 (highlight-face nil))
3108 (unless (and noun translation)
3109 (error "Missing information for `decline_noun' card (%s, %s, %s, %s) at %s"
3110 noun translation noun-hint noun-root (point)))
3111 (setq noun-root (if noun-root (car (read-from-string noun-root)))
3112 noun (car (read-from-string noun))
3113 noun-gender (downcase (car (read-from-string noun-gender)))
3114 noun-hint (if noun-hint (car (read-from-string noun-hint)))
3115 translation (car (read-from-string translation)))
3116 (setq highlight-face
3117 (list :foreground
3118 (or (second (assoc-string noun-gender
3119 org-drill-noun-gender-alist t))
3120 "red")))
3121 (setq noun (propertize noun 'face highlight-face))
3122 (setq translation (propertize translation 'face highlight-face))
3123 (list noun noun-root noun-gender noun-hint translation)))
3126 (defun org-drill-present-noun-declension ()
3127 "Present a drill entry whose card type is 'decline_noun'."
3128 (destructuring-bind (noun noun-root noun-gender noun-hint translation)
3129 (org-drill-get-noun-info)
3130 (let* ((props (org-entry-properties (point)))
3131 (definite
3132 (cond
3133 ((assoc "DECLINE_DEFINITE" props)
3134 (propertize (if (org-entry-get (point) "DECLINE_DEFINITE")
3135 "definite" "indefinite")
3136 'face 'warning))
3137 (t nil)))
3138 (plural
3139 (cond
3140 ((assoc "DECLINE_PLURAL" props)
3141 (propertize (if (org-entry-get (point) "DECLINE_PLURAL")
3142 "plural" "singular")
3143 'face 'warning))
3144 (t nil))))
3145 (org-drill-present-card-using-text
3146 (cond
3147 ((zerop (random* 2))
3148 (format "\nTranslate the noun\n\n%s (%s)\n\nand list its declensions%s.\n\n"
3149 noun noun-gender
3150 (if (or plural definite)
3151 (format " for the %s %s form" definite plural)
3152 "")))
3154 (format "\nGive the noun that means\n\n%s %s\n
3155 and list its declensions%s.\n\n"
3156 translation
3157 (if noun-hint (format " [HINT: %s]" noun-hint) "")
3158 (if (or plural definite)
3159 (format " for the %s %s form" definite plural)
3160 ""))))))))
3163 (defun org-drill-show-answer-noun-declension (reschedule-fn)
3164 "Show the answer for a drill item whose card type is 'decline_noun'.
3165 RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
3166 returns its return value."
3167 (destructuring-bind (noun noun-root noun-gender noun-hint translation)
3168 (org-drill-get-noun-info)
3169 (with-replaced-entry-heading
3170 (format "Declensions of %s (%s) ==> %s\n\n"
3171 noun noun-gender translation)
3172 (org-cycle-hide-drawers 'all)
3173 (funcall reschedule-fn))))
3176 ;;; `translate_number' card type ==============================================
3177 ;;; See spanish.org for usage
3180 (defun spelln-integer-in-language (n lang)
3181 (let ((spelln-language lang))
3182 (spelln-integer-in-words n)))
3184 (defun org-drill-present-translate-number ()
3185 (let ((num-min (read (org-entry-get (point) "DRILL_NUMBER_MIN")))
3186 (num-max (read (org-entry-get (point) "DRILL_NUMBER_MAX")))
3187 (language (read (org-entry-get (point) "DRILL_LANGUAGE" t)))
3188 (drilled-number 0)
3189 (drilled-number-direction 'to-english)
3190 (highlight-face 'font-lock-warning-face))
3191 (cond
3192 ((not (fboundp 'spelln-integer-in-words))
3193 (message "`spell-number.el' not loaded, skipping 'translate_number' card...")
3194 (sit-for 0.5)
3195 'skip)
3196 ((not (and (numberp num-min) (numberp num-max) language))
3197 (error "Missing language or minimum or maximum numbers for number card"))
3199 (if (> num-min num-max)
3200 (psetf num-min num-max
3201 num-max num-min))
3202 (setq drilled-number
3203 (+ num-min (random* (abs (1+ (- num-max num-min))))))
3204 (setq drilled-number-direction
3205 (if (zerop (random* 2)) 'from-english 'to-english))
3206 (cond
3207 ((eql 'to-english drilled-number-direction)
3208 (org-drill-present-card-using-text
3209 (format "\nTranslate into English:\n\n%s\n"
3210 (propertize
3211 (spelln-integer-in-language drilled-number language)
3212 'face highlight-face))
3213 (spelln-integer-in-language drilled-number 'english-gb)))
3215 (org-drill-present-card-using-text
3216 (format "\nTranslate into %s:\n\n%s\n"
3217 (capitalize (format "%s" language))
3218 (propertize
3219 (spelln-integer-in-language drilled-number 'english-gb)
3220 'face highlight-face))
3221 (spelln-integer-in-language drilled-number language))))))))
3224 ;; (defun org-drill-show-answer-translate-number (reschedule-fn)
3225 ;; (let* ((language (read (org-entry-get (point) "DRILL_LANGUAGE" t)))
3226 ;; (highlight-face 'font-lock-warning-face)
3227 ;; (non-english
3228 ;; (let ((spelln-language language))
3229 ;; (propertize (spelln-integer-in-words *drilled-number*)
3230 ;; 'face highlight-face)))
3231 ;; (english
3232 ;; (let ((spelln-language 'english-gb))
3233 ;; (propertize (spelln-integer-in-words *drilled-number*)
3234 ;; 'face 'highlight-face))))
3235 ;; (with-replaced-entry-text
3236 ;; (cond
3237 ;; ((eql 'to-english *drilled-number-direction*)
3238 ;; (format "\nThe English translation of %s is:\n\n%s\n"
3239 ;; non-english english))
3240 ;; (t
3241 ;; (format "\nThe %s translation of %s is:\n\n%s\n"
3242 ;; (capitalize (format "%s" language))
3243 ;; english non-english)))
3244 ;; (funcall reschedule-fn))))
3247 ;;; `spanish_verb' card type ==================================================
3248 ;;; Not very interesting, but included to demonstrate how a presentation
3249 ;;; function can manipulate which subheading are hidden versus shown.
3252 (defun org-drill-present-spanish-verb ()
3253 (let ((prompt nil)
3254 (reveal-headings nil))
3255 (with-hidden-comments
3256 (with-hidden-cloze-hints
3257 (with-hidden-cloze-text
3258 (case (random* 6)
3260 (org-drill-hide-all-subheadings-except '("Infinitive"))
3261 (setq prompt
3262 (concat "Translate this Spanish verb, and conjugate it "
3263 "for the *present* tense.")
3264 reveal-headings '("English" "Present Tense" "Notes")))
3266 (org-drill-hide-all-subheadings-except '("English"))
3267 (setq prompt (concat "For the *present* tense, conjugate the "
3268 "Spanish translation of this English verb.")
3269 reveal-headings '("Infinitive" "Present Tense" "Notes")))
3271 (org-drill-hide-all-subheadings-except '("Infinitive"))
3272 (setq prompt (concat "Translate this Spanish verb, and "
3273 "conjugate it for the *past* tense.")
3274 reveal-headings '("English" "Past Tense" "Notes")))
3276 (org-drill-hide-all-subheadings-except '("English"))
3277 (setq prompt (concat "For the *past* tense, conjugate the "
3278 "Spanish translation of this English verb.")
3279 reveal-headings '("Infinitive" "Past Tense" "Notes")))
3281 (org-drill-hide-all-subheadings-except '("Infinitive"))
3282 (setq prompt (concat "Translate this Spanish verb, and "
3283 "conjugate it for the *future perfect* tense.")
3284 reveal-headings '("English" "Future Perfect Tense" "Notes")))
3286 (org-drill-hide-all-subheadings-except '("English"))
3287 (setq prompt (concat "For the *future perfect* tense, conjugate the "
3288 "Spanish translation of this English verb.")
3289 reveal-headings '("Infinitive" "Future Perfect Tense" "Notes"))))
3290 (org-cycle-hide-drawers 'all)
3291 (prog1 (org-drill-presentation-prompt)
3292 (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
3295 (provide 'org-drill)