;;; buc.el --- move between buffers based on category -*- lexical-binding: t -*- ;; ;; Author: Emanuel Berg ;; Created: 2021-05-23 ;; Keywords: docs, files ;; License: GPL3+ ;; Package-Requires: ((cl-lib "1.0")) ;; URL: https://dataswamp.org/~incal/emacs-init/buc.el ;; Version: 1.3.5 ;; ;; Commentary: ;; ;; This package enables you to move between similar buffers. ;; So far I have only found one use case for it, namely ;; accessing man pages - try `man-buc' below. ;; ;; The idea behind the interface is to present alternatives ;; based on your recent choises. The alternatives are sorted ;; to present you with the most recent first. One picks an ;; alternative by hitting a single key - still, if your ;; desired choice is not among the alternatives shown, as soon ;; as one starts to type the cache-based interface is ;; dismissed and one can type whatever, like one normally ;; would. So the cache interface greatly reduces the amount of ;; typing, however in the occasional fail cases does not ;; require any more typing than it normally would, before and ;; without the cache feature. ;; ;; As hinted, the principles behind the interface are those of ;; a computer memory cache, a very fast part of memory based ;; on data proximity in space and time; it refreshes after ;; every access. ;; ;; Theorizing even more broadly, the interface is an hybrid ;; between the GUI and CLI styles, the difference being the ;; GUI "icons" are here words and not pictorial icons. ;; Some say that icons are more intuitive than words - this is ;; maybe true for infants but many adults have spent all or huge ;; part of their life either reading or writing, so to them, ;; words are as much or more intuitive and, obviously much ;; more versatile than the very simple concepts typically conveyed by ;; no smoking signs and the like. ;; ;;; Code: (require 'cl-lib) (defun buffer-names () "Get the names of all open buffers, as strings." (mapcar #'buffer-name (buffer-list)) ) (defun extract-strings (strs match) "From STRS, get a list with the parts that MATCH." (remove nil (mapcar (lambda (s) (when (string-match match s) (match-string 1 s) )) strs) )) (let*((nav-keys '(?\r ?\s ?\t ?\d ?\C-j ?\C-k ?\C-l ?\C-u ?\C-o ?\C-p)) (nav-keys-str (split-string (key-description nav-keys))) ) (defun get-ps (names) "Make the prompt-string with NAMES." (let ((k -1) (s " [") ) (dolist (e names (concat s "] ")) (setq s (format "%s %s:%s " s (nth (cl-incf k) nav-keys-str) e)) ))) (declare-function get-ps nil) (defun navigate-buffer-category (prefix &optional bad-key &rest args) "Display all buffers that start with PREFIX. If none of the offered buffers are chosen by the user's keystroke, evaluate (BAD-KEY ARGS)." (let ((pages (extract-strings (buffer-names) (format "%s%s" prefix "\\(.*\\)\\*") ))) (if pages (let*((ps (get-ps pages)) (key (read-key ps)) (page-index (cl-position key nav-keys)) (page (when page-index (nth page-index pages) ))) (if (= key 7) (message "quit") (if page (switch-to-buffer (format "%s%s*" prefix page)) (when bad-key (apply bad-key `(,@args ,key)) )))) (if bad-key (apply bad-key args) (error "Empty category, no fallback function") )))) (declare-function navigate-buffer-category load-nil) ) (defun switch-to-type (ps fun &optional key) "Ask for a string with the prompt-string PS. Use the string as input to FUN. If KEY, it'll be the first KEY of the string, auto-inserted." (apply fun (list (read-string ps (and key (char-to-string key))))) ) (defun man-buc () "Show man pages. If you start typing, you get the common prompt." (interactive) (navigate-buffer-category "*Man " #'switch-to-type "[buc] man page: " 'man) ) (provide 'buc) ;;; buc.el ends here