diff options
-rw-r--r-- | gnosis.el | 117 |
1 files changed, 92 insertions, 25 deletions
@@ -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))) |