Instead of walking the old grid cell-by-cell, and checking for
tracking points, OSC-8 URIs etc on each cell, memcpy() sequences of
cells.
For each row, find the end column, by scanning backward, looking for
the first non-empty cell.
Chunk the row based on tracking point coordinates. If there aren’t any
tracking coordinates, or OSC-8 URIs on the current row, the entire row
is copied in one go.
The chunk of cells is copied to the new grid. We may have to split it
up into multiple copies, since not all cells may fit on the current
“new” row.
Care must also be taken to not line break in the middle of a
multi-column character.
When we resize the alt screen, we don’t reflow the text, we simply
truncate all the lines.
When doing this, make sure we don’t truncate in the middle of a
multi-column character.
Since we know the following:
* URI ranges are sorted
* URI coordinates are unique
* URI ranges don’t cross rows
We can optimize URI range reflowing by:
* Checking if the *first* URI range’s start coordinate is on the
current column. If so, keep a pointer to it.
* Use this pointer as source when instantiating the reflowed URI range
* If we already have a non-NULL range pointer, check its end
coordinate instead.
* If it matches, close the *last* URI range we inserted on the new
row, and free/remove the range from the old row.
* When line breaking, we only need to check if the *last* URI range is
unclosed.
This reduces the memory cost of reflowing text, as we no longer needs
to hold both the old and the new grid, in their entirety, in memory at
the same time.
We’re going to write to it immediately anyway. In most cases, *all*
newly allocated, and zero-initialized, cells are overwritten.
So let’s skip the zero-initialization of the new cells.
There are two cases where we need to explicitly clear cells now:
* When inserting a hard line break - erase the remaining cells
* When done, the *last* row may not have been completely written -
erase the remaining cells
The row numbers in the tracking points are in absolute
numbers. However, when we walk the old grid, we do so starting in the
beginning of the scrollback history.
We must ensure the tracking points are sorted such that the *first*
one we see is the “oldest” one. I.e. the one furthest back in the
scrollback history.
Instead of iterating a linked list of tracking points, for *each and
every* cell in the old grid, use a sorted array.
This allows us to step through the array of tracking points as we walk
the old grid; each time we match a tracking point, we move to the next
one.
This means we only have to check a single tracking point for each cell.
Calling wcwidth() on every character in the entire scrollback history
is slow.
We already have the character width encoded in the grid; it’s in the
CELL_SPACERs following a multi-column character.
Thus, when we see a non-SPACER character, that isn’t in the last
column, peek the next character. If it’s a SPACER, get the current
characters width from it.
The only thing we need the width for, is to be able to print padding
SPACERS in the right margin, if the there isn’t enough space on the
current row for the current character.
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
Which means, when we match URI start and end points against the
current column index, we must *not* use ‘if...else if’, but two
‘if... if’.
Fixes an assertion when resizing a window with an URI range of just
one cell.
URI ranges are per row. Translate by detecting URI range start/end
coordinates, and opening and closing a corresponding URI range on the
new grid.
We need to take care when line-wrapping the new grid; here we need to
manually close the still-open URI ranges (on the new grid), and
re-opening them on the next row.
This patch adds an ‘extra’ member to the row struct. It is a pointer
to a struct containing extra data to be associated with this row.
Initially, this struct contains a list of URL ranges. These
define (OSC-8) URLs on this row.
The ‘extra’ data is allocated on-demand. I.e. the pointer is NULL by
default; it is *not* allocated by grid_row_alloc().
Alt screen applications normally reflow/readjust themselves on a
window resize.
When we do it too, the result is graphical glitches/flashes since our
re-flowed text is rendered in one frame, and the application re-flowed
text soon thereafter.
We can’t avoid rendering some kind of re-flowed frame, since we don’t
know when, or even if, the application will update itself. To avoid
glitches, we need to render, as closely as possible, what the
application itself will render shortly.
This is actually pretty simple; we just need to copy the visible
content over from the old grid to the new grid. We don’t bother with
text re-flow, but simply truncate long lines.
To simplify things, we simply cancel any active selection (since often
times, it will be corrupted anyway when the application redraws
itself).
Since we’re not reflowing text, there’s no need to translate e.g. the
cursor position - we just keep the current position (but bounded to
the new dimensions).
Fun thing: ‘less’ gets corrupted if we don’t leave the cursor at
the (new) bottom row. To handle this, we check if the cursor (before
resize) is at the bottom row, and if so, we move it to the new bottom
row.
Closes#221
Move sixel reflow from grid_reflow() to sixel_reflow(). This lets us
use internal sixel functions to update the sixels.
Note that grid_reflow() still needs to remap the sixelss coordinates.