summaryrefslogtreecommitdiff
path: root/gnosis.el
diff options
context:
space:
mode:
authorThanos Apollo <[email protected]>2024-08-07 15:04:00 +0300
committerThanos Apollo <[email protected]>2024-08-07 15:04:00 +0300
commitbc0d2a3ec6195d79903ad7badec10a26538763c2 (patch)
tree62051e3309b420242e4809b02990bd7531308f1b /gnosis.el
parent4cd3ce6993a9b97b698537c428addf7387b13a3f (diff)
parentcaae059a0988cdb08ef01f1adc695972300fe09c (diff)
Release version 0.4.00.4.0
* Major rewrite on gnosis algorithm. * Add gnosis-score * Add epignosis and agnoia * Add lethe and anagnosis events * Anagnosis events adjust gnosis-score depending on review performance, using epignosis and agnoia * Lethe resets next interval to 0 * Refactor calculations of next interval and gnosis-score * Add custom variables for tags and decks, configured using emacs lisp. * Rewrite gnosis database. * Remove deck specific values. * Use new algorithm variables.
Diffstat (limited to 'gnosis.el')
-rw-r--r--gnosis.el1137
1 files changed, 617 insertions, 520 deletions
diff --git a/gnosis.el b/gnosis.el
index 8291a1b..8790d5e 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -5,9 +5,9 @@
;; Author: Thanos Apollo <[email protected]>
;; Keywords: extensions
;; URL: https://thanosapollo.org/projects/gnosis
-;; Version: 0.3.2
+;; Version: 0.4.0
-;; Package-Requires: ((emacs "27.2") (emacsql "20240124") (compat "29.1.4.2"))
+;; Package-Requires: ((emacs "27.2") (emacsql "20240124") (compat "29.1.4.2") (transient "0.7.2"))
;; 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
@@ -24,16 +24,19 @@
;;; Commentary:
-;; Gnosis is a spaced repetition system for note taking and
-;; self-testing. Notes are organized in a Question/Answer/Explanation
-;; format and reviewed at spaced intervals. Interval durations are
-;; based on the success or failure of recalling the answer to each
-;; question.
+;; Gnosis (γνῶσις) is a spaced repetition system that enhances memory
+;; retention through active recall. It employs a Q&A format, where each
+;; note consists of a question, answer, and explanation. Notes are
+;; reviewed at optimally spaced intervals based on the user's success or
+;; failure to recall the answer. Key benefits arise from writing out
+;; answers when reviewing notes, fostering deeper understanding
+;; and improved memory retention.
-;; Gnosis uses a highly customizable algorithm. Unlike traditional
-;; methods, it doesn't depend on subjective user ratings to determine
-;; the next review interval. Instead, it evaluates the user's success
-;; or failure in recalling an answer by typing it.
+;; Gnosis algorithm is highly adjustable, allowing users to set specific
+;; values not just for note decks but for tags as well. Gnosis'
+;; adjustability allows users to fine-tune settings not only for entire
+;; note collections but also for specific tagged topics, thereby creating
+;; a personalized learning environment for each topic.
;;; Code:
@@ -45,6 +48,7 @@
(require 'gnosis-algorithm)
(require 'gnosis-string-edit)
+(require 'gnosis-dashboard)
(require 'animate)
@@ -136,10 +140,7 @@ a string describing the action."
(defvar gnosis-testing nil
"When t, warn user he is in a testing environment.")
-(defvar gnosis-dashboard-note-ids nil
- "Store note ids for dashboard.")
-
-(defconst gnosis-db-version 2
+(defconst gnosis-db-version 3
"Gnosis database version.")
(defvar gnosis-note-types '("MCQ" "Cloze" "Basic" "Double" "y-or-n")
@@ -190,16 +191,41 @@ Seperate the question/stem from options."
(defcustom gnosis-center-content-p t
"Non-nil means center content."
:type 'boolean
- :group 'gosis)
+ :group 'gnosis)
(defcustom gnosis-apply-highlighting-p t
"Non-nil means apply syntax highlighting."
:type 'boolean
- :group 'gosis)
+ :group 'gnosis)
+
+(defcustom gnosis-new-notes-limit nil
+ "Total new notes limit."
+ :type '(choice (const :tag "None" nil)
+ (integer :tag "Number"))
+ :group 'gnosis)
+
+(defcustom gnosis-review-new-first t
+ "Review new notes first.
+
+When nil, review new notes last."
+ :type 'bolean
+ :group 'gnosis)
(defvar gnosis-due-notes-total nil
"Total due notes.")
+(defvar gnosis-review-notes nil
+ "Review notes.")
+
+;; TODO: Make this as a defcustom
+(defvar gnosis-custom-values
+ '((:deck "demo" (:proto (0 1 3) :anagnosis 3 :epignosis 0.5 :agnoia 0.3 :amnesia 0.5 :lethe 3))
+ (:tag "demo" (:proto (1 2) :anagnosis 3 :epignosis 0.5 :agnoia 0.3 :amnesia 0.45 :lethe 3)))
+ "Custom review values for adjusting gnosis algorithm.")
+
+(defvar gnosis-custom--valid-values
+ '(:proto :anagnosis :epignosis :agnoia :amnesia :lethe))
+
;;; Faces
(defgroup gnosis-faces nil
@@ -278,13 +304,21 @@ Optional argument FLATTEN, when non-nil, flattens the result."
"Select VALUE from TABLE for note ID."
(gnosis-select value table `(= id ,id) t))
+(defun gnosis-table-exists-p (table)
+ "Check if TABLE exists."
+ (let ((tables (mapcar (lambda (str) (replace-regexp-in-string "_" "-" (symbol-name str)))
+ (cdr (gnosis-select 'name 'sqlite-master '(= type table) t)))))
+ (member (symbol-name table) tables)))
+
(cl-defun gnosis--create-table (table &optional values)
"Create TABLE for VALUES."
- (emacsql gnosis-db `[:create-table ,table ,values]))
+ (unless (gnosis-table-exists-p table)
+ (emacsql gnosis-db `[:create-table ,table ,values])))
(cl-defun gnosis--drop-table (table)
"Drop TABLE from `gnosis-db'."
- (emacsql gnosis-db `[:drop-table ,table]))
+ (when (gnosis-table-exists-p table)
+ (emacsql gnosis-db `[:drop-table ,table])))
(cl-defun gnosis--insert-into (table values)
"Insert VALUES to TABLE."
@@ -309,9 +343,11 @@ Example:
"From TABLE use where to delete VALUE."
(emacsql gnosis-db `[:delete :from ,table :where ,value]))
-(defun gnosis-delete-note (id)
- "Delete note with ID."
- (when (y-or-n-p "Delete note?")
+(defun gnosis-delete-note (id &optional verification)
+ "Delete note with ID.
+
+When VERIFICATION is non-nil, skip `y-or-n-p' prompt."
+ (when (or verification (y-or-n-p "Delete note?"))
(emacsql-with-transaction gnosis-db (gnosis--delete 'notes `(= id ,id)))))
(defun gnosis-delete-deck (&optional id)
@@ -374,22 +410,22 @@ Acts only when CENTER? is t."
(let ((window-width (window-width))
(center? (or center? gnosis-center-content-p)))
(if center?
- (mapconcat
- (lambda (line)
- (let* ((text (string-trim line))
- (wrapped (with-temp-buffer
- (insert text)
- (fill-region (point-min) (point-max))
- (buffer-string)))
- (lines (split-string wrapped "\n")))
- (mapconcat
- (lambda (line)
- (let ((padding (max (/ (- window-width (length line)) 2) 0)))
- (concat (make-string padding ? ) line)))
- lines
- "\n")))
- (split-string input-string "\n")
- "\n")
+ (mapconcat
+ (lambda (line)
+ (let* ((text (string-trim line))
+ (wrapped (with-temp-buffer
+ (insert text)
+ (fill-region (point-min) (point-max))
+ (buffer-string)))
+ (lines (split-string wrapped "\n")))
+ (mapconcat
+ (lambda (line)
+ (let ((padding (max (/ (- window-width (length line)) 2) 0)))
+ (concat (make-string padding ? ) line)))
+ lines
+ "\n")))
+ (split-string input-string "\n")
+ "\n")
input-string)))
(defun gnosis-apply-center-buffer-overlay (&optional point)
@@ -542,7 +578,7 @@ When SUCCESS nil, display USER-INPUT as well"
;; Insert user wrong answer
(when (not success)
(insert "\n"
- (propertize "Your answer:" 'face 'gnosis-face-directions)
+ (propertize "Your answer:" 'face 'gnosis-face-directions)
" "
(propertize user-input 'face 'gnosis-face-false))
(gnosis-center-current-line)))
@@ -617,12 +653,12 @@ inserted in the buffer.
Also see `gnosis-string-edit'."
(gnosis-string-edit prompt string
- (lambda (edited)
- (setq string (substring-no-properties edited))
- (exit-recursive-edit))
- :abort-callback (lambda ()
- (exit-recursive-edit)
- (error "Aborted edit")))
+ (lambda (edited)
+ (setq string (substring-no-properties edited))
+ (exit-recursive-edit))
+ :abort-callback (lambda ()
+ (exit-recursive-edit)
+ (error "Aborted edit")))
(recursive-edit)
string)
@@ -663,14 +699,14 @@ Set SPLIT to t to split all input given."
(defun gnosis-add-deck (name)
"Create deck with NAME."
(interactive (list (read-string "Deck Name: ")))
- (when gnosis-testing
- (unless (y-or-n-p "You are using a testing environment! Continue?")
- (error "Aborted")))
- (if (gnosis-get 'name 'decks `(= name ,name))
- (error "Deck `%s' already exists" name)
- (let ((deck-id (gnosis-generate-id 5 t)))
- (gnosis--insert-into 'decks `([,deck-id ,name nil nil nil nil nil]))
- (message "Created deck '%s'" name))))
+ (when gnosis-testing
+ (unless (y-or-n-p "You are using a testing environment! Continue?")
+ (error "Aborted")))
+ (if (gnosis-get 'name 'decks `(= name ,name))
+ (error "Deck `%s' already exists" name)
+ (let ((deck-id (gnosis-generate-id 5 t)))
+ (gnosis--insert-into 'decks `([,deck-id ,name]))
+ (message "Created deck '%s'" name))))
(defun gnosis--get-deck-name (&optional id)
"Get deck name for ID, or prompt for deck name when ID is nil."
@@ -684,6 +720,11 @@ Set SPLIT to t to split all input given."
"Return id for DECK name."
(gnosis-get 'id 'decks `(= name ,deck)))
+(defun gnosis-get-note-deck-name (id)
+ "Return deck name of note ID."
+ (let ((deck (gnosis-get 'deck-id 'notes `(= id ,id))))
+ (and deck (gnosis--get-deck-name deck))))
+
(defun gnosis-get-deck--note (id &optional name)
"Get deck id for note ID.
@@ -692,24 +733,13 @@ If NAME is t, return name of deck."
(deck (gnosis-get 'deck-id 'notes id-clause)))
(if name (gnosis--get-deck-name deck) deck)))
-(defun gnosis-get-deck-ff (id)
- "Return failure factor for deck of ID."
- (let* ((id-clause `(= id ,id))
- (deck-ff (gnosis-get 'failure-factor 'decks id-clause)))
- deck-ff))
-
-(defun gnosis-get-note-ff (id)
- "Return failure factor for note ID."
- (let ((deck-ff (gnosis-get-deck-ff (gnosis-get-deck--note id)))
- (note-ff (gnosis-get 'ff 'review `(= id ,id))))
- (if (and deck-ff (> deck-ff note-ff))
- deck-ff
- note-ff)))
-
-(cl-defun gnosis-suspend-note (id)
- "Suspend note with ID."
- (let ((suspended (= (gnosis-get 'suspend 'review-log `(= id ,id)) 1)))
- (when (y-or-n-p (if suspended "Unsuspend note? " "Suspend note? "))
+(cl-defun gnosis-suspend-note (id &optional verification)
+ "Suspend note with ID.
+
+When VERIFICATION is non-nil, skips `y-or-n-p' prompt."
+ (let* ((suspended (= (gnosis-get 'suspend 'review-log `(= id ,id)) 1))
+ (verification (or verification (y-or-n-p (if suspended "Unsuspend note? " "Suspend note? ")))))
+ (when verification
(if suspended
(gnosis-update 'review-log '(= suspend 0) `(= id ,id))
(gnosis-update 'review-log '(= suspend 1) `(= id ,id))))))
@@ -780,16 +810,14 @@ SUSPEND: Integer value of 1 or 0, where 1 suspends the card
IMAGE: Image to display during review.
SECOND-IMAGE: Image to display after user-input.
-NOTE: If a gnosis--insert-into fails, the whole transaction will be
- (or at least it should). Else there will be an error for foreign key
- constraint."
+If a gnosis--insert-into fails, the whole transaction will be."
(let* ((deck-id (gnosis--get-deck-id deck))
- (initial-interval (gnosis-get-deck-initial-interval deck-id))
(note-id (gnosis-generate-id)))
(emacsql-with-transaction gnosis-db
;; Refer to `gnosis-db-schema-SCHEMA' e.g `gnosis-db-schema-review-log'
(gnosis--insert-into 'notes `([,note-id ,type ,main ,options ,answer ,tags ,deck-id]))
- (gnosis--insert-into 'review `([,note-id ,gnosis-algorithm-ef ,gnosis-algorithm-ff ,initial-interval]))
+ (gnosis--insert-into 'review `([,note-id ,gnosis-algorithm-gnosis-value
+ ,gnosis-algorithm-amnesia-value]))
(gnosis--insert-into 'review-log `([,note-id ,(gnosis-algorithm-date)
,(gnosis-algorithm-date) 0 0 0 0 ,suspend 0]))
(gnosis--insert-into 'extras `([,note-id ,extra ,image ,second-image])))))
@@ -1119,11 +1147,11 @@ TYPE: Type of gnosis note, must be one of `gnosis-note-types'"
(defun gnosis-cloze-check (sentence clozes)
"Check if CLOZES are found in SENTENCE."
- (catch 'not-found
- (dolist (cloze clozes)
- (unless (string-match-p cloze sentence)
- (throw 'not-found nil)))
- t))
+ (catch 'not-found
+ (dolist (cloze clozes)
+ (unless (string-match-p cloze sentence)
+ (throw 'not-found nil)))
+ t))
(defun gnosis-cloze-remove-tags (string)
"Replace cloze tags and hints in STRING.
@@ -1155,7 +1183,7 @@ Valid cloze formats include:
(nreverse result-alist))))
(defun gnosis-cloze-extract-answers (nested-lst)
- "Extract cloze answers for string clozes inside the NESTED-LST.
+ "Extract cloze answers for string clozes inside the NESTED-LST.
This function should be used in combination with `gnosis-cloze-extract-answers'."
(mapcar (lambda (lst)
@@ -1224,11 +1252,11 @@ Optionally, add cusotm PROMPT."
(let* ((prompt (or prompt "Select image: "))
(image (if (y-or-n-p "Add review image?")
(gnosis-completing-read prompt
- (cons nil (gnosis-directory-files gnosis-images-dir)))
+ (cons nil (gnosis-directory-files gnosis-images-dir)))
nil))
(extra-image (if (y-or-n-p "Add post review image?")
(gnosis-completing-read prompt
- (cons nil (gnosis-directory-files gnosis-images-dir))))))
+ (cons nil (gnosis-directory-files gnosis-images-dir))))))
(cons image extra-image))
nil))
@@ -1237,19 +1265,25 @@ Optionally, add cusotm PROMPT."
(cl-loop for tags in (gnosis-select 'tags 'notes '1=1 t)
nconc tags into all-tags
finally return (delete-dups all-tags)))
-
-(defun gnosis-select-by-tag (input-tags &optional due)
+;; TODO: Rewrite this using `gnosis-get-tag-notes'.
+(defun gnosis-select-by-tag (input-tags &optional due suspended-p)
"Return note ID's for every note with INPUT-TAGS.
-If DUE, return only due notes."
+If DUE, return only due notes.
+If SUSPENDED-P, return suspended notes as well."
(cl-assert (listp input-tags) t "Input tags must be a list")
(cl-assert (booleanp due) "Due value must be a boolean")
(cl-loop for (id tags) in (gnosis-select '[id tags] 'notes)
when (and (cl-every (lambda (tag) (member tag tags)) input-tags)
- (not (gnosis-suspended-p id))
+ (or (not suspended-p) (not (gnosis-suspended-p id)))
(if due (gnosis-review-is-due-p id) t))
collect id))
+(defun gnosis-get-tag-notes (tag)
+ "Return note ids for TAG."
+ (let ((notes (gnosis-select 'id 'notes `(like tags ',(format "%%\"%s\"%%" tag)) t)))
+ notes))
+
(defun gnosis-suspended-p (id)
"Return t if note with ID is suspended."
(= (gnosis-get 'suspend 'review-log `(= id ,id)) 1))
@@ -1349,6 +1383,40 @@ provided, use it as the default value."
(setf gnosis-previous-note-tags tags)
(if (equal tags '("")) '("untagged") tags)))
+;; Collecting note ids
+
+;; 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.
+
+TAGS: boolean value, t to specify tags.
+DUE: boolean value, t to specify due notes.
+DECK: Integer, specify deck id.
+QUERY: String value,"
+ (cl-assert (and (booleanp due) (booleanp tags) (or (numberp deck) (null deck)) (or (stringp query) (null query)))
+ nil "Incorrect value passed to `gnosis-collect-note-ids'")
+ (cond ((and (null tags) (null due) (null deck) (null query))
+ (gnosis-select 'id 'notes '1=1 t))
+ ;; All due notes
+ ((and (null tags) due (null deck))
+ (gnosis-review-get-due-notes))
+ ;; All notes for tags
+ ((and tags (null due) (null deck))
+ (gnosis-select-by-tag (gnosis-tag-prompt)))
+ ;; All due notes for tags
+ ((and tags due (null deck))
+ (gnosis-select-by-tag (gnosis-tag-prompt) t))
+ ;; All notes for deck
+ ((and (null tags) (null due) deck)
+ (gnosis-get-deck-notes deck nil))
+ ;; All due notes for deck
+ ((and (null tags) deck due)
+ (gnosis-get-deck-notes deck t))
+ ;; Query
+ ((and (null tags) (null due) (null deck) query)
+ (gnosis-search-note query))))
+
;; Review
;;;;;;;;;;
(defun gnosis-review-is-due-p (note-id)
@@ -1369,10 +1437,19 @@ well."
(defun gnosis-review-get-due-notes ()
"Return a list due notes id for current date."
- (let ((notes (gnosis-select 'id 'notes '1=1 t)))
- (cl-loop for note in notes
- when (gnosis-review-is-due-p note)
- collect note)))
+ (let* ((old-notes (cl-loop for note in (gnosis-select 'id 'review-log '(and (> n 0)
+ (= suspend 0))
+ t)
+ when (gnosis-review-is-due-p note)
+ collect note))
+ (new-notes (cl-loop for note in (gnosis-select 'id 'review-log '(and (= n 0)
+ (= suspend 0))
+ t)
+ when (gnosis-review-is-due-today-p note)
+ collect note)))
+ (if gnosis-review-new-first
+ (append (cl-subseq new-notes 0 gnosis-new-notes-limit) old-notes)
+ (append old-notes (cl-subseq new-notes 0 gnosis-new-notes-limit)))))
(defun gnosis-review-get-due-tags ()
"Return a list of due note tags."
@@ -1396,13 +1473,13 @@ well."
(max (gnosis-algorithm-date-diff last-rev rev-date) 1)))
(defun gnosis-review-algorithm (id success)
- "Return next review date & ef for note with value of id ID.
+ "Return next review date & gnosis for note 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 ((ff (gnosis-get-note-ff id))
- (ef (gnosis-get 'ef 'review `(= id ,id)))
+ (let ((amnesia (gnosis-get-note-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
@@ -1410,40 +1487,51 @@ Returns a list of the form ((yyyy mm dd) (ef-increase ef-decrease ef-total))."
;; (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
- :ef (nth 2 ef) ;; total ef is used for next interval
- :success success
- :successful-reviews t-success
- :failure-factor ff
- :initial-interval (gnosis-get-note-initial-interval id))
- (gnosis-algorithm-next-ef :ef ef
- :success success
- :increase (gnosis-get-ef-increase id)
- :decrease (gnosis-get-ef-decrease id)
- :threshold (gnosis-get-ef-threshold id)
- :c-successes c-success
- :c-failures c-fails))))
+ (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-note-lethe id)
+ :amnesia amnesia
+ :proto (gnosis-get-note-proto id))
+ (gnosis-algorithm-next-gnosis
+ :gnosis gnosis
+ :success success
+ :epignosis (gnosis-get-note-epignosis id)
+ :agnoia (gnosis-get-note-agnoia id)
+ :anagnosis (gnosis-get-note-anagnosis id)
+ :c-successes c-success
+ :c-failures c-fails))))
(defun gnosis-review--update (id success)
"Update review-log for note with value of id ID.
SUCCESS is a boolean value, t for success, nil for failure."
- (let ((ef (cadr (gnosis-review-algorithm id success)))
+ (let ((gnosis (cadr (gnosis-review-algorithm id success)))
(next-rev (car (gnosis-review-algorithm id success))))
+ ;; Update activity-log
+ (gnosis-review-increment-activity-log (gnosis-review-is-note-new-p id))
;; Update review-log
(gnosis-update 'review-log `(= last-rev ',(gnosis-algorithm-date)) `(= id ,id))
(gnosis-update 'review-log `(= next-rev ',next-rev) `(= id ,id))
(gnosis-update 'review-log `(= n (+ 1 ,(gnosis-get 'n 'review-log `(= id ,id)))) `(= id ,id))
;; Update review
- (gnosis-update 'review `(= ef ',ef) `(= id ,id))
+ (gnosis-update 'review `(= gnosis ',gnosis) `(= id ,id))
(if success
(progn (gnosis-update 'review-log
- `(= c-success ,(1+ (gnosis-get 'c-success 'review-log `(= id ,id)))) `(= id ,id))
- (gnosis-update 'review-log `(= t-success ,(1+ (gnosis-get 't-success 'review-log `(= id ,id))))
+ `(= c-success ,(1+ (gnosis-get 'c-success 'review-log `(= id ,id))))
+ `(= id ,id))
+ (gnosis-update 'review-log
+ `(= t-success ,(1+ (gnosis-get 't-success 'review-log `(= id ,id))))
`(= id ,id))
(gnosis-update 'review-log `(= c-fails 0) `(= id ,id)))
- (gnosis-update 'review-log `(= c-fails ,(1+ (gnosis-get 'c-fails 'review-log `(= id ,id)))) `(= id ,id))
- (gnosis-update 'review-log `(= t-fails ,(1+ (gnosis-get 't-fails 'review-log `(= id ,id)))) `(= id ,id))
+ (gnosis-update 'review-log
+ `(= c-fails ,(1+ (gnosis-get 'c-fails 'review-log `(= id ,id)))) `(= id ,id))
+ (gnosis-update 'review-log
+ `(= t-fails ,(1+ (gnosis-get 't-fails 'review-log `(= id ,id)))) `(= id ,id))
(gnosis-update 'review-log `(= c-success 0) `(= id ,id)))))
(defun gnosis-review-result (id success)
@@ -1519,11 +1607,13 @@ If user-input is equal to CLOZE, return t."
;; Correct answer -> reveal the current cloze
(progn (cl-incf num)
(gnosis-display-cloze-string main (nthcdr num clozes)
- (nthcdr num hints)
- (cl-subseq clozes 0 num)
- nil))
+ (nthcdr num hints)
+ (cl-subseq clozes 0 num)
+ nil))
;; Incorrect answer
- (gnosis-display-cloze-string main nil nil (cl-subseq clozes 0 num) (member cloze clozes))
+ (gnosis-display-cloze-string main nil nil
+ (cl-subseq clozes 0 num)
+ (member cloze clozes))
(gnosis-display-cloze-user-answer (cdr input))
(setq success nil)
(cl-return)))
@@ -1554,8 +1644,27 @@ If user-input is equal to CLOZE, return t."
(gnosis-display-next-review id success)
success))
+(defun gnosis-review-is-note-new-p (id)
+ "Return t if note with ID is new."
+ (let ((reviews (car (gnosis-select-id 'n 'review-log id))))
+ (not (> reviews 0))))
+
+(defun gnosis-review-increment-activity-log (new? &optional date)
+ "Increament activity log for DATE by one.
+
+If NEW? is non-nil, increment new notes log by 1."
+ (let* ((current-total-value (gnosis-get-date-total-notes))
+ (inc-total (cl-incf current-total-value))
+ (current-new-value (gnosis-get-date-new-notes))
+ (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-review-note (id)
- "Start review for note with value of id ID, if note is unsuspended."
+ "Start review for note with value of id ID, if note is unsuspended.
+
+DATE: Date to log the note review on the activity-log."
(when (gnosis-suspended-p id)
(message "Suspended note with id: %s" id)
(sit-for 0.3)) ;; this should only occur in testing/dev cases
@@ -1587,14 +1696,14 @@ If user-input is equal to CLOZE, return t."
;; Reopen gnosis-db after pull
(setf gnosis-db (emacsql-sqlite-open (expand-file-name "gnosis.db" dir)))))
-(defun gnosis-review-commit (note-num)
+(defun gnosis-review-commit (note-count)
"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 of notes.
-NOTE-NUM: The number of notes reviewed in the session."
+NOTE-COUNT: The number of notes reviewed in the session to be commited."
(let ((git (executable-find "git"))
(default-directory gnosis-dir))
(unless git
@@ -1605,10 +1714,11 @@ NOTE-NUM: The number of notes reviewed in the session."
(unless gnosis-testing
(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)))))
+ (shell-quote-argument
+ (format "Reviewed %d notes." note-count)))))
(when (and gnosis-vc-auto-push (not gnosis-testing))
(gnosis-vc-push))
- (message "Review session finished. %d notes reviewed." note-num)))
+ (message "Review session finished.")))
(defun gnosis-review-action--edit (success note note-count)
"Edit NOTE during review.
@@ -1619,11 +1729,11 @@ editing NOTE with it's new contents.
After done editing, call `gnosis-review-actions' with SUCCESS NOTE
NOTE-COUNT."
(gnosis-edit-save-exit)
- (gnosis-edit-note note t)
+ (gnosis-edit-note note)
(recursive-edit)
(gnosis-review-actions success note note-count))
-(defun gnosis-review-action--quit (success note note-count)
+(defun gnosis-review-action--quit (success note)
"Quit review session.
Update result for NOTE review with SUCCESS and commit session for NOTE-COUNT.
@@ -1631,9 +1741,8 @@ Update result for NOTE review with SUCCESS and commit session for NOTE-COUNT.
This function should be used with `gnosis-review-actions', to finish
the review session."
(gnosis-review-result note success)
- (gnosis-review-commit note-count)
- ;; Break the loop of `gnosis-review-session'
- (throw 'stop-loop t))
+ ;; Break the review loop of `gnosis-review-session'
+ (throw 'review-loop t))
(defun gnosis-review-action--suspend (success note note-count)
"Suspend/Unsuspend NOTE.
@@ -1678,22 +1787,29 @@ To customize the keybindings, adjust `gnosis-review-keybindings'."
("override" (gnosis-review-action--override success note note-count))
("suspend" (gnosis-review-action--suspend success note note-count))
("edit" (gnosis-review-action--edit success note note-count))
- ("quit" (gnosis-review-action--quit success note note-count)))))
+ ("quit" (gnosis-review-action--quit success note)))))
-(defun gnosis-review-session (notes)
+(defun gnosis-review-session (notes &optional due note-count)
"Start review session for NOTES.
-NOTES: List of note ids"
- (let ((note-count 0))
+NOTES: List of note ids
+DUE: If due is non-nil, session will loop for due notes.
+NOTE-COUNT: Total notes to be commited for session."
+ (let ((note-count (or note-count 0)))
(if (null notes)
(message "No notes for review.")
- (when (y-or-n-p (format "You have %s total notes for review, start session?" (length notes)))
- (catch 'stop-loop
- (cl-loop for note in notes
- do (let ((success (gnosis-review-note note)))
- (cl-incf note-count)
- (gnosis-review-actions success note note-count))
- finally (gnosis-review-commit note-count)))))))
+ (setf gnosis-review-notes notes)
+ (catch 'review-loop
+ (cl-loop for note in notes
+ do (let ((success (gnosis-review-note note)))
+ (cl-incf note-count)
+ (gnosis-review-actions success note note-count))
+ finally
+ ;; TODO: Add optional arg to repeat for specific deck/tag
+ ;; Repeat until there are no due notes
+ (and due (gnosis-review-session (gnosis-collect-note-ids :due t) t note-count))))
+ (gnosis-dashboard)
+ (gnosis-review-commit note-count))))
;;;###autoload
(defun gnosis-review ()
@@ -1701,14 +1817,17 @@ NOTES: List of note ids"
(interactive)
;; Refresh modeline
(setq gnosis-due-notes-total (length (gnosis-review-get-due-notes)))
+ ;; Select review type
(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))))
+ ("Due notes" (gnosis-review-session (gnosis-collect-note-ids :due t) t))
+ ("Due notes of deck" (gnosis-review-session
+ (gnosis-collect-note-ids :due t :deck (gnosis--get-deck-id))))
("Due notes of specified tag(s)" (gnosis-review-session (gnosis-collect-note-ids :due t :tags t)))
+ ("All notes of deck" (gnosis-review-session (gnosis-collect-note-ids :deck (gnosis--get-deck-id))))
("All notes of tag(s)" (gnosis-review-session (gnosis-collect-note-ids :tags t))))))
@@ -1721,7 +1840,7 @@ NOTES: List of note ids"
(put-text-property (match-beginning 0) (match-end 0) 'read-only t)))
(goto-char (point-min)))
-(cl-defun gnosis-edit-note (id &optional (recursive-edit nil) (dashboard "notes"))
+(cl-defun gnosis-edit-note (id)
"Edit the contents of a note with the given ID.
This function creates an Emacs Lisp buffer named *gnosis-edit* on the
@@ -1736,9 +1855,6 @@ RECURSIVE-EDIT: If t, exit `recursive-edit' after finishing editing.
It should only be t when starting a recursive edit, when editing a
note during a review session.
-DASHBOARD: Dashboard to return after editing. Default value is
-Notes.
-
The buffer automatically indents the expressions for readability.
After finishing editing, evaluate the entire expression to apply the
changes."
@@ -1754,29 +1870,8 @@ changes."
;; Insert id & fields as read-only values
(gnosis-edit-read-only-values (format ":id %s" id) ":main" ":options" ":answer"
":tags" ":extra-notes" ":image" ":second-image"
- ":ef" ":ff" ":suspend")
- (local-unset-key (kbd "C-c C-c"))
- (local-set-key (kbd "C-c C-c") (lambda () (interactive) (if recursive-edit
- (gnosis-edit-save-exit 'exit-recursive-edit)
- (gnosis-edit-save-exit 'gnosis-dashboard dashboard
- gnosis-dashboard-note-ids)))))
-
-(defun gnosis-edit-deck--export (id)
- "Export deck with ID.
-
-WARNING: This export is only for editing said deck!
-
-Insert deck values:
- `ef-increase', `ef-decrease', `ef-threshold', `failure-factor'"
- (let ((name (gnosis-get 'name 'decks `(= id ,id)))
- (ef-increase (gnosis-get 'ef-increase 'decks `(= id ,id)))
- (ef-decrease (gnosis-get 'ef-decrease 'decks `(= id ,id)))
- (ef-threshold (gnosis-get 'ef-threshold 'decks `(= id ,id)))
- (failure-factor (gnosis-get 'failure-factor 'decks `(= id ,id)))
- (initial-interval (gnosis-get 'initial-interval 'decks `(= id ,id))))
- (insert
- (format "\n:id %s\n:name \"%s\"\n:ef-increase %s\n:ef-decrease %s\n:ef-threshold %s\n:failure-factor %s\n :initial-interval '%s"
- id name ef-increase ef-decrease ef-threshold failure-factor initial-interval))))
+ ":gnosis" ":amensia" ":suspend")
+ (local-set-key (kbd "C-c C-c") (lambda () (interactive) (gnosis-edit-note-save-exit))))
(defun gnosis-assert-int-or-nil (value description)
"Assert that VALUE is an integer or nil.
@@ -1803,60 +1898,23 @@ DESCRIPTION is a string that describes the value."
(unless (or (null value) (numberp value))
(error "Invalid value: %s, %s" value description)))
-(cl-defun gnosis-edit-update-deck (&key id name ef-increase ef-decrease ef-threshold failure-factor initial-interval)
- "Update deck with id value of ID.
-
-NAME: Name of deck
-EF-INCREASE: Easiness factor increase value
-EF-DECREASE: Easiness factor decrease value
-EF-THRESHOLD: Easiness factor threshold value
-FAILURE-FACTOR: Failure factor value
-INITIAL-INTERVAL: Initial interval for notes of deck"
- (gnosis-assert-float-or-nil failure-factor "failure-factor must be a float less than 1" t)
- (gnosis-assert-int-or-nil ef-threshold "ef-threshold must be an integer")
- (gnosis-assert-number-or-nil ef-increase "ef-increase must be a number")
- (cl-assert (or (and (listp initial-interval)
- (and (cl-every #'integerp initial-interval)
- (length= initial-interval 2)))
- (null initial-interval))
- nil "Initial-interval must be a list of 2 integers")
- (cl-loop for (field . value) in
- `((ef-increase . ,ef-increase)
- (ef-decrease . ,ef-decrease)
- (ef-threshold . ,ef-threshold)
- (failure-factor . ,failure-factor)
- (initial-interval . ',initial-interval)
- (name . ,name))
- when value
- do (gnosis-update 'decks `(= ,field ,value) `(= id ,id))))
-
-(defun gnosis-edit-deck (&optional id)
- "Edit the contents of a deck with the given ID."
- (interactive "P")
- (let ((id (or id (gnosis--get-deck-id))))
- (pop-to-buffer-same-window (get-buffer-create "*gnosis-edit*"))
- (gnosis-edit-mode)
- (erase-buffer)
- (insert ";;\n;; You are editing a gnosis deck.\n\n")
- (insert "(gnosis-edit-update-deck ")
- (gnosis-edit-deck--export id)
- (insert ")")
- (insert "\n\n;; After finishing editing, save changes with `<C-c> <C-c>'\n;; Avoid exiting without saving.")
- (indent-region (point-min) (point-max))
- (gnosis-edit-read-only-values (format ":id %s" id) ":name" ":ef-increase"
- ":ef-decrease" ":ef-threshold" ":failure-factor")
- (local-unset-key (kbd "C-c C-c"))
- (local-set-key (kbd "C-c C-c") (lambda () (interactive) (gnosis-edit-save-exit 'gnosis-dashboard "decks")))))
-
-(cl-defun gnosis-edit-save-exit (&optional exit-func &rest args)
+(cl-defun gnosis-edit-save-exit ()
"Save edits and exit using EXIT-FUNC, with ARGS."
(interactive)
(when (get-buffer "*gnosis-edit*")
(switch-to-buffer "*gnosis-edit*")
(eval-buffer)
(quit-window t)
- (when exit-func
- (apply exit-func args))))
+ (gnosis-dashboard-return)))
+
+(cl-defun gnosis-edit-note-save-exit ()
+ "Save edits and exit using EXIT-FUNC, with ARGS."
+ (interactive)
+ (when (get-buffer "*gnosis-edit*")
+ (switch-to-buffer "*gnosis-edit*")
+ (eval-buffer)
+ (quit-window t)
+ (exit-recursive-edit)))
(defvar-keymap gnosis-edit-mode-map
:doc "gnosis-edit keymap"
@@ -1868,8 +1926,8 @@ INITIAL-INTERVAL: Initial interval for notes of deck"
:lighter " Gnosis Edit"
:keymap gnosis-edit-mode-map)
-(cl-defun gnosis-edit-update-note (&key id main options answer tags (extra-notes nil) (image nil) (second-image nil)
- ef ff suspend)
+(cl-defun gnosis-edit-update-note (&key id main options answer tags (extra-notes nil) (image nil)
+ (second-image nil) gnosis amnesia suspend)
"Update note with id value of ID.
ID: Note id
@@ -1880,8 +1938,8 @@ TAGS: Tags for note, used to organize & differentiate between notes
EXTRA-NOTES: Notes to display after user-input
IMAGE: Image to display before user-input
SECOND-IMAGE: Image to display after user-input
-EF: Easiness factor value
-FF: Failure factor value
+GNOSIS: Gnosis score
+AMNESIA: Amnesia value
SUSPEND: Suspend note, 0 for unsuspend, 1 for suspend"
(cl-assert (stringp main) nil "Main must be a string")
(cl-assert (or (stringp image) (null image)) nil
@@ -1891,14 +1949,15 @@ SUSPEND: Suspend note, 0 for unsuspend, 1 for suspend"
(cl-assert (or (stringp extra-notes) (null extra-notes)) nil
"Extra-notes must be a string, or nil")
(cl-assert (listp tags) nil "Tags must be a list of strings")
- (cl-assert (and (listp ef) (length= ef 3)) nil "ef must be a list of 3 floats")
+ (cl-assert (and (listp gnosis) (length= gnosis 3)) nil "gnosis must be a list of 3 floats")
(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")
(when (and (string= (gnosis-get-type id) "cloze")
(not (stringp options)))
(cl-assert (or (listp options) (stringp options)) nil "Options must be a list or a string.")
(cl-assert (gnosis-cloze-check main answer) nil "Clozes are not part of the question (main).")
- (cl-assert (>= (length answer) (length options)) nil "Hints (options) must be equal or less than clozes (answer).")
+ (cl-assert (>= (length answer) (length options)) nil
+ "Hints (options) must be equal or less than clozes (answer).")
(cl-assert (cl-every (lambda (item) (or (null item) (stringp item))) options) nil "Hints (options) must be either nil or a string."))
;; Construct the update clause for the emacsql update statement.
(cl-loop for (field . value) in `((main . ,main)
@@ -1908,13 +1967,13 @@ SUSPEND: Suspend note, 0 for unsuspend, 1 for suspend"
(extra-notes . ,extra-notes)
(images . ,image)
(extra-image . ,second-image)
- (ef . ',ef)
- (ff . ,ff)
+ (gnosis . ',gnosis)
+ (amnesia . ,amnesia)
(suspend . ,suspend))
when value
do (cond ((memq field '(extra-notes images extra-image))
(gnosis-update 'extras `(= ,field ,value) `(= id ,id)))
- ((memq field '(ef ff))
+ ((memq field '(gnosis amnesia))
(gnosis-update 'review `(= ,field ,value) `(= id ,id)))
((eq field 'suspend)
(gnosis-update 'review-log `(= ,field ,value) `(= id ,id)))
@@ -1922,30 +1981,224 @@ SUSPEND: Suspend note, 0 for unsuspend, 1 for suspend"
(gnosis-update 'notes `(= ,field ',value) `(= id ,id)))
(t (gnosis-update 'notes `(= ,field ,value) `(= id ,id))))))
-(defun gnosis-get-ef-increase (id)
- "Return ef-increase for note with value of id ID."
- (let ((ef-increase (gnosis-get 'ef-increase 'decks `(= id ,(gnosis-get 'deck-id 'notes `(= id ,id))))))
- (or ef-increase gnosis-algorithm-ef-increase)))
-
-(defun gnosis-get-ef-decrease (id)
- "Return ef-decrease for note with value of id ID."
- (let ((ef-decrease (gnosis-get 'ef-decrease 'decks `(= id ,(gnosis-get 'deck-id 'notes `(= id ,id))))))
- (or ef-decrease gnosis-algorithm-ef-decrease)))
-
-(defun gnosis-get-ef-threshold (id)
- "Return ef-threshold for note with value of id ID."
- (let ((ef-threshold (gnosis-get 'ef-threshold 'decks `(= id ,(gnosis-get 'deck-id 'notes `(= id ,id))))))
- (or ef-threshold gnosis-algorithm-ef-threshold)))
+(defun gnosis-get-custom-values--validate (plist valid-keywords)
+ "Verify that PLIST consists of VALID-KEYWORDS."
+ (let ((keys (let (ks)
+ (while plist
+ (setq ks (cons (car plist) ks))
+ (setq plist (cddr plist)))
+ ks)))
+ (let ((invalid-key (cl-find-if (lambda (key) (not (member key valid-keywords))) keys)))
+ (if invalid-key
+ (error "Invalid custom keyword found in: %s" invalid-key)
+ t))))
+
+(defun gnosis-get-custom-values (key search-value &optional values)
+ "Return SEARCH-VALUE for KEY from VALUES.
+
+VALUES: Defaults to `gnosis-custom-values'."
+ (cl-assert (or (eq key :deck) (eq key :tag)) nil "Key value must be either :tag or :deck")
+ (cl-assert (stringp search-value) nil "Search-value must be the name of tag or deck as a string.")
+ (let ((results)
+ (values (or values gnosis-custom-values)))
+ (dolist (rule values)
+ (when (and (plist-get rule key)
+ (equal (plist-get rule key) search-value))
+ (setq results (append results (nth 2 rule)))))
+ (gnosis-get-custom-values--validate results gnosis-custom--valid-values)
+ results))
+
+(defun gnosis-get-custom-deck-value (deck value &optional values)
+ "Return custom VALUE for note DECK."
+ (plist-get (gnosis-get-custom-values :deck deck values) value))
+
+(defun gnosis-get-custom-tag-values (id keyword &optional custom-tags custom-values)
+ "Return KEYWORD values for note ID."
+ (cl-assert (keywordp keyword) nil "keyword must be a keyword!")
+ (let ((tags (if id (gnosis-get 'tags 'notes `(= id ,id)) custom-tags)))
+ (cl-loop for tag in tags
+ ;; Only collect non-nil values
+ when (plist-get (gnosis-get-custom-values :tag tag custom-values) keyword)
+ collect (plist-get (gnosis-get-custom-values :tag tag custom-values) keyword))))
+
+(defun gnosis-get-note-tag-amnesia (id &optional custom-tags custom-values)
+ "Return tag MINIMUM amnesia for note ID.
+
+The closer the amnesia value is to 0, the closer it is to total
+amnesia i.e next interval to be 0.
+
+CUSTOM-TAGS: Specify tags for note id.
+CUSTOM-VALUES: Specify values for tags."
+ (let ((amnesia-values (gnosis-get-custom-tag-values id :amnesia custom-tags custom-values)))
+ (and amnesia-values (apply #'max amnesia-values))))
+
+(defun gnosis-get-note-deck-amnesia (id &optional custom-deck custom-values)
+ "Return tag amnesia for note ID.
+
+Optionally, use CUSTOM-DECK and CUSTOM-VALUES."
+ (let ((deck (or (gnosis-get-note-deck-name id) custom-deck )))
+ (or (gnosis-get-custom-deck-value deck :amnesia custom-values)
+ gnosis-algorithm-amnesia-value)))
+
+(defun gnosis-get-note-amnesia (id &optional custom-deck custom-tags custom-values )
+ "Return amnesia value for note ID.
+
+Note amnesia should be hte MINIMUM value of deck's & tags' amnesia.
+
+CUSTOM-TAGS: Specify tags for note id.
+CUSTOM-VALUES: Specify values for tags."
+ (let* ((deck-amnesia (gnosis-get-note-deck-amnesia id custom-deck custom-values))
+ (tags-amnesia (gnosis-get-note-tag-amnesia id custom-tags custom-values))
+ (note-amnesia (or tags-amnesia deck-amnesia)))
+ (if (>= note-amnesia 1)
+ (error "Amnesia value must be lower than 1")
+ note-amnesia)))
+
+(defun gnosis-get-note-tag-epignosis (id &optional custom-tags custom-values)
+ "Return tag epignosis for note ID.
+
+CUSTOM-TAGS: Specify tags for note id.
+CUSTOM-VALUES: Specify values for tags."
+ (let* ((epignosis-values (gnosis-get-custom-tag-values id :epignosis custom-tags custom-values)))
+ (and epignosis-values (apply #'max epignosis-values))))
+
+(defun gnosis-get-note-deck-epignosis (id &optional custom-deck custom-values)
+ "Return deck epignosis for note ID."
+ (let ((deck (or (gnosis-get-note-deck-name id) custom-deck)))
+ (or (gnosis-get-custom-deck-value deck :epignosis custom-values)
+ gnosis-algorithm-epignosis-value)))
+
+(defun gnosis-get-note-epignosis (id &optional custom-deck custom-tags custom-values)
+ "Return epignosis value for note ID."
+ (let* ((deck-epignosis (gnosis-get-note-deck-epignosis id custom-deck custom-values))
+ (tag-epignosis (gnosis-get-note-tag-epignosis id custom-tags custom-values))
+ (note-epignosis (or tag-epignosis deck-epignosis)))
+ (if (>= note-epignosis 1)
+ (error "Epignosis value must be lower than 1")
+ note-epignosis)))
+
+(defun gnosis-get-note-tag-agnoia (id &optional custom-tags custom-values)
+ "Return agnoia value for note ID."
+ (let ((agnoia-values (gnosis-get-custom-tag-values id :agnoia custom-tags custom-values)))
+ (and agnoia-values (apply #'max agnoia-values))))
+
+(defun gnosis-get-note-deck-agnoia (id &optional custom-deck custom-values)
+ "Return agnoia value for note ID."
+ (let ((deck (or (gnosis-get-note-deck-name id) custom-deck)))
+ (or (gnosis-get-custom-deck-value deck :agnoia custom-values)
+ gnosis-algorithm-agnoia-value)))
+
+(defun gnosis-get-note-agnoia (id &optional custom-deck custom-tags custom-values)
+ "Return agnoia value for note ID."
+ (let* ((deck-agnoia (gnosis-get-note-deck-agnoia id custom-deck custom-values))
+ (tag-agnoia (gnosis-get-note-tag-agnoia id custom-tags custom-values))
+ (note-agnoia (or tag-agnoia deck-agnoia)))
+ (if (>= note-agnoia 1)
+ (error "Agnoia value must be lower than 1")
+ note-agnoia)))
+
+(defun gnosis-proto-max-values (proto-values)
+ "Return max values from PROTO-VALUES."
+ (if (not (and (listp proto-values) (cl-every #'listp proto-values)))
+ proto-values
+ (let* ((max-len (apply #'max (mapcar #'length proto-values)))
+ (padded-lists (mapcar (lambda (lst)
+ (append lst (make-list (- max-len (length lst)) 0)))
+ proto-values)))
+ (apply #'cl-mapcar #'max padded-lists))))
+
+(defun gnosis-get-note-proto (id &optional custom-tags custom-deck custom-values)
+ "Return tag proto values for note ID.
+
+CUSTOM-VALUES: Custom values to be used instead.
+CUSTOM-TAGS: Custom tags to be used instead.
+CUSTOM-DECK: Custom deck to be used instead."
+ (let* ((deck (or custom-deck (gnosis-get-note-deck-name id)))
+ (tags-proto (gnosis-get-custom-tag-values id :proto custom-tags custom-values))
+ (decks-proto (gnosis-get-custom-deck-value deck :proto custom-values)))
+ (if tags-proto (gnosis-proto-max-values tags-proto) (gnosis-proto-max-values (or decks-proto gnosis-algorithm-proto)))))
+
+(defun gnosis-get-note-tag-anagnosis (id &optional custom-tags custom-values)
+ "Return the minimum anagnosis tag value for note ID.
+
+CUSTOM-VALUES: Custom values to be used instead.
+CUSTOM-TAGS: Custom tags to be used instead."
+ (let ((anagnosis-values (gnosis-get-custom-tag-values id :anagnosis custom-tags custom-values)))
+ (and anagnosis-values (apply #'min anagnosis-values))))
+
+(defun gnosis-get-note-deck-anagnosis (id &optional custom-deck custom-values)
+ "Return anagnosis deck value for note ID.
+
+CUSTOM-VALUES: Custom values to be used instead.
+CUSTOM-DECK: Custom deck to be used instead."
+ (let ((deck (or (gnosis-get-note-deck-name id) custom-deck)))
+ (or (gnosis-get-custom-deck-value deck :anagnosis custom-values)
+ gnosis-algorithm-anagnosis-value)))
+
+(defun gnosis-get-note-anagnosis (id &optional custom-deck custom-tags custom-values)
+ "Return minimum anagnosis value for note ID.
+
+CUSTOM-VALUES: Custom values to be used instead.
+CUSTOM-TAGS: Custom tags to be used instead.
+CUSTOM-DECK: Custom deck to be used instead."
+ (let* ((deck-anagnosis (gnosis-get-note-deck-anagnosis id custom-deck custom-values))
+ (tag-anagnosis (gnosis-get-note-tag-anagnosis id custom-tags custom-values))
+ (note-anagnosis (or tag-anagnosis deck-anagnosis)))
+ note-anagnosis))
+
+(defun gnosis-get-note-deck-lethe (id &optional custom-deck custom-values)
+ "Return lethe deck value for note ID.
+
+CUSTOM-VALUES: Custom values to be used instead.
+CUSTOM-DECK: Custom deck to be used instead."
+ (let ((deck (or (gnosis-get-note-deck-name id) custom-deck)))
+ (or (gnosis-get-custom-deck-value deck :lethe custom-values)
+ gnosis-algorithm-lethe-value)))
+
+(defun gnosis-get-note-tag-lethe (id &optional custom-tags custom-values)
+ "Return note ID tag lethe values.
+
+CUSTOM-VALUES: Custom values to be used instead.
+CUSTOM-TAGS: Custom tags to be used instead."
+ (let ((lethe-values (gnosis-get-custom-tag-values id :lethe custom-tags custom-values)))
+ (and lethe-values (apply #'min lethe-values))))
+
+(defun gnosis-get-note-lethe (id &optional custom-deck custom-tags custom-values)
+ "Return note ID lethe value.
+
+CUSTOM-VALUES: Custom values to be used instead.
+CUSTOM-TAGS: Custom tags to be used instead.
+CUSTOM-DECK: Custom deck to be used instead."
+ (let* ((deck-lethe (gnosis-get-note-deck-lethe id custom-deck custom-values))
+ (tag-lethe (gnosis-get-note-tag-lethe id custom-tags custom-values))
+ (note-lethe (or tag-lethe deck-lethe)))
+ note-lethe))
+
+(defun gnosis-get-date-total-notes (&optional date)
+ "Return total notes reviewed for DATE.
+
+If entry for DATE does not exist, it will be created.
+
+Defaults to current date."
+ (cl-assert (listp date) nil "Date must be a list.")
+ (let* ((date (or date (gnosis-algorithm-date)))
+ (reviewed-total (car (gnosis-select 'reviewed-total 'activity-log `(= date ',date) t)))
+ (reviewed-new (or (car (gnosis-select 'reviewed-new 'activity-log `(= date ',date) t)) 0)))
+ (or reviewed-total
+ (progn
+ ;; Using reviewed-new instead of hardcoding 0 just to not mess up tests.
+ (and (equal date (gnosis-algorithm-date))
+ (gnosis--insert-into 'activity-log `([,date 0 ,reviewed-new])))
+ 0))))
-(defun gnosis-get-deck-initial-interval (id)
- "Return initial-interval for notes of deck ID."
- (let ((initial-interval (gnosis-get 'initial-interval 'decks `(= id ,id))))
- (or initial-interval gnosis-algorithm-interval)))
+(defun gnosis-get-date-new-notes (&optional date)
+ "Return total notes reviewed for DATE.
-(defun gnosis-get-note-initial-interval (id)
- "Return initial-interval for note with ID."
- (let ((deck-id (gnosis-get 'deck-id 'notes `(= id ,id))))
- (gnosis-get-deck-initial-interval deck-id)))
+Defaults to current date."
+ (cl-assert (listp date) nil "Date must be a list.")
+ (let* ((date (or date (gnosis-algorithm-date)))
+ (reviewed-new (or (car (gnosis-select 'reviewed-new 'activity-log `(= date ',date) t)) 0)))
+ reviewed-new))
(cl-defun gnosis-export-note (id &optional (export-for-deck nil))
"Export fields for note with value of id ID.
@@ -1975,9 +2228,10 @@ The final exported note is indented using the `indent-region' function
to improve readability."
(let ((values (append (gnosis-select '[id main options answer tags] 'notes `(= id ,id) t)
(gnosis-select '[extra-notes images extra-image] 'extras `(= id ,id) t)
- (gnosis-select '[ef ff] 'review `(= id ,id) t)
+ (gnosis-select '[gnosis amnesia] 'review `(= id ,id) t)
(gnosis-select 'suspend 'review-log `(= id ,id) t)))
- (fields '(:id :main :options :answer :tags :extra-notes :image :second-image :ef :ff :suspend)))
+ (fields (list :id :main :options :answer :tags
+ :extra-notes :image :second-image :gnosis :amnesia :suspend)))
(when export-for-deck
(setf values (append (gnosis-select 'type 'notes `(= id ,id) t)
(butlast (cdr values) 3)))
@@ -1991,12 +2245,7 @@ to improve readability."
;;; Database Schemas
(defvar gnosis-db-schema-decks '([(id integer :primary-key :autoincrement)
- (name text :not-null)
- (failure-factor float)
- (ef-increase float)
- (ef-decrease float)
- (ef-threshold integer)
- (initial-interval listp)]))
+ (name text :not-null)]))
(defvar gnosis-db-schema-notes '([(id integer :primary-key :autoincrement)
(type text :not-null)
@@ -2009,33 +2258,29 @@ to improve readability."
:on-delete :cascade)))
(defvar gnosis-db-schema-review '([(id integer :primary-key :not-null) ;; note-id
- (ef integer :not-null) ;; Easiness factor
- (ff integer :not-null) ;; Forgetting factor
- (interval integer :not-null)] ;; Initial Interval
+ (gnosis integer :not-null)
+ (amnesia integer :not-null)]
(:foreign-key [id] :references notes [id]
:on-delete :cascade)))
(defvar gnosis-db-schema-review-log '([(id integer :primary-key :not-null) ;; note-id
(last-rev integer :not-null) ;; Last review date
(next-rev integer :not-null) ;; Next review date
- (c-success integer :not-null) ;; number of consecutive successful reviews
- (t-success integer :not-null) ;; Number of total successful reviews
- (c-fails integer :not-null) ;; Number of consecutive failed reviewss
- (t-fails integer :not-null) ;; Number of total failed reviews
+ (c-success integer :not-null) ;; Consecutive successful reviews
+ (t-success integer :not-null) ;; Total successful reviews
+ (c-fails integer :not-null) ;; Consecutive failed reviewss
+ (t-fails integer :not-null) ;; Total failed reviews
(suspend integer :not-null) ;; Binary value, 1=suspended
(n integer :not-null)] ;; Number of reviews
(:foreign-key [id] :references notes [id]
:on-delete :cascade)))
+(defvar gnosis-db-schema-activity-log '([(date text :not-null)
+ (reviewed-total integer :not-null)
+ (reviewed-new integer :not-null)]))
+
(defvar gnosis-db-schema-extras '([(id integer :primary-key :not-null)
(extra-notes string)
- ;; Despite the name 'images', this
- ;; is a single string value. At
- ;; first it was designed to hold a
- ;; list of strings for image paths,
- ;; but it was changed to just a
- ;; string to hold a single image
- ;; path.
(images string)
;; Extra image path to show after review
(extra-image string)]
@@ -2052,217 +2297,69 @@ Return note ids for notes that match QUERY."
(cl-assert (or (stringp query) (eq query nil)))
(let* ((query (or query (read-string "Search for note: ")))
(words (split-string query))
- (clause `(and ,@(mapcar (lambda (word)
- `(like main ,(format "%%%s%%" word)))
- words))))
- (gnosis-select 'id 'notes clause t)))
-
-;; Dashboard
-
-(defun gnosis-dashboard-output-note (id)
- "Output contents for note with ID, formatted for gnosis dashboard."
- (cl-loop for item in (append (gnosis-select '[main options answer tags type] 'notes `(= id ,id) t)
- (gnosis-select 'suspend 'review-log `(= id ,id) t))
- if (listp item)
- collect (mapconcat #'identity item ",")
- 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.
-
-TAGS: boolean value, t to specify tags.
-DUE: boolean value, t to specify due notes.
-DECK: Integer, specify deck id.
-QUERY: String value,"
- (cl-assert (and (booleanp due) (booleanp tags) (or (numberp deck) (null deck)) (or (stringp query) (null query)))
- nil "Incorrect value passed to `gnosis-collect-note-ids'")
- (cond ((and (null tags) (null due) (null deck) (null query))
- (gnosis-select 'id 'notes '1=1 t))
- ;; All due notes
- ((and (null tags) due (null deck))
- (gnosis-review-get-due-notes))
- ;; All notes for tags
- ((and tags (null due) (null deck))
- (gnosis-select-by-tag (gnosis-tag-prompt)))
- ;; All due notes for tags
- ((and tags due (null deck))
- (gnosis-select-by-tag (gnosis-tag-prompt) t))
- ;; All notes for deck
- ((and (null tags) (null due) deck)
- (gnosis-get-deck-notes deck nil))
- ;; All due notes for deck
- ((and (null tags) deck due)
- (gnosis-get-deck-notes deck t))
- ;; Query
- ((and (null tags) (null due) (null deck) query)
- (gnosis-search-note query))))
-
-(defun gnosis-dashboard-output-notes (note-ids)
- "Return NOTE-IDS contents on gnosis dashboard."
- (cl-assert (listp note-ids) t "`note-ids' must be a list of note ids.")
- (pop-to-buffer "*gnosis-dashboard*")
- (gnosis-dashboard-mode)
- (setf tabulated-list-format `[("Main" ,(/ (window-width) 4) t)
- ("Options" ,(/ (window-width) 6) t)
- ("Answer" ,(/ (window-width) 6) t)
- ("Tags" ,(/ (window-width) 5) t)
- ("Type" ,(/ (window-width) 10) T)
- ("Suspend" ,(/ (window-width) 6) t)]
- tabulated-list-entries (cl-loop for id in note-ids
- for output = (gnosis-dashboard-output-note id)
- when output
- collect (list (number-to-string id) (vconcat output)))
- gnosis-dashboard-note-ids note-ids)
- (tabulated-list-init-header)
- ;; Keybindings, for editing, suspending, deleting notes.
- ;; We use `local-set-key' to bind keys to the buffer to avoid
- ;; conflicts when using the dashboard for displaying either notes
- ;; or decks.
- (local-set-key (kbd "e") #'gnosis-dashboard-edit-note)
- (local-set-key (kbd "s") #'(lambda () (interactive)
- (gnosis-suspend-note (string-to-number (tabulated-list-get-id)))
- (gnosis-dashboard-output-notes gnosis-dashboard-note-ids)
- (revert-buffer t t t)))
- (local-set-key (kbd "a") #'gnosis-add-note)
- (local-set-key (kbd "r") #'gnosis-dashboard)
- (local-set-key (kbd "d") #'(lambda () (interactive)
- (gnosis-delete-note (string-to-number (tabulated-list-get-id)))
- (gnosis-dashboard-output-notes gnosis-dashboard-note-ids)
- (revert-buffer t t t)))
- (local-unset-key (kbd "RET")))
-
-(defun gnosis-dashboard-deck-note-count (id)
- "Return total note count for deck with ID."
- (let ((note-count (caar (emacsql gnosis-db (format "SELECT COUNT(*) FROM notes WHERE deck_id=%s" id)))))
- (when (gnosis-select 'id 'decks `(= id ,id))
- (list (number-to-string note-count)))))
-
-(defun gnosis-dashboard-output-deck (id)
- "Output contents from deck with ID, formatted for gnosis dashboard."
- (cl-loop for item in (append (gnosis-select
- '[name failure-factor ef-increase ef-decrease ef-threshold initial-interval]
- 'decks `(= id ,id) t)
- (mapcar 'string-to-number (gnosis-dashboard-deck-note-count id)))
- when (listp item)
- do (cl-remove-if (lambda (x) (and (vectorp x) (zerop (length x)))) item)
- collect (format "%s" item)))
-
-(defun gnosis-dashboard-output-decks ()
- "Return deck contents for gnosis dashboard."
- (pop-to-buffer "*gnosis-dashboard*")
- (gnosis-dashboard-mode)
- (setq tabulated-list-format [("Name" 15 t)
- ("failure-factor" 15 t)
- ("ef-increase" 15 t)
- ("ef-decrease" 15 t)
- ("ef-threshold" 15 t)
- ("Initial Interval" 20 t)
- ("Total Notes" 10 t)])
- (tabulated-list-init-header)
- (setq tabulated-list-entries
- (cl-loop for id in (gnosis-select 'id 'decks '1=1 t)
- for output = (gnosis-dashboard-output-deck id)
- when output
- collect (list (number-to-string id) (vconcat output))))
- (local-set-key (kbd "e") #'gnosis-dashboard-edit-deck)
- (local-set-key (kbd "a") #'(lambda () "Add deck & refresh" (interactive)
- (gnosis-add-deck (read-string "Deck name: "))
- (gnosis-dashboard-output-decks)
- (revert-buffer t t t)))
- (local-set-key (kbd "s") #'(lambda () "Suspend notes" (interactive)
- (gnosis-suspend-deck
- (string-to-number (tabulated-list-get-id)))
- (gnosis-dashboard-output-decks)
- (revert-buffer t t t)))
- (local-set-key (kbd "d") #'(lambda () "Delete deck" (interactive)
- (gnosis-delete-deck (string-to-number (tabulated-list-get-id)))
- (gnosis-dashboard-output-decks)
- (revert-buffer t t t)))
- (local-set-key (kbd "RET") #'(lambda () "View notes of deck" (interactive)
- (gnosis-dashboard "notes"
- (gnosis-collect-note-ids
- :deck (string-to-number (tabulated-list-get-id)))))))
-
-(defun gnosis-dashboard-edit-note (&optional dashboard)
- "Get note id from tabulated list and edit it.
-
-DASHBOARD: Dashboard to return to after editing."
- (interactive)
- (let ((id (tabulated-list-get-id))
- (dashboard (or dashboard "notes")))
- (gnosis-edit-note (string-to-number id) nil dashboard)
- (message "Editing note with id: %s" id)))
-
-(defun gnosis-dashboard-edit-deck ()
- "Get deck id from tabulated list and edit it."
- (interactive)
- (let ((id (tabulated-list-get-id)))
- (gnosis-edit-deck (string-to-number id))))
-
-(defvar-keymap gnosis-dashboard-mode-map
- :doc "gnosis-dashboard keymap"
- "q" #'quit-window)
-
-(define-derived-mode gnosis-dashboard-mode tabulated-list-mode "Gnosis Dashboard"
- "Major mode for displaying Gnosis dashboard."
- :keymap gnosis-dashboard-mode-map
- (setq tabulated-list-padding 2
- tabulated-list-sort-key nil))
-
-;;;###autoload
-(cl-defun gnosis-dashboard (&optional dashboard-type (note-ids nil))
- "Display gnosis dashboard.
-
-NOTE-IDS: List of note ids to display on dashboard. When nil, prompt
-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")))))))
- (if note-ids (gnosis-dashboard-output-notes note-ids)
- (pcase dashboard-type
- ("notes" (gnosis-dashboard-output-notes (gnosis-collect-note-ids)))
- ("decks" (gnosis-dashboard-output-decks))
- ("tags" (gnosis-dashboard-output-notes (gnosis-collect-note-ids :tags t)))
- ("search" (gnosis-dashboard-output-notes
- (gnosis-collect-note-ids :query (read-string "Search for note: "))))))
- (tabulated-list-print t)))
+ (clause-main `(and ,@(mapcar (lambda (word)
+ `(like main ,(format "%%%s%%" word)))
+ words)))
+ (clause-answer `(and ,@(mapcar (lambda (word)
+ `(like answer ,(format "%%%s%%" word)))
+ words))))
+ (append (gnosis-select 'id 'notes clause-main t)
+ (gnosis-select 'id 'notes clause-answer t))))
+
+(defun gnosis-db-update-v2 ()
+ "Update to first gnosis-db version."
+ (emacsql-with-transaction gnosis-db
+ (emacsql gnosis-db [:alter-table decks :add failure-factor])
+ (emacsql gnosis-db [:alter-table decks :add ef-increase])
+ (emacsql gnosis-db [:alter-table decks :add ef-decrease])
+ (emacsql gnosis-db [:alter-table decks :add ef-threshold])
+ (emacsql gnosis-db [:alter-table decks :add initial-interval])
+ (emacsql gnosis-db (format "PRAGMA user_version = 2"))
+ (gnosis--create-table 'activity-log gnosis-db-schema-activity-log)
+ ;; Update to most recent gnosis db version.
+ (gnosis-db-update-v3)))
+
+(defun gnosis-db-update-v3 ()
+ "Upgrade database to version 3."
+ (emacsql-with-transaction gnosis-db
+ (emacsql gnosis-db [:alter-table decks :drop-column failure-factor])
+ (emacsql gnosis-db [:alter-table decks :drop-column ef-increase])
+ (emacsql gnosis-db [:alter-table decks :drop-column ef-threshold])
+ (emacsql gnosis-db [:alter-table decks :drop-column ef-decrease])
+ (emacsql gnosis-db [:alter-table decks :drop-column initial-interval])
+ ;; Review changes
+ (emacsql gnosis-db [:alter-table review :rename ef :to gnosis])
+ (emacsql gnosis-db [:alter-table review :rename ff :to amnesia])
+ (emacsql gnosis-db [:alter-table review :drop-column interval])
+ ;; Add activity log
+ (gnosis--create-table 'activity-log gnosis-db-schema-activity-log)
+ ;; Update version
+ (emacsql gnosis-db (format "PRAGMA user_version = %s" gnosis-db-version))))
(defun gnosis-db-init ()
- "Create gnosis essential directories & database."
+ "Create essential directories & database."
(let ((gnosis-curr-version (caar (emacsql gnosis-db (format "PRAGMA user_version")))))
- (unless (length= (emacsql gnosis-db [:select name :from sqlite-master :where (= type table)]) 6)
- ;; Enable foreign keys
- (emacsql gnosis-db "PRAGMA foreign_keys = ON")
- ;; Gnosis version
- (emacsql gnosis-db (format "PRAGMA user_version = %s" gnosis-db-version))
- ;; Create decks table
- (gnosis--create-table 'decks gnosis-db-schema-decks)
- ;; Create notes table
- (gnosis--create-table 'notes gnosis-db-schema-notes)
- ;; Create review table
- (gnosis--create-table 'review gnosis-db-schema-review)
- ;; Create review-log table
- (gnosis--create-table 'review-log gnosis-db-schema-review-log)
- ;; Create extras table
- (gnosis--create-table 'extras gnosis-db-schema-extras))
+ (unless (length> (emacsql gnosis-db [:select name :from sqlite-master :where (= type table)]) 3)
+ (emacsql-with-transaction gnosis-db
+ ;; Enable foreign keys
+ (emacsql gnosis-db "PRAGMA foreign_keys = ON")
+ ;; Gnosis version
+ (emacsql gnosis-db (format "PRAGMA user_version = %s" gnosis-db-version))
+ ;; Create decks table
+ (gnosis--create-table 'decks gnosis-db-schema-decks)
+ ;; Create notes table
+ (gnosis--create-table 'notes gnosis-db-schema-notes)
+ ;; Create review table
+ (gnosis--create-table 'review gnosis-db-schema-review)
+ ;; Create review-log table
+ (gnosis--create-table 'review-log gnosis-db-schema-review-log)
+ ;; Create extras table
+ (gnosis--create-table 'extras gnosis-db-schema-extras)
+ ;; Create activity-log table
+ (gnosis--create-table 'activity-log gnosis-db-schema-activity-log)))
;; Update database schema for version
- (cond ((= gnosis-curr-version 1) ;; Update to version 2
- (emacsql gnosis-db [:alter-table decks :add failure-factor])
- (emacsql gnosis-db [:alter-table decks :add ef-increase])
- (emacsql gnosis-db [:alter-table decks :add ef-decrease])
- (emacsql gnosis-db [:alter-table decks :add ef-threshold])
- (emacsql gnosis-db [:alter-table decks :add initial-interval])
- (emacsql gnosis-db (format "PRAGMA user_version = %s" gnosis-db-version))))))
+ (cond ((<= gnosis-curr-version 2)
+ (gnosis-db-update-v3)))))
(gnosis-db-init)
@@ -2280,12 +2377,12 @@ If STRING-SECTION is nil, apply FACE to the entire STRING."
(goto-char (point-min))
(animate-string string vpos hpos)
(and face
- (if string-section
- (progn
- (goto-char (point-min))
- (while (search-forward string-section nil t)
- (add-text-properties (match-beginning 0) (match-end 0) `(face ,face))))
- (add-text-properties (line-beginning-position) (line-end-position) `(face ,face))))))
+ (if string-section
+ (progn
+ (goto-char (point-min))
+ (while (search-forward string-section nil t)
+ (add-text-properties (match-beginning 0) (match-end 0) `(face ,face))))
+ (add-text-properties (line-beginning-position) (line-end-position) `(face ,face))))))
;;;###autoload
(defun gnosis-demo ()
@@ -2323,33 +2420,33 @@ If STRING-SECTION is nil, apply FACE to the entire STRING."
(if (not (cl-some #'(lambda (x) (member "demo" x)) (gnosis-select 'name 'decks)))
(progn (gnosis-add-deck deck-name)
(gnosis-add-note--basic :deck deck-name
- :question "Repetitio est mater memoriae"
- :hint "Translate this Latin phrase to English."
- :answer "Repetition is the mother of memory"
- :extra "/Regular review/ at increasing intervals *reinforces* *memory* *retention*. Strengthening neural connections & making it easier to recall information long-term"
+ :question "Repetitio est mater memoriae"
+ :hint "Translate this Latin phrase to English."
+ :answer "Repetition is the mother of memory"
+ :extra "/Regular review/ at increasing intervals *reinforces* *memory* *retention*. Strengthening neural connections & making it easier to recall information long-term"
+ :tags note-tags)
+ (gnosis-add-note--mc-cloze :deck deck-name
+ :question "Consistency is _key_ to using gnosis effectively."
+ :options '("Consistency" "Procrastination" "Incosistency")
+ :answer "Consistency"
+ :extra "Avoid monotony, try to engage with the material actively, and stay _consistent_!"
+ :tags note-tags)
+ (gnosis-add-note--mcq :deck deck-name
+ :question "Which one is the capital of Greece?"
+ :choices '("Athens" "Sparta" "Rome" "Berlin")
+ :correct-answer 1
+ :extra "Athens (Αθήνα) is the largest city of Greece & one of the world's oldest cities, with it's recorded history spanning over 3,500 years."
:tags note-tags)
- (gnosis-add-note--mc-cloze :deck deck-name
- :question "Consistency is _key_ to using gnosis effectively."
- :options '("Consistency" "Procrastination" "Incosistency")
- :answer "Consistency"
- :extra "Avoid monotony, try to engage with the material actively, and stay _consistent_!"
- :tags note-tags)
- (gnosis-add-note--mcq :deck deck-name
- :question "Which one is the capital of Greece?"
- :choices '("Athens" "Sparta" "Rome" "Berlin")
- :correct-answer 1
- :extra "Athens (Αθήνα) is the largest city of Greece & one of the world's oldest cities, with it's recorded history spanning over 3,500 years."
- :tags note-tags)
- (gnosis-add-note--cloze :deck deck-name
- :note "GNU Emacs is an extensible editor created by {{c1::Richard}} {{c1::Stallman}} in {{c2::1984::year}}"
- :tags note-tags
- :extra "Emacs was originally implemented in 1976 on the MIT AI Lab's Incompatible Timesharing System (ITS), as a collection of TECO macros. The name “Emacs” was originally chosen as an abbreviation of “Editor MACroS”. =This version of Emacs=, GNU Emacs, was originally *written in 1984*")
- (gnosis-add-note--y-or-n :deck deck-name
- :question "Is GNU Emacs the greatest program ever written?"
- :hint "Duh"
- :answer 121
- :extra ""
- :tags note-tags))
+ (gnosis-add-note--cloze :deck deck-name
+ :note "GNU Emacs is an extensible editor created by {{c1::Richard}} {{c1::Stallman}} in {{c2::1984::year}}"
+ :tags note-tags
+ :extra "Emacs was originally implemented in 1976 on the MIT AI Lab's Incompatible Timesharing System (ITS), as a collection of TECO macros. The name “Emacs” was originally chosen as an abbreviation of “Editor MACroS”. =This version of Emacs=, GNU Emacs, was originally *written in 1984*")
+ (gnosis-add-note--y-or-n :deck deck-name
+ :question "Is GNU Emacs the greatest program ever written?"
+ :hint "Duh"
+ :answer 121
+ :extra ""
+ :tags note-tags))
(error "Demo deck already exists"))))
;; Gnosis mode ;;
@@ -2361,18 +2458,18 @@ If STRING-SECTION is nil, apply FACE to the entire STRING."
:global t
:group 'gnosis
:lighter nil
- (setq gnosis-due-notes-total (length (gnosis-review-get-due-notes)))
- (if gnosis-modeline-mode
- (progn
- (add-to-list 'global-mode-string '(:eval
- (format " G:%d" gnosis-due-notes-total)))
- (force-mode-line-update))
- (setq global-mode-string
- (seq-remove (lambda (item)
- (and (listp item) (eq (car item) :eval)
- (string-prefix-p " G:" (format "%s" (eval (cadr item))))))
- global-mode-string))
- (force-mode-line-update)))
+ (setq gnosis-due-notes-total (length (gnosis-review-get-due-notes)))
+ (if gnosis-modeline-mode
+ (progn
+ (add-to-list 'global-mode-string '(:eval
+ (format " G:%d" gnosis-due-notes-total)))
+ (force-mode-line-update))
+ (setq global-mode-string
+ (seq-remove (lambda (item)
+ (and (listp item) (eq (car item) :eval)
+ (string-prefix-p " G:" (format "%s" (eval (cadr item))))))
+ global-mode-string))
+ (force-mode-line-update)))
(define-derived-mode gnosis-mode special-mode "Gnosis"
"Gnosis Mode."