org-drill.el 96 KB

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