help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: bmenu for recentf (or abstracting-away bmenu)


From: Yuri Khan
Subject: Re: bmenu for recentf (or abstracting-away bmenu)
Date: Thu, 16 Nov 2023 14:03:55 +0700

On Thu, 16 Nov 2023 at 12:54, Lockywolf
<for_help-gnu-emacs_at_gnu_org_2023-11-16@lockywolf.net> wrote:

> I want to have a buffer-menu for recent files, in a way similar to
> bookmarks.
>
> In fact, the bmenu code in bookmarks.el is just about 700 lines, not
> very huge, so I considered writing it myself. However, later I thought
> that this would mean effectively duplicating a lot of code which might
> happen to be useful in other packages too.
>
> Are there some plans on creating a common bmenu library for Emacs?
> Perhaps it could also integrate with the menu-bar mode somehow (which
> would be less surprising for the users used to the Windows way of
> self-discovery). Or maybe such a library already exists?

The abstraction behind bmenu is called tabulated-list-mode. Here’s a
simplified extract from my config implementing a recent files
navigator. The core functionality is just about 50 lines of code and
all of it is domain-specific (cannot be abstracted).

You start with a top-level command to conjure a buffer and put it into
a major mode derived from ‘tabulated-list-mode’:

    (defun yk-recentf-show ()
      "Show a list of recently opened files."
      (interactive)
      (with-current-buffer (get-buffer-create " *Recentf*")
        (yk-recentf-show-mode)              ;; see below
        (tabulated-list-revert)
        (pop-to-buffer (current-buffer))))

That mode needs to set a few buffer-local variables to teach the base
mode how to display entries and how to revert itself on ‘g’:

    (define-derived-mode yk-recentf-show-mode
      tabulated-list-mode
      "Recentf"
      "Major mode for `yk-recentf-show' buffers."
      (setq tabulated-list-format [("M" 1 nil)
                                   ("Filename" 20 nil)
                                   ("Directory" -1 nil)])
      (setq-local tabulated-list-revert-hook 'yk-recentf-show--revert)
 ;; see below
      (tabulated-list-init-header))

Your revert hook needs to populate the buffer-local
‘tabulated-list-entries’ with a list whose each element is a list of
an ID and a vector of cell values:

    (defun yk-recentf-show--revert ()
      "Re-populate the buffer listing recently opened files."
      (setq tabulated-list-entries
            (mapcar (lambda (filename)
                      `(,filename [""
                                   ,(file-name-nondirectory filename)
                                   ,(file-name-directory filename)]))
                    recentf-list)))

That’s basically all you need for the displaying part. Now for the
acting part. You define a keymap for the derived mode (named by
‘*-map’ convention so the mode uses it automatically):

    (defvar yk-recentf-show-mode-map
      (let ((map (make-sparse-keymap)))
        (define-key map (kbd "RET") 'yk-recentf-show-find-file)
        (define-key map (kbd "o") 'yk-recentf-show-find-file-other-window)
        map))

The handlers are simple wrappers that get the ID of the entry at point
and pass it to whatever command:

    (defun yk-recentf-show-find-file ()
      "Visit the file at point."
      (interactive)
      (find-file (tabulated-list-get-id)))

    (defun yk-recentf-show-find-file-other-window ()
      "Visit the file at point in other window."
      (interactive)
      (find-file-other-window (tabulated-list-get-id)))

All that is left now is to bind the top-level command to some easy
key. I use Alt+F11 in memory of a certain blue-and-cyan text mode file
manager on a popular non-free platform.

    (global-set-key (kbd "M-<f11>") 'yk-recentf-show)


One thing I found lacking in the base tabulated-list-mode is generic
support for marks and deletion. Like, in Dired, Bmenu, and Ibuffer you
can type ‘D’ to delete an entry at point immediately (possibly after
confirmation), ‘d’ to mark an entry for deletion, ‘x’ to delete all
entries marked thus, ‘m’ to mark for other operations, and ‘u’ to
unmark. With tabulated-list-mode, you have to program all that for
every derived mode. It could be abstracted by defining a buffer-local
variable that would point to a mode-specific deletion function (taking
an ID); all the marking mechanics could be implemented generically.

As for menu bar integration, I’m not interested. Emacs tends to
accumulate buffers, bookmarks, and recent files in numbers that are
unwieldy in a pull-down menu but work just well in a tabulated-list
buffer.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]