In part 2 I talked about how I used Hyrdas to quickly navigate through elfeed tags. It was a nice step up but the fact that I still had to manually edit my configuration code for every new tag to update the hydra was a problem.
Basically, I had to somehow or other, take a list of all the active tags and with it build a defhydra command that will then make my Hydra.
Fortunately, emacs, being a lisp, has macros. I'm not talking about keyboard macros which I talked about in episode 15 but rather Lisp style macros. Macros let you transform code and then execute the transformed code.
The example I give in the video:
(defmacro infix (a op b) `(,op ,a ,b)) (infix 3 + 8) ; evaluates to 11
This transforms the 3+8 into (+ 3 8) and then evaluates it to be 11.
We can use this idea with our Hydra.
We can use the call elfeed-db-get-all-tags to get a list of all the tags in our database. I decided that if I had an uppercase letter in the tag, I'd use the lowercase version of that letter as my "hotkey" and if it didn't, I'd just use the first letter.
So, given a tag list of:
(active blogs cs eDucation emacs local misc sports star tech unread webcomics)
I'd want a "hotkey" of b for logs and d for eDucation.
The routine z/hasCap tests to see if a tag has a capital letter in it and z/get-hydra-option-key returns the final "hotkey:"
(defun z/hasCap (s) "" (let ((case-fold-search nil)) (string-match-p "[[:upper:]]" s) )) (defun z/get-hydra-option-key (s) "returns single upper case letter (converted to lower) or first" (interactive) (let ( (loc (z/hasCap s))) (if loc (downcase (substring s loc (+ loc 1))) (substring s 0 1) )))
mz/make-elfeed-cats takes a list of tags and returns a list of items where each item is in the form expected by the hydra definition:
("t" (elfeed-search-set-filter "@6-months-ago +tagname") "tagname")
(defun mz/make-elfeed-cats (tags) "Returns a list of lists. Each one is line for the hydra configuratio in the form (c function hint)" (interactive) (mapcar (lambda (tag) (let* ( (tagstring (symbol-name tag)) (c (z/get-hydra-option-key tagstring)) ) (list c (append '(elfeed-search-set-filter) (list (format "@6-months-ago +%s" tagstring) ))tagstring ))) tags))
Finally, here's our macro:
(defmacro mz/make-elfeed-hydra () `(defhydra mz/hydra-elfeed () "filter" ,@(mz/make-elfeed-cats (elfeed-db-get-all-tags)) ("*" (elfeed-search-set-filter "@6-months-ago +star") "Starred") ("M" elfeed-toggle-star "Mark") ("A" (elfeed-search-set-filter "@6-months-ago") "All") ("T" (elfeed-search-set-filter "@1-day-ago") "Today") ("Q" bjm/elfeed-save-db-and-bury "Quit Elfeed" :color blue) ("q" nil "quit" :color blue) ))
The line that starts with *,@* calls the routine that builds lines of code for all the tags in the database and the macro leaves me with the defhydra I need.
I then redefine the hydra every time I need it, just in case tags changed:
(defun mz/make-and-run-elfeed-hydra () "" (interactive) (mz/make-elfeed-hydra) (mz/hydra-elfeed/body))
and bind mz/make-and-run-elfeed-hydra to j and J in my elfeed keymap (this code goes in the bind section of my use-package elfeed section):
("j" . mz/make-and-run-elfeed-hydra) ("J" . mz/make-and-run-elfeed-hydra)
As long as I remember to name my tags in a way that they don't conflict with one another I can quickly navigate all around elfeed.
Here's the video:
<iframe width="560" height="315" src="https://www.youtube.com/embed/5zuSUbAHH8c" frameborder="0" allowfullscreen></iframe>
- Video series overview page: