Subject: bug#69837: 29.2; vtable-update-object only works in visible windows

From: Adam Porter <[email-masked]>
Date: Tue, 19 Mar 2024 20:41:59 -0500

Hi Eli,

On 3/18/24 12:05, Eli Zaretskii wrote:

 > I'm sorry, I'm not familiar with the design of vtable, so I will
 > have to ask you possibly stupid questions.  Apologies in advance.
I'm not very familiar with it, either, so I will have to give you
possibly stupid answers.  :)  But I will try to give accurate ones.

 >> 1. vtable-update-object looks in the vtable's cache to find the
 >> cached information about the representation of the object being
 >> updated or replaced.
 >
 > Is this cache used just to speed up something (and so if the object
 > is not in the cache, Emacs will just work harder to obtain the same
 > information), or is finding the object in the cache absolutely
 > necessary for working with the object?
It appears that the caching is integral to the rendering of vtables.  In
`vtable-insert`, a comment explains:

     ;; We maintain a cache per screen/window width, so that we render
     ;; correctly if Emacs is open on two different screens (or the
     ;; user resizes the frame).

Then `vtable--ensure-cache` is called, which returns either an existing
cache or a newly computed one, and its return value is bound and used
when inserting the table's header line and when inserting each object's
representation.  So it seems that the rendering of objects always uses
the cached values.

 >> 2. In the process of doing that, it calls vtable--cache-key, which
 >> returns a cons cell containing the frame-terminal and
 >> window-width.
 >
 > If working with an object needs its window-width, does it mean
 > objects are tightly coupled to their windows?  If so, what happens
 > when the user or Emacs resizes the window? are the cached objects
 > recomputed to reflect that?
AFAICT, if a vtable's buffer's window is resized, nothing happens
automatically.  But if the vtable is reverted (causing it to be
completely re-rendered), the window's width is taken into account
compared to the width of the table, affecting the vtable's header line,
and possibly the widths of some columns, depending on the table's
specification (see functions `vtable--compute-width` and
`vtable--compute-widths`).  And if an individual object is updated, it
fails if the window width has changed, since the cache key misses.

I suppose the cache serves to sometimes help avoid re-rendering all the
objects in the table, and it uses the window-width as part of the cache
key because the table's buffer could be shown in multiple windows, each
of which could have a different width, requiring a different rendering
of the table (though I can't say I understand exactly how that could
work, since ISTM that rendering it for one window would display the same
rendering in the other window).

 >> 3. So if the selected frame is on the same terminal, and the
 >> selected window has the same width, as the ones when the vtable was
 >> last generated, the cache key will match.  This will allow
 >> vtable-update-object to access the cached values and look for the
 >> object in them.
 >
 > This again seems to imply that an object is tightly coupled to its
 > window, and changes affecting the window must recompute the cached
 > value.  Right?
I think the way it works is roughly like this:

- An object's value(s) affect the width of the columns in its
representation's row.

- A row's columns' widths (or their total width) is compared with the
width of the window to determine how to display the last column (or the
last one visible in the window?).

- So the inverse holds as well: the window's width affects the display
of (at least one of) an object's representation's columns.

So if an object is being re-rendered, and the window's width is the
same, the cache can be used (which also seems to record the line number
at which the object is rendered, which saves from having to iterate
through the table to find the buffer line).  Otherwise, if the window's
width has changed, the cache misses; and since `vtable-update-object`
does not handle this case (it just causes an error), the object is not
updated.  This would require the application to handle the error and
revert the whole table instead.

I would suppose that, in that case, the last-used window width (if it
were known) could be used to update the object, assuming that the width
is either the same or "good enough," to avoid having to re-render the
whole table; if the worst that happened is that a column at the edge of
the table were a bit too wide or narrow compared to the ideal, that
would often be preferable to re-rendering the whole table.  (In my case,
I could have 1,000-2,000 rows in the table, in which case I would prefer
to avoid re-rendering the whole thing; leaving the columns at their
existing width is no problem, especially if some of them are already
past the width of the window--and in my case, the table is always wider
than the window.)

 > Are these keys (window-width, terminal, etc.) only used to look up
 > the object, or are they needed for processing the objects as well?
The window's width appears to be integral to the rendering of the
objects, both individually and when inserting the whole table at once.
The terminal is used as part of the cache key, probably because a
different display could have a different resolution (DPI), which would
make calculations on one invalid on another (I'm sure you know more
about how that works than I do).

 >>> If not, can you show a recipe, starting from "emacs -Q", that
 >>> reproduces the problem, so we could study it in more detail?
 >>
 >> I had hoped to avoid writing that much code to demonstrate it.  But
 >> if the explanation above doesn't suffice, let me know and I will.
 >
 > It doesn't have to be code, it could be a (possibly long) list of
 > instructions what to type.
In my case, I'm using vtable in my `listen` package on ELPA.  If I, e.g.
add all the tracks in my music library to a `listen-queue`, which is
rendered with vtable, it's helpful to be able to update just one track's
representation at a time (so that the currently playing track can have
an arrow next to it, and the previously playing one can have its arrow
removed), because rendering the whole table can take a few seconds.
(Smaller tables, e.g. a hundred tracks or so, render very quickly.)

If a concrete demo is still needed, let me know and I can hack something up.

Thanks,
Adam