all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Yuri Khan <yuri.v.khan@gmail.com>
To: Lockywolf <for_help-gnu-emacs_at_gnu_org_2023-11-16@lockywolf.net>
Cc: "help-gnu-emacs@gnu.org" <help-gnu-emacs@gnu.org>
Subject: Re: bmenu for recentf (or abstracting-away bmenu)
Date: Thu, 16 Nov 2023 14:03:55 +0700	[thread overview]
Message-ID: <CAP_d_8UFU9y6B5sL+U8Rh0riUctM7j5ZRF6qYvLJ9BmkcrXOHw@mail.gmail.com> (raw)
In-Reply-To: <87pm0afkr5.fsf@laptop.lockywolf.net>

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	other threads:[~2023-11-16  7:03 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-16  3:13 bmenu for recentf (or abstracting-away bmenu) Lockywolf
2023-11-16  7:03 ` Yuri Khan [this message]
2023-11-16 16:46 ` [External] : " Drew Adams

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAP_d_8UFU9y6B5sL+U8Rh0riUctM7j5ZRF6qYvLJ9BmkcrXOHw@mail.gmail.com \
    --to=yuri.v.khan@gmail.com \
    --cc=for_help-gnu-emacs_at_gnu_org_2023-11-16@lockywolf.net \
    --cc=help-gnu-emacs@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.