From 5ac991dade721188c28b81248ba4961ca4f9a963 Mon Sep 17 00:00:00 2001 From: Cash Weaver Date: Tue, 27 Sep 2022 06:57:56 -0700 Subject: [PATCH 1/3] refactor: Use card and position classes --- org-fc-card.el | 115 ++++++++++++++++++++++++++ org-fc-core.el | 93 +++++++-------------- org-fc-position.el | 80 ++++++++++++++++++ org-fc-review.el | 197 ++++++++++++++++++++++++++++++--------------- 4 files changed, 356 insertions(+), 129 deletions(-) create mode 100644 org-fc-card.el create mode 100644 org-fc-position.el diff --git a/org-fc-card.el b/org-fc-card.el new file mode 100644 index 0000000..ea716ec --- /dev/null +++ b/org-fc-card.el @@ -0,0 +1,115 @@ +;;; org-fc-card.el --- Representation of a card. -*- lexical-binding: t; -*- + +;; Copyright (C) 2020 Leon Rische + +;; Author: Leon Rische +;; Url: https://www.leonrische.me/pages/org_flashcards.html +;; Package-requires: ((emacs "26.3") (org "9.3")) +;; Version: 0.0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: +;; +;; A single card. +;; +;;; Code: + +(require 'eieio) +(require 'org-fc-position) + +(defclass org-fc-card () + ((created + :initform nil + :initarg :created + :type list + :custom (repeat integer) + :documentation "The time at which this card was created.") + (filetitle + :initform "" + :initarg :filetitle + :type string + :documentation "The title of the file which contains this card.") + (tags + :initform nil + :initarg :tags + :type list + :custom (repeat string) + :documentation "The tags on this card.") + (id + :initform "" + :initarg :id + :type string + :documentation "The org-id for this card.") + (inherited-tags + :initform "" + :initarg :inherited-tags + :type string + :documentation "Inherited tags in the form :taga:tagb:." + ) + (local-tags + :initform "" + :initarg :local-tags + :type string + :documentation "Tags on the card itself in the form :taga:tagb:.") + (path + :initform "" + :initarg :path + :type string + :documentation "Full path to the file which contains this card.") + (positions + :initform nil + :initarg :positions + :type list + :documentation "The `org-fc-position's for this card.") + (suspended + :initform nil + :initarg :suspended + :type boolean + :documentation "t when the card is suspended; nil otherwise.") + (title + :initform nil + :initarg :title + :type (or null string)) + (type + :initform nil + :initarg :type + :type symbol + :documentation "The org-fc-* card type (e.g. double or cloze)."))) + +(cl-defmethod org-fc-card--to-positions ((card org-fc-card card)) + "Return list of `org-fc-position' extracted from CARD." + (mapcar + (lambda (pos) + (let ((box (plist-get pos :box)) + (ease (plist-get pos :ease)) + (interval (plist-get pos :interval)) + (pos-pos (plist-get pos :position)) + (due (plist-get pos :due))) + (org-fc-position + :box box + :card card + :due due + :ease ease + :interval interval + :pos pos-pos))) + (oref card positions))) + +(defun org-fc-cards--to-positions (cards) + "Convert a list of CARDS (`org-fc-card's) to a list of `org-fc-positions's." + (cl-loop for card in cards + append (org-fc-card--to-positions card))) + +(provide 'org-fc-card) +;;; org-fc-card.el ends here diff --git a/org-fc-core.el b/org-fc-core.el index db4ff86..542f198 100644 --- a/org-fc-core.el +++ b/org-fc-core.el @@ -25,11 +25,14 @@ ;;; Code: (require 'outline) +(require 'dash) (require 'org-id) (require 'org-indent) (require 'org-element) +(require 'org-fc-card) + (require 'subr-x) ;;; Customization @@ -180,14 +183,6 @@ This is expected to be called on an card entry heading." Used to determine if a card uses the compact style." (not (null (org-fc-back-heading-position)))) -(defun org-fc-sorted-random (n) - "Generate a list of N sorted random numbers." - (sort (cl-loop for i below n collect (cl-random 1.0)) #'>)) - -(defun org-fc-zip (as bs) - "Zip two lists AS and BS." - (cl-loop for a in as for b in bs collect (cons a b))) - ;; File-scoped variant of `org-id-goto' (defun org-fc-id-goto (id file) "Go to the heading with ID in FILE." @@ -583,65 +578,35 @@ use `(and (type double) (tag \"math\"))'." (funcall org-fc-index-function paths filter))) -(defun org-fc-index-flatten-card (card) - "Flatten CARD into a list of positions. -Relevant data from the card is included in each position -element." +(defun org-fc-shuffle (items) + "Return a shuffled version of ITEMS." + (let* ((randoms (cl-loop for _ below (length items) + collect (cl-random 1.0))) + (zipped (-zip randoms items)) + (sorted-zipped (sort zipped + (lambda (a b) + (> (car a) (car b)))))) + (mapcar + #'cdr + sorted-zipped))) + +(defun org-fc-index--to-cards (index) + "Convert an index to a list of `org-fc-card'." (mapcar - (lambda (pos) - (list + (lambda (card) + (org-fc-card + :created (plist-get card :created) :filetitle (plist-get card :filetitle) - :tags (plist-get card :tags) - :path (plist-get card :path) :id (plist-get card :id) - :type (plist-get card :type) - :due (plist-get pos :due) - :position (plist-get pos :position))) - (plist-get card :positions))) - -(defun org-fc-index-filter-due (index) - "Filter INDEX to include only unsuspended due positions. -Cards with no positions are removed from the index." - (let (res (now (current-time))) - (dolist (card index) - (unless (plist-get card :suspended) - (let ((due - (cl-remove-if-not - (lambda (pos) - (time-less-p (plist-get pos :due) now)) - (plist-get card :positions)))) - (unless (null due) - (plist-put - card :positions - (if (or (not org-fc-bury-siblings) - (member (plist-get card :cloze-type) '(single enumeration))) - due (list (car due)))) - (push card res))))) - res)) - -(defun org-fc-index-positions (index) - "Return all positions in INDEX." - (mapcan (lambda (card) (org-fc-index-flatten-card card)) index)) - -(defun org-fc-index-shuffled-positions (index) - "Return all positions in INDEX in random order. -Positions are shuffled in a way that preserves the order of the - positions for each card." - ;; 1. assign each position a random number - ;; 2. flatten the list - ;; 3. sort by the random number - ;; 4. remove the random numbers from the result - (let ((positions - (mapcan - (lambda (card) - (let ((pos (org-fc-index-flatten-card card))) - (org-fc-zip - (org-fc-sorted-random (length pos)) - pos))) - index))) - (mapcar - #'cdr - (sort positions (lambda (a b) (> (car a) (car b))))))) + :inherited-tags (plist-get card :inherited-tags) + :local-tags (plist-get card :local-tags) + :path (plist-get card :path) + :positions (plist-get card :positions) + :suspended (plist-get card :suspended) + :tags (plist-get card :tags) + :title (plist-get card :title) + :type (plist-get card :type))) + index)) ;;; Demo Mode diff --git a/org-fc-position.el b/org-fc-position.el new file mode 100644 index 0000000..59f7be9 --- /dev/null +++ b/org-fc-position.el @@ -0,0 +1,80 @@ +;;; org-fc-position.el --- Representation of a position -*- lexical-binding: t; -*- +;; +;; Copyright (C) 2022 Cash Weaver +;; +;; Author: Cash Weaver +;; Maintainer: Cash Weaver +;; Created: September 26, 2022 +;; Modified: September 26, 2022 +;; Version: 0.0.1 +;; Keywords: abbrev bib c calendar comm convenience data docs emulations extensions faces files frames games hardware help hypermedia i18n internal languages lisp local maint mail matching mouse multimedia news outlines processes terminals tex tools unix vc wp +;; Homepage: https://github.com/cashweaver/org-fc-position +;; Package-Requires: ((emacs "24.3")) +;; +;; This file is not part of GNU Emacs. +;; +;;; Commentary: +;; +;; A position is a single "view" of a card. +;; +;;; Code: + +(require 'eieio) +(require 'dash) + +(defclass org-fc-position () + ((box + :initform 0 + :initarg :box + :type (integer -1 *) + :documentation "The positions's current Leitner system box.") + (card + :initform nil + :initarg :card + :documentation "The `org-fc-card' which contains this position.") + (due + :initform nil + :initarg :due + :type list + :custom (repeat integer) + :documentation "The time at which this position is due.") + (ease + :initform 0 + :initarg :ease + :type (or float integer) + :documentation "The ease factor.") + (interval + :initform 0 + :initarg :interval + :type (or float integer) + :documentation "The interval, in days.") + (pos + :initform "" + :initarg :pos + :type (or integer string) + :documentation "The name of the position (e.g. \"front\" for double/normal or \"0\" for cloze).")) + "Represents a single position.") + +(cl-defmethod org-fc-position--is-due ((pos org-fc-position)) + "Return t if POS is due; else nil." + (time-less-p (oref pos due) (current-time))) + +(defun org-fc-positions--filter-due (positions) + "Filter POSITIONS to include only enabled and due positions." + (let ((due-enabled-positions (cl-loop for pos in positions + when (and (not (oref (oref pos card) suspended)) + (org-fc-position--is-due pos)) + collect pos))) + (if org-fc-bury-siblings + (org-fc-positions--one-per-card due-enabled-positions) + due-enabled-positions))) + +(defun org-fc-positions--one-per-card (positions) + "Return filtered list of POSITIONS; only one position per card." + (let ((-compare-fn (lambda (a b) + (equal (oref (oref a card) id) + (oref (oref b card) id))))) + (-distinct positions))) + +(provide 'org-fc-position) +;;; org-fc-position.el ends here diff --git a/org-fc-review.el b/org-fc-review.el index d539439..610a7ea 100644 --- a/org-fc-review.el +++ b/org-fc-review.el @@ -37,7 +37,9 @@ (require 'eieio) +(require 'org-fc-awk) (require 'org-fc-core) +(require 'org-fc-position) ;;; Hooks @@ -101,17 +103,19 @@ Valid contexts: (when (yes-or-no-p "Flashcards are already being reviewed. Resume? ") (org-fc-review-resume)) (let* ((index (org-fc-index context)) - (cards (org-fc-index-filter-due index))) - (if org-fc-shuffle-positions - (setq cards (org-fc-index-shuffled-positions cards)) - (setq cards (org-fc-index-positions cards))) + (cards (org-fc-index--to-cards index)) + (positions (org-fc-positions--filter-due + (org-fc-cards--to-positions cards))) + (positions (if org-fc-shuffle-positions + (org-fc-shuffle positions) + positions))) (if (null cards) (message "No cards due right now") (progn (setq org-fc-review--session - (org-fc-make-review-session cards)) + (org-fc-review-session--create positions)) (run-hooks 'org-fc-before-review-hook) - (org-fc-review-next-card)))))) + (org-fc-review-next-position)))))) (defun org-fc-review-resume () "Resume review session, if it was paused." @@ -119,7 +123,7 @@ Valid contexts: (if org-fc-review--session (progn (org-fc-review-edit-mode -1) - (org-fc-review-next-card 'resuming)) + (org-fc-review-next-position 'resuming)) (message "No session to resume to"))) ;;;###autoload @@ -134,17 +138,17 @@ Valid contexts: (interactive) (org-fc-review org-fc-context-all)) -(defun org-fc-review-next-card (&optional resuming) +(defun org-fc-review-next-position (&optional resuming) "Review the next card of the current session. If RESUMING is non-nil, some parts of the buffer setup are skipped." - (if (not (null (oref org-fc-review--session cards))) + (if (not (null (oref org-fc-review--session positions))) (condition-case err - (let* ((card (pop (oref org-fc-review--session cards))) - (path (plist-get card :path)) - (id (plist-get card :id)) - (type (plist-get card :type)) - (position (plist-get card :position))) - (setf (oref org-fc-review--session current-item) card) + (let* ((pos (pop (oref org-fc-review--session positions))) + (card (oref pos card)) + (path (oref card path)) + (id (oref card id)) + (type (oref card type))) + (setf (oref org-fc-review--session current-item) pos) (let ((buffer (find-buffer-visiting path))) (with-current-buffer (find-file path) (unless resuming @@ -168,7 +172,7 @@ If RESUMING is non-nil, some parts of the buffer setup are skipped." (run-hooks 'org-fc-before-setup-hook) (setq org-fc-review--timestamp (time-to-seconds (current-time))) - (let ((step (funcall (org-fc-type-setup-fn type) position))) + (let ((step (funcall (org-fc-type-setup-fn type) (oref pos pos)))) (run-hooks 'org-fc-after-setup-hook) ;; If the card has a no-noop flip function, @@ -193,7 +197,7 @@ same ID as the current card in the session." (declare (indent defun)) `(if org-fc-review--session (if-let ((,var (oref org-fc-review--session current-item))) - (if (string= (plist-get ,var :id) (org-id-get)) + (if (string= (oref (oref ,var card) id) (org-id-get)) (progn ,@body) (message "Flashcard ID mismatch")) (message "No flashcard review is in progress")))) @@ -202,8 +206,9 @@ same ID as the current card in the session." "Flip the current flashcard." (interactive) (condition-case err - (org-fc-review-with-current-item card - (let ((type (plist-get card :type))) + (org-fc-review-with-current-item pos + (let* ((card (oref pos card)) + (type (oref card type))) (funcall (org-fc-type-flip-fn type)) (run-hooks 'org-fc-after-flip-hook) (org-fc-review-rate-mode))) @@ -215,25 +220,25 @@ same ID as the current card in the session." "Rate the card at point with RATING." (interactive) (condition-case err - (org-fc-review-with-current-item card - (let* ((path (plist-get card :path)) - (id (plist-get card :id)) - (position (plist-get card :position)) + (org-fc-review-with-current-item pos + (let* ((card (oref pos card)) + (path (oref card path)) + (id (oref card id)) (now (time-to-seconds (current-time))) (delta (- now org-fc-review--timestamp))) (org-fc-review-add-rating org-fc-review--session rating) - (org-fc-review-update-data path id position rating delta) + (org-fc-review-update-data path id pos rating delta) (org-fc-review-reset) (if (and (eq rating 'again) org-fc-append-failed-cards) - (with-slots (cards) org-fc-review--session - (setf cards (append cards (list card))))) + (with-slots (positions) org-fc-review--session + (setf positions (append positions (list pos))))) (save-buffer) (if org-fc-reviewing-existing-buffer (org-fc-review-reset) (kill-buffer)) - (org-fc-review-next-card))) + (org-fc-review-next-position))) (error (org-fc-review-quit) (signal (car err) (cdr err))))) @@ -262,21 +267,23 @@ same ID as the current card in the session." "Skip card and proceed to next." (interactive) (org-fc-review-reset) - (org-fc-review-next-card)) + (org-fc-review-next-position)) (defun org-fc-review-suspend-card () "Suspend card and proceed to next." (interactive) (org-fc-suspend-card) ;; Remove all other positions from review session - (with-slots (current-item cards) org-fc-review--session - (let ((id (plist-get current-item :id))) - (setf cards + (with-slots (current-item positions) org-fc-review--session + (let* ((card (oref current-item card)) + (id (oref card id))) + (setf positions (cl-remove-if (lambda (card) - (string= id (plist-get card :id))) cards)))) + (string= id (oref card id))) + positions)))) (org-fc-review-reset) - (org-fc-review-next-card)) + (org-fc-review-next-position)) (defun org-fc-review-update-data (path id position rating delta) "Update the review data of the card. @@ -289,18 +296,21 @@ rating the card." ;; don't update its review data (unless (member org-fc-demo-tag (org-get-tags)) (let* ((data (org-fc-review-data-get)) - (current (assoc position data #'string=))) + (pos-pos (oref position pos)) + (current (assoc pos-pos + data + #'string=))) (unless current (error "No review data found for this position")) - (let ((ease (string-to-number (cl-second current))) - (box (string-to-number (cl-third current))) - (interval (string-to-number (cl-fourth current)))) + (let ((ease (oref position ease)) + (box (oref position box)) + (interval (oref position interval))) (org-fc-review-history-add (list (org-fc-timestamp-in 0) path id - position + pos-pos (format "%.2f" ease) (format "%d" box) (format "%.2f" interval) @@ -346,7 +356,7 @@ Pauses the review, unnarrows the buffer and activates ;; Queue the current flashcard so it's reviewed a second time (push (oref org-fc-review--session current-item) - (oref org-fc-review--session cards)) + (oref org-fc-review--session positions)) (setf (oref org-fc-review--session paused) t) (setf (oref org-fc-review--session current-item) nil) (org-fc-review-edit-mode 1)) @@ -429,21 +439,78 @@ removed." ;;; Sessions (defclass org-fc-review-session () - ((current-item :initform nil) - (paused :initform nil :initarg :paused) - (history :initform nil) - (ratings :initform nil :initarg :ratings) - (cards :initform nil :initarg :cards))) - -(defun org-fc-make-review-session (cards) - "Create a new review session with CARDS." - (make-instance - 'org-fc-review-session - :ratings - (if-let ((stats (org-fc-awk-stats-reviews))) - (plist-get stats :day) - '(:total 0 :again 0 :hard 0 :good 0 :easy 0)) - :cards cards)) + ((current-item + :initform nil + :initarg :current-item + :documentation "The `org-fc-position' currently under review.") + (paused + :initform nil + :initarg :paused + :type boolean + :documentation "t when the review session is paused; nil otherwise") + (history + :initform nil + :initarg :paused) + (ratings + :initform nil + :initarg :ratings + :documentation "`org-fc-review-session-rating'." + ) + (positions + :initform nil + :initarg :positions + :documentation "List of `org-fc-position's."))) + +(defclass org-fc-review-session-rating () + ((total + :intiform 0 + :initarg :total + :type integer) + (again + :intiform 0 + :initarg :again + :type integer) + (hard + :intiform 0 + :initarg :hard + :type integer) + (good + :intiform 0 + :initarg :good + :type integer) + (easy + :intiform 0 + :initarg :easy + :type integer))) + +(cl-defmethod org-fc-awk-stats-reviews-as-rating (stats key) + "Return the KEY of STATS as `org-fc-review-session-rating'." + (cl-destructuring-bind + (&key total again hard good easy &allow-other-keys) + (pcase key + ('all + (plist-get stats :all)) + ('month + (plist-get stats :month)) + ('week + (plist-get stats :week)) + ('day + (plist-get stats :day))) + (org-fc-review-session-rating + :total total + :again again + :hard hard + :good good + :easy easy))) + +(defun org-fc-review-session--create (positions) + "Create a new review session with POSITIONS." + (let ((ratings (if-let ((stats (org-fc-awk-stats-reviews))) + (org-fc-awk-stats-reviews-as-rating stats 'day) + (org-fc-review-session-rating)))) + (org-fc-review-session + :positions positions + :ratings ratings))) (defun org-fc-review-history-add (elements) "Add ELEMENTS to review history." @@ -472,11 +539,11 @@ removed." "Store RATING in the review history of SESSION." (with-slots (ratings) session (cl-case rating - ('again (cl-incf (cl-getf ratings :again) 1)) - ('hard (cl-incf (cl-getf ratings :hard) 1)) - ('good (cl-incf (cl-getf ratings :good) 1)) - ('easy (cl-incf (cl-getf ratings :easy) 1))) - (cl-incf (cl-getf ratings :total 1)))) + ('again (cl-incf (oref ratings again) 1)) + ('hard (cl-incf (oref ratings hard) 1)) + ('good (cl-incf (oref ratings good) 1)) + ('easy (cl-incf (oref ratings easy) 1))) + (cl-incf (oref ratings total) 1))) ;;; Header Line @@ -485,12 +552,12 @@ removed." (defun org-fc-set-header-line () "Set the header-line for review." - (let* ((remaining (1+ (length (oref org-fc-review--session cards)))) - (current (oref org-fc-review--session current-item)) - (title - (unless (or org-fc-review-hide-title-in-header-line - (member "notitle" (plist-get current :tags))) - (plist-get current :filetitle)))) + (let* ((remaining (1+ (length (oref org-fc-review--session positions)))) + (current-position (oref org-fc-review--session current-item)) + (current-card (oref current-position card)) + (title (unless (or org-fc-review-hide-title-in-header-line + (member "notitle" (oref current-card tags))) + (oref current-card filetitle)))) (setq org-fc-original-header-line-format header-line-format) (setq-local header-line-format From ef37c5be7bd5074be39af73d08f8efd3d2ff79ca Mon Sep 17 00:00:00 2001 From: Cash Weaver Date: Tue, 4 Oct 2022 09:26:27 -0700 Subject: [PATCH 2/3] fix: Use class accessor in org-fc-cache --- org-fc-cache.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org-fc-cache.el b/org-fc-cache.el index 9168bd6..0c4adbb 100644 --- a/org-fc-cache.el +++ b/org-fc-cache.el @@ -166,7 +166,7 @@ This is especially relevant w.r.t a card's due date / suspension state before re (org-fc-review-with-current-item cur (if (org-fc-suspended-entry-p) (error "Trying to review a suspended card")) - (let* ((position (plist-get cur :position)) + (let* ((position (oref cur pos)) (review-data (org-fc-review-data-get)) (row (assoc position review-data #'string=)) (due (parse-iso8601-time-string (nth 4 row)))) From 335f190dfb03f9504e3ec6e11f9d29064faa3661 Mon Sep 17 00:00:00 2001 From: Cash Weaver Date: Mon, 19 Sep 2022 07:14:39 -0700 Subject: [PATCH 3/3] feat: Allow custom, per-card, cloze context value --- org-fc-core.el | 5 ----- org-fc-type-cloze.el | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/org-fc-core.el b/org-fc-core.el index 542f198..ee449ae 100644 --- a/org-fc-core.el +++ b/org-fc-core.el @@ -85,11 +85,6 @@ Used to generate absolute paths to the awk scripts.") :type 'string :group 'org-fc) -(defcustom org-fc-type-cloze-max-hole-property "FC_CLOZE_MAX" - "Name of the property to use for storing the max hole index." - :type 'string - :group 'org-fc) - (defcustom org-fc-suspended-tag "suspended" "Tag for marking suspended cards." :type 'string diff --git a/org-fc-type-cloze.el b/org-fc-type-cloze.el index 9a1ef04..5a0ee2e 100644 --- a/org-fc-type-cloze.el +++ b/org-fc-type-cloze.el @@ -31,8 +31,22 @@ :type 'string :group 'org-fc) +(defcustom org-fc-type-cloze-max-hole-property "FC_CLOZE_MAX" + "Name of the property to use for storing the max hole index." + :type 'string + :group 'org-fc) + +(defcustom org-fc-type-cloze-context-count-property "FC_CLOZE_CONTEXT_COUNT" + "Number of contextual clozes to show in a `'context'-type card. + +Takes prescedence over `org-fc-type-cloze-context'." + :type 'number + :group 'org-fc) + (defcustom org-fc-type-cloze-context 1 - "Number of surrounding cards to show for 'context' type cards." + "Number of surrounding cards to show for 'context' type cards. + +Use `org-fc-type-cloze-context-count-property' to set this value on a per-card basis." :type 'number :group 'org-fc) @@ -88,14 +102,16 @@ the hole for the current position." (setq current-index (1- (length holes)))))) (cons (reverse holes) current-index))) -(defun org-fc-type-cloze--hole-visible-p (type i current-index) +(defun org-fc-type-cloze--hole-visible-p (type i current-index context-count) "Determine whether hole I of card TYPE should be visible based. -CURRENT-INDEX is the index of the current position in the list of all holes." + +- CURRENT-INDEX is the index of the current position in the list of all holes. +- CONTEXT-COUNT is the number of contextual clozes to show." (cl-case type ('enumeration (< i current-index)) ('deletion t) ('single nil) - ('context (<= (abs (- i current-index)) org-fc-type-cloze-context)) + ('context (<= (abs (- i current-index)) context-count)) (t (error "Org-fc: Unknown cloze card type %s" type)))) (defun org-fc-type-cloze--end () @@ -112,7 +128,12 @@ CURRENT-INDEX is the index of the current position in the list of all holes." (end (1+ (org-fc-type-cloze--end))) (holes-index (org-fc-type-cloze--parse-holes position end)) (holes (car holes-index)) - (current-index (cdr holes-index))) + (current-index (cdr holes-index)) + (context-count (or (ignore-errors + (string-to-number + (org-entry-get (point) + org-fc-type-cloze-context-count-property))) + org-fc-type-cloze-context))) (cl-loop for i below (length holes) for (hole-beg hole-end text-beg text-end hint-beg hint-end) in holes @@ -143,7 +164,7 @@ CURRENT-INDEX is the index of the current position in the list of all holes." 'face 'org-fc-type-cloze-hole-face)) ;; If the text of another hole should be visible, ;; hide the hole markup and the hint - ((org-fc-type-cloze--hole-visible-p type i current-index) + ((org-fc-type-cloze--hole-visible-p type i current-index context-count) (org-fc-hide-region hole-beg text-beg) (org-fc-hide-region text-end hole-end)) ;; If the text of another hole should not be visible, @@ -188,6 +209,8 @@ Processes all holes in the card text." "Update the review data & deletions of the current heading." (let* ((end (org-fc-type-cloze--end)) (hole-id (1+ (org-fc-type-cloze-max-hole-id))) + (cloze-count (or (org-entry-get (point) org-fc-type-cloze-context-count-property) + org-fc-type-cloze-context)) ids) (save-excursion (while (re-search-forward org-fc-type-cloze-hole-re end t) @@ -207,6 +230,12 @@ Processes all holes in the card text." (format "%s" (1- hole-id))) (org-fc-review-data-update (reverse ids)))) +(defun org-fc-type-cloze-set-context (context-count) + "Sets `org-fc-type-cloze-context-count-property'." + (interactive "nCloze count: ") + (org-set-property org-fc-type-cloze-context-count-property + (format "%s" context-count))) + (org-fc-register-type 'cloze 'org-fc-type-cloze-setup