(defvar flashdeck-buffer-size 20
"*The number of new words the user can handle at once.")
(defvar flashdeck-successive-correct-answers 2
"*The number of times the user needs to answer correctly in succession
for the word to be removed.")
(defvar flashdeck-words)
(defvar flashdeck-next)
(defvar flashdeck-buffer)
(defvar flashdeck-index)
(defvar flashdeck-state)
(defun flashdeck-shuffle (array &optional start end)
(let* ((start (or start 0))
(end (or end (length array)))
(n (- end start)))
(dotimes (i (1- n))
(let* ((real-i (+ start i))
(index (+ real-i 1 (random (- n i 1))))
(other (aref array index)))
(aset array index (aref array real-i))
(aset array real-i other))))
array)
(defun flashdeck-init ()
(random t)
(let ((count (count-matches ".* = .*" (point-min) (point-max))))
(when (= count 0)
(error "There are no usable word pairs in this buffer"))
(setq flashdeck-words (make-vector count nil))
(save-excursion
(goto-char (point-min))
(dotimes (i count)
(search-forward-regexp "\\(.*\\) = \\(.*\\)")
(aset flashdeck-words i (list (match-string 1) (match-string 2)))))
(flashdeck-shuffle flashdeck-words)
(let ((n (min count flashdeck-buffer-size)))
(setq flashdeck-buffer (make-vector n nil))
(dotimes (i n)
(aset flashdeck-buffer i (cons 0 (aref flashdeck-words i))))
(setq flashdeck-next n)
(setq flashdeck-index 0))))
(defun flashdeck-next ()
(when (>= flashdeck-index (length flashdeck-buffer))
(let ((k (floor (length flashdeck-buffer) 2)))
(flashdeck-shuffle flashdeck-buffer 0 k)
(flashdeck-shuffle flashdeck-buffer k))
(setq flashdeck-index 0))
(setq flashdeck-index (1+ flashdeck-index)))
(defun flashdeck-remaining ()
(- (+ (length flashdeck-buffer) (length flashdeck-words))
flashdeck-next))
(defun flashdeck-help ()
(erase-buffer)
(insert (format "
Welcome to Emacs Flashdeck!
The definitions appear in the buffer one by one.
Pressing space will show the correct answer.
After the answer is displayed you should press
- `y' if you remembered it correctly
- `n' if you didn't
Press q any time to quit (the list of remaining words will be displayed).
There are %d words yet to be learned.
=> " (flashdeck-remaining))))
(defun flashdeck-message (str)
(let ((buffer-read-only nil))
(goto-char (point-max))
(search-backward-regexp "^=> .*$")
(replace-match "=> ")
(insert str)))
(defun flashdeck-current ()
(aref flashdeck-buffer (1- flashdeck-index)))
(defun flashdeck-show-answer ()
(interactive)
(when (eq flashdeck-state 'question)
(let ((word (cdr (flashdeck-current))))
(flashdeck-message (concat (cadr word) " = " (car word))))
(setq flashdeck-state 'answer)))
(defun flashdeck-show-next ()
(interactive)
(when (eq flashdeck-state 'answer)
(flashdeck-next)
(flashdeck-message (caddr (flashdeck-current)))
(setq flashdeck-state 'question)))
(defun flashdeck-reject-and-show-next ()
(interactive)
(setcar (flashdeck-current) 0)
(flashdeck-show-next))
(defun flashdeck-show-removal-check-end ()
(let ((buffer-read-only nil))
(if (= (length flashdeck-buffer) 0)
(progn
(erase-buffer)
(insert "\nNo more words left!\n\nPress any key to quit.")
(read-char-exclusive)
(kill-buffer nil)
(message "Bye!")
t)
(goto-char (point-min))
(search-forward-regexp "There are [0-9]+ words yet to be learned.")
(replace-match (format "There are %d words yet to be learned."
(flashdeck-remaining)))
nil)))
(defun flashdeck-accept-and-show-next ()
(interactive)
(when (eq flashdeck-state 'answer)
(setcar (flashdeck-current) (1+ (car (flashdeck-current))))
(when (= (car (flashdeck-current))
flashdeck-successive-correct-answers)
(if (= flashdeck-next (length flashdeck-words))
(progn
(setq flashdeck-buffer (remove (flashdeck-current) flashdeck-buffer))
(setq flashdeck-index (1- flashdeck-index)))
(setcar (flashdeck-current) 0)
(setcdr (flashdeck-current) (aref flashdeck-words flashdeck-next))
(setq flashdeck-next (1+ flashdeck-next))))
(unless (flashdeck-show-removal-check-end)
(flashdeck-show-next))))
(defun flashdeck-quit ()
(interactive)
(let ((buffer-read-only nil))
(erase-buffer)
(insert "\nThese words remained:\n\n")
(dotimes (i (length flashdeck-buffer))
(let ((word (cdr (aref flashdeck-buffer i))))
(insert (format "%s = %s\n" (car word) (cadr word)))))
(dotimes (i (- (length flashdeck-words) flashdeck-next))
(let ((word (aref flashdeck-words (+ flashdeck-next i))))
(insert (format "%s = %s\n" (car word) (cadr word)))))
(setq cursor-type t)
(message "Bye!")))
(defun flashdeck-impl ()
(dolist (i '(flashdeck-words flashdeck-next flashdeck-buffer flashdeck-index flashdeck-state))
(kill-local-variable i))
(flashdeck-init)
(switch-to-buffer (generate-new-buffer-name "*flashdeck*"))
(dolist (i '(flashdeck-words flashdeck-next flashdeck-buffer flashdeck-index flashdeck-state))
(make-local-variable i))
(flashdeck-help)
(setq cursor-type nil)
(make-local-variable 'show-paren-mode)
(show-paren-mode 0)
(setq buffer-read-only t)
(local-set-key (kbd "SPC") 'flashdeck-show-answer)
(local-set-key (kbd "n") 'flashdeck-reject-and-show-next)
(local-set-key (kbd "y") 'flashdeck-accept-and-show-next)
(local-set-key (kbd "q") 'flashdeck-quit)
(setq flashdeck-state 'answer)
(flashdeck-show-next))
(defun flashdeck ()
"Display flashcards in a new buffer.
Emacs Flashdeck searches the current buffer for lines of the format
`solution = definition' and tests the user if he remembers them.
With a universal argument, it only looks for words in the current region."
(interactive)
(if (not current-prefix-arg)
(flashdeck-impl)
(let ((buffer (current-buffer)))
(narrow-to-region (mark) (point))
(flashdeck-impl)
(save-excursion
(set-buffer buffer)
(widen)))))