summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThanos Apollo <[email protected]>2024-06-28 21:59:57 +0300
committerThanos Apollo <[email protected]>2024-06-28 21:59:57 +0300
commit688dc187a1684fabb209d4d08a2d2baa5ae0e99d (patch)
tree3b67cdf12439f95819e28c79f869a1458038b5f9
parent758e915fd92b4052229b2b89fa920d1f93b61b65 (diff)
parent30e8863e0ba226f67508b75b5eb72075a7b82c0e (diff)
Release version 0.3.0: Merge branch '0.3.0-dev'0.3.0
- Major aesthetic & display improvments - Add gnosis-demo - Add MC-cloze note type
-rw-r--r--doc/gnosis.info99
-rw-r--r--doc/gnosis.org54
-rw-r--r--doc/gnosis.texi44
-rw-r--r--gnosis-string-edit.el2
-rw-r--r--gnosis-test.el14
-rw-r--r--gnosis.el457
6 files changed, 528 insertions, 142 deletions
diff --git a/doc/gnosis.info b/doc/gnosis.info
index 885f23d..f9296f3 100644
--- a/doc/gnosis.info
+++ b/doc/gnosis.info
@@ -1,4 +1,5 @@
-This is gnosis.info, produced by makeinfo version 7.1 from gnosis.texi.
+This is gnosis.info, produced by .texi2any-real version 7.1 from
+gnosis.texi.
INFO-DIR-SECTION Emacs misc features
START-INFO-DIR-ENTRY
@@ -13,9 +14,14 @@ Gnosis User Manual
Gnosis (γνῶσις), pronounced "noh-sis", _meaning knowledge in Greek_, is
a spaced repetition system implementation for note taking and self
-testing.
+testing. The objective of gnosis is to maximize memory retention by
+implementing reviewing of information at increasing intervals.
-This manual is written for Gnosis version 0.2.0, released on 2023-03-08.
+ The intervals grow longer as you become more familiar with the
+information, which reinforces long-term retention and reduces the risk
+of forgetting
+
+This manual is written for Gnosis version 0.3.0, released on 2024-06-28.
• Official manual: <https://thanosapollo.org/user-manual/gnosis>
• Git repositories:
@@ -37,6 +43,7 @@ This manual is written for Gnosis version 0.2.0, released on 2023-03-08.
Note Types
* Cloze::
+* MC-Cloze::
* MCQ (Multiple Choice Question)::
* Basic Type::
* Double::
@@ -64,7 +71,10 @@ File: gnosis.info, Node: Introduction, Next: Adding notes, Prev: Top, Up: To
1 Introduction
**************
-Gnosis, is a spaced repetition system for note taking & self testing,
+Before reading this manual, it's recommended you first try out
+‘gnosis-demo’
+
+ Gnosis, is a spaced repetition system for note taking & self testing,
where notes are taken in a Question/Answer/Explanation format & reviewed
in spaced intervals, determined by the success or failure to recall a
given answer.
@@ -98,13 +108,14 @@ File: gnosis.info, Node: Note Types, Next: Customization, Prev: Adding notes,
* Menu:
* Cloze::
+* MC-Cloze::
* MCQ (Multiple Choice Question)::
* Basic Type::
* Double::
* y-or-n::

-File: gnosis.info, Node: Cloze, Next: MCQ (Multiple Choice Question), Up: Note Types
+File: gnosis.info, Node: Cloze, Next: MC-Cloze, Up: Note Types
3.1 Cloze
=========
@@ -131,9 +142,31 @@ selecting ‘Cloze’, the question should be formatted like this:
‘gnosis-cloze-guidance’.

-File: gnosis.info, Node: MCQ (Multiple Choice Question), Next: Basic Type, Prev: Cloze, Up: Note Types
+File: gnosis.info, Node: MC-Cloze, Next: MCQ (Multiple Choice Question), Prev: Cloze, Up: Note Types
+
+3.2 MC-Cloze
+============
-3.2 MCQ (Multiple Choice Question)
+A MC-Cloze (_Multiple Choice Cloze_) is a fill-in-the-blank note, but
+unlike *note cloze note type: Cloze. the user is prompted to select an
+option instead of typing an answer.
+
+ You can create multiple notes from one input, but each note can only
+have *one* cloze. The first option will always be the right answer
+(will be randomized in the database), separated by the rest of the
+answer by ‘gnosis-mc-cloze-separator’ (default value is "&&"), and a
+note will be generated from each cloze.
+
+ Example:
+ The greatest text editor is Emacs&&Vim&&Helix
+
+ When customizing ‘gnosis-mc-cloze=separator’ pay attention to not use
+values that would mess up with regex functions.
+
+
+File: gnosis.info, Node: MCQ (Multiple Choice Question), Next: Basic Type, Prev: MC-Cloze, Up: Note Types
+
+3.3 MCQ (Multiple Choice Question)
==================================
A MCQ note type, as the name suggests, is a multiple choice question.
@@ -148,7 +181,7 @@ A MCQ note type, as the name suggests, is a multiple choice question.

