2019-06-17 19:33:10 +02:00
|
|
|
|
#include "grid.h"
|
|
|
|
|
|
|
2021-05-15 13:32:10 +02:00
|
|
|
|
#include <stdlib.h>
|
2020-02-15 22:19:08 +01:00
|
|
|
|
#include <string.h>
|
2019-06-17 21:15:20 +02:00
|
|
|
|
|
2019-06-19 10:27:31 +02:00
|
|
|
|
#define LOG_MODULE "grid"
|
2019-07-03 20:21:03 +02:00
|
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-06-19 10:27:31 +02:00
|
|
|
|
#include "log.h"
|
2021-01-15 20:39:45 +00:00
|
|
|
|
#include "debug.h"
|
2020-09-24 18:53:05 +02:00
|
|
|
|
#include "macros.h"
|
2020-03-13 18:44:23 +01:00
|
|
|
|
#include "sixel.h"
|
2021-02-22 10:21:58 +01:00
|
|
|
|
#include "stride.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
|
#include "util.h"
|
2020-08-08 20:34:30 +01:00
|
|
|
|
#include "xmalloc.h"
|
2020-02-15 22:19:08 +01:00
|
|
|
|
|
grid: reflow: memcpy() chunks of cells, instead of single cell-by-cell
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.
2021-05-23 21:15:06 +02:00
|
|
|
|
#define TIME_REFLOW 1
|
2021-05-11 17:43:17 +02:00
|
|
|
|
|
2021-02-22 10:21:58 +01:00
|
|
|
|
struct grid *
|
|
|
|
|
|
grid_snapshot(const struct grid *grid)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct grid *clone = xmalloc(sizeof(*clone));
|
|
|
|
|
|
clone->num_rows = grid->num_rows;
|
|
|
|
|
|
clone->num_cols = grid->num_cols;
|
|
|
|
|
|
clone->offset = grid->offset;
|
|
|
|
|
|
clone->view = grid->view;
|
|
|
|
|
|
clone->cursor = grid->cursor;
|
|
|
|
|
|
clone->rows = xcalloc(grid->num_rows, sizeof(clone->rows[0]));
|
|
|
|
|
|
memset(&clone->scroll_damage, 0, sizeof(clone->scroll_damage));
|
|
|
|
|
|
memset(&clone->sixel_images, 0, sizeof(clone->sixel_images));
|
|
|
|
|
|
|
2021-02-22 10:31:56 +01:00
|
|
|
|
tll_foreach(grid->scroll_damage, it)
|
|
|
|
|
|
tll_push_back(clone->scroll_damage, it->item);
|
|
|
|
|
|
|
2021-02-22 10:21:58 +01:00
|
|
|
|
for (int r = 0; r < grid->num_rows; r++) {
|
|
|
|
|
|
const struct row *row = grid->rows[r];
|
|
|
|
|
|
|
|
|
|
|
|
if (row == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
struct row *clone_row = xmalloc(sizeof(*row));
|
|
|
|
|
|
clone->rows[r] = clone_row;
|
|
|
|
|
|
|
|
|
|
|
|
clone_row->cells = xmalloc(grid->num_cols * sizeof(clone_row->cells[0]));
|
|
|
|
|
|
clone_row->linebreak = row->linebreak;
|
2021-02-26 09:25:17 +01:00
|
|
|
|
clone_row->dirty = row->dirty;
|
2021-02-22 10:21:58 +01:00
|
|
|
|
|
2021-02-26 09:25:27 +01:00
|
|
|
|
for (int c = 0; c < grid->num_cols; c++)
|
2021-02-22 10:21:58 +01:00
|
|
|
|
clone_row->cells[c] = row->cells[c];
|
|
|
|
|
|
|
|
|
|
|
|
if (row->extra != NULL) {
|
|
|
|
|
|
const struct row_data *extra = row->extra;
|
|
|
|
|
|
struct row_data *new_extra = xcalloc(1, sizeof(*new_extra));
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(extra->uri_ranges, it) {
|
|
|
|
|
|
struct row_uri_range range = {
|
|
|
|
|
|
.start = it->item.start,
|
|
|
|
|
|
.end = it->item.end,
|
|
|
|
|
|
.id = it->item.id,
|
|
|
|
|
|
.uri = xstrdup(it->item.uri),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
tll_push_back(new_extra->uri_ranges, range);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
clone_row->extra = new_extra;
|
|
|
|
|
|
} else
|
|
|
|
|
|
clone_row->extra = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(grid->sixel_images, it) {
|
|
|
|
|
|
int width = it->item.width;
|
|
|
|
|
|
int height = it->item.height;
|
|
|
|
|
|
pixman_image_t *pix = it->item.pix;
|
|
|
|
|
|
pixman_format_code_t pix_fmt = pixman_image_get_format(pix);
|
|
|
|
|
|
int stride = stride_for_format_and_width(pix_fmt, width);
|
|
|
|
|
|
|
|
|
|
|
|
size_t size = stride * height;
|
|
|
|
|
|
void *new_data = xmalloc(size);
|
|
|
|
|
|
memcpy(new_data, it->item.data, size);
|
|
|
|
|
|
|
|
|
|
|
|
pixman_image_t *new_pix = pixman_image_create_bits_no_clear(
|
|
|
|
|
|
pix_fmt, width, height, new_data, stride);
|
|
|
|
|
|
|
|
|
|
|
|
struct sixel six = {
|
|
|
|
|
|
.data = new_data,
|
|
|
|
|
|
.pix = new_pix,
|
|
|
|
|
|
.width = width,
|
|
|
|
|
|
.height = height,
|
|
|
|
|
|
.rows = it->item.rows,
|
|
|
|
|
|
.cols = it->item.cols,
|
|
|
|
|
|
.pos = it->item.pos,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
tll_push_back(clone->sixel_images, six);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return clone;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-22 10:20:52 +01:00
|
|
|
|
void
|
|
|
|
|
|
grid_free(struct grid *grid)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int r = 0; r < grid->num_rows; r++)
|
|
|
|
|
|
grid_row_free(grid->rows[r]);
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(grid->sixel_images, it) {
|
|
|
|
|
|
sixel_destroy(&it->item);
|
|
|
|
|
|
tll_remove(grid->sixel_images, it);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
free(grid->rows);
|
2021-02-22 10:31:40 +01:00
|
|
|
|
tll_free(grid->scroll_damage);
|
2021-02-22 10:20:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-01 12:23:38 +02:00
|
|
|
|
void
|
2020-05-16 23:43:05 +02:00
|
|
|
|
grid_swap_row(struct grid *grid, int row_a, int row_b)
|
2019-07-01 12:23:38 +02:00
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(grid->offset >= 0);
|
|
|
|
|
|
xassert(row_a != row_b);
|
2019-07-01 19:18:52 +02:00
|
|
|
|
|
2019-08-22 17:33:23 +02:00
|
|
|
|
int real_a = (grid->offset + row_a) & (grid->num_rows - 1);
|
|
|
|
|
|
int real_b = (grid->offset + row_b) & (grid->num_rows - 1);
|
|
|
|
|
|
|
|
|
|
|
|
struct row *a = grid->rows[real_a];
|
|
|
|
|
|
struct row *b = grid->rows[real_b];
|
2019-08-23 20:07:27 +02:00
|
|
|
|
|
2019-08-22 17:33:23 +02:00
|
|
|
|
grid->rows[real_a] = b;
|
|
|
|
|
|
grid->rows[real_b] = a;
|
2019-07-01 19:18:52 +02:00
|
|
|
|
}
|
2019-07-10 16:27:55 +02:00
|
|
|
|
|
|
|
|
|
|
struct row *
|
2019-08-22 17:33:23 +02:00
|
|
|
|
grid_row_alloc(int cols, bool initialize)
|
2019-07-10 16:27:55 +02:00
|
|
|
|
{
|
2020-08-08 20:34:30 +01:00
|
|
|
|
struct row *row = xmalloc(sizeof(*row));
|
2019-08-22 17:33:23 +02:00
|
|
|
|
row->dirty = false;
|
2020-02-14 22:39:26 +01:00
|
|
|
|
row->linebreak = false;
|
2021-02-13 12:31:55 +01:00
|
|
|
|
row->extra = NULL;
|
2019-08-22 17:33:23 +02:00
|
|
|
|
|
|
|
|
|
|
if (initialize) {
|
2020-08-08 20:34:30 +01:00
|
|
|
|
row->cells = xcalloc(cols, sizeof(row->cells[0]));
|
2019-08-22 17:33:23 +02:00
|
|
|
|
for (size_t c = 0; c < cols; c++)
|
|
|
|
|
|
row->cells[c].attrs.clean = 1;
|
unicode-combining: store seen combining chains "globally" in the term struct
Instead of storing combining data per cell, realize that most
combinations are re-occurring and that there's lots of available space
left in the unicode range, and store seen base+combining combinations
chains in a per-terminal array.
When we encounter a combining character, we first try to pre-compose,
like before. If that fails, we then search for the current
base+combining combo in the list of previously seen combinations. If
not found there either, we allocate a new combo and add it to the
list. Regardless, the result is an index into this array. We store
this index, offsetted by COMB_CHARS_LO=0x40000000ul in the cell.
When rendering, we need to check if the cell character is a plain
character, or if it's a composed character (identified by checking if
the cell character is >= COMB_CHARS_LO).
Then we render the grapheme pretty much like before.
2020-05-03 11:03:22 +02:00
|
|
|
|
} else
|
2020-08-08 20:34:30 +01:00
|
|
|
|
row->cells = xmalloc(cols * sizeof(row->cells[0]));
|
2019-08-22 17:33:23 +02:00
|
|
|
|
|
2019-07-10 16:27:55 +02:00
|
|
|
|
return row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
grid_row_free(struct row *row)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (row == NULL)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2021-02-14 20:34:25 +01:00
|
|
|
|
grid_row_reset_extra(row);
|
2021-02-13 12:31:55 +01:00
|
|
|
|
free(row->extra);
|
2019-07-10 16:27:55 +02:00
|
|
|
|
free(row->cells);
|
|
|
|
|
|
free(row);
|
|
|
|
|
|
}
|
2020-02-15 22:19:08 +01:00
|
|
|
|
|
2020-04-16 19:38:30 +02:00
|
|
|
|
void
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
grid_resize_without_reflow(
|
|
|
|
|
|
struct grid *grid, int new_rows, int new_cols,
|
|
|
|
|
|
int old_screen_rows, int new_screen_rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct row *const *old_grid = grid->rows;
|
|
|
|
|
|
const int old_rows = grid->num_rows;
|
|
|
|
|
|
const int old_cols = grid->num_cols;
|
|
|
|
|
|
|
|
|
|
|
|
struct row **new_grid = xcalloc(new_rows, sizeof(new_grid[0]));
|
|
|
|
|
|
|
|
|
|
|
|
tll(struct sixel) untranslated_sixels = tll_init();
|
|
|
|
|
|
tll_foreach(grid->sixel_images, it)
|
|
|
|
|
|
tll_push_back(untranslated_sixels, it->item);
|
|
|
|
|
|
tll_free(grid->sixel_images);
|
|
|
|
|
|
|
|
|
|
|
|
int new_offset = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* Copy old lines, truncating them if old rows were longer */
|
2020-11-25 20:33:07 +01:00
|
|
|
|
for (int r = 0, n = min(old_screen_rows, new_screen_rows); r < n; r++) {
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
const int old_row_idx = (grid->offset + r) & (old_rows - 1);
|
|
|
|
|
|
const int new_row_idx = (new_offset + r) & (new_rows - 1);
|
|
|
|
|
|
|
|
|
|
|
|
const struct row *old_row = old_grid[old_row_idx];
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(old_row != NULL);
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
|
|
|
|
|
|
struct row *new_row = grid_row_alloc(new_cols, false);
|
|
|
|
|
|
new_grid[new_row_idx] = new_row;
|
|
|
|
|
|
|
|
|
|
|
|
memcpy(new_row->cells,
|
|
|
|
|
|
old_row->cells,
|
|
|
|
|
|
sizeof(struct cell) * min(old_cols, new_cols));
|
|
|
|
|
|
|
|
|
|
|
|
new_row->dirty = old_row->dirty;
|
|
|
|
|
|
new_row->linebreak = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (new_cols > old_cols) {
|
2021-06-02 19:32:05 +02:00
|
|
|
|
/* Clear "new" columns */
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
memset(&new_row->cells[old_cols], 0,
|
|
|
|
|
|
sizeof(struct cell) * (new_cols - old_cols));
|
|
|
|
|
|
new_row->dirty = true;
|
2021-06-02 19:32:05 +02:00
|
|
|
|
} else if (old_cols > new_cols) {
|
|
|
|
|
|
/* Make sure we don't cut a multi-column character in two */
|
|
|
|
|
|
for (int i = new_cols; i > 0 && old_row->cells[i].wc > CELL_SPACER; i--)
|
|
|
|
|
|
new_row->cells[i - 1].wc = 0;
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Map sixels on current "old" row to current "new row" */
|
|
|
|
|
|
tll_foreach(untranslated_sixels, it) {
|
|
|
|
|
|
if (it->item.pos.row != old_row_idx)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
struct sixel sixel = it->item;
|
|
|
|
|
|
sixel.pos.row = new_row_idx;
|
|
|
|
|
|
|
|
|
|
|
|
if (sixel.pos.col < new_cols)
|
|
|
|
|
|
tll_push_back(grid->sixel_images, sixel);
|
|
|
|
|
|
else
|
|
|
|
|
|
sixel_destroy(&it->item);
|
|
|
|
|
|
tll_remove(untranslated_sixels, it);
|
|
|
|
|
|
}
|
2021-02-14 20:42:42 +01:00
|
|
|
|
|
|
|
|
|
|
/* Copy URI ranges, truncating them if necessary */
|
|
|
|
|
|
if (old_row->extra == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(old_row->extra->uri_ranges, it) {
|
|
|
|
|
|
if (it->item.start >= new_rows) {
|
|
|
|
|
|
/* The whole range is truncated */
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct row_uri_range range = {
|
|
|
|
|
|
.start = it->item.start,
|
|
|
|
|
|
.end = min(it->item.end, new_cols - 1),
|
|
|
|
|
|
.id = it->item.id,
|
|
|
|
|
|
.uri = xstrdup(it->item.uri),
|
|
|
|
|
|
};
|
2021-02-14 20:50:46 +01:00
|
|
|
|
grid_row_add_uri_range(new_row, range);
|
2021-02-14 20:42:42 +01:00
|
|
|
|
}
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Clear "new" lines */
|
|
|
|
|
|
for (int r = min(old_screen_rows, new_screen_rows); r < new_screen_rows; r++) {
|
|
|
|
|
|
struct row *new_row = grid_row_alloc(new_cols, false);
|
|
|
|
|
|
new_grid[(new_offset + r) & (new_rows - 1)] = new_row;
|
|
|
|
|
|
|
|
|
|
|
|
memset(new_row->cells, 0, sizeof(struct cell) * new_cols);
|
|
|
|
|
|
new_row->dirty = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Free old grid */
|
|
|
|
|
|
for (int r = 0; r < grid->num_rows; r++)
|
|
|
|
|
|
grid_row_free(old_grid[r]);
|
|
|
|
|
|
free(grid->rows);
|
|
|
|
|
|
|
|
|
|
|
|
grid->rows = new_grid;
|
|
|
|
|
|
grid->num_rows = new_rows;
|
|
|
|
|
|
grid->num_cols = new_cols;
|
|
|
|
|
|
|
|
|
|
|
|
grid->view = grid->offset = new_offset;
|
|
|
|
|
|
|
|
|
|
|
|
/* Keep cursor at current position, but clamp to new dimensions */
|
|
|
|
|
|
struct coord cursor = grid->cursor.point;
|
|
|
|
|
|
if (cursor.row == old_screen_rows - 1) {
|
|
|
|
|
|
/* 'less' breaks if the cursor isn't at the bottom */
|
|
|
|
|
|
cursor.row = new_screen_rows - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
cursor.row = min(cursor.row, new_screen_rows - 1);
|
|
|
|
|
|
cursor.col = min(cursor.col, new_cols - 1);
|
|
|
|
|
|
grid->cursor.point = cursor;
|
|
|
|
|
|
|
|
|
|
|
|
struct coord saved_cursor = grid->saved_cursor.point;
|
|
|
|
|
|
if (saved_cursor.row == old_screen_rows - 1)
|
|
|
|
|
|
saved_cursor.row = new_screen_rows - 1;
|
|
|
|
|
|
saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1);
|
|
|
|
|
|
saved_cursor.col = min(saved_cursor.col, new_cols - 1);
|
|
|
|
|
|
grid->saved_cursor.point = saved_cursor;
|
|
|
|
|
|
|
|
|
|
|
|
grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)];
|
|
|
|
|
|
grid->cursor.lcf = false;
|
|
|
|
|
|
grid->saved_cursor.lcf = false;
|
|
|
|
|
|
|
|
|
|
|
|
/* Free sixels we failed to "map" to the new grid */
|
|
|
|
|
|
tll_foreach(untranslated_sixels, it)
|
|
|
|
|
|
sixel_destroy(&it->item);
|
|
|
|
|
|
tll_free(untranslated_sixels);
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
|
for (int r = 0; r < new_screen_rows; r++)
|
|
|
|
|
|
grid_row_in_view(grid, r);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-14 20:34:49 +01:00
|
|
|
|
static void
|
2021-05-22 20:29:10 +02:00
|
|
|
|
reflow_uri_range_start(struct row_uri_range *range, struct row *new_row,
|
|
|
|
|
|
int new_col_idx)
|
2021-02-14 20:34:49 +01:00
|
|
|
|
{
|
2021-05-22 20:29:10 +02:00
|
|
|
|
struct row_uri_range new_range = {
|
|
|
|
|
|
.start = new_col_idx,
|
|
|
|
|
|
.end = -1,
|
|
|
|
|
|
.id = range->id,
|
|
|
|
|
|
.uri = xstrdup(range->uri),
|
|
|
|
|
|
};
|
|
|
|
|
|
grid_row_add_uri_range(new_row, new_range);
|
|
|
|
|
|
}
|
2021-02-14 20:34:49 +01:00
|
|
|
|
|
2021-05-22 20:29:10 +02:00
|
|
|
|
static void
|
|
|
|
|
|
reflow_uri_range_end(struct row_uri_range *range, struct row *new_row,
|
|
|
|
|
|
int new_col_idx)
|
|
|
|
|
|
{
|
|
|
|
|
|
xassert(tll_length(new_row->extra->uri_ranges) > 0);
|
|
|
|
|
|
struct row_uri_range *new_range = &tll_back(new_row->extra->uri_ranges);
|
2021-02-14 20:34:49 +01:00
|
|
|
|
|
2021-05-22 20:29:10 +02:00
|
|
|
|
xassert(new_range->id == range->id);
|
|
|
|
|
|
xassert(new_range->end < 0);
|
|
|
|
|
|
new_range->end = new_col_idx;
|
2021-02-14 20:34:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct row *
|
|
|
|
|
|
_line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
|
|
|
|
|
|
int *row_idx, int *col_idx, int row_count, int col_count)
|
|
|
|
|
|
{
|
|
|
|
|
|
*col_idx = 0;
|
|
|
|
|
|
*row_idx = (*row_idx + 1) & (row_count - 1);
|
|
|
|
|
|
|
|
|
|
|
|
struct row *new_row = new_grid[*row_idx];
|
|
|
|
|
|
|
|
|
|
|
|
if (new_row == NULL) {
|
|
|
|
|
|
/* Scrollback not yet full, allocate a completely new row */
|
2021-05-15 15:15:32 +02:00
|
|
|
|
new_row = grid_row_alloc(col_count, false);
|
2021-02-14 20:34:49 +01:00
|
|
|
|
new_grid[*row_idx] = new_row;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
/* Scrollback is full, need to re-use a row */
|
|
|
|
|
|
grid_row_reset_extra(new_row);
|
|
|
|
|
|
new_row->linebreak = false;
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(old_grid->sixel_images, it) {
|
|
|
|
|
|
if (it->item.pos.row == *row_idx) {
|
|
|
|
|
|
sixel_destroy(&it->item);
|
|
|
|
|
|
tll_remove(old_grid->sixel_images, it);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (row->extra == NULL)
|
|
|
|
|
|
return new_row;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* URI ranges are per row. Thus, we need to ‘close’ the still-open
|
|
|
|
|
|
* ranges on the previous row, and re-open them on the
|
|
|
|
|
|
* next/current row.
|
|
|
|
|
|
*/
|
2021-05-22 20:29:10 +02:00
|
|
|
|
if (tll_length(row->extra->uri_ranges) > 0) {
|
|
|
|
|
|
struct row_uri_range *range = &tll_back(row->extra->uri_ranges);
|
|
|
|
|
|
if (range->end < 0) {
|
2021-02-14 20:34:49 +01:00
|
|
|
|
|
2021-05-22 20:29:10 +02:00
|
|
|
|
/* Terminate URI range on the previous row */
|
|
|
|
|
|
range->end = col_count - 1;
|
2021-02-14 20:34:49 +01:00
|
|
|
|
|
2021-05-22 20:29:10 +02:00
|
|
|
|
/* Open a new range on the new/current row */
|
|
|
|
|
|
struct row_uri_range new_range = {
|
|
|
|
|
|
.start = 0,
|
|
|
|
|
|
.end = -1,
|
|
|
|
|
|
.id = range->id,
|
|
|
|
|
|
.uri = xstrdup(range->uri),
|
|
|
|
|
|
};
|
|
|
|
|
|
grid_row_add_uri_range(new_row, new_range);
|
|
|
|
|
|
}
|
2021-02-14 20:34:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new_row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-15 13:37:46 +02:00
|
|
|
|
static struct {
|
2021-05-15 12:54:59 +02:00
|
|
|
|
int scrollback_start;
|
|
|
|
|
|
int rows;
|
2021-05-15 13:37:46 +02:00
|
|
|
|
} tp_cmp_ctx;
|
2021-05-15 12:54:59 +02:00
|
|
|
|
|
2021-05-15 11:40:39 +02:00
|
|
|
|
static int
|
2021-05-15 13:37:46 +02:00
|
|
|
|
tp_cmp(const void *_a, const void *_b)
|
2021-05-15 11:40:39 +02:00
|
|
|
|
{
|
|
|
|
|
|
const struct coord *a = *(const struct coord **)_a;
|
|
|
|
|
|
const struct coord *b = *(const struct coord **)_b;
|
2021-05-15 12:54:59 +02:00
|
|
|
|
|
2021-05-15 13:37:46 +02:00
|
|
|
|
int scrollback_start = tp_cmp_ctx.scrollback_start;
|
|
|
|
|
|
int num_rows = tp_cmp_ctx.rows;
|
|
|
|
|
|
|
|
|
|
|
|
int a_row = (a->row - scrollback_start + num_rows) & (num_rows - 1);
|
|
|
|
|
|
int b_row = (b->row - scrollback_start + num_rows) & (num_rows - 1);
|
2021-05-15 11:40:39 +02:00
|
|
|
|
|
2021-05-15 12:54:59 +02:00
|
|
|
|
xassert(a_row >= 0);
|
2021-05-15 13:37:46 +02:00
|
|
|
|
xassert(a_row < num_rows || num_rows == 0);
|
2021-05-15 12:54:59 +02:00
|
|
|
|
xassert(b_row >= 0);
|
2021-05-15 13:37:46 +02:00
|
|
|
|
xassert(b_row < num_rows || num_rows == 0);
|
2021-05-15 12:54:59 +02:00
|
|
|
|
|
|
|
|
|
|
if (a_row < b_row)
|
2021-05-15 11:40:39 +02:00
|
|
|
|
return -1;
|
2021-05-15 12:54:59 +02:00
|
|
|
|
if (a_row > b_row)
|
2021-05-15 11:40:39 +02:00
|
|
|
|
return 1;
|
|
|
|
|
|
|
2021-05-15 12:54:59 +02:00
|
|
|
|
xassert(a_row == b_row);
|
2021-05-15 11:40:39 +02:00
|
|
|
|
|
|
|
|
|
|
if (a->col < b->col)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
if (a->col > b->col)
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
|
|
xassert(a->col == b->col);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
void
|
|
|
|
|
|
grid_resize_and_reflow(
|
|
|
|
|
|
struct grid *grid, int new_rows, int new_cols,
|
|
|
|
|
|
int old_screen_rows, int new_screen_rows,
|
|
|
|
|
|
size_t tracking_points_count,
|
|
|
|
|
|
struct coord *const _tracking_points[static tracking_points_count],
|
|
|
|
|
|
size_t compose_count, const struct
|
|
|
|
|
|
composed composed[static compose_count])
|
2020-02-15 22:19:08 +01:00
|
|
|
|
{
|
2021-05-11 17:43:17 +02:00
|
|
|
|
#if defined(TIME_REFLOW) && TIME_REFLOW
|
|
|
|
|
|
struct timeval start;
|
|
|
|
|
|
gettimeofday(&start, NULL);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
struct row *const *old_grid = grid->rows;
|
|
|
|
|
|
const int old_rows = grid->num_rows;
|
|
|
|
|
|
const int old_cols = grid->num_cols;
|
|
|
|
|
|
|
2020-09-24 18:35:40 +02:00
|
|
|
|
/* Is viewpoint tracking current grid offset? */
|
|
|
|
|
|
const bool view_follows = grid->view == grid->offset;
|
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
int new_col_idx = 0;
|
|
|
|
|
|
int new_row_idx = 0;
|
|
|
|
|
|
|
2020-08-08 20:34:30 +01:00
|
|
|
|
struct row **new_grid = xcalloc(new_rows, sizeof(new_grid[0]));
|
2020-02-15 22:19:08 +01:00
|
|
|
|
struct row *new_row = new_grid[new_row_idx];
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(new_row == NULL);
|
2021-05-15 15:15:32 +02:00
|
|
|
|
new_row = grid_row_alloc(new_cols, false);
|
2020-02-15 22:19:08 +01:00
|
|
|
|
new_grid[new_row_idx] = new_row;
|
|
|
|
|
|
|
|
|
|
|
|
/* Start at the beginning of the old grid's scrollback. That is,
|
|
|
|
|
|
* at the output that is *oldest* */
|
|
|
|
|
|
int offset = grid->offset + old_screen_rows;
|
|
|
|
|
|
|
2020-10-03 23:00:34 +02:00
|
|
|
|
tll(struct sixel) untranslated_sixels = tll_init();
|
2020-06-29 21:59:40 +02:00
|
|
|
|
tll_foreach(grid->sixel_images, it)
|
2020-10-03 23:00:34 +02:00
|
|
|
|
tll_push_back(untranslated_sixels, it->item);
|
2020-06-29 21:59:40 +02:00
|
|
|
|
tll_free(grid->sixel_images);
|
2020-03-13 18:44:23 +01:00
|
|
|
|
|
2020-04-16 19:38:30 +02:00
|
|
|
|
/* Turn cursor coordinates into grid absolute coordinates */
|
|
|
|
|
|
struct coord cursor = grid->cursor.point;
|
|
|
|
|
|
cursor.row += grid->offset;
|
|
|
|
|
|
cursor.row &= old_rows - 1;
|
|
|
|
|
|
|
|
|
|
|
|
struct coord saved_cursor = grid->saved_cursor.point;
|
|
|
|
|
|
saved_cursor.row += grid->offset;
|
|
|
|
|
|
saved_cursor.row &= old_rows - 1;
|
|
|
|
|
|
|
2021-05-15 11:40:39 +02:00
|
|
|
|
size_t tp_count =
|
|
|
|
|
|
tracking_points_count +
|
|
|
|
|
|
1 + /* cursor */
|
|
|
|
|
|
1 + /* saved cursor */
|
|
|
|
|
|
!view_follows + /* viewport */
|
|
|
|
|
|
1; /* terminator */
|
|
|
|
|
|
|
|
|
|
|
|
struct coord *tracking_points[tp_count];
|
|
|
|
|
|
memcpy(tracking_points, _tracking_points, tracking_points_count * sizeof(_tracking_points[0]));
|
|
|
|
|
|
tracking_points[tracking_points_count] = &cursor;
|
|
|
|
|
|
tracking_points[tracking_points_count + 1] = &saved_cursor;
|
2020-04-17 21:00:37 +02:00
|
|
|
|
|
2020-09-24 18:35:40 +02:00
|
|
|
|
struct coord viewport = {0, grid->view};
|
|
|
|
|
|
if (!view_follows)
|
2021-05-15 11:40:39 +02:00
|
|
|
|
tracking_points[tracking_points_count + 2] = &viewport;
|
|
|
|
|
|
|
2021-05-15 13:37:46 +02:00
|
|
|
|
/* Not thread safe! */
|
|
|
|
|
|
tp_cmp_ctx.scrollback_start = offset;
|
|
|
|
|
|
tp_cmp_ctx.rows = old_rows;
|
|
|
|
|
|
qsort(
|
|
|
|
|
|
tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp);
|
2020-09-24 18:35:40 +02:00
|
|
|
|
|
2021-05-15 11:40:39 +02:00
|
|
|
|
/* NULL terminate */
|
|
|
|
|
|
struct coord terminator = {-1, -1};
|
|
|
|
|
|
tracking_points[tp_count - 1] = &terminator;
|
|
|
|
|
|
struct coord **next_tp = &tracking_points[0];
|
2020-04-17 21:04:32 +02:00
|
|
|
|
|
2021-05-15 12:54:59 +02:00
|
|
|
|
LOG_DBG("scrollback-start=%d", offset);
|
|
|
|
|
|
for (size_t i = 0; i < tp_count - 1; i++) {
|
|
|
|
|
|
LOG_DBG("TP #%zu: row=%d, col=%d",
|
|
|
|
|
|
i, tracking_points[i]->row, tracking_points[i]->col);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* Walk the old grid
|
|
|
|
|
|
*/
|
|
|
|
|
|
for (int r = 0; r < old_rows; r++) {
|
|
|
|
|
|
|
2020-04-16 19:38:30 +02:00
|
|
|
|
const size_t old_row_idx = (offset + r) & (old_rows - 1);
|
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
/* Unallocated (empty) rows we can simply skip */
|
2020-04-16 19:38:30 +02:00
|
|
|
|
const struct row *old_row = old_grid[old_row_idx];
|
2020-02-15 22:19:08 +01:00
|
|
|
|
if (old_row == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2020-06-29 21:59:40 +02:00
|
|
|
|
/* Map sixels on current "old" row to current "new row" */
|
2020-10-03 23:00:34 +02:00
|
|
|
|
tll_foreach(untranslated_sixels, it) {
|
2020-06-29 21:59:40 +02:00
|
|
|
|
if (it->item.pos.row != old_row_idx)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
struct sixel sixel = it->item;
|
|
|
|
|
|
sixel.pos.row = new_row_idx;
|
|
|
|
|
|
|
2020-10-04 13:12:44 +02:00
|
|
|
|
tll_push_back(grid->sixel_images, sixel);
|
2020-10-03 23:00:34 +02:00
|
|
|
|
tll_remove(untranslated_sixels, it);
|
2020-03-13 18:44:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-14 20:34:49 +01:00
|
|
|
|
#define line_wrap() \
|
|
|
|
|
|
new_row = _line_wrap( \
|
|
|
|
|
|
grid, new_grid, new_row, &new_row_idx, &new_col_idx, \
|
|
|
|
|
|
new_rows, new_cols)
|
|
|
|
|
|
|
grid: reflow: memcpy() chunks of cells, instead of single cell-by-cell
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.
2021-05-23 21:15:06 +02:00
|
|
|
|
/* Find last non-empty cell */
|
|
|
|
|
|
int col_count = 0;
|
|
|
|
|
|
for (int c = old_cols - 1; c > 0; c--) {
|
|
|
|
|
|
const struct cell *cell = &old_row->cells[c];
|
|
|
|
|
|
if (!(cell->wc == 0 || cell->wc == CELL_SPACER)) {
|
|
|
|
|
|
col_count = c + 1;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xassert(col_count >= 0 && col_count <= old_cols);
|
|
|
|
|
|
|
|
|
|
|
|
struct coord *tp = (*next_tp)->row == old_row_idx ? *next_tp : NULL;
|
|
|
|
|
|
struct row_uri_range *_range =
|
|
|
|
|
|
old_row->extra != NULL && tll_length(old_row->extra->uri_ranges) > 0
|
|
|
|
|
|
? &tll_front(old_row->extra->uri_ranges)
|
|
|
|
|
|
: NULL;
|
|
|
|
|
|
|
|
|
|
|
|
if (tp != NULL)
|
|
|
|
|
|
col_count = max(col_count, tp->col + 1);
|
|
|
|
|
|
if (_range != NULL)
|
|
|
|
|
|
col_count = max(col_count, _range->start + 1);
|
|
|
|
|
|
|
|
|
|
|
|
for (int start = 0, left = col_count; left > 0;) {
|
|
|
|
|
|
int tp_col = -1;
|
|
|
|
|
|
int uri_col = -1;
|
|
|
|
|
|
int end;
|
|
|
|
|
|
|
|
|
|
|
|
if (tp != NULL && _range != NULL) {
|
|
|
|
|
|
tp_col = tp->col + 1;
|
|
|
|
|
|
uri_col = (_range->start >= start ? _range->start : _range->end) + 1;
|
|
|
|
|
|
end = min(tp_col, uri_col);
|
|
|
|
|
|
} else if (tp != NULL)
|
|
|
|
|
|
end = tp_col = tp->col + 1;
|
|
|
|
|
|
else if (_range != NULL) {
|
|
|
|
|
|
if (_range->start >= start)
|
|
|
|
|
|
uri_col = _range->start + 1;
|
|
|
|
|
|
else
|
|
|
|
|
|
uri_col = _range->end + 1;
|
|
|
|
|
|
end = uri_col;
|
|
|
|
|
|
} else
|
|
|
|
|
|
end = col_count;
|
|
|
|
|
|
|
|
|
|
|
|
int cols = end - start;
|
|
|
|
|
|
xassert(cols > 0);
|
|
|
|
|
|
xassert(start + cols <= old_cols);
|
|
|
|
|
|
|
|
|
|
|
|
bool tp_break = tp_col == end;
|
|
|
|
|
|
bool uri_break = uri_col == end;
|
|
|
|
|
|
|
|
|
|
|
|
for (int count = cols, from = start; count > 0;) {
|
|
|
|
|
|
xassert(new_col_idx <= new_cols);
|
|
|
|
|
|
int new_row_cells_left = new_cols - new_col_idx;
|
|
|
|
|
|
|
|
|
|
|
|
if (new_row_cells_left <= 0) {
|
|
|
|
|
|
line_wrap();
|
|
|
|
|
|
new_row_cells_left = new_cols;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int amount = min(count, new_row_cells_left);
|
|
|
|
|
|
xassert(amount > 0);
|
|
|
|
|
|
|
|
|
|
|
|
int spacers = 0;
|
|
|
|
|
|
if (new_col_idx + amount >= new_cols) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* The cell *after* the last cell is a CELL_SPACER
|
|
|
|
|
|
*
|
|
|
|
|
|
* This means we have a multi-column character
|
|
|
|
|
|
* that doesn’t fit on the current row. We need to
|
|
|
|
|
|
* push it to the next row, and insert CELL_SPACER
|
|
|
|
|
|
* cells as padding.
|
|
|
|
|
|
*/
|
|
|
|
|
|
while (
|
|
|
|
|
|
unlikely(
|
|
|
|
|
|
amount > 1 &&
|
|
|
|
|
|
from + amount < old_cols &&
|
|
|
|
|
|
old_row->cells[from + amount].wc >= CELL_SPACER + 1))
|
|
|
|
|
|
{
|
|
|
|
|
|
amount--;
|
|
|
|
|
|
spacers++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xassert(
|
|
|
|
|
|
amount == 1 ||
|
|
|
|
|
|
old_row->cells[from + amount - 1].wc <= CELL_SPACER + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xassert(new_col_idx + amount <= new_cols);
|
|
|
|
|
|
xassert(from + amount <= old_cols);
|
|
|
|
|
|
|
|
|
|
|
|
memcpy(
|
|
|
|
|
|
&new_row->cells[new_col_idx], &old_row->cells[from],
|
|
|
|
|
|
amount * sizeof(struct cell));
|
|
|
|
|
|
|
|
|
|
|
|
count -= amount;
|
|
|
|
|
|
from += amount;
|
|
|
|
|
|
new_col_idx += amount;
|
|
|
|
|
|
|
|
|
|
|
|
if (unlikely(spacers > 0)) {
|
|
|
|
|
|
xassert(new_col_idx + spacers == new_cols);
|
|
|
|
|
|
|
|
|
|
|
|
const struct cell *cell = &old_row->cells[from + amount - 1];
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < spacers; i++, new_col_idx++) {
|
|
|
|
|
|
new_row->cells[new_col_idx].wc = CELL_SPACER;
|
|
|
|
|
|
new_row->cells[new_col_idx].attrs = cell->attrs;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
new_col_idx--;
|
|
|
|
|
|
|
|
|
|
|
|
if (tp_break) {
|
|
|
|
|
|
do {
|
|
|
|
|
|
xassert(tp != NULL);
|
|
|
|
|
|
xassert(tp->row == old_row_idx);
|
|
|
|
|
|
xassert(tp->col == start + cols - 1);
|
|
|
|
|
|
|
|
|
|
|
|
tp->row = new_row_idx;
|
|
|
|
|
|
tp->col = new_col_idx;
|
|
|
|
|
|
|
|
|
|
|
|
next_tp++;
|
|
|
|
|
|
tp = *next_tp;
|
|
|
|
|
|
} while (tp->row == old_row_idx && tp->col == start + cols - 1);
|
|
|
|
|
|
|
|
|
|
|
|
if (tp->row != old_row_idx)
|
|
|
|
|
|
tp = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (uri_break) {
|
|
|
|
|
|
if (_range->start == start + cols - 1)
|
|
|
|
|
|
reflow_uri_range_start(_range, new_row, new_col_idx);
|
|
|
|
|
|
|
|
|
|
|
|
if (_range->end == start + cols - 1) {
|
|
|
|
|
|
reflow_uri_range_end(_range, new_row, new_col_idx);
|
|
|
|
|
|
|
|
|
|
|
|
xassert(&tll_front(old_row->extra->uri_ranges) == _range);
|
|
|
|
|
|
grid_row_uri_range_destroy(_range);
|
|
|
|
|
|
tll_pop_front(old_row->extra->uri_ranges);
|
|
|
|
|
|
|
|
|
|
|
|
_range = tll_length(old_row->extra->uri_ranges) > 0
|
|
|
|
|
|
? &tll_front(old_row->extra->uri_ranges)
|
|
|
|
|
|
: NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
new_col_idx++;
|
|
|
|
|
|
|
|
|
|
|
|
left -= cols;
|
|
|
|
|
|
start += cols;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
|
|
|
|
|
|
if (old_row->linebreak) {
|
2021-05-15 15:15:32 +02:00
|
|
|
|
/* Erase the remaining cells */
|
|
|
|
|
|
memset(&new_row->cells[new_col_idx], 0,
|
|
|
|
|
|
(new_cols - new_col_idx) * sizeof(new_row->cells[0]));
|
2020-02-15 22:19:08 +01:00
|
|
|
|
new_row->linebreak = true;
|
2020-04-17 20:46:08 +02:00
|
|
|
|
line_wrap();
|
2020-02-15 22:19:08 +01:00
|
|
|
|
}
|
2020-04-17 20:46:08 +02:00
|
|
|
|
|
2021-05-16 10:11:41 +02:00
|
|
|
|
grid_row_free(old_grid[old_row_idx]);
|
|
|
|
|
|
grid->rows[old_row_idx] = NULL;
|
|
|
|
|
|
|
2020-04-17 20:46:08 +02:00
|
|
|
|
#undef line_wrap
|
2020-02-15 22:19:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-15 15:15:32 +02:00
|
|
|
|
/* Erase the remaining cells */
|
|
|
|
|
|
memset(&new_row->cells[new_col_idx], 0,
|
|
|
|
|
|
(new_cols - new_col_idx) * sizeof(new_row->cells[0]));
|
|
|
|
|
|
|
grid: reflow: memcpy() chunks of cells, instead of single cell-by-cell
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.
2021-05-23 21:15:06 +02:00
|
|
|
|
for (struct coord **tp = next_tp; *tp != &terminator; tp++) {
|
|
|
|
|
|
LOG_DBG("TP: row=%d, col=%d",
|
|
|
|
|
|
(*tp)->row, (*tp)->col);
|
|
|
|
|
|
}
|
2021-05-15 11:40:39 +02:00
|
|
|
|
xassert(old_rows == 0 || *next_tp == &terminator);
|
|
|
|
|
|
|
2021-02-14 20:34:49 +01:00
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
|
/* Verify all URI ranges have been “closed” */
|
|
|
|
|
|
for (int r = 0; r < new_rows; r++) {
|
|
|
|
|
|
const struct row *row = new_grid[r];
|
|
|
|
|
|
|
|
|
|
|
|
if (row == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
if (row->extra == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(row->extra->uri_ranges, it)
|
|
|
|
|
|
xassert(it->item.end >= 0);
|
|
|
|
|
|
}
|
2021-05-16 10:11:41 +02:00
|
|
|
|
|
|
|
|
|
|
/* Verify all old rows have been free:d */
|
|
|
|
|
|
for (int i = 0; i < old_rows; i++)
|
|
|
|
|
|
xassert(grid->rows[i] == NULL);
|
2021-02-14 20:34:49 +01:00
|
|
|
|
#endif
|
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
/* Set offset such that the last reflowed row is at the bottom */
|
|
|
|
|
|
grid->offset = new_row_idx - new_screen_rows + 1;
|
|
|
|
|
|
while (grid->offset < 0)
|
|
|
|
|
|
grid->offset += new_rows;
|
|
|
|
|
|
while (new_grid[grid->offset] == NULL)
|
|
|
|
|
|
grid->offset = (grid->offset + 1) & (new_rows - 1);
|
2020-09-24 18:35:40 +02:00
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
/* Ensure all visible rows have been allocated */
|
|
|
|
|
|
for (int r = 0; r < new_screen_rows; r++) {
|
|
|
|
|
|
int idx = (grid->offset + r) & (new_rows - 1);
|
|
|
|
|
|
if (new_grid[idx] == NULL)
|
|
|
|
|
|
new_grid[idx] = grid_row_alloc(new_cols, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-24 18:48:41 +02:00
|
|
|
|
grid->view = view_follows ? grid->offset : viewport.row;
|
|
|
|
|
|
|
|
|
|
|
|
/* If enlarging the window, the old viewport may be too far down,
|
|
|
|
|
|
* with unallocated rows. Make sure this cannot happen */
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
int idx = (grid->view + new_screen_rows - 1) & (new_rows - 1);
|
|
|
|
|
|
if (new_grid[idx] != NULL)
|
|
|
|
|
|
break;
|
|
|
|
|
|
grid->view--;
|
|
|
|
|
|
if (grid->view < 0)
|
|
|
|
|
|
grid->view += new_rows;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (size_t r = 0; r < new_screen_rows; r++) {
|
2020-09-24 18:53:05 +02:00
|
|
|
|
int UNUSED idx = (grid->view + r) & (new_rows - 1);
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(new_grid[idx] != NULL);
|
2020-09-24 18:48:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-16 10:11:41 +02:00
|
|
|
|
/* Free old grid (rows already free:d) */
|
2020-02-15 22:19:08 +01:00
|
|
|
|
free(grid->rows);
|
|
|
|
|
|
|
|
|
|
|
|
grid->rows = new_grid;
|
|
|
|
|
|
grid->num_rows = new_rows;
|
|
|
|
|
|
grid->num_cols = new_cols;
|
|
|
|
|
|
|
2020-04-16 19:38:30 +02:00
|
|
|
|
/* Convert absolute coordinates to screen relative */
|
2020-04-17 21:00:37 +02:00
|
|
|
|
cursor.row -= grid->offset;
|
|
|
|
|
|
while (cursor.row < 0)
|
|
|
|
|
|
cursor.row += grid->num_rows;
|
2020-09-09 18:40:06 +02:00
|
|
|
|
cursor.row = min(cursor.row, new_screen_rows - 1);
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
cursor.col = min(cursor.col, new_cols - 1);
|
2020-04-16 19:38:30 +02:00
|
|
|
|
|
2020-04-17 21:00:37 +02:00
|
|
|
|
saved_cursor.row -= grid->offset;
|
|
|
|
|
|
while (saved_cursor.row < 0)
|
|
|
|
|
|
saved_cursor.row += grid->num_rows;
|
2020-09-09 18:40:06 +02:00
|
|
|
|
saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1);
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
saved_cursor.col = min(saved_cursor.col, new_cols - 1);
|
2020-04-16 19:38:30 +02:00
|
|
|
|
|
resize: don’t reflow text on alt screen
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
2020-11-24 19:00:57 +01:00
|
|
|
|
grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)];
|
2020-04-17 21:00:37 +02:00
|
|
|
|
grid->cursor.point = cursor;
|
|
|
|
|
|
grid->saved_cursor.point = saved_cursor;
|
2020-04-16 19:38:30 +02:00
|
|
|
|
|
|
|
|
|
|
grid->cursor.lcf = false;
|
|
|
|
|
|
grid->saved_cursor.lcf = false;
|
|
|
|
|
|
|
2020-06-29 21:59:40 +02:00
|
|
|
|
/* Free sixels we failed to "map" to the new grid */
|
2020-10-03 23:00:34 +02:00
|
|
|
|
tll_foreach(untranslated_sixels, it)
|
2020-03-13 18:44:23 +01:00
|
|
|
|
sixel_destroy(&it->item);
|
2020-10-03 23:00:34 +02:00
|
|
|
|
tll_free(untranslated_sixels);
|
|
|
|
|
|
|
2021-05-11 17:43:17 +02:00
|
|
|
|
#if defined(TIME_REFLOW) && TIME_REFLOW
|
|
|
|
|
|
struct timeval stop;
|
|
|
|
|
|
gettimeofday(&stop, NULL);
|
|
|
|
|
|
|
|
|
|
|
|
struct timeval diff;
|
|
|
|
|
|
timersub(&stop, &start, &diff);
|
2021-05-15 12:11:58 +02:00
|
|
|
|
LOG_INFO("reflowed %d -> %d rows in %llds %lldµs",
|
|
|
|
|
|
old_rows, new_rows,
|
|
|
|
|
|
(long long)diff.tv_sec,
|
|
|
|
|
|
(long long)diff.tv_usec);
|
2021-05-11 17:43:17 +02:00
|
|
|
|
#endif
|
2020-02-15 22:19:08 +01:00
|
|
|
|
}
|
2021-02-14 20:50:33 +01:00
|
|
|
|
|
2021-02-14 20:52:26 +01:00
|
|
|
|
static void
|
|
|
|
|
|
ensure_row_has_extra_data(struct row *row)
|
2021-02-14 20:50:33 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (row->extra == NULL)
|
|
|
|
|
|
row->extra = xcalloc(1, sizeof(*row->extra));
|
2021-02-14 20:52:26 +01:00
|
|
|
|
}
|
2021-02-14 20:50:33 +01:00
|
|
|
|
|
2021-02-14 20:52:26 +01:00
|
|
|
|
void
|
|
|
|
|
|
grid_row_add_uri_range(struct row *row, struct row_uri_range range)
|
|
|
|
|
|
{
|
|
|
|
|
|
ensure_row_has_extra_data(row);
|
2021-05-22 17:12:58 +02:00
|
|
|
|
tll_rforeach(row->extra->uri_ranges, it) {
|
|
|
|
|
|
if (it->item.end < range.start) {
|
|
|
|
|
|
tll_insert_after(row->extra->uri_ranges, it, range);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
tll_push_front(row->extra->uri_ranges, range);
|
2021-02-14 20:50:33 +01:00
|
|
|
|
}
|