osc8: uri ranges: use a dynamically re-sizable array instead of a tllist

This commit is contained in:
Daniel Eklöf 2021-11-26 19:55:27 +01:00
parent b1043a72f8
commit ccee08a393
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
4 changed files with 319 additions and 123 deletions

408
grid.c
View file

@ -49,19 +49,26 @@ grid_snapshot(const struct grid *grid)
if (row->extra != NULL) {
const struct row_data *extra = row->extra;
struct row_data *new_extra = xcalloc(1, sizeof(*new_extra));
struct row_data *new_extra = xmalloc(sizeof(*new_extra));
struct row_uri_range *ranges =
xmalloc(extra->uri_ranges.count * sizeof(ranges[0]));
tll_foreach(extra->uri_ranges, it) {
struct row_uri_range range = {
.start = it->item.start,
.end = it->item.end,
.id = it->item.id,
.uri = xstrdup(it->item.uri),
for (size_t i = 0; i < extra->uri_ranges.count; i++) {
const struct row_uri_range *range = &extra->uri_ranges.v[i];
ranges[i] = (struct row_uri_range){
.start = range->start,
.end = range->end,
.id = range->id,
.uri = xstrdup(range->uri),
};
tll_push_back(new_extra->uri_ranges, range);
}
new_extra->uri_ranges.v = ranges;
new_extra->uri_ranges.size = extra->uri_ranges.count;
new_extra->uri_ranges.count = extra->uri_ranges.count;
clone_row->extra = new_extra;
} else
clone_row->extra = NULL;
@ -224,17 +231,20 @@ grid_resize_without_reflow(
if (old_row->extra == NULL)
continue;
tll_foreach(old_row->extra->uri_ranges, it) {
if (it->item.start >= new_rows) {
for (size_t i = 0; i < old_row->extra->uri_ranges.count; i++) {
const struct row_uri_range *old_range =
&old_row->extra->uri_ranges.v[i];
if (old_range->start >= new_rows) {
/* The whole range is truncated */
continue;
}
struct row_uri_range range = {
.start = it->item.start,
.end = min(it->item.end, new_cols - 1),
.id = it->item.id,
.uri = xstrdup(it->item.uri),
.start = old_range->start,
.end = min(old_range->end, new_cols - 1),
.id = old_range->id,
.uri = xstrdup(old_range->uri),
};
grid_row_uri_range_add(new_row, range);
}
@ -310,8 +320,11 @@ static void
reflow_uri_range_end(struct row_uri_range *range, struct row *new_row,
int new_col_idx)
{
xassert(tll_length(new_row->extra->uri_ranges) > 0);
struct row_uri_range *new_range = &tll_back(new_row->extra->uri_ranges);
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];
xassert(new_range->id == range->id);
xassert(new_range->end < 0);
@ -344,7 +357,8 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
}
}
if (row->extra == NULL)
struct row_data *extra = row->extra;
if (extra == NULL)
return new_row;
/*
@ -352,8 +366,10 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
* ranges on the previous row, and re-open them on the
* next/current row.
*/
if (tll_length(row->extra->uri_ranges) > 0) {
struct row_uri_range *range = &tll_back(row->extra->uri_ranges);
if (extra->uri_ranges.count > 0) {
struct row_uri_range *range =
&extra->uri_ranges.v[extra->uri_ranges.count - 1];
if (range->end < 0) {
/* Terminate URI range on the previous row */
@ -550,12 +566,15 @@ grid_resize_and_reflow(
/* Does this row have any URIs? */
struct row_uri_range *range;
if (old_row->extra != NULL && tll_length(old_row->extra->uri_ranges) > 0) {
range = &tll_front(old_row->extra->uri_ranges);
struct row_data *extra = old_row->extra;
size_t uri_range_idx = 0;
if (extra != NULL && extra->uri_ranges.count > 0) {
range = &extra->uri_ranges.v[uri_range_idx];
/* Make sure the *last* URI range's end point is included in the copy */
const struct row_uri_range *last_on_row =
&tll_back(old_row->extra->uri_ranges);
&extra->uri_ranges.v[extra->uri_ranges.count - 1];
col_count = max(col_count, last_on_row->end + 1);
} else
range = NULL;
@ -703,12 +722,13 @@ grid_resize_and_reflow(
if (range->end == end - 1) {
reflow_uri_range_end(range, new_row, new_col_idx - 1);
xassert(&tll_front(old_row->extra->uri_ranges) == range);
xassert(&extra->uri_ranges.v[uri_range_idx] == range);
grid_row_uri_range_destroy(range);
tll_pop_front(old_row->extra->uri_ranges);
range = tll_length(old_row->extra->uri_ranges) > 0
? &tll_front(old_row->extra->uri_ranges)
uri_range_idx++;
range = uri_range_idx < extra->uri_ranges.count
? &old_row->extra->uri_ranges.v[uri_range_idx]
: NULL;
}
}
@ -752,8 +772,8 @@ grid_resize_and_reflow(
if (row->extra == NULL)
continue;
tll_foreach(row->extra->uri_ranges, it)
xassert(it->item.end >= 0);
for (size_t i = 0; i < row->extra->uri_ranges.count; i++)
xassert(row->extra->uri_ranges.v[i].end >= 0);
}
/* Verify all old rows have been free:d */
@ -849,14 +869,12 @@ verify_no_overlapping_uris(const struct row *row)
{
#if defined(_DEBUG)
const struct row_data *extra = row->extra;
tll_foreach(extra->uri_ranges, it1) {
const struct row_uri_range *r1 = &it1->item;
for (size_t i = 0; i < extra->uri_ranges.count; i++) {
const struct row_uri_range *r1 = &extra->uri_ranges.v[i];
tll_foreach(extra->uri_ranges, it2) {
const struct row_uri_range *r2 = &it2->item;
if (r1 == r2)
continue;
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))
@ -870,39 +888,129 @@ verify_no_overlapping_uris(const struct row *row)
#endif
}
static void
verify_uris_are_sorted(const struct row *row)
{
#if defined(_DEBUG)
const struct row_data *extra = row->extra;
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
uri_range_ensure_size(struct row_data *extra, size_t count_to_add)
{
if (extra->uri_ranges.count + count_to_add > extra->uri_ranges.size) {
extra->uri_ranges.size += count_to_add + 4;
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);
}
static void
uri_range_insert(struct row_data *extra, size_t idx, struct row_uri_range range)
{
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.v[idx] = range;
extra->uri_ranges.count++;
}
#if 0
static void
uri_range_append(struct row_data *extra, struct row_uri_range range)
{
uri_range_ensure_size(extra, 1);
extra->uri_ranges.v[extra->uri_ranges.count++] = range;
}
#endif
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--;
}
void
grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id)
{
ensure_row_has_extra_data(row);
struct row_data *extra = row->extra;
tll_rforeach(extra->uri_ranges, it) {
struct row_uri_range *r = &it->item;
size_t insert_idx = 0;
bool replace = false;
bool run_merge_pass = false;
if (r->end + 1 < col) {
/* Ranges are sorted, and this means all the remaining
* URIs *end* before col */
struct row_data *extra = row->extra;
for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) {
struct row_uri_range *r = &extra->uri_ranges.v[i];
const bool matching_id = r->id == id;
if (matching_id && r->end + 1 == col) {
/* Extend existing URIs tail */
r->end++;
goto out;
}
else if (r->end < col) {
insert_idx = i + 1;
break;
}
if (r->start <= col && r->end >= col) {
/*
* col is *inside* an existing range
*/
else if (r->start > col)
continue;
if (r->id == id) {
/* col is already inside a matching URI range */
else {
xassert(r->start <= col);
xassert(r->end >= col);
if (matching_id)
goto out;
}
/* Splice old range */
if (r->start == r->end)
tll_remove(extra->uri_ranges, it);
else if (r->start == col)
if (r->start == r->end) {
replace = true;
run_merge_pass = true;
insert_idx = i;
} else if (r->start == col) {
run_merge_pass = true;
r->start++;
else if (r->end == col)
insert_idx = i;
} else if (r->end == col) {
run_merge_pass = true;
r->end--;
else {
insert_idx = i + 1;
} else {
xassert(r->start < col);
xassert(r->end > col);
@ -912,34 +1020,17 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id)
.id = r->id,
.uri = xstrdup(r->uri),
};
r->end = col - 1;
xassert(r->start <= r->end);
xassert(new_tail.start <= new_tail.end);
tll_insert_after(extra->uri_ranges, it, new_tail);
uri_range_insert(extra, i + 1, new_tail);
r->end = col - 1;
xassert(r->start <= r->end);
insert_idx = i + 1;
}
break; /* Break out add and a new range for col */
}
else if (r->id != id)
continue;
#if 0 /* Trust the URI ID, for now... */
if (strcmp(r->uri, uri) != 0)
continue;
#endif
else if (likely(r->end + 1 == col)) {
r->end = col;
goto out;
}
else if (col + 1 == r->start) {
r->start = col;
goto out;
break;
}
}
@ -949,27 +1040,119 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id)
.id = id,
.uri = xstrdup(uri),
};
grid_row_uri_range_add(row, new_range);
xassert(insert_idx >= 0);
xassert(insert_idx <= extra->uri_ranges.count);
if (replace) {
grid_row_uri_range_destroy(&extra->uri_ranges.v[insert_idx]);
extra->uri_ranges.v[insert_idx] = new_range;
} else
uri_range_insert(extra, insert_idx, new_range);
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--;
}
}
}
out:
verify_no_overlapping_uris(row);
verify_uris_are_sorted(row);
}
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
}
void
grid_row_uri_range_add(struct row *row, struct row_uri_range range)
{
ensure_row_has_extra_data(row);
tll_rforeach(row->extra->uri_ranges, it) {
if (it->item.end < range.start) {
tll_insert_after(row->extra->uri_ranges, it, range);
struct row_data *extra = row->extra;
for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) {
const struct row_uri_range *r = &extra->uri_ranges.v[i];
if (r->end < range.start) {
uri_range_insert(extra, i + 1, range);
goto out;
}
}
tll_push_front(row->extra->uri_ranges, range);
uri_range_insert(extra, 0, range);
out:
verify_no_overlapping_uris(row);
verify_uris_are_sorted(row);
}
void
@ -978,9 +1161,11 @@ grid_row_uri_range_erase(struct row *row, int start, int end)
xassert(row->extra != NULL);
xassert(start <= end);
struct row_data *extra = row->extra;
/* Split up, or remove, URI ranges affected by the erase */
tll_foreach(row->extra->uri_ranges, it) {
struct row_uri_range *old = &it->item;
for (ssize_t i = 0; i < extra->uri_ranges.count; i++) {
struct row_uri_range *old = &extra->uri_ranges.v[i];
if (old->end < start)
continue;
@ -990,8 +1175,8 @@ grid_row_uri_range_erase(struct row *row, int start, int end)
if (start <= old->start && end >= old->end) {
/* Erase range covers URI completely - remove it */
grid_row_uri_range_destroy(old);
tll_remove(row->extra->uri_ranges, it);
uri_range_delete(extra, i);
i--;
}
else if (start > old->start && end < old->end) {
@ -1002,7 +1187,7 @@ grid_row_uri_range_erase(struct row *row, int start, int end)
.id = old->id,
.uri = old->uri != NULL ? xstrdup(old->uri) : NULL,
};
tll_insert_after(row->extra->uri_ranges, it, old_tail);
uri_range_insert(extra, i + 1, old_tail);
old->end = start - 1;
return; /* There can be no more URIs affected by the erase range */
}
@ -1024,51 +1209,54 @@ grid_row_uri_range_erase(struct row *row, int start, int end)
UNITTEST
{
struct row_data row_data = {.uri_ranges = tll_init()};
struct row_data row_data = {.uri_ranges = {0}};
struct row row = {.extra = &row_data};
grid_row_uri_range_add(&row, (struct row_uri_range){1, 10});
xassert(tll_length(row_data.uri_ranges) == 1);
xassert(tll_front(row_data.uri_ranges).start == 1);
xassert(tll_front(row_data.uri_ranges).end == 10);
xassert(row_data.uri_ranges.count == 1);
xassert(row_data.uri_ranges.v[0].start == 1);
xassert(row_data.uri_ranges.v[0].end == 10);
verify_no_overlapping_uris(&row);
verify_uris_are_sorted(&row);
grid_row_uri_range_add(&row, (struct row_uri_range){11, 20});
xassert(tll_length(row_data.uri_ranges) == 2);
xassert(tll_back(row_data.uri_ranges).start == 11);
xassert(tll_back(row_data.uri_ranges).end == 20);
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);
verify_no_overlapping_uris(&row);
verify_uris_are_sorted(&row);
/* Erase both URis */
grid_row_uri_range_erase(&row, 1, 20);
xassert(tll_length(row_data.uri_ranges) == 0);
xassert(row_data.uri_ranges.count == 0);
verify_no_overlapping_uris(&row);
verify_uris_are_sorted(&row);
/* Two URIs, then erase second half of the first, first half of
the second */
grid_row_uri_range_add(&row, (struct row_uri_range){1, 10});
grid_row_uri_range_add(&row, (struct row_uri_range){11, 20});
grid_row_uri_range_erase(&row, 5, 15);
xassert(tll_length(row_data.uri_ranges) == 2);
xassert(tll_front(row_data.uri_ranges).start == 1);
xassert(tll_front(row_data.uri_ranges).end == 4);
xassert(tll_back(row_data.uri_ranges).start == 16);
xassert(tll_back(row_data.uri_ranges).end == 20);
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);
verify_no_overlapping_uris(&row);
verify_uris_are_sorted(&row);
tll_pop_back(row_data.uri_ranges);
tll_pop_back(row_data.uri_ranges);
xassert(tll_length(row_data.uri_ranges) == 0);
row_data.uri_ranges.count = 0;
/* One URI, erase middle part of it */
grid_row_uri_range_add(&row, (struct row_uri_range){1, 10});
grid_row_uri_range_erase(&row, 5, 6);
xassert(tll_length(row_data.uri_ranges) == 2);
xassert(tll_front(row_data.uri_ranges).start == 1);
xassert(tll_front(row_data.uri_ranges).end == 4);
xassert(tll_back(row_data.uri_ranges).start == 7);
xassert(tll_back(row_data.uri_ranges).end == 10);
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);
verify_no_overlapping_uris(&row);
verify_uris_are_sorted(&row);
tll_free(row_data.uri_ranges);
free(row_data.uri_ranges.v);
}

13
grid.h
View file

@ -88,14 +88,15 @@ grid_row_uri_range_destroy(struct row_uri_range *range)
static inline void
grid_row_reset_extra(struct row *row)
{
if (likely(row->extra == NULL))
struct row_data *extra = row->extra;
if (likely(extra == NULL))
return;
tll_foreach(row->extra->uri_ranges, it) {
grid_row_uri_range_destroy(&it->item);
tll_remove(row->extra->uri_ranges, it);
}
for (size_t i = 0; i < extra->uri_ranges.count; i++)
grid_row_uri_range_destroy(&extra->uri_ranges.v[i]);
free(extra->uri_ranges.v);
free(row->extra);
free(extra);
row->extra = NULL;
}

View file

@ -102,7 +102,11 @@ struct row_uri_range {
};
struct row_data {
tll(struct row_uri_range) uri_ranges;
struct {
struct row_uri_range *v;
uint32_t size;
uint32_t count;
} uri_ranges;
};
struct row {

View file

@ -439,24 +439,27 @@ osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls)
for (int r = 0; r < term->rows; r++) {
const struct row *row = grid_row_in_view(term->grid, r);
const struct row_data *extra = row->extra;
if (row->extra == NULL)
if (extra == NULL)
continue;
tll_foreach(row->extra->uri_ranges, it) {
for (size_t i = 0; i < extra->uri_ranges.count; i++) {
const struct row_uri_range *range = &extra->uri_ranges.v[i];
struct coord start = {
.col = it->item.start,
.col = range->start,
.row = r + term->grid->view,
};
struct coord end = {
.col = it->item.end,
.col = range->end,
.row = r + term->grid->view,
};
tll_push_back(
*urls,
((struct url){
.id = it->item.id,
.url = xstrdup(it->item.uri),
.id = range->id,
.url = xstrdup(range->uri),
.start = start,
.end = end,
.action = action,