     [file docstyle.texi not included]

vtable
******

This file documents the GNU vtable.el package.

Copyright © 2022--2024 Free Software Foundation, Inc.

     Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being ``A GNU Manual,'' and with the Back-Cover Texts as in (a) below.  A copy of the license is included in the section entitled ``GNU Free Documentation License.''

     (a) The FSF's Back-Cover Text is: ``You have the freedom to copy and modify this GNU manual.''

Introduction and Tutorial
*************************

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.

Concepts
********

A vtable lists data about a number of objects.  Each object can be a list or a vector, but it can also be anything else.

To get the value for a particular column, the getter function is called on the object.  If no getter function is defined, the default is to try to index the object as a sequence.  In any case, we end up with a value that is then used for sorting.

This value is then formatted via a formatter function, which is called with the value as the argument.  The formatter commonly makes the value more reader friendly.

Finally, the formatted value is passed to the displayer function, which is responsible for putting the table face on the formatted value, and also ensuring that it's not wider than the column width.  The displayer will commonly truncate too-long strings and scale image sizes.

All these three transforms, the getter, the formatter and the display functions, can be defined on a per-column basis, and also on a per-table basis.  (The per-column transform takes precedence over the per-table transform.)

User commands that are defined on a table does not work on the displayed data.  Instead they are called with the original object as the argument.

Making A Table
**************

The interface function for making (and optionally inserting a table into a buffer) is make-vtable.  It returns a table object.

The keyword parameters are described below.

There are many callback interface functions possible in make-vtable, and many of them take a OBJECT argument (an object from the :objects list), a column index argument (an integer starting at zero), and a table argument (the object returned by make-vtable).

:objects
     This is a list of objects to be displayed.  It should either be a list of strings (which will then be displayed as a single-column table), or a list where each element is a sequence containing a mixture of strings, numbers, and other objects that can be displayed ``simply''.

     In the latter case, if :columns is non-nil and there's more elements in the sequence than there is in :columns, only the :columns first elements are displayed.

:objects-function
     It's often convenient to generate the objects dynamically (for instance, to make reversion work automatically).  In that case, this should be a function (which will be called with no arguments), and should return a value as accepted as an :objects list.

