org-drill.el 130 KB

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