Most modes that display tabular data in Emacs use
tabulated-list-mode
, but it has some limitations: It assumes
that the text it’s displaying is monospaced, which makes it difficult
to mix fonts and images in a single list. The vtable (“variable
pitch tables”) package tackles this instead.
tabulated-list-mode
is a major mode, and assumes that it
controls the entire buffer. A vtable doesn’t assume that—you can have
a vtable in the middle of other data, or have several vtables in the
same buffer.
Here’s just about the simplest vtable that can be created:
(make-vtable :objects '(("Foo" 1034) ("Gazonk" 45)))
By default, vtable uses the vtable
face (which inherits from
the variable-pitch
face), and right-aligns columns that have
only numerical data (and left-aligns the rest).
You’d normally want to name the columns:
(make-vtable :columns '("Name" "ID") :objects '(("Foo" 1034) ("Gazonk" 45)))
Clicking on the column names will sort the table based on the data in each column (which, in this example, corresponds to an element in a list).
By default, the data is displayed “as is”, that is, the way ‘(format "%s" ...)’ would display it, but you can override that.
(make-vtable :columns '("Name" "ID") :objects '(("Foo" 1034) ("Gazonk" 45)) :formatter (lambda (value column &rest _) (if (= column 1) (file-size-human-readable value) value)))
In this case, that ‘1034’ will be displayed as ‘1k’—but will still sort after ‘45’, because sorting is done on the actual data, and not the displayed data.
Alternatively, instead of having a general formatter for the table, you can put the formatter in the column definition:
(make-vtable :columns '("Name" (:name "ID" :formatter file-size-human-readable)) :objects '(("Foo" 1034) ("Gazonk" 45)))
The data doesn’t have to be simple lists—you can give any type of object to vtable, but then you also have to write a function that returns the data for each column. For instance, here’s a very simple version of M-x list-buffers:
(make-vtable :columns '("Name" "Size" "File") :objects (buffer-list) :actions '("k" kill-buffer "RET" display-buffer) :getter (lambda (object column vtable) (pcase (vtable-column vtable column) ("Name" (buffer-name object)) ("Size" (buffer-size object)) ("File" (or (buffer-file-name object) "")))))
objects in this case is a list of buffers. To get the data to be displayed, vtable calls the getter function, which is called for each column of every object, and which should return the data that will eventually be displayed.
Also note the actions: These are simple commands that will be
called with the object under point. So hitting RET on a line
will result in display-buffer
being called with a buffer object
as the parameter. (You can also supply a keymap to be used, but then
you have to write commands that call vtable-current-object
to
get at the object.)
Note that the actions aren’t called with the data displayed in the buffer—they’re called with the original objects.
Finally, here’s an example that uses just about all the features:
(make-vtable :columns `(( :name "Thumb" :width "500px" :displayer ,(lambda (value max-width table) (propertize "*" 'display (create-image value nil nil :max-width max-width)))) (:name "Size" :width 10 :formatter file-size-human-readable) (:name "Time" :width 10 :primary ascend) "Name") :objects-function (lambda () (directory-files "/tmp/" t "\\.jpg\\'")) :actions '("RET" find-file) :getter (lambda (object column table) (pcase (vtable-column table column) ("Name" (file-name-nondirectory object)) ("Thumb" object) ("Size" (file-attribute-size (file-attributes object))) ("Time" (format-time-string "%F" (file-attribute-modification-time (file-attributes object)))))) :separator-width 5 :keymap (define-keymap "q" #'kill-buffer))
This vtable implements a simple image browser that displays image thumbnails (that change sizes dynamically depending on the width of the column), human-readable file sizes, date and file name. The separator width is 5 typical characters wide. Hitting RET on a line will open the image in a new window, and hitting q will kill a buffer.