Elfeed Tips and Tricks
This past weekend I had some questions from next-user-here (NUH) on my original Elfeed post about changing some of Elfeed’s behavior. NUH is an Elisp novice so accomplishing some of the requested modifications wasn’t obvious. A novice is mostly limited to setting variables, not defining advice or using hooks. I’ve also been using Elfeed daily for about three months now as my sole web feed reader and along the way I’ve developed some best practices. In addition to responding to some of NIH’s questions here, I’d like to share some tips and tricks.
Custom Entry Launchers
Currently you can press “b” to launch one or more entries in your browser. You can use “y” to copy an single entry to the clipboard. What if you want to make another action.
In my configuration I have a fancy binding that sends the entry URLs
in the selected region to youtube-dl for downloading the
videos. It’s too large to share as a snippet so here’s a small example
of something similar using a program called
(defun xcowsay (message) (call-process "xcowsay" nil nil nil message)) (defun elfeed-xcowsay () (interactive) (let ((entry (elfeed-search-selected :single))) (xcowsay (elfeed-entry-title entry)))) (define-key elfeed-search-mode-map "x" #'elfeed-xcowsay)
Now when I hit “x” over an entry in Elfeed I’m greeted by a cow announcing the title.
Entry Listing Customization
The search buffer you see when starting Elfeed, where entries are
listed, can be customized a few different ways. First, this buffer
does grow dynamically. After re-sizing the window/frame horizontally
you just have to refresh the view by pressing
g (an Emacs
convention). How it fills out depends on the settings of these
They control how wide the different columns should be as the window
size changes. An important caveat to this is that the cache stored in
elfeed-search-cache must be cleared before the changes will be
reflected in the display. This cache exists because building the
display, assembling all the special faces, is actually quite
CPU-intensive. It was an optimization I established early on.
If you set these variables in your start-up configuration you don’t need to worry about clearing the cache because it will already be empty. It’s only a concern when playing with the settings.
Another question was about adding time to the entry listing. Elfeed
only displays the entry’s date. Dates are formatted by the function
elfeed-search-format-date. This can be redefined to display dates
(defun elfeed-search-format-date (date) (format-time-string "%Y-%m-%d %H:%M" (seconds-to-time date)))
It’s given epoch seconds as a float and it returns a string to display as a date.
Faces and Colors
All of the faces used in the display are declared for customization, so these can be changed to whatever you like.
Say you suffered a head injury and decided you want your Elfeed dates to be bold, purple, and underlined,
(custom-set-faces '(elfeed-search-date-face ((t :foreground "#f0f" :weight extra-bold :underline t))))
Feeds and entries in the database can be manipulated to become whatever you want them to be. Because Elfeed is regularly modifying the database, the trick is to perform the manipulation at just the right time.
Feed Title Changes
Say you want to change a feed title because you don’t like the title
supplied by the feed. For example, the title to my blog’s feed is
“null program” but instead you think it should be “Seriously Handsome
Programmer” (head injury, remember?). The function
elfeed-db-get-feed can be used to fetch a feed’s data structure from
the database, given it’s exact URL as listed in your
(let ((feed (elfeed-db-get-feed "http://nullprogram.com/feed/"))) (setf (elfeed-feed-title feed) "Seriously Handsome Programmer"))
Hold it, that didn’t work. First, that display cache is getting in the way again. Feed titles change very infrequently so they’re cached aggressively. More importantly, next time you update your feeds Elfeed will re-synchronize the feed title with the official title. It’s going to fight against your intervention.
The solution is to do it with a little bit of advice just before the
title is displayed. Advise the function
some “before” advice.
(defadvice elfeed-search-update (before nullprogram activate) (let ((feed (elfeed-db-get-feed "http://nullprogram.com/feed/"))) (setf (elfeed-feed-title feed) "Seriously Handsome Programmer")))
Automatic entry modification should happen immediately upon discovery
so that it looks like the entry arrived that way. This is done through
elfeed-new-entry-hook. Generally this would be used for applying
custom tags. These examples are from the documentation:
;; Mark all YouTube entries (add-hook 'elfeed-new-entry-hook (elfeed-make-tagger :feed-url "youtube\\.com" :add '(video youtube))) ;; Entries older than 2 weeks are marked as read (add-hook 'elfeed-new-entry-hook (elfeed-make-tagger :before "2 weeks ago" :remove 'unread)) ;; Building subset feeds (add-hook 'elfeed-new-entry-hook (elfeed-make-tagger :feed-url "example\\.com" :entry-title '(not "something interesting") :add 'junk :remove 'unread))
Due to a feature I recently ported from my personal configuration,
this tagger helper function is less necessary. You can put lists in
elfeed-feeds list to supply automatic tags.
(setq elfeed-feeds '(("http://nullprogram.com/feed/" blog emacs) "http://www.50ply.com/atom.xml" ; no autotagging ("http://nedroid.com/feed/" webcomic)))
Going beyond tagging you could change the content of the feed. Say you want to make feeds 100 times better.
(defun hundred-times-better (entry) (let* ((original (elfeed-deref (elfeed-entry-content entry))) (replace (replace-regexp-in-string "keyboard" "leopard" original))) (setf (elfeed-entry-content entry) (elfeed-ref replace)))) (add-hook 'elfeed-new-entry-hook #'hundred-times-better)
The same trick could be used to remove advertising, change the date,
change the title, etc. The
elfeed-ref parts are
needed to fetch and store content in the content database. Only a
reference is stored on the structure. You can actually use these
functions at any time outside of Elfeed, but they’ll eventually get
garbage collected if Elfeed doesn’t know about them.
(setf ref (elfeed-ref "Hello, World")) ;; => [cl-struct-elfeed-ref "907d14fb3af2b0d4f18c2d46abe8aedce17367bd"] (elfeed-deref ref) ;; => "Hello, World"
A question that’s been asked few times is if entries can be deleted. To start off, the answer to that question is “no.” There is no function provided to remove entries from the database. If you want to remove entries you’re probably taking the wrong approach.
The main problem with removal is that Elfeed needs to keep track of what it’s seen before. If an entry is removed and then rediscovered, it will reappear as unread. There are better ways to “remove” entries, such as tagging them specially.
On a moderately-powerful computer Elfeed can easily handle at least several tens of thousands of database entries. If “too many entries” ever becomes a performance problem I’d rather solve it by making the database faster than by removing information from the database. It’s already very date-oriented so that older entries are infrequently touched.
If storage is a concern, you shouldn’t get too worked up about that.
As of this post I have about 6,000 entries in my database and the
index file is only 3.5 MB. The content database after garbage
collection, which is the
data/ directory under
these 6k entries is 17MB. When I run
currently an experimental feature, it drops down to 1.8MB. That’s less
than 1 kB per entry. It’s also less than my personal Liferea database
of roughly the same amount of content (~15MB) before I wrote Elfeed.
If even this storage is still too much you can always blow away your
data/ content database directory. This is safe to do even while
Emacs is running. You’ll still see all of the entries listed in the
search buffer but won’t be able to read them within Emacs until after
the next database update (when it re-fetches the most recent entry
You can also clear out the content database from within Elisp by visiting every entry and clearing its content field.
(with-elfeed-db-visit (entry _) (setf (elfeed-entry-content entry) nil)) (elfeed-db-gc) ;; garbage collect everything
The same sort of expression can be used to run over all known entries to perform other changes. If there was a delete function you might use it here to remove entries older than a certain date, then hope they’re not rediscovered.
If you never want to store entry content (you never read entries within Emacs), you can use a hook to always drop it on the floor as it arrives,
(add-hook 'elfeed-new-entry-hook (lambda (entry) (setf (elfeed-entry-content entry) nil)))
If you have any questions or suggestions about how to make Elfeed do what you want it to do, feel free to ask. Some things may actually require that I make changes to Elfeed to support it, though I hope I’ve anticipated your particular need well enough to avoid that.