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
|
|
|
|
|
2021-05-26 12:22:28 +02:00
|
|
|
|
#define TIME_REFLOW 0
|
2021-05-11 17:43:17 +02:00
|
|
|
|
|
2022-04-25 19:57:18 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* “sb” (scrollback relative) coordinates
|
|
|
|
|
|
*
|
|
|
|
|
|
* The scrollback relative row number 0 is the *first*, and *oldest*
|
|
|
|
|
|
* row in the scrollback history (and thus the *first* row to be
|
|
|
|
|
|
* scrolled out). Thus, a higher number means further *down* in the
|
|
|
|
|
|
* scrollback, with the *highest* number being at the bottom of the
|
|
|
|
|
|
* screen, where new input appears.
|
|
|
|
|
|
*/
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
|
|
2022-04-25 19:57:18 +02:00
|
|
|
|
int
|
|
|
|
|
|
grid_row_abs_to_sb(const struct grid *grid, int screen_rows, int abs_row)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int scrollback_start = grid->offset + screen_rows;
|
|
|
|
|
|
int rebased_row = abs_row - scrollback_start + grid->num_rows;
|
|
|
|
|
|
|
|
|
|
|
|
rebased_row &= grid->num_rows - 1;
|
|
|
|
|
|
return rebased_row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int scrollback_start = grid->offset + screen_rows;
|
|
|
|
|
|
int abs_row = sb_rel_row + scrollback_start;
|
|
|
|
|
|
|
|
|
|
|
|
abs_row &= grid->num_rows - 1;
|
|
|
|
|
|
return abs_row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
|
int
|
|
|
|
|
|
grid_sb_start_ignore_uninitialized(const struct grid *grid, int screen_rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
int scrollback_start = grid->offset + screen_rows;
|
|
|
|
|
|
scrollback_start &= grid->num_rows - 1;
|
|
|
|
|
|
|
|
|
|
|
|
while (grid->rows[scrollback_start] == NULL) {
|
|
|
|
|
|
scrollback_start++;
|
|
|
|
|
|
scrollback_start &= grid->num_rows - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return scrollback_start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(const struct grid *grid, int sb_start,
|
|
|
|
|
|
int abs_row)
|
|
|
|
|
|
{
|
|
|
|
|
|
int rebased_row = abs_row - sb_start + grid->num_rows;
|
|
|
|
|
|
rebased_row &= grid->num_rows - 1;
|
|
|
|
|
|
return rebased_row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
|
grid_row_sb_to_abs_precalc_sb_start(const struct grid *grid, int sb_start,
|
|
|
|
|
|
int sb_rel_row)
|
|
|
|
|
|
{
|
|
|
|
|
|
int abs_row = sb_rel_row + sb_start;
|
|
|
|
|
|
abs_row &= grid->num_rows - 1;
|
|
|
|
|
|
return abs_row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-26 20:25:07 +01:00
|
|
|
|
static void
|
|
|
|
|
|
ensure_row_has_extra_data(struct row *row)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (row->extra == NULL)
|
|
|
|
|
|
row->extra = xcalloc(1, sizeof(*row->extra));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
verify_no_overlapping_uris(const struct row_data *extra)
|
|
|
|
|
|
{
|
|
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
|
for (size_t i = 0; i < extra->uri_ranges.count; i++) {
|
|
|
|
|
|
const struct row_uri_range *r1 = &extra->uri_ranges.v[i];
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t j = i + 1; j < extra->uri_ranges.count; j++) {
|
|
|
|
|
|
const struct row_uri_range *r2 = &extra->uri_ranges.v[j];
|
|
|
|
|
|
xassert(r1 != r2);
|
|
|
|
|
|
|
|
|
|
|
|
if ((r1->start <= r2->start && r1->end >= r2->start) ||
|
|
|
|
|
|
(r1->start <= r2->end && r1->end >= r2->end))
|
|
|
|
|
|
{
|
|
|
|
|
|
BUG("OSC-8 URI overlap: %s: %d-%d: %s: %d-%d",
|
|
|
|
|
|
r1->uri, r1->start, r1->end,
|
|
|
|
|
|
r2->uri, r2->start, r2->end);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
verify_uris_are_sorted(const struct row_data *extra)
|
|
|
|
|
|
{
|
|
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
|
const struct row_uri_range *last = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < extra->uri_ranges.count; i++) {
|
|
|
|
|
|
const struct row_uri_range *r = &extra->uri_ranges.v[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (last != NULL) {
|
|
|
|
|
|
if (last->start >= r->start || last->end >= r->end) {
|
|
|
|
|
|
BUG("OSC-8 URI not sorted correctly: "
|
|
|
|
|
|
"%s: %d-%d came before %s: %d-%d",
|
|
|
|
|
|
last->uri, last->start, last->end,
|
|
|
|
|
|
r->uri, r->start, r->end);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
last = r;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-12-08 17:41:29 +01:00
|
|
|
|
uri_range_ensure_size(struct row_data *extra, uint32_t count_to_add)
|
2021-11-26 20:25:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (extra->uri_ranges.count + count_to_add > extra->uri_ranges.size) {
|
2021-12-08 17:41:29 +01:00
|
|
|
|
extra->uri_ranges.size = extra->uri_ranges.count + count_to_add;
|
2021-11-26 20:25:07 +01:00
|
|
|
|
extra->uri_ranges.v = xrealloc(
|
|
|
|
|
|
extra->uri_ranges.v,
|
|
|
|
|
|
extra->uri_ranges.size * sizeof(extra->uri_ranges.v[0]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xassert(extra->uri_ranges.count + count_to_add <= extra->uri_ranges.size);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-12-08 17:41:29 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* Be careful! This function may xrealloc() the URI range vector, thus
|
|
|
|
|
|
* invalidating pointers into it.
|
|
|
|
|
|
*/
|
2021-11-26 20:25:07 +01:00
|
|
|
|
static void
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_insert(struct row_data *extra, size_t idx, int start, int end,
|
|
|
|
|
|
uint64_t id, const char *uri)
|
2021-11-26 20:25:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
uri_range_ensure_size(extra, 1);
|
|
|
|
|
|
|
|
|
|
|
|
xassert(idx <= extra->uri_ranges.count);
|
|
|
|
|
|
|
|
|
|
|
|
const size_t move_count = extra->uri_ranges.count - idx;
|
|
|
|
|
|
memmove(&extra->uri_ranges.v[idx + 1],
|
|
|
|
|
|
&extra->uri_ranges.v[idx],
|
|
|
|
|
|
move_count * sizeof(extra->uri_ranges.v[0]));
|
|
|
|
|
|
|
|
|
|
|
|
extra->uri_ranges.count++;
|
2021-11-27 16:24:26 +01:00
|
|
|
|
extra->uri_ranges.v[idx] = (struct row_uri_range){
|
|
|
|
|
|
.start = start,
|
|
|
|
|
|
.end = end,
|
|
|
|
|
|
.id = id,
|
|
|
|
|
|
.uri = xstrdup(uri),
|
|
|
|
|
|
};
|
2021-11-26 20:25:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_append_no_strdup(struct row_data *extra, int start, int end,
|
|
|
|
|
|
uint64_t id, char *uri)
|
2021-11-26 20:25:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
uri_range_ensure_size(extra, 1);
|
2021-11-27 16:24:26 +01:00
|
|
|
|
extra->uri_ranges.v[extra->uri_ranges.count++] = (struct row_uri_range){
|
|
|
|
|
|
.start = start,
|
|
|
|
|
|
.end = end,
|
|
|
|
|
|
.id = id,
|
|
|
|
|
|
.uri = uri,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
uri_range_append(struct row_data *extra, int start, int end, uint64_t id,
|
|
|
|
|
|
const char *uri)
|
|
|
|
|
|
{
|
|
|
|
|
|
uri_range_append_no_strdup(extra, start, end, id, xstrdup(uri));
|
2021-11-26 20:25:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
uri_range_delete(struct row_data *extra, size_t idx)
|
|
|
|
|
|
{
|
|
|
|
|
|
xassert(idx < extra->uri_ranges.count);
|
|
|
|
|
|
grid_row_uri_range_destroy(&extra->uri_ranges.v[idx]);
|
|
|
|
|
|
|
|
|
|
|
|
const size_t move_count = extra->uri_ranges.count - idx - 1;
|
|
|
|
|
|
memmove(&extra->uri_ranges.v[idx],
|
|
|
|
|
|
&extra->uri_ranges.v[idx + 1],
|
|
|
|
|
|
move_count * sizeof(extra->uri_ranges.v[0]));
|
|
|
|
|
|
extra->uri_ranges.count--;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
|
clone_row->prompt_marker = row->prompt_marker;
|
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];
|
|
|
|
|
|
|
2021-11-27 16:07:50 +01:00
|
|
|
|
const struct row_data *extra = row->extra;
|
2021-11-26 19:55:27 +01:00
|
|
|
|
|
2021-11-27 16:07:50 +01:00
|
|
|
|
if (extra != NULL) {
|
2021-11-27 16:24:26 +01:00
|
|
|
|
struct row_data *clone_extra = xcalloc(1, sizeof(*clone_extra));
|
|
|
|
|
|
clone_row->extra = clone_extra;
|
2021-11-26 19:55:27 +01:00
|
|
|
|
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_ensure_size(clone_extra, extra->uri_ranges.count);
|
2021-02-22 10:21:58 +01:00
|
|
|
|
|
2021-11-27 16:07:50 +01:00
|
|
|
|
for (size_t i = 0; i < extra->uri_ranges.count; i++) {
|
|
|
|
|
|
const struct row_uri_range *range = &extra->uri_ranges.v[i];
|
|
|
|
|
|
uri_range_append(
|
|
|
|
|
|
clone_extra,
|
2021-11-27 16:24:26 +01:00
|
|
|
|
range->start, range->end, range->id, range->uri);
|
2021-02-22 10:21:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
} 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;
|
2022-06-30 19:37:01 +02:00
|
|
|
|
row->linebreak = false;
|
2021-02-13 12:31:55 +01:00
|
|
|
|
row->extra = NULL;
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
|
row->prompt_marker = false;
|
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;
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
|
new_row->prompt_marker = old_row->prompt_marker;
|
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
|
|
|
|
|
|
|
|
|
|
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 */
|
2021-11-27 16:31:33 +01:00
|
|
|
|
const struct row_data *old_extra = old_row->extra;
|
|
|
|
|
|
if (old_extra == NULL)
|
2021-02-14 20:42:42 +01:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2021-11-27 16:31:33 +01:00
|
|
|
|
ensure_row_has_extra_data(new_row);
|
|
|
|
|
|
struct row_data *new_extra = new_row->extra;
|
2021-11-26 19:55:27 +01:00
|
|
|
|
|
2021-11-27 16:31:33 +01:00
|
|
|
|
uri_range_ensure_size(new_extra, old_extra->uri_ranges.count);
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < old_extra->uri_ranges.count; i++) {
|
|
|
|
|
|
const struct row_uri_range *range = &old_extra->uri_ranges.v[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (range->start >= new_cols) {
|
2021-02-14 20:42:42 +01:00
|
|
|
|
/* The whole range is truncated */
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-27 16:31:33 +01:00
|
|
|
|
const int start = range->start;
|
|
|
|
|
|
const int end = min(range->end, new_cols - 1);
|
|
|
|
|
|
uri_range_append(new_extra, start, end, range->id, range->uri);
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-26 20:36:59 +01:00
|
|
|
|
#if defined(_DEBUG)
|
2021-11-26 20:25:07 +01:00
|
|
|
|
for (size_t r = 0; r < new_rows; r++) {
|
|
|
|
|
|
const struct row *row = new_grid[r];
|
|
|
|
|
|
|
|
|
|
|
|
if (row == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
if (row->extra == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
verify_no_overlapping_uris(row->extra);
|
|
|
|
|
|
verify_uris_are_sorted(row->extra);
|
|
|
|
|
|
}
|
2021-11-26 20:36:59 +01:00
|
|
|
|
#endif
|
2021-11-26 20:25:07 +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
|
|
|
|
/* 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-11-26 20:25:07 +01:00
|
|
|
|
ensure_row_has_extra_data(new_row);
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_append_no_strdup
|
|
|
|
|
|
(new_row->extra, new_col_idx, -1, range->id, range->uri);
|
|
|
|
|
|
range->uri = NULL;
|
2021-05-22 20:29:10 +02:00
|
|
|
|
}
|
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)
|
|
|
|
|
|
{
|
2021-11-26 19:55:27 +01:00
|
|
|
|
struct row_data *extra = new_row->extra;
|
|
|
|
|
|
xassert(extra->uri_ranges.count > 0);
|
|
|
|
|
|
|
|
|
|
|
|
struct row_uri_range *new_range =
|
|
|
|
|
|
&extra->uri_ranges.v[extra->uri_ranges.count - 1];
|
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);
|
2022-06-30 19:37:01 +02:00
|
|
|
|
new_row->linebreak = false;
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
|
new_row->prompt_marker = false;
|
2021-02-14 20:34:49 +01:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-02-07 13:57:25 +01:00
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* TODO: detect if the re-used row is covered by the
|
|
|
|
|
|
* selection. Of so, cancel the selection. The problem: we
|
|
|
|
|
|
* don’t know if we’ve translated the selection coordinates
|
|
|
|
|
|
* yet.
|
|
|
|
|
|
*/
|
2021-02-14 20:34:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
struct row_data *extra = row->extra;
|
|
|
|
|
|
if (extra == NULL)
|
2021-02-14 20:34:49 +01:00
|
|
|
|
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-11-26 19:55:27 +01:00
|
|
|
|
if (extra->uri_ranges.count > 0) {
|
|
|
|
|
|
struct row_uri_range *range =
|
|
|
|
|
|
&extra->uri_ranges.v[extra->uri_ranges.count - 1];
|
|
|
|
|
|
|
2021-05-22 20:29:10 +02:00
|
|
|
|
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 */
|
2021-11-26 20:25:07 +01:00
|
|
|
|
ensure_row_has_extra_data(new_row);
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_append(new_row->extra, 0, -1, range->id, range->uri);
|
2021-05-22 20:29:10 +02:00
|
|
|
|
}
|
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,
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
|
struct coord *const _tracking_points[static tracking_points_count])
|
2020-02-15 22:19:08 +01:00
|
|
|
|
{
|
2021-05-11 17:43:17 +02:00
|
|
|
|
#if defined(TIME_REFLOW) && TIME_REFLOW
|
2022-01-15 14:56:13 +05:30
|
|
|
|
struct timespec start;
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start);
|
2021-05-11 17:43:17 +02:00
|
|
|
|
#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;
|
2021-05-28 17:41:13 +02:00
|
|
|
|
for (int c = old_cols - 1; c >= 0; c--) {
|
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
|
|
|
|
const struct cell *cell = &old_row->cells[c];
|
|
|
|
|
|
if (!(cell->wc == 0 || cell->wc == CELL_SPACER)) {
|
|
|
|
|
|
col_count = c + 1;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-30 19:47:18 +02:00
|
|
|
|
if (!old_row->linebreak && col_count > 0) {
|
2022-06-01 19:29:30 +02:00
|
|
|
|
/* Don’t truncate logical lines */
|
|
|
|
|
|
col_count = old_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
|
|
|
|
xassert(col_count >= 0 && col_count <= old_cols);
|
|
|
|
|
|
|
2021-05-26 12:27:12 +02:00
|
|
|
|
/* Do we have a (at least one) tracking point on this row */
|
|
|
|
|
|
struct coord *tp;
|
2021-05-28 17:41:47 +02:00
|
|
|
|
if (unlikely((*next_tp)->row == old_row_idx)) {
|
2021-05-26 12:27:12 +02:00
|
|
|
|
tp = *next_tp;
|
2021-05-28 17:41:47 +02:00
|
|
|
|
|
|
|
|
|
|
/* 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);
|
2021-05-26 12:27:12 +02:00
|
|
|
|
} else
|
|
|
|
|
|
tp = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
/* Does this row have any URIs? */
|
2021-11-26 20:57:08 +01:00
|
|
|
|
struct row_uri_range *range, *range_terminator;
|
2021-11-26 19:55:27 +01:00
|
|
|
|
struct row_data *extra = old_row->extra;
|
|
|
|
|
|
|
|
|
|
|
|
if (extra != NULL && extra->uri_ranges.count > 0) {
|
2021-11-26 20:57:08 +01:00
|
|
|
|
range = &extra->uri_ranges.v[0];
|
|
|
|
|
|
range_terminator = &extra->uri_ranges.v[extra->uri_ranges.count];
|
2021-05-28 17:42:09 +02:00
|
|
|
|
|
2021-11-26 20:57:08 +01:00
|
|
|
|
/* Make sure the *last* URI range's end point is included
|
|
|
|
|
|
* in the copy */
|
2021-05-28 17:42:09 +02:00
|
|
|
|
const struct row_uri_range *last_on_row =
|
2021-11-26 19:55:27 +01:00
|
|
|
|
&extra->uri_ranges.v[extra->uri_ranges.count - 1];
|
2021-05-28 17:42:09 +02:00
|
|
|
|
col_count = max(col_count, last_on_row->end + 1);
|
2021-05-26 12:27:12 +02:00
|
|
|
|
} else
|
2021-11-26 20:57:08 +01:00
|
|
|
|
range = range_terminator = NULL;
|
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 (int start = 0, left = col_count; left > 0;) {
|
|
|
|
|
|
int end;
|
2021-05-25 19:31:38 +02:00
|
|
|
|
bool tp_break = false;
|
|
|
|
|
|
bool uri_break = false;
|
|
|
|
|
|
|
2021-05-25 19:40:10 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* 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,
|
|
|
|
|
|
*/
|
2021-11-26 20:57:08 +01:00
|
|
|
|
if (range != range_terminator) {
|
2021-05-25 19:34:21 +02:00
|
|
|
|
int uri_col = (range->start >= start ? range->start : range->end) + 1;
|
2021-05-25 19:28:11 +02:00
|
|
|
|
|
|
|
|
|
|
if (tp != NULL) {
|
2021-05-25 19:34:21 +02:00
|
|
|
|
int tp_col = tp->col + 1;
|
2021-05-25 19:28:11 +02:00
|
|
|
|
end = min(tp_col, uri_col);
|
2021-05-25 19:31:38 +02:00
|
|
|
|
|
|
|
|
|
|
tp_break = end == tp_col;
|
|
|
|
|
|
uri_break = end == uri_col;
|
2021-05-28 17:42:25 +02:00
|
|
|
|
LOG_DBG("tp+uri break at %d (%d, %d)", end, tp_col, uri_col);
|
2021-05-25 19:31:38 +02:00
|
|
|
|
} else {
|
2021-05-25 19:28:11 +02:00
|
|
|
|
end = uri_col;
|
2021-05-25 19:31:38 +02:00
|
|
|
|
uri_break = true;
|
2021-05-28 17:42:25 +02:00
|
|
|
|
LOG_DBG("uri break at %d", end);
|
2021-05-25 19:31:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
} else if (tp != NULL) {
|
2021-05-25 19:34:21 +02:00
|
|
|
|
end = tp->col + 1;
|
2021-05-25 19:31:38 +02:00
|
|
|
|
tp_break = true;
|
2021-05-28 17:42:25 +02:00
|
|
|
|
LOG_DBG("TP break at %d", end);
|
2021-05-25 19:31:38 +02:00
|
|
|
|
} else
|
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
|
|
|
|
end = col_count;
|
|
|
|
|
|
|
|
|
|
|
|
int cols = end - start;
|
|
|
|
|
|
xassert(cols > 0);
|
|
|
|
|
|
xassert(start + cols <= old_cols);
|
|
|
|
|
|
|
2021-05-25 19:40:10 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* 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.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
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 (int count = cols, from = start; count > 0;) {
|
|
|
|
|
|
xassert(new_col_idx <= new_cols);
|
|
|
|
|
|
int new_row_cells_left = new_cols - new_col_idx;
|
|
|
|
|
|
|
2021-05-25 19:40:10 +02:00
|
|
|
|
/* Row full, emit newline and get a new, fresh, row */
|
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
|
|
|
|
if (new_row_cells_left <= 0) {
|
|
|
|
|
|
line_wrap();
|
|
|
|
|
|
new_row_cells_left = new_cols;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 19:40:10 +02:00
|
|
|
|
/* Number of cells we can copy */
|
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
|
|
|
|
int amount = min(count, new_row_cells_left);
|
|
|
|
|
|
xassert(amount > 0);
|
|
|
|
|
|
|
2021-05-25 19:40:10 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* 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.
|
|
|
|
|
|
*/
|
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
|
|
|
|
int spacers = 0;
|
|
|
|
|
|
if (new_col_idx + amount >= new_cols) {
|
|
|
|
|
|
/*
|
2021-05-25 19:40:10 +02:00
|
|
|
|
* While the cell *after* the last cell is a CELL_SPACER
|
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
|
|
|
|
*
|
|
|
|
|
|
* 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);
|
|
|
|
|
|
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
|
if (from == 0)
|
|
|
|
|
|
new_row->prompt_marker = old_row->prompt_marker;
|
|
|
|
|
|
|
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
|
|
|
|
memcpy(
|
|
|
|
|
|
&new_row->cells[new_col_idx], &old_row->cells[from],
|
|
|
|
|
|
amount * sizeof(struct cell));
|
|
|
|
|
|
|
|
|
|
|
|
count -= amount;
|
|
|
|
|
|
from += amount;
|
|
|
|
|
|
new_col_idx += amount;
|
|
|
|
|
|
|
2021-05-28 17:42:25 +02:00
|
|
|
|
xassert(new_col_idx <= 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
|
|
|
|
if (unlikely(spacers > 0)) {
|
|
|
|
|
|
xassert(new_col_idx + spacers == new_cols);
|
|
|
|
|
|
|
2021-06-02 19:30:01 +02:00
|
|
|
|
const struct cell *cell = &old_row->cells[from - 1];
|
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 (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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-28 17:42:25 +02:00
|
|
|
|
xassert(new_col_idx > 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
|
|
|
|
|
|
|
|
|
|
if (tp_break) {
|
|
|
|
|
|
do {
|
|
|
|
|
|
xassert(tp != NULL);
|
|
|
|
|
|
xassert(tp->row == old_row_idx);
|
2021-05-28 17:42:25 +02:00
|
|
|
|
xassert(tp->col == end - 1);
|
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
|
|
|
|
|
|
|
|
|
|
tp->row = new_row_idx;
|
2021-05-28 17:42:25 +02:00
|
|
|
|
tp->col = new_col_idx - 1;
|
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
|
|
|
|
|
|
|
|
|
|
next_tp++;
|
|
|
|
|
|
tp = *next_tp;
|
2021-05-28 17:42:25 +02:00
|
|
|
|
} while (tp->row == old_row_idx && tp->col == end - 1);
|
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
|
|
|
|
|
|
|
|
|
|
if (tp->row != old_row_idx)
|
|
|
|
|
|
tp = NULL;
|
2021-05-28 17:42:25 +02:00
|
|
|
|
|
|
|
|
|
|
LOG_DBG("next TP (tp=%p): %dx%d",
|
|
|
|
|
|
(void*)tp, (*next_tp)->row, (*next_tp)->col);
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (uri_break) {
|
2021-11-26 20:57:08 +01:00
|
|
|
|
xassert(range != NULL);
|
|
|
|
|
|
|
2021-05-28 17:42:25 +02:00
|
|
|
|
if (range->start == end - 1)
|
|
|
|
|
|
reflow_uri_range_start(range, new_row, new_col_idx - 1);
|
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
|
|
|
|
|
2021-05-28 17:42:25 +02:00
|
|
|
|
if (range->end == end - 1) {
|
|
|
|
|
|
reflow_uri_range_end(range, new_row, new_col_idx - 1);
|
2021-05-25 19:22:52 +02:00
|
|
|
|
grid_row_uri_range_destroy(range);
|
2021-11-26 20:57:08 +01:00
|
|
|
|
range++;
|
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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
left -= cols;
|
|
|
|
|
|
start += cols;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-30 19:37:01 +02: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;
|
2022-08-29 20:47:33 +02:00
|
|
|
|
|
|
|
|
|
|
if (r + 1 < old_rows)
|
|
|
|
|
|
line_wrap();
|
2022-08-29 21:03:21 +02:00
|
|
|
|
else if (new_row->extra != NULL &&
|
|
|
|
|
|
new_row->extra->uri_ranges.count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
/*
|
|
|
|
|
|
* line_wrap() "closes" still-open URIs. Since this is
|
|
|
|
|
|
* the *last* row, and since we’re line-breaking due
|
|
|
|
|
|
* to a hard line-break (rather than running out of
|
|
|
|
|
|
* cells in the "new_row"), there shouldn’t be an open
|
|
|
|
|
|
* URI (it would have been closed when we reached the
|
|
|
|
|
|
* end of the URI while reflowing the last "old"
|
|
|
|
|
|
* row).
|
|
|
|
|
|
*/
|
|
|
|
|
|
uint32_t last_idx = new_row->extra->uri_ranges.count - 1;
|
|
|
|
|
|
xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0);
|
|
|
|
|
|
}
|
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++) {
|
2021-05-28 17:42:25 +02:00
|
|
|
|
LOG_DBG("TP: row=%d, col=%d (old cols: %d, new cols: %d)",
|
|
|
|
|
|
(*tp)->row, (*tp)->col, old_cols, 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
|
|
|
|
}
|
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;
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
for (size_t i = 0; i < row->extra->uri_ranges.count; i++)
|
|
|
|
|
|
xassert(row->extra->uri_ranges.v[i].end >= 0);
|
2021-11-26 20:25:07 +01:00
|
|
|
|
|
|
|
|
|
|
verify_no_overlapping_uris(row->extra);
|
|
|
|
|
|
verify_uris_are_sorted(row->extra);
|
2021-02-14 20:34:49 +01:00
|
|
|
|
}
|
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;
|
2022-08-05 18:26:37 +02:00
|
|
|
|
|
2020-02-15 22:19:08 +01:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2022-08-29 20:46:19 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* Set new viewport, making sure it’s not too far down.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is done by using scrollback-start relative cooardinates,
|
|
|
|
|
|
* and bounding the new viewport to (grid_rows - screen_rows).
|
|
|
|
|
|
*/
|
|
|
|
|
|
int sb_view = grid_row_abs_to_sb(
|
|
|
|
|
|
grid, new_screen_rows, view_follows ? grid->offset : viewport.row);
|
|
|
|
|
|
grid->view = grid_row_sb_to_abs(
|
|
|
|
|
|
grid, new_screen_rows, min(sb_view, new_rows - new_screen_rows));
|
|
|
|
|
|
|
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
|
2022-01-15 14:56:13 +05:30
|
|
|
|
struct timespec stop;
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &stop);
|
2021-05-11 17:43:17 +02:00
|
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
|
struct timespec diff;
|
|
|
|
|
|
timespec_sub(&stop, &start, &diff);
|
|
|
|
|
|
LOG_INFO("reflowed %d -> %d rows in %lds %ldns",
|
2021-05-15 12:11:58 +02:00
|
|
|
|
old_rows, new_rows,
|
2022-01-15 14:56:13 +05:30
|
|
|
|
(long)diff.tv_sec,
|
|
|
|
|
|
diff.tv_nsec);
|
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
|
|
|
|
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
void
|
|
|
|
|
|
grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id)
|
|
|
|
|
|
{
|
|
|
|
|
|
ensure_row_has_extra_data(row);
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
size_t insert_idx = 0;
|
|
|
|
|
|
bool replace = false;
|
|
|
|
|
|
bool run_merge_pass = false;
|
|
|
|
|
|
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
struct row_data *extra = row->extra;
|
2021-11-26 19:55:27 +01:00
|
|
|
|
for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) {
|
|
|
|
|
|
struct row_uri_range *r = &extra->uri_ranges.v[i];
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
const bool matching_id = r->id == id;
|
|
|
|
|
|
|
|
|
|
|
|
if (matching_id && r->end + 1 == col) {
|
|
|
|
|
|
/* Extend existing URI’s tail */
|
|
|
|
|
|
r->end++;
|
|
|
|
|
|
goto out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else if (r->end < col) {
|
|
|
|
|
|
insert_idx = i + 1;
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
else if (r->start > col)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
xassert(r->start <= col);
|
|
|
|
|
|
xassert(r->end >= col);
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
if (matching_id)
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
goto out;
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
if (r->start == r->end) {
|
|
|
|
|
|
replace = true;
|
|
|
|
|
|
run_merge_pass = true;
|
|
|
|
|
|
insert_idx = i;
|
|
|
|
|
|
} else if (r->start == col) {
|
|
|
|
|
|
run_merge_pass = true;
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
r->start++;
|
2021-11-26 19:55:27 +01:00
|
|
|
|
insert_idx = i;
|
|
|
|
|
|
} else if (r->end == col) {
|
|
|
|
|
|
run_merge_pass = true;
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
r->end--;
|
2021-11-26 19:55:27 +01:00
|
|
|
|
insert_idx = i + 1;
|
|
|
|
|
|
} else {
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
xassert(r->start < col);
|
|
|
|
|
|
xassert(r->end > col);
|
|
|
|
|
|
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_insert(extra, i + 1, col + 1, r->end, r->id, r->uri);
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
|
2021-12-08 17:41:29 +01:00
|
|
|
|
/* The insertion may xrealloc() the vector, making our
|
|
|
|
|
|
* ‘old’ pointer invalid */
|
|
|
|
|
|
r = &extra->uri_ranges.v[i];
|
2021-11-26 19:55:27 +01:00
|
|
|
|
r->end = col - 1;
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
xassert(r->start <= r->end);
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
insert_idx = i + 1;
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
break;
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
xassert(insert_idx <= extra->uri_ranges.count);
|
|
|
|
|
|
|
|
|
|
|
|
if (replace) {
|
|
|
|
|
|
grid_row_uri_range_destroy(&extra->uri_ranges.v[insert_idx]);
|
2021-11-27 16:24:26 +01:00
|
|
|
|
extra->uri_ranges.v[insert_idx] = (struct row_uri_range){
|
|
|
|
|
|
.start = col,
|
|
|
|
|
|
.end = col,
|
|
|
|
|
|
.id = id,
|
|
|
|
|
|
.uri = xstrdup(uri),
|
|
|
|
|
|
};
|
2021-11-26 19:55:27 +01:00
|
|
|
|
} else
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_insert(extra, insert_idx, col, col, id, uri);
|
2021-11-26 19:55:27 +01:00
|
|
|
|
|
|
|
|
|
|
if (run_merge_pass) {
|
|
|
|
|
|
for (size_t i = 1; i < extra->uri_ranges.count; i++) {
|
|
|
|
|
|
struct row_uri_range *r1 = &extra->uri_ranges.v[i - 1];
|
|
|
|
|
|
struct row_uri_range *r2 = &extra->uri_ranges.v[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (r1->id == r2->id && r1->end + 1 == r2->start) {
|
|
|
|
|
|
r1->end = r2->end;
|
|
|
|
|
|
uri_range_delete(extra, i);
|
|
|
|
|
|
i--;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
|
|
|
|
|
|
out:
|
2021-11-26 20:25:07 +01:00
|
|
|
|
verify_no_overlapping_uris(extra);
|
|
|
|
|
|
verify_uris_are_sorted(extra);
|
2021-11-26 19:55:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UNITTEST
|
|
|
|
|
|
{
|
|
|
|
|
|
struct row_data row_data = {.uri_ranges = {0}};
|
|
|
|
|
|
struct row row = {.extra = &row_data};
|
|
|
|
|
|
|
|
|
|
|
|
#define verify_range(idx, _start, _end, _id) \
|
|
|
|
|
|
do { \
|
|
|
|
|
|
xassert(idx < row_data.uri_ranges.count); \
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[idx].start == _start); \
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[idx].end == _end); \
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[idx].id == _id); \
|
|
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
|
|
|
|
grid_row_uri_range_put(&row, 0, "http://foo.bar", 123);
|
|
|
|
|
|
grid_row_uri_range_put(&row, 1, "http://foo.bar", 123);
|
|
|
|
|
|
grid_row_uri_range_put(&row, 2, "http://foo.bar", 123);
|
|
|
|
|
|
grid_row_uri_range_put(&row, 3, "http://foo.bar", 123);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 1);
|
|
|
|
|
|
verify_range(0, 0, 3, 123);
|
|
|
|
|
|
|
|
|
|
|
|
/* No-op */
|
|
|
|
|
|
grid_row_uri_range_put(&row, 0, "http://foo.bar", 123);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 1);
|
|
|
|
|
|
verify_range(0, 0, 3, 123);
|
|
|
|
|
|
|
|
|
|
|
|
/* Replace head */
|
|
|
|
|
|
grid_row_uri_range_put(&row, 0, "http://head", 456);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 2);
|
|
|
|
|
|
verify_range(0, 0, 0, 456);
|
|
|
|
|
|
verify_range(1, 1, 3, 123);
|
|
|
|
|
|
|
|
|
|
|
|
/* Replace tail */
|
|
|
|
|
|
grid_row_uri_range_put(&row, 3, "http://tail", 789);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 3);
|
|
|
|
|
|
verify_range(1, 1, 2, 123);
|
|
|
|
|
|
verify_range(2, 3, 3, 789);
|
|
|
|
|
|
|
|
|
|
|
|
/* Replace tail + extend head */
|
|
|
|
|
|
grid_row_uri_range_put(&row, 2, "http://tail", 789);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 3);
|
|
|
|
|
|
verify_range(1, 1, 1, 123);
|
|
|
|
|
|
verify_range(2, 2, 3, 789);
|
|
|
|
|
|
|
|
|
|
|
|
/* Replace + extend tail */
|
|
|
|
|
|
grid_row_uri_range_put(&row, 1, "http://head", 456);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 2);
|
|
|
|
|
|
verify_range(0, 0, 1, 456);
|
|
|
|
|
|
verify_range(1, 2, 3, 789);
|
|
|
|
|
|
|
|
|
|
|
|
/* Replace + extend, then splice */
|
|
|
|
|
|
grid_row_uri_range_put(&row, 1, "http://tail", 789);
|
|
|
|
|
|
grid_row_uri_range_put(&row, 2, "http://splice", 000);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 4);
|
|
|
|
|
|
verify_range(0, 0, 0, 456);
|
|
|
|
|
|
verify_range(1, 1, 1, 789);
|
|
|
|
|
|
verify_range(2, 2, 2, 000);
|
|
|
|
|
|
verify_range(3, 3, 3, 789);
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < row_data.uri_ranges.count; i++)
|
|
|
|
|
|
grid_row_uri_range_destroy(&row_data.uri_ranges.v[i]);
|
|
|
|
|
|
free(row_data.uri_ranges.v);
|
|
|
|
|
|
|
|
|
|
|
|
#undef verify_range
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-20 13:42:07 +01:00
|
|
|
|
void
|
|
|
|
|
|
grid_row_uri_range_erase(struct row *row, int start, int end)
|
|
|
|
|
|
{
|
2021-11-20 14:40:16 +01:00
|
|
|
|
xassert(row->extra != NULL);
|
2021-11-20 15:46:20 +01:00
|
|
|
|
xassert(start <= end);
|
2021-11-20 13:42:07 +01:00
|
|
|
|
|
2021-11-26 19:55:27 +01:00
|
|
|
|
struct row_data *extra = row->extra;
|
|
|
|
|
|
|
2021-11-20 13:42:07 +01:00
|
|
|
|
/* Split up, or remove, URI ranges affected by the erase */
|
2021-11-28 10:56:32 +01:00
|
|
|
|
for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) {
|
2021-11-26 19:55:27 +01:00
|
|
|
|
struct row_uri_range *old = &extra->uri_ranges.v[i];
|
2021-11-20 13:42:07 +01:00
|
|
|
|
|
2021-11-20 15:46:20 +01:00
|
|
|
|
if (old->end < start)
|
2021-11-28 10:56:32 +01:00
|
|
|
|
return;
|
2021-11-20 15:46:20 +01:00
|
|
|
|
|
|
|
|
|
|
if (old->start > end)
|
2021-11-28 10:56:32 +01:00
|
|
|
|
continue;
|
2021-11-20 15:46:20 +01:00
|
|
|
|
|
|
|
|
|
|
if (start <= old->start && end >= old->end) {
|
|
|
|
|
|
/* Erase range covers URI completely - remove it */
|
2021-11-26 19:55:27 +01:00
|
|
|
|
uri_range_delete(extra, i);
|
2021-11-20 13:42:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-20 15:46:20 +01:00
|
|
|
|
else if (start > old->start && end < old->end) {
|
|
|
|
|
|
/* Erase range erases a part in the middle of the URI */
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_insert(
|
|
|
|
|
|
extra, i + 1, end + 1, old->end, old->id, old->uri);
|
2021-12-08 17:41:29 +01:00
|
|
|
|
|
|
|
|
|
|
/* The insertion may xrealloc() the vector, making our
|
|
|
|
|
|
* ‘old’ pointer invalid */
|
|
|
|
|
|
old = &extra->uri_ranges.v[i];
|
2021-11-20 15:46:20 +01:00
|
|
|
|
old->end = start - 1;
|
|
|
|
|
|
return; /* There can be no more URIs affected by the erase range */
|
2021-11-20 13:42:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-20 15:46:20 +01:00
|
|
|
|
else if (start <= old->start && end >= old->start) {
|
|
|
|
|
|
/* Erase range erases the head of the URI */
|
|
|
|
|
|
xassert(start <= old->start);
|
|
|
|
|
|
old->start = end + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else if (start <= old->end && end >= old->end) {
|
|
|
|
|
|
/* Erase range erases the tail of the URI */
|
|
|
|
|
|
xassert(end >= old->end);
|
|
|
|
|
|
old->end = start - 1;
|
2021-11-28 10:56:32 +01:00
|
|
|
|
return; /* There can be no more overlapping URIs */
|
2021-11-20 13:42:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UNITTEST
|
|
|
|
|
|
{
|
2021-11-26 19:55:27 +01:00
|
|
|
|
struct row_data row_data = {.uri_ranges = {0}};
|
2021-11-20 13:42:07 +01:00
|
|
|
|
struct row row = {.extra = &row_data};
|
|
|
|
|
|
|
2021-11-28 11:05:00 +01:00
|
|
|
|
/* Try erasing a row without any URIs */
|
|
|
|
|
|
grid_row_uri_range_erase(&row, 0, 200);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 0);
|
|
|
|
|
|
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_append(&row_data, 1, 10, 0, "dummy");
|
|
|
|
|
|
uri_range_append(&row_data, 11, 20, 0, "dummy");
|
2021-11-26 19:55:27 +01:00
|
|
|
|
xassert(row_data.uri_ranges.count == 2);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[1].start == 11);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[1].end == 20);
|
2021-11-26 20:25:07 +01:00
|
|
|
|
verify_no_overlapping_uris(&row_data);
|
|
|
|
|
|
verify_uris_are_sorted(&row_data);
|
2021-11-20 13:42:07 +01:00
|
|
|
|
|
|
|
|
|
|
/* Erase both URis */
|
|
|
|
|
|
grid_row_uri_range_erase(&row, 1, 20);
|
2021-11-26 19:55:27 +01:00
|
|
|
|
xassert(row_data.uri_ranges.count == 0);
|
2021-11-26 20:25:07 +01:00
|
|
|
|
verify_no_overlapping_uris(&row_data);
|
|
|
|
|
|
verify_uris_are_sorted(&row_data);
|
2021-11-20 13:42:07 +01:00
|
|
|
|
|
|
|
|
|
|
/* Two URIs, then erase second half of the first, first half of
|
|
|
|
|
|
the second */
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_append(&row_data, 1, 10, 0, "dummy");
|
|
|
|
|
|
uri_range_append(&row_data, 11, 20, 0, "dummy");
|
2021-11-20 13:42:07 +01:00
|
|
|
|
grid_row_uri_range_erase(&row, 5, 15);
|
2021-11-26 19:55:27 +01:00
|
|
|
|
xassert(row_data.uri_ranges.count == 2);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[0].start == 1);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[0].end == 4);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[1].start == 16);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[1].end == 20);
|
2021-11-26 20:25:07 +01:00
|
|
|
|
verify_no_overlapping_uris(&row_data);
|
|
|
|
|
|
verify_uris_are_sorted(&row_data);
|
2021-11-20 13:42:07 +01:00
|
|
|
|
|
2021-11-27 16:24:26 +01:00
|
|
|
|
grid_row_uri_range_destroy(&row_data.uri_ranges.v[0]);
|
|
|
|
|
|
grid_row_uri_range_destroy(&row_data.uri_ranges.v[1]);
|
2021-11-26 19:55:27 +01:00
|
|
|
|
row_data.uri_ranges.count = 0;
|
2021-11-20 13:42:07 +01:00
|
|
|
|
|
|
|
|
|
|
/* One URI, erase middle part of it */
|
2021-11-27 16:24:26 +01:00
|
|
|
|
uri_range_append(&row_data, 1, 10, 0, "dummy");
|
2021-11-20 13:42:07 +01:00
|
|
|
|
grid_row_uri_range_erase(&row, 5, 6);
|
2021-11-26 19:55:27 +01:00
|
|
|
|
xassert(row_data.uri_ranges.count == 2);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[0].start == 1);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[0].end == 4);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[1].start == 7);
|
|
|
|
|
|
xassert(row_data.uri_ranges.v[1].end == 10);
|
2021-11-26 20:25:07 +01:00
|
|
|
|
verify_no_overlapping_uris(&row_data);
|
|
|
|
|
|
verify_uris_are_sorted(&row_data);
|
2021-11-20 13:42:07 +01:00
|
|
|
|
|
2021-12-08 17:41:29 +01:00
|
|
|
|
grid_row_uri_range_destroy(&row_data.uri_ranges.v[0]);
|
|
|
|
|
|
grid_row_uri_range_destroy(&row_data.uri_ranges.v[1]);
|
|
|
|
|
|
row_data.uri_ranges.count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Regression test: erasing the middle part of an URI causes us to
|
|
|
|
|
|
* insert a new URI (we split the partly erased URI into two).
|
|
|
|
|
|
*
|
|
|
|
|
|
* The insertion logic typically triggers an xrealloc(), which, in
|
|
|
|
|
|
* some cases, *moves* the entire URI vector to a new base
|
|
|
|
|
|
* address. grid_row_uri_range_erase() did not account for this,
|
|
|
|
|
|
* and tried to update the ‘end’ member in the URI range we just
|
|
|
|
|
|
* split. This causes foot to crash when the xrealloc() has moved
|
|
|
|
|
|
* the URI range vector.
|
|
|
|
|
|
*
|
|
|
|
|
|
* (note: we’re only verifying we don’t crash here, hence the lack
|
|
|
|
|
|
* of assertions).
|
|
|
|
|
|
*/
|
|
|
|
|
|
free(row_data.uri_ranges.v);
|
|
|
|
|
|
row_data.uri_ranges.v = NULL;
|
|
|
|
|
|
row_data.uri_ranges.size = 0;
|
|
|
|
|
|
uri_range_append(&row_data, 1, 10, 0, "dummy");
|
|
|
|
|
|
xassert(row_data.uri_ranges.size == 1);
|
|
|
|
|
|
|
|
|
|
|
|
grid_row_uri_range_erase(&row, 5, 7);
|
|
|
|
|
|
xassert(row_data.uri_ranges.count == 2);
|
|
|
|
|
|
|
2021-11-27 16:24:26 +01:00
|
|
|
|
for (size_t i = 0; i < row_data.uri_ranges.count; i++)
|
|
|
|
|
|
grid_row_uri_range_destroy(&row_data.uri_ranges.v[i]);
|
2021-11-26 19:55:27 +01:00
|
|
|
|
free(row_data.uri_ranges.v);
|
2021-02-14 20:50:33 +01:00
|
|
|
|
}
|