:columns
     This is a list where each element is either a string (the column name), a plist of keyword/values (to make a vtable-column object), or a full vtable-column object.  A vtable-column object has the following slots:

     name
          The name of the column.

     width
          The width of the column.  This is either a number (the width of that many x characters in the table's face), or a string on the form XeX, where X is a number of x characters, or a string on the form XpX (denoting a number of pixels), or a string on the form X% (a percentage of the window's width).

     min-width
          This uses the same format as width, but specifies the minimum width (and overrides width if width is smaller than this.

     max-width
          This uses the same format as width, but specifies the maximum width (and overrides width if width is larger than this. min-width/max-width can be useful if width is given as a percentage of the window width, and you want to ensure that the column doesn't grow pointlessly large or unreadably narrow.

     primary
          Whether this is the primary column---this will be used for initial sorting.  This should be either ascend or descend to say in which order the table should be sorted.

     getter
          If present, this function will be called to return the column value.

           -- Function: column-getter object table
               It's called with two parameters: the object and the table.

     formatter
          If present, this function will be called to format the value.

           -- Function: column-formatter value
               It's called with one parameter: the column value.

     displayer
          If present, this function will be called to prepare the formatted value for display.  This function should return a string with the table face applied, and also limit the width of the string to the display width.

           -- Function: column-displayer fvalue max-width table
               FVALUE is the formatted value; MAX-WIDTH is the maximum width (in pixels), and TABLE is the table.

     align
          Should be either right or left.

:getter
     If given, this is a function that should return the values to use in the table, and will be called once for each element in the table (unless overridden by a column getter function).

      -- Function: getter object index table
          For a simple object (like a sequence), this function will typically just return the element corresponding to the column index (zero-based), but the function can do any computation it wants.  If it's more convenient to write the function based on column names rather than the column index, the vtable-column function can be used to map from index to name.

:formatter
     If present, this is a function that should format the value, and it will be called on all values in the table (unless overridden by a column formatter).

      -- Function: formatter value index table
          This function is called with three parameters: the value (as returned by the getter); the column index, and the table.  It can return any value.

          This can be used to (for instance) format numbers in a human-readable form.

:displayer
     Before displaying an element, it's passed to the displaying function (if any).

      -- Function: displayer fvalue index max-width table
          This is called with four arguments: the formatted value of the element (as returned by the formatter function); the column index; the display width (in pixels); and the table.

          This function should return a string with the table face applied, and truncated to the display width.

          This can be used to (for instance) change the size of images that are displayed in the table.

:use-header-line
     If non-nil (which is the default), display the column names on the header line.  This is the most common use case, but if there's other text in the buffer before the table, or there are several tables in the same buffer, then this should be nil.

:face
     The face to be used.  This defaults to vtable.  This face doesn't override the faces in the data, or the faces supplied by the getter and formatter functions.

:row-colors
     If present, this should be a list of color names to be used as the background color on the rows.  If there are fewer colors here than there are rows, the rows will be repeated.  The most common use case here is to have alternating background colors on the rows, so this would usually be a list of two colors.  This can also be a list of faces to be used.

:column-colors
     If present, this should be a list of color names to be used as the background color on the columns.  If there are fewer colors here than there are columns, the colors will be repeated.  The most common use case here is to have alternating background colors on the columns, so this would usually be a list of two colors.  This can also be a list of faces to be used.  If both :row-colors and :column-colors is present, the colors will be ``blended'' to produce the final colors in the table.

:actions
     This uses the same syntax as define-keymap, but doesn't refer to commands directly.  Instead each key is bound to a command that picks out the current object, and then calls the function specified with that as the argument.

:keymap
     This is a keymap used on the table.  The commands here are called as usual, and if they're supposed to work on the object displayed on the current line, they can use the vtable-current-object function (see Interface Functions) to determine what that object is.

:separator-width
     The width of the blank space between columns.

:divider-width
:divider
     You can have a divider inserted between the columns.  This can either be specified by using :divider, which should be a string to be displayed between the columns, or :divider-width, which specifies the width of the space to be used as the divider.

:sort-by
     This should be a list of tuples, and specifies how the table is to be sorted.  Each tuple should consist of an integer (the column index) and either ascend or descend.

     The table is first sorted by the first element in this list, and then the next, until the end is reached.

:ellipsis
     By default, when shortening displayed values, an ellipsis will be shown.  If this is nil, no ellipsis is shown.  (The text to use as the ellipsis is determined by the truncate-string-ellipsis function.)

:insert
     By default, make-vtable will insert the table at point.  If this is nil, nothing is inserted, but the vtable object is returned, and you can insert it later with the vtable-insert function.

make-table returns a vtable object.  You can access the slots in that object by using accessor functions that have names based on the keywords described above.  For instance, to access the face, use vtable-face.

Commands
********

When point is placed on a vtable, the following keys are bound:

S
     Sort the table by the current column (vtable-sort-by-current-column).  Note that the table is sorted according to the data returned by the getter function (see Making A Table), not by how it's displayed in the buffer.  Columns that have only numerical data are sorted as numbers, the rest are sorted as strings.

{
     Make the current column narrower (vtable-narrow-current-column).

}
     Make the current column wider (vtable-widen-current-column).

M-<left>
     Move to the previous column (vtable-previous-column).

M-<right>
     Move to the next column (vtable-next-column).

g
     Regenerate the table (vtable-revert-command).  This command mostly makes sense if the table has a :objects-function that can fetch new data.

Interface Functions
*******************

If you need to write a mode based on vtable, you will have to interact with the table in various ways---for instance, you'll need to write commands that updates an object and then displays the result.  This chapter describes functions for such interaction.

 -- Function: vtable-current-table
     This function returns the table under point.

 -- Function: vtable-current-object
     This function returns the object on the current line.  (Note that this is the original object, not the characters displayed in the buffer.)

 -- Function: vtable-current-column
     This function returns the column index of the column under point.

 -- Function: vtable-goto-table table
     Move point to the start of TABLE and return the position.  If TABLE can't be found in the current buffer, don't move point and return nil.

 -- Function: vtable-goto-object object
     Move point to the start of the line where OBJECT is displayed in the current table and return the position.  If OBJECT can't be found, don't move point and return nil.

 -- Function: vtable-goto-column index
     Move point to the start of the INDEXth column.  (The first column is numbered zero.)

 -- Function: vtable-beginning-of-table
     Move to the beginning of the current table.

 -- Function: vtable-end-of-table
     Move to the end of the current table.

 -- Function: vtable-remove-object table object
     Remove OBJECT from TABLE.  This also updates the displayed table.

 -- Function: vtable-insert-object table object &optional after-object
     Insert OBJECT into TABLE.  If AFTER-OBJECT, insert the object after this object; otherwise append to TABLE.  This also updates the displayed table.

 -- Function: vtable-update-object table object old-object
     Change OLD-OBJECT into OBJECT in TABLE.  This also updates the displayed table.

     This has the same effect as calling vtable-remove-object and then vtable-insert-object, but is more efficient.

 -- Function: vtable-column table index
     Return the column name of the INDEXth column in TABLE.

GNU Free Documentation License
******************************

     [file doclicense.texi not included]