2019-06-17 19:33:10 +02:00
|
|
|
#include "grid.h"
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
#include <string.h>
|
2019-06-17 21:15:20 +02:00
|
|
|
#include <assert.h>
|
|
|
|
|
|
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"
|
2020-03-13 18:44:23 +01:00
|
|
|
#include "sixel.h"
|
2019-07-01 12:23:38 +02:00
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
#define max(x, y) ((x) > (y) ? (x) : (y))
|
|
|
|
|
|
2019-07-01 12:23:38 +02:00
|
|
|
void
|
2019-08-22 17:33:23 +02:00
|
|
|
grid_swap_row(struct grid *grid, int row_a, int row_b, bool initialize)
|
2019-07-01 12:23:38 +02:00
|
|
|
{
|
2019-07-10 16:08:53 +02:00
|
|
|
assert(grid->offset >= 0);
|
|
|
|
|
assert(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
|
|
|
{
|
|
|
|
|
struct row *row = malloc(sizeof(*row));
|
2019-08-22 17:33:23 +02:00
|
|
|
row->dirty = false;
|
2020-02-14 22:39:26 +01:00
|
|
|
row->linebreak = false;
|
2019-08-22 17:33:23 +02:00
|
|
|
|
|
|
|
|
if (initialize) {
|
|
|
|
|
row->cells = calloc(cols, sizeof(row->cells[0]));
|
|
|
|
|
for (size_t c = 0; c < cols; c++)
|
|
|
|
|
row->cells[c].attrs.clean = 1;
|
|
|
|
|
} else
|
|
|
|
|
row->cells = malloc(cols * sizeof(row->cells[0]));
|
|
|
|
|
|
2019-07-10 16:27:55 +02:00
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
grid_row_free(struct row *row)
|
|
|
|
|
{
|
|
|
|
|
if (row == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
free(row->cells);
|
|
|
|
|
free(row);
|
|
|
|
|
}
|
2020-02-15 22:19:08 +01:00
|
|
|
|
2020-04-16 19:38:30 +02:00
|
|
|
void
|
2020-02-15 22:19:08 +01:00
|
|
|
grid_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;
|
|
|
|
|
|
2020-02-25 20:33:27 +01:00
|
|
|
//assert(old_rows != new_rows || old_cols != new_cols);
|
2020-02-15 22:19:08 +01:00
|
|
|
|
|
|
|
|
int new_col_idx = 0;
|
|
|
|
|
int new_row_idx = 0;
|
|
|
|
|
|
|
|
|
|
struct row **new_grid = calloc(new_rows, sizeof(new_grid[0]));
|
|
|
|
|
struct row *new_row = new_grid[new_row_idx];
|
|
|
|
|
|
|
|
|
|
assert(new_row == NULL);
|
|
|
|
|
new_row = grid_row_alloc(new_cols, true);
|
|
|
|
|
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-03-13 18:44:23 +01:00
|
|
|
tll(struct sixel) new_sixels = tll_init();
|
|
|
|
|
|
2020-04-16 19:38:30 +02:00
|
|
|
/* Turn cursor coordinates into grid absolute coordinates */
|
|
|
|
|
struct coord cursor = grid->cursor.point;
|
|
|
|
|
struct coord new_cursor = {};
|
|
|
|
|
cursor.row += grid->offset;
|
|
|
|
|
cursor.row &= old_rows - 1;
|
|
|
|
|
|
|
|
|
|
struct coord saved_cursor = grid->saved_cursor.point;
|
|
|
|
|
struct coord new_saved_cursor = {};
|
|
|
|
|
saved_cursor.row += grid->offset;
|
|
|
|
|
saved_cursor.row &= old_rows - 1;
|
|
|
|
|
|
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-03-13 18:44:23 +01:00
|
|
|
/*
|
|
|
|
|
* Update 'row' in all sixels that *begin* at current row
|
|
|
|
|
*
|
|
|
|
|
* Since we might end up pushing the sixel down, we can't
|
|
|
|
|
* simply update the row inline - we'd then end up pushing the
|
|
|
|
|
* sixel down again, when we reach the next 'old'
|
|
|
|
|
* row. Instead, copy the sixel (with 'row' updated), to a
|
|
|
|
|
* temporary list and remove the original sixel.
|
|
|
|
|
*
|
|
|
|
|
* After we've reflowed the grid we'll move the sixels back to
|
|
|
|
|
* the "real" sixel list.
|
|
|
|
|
*/
|
|
|
|
|
tll_foreach(grid->sixel_images, it) {
|
2020-04-16 19:38:30 +02:00
|
|
|
if (it->item.pos.row == old_row_idx) {
|
2020-03-13 18:44:23 +01:00
|
|
|
struct sixel six = it->item;
|
|
|
|
|
six.pos.row = new_row_idx;
|
|
|
|
|
|
|
|
|
|
tll_push_back(new_sixels, six);
|
|
|
|
|
tll_remove(grid->sixel_images, it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
/*
|
|
|
|
|
* Keep track of empty cells. If the old line ends with a
|
|
|
|
|
* string of empty cells, we don't need to, nor do we want to,
|
|
|
|
|
* add those to the new line. However, if there are non-empty
|
|
|
|
|
* cells *after* the string of empty cells, we need to emit
|
|
|
|
|
* the empty cells too. And that may trigger linebreaks
|
|
|
|
|
*/
|
|
|
|
|
int empty_count = 0;
|
|
|
|
|
|
|
|
|
|
/* Walk current line of the old grid */
|
|
|
|
|
for (int c = 0; c < old_cols; c++) {
|
2020-04-16 19:38:30 +02:00
|
|
|
bool has_cursor = cursor.row == old_row_idx && cursor.col == c;
|
|
|
|
|
bool has_saved_cursor
|
|
|
|
|
= saved_cursor.row == old_row_idx && saved_cursor.col == c;
|
|
|
|
|
|
|
|
|
|
if (old_row->cells[c].wc == 0 && !has_cursor && !has_saved_cursor) {
|
|
|
|
|
assert(!has_cursor);
|
|
|
|
|
assert(!has_saved_cursor);
|
2020-02-15 22:19:08 +01:00
|
|
|
empty_count++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int old_cols_left = old_cols - c;
|
|
|
|
|
int cols_needed = empty_count + old_cols_left;
|
|
|
|
|
int new_cols_left = new_cols - new_col_idx;
|
|
|
|
|
if (new_cols_left < cols_needed && new_cols_left >= old_cols_left)
|
|
|
|
|
empty_count = max(0, empty_count - (cols_needed - new_cols_left));
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < empty_count + 1; i++) {
|
|
|
|
|
const struct cell *old_cell = &old_row->cells[c - empty_count + i];
|
|
|
|
|
|
|
|
|
|
/* Out of columns on current row in new grid? */
|
|
|
|
|
if (new_col_idx >= new_cols) {
|
2020-04-17 20:33:08 +02:00
|
|
|
#if 0
|
2020-02-15 22:19:08 +01:00
|
|
|
/*
|
|
|
|
|
* If last cell on last row and first cell on new
|
|
|
|
|
* row are non-empty, wrap the line, otherwise
|
|
|
|
|
* insert a hard line break.
|
|
|
|
|
*/
|
|
|
|
|
if (new_row->cells[new_cols - 1].wc == 0 ||
|
|
|
|
|
old_cell->wc == 0)
|
|
|
|
|
{
|
|
|
|
|
new_row->linebreak = true;
|
|
|
|
|
}
|
2020-04-17 20:33:08 +02:00
|
|
|
#endif
|
2020-02-15 22:19:08 +01:00
|
|
|
|
|
|
|
|
new_col_idx = 0;
|
|
|
|
|
new_row_idx = (new_row_idx + 1) & (new_rows - 1);
|
|
|
|
|
|
|
|
|
|
new_row = new_grid[new_row_idx];
|
|
|
|
|
if (new_row == NULL) {
|
|
|
|
|
new_row = grid_row_alloc(new_cols, true);
|
|
|
|
|
new_grid[new_row_idx] = new_row;
|
|
|
|
|
} else {
|
|
|
|
|
memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0]));
|
|
|
|
|
new_row->linebreak = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(new_row != NULL);
|
|
|
|
|
assert(new_col_idx >= 0);
|
|
|
|
|
assert(new_col_idx < new_cols);
|
|
|
|
|
|
|
|
|
|
new_row->cells[new_col_idx] = *old_cell;
|
|
|
|
|
new_row->cells[new_col_idx].attrs.clean = 1;
|
2020-04-16 19:38:30 +02:00
|
|
|
|
|
|
|
|
if (has_cursor)
|
|
|
|
|
new_cursor = (struct coord){new_col_idx, new_row_idx};
|
|
|
|
|
if (has_saved_cursor)
|
|
|
|
|
new_saved_cursor = (struct coord){new_col_idx, new_row_idx};
|
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
new_col_idx++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
empty_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (old_row->linebreak) {
|
|
|
|
|
new_row->linebreak = true;
|
|
|
|
|
|
|
|
|
|
new_col_idx = 0;
|
|
|
|
|
new_row_idx = (new_row_idx + 1) & (new_rows - 1);
|
|
|
|
|
|
|
|
|
|
new_row = new_grid[new_row_idx];
|
|
|
|
|
if (new_row == NULL) {
|
|
|
|
|
new_row = grid_row_alloc(new_cols, true);
|
|
|
|
|
new_grid[new_row_idx] = new_row;
|
|
|
|
|
} else {
|
|
|
|
|
memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0]));
|
|
|
|
|
new_row->linebreak = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
grid->view = grid->offset;
|
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Free old grid */
|
|
|
|
|
for (int r = 0; r < grid->num_rows; r++)
|
|
|
|
|
grid_row_free(old_grid[r]);
|
|
|
|
|
free(grid->rows);
|
|
|
|
|
|
2020-04-16 19:38:30 +02:00
|
|
|
grid->cur_row = new_grid[new_cursor.row];
|
2020-02-15 22:19:08 +01:00
|
|
|
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 */
|
|
|
|
|
new_cursor.row -= grid->offset;
|
|
|
|
|
while (new_cursor.row < 0)
|
|
|
|
|
new_cursor.row += grid->num_rows;
|
|
|
|
|
|
|
|
|
|
assert(new_cursor.row >= 0);
|
|
|
|
|
assert(new_cursor.row < grid->num_rows);
|
|
|
|
|
|
|
|
|
|
new_saved_cursor.row -= grid->offset;
|
|
|
|
|
while (new_saved_cursor.row < 0)
|
|
|
|
|
new_saved_cursor.row += grid->num_rows;
|
|
|
|
|
|
|
|
|
|
assert(new_saved_cursor.row >= 0);
|
|
|
|
|
assert(new_saved_cursor.row < grid->num_rows);
|
|
|
|
|
|
|
|
|
|
grid->cursor.point = new_cursor;
|
|
|
|
|
grid->saved_cursor.point = new_saved_cursor;
|
|
|
|
|
|
|
|
|
|
grid->cursor.lcf = false;
|
|
|
|
|
grid->saved_cursor.lcf = false;
|
|
|
|
|
|
2020-03-13 18:44:23 +01:00
|
|
|
/* Destroy any non-moved sixels */
|
|
|
|
|
tll_foreach(grid->sixel_images, it)
|
|
|
|
|
sixel_destroy(&it->item);
|
|
|
|
|
tll_free(grid->sixel_images);
|
|
|
|
|
|
|
|
|
|
/* Move updated sixels back */
|
|
|
|
|
tll_foreach(new_sixels, it)
|
|
|
|
|
tll_push_back(grid->sixel_images, it->item);
|
|
|
|
|
tll_free(new_sixels);
|
2020-02-15 22:19:08 +01:00
|
|
|
}
|