File: gnosis.info, Node: Basic Type, Next: Double, Prev: MCQ (Multiple Choice Question), Up: Note Types
-3.3 Basic Type
+3.4 Basic Type
==============
Basic note type is a simple question/answer note, where the user first
@@ -158,7 +191,7 @@ input the answer.

File: gnosis.info, Node: Double, Next: y-or-n, Prev: Basic Type, Up: Note Types
-3.4 Double
+3.5 Double
==========
Double note type, is essentially a note that generates 2 basic notes.
@@ -169,7 +202,7 @@ The second one reverses question/answer.

File: gnosis.info, Node: y-or-n, Prev: Double, Up: Note Types
-3.5 y-or-n
+3.6 y-or-n
==========
y-or-n (yes or no) note type, user is presented with a question and
@@ -272,6 +305,9 @@ first two initial intervals for successful reviews.
you will see it again in the next review session, if you successfully
review said note again, the next review will be tomorrow.
+ Upon failing to review a note without completing 2 successful
+reviews, you will have to review it again on the same day.
+

File: gnosis.info, Node: Easiness Factor, Next: Forgetting Factor, Prev: Initial Interval, Up: Gnosis Algorithm
@@ -349,7 +385,7 @@ synchronization across devices.
cd ~/.emacs.d/gnosis # default location for gnosis
git init # After completing your first review session, a git repo should have been initialized automatically.
- git remote add <remote_name> <remote_url>
+ git remote add origin <remote_url>
git push --set-upstream origin master
You can interactively use ‘gnosis-vc-push’ & ‘gnosis-vc-pull’. As
@@ -415,26 +451,27 @@ should be done.

Tag Table:
-Node: Top244
-Node: Introduction1209
-Node: Adding notes1851
-Node: Note Types2220
-Node: Cloze2432
-Node: MCQ (Multiple Choice Question)3350
-Node: Basic Type3846
-Node: Double4149
-Node: y-or-n4415
-Node: Customization4817
-Node: Image size5002
-Node: Typos | String Comparison5288
-Node: Gnosis Algorithm6063
-Node: Initial Interval7099
-Node: Easiness Factor7764
-Node: Forgetting Factor8712
-Node: Editing notes9320
-Node: Sync between devices9712
-Node: Extending Gnosis10740
-Node: Creating Custom Note Types11195
+Node: Top250
+Node: Introduction1503
+Node: Adding notes2230
+Node: Note Types2599
+Node: Cloze2824
+Node: MC-Cloze3720
+Node: MCQ (Multiple Choice Question)4544
+Node: Basic Type5043
+Node: Double5346
+Node: y-or-n5612
+Node: Customization6014
+Node: Image size6199
+Node: Typos | String Comparison6485
+Node: Gnosis Algorithm7260
+Node: Initial Interval8296
+Node: Easiness Factor9086
+Node: Forgetting Factor10034
+Node: Editing notes10642
+Node: Sync between devices11034
+Node: Extending Gnosis12055
+Node: Creating Custom Note Types12510

