Raven 🐦
Raven is a flexible tool for selecting candidates from the Emacs minibuffer. It supports multiple candidate sources and multiple actions, and is well-integrated with Evil mode. It also supports dummy candidates, which are similar to Helm’s dummy sources (except that they are candidates, not sources, and therefore one might include multiple dummy candidates within a single selection). It is also reasonably quick (works fairly well with 100,000 candidates when testing).
Raven is not:
- An alternate
completing-read, as it neither reads nor completes. Raven selects from a list of choices. A functionraven-completing-readis provided to handle the many cases ofcompleting-readthat are merely selection (for example, inacademic-phrases), but it will fail in cases that supply a completion function, such asfind-file. Use custom commands in these cases. - Asynchronous. Caching of candidate lists should (and easily can) be handled elsewhere.
- Intended for all audiences. Raven is tailored to my needs and preferences in many ways. Particularly, Raven’s minibuffer interface uses Evil. It should be easy to change this, but the defaults will always assume an Evil-based configuration.
- All that large. Check out the source!
Future Work
- Figure out how to bind action keys in only some Evil states. Naively using
evil-define-keyon the source-specific keymap interacts poorly (read: it doesn’t work) when the keymap is activated withset-transient-map. - More interesting prebuilt sources (probably in another package).
Example A - raven-mini?
(defun me/raven-unaffiliated-buffers ()
(raven-source "Buffers" (me/unaffiliated-buffers) raven-buffer-actions))
(defun me/raven-project-buffers ()
(raven-source "Project Buffers"
(when (projectile-project-p) (remove-if 'me/buffer-boring-p (projectile-project-buffer-names)))
raven-buffer-actions))
(defun me/raven-project-files ()
(raven-source "Project Files"
(when (projectile-project-p)
(cl-loop with root = (projectile-project-root)
for display in (projectile-current-project-files)
collect (cons display (expand-file-name display root))))
raven-file-actions))
(defun me/raven-create-file-or-buffer ()
(raven-source "Other"
'((buffer . "Create buffer")
(file . "Create file"))
(list (lambda (a)
(cond ((eq a 'buffer)
(switch-to-buffer (raven-input)))
((eq a 'file)
(find-file (raven-input))))))))
(defun me/navigate ()
(interactive)
(raven (list (me/raven-project-buffers)
(me/raven-unaffiliated-buffers)
(me/raven-project-files)
(raven-recentf-source)
(me/raven-create-file-or-buffer)))))Projectile buffers, projectile files, Recentf, and file/buffer creation, all in one convenient minibuffer interface with modal line-editing and navigation!
In my actual configs, I use a setup similar to above, but with even more sources, such as active/inactive Circe IRC buffers and EXWM windows.
Example B - Web Browser
(defvar me/bookmarks
'(("github" . "https://github.com")
("example" . "http://example.com")))
(defun me/raven-bookmarks ()
(raven-source "Bookmarks"
me/bookmarks
'(browse-url)))
(defun me/raven-browser-actions ()
(raven-source
"Other"
'((ddg . "Search DuckDuckGo")
(browse . "Browse URL"))
(list (lambda (a)
(cond ((eq a 'ddg)
(browse-url (concat "https://duckduckgo.com/?q="
(raven-input))))
((eq a 'browse)
(browse-url (raven-input))))))))
(defun me/browser ()
(interactive)
(raven (list (me/raven-bookmarks)
(me/raven-browser-actions))))
