0

I use elfeed to read RSS in emacs and want to make a function that cycle through different views: news, 9gag, nonnews etc.

I have tried to make some code via quick research. When I press ctrl + right arrow key it cycle through the views. It works fine, but I don't think it is the most convenient code.

I also want to make it go reverse through the views when pressing ctrl + left arrow key.

;;cycle through views
(setq a 0)
(defun cycle-list-view ()
  (interactive)
  (if (= a 3)
    (progn
    (elfeed-read-9gag)
    (setq a 4)
    ))
  (if (= a 2)
    (progn
    (elfeed-read-nonnews)
    (setq a 3)      
    ))
  (if (= a 1)
    (progn
    (elfeed-read-news)
    (setq a 2)      
    ))
  (if (= a 0)
    (progn
    (elfeed-read-defaultnews)
    (setq a 1)      
    ))
  (if (= a 4)
    (progn
    (setq a 0)
    ))
  )

 (global-set-key (kbd "<C-right>") 'cycle-list-view)
BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Batterman
  • 33
  • 2
  • See function `set-transient-map`. You can use it to cycle without needing a global variable. Or test `last-command` and cycle if it is the same command. (There are in fact several ways to cycle among a variety of behaviors.) – Drew Dec 29 '15 at 06:42

2 Answers2

1

You should probably put the data in a separate variable, and have your code simply maintain a global index variable into that structure. Perhaps like this:

(defvar elfeed-views-list "List of views for `elfeed-cycle-views'.
Each member should be a cons of a label string and a function.
`elfeed-views--index' is a pointer into this structure."
  (("default" . #'elfeed-read-defaultnews)
   ("news" . #'elfeed-read-news)
   ("nonnews" . #'elfeed-read-nonnews)
   ("9gag" . #'elfeed-read-9gag)))

Adding a new view no longer requires code changes -- just push another item to the end of this list.

Now all the cycling function needs to do is increment or decrement an index into this structure, and take care of wrapping if you go past the end:

;; The double dash in the name suggests a private variable
(defvar elfeed-views--index "Index into `elfeed-views-list' for `elfeed-cycle-views'.
Use that function to manipulate the index." 0)

(defun elfeed-cycle-views (&optional skip)
  "Update `elfeed-views--index' by adding SKIP (default 1) and
wrapping if necessary, then call the function at this index in
`elfeed-views-list'."
  (interactive)
  (setq elfeed-views--index (+ elfeed-views-index (or skip 1)))
   ;; BUG: skips with an absolute value bigger than 1 don't wrap properly.
  (if (>= elfeed-views--index (length elfeed-views-list))
    (setq elfeed-views--index 0)
   (when (< elfeed-views--index 0)
     (setq elfeed-views--index (1- (length elfeed-views-list))) ))
  (eval (cdr (nth elfeed-views--index elfeed-views-list))) )

Cycling the other way can now simply be achieved by passing -1 as the SKIP parameter.

(Untested, but at least this should give you ideas.)

As a stylistic aside, you want to learn about cond instead of the ghastly sequence of if statements. You apparently had to force an unintuitive reverse sorting order of your code because you did not know about this control structure.

tripleee
  • 175,061
  • 34
  • 275
  • 318
1

It looks like tripleee's answer will work for the general question you asked (regarding how to cycle through a list of functions). For the specific question (cycling through Elfeed filters), I wrote an implementation of this for myself a while back, and thought I'd post it here in case it's of use to you.

Without seeing the code you're calling (e.g., elfeed-read-news) it's hard to say how this differs from your implementation, but I'm guessing you don't need to have all of your views written up as separate functions (unless you also want a command to jump straight to a particular view).

Also, I should note that my implementation uses the dash library, which you can find on MELPA.

(defvar my/elfeed-favorite-filters
  '("@6-months-ago +unread"
    "+todo")
  "A list of commonly-used filters for Elfeed.
Use `my/elfeed-search-next-favorite-filter' to cycle through
these.")

(defun my/elfeed-search-next-favorite-filter (&optional verbose)
  "Apply the next filter in `my/elfeed-favorite-filters'.

If the current search filter is an element of
`my/elfeed-favorite-filters', apply the filter immediately
following that one in the list, looping back to the beginning if
necessary.

If the current search filter is not an element of
`my/elfeed-favorite-filters', apply the first filter in the
list.

If `my/elfeed-favorite-filters' is empty, just apply the default
filter.

Return the filter applied.  When called interactively or the optional
VERBOSE parameter is non-nil, also print a message informing the user
of the newly applied filter."
  (interactive "p")
  (let ((new-filter
         (my/successor-in-list my/elfeed-favorite-filters
                                elfeed-search-filter :cyclical)))
    (elfeed-search-set-filter new-filter)
    (when verbose (message "Filter applied: %s" elfeed-search-filter))
    elfeed-search-filter))

(defun my/successor-in-list (list elt &optional cycle)
  "Return the element in LIST following ELT.
If ELT is not an element of LIST, return nil.

If ELT is the last element in LIST, return (car LIST) if the
optional parameter CYCLE is non-nil; otherwise, return nil."
  (require 'dash)                       ; For `-drop-while'
  (let ((found  (-drop-while (lambda (x) (not (equal x elt)))
                             list)))
    (cond
     ((null found)                   nil)
     ((or (cdr found) (null cycle))  (cadr found))
     (:else                          (car list)))))

I don't use a "cycle backward" command, but it's pretty easy to tack one on:

(defun my/elfeed-search-prev-favorite-filter (&optional verbose)
  "Apply the previous filter in `my/elfeed-favorite-filters'.

As `my/elfeed-search-next-favorite-filter', but cycle backwards."
  (interactive "p")
  (let ((my/elfeed-favorite-filters (reverse my/elfeed-favorite-filters)))
    (my/elfeed-search-next-favorite-filter verbose)))
Aaron Harris
  • 876
  • 12
  • 14