End Tag Table
diff --git a/doc/gnosis.org b/doc/gnosis.org
index 9ff58e8..0e0ccde 100644
--- a/doc/gnosis.org
+++ b/doc/gnosis.org
@@ -4,8 +4,8 @@
#+language: en
#+options: ':t toc:nil author:t email:t num:t
#+startup: content
-#+macro: stable-version 0.2.0
-#+macro: release-date 2023-03-08
+#+macro: stable-version 0.3.0
+#+macro: release-date 2024-06-28
#+macro: file @@texinfo:@file{@@$1@@texinfo:}@@
#+macro: space @@texinfo:@: @@
#+macro: kbd @@texinfo:@kbd{@@$1@@texinfo:}@@
@@ -22,9 +22,14 @@
#+texinfo_header: @set MAINTAINERCONTACT @uref{mailto:[email protected],contact the maintainer}
-Gnosis (γνῶσις), pronounced "noh-sis", /meaning knowledge in Greek/, is
-a spaced repetition system implementation for note taking and self
-testing.
+Gnosis (γνῶσις), pronounced "noh-sis", /meaning knowledge in Greek/,
+is a spaced repetition system implementation for note taking and self
+testing. The objective of gnosis is to maximize memory retention by
+implementing reviewing of information at increasing intervals.
+
+The intervals grow longer as you become more familiar with the
+information, which reinforces long-term retention and reduces the risk
+of forgetting
#+texinfo: @noindent
This manual is written for Gnosis version {{{stable-version}}}, released on {{{release-date}}}.
@@ -36,6 +41,9 @@ This manual is written for Gnosis version {{{stable-version}}}, released on {{{r
#+texinfo: @insertcopying
* Introduction
+
+Before reading this manual, it's recommended you first try out =gnosis-demo=
+
Gnosis, is a spaced repetition system for note taking & self
testing, where notes are taken in a Question/Answer/Explanation
format & reviewed in spaced intervals, determined by the success or
@@ -53,11 +61,13 @@ Creating notes for gnosis can be done interactively with:
=M-x gnosis-add-note=
When it comes to adding images, you can select images that are inside
-=gnosis-images-dir=. For adjusting image size, refer to [[Customization]]
+=gnosis-images-dir=. For adjusting image size, refer to [[#Customization][Customization]]
* Note Types
** Cloze
-
+:PROPERTIES:
+:CUSTOM_ID: Cloze
+:END:
A cloze note type is a format where you create sentences or paragraphs
with "missing" words. A fill-in-the-blanks question.
@@ -79,6 +89,24 @@ You can also format clozes like Anki if you prefer; e.g ~{{c1::Cyproheptadine}}~
You can remove the /guidance/ string by adjusting
=gnosis-cloze-guidance=.
+** MC-Cloze
+A MC-Cloze (/Multiple Choice Cloze/) is a fill-in-the-blank note,
+but unlike [[#Cloze][cloze note type]] the user is prompted to select an option
+instead of typing an answer.
+
+You can create multiple notes from one input, but each note can only
+have *one* cloze. The first option will always be the right answer
+(will be randomized in the database), separated by the rest of the
+answer by =gnosis-mc-cloze-separator= (default value is "&&"), and a
+note will be generated from each cloze.
+
+Example:
+#+BEGIN_QUOTE
+ The greatest text editor is Emacs&&Vim&&Helix
+#+END_QUOTE
+
+When customizing =gnosis-mc-cloze=separator= pay attention to not use
+values that would mess up with regex functions.
** MCQ (Multiple Choice Question)
A MCQ note type, as the name suggests, is a multiple choice question.
@@ -111,7 +139,13 @@ ANSWER must be either 121 (~y~) or 110 (~n~), as those correspond to the
character values used to represent them.
* Customization
+:PROPERTIES:
+:CUSTOM_ID: Customization
+:END:
** Image size
+:PROPERTIES:
+:CUSTOM_ID: image-size
+:END:
Adjust image size using =gnosis-image-height= & =gnosis-image-width=
Example:
@@ -176,6 +210,9 @@ Using the above example, after first successfully reviewing a note,
you will see it again in the next review session, if you successfully
review said note again, the next review will be tomorrow.
+Upon failing to review a note without completing 2 successful reviews,
+you will have to review it again on the same day.
+
** Easiness Factor
The =gnosis-algorithm-ef= is a list that consists of three items:
@@ -240,7 +277,7 @@ Example:
#+begin_src bash
cd ~/.emacs.d/gnosis # default location for gnosis
git init # After completing your first review session, a git repo should have been initialized automatically.
- git remote add <remote_name> <remote_url>
+ git remote add origin <remote_url>
git push --set-upstream origin master
#+end_src
@@ -257,6 +294,7 @@ To automatically push changes after a review session, add this to your configura
(setf gnosis-vc-auto-push t)
(gnosis-vc-pull) ;; Run vc-pull for gnosis on startup
#+end_src
+
* Extending Gnosis
To make development and customization easier, gnosis comes with
=gnosis-test= module, that should be used to create a custom database for
diff --git a/doc/gnosis.texi b/doc/gnosis.texi
index 7f98492..f213740 100644
--- a/doc/gnosis.texi
+++ b/doc/gnosis.texi
@@ -25,12 +25,17 @@
@node Top
@top Gnosis User Manual
-Gnosis (γνῶσις), pronounced ``noh-sis'', @emph{meaning knowledge in Greek}, is
-a spaced repetition system implementation for note taking and self
-testing.
+Gnosis (γνῶσις), pronounced ``noh-sis'', @emph{meaning knowledge in Greek},
+is a spaced repetition system implementation for note taking and self
+testing. The objective of gnosis is to maximize memory retention by
+implementing reviewing of information at increasing intervals.
+
+The intervals grow longer as you become more familiar with the
+information, which reinforces long-term retention and reduces the risk
+of forgetting
@noindent
-This manual is written for Gnosis version 0.2.0, released on 2023-03-08.
+This manual is written for Gnosis version 0.3.0, released on 2024-06-28.
@itemize
@item
@@ -62,6 +67,7 @@ Git repositories:
Note Types
* Cloze::
+* MC-Cloze::
* MCQ (Multiple Choice Question)::
* Basic Type::
* Double::
@@ -88,6 +94,8 @@ Extending Gnosis
@node Introduction
@chapter Introduction
+Before reading this manual, it's recommended you first try out @samp{gnosis-demo}
+
Gnosis, is a spaced repetition system for note taking & self
testing, where notes are taken in a Question/Answer/Explanation
format & reviewed in spaced intervals, determined by the success or
@@ -114,6 +122,7 @@ When it comes to adding images, you can select images that are inside
@menu
* Cloze::
+* MC-Cloze::
* MCQ (Multiple Choice Question)::
* Basic Type::
* Double::
@@ -149,6 +158,28 @@ Each `cX` tag can have multiple clozes, but each cloze must be a
You can remove the @emph{guidance} string by adjusting
@samp{gnosis-cloze-guidance}.
+@node MC-Cloze
+@section MC-Cloze
+
+A MC-Cloze (@emph{Multiple Choice Cloze}) is a fill-in-the-blank note,
+but unlike @ref{Cloze, , cloze note type} the user is prompted to select an option
+instead of typing an answer.
+
+You can create multiple notes from one input, but each note can only
+have @strong{one} cloze. The first option will always be the right answer
+(will be randomized in the database), separated by the rest of the
+answer by @samp{gnosis-mc-cloze-separator} (default value is ``&&''), and a
+note will be generated from each cloze.
+
+Example:
+@quotation
+The greatest text editor is Emacs&&Vim&&Helix
+
+@end quotation
+
+When customizing @samp{gnosis-mc-cloze=separator} pay attention to not use
+values that would mess up with regex functions.
+
@node MCQ (Multiple Choice Question)
@section MCQ (Multiple Choice Question)
@@ -271,6 +302,9 @@ Using the above example, after first successfully reviewing a note,
you will see it again in the next review session, if you successfully
review said note again, the next review will be tomorrow.
+Upon failing to review a note without completing 2 successful reviews,
+you will have to review it again on the same day.
+
@node Easiness Factor
@section Easiness Factor
@@ -354,7 +388,7 @@ Example:
@example
cd ~/.emacs.d/gnosis # default location for gnosis
git init # After completing your first review session, a git repo should have been initialized automatically.
-git remote add <remote_name> <remote_url>
+git remote add origin <remote_url>
git push --set-upstream origin master
@end example
diff --git a/gnosis-string-edit.el b/gnosis-string-edit.el
index e6a90e4..836fddd 100644
--- a/gnosis-string-edit.el
+++ b/gnosis-string-edit.el
@@ -28,7 +28,7 @@
;; modified version string-edit.el to make gnosis available to users
;; of Emacs versions < 29.
-;; Code:
+;;; Code:
(require 'cl-lib)
diff --git a/gnosis-test.el b/gnosis-test.el
index 045506a..24dc4bc 100644
--- a/gnosis-test.el
+++ b/gnosis-test.el
@@ -62,11 +62,11 @@ DECK: Deck to add the inputs to."
(when (y-or-n-p "Add MCQ type?")
(dotimes (_ num)
(gnosis-add-note--mcq :deck testing-deck
- :question "A 37-year-old man is admitted to the
-emergency department after a severe car crash. After examining the
-patient the emergency medicine physician concludes that the serratus
-anterior muscle is damaged. Which of the following nerves innervates
-the serratus anterior muscle?"
+ :question "A *37-year-old* man is admitted to the
+emergency department after a severe car crash. /After/ examining the
+patient the emergency medicine physician concludes *that* the serratus
+anterior muscle is damaged. ~Which~ of the following nerves innervates
+the =serratus anterior muscle=?"
:choices '("Long thoracic" "Axillary" "Spinal accessory" "Dorsal scapular" "Thoracodorsal")
:correct-answer 1
:extra "The long thoracic is the only nerve that
@@ -112,7 +112,7 @@ by the thoracodorsal nerve."
:images (cons gnosis-test-image gnosis-test-image)
:tags (gnosis-test-random-items gnosis-test-tags 2))))))
-(defun gnosis-test-start ()
+(defun gnosis-test-start (&optional note-num)
"Begin/End testing env.
If ask nil, leave testing env"
@@ -130,7 +130,7 @@ If ask nil, leave testing env"
(gnosis--drop-table table)
(error (message "No %s table to drop." table))))
(gnosis-db-init)
- (gnosis-test-add-fields)
+ (gnosis-test-add-fields note-num)
(message "Adding testing values...")
(message "Development env is ready for testing."))
(setf gnosis-db (emacsql-sqlite-open (expand-file-name "gnosis.db" gnosis-dir)))
diff --git a/gnosis.el b/gnosis.el
index eceb00e..1a7edf9 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -5,7 +5,7 @@
;; Author: Thanos Apollo <[email protected]>
;; Keywords: extensions
;; URL: https://thanosapollo.org/projects/gnosis
-;; Version: 0.2.4
+;; Version: 0.3.0
;; Package-Requires: ((emacs "27.2") (emacsql "20240124") (compat "29.1.4.2"))
@@ -36,13 +36,15 @@
;;; Code:
-(require 'emacsql-sqlite)
(require 'cl-lib)
+(require 'vc)
+(require 'emacsql-sqlite)
+
(require 'gnosis-algorithm)
(require 'gnosis-string-edit)
-(require 'vc)
+(require 'animate)
(defgroup gnosis nil
"Spaced Repetition System For Note Taking & Self Testing."
@@ -125,7 +127,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" "MC-Cloze" "Cloze" "Basic" "Double" "y-or-n")
"Gnosis available note types.")
(defvar gnosis-previous-note-tags '()
@@ -150,6 +152,14 @@ car value is the prompt, cdr is the prewritten string.")
car value is the prompt, cdr is the prewritten string.")
+(defvar gnosis-mc-cloze-guidance
+ '("MC-Cloze Example: This is an example answer&&option2&&option3" . ""))
+
+(defcustom gnosis-mc-cloze-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.
@@ -162,6 +172,11 @@ Seperate the question/stem from options."
:type 'string
:group 'gnosis)
+(defcustom gnosis-center-content t
+ "When t, centers text."
+ :type 'boolean
+ :group 'gosis)
+
(defvar gnosis-due-notes-total nil
"Total due notes.")
@@ -174,8 +189,7 @@ Seperate the question/stem from options."
:prefix 'gnosis-face)
(defface gnosis-face-extra
- '((t :inherit italic
- :foreground "#9C91E4"))
+ '((t :inherit font-lock-doc-face))
"Face for extra-notes."
:group 'gnosis-faces)
@@ -185,7 +199,9 @@ Seperate the question/stem from options."
:group 'gnosis-face-faces)
(defface gnosis-face-separator
- '((t :inherit warning))
+ '((default :inherit org-hide)
+ (((background light)) :strike-through "gray70")
+ (t :strike-through "gray30"))
"Face for section separator."
:group 'gnosis-face)
@@ -296,15 +312,118 @@ History is disabled."
if (= i index) collect new-item
else collect item))
+(defun gnosis-insert-separator ()
+ "Insert a dashed line spanning the entire width of the buffer."
+ (interactive)
+ (let* ((width (window-width))
+ (dash-line (concat (make-string width ?-))))
+ (insert "\n" dash-line "\n")
+ ;; Apply an overlay to hide only the dashes
+ (let ((start (save-excursion (forward-line -1) (point)))
+ (end (point)))
+ (let ((overlay (make-overlay start end)))
+ (overlay-put overlay 'face 'gnosis-face-separator)
+ (overlay-put overlay 'display (make-string width ?\s))))))
+
+(defun gnosis-center-current-line (&optional center?)
+ "Centers text in the current line ignoring leading spaces.
+
+Acts only when CENTER? is t."
+ (interactive)
+ (let* ((start (line-beginning-position))
+ (end (line-end-position))
+ (text (string-trim (buffer-substring start end)))
+ (padding (max (/ (- (window-width) (length text)) 2) 0))
+ (center? (or center? gnosis-center-content)))
+ (when center?
+ (delete-region start end)
+ (insert (make-string padding ? ) text))))
+
+(defun gnosis-center-string (input-string &optional center?)
+ "Center each line of the given INPUT-STRING in the current window width.
+
+Acts only when CENTER? is t."
+ (let ((window-width (window-width))
+ (center? (or center? gnosis-center-content)))
+ (when 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"))))
+
+(defun gnosis-apply-center-buffer-overlay (&optional point)
+ "Center text in buffer starting at POINT using `gnosis-center-current-line'.
+This will not be applied to sentences that start with double space."
+ (save-excursion
+ (goto-char (or point (point-min)))
+ (while (not (or (= (point-max) (point)) (looking-at "^ ")))
+ (gnosis-center-current-line)
+ (forward-line 1))))
+
+(defun gnosis-apply-syntax-overlay ()
+ "Apply custom font overlays for syntax highlighting, and remove delimiters."
+ (let ((syntax-highlights '(("\\*\\([^*[:space:]][^*]*[^*[:space:]]\\)\\*" . bold)
+ ("/\\([^/[:space:]][^/]*[^/[:space:]]\\)/" . italic)
+ ("=\\([^=[:space:]][^=]*[^=[:space:]]\\)=" . font-lock-constant-face)
+ ("~\\([^~[:space:]][^~]*[^~[:space:]]\\)~" . font-lock-keyword-face))))
+ (save-excursion
+ (cl-loop for (regex . face) in syntax-highlights
+ do (progn
+ (goto-char (point-min))
+ (while (re-search-forward regex nil t)
+ (let ((start (match-beginning 1))
+ (end (match-end 1)))
+ (overlay-put (make-overlay start end) 'face face)
+ (delete-region end (match-end 0))
+ (delete-region (match-beginning 0) start))))))))
+
(defun gnosis-display-question (id &optional fill-paragraph-p)
"Display main row for note ID.
If FILL-PARAGRAPH-P, insert question using `fill-paragraph'."
- (let ((question (gnosis-get 'main 'notes `(= id ,id))))
+ (let ((question (gnosis-get 'main 'notes `(= id ,id)))
+ (fill-paragraph-p (or fill-paragraph-p t)))
(erase-buffer)
(if fill-paragraph-p
- (fill-paragraph (insert "\n" (propertize question 'face 'gnosis-face-main))))
- (insert "\n" (propertize question 'face 'gnosis-face-main))))
+ (fill-paragraph (insert "\n" (propertize question 'face 'gnosis-face-main)))
+ (insert "\n" (propertize question 'face 'gnosis-face-main)))
+ (newline)
+ (gnosis-apply-center-buffer-overlay)
+ (gnosis-apply-syntax-overlay)))
+
+(cl-defun gnosis-display-image (id &optional (image 'images))
+ "Display image for note ID.
+
+IMAGE is the image type to display, usually should be either =images'
+or =extra-image'. Instead of using =extra-image' post review, prefer
+=gnosis-display-extra' which displays the =extra-image' as well.
+
+Refer to =gnosis-db-schema-extras' for informations on images stored."
+ (let* ((img (gnosis-get image 'extras `(= id ,id)))
+ (path-to-image (expand-file-name (or img "") (file-name-as-directory gnosis-images-dir)))
+ (image (create-image path-to-image 'png nil :width gnosis-image-width :height gnosis-image-height))
+ (image-width (car (image-size image t)))
+ (frame-width (window-text-width))) ;; Width of the current window in columns
+ (cond ((or (not img) (string-empty-p img))
+ (insert "\n\n"))
+ ((and img (file-exists-p path-to-image))
+ (let* ((padding-cols (/ (- frame-width (floor (/ image-width (frame-char-width)))) 2))
+ (padding (make-string (max 0 padding-cols) ?\s)))
+ (insert "\n\n" padding) ;; Insert padding before the image
+ (insert-image image)
+ (insert "\n\n"))))))
(defun gnosis-display-mcq-options (id)
"Display answer options for mcq note ID."
@@ -315,7 +434,7 @@ If FILL-PARAGRAPH-P, insert question using `fill-paragraph'."
do (insert (format "\n%s. %s" option-num option))
(setf option-num (1+ option-num)))))
-(defun gnosis-display-cloze-sentence (sentence clozes &optional fill-paragraph-p)
+(cl-defun gnosis-display-cloze-sentence (sentence clozes &optional (fill-paragraph-p nil))
"Display cloze sentence for SENTENCE with CLOZES.
If FILL-PARAGRAPH-P, insert using `fill-paragraph'"
@@ -325,7 +444,8 @@ If FILL-PARAGRAPH-P, insert using `fill-paragraph'"
(if fill-paragraph-p
(fill-paragraph
(insert "\n" cloze-sentence))
- (insert "\n" cloze-sentence))))
+ (insert "\n" (gnosis-center-string cloze-sentence)))
+ (gnosis-apply-syntax-overlay)))
(defun gnosis-display-basic-answer (answer success user-input)
"Display ANSWER.
@@ -335,12 +455,14 @@ When SUCCESS nil, display USER-INPUT as well"
(propertize "Answer:" 'face 'gnosis-face-directions)
" "
(propertize answer 'face 'gnosis-face-correct))
+ (gnosis-center-current-line)
;; 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))))
+ (propertize user-input 'face 'gnosis-face-false))
+ (gnosis-center-current-line)))
(cl-defun gnosis-display-y-or-n-answer (&key answer success)
"Display y-or-n answer for note ID.
@@ -353,16 +475,17 @@ SUCCESS is t when user-input is correct, else nil"
"\n\n"
(propertize "Answer:" 'face 'gnosis-face-directions)
" "
- (propertize answer 'face (if success 'gnosis-face-correct 'gnosis-face-false)))))
+ (propertize answer 'face (if success 'gnosis-face-correct 'gnosis-face-false)))
+ (gnosis-center-current-line)))
(defun gnosis-display-hint (hint)
"Display HINT."
(let ((hint (or hint "")))
(goto-char (point-max))
- (insert
- (propertize "\n\n-----\n" 'face 'gnosis-face-separator)
- (propertize hint 'face 'gnosis-face-hint))))
+ (gnosis-insert-separator)
+ (and (not (string-empty-p hint))
+ (insert (gnosis-center-string (propertize hint 'face 'gnosis-face-hint))))))
(cl-defun gnosis-display-cloze-reveal (&key (cloze-char gnosis-cloze-string) replace (success t) (face nil))
"Replace CLOZE-CHAR with REPLACE.
@@ -373,7 +496,8 @@ If FACE nil, propertize replace using `gnosis-face-correct', or
(search-forward cloze-char nil t)
(replace-match (propertize replace 'face (if (not face)
(if success 'gnosis-face-correct 'gnosis-face-false)
- face))))
+ face)))
+ (gnosis-center-current-line))
(cl-defun gnosis-display-cloze-user-answer (user-input &optional (false t))
"Display USER-INPUT answer for cloze note upon failed review.
@@ -383,45 +507,31 @@ If FALSE t, use gnosis-face-false face"
(insert "\n\n"
(propertize "Your answer:" 'face 'gnosis-face-directions)
" "
- (propertize user-input 'face (if false 'gnosis-face-false 'gnosis-face-correct))))
+ (propertize user-input 'face (if false 'gnosis-face-false 'gnosis-face-correct)))
+ (gnosis-center-current-line)
+ (newline))
(defun gnosis-display-correct-answer-mcq (answer user-choice)
"Display correct ANSWER & USER-CHOICE for MCQ note."
- (insert "\n\n"
- (propertize "Correct Answer:" 'face 'gnosis-face-directions)
- " "
- (propertize answer 'face 'gnosis-face-correct)
- "\n"
- (propertize "Your answer:" 'face 'gnosis-face-directions)
- " "
- (propertize user-choice 'face (if (string= answer user-choice)
- 'gnosis-face-correct
- 'gnosis-face-false))))
-
-(cl-defun gnosis-display-image (id &optional (image 'images))
- "Display image for note ID.
-
-IMAGE is the image type to display, usually should be either `images'
-or `extra-image'. Instead of using `extra-image' post review, prefer
-`gnosis-display-extra' which displays the `extra-image' as well.
-
-Refer to `gnosis-db-schema-extras' for informations on images stored."
- (let* ((img (gnosis-get image 'extras `(= id ,id)))
- (path-to-image (expand-file-name (or img "") (file-name-as-directory gnosis-images-dir)))
- (image (create-image path-to-image 'png nil :width gnosis-image-width :height gnosis-image-height)))
- (cond ((or (not img) (string-empty-p img))
- (insert "\n\n"))
- ((and img (file-exists-p path-to-image))
- (insert "\n\n")
- (insert-image image)))))
+ (insert (gnosis-center-string
+ (format "\n\n%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))))))
(defun gnosis-display-extra (id)
"Display extra information & extra-image for note ID."
(let ((extras (or (gnosis-get 'extra-notes 'extras `(= id ,id)) "")))
(goto-char (point-max))
- (insert (propertize "\n\n-----\n" 'face 'gnosis-face-separator))
+ (gnosis-insert-separator)
(gnosis-display-image id 'extra-image)
- (fill-paragraph (insert "\n" (propertize extras 'face 'gnosis-face-extra)))))
+ (insert "\n" (gnosis-center-string
+ (propertize extras 'face 'gnosis-face-extra))
+ "\n")
+ (gnosis-apply-syntax-overlay)))
;;;###autoload
(defun gnosis-read-string-from-buffer (prompt string)
@@ -433,9 +543,7 @@ included in the resulting string. If nil, no prompt will be
inserted in the buffer.
Also see `gnosis-string-edit'."
- (gnosis-string-edit
- prompt
- string
+ (gnosis-string-edit prompt string
(lambda (edited)
(setq string (substring-no-properties edited))
(exit-recursive-edit))
@@ -459,7 +567,7 @@ Also see `gnosis-string-edit'."
'face (if success 'gnosis-face-correct 'gnosis-face-false))))
;; Default behaviour
(goto-char (point-max))
- (insert next-review-msg))))
+ (insert (gnosis-center-string next-review-msg)))))
(cl-defun gnosis--prompt (prompt &optional (downcase nil) (split nil))
"PROMPT user for input until `q' is given.
@@ -496,7 +604,7 @@ Set SPLIT to t to split all input given."
(error "No decks found. Please create a deck first with `gnosis-add-deck'"))
(if id
(gnosis-get 'name 'decks `(= id ,id))
- (gnosis-completing-read "Deck: " (gnosis-select 'name 'decks))))
+ (funcall gnosis-completing-read-function "Deck: " (gnosis-select 'name 'decks))))
(cl-defun gnosis--get-deck-id (&optional (deck (gnosis--get-deck-name)))
"Return id for DECK name."
@@ -842,6 +950,62 @@ See `gnosis-add-note--cloze' for more reference."
:images (gnosis-select-images)
:tags (gnosis-prompt-tags--split gnosis-previous-note-tags)))
+(cl-defun gnosis-mc-cloze-extract-options (str &optional (char gnosis-mc-cloze-separator))
+ "Extract options for MC-CLOZE note type from STR.
+
+CHAR: separator for mc-cloze, default to `gnosis-mc-cloze-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 " "))))
+
+(cl-defun gnosis-add-note--mc-cloze (&key deck question options answer
+ extra (images nil) tags (suspend 0))
+ "Add MC-CLOZE note type to DECK.
+
+Refer to `gnosis-add-note-mc-cloze' for how this procedure should be used
+
+DECK: Deck to add note to
+QUESTION: Question, a string
+OPTIONS: Answer options, a list of strings
+ANSWER: the correct string, from OPTIONS.
+EXTRA: Extra notes
+IMAGES: Images to display during & after review
+TAGS: Tags for note
+SUSPEND: whether to suspend not"
+ (cl-assert (stringp deck) nil "Deck name must be a string")
+ (cl-assert (stringp question) nil "Question must be a string")
+ (cl-assert (listp options) nil "Options must be a list")
+ (cl-assert (stringp extra) nil "Extra value must be a string")
+ (cl-assert (listp images) nil "Images must be a list of string paths")
+ (cl-assert (listp tags) nil "Tags value must be a list of tags as strings")
+ (cl-assert (or (= suspend 1) (= suspend 0)) nil "Suspend value must be either 0 or 1")
+ (gnosis-add-note-fields deck "mc-cloze" question options answer extra tags (or suspend 0)
+ (car images) (cdr images)))
+
+(defun gnosis-add-note-mc-cloze (deck)
+ "Add MC-CLOZE note type to DECK.
+
+MC-CLOZE (Multiple Choice Cloze) note type consists of a sentence with a
+single cloze, for which the user will be prompted to select the correct
+answer."
+ (interactive)
+ (let* ((input (gnosis-read-string-from-buffer (or (car gnosis-mc-cloze-guidance) "")
+ (or (cdr gnosis-mc-cloze-guidance) "")))
+ (question (gnosis-mc-cloze-remove-separator input))
+ (options (gnosis-mc-cloze-extract-options input)))
+ ;; Create a note for each option extracted
+ (cl-loop for option in options
+ do (gnosis-add-note--mc-cloze :deck deck
+ :question question
+ :options option
+ :answer (car option)
+ :extra (gnosis-read-string-from-buffer "Extra" "")
+ :images (gnosis-select-images)
+ :tags (gnosis-prompt-tags--split gnosis-previous-note-tags)))))
+
;;;###autoload
(defun gnosis-add-note (&optional deck type)
"Create note(s) as TYPE interactively.
@@ -853,7 +1017,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)
@@ -915,6 +1079,12 @@ Valid cloze formats include:
(mapcar (lambda (tag-group) (nreverse (cdr tag-group)))
(nreverse result-alist))))
+(defun gnosis-mc-cloze-remove-separator (string &optional separator)
+ "Remove SEPARATOR and all followed words from STRING."
+ (let* ((separator (or separator gnosis-mc-cloze-separator))
+ (result (replace-regexp-in-string (format "%s[^ ]*" separator) "" string)))
+ result))
+
(defun gnosis-compare-strings (str1 str2)
"Compare STR1 and STR2.
@@ -1269,6 +1439,26 @@ Used to reveal all clozes left with `gnosis-face-cloze-unanswered' face."
(gnosis-display-next-review id success)
success))
+(defun gnosis-review-mc-cloze (id)
+ "Review MC-CLOZE 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)
@@ -1297,6 +1487,8 @@ Used to reveal all clozes left with `gnosis-face-cloze-unanswered' face."
(interactive)
(let ((default-directory dir))
(vc-pull)
+ ;; Fix sync by adding a small delay
+ (sit-for 0.3)
;; Reopen gnosis-db after pull
(setf gnosis-db (emacsql-sqlite-open (expand-file-name "gnosis.db" dir)))))
@@ -1319,8 +1511,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)))
@@ -1583,17 +1774,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)))
@@ -1678,10 +1868,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))))
@@ -1767,6 +1957,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.
@@ -1919,12 +2111,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)))
@@ -1963,6 +2156,90 @@ DASHBOARD-TYPE: either 'Notes' or 'Decks' to display the respective dashboard."
(gnosis-db-init)
+;;;; Gnosis Demo ;;;;
+;;;;;;;;;;;;;;;;;;;;;
+
+(defun gnosis-animate-string (string vpos &optional hpos string-section face)
+ "Display STRING animations starting at position VPOS, HPOS in BUFFER-NAME.
+
+If STRING-SECTION and FACE are provided, highlight the occurrences of
+STRING-SECTION in the STRING with FACE.
+
+If STRING-SECTION is nil, apply FACE to the entire STRING."
+ (let ((animate-n-steps 60))
+ (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))))))
+
+;;;###autoload
+(defun gnosis-demo ()
+ "Start gnosis demo."
+ (interactive)
+ (pop-to-buffer-same-window "*Gnosis Demo*")
+ (fundamental-mode)
+ (setq-local display-line-numbers nil)
+ (erase-buffer)
+ (gnosis-animate-string "Welcome to the Gnosis demo!" 2 nil "Gnosis demo" 'underline)
+ (sit-for 1)
+ (gnosis-animate-string "Gnosis is a tool designed to create a gnostikon" 3 nil "gnostikon" 'bold-italic)
+ (sit-for 1.5)
+ (gnosis-animate-string "--A place to store & test your knowledge--" 4 nil nil 'italic)
+ (sit-for 1)
+ (gnosis-animate-string "The objective of gnosis is to maximize memory retention." 6 nil
+ "maximize memory retention" 'underline)
+ (sit-for 1)
+ (gnosis-animate-string "Consistency is key; be sure to do your daily reviews!" 8 nil "Consistency is key" 'bold)
+ (sit-for 1)
+ (gnosis-animate-string "Create meaningful notes; Gnosis offers a plethora of note types --try them!"
+ 9 nil "Create Meaningful Notes" 'bold)
+ (sit-for 1)
+ (when (y-or-n-p "Try out demo gnosis note review session?")
+ (gnosis-demo-create-deck)
+ (gnosis-review-session (gnosis-select-by-tag '("demo")))))
+
+(defun gnosis-demo-create-deck ()
+ "Create demo deck."
+ (let ((deck-name "demo")
+ (note-tags '("demo")))
+ (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 "What's the =key= that was mentioned in the /demo/ of gnosis?"
+ :hint "__ is key!"
+ :answer "Consistency"
+ :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 "Gnosis has a plethora of note types!"
+ :options '("plethora" "limited selection")
+ :answer "plethora"
+ :extra "*Note types include*:\n\n=MCQ=: Multiple Choice Questions, to easily copy textbook review questions :)\n=Cloze=: Fill in the blank sentences\n=Basic=: Question-Answer-Explanation note\n=Double=: Creates 2 basic notes, second one is generated with the question & answer switched\n=MC-Cloze=: Multiple question cloze question, just like this current note\n=y-or-n=: Y or N questions"
+ :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}"
+ :hint ""
+ :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 ;;
;;;;;;;;;;;;;;;;;
@@ -1972,18 +2249,18 @@ DASHBOARD-TYPE: either 'Notes' or 'Decks' to display the respective dashboard."
: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."