summaryrefslogtreecommitdiff
path: root/gnosis.el
diff options
context:
space:
mode:
Diffstat (limited to 'gnosis.el')
-rw-r--r--gnosis.el117
1 files changed, 92 insertions, 25 deletions
diff --git a/gnosis.el b/gnosis.el
index 0d815d9..898023c 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -125,7 +125,7 @@ When nil, the image will be displayed at its original size."
(defconst gnosis-db-version 2
"Gnosis database version.")
-(defvar gnosis-note-types '("MCQ" "Cloze" "Basic" "Double" "y-or-n")
+(defvar gnosis-note-types '("MCQ" "MCC" "Cloze" "Basic" "Double" "y-or-n")
"Gnosis available note types.")
(defvar gnosis-previous-note-tags '()
@@ -150,6 +150,11 @@ car value is the prompt, cdr is the prewritten string.")
car value is the prompt, cdr is the prewritten string.")
+(defcustom gnosis-mcc-separator "&-"
+ "Sseparator for choices on multiple choice clozes."
+ :type 'string
+ :group 'gnosis)
+
(defcustom gnosis-mcq-separator "\n--\n"
"Separator for stem field and options in mcq note type.
@@ -842,6 +847,33 @@ See `gnosis-add-note--cloze' for more reference."
:images (gnosis-select-images)
:tags (gnosis-prompt-tags--split gnosis-previous-note-tags)))
+(cl-defun gnosis-mcc-extract-options (str &optional (char gnosis-mcc-separator))
+ "Extract options for MCC note type from STR.
+
+CHAR: separator for mcc, default to `gnosis-mcc-separator'"
+ (cl-remove-if
+ #'null
+ (mapcar (lambda (s)
+ (when (string-match-p (regexp-quote char) s)
+ (split-string s (regexp-quote char))))
+ (split-string str " "))))
+
+(defun gnosis-add-note-mcc (deck)
+ "Add MCC note type to DECK.
+
+MCC (Multiple Choice Cloze) note type consists of a sentence with a
+single cloze, for which user will be prompted to select the correct
+answer."
+ (interactive)
+ (let* ((input (gnosis-read-string-from-buffer "prompt" "string"))
+ (question (gnosis-mcc-remove-separator input))
+ (options (gnosis-mcc-extract-options input))
+ (tags (gnosis-prompt-tags--split gnosis-previous-note-tags))
+ (images (gnosis-select-images)))
+ (cl-loop for option in options
+ do (gnosis-add-note-fields deck "mcc" question option (car option)
+ "extra" tags 0 (car images) (cdr images)))))
+
;;;###autoload
(defun gnosis-add-note (&optional deck type)
"Create note(s) as TYPE interactively.
@@ -853,7 +885,7 @@ TYPE: Type of gnosis note, must be one of `gnosis-note-types'"
(unless (y-or-n-p "You are using a testing environment! Continue?")
(error "Aborted")))
(let* ((deck (or deck (gnosis--get-deck-name)))
- (type (or type (funcall gnosis-completing-read-function "Type: " gnosis-note-types nil t)))
+ (type (or type (completing-read "Type: " gnosis-note-types nil t)))
(func-name (intern (format "gnosis-add-note-%s" (downcase type)))))
(if (fboundp func-name)
(progn (funcall func-name deck)
@@ -893,6 +925,14 @@ Works both with {} and {{}} to make easier to import anki notes."
word string)))
string)
+(defun gnosis-cloze-remove-tags (string)
+ "Remove cx-tags in STRING.
+
+Works both with {} and {{}} to make easier to import anki notes."
+ (let* ((regex "{\\{1,2\\}c\\([0-9]+\\)::?\\(.*?\\)}\\{1,2\\}")
+ (result (replace-regexp-in-string regex "\\2" string)))
+ result))
+
(defun gnosis-cloze-extract-answers (str)
"Extract cloze answers for STR.
@@ -915,6 +955,12 @@ Valid cloze formats include:
(mapcar (lambda (tag-group) (nreverse (cdr tag-group)))
(nreverse result-alist))))
+(defun gnosis-mcc-remove-separator (string &optional separator)
+ "Remove SEPARATOR and all followed words from STRING."
+ (let* ((separator (or separator gnosis-mcc-separator))
+ (result (replace-regexp-in-string (format "%s[^ ]*" separator) "" string)))
+ result))
+
(defun gnosis-compare-strings (str1 str2)
"Compare STR1 and STR2.
@@ -1269,6 +1315,26 @@ Used to reveal all clozes left with `gnosis-face-cloze-unanswered' face."
(gnosis-display-next-review id success)
success))
+(defun gnosis-review-mcc (id)
+ "Review MCC note of ID."
+ (let ((main (gnosis-get 'main 'notes `(= id ,id)))
+ ;; Cloze needs to be a list, we take car as the answer
+ (cloze (list (gnosis-get 'answer 'notes `(= id ,id))))
+ (user-choice nil)
+ (success nil))
+ (gnosis-display-cloze-sentence main cloze)
+ (gnosis-display-image id)
+ (setf user-choice (gnosis-mcq-answer id)
+ success (string= user-choice (car cloze)))
+ (gnosis-display-cloze-reveal :replace (car cloze)
+ :success success)
+ ;; Display user answer only upon failure
+ (unless success
+ (gnosis-display-cloze-user-answer user-choice))
+ (gnosis-display-extra id)
+ (gnosis-display-next-review id success)
+ success))
+
(defun gnosis-review-note (id)
"Start review for note with value of id ID, if note is unsuspended."
(when (gnosis-suspended-p id)
@@ -1319,8 +1385,7 @@ NOTE-NUM: The number of notes reviewed in the session."
(shell-command (format "%s %s %s" git "add" (shell-quote-argument "gnosis.db")))
(shell-command (format "%s %s %s" git "commit -m"
(shell-quote-argument (format "Total notes for session: %d" note-num)))))
- (when (and gnosis-vc-auto-push
- (not gnosis-testing))
+ (when (and gnosis-vc-auto-push (not gnosis-testing))
(gnosis-vc-push))
(message "Review session finished. %d notes reviewed." note-num)))
@@ -1546,17 +1611,16 @@ SUSPEND: Suspend note, 0 for unsuspend, 1 for suspend"
(cl-assert (or (stringp options) (listp options)) nil "Options must be a string, or a list for MCQ")
(cl-assert (or (= suspend 0) (= suspend 1)) nil "Suspend must be either 0 or 1")
;; Construct the update clause for the emacsql update statement.
- (cl-loop for (field . value) in
- `((main . ,main)
- (options . ,options)
- (answer . ,answer)
- (tags . ,tags)
- (extra-notes . ,extra-notes)
- (images . ,image)
- (extra-image . ,second-image)
- (ef . ',ef)
- (ff . ,ff)
- (suspend . ,suspend))
+ (cl-loop for (field . value) in `((main . ,main)
+ (options . ,options)
+ (answer . ,answer)
+ (tags . ,tags)
+ (extra-notes . ,extra-notes)
+ (images . ,image)
+ (extra-image . ,second-image)
+ (ef . ',ef)
+ (ff . ,ff)
+ (suspend . ,suspend))
when value
do (cond ((memq field '(extra-notes images extra-image))
(gnosis-update 'extras `(= ,field ,value) `(= id ,id)))
@@ -1641,10 +1705,10 @@ to improve readability."
(interactive)
;; Refresh modeline
(setq gnosis-due-notes-total (length (gnosis-review-get-due-notes)))
- (let ((review-type (funcall gnosis-completing-read-function "Review: " '("Due notes"
- "Due notes of deck"
- "Due notes of specified tag(s)"
- "All notes of tag(s)"))))
+ (let ((review-type (gnosis-completing-read "Review: " '("Due notes"
+ "Due notes of deck"
+ "Due notes of specified tag(s)"
+ "All notes of tag(s)"))))
(pcase review-type
("Due notes" (gnosis-review-session (gnosis-collect-note-ids :due t)))
("Due notes of deck" (gnosis-review-session (gnosis-collect-note-ids :due t :deck (gnosis--get-deck-id))))
@@ -1730,6 +1794,8 @@ Return note ids for notes that match QUERY."
else
collect (replace-regexp-in-string "\n" " " (format "%s" item))))
+;; TODO: Rewrite. Tags should be an input of strings, interactive
+;; handling should be done by "helper" funcs
(cl-defun gnosis-collect-note-ids (&key (tags nil) (due nil) (deck nil) (query nil))
"Return list of note ids based on TAGS, DUE, DECKS, QUERY.
@@ -1882,12 +1948,13 @@ for dashboard type.
DASHBOARD-TYPE: either 'Notes' or 'Decks' to display the respective dashboard."
(interactive)
- (let ((dashboard-type (or dashboard-type (cadr (read-multiple-choice
- "Display dashboard for:"
- '((?n "notes")
- (?d "decks")
- (?t "tags")
- (?s "search")))))))
+ (let ((dashboard-type (or dashboard-type
+ (cadr (read-multiple-choice
+ "Display dashboard for:"
+ '((?n "notes")
+ (?d "decks")
+ (?t "tags")
+ (?s "search")))))))
(if note-ids (gnosis-dashboard-output-notes note-ids)
(pcase dashboard-type
("notes" (gnosis-dashboard-output-notes (gnosis-collect-note-ids)))