1
0
Fork 0

Compare commits

...

10 commits

Author SHA1 Message Date
Bram Schoenmakers 9d61808af0 Don't kill the region but delete it
This operation fills the kill-ring uncessearily.
2023-10-01 18:12:15 +02:00
Leon Rische 7ab1791dfa Remove suspended cards from review session 2023-05-14 13:41:04 +02:00
Leon Rische 0d4ac6eb77 Fix tests 2023-05-14 13:22:28 +02:00
Leon Rische 364172d7fb Inhibit "Creating LaTeX previews ..." message during review 2023-05-14 13:11:04 +02:00
Leon Rische 37d2aabcbb Set buffer to read-only in org-fc-demo 2023-05-14 12:56:47 +02:00
Leon Rische 1ce66c21dc Resolve 'buffer context immediately on selection 2023-05-14 12:43:28 +02:00
Leon Rische 073bfef995 Update and refactor indexer tests 2022-11-30 13:19:24 +01:00
Leon Rische 44876305b3 Change indexing functions to group cards by files 2022-11-30 12:45:24 +01:00
Leon Rische 973a16a956 Make audio play command interactive 2022-09-27 20:31:27 +02:00
Leon Rische e31fada906 Add audio replay commands 2022-09-27 20:30:04 +02:00
12 changed files with 173 additions and 158 deletions

View file

@ -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)~.

View file

@ -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)

View file

@ -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 ()

View file

@ -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 ()

View file

@ -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

View file

@ -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")

View file

@ -1,5 +1,5 @@
#+title: File Title
#+filetags: :tag1:tag2:
#+title: File Title Lowercase
#+filetags: :tag3:tag4:
* Card :fc:
:PROPERTIES:

View file

@ -1,4 +1,4 @@
#+TITLE: File Title
#+TITLE: File Title Uppercase
#+FILETAGS: :tag1:tag2:
* Card :fc:

View file

@ -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

View file

@ -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")))))

View file

@ -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)))

View file

@ -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)