diff --git a/grid.c b/grid.c index 2dcbaa55..e1b7ce38 100644 --- a/grid.c +++ b/grid.c @@ -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 URI’s 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); } diff --git a/grid.h b/grid.h index 8ede4773..7819db4d 100644 --- a/grid.h +++ b/grid.h @@ -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; } diff --git a/terminal.h b/terminal.h index 1f3913ae..3096186f 100644 --- a/terminal.h +++ b/terminal.h @@ -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 { diff --git a/url-mode.c b/url-mode.c index 8fb8a8a5..b565a87d 100644 --- a/url-mode.c +++ b/url-mode.c @@ -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,