org-drill.el 107 KB

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