forked from dnkl/foot
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
858 lines
27 KiB
858 lines
27 KiB
#include "grid.h" |
|
|
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
#define LOG_MODULE "grid" |
|
#define LOG_ENABLE_DBG 0 |
|
#include "log.h" |
|
#include "debug.h" |
|
#include "macros.h" |
|
#include "sixel.h" |
|
#include "stride.h" |
|
#include "util.h" |
|
#include "xmalloc.h" |
|
|
|
#define TIME_REFLOW 0 |
|
|
|
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)); |
|
|
|
tll_foreach(grid->scroll_damage, it) |
|
tll_push_back(clone->scroll_damage, it->item); |
|
|
|
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; |
|
clone_row->dirty = row->dirty; |
|
|
|
for (int c = 0; c < grid->num_cols; c++) |
|
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; |
|
} |
|
|
|
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); |
|
tll_free(grid->scroll_damage); |
|
} |
|
|
|
void |
|
grid_swap_row(struct grid *grid, int row_a, int row_b) |
|
{ |
|
xassert(grid->offset >= 0); |
|
xassert(row_a != row_b); |
|
|
|
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]; |
|
|
|
grid->rows[real_a] = b; |
|
grid->rows[real_b] = a; |
|
} |
|
|
|
struct row * |
|
grid_row_alloc(int cols, bool initialize) |
|
{ |
|
struct row *row = xmalloc(sizeof(*row)); |
|
row->dirty = false; |
|
row->linebreak = false; |
|
row->extra = NULL; |
|
|
|
if (initialize) { |
|
row->cells = xcalloc(cols, sizeof(row->cells[0])); |
|
for (size_t c = 0; c < cols; c++) |
|
row->cells[c].attrs.clean = 1; |
|
} else |
|
row->cells = xmalloc(cols * sizeof(row->cells[0])); |
|
|
|
return row; |
|
} |
|
|
|
void |
|
grid_row_free(struct row *row) |
|
{ |
|
if (row == NULL) |
|
return; |
|
|
|
grid_row_reset_extra(row); |
|
free(row->extra); |
|
free(row->cells); |
|
free(row); |
|
} |
|
|
|
void |
|
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 */ |
|
for (int r = 0, n = min(old_screen_rows, new_screen_rows); r < n; r++) { |
|
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]; |
|
xassert(old_row != NULL); |
|
|
|
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) { |
|
/* Clear "new" columns */ |
|
memset(&new_row->cells[old_cols], 0, |
|
sizeof(struct cell) * (new_cols - old_cols)); |
|
new_row->dirty = true; |
|
} 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; |
|
} |
|
|
|
/* 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); |
|
} |
|
|
|
/* 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), |
|
}; |
|
grid_row_add_uri_range(new_row, range); |
|
} |
|
} |
|
|
|
/* 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 |
|
} |
|
|
|
static void |
|
reflow_uri_range_start(struct row_uri_range *range, struct row *new_row, |
|
int new_col_idx) |
|
{ |
|
struct row_uri_range new_range = { |
|
.start = new_col_idx, |
|
.end = -1, |
|
.id = range->id, |
|
.uri = range->uri, |
|
}; |
|
range->uri = NULL; |
|
grid_row_add_uri_range(new_row, new_range); |
|
} |
|
|
|
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); |
|
|
|
xassert(new_range->id == range->id); |
|
xassert(new_range->end < 0); |
|
new_range->end = new_col_idx; |
|
} |
|
|
|
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 */ |
|
new_row = grid_row_alloc(col_count, false); |
|
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. |
|
*/ |
|
if (tll_length(row->extra->uri_ranges) > 0) { |
|
struct row_uri_range *range = &tll_back(row->extra->uri_ranges); |
|
if (range->end < 0) { |
|
|
|
/* Terminate URI range on the previous row */ |
|
range->end = col_count - 1; |
|
|
|
/* 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); |
|
} |
|
} |
|
|
|
return new_row; |
|
} |
|
|
|
static struct { |
|
int scrollback_start; |
|
int rows; |
|
} tp_cmp_ctx; |
|
|
|
static int |
|
tp_cmp(const void *_a, const void *_b) |
|
{ |
|
const struct coord *a = *(const struct coord **)_a; |
|
const struct coord *b = *(const struct coord **)_b; |
|
|
|
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); |
|
|
|
xassert(a_row >= 0); |
|
xassert(a_row < num_rows || num_rows == 0); |
|
xassert(b_row >= 0); |
|
xassert(b_row < num_rows || num_rows == 0); |
|
|
|
if (a_row < b_row) |
|
return -1; |
|
if (a_row > b_row) |
|
return 1; |
|
|
|
xassert(a_row == b_row); |
|
|
|
if (a->col < b->col) |
|
return -1; |
|
if (a->col > b->col) |
|
return 1; |
|
|
|
xassert(a->col == b->col); |
|
return 0; |
|
} |
|
|
|
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]) |
|
{ |
|
#if defined(TIME_REFLOW) && TIME_REFLOW |
|
struct timeval start; |
|
gettimeofday(&start, NULL); |
|
#endif |
|
|
|
struct row *const *old_grid = grid->rows; |
|
const int old_rows = grid->num_rows; |
|
const int old_cols = grid->num_cols; |
|
|
|
/* Is viewpoint tracking current grid offset? */ |
|
const bool view_follows = grid->view == grid->offset; |
|
|
|
int new_col_idx = 0; |
|
int new_row_idx = 0; |
|
|
|
struct row **new_grid = xcalloc(new_rows, sizeof(new_grid[0])); |
|
struct row *new_row = new_grid[new_row_idx]; |
|
|
|
xassert(new_row == NULL); |
|
new_row = grid_row_alloc(new_cols, false); |
|
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; |
|
|
|
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); |
|
|
|
/* 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; |
|
|
|
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; |
|
|
|
struct coord viewport = {0, grid->view}; |
|
if (!view_follows) |
|
tracking_points[tracking_points_count + 2] = &viewport; |
|
|
|
/* 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); |
|
|
|
/* NULL terminate */ |
|
struct coord terminator = {-1, -1}; |
|
tracking_points[tp_count - 1] = &terminator; |
|
struct coord **next_tp = &tracking_points[0]; |
|
|
|
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); |
|
} |
|
|
|
/* |
|
* Walk the old grid |
|
*/ |
|
for (int r = 0; r < old_rows; r++) { |
|
|
|
const size_t old_row_idx = (offset + r) & (old_rows - 1); |
|
|
|
/* Unallocated (empty) rows we can simply skip */ |
|
const struct row *old_row = old_grid[old_row_idx]; |
|
if (old_row == NULL) |
|
continue; |
|
|
|
/* 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; |
|
|
|
tll_push_back(grid->sixel_images, sixel); |
|
tll_remove(untranslated_sixels, it); |
|
} |
|
|
|
#define line_wrap() \ |
|
new_row = _line_wrap( \ |
|
grid, new_grid, new_row, &new_row_idx, &new_col_idx, \ |
|
new_rows, new_cols) |
|
|
|
/* 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); |
|
|
|
/* Do we have a (at least one) tracking point on this row */ |
|
struct coord *tp; |
|
if (unlikely((*next_tp)->row == old_row_idx)) { |
|
tp = *next_tp; |
|
|
|
/* Find the *last* tracking point on this row */ |
|
struct coord *last_on_row = tp; |
|
for (struct coord **iter = next_tp; (*iter)->row == old_row_idx; iter++) |
|
last_on_row = *iter; |
|
|
|
/* And make sure its end point is included in the col range */ |
|
xassert(last_on_row->row == old_row_idx); |
|
col_count = max(col_count, last_on_row->col + 1); |
|
} else |
|
tp = NULL; |
|
|
|
/* Does this row have any URIs? */ |
|
struct row_uri_range *range; |
|
if (old_row->extra != NULL && tll_length(old_row->extra->uri_ranges) > 0) { |
|
range = &tll_front(old_row->extra->uri_ranges); |
|
|
|
/* Make sure the *last* URI range's end point is included in the copy */ |
|
const struct row_uri_range *last_on_row = |
|
&tll_back(old_row->extra->uri_ranges); |
|
col_count = max(col_count, last_on_row->end + 1); |
|
} else |
|
range = NULL; |
|
|
|
for (int start = 0, left = col_count; left > 0;) { |
|
int end; |
|
bool tp_break = false; |
|
bool uri_break = false; |
|
|
|
/* |
|
* Set end-coordinate for this chunk, by finding the next |
|
* point-of-interrest on this row. |
|
* |
|
* If there are no more tracking points, or URI ranges, |
|
* the end-coordinate will be at the end of the row, |
|
*/ |
|
if (range != NULL) { |
|
int uri_col = (range->start >= start ? range->start : range->end) + 1; |
|
|
|
if (tp != NULL) { |
|
int tp_col = tp->col + 1; |
|
end = min(tp_col, uri_col); |
|
|
|
tp_break = end == tp_col; |
|
uri_break = end == uri_col; |
|
LOG_DBG("tp+uri break at %d (%d, %d)", end, tp_col, uri_col); |
|
} else { |
|
end = uri_col; |
|
uri_break = true; |
|
LOG_DBG("uri break at %d", end); |
|
} |
|
} else if (tp != NULL) { |
|
end = tp->col + 1; |
|
tp_break = true; |
|
LOG_DBG("TP break at %d", end); |
|
} else |
|
end = col_count; |
|
|
|
int cols = end - start; |
|
xassert(cols > 0); |
|
xassert(start + cols <= old_cols); |
|
|
|
/* |
|
* Copy the row chunk to the new grid. Note that there may |
|
* be fewer cells left on the new row than what we have in |
|
* the chunk. I.e. the chunk may have to be split up into |
|
* multiple memcpy:ies. |
|
*/ |
|
|
|
for (int count = cols, from = start; count > 0;) { |
|
xassert(new_col_idx <= new_cols); |
|
int new_row_cells_left = new_cols - new_col_idx; |
|
|
|
/* Row full, emit newline and get a new, fresh, row */ |
|
if (new_row_cells_left <= 0) { |
|
line_wrap(); |
|
new_row_cells_left = new_cols; |
|
} |
|
|
|
/* Number of cells we can copy */ |
|
int amount = min(count, new_row_cells_left); |
|
xassert(amount > 0); |
|
|
|
/* |
|
* If we’re going to reach the end of the new row, we |
|
* need to make sure we don’t end in the middle of a |
|
* multi-column character. |
|
*/ |
|
int spacers = 0; |
|
if (new_col_idx + amount >= new_cols) { |
|
/* |
|
* While 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; |
|
|
|
xassert(new_col_idx <= new_cols); |
|
|
|
if (unlikely(spacers > 0)) { |
|
xassert(new_col_idx + spacers == new_cols); |
|
|
|
const struct cell *cell = &old_row->cells[from - 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; |
|
} |
|
} |
|
} |
|
|
|
xassert(new_col_idx > 0); |
|
|
|
if (tp_break) { |
|
do { |
|
xassert(tp != NULL); |
|
xassert(tp->row == old_row_idx); |
|
xassert(tp->col == end - 1); |
|
|
|
tp->row = new_row_idx; |
|
tp->col = new_col_idx - 1; |
|
|
|
next_tp++; |
|
tp = *next_tp; |
|
} while (tp->row == old_row_idx && tp->col == end - 1); |
|
|
|
if (tp->row != old_row_idx) |
|
tp = NULL; |
|
|
|
LOG_DBG("next TP (tp=%p): %dx%d", |
|
(void*)tp, (*next_tp)->row, (*next_tp)->col); |
|
} |
|
|
|
if (uri_break) { |
|
if (range->start == end - 1) |
|
reflow_uri_range_start(range, new_row, new_col_idx - 1); |
|
|
|
if (range->end == end - 1) { |
|
reflow_uri_range_end(range, new_row, new_col_idx - 1); |
|
|
|
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; |
|
} |
|
} |
|
|
|
left -= cols; |
|
start += cols; |
|
} |
|
|
|
|
|
if (old_row->linebreak) { |
|
/* Erase the remaining cells */ |
|
memset(&new_row->cells[new_col_idx], 0, |
|
(new_cols - new_col_idx) * sizeof(new_row->cells[0])); |
|
new_row->linebreak = true; |
|
line_wrap(); |
|
} |
|
|
|
grid_row_free(old_grid[old_row_idx]); |
|
grid->rows[old_row_idx] = NULL; |
|
|
|
#undef line_wrap |
|
} |
|
|
|
/* Erase the remaining cells */ |
|
memset(&new_row->cells[new_col_idx], 0, |
|
(new_cols - new_col_idx) * sizeof(new_row->cells[0])); |
|
|
|
for (struct coord **tp = next_tp; *tp != &terminator; tp++) { |
|
LOG_DBG("TP: row=%d, col=%d (old cols: %d, new cols: %d)", |
|
(*tp)->row, (*tp)->col, old_cols, new_cols); |
|
} |
|
xassert(old_rows == 0 || *next_tp == &terminator); |
|
|
|
#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); |
|
} |
|
|
|
/* Verify all old rows have been free:d */ |
|
for (int i = 0; i < old_rows; i++) |
|
xassert(grid->rows[i] == NULL); |
|
#endif |
|
|
|
/* 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); |
|
|
|
/* 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); |
|
} |
|
|
|
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++) { |
|
int UNUSED idx = (grid->view + r) & (new_rows - 1); |
|
xassert(new_grid[idx] != NULL); |
|
} |
|
|
|
/* Free old grid (rows already free:d) */ |
|
free(grid->rows); |
|
|
|
grid->rows = new_grid; |
|
grid->num_rows = new_rows; |
|
grid->num_cols = new_cols; |
|
|
|
/* Convert absolute coordinates to screen relative */ |
|
cursor.row -= grid->offset; |
|
while (cursor.row < 0) |
|
cursor.row += grid->num_rows; |
|
cursor.row = min(cursor.row, new_screen_rows - 1); |
|
cursor.col = min(cursor.col, new_cols - 1); |
|
|
|
saved_cursor.row -= grid->offset; |
|
while (saved_cursor.row < 0) |
|
saved_cursor.row += grid->num_rows; |
|
saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1); |
|
saved_cursor.col = min(saved_cursor.col, new_cols - 1); |
|
|
|
grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; |
|
grid->cursor.point = cursor; |
|
grid->saved_cursor.point = saved_cursor; |
|
|
|
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(TIME_REFLOW) && TIME_REFLOW |
|
struct timeval stop; |
|
gettimeofday(&stop, NULL); |
|
|
|
struct timeval diff; |
|
timersub(&stop, &start, &diff); |
|
LOG_INFO("reflowed %d -> %d rows in %llds %lldµs", |
|
old_rows, new_rows, |
|
(long long)diff.tv_sec, |
|
(long long)diff.tv_usec); |
|
#endif |
|
} |
|
|
|
static void |
|
ensure_row_has_extra_data(struct row *row) |
|
{ |
|
if (row->extra == NULL) |
|
row->extra = xcalloc(1, sizeof(*row->extra)); |
|
} |
|
|
|
void |
|
grid_row_add_uri_range(struct row *row, struct row_uri_range range) |
|
{ |
|
ensure_row_has_extra_data(row); |
|
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); |
|
}
|
|
|