org-drill.el 108 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818
  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.3.3
  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.
  25. (eval-when-compile (require 'cl))
  26. (eval-when-compile (require 'hi-lock))
  27. (require 'org)
  28. (require 'org-id)
  29. (require 'org-learn)
  30. (defgroup org-drill nil
  31. "Options concerning interactive drill sessions in Org mode (org-drill)."
  32. :tag "Org-Drill"
  33. :group 'org-link)
  34. (defcustom org-drill-question-tag
  35. "drill"
  36. "Tag which topics must possess in order to be identified as review topics
  37. by `org-drill'."
  38. :group 'org-drill
  39. :type 'string)
  40. (defcustom org-drill-maximum-items-per-session
  41. 30
  42. "Each drill session will present at most this many topics for review.
  43. Nil means unlimited."
  44. :group 'org-drill
  45. :type '(choice integer (const nil)))
  46. (defcustom org-drill-maximum-duration
  47. 20
  48. "Maximum duration of a drill session, in minutes.
  49. Nil means unlimited."
  50. :group 'org-drill
  51. :type '(choice integer (const nil)))
  52. (defcustom org-drill-failure-quality
  53. 2
  54. "If the quality of recall for an item is this number or lower,
  55. it is regarded as an unambiguous failure, and the repetition
  56. interval for the card is reset to 0 days. If the quality is higher
  57. than this number, it is regarded as successfully recalled, but the
  58. time interval to the next repetition will be lowered if the quality
  59. was near to a fail.
  60. By default this is 2, for SuperMemo-like behaviour. For
  61. Mnemosyne-like behaviour, set it to 1. Other values are not
  62. really sensible."
  63. :group 'org-drill
  64. :type '(choice (const 2) (const 1)))
  65. (defcustom org-drill-forgetting-index
  66. 10
  67. "What percentage of items do you consider it is 'acceptable' to
  68. forget each drill session? The default is 10%. A warning message
  69. is displayed at the end of the session if the percentage forgotten
  70. climbs above this number."
  71. :group 'org-drill
  72. :type 'integer)
  73. (defcustom org-drill-leech-failure-threshold
  74. 15
  75. "If an item is forgotten more than this many times, it is tagged
  76. as a 'leech' item."
  77. :group 'org-drill
  78. :type '(choice integer (const nil)))
  79. (defcustom org-drill-leech-method
  80. 'skip
  81. "How should 'leech items' be handled during drill sessions?
  82. Possible values:
  83. - nil :: Leech items are treated the same as normal items.
  84. - skip :: Leech items are not included in drill sessions.
  85. - warn :: Leech items are still included in drill sessions,
  86. but a warning message is printed when each leech item is
  87. presented."
  88. :group 'org-drill
  89. :type '(choice (const 'warn) (const 'skip) (const nil)))
  90. (defface org-drill-visible-cloze-face
  91. '((t (:foreground "darkseagreen")))
  92. "The face used to hide the contents of cloze phrases."
  93. :group 'org-drill)
  94. (defface org-drill-visible-cloze-hint-face
  95. '((t (:foreground "dark slate blue")))
  96. "The face used to hide the contents of cloze phrases."
  97. :group 'org-drill)
  98. (defface org-drill-hidden-cloze-face
  99. '((t (:foreground "deep sky blue" :background "blue")))
  100. "The face used to hide the contents of cloze phrases."
  101. :group 'org-drill)
  102. (defcustom org-drill-use-visible-cloze-face-p
  103. nil
  104. "Use a special face to highlight cloze-deleted text in org mode
  105. buffers?"
  106. :group 'org-drill
  107. :type 'boolean)
  108. (defcustom org-drill-hide-item-headings-p
  109. nil
  110. "Conceal the contents of the main heading of each item during drill
  111. sessions? You may want to enable this behaviour if item headings or tags
  112. contain information that could 'give away' the answer."
  113. :group 'org-drill
  114. :type 'boolean)
  115. (defcustom org-drill-new-count-color
  116. "royal blue"
  117. "Foreground colour used to display the count of remaining new items
  118. during a drill session."
  119. :group 'org-drill
  120. :type 'color)
  121. (defcustom org-drill-mature-count-color
  122. "green"
  123. "Foreground colour used to display the count of remaining mature items
  124. during a drill session. Mature items are due for review, but are not new."
  125. :group 'org-drill
  126. :type 'color)
  127. (defcustom org-drill-failed-count-color
  128. "red"
  129. "Foreground colour used to display the count of remaining failed items
  130. during a drill session."
  131. :group 'org-drill
  132. :type 'color)
  133. (defcustom org-drill-done-count-color
  134. "sienna"
  135. "Foreground colour used to display the count of reviewed items
  136. during a drill session."
  137. :group 'org-drill
  138. :type 'color)
  139. (setplist 'org-drill-cloze-overlay-defaults
  140. '(display "[...]"
  141. face org-drill-hidden-cloze-face
  142. window t))
  143. (setplist 'org-drill-hidden-text-overlay
  144. '(invisible t))
  145. (setplist 'org-drill-replaced-text-overlay
  146. '(display "Replaced text"
  147. face default
  148. window t))
  149. (defvar org-drill-cloze-regexp
  150. ;; ver 1 "[^][]\\(\\[[^][][^]]*\\]\\)"
  151. ;; ver 2 "\\(\\[.*?\\]\\|^[^[[:cntrl:]]*?\\]\\|\\[.*?$\\)"
  152. ;; ver 3! "\\(\\[.*?\\]\\|\\[.*?[[:cntrl:]]+.*?\\]\\)"
  153. "\\(\\[[[:cntrl:][:graph:][:space:]]*?\\)\\(\\||.+?\\)\\(\\]\\)")
  154. (defvar org-drill-cloze-keywords
  155. `((,org-drill-cloze-regexp
  156. (1 'org-drill-visible-cloze-face nil)
  157. (2 'org-drill-visible-cloze-hint-face t)
  158. (3 'org-drill-visible-cloze-face nil)
  159. )))
  160. (defcustom org-drill-card-type-alist
  161. '((nil . org-drill-present-simple-card)
  162. ("simple" . org-drill-present-simple-card)
  163. ("twosided" . org-drill-present-two-sided-card)
  164. ("multisided" . org-drill-present-multi-sided-card)
  165. ("hide1cloze" . org-drill-present-multicloze-hide1)
  166. ("hide2cloze" . org-drill-present-multicloze-hide2)
  167. ("show1cloze" . org-drill-present-multicloze-show1)
  168. ("show2cloze" . org-drill-present-multicloze-show2)
  169. ("multicloze" . org-drill-present-multicloze-hide1)
  170. ("hidefirst" . org-drill-present-multicloze-hide-first)
  171. ("hidelast" . org-drill-present-multicloze-hide-last)
  172. ("hide1_firstmore" . org-drill-present-multicloze-hide1-firstmore)
  173. ("show1_lastmore" . org-drill-present-multicloze-show1-lastmore)
  174. ("show1_firstless" . org-drill-present-multicloze-show1-firstless)
  175. ("conjugate" org-drill-present-verb-conjugation
  176. org-drill-show-answer-verb-conjugation)
  177. ("spanish_verb" . org-drill-present-spanish-verb)
  178. ("translate_number" org-drill-present-translate-number
  179. org-drill-show-answer-translate-number))
  180. "Alist associating card types with presentation functions. Each entry in the
  181. alist takes one of two forms:
  182. 1. (CARDTYPE . QUESTION-FN), where CARDTYPE is a string or nil (for default),
  183. and QUESTION-FN is a function which takes no arguments and returns a boolean
  184. value.
  185. 2. (CARDTYPE QUESTION-FN ANSWER-FN), where ANSWER-FN is a function that takes
  186. one argument -- the argument is a function that itself takes no arguments.
  187. ANSWER-FN is called with the point on the active item's
  188. heading, just prior to displaying the item's 'answer'. It can therefore be
  189. used to modify the appearance of the answer. ANSWER-FN must call its argument
  190. before returning. (Its argument is a function that prompts the user and
  191. performs rescheduling)."
  192. :group 'org-drill
  193. :type '(alist :key-type (choice string (const nil)) :value-type function))
  194. (defcustom org-drill-scope
  195. 'file
  196. "The scope in which to search for drill items when conducting a
  197. drill session. This can be any of:
  198. file The current buffer, respecting the restriction if any.
  199. This is the default.
  200. tree The subtree started with the entry at point
  201. file-no-restriction The current buffer, without restriction
  202. file-with-archives The current buffer, and any archives associated with it.
  203. agenda All agenda files
  204. agenda-with-archives All agenda files with any archive files associated
  205. with them.
  206. directory All files with the extension '.org' in the same
  207. directory as the current file (includes the current
  208. file if it is an .org file.)
  209. (FILE1 FILE2 ...) If this is a list, all files in the list will be scanned.
  210. "
  211. ;; Note -- meanings differ slightly from the argument to org-map-entries:
  212. ;; 'file' means current file/buffer, respecting any restriction
  213. ;; 'file-no-restriction' means current file/buffer, ignoring restrictions
  214. ;; 'directory' means all *.org files in current directory
  215. :group 'org-drill
  216. :type '(choice (const 'file) (const 'tree) (const 'file-no-restriction)
  217. (const 'file-with-archives) (const 'agenda)
  218. (const 'agenda-with-archives) (const 'directory)
  219. list))
  220. (defcustom org-drill-save-buffers-after-drill-sessions-p
  221. t
  222. "If non-nil, prompt to save all modified buffers after a drill session
  223. finishes."
  224. :group 'org-drill
  225. :type 'boolean)
  226. (defcustom org-drill-spaced-repetition-algorithm
  227. 'sm5
  228. "Which SuperMemo spaced repetition algorithm to use for scheduling items.
  229. Available choices are:
  230. - SM2 :: the SM2 algorithm, used in SuperMemo 2.0
  231. - SM5 :: the SM5 algorithm, used in SuperMemo 5.0
  232. - Simple8 :: a modified version of the SM8 algorithm. SM8 is used in
  233. SuperMemo 98. The version implemented here is simplified in that while it
  234. 'learns' the difficulty of each item using quality grades and number of
  235. failures, it does not modify the matrix of values that
  236. governs how fast the inter-repetition intervals increase. A method for
  237. adjusting intervals when items are reviewed early or late has been taken
  238. from SM11, a later version of the algorithm, and included in Simple8."
  239. :group 'org-drill
  240. :type '(choice (const 'sm2) (const 'sm5) (const 'simple8)))
  241. (defcustom org-drill-optimal-factor-matrix
  242. nil
  243. "DO NOT CHANGE THE VALUE OF THIS VARIABLE.
  244. Persistent matrix of optimal factors, used by the SuperMemo SM5 algorithm.
  245. The matrix is saved (using the 'customize' facility) at the end of each
  246. drill session.
  247. Over time, values in the matrix will adapt to the individual user's
  248. pace of learning."
  249. :group 'org-drill
  250. :type 'sexp)
  251. (defcustom org-drill-add-random-noise-to-intervals-p
  252. nil
  253. "If true, the number of days until an item's next repetition
  254. will vary slightly from the interval calculated by the SM2
  255. algorithm. The variation is very small when the interval is
  256. small, but scales up with the interval."
  257. :group 'org-drill
  258. :type 'boolean)
  259. (defcustom org-drill-adjust-intervals-for-early-and-late-repetitions-p
  260. nil
  261. "If true, when the student successfully reviews an item 1 or more days
  262. before or after the scheduled review date, this will affect that date of
  263. the item's next scheduled review, according to the algorithm presented at
  264. [[http://www.supermemo.com/english/algsm11.htm#Advanced%20repetitions]].
  265. Items that were reviewed early will have their next review date brought
  266. forward. Those that were reviewed late will have their next review
  267. date postponed further.
  268. Note that this option currently has no effect if the SM2 algorithm
  269. is used."
  270. :group 'org-drill
  271. :type 'boolean)
  272. (defcustom org-drill-cram-hours
  273. 12
  274. "When in cram mode, items are considered due for review if
  275. they were reviewed at least this many hours ago."
  276. :group 'org-drill
  277. :type 'integer)
  278. ;;; NEW items have never been presented in a drill session before.
  279. ;;; MATURE items HAVE been presented at least once before.
  280. ;;; - YOUNG mature items were scheduled no more than
  281. ;;; ORG-DRILL-DAYS-BEFORE-OLD days after their last
  282. ;;; repetition. These items will have been learned 'recently' and will have a
  283. ;;; low repetition count.
  284. ;;; - OLD mature items have intervals greater than
  285. ;;; ORG-DRILL-DAYS-BEFORE-OLD.
  286. ;;; - OVERDUE items are past their scheduled review date by more than
  287. ;;; LAST-INTERVAL * (ORG-DRILL-OVERDUE-INTERVAL-FACTOR - 1) days,
  288. ;;; regardless of young/old status.
  289. (defcustom org-drill-days-before-old
  290. 10
  291. "When an item's inter-repetition interval rises above this value in days,
  292. it is no longer considered a 'young' (recently learned) item."
  293. :group 'org-drill
  294. :type 'integer)
  295. (defcustom org-drill-overdue-interval-factor
  296. 1.2
  297. "An item is considered overdue if its scheduled review date is
  298. more than (ORG-DRILL-OVERDUE-INTERVAL-FACTOR - 1) * LAST-INTERVAL
  299. days in the past. For example, a value of 1.2 means an additional
  300. 20% of the last scheduled interval is allowed to elapse before
  301. the item is overdue. A value of 1.0 means no extra time is
  302. allowed at all - items are immediately considered overdue if
  303. there is even one day's delay in reviewing them. This variable
  304. should never be less than 1.0."
  305. :group 'org-drill
  306. :type 'float)
  307. (defcustom org-drill-learn-fraction
  308. 0.5
  309. "Fraction between 0 and 1 that governs how quickly the spaces
  310. between successive repetitions increase, for all items. The
  311. default value is 0.5. Higher values make spaces increase more
  312. quickly with each successful repetition. You should only change
  313. this in small increments (for example 0.05-0.1) as it has an
  314. exponential effect on inter-repetition spacing."
  315. :group 'org-drill
  316. :type 'float)
  317. (defvar *org-drill-session-qualities* nil)
  318. (defvar *org-drill-start-time* 0)
  319. (defvar *org-drill-new-entries* nil)
  320. (defvar *org-drill-dormant-entry-count* 0)
  321. (defvar *org-drill-due-entry-count* 0)
  322. (defvar *org-drill-overdue-entry-count* 0)
  323. (defvar *org-drill-due-tomorrow-count* 0)
  324. (defvar *org-drill-overdue-entries* nil
  325. "List of markers for items that are considered 'overdue', based on
  326. the value of ORG-DRILL-OVERDUE-INTERVAL-FACTOR.")
  327. (defvar *org-drill-young-mature-entries* nil
  328. "List of markers for mature entries whose last inter-repetition
  329. interval was <= ORG-DRILL-DAYS-BEFORE-OLD days.")
  330. (defvar *org-drill-old-mature-entries* nil
  331. "List of markers for mature entries whose last inter-repetition
  332. interval was greater than ORG-DRILL-DAYS-BEFORE-OLD days.")
  333. (defvar *org-drill-failed-entries* nil)
  334. (defvar *org-drill-again-entries* nil)
  335. (defvar *org-drill-done-entries* nil)
  336. (defvar *org-drill-current-item* nil
  337. "Set to the marker for the item currently being tested.")
  338. (defvar *org-drill-cram-mode* nil
  339. "Are we in 'cram mode', where all items are considered due
  340. for review unless they were already reviewed in the recent past?")
  341. (defvar org-drill-scheduling-properties
  342. '("LEARN_DATA" "DRILL_LAST_INTERVAL" "DRILL_REPEATS_SINCE_FAIL"
  343. "DRILL_TOTAL_REPEATS" "DRILL_FAILURE_COUNT" "DRILL_AVERAGE_QUALITY"
  344. "DRILL_EASE" "DRILL_LAST_QUALITY" "DRILL_LAST_REVIEWED"))
  345. ;;; Make the above settings safe as file-local variables.
  346. (put 'org-drill-question-tag 'safe-local-variable 'stringp)
  347. (put 'org-drill-maximum-items-per-session 'safe-local-variable
  348. '(lambda (val) (or (integerp val) (null val))))
  349. (put 'org-drill-maximum-duration 'safe-local-variable
  350. '(lambda (val) (or (integerp val) (null val))))
  351. (put 'org-drill-failure-quality 'safe-local-variable 'integerp)
  352. (put 'org-drill-forgetting-index 'safe-local-variable 'integerp)
  353. (put 'org-drill-leech-failure-threshold 'safe-local-variable 'integerp)
  354. (put 'org-drill-leech-method 'safe-local-variable
  355. '(lambda (val) (memq val '(nil skip warn))))
  356. (put 'org-drill-use-visible-cloze-face-p 'safe-local-variable 'booleanp)
  357. (put 'org-drill-hide-item-headings-p 'safe-local-variable 'booleanp)
  358. (put 'org-drill-spaced-repetition-algorithm 'safe-local-variable
  359. '(lambda (val) (memq val '(simple8 sm5 sm2))))
  360. (put 'org-drill-add-random-noise-to-intervals-p 'safe-local-variable 'booleanp)
  361. (put 'org-drill-adjust-intervals-for-early-and-late-repetitions-p
  362. 'safe-local-variable 'booleanp)
  363. (put 'org-drill-cram-hours 'safe-local-variable 'integerp)
  364. (put 'org-drill-learn-fraction 'safe-local-variable 'floatp)
  365. (put 'org-drill-days-before-old 'safe-local-variable 'integerp)
  366. (put 'org-drill-overdue-interval-factor 'safe-local-variable 'floatp)
  367. (put 'org-drill-scope 'safe-local-variable
  368. '(lambda (val) (or (symbolp val) (listp val))))
  369. (put 'org-drill-save-buffers-after-drill-sessions-p 'safe-local-variable 'booleanp)
  370. ;;;; Utilities ================================================================
  371. (defun free-marker (m)
  372. (set-marker m nil))
  373. (defmacro pop-random (place)
  374. (let ((idx (gensym)))
  375. `(if (null ,place)
  376. nil
  377. (let ((,idx (random* (length ,place))))
  378. (prog1 (nth ,idx ,place)
  379. (setq ,place (append (subseq ,place 0 ,idx)
  380. (subseq ,place (1+ ,idx)))))))))
  381. (defmacro push-end (val place)
  382. "Add VAL to the end of the sequence stored in PLACE. Return the new
  383. value."
  384. `(setq ,place (append ,place (list ,val))))
  385. (defun shuffle-list (list)
  386. "Randomly permute the elements of LIST (all permutations equally likely)."
  387. ;; Adapted from 'shuffle-vector' in cookie1.el
  388. (let ((i 0)
  389. j
  390. temp
  391. (len (length list)))
  392. (while (< i len)
  393. (setq j (+ i (random* (- len i))))
  394. (setq temp (nth i list))
  395. (setf (nth i list) (nth j list))
  396. (setf (nth j list) temp)
  397. (setq i (1+ i))))
  398. list)
  399. (defun round-float (floatnum fix)
  400. "Round the floating point number FLOATNUM to FIX decimal places.
  401. Example: (round-float 3.56755765 3) -> 3.568"
  402. (let ((n (expt 10 fix)))
  403. (/ (float (round (* floatnum n))) n)))
  404. (defun command-keybinding-to-string (cmd)
  405. "Return a human-readable description of the key/keys to which the command
  406. CMD is bound, or nil if it is not bound to a key."
  407. (let ((key (where-is-internal cmd overriding-local-map t)))
  408. (if key (key-description key))))
  409. (defun time-to-inactive-org-timestamp (time)
  410. (format-time-string
  411. (concat "[" (substring (cdr org-time-stamp-formats) 1 -1) "]")
  412. time))
  413. (defun org-map-drill-entries (func &optional scope &rest skip)
  414. "Like `org-map-entries', but only drill entries are processed."
  415. (let ((org-drill-scope (or scope org-drill-scope)))
  416. (apply 'org-map-entries func
  417. (concat "+" org-drill-question-tag)
  418. (case org-drill-scope
  419. (file nil)
  420. (file-no-restriction 'file)
  421. (directory
  422. (directory-files (file-name-directory (buffer-file-name))
  423. t "\\.org$"))
  424. (t org-drill-scope))
  425. skip)))
  426. (defmacro with-hidden-cloze-text (&rest body)
  427. `(progn
  428. (org-drill-hide-clozed-text)
  429. (unwind-protect
  430. (progn
  431. ,@body)
  432. (org-drill-unhide-clozed-text))))
  433. (defmacro with-hidden-cloze-hints (&rest body)
  434. `(progn
  435. (org-drill-hide-cloze-hints)
  436. (unwind-protect
  437. (progn
  438. ,@body)
  439. (org-drill-unhide-text))))
  440. (defmacro with-hidden-comments (&rest body)
  441. `(progn
  442. (if org-drill-hide-item-headings-p
  443. (org-drill-hide-heading-at-point))
  444. (org-drill-hide-comments)
  445. (unwind-protect
  446. (progn
  447. ,@body)
  448. (org-drill-unhide-text))))
  449. (defun org-drill-days-since-last-review ()
  450. "Nil means a last review date has not yet been stored for
  451. the item.
  452. Zero means it was reviewed today.
  453. A positive number means it was reviewed that many days ago.
  454. A negative number means the date of last review is in the future --
  455. this should never happen."
  456. (let ((datestr (org-entry-get (point) "DRILL_LAST_REVIEWED")))
  457. (when datestr
  458. (- (time-to-days (current-time))
  459. (time-to-days (apply 'encode-time
  460. (org-parse-time-string datestr)))))))
  461. (defun org-drill-hours-since-last-review ()
  462. "Like `org-drill-days-since-last-review', but return value is
  463. in hours rather than days."
  464. (let ((datestr (org-entry-get (point) "DRILL_LAST_REVIEWED")))
  465. (when datestr
  466. (floor
  467. (/ (- (time-to-seconds (current-time))
  468. (time-to-seconds (apply 'encode-time
  469. (org-parse-time-string datestr))))
  470. (* 60 60))))))
  471. (defun org-drill-entry-p (&optional marker)
  472. "Is MARKER, or the point, in a 'drill item'? This will return nil if
  473. the point is inside a subheading of a drill item -- to handle that
  474. situation use `org-part-of-drill-entry-p'."
  475. (save-excursion
  476. (when marker
  477. (org-drill-goto-entry marker))
  478. (member org-drill-question-tag (org-get-local-tags))))
  479. (defun org-drill-goto-entry (marker)
  480. (switch-to-buffer (marker-buffer marker))
  481. (goto-char marker))
  482. (defun org-part-of-drill-entry-p ()
  483. "Is the current entry either the main heading of a 'drill item',
  484. or a subheading within a drill item?"
  485. (or (org-drill-entry-p)
  486. ;; Does this heading INHERIT the drill tag
  487. (member org-drill-question-tag (org-get-tags-at))))
  488. (defun org-drill-goto-drill-entry-heading ()
  489. "Move the point to the heading which holds the :drill: tag for this
  490. drill entry."
  491. (unless (org-at-heading-p)
  492. (org-back-to-heading))
  493. (unless (org-part-of-drill-entry-p)
  494. (error "Point is not inside a drill entry"))
  495. (while (not (org-drill-entry-p))
  496. (unless (org-up-heading-safe)
  497. (error "Cannot find a parent heading that is marked as a drill entry"))))
  498. (defun org-drill-entry-leech-p ()
  499. "Is the current entry a 'leech item'?"
  500. (and (org-drill-entry-p)
  501. (member "leech" (org-get-local-tags))))
  502. ;; (defun org-drill-entry-due-p ()
  503. ;; (cond
  504. ;; (*org-drill-cram-mode*
  505. ;; (let ((hours (org-drill-hours-since-last-review)))
  506. ;; (and (org-drill-entry-p)
  507. ;; (or (null hours)
  508. ;; (>= hours org-drill-cram-hours)))))
  509. ;; (t
  510. ;; (let ((item-time (org-get-scheduled-time (point))))
  511. ;; (and (org-drill-entry-p)
  512. ;; (or (not (eql 'skip org-drill-leech-method))
  513. ;; (not (org-drill-entry-leech-p)))
  514. ;; (or (null item-time) ; not scheduled
  515. ;; (not (minusp ; scheduled for today/in past
  516. ;; (- (time-to-days (current-time))
  517. ;; (time-to-days item-time))))))))))
  518. (defun org-drill-entry-days-overdue ()
  519. "Returns:
  520. - NIL if the item is not to be regarded as scheduled for review at all.
  521. This is the case if it is not a drill item, or if it is a leech item
  522. that we wish to skip, or if we are in cram mode and have already reviewed
  523. the item within the last few hours.
  524. - 0 if the item is new, or if it scheduled for review today.
  525. - A negative integer - item is scheduled that many days in the future.
  526. - A positive integer - item is scheduled that many days in the past."
  527. (cond
  528. (*org-drill-cram-mode*
  529. (let ((hours (org-drill-hours-since-last-review)))
  530. (and (org-drill-entry-p)
  531. (or (null hours)
  532. (>= hours org-drill-cram-hours))
  533. 0)))
  534. (t
  535. (let ((item-time (org-get-scheduled-time (point))))
  536. (cond
  537. ((or (not (org-drill-entry-p))
  538. (and (eql 'skip org-drill-leech-method)
  539. (org-drill-entry-leech-p)))
  540. nil)
  541. ((null item-time) ; not scheduled -> due now
  542. 0)
  543. (t
  544. (- (time-to-days (current-time))
  545. (time-to-days item-time))))))))
  546. (defun org-drill-entry-overdue-p (&optional days-overdue last-interval)
  547. "Returns true if entry that is scheduled DAYS-OVERDUE dasy in the past,
  548. and whose last inter-repetition interval was LAST-INTERVAL, should be
  549. considered 'overdue'. If the arguments are not given they are extracted
  550. from the entry at point."
  551. (unless days-overdue
  552. (setq days-overdue (org-drill-entry-days-overdue)))
  553. (unless last-interval
  554. (setq last-interval (org-drill-entry-last-interval 1)))
  555. (and (numberp days-overdue)
  556. (> days-overdue 1) ; enforce a sane minimum 'overdue' gap
  557. ;;(> due org-drill-days-before-overdue)
  558. (> (/ (+ days-overdue last-interval 1.0) last-interval)
  559. org-drill-overdue-interval-factor)))
  560. (defun org-drill-entry-due-p ()
  561. (let ((due (org-drill-entry-days-overdue)))
  562. (and (not (null due))
  563. (not (minusp due)))))
  564. (defun org-drill-entry-new-p ()
  565. (and (org-drill-entry-p)
  566. (let ((item-time (org-get-scheduled-time (point))))
  567. (null item-time))))
  568. (defun org-drill-entry-last-quality (&optional default)
  569. (let ((quality (org-entry-get (point) "DRILL_LAST_QUALITY")))
  570. (if quality
  571. (string-to-number quality)
  572. default)))
  573. (defun org-drill-entry-failure-count ()
  574. (let ((quality (org-entry-get (point) "DRILL_FAILURE_COUNT")))
  575. (if quality
  576. (string-to-number quality)
  577. 0)))
  578. (defun org-drill-entry-average-quality (&optional default)
  579. (let ((val (org-entry-get (point) "DRILL_AVERAGE_QUALITY")))
  580. (if val
  581. (string-to-number val)
  582. (or default nil))))
  583. (defun org-drill-entry-last-interval (&optional default)
  584. (let ((val (org-entry-get (point) "DRILL_LAST_INTERVAL")))
  585. (if val
  586. (string-to-number val)
  587. (or default 0))))
  588. (defun org-drill-entry-repeats-since-fail (&optional default)
  589. (let ((val (org-entry-get (point) "DRILL_REPEATS_SINCE_FAIL")))
  590. (if val
  591. (string-to-number val)
  592. (or default 0))))
  593. (defun org-drill-entry-total-repeats (&optional default)
  594. (let ((val (org-entry-get (point) "DRILL_TOTAL_REPEATS")))
  595. (if val
  596. (string-to-number val)
  597. (or default 0))))
  598. (defun org-drill-entry-ease (&optional default)
  599. (let ((val (org-entry-get (point) "DRILL_EASE")))
  600. (if val
  601. (string-to-number val)
  602. default)))
  603. ;;; From http://www.supermemo.com/english/ol/sm5.htm
  604. (defun org-drill-random-dispersal-factor ()
  605. "Returns a random number between 0.5 and 1.5."
  606. (let ((a 0.047)
  607. (b 0.092)
  608. (p (- (random* 1.0) 0.5)))
  609. (flet ((sign (n)
  610. (cond ((zerop n) 0)
  611. ((plusp n) 1)
  612. (t -1))))
  613. (/ (+ 100 (* (* (/ -1 b) (log (- 1 (* (/ b a ) (abs p)))))
  614. (sign p)))
  615. 100.0))))
  616. (defun pseudonormal (mean variation)
  617. "Random numbers in a pseudo-normal distribution with mean MEAN, range
  618. MEAN-VARIATION to MEAN+VARIATION"
  619. (+ (random* variation)
  620. (random* variation)
  621. (- variation)
  622. mean))
  623. (defun org-drill-early-interval-factor (optimal-factor
  624. optimal-interval
  625. days-ahead)
  626. "Arguments:
  627. - OPTIMAL-FACTOR: interval-factor if the item had been tested
  628. exactly when it was supposed to be.
  629. - OPTIMAL-INTERVAL: interval for next repetition (days) if the item had been
  630. tested exactly when it was supposed to be.
  631. - DAYS-AHEAD: how many days ahead of time the item was reviewed.
  632. Returns an adjusted optimal factor which should be used to
  633. calculate the next interval, instead of the optimal factor found
  634. in the matrix."
  635. (let ((delta-ofmax (* (1- optimal-factor)
  636. (/ (+ optimal-interval
  637. (* 0.6 optimal-interval) -1) (1- optimal-interval)))))
  638. (- optimal-factor
  639. (* delta-ofmax (/ days-ahead (+ days-ahead (* 0.6 optimal-interval)))))))
  640. (defun org-drill-get-item-data ()
  641. "Returns a list of 6 items, containing all the stored recall
  642. data for the item at point:
  643. - LAST-INTERVAL is the interval in days that was used to schedule the item's
  644. current review date.
  645. - REPEATS is the number of items the item has been successfully recalled without
  646. without any failures. It is reset to 0 upon failure to recall the item.
  647. - FAILURES is the total number of times the user has failed to recall the item.
  648. - TOTAL-REPEATS includes both successful and unsuccessful repetitions.
  649. - AVERAGE-QUALITY is the mean quality of recall of the item over
  650. all its repetitions, successful and unsuccessful.
  651. - EASE is a number reflecting how easy the item is to learn. Higher is easier.
  652. "
  653. (let ((learn-str (org-entry-get (point) "LEARN_DATA"))
  654. (repeats (org-drill-entry-total-repeats :missing)))
  655. (cond
  656. (learn-str
  657. (let ((learn-data (or (and learn-str
  658. (read learn-str))
  659. (copy-list initial-repetition-state))))
  660. (list (nth 0 learn-data) ; last interval
  661. (nth 1 learn-data) ; repetitions
  662. (org-drill-entry-failure-count)
  663. (nth 1 learn-data)
  664. (org-drill-entry-last-quality)
  665. (nth 2 learn-data) ; EF
  666. )))
  667. ((not (eql :missing repeats))
  668. (list (org-drill-entry-last-interval)
  669. (org-drill-entry-repeats-since-fail)
  670. (org-drill-entry-failure-count)
  671. (org-drill-entry-total-repeats)
  672. (org-drill-entry-average-quality)
  673. (org-drill-entry-ease)))
  674. (t ; virgin item
  675. (list 0 0 0 0 nil nil)))))
  676. (defun org-drill-store-item-data (last-interval repeats failures
  677. total-repeats meanq
  678. ease)
  679. "Stores the given data in the item at point."
  680. (org-entry-delete (point) "LEARN_DATA")
  681. (org-set-property "DRILL_LAST_INTERVAL"
  682. (number-to-string (round-float last-interval 4)))
  683. (org-set-property "DRILL_REPEATS_SINCE_FAIL" (number-to-string repeats))
  684. (org-set-property "DRILL_TOTAL_REPEATS" (number-to-string total-repeats))
  685. (org-set-property "DRILL_FAILURE_COUNT" (number-to-string failures))
  686. (org-set-property "DRILL_AVERAGE_QUALITY"
  687. (number-to-string (round-float meanq 3)))
  688. (org-set-property "DRILL_EASE"
  689. (number-to-string (round-float ease 3))))
  690. ;;; SM2 Algorithm =============================================================
  691. (defun determine-next-interval-sm2 (last-interval n ef quality
  692. failures meanq total-repeats)
  693. "Arguments:
  694. - LAST-INTERVAL -- the number of days since the item was last reviewed.
  695. - REPEATS -- the number of times the item has been successfully reviewed
  696. - EF -- the 'easiness factor'
  697. - QUALITY -- 0 to 5
  698. Returns a list: (INTERVAL REPEATS EF FAILURES MEAN TOTAL-REPEATS OFMATRIX), where:
  699. - INTERVAL is the number of days until the item should next be reviewed
  700. - REPEATS is incremented by 1.
  701. - EF is modified based on the recall quality for the item.
  702. - OF-MATRIX is not modified."
  703. (assert (> n 0))
  704. (assert (and (>= quality 0) (<= quality 5)))
  705. (if (<= quality org-drill-failure-quality)
  706. ;; When an item is failed, its interval is reset to 0,
  707. ;; but its EF is unchanged
  708. (list -1 1 ef (1+ failures) meanq (1+ total-repeats)
  709. org-drill-optimal-factor-matrix)
  710. ;; else:
  711. (let* ((next-ef (modify-e-factor ef quality))
  712. (interval
  713. (cond
  714. ((<= n 1) 1)
  715. ((= n 2)
  716. (cond
  717. (org-drill-add-random-noise-to-intervals-p
  718. (case quality
  719. (5 6)
  720. (4 4)
  721. (3 3)
  722. (2 1)
  723. (t -1)))
  724. (t 6)))
  725. (t (* last-interval next-ef)))))
  726. (list (if org-drill-add-random-noise-to-intervals-p
  727. (+ last-interval (* (- interval last-interval)
  728. (org-drill-random-dispersal-factor)))
  729. interval)
  730. (1+ n)
  731. next-ef
  732. failures meanq (1+ total-repeats)
  733. org-drill-optimal-factor-matrix))))
  734. ;;; SM5 Algorithm =============================================================
  735. (defun inter-repetition-interval-sm5 (last-interval n ef &optional of-matrix)
  736. (let ((of (get-optimal-factor n ef (or of-matrix
  737. org-drill-optimal-factor-matrix))))
  738. (if (= 1 n)
  739. of
  740. (* of last-interval))))
  741. (defun determine-next-interval-sm5 (last-interval n ef quality
  742. failures meanq total-repeats
  743. of-matrix &optional delta-days)
  744. (if (zerop n) (setq n 1))
  745. (if (null ef) (setq ef 2.5))
  746. (assert (> n 0))
  747. (assert (and (>= quality 0) (<= quality 5)))
  748. (unless of-matrix
  749. (setq of-matrix org-drill-optimal-factor-matrix))
  750. (setq of-matrix (cl-copy-tree of-matrix))
  751. (setq meanq (if meanq
  752. (/ (+ quality (* meanq total-repeats 1.0))
  753. (1+ total-repeats))
  754. quality))
  755. (let ((next-ef (modify-e-factor ef quality))
  756. (old-ef ef)
  757. (new-of (modify-of (get-optimal-factor n ef of-matrix)
  758. quality org-drill-learn-fraction))
  759. (interval nil))
  760. (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
  761. delta-days (minusp delta-days))
  762. (setq new-of (org-drill-early-interval-factor
  763. (get-optimal-factor n ef of-matrix)
  764. (inter-repetition-interval-sm5
  765. last-interval n ef of-matrix)
  766. delta-days)))
  767. (setq of-matrix
  768. (set-optimal-factor n next-ef of-matrix
  769. (round-float new-of 3))) ; round OF to 3 d.p.
  770. (setq ef next-ef)
  771. (cond
  772. ;; "Failed" -- reset repetitions to 0,
  773. ((<= quality org-drill-failure-quality)
  774. (list -1 1 old-ef (1+ failures) meanq (1+ total-repeats)
  775. of-matrix)) ; Not clear if OF matrix is supposed to be
  776. ; preserved
  777. ;; For a zero-based quality of 4 or 5, don't repeat
  778. ;; ((and (>= quality 4)
  779. ;; (not org-learn-always-reschedule))
  780. ;; (list 0 (1+ n) ef failures meanq
  781. ;; (1+ total-repeats) of-matrix)) ; 0 interval = unschedule
  782. (t
  783. (setq interval (inter-repetition-interval-sm5
  784. last-interval n ef of-matrix))
  785. (if org-drill-add-random-noise-to-intervals-p
  786. (setq interval (* interval (org-drill-random-dispersal-factor))))
  787. (list interval
  788. (1+ n)
  789. ef
  790. failures
  791. meanq
  792. (1+ total-repeats)
  793. of-matrix)))))
  794. ;;; Simple8 Algorithm =========================================================
  795. (defun org-drill-simple8-first-interval (failures)
  796. "Arguments:
  797. - FAILURES: integer >= 0. The total number of times the item has
  798. been forgotten, ever.
  799. Returns the optimal FIRST interval for an item which has previously been
  800. forgotten on FAILURES occasions."
  801. (* 2.4849 (exp (* -0.057 failures))))
  802. (defun org-drill-simple8-interval-factor (ease repetition)
  803. "Arguments:
  804. - EASE: floating point number >= 1.2. Corresponds to `AF' in SM8 algorithm.
  805. - REPETITION: the number of times the item has been tested.
  806. 1 is the first repetition (ie the second trial).
  807. Returns:
  808. The factor by which the last interval should be
  809. multiplied to give the next interval. Corresponds to `RF' or `OF'."
  810. (+ 1.2 (* (- ease 1.2) (expt org-drill-learn-fraction (log repetition 2)))))
  811. (defun org-drill-simple8-quality->ease (quality)
  812. "Returns the ease (`AF' in the SM8 algorithm) which corresponds
  813. to a mean item quality of QUALITY."
  814. (+ (* 0.0542 (expt quality 4))
  815. (* -0.4848 (expt quality 3))
  816. (* 1.4916 (expt quality 2))
  817. (* -1.2403 quality)
  818. 1.4515))
  819. (defun determine-next-interval-simple8 (last-interval repeats quality
  820. failures meanq totaln
  821. &optional delta-days)
  822. "Arguments:
  823. - LAST-INTERVAL -- the number of days since the item was last reviewed.
  824. - REPEATS -- the number of times the item has been successfully reviewed
  825. - EASE -- the 'easiness factor'
  826. - QUALITY -- 0 to 5
  827. - DELTA-DAYS -- how many days overdue was the item when it was reviewed.
  828. 0 = reviewed on the scheduled day. +N = N days overdue.
  829. -N = reviewed N days early.
  830. Returns the new item data, as a list of 6 values:
  831. - NEXT-INTERVAL
  832. - REPEATS
  833. - EASE
  834. - FAILURES
  835. - AVERAGE-QUALITY
  836. - TOTAL-REPEATS.
  837. See the documentation for `org-drill-get-item-data' for a description of these."
  838. (assert (>= repeats 0))
  839. (assert (and (>= quality 0) (<= quality 5)))
  840. (assert (or (null meanq) (and (>= meanq 0) (<= meanq 5))))
  841. (let ((next-interval nil))
  842. (setf meanq (if meanq
  843. (/ (+ quality (* meanq totaln 1.0)) (1+ totaln))
  844. quality))
  845. (cond
  846. ((<= quality org-drill-failure-quality)
  847. (incf failures)
  848. (setf repeats 0
  849. next-interval -1))
  850. ((or (zerop repeats)
  851. (zerop last-interval))
  852. (setf next-interval (org-drill-simple8-first-interval failures))
  853. (incf repeats)
  854. (incf totaln))
  855. (t
  856. (let* ((use-n
  857. (if (and
  858. org-drill-adjust-intervals-for-early-and-late-repetitions-p
  859. (numberp delta-days) (plusp delta-days)
  860. (plusp last-interval))
  861. (+ repeats (min 1 (/ delta-days last-interval 1.0)))
  862. repeats))
  863. (factor (org-drill-simple8-interval-factor
  864. (org-drill-simple8-quality->ease meanq) use-n))
  865. (next-int (* last-interval factor)))
  866. (when (and org-drill-adjust-intervals-for-early-and-late-repetitions-p
  867. (numberp delta-days) (minusp delta-days))
  868. ;; The item was reviewed earlier than scheduled.
  869. (setf factor (org-drill-early-interval-factor
  870. factor next-int (abs delta-days))
  871. next-int (* last-interval factor)))
  872. (setf next-interval next-int)
  873. (incf repeats)
  874. (incf totaln))))
  875. (list
  876. (if (and org-drill-add-random-noise-to-intervals-p
  877. (plusp next-interval))
  878. (* next-interval (org-drill-random-dispersal-factor))
  879. next-interval)
  880. repeats
  881. (org-drill-simple8-quality->ease meanq)
  882. failures
  883. meanq
  884. totaln
  885. )))
  886. ;;; Essentially copied from `org-learn.el', but modified to
  887. ;;; optionally call the SM2 or simple8 functions.
  888. (defun org-drill-smart-reschedule (quality &optional days-ahead)
  889. "If DAYS-AHEAD is supplied it must be a positive integer. The
  890. item will be scheduled exactly this many days into the future."
  891. (let ((delta-days (- (time-to-days (current-time))
  892. (time-to-days (or (org-get-scheduled-time (point))
  893. (current-time)))))
  894. (ofmatrix org-drill-optimal-factor-matrix)
  895. ;; Entries can have weights, 1 by default. Intervals are divided by the
  896. ;; item's weight, so an item with a weight of 2 will have all intervals
  897. ;; halved, meaning you will end up reviewing it twice as often.
  898. ;; Useful for entries which randomly present any of several facts.
  899. (weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
  900. (if (stringp weight)
  901. (setq weight (read weight)))
  902. (destructuring-bind (last-interval repetitions failures
  903. total-repeats meanq ease)
  904. (org-drill-get-item-data)
  905. (destructuring-bind (next-interval repetitions ease
  906. failures meanq total-repeats
  907. &optional new-ofmatrix)
  908. (case org-drill-spaced-repetition-algorithm
  909. (sm5 (determine-next-interval-sm5 last-interval repetitions
  910. ease quality failures
  911. meanq total-repeats ofmatrix))
  912. (sm2 (determine-next-interval-sm2 last-interval repetitions
  913. ease quality failures
  914. meanq total-repeats))
  915. (simple8 (determine-next-interval-simple8 last-interval repetitions
  916. quality failures meanq
  917. total-repeats
  918. delta-days)))
  919. (if (numberp days-ahead)
  920. (setq next-interval days-ahead))
  921. (org-drill-store-item-data next-interval repetitions failures
  922. total-repeats meanq ease)
  923. (if (and (null days-ahead)
  924. (numberp weight) (plusp weight)
  925. (not (minusp next-interval)))
  926. (setq next-interval (max 1.0 (/ next-interval weight))))
  927. (if (eql 'sm5 org-drill-spaced-repetition-algorithm)
  928. (setq org-drill-optimal-factor-matrix new-ofmatrix))
  929. (cond
  930. ((= 0 days-ahead)
  931. (org-schedule t))
  932. ((minusp days-ahead)
  933. (org-schedule nil (current-time)))
  934. (t
  935. (org-schedule nil (time-add (current-time)
  936. (days-to-time
  937. (round next-interval))))))))))
  938. (defun org-drill-hypothetical-next-review-date (quality)
  939. "Returns an integer representing the number of days into the future
  940. that the current item would be scheduled, based on a recall quality
  941. of QUALITY."
  942. (let ((weight (org-entry-get (point) "DRILL_CARD_WEIGHT")))
  943. (destructuring-bind (last-interval repetitions failures
  944. total-repeats meanq ease)
  945. (org-drill-get-item-data)
  946. (if (stringp weight)
  947. (setq weight (read weight)))
  948. (destructuring-bind (next-interval repetitions ease
  949. failures meanq total-repeats
  950. &optional ofmatrix)
  951. (case org-drill-spaced-repetition-algorithm
  952. (sm5 (determine-next-interval-sm5 last-interval repetitions
  953. ease quality failures
  954. meanq total-repeats
  955. org-drill-optimal-factor-matrix))
  956. (sm2 (determine-next-interval-sm2 last-interval repetitions
  957. ease quality failures
  958. meanq total-repeats))
  959. (simple8 (determine-next-interval-simple8 last-interval repetitions
  960. quality failures meanq
  961. total-repeats)))
  962. (cond
  963. ((not (plusp next-interval))
  964. 0)
  965. ((and (numberp weight) (plusp weight))
  966. (max 1.0 (/ next-interval weight)))
  967. (t
  968. next-interval))))))
  969. (defun org-drill-hypothetical-next-review-dates ()
  970. (let ((intervals nil))
  971. (dotimes (q 6)
  972. (push (max (or (car intervals) 0)
  973. (org-drill-hypothetical-next-review-date q))
  974. intervals))
  975. (reverse intervals)))
  976. (defun org-drill-reschedule ()
  977. "Returns quality rating (0-5), or nil if the user quit."
  978. (let ((ch nil)
  979. (input nil)
  980. (next-review-dates (org-drill-hypothetical-next-review-dates)))
  981. (save-excursion
  982. (while (not (memq ch '(?q ?e ?0 ?1 ?2 ?3 ?4 ?5)))
  983. (setq input (read-key-sequence
  984. (if (eq ch ??)
  985. (format "0-2 Means you have forgotten the item.
  986. 3-5 Means you have remembered the item.
  987. 0 - Completely forgot.
  988. 1 - Even after seeing the answer, it still took a bit to sink in.
  989. 2 - After seeing the answer, you remembered it.
  990. 3 - It took you awhile, but you finally remembered. (+%s days)
  991. 4 - After a little bit of thought you remembered. (+%s days)
  992. 5 - You remembered the item really easily. (+%s days)
  993. How well did you do? (0-5, ?=help, e=edit, t=tags, q=quit)"
  994. (round (nth 3 next-review-dates))
  995. (round (nth 4 next-review-dates))
  996. (round (nth 5 next-review-dates)))
  997. "How well did you do? (0-5, ?=help, e=edit, t=tags, q=quit)")))
  998. (cond
  999. ((stringp input)
  1000. (setq ch (elt input 0)))
  1001. ((and (vectorp input) (symbolp (elt input 0)))
  1002. (case (elt input 0)
  1003. (up (ignore-errors (forward-line -1)))
  1004. (down (ignore-errors (forward-line 1)))
  1005. (left (ignore-errors (backward-char)))
  1006. (right (ignore-errors (forward-char)))
  1007. (prior (ignore-errors (scroll-down))) ; pgup
  1008. (next (ignore-errors (scroll-up))))) ; pgdn
  1009. ((and (vectorp input) (listp (elt input 0))
  1010. (eventp (elt input 0)))
  1011. (case (car (elt input 0))
  1012. (wheel-up (ignore-errors (mwheel-scroll (elt input 0))))
  1013. (wheel-down (ignore-errors (mwheel-scroll (elt input 0)))))))
  1014. (if (eql ch ?t)
  1015. (org-set-tags-command))))
  1016. (cond
  1017. ((and (>= ch ?0) (<= ch ?5))
  1018. (let ((quality (- ch ?0))
  1019. (failures (org-drill-entry-failure-count)))
  1020. (save-excursion
  1021. (org-drill-smart-reschedule quality
  1022. (nth quality next-review-dates)))
  1023. (push quality *org-drill-session-qualities*)
  1024. (cond
  1025. ((<= quality org-drill-failure-quality)
  1026. (when org-drill-leech-failure-threshold
  1027. ;;(setq failures (if failures (string-to-number failures) 0))
  1028. ;; (org-set-property "DRILL_FAILURE_COUNT"
  1029. ;; (format "%d" (1+ failures)))
  1030. (if (> (1+ failures) org-drill-leech-failure-threshold)
  1031. (org-toggle-tag "leech" 'on))))
  1032. (t
  1033. (let ((scheduled-time (org-get-scheduled-time (point))))
  1034. (when scheduled-time
  1035. (message "Next review in %d days"
  1036. (- (time-to-days scheduled-time)
  1037. (time-to-days (current-time))))
  1038. (sit-for 0.5)))))
  1039. (org-set-property "DRILL_LAST_QUALITY" (format "%d" quality))
  1040. (org-set-property "DRILL_LAST_REVIEWED"
  1041. (time-to-inactive-org-timestamp (current-time)))
  1042. quality))
  1043. ((= ch ?e)
  1044. 'edit)
  1045. (t
  1046. nil))))
  1047. ;; (defun org-drill-hide-all-subheadings-except (heading-list)
  1048. ;; "Returns a list containing the position of each immediate subheading of
  1049. ;; the current topic."
  1050. ;; (let ((drill-entry-level (org-current-level))
  1051. ;; (drill-sections nil)
  1052. ;; (drill-heading nil))
  1053. ;; (org-show-subtree)
  1054. ;; (save-excursion
  1055. ;; (org-map-entries
  1056. ;; (lambda ()
  1057. ;; (when (and (not (outline-invisible-p))
  1058. ;; (> (org-current-level) drill-entry-level))
  1059. ;; (setq drill-heading (org-get-heading t))
  1060. ;; (unless (and (= (org-current-level) (1+ drill-entry-level))
  1061. ;; (member drill-heading heading-list))
  1062. ;; (hide-subtree))
  1063. ;; (push (point) drill-sections)))
  1064. ;; "" 'tree))
  1065. ;; (reverse drill-sections)))
  1066. (defun org-drill-hide-subheadings-if (test)
  1067. "TEST is a function taking no arguments. TEST will be called for each
  1068. of the immediate subheadings of the current drill item, with the point
  1069. on the relevant subheading. TEST should return nil if the subheading is
  1070. to be revealed, non-nil if it is to be hidden.
  1071. Returns a list containing the position of each immediate subheading of
  1072. the current topic."
  1073. (let ((drill-entry-level (org-current-level))
  1074. (drill-sections nil))
  1075. (org-show-subtree)
  1076. (save-excursion
  1077. (org-map-entries
  1078. (lambda ()
  1079. (when (and (not (outline-invisible-p))
  1080. (> (org-current-level) drill-entry-level))
  1081. (when (or (/= (org-current-level) (1+ drill-entry-level))
  1082. (funcall test))
  1083. (hide-subtree))
  1084. (push (point) drill-sections)))
  1085. "" 'tree))
  1086. (reverse drill-sections)))
  1087. (defun org-drill-hide-all-subheadings-except (heading-list)
  1088. (org-drill-hide-subheadings-if
  1089. (lambda () (let ((drill-heading (org-get-heading t)))
  1090. (not (member drill-heading heading-list))))))
  1091. (defun org-drill-presentation-prompt (&rest fmt-and-args)
  1092. (let* ((item-start-time (current-time))
  1093. (input nil)
  1094. (ch nil)
  1095. (last-second 0)
  1096. (mature-entry-count (+ (length *org-drill-young-mature-entries*)
  1097. (length *org-drill-old-mature-entries*)
  1098. (length *org-drill-overdue-entries*)))
  1099. (status (first (org-drill-entry-status)))
  1100. (prompt
  1101. (if fmt-and-args
  1102. (apply 'format
  1103. (first fmt-and-args)
  1104. (rest fmt-and-args))
  1105. (concat "Press key for answer, "
  1106. "e=edit, t=tags, s=skip, q=quit."))))
  1107. (setq prompt
  1108. (format "%s %s %s %s %s %s"
  1109. (propertize
  1110. (char-to-string
  1111. (case status
  1112. (:new ?N) (:young ?Y) (:old ?o) (:overdue ?!)
  1113. (:failed ?F) (t ??)))
  1114. 'face `(:foreground
  1115. ,(case status
  1116. (:new org-drill-new-count-color)
  1117. ((:young :old) org-drill-mature-count-color)
  1118. ((:overdue :failed) org-drill-failed-count-color)
  1119. (t org-drill-done-count-color))))
  1120. (propertize
  1121. (number-to-string (length *org-drill-done-entries*))
  1122. 'face `(:foreground ,org-drill-done-count-color)
  1123. 'help-echo "The number of items you have reviewed this session.")
  1124. (propertize
  1125. (number-to-string (+ (length *org-drill-again-entries*)
  1126. (length *org-drill-failed-entries*)))
  1127. 'face `(:foreground ,org-drill-failed-count-color)
  1128. 'help-echo (concat "The number of items that you failed, "
  1129. "and need to review again."))
  1130. (propertize
  1131. (number-to-string mature-entry-count)
  1132. 'face `(:foreground ,org-drill-mature-count-color)
  1133. 'help-echo "The number of old items due for review.")
  1134. (propertize
  1135. (number-to-string (length *org-drill-new-entries*))
  1136. 'face `(:foreground ,org-drill-new-count-color)
  1137. 'help-echo (concat "The number of new items that you "
  1138. "have never reviewed."))
  1139. prompt))
  1140. (if (and (eql 'warn org-drill-leech-method)
  1141. (org-drill-entry-leech-p))
  1142. (setq prompt (concat
  1143. (propertize "!!! LEECH ITEM !!!
  1144. You seem to be having a lot of trouble memorising this item.
  1145. Consider reformulating the item to make it easier to remember.\n"
  1146. 'face '(:foreground "red"))
  1147. prompt)))
  1148. (while (memq ch '(nil ?t))
  1149. (setq ch nil)
  1150. (while (not (input-pending-p))
  1151. (let ((elapsed (time-subtract (current-time) item-start-time)))
  1152. (message (concat (if (>= (time-to-seconds elapsed) (* 60 60))
  1153. "++:++ "
  1154. (format-time-string "%M:%S " elapsed))
  1155. prompt))
  1156. (sit-for 1)))
  1157. (setq input (read-key-sequence nil))
  1158. (if (stringp input) (setq ch (elt input 0)))
  1159. (if (eql ch ?t)
  1160. (org-set-tags-command)))
  1161. (case ch
  1162. (?q nil)
  1163. (?e 'edit)
  1164. (?s 'skip)
  1165. (otherwise t))))
  1166. (defun org-pos-in-regexp (pos regexp &optional nlines)
  1167. (save-excursion
  1168. (goto-char pos)
  1169. (org-in-regexp regexp nlines)))
  1170. (defun org-drill-hide-region (beg end &optional text)
  1171. "Hide the buffer region between BEG and END with an 'invisible text'
  1172. visual overlay, or with the string TEXT if it is supplied."
  1173. (let ((ovl (make-overlay beg end)))
  1174. (overlay-put ovl 'category
  1175. 'org-drill-hidden-text-overlay)
  1176. (when (stringp text)
  1177. (overlay-put ovl 'invisible nil)
  1178. (overlay-put ovl 'face 'default)
  1179. (overlay-put ovl 'display text))))
  1180. (defun org-drill-hide-heading-at-point (&optional text)
  1181. (unless (org-at-heading-p)
  1182. (error "Point is not on a heading."))
  1183. (save-excursion
  1184. (let ((beg (point)))
  1185. (end-of-line)
  1186. (org-drill-hide-region beg (point) text))))
  1187. (defun org-drill-hide-comments ()
  1188. (save-excursion
  1189. (while (re-search-forward "^#.*$" nil t)
  1190. (org-drill-hide-region (match-beginning 0) (match-end 0)))))
  1191. (defun org-drill-unhide-text ()
  1192. ;; This will also unhide the item's heading.
  1193. (save-excursion
  1194. (dolist (ovl (overlays-in (point-min) (point-max)))
  1195. (when (eql 'org-drill-hidden-text-overlay (overlay-get ovl 'category))
  1196. (delete-overlay ovl)))))
  1197. (defun org-drill-hide-clozed-text ()
  1198. (save-excursion
  1199. (while (re-search-forward org-drill-cloze-regexp nil t)
  1200. ;; Don't hide org links, partly because they might contain inline
  1201. ;; images which we want to keep visible
  1202. (unless (save-match-data
  1203. (org-pos-in-regexp (match-beginning 0)
  1204. org-bracket-link-regexp 1))
  1205. (org-drill-hide-matched-cloze-text)))))
  1206. (defun org-drill-hide-matched-cloze-text ()
  1207. "Hide the current match with a 'cloze' visual overlay."
  1208. (let ((ovl (make-overlay (match-beginning 0) (match-end 0))))
  1209. (overlay-put ovl 'category
  1210. 'org-drill-cloze-overlay-defaults)
  1211. (when (find ?| (match-string 0))
  1212. (let ((hint (substring-no-properties
  1213. (match-string 0)
  1214. (1+ (position ?| (match-string 0)))
  1215. (1- (length (match-string 0))))))
  1216. (overlay-put
  1217. ovl 'display
  1218. ;; If hint is like `X...' then display [X...]
  1219. ;; otherwise display [...X]
  1220. (format (if (string-match-p "\\.\\.\\." hint) "[%s]" "[%s...]")
  1221. hint))))))
  1222. (defun org-drill-hide-cloze-hints ()
  1223. (save-excursion
  1224. (while (re-search-forward org-drill-cloze-regexp nil t)
  1225. (unless (or (save-match-data
  1226. (org-pos-in-regexp (match-beginning 0)
  1227. org-bracket-link-regexp 1))
  1228. (null (match-beginning 2))) ; hint subexpression matched
  1229. (org-drill-hide-region (match-beginning 2) (match-end 2))))))
  1230. (defmacro with-replaced-entry-text (text &rest body)
  1231. "During the execution of BODY, the entire text of the current entry is
  1232. concealed by an overlay that displays the string TEXT."
  1233. `(progn
  1234. (org-drill-replace-entry-text ,text)
  1235. (unwind-protect
  1236. (progn
  1237. ,@body)
  1238. (org-drill-unreplace-entry-text))))
  1239. (defun org-drill-replace-entry-text (text)
  1240. "Make an overlay that conceals the entire text of the item, not
  1241. including properties or the contents of subheadings. The overlay shows
  1242. the string TEXT.
  1243. Note: does not actually alter the item."
  1244. (let ((ovl (make-overlay (point-min)
  1245. (save-excursion
  1246. (outline-next-heading)
  1247. (point)))))
  1248. (overlay-put ovl 'category
  1249. 'org-drill-replaced-text-overlay)
  1250. (overlay-put ovl 'display text)))
  1251. (defun org-drill-unreplace-entry-text ()
  1252. (save-excursion
  1253. (dolist (ovl (overlays-in (point-min) (point-max)))
  1254. (when (eql 'org-drill-replaced-text-overlay (overlay-get ovl 'category))
  1255. (delete-overlay ovl)))))
  1256. (defmacro with-replaced-entry-heading (heading &rest body)
  1257. `(progn
  1258. (org-drill-replace-entry-heading ,heading)
  1259. (unwind-protect
  1260. (progn
  1261. ,@body)
  1262. (org-drill-unhide-text))))
  1263. (defun org-drill-replace-entry-heading (heading)
  1264. "Make an overlay that conceals the heading of the item. The overlay shows
  1265. the string TEXT.
  1266. Note: does not actually alter the item."
  1267. (org-drill-hide-heading-at-point heading))
  1268. (defun org-drill-unhide-clozed-text ()
  1269. (save-excursion
  1270. (dolist (ovl (overlays-in (point-min) (point-max)))
  1271. (when (eql 'org-drill-cloze-overlay-defaults (overlay-get ovl 'category))
  1272. (delete-overlay ovl)))))
  1273. (defun org-drill-get-entry-text (&optional keep-properties-p)
  1274. (let ((text (org-agenda-get-some-entry-text (point-marker) 100)))
  1275. (if keep-properties-p
  1276. text
  1277. (substring-no-properties text))))
  1278. (defun org-drill-entry-empty-p ()
  1279. (zerop (length (org-drill-get-entry-text))))
  1280. ;;; Presentation functions ====================================================
  1281. ;; Each of these is called with point on topic heading. Each needs to show the
  1282. ;; topic in the form of a 'question' or with some information 'hidden', as
  1283. ;; appropriate for the card type. The user should then be prompted to press a
  1284. ;; key. The function should then reveal either the 'answer' or the entire
  1285. ;; topic, and should return t if the user chose to see the answer and rate their
  1286. ;; recall, nil if they chose to quit.
  1287. (defun org-drill-present-simple-card ()
  1288. (with-hidden-comments
  1289. (with-hidden-cloze-hints
  1290. (with-hidden-cloze-text
  1291. (org-drill-hide-all-subheadings-except nil)
  1292. (org-display-inline-images t)
  1293. (org-cycle-hide-drawers 'all)
  1294. (prog1 (org-drill-presentation-prompt)
  1295. (org-drill-hide-subheadings-if 'org-drill-entry-p))))))
  1296. (defun org-drill-present-default-answer (reschedule-fn)
  1297. (org-drill-hide-subheadings-if 'org-drill-entry-p)
  1298. (org-drill-unhide-clozed-text)
  1299. (with-hidden-cloze-hints
  1300. (funcall reschedule-fn)))
  1301. (defun org-drill-present-two-sided-card ()
  1302. (with-hidden-comments
  1303. (with-hidden-cloze-hints
  1304. (with-hidden-cloze-text
  1305. (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
  1306. (when drill-sections
  1307. (save-excursion
  1308. (goto-char (nth (random* (min 2 (length drill-sections)))
  1309. drill-sections))
  1310. (org-show-subtree)))
  1311. (org-display-inline-images t)
  1312. (org-cycle-hide-drawers 'all)
  1313. (prog1 (org-drill-presentation-prompt)
  1314. (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
  1315. (defun org-drill-present-multi-sided-card ()
  1316. (with-hidden-comments
  1317. (with-hidden-cloze-hints
  1318. (with-hidden-cloze-text
  1319. (let ((drill-sections (org-drill-hide-all-subheadings-except nil)))
  1320. (when drill-sections
  1321. (save-excursion
  1322. (goto-char (nth (random* (length drill-sections)) drill-sections))
  1323. (org-show-subtree)))
  1324. (org-display-inline-images t)
  1325. (org-cycle-hide-drawers 'all)
  1326. (prog1 (org-drill-presentation-prompt)
  1327. (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
  1328. (defun org-drill-present-multicloze-hide-n (number-to-hide
  1329. &optional
  1330. force-show-first
  1331. force-show-last
  1332. force-hide-first)
  1333. "Hides NUMBER-TO-HIDE pieces of text that are marked for cloze deletion,
  1334. chosen at random.
  1335. If NUMBER-TO-HIDE is negative, show only (ABS NUMBER-TO-HIDE) pieces,
  1336. hiding all the rest.
  1337. If FORCE-HIDE-FIRST is non-nil, force the first piece of text to be one of
  1338. the hidden items.
  1339. If FORCE-SHOW-FIRST is non-nil, never hide the first piece of text.
  1340. If FORCE-SHOW-LAST is non-nil, never hide the last piece of text.
  1341. If the number of text pieces in the item is less than
  1342. NUMBER-TO-HIDE, then all text pieces will be hidden (except the first or last
  1343. items if FORCE-SHOW-FIRST or FORCE-SHOW-LAST is non-nil)."
  1344. (with-hidden-comments
  1345. (with-hidden-cloze-hints
  1346. (let ((item-end nil)
  1347. (match-count 0)
  1348. (body-start (or (cdr (org-get-property-block))
  1349. (point))))
  1350. (if (and force-hide-first force-show-first)
  1351. (error "FORCE-HIDE-FIRST and FORCE-SHOW-FIRST are mutually exclusive"))
  1352. (org-drill-hide-all-subheadings-except nil)
  1353. (save-excursion
  1354. (outline-next-heading)
  1355. (setq item-end (point)))
  1356. (save-excursion
  1357. (goto-char body-start)
  1358. (while (re-search-forward org-drill-cloze-regexp item-end t)
  1359. (let ((in-regexp? (save-match-data
  1360. (org-pos-in-regexp (match-beginning 0)
  1361. org-bracket-link-regexp 1))))
  1362. (unless in-regexp?
  1363. (incf match-count)))))
  1364. (if (minusp number-to-hide)
  1365. (setq number-to-hide (+ match-count number-to-hide)))
  1366. (when (plusp match-count)
  1367. (let* ((positions (shuffle-list (loop for i from 1
  1368. to match-count
  1369. collect i)))
  1370. (match-nums nil)
  1371. (cnt nil))
  1372. (if force-hide-first
  1373. ;; Force '1' to be in the list, and to be the first item
  1374. ;; in the list.
  1375. (setq positions (cons 1 (remove 1 positions))))
  1376. (if force-show-first
  1377. (setq positions (remove 1 positions)))
  1378. (if force-show-last
  1379. (setq positions (remove match-count positions)))
  1380. (setq match-nums
  1381. (subseq positions
  1382. 0 (min number-to-hide (length positions))))
  1383. ;; (dolist (pos-to-hide match-nums)
  1384. (save-excursion
  1385. (goto-char body-start)
  1386. (setq cnt 0)
  1387. (while (re-search-forward org-drill-cloze-regexp item-end t)
  1388. (unless (save-match-data
  1389. (org-pos-in-regexp (match-beginning 0)
  1390. org-bracket-link-regexp 1))
  1391. (incf cnt)
  1392. (if (memq cnt match-nums)
  1393. (org-drill-hide-matched-cloze-text)))))))
  1394. ;; (loop
  1395. ;; do (re-search-forward org-drill-cloze-regexp
  1396. ;; item-end t pos-to-hide)
  1397. ;; while (org-pos-in-regexp (match-beginning 0)
  1398. ;; org-bracket-link-regexp 1))
  1399. ;; (org-drill-hide-matched-cloze-text)))))
  1400. (org-display-inline-images t)
  1401. (org-cycle-hide-drawers 'all)
  1402. (prog1 (org-drill-presentation-prompt)
  1403. (org-drill-hide-subheadings-if 'org-drill-entry-p)
  1404. (org-drill-unhide-clozed-text))))))
  1405. (defun org-drill-present-multicloze-hide-nth (to-hide)
  1406. "Hide the TO-HIDE'th piece of clozed text. 1 is the first piece. If
  1407. TO-HIDE is negative, count backwards, so -1 means the last item, -2
  1408. the second to last, etc."
  1409. (with-hidden-comments
  1410. (with-hidden-cloze-hints
  1411. (let ((item-end nil)
  1412. (match-count 0)
  1413. (body-start (or (cdr (org-get-property-block))
  1414. (point)))
  1415. (cnt 0))
  1416. (org-drill-hide-all-subheadings-except nil)
  1417. (save-excursion
  1418. (outline-next-heading)
  1419. (setq item-end (point)))
  1420. (save-excursion
  1421. (goto-char body-start)
  1422. (while (re-search-forward org-drill-cloze-regexp item-end t)
  1423. (let ((in-regexp? (save-match-data
  1424. (org-pos-in-regexp (match-beginning 0)
  1425. org-bracket-link-regexp 1))))
  1426. (unless in-regexp?
  1427. (incf match-count)))))
  1428. (if (minusp to-hide)
  1429. (setq to-hide (+ 1 to-hide match-count)))
  1430. (cond
  1431. ((or (not (plusp match-count))
  1432. (> to-hide match-count))
  1433. nil)
  1434. (t
  1435. (save-excursion
  1436. (goto-char body-start)
  1437. (setq cnt 0)
  1438. (while (re-search-forward org-drill-cloze-regexp item-end t)
  1439. (unless (save-match-data
  1440. (org-pos-in-regexp (match-beginning 0)
  1441. org-bracket-link-regexp 1))
  1442. (incf cnt)
  1443. (if (= cnt to-hide)
  1444. (org-drill-hide-matched-cloze-text)))))))
  1445. (org-display-inline-images t)
  1446. (org-cycle-hide-drawers 'all)
  1447. (prog1 (org-drill-presentation-prompt)
  1448. (org-drill-hide-subheadings-if 'org-drill-entry-p)
  1449. (org-drill-unhide-clozed-text))))))
  1450. (defun org-drill-present-multicloze-hide1 ()
  1451. "Hides one of the pieces of text that are marked for cloze deletion,
  1452. chosen at random."
  1453. (org-drill-present-multicloze-hide-n 1))
  1454. (defun org-drill-present-multicloze-hide2 ()
  1455. "Hides two of the pieces of text that are marked for cloze deletion,
  1456. chosen at random."
  1457. (org-drill-present-multicloze-hide-n 2))
  1458. (defun org-drill-present-multicloze-hide-first ()
  1459. "Hides the first piece of text that is marked for cloze deletion."
  1460. (org-drill-present-multicloze-hide-nth 1))
  1461. (defun org-drill-present-multicloze-hide-last ()
  1462. "Hides the last piece of text that is marked for cloze deletion."
  1463. (org-drill-present-multicloze-hide-nth -1))
  1464. (defun org-drill-present-multicloze-hide1-firstmore ()
  1465. "Three out of every four repetitions, hides the FIRST piece of
  1466. text that is marked for cloze deletion. One out of every four
  1467. repetitions, hide one of the other pieces of text, chosen at
  1468. random."
  1469. ;; The 'firstmore' and 'lastmore' functions used to randomly choose whether
  1470. ;; to hide the 'favoured' piece of text. However even when the chance of
  1471. ;; hiding it was set quite high (80%), the outcome was too unpredictable over
  1472. ;; the small number of repetitions where most learning takes place for each
  1473. ;; item. In other words, the actual frequency during the first 10 repetitions
  1474. ;; was often very different from 80%. Hence we use modulo instead.
  1475. (if (zerop (mod (1+ (org-drill-entry-total-repeats 0)) 4))
  1476. ;; 25% of time, hide any item except the first
  1477. (org-drill-present-multicloze-hide-n 1 t)
  1478. ;; 75% of time, hide first item
  1479. (org-drill-present-multicloze-hide-first)))
  1480. (defun org-drill-present-multicloze-show1-lastmore ()
  1481. "Three out of every four repetitions, hides all pieces except
  1482. the last. One out of every four repetitions, shows any random
  1483. piece. The effect is similar to 'show1cloze' except that the last
  1484. item is much less likely to be the item that is visible."
  1485. (if (zerop (mod (1+ (org-drill-entry-total-repeats 0)) 4))
  1486. ;; 25% of time, show any item except the last
  1487. (org-drill-present-multicloze-hide-n -1 nil nil t)
  1488. ;; 75% of time, show the LAST item
  1489. (org-drill-present-multicloze-hide-n -1 nil t)))
  1490. (defun org-drill-present-multicloze-show1-firstless ()
  1491. "Three out of every four repetitions, hides all pieces except
  1492. one, where the shown piece is guaranteed NOT to be the first
  1493. piece. One out of every four repetitions, shows any random
  1494. piece. The effect is similar to 'show1cloze' except that the
  1495. first item is much less likely to be the item that is visible."
  1496. (if (zerop (mod (1+ (org-drill-entry-total-repeats 0)) 4))
  1497. ;; 25% of time, show the first item
  1498. (org-drill-present-multicloze-hide-n -1 t)
  1499. ;; 75% of time, show any item, except the first
  1500. (org-drill-present-multicloze-hide-n -1 nil nil t)))
  1501. (defun org-drill-present-multicloze-show1 ()
  1502. "Similar to `org-drill-present-multicloze-hide1', but hides all
  1503. the pieces of text that are marked for cloze deletion, except for one
  1504. piece which is chosen at random."
  1505. (org-drill-present-multicloze-hide-n -1))
  1506. (defun org-drill-present-multicloze-show2 ()
  1507. "Similar to `org-drill-present-multicloze-show1', but reveals two
  1508. pieces rather than one."
  1509. (org-drill-present-multicloze-hide-n -2))
  1510. ;; (defun org-drill-present-multicloze-show1 ()
  1511. ;; "Similar to `org-drill-present-multicloze-hide1', but hides all
  1512. ;; the pieces of text that are marked for cloze deletion, except for one
  1513. ;; piece which is chosen at random."
  1514. ;; (with-hidden-comments
  1515. ;; (with-hidden-cloze-hints
  1516. ;; (let ((item-end nil)
  1517. ;; (match-count 0)
  1518. ;; (body-start (or (cdr (org-get-property-block))
  1519. ;; (point))))
  1520. ;; (org-drill-hide-all-subheadings-except nil)
  1521. ;; (save-excursion
  1522. ;; (outline-next-heading)
  1523. ;; (setq item-end (point)))
  1524. ;; (save-excursion
  1525. ;; (goto-char body-start)
  1526. ;; (while (re-search-forward org-drill-cloze-regexp item-end t)
  1527. ;; (incf match-count)))
  1528. ;; (when (plusp match-count)
  1529. ;; (let ((match-to-hide (random* match-count)))
  1530. ;; (save-excursion
  1531. ;; (goto-char body-start)
  1532. ;; (dotimes (n match-count)
  1533. ;; (re-search-forward org-drill-cloze-regexp
  1534. ;; item-end t)
  1535. ;; (unless (= n match-to-hide)
  1536. ;; (org-drill-hide-matched-cloze-text))))))
  1537. ;; (org-display-inline-images t)
  1538. ;; (org-cycle-hide-drawers 'all)
  1539. ;; (prog1 (org-drill-presentation-prompt)
  1540. ;; (org-drill-hide-subheadings-if 'org-drill-entry-p)
  1541. ;; (org-drill-unhide-clozed-text))))))
  1542. (defun org-drill-present-card-using-text (question &optional answer)
  1543. "Present the string QUESTION as the only visible content of the card."
  1544. (with-hidden-comments
  1545. (with-replaced-entry-text
  1546. question
  1547. (org-drill-hide-all-subheadings-except nil)
  1548. (org-cycle-hide-drawers 'all)
  1549. (prog1 (org-drill-presentation-prompt)
  1550. (org-drill-hide-subheadings-if 'org-drill-entry-p)))))
  1551. ;;; The following macro is necessary because `org-save-outline-visibility'
  1552. ;;; currently discards the value returned by its body and returns a garbage
  1553. ;;; value instead. (as at org mode v 7.5)
  1554. (defmacro org-drill-save-visibility (&rest body)
  1555. "Store the current visibility state of the org buffer, and restore it
  1556. after executing BODY. Return the value of the last expression
  1557. in BODY."
  1558. (let ((retval (gensym)))
  1559. `(let ((,retval nil))
  1560. (org-save-outline-visibility t
  1561. (setq ,retval
  1562. (progn
  1563. ,@body)))
  1564. ,retval)))
  1565. (defun org-drill-entry ()
  1566. "Present the current topic for interactive review, as in `org-drill'.
  1567. Review will occur regardless of whether the topic is due for review or whether
  1568. it meets the definition of a 'review topic' used by `org-drill'.
  1569. Returns a quality rating from 0 to 5, or nil if the user quit, or the symbol
  1570. EDIT if the user chose to exit the drill and edit the current item. Choosing
  1571. the latter option leaves the drill session suspended; it can be resumed
  1572. later using `org-drill-resume'.
  1573. See `org-drill' for more details."
  1574. (interactive)
  1575. (org-drill-goto-drill-entry-heading)
  1576. ;;(unless (org-part-of-drill-entry-p)
  1577. ;; (error "Point is not inside a drill entry"))
  1578. ;;(unless (org-at-heading-p)
  1579. ;; (org-back-to-heading))
  1580. (let ((card-type (org-entry-get (point) "DRILL_CARD_TYPE"))
  1581. (answer-fn 'org-drill-present-default-answer)
  1582. (cont nil))
  1583. (org-drill-save-visibility
  1584. (save-restriction
  1585. (org-narrow-to-subtree)
  1586. (org-show-subtree)
  1587. (org-cycle-hide-drawers 'all)
  1588. (let ((presentation-fn (cdr (assoc card-type org-drill-card-type-alist))))
  1589. (if (listp presentation-fn)
  1590. (psetq answer-fn (or (second presentation-fn)
  1591. 'org-drill-present-default-answer)
  1592. presentation-fn (first presentation-fn)))
  1593. (cond
  1594. ((null presentation-fn)
  1595. (message "%s:%d: Unrecognised card type '%s', skipping..."
  1596. (buffer-name) (point) card-type)
  1597. (sit-for 0.5)
  1598. 'skip)
  1599. (t
  1600. (setq cont (funcall presentation-fn))
  1601. (cond
  1602. ((not cont)
  1603. (message "Quit")
  1604. nil)
  1605. ((eql cont 'edit)
  1606. 'edit)
  1607. ((eql cont 'skip)
  1608. 'skip)
  1609. (t
  1610. (save-excursion
  1611. (funcall answer-fn
  1612. (lambda () (org-drill-reschedule)))))))))))))
  1613. (defun org-drill-entries-pending-p ()
  1614. (or *org-drill-again-entries*
  1615. (and (not (org-drill-maximum-item-count-reached-p))
  1616. (not (org-drill-maximum-duration-reached-p))
  1617. (or *org-drill-new-entries*
  1618. *org-drill-failed-entries*
  1619. *org-drill-young-mature-entries*
  1620. *org-drill-old-mature-entries*
  1621. *org-drill-overdue-entries*
  1622. *org-drill-again-entries*))))
  1623. (defun org-drill-pending-entry-count ()
  1624. (+ (length *org-drill-new-entries*)
  1625. (length *org-drill-failed-entries*)
  1626. (length *org-drill-young-mature-entries*)
  1627. (length *org-drill-old-mature-entries*)
  1628. (length *org-drill-overdue-entries*)
  1629. (length *org-drill-again-entries*)))
  1630. (defun org-drill-maximum-duration-reached-p ()
  1631. "Returns true if the current drill session has continued past its
  1632. maximum duration."
  1633. (and org-drill-maximum-duration
  1634. *org-drill-start-time*
  1635. (> (- (float-time (current-time)) *org-drill-start-time*)
  1636. (* org-drill-maximum-duration 60))))
  1637. (defun org-drill-maximum-item-count-reached-p ()
  1638. "Returns true if the current drill session has reached the
  1639. maximum number of items."
  1640. (and org-drill-maximum-items-per-session
  1641. (>= (length *org-drill-done-entries*)
  1642. org-drill-maximum-items-per-session)))
  1643. (defun org-drill-pop-next-pending-entry ()
  1644. (block org-drill-pop-next-pending-entry
  1645. (let ((m nil))
  1646. (while (or (null m)
  1647. (not (org-drill-entry-p m)))
  1648. (setq
  1649. m
  1650. (cond
  1651. ;; First priority is items we failed in a prior session.
  1652. ((and *org-drill-failed-entries*
  1653. (not (org-drill-maximum-item-count-reached-p))
  1654. (not (org-drill-maximum-duration-reached-p)))
  1655. (pop-random *org-drill-failed-entries*))
  1656. ;; Next priority is overdue items.
  1657. ((and *org-drill-overdue-entries*
  1658. (not (org-drill-maximum-item-count-reached-p))
  1659. (not (org-drill-maximum-duration-reached-p)))
  1660. ;; We use `pop', not `pop-random', because we have already
  1661. ;; sorted overdue items into a random order which takes
  1662. ;; number of days overdue into account.
  1663. (pop *org-drill-overdue-entries*))
  1664. ;; Next priority is 'young' items.
  1665. ((and *org-drill-young-mature-entries*
  1666. (not (org-drill-maximum-item-count-reached-p))
  1667. (not (org-drill-maximum-duration-reached-p)))
  1668. (pop-random *org-drill-young-mature-entries*))
  1669. ;; Next priority is newly added items, and older entries.
  1670. ;; We pool these into a single group.
  1671. ((and (or *org-drill-new-entries*
  1672. *org-drill-old-mature-entries*)
  1673. (not (org-drill-maximum-item-count-reached-p))
  1674. (not (org-drill-maximum-duration-reached-p)))
  1675. (cond
  1676. ((< (random* (+ (length *org-drill-new-entries*)
  1677. (length *org-drill-old-mature-entries*)))
  1678. (length *org-drill-new-entries*))
  1679. (pop-random *org-drill-new-entries*))
  1680. (t
  1681. (pop-random *org-drill-old-mature-entries*))))
  1682. ;; After all the above are done, last priority is items
  1683. ;; that were failed earlier THIS SESSION.
  1684. (*org-drill-again-entries*
  1685. (pop *org-drill-again-entries*))
  1686. (t ; nothing left -- return nil
  1687. (return-from org-drill-pop-next-pending-entry nil)))))
  1688. m)))
  1689. (defun org-drill-entries (&optional resuming-p)
  1690. "Returns nil, t, or a list of markers representing entries that were
  1691. 'failed' and need to be presented again before the session ends.
  1692. RESUMING-P is true if we are resuming a suspended drill session."
  1693. (block org-drill-entries
  1694. (while (org-drill-entries-pending-p)
  1695. (let ((m (cond
  1696. ((or (not resuming-p)
  1697. (null *org-drill-current-item*)
  1698. (not (org-drill-entry-p *org-drill-current-item*)))
  1699. (org-drill-pop-next-pending-entry))
  1700. (t ; resuming a suspended session.
  1701. (setq resuming-p nil)
  1702. *org-drill-current-item*))))
  1703. (setq *org-drill-current-item* m)
  1704. (unless m
  1705. (error "Unexpectedly ran out of pending drill items"))
  1706. (save-excursion
  1707. (org-drill-goto-entry m)
  1708. (cond
  1709. ((not (org-drill-entry-due-p))
  1710. ;; The entry is not due anymore. This could arise if the user
  1711. ;; suspends a drill session, then drills an individual entry,
  1712. ;; then resumes the session.
  1713. (message "Entry no longer due, skipping...")
  1714. (sit-for 0.3)
  1715. nil)
  1716. (t
  1717. (setq result (org-drill-entry))
  1718. (cond
  1719. ((null result)
  1720. (message "Quit")
  1721. (setq end-pos :quit)
  1722. (return-from org-drill-entries nil))
  1723. ((eql result 'edit)
  1724. (setq end-pos (point-marker))
  1725. (return-from org-drill-entries nil))
  1726. ((eql result 'skip)
  1727. nil) ; skip this item
  1728. (t
  1729. (cond
  1730. ((<= result org-drill-failure-quality)
  1731. (if *org-drill-again-entries*
  1732. (setq *org-drill-again-entries*
  1733. (shuffle-list *org-drill-again-entries*)))
  1734. (push-end m *org-drill-again-entries*))
  1735. (t
  1736. (push m *org-drill-done-entries*))))))))))))
  1737. (defun org-drill-final-report ()
  1738. (let ((pass-percent
  1739. (round (* 100 (count-if (lambda (qual)
  1740. (> qual org-drill-failure-quality))
  1741. *org-drill-session-qualities*))
  1742. (max 1 (length *org-drill-session-qualities*))))
  1743. (prompt nil))
  1744. (setq prompt
  1745. (format
  1746. "%d items reviewed. Session duration %s.
  1747. Recall of reviewed items:
  1748. Excellent (5): %3d%% | Near miss (2): %3d%%
  1749. Good (4): %3d%% | Failure (1): %3d%%
  1750. Hard (3): %3d%% | Abject failure (0): %3d%%
  1751. You successfully recalled %d%% of reviewed items (quality > %s)
  1752. %d/%d items still await review (%s, %s, %s, %s, %s).
  1753. Tomorrow, %d more items will become due for review.
  1754. Session finished. Press a key to continue..."
  1755. (length *org-drill-done-entries*)
  1756. (format-seconds "%h:%.2m:%.2s"
  1757. (- (float-time (current-time)) *org-drill-start-time*))
  1758. (round (* 100 (count 5 *org-drill-session-qualities*))
  1759. (max 1 (length *org-drill-session-qualities*)))
  1760. (round (* 100 (count 2 *org-drill-session-qualities*))
  1761. (max 1 (length *org-drill-session-qualities*)))
  1762. (round (* 100 (count 4 *org-drill-session-qualities*))
  1763. (max 1 (length *org-drill-session-qualities*)))
  1764. (round (* 100 (count 1 *org-drill-session-qualities*))
  1765. (max 1 (length *org-drill-session-qualities*)))
  1766. (round (* 100 (count 3 *org-drill-session-qualities*))
  1767. (max 1 (length *org-drill-session-qualities*)))
  1768. (round (* 100 (count 0 *org-drill-session-qualities*))
  1769. (max 1 (length *org-drill-session-qualities*)))
  1770. pass-percent
  1771. org-drill-failure-quality
  1772. (org-drill-pending-entry-count)
  1773. (+ (org-drill-pending-entry-count)
  1774. *org-drill-dormant-entry-count*)
  1775. (propertize
  1776. (format "%d failed"
  1777. (+ (length *org-drill-failed-entries*)
  1778. (length *org-drill-again-entries*)))
  1779. 'face `(:foreground ,org-drill-failed-count-color))
  1780. (propertize
  1781. (format "%d overdue"
  1782. (length *org-drill-overdue-entries*))
  1783. 'face `(:foreground ,org-drill-failed-count-color))
  1784. (propertize
  1785. (format "%d new"
  1786. (length *org-drill-new-entries*))
  1787. 'face `(:foreground ,org-drill-new-count-color))
  1788. (propertize
  1789. (format "%d young"
  1790. (length *org-drill-young-mature-entries*))
  1791. 'face `(:foreground ,org-drill-mature-count-color))
  1792. (propertize
  1793. (format "%d old"
  1794. (length *org-drill-old-mature-entries*))
  1795. 'face `(:foreground ,org-drill-mature-count-color))
  1796. *org-drill-due-tomorrow-count*
  1797. ))
  1798. (while (not (input-pending-p))
  1799. (message "%s" prompt)
  1800. (sit-for 0.5))
  1801. (read-char-exclusive)
  1802. (if (and *org-drill-session-qualities*
  1803. (< pass-percent (- 100 org-drill-forgetting-index)))
  1804. (read-char-exclusive
  1805. (format
  1806. "%s
  1807. You failed %d%% of the items you reviewed during this session.
  1808. %d (%d%%) of all items scanned were overdue.
  1809. Are you keeping up with your items, and reviewing them
  1810. when they are scheduled? If so, you may want to consider
  1811. lowering the value of `org-drill-learn-fraction' slightly in
  1812. order to make items appear more frequently over time."
  1813. (propertize "WARNING!" 'face 'org-warning)
  1814. (- 100 pass-percent)
  1815. *org-drill-overdue-entry-count*
  1816. (round (* 100 *org-drill-overdue-entry-count*)
  1817. (+ *org-drill-dormant-entry-count*
  1818. *org-drill-due-entry-count*)))
  1819. ))))
  1820. (defun org-drill-free-markers (markers)
  1821. "MARKERS is a list of markers, all of which will be freed (set to
  1822. point nowhere). Alternatively, MARKERS can be 't', in which case
  1823. all the markers used by Org-Drill will be freed."
  1824. (dolist (m (if (eql t markers)
  1825. (append *org-drill-done-entries*
  1826. *org-drill-new-entries*
  1827. *org-drill-failed-entries*
  1828. *org-drill-again-entries*
  1829. *org-drill-overdue-entries*
  1830. *org-drill-young-mature-entries*
  1831. *org-drill-old-mature-entries*)
  1832. markers))
  1833. (free-marker m)))
  1834. (defun org-drill-order-overdue-entries (overdue-data)
  1835. (setq *org-drill-overdue-entries*
  1836. (mapcar 'car
  1837. (sort (shuffle-list overdue-data)
  1838. (lambda (a b) (> (cdr a) (cdr b)))))))
  1839. (defun org-drill-entry-status ()
  1840. "Returns a list (STATUS DUE) where DUE is the number of days overdue,
  1841. zero being due today, -1 being scheduled 1 day in the future. STATUS is
  1842. one of the following values:
  1843. - nil, if the item is not a drill entry, or has an empty body
  1844. - :unscheduled
  1845. - :future
  1846. - :new
  1847. - :failed
  1848. - :overdue
  1849. - :young
  1850. - :old
  1851. "
  1852. (save-excursion
  1853. (unless (org-at-heading-p)
  1854. (org-back-to-heading))
  1855. (let ((due (org-drill-entry-days-overdue))
  1856. (last-int (org-drill-entry-last-interval 1)))
  1857. (list
  1858. (cond
  1859. ((not (org-drill-entry-p))
  1860. nil)
  1861. ((org-drill-entry-empty-p)
  1862. nil) ; skip -- item body is empty
  1863. ((null due) ; unscheduled - usually a skipped leech
  1864. :unscheduled)
  1865. ;; ((eql -1 due)
  1866. ;; :tomorrow)
  1867. ((minusp due) ; scheduled in the future
  1868. :future)
  1869. ;; The rest of the stati all denote 'due' items ==========================
  1870. ((<= (org-drill-entry-last-quality 9999)
  1871. org-drill-failure-quality)
  1872. ;; Mature entries that were failed last time are
  1873. ;; FAILED, regardless of how young, old or overdue
  1874. ;; they are.
  1875. :failed)
  1876. ((org-drill-entry-new-p)
  1877. :new)
  1878. ((org-drill-entry-overdue-p due last-int)
  1879. ;; Overdue status overrides young versus old
  1880. ;; distinction.
  1881. ;; Store marker + due, for sorting of overdue entries
  1882. :overdue)
  1883. ((<= (org-drill-entry-last-interval 9999)
  1884. org-drill-days-before-old)
  1885. :young)
  1886. (t
  1887. :old))
  1888. due))))
  1889. (defun org-drill (&optional scope resume-p)
  1890. "Begin an interactive 'drill session'. The user is asked to
  1891. review a series of topics (headers). Each topic is initially
  1892. presented as a 'question', often with part of the topic content
  1893. hidden. The user attempts to recall the hidden information or
  1894. answer the question, then presses a key to reveal the answer. The
  1895. user then rates his or her recall or performance on that
  1896. topic. This rating information is used to reschedule the topic
  1897. for future review.
  1898. Org-drill proceeds by:
  1899. - Finding all topics (headings) in SCOPE which have either been
  1900. used and rescheduled before, or which have a tag that matches
  1901. `org-drill-question-tag'.
  1902. - All matching topics which are either unscheduled, or are
  1903. scheduled for the current date or a date in the past, are
  1904. considered to be candidates for the drill session.
  1905. - If `org-drill-maximum-items-per-session' is set, a random
  1906. subset of these topics is presented. Otherwise, all of the
  1907. eligible topics will be presented.
  1908. SCOPE determines the scope in which to search for
  1909. questions. It accepts the same values as `org-drill-scope',
  1910. which see.
  1911. If RESUME-P is non-nil, resume a suspended drill session rather
  1912. than starting a new one."
  1913. (interactive)
  1914. (let ((end-pos nil)
  1915. (overdue-data nil)
  1916. (cnt 0))
  1917. (block org-drill
  1918. (unless resume-p
  1919. (org-drill-free-markers t)
  1920. (setq *org-drill-current-item* nil
  1921. *org-drill-done-entries* nil
  1922. *org-drill-dormant-entry-count* 0
  1923. *org-drill-due-entry-count* 0
  1924. *org-drill-due-tomorrow-count* 0
  1925. *org-drill-overdue-entry-count* 0
  1926. *org-drill-new-entries* nil
  1927. *org-drill-overdue-entries* nil
  1928. *org-drill-young-mature-entries* nil
  1929. *org-drill-old-mature-entries* nil
  1930. *org-drill-failed-entries* nil
  1931. *org-drill-again-entries* nil)
  1932. (setq *org-drill-session-qualities* nil)
  1933. (setq *org-drill-start-time* (float-time (current-time))))
  1934. (setq *random-state* (make-random-state t)) ; reseed RNG
  1935. (unwind-protect
  1936. (save-excursion
  1937. (unless resume-p
  1938. (let ((org-trust-scanner-tags t)
  1939. (warned-about-id-creation nil))
  1940. (org-map-drill-entries
  1941. (lambda ()
  1942. (when (zerop (% (incf cnt) 50))
  1943. (message "Processing drill items: %4d%s"
  1944. (+ (length *org-drill-new-entries*)
  1945. (length *org-drill-overdue-entries*)
  1946. (length *org-drill-young-mature-entries*)
  1947. (length *org-drill-old-mature-entries*)
  1948. (length *org-drill-failed-entries*))
  1949. (make-string (ceiling cnt 50) ?.)))
  1950. (cond
  1951. ((not (org-drill-entry-p))
  1952. nil) ; skip
  1953. (t
  1954. (when (and (not warned-about-id-creation)
  1955. (null (org-id-get)))
  1956. (message (concat "Creating unique IDs for items "
  1957. "(slow, but only happens once)"))
  1958. (sit-for 0.5)
  1959. (setq warned-about-id-creation t))
  1960. (org-id-get-create) ; ensure drill entry has unique ID
  1961. (destructuring-bind (status due) (org-drill-entry-status)
  1962. (case status
  1963. (:unscheduled
  1964. (incf *org-drill-dormant-entry-count*))
  1965. ;; (:tomorrow
  1966. ;; (incf *org-drill-dormant-entry-count*)
  1967. ;; (incf *org-drill-due-tomorrow-count*))
  1968. (:future
  1969. (incf *org-drill-dormant-entry-count*)
  1970. (if (eq -1 due)
  1971. (incf *org-drill-due-tomorrow-count*)))
  1972. (:new
  1973. (push (point-marker) *org-drill-new-entries*))
  1974. (:failed
  1975. (push (point-marker) *org-drill-failed-entries*))
  1976. (:young
  1977. (push (point-marker) *org-drill-young-mature-entries*))
  1978. (:overdue
  1979. (push (cons (point-marker) due) overdue-data))
  1980. (:old
  1981. (push (point-marker) *org-drill-old-mature-entries*)))))))
  1982. scope)
  1983. ;; (let ((due (org-drill-entry-days-overdue))
  1984. ;; (last-int (org-drill-entry-last-interval 1)))
  1985. ;; (cond
  1986. ;; ((org-drill-entry-empty-p)
  1987. ;; nil) ; skip -- item body is empty
  1988. ;; ((or (null due) ; unscheduled - usually a skipped leech
  1989. ;; (minusp due)) ; scheduled in the future
  1990. ;; (incf *org-drill-dormant-entry-count*)
  1991. ;; (if (eq -1 due)
  1992. ;; (incf *org-drill-due-tomorrow-count*)))
  1993. ;; ((org-drill-entry-new-p)
  1994. ;; (push (point-marker) *org-drill-new-entries*))
  1995. ;; ((<= (org-drill-entry-last-quality 9999)
  1996. ;; org-drill-failure-quality)
  1997. ;; ;; Mature entries that were failed last time are
  1998. ;; ;; FAILED, regardless of how young, old or overdue
  1999. ;; ;; they are.
  2000. ;; (push (point-marker) *org-drill-failed-entries*))
  2001. ;; ((org-drill-entry-overdue-p due last-int)
  2002. ;; ;; Overdue status overrides young versus old
  2003. ;; ;; distinction.
  2004. ;; ;; Store marker + due, for sorting of overdue entries
  2005. ;; (push (cons (point-marker) due) overdue-data))
  2006. ;; ((<= (org-drill-entry-last-interval 9999)
  2007. ;; org-drill-days-before-old)
  2008. ;; ;; Item is 'young'.
  2009. ;; (push (point-marker)
  2010. ;; *org-drill-young-mature-entries*))
  2011. ;; (t
  2012. ;; (push (point-marker)
  2013. ;; *org-drill-old-mature-entries*))))
  2014. ;; Order 'overdue' items so that the most overdue will tend to
  2015. ;; come up for review first, while keeping exact order random
  2016. (org-drill-order-overdue-entries overdue-data)
  2017. (setq *org-drill-overdue-entry-count*
  2018. (length *org-drill-overdue-entries*))))
  2019. (setq *org-drill-due-entry-count* (org-drill-pending-entry-count))
  2020. (cond
  2021. ((and (null *org-drill-new-entries*)
  2022. (null *org-drill-failed-entries*)
  2023. (null *org-drill-overdue-entries*)
  2024. (null *org-drill-young-mature-entries*)
  2025. (null *org-drill-old-mature-entries*))
  2026. (message "I did not find any pending drill items."))
  2027. (t
  2028. (org-drill-entries resume-p)
  2029. (message "Drill session finished!"))))
  2030. (progn
  2031. (unless end-pos
  2032. (org-drill-free-markers *org-drill-done-entries*)))))
  2033. (cond
  2034. (end-pos
  2035. (when (markerp end-pos)
  2036. (org-drill-goto-entry end-pos))
  2037. (let ((keystr (command-keybinding-to-string 'org-drill-resume)))
  2038. (message
  2039. "You can continue the drill session with the command `org-drill-resume'.%s"
  2040. (if keystr (format "\nYou can run this command by pressing %s." keystr)
  2041. ""))))
  2042. (t
  2043. (org-drill-final-report)
  2044. (if (eql 'sm5 org-drill-spaced-repetition-algorithm)
  2045. (org-drill-save-optimal-factor-matrix))
  2046. (if org-drill-save-buffers-after-drill-sessions-p
  2047. (save-some-buffers))
  2048. ))))
  2049. (defun org-drill-save-optimal-factor-matrix ()
  2050. (message "Saving optimal factor matrix...")
  2051. (customize-save-variable 'org-drill-optimal-factor-matrix
  2052. org-drill-optimal-factor-matrix))
  2053. (defun org-drill-cram (&optional scope)
  2054. "Run an interactive drill session in 'cram mode'. In cram mode,
  2055. all drill items are considered to be due for review, unless they
  2056. have been reviewed within the last `org-drill-cram-hours'
  2057. hours."
  2058. (interactive)
  2059. (let ((*org-drill-cram-mode* t))
  2060. (org-drill scope)))
  2061. (defun org-drill-tree ()
  2062. "Run an interactive drill session using drill items within the
  2063. subtree at point."
  2064. (interactive)
  2065. (org-drill 'tree))
  2066. (defun org-drill-directory ()
  2067. "Run an interactive drill session using drill items from all org
  2068. files in the same directory as the current file."
  2069. (interactive)
  2070. (org-drill 'directory))
  2071. (defun org-drill-again (&optional scope)
  2072. "Run a new drill session, but try to use leftover due items that
  2073. were not reviewed during the last session, rather than scanning for
  2074. unreviewed items. If there are no leftover items in memory, a full
  2075. scan will be performed."
  2076. (interactive)
  2077. (cond
  2078. ((plusp (org-drill-pending-entry-count))
  2079. (org-drill-free-markers *org-drill-done-entries*)
  2080. (if (markerp *org-drill-current-item*)
  2081. (free-marker *org-drill-current-item*))
  2082. (setq *org-drill-start-time* (float-time (current-time))
  2083. *org-drill-done-entries* nil
  2084. *org-drill-current-item* nil)
  2085. (org-drill scope t))
  2086. (t
  2087. (org-drill scope))))
  2088. (defun org-drill-resume ()
  2089. "Resume a suspended drill session. Sessions are suspended by
  2090. exiting them with the `edit' or `quit' options."
  2091. (interactive)
  2092. (cond
  2093. ((org-drill-entries-pending-p)
  2094. (org-drill nil t))
  2095. ((and (plusp (org-drill-pending-entry-count))
  2096. ;; Current drill session is finished, but there are still
  2097. ;; more items which need to be reviewed.
  2098. (y-or-n-p (format
  2099. "You have finished the drill session. However, %d items still
  2100. need reviewing. Start a new drill session? "
  2101. (org-drill-pending-entry-count))))
  2102. (org-drill-again))
  2103. (t
  2104. (message "You have finished the drill session."))))
  2105. (defun org-drill-strip-entry-data ()
  2106. (dolist (prop org-drill-scheduling-properties)
  2107. (org-delete-property prop))
  2108. (org-schedule t))
  2109. (defun org-drill-strip-all-data (&optional scope)
  2110. "Delete scheduling data from every drill entry in scope. This
  2111. function may be useful if you want to give your collection of
  2112. entries to someone else. Scope defaults to the current buffer,
  2113. and is specified by the argument SCOPE, which accepts the same
  2114. values as `org-drill-scope'."
  2115. (interactive)
  2116. (when (yes-or-no-p
  2117. "Delete scheduling data from ALL items in scope: are you sure?")
  2118. (cond
  2119. ((null scope)
  2120. ;; Scope is the current buffer. This means we can use
  2121. ;; `org-delete-property-globally', which is faster.
  2122. (dolist (prop org-drill-scheduling-properties)
  2123. (org-delete-property-globally prop))
  2124. (org-map-drill-entries (lambda () (org-schedule t)) scope))
  2125. (t
  2126. (org-map-drill-entries 'org-drill-strip-entry-data scope)))
  2127. (message "Done.")))
  2128. (defun org-drill-add-cloze-fontification ()
  2129. (when org-drill-use-visible-cloze-face-p
  2130. (font-lock-add-keywords 'org-mode
  2131. org-drill-cloze-keywords
  2132. nil)))
  2133. (add-hook 'org-mode-hook 'org-drill-add-cloze-fontification)
  2134. (org-drill-add-cloze-fontification)
  2135. ;;; Synching card collections =================================================
  2136. (defvar *org-drill-dest-id-table* (make-hash-table :test 'equal))
  2137. (defun org-drill-copy-entry-to-other-buffer (dest &optional path)
  2138. "Copy the subtree at point to the buffer DEST. The copy will receive
  2139. the tag 'imported'."
  2140. (block org-drill-copy-entry-to-other-buffer
  2141. (save-excursion
  2142. (let ((src (current-buffer))
  2143. (m nil))
  2144. (flet ((paste-tree-here (&optional level)
  2145. (org-paste-subtree level)
  2146. (org-drill-strip-entry-data)
  2147. (org-toggle-tag "imported" 'on)
  2148. (org-map-drill-entries
  2149. (lambda ()
  2150. (let ((id (org-id-get)))
  2151. (org-drill-strip-entry-data)
  2152. (unless (gethash id *org-drill-dest-id-table*)
  2153. (puthash id (point-marker)
  2154. *org-drill-dest-id-table*))))
  2155. 'tree)))
  2156. (unless path
  2157. (setq path (org-get-outline-path)))
  2158. (org-copy-subtree)
  2159. (switch-to-buffer dest)
  2160. (setq m
  2161. (condition-case nil
  2162. (org-find-olp path t)
  2163. (error ; path does not exist in DEST
  2164. (return-from org-drill-copy-entry-to-other-buffer
  2165. (cond
  2166. ((cdr path)
  2167. (org-drill-copy-entry-to-other-buffer
  2168. dest (butlast path)))
  2169. (t
  2170. ;; We've looked all the way up the path
  2171. ;; Default to appending to the end of DEST
  2172. (goto-char (point-max))
  2173. (newline)
  2174. (paste-tree-here)))))))
  2175. (goto-char m)
  2176. (outline-next-heading)
  2177. (newline)
  2178. (forward-line -1)
  2179. (paste-tree-here (1+ (or (org-current-level) 0)))
  2180. )))))
  2181. (defun org-drill-merge-buffers (src &optional dest ignore-new-items-p)
  2182. "SRC and DEST are two org mode buffers containing drill items.
  2183. For each drill item in DEST that shares an ID with an item in SRC,
  2184. overwrite scheduling data in DEST with data taken from the item in SRC.
  2185. This is intended for use when two people are sharing a set of drill items,
  2186. one person has made some updates to the item set, and the other person
  2187. wants to migrate to the updated set without losing their scheduling data.
  2188. By default, any drill items in SRC which do not exist in DEST are
  2189. copied into DEST. We attempt to place the copied item in the
  2190. equivalent location in DEST to its location in SRC, by matching
  2191. the heading hierarchy. However if IGNORE-NEW-ITEMS-P is non-nil,
  2192. we simply ignore any items that do not exist in DEST, and do not
  2193. copy them across."
  2194. (interactive "bImport scheduling info from which buffer?")
  2195. (unless dest
  2196. (setq dest (current-buffer)))
  2197. (setq src (get-buffer src)
  2198. dest (get-buffer dest))
  2199. (when (yes-or-no-p
  2200. (format
  2201. (concat "About to overwrite all scheduling data for drill items in `%s' "
  2202. "with information taken from matching items in `%s'. Proceed? ")
  2203. (buffer-name dest) (buffer-name src)))
  2204. ;; Compile list of all IDs in the destination buffer.
  2205. (clrhash *org-drill-dest-id-table*)
  2206. (with-current-buffer dest
  2207. (org-map-drill-entries
  2208. (lambda ()
  2209. (let ((this-id (org-id-get)))
  2210. (when this-id
  2211. (puthash this-id (point-marker) *org-drill-dest-id-table*))))
  2212. 'file))
  2213. ;; Look through all entries in source buffer.
  2214. (with-current-buffer src
  2215. (org-map-drill-entries
  2216. (lambda ()
  2217. (let ((id (org-id-get))
  2218. (last-quality nil) (last-reviewed nil)
  2219. (scheduled-time nil))
  2220. (cond
  2221. ((or (null id)
  2222. (not (org-drill-entry-p)))
  2223. nil)
  2224. ((gethash id *org-drill-dest-id-table*)
  2225. ;; This entry matches an entry in dest. Retrieve all its
  2226. ;; scheduling data, then go to the matching location in dest
  2227. ;; and write the data.
  2228. (let ((marker (gethash id *org-drill-dest-id-table*)))
  2229. (destructuring-bind (last-interval repetitions failures
  2230. total-repeats meanq ease)
  2231. (org-drill-get-item-data)
  2232. (setq last-reviewed (org-entry-get (point) "DRILL_LAST_REVIEWED")
  2233. last-quality (org-entry-get (point) "DRILL_LAST_QUALITY")
  2234. scheduled-time (org-get-scheduled-time (point)))
  2235. (save-excursion
  2236. ;; go to matching entry in destination buffer
  2237. (switch-to-buffer (marker-buffer marker))
  2238. (goto-char marker)
  2239. (org-drill-strip-entry-data)
  2240. (unless (zerop total-repeats)
  2241. (org-drill-store-item-data last-interval repetitions failures
  2242. total-repeats meanq ease)
  2243. (if last-quality
  2244. (org-set-property "LAST_QUALITY" last-quality)
  2245. (org-delete-property "LAST_QUALITY"))
  2246. (if last-reviewed
  2247. (org-set-property "LAST_REVIEWED" last-reviewed)
  2248. (org-delete-property "LAST_REVIEWED"))
  2249. (if scheduled-time
  2250. (org-schedule nil scheduled-time)))))
  2251. (remhash id *org-drill-dest-id-table*)
  2252. (free-marker marker)))
  2253. (t
  2254. ;; item in SRC has ID, but no matching ID in DEST.
  2255. ;; It must be a new item that does not exist in DEST.
  2256. ;; Copy the entire item to the *end* of DEST.
  2257. (unless ignore-new-items-p
  2258. (org-drill-copy-entry-to-other-buffer dest))))))
  2259. 'file))
  2260. ;; Finally: there may be some items in DEST which are not in SRC, and
  2261. ;; which have been scheduled by another user of DEST. Clear out the
  2262. ;; scheduling info from all the unmatched items in DEST.
  2263. (with-current-buffer dest
  2264. (maphash (lambda (id m)
  2265. (goto-char m)
  2266. (org-drill-strip-entry-data)
  2267. (free-marker m))
  2268. *org-drill-dest-id-table*))))
  2269. ;;; Card types for learning languages =========================================
  2270. ;;; Get spell-number.el from:
  2271. ;;; http://www.emacswiki.org/emacs/spell-number.el
  2272. (autoload 'spelln-integer-in-words "spell-number")
  2273. ;;; `conjugate' card type =====================================================
  2274. ;;; See spanish.org for usage
  2275. (defvar org-drill-verb-tense-alist
  2276. '(("present" "tomato")
  2277. ("simple present" "tomato")
  2278. ("present indicative" "tomato")
  2279. ;; past tenses
  2280. ("past" "purple")
  2281. ("simple past" "purple")
  2282. ("preterite" "purple")
  2283. ("imperfect" "darkturquoise")
  2284. ("present perfect" "royalblue")
  2285. ;; future tenses
  2286. ("future" "green"))
  2287. "Alist where each entry has the form (TENSE COLOUR), where
  2288. TENSE is a string naming a tense in which verbs can be
  2289. conjugated, and COLOUR is a string specifying a foreground colour
  2290. which will be used by `org-drill-present-verb-conjugation' and
  2291. `org-drill-show-answer-verb-conjugation' to fontify the verb and
  2292. the name of the tense.")
  2293. (defun org-drill-get-verb-conjugation-info ()
  2294. "Auxiliary function used by `org-drill-present-verb-conjugation' and
  2295. `org-drill-show-answer-verb-conjugation'."
  2296. (let ((infinitive (org-entry-get (point) "VERB_INFINITIVE" t))
  2297. (inf-hint (org-entry-get (point) "VERB_INFINITIVE_HINT" t))
  2298. (translation (org-entry-get (point) "VERB_TRANSLATION" t))
  2299. (tense (org-entry-get (point) "VERB_TENSE" nil))
  2300. (highlight-face nil))
  2301. (unless (and infinitive translation tense)
  2302. (error "Missing information for verb conjugation card (%s, %s, %s) at %s"
  2303. infinitive translation tense (point)))
  2304. (setq tense (downcase (car (read-from-string tense)))
  2305. infinitive (car (read-from-string infinitive))
  2306. inf-hint (if inf-hint (car (read-from-string inf-hint)))
  2307. translation (car (read-from-string translation)))
  2308. (setq highlight-face
  2309. (list :foreground
  2310. (or (second (assoc-string tense org-drill-verb-tense-alist t))
  2311. "red")))
  2312. (setq infinitive (propertize infinitive 'face highlight-face))
  2313. (setq translation (propertize translation 'face highlight-face))
  2314. (setq tense (propertize tense 'face highlight-face))
  2315. (list infinitive inf-hint translation tense)))
  2316. (defun org-drill-present-verb-conjugation ()
  2317. "Present a drill entry whose card type is 'conjugate'."
  2318. (destructuring-bind (infinitive inf-hint translation tense)
  2319. (org-drill-get-verb-conjugation-info)
  2320. (org-drill-present-card-using-text
  2321. (cond
  2322. ((zerop (random* 2))
  2323. (format "\nTranslate the verb\n\n%s\n\nand conjugate for the %s tense.\n\n"
  2324. infinitive tense))
  2325. (t
  2326. (format "\nGive the verb that means\n\n%s %s\n
  2327. and conjugate for the %s tense.\n\n"
  2328. translation
  2329. (if inf-hint (format " [HINT: %s]" inf-hint) "")
  2330. tense))))))
  2331. (defun org-drill-show-answer-verb-conjugation (reschedule-fn)
  2332. "Show the answer for a drill item whose card type is 'conjugate'.
  2333. RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
  2334. returns its return value."
  2335. (destructuring-bind (infinitive inf-hint translation tense)
  2336. (org-drill-get-verb-conjugation-info)
  2337. (with-replaced-entry-heading
  2338. (format "%s tense of %s ==> %s\n\n"
  2339. (capitalize tense)
  2340. infinitive translation)
  2341. (funcall reschedule-fn))))
  2342. ;;; `translate_number' card type ==============================================
  2343. ;;; See spanish.org for usage
  2344. (defvar *drilled-number* 0)
  2345. (defvar *drilled-number-direction* 'to-english)
  2346. (defun org-drill-present-translate-number ()
  2347. (let ((num-min (read (org-entry-get (point) "DRILL_NUMBER_MIN")))
  2348. (num-max (read (org-entry-get (point) "DRILL_NUMBER_MAX")))
  2349. (language (read (org-entry-get (point) "DRILL_LANGUAGE" t)))
  2350. (highlight-face 'font-lock-warning-face))
  2351. (cond
  2352. ((not (fboundp 'spelln-integer-in-words))
  2353. (message "`spell-number.el' not loaded, skipping 'translate_number' card...")
  2354. (sit-for 0.5)
  2355. 'skip)
  2356. ((not (and (numberp num-min) (numberp num-max) language))
  2357. (error "Missing language or minimum or maximum numbers for number card"))
  2358. (t
  2359. (if (> num-min num-max)
  2360. (psetf num-min num-max
  2361. num-max num-min))
  2362. (setq *drilled-number*
  2363. (+ num-min (random* (abs (1+ (- num-max num-min))))))
  2364. (setq *drilled-number-direction*
  2365. (if (zerop (random* 2)) 'from-english 'to-english))
  2366. (org-drill-present-card-using-text
  2367. (if (eql 'to-english *drilled-number-direction*)
  2368. (format "\nTranslate into English:\n\n%s\n"
  2369. (let ((spelln-language language))
  2370. (propertize
  2371. (spelln-integer-in-words *drilled-number*)
  2372. 'face highlight-face)))
  2373. (format "\nTranslate into %s:\n\n%s\n"
  2374. (capitalize (format "%s" language))
  2375. (let ((spelln-language 'english-gb))
  2376. (propertize
  2377. (spelln-integer-in-words *drilled-number*)
  2378. 'face highlight-face)))))))))
  2379. (defun org-drill-show-answer-translate-number (reschedule-fn)
  2380. (let* ((language (read (org-entry-get (point) "DRILL_LANGUAGE" t)))
  2381. (highlight-face 'font-lock-warning-face)
  2382. (non-english
  2383. (let ((spelln-language language))
  2384. (propertize (spelln-integer-in-words *drilled-number*)
  2385. 'face highlight-face)))
  2386. (english
  2387. (let ((spelln-language 'english-gb))
  2388. (propertize (spelln-integer-in-words *drilled-number*)
  2389. 'face 'highlight-face))))
  2390. (with-replaced-entry-text
  2391. (cond
  2392. ((eql 'to-english *drilled-number-direction*)
  2393. (format "\nThe English translation of %s is:\n\n%s\n"
  2394. non-english english))
  2395. (t
  2396. (format "\nThe %s translation of %s is:\n\n%s\n"
  2397. (capitalize (format "%s" language))
  2398. english non-english)))
  2399. (funcall reschedule-fn))))
  2400. ;;; `spanish_verb' card type ==================================================
  2401. ;;; Not very interesting, but included to demonstrate how a presentation
  2402. ;;; function can manipulate which subheading are hidden versus shown.
  2403. (defun org-drill-present-spanish-verb ()
  2404. (let ((prompt nil)
  2405. (reveal-headings nil))
  2406. (with-hidden-comments
  2407. (with-hidden-cloze-hints
  2408. (with-hidden-cloze-text
  2409. (case (random* 6)
  2410. (0
  2411. (org-drill-hide-all-subheadings-except '("Infinitive"))
  2412. (setq prompt
  2413. (concat "Translate this Spanish verb, and conjugate it "
  2414. "for the *present* tense.")
  2415. reveal-headings '("English" "Present Tense" "Notes")))
  2416. (1
  2417. (org-drill-hide-all-subheadings-except '("English"))
  2418. (setq prompt (concat "For the *present* tense, conjugate the "
  2419. "Spanish translation of this English verb.")
  2420. reveal-headings '("Infinitive" "Present Tense" "Notes")))
  2421. (2
  2422. (org-drill-hide-all-subheadings-except '("Infinitive"))
  2423. (setq prompt (concat "Translate this Spanish verb, and "
  2424. "conjugate it for the *past* tense.")
  2425. reveal-headings '("English" "Past Tense" "Notes")))
  2426. (3
  2427. (org-drill-hide-all-subheadings-except '("English"))
  2428. (setq prompt (concat "For the *past* tense, conjugate the "
  2429. "Spanish translation of this English verb.")
  2430. reveal-headings '("Infinitive" "Past Tense" "Notes")))
  2431. (4
  2432. (org-drill-hide-all-subheadings-except '("Infinitive"))
  2433. (setq prompt (concat "Translate this Spanish verb, and "
  2434. "conjugate it for the *future perfect* tense.")
  2435. reveal-headings '("English" "Future Perfect Tense" "Notes")))
  2436. (5
  2437. (org-drill-hide-all-subheadings-except '("English"))
  2438. (setq prompt (concat "For the *future perfect* tense, conjugate the "
  2439. "Spanish translation of this English verb.")
  2440. reveal-headings '("Infinitive" "Future Perfect Tense" "Notes"))))
  2441. (org-cycle-hide-drawers 'all)
  2442. (prog1 (org-drill-presentation-prompt)
  2443. (org-drill-hide-subheadings-if 'org-drill-entry-p)))))))
  2444. (provide 'org-drill)