branch: externals/gnosis
commit aef7f4a4d7f06a83925d25d9e80110af80d5af47
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>
[Refactor] gnosis.el: Use gnosis-review and gnosis-import-export module.
* Separate gnosis-review and gnosis-export/import logic.
---
gnosis.el | 1116 ++-----------------------------------------------------------
1 file changed, 27 insertions(+), 1089 deletions(-)
diff --git a/gnosis.el b/gnosis.el
index fe6523624f..a45194b659 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -189,13 +189,6 @@ This is set automatically based on buffer type:
(defvar gnosis-testing nil
"Change this to non-nil when running manual tests.")
-(defvar gnosis-review-types '("Due themata"
- "Due themata of deck"
- "Due themata of specified tag(s)"
- "Overdue themata"
- "Due themata (Without Overdue)"
- "All themata of deck"
- "All themata of tag(s)"))
(defconst gnosis-db-version 4
"Gnosis database version.")
@@ -217,11 +210,34 @@ This is set automatically based on buffer type:
(defvar gnosis-due-themata-total nil
"Total due themata.")
-(defvar gnosis-review-themata nil
- "Review themata.")
-(defvar gnosis-review-buffer-name "*gnosis*"
- "Review buffer name.")
+;; Review autoloads
+(autoload 'gnosis-review "gnosis-review" nil t)
+(autoload 'gnosis-review-topic "gnosis-review" nil t)
+(autoload 'gnosis-review-get-due-themata "gnosis-review")
+(autoload 'gnosis-review-get--due-themata "gnosis-review")
+(autoload 'gnosis-review-is-due-p "gnosis-review")
+(autoload 'gnosis-review-is-due-today-p "gnosis-review")
+(autoload 'gnosis-review-is-thema-new-p "gnosis-review")
+(autoload 'gnosis-review-get-overdue-themata "gnosis-review")
+(autoload 'gnosis-review-algorithm "gnosis-review")
+(autoload 'gnosis-display-next-review "gnosis-review")
+(autoload 'gnosis-get-linked-nodes "gnosis-review")
+(autoload 'gnosis-monkeytype-start "gnosis-review" nil t)
+(autoload 'gnosis-history-clear "gnosis-review" nil t)
+
+;; Export/import autoloads
+(autoload 'gnosis-export--insert-thema "gnosis-export-import")
+(autoload 'gnosis-export-themata "gnosis-export-import")
+(autoload 'gnosis-export-parse-themata "gnosis-export-import")
+(autoload 'gnosis-export-parse--deck-name "gnosis-export-import")
+(autoload 'gnosis-export-deck "gnosis-export-import" nil t)
+(autoload 'gnosis-export-deck-async "gnosis-export-import" nil t)
+(autoload 'gnosis-save-thema "gnosis-export-import")
+(autoload 'gnosis-save "gnosis-export-import" nil t)
+(autoload 'gnosis-save-deck "gnosis-export-import" nil t)
+(autoload 'gnosis-import-deck "gnosis-export-import" nil t)
+(autoload 'gnosis-import-deck-async "gnosis-export-import" nil t)
(defvar gnosis-export-separator "\n- ")
@@ -445,22 +461,6 @@ Respects `gnosis-center-content' buffer-local setting."
(font-lock-ensure)
(buffer-string)))
-(defun gnosis-display-keimenon (str)
- "Display STR as keimenon."
- (with-current-buffer gnosis-review-buffer-name
- (erase-buffer)
- (insert "\n" (gnosis-format-string str))
- (gnosis-insert-separator)
- (gnosis-apply-center-buffer-overlay)))
-
-(defun gnosis-display-image (keimenon)
- "Display image link from KEIMENON in new window."
- (let ((image-path (and (string-match "\\[file:\\(.*?\\)\\]" keimenon)
- (match-string 1 keimenon))))
- (when image-path
- (find-file-other-window image-path)
- (switch-to-buffer-other-window gnosis-review-buffer-name))))
-
(defun gnosis-cloze-create (str clozes &optional cloze-string)
"Replace CLOZES in STR with CLOZE-STRING, preserving whitespace pattern."
(cl-assert (listp clozes) nil "Adding clozes: Clozes need to be a list.")
@@ -516,106 +516,6 @@ First item of answers will be marked as false, while the
rest unanswered."
(setq final (or str-with-false str)))
final))
-(defun gnosis-display-cloze-string (str clozes hints correct false)
- "Display STR with CLOZES and HINTS.
-
-Applies highlighting for CORRECT & FALSE."
- (let* ((cloze-str (gnosis-cloze-create str clozes))
- (str-with-hints (gnosis-cloze-add-hints cloze-str hints))
- (str-with-c-answers
- (gnosis-utils-highlight-words str-with-hints correct
'gnosis-face-correct))
- (final (gnosis-cloze-mark-false str-with-c-answers false)))
- (gnosis-display-keimenon final)))
-
-(defun gnosis-display-basic-answer (answer success user-input)
- "Display ANSWER.
-
-When SUCCESS nil, display USER-INPUT as well"
- (with-current-buffer gnosis-review-buffer-name
- (goto-char (point-max))
- (insert "\n\n"
- (propertize "Answer:" 'face 'gnosis-face-directions)
- " "
- (propertize answer 'face 'gnosis-face-correct))
- (when gnosis-center-content
- (gnosis-center-current-line))
- ;; Insert user wrong answer
- (when (not success)
- (insert "\n"
- (propertize "Your answer:" 'face 'gnosis-face-directions)
- " "
- (propertize user-input 'face 'gnosis-face-false))
- (when gnosis-center-content
- (gnosis-center-current-line)))))
-
-(defun gnosis-display-hint (hint)
- "Display HINT."
- (let ((hint (or hint "")))
- (unless (string-empty-p hint)
- (goto-char (point-max))
- (and (not (string-empty-p hint))
- (insert "\n" (gnosis-format-string (propertize hint 'face
'gnosis-face-hint))))
- (gnosis-insert-separator))))
-
-(defun gnosis-display-cloze-user-answer (user-input &optional false)
- "Display USER-INPUT answer for cloze thema upon failed review.
-
-If FALSE t, use gnosis-face-false face"
- (goto-char (point-max))
- (insert "\n\n"
- (propertize "Your answer:" 'face 'gnosis-face-directions)
- " "
- (propertize user-input 'face
- (if false 'gnosis-face-false 'gnosis-face-correct)))
- (when gnosis-center-content
- (gnosis-center-current-line))
- (newline))
-
-(defun gnosis-display-correct-answer-mcq (answer user-choice)
- "Display correct ANSWER & USER-CHOICE for MCQ thema."
- (goto-char (point-max))
- (insert (gnosis-format-string
- (format "%s %s\n%s %s"
- (propertize "Correct Answer:" 'face 'gnosis-face-directions)
- (propertize answer 'face 'gnosis-face-correct)
- (propertize "Your answer:" 'face 'gnosis-face-directions)
- (propertize user-choice 'face (if (string= answer
user-choice)
- 'gnosis-face-correct
- 'gnosis-face-false))))
- "\n")
- (gnosis-insert-separator))
-
-(defun gnosis-display-parathema (parathema)
- "Display PARATHEMA."
- (when (and parathema (not (string-empty-p parathema)))
- (search-backward "----") ; search back for separator
- (forward-line 1)
- (insert "\n" (gnosis-format-string (gnosis-org-format-string parathema))
"\n")))
-
-(defun gnosis-display-next-review (id success)
- "Display next interval of thema ID for SUCCESS."
- (with-current-buffer gnosis-review-buffer-name
- (let* ((interval (car (gnosis-review-algorithm id success)))
- (next-review-msg (format "\n\n%s %s"
- (propertize "Next review:" 'face
'gnosis-face-directions)
- (propertize
- (replace-regexp-in-string
- "[]()[:space:]]"
- (lambda (match)
- (if (string= match " ") "/" ""))
- (format "%s" interval) t t)
- 'face 'gnosis-face-next-review))))
- (if (search-backward "Next review" nil t)
- ;; Delete previous result, and override with new this should
- ;; occur only when used for overriding review result.
- (progn (delete-region (point) (progn (end-of-line) (point)))
- (insert (propertize (replace-regexp-in-string "\n" ""
next-review-msg)
- 'face (if success 'gnosis-face-correct
- 'gnosis-face-false))))
- ;; Default behaviour
- (goto-char (point-max))
- (insert (gnosis-format-string next-review-msg))))))
-
(cl-defun gnosis--prompt (prompt &optional (downcase nil) (split nil))
"PROMPT user for input until `q' is given.
@@ -956,41 +856,6 @@ START is the search starting position, used internally for
recursion."
(gnosis-extract-id-links input (match-end 0)))
nil)))
-(defun gnosis-get-linked-nodes (id)
- "Return the title of linked org-gnosis node(s) for thema ID."
- (let* ((links (gnosis-select 'dest 'links `(= source ,id) t))
- (org-gnosis-nodes (cl-loop for node-id in links
- collect (org-gnosis-select 'title 'nodes
`(= id ,node-id) t))))
- (and links (apply #'append org-gnosis-nodes))))
-
-(defun gnosis-view-linked-node (id)
- "Visit linked node(s) for thema ID."
- (let* ((node (gnosis-completing-read "Select node: "
(gnosis-get-linked-nodes id) t)))
- (window-configuration-to-register :gnosis-link-view)
- (org-gnosis-find node)
- (gnosis-link-view-mode)))
-
-(defun gnosis-link-view--exit ()
- "Exit link view mode."
- (interactive nil gnosis-link-view-mode)
- (gnosis-link-view-mode -1)
- (jump-to-register :gnosis-link-view)
- (exit-recursive-edit))
-
-(defvar-keymap gnosis-link-view-mode-map
- :doc "Keymap for `gnosis-link-view-mode'."
- "C-c C-c" #'gnosis-link-view--exit)
-
-(define-minor-mode gnosis-link-view-mode "Gnosis Link View."
- :interactive nil
- :lighter " Gnosis Link View"
- :keymap gnosis-link-view-mode-map
- (if gnosis-link-view-mode
- (setq-local header-line-format
- (substitute-command-keys
- " Return to review with: \\[gnosis-link-view--exit]"))
- (setq-local header-line-format nil)))
-
;; TODO: Rewrite this! Tags should be an input of strings,
;; interactive handling should be done by "helper" funcs
(cl-defun gnosis-collect-thema-ids (&key (tags nil) (due nil) (deck nil)
(query nil))
@@ -1025,243 +890,6 @@ QUERY: String value."
((and (null tags) (null due) (null deck) query)
(gnosis-search-thema query))))
-;; Review
-;;;;;;;;;;
-
-(defun gnosis-review-is-due-p (thema-id)
- "Check if thema with value of THEMA-ID for id is due for review.
-
-Check if it's suspended, and if it's due today."
- (and (not (gnosis-suspended-p thema-id))
- (gnosis-review-is-due-today-p thema-id)))
-
-(defun gnosis-review-is-due-today-p (id)
- "Return t if thema with ID is due today.
-
-This function ignores if thema is suspended. Refer to
-`gnosis-review-is-due-p' if you need to check for suspended value as
-well."
- (let ((next-rev (gnosis-get 'next-rev 'review-log `(= id ,id))))
- (gnosis-past-or-present-p next-rev)))
-
-(defun gnosis-review-get--due-themata ()
- "Return due thema IDs & due dates."
- (let* ((today (gnosis--date-to-int (gnosis-algorithm-date)))
- (old-themata (cl-loop for thema in
- (gnosis-select '[id next-rev] 'review-log
- '(and (> n 0)
- (= suspend 0))
- nil)
- when (<= (gnosis--date-to-int (cadr thema))
today)
- collect thema))
- (new-themata (cl-loop for thema in
- (gnosis-select '[id next-rev] 'review-log
- '(and (= n 0)
- (= suspend 0))
- nil)
- when (<= (gnosis--date-to-int (cadr thema))
today)
- collect thema)))
- (if gnosis-review-new-first
- (append (cl-subseq new-themata 0 gnosis-new-themata-limit) old-themata)
- (append old-themata (cl-subseq new-themata 0
gnosis-new-themata-limit)))))
-
-(defun gnosis-review-get-due-themata ()
- "Return all due thema IDs."
- (mapcar #'car (gnosis-review-get--due-themata)))
-
-(defun gnosis-review-get-overdue-themata (&optional thema-ids)
- "Return overdue themata for current DATE.
-
-Optionally, provide THEMA-IDS of which the overdue ones will be returned."
- (cl-loop for thema in (or thema-ids (gnosis-review-get--due-themata))
- when (not (equal (cadr thema) (gnosis-algorithm-date)))
- collect (car thema)))
-
-(defun gnosis-review-last-interval (id)
- "Return last review interval for thema ID."
- (let* ((last-rev (gnosis-get 'last-rev 'review-log `(= id ,id)))
- (rev-date (gnosis-get 'next-rev 'review-log `(= id ,id))))
- (gnosis-algorithm-date-diff last-rev rev-date)))
-
-(defun gnosis-review-algorithm (id success)
- "Return next review date & gnosis for thema with value of id ID.
-
-SUCCESS is a boolean value, t for success, nil for failure.
-
-Returns a list of the form ((yyyy mm dd) (ef-increase ef-decrease ef-total))."
- (let ((amnesia (gnosis-get-thema-amnesia id))
- (gnosis (gnosis-get 'gnosis 'review `(= id ,id)))
- (t-success (gnosis-get 't-success 'review-log `(= id ,id))) ;; total
successful reviews
- (c-success (gnosis-get 'c-success 'review-log `(= id ,id))) ;;
consecutive successful reviews
- (c-fails (gnosis-get 'c-fails 'review-log `(= id ,id))) ;; consecutive
failed reviews
- ;; (t-fails (gnosis-get 't-fails 'review-log `(= id ,id))) ;; total
failed reviews
- ;; (review-num (gnosis-get 'n 'review-log `(= id ,id))) ;; total reviews
- ;; (last-interval (max (gnosis-review--get-offset id) 1))
- (last-interval (gnosis-review-last-interval id))) ;; last interval
- (list
- (gnosis-algorithm-next-interval
- :last-interval last-interval
- :gnosis-synolon (nth 2 gnosis)
- :success success
- :successful-reviews t-success
- :c-fails c-fails
- :lethe (gnosis-get-thema-lethe id)
- :amnesia amnesia
- :proto (gnosis-get-thema-proto id))
- (gnosis-algorithm-next-gnosis
- :gnosis gnosis
- :success success
- :epignosis (gnosis-get-thema-epignosis id)
- :agnoia (gnosis-get-thema-agnoia id)
- :anagnosis (gnosis-get-thema-anagnosis id)
- :c-successes c-success
- :c-failures c-fails
- :lethe (gnosis-get-thema-lethe id)))))
-
-(defun gnosis-review--update (id success)
- "Update review-log for thema ID.
-
-SUCCESS is a boolean value, t for success, nil for failure."
- (let* ((result (gnosis-review-algorithm id success))
- (next-rev (car result))
- (gnosis-score (cadr result))
- (log (car (gnosis-select '[n c-success c-fails t-success t-fails]
- 'review-log `(= id ,id))))
- (n (nth 0 log))
- (c-success (nth 1 log))
- (c-fails (nth 2 log))
- (t-success (nth 3 log))
- (t-fails (nth 4 log)))
- (gnosis-review-increment-activity-log (not (> n 0)))
- ;; Single review-log UPDATE
- (emacsql gnosis-db
- "UPDATE review_log SET last_rev = $s1, next_rev = $s2, n = $s3,
c_success = $s4, c_fails = $s5, t_success = $s6, t_fails = $s7 WHERE id = $s8"
- (gnosis-algorithm-date) next-rev (1+ n)
- (if success (1+ c-success) 0)
- (if success 0 (1+ c-fails))
- (if success (1+ t-success) t-success)
- (if success t-fails (1+ t-fails))
- id)
- ;; Single review UPDATE
- (gnosis-update 'review `(= gnosis ',gnosis-score) `(= id ,id))))
-
-(defun gnosis-review-result (id success)
- "Update review thema ID results for SUCCESS."
- (gnosis-review--update id success)
- (setf gnosis-due-themata-total (length (gnosis-review-get-due-themata))))
-
-(defun gnosis-review-mcq (id)
- "Review MCQ thema with ID."
- (gnosis-display-image (gnosis-get 'keimenon 'themata `(= id ,id)))
- (gnosis-display-keimenon (gnosis-org-format-string
- (gnosis-get 'keimenon 'themata `(= id ,id))))
- (let* ((answer (car (gnosis-get 'answer 'themata `(= id ,id))))
- (user-choice (gnosis-mcq-answer id))
- (success (string= answer user-choice)))
- (gnosis-display-correct-answer-mcq answer user-choice)
- (gnosis-display-parathema (gnosis-get 'parathema 'extras `(= id ,id)))
- (gnosis-display-next-review id success)
- success))
-
-(defun gnosis-review-basic (id)
- "Review basic type thema for ID."
- (let* ((hypothesis (car (gnosis-get 'hypothesis 'themata `(= id ,id))))
- (parathema (gnosis-get 'parathema 'extras `(= id ,id)))
- (keimenon (gnosis-get 'keimenon 'themata `(= id ,id)))
- (answer (car (gnosis-get 'answer 'themata `(= id ,id)))))
- (gnosis-display-image keimenon)
- (gnosis-display-keimenon (gnosis-org-format-string keimenon))
- (gnosis-display-hint hypothesis)
- (let* ((answer answer)
- (user-input (read-string "Answer: "))
- (success (gnosis-compare-strings answer user-input)))
- (gnosis-display-basic-answer answer success user-input)
- (gnosis-display-parathema parathema)
- (gnosis-display-next-review id success)
- success)))
-
-(defun gnosis-review-cloze--input (clozes &optional user-input)
- "Prompt for USER-INPUT during cloze review.
-
-CLOZES is a list of possible correct answers.
-
-Returns a cons; ='(position . user-input) if correct,
-='(nil . user-input) if incorrect."
- (let* ((user-input (or user-input (read-string "Answer: ")))
- (position (cl-position user-input clozes :test
#'gnosis-compare-strings)))
- (cons position user-input)))
-
-(defun gnosis-review-cloze (id)
- "Review cloze type thema for ID."
- (let* ((keimenon (gnosis-get 'keimenon 'themata `(= id ,id)))
- (all-clozes (gnosis-get 'answer 'themata `(= id ,id)))
- (all-hints (gnosis-get 'hypothesis 'themata `(= id ,id)))
- (revealed-clozes '()) ;; List of revealed clozes
- (unrevealed-clozes all-clozes)
- (unrevealed-hints all-hints)
- (parathema (gnosis-get 'parathema 'extras `(= id ,id)))
- (success t))
- ;; Initially display the sentence with no reveals
- (gnosis-display-cloze-string keimenon unrevealed-clozes unrevealed-hints
nil nil)
- (catch 'done
- (while unrevealed-clozes
- (let* ((input (gnosis-review-cloze--input unrevealed-clozes))
- (position (car input))
- (matched-cloze (when position (nth position unrevealed-clozes)))
- (matched-hint (when (and position (< position (length
unrevealed-hints)))
- (nth position unrevealed-hints))))
- (if matched-cloze
- ;; Correct answer - move cloze from unrevealed to revealed
- (progn
- ;; Add to revealed clozes list, preserving original order
- (setq revealed-clozes
- (cl-sort (cons matched-cloze revealed-clozes)
- #'< :key (lambda (cloze)
- (cl-position cloze all-clozes))))
- ;; Remove from unrevealed lists by position
- (setq unrevealed-clozes (append (cl-subseq unrevealed-clozes 0
position)
- (cl-subseq unrevealed-clozes
(1+ position))))
- (when (and matched-hint (< position (length unrevealed-hints)))
- (setq unrevealed-hints (append (cl-subseq unrevealed-hints 0
position)
- (cl-subseq unrevealed-hints
(1+ position)))))
- ;; Display with updated revealed/unrevealed lists
- (gnosis-display-cloze-string keimenon unrevealed-clozes
unrevealed-hints
- revealed-clozes nil))
- ;; Incorrect answer
- (gnosis-display-cloze-string keimenon nil nil
- revealed-clozes unrevealed-clozes)
- (gnosis-display-cloze-user-answer (cdr input))
- (setq success nil)
- (throw 'done nil)))))
- (gnosis-display-parathema parathema)
- (gnosis-display-next-review id success)
- success))
-
-(defun gnosis-review-mc-cloze (id)
- "Review mc-cloze type thema for ID."
- (let* ((keimenon (gnosis-get 'keimenon 'themata `(= id ,id)))
- (cloze (gnosis-get 'answer 'themata `(= id ,id)))
- (options (gnosis-get 'hypothesis 'themata `(= id ,id)))
- (parathema (gnosis-get 'parathema 'extras `(= id ,id)))
- (user-input)
- (success))
- (gnosis-display-cloze-string keimenon cloze nil nil nil)
- (setq user-input (gnosis-completing-read "Select answer: "
- (gnosis-shuffle options)))
- (if (string= user-input (car cloze))
- (progn
- (gnosis-display-cloze-string keimenon nil nil cloze nil)
- (setq success t))
- (gnosis-display-cloze-string keimenon nil nil nil cloze)
- (gnosis-display-correct-answer-mcq (car cloze) user-input))
- (gnosis-display-parathema parathema)
- (gnosis-display-next-review id success)
- success))
-
-(defun gnosis-review-is-thema-new-p (id)
- "Return t if thema with ID is new."
- (let ((reviews (car (gnosis-select 'n 'review-log `(= id ,id) t))))
- (not (> reviews 0))))
(defun gnosis-get-themata-by-reviews (max-reviews &optional thema-ids)
"Return thema IDs with at most MAX-REVIEWS total reviews.
@@ -1273,318 +901,6 @@ When THEMA-IDS is non-nil, restrict to that subset."
`(<= n ,max-reviews))
t))
-(defun gnosis-review-increment-activity-log (new? &optional date)
- "Increment activity log for DATE by one.
-
-If NEW? is non-nil, increment new themata log by 1."
- (let* ((current-total-value (gnosis-get-date-total-themata))
- (inc-total (cl-incf current-total-value))
- (current-new-value (gnosis-get-date-new-themata))
- (inc-new (cl-incf current-new-value))
- (date (or date (gnosis-algorithm-date))))
- (gnosis-update 'activity-log `(= reviewed-total ,inc-total) `(= date
',date))
- (and new? (gnosis-update 'activity-log `(= reviewed-new ,inc-new) `(= date
',date)))))
-
-(defun gnosis-history-clear ()
- "Delete all activity log entries."
- (interactive)
- (when (y-or-n-p "Delete all activity log?")
- (emacsql gnosis-db [:delete :from activity-log])))
-
-(defun gnosis-review--display-thema (id)
- "Display thema with ID and call the appropriate review func."
- (let* ((type (gnosis-get 'type 'themata `(= id ,id)))
- (func-name (intern (format "gnosis-review-%s" (downcase type)))))
- (if (fboundp func-name)
- (progn
- (unless (eq major-mode 'gnosis-mode)
- (pop-to-buffer-same-window (get-buffer-create
gnosis-review-buffer-name))
- (gnosis-mode)
- (gnosis-review-update-header 0))
- (window-configuration-to-register :gnosis-pre-image)
- (funcall func-name id))
- (error "Malformed thema type: '%s'" type))))
-
-(defun gnosis-monkeytype-session (themata &rest _)
- "Start monkeytype session for THEMATA ids."
- (cl-assert (listp themata) nil "Themata must be a list of ids")
- (catch 'monkeytype-loop
- (cl-loop for thema in themata
- do (gnosis-monkeytype-thema thema))))
-
-(defun gnosis-monkeytype-start ()
- "Gnosis Monkeytype Session"
- (interactive)
- (gnosis-review #'gnosis-monkeytype-session))
-
-(defun gnosis-monkeytype-thema (thema)
- "Process monkeytyping for THEMA id.
-
-This is used to type the keimenon of thema, with the answers highlighted.
-To monkeytype only the wrong answers use `gnosis-monkeytype-answer'."
- (let* ((thema-context (gnosis-select '[keimenon type answer] 'themata `(= id
,thema) t))
- (keimenon (replace-regexp-in-string
- "\\[\\[\\([^]]+\\)\\]\\[\\([^]]+\\)\\]\\]" "\\2" ;; remove
links
- (nth 0 thema-context)))
- (type (nth 1 thema-context))
- (answer (cl-loop for answer in (nth 2 thema-context)
- collect (gnosis-utils-trim-quotes answer))))
- (cond ((string= type "basic")
- (gnosis-monkeytype (concat keimenon "\n" (car answer)) type
- answer))
- (t (gnosis-monkeytype keimenon type answer)))))
-
-(defun gnosis-monkeytype-answer (thema)
- "Monkeytype answer for THEMA id."
- (let* ((thema-context (gnosis-select '[type answer] 'themata `(= id ,thema)
t))
- (type (nth 0 thema-context))
- (answer (cl-loop for answer in (nth 1 thema-context)
- collect (gnosis-utils-trim-quotes answer))))
- (gnosis-monkeytype (mapconcat #'identity answer " ") type answer)))
-
-(defun gnosis-review-process-thema (thema &optional thema-count)
- "Process review for THEMA and update session statistics.
-
-Displays the thema, processes the review result, and updates the
-header. Returns the incremented THEMA-COUNT after processing.
-
-This is a helper function for `gnosis-review-session'."
- (let ((success (gnosis-review--display-thema thema))
- (thema-count (or thema-count 0)))
- (cl-incf thema-count)
- (unless success (gnosis-monkeytype-answer thema))
- (gnosis-review-actions success thema thema-count)
- ;; Use jump-to-register after first review.
- (and (not (null (get-register :gnosis-pre-image))) (jump-to-register
:gnosis-pre-image))
- (setq gnosis-review-themata (remove thema gnosis-review-themata))
- (gnosis-review-update-header thema-count (length gnosis-review-themata))
- thema-count))
-
-(defun gnosis-review-update-header (reviewed-count &optional remaining-reviews)
- "Update the review session header with current stats.
-
-REVIEWED-COUNT: Total number of items that have been reviewed in
-current session.
-REMAINING-REVIEWS: Total number of remaining items to be reviewed."
- (with-current-buffer (get-buffer-create gnosis-review-buffer-name)
- (let ((remaining-reviews (or remaining-reviews (1+ (length
gnosis-review-themata)))))
- (setq-local header-line-format
- (gnosis-center-string
- (format "%s %s %s"
- (propertize (number-to-string reviewed-count)
- 'face 'font-lock-type-face)
- (propertize "|" 'face 'font-lock-comment-face)
- (propertize (number-to-string remaining-reviews)
- 'face 'gnosis-face-false)))))))
-
-(defun gnosis-review-session (themata &optional due thema-count)
- "Start review session for THEMATA.
-THEMATA: List of thema ids
-DUE: If due is non-nil, session will loop for due themata.
-THEMA-COUNT: Total themata to be commited for session."
- (let ((thema-count (or thema-count 0)))
- (if (null themata)
- (message "No themata for review.")
- (setf gnosis-review-themata themata)
- (catch 'review-loop
- (cl-loop for thema in themata
- do (setq thema-count (gnosis-review-process-thema thema
thema-count))
- finally
- (and due (gnosis-review-session
- (gnosis-collect-thema-ids :due t) t thema-count))))
- (gnosis-dashboard)
- (gnosis-review-commit thema-count))))
-
-(defun gnosis-review-commit (thema-num)
- "Commit review session on git repository.
-
-This function initializes the `gnosis-dir' as a Git repository if it is not
-already one. It then adds the gnosis.db file to the repository and commits
-the changes with a message containing the reviewed number THEMA-NUM."
- (let ((git (executable-find "git"))
- (default-directory gnosis-dir))
- (unless git
- (error "Git not found, please install git"))
- (unless (file-exists-p (expand-file-name ".git" gnosis-dir))
- (vc-git-create-repo))
- (unless gnosis-testing
- (shell-command
- (format "%s add gnosis.db" git))
- (gnosis--shell-cmd-with-password
- (format "%s commit -m 'Total themata reviewed: %d'" git thema-num)))
- (sit-for 0.1)
- (when (and gnosis-vc-auto-push (not gnosis-testing))
- (gnosis-vc-push))
- (message "Review session finished. %d themata reviewed." thema-num)))
-
-(defun gnosis-review-action--edit (success thema thema-count)
- "Edit THEMA during review.
-
-Save current contents of *gnosis-edit* buffer, if any, and start
-editing THEMA with it's new contents.
-
-After done editing, call `gnosis-review-actions' with SUCCESS THEMA
-THEMA-COUNT."
- (gnosis-edit-thema thema)
- (setf gnosis-review-editing-p t)
- (recursive-edit)
- (gnosis-review-actions success thema thema-count))
-
-(defun gnosis-review-action--quit (success thema)
- "Quit review session.
-
-Update result for THEMA review with SUCCESS and commit session for THEMA-COUNT.
-
-This function should be used with `gnosis-review-actions', to finish
-the review session."
- (gnosis-review-result thema success)
- ;; Break the review loop of `gnosis-review-session'
- (throw 'review-loop t))
-
-(defun gnosis-review-action--suspend (success thema thema-count)
- "Suspend/Unsuspend THEMA.
-
-This function should be used with `gnosis-review-actions', which
-should be recursively called using SUCCESS, THEMA, THEMA-COUNT."
- (gnosis-toggle-suspend-themata (list thema))
- (gnosis-review-actions success thema thema-count))
-
-(defun gnosis-review-action--override (success thema thema-count)
- "Override current review result for SUCCESS.
-
-This function should be used with `gnosis-review-actions', which will
-be called with new SUCCESS value plus THEMA & THEMA-COUNT."
- (setf success (if success nil t))
- (gnosis-display-next-review thema success)
- (gnosis-review-actions success thema thema-count))
-
-(defun gnosis-review-action--view-link (success thema thema-count)
- "View linked node(s) for THEMA."
- (if (gnosis-get-linked-nodes thema)
- (progn (gnosis-view-linked-node thema)
- (recursive-edit))
- (message (format "No linked nodes for thema: %d" thema))
- (sleep-for 0.5))
- (gnosis-review-actions success thema thema-count))
-
-(defun gnosis-review-actions (success id thema-count)
- "Specify action during review of thema.
-
-SUCCESS: Review result
-ID: Thema ID
-THEMA-COUNT: Total themata reviewed
-
-To customize the keybindings, adjust `gnosis-review-keybindings'."
- (let* ((prompt
- "Action: %sext, %sverride result, %suspend, %selete, %sdit thema,
%siew link, %suit: ")
- (choice (read-char-choice
- (apply #'format prompt
- (mapcar
- (lambda (str) (propertize str 'face 'match))
- '("n" "o" "s" "d" "e" "v" "q")))
- '(?n ?o ?s ?d ?e ?v ?q))))
- (pcase choice
- (?n (gnosis-review-result id success))
- (?o (gnosis-review-action--override success id thema-count))
- (?s (gnosis-review-action--suspend success id thema-count))
- (?d (gnosis-delete-thema id))
- (?e (gnosis-review-action--edit success id thema-count))
- (?v (gnosis-review-action--view-link success id thema-count))
- (?q (gnosis-review-action--quit success id)))))
-
-;;;###autoload
-(defun gnosis-review (&optional fn)
- "Start gnosis review session.
-
-FN: Review function, defaults to `gnosis-review-session'"
- (interactive)
- (setq gnosis-due-themata-total (length (gnosis-review-get-due-themata)))
- (set-register :gnosis-pre-image nil)
- (let ((review-type (gnosis-completing-read "Review: " gnosis-review-types))
- (fn (or fn #'gnosis-review-session)))
- (pcase review-type
- ("Due themata"
- (funcall fn (gnosis-collect-thema-ids :due t) t))
- ("Due themata of deck"
- (funcall fn (gnosis-collect-thema-ids :due t :deck
(gnosis--get-deck-id))))
- ("Due themata of specified tag(s)"
- (funcall fn (gnosis-collect-thema-ids :due t :tags t)))
- ("Overdue themata"
- (funcall fn (gnosis-review-get-overdue-themata)))
- ("Due themata (Without Overdue)"
- (funcall fn (cl-set-difference (mapcar #'car
(gnosis-review-get--due-themata))
- (gnosis-review-get-overdue-themata))))
- ("All themata of deck"
- (funcall fn (gnosis-collect-thema-ids :deck (gnosis--get-deck-id))))
- ("All themata of tag(s)"
- (funcall fn (gnosis-collect-thema-ids :tags t))))))
-
-(defun gnosis-review--select-topic ()
- "Prompt for topic from org-gnosis database and return it's id."
- (let* ((topic-title (gnosis-completing-read "Select topic: "
- (org-gnosis-select 'title
'nodes)))
- (topic-id (caar (org-gnosis-select 'id 'nodes `(= title
,topic-title)))))
- topic-id))
-
-(defun gnosis-collect-nodes-at-depth (node-id &optional fwd-depth back-depth)
- "Collect node IDs reachable from NODE-ID within depth limits.
-FWD-DEPTH is max hops for forward links (default 0).
-BACK-DEPTH is max hops for backlinks (default 0).
-Returns a deduplicated list including NODE-ID itself."
- (let ((fwd-depth (or fwd-depth 0))
- (back-depth (or back-depth 0))
- (max-depth (max fwd-depth back-depth))
- (visited (make-hash-table :test 'equal))
- (queue (list node-id)))
- (puthash node-id t visited)
- (dotimes (level max-depth)
- (when queue
- (let* ((qvec (vconcat queue))
- (neighbors (append
- (when (< level fwd-depth)
- (org-gnosis-select 'dest 'links
- `(in source ,qvec) t))
- (when (< level back-depth)
- (org-gnosis-select 'source 'links
- `(in dest ,qvec) t))))
- (next-queue nil))
- (dolist (neighbor neighbors)
- (unless (gethash neighbor visited)
- (puthash neighbor t visited)
- (push neighbor next-queue)))
- (setq queue next-queue))))
- (hash-table-keys visited)))
-
-;;;###autoload
-(defun gnosis-review-topic (&optional node-id fwd-depth back-depth)
- "Review themata linked to topic NODE-ID.
-FWD-DEPTH and BACK-DEPTH control forward/backlink traversal depth.
-With prefix arg, prompt for depths."
- (interactive
- (list nil
- (when current-prefix-arg (read-number "Forward link depth: " 1))
- (when current-prefix-arg (read-number "Backlink depth: " 0))))
- (let* ((node-id (or node-id (gnosis-review--select-topic)))
- (fwd-depth (or fwd-depth 0))
- (back-depth (or back-depth 0))
- (node-title (car (org-gnosis-select 'title 'nodes
- `(= id ,node-id) t)))
- (node-ids (if (or (> fwd-depth 0) (> back-depth 0))
- (gnosis-collect-nodes-at-depth
- node-id fwd-depth back-depth)
- (list node-id)))
- (gnosis-questions (gnosis-select 'source 'links
- `(in dest ,(vconcat node-ids)) t)))
- (if (and gnosis-questions
- (y-or-n-p
- (format "Review %s thema(s) for '%s'%s?"
- (length gnosis-questions) node-title
- (if (> (length node-ids) 1)
- (format " (%d nodes, fwd:%d back:%d)"
- (length node-ids) fwd-depth back-depth)
- ""))))
- (gnosis-review-session gnosis-questions)
- (message "No thema found for %s (id:%s)" node-title node-id))))
(defun gnosis-add-thema-fields (deck-id type keimenon hypothesis answer
parathema tags suspend links
@@ -1850,384 +1166,6 @@ LINKS: list of strings."
answer parathema tags suspend links)
(gnosis-update-thema id keimenon hypothesis answer parathema tags links
deck-id type)))
-(defun gnosis-export--insert-read-only (string)
- "Insert STRING as read-only."
- (let ((start (point)))
- (insert string)
- ;; Set the just inserted string as read-only
- (add-text-properties start (point) '(read-only t))
- ;; Since the space is inserted outside of the read-only region, it's
editable
- (let ((inhibit-read-only t))
- (insert " "))))
-
-(cl-defun gnosis-export--insert-thema (id type &optional keimenon hypothesis
- answer parathema tags example)
- "Insert thema for thema ID.
-
-TYPE: Thema type, refer to `gnosis-thema-types'
-KEIMENON: Text user is first presented with.
-HYPOTHESIS: Hypothesis for what the ANSWER is
-ANSWER: The revelation after KEIMENON
-PARATHEMA: The text where THEMA is derived from.
-TAGS: List of THEMA tags
-EXAMPLE: Boolean value, if non-nil do not add properties for thema."
- (let ((components `(("** Keimenon" . ,keimenon)
- ("** Hypothesis" . ,hypothesis)
- ("** Answer" . ,answer)
- ("** Parathema" . ,parathema))))
- (goto-char (point-max))
- (insert "\n* Thema")
- (when tags
- (insert " :" (mapconcat #'identity tags ":") ":"))
- (insert "\n")
- (unless example
- (let ((start (point)))
- (insert ":PROPERTIES:\n:GNOSIS_ID: " id "\n:GNOSIS_TYPE: " type
"\n:END:\n")
- (add-text-properties start (point)
- '(read-only t rear-nonsticky (read-only)))))
- (dolist (comp components)
- (goto-char (point-max))
- (gnosis-export--insert-read-only (car comp))
- (insert "\n" (or (cdr comp) "") "\n\n"))))
-
-(defun gnosis-export-parse--deck-name (&optional parsed-data)
- "Retrieve deck name from PARSED-DATA."
- (let* ((parsed-data (or parsed-data (org-element-parse-buffer)))
- (title (org-element-map parsed-data 'keyword
- (lambda (kw)
- (when (string= (org-element-property :key kw) "DECK")
- (org-element-property :value kw)))
- nil t)))
- title))
-
-(defun gnosis-export-parse-themata (&optional separator)
- "Extract content for each level-2 heading for thema headings with a
GNOSIS_ID.
-
-Split content of Hypothesis and Answer headings using SEPARATOR."
- (let ((sep (or separator gnosis-export-separator))
- results)
- (org-element-map (org-element-parse-buffer) 'headline
- (lambda (headline)
- (let* ((level (org-element-property :level headline))
- (gnosis-id (org-element-property :GNOSIS_ID headline))
- (gnosis-type (org-element-property :GNOSIS_TYPE headline))
- (tags (org-element-property :tags headline)))
- (when (and (= level 1) gnosis-id gnosis-type)
- (let ((line (line-number-at-pos
- (org-element-property :begin headline)))
- entry)
- (push gnosis-id entry)
- (push gnosis-type entry)
- (dolist (child (org-element-contents headline))
- (when (eq 'headline (org-element-type child))
- (let* ((child-title (org-element-property :raw-value child))
- (child-text (substring-no-properties
- (string-trim
- (org-element-interpret-data
- (org-element-contents child)))))
- (processed-text
- (cond
- ((and (member child-title '("Hypothesis" "Answer"))
- (not (string-empty-p child-text)))
- (mapcar (lambda (s)
- (string-trim
- (string-remove-prefix "-"
- (string-remove-prefix sep s))))
- (split-string child-text sep t "[ \t\n]+")))
- ((string-empty-p child-text) nil)
- (t child-text))))
- (push processed-text entry))))
- (push tags entry)
- (push line entry)
- (push (nreverse entry) results)))))
- nil nil)
- results))
-
-(defun gnosis-export-themata (ids &optional new-p)
- "Export themata for IDS.
-
-If NEW-P replace the ids of themata with NEW, used for new themata to
-generate new thema id."
- (cl-assert (listp ids) nil "IDS value must be a list.")
- ;; Extract just the ID values if they're in a list structure
- (let ((id-values (mapcar (lambda (id)
- (if (listp id) (car id) id))
- ids)))
- ;; Process each thema
- (dolist (id id-values)
- (let ((thema-data (append (gnosis-select '[type keimenon hypothesis
answer tags]
- 'themata `(= id ,id) t)
- (gnosis-select 'parathema 'extras `(= id ,id)
t))))
- (gnosis-export--insert-thema
- (if new-p "NEW" (number-to-string id))
- (nth 0 thema-data)
- (nth 1 thema-data)
- (concat (string-remove-prefix "\n" gnosis-export-separator)
- (mapconcat 'identity (nth 2 thema-data)
gnosis-export-separator))
- (concat (string-remove-prefix "\n" gnosis-export-separator)
- (mapconcat 'identity (nth 3 thema-data)
gnosis-export-separator))
- (nth 5 thema-data)
- (nth 4 thema-data))))))
-
-(defun gnosis-export-deck (&optional deck filename new-p include-suspended)
- "Export contents of DECK to FILENAME.
-
-When NEW-P, replace thema IDs with NEW for fresh import.
-When INCLUDE-SUSPENDED, also export suspended themata."
- (interactive (list (gnosis--get-deck-id)
- (read-file-name "Export to file: ")
- (not (y-or-n-p "Export with current thema ids? "))
- (y-or-n-p "Include suspended themata? ")))
- (let* ((gc-cons-threshold most-positive-fixnum)
- (deck-name (gnosis--get-deck-name deck))
- (filename (if (file-directory-p filename)
- (expand-file-name deck-name filename)
- filename)))
- (unless (string-match-p "\\.org$" filename)
- (setq filename (concat (or filename deck-name) ".org")))
- (with-current-buffer (get-buffer-create (format "EXPORT: %s" deck-name))
- (let ((inhibit-read-only t))
- (org-mode)
- (erase-buffer)
- (insert (format "#+DECK: %s\n" deck-name))
- ;; Batch-fetch: 2 queries instead of 2*N
- (let* ((all-themata (emacsql gnosis-db
- [:select [id type keimenon hypothesis answer tags]
- :from themata :where (= deck-id $s1)] deck))
- (all-ids (mapcar #'car all-themata))
- (suspended-ids (when (and all-ids (not include-suspended))
- (mapcar #'car
- (emacsql gnosis-db
- [:select id :from review-log
- :where (and (in id $v1) (= suspend
1))]
- (vconcat all-ids)))))
- (all-themata (if suspended-ids
- (cl-remove-if (lambda (row)
- (member (car row)
suspended-ids))
- all-themata)
- all-themata))
- (all-ids (mapcar #'car all-themata))
- (all-extras (when all-ids
- (emacsql gnosis-db
- [:select [id parathema] :from extras
- :where (in id $v1)] (vconcat all-ids))))
- (extras-ht (let ((ht (make-hash-table :test 'equal
- :size (length all-ids))))
- (dolist (row all-extras ht)
- (puthash (car row) (cadr row) ht)))))
- (insert (format "#+THEMATA: %d\n\n" (length all-themata)))
- (dolist (row all-themata)
- (let* ((id (nth 0 row))
- (type (nth 1 row))
- (hypothesis (nth 3 row))
- (answer (nth 4 row))
- (tags (nth 5 row))
- (parathema (gethash id extras-ht "")))
- (gnosis-export--insert-thema
- (if new-p "NEW" (number-to-string id))
- type
- (nth 2 row)
- (concat (string-remove-prefix "\n" gnosis-export-separator)
- (mapconcat #'identity hypothesis
gnosis-export-separator))
- (concat (string-remove-prefix "\n" gnosis-export-separator)
- (mapconcat #'identity answer gnosis-export-separator))
- parathema
- tags)))
- (when filename
- (write-file filename)
- (message "Exported deck to %s" filename)))))))
-
-(defun gnosis-save-thema (thema deck)
- "Save THEMA for DECK.
-Returns nil on success, or an error message string on failure."
- (let* ((id (nth 0 thema))
- (type (nth 1 thema))
- (keimenon (nth 2 thema))
- (hypothesis (nth 3 thema))
- (answer (nth 4 thema))
- (parathema (or (nth 5 thema) ""))
- (tags (nth 6 thema))
- (line (nth 7 thema))
- (links (append (gnosis-extract-id-links parathema)
- (gnosis-extract-id-links keimenon)))
- (thema-func (cdr (assoc (downcase type)
- (mapcar (lambda (pair) (cons (downcase (car
pair))
- (cdr pair)))
- gnosis-thema-types)))))
- (condition-case err
- (progn
- (funcall thema-func id deck type keimenon hypothesis
- answer parathema tags 0 links)
- nil)
- (error (format "Line %s (id:%s): %s" (or line "?") id
- (error-message-string err))))))
-
-(defun gnosis-save ()
- "Save themata in current buffer."
- (interactive nil gnosis-edit-mode)
- (let* ((gc-cons-threshold most-positive-fixnum)
- (themata (gnosis-export-parse-themata))
- (deck (gnosis--get-deck-id (gnosis-export-parse--deck-name)))
- (gnosis--id-cache (let ((ht (make-hash-table :test 'equal)))
- (dolist (id (gnosis-select 'id 'themata nil t) ht)
- (puthash id t ht))))
- (errors nil)
- (edited-id (string-to-number (caar themata))))
- (emacsql-with-transaction gnosis-db
- (cl-loop for thema in themata
- for err = (gnosis-save-thema thema deck)
- when err do (push err errors)))
- (if errors
- (user-error "Failed to import %d thema(ta):\n%s"
- (length errors) (mapconcat #'identity (nreverse errors)
"\n"))
- (gnosis-edit-quit)
- (run-hook-with-args 'gnosis-save-hook edited-id))))
-
-;;;###autoload
-(defun gnosis-save-deck (deck-name)
- "Save themata for deck with DECK-NAME.
-
-If a deck with DECK-NAME already exists, prompt for confirmation
-before importing into it."
- (interactive
- (progn
- (unless (eq major-mode 'org-mode)
- (user-error "This function can only be used in org-mode buffers"))
- (list (read-string "Deck name: " (gnosis-export-parse--deck-name)))))
- (when (and (gnosis-get 'id 'decks `(= name ,deck-name))
- (not (y-or-n-p (format "Deck '%s' already exists. Import into it?
"
- deck-name))))
- (user-error "Aborted"))
- (let* ((gc-cons-threshold most-positive-fixnum)
- (themata (gnosis-export-parse-themata))
- (deck (gnosis-get-deck-id deck-name))
- (gnosis--id-cache (let ((ht (make-hash-table :test 'equal)))
- (dolist (id (gnosis-select 'id 'themata nil t) ht)
- (puthash id t ht))))
- (errors nil))
- (emacsql-with-transaction gnosis-db
- (cl-loop for thema in themata
- for err = (gnosis-save-thema thema deck)
- when err do (push err errors)))
- (if errors
- (user-error "Failed to import %d thema(ta):\n%s"
- (length errors) (mapconcat #'identity (nreverse errors)
"\n"))
- (message "Imported %d themata for deck '%s'" (length themata)
deck-name))))
-
-;;;###autoload
-(defun gnosis-import-deck (file)
- "Save gnosis deck from FILE."
- (interactive "fFile: ")
- (let ((gc-cons-threshold most-positive-fixnum))
- (with-temp-buffer
- (insert-file-contents file)
- (org-mode)
- (gnosis-save-deck (gnosis-export-parse--deck-name)))))
-
-(defun gnosis--import-split-chunks (text chunk-size)
- "Split org TEXT into chunks of CHUNK-SIZE themata.
-
-Return a list of strings, each containing up to CHUNK-SIZE
-`* Thema' headings."
- (let ((headings '())
- (start 0))
- ;; Find all `* Thema' positions
- (while (string-match "^\\* Thema" text start)
- (push (match-beginning 0) headings)
- (setf start (1+ (match-beginning 0))))
- (setq headings (nreverse headings))
- (let ((chunks '())
- (total (length headings)))
- (cl-loop for i from 0 below total by chunk-size
- for beg = (nth i headings)
- for end-idx = (min (+ i chunk-size) total)
- for end = (if (< end-idx total)
- (nth end-idx headings)
- (length text))
- do (push (substring text beg end) chunks))
- (nreverse chunks))))
-
-(defun gnosis--import-chunk (header chunk deck-id id-cache)
- "Import a single CHUNK of org text.
-
-HEADER is the #+DECK line to prepend. DECK-ID is the resolved
-deck id. ID-CACHE is the shared `gnosis--id-cache' hash table.
-Returns a list of error strings (nil on full success)."
- (let ((gc-cons-threshold most-positive-fixnum)
- (gnosis--id-cache id-cache)
- (errors nil))
- (with-temp-buffer
- (insert header "\n" chunk)
- (org-mode)
- (let ((themata (gnosis-export-parse-themata)))
- (emacsql-with-transaction gnosis-db
- (cl-loop for thema in themata
- for err = (gnosis-save-thema thema deck-id)
- when err do (push err errors)))))
- (nreverse errors)))
-
-;;;###autoload
-(defun gnosis-import-deck-async (file &optional chunk-size)
- "Import gnosis deck from FILE asynchronously in chunks.
-
-CHUNK-SIZE controls how many themata to process per batch
-\(default 500). Uses `run-with-timer' between chunks so Emacs
-stays responsive. Progress is reported in the echo area."
- (interactive "fFile: ")
- (let* ((chunk-size (or chunk-size 500))
- (text (with-temp-buffer
- (insert-file-contents file)
- (buffer-string)))
- ;; Extract header (everything before first `* Thema')
- (header-end (or (string-match "^\\* Thema" text) 0))
- (header (string-trim-right (substring text 0 header-end)))
- (deck-name (with-temp-buffer
- (insert header)
- (org-mode)
- (gnosis-export-parse--deck-name)))
- (deck-id (progn
- (when (and (gnosis-get 'id 'decks `(= name ,deck-name))
- (not (y-or-n-p
- (format "Deck '%s' already exists.
Import into it? "
- deck-name))))
- (user-error "Aborted"))
- (gnosis-get-deck-id deck-name)))
- (id-cache (let ((ht (make-hash-table :test 'equal)))
- (dolist (id (gnosis-select 'id 'themata nil t) ht)
- (puthash id t ht))))
- (chunks (gnosis--import-split-chunks text chunk-size))
- (total-chunks (length chunks))
- ;; Count total themata from the text
- (total-themata (with-temp-buffer
- (insert text)
- (count-matches "^\\* Thema" (point-min)
(point-max))))
- (imported 0)
- (all-errors '()))
- (message "Importing %d themata in %d chunks..." total-themata total-chunks)
- (cl-labels
- ((process-next (remaining chunk-n)
- (if (null remaining)
- ;; Done
- (if all-errors
- (message "Import complete: %d themata, %d errors"
- imported (length all-errors))
- (message "Import complete: %d themata for deck '%s'"
- imported deck-name))
- (let* ((chunk (car remaining))
- (errors (gnosis--import-chunk header chunk deck-id
id-cache))
- ;; Count headings in this chunk
- (n (with-temp-buffer
- (insert chunk)
- (count-matches "^\\* Thema" (point-min)
(point-max)))))
- (setq imported (+ imported n))
- (when errors
- (setq all-errors (append all-errors errors)))
- (message "Importing... %d/%d themata (chunk %d/%d)"
- imported total-themata chunk-n total-chunks)
- (run-with-timer 0.01 nil
- #'process-next (cdr remaining) (1+ chunk-n))))))
- (process-next chunks 1))))
-
;;;###autoload
(defun gnosis-add-thema (deck type &optional keimenon hypothesis
answer parathema tags example)