Compare commits
10 commits
684cf7435f
...
9d61808af0
Author | SHA1 | Date | |
---|---|---|---|
Bram Schoenmakers | 9d61808af0 | ||
7ab1791dfa | |||
0d4ac6eb77 | |||
364172d7fb | |||
37d2aabcbb | |||
1ce66c21dc | |||
073bfef995 | |||
44876305b3 | |||
973a16a956 | |||
e31fada906 |
|
@ -16,6 +16,14 @@ Files are played using the ~mpv~ media player.
|
|||
Commands:
|
||||
- ~org-fc-audio-set-before~
|
||||
- ~org-fc-audio-set-after~
|
||||
|
||||
Each time an audio file is played, it's path is stored in
|
||||
~org-fc-audio-last-file~ and can be replayed using
|
||||
~org-fc-audio-replay~ or ~org-fc-audio-replay-slow~.
|
||||
|
||||
When browsing cards outside of a review,
|
||||
their audio files can be played with ~org-fc-audio-play~.
|
||||
|
||||
* ~org-fc-keymap-hint~
|
||||
Can be enabled with ~(require 'org-fc-keymap-hint)~.
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
:type 'string
|
||||
:group 'org-fc)
|
||||
|
||||
(defvar org-fc-audio-last-file nil)
|
||||
|
||||
(defun org-fc-audio-set-before-setup (file)
|
||||
"Set the befor-setup audio property of the current card to FILE."
|
||||
(interactive "f")
|
||||
|
@ -74,11 +76,19 @@
|
|||
"Play the audio of the current card.
|
||||
Look up the file from PROPERTY. If SPEED is non-nil, play back
|
||||
the file at the given speed."
|
||||
(interactive
|
||||
(list
|
||||
(completing-read
|
||||
"Type: "
|
||||
`(,org-fc-audio-before-setup-property
|
||||
,org-fc-audio-after-setup-property
|
||||
,org-fc-audio-after-flip-property))))
|
||||
(if-let ((file (org-entry-get (point) property)))
|
||||
(org-fc-audio-play-file file (or speed 1.0))))
|
||||
|
||||
(defun org-fc-audio-play-file (file speed)
|
||||
"Play the audio FILE at SPEED."
|
||||
(setq org-fc-audio-last-file file)
|
||||
(start-process-shell-command
|
||||
"org-fc audio"
|
||||
nil
|
||||
|
@ -96,6 +106,17 @@ the file at the given speed."
|
|||
'org-fc-after-flip-hook
|
||||
(lambda () (org-fc-audio-play org-fc-audio-after-flip-property)))
|
||||
|
||||
(defun org-fc-audio-replay ()
|
||||
(interactive)
|
||||
(when org-fc-audio-last-file
|
||||
(org-fc-audio-play-file org-fc-audio-last-file 1.0)))
|
||||
|
||||
(defun org-fc-audio-replay-slow ()
|
||||
(interactive)
|
||||
(when org-fc-audio-last-file
|
||||
(org-fc-audio-play-file org-fc-audio-last-file 0.7)))
|
||||
|
||||
|
||||
;;; Footer
|
||||
|
||||
(provide 'org-fc-audio)
|
||||
|
|
|
@ -87,27 +87,7 @@ ITAGS and LTAGS are strings `\":tag1:tag2:\"'"
|
|||
(org-remove-uninherited-tags (split-string itags ":" t))
|
||||
(split-string ltags ":" t))))
|
||||
|
||||
(defun org-fc-awk-flatten-index (index)
|
||||
"Remove the file-level of INDEX."
|
||||
(mapcan
|
||||
(lambda (file)
|
||||
(mapcar
|
||||
(lambda (card)
|
||||
(plist-put card :path (plist-get file :path))
|
||||
(plist-put card :filetitle (plist-get file :title)))
|
||||
(plist-get file :cards)))
|
||||
index))
|
||||
|
||||
(defun org-fc-awk-index (paths &optional filter)
|
||||
"Find cards in PATHS matching an optional FILTER predicate.
|
||||
FILTER can be either nil or a function taking a single card as
|
||||
its input."
|
||||
(let ((index (org-fc-awk-index-paths paths)))
|
||||
(if filter
|
||||
(cl-remove-if-not filter index)
|
||||
index)))
|
||||
|
||||
(defun org-fc-awk-index-paths (paths)
|
||||
"Generate a list of all cards and positions in PATHS."
|
||||
(let ((output (shell-command-to-string
|
||||
(org-fc-awk--pipe
|
||||
|
@ -117,19 +97,22 @@ FILTER can be either nil or a function taking a single card as
|
|||
"awk/index.awk"
|
||||
:variables (org-fc-awk--indexer-variables)))))))
|
||||
(if (string-prefix-p "(" output)
|
||||
(org-fc-awk-flatten-index
|
||||
(mapcar
|
||||
(lambda (file)
|
||||
(plist-put file :cards
|
||||
(mapcar
|
||||
(lambda (card)
|
||||
(plist-put
|
||||
card :tags
|
||||
(org-fc-awk-combine-tags
|
||||
(plist-get card :inherited-tags)
|
||||
(plist-get card :local-tags))))
|
||||
(plist-get file :cards))))
|
||||
(read output)))
|
||||
(mapcar
|
||||
(lambda (file)
|
||||
(let ((cards
|
||||
(mapcar
|
||||
(lambda (card)
|
||||
(plist-put
|
||||
card :tags
|
||||
(org-fc-awk-combine-tags
|
||||
(plist-get card :inherited-tags)
|
||||
(plist-get card :local-tags))))
|
||||
(plist-get file :cards))))
|
||||
(plist-put file :cards
|
||||
(if filter
|
||||
(cl-remove-if-not filter cards)
|
||||
cards))))
|
||||
(read output))
|
||||
(error "Org-fc shell error: %s" output))))
|
||||
|
||||
(defun org-fc-awk-stats-reviews ()
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
(gethash file hashes)))
|
||||
(hash-table-keys hashes))))
|
||||
;; Update changed files
|
||||
(dolist (new (org-fc-awk-index-files changed))
|
||||
(dolist (new (org-fc-awk-index-paths changed))
|
||||
(let* ((path (plist-get new :path))
|
||||
(hash (gethash path hashes)))
|
||||
(puthash
|
||||
|
@ -83,45 +83,16 @@ as its input."
|
|||
(when (cl-some (lambda (p) (string-prefix-p p path)) paths)
|
||||
;; Use push instead of `nconc' because `nconc' would break
|
||||
;; the entries of the hash table.
|
||||
(if filter
|
||||
(dolist (card (cl-remove-if-not filter (plist-get file :cards)))
|
||||
(push (plist-put
|
||||
(plist-put card :path path)
|
||||
:filetitle
|
||||
(plist-get file :title)) res))
|
||||
(dolist (card (plist-get file :cards))
|
||||
(push
|
||||
(plist-put
|
||||
(plist-put card :path path)
|
||||
:filetitle
|
||||
(plist-get file :title)) res)))))
|
||||
(push
|
||||
(list :path path
|
||||
:cards
|
||||
(if filter
|
||||
(cl-remove-if-not filter (plist-get file :cards))
|
||||
(plist-get file :cards)))
|
||||
res)))
|
||||
org-fc-cache)
|
||||
res))
|
||||
|
||||
;; TODO: Check for awk errors
|
||||
;; TODO: This should go into the awk file
|
||||
(defun org-fc-awk-index-files (files)
|
||||
"Generate a list of all cards and positions in FILES.
|
||||
Unlike `org-fc-awk-index-paths', files are included directly in
|
||||
the AWK command and directories are not supported."
|
||||
(mapcar
|
||||
(lambda (file)
|
||||
(plist-put file :cards
|
||||
(mapcar
|
||||
(lambda (card)
|
||||
(plist-put
|
||||
card :tags
|
||||
(org-fc-awk-combine-tags
|
||||
(plist-get card :inherited-tags)
|
||||
(plist-get card :local-tags))))
|
||||
(plist-get file :cards))))
|
||||
(read
|
||||
(shell-command-to-string
|
||||
(org-fc-awk--command
|
||||
"awk/index.awk"
|
||||
:variables (org-fc-awk--indexer-variables)
|
||||
:input (mapconcat #'identity files " "))))))
|
||||
|
||||
;;; Cache Mode
|
||||
|
||||
(defun org-fc-cache--enable ()
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
(require 'org-element)
|
||||
|
||||
(require 'subr-x)
|
||||
(require 'cl)
|
||||
|
||||
;;; Customization
|
||||
|
||||
|
@ -157,7 +158,8 @@ Does not apply to cloze single and cloze enumeration cards."
|
|||
|
||||
(defun org-fc-show-latex ()
|
||||
"Show latex fragments of heading at point."
|
||||
(org-latex-preview 4))
|
||||
(let ((inhibit-message t))
|
||||
(org-latex-preview 4)))
|
||||
|
||||
(defun org-fc-back-heading-position ()
|
||||
"Return point at the beginning of an entries 'Back' subheading.
|
||||
|
@ -479,10 +481,20 @@ Other useful values are:
|
|||
|
||||
;;;###autoload
|
||||
(defun org-fc-suspend-card ()
|
||||
"Suspend the headline at point if it is a flashcard."
|
||||
"Suspend the headline at point if it is a flashcard.
|
||||
If there is an active review session, all positions of
|
||||
the now suspended card are removed from it."
|
||||
(interactive)
|
||||
(org-fc-with-point-at-entry
|
||||
(org-fc--add-tag org-fc-suspended-tag)))
|
||||
(org-fc--add-tag org-fc-suspended-tag)
|
||||
|
||||
(when org-fc-review--session
|
||||
(let ((id (org-id-get)))
|
||||
(with-slots (cards) org-fc-review--session
|
||||
(setf cards
|
||||
(cl-remove-if
|
||||
(lambda (card)
|
||||
(string= id (plist-get card :id))) cards)))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-fc-suspend-tree ()
|
||||
|
@ -581,7 +593,20 @@ use `(and (type double) (tag \"math\"))'."
|
|||
|
||||
(if filter (setq filter (org-fc--compile-filter filter)))
|
||||
|
||||
(funcall org-fc-index-function paths filter)))
|
||||
(org-fc-index-flatten-file
|
||||
(funcall org-fc-index-function paths filter))))
|
||||
|
||||
(defun org-fc-index-flatten-file (index)
|
||||
"Flatten INDEX into a list of cards.
|
||||
Relevant data from the file is included in each card element."
|
||||
(mapcan
|
||||
(lambda (file)
|
||||
(mapcar
|
||||
(lambda (card)
|
||||
(plist-put card :path (plist-get file :path))
|
||||
(plist-put card :filetitle (plist-get file :title)))
|
||||
(plist-get file :cards)))
|
||||
index))
|
||||
|
||||
(defun org-fc-index-flatten-card (card)
|
||||
"Flatten CARD into a list of positions.
|
||||
|
@ -651,6 +676,8 @@ Positions are shuffled in a way that preserves the order of the
|
|||
(interactive)
|
||||
(let ((path (expand-file-name "demo.org" org-fc-source-path)))
|
||||
(with-current-buffer (find-file path)
|
||||
;; Prevent any changes to the demo file
|
||||
(read-only-mode 1)
|
||||
(org-fc-review-buffer))))
|
||||
|
||||
;;; Contexts
|
||||
|
@ -671,14 +698,26 @@ Positions are shuffled in a way that preserves the order of the
|
|||
org-fc-custom-contexts))
|
||||
|
||||
(defun org-fc-select-context ()
|
||||
"Select a review context."
|
||||
(let ((context (completing-read
|
||||
"Select a review context.
|
||||
The `buffer' context is resolved to the filename of the current
|
||||
buffer immediately."
|
||||
(let* ((choice (completing-read
|
||||
"Context: "
|
||||
(mapcar (lambda (c) (car c)) (org-fc-contexts))
|
||||
nil
|
||||
:require-match)))
|
||||
(unless (string= context "")
|
||||
(alist-get (intern context) (org-fc-contexts)))))
|
||||
:require-match))
|
||||
(context
|
||||
;; If the result is empty, the user quit the prompt
|
||||
(unless (string= choice "")
|
||||
(alist-get (intern choice) (org-fc-contexts)))))
|
||||
;; Resolve the `buffer' immediately so it will be valid even when
|
||||
;; using the selected context from within another buffer,
|
||||
;; e.g. when starting a review from the dashboard.
|
||||
(if (eq (plist-get context :paths) 'buffer)
|
||||
(list
|
||||
:paths (buffer-file-name)
|
||||
:filter (plist-get context :filter))
|
||||
context)))
|
||||
|
||||
;;; Footer
|
||||
|
||||
|
|
|
@ -268,13 +268,6 @@ same ID as the current card in the session."
|
|||
"Suspend card and proceed to next."
|
||||
(interactive)
|
||||
(org-fc-suspend-card)
|
||||
;; Remove all other positions from review session
|
||||
(with-slots (current-item cards) org-fc-review--session
|
||||
(let ((id (plist-get current-item :id)))
|
||||
(setf cards
|
||||
(cl-remove-if
|
||||
(lambda (card)
|
||||
(string= id (plist-get card :id))) cards))))
|
||||
(org-fc-review-reset)
|
||||
(org-fc-review-next-card))
|
||||
|
||||
|
@ -395,7 +388,7 @@ END is the start of the line with :END: on it."
|
|||
"Set the cards review data to DATA."
|
||||
(save-excursion
|
||||
(let ((position (org-fc-review-data-position 'create)))
|
||||
(kill-region (car position) (cdr position))
|
||||
(delete-region (car position) (cdr position))
|
||||
(goto-char (car position))
|
||||
(insert "| position | ease | box | interval | due |\n")
|
||||
(insert "|-|-|-|-|-|\n")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#+title: File Title
|
||||
#+filetags: :tag1:tag2:
|
||||
#+title: File Title Lowercase
|
||||
#+filetags: :tag3:tag4:
|
||||
|
||||
* Card :fc:
|
||||
:PROPERTIES:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#+TITLE: File Title
|
||||
#+TITLE: File Title Uppercase
|
||||
#+FILETAGS: :tag1:tag2:
|
||||
|
||||
* Card :fc:
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
index))
|
||||
|
||||
(ert-deftest org-fc-filter-test ()
|
||||
(let* ((index (org-fc-awk-index-paths (list (org-fc-test-fixture "filter/")))))
|
||||
(let* ((index
|
||||
(org-fc-index-flatten-file
|
||||
(org-fc-awk-index (list (org-fc-test-fixture "filter/"))))))
|
||||
;; Index of all cards
|
||||
(should (org-fc-test-compare-ids
|
||||
index
|
||||
|
|
|
@ -3,73 +3,43 @@
|
|||
(require 'ert)
|
||||
|
||||
(ert-deftest org-fc-test-index-malformed ()
|
||||
(should (null (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "malformed/no_review_data.org")))))
|
||||
(should (null (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "malformed/no_properties.org")))))
|
||||
(should (null (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "malformed/normal_swapped_drawers.org")))))
|
||||
(should (null (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "malformed/unclosed_drawer1.org")))))
|
||||
(should (null (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "malformed/unclosed_drawer2.org"))))))
|
||||
(let ((files
|
||||
'("malformed/no_review_data.org"
|
||||
"malformed/no_properties.org"
|
||||
"malformed/normal_swapped_drawers.org"
|
||||
"malformed/unclosed_drawer1.org"
|
||||
"malformed/unclosed_drawer2.org")))
|
||||
(dolist (file files)
|
||||
(org-fc-test-check-structure
|
||||
'((:cards ()))
|
||||
(org-fc-awk-index (list (org-fc-test-fixture file)))))))
|
||||
|
||||
(ert-deftest org-fc-test-escaping ()
|
||||
(let ((index (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "escaping/spaces in filename.org")))))
|
||||
(should (eq (length index) 1))
|
||||
(should
|
||||
(equal (plist-get (car index) :id)
|
||||
"33645f3a-384d-44ed-aed2-a2d56b973800"))))
|
||||
(org-fc-test-check-structure
|
||||
'((:cards ((:id "33645f3a-384d-44ed-aed2-a2d56b973800"))))
|
||||
(org-fc-awk-index
|
||||
(list (org-fc-test-fixture "escaping/spaces in filename.org")))))
|
||||
|
||||
(ert-deftest org-fc-test-index-keywords ()
|
||||
(let ((index (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "index/uppercase.org")))))
|
||||
(should (eq (length index) 1))
|
||||
(let ((card (car index)))
|
||||
(should (equal (plist-get card :inherited-tags) ":tag1:tag2:"))
|
||||
(should (equal (plist-get card :filetitle) "File Title"))))
|
||||
(let ((index (org-fc-awk-index-paths
|
||||
(list (org-fc-test-fixture "index/lowercase.org")))))
|
||||
(should (eq (length index) 1))
|
||||
(let ((card (car index)))
|
||||
(should (equal (plist-get card :inherited-tags) ":tag1:tag2:"))
|
||||
(should (equal (plist-get card :filetitle) "File Title")))))
|
||||
(org-fc-test-check-structure
|
||||
'((:title "File Title Uppercase"
|
||||
:cards ((:inherited-tags ":tag1:tag2:")))
|
||||
(:title "File Title Lowercase"
|
||||
:cards ((:inherited-tags ":tag3:tag4:"))))
|
||||
(org-fc-awk-index
|
||||
(list (org-fc-test-fixture "index/uppercase.org")
|
||||
(org-fc-test-fixture "index/lowercase.org")))))
|
||||
|
||||
(ert-deftest org-fc-test-index ()
|
||||
(let ((index (org-fc-awk-index-paths
|
||||
(list
|
||||
(org-fc-test-fixture "index/test.org")))))
|
||||
(should (eq (length index) 3))
|
||||
(let ((card1 (car index))
|
||||
(card2 (cadr index))
|
||||
(card3 (caddr index)))
|
||||
(should
|
||||
(equal (plist-get card1 :id)
|
||||
"edee8940-5c9a-4c70-b1c4-f45c194c0c97"))
|
||||
(should
|
||||
(equal (plist-get card1 :local-tags)
|
||||
":fc:tag1:"))
|
||||
(should
|
||||
(equal (plist-get card1 :title)
|
||||
"Headline"))
|
||||
|
||||
(should
|
||||
(equal (plist-get card2 :id)
|
||||
"59b3b102-aebd-44ba-a1fd-6dc912c34fcf"))
|
||||
(should
|
||||
(equal (plist-get card2 :local-tags)
|
||||
":fc:tag2:"))
|
||||
(should
|
||||
(equal (plist-get card2 :title)
|
||||
"Headline 2"))
|
||||
|
||||
(should
|
||||
(equal (plist-get card3 :id)
|
||||
"a7ed2686-73e6-4780-825d-78cf4b2e5374"))
|
||||
(should
|
||||
(equal (plist-get card3 :local-tags)
|
||||
":fc:tag3:"))
|
||||
(should
|
||||
(equal (plist-get card3 :title)
|
||||
"Headline 3:not_a_tag:")))))
|
||||
(org-fc-test-check-structure
|
||||
'((:cards
|
||||
((:id "edee8940-5c9a-4c70-b1c4-f45c194c0c97"
|
||||
:local-tags ":fc:tag1:"
|
||||
:title "Headline")
|
||||
(:id "59b3b102-aebd-44ba-a1fd-6dc912c34fcf"
|
||||
:local-tags ":fc:tag2:"
|
||||
:title "Headline 2")
|
||||
(:id "a7ed2686-73e6-4780-825d-78cf4b2e5374"
|
||||
:local-tags ":fc:tag3:"
|
||||
:title "Headline 3:not_a_tag:"))))
|
||||
(org-fc-awk-index (list (org-fc-test-fixture "index/test.org")))))
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
(require 'ert)
|
||||
|
||||
(ert-deftest org-fc-test-review-data ()
|
||||
(let ((index (org-fc-awk-index-paths
|
||||
(list
|
||||
(org-fc-test-fixture "index/review_data.org")))))
|
||||
(let ((index
|
||||
(org-fc-index-flatten-file
|
||||
(org-fc-awk-index
|
||||
(list
|
||||
(org-fc-test-fixture "index/review_data.org"))))))
|
||||
(should (eq (length index) 2))
|
||||
(let ((card1 (car index))
|
||||
(card2 (cadr index)))
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
(require 'cl)
|
||||
|
||||
(defun org-fc-test-fixture (name)
|
||||
"Return the full path of fixture file NAME."
|
||||
(expand-file-name
|
||||
|
@ -10,4 +12,28 @@
|
|||
(lambda (card) (plist-get card :id))
|
||||
index))
|
||||
|
||||
(defun org-fc-test-check-structure (expected got)
|
||||
"Check structural equality of parts of larger objects.
|
||||
For plists, values of all keys in EXPECTED are compared,
|
||||
lists are compared element-by-element,
|
||||
everything else is checked for equality."
|
||||
(cond
|
||||
;; plist
|
||||
((and (listp expected)
|
||||
expected
|
||||
(symbolp (car expected)))
|
||||
(let ((keys
|
||||
(cl-loop for key in expected by #'cddr collecting key)))
|
||||
(dolist (key keys)
|
||||
(org-fc-test-check-structure
|
||||
(plist-get expected key)
|
||||
(plist-get got key)))))
|
||||
;; Normal list
|
||||
((listp expected)
|
||||
(should (eq (length expected) (length got)))
|
||||
(cl-loop for e in expected for g in got do
|
||||
(org-fc-test-check-structure e g)))
|
||||
;; Anything else
|
||||
(t (should (equal expected got)))))
|
||||
|
||||
(provide 'org-fc-test-helper)
|
||||
|
|
Loading…
Reference in a new issue