Power up Anki with Emacs, Org mode, anki-editor and more

It’s been a while since the last post, one reason is that for the past few months I’ve been drawn to Anki, the spaced-repetition flashcard program. If you haven’t checked it out yet, I strongly recommended it. (Why? Read Michael Nielson’s article)

One question recurred among Anki community is how to generate cards quickly. 3 approaches are common:

  1. Use Anki’s “Import” function to read csv/tsv
  2. Write Python scripts with genanki that generate .apkg for import
  3. Use third-party browser plugin that supports AnkiConnect (e.g. yomichan)

While these are viable and oftentimes powerful options, for users they usually don’t come as flexible for customization (change matching of columns & fields, ad-hoc editing, formula/highlight/table/etc support). Programming experience is yet another entry barrier for many people.

In this post I’ll try to demonstrate some workflows of creating cards in Emacs based on anki-editor, a package that connects Emacs’s Org-mode and AnkiConnect. Combining anki-editor’s connectivity, Emacs’s customizability, and Org-mode’s rich formatting and export support, a few card generation scenarios can be handled with ease:

  • Utilize a powerful editor for card editing
  • Capture ideas with least interruption as you encounter interesting content
  • Customize shortcut keys and workflow, minimize mouse pointing/clicking
  • etc…

Note for Anki users who hear of Org-mode for the first time: Take a look at John Kitchin - org mode is awesome to have a grasp of Org’s capability. (Find a pencil and mark down the date, it may become commemorable later :)). If you already knew that Org can do outline-structured editing, has tagging system and rich export support, you should be able to follow the post fine.

Note for setup: Most setup is done in Emacs configuration language, Emacs Lisp, under Linux. I will point out where I think is relevant to explain the details, and provide download at the end of article. But as always, YMMV in other platforms. If you are not sure how to configure, comment below, take a look at my init.el, or join the friendly r/emacs, r/orgmode and raise your hand.

Emacs/Org-mode as editor to Anki

An Anki note is represented as an Org entry with property ANKI_NOTE_TYPE. Each subheading of the entry corresponds to a field in Anki. Tags can be synchronized to Anki. anki-editor uses property value ANKI_NOTE_ID to synchronized an Org entry and corresponding Anki note.

Upon pushing to Anki, anki-editor will export Org tree as Anki-compatible HTML. However, without a proper CSS file it will still look plain. So we replace it with gongzhitaao/orgcss, a simple stylesheet for Org-exported HTML, to obtain a proper styling for source code blocks, lists, tables, etc. (Download at the end of article)

Apart from “Basic”, another commonly-used note type is Cloze Deletion.

(use-package anki-editor
  :after org
  :bind (:map org-mode-map
              ("<f12>" . anki-editor-cloze-region-auto-incr)
              ("<f11>" . anki-editor-cloze-region-dont-incr)
              ("<f10>" . anki-editor-reset-cloze-number)
              ("<f9>"  . anki-editor-push-tree))
  :hook (org-capture-after-finalize . anki-editor-reset-cloze-number) ; Reset cloze-number after each capture.
  :config
  (setq anki-editor-create-decks t ;; Allow anki-editor to create a new deck if it doesn't exist
        anki-editor-org-tags-as-anki-tags t)

  (defun anki-editor-cloze-region-auto-incr (&optional arg)
    "Cloze region without hint and increase card number."
    (interactive)
    (anki-editor-cloze-region my-anki-editor-cloze-number "")
    (setq my-anki-editor-cloze-number (1+ my-anki-editor-cloze-number))
    (forward-sexp))
  (defun anki-editor-cloze-region-dont-incr (&optional arg)
    "Cloze region without hint using the previous card number."
    (interactive)
    (anki-editor-cloze-region (1- my-anki-editor-cloze-number) "")
    (forward-sexp))
  (defun anki-editor-reset-cloze-number (&optional arg)
    "Reset cloze number to ARG or 1"
    (interactive)
    (setq my-anki-editor-cloze-number (or arg 1)))
  (defun anki-editor-push-tree ()
    "Push all notes under a tree."
    (interactive)
    (anki-editor-push-notes '(4))
    (anki-editor-reset-cloze-number))
  ;; Initialize
  (anki-editor-reset-cloze-number)
  )
  • By default anki-editor-cloze-{dwim,region} always asks for hints and requires card number input. I don’t use hints much, and usually want card number to increase, so two helper functions anki-editor-cloze-region-{auto-incr,dont-incr} are written to skip these behaviors. (Note: Such kind of customizations are ubiquitous in Emacs community, where users don’t have to wait for upstream to implement a desired new feature. This is quite different from Anki community where version updates frequently break existing add-ons, leaving end-users hands tied, or new features being delayed due to technical difficulty in understanding the code base.)
  • A function is added to org-capture-after-finalize-hook to reset cloze number to 1 after each capture
  • By default anki-editor-push-notes will push the whole file. This is slow when the file contain old entries that didn’t really need to change. In my workflow, I keep all pending notes under Dispatch Shelf subtree, and push that whole subtree (with <f9>) when I feel ready. Once they’re pushed, I would refile/relocate them under Exported subtree. anki-editor-push-tree is added for this purpose.
  • Assign handy keybindings (<f9>-<f12> in this case) to your liking.

Org-capture: Swiftly create cards

Now that we know what a proper Anki note should look like in Org-mode, we can define a template and use org-capture to create cards swiftly as we came across different materials on web.

;; Org-capture templates
(setq org-my-anki-file "/path/to/your/anki.org")
(add-to-list 'org-capture-templates
             '("a" "Anki basic"
               entry
               (file+headline org-my-anki-file "Dispatch Shelf")
               "* %<%H:%M>   %^g\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Basic\n:ANKI_DECK: Mega\n:END:\n** Front\n%?\n** Back\n%x\n"))
(add-to-list 'org-capture-templates
             '("A" "Anki cloze"
               entry
               (file+headline org-my-anki-file "Dispatch Shelf")
               "* %<%H:%M>   %^g\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Cloze\n:ANKI_DECK: Mega\n:END:\n** Text\n%x\n** Extra\n"))

;; Allow Emacs to access content from clipboard.
(setq x-select-enable-clipboard t
      x-select-enable-primary t)
  • What’s org-capture?
  • Note the %x in org-capture-templates: this means we want to fill in content of X clipboard upon capture. For Cloze note, this would be in Text field. For Basic note, I usually like to put them in Back, and come up with a good question for Front field.
  • The key to be as lazy as possible is to let Emacs not only read explicitly copied/paste content (via C-c / C-v, the CLIPBOARD selection), but also the currently selected text (the PRIMARY selection). That way, after highlighting text with mouse I can immediately call org-capture (C-c c) in Emacs. See Clipboard - ArchWiki for details.
  • Header name does not really matter in anki-editor, %H:%M is an arbitrary choice
  • I put most notes in a Mega deck following Michael Nielson’s advice (Search “Use one big deck”). It served me well. If you have many decks/note types, you may want to create multiple capture templates, or write some elisp functions to reduce typing.

Enabling X clipboard support makes Emacs versatile for textual input from any other applications. For me it’s mainly web articles from Firefox, and sometimes books in PDF.js … Or, do I really need PDF.js when I can read books in pdf-tools, the best PDF reader in Emacs?

Emacs command fill-paragraph (M-q) comes handy for aligning irregular-shaped text copied from PDF.

org-download + Flameshot: Add image too

Lastly, it’s also good to use screen crop as note content. Thankfully anki-editor also supports that, we only need to find a convenient way to download image in Emacs.

There are many packages for adding images to Emacs, as well as for screenshots. I chose the combination of abo-abo/org-download and Flameshot. While Flameshot allows me to add preliminary annotation, it’s a little tricky to get it working properly. Curious minds may search “flameshot” in my config for details.

Integrate with X Window Manager

To start org-capture frame in whichever application I’m using, I took a little elisp function here:

(defun make-orgcapture-frame ()
    "Create a new frame and run org-capture."
    (interactive)
    (make-frame '((name . "org-capture") (window-system . x)))
    (select-frame-by-name "org-capture")
    (counsel-org-capture)
    (delete-other-windows)
    )

Then assign a keybinding in X Window Manager (xmonad in my case) that runs emacsclient -e '(make-orgcapture-frame)'. Now I may create cards with a key combo anywhere in my system.

Conclusion

This setup has served me well in the past few months. During daytime, as I was writing code in Emacs or browsing web articles, I may capture code snippets or interesting information without switching to Anki Desktop to do much clicking. At nighttime I would polish the notes (fixing typo, adding context/images), push them in one go and call it a day.

The possibilities don’t end there. Given Org’s capabilities, I’m sure there are more interesting applications. Here are some I have in mind:

  • Org Babel to evaluate code block in place, pushing both code and output (plain text, images, tables) to Anki: ob-lilypond, ob-dot, ob-R
  • Use keyboard macro to create cards in bulk, quickly
  • An Incremental Reading system may be built from utilities that centered on plain/PDF/EPUB(nov.el) text processing, and that integrate with org-noter (note taking with pdf-tools), org-ref (academic paper management), org-capture (content capture), org-agenda (schedule/deadline reminder, progress tracking)…
  • An Emacs distribution that is solely designed to smooth out learning curve for users from Anki community, just as what Spacemacs is for Vimmers, encompassing above-mentioned features with reasonable defaults
  • etc…

Hope it’s useful for users from either Anki or Emacs, and I’m sure there will be bold, imaginative design that stemmed from the intersection of both. Until next time, happy hacking, learning and living under the sun!

Download files used in this post: