Custom Emacs functions No. 6 - Elfeed

Intro

I spend quite a lot of time of my day in Emacs, which includes reading articles, watching videos, listening to podcast and more from all sort of sources.

These things get aggregated into Elfeed. I have got a particular way of navigating and consuming the content, which is why I had to use some custom functions. Some are stolen and bent to my liking, some are straight forward and some are just plain ugly. All do the job however.

The functions

timu-elfeed-load-db-and-open

I always wand to open Elfeed in a new tab. This function helps me do that.

(defun timu-elfeed-load-db-and-open ()
  "Wrapper to load the `elfeed' db from disk before opening.
`elfeed' will be opened in a new tab with `tab-bar-new-tab'."
  (interactive)
  (tab-bar-new-tab)
  (elfeed-db-load)
  (elfeed)
  (elfeed-update))

The function also loads the elfeed-db from disk beforehand. Granted, I haven’t checked to see whether elfeed does this now. But as of the initial setup it did not… Might check at a later time.

timu-elfeed-search-other-window

Do you now how modern reeders or email clients have a listing in a column to the left and the content to the right? Yeah, this function helps me achieve this, when hitting return on an item in the *elfeed-search* buffer.

(defun timu-elfeed-search-other-window ()
  "Browse `elfeed' entry in the other window.
Credit: https://protesilaos.com/dotemacs"
  (interactive)
  (let* ((entry (if (eq major-mode 'elfeed-show-mode)
                    elfeed-show-entry
                  (elfeed-search-selected :ignore-region)))
         (link (elfeed-entry-link entry))
         (win (selected-window)))
    (with-current-buffer (get-buffer "*elfeed-search*")
      (unless (one-window-p)              ; experimental
        (delete-other-windows win))
      (split-window-right)
      (other-window 1)
      (evil-window-increase-width 10)
      (elfeed-search-show-entry entry))))

timu-elfeed-kill-buffer-and-window

I am pretty sure I did not write this one myself. It is bound to q in any elfeed mode and does what is appropriate for each mode. The docstring of the function does describe it well actually.

(defun timu-elfeed-kill-buffer-and-window ()
  "Do-what-I-mean way to handle `elfeed' windows and buffers.
When in an entry buffer, kill the buffer and return to the Search view.
If the entry is in its own window, delete it as well.
When in the search view, close all other windows, else kill the buffer."
  (interactive)
  (let ((win (selected-window)))
    (cond ((eq major-mode 'elfeed-show-mode)
           (elfeed-kill-buffer)
           (unless (one-window-p) (delete-window win))
           (switch-to-buffer "*elfeed-search*"))
          ((eq major-mode 'elfeed-search-mode)
           (if (one-window-p)
               (progn
                 (elfeed-search-quit-window)
                 (kill-buffer "*elfeed-search*")
                 (kill-buffer "*elfeed-log*")
                 (kill-buffer "elfeed-list.org")
                 (tab-bar-close-tab))
             (delete-other-windows win))))))

timu-elfeed-filter-include-tag & timu-elfeed-filter-exclude-tag

These two functions help me narrow the elfeed-search-list to a tag. These use completing-read to select or deselect a tag to be included in the listed articles/entries.

(defun timu-elfeed-filter-include-tag ()
  "Use `completing-read' to select tags to include `+'.
The function reads the tags from the `elfeed' db."
  (interactive)
  (let ((filtered-tag (completing-read "Select Tags: " (elfeed-db-get-all-tags))))
    (progn
      (setq elfeed-search-filter (concat elfeed-search-filter " +" filtered-tag))
      (elfeed-search-update--force))))

(defun timu-elfeed-filter-exclude-tag ()
  "Use `completing-read' to select tags to exclude `-'.
The function reads the tags from the `elfeed' db."
  (interactive)
  (let ((filtered-tag (completing-read "Select Tags: " (elfeed-db-get-all-tags))))
    (progn
      (setq elfeed-search-filter (concat elfeed-search-filter " -" filtered-tag))
      (elfeed-search-update--force))))

Capturing entries

News feeds are usually quite ephemeral. I reed them if they are of any use for me or skip them. However some articles hide gems that I either want to act on at a later time or archive for posterity.

I those rare cases I capture these to my gtd files as todos with the url to the original sources. I use the following functions and commands that I cobbled together from different sources. The docstrings should be descriptive enough.

Helper function to get title & url

;; TODO: I realize that this does not need to be `interactive'.
(defun timu-elfeed-link-title (entry)
  "Copy the ENTRY title and URL as org link to the clipboard."
  (interactive)
  (let* ((link (elfeed-entry-link entry))
         (title (elfeed-entry-title entry))
         (titlelink (concat "[[" link "][" title "]]")))
    (when titlelink
      (kill-new titlelink)
      (x-set-selection 'PRIMARY titlelink)
      (message "Yanked: %s" titlelink))))

Capturing or copying in the search view

(defun timu-elfeed-search-copy-link ()
  "Copy the current entry title and url as org link to the clipboard."
  (interactive)
  (let ((entries (elfeed-search-selected)))
    (cl-loop for entry in entries
             when (elfeed-entry-link entry)
             do (timu-elfeed-link-title entry))))

(defun timu-elfeed-search-capture ()
  "Capture the title and url for the selected entry or entries in org aganda.
Credit: http://heikkil.github.io/blog/2015/05/09/notes-from-elfeed-entries/"
  (interactive)
  (let ((entries (elfeed-search-selected)))
    (cl-loop for entry in entries
             do (elfeed-untag entry 'unread)
             when (elfeed-entry-link entry)
             do (timu-elfeed-link-title entry)
             do (org-capture nil "l")
             do (yank)
             do (org-capture-finalize)
             (mapc #'elfeed-search-update-entry entries))
    (unless (use-region-p) (forward-line))))

Capturing or copying in the show view

(defun timu-elfeed-show-copy-link ()
  "Copy the current entry title and url as org link to the clipboard."
  (interactive)
  (timu-elfeed-link-title elfeed-show-entry))

(defun timu-elfeed-show-capture ()
  "Fastest way to capture entry link to org agenda from `elfeed' show mode.
Credit: http://heikkil.github.io/blog/2015/05/09/notes-from-elfeed-entries/"
  (interactive)
  (timu-elfeed-link-title elfeed-show-entry)
  (org-capture nil "l")
  (yank)
  (org-capture-finalize))

The capture template

This is in another file/library of mine, that will be discussed at a later time. But it is crucial in this case to be included here. You know, for completeness sake.

(add-to-list 'org-capture-templates
             '("l" "elfeed capture" entry
               (file+headline "~/org/files/gtd.org" "ELFEED")
               "* %?    :elfeed:\nCapture on %U\n%i\n"))

Leveraging Xwidget Webkit for viewing

I most cases the defult view in Elfeed is more that enough. But some article are best consumed as intended to be viewed in a “full fledged” browser.

I don’t know about you lot, but I don’t enjoy switching back and forth between Emacs and Safari (I like it, sue me 😁). Xwidget Webkit to the rescue.

The following command shows the current entry in an Xwidget Webkit buffer in the same window.

(defun timu-elfeed-show-visit-xwidget (&optional generic)
  "Visit the current entry in Xwidget using `xwidget-webkit-browse-url'.
If there is a prefix argument, visit the current entry in the
GENERIC browser defined by `browse-url-generic-program'."
  (interactive "P")
  (let ((link (elfeed-entry-link elfeed-show-entry)))
    (when link
      (message "Sent to browser: %s" link)
      (if generic
          (browse-url-generic link)
        (xwidget-webkit-browse-url link)))))