From 20923bb2e8235c0e3d72f7c6195523ad0b0a2afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 23 Jun 2024 13:29:12 +0200 Subject: [PATCH 01/24] grid: refactor: first step towards a more generic range handling --- grid.c | 163 +++++++++++++++++++++++++++++++---------------------- grid.h | 20 ++++++- terminal.h | 27 ++++++--- url-mode.c | 6 +- 4 files changed, 135 insertions(+), 81 deletions(-) diff --git a/grid.c b/grid.c index 85e6183f..cbc00177 100644 --- a/grid.c +++ b/grid.c @@ -85,22 +85,27 @@ ensure_row_has_extra_data(struct row *row) } static void -verify_no_overlapping_uris(const struct row_data *extra) +verify_no_overlapping_ranges_of_type(const struct row_ranges *ranges, + enum row_range_type type) { #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 i = 0; i < ranges->count; i++) { + const struct row_range *r1 = &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]; + for (size_t j = i + 1; j < ranges->count; j++) { + const struct row_range *r2 = &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); + switch (type) { + case ROW_RANGE_URI: + BUG("OSC-8 URI overlap: %s: %d-%d: %s: %d-%d", + r1->uri.uri, r1->start, r1->end, + r2->uri.uri, r2->start, r2->end); + break; + } } } } @@ -108,20 +113,31 @@ verify_no_overlapping_uris(const struct row_data *extra) } static void -verify_uris_are_sorted(const struct row_data *extra) +verify_no_overlapping_ranges(const struct row_data *extra) +{ + verify_no_overlapping_ranges_of_type(&extra->uri_ranges, ROW_RANGE_URI); +} + +static void +verify_ranges_of_type_are_sorted(const struct row_ranges *ranges, + enum row_range_type type) { #if defined(_DEBUG) - const struct row_uri_range *last = NULL; + const struct row_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]; + for (size_t i = 0; i < ranges->count; i++) { + const struct row_range *r = &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); + switch (type) { + case ROW_RANGE_URI: + BUG("OSC-8 URI not sorted correctly: " + "%s: %d-%d came before %s: %d-%d", + last->uri.uri, last->start, last->end, + r->uri.uri, r->start, r->end); + break; + } } } @@ -130,6 +146,12 @@ verify_uris_are_sorted(const struct row_data *extra) #endif } +static void +verify_ranges_are_sorted(const struct row_data *extra) +{ + verify_ranges_of_type_are_sorted(&extra->uri_ranges, ROW_RANGE_URI); +} + static void uri_range_ensure_size(struct row_data *extra, uint32_t count_to_add) { @@ -161,11 +183,13 @@ uri_range_insert(struct row_data *extra, size_t idx, int start, int end, move_count * sizeof(extra->uri_ranges.v[0])); extra->uri_ranges.count++; - extra->uri_ranges.v[idx] = (struct row_uri_range){ + extra->uri_ranges.v[idx] = (struct row_range){ .start = start, .end = end, - .id = id, - .uri = xstrdup(uri), + .uri = { + .id = id, + .uri = xstrdup(uri), + }, }; } @@ -174,11 +198,13 @@ uri_range_append_no_strdup(struct row_data *extra, int start, int end, uint64_t id, char *uri) { uri_range_ensure_size(extra, 1); - extra->uri_ranges.v[extra->uri_ranges.count++] = (struct row_uri_range){ + extra->uri_ranges.v[extra->uri_ranges.count++] = (struct row_range){ .start = start, .end = end, - .id = id, - .uri = uri, + .uri = { + .id = id, + .uri = uri, + }, }; } @@ -246,10 +272,10 @@ grid_snapshot(const struct grid *grid) uri_range_ensure_size(clone_extra, extra->uri_ranges.count); for (size_t i = 0; i < extra->uri_ranges.count; i++) { - const struct row_uri_range *range = &extra->uri_ranges.v[i]; + const struct row_range *range = &extra->uri_ranges.v[i]; uri_range_append( clone_extra, - range->start, range->end, range->id, range->uri); + range->start, range->end, range->uri.id, range->uri.uri); } } else clone_row->extra = NULL; @@ -467,7 +493,7 @@ grid_resize_without_reflow( 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]; + const struct row_range *range = &old_extra->uri_ranges.v[i]; if (range->start >= new_cols) { /* The whole range is truncated */ @@ -476,7 +502,7 @@ grid_resize_without_reflow( 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); + uri_range_append(new_extra, start, end, range->uri.id, range->uri.uri); } } @@ -498,8 +524,8 @@ grid_resize_without_reflow( if (row->extra == NULL) continue; - verify_no_overlapping_uris(row->extra); - verify_uris_are_sorted(row->extra); + verify_no_overlapping_ranges(row->extra); + verify_ranges_are_sorted(row->extra); } #endif @@ -549,26 +575,26 @@ grid_resize_without_reflow( } static void -reflow_uri_range_start(struct row_uri_range *range, struct row *new_row, +reflow_uri_range_start(struct row_range *range, struct row *new_row, int new_col_idx) { ensure_row_has_extra_data(new_row); uri_range_append_no_strdup - (new_row->extra, new_col_idx, -1, range->id, range->uri); - range->uri = NULL; + (new_row->extra, new_col_idx, -1, range->uri.id, range->uri.uri); + range->uri.uri = NULL; } static void -reflow_uri_range_end(struct row_uri_range *range, struct row *new_row, +reflow_uri_range_end(struct row_range *range, struct row *new_row, int new_col_idx) { struct row_data *extra = new_row->extra; xassert(extra->uri_ranges.count > 0); - struct row_uri_range *new_range = + struct row_range *new_range = &extra->uri_ranges.v[extra->uri_ranges.count - 1]; - xassert(new_range->id == range->id); + xassert(new_range->uri.id == range->uri.id); xassert(new_range->end < 0); new_range->end = new_col_idx; } @@ -619,7 +645,7 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, * next/current row. */ if (extra->uri_ranges.count > 0) { - struct row_uri_range *range = + struct row_range *range = &extra->uri_ranges.v[extra->uri_ranges.count - 1]; if (range->end < 0) { @@ -629,7 +655,7 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, /* Open a new range on the new/current row */ ensure_row_has_extra_data(new_row); - uri_range_append(new_row->extra, 0, -1, range->id, range->uri); + uri_range_append(new_row->extra, 0, -1, range->uri.id, range->uri.uri); } } @@ -817,7 +843,7 @@ grid_resize_and_reflow( tp = NULL; /* Does this row have any URIs? */ - struct row_uri_range *range, *range_terminator; + struct row_range *range, *range_terminator; struct row_data *extra = old_row->extra; if (extra != NULL && extra->uri_ranges.count > 0) { @@ -826,7 +852,7 @@ grid_resize_and_reflow( /* Make sure the *last* URI range's end point is included * in the copy */ - const struct row_uri_range *last_on_row = + const struct row_range *last_on_row = &extra->uri_ranges.v[extra->uri_ranges.count - 1]; col_count = max(col_count, last_on_row->end + 1); } else @@ -1043,8 +1069,8 @@ grid_resize_and_reflow( for (size_t i = 0; i < row->extra->uri_ranges.count; i++) xassert(row->extra->uri_ranges.v[i].end >= 0); - verify_no_overlapping_uris(row->extra); - verify_uris_are_sorted(row->extra); + verify_no_overlapping_ranges(row->extra); + verify_ranges_are_sorted(row->extra); } /* Verify all old rows have been free:d */ @@ -1136,9 +1162,9 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) 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]; + struct row_range *r = &extra->uri_ranges.v[i]; - const bool matching_id = r->id == id; + const bool matching_id = r->uri.id == id; if (matching_id && r->end + 1 == col) { /* Extend existing URI's tail */ @@ -1177,7 +1203,7 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) xassert(r->start < col); xassert(r->end > col); - uri_range_insert(extra, i + 1, col + 1, r->end, r->id, r->uri); + uri_range_insert(extra, i + 1, col + 1, r->end, r->uri.id, r->uri.uri); /* The insertion may xrealloc() the vector, making our * 'old' pointer invalid */ @@ -1196,21 +1222,23 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) if (replace) { grid_row_uri_range_destroy(&extra->uri_ranges.v[insert_idx]); - extra->uri_ranges.v[insert_idx] = (struct row_uri_range){ + extra->uri_ranges.v[insert_idx] = (struct row_range){ .start = col, .end = col, - .id = id, - .uri = xstrdup(uri), + .uri = { + .id = id, + .uri = xstrdup(uri), + }, }; } else uri_range_insert(extra, insert_idx, col, col, id, uri); 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]; + struct row_range *r1 = &extra->uri_ranges.v[i - 1]; + struct row_range *r2 = &extra->uri_ranges.v[i]; - if (r1->id == r2->id && r1->end + 1 == r2->start) { + if (r1->uri.id == r2->uri.id && r1->end + 1 == r2->start) { r1->end = r2->end; uri_range_delete(extra, i); i--; @@ -1219,8 +1247,8 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) } out: - verify_no_overlapping_uris(extra); - verify_uris_are_sorted(extra); + verify_no_overlapping_ranges(extra); + verify_ranges_are_sorted(extra); } UNITTEST @@ -1233,7 +1261,7 @@ UNITTEST 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); \ + xassert(row_data.uri_ranges.v[idx].uri.id == _id); \ } while (0) grid_row_uri_range_put(&row, 0, "http://foo.bar", 123); @@ -1298,7 +1326,7 @@ grid_row_uri_range_erase(struct row *row, int start, int end) /* Split up, or remove, URI ranges affected by the erase */ for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { - struct row_uri_range *old = &extra->uri_ranges.v[i]; + struct row_range *old = &extra->uri_ranges.v[i]; if (old->end < start) return; @@ -1314,7 +1342,7 @@ grid_row_uri_range_erase(struct row *row, int start, int end) else if (start > old->start && end < old->end) { /* Erase range erases a part in the middle of the URI */ uri_range_insert( - extra, i + 1, end + 1, old->end, old->id, old->uri); + extra, i + 1, end + 1, old->end, old->uri.id, old->uri.uri); /* The insertion may xrealloc() the vector, making our * 'old' pointer invalid */ @@ -1352,14 +1380,14 @@ UNITTEST 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_data); - verify_uris_are_sorted(&row_data); + verify_no_overlapping_ranges(&row_data); + verify_ranges_are_sorted(&row_data); /* Erase both URis */ grid_row_uri_range_erase(&row, 1, 20); xassert(row_data.uri_ranges.count == 0); - verify_no_overlapping_uris(&row_data); - verify_uris_are_sorted(&row_data); + verify_no_overlapping_ranges(&row_data); + verify_ranges_are_sorted(&row_data); /* Two URIs, then erase second half of the first, first half of the second */ @@ -1371,11 +1399,11 @@ UNITTEST 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_data); - verify_uris_are_sorted(&row_data); + verify_no_overlapping_ranges(&row_data); + verify_ranges_are_sorted(&row_data); - grid_row_uri_range_destroy(&row_data.uri_ranges.v[0]); - grid_row_uri_range_destroy(&row_data.uri_ranges.v[1]); + grid_row_range_destroy(&row_data.uri_ranges.v[0], ROW_RANGE_URI); + grid_row_range_destroy(&row_data.uri_ranges.v[1], ROW_RANGE_URI); row_data.uri_ranges.count = 0; /* One URI, erase middle part of it */ @@ -1386,11 +1414,11 @@ UNITTEST 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_data); - verify_uris_are_sorted(&row_data); + verify_no_overlapping_ranges(&row_data); + verify_ranges_are_sorted(&row_data); - grid_row_uri_range_destroy(&row_data.uri_ranges.v[0]); - grid_row_uri_range_destroy(&row_data.uri_ranges.v[1]); + grid_row_range_destroy(&row_data.uri_ranges.v[0], ROW_RANGE_URI); + grid_row_range_destroy(&row_data.uri_ranges.v[1], ROW_RANGE_URI); row_data.uri_ranges.count = 0; /* @@ -1416,7 +1444,6 @@ UNITTEST grid_row_uri_range_erase(&row, 5, 7); xassert(row_data.uri_ranges.count == 2); - for (size_t i = 0; i < row_data.uri_ranges.count; i++) - grid_row_uri_range_destroy(&row_data.uri_ranges.v[i]); + grid_row_ranges_destroy(&row_data.uri_ranges, ROW_RANGE_URI); free(row_data.uri_ranges.v); } diff --git a/grid.h b/grid.h index 8ea5200b..7c1f14be 100644 --- a/grid.h +++ b/grid.h @@ -89,9 +89,25 @@ void grid_row_uri_range_put( void grid_row_uri_range_erase(struct row *row, int start, int end); static inline void -grid_row_uri_range_destroy(struct row_uri_range *range) +grid_row_uri_range_destroy(struct row_range *range) { - free(range->uri); + free(range->uri.uri); +} + +static inline void +grid_row_range_destroy(struct row_range *range, enum row_range_type type) +{ + switch (type) { + case ROW_RANGE_URI: grid_row_uri_range_destroy(range); break; + } +} + +static inline void +grid_row_ranges_destroy(struct row_ranges *ranges, enum row_range_type type) +{ + for (int i = 0; i < ranges->count; i++) { + grid_row_range_destroy(&ranges->v[i], type); + } } static inline void diff --git a/terminal.h b/terminal.h index 5033c550..fc654702 100644 --- a/terminal.h +++ b/terminal.h @@ -99,19 +99,30 @@ struct damage { uint16_t lines; }; -struct row_uri_range { - int start; - int end; +struct uri_range_data { uint64_t id; char *uri; }; +struct row_range { + int start; + int end; + + union { + struct uri_range_data uri; + }; +}; + +struct row_ranges { + struct row_range *v; + int size; + int count; +}; + +enum row_range_type {ROW_RANGE_URI}; + struct row_data { - struct { - struct row_uri_range *v; - uint32_t size; - uint32_t count; - } uri_ranges; + struct row_ranges uri_ranges; }; struct row { diff --git a/url-mode.c b/url-mode.c index 9356a362..57f47dd0 100644 --- a/url-mode.c +++ b/url-mode.c @@ -509,7 +509,7 @@ osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls) continue; for (size_t i = 0; i < extra->uri_ranges.count; i++) { - const struct row_uri_range *range = &extra->uri_ranges.v[i]; + const struct row_range *range = &extra->uri_ranges.v[i]; struct coord start = { .col = range->start, @@ -522,8 +522,8 @@ osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls) tll_push_back( *urls, ((struct url){ - .id = range->id, - .url = xstrdup(range->uri), + .id = range->uri.id, + .url = xstrdup(range->uri.uri), .range = { .start = start, .end = end, From 32effc6657c1802cdf34098748337b0d7bc39dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 23 Jun 2024 17:39:15 +0200 Subject: [PATCH 02/24] csi: wip: styled underlines This is work in progress, and fairly untested. This adds initial tracking of styled underlines. Setting attributes seems to work (both color and underline style). Grid reflow has *not* been tested. When rendering, style is currently ignored (all styles are rendered as a plain, legacy underline). Color however, *is* applied. --- csi.c | 58 ++++++++++- grid.c | 277 ++++++++++++++++++++++++++++++++++++++++++----------- grid.h | 15 ++- render.c | 39 +++++++- terminal.c | 22 ++++- terminal.h | 26 ++++- 6 files changed, 371 insertions(+), 66 deletions(-) diff --git a/csi.c b/csi.c index be470e70..b3583df5 100644 --- a/csi.c +++ b/csi.c @@ -32,7 +32,14 @@ static void sgr_reset(struct terminal *term) { + /* TODO: can we drop this check? */ + const enum curly_style curly_style = term->vt.curly.style; + memset(&term->vt.attrs, 0, sizeof(term->vt.attrs)); + memset(&term->vt.curly, 0, sizeof(term->vt.curly)); + + if (unlikely(curly_style > CURLY_SINGLE)) + term_update_ascii_printer(term); } static const char * @@ -88,7 +95,34 @@ csi_sgr(struct terminal *term) case 1: term->vt.attrs.bold = true; break; case 2: term->vt.attrs.dim = true; break; case 3: term->vt.attrs.italic = true; break; - case 4: term->vt.attrs.underline = true; break; + case 4: { + term->vt.attrs.underline = true; + term->vt.curly.style = CURLY_SINGLE; + + if (unlikely(term->vt.params.v[i].sub.idx == 1)) { + enum curly_style style = term->vt.params.v[i].sub.value[0]; + + switch (style) { + default: + case CURLY_NONE: + term->vt.attrs.underline = false; + term->vt.curly.style = CURLY_NONE; + break; + + case CURLY_SINGLE: + case CURLY_DOUBLE: + case CURLY_CURLY: + case CURLY_DOTTED: + case CURLY_DASHED: + term->vt.curly.style = style; break; + break; + } + + term_update_ascii_printer(term); + } + LOG_WARN("CURLY: %d", term->vt.curly.style); + break; + } case 5: term->vt.attrs.blink = true; break; case 6: LOG_WARN("ignored: rapid blink"); break; case 7: term->vt.attrs.reverse = true; break; @@ -98,7 +132,12 @@ csi_sgr(struct terminal *term) case 21: break; /* double-underline, not implemented */ case 22: term->vt.attrs.bold = term->vt.attrs.dim = false; break; case 23: term->vt.attrs.italic = false; break; - case 24: term->vt.attrs.underline = false; break; + case 24: { + term->vt.attrs.underline = false; + term->vt.curly.style = CURLY_NONE; + term_update_ascii_printer(term); + break; + } case 25: term->vt.attrs.blink = false; break; case 26: break; /* rapid blink, ignored */ case 27: term->vt.attrs.reverse = false; break; @@ -119,7 +158,8 @@ csi_sgr(struct terminal *term) break; case 38: - case 48: { + case 48: + case 58: { uint32_t color; enum color_source src; @@ -194,7 +234,11 @@ csi_sgr(struct terminal *term) break; } - if (param == 38) { + if (unlikely(param == 58)) { + term->vt.curly.color_src = src; + term->vt.curly.color = color; + term_update_ascii_printer(term); + } else if (param == 38) { term->vt.attrs.fg_src = src; term->vt.attrs.fg = color; } else { @@ -226,6 +270,12 @@ csi_sgr(struct terminal *term) term->vt.attrs.bg_src = COLOR_DEFAULT; break; + case 59: + term->vt.curly.color_src = COLOR_DEFAULT; + term->vt.curly.color = 0; + term_update_ascii_printer(term); + break; + /* Bright foreground colors */ case 90: case 91: diff --git a/grid.c b/grid.c index cbc00177..2eade784 100644 --- a/grid.c +++ b/grid.c @@ -105,6 +105,11 @@ verify_no_overlapping_ranges_of_type(const struct row_ranges *ranges, r1->uri.uri, r1->start, r1->end, r2->uri.uri, r2->start, r2->end); break; + + case ROW_RANGE_CURLY: + BUG("curly underline overlap: %d-%d, %d-%d", + r1->start, r1->end, r2->start, r2->end); + break; } } } @@ -116,6 +121,7 @@ static void verify_no_overlapping_ranges(const struct row_data *extra) { verify_no_overlapping_ranges_of_type(&extra->uri_ranges, ROW_RANGE_URI); + verify_no_overlapping_ranges_of_type(&extra->curly_ranges, ROW_RANGE_CURLY); } static void @@ -137,6 +143,12 @@ verify_ranges_of_type_are_sorted(const struct row_ranges *ranges, last->uri.uri, last->start, last->end, r->uri.uri, r->start, r->end); break; + + case ROW_RANGE_CURLY: + BUG("curly ranges not sorted correctly: " + "%d-%d came before %d-%d", + last->start, last->end, r->start, r->end); + break; } } } @@ -150,19 +162,37 @@ static void verify_ranges_are_sorted(const struct row_data *extra) { verify_ranges_of_type_are_sorted(&extra->uri_ranges, ROW_RANGE_URI); + verify_ranges_of_type_are_sorted(&extra->curly_ranges, ROW_RANGE_CURLY); } static void -uri_range_ensure_size(struct row_data *extra, uint32_t count_to_add) +range_ensure_size(struct row_ranges *ranges, int count_to_add) { - if (extra->uri_ranges.count + count_to_add > extra->uri_ranges.size) { - extra->uri_ranges.size = extra->uri_ranges.count + count_to_add; - extra->uri_ranges.v = xrealloc( - extra->uri_ranges.v, - extra->uri_ranges.size * sizeof(extra->uri_ranges.v[0])); + if (ranges->count + count_to_add > ranges->size) { + ranges->size = ranges->count + count_to_add; + ranges->v = xrealloc(ranges->v, ranges->size * sizeof(ranges->v[0])); } - xassert(extra->uri_ranges.count + count_to_add <= extra->uri_ranges.size); + xassert(ranges->count + count_to_add <= ranges->size); +} + +static void +range_insert(struct row_ranges *ranges, size_t idx, int start, int end) +{ + range_ensure_size(ranges, 1); + + xassert(idx <= ranges->count); + + const size_t move_count = ranges->count - idx; + memmove(&ranges->v[idx + 1], + &ranges->v[idx], + move_count * sizeof(ranges->v[0])); + + ranges->count++; + ranges->v[idx] = (struct row_range){ + .start = start, + .end = end, + }; } /* @@ -170,34 +200,27 @@ uri_range_ensure_size(struct row_data *extra, uint32_t count_to_add) * invalidating pointers into it. */ static void -uri_range_insert(struct row_data *extra, size_t idx, int start, int end, +uri_range_insert(struct row_ranges *ranges, size_t idx, int start, int end, uint64_t id, const char *uri) { - uri_range_ensure_size(extra, 1); + range_insert(ranges, idx, start, end); + ranges->v[idx].uri.id = id; + ranges->v[idx].uri.uri = xstrdup(uri); +} - 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++; - extra->uri_ranges.v[idx] = (struct row_range){ - .start = start, - .end = end, - .uri = { - .id = id, - .uri = xstrdup(uri), - }, - }; +static void +curly_range_insert(struct row_ranges *ranges, size_t idx, int start, int end, + struct curly_range_data data) +{ + range_insert(ranges, idx, start, end); + ranges->v[idx].curly = data; } static void uri_range_append_no_strdup(struct row_data *extra, int start, int end, uint64_t id, char *uri) { - uri_range_ensure_size(extra, 1); + range_ensure_size(&extra->uri_ranges, 1); extra->uri_ranges.v[extra->uri_ranges.count++] = (struct row_range){ .start = start, .end = end, @@ -216,16 +239,16 @@ uri_range_append(struct row_data *extra, int start, int end, uint64_t id, } static void -uri_range_delete(struct row_data *extra, size_t idx) +range_delete(struct row_ranges *ranges, enum row_range_type type, size_t idx) { - xassert(idx < extra->uri_ranges.count); - grid_row_uri_range_destroy(&extra->uri_ranges.v[idx]); + xassert(idx < ranges->count); + grid_row_range_destroy(&ranges->v[idx], type); - 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--; + const size_t move_count = ranges->count - idx - 1; + memmove(&ranges->v[idx], + &ranges->v[idx + 1], + move_count * sizeof(ranges->v[0])); + ranges->count--; } struct grid * @@ -269,14 +292,20 @@ grid_snapshot(const struct grid *grid) struct row_data *clone_extra = xcalloc(1, sizeof(*clone_extra)); clone_row->extra = clone_extra; - uri_range_ensure_size(clone_extra, extra->uri_ranges.count); + range_ensure_size(&clone_extra->uri_ranges, extra->uri_ranges.count); + range_ensure_size(&clone_extra->curly_ranges, extra->curly_ranges.count); - for (size_t i = 0; i < extra->uri_ranges.count; i++) { + for (int i = 0; i < extra->uri_ranges.count; i++) { const struct row_range *range = &extra->uri_ranges.v[i]; uri_range_append( clone_extra, range->start, range->end, range->uri.id, range->uri.uri); } + + for (int i = 0; i < extra->curly_ranges.count; i++) { + //const struct row_range *range = &extra->curly_ranges.v[i]; + BUG("TODO"); + } } else clone_row->extra = NULL; } @@ -490,9 +519,10 @@ grid_resize_without_reflow( ensure_row_has_extra_data(new_row); struct row_data *new_extra = new_row->extra; - uri_range_ensure_size(new_extra, old_extra->uri_ranges.count); + range_ensure_size(&new_extra->uri_ranges, old_extra->uri_ranges.count); + range_ensure_size(&new_extra->curly_ranges, old_extra->curly_ranges.count); - for (size_t i = 0; i < old_extra->uri_ranges.count; i++) { + for (int i = 0; i < old_extra->uri_ranges.count; i++) { const struct row_range *range = &old_extra->uri_ranges.v[i]; if (range->start >= new_cols) { @@ -504,7 +534,21 @@ grid_resize_without_reflow( const int end = min(range->end, new_cols - 1); uri_range_append(new_extra, start, end, range->uri.id, range->uri.uri); } - } + + for (int i = 0; i < old_extra->uri_ranges.count; i++) { + const struct row_range *range = &old_extra->uri_ranges.v[i]; + + if (range->start >= new_cols) { + /* The whole range is truncated */ + continue; + } + + //const int start = range->start; + //const int end = min(range->end, new_cols - 1); + //uri_range_append(new_extra, start, end, range->uri.id, range->uri.uri); + BUG("TODO"); + } +} /* Clear "new" lines */ for (int r = min(old_screen_rows, new_screen_rows); r < new_screen_rows; r++) { @@ -1161,7 +1205,7 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) bool run_merge_pass = false; struct row_data *extra = row->extra; - for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { + for (int i = extra->uri_ranges.count - 1; i >= 0; i--) { struct row_range *r = &extra->uri_ranges.v[i]; const bool matching_id = r->uri.id == id; @@ -1203,7 +1247,8 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) xassert(r->start < col); xassert(r->end > col); - uri_range_insert(extra, i + 1, col + 1, r->end, r->uri.id, r->uri.uri); + uri_range_insert( + &extra->uri_ranges, i + 1, col + 1, r->end, r->uri.id, r->uri.uri); /* The insertion may xrealloc() the vector, making our * 'old' pointer invalid */ @@ -1231,7 +1276,7 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) }, }; } else - uri_range_insert(extra, insert_idx, col, col, id, uri); + uri_range_insert(&extra->uri_ranges, insert_idx, col, col, id, uri); if (run_merge_pass) { for (size_t i = 1; i < extra->uri_ranges.count; i++) { @@ -1240,7 +1285,111 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) if (r1->uri.id == r2->uri.id && r1->end + 1 == r2->start) { r1->end = r2->end; - uri_range_delete(extra, i); + range_delete(&extra->uri_ranges, ROW_RANGE_URI, i); + i--; + } + } + } + +out: + verify_no_overlapping_ranges(extra); + verify_ranges_are_sorted(extra); +} + +void +grid_row_curly_range_put(struct row *row, int col, struct curly_range_data data) +{ + ensure_row_has_extra_data(row); + + size_t insert_idx = 0; + bool replace = false; + bool run_merge_pass = false; + + struct row_data *extra = row->extra; + for (int i = extra->curly_ranges.count - 1; i >= 0; i--) { + struct row_range *r = &extra->curly_ranges.v[i]; + + const bool matching = r->curly.style == data.style && + r->curly.color_src == data.color_src && + r->curly.color == data.color; + + if (matching && r->end + 1 == col) { + /* Extend existing curly tail */ + r->end++; + goto out; + } + + else if (r->end < col) { + insert_idx = i + 1; + break; + } + + else if (r->start > col) + continue; + + else { + xassert(r->start <= col); + xassert(r->end >= col); + + if (matching) + goto out; + + 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++; + insert_idx = i; + } else if (r->end == col) { + run_merge_pass = true; + r->end--; + insert_idx = i + 1; + } else { + xassert(r->start < col); + xassert(r->end > col); + + curly_range_insert( + &extra->curly_ranges, i + 1, col + 1, r->end, data); + + /* The insertion may xrealloc() the vector, making our + * 'old' pointer invalid */ + r = &extra->curly_ranges.v[i]; + r->end = col - 1; + xassert(r->start <= r->end); + + insert_idx = i + 1; + } + + break; + } + } + + xassert(insert_idx <= extra->curly_ranges.count); + + if (replace) { + grid_row_curly_range_destroy(&extra->curly_ranges.v[insert_idx]); + extra->curly_ranges.v[insert_idx] = (struct row_range){ + .start = col, + .end = col, + .curly = data, + }; + } else + curly_range_insert(&extra->curly_ranges, insert_idx, col, col, data); + + if (run_merge_pass) { + for (size_t i = 1; i < extra->curly_ranges.count; i++) { + struct row_range *r1 = &extra->curly_ranges.v[i - 1]; + struct row_range *r2 = &extra->curly_ranges.v[i]; + + if (r1->curly.style == r2->curly.style && + r1->curly.color_src == r2->curly.color_src && + r1->curly.color == r2->curly.color && + r1->end + 1 == r2->start) + { + r1->end = r2->end; + range_delete(&extra->curly_ranges, ROW_RANGE_CURLY, i); i--; } } @@ -1316,17 +1465,15 @@ UNITTEST #undef verify_range } -void -grid_row_uri_range_erase(struct row *row, int start, int end) +static void +grid_row_range_erase(struct row_ranges *ranges, enum row_range_type type, + 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 */ - for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { - struct row_range *old = &extra->uri_ranges.v[i]; + for (int i = ranges->count - 1; i >= 0; i--) { + struct row_range *old = &ranges->v[i]; if (old->end < start) return; @@ -1336,17 +1483,25 @@ 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 */ - uri_range_delete(extra, i); + range_delete(ranges, type, i); } else if (start > old->start && end < old->end) { /* Erase range erases a part in the middle of the URI */ - uri_range_insert( - extra, i + 1, end + 1, old->end, old->uri.id, old->uri.uri); + switch (type) { + case ROW_RANGE_URI: + uri_range_insert( + ranges, i + 1, end + 1, old->end, old->uri.id, old->uri.uri); + break; + + case ROW_RANGE_CURLY: + curly_range_insert(ranges, i + 1, end + 1, old->end, old->curly); + break; + } /* The insertion may xrealloc() the vector, making our * 'old' pointer invalid */ - old = &extra->uri_ranges.v[i]; + old = &ranges->v[i]; old->end = start - 1; return; /* There can be no more URIs affected by the erase range */ } @@ -1366,6 +1521,20 @@ grid_row_uri_range_erase(struct row *row, int start, int end) } } +void +grid_row_uri_range_erase(struct row *row, int start, int end) +{ + xassert(row->extra != NULL); + grid_row_range_erase(&row->extra->uri_ranges, ROW_RANGE_URI, start, end); +} + +void +grid_row_curly_range_erase(struct row *row, int start, int end) +{ + xassert(row->extra != NULL); + grid_row_range_erase(&row->extra->curly_ranges, ROW_RANGE_CURLY, start, end); +} + UNITTEST { struct row_data row_data = {.uri_ranges = {0}}; diff --git a/grid.h b/grid.h index 7c1f14be..c5c2b60c 100644 --- a/grid.h +++ b/grid.h @@ -88,17 +88,27 @@ void grid_row_uri_range_put( struct row *row, int col, const char *uri, uint64_t id); void grid_row_uri_range_erase(struct row *row, int start, int end); +void grid_row_curly_range_put( + struct row *row, int col, struct curly_range_data data); +void grid_row_curly_range_erase(struct row *row, int start, int end); + static inline void grid_row_uri_range_destroy(struct row_range *range) { free(range->uri.uri); } +static inline void +grid_row_curly_range_destroy(struct row_range *range) +{ +} + static inline void grid_row_range_destroy(struct row_range *range, enum row_range_type type) { switch (type) { case ROW_RANGE_URI: grid_row_uri_range_destroy(range); break; + case ROW_RANGE_CURLY: grid_row_curly_range_destroy(range); break; } } @@ -118,9 +128,10 @@ grid_row_reset_extra(struct row *row) if (likely(extra == NULL)) return; - for (size_t i = 0; i < extra->uri_ranges.count; i++) - grid_row_uri_range_destroy(&extra->uri_ranges.v[i]); + grid_row_ranges_destroy(&extra->uri_ranges, ROW_RANGE_URI); + grid_row_ranges_destroy(&extra->curly_ranges, ROW_RANGE_CURLY); free(extra->uri_ranges.v); + free(extra->curly_ranges.v); free(extra); row->extra = NULL; diff --git a/render.c b/render.c index fd7e743a..78cc5325 100644 --- a/render.c +++ b/render.c @@ -847,8 +847,43 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag pixman_image_unref(clr_pix); /* Underline */ - if (cell->attrs.underline) - draw_underline(term, pix, font, &fg, x, y, cell_cols); + if (cell->attrs.underline) { + pixman_color_t underline_color = fg; + + /* Check if cell has a styled underline. This lookup is fairly + expensive... */ + if (row->extra != NULL) { + for (int i = 0; i < row->extra->curly_ranges.count; i++) { + const struct row_range *range = &row->extra->curly_ranges.v[i]; + + if (range->start <= col && col <= range->end) { + switch (range->curly.color_src) { + case COLOR_BASE256: + underline_color = color_hex_to_pixman( + term->colors.table[range->curly.color]); + break; + + case COLOR_RGB: + underline_color = + color_hex_to_pixman(range->curly.color); + break; + + case COLOR_DEFAULT: + break; + + case COLOR_BASE16: + BUG("underline color can't be base-16"); + break; + } + + break; + } + } + } + + draw_underline(term, pix, font, &underline_color, x, y, cell_cols); + + } if (cell->attrs.strikethrough) draw_strikeout(term, pix, font, &fg, x, y, cell_cols); diff --git a/terminal.c b/terminal.c index 55c387c1..48c0b298 100644 --- a/terminal.c +++ b/terminal.c @@ -1940,8 +1940,10 @@ erase_cell_range(struct terminal *term, struct row *row, int start, int end) } else memset(&row->cells[start], 0, (end - start + 1) * sizeof(row->cells[0])); - if (unlikely(row->extra != NULL)) + if (unlikely(row->extra != NULL)) { grid_row_uri_range_erase(row, start, end); + grid_row_curly_range_erase(row, start, end); + } } static inline void @@ -3621,6 +3623,7 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, cell->wc = data; cell->attrs = attrs; + /* TODO: why do we print the URI here, and then erase it below? */ if (unlikely(term->vt.osc8.uri != NULL)) { grid_row_uri_range_put(row, c, term->vt.osc8.uri, term->vt.osc8.id); @@ -3635,8 +3638,10 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, } } - if (unlikely(row->extra != NULL)) + if (unlikely(row->extra != NULL)) { grid_row_uri_range_erase(row, c, c + count - 1); + grid_row_curly_range_erase(row, c, c + count - 1); + } } void @@ -3706,6 +3711,13 @@ term_print(struct terminal *term, char32_t wc, int width) } else if (row->extra != NULL) grid_row_uri_range_erase(row, col, col + width - 1); + if (unlikely(term->vt.curly.style > CURLY_SINGLE || + term->vt.curly.color_src != COLOR_DEFAULT)) + { + grid_row_curly_range_put(row, col, term->vt.curly); + } else if (row->extra != NULL) + grid_row_curly_range_erase(row, col, col + width - 1); + /* Advance cursor the 'additional' columns while dirty:ing the cells */ for (int i = 1; i < width && (col + 1) < term->cols; i++) { col++; @@ -3763,8 +3775,10 @@ ascii_printer_fast(struct terminal *term, char32_t wc) grid->cursor.point.col = col; - if (unlikely(row->extra != NULL)) + if (unlikely(row->extra != NULL)) { grid_row_uri_range_erase(row, uri_start, uri_start); + grid_row_curly_range_erase(row, uri_start, uri_start); + } } static void @@ -3781,6 +3795,8 @@ term_update_ascii_printer(struct terminal *term) void (*new_printer)(struct terminal *term, char32_t wc) = unlikely(tll_length(term->grid->sixel_images) > 0 || term->vt.osc8.uri != NULL || + term->vt.curly.style > CURLY_SINGLE || + term->vt.curly.color_src != COLOR_DEFAULT || term->charsets.set[term->charsets.selected] == CHARSET_GRAPHIC || term->insert_mode) ? &ascii_printer_generic diff --git a/terminal.h b/terminal.h index fc654702..7f6814d3 100644 --- a/terminal.h +++ b/terminal.h @@ -104,12 +104,33 @@ struct uri_range_data { char *uri; }; +enum curly_style { + CURLY_NONE, + CURLY_SINGLE, /* Legacy underline */ + CURLY_DOUBLE, + CURLY_CURLY, + CURLY_DOTTED, + CURLY_DASHED, +}; + +struct curly_range_data { + enum curly_style style; + enum color_source color_src; + uint32_t color; +}; + +union row_range_data { + struct uri_range_data uri; + struct curly_range_data curly; +}; + struct row_range { int start; int end; union { struct uri_range_data uri; + struct curly_range_data curly; }; }; @@ -119,10 +140,11 @@ struct row_ranges { int count; }; -enum row_range_type {ROW_RANGE_URI}; +enum row_range_type {ROW_RANGE_URI, ROW_RANGE_CURLY}; struct row_data { struct row_ranges uri_ranges; + struct row_ranges curly_ranges; }; struct row { @@ -271,6 +293,8 @@ struct vt { char *uri; } osc8; + struct curly_range_data curly; + struct { uint8_t *data; size_t size; From 05f97744164985542b2534c02b817b3ff50f364f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 23 Jun 2024 18:53:51 +0200 Subject: [PATCH 03/24] foot.info: add smulx (styled underlines) --- foot.info | 1 + 1 file changed, 1 insertion(+) diff --git a/foot.info b/foot.info index cf5c7b82..e88f07c2 100644 --- a/foot.info +++ b/foot.info @@ -40,6 +40,7 @@ RV=\E[>c, Rect=\E[%p1%d;%p2%d;%p3%d;%p4%d;%p5%d$x, Se=\E[ q, + Smulx=\E[4:%p1%dm, Ss=\E[%p1%d q, Sync=\E[?2026%?%p1%{1}%-%tl%eh%;, TS=\E]2;, From a45ccfaed01b3c2bf52dd436a24b93a27a7c0742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 23 Jun 2024 18:55:29 +0200 Subject: [PATCH 04/24] csi: remove debug log --- csi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/csi.c b/csi.c index b3583df5..fe06a31f 100644 --- a/csi.c +++ b/csi.c @@ -120,7 +120,6 @@ csi_sgr(struct terminal *term) term_update_ascii_printer(term); } - LOG_WARN("CURLY: %d", term->vt.curly.style); break; } case 5: term->vt.attrs.blink = true; break; From 8e2402605ec81358cd18c657c9698044598b72f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 23 Jun 2024 18:55:37 +0200 Subject: [PATCH 05/24] render: styled underlines This was originally contributed by @kraftwerk28 in https://codeberg.org/dnkl/foot/pulls/1099 Here, we re-use the rendering logic only, as attribute tracking has been completely rewritten. --- render.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/render.c b/render.c index 78cc5325..471ca467 100644 --- a/render.c +++ b/render.c @@ -384,6 +384,120 @@ draw_underline(const struct terminal *term, pixman_image_t *pix, x, y + y_ofs, cols * term->cell_width, thickness}); } +static void +draw_styled_underline(const struct terminal *term, pixman_image_t *pix, + const struct fcft_font *font, + const pixman_color_t *color, + enum curly_style style, int x, int y, int cols) +{ + xassert(style != CURLY_NONE); + + const int thickness = font->underline.thickness; + + int y_ofs; + + /* Make sure the line isn't positioned below the cell */ + switch (style) { + case CURLY_DOUBLE: + case CURLY_CURLY: + y_ofs = min(underline_offset(term, font), + term->cell_height - thickness * 3); + break; + + default: + y_ofs = min(underline_offset(term, font), + term->cell_height - thickness); + break; + } + + const int ceil_w = cols * term->cell_width; + + switch (style) { + case CURLY_DOUBLE: { + const pixman_rectangle16_t rects[] = { + {x, y + y_ofs, ceil_w, thickness}, + {x, y + y_ofs + thickness * 2, ceil_w, thickness}}; + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, 2, rects); + break; + } + + case CURLY_DASHED: { + const int ceil_w = cols * term->cell_width; + const int dash_w = ceil_w / 3 + (ceil_w % 3 > 0); + const pixman_rectangle16_t rects[] = { + {x, y + y_ofs, dash_w, thickness}, + {x + dash_w * 2, y + y_ofs, dash_w, thickness}, + }; + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, pix, color, 2, rects); + break; + } + case CURLY_DOTTED: { + const int ceil_w = cols * term->cell_width; + const int nrects = min(ceil_w / thickness / 2, 16); + pixman_rectangle16_t rects[16] = {0}; + + for (int i = 0; i < nrects; i++) { + rects[i] = (pixman_rectangle16_t){ + x + i * thickness * 2, y + y_ofs, thickness, thickness}; + } + + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, nrects, rects); + break; + } + + case CURLY_CURLY: { + const int top = y + y_ofs; + const int bot = top + thickness * 3; + const int half_x = x + ceil_w / 2.0, full_x = x + ceil_w; + + const double bt_2 = (bot - top) * (bot - top); + const double th_2 = thickness * thickness; + const double hx_2 = ceil_w * ceil_w / 4.0; + const int th = round(sqrt(th_2 + (th_2 * bt_2 / hx_2)) / 2.); + + #define I(x) pixman_int_to_fixed(x) + const pixman_trapezoid_t traps[] = { +#if 0 /* characters sit within the "dips" of the curlies */ + { + I(top), I(bot), + {{I(x), I(top + th)}, {I(half_x), I(bot + th)}}, + {{I(x), I(top - th)}, {I(half_x), I(bot - th)}}, + }, + { + I(top), I(bot), + {{I(half_x), I(bot - th)}, {I(full_x), I(top - th)}}, + {{I(half_x), I(bot + th)}, {I(full_x), I(top + th)}}, + } +#else /* characters sit on top of the curlies */ + { + I(top), I(bot), + {{I(x), I(bot - th)}, {I(half_x), I(top - th)}}, + {{I(x), I(bot + th)}, {I(half_x), I(top + th)}}, + }, + { + I(top), I(bot), + {{I(half_x), I(top + th)}, {I(full_x), I(bot + th)}}, + {{I(half_x), I(top - th)}, {I(full_x), I(bot - th)}}, + } +#endif + }; + + pixman_image_t *fill = pixman_image_create_solid_fill(color); + pixman_composite_trapezoids( + PIXMAN_OP_OVER, fill, pix, PIXMAN_a8, 0, 0, 0, 0, + sizeof(traps) / sizeof(traps[0]), traps); + + pixman_image_unref(fill); + break; + } + + default: + draw_underline(term, pix, font, color, x, y, cols); + break; + } +} + static void draw_strikeout(const struct terminal *term, pixman_image_t *pix, const struct fcft_font *font, @@ -849,6 +963,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag /* Underline */ if (cell->attrs.underline) { pixman_color_t underline_color = fg; + enum curly_style underline_style = CURLY_SINGLE; /* Check if cell has a styled underline. This lookup is fairly expensive... */ @@ -876,12 +991,14 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag break; } + underline_style = range->curly.style; break; } } } - draw_underline(term, pix, font, &underline_color, x, y, cell_cols); + draw_styled_underline( + term, pix, font, &underline_color, underline_style, x, y, cell_cols); } From b20302c2a7bcb70844f04c122c640b88be45ff60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 00:57:03 +0200 Subject: [PATCH 06/24] grid: reflow: handle styled underlines --- grid.c | 166 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 128 insertions(+), 38 deletions(-) diff --git a/grid.c b/grid.c index 2eade784..3cd818a7 100644 --- a/grid.c +++ b/grid.c @@ -238,6 +238,18 @@ uri_range_append(struct row_data *extra, int start, int end, uint64_t id, uri_range_append_no_strdup(extra, start, end, id, xstrdup(uri)); } +static void +curly_range_append(struct row_data *extra, int start, int end, + struct curly_range_data data) +{ + range_ensure_size(&extra->curly_ranges, 1); + extra->curly_ranges.v[extra->curly_ranges.count++] = (struct row_range){ + .start = start, + .end = end, + .curly = data, + }; +} + static void range_delete(struct row_ranges *ranges, enum row_range_type type, size_t idx) { @@ -303,8 +315,8 @@ grid_snapshot(const struct grid *grid) } for (int i = 0; i < extra->curly_ranges.count; i++) { - //const struct row_range *range = &extra->curly_ranges.v[i]; - BUG("TODO"); + const struct row_range *range = &extra->curly_ranges.v[i]; + curly_range_append(clone_extra, range->start, range->end, range->curly); } } else clone_row->extra = NULL; @@ -535,18 +547,17 @@ grid_resize_without_reflow( uri_range_append(new_extra, start, end, range->uri.id, range->uri.uri); } - for (int i = 0; i < old_extra->uri_ranges.count; i++) { - const struct row_range *range = &old_extra->uri_ranges.v[i]; + for (int i = 0; i < old_extra->curly_ranges.count; i++) { + const struct row_range *range = &old_extra->curly_ranges.v[i]; if (range->start >= new_cols) { /* The whole range is truncated */ continue; } - //const int start = range->start; - //const int end = min(range->end, new_cols - 1); - //uri_range_append(new_extra, start, end, range->uri.id, range->uri.uri); - BUG("TODO"); + const int start = range->start; + const int end = min(range->end, new_cols - 1); + curly_range_append(new_extra, start, end, range->curly); } } @@ -623,8 +634,8 @@ reflow_uri_range_start(struct row_range *range, struct row *new_row, int new_col_idx) { ensure_row_has_extra_data(new_row); - uri_range_append_no_strdup - (new_row->extra, new_col_idx, -1, range->uri.id, range->uri.uri); + uri_range_append_no_strdup( + new_row->extra, new_col_idx, -1, range->uri.id, range->uri.uri); range->uri.uri = NULL; } @@ -643,6 +654,33 @@ reflow_uri_range_end(struct row_range *range, struct row *new_row, new_range->end = new_col_idx; } +static void +reflow_curly_range_start(struct row_range *range, struct row *new_row, + int new_col_idx) +{ + ensure_row_has_extra_data(new_row); + curly_range_append(new_row->extra, new_col_idx, -1, range->curly); +} + + +static void +reflow_curly_range_end(struct row_range *range, struct row *new_row, + int new_col_idx) +{ + struct row_data *extra = new_row->extra; + xassert(extra->curly_ranges.count > 0); + + struct row_range *new_range = + &extra->curly_ranges.v[extra->curly_ranges.count - 1]; + + xassert(new_range->curly.style == range->curly.style); + xassert(new_range->curly.color_src == range->curly.color_src); + xassert(new_range->curly.color == range->curly.color); + + xassert(new_range->end < 0); + new_range->end = new_col_idx; +} + static struct row * _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, int *row_idx, int *col_idx, int row_count, int col_count) @@ -703,6 +741,21 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, } } + if (extra->curly_ranges.count > 0) { + struct row_range *range = + &extra->curly_ranges.v[extra->curly_ranges.count - 1]; + + if (range->end < 0) { + + /* Terminate URI range on the previous row */ + range->end = col_count - 1; + + /* Open a new range on the new/current row */ + ensure_row_has_extra_data(new_row); + curly_range_append(new_row->extra, 0, -1, range->curly); + } + } + return new_row; } @@ -887,12 +940,13 @@ grid_resize_and_reflow( tp = NULL; /* Does this row have any URIs? */ - struct row_range *range, *range_terminator; + struct row_range *uri_range, *uri_range_terminator; + struct row_range *curly_range, *curly_range_terminator; struct row_data *extra = old_row->extra; if (extra != NULL && extra->uri_ranges.count > 0) { - range = &extra->uri_ranges.v[0]; - range_terminator = &extra->uri_ranges.v[extra->uri_ranges.count]; + uri_range = &extra->uri_ranges.v[0]; + uri_range_terminator = &extra->uri_ranges.v[extra->uri_ranges.count]; /* Make sure the *last* URI range's end point is included * in the copy */ @@ -900,18 +954,32 @@ grid_resize_and_reflow( &extra->uri_ranges.v[extra->uri_ranges.count - 1]; col_count = max(col_count, last_on_row->end + 1); } else - range = range_terminator = NULL; + uri_range = uri_range_terminator = NULL; + + if (extra != NULL && extra->curly_ranges.count > 0) { + curly_range = &extra->curly_ranges.v[0]; + curly_range_terminator = &extra->curly_ranges.v[extra->curly_ranges.count]; + + const struct row_range *last_on_row = + &extra->curly_ranges.v[extra->curly_ranges.count - 1]; + col_count = max(col_count, last_on_row->end + 1); + } else + curly_range = curly_range_terminator = NULL; for (int start = 0, left = col_count; left > 0;) { int end; bool tp_break = false; bool uri_break = false; + bool curly_break = false; bool ftcs_break = false; /* Figure out where to end this chunk */ { - const int uri_col = range != range_terminator - ? ((range->start >= start ? range->start : range->end) + 1) + const int uri_col = uri_range != uri_range_terminator + ? ((uri_range->start >= start ? uri_range->start : uri_range->end) + 1) + : INT_MAX; + const int curly_col = curly_range != curly_range_terminator + ? ((curly_range->start >= start ? curly_range->start : curly_range->end) + 1) : INT_MAX; const int tp_col = tp != NULL ? tp->col + 1 : INT_MAX; const int ftcs_col = old_row->shell_integration.cmd_start >= start @@ -920,9 +988,10 @@ grid_resize_and_reflow( ? old_row->shell_integration.cmd_end + 1 : INT_MAX; - end = min(col_count, min(min(tp_col, uri_col), ftcs_col)); + end = min(col_count, min(min(tp_col, min(uri_col, curly_col)), ftcs_col)); uri_break = end == uri_col; + curly_break = end == curly_col; tp_break = end == tp_col; ftcs_break = end == ftcs_col; } @@ -1033,15 +1102,28 @@ grid_resize_and_reflow( } if (uri_break) { - xassert(range != NULL); + xassert(uri_range != NULL); - if (range->start == end - 1) - reflow_uri_range_start(range, new_row, new_col_idx - 1); + if (uri_range->start == end - 1) + reflow_uri_range_start(uri_range, new_row, new_col_idx - 1); - if (range->end == end - 1) { - reflow_uri_range_end(range, new_row, new_col_idx - 1); - grid_row_uri_range_destroy(range); - range++; + if (uri_range->end == end - 1) { + reflow_uri_range_end(uri_range, new_row, new_col_idx - 1); + grid_row_uri_range_destroy(uri_range); + uri_range++; + } + } + + if (curly_break) { + xassert(curly_range != NULL); + + if (curly_range->start == end - 1) + reflow_curly_range_start(curly_range, new_row, new_col_idx - 1); + + if (curly_range->end == end - 1) { + reflow_curly_range_end(curly_range, new_row, new_col_idx - 1); + grid_row_curly_range_destroy(curly_range); + curly_range++; } } @@ -1067,20 +1149,26 @@ grid_resize_and_reflow( if (r + 1 < old_rows) line_wrap(); - 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); + else if (new_row->extra != NULL) { + if (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). + */ + int last_idx = new_row->extra->uri_ranges.count - 1; + xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0); + } + + if (new_row->extra->curly_ranges.count > 0) { + int last_idx = new_row->extra->curly_ranges.count - 1; + xassert(new_row->extra->curly_ranges.v[last_idx].end >= 0); + + } } } @@ -1112,6 +1200,8 @@ grid_resize_and_reflow( for (size_t i = 0; i < row->extra->uri_ranges.count; i++) xassert(row->extra->uri_ranges.v[i].end >= 0); + for (size_t i = 0; i < row->extra->curly_ranges.count; i++) + xassert(row->extra->curly_ranges.v[i].end >= 0); verify_no_overlapping_ranges(row->extra); verify_ranges_are_sorted(row->extra); From 963ce45f3f29bccf7ca5aa0dc8e258a11cfbc552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 00:57:24 +0200 Subject: [PATCH 07/24] render: resize: copy styled underlines to temporary grid When doing an interactive resize, we create a small grid copy of the current viewport, and then do a non-reflow resize. When the interactive resize is done, we do a proper reflow. This is for performance reasons. When creating the viewport copy, we also need to copy the styled underlines. Otherwise, styled underlines will be rendered as plain underlines *while resizing*. --- render.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/render.c b/render.c index 471ca467..b9a597a9 100644 --- a/render.c +++ b/render.c @@ -4395,6 +4395,29 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) memcpy(g.rows[i]->cells, orig->rows[j]->cells, g.num_cols * sizeof(g.rows[i]->cells[0])); + + if (orig->rows[j]->extra == NULL || + orig->rows[j]->extra->curly_ranges.count == 0) + { + continue; + } + + /* + * Copy undercurly ranges + */ + + const struct row_ranges *curly_src = &orig->rows[j]->extra->curly_ranges; + + const int count = curly_src->count; + g.rows[i]->extra = xcalloc(1, sizeof(*g.rows[i]->extra)); + g.rows[i]->extra->curly_ranges.v = xmalloc( + count * sizeof(g.rows[i]->extra->curly_ranges.v[0])); + + struct row_ranges *curly_dst = &g.rows[i]->extra->curly_ranges; + curly_dst->count = curly_dst->size = count; + + for (int k = 0; k < count; k++) + curly_dst->v[k] = curly_src->v[k]; } term->normal = g; From 3b738c6e683702f25ad8101a686db5917de8e0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 01:07:17 +0200 Subject: [PATCH 08/24] terminal: term_fill(): fix osc8 erase bug + handle styled underlines Only clear OSC-8 hyperlinks at the target columns if we don't have an active OSC-8 URI. This corresponds to normal VT attributes; the currently active attributes are set, and all others are cleared. Handle styled underlines in the same way --- terminal.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 48c0b298..771e4878 100644 --- a/terminal.c +++ b/terminal.c @@ -3636,11 +3636,25 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, break; } } + + if (unlikely(term->vt.curly.style > CURLY_SINGLE || + term->vt.curly.color_src != COLOR_DEFAULT)) + { + grid_row_curly_range_put(row, c, term->vt.curly); + } } if (unlikely(row->extra != NULL)) { - grid_row_uri_range_erase(row, c, c + count - 1); - grid_row_curly_range_erase(row, c, c + count - 1); + if (likely(term->vt.osc8.uri != NULL)) + grid_row_uri_range_erase(row, c, c + count - 1); + + if (likely(term->vt.curly.style <= CURLY_SINGLE && + term->vt.curly.color_src == COLOR_DEFAULT)) + { + /* No extended/styled underlines active, so erase any such + attributes at the target columns */ + grid_row_curly_range_erase(row, c, c + count - 1); + } } } From 22302d8bccaa76aef9b9dca97ebc35b4ed37afff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 01:09:24 +0200 Subject: [PATCH 09/24] term: term_fill(): only set OSC-8 + styled hyperlinks when use_sgr_attrs is set --- terminal.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/terminal.c b/terminal.c index 771e4878..ecd55d77 100644 --- a/terminal.c +++ b/terminal.c @@ -3624,7 +3624,7 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, cell->attrs = attrs; /* TODO: why do we print the URI here, and then erase it below? */ - if (unlikely(term->vt.osc8.uri != NULL)) { + if (unlikely(use_sgr_attrs && term->vt.osc8.uri != NULL)) { grid_row_uri_range_put(row, c, term->vt.osc8.uri, term->vt.osc8.id); switch (term->conf->url.osc8_underline) { @@ -3637,8 +3637,9 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, } } - if (unlikely(term->vt.curly.style > CURLY_SINGLE || - term->vt.curly.color_src != COLOR_DEFAULT)) + if (unlikely(use_sgr_attrs && + (term->vt.curly.style > CURLY_SINGLE || + term->vt.curly.color_src != COLOR_DEFAULT))) { grid_row_curly_range_put(row, c, term->vt.curly); } From 48cf57818d354625fd1820ee82e31b8bb3227202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 01:26:57 +0200 Subject: [PATCH 10/24] term: performance: use a bitfield to track which ascii printer to use The things affecting which ASCII printer we use have grown... Instead of checking everything inside term_update_ascii_printer(), use a bitfield. Anything affecting the printer used, must now set a bit in this bitfield. This makes term_update_ascii_printer() much faster, since all it needs to do is check if the bitfield is zero or not. --- csi.c | 19 ++++++++++++++----- sixel.c | 11 +++++++++++ terminal.c | 23 +++++++++++++++-------- terminal.h | 11 +++++++++++ vt.c | 12 ++++++++++++ 5 files changed, 63 insertions(+), 13 deletions(-) diff --git a/csi.c b/csi.c index fe06a31f..387c37d9 100644 --- a/csi.c +++ b/csi.c @@ -33,13 +33,12 @@ static void sgr_reset(struct terminal *term) { /* TODO: can we drop this check? */ - const enum curly_style curly_style = term->vt.curly.style; - memset(&term->vt.attrs, 0, sizeof(term->vt.attrs)); memset(&term->vt.curly, 0, sizeof(term->vt.curly)); - if (unlikely(curly_style > CURLY_SINGLE)) - term_update_ascii_printer(term); + term->bits_affecting_ascii_printer.curly_style = false; + term->bits_affecting_ascii_printer.curly_color = false; + term_update_ascii_printer(term); } static const char * @@ -107,6 +106,7 @@ csi_sgr(struct terminal *term) case CURLY_NONE: term->vt.attrs.underline = false; term->vt.curly.style = CURLY_NONE; + term->bits_affecting_ascii_printer.curly_style = false; break; case CURLY_SINGLE: @@ -114,7 +114,9 @@ csi_sgr(struct terminal *term) case CURLY_CURLY: case CURLY_DOTTED: case CURLY_DASHED: - term->vt.curly.style = style; break; + term->vt.curly.style = style; + term->bits_affecting_ascii_printer.curly_style = + style > CURLY_SINGLE; break; } @@ -134,6 +136,7 @@ csi_sgr(struct terminal *term) case 24: { term->vt.attrs.underline = false; term->vt.curly.style = CURLY_NONE; + term->bits_affecting_ascii_printer.curly_style = false; term_update_ascii_printer(term); break; } @@ -236,6 +239,7 @@ csi_sgr(struct terminal *term) if (unlikely(param == 58)) { term->vt.curly.color_src = src; term->vt.curly.color = color; + term->bits_affecting_ascii_printer.curly_color = true; term_update_ascii_printer(term); } else if (param == 38) { term->vt.attrs.fg_src = src; @@ -272,6 +276,7 @@ csi_sgr(struct terminal *term) case 59: term->vt.curly.color_src = COLOR_DEFAULT; term->vt.curly.color = 0; + term->bits_affecting_ascii_printer.curly_color = false; term_update_ascii_printer(term); break; @@ -527,6 +532,9 @@ decset_decrst(struct terminal *term, unsigned param, bool enable) tll_free(term->alt.scroll_damage); term_damage_view(term); } + + term->bits_affecting_ascii_printer.sixels = + tll_length(term->grid->sixel_images) > 0; term_update_ascii_printer(term); break; @@ -1165,6 +1173,7 @@ csi_dispatch(struct terminal *term, uint8_t final) if (param == 4) { /* Insertion Replacement Mode (IRM) */ term->insert_mode = sm; + term->bits_affecting_ascii_printer.insert_mode = sm; term_update_ascii_printer(term); break; } diff --git a/sixel.c b/sixel.c index 279aef05..161eaad5 100644 --- a/sixel.c +++ b/sixel.c @@ -420,6 +420,8 @@ sixel_scroll_up(struct terminal *term, int rows) } } + term->bits_affecting_ascii_printer.sixels = + tll_length(term->grid->sixel_images) > 0; term_update_ascii_printer(term); verify_sixels(term); } @@ -444,6 +446,8 @@ sixel_scroll_down(struct terminal *term, int rows) break; } + term->bits_affecting_ascii_printer.sixels = + tll_length(term->grid->sixel_images) > 0; term_update_ascii_printer(term); verify_sixels(term); } @@ -852,6 +856,8 @@ sixel_overwrite_by_rectangle( } else _sixel_overwrite_by_rectangle(term, start, col, height, width, NULL, NULL); + term->bits_affecting_ascii_printer.sixels = + tll_length(term->grid->sixel_images) > 0; term_update_ascii_printer(term); } @@ -908,6 +914,8 @@ sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width) } } + term->bits_affecting_ascii_printer.sixels = + tll_length(term->grid->sixel_images) > 0; term_update_ascii_printer(term); } @@ -1387,6 +1395,9 @@ sixel_unhook(struct terminal *term) LOG_DBG("you now have %zu sixels in current grid", tll_length(term->grid->sixel_images)); + + term->bits_affecting_ascii_printer.sixels = + tll_length(term->grid->sixel_images) > 0; term_update_ascii_printer(term); render_refresh(term); } diff --git a/terminal.c b/terminal.c index ecd55d77..83cbb42b 100644 --- a/terminal.c +++ b/terminal.c @@ -2023,6 +2023,7 @@ term_reset(struct terminal *term, bool hard) term_ime_enable(term); #endif + term->bits_affecting_ascii_printer.value = 0; term_update_ascii_printer(term); if (!hard) @@ -3021,6 +3022,9 @@ term_restore_cursor(struct terminal *term, const struct cursor *cursor) term->vt.attrs = term->vt.saved_attrs; term->charsets = term->saved_charsets; + + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); } @@ -3801,21 +3805,21 @@ ascii_printer_single_shift(struct terminal *term, char32_t wc) { ascii_printer_generic(term, wc); term->charsets.selected = term->charsets.saved; + + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); } void term_update_ascii_printer(struct terminal *term) { + _Static_assert(sizeof(term->bits_affecting_ascii_printer) == sizeof(uint8_t), "bad size"); + void (*new_printer)(struct terminal *term, char32_t wc) = - unlikely(tll_length(term->grid->sixel_images) > 0 || - term->vt.osc8.uri != NULL || - term->vt.curly.style > CURLY_SINGLE || - term->vt.curly.color_src != COLOR_DEFAULT || - term->charsets.set[term->charsets.selected] == CHARSET_GRAPHIC || - term->insert_mode) - ? &ascii_printer_generic - : &ascii_printer_fast; + unlikely(term->bits_affecting_ascii_printer.value != 0) + ? &ascii_printer_generic + : &ascii_printer_fast; #if defined(_DEBUG) && LOG_ENABLE_DBG if (term->ascii_printer != new_printer) { @@ -4108,6 +4112,8 @@ term_osc8_open(struct terminal *term, uint64_t id, const char *uri) term->vt.osc8.id = id; term->vt.osc8.uri = xstrdup(uri); + + term->bits_affecting_ascii_printer.osc8 = true; term_update_ascii_printer(term); } @@ -4117,6 +4123,7 @@ term_osc8_close(struct terminal *term) free(term->vt.osc8.uri); term->vt.osc8.uri = NULL; term->vt.osc8.id = 0; + term->bits_affecting_ascii_printer.osc8 = false; term_update_ascii_printer(term); } diff --git a/terminal.h b/terminal.h index 7f6814d3..9fdd3dc0 100644 --- a/terminal.h +++ b/terminal.h @@ -393,6 +393,17 @@ struct terminal { const struct config *conf; void (*ascii_printer)(struct terminal *term, char32_t c); + union { + struct { + bool sixels:1; + bool osc8:1; + bool curly_style:1; + bool curly_color:1; + bool insert_mode:1; + bool charset:1; + }; + uint8_t value; + } bits_affecting_ascii_printer; pid_t slave; int ptmx; diff --git a/vt.c b/vt.c index ba78540f..487c5f5f 100644 --- a/vt.c +++ b/vt.c @@ -243,12 +243,16 @@ action_execute(struct terminal *term, uint8_t c) case '\x0e': /* SO - shift out */ term->charsets.selected = G1; + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); break; case '\x0f': /* SI - shift in */ term->charsets.selected = G0; + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); break; @@ -482,12 +486,16 @@ action_esc_dispatch(struct terminal *term, uint8_t final) case 'n': /* LS2 - Locking Shift 2 */ term->charsets.selected = G2; + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); break; case 'o': /* LS3 - Locking Shift 3 */ term->charsets.selected = G3; + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); break; @@ -546,6 +554,8 @@ action_esc_dispatch(struct terminal *term, uint8_t final) size_t idx = term->vt.private - '('; xassert(idx <= G3); term->charsets.set[idx] = CHARSET_GRAPHIC; + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); break; } @@ -554,6 +564,8 @@ action_esc_dispatch(struct terminal *term, uint8_t final) size_t idx = term->vt.private - '('; xassert(idx <= G3); term->charsets.set[idx] = CHARSET_ASCII; + term->bits_affecting_ascii_printer.charset = + term->charsets.set[term->charsets.selected] != CHARSET_ASCII; term_update_ascii_printer(term); break; } From a33954a8f4f49b1af427edcadb43f40d8086537e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 01:31:42 +0200 Subject: [PATCH 11/24] doc: foot-ctlseq: add 58/59 (styled + colored underlines) --- doc/foot-ctlseqs.7.scd | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 38742aa3..6d324156 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -150,7 +150,7 @@ m*. | 3 : Italic | 4 -: Underline +: Underline, including styled underlines | 5 : Blink | 7 @@ -176,15 +176,19 @@ m*. | 30-37 : Select foreground color (using *regularN* in *foot.ini*(5)) | 38 -: See "indexed and RGB colors" below +: Select foreground color, see "indexed and RGB colors" below | 39 : Use the default foreground color (*foreground* in *foot.ini*(5)) | 40-47 : Select background color (using *regularN* in *foot.ini*(5)) | 48 -: See "indexed and RGB colors" below +: Select background color, see "indexed and RGB colors" below | 49 : Use the default background color (*background* in *foot.ini*(5)) +| 58 +: Select underline color, see "indexed and RGB colors" below +| 59 +: Use the default underline color | 90-97 : Select foreground color (using *brightN* in *foot.ini*(5)) | 100-107 From 0759caec6ee595182c2add37ce38bb2a8b2e15bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 01:33:07 +0200 Subject: [PATCH 12/24] changelog: styled + colored underlines --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 901f5a6b..2de1929f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,10 +61,11 @@ will now utilize the new single-pixel buffer protocol. This mainly reduces the memory usage, but should also be slightly faster. * Support for high-res mouse wheel scroll events ([#1738][1738]). +* Styled and colored underlines ([#828][828]). [1707]: https://codeberg.org/dnkl/foot/issues/1707 [1738]: https://codeberg.org/dnkl/foot/issues/1738 - +[828]: https://codeberg.org/dnkl/foot/issues/828 ### Changed From 08138e9546a00736d6dab6197070f5455d4d7f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 01:50:52 +0200 Subject: [PATCH 13/24] render: underlines: minor perf-tweak: early break out When looking up the extender underline range, break out early if we see that there can't possibly be any ranges matching the current column. --- render.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/render.c b/render.c index b9a597a9..d66bce65 100644 --- a/render.c +++ b/render.c @@ -971,6 +971,9 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag for (int i = 0; i < row->extra->curly_ranges.count; i++) { const struct row_range *range = &row->extra->curly_ranges.v[i]; + if (range->start > col) + break; + if (range->start <= col && col <= range->end) { switch (range->curly.color_src) { case COLOR_BASE256: From 5e046e6a84badbf21e99799db42712026cae2029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 Jun 2024 11:02:47 +0200 Subject: [PATCH 14/24] csi: sgr_reset(): avoid using memset() This _should_ be what the compiler does anyway (i.e. it _should_ replace the memset() with inline MOVs). But let's be sure. --- csi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/csi.c b/csi.c index 387c37d9..10f525ef 100644 --- a/csi.c +++ b/csi.c @@ -32,9 +32,8 @@ static void sgr_reset(struct terminal *term) { - /* TODO: can we drop this check? */ - memset(&term->vt.attrs, 0, sizeof(term->vt.attrs)); - memset(&term->vt.curly, 0, sizeof(term->vt.curly)); + term->vt.attrs = (struct attributes){0}; + term->vt.curly = (struct curly_range_data){0}; term->bits_affecting_ascii_printer.curly_style = false; term->bits_affecting_ascii_printer.curly_color = false; From 1297b13cd2e90d63ce705650f3ae7454e6982679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 25 Jun 2024 08:33:14 +0200 Subject: [PATCH 15/24] foot.info: add 'Su' (Styled Underlines) boolean capability --- foot.info | 1 + 1 file changed, 1 insertion(+) diff --git a/foot.info b/foot.info index e88f07c2..0605e39f 100644 --- a/foot.info +++ b/foot.info @@ -13,6 +13,7 @@ @default_terminfo@+base|foot base fragment, AX, + Su, Tc, XF, XT, From 8e4ca9068050c283a60f43f764c4519ade14759d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 25 Jun 2024 08:33:31 +0200 Subject: [PATCH 16/24] foot.info: add 'Setulc' (set underline color) capability --- foot.info | 1 + 1 file changed, 1 insertion(+) diff --git a/foot.info b/foot.info index 0605e39f..b87df007 100644 --- a/foot.info +++ b/foot.info @@ -41,6 +41,7 @@ RV=\E[>c, Rect=\E[%p1%d;%p2%d;%p3%d;%p4%d;%p5%d$x, Se=\E[ q, + Setulc=\E[58\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m, Smulx=\E[4:%p1%dm, Ss=\E[%p1%d q, Sync=\E[?2026%?%p1%{1}%-%tl%eh%;, From 6a0110446c1c96379b5b34dfdff5236a9bee311a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 25 Jun 2024 09:55:55 +0200 Subject: [PATCH 17/24] grid: grid_row_{uri,curly}_range_put(): share code grid_row_uri_range_put() and grid_row_curly_range_put() now share the same base logic. Range specific data is passed through a union, and range specific checks are done through switched functions. --- grid.c | 250 +++++++++++++++++++++++++++------------------------------ 1 file changed, 117 insertions(+), 133 deletions(-) diff --git a/grid.c b/grid.c index 3cd818a7..30ef3b65 100644 --- a/grid.c +++ b/grid.c @@ -176,8 +176,17 @@ range_ensure_size(struct row_ranges *ranges, int count_to_add) xassert(ranges->count + count_to_add <= ranges->size); } +union range_data_for_insertion { + struct { + uint64_t id; + const char *uri; + } uri; + struct curly_range_data curly; +}; + static void -range_insert(struct row_ranges *ranges, size_t idx, int start, int end) +range_insert(struct row_ranges *ranges, size_t idx, int start, int end, + enum row_range_type type, const union range_data_for_insertion *data) { range_ensure_size(ranges, 1); @@ -193,6 +202,17 @@ range_insert(struct row_ranges *ranges, size_t idx, int start, int end) .start = start, .end = end, }; + + switch (type) { + case ROW_RANGE_URI: + ranges->v[idx].uri.id = data->uri.id; + ranges->v[idx].uri.uri = xstrdup(data->uri.uri); + break; + + case ROW_RANGE_CURLY: + ranges->v[idx].curly = data->curly; + break; + } } /* @@ -203,17 +223,16 @@ static void uri_range_insert(struct row_ranges *ranges, size_t idx, int start, int end, uint64_t id, const char *uri) { - range_insert(ranges, idx, start, end); - ranges->v[idx].uri.id = id; - ranges->v[idx].uri.uri = xstrdup(uri); + range_insert(ranges, idx, start, end, ROW_RANGE_URI, + &(union range_data_for_insertion){.uri = {.id = id, .uri = uri}}); } static void curly_range_insert(struct row_ranges *ranges, size_t idx, int start, int end, struct curly_range_data data) { - range_insert(ranges, idx, start, end); - ranges->v[idx].curly = data; + range_insert(ranges, idx, start, end, ROW_RANGE_CURLY, + &(union range_data_for_insertion){.curly = data}); } static void @@ -1285,128 +1304,62 @@ grid_resize_and_reflow( #endif } -void -grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) +static bool +ranges_match(const struct row_range *r1, const struct row_range *r2, + enum row_range_type type) { - ensure_row_has_extra_data(row); + switch (type) { + case ROW_RANGE_URI: + /* TODO: also match URI? */ + return r1->uri.id == r2->uri.id; - size_t insert_idx = 0; - bool replace = false; - bool run_merge_pass = false; - - struct row_data *extra = row->extra; - for (int i = extra->uri_ranges.count - 1; i >= 0; i--) { - struct row_range *r = &extra->uri_ranges.v[i]; - - const bool matching_id = r->uri.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; - } - - else if (r->start > col) - continue; - - else { - xassert(r->start <= col); - xassert(r->end >= col); - - if (matching_id) - goto out; - - 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++; - insert_idx = i; - } else if (r->end == col) { - run_merge_pass = true; - r->end--; - insert_idx = i + 1; - } else { - xassert(r->start < col); - xassert(r->end > col); - - uri_range_insert( - &extra->uri_ranges, i + 1, col + 1, r->end, r->uri.id, r->uri.uri); - - /* The insertion may xrealloc() the vector, making our - * 'old' pointer invalid */ - r = &extra->uri_ranges.v[i]; - r->end = col - 1; - xassert(r->start <= r->end); - - insert_idx = i + 1; - } - - break; - } + case ROW_RANGE_CURLY: + return r1->curly.style == r2->curly.style && + r1->curly.color_src == r2->curly.color_src && + r1->curly.color == r2->curly.color; } - 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] = (struct row_range){ - .start = col, - .end = col, - .uri = { - .id = id, - .uri = xstrdup(uri), - }, - }; - } else - uri_range_insert(&extra->uri_ranges, insert_idx, col, col, id, uri); - - if (run_merge_pass) { - for (size_t i = 1; i < extra->uri_ranges.count; i++) { - struct row_range *r1 = &extra->uri_ranges.v[i - 1]; - struct row_range *r2 = &extra->uri_ranges.v[i]; - - if (r1->uri.id == r2->uri.id && r1->end + 1 == r2->start) { - r1->end = r2->end; - range_delete(&extra->uri_ranges, ROW_RANGE_URI, i); - i--; - } - } - } - -out: - verify_no_overlapping_ranges(extra); - verify_ranges_are_sorted(extra); + BUG("invalid range type"); + return false; } -void -grid_row_curly_range_put(struct row *row, int col, struct curly_range_data data) +static bool +range_match_data(const struct row_range *r, + const union range_data_for_insertion *data, + enum row_range_type type) { - ensure_row_has_extra_data(row); + switch (type) { + case ROW_RANGE_URI: + return r->uri.id == data->uri.id; + case ROW_RANGE_CURLY: + return r->curly.style == data->curly.style && + r->curly.color_src == data->curly.color_src && + r->curly.color == data->curly.color; + } + + BUG("invalid range type"); + return false; +} + +static void +grid_row_range_put(struct row_ranges *ranges, int col, + const union range_data_for_insertion *data, + enum row_range_type type) +{ size_t insert_idx = 0; bool replace = false; bool run_merge_pass = false; - struct row_data *extra = row->extra; - for (int i = extra->curly_ranges.count - 1; i >= 0; i--) { - struct row_range *r = &extra->curly_ranges.v[i]; + for (int i = ranges->count - 1; i >= 0; i--) { + struct row_range *r = &ranges->v[i]; - const bool matching = r->curly.style == data.style && - r->curly.color_src == data.color_src && - r->curly.color == data.color; + const bool matching = range_match_data(r, data, type); if (matching && r->end + 1 == col) { - /* Extend existing curly tail */ + /* Extend existing range tail */ r->end++; - goto out; + return; } else if (r->end < col) { @@ -1422,7 +1375,7 @@ grid_row_curly_range_put(struct row *row, int col, struct curly_range_data data) xassert(r->end >= col); if (matching) - goto out; + return; if (r->start == r->end) { replace = true; @@ -1440,12 +1393,13 @@ grid_row_curly_range_put(struct row *row, int col, struct curly_range_data data) xassert(r->start < col); xassert(r->end > col); - curly_range_insert( - &extra->curly_ranges, i + 1, col + 1, r->end, data); + range_insert( + ranges, i + 1, col + 1, r->end, type, + &(union range_data_for_insertion){.uri = {.id = r->uri.id, .uri = r->uri.uri}}); /* The insertion may xrealloc() the vector, making our * 'old' pointer invalid */ - r = &extra->curly_ranges.v[i]; + r = &ranges->v[i]; r->end = col - 1; xassert(r->start <= r->end); @@ -1456,38 +1410,68 @@ grid_row_curly_range_put(struct row *row, int col, struct curly_range_data data) } } - xassert(insert_idx <= extra->curly_ranges.count); + xassert(insert_idx <= ranges->count); if (replace) { - grid_row_curly_range_destroy(&extra->curly_ranges.v[insert_idx]); - extra->curly_ranges.v[insert_idx] = (struct row_range){ + grid_row_range_destroy(&ranges->v[insert_idx], type); + ranges->v[insert_idx] = (struct row_range){ .start = col, .end = col, - .curly = data, }; + + switch (type) { + case ROW_RANGE_URI: + ranges->v[insert_idx].uri.id = data->uri.id; + ranges->v[insert_idx].uri.uri = xstrdup(data->uri.uri); + break; + + case ROW_RANGE_CURLY: + ranges->v[insert_idx].curly = data->curly; + break; + } } else - curly_range_insert(&extra->curly_ranges, insert_idx, col, col, data); + range_insert(ranges, insert_idx, col, col, type, data); if (run_merge_pass) { - for (size_t i = 1; i < extra->curly_ranges.count; i++) { - struct row_range *r1 = &extra->curly_ranges.v[i - 1]; - struct row_range *r2 = &extra->curly_ranges.v[i]; + for (size_t i = 1; i < ranges->count; i++) { + struct row_range *r1 = &ranges->v[i - 1]; + struct row_range *r2 = &ranges->v[i]; - if (r1->curly.style == r2->curly.style && - r1->curly.color_src == r2->curly.color_src && - r1->curly.color == r2->curly.color && - r1->end + 1 == r2->start) - { + if (ranges_match(r1, r2, type) && r1->end + 1 == r2->start) { r1->end = r2->end; - range_delete(&extra->curly_ranges, ROW_RANGE_CURLY, i); + range_delete(ranges, ROW_RANGE_URI, i); i--; } } } +} -out: - verify_no_overlapping_ranges(extra); - verify_ranges_are_sorted(extra); +void +grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) +{ + ensure_row_has_extra_data(row); + + grid_row_range_put( + &row->extra->uri_ranges, col, + &(union range_data_for_insertion){.uri = {.id = id, .uri = uri}}, + ROW_RANGE_URI); + + verify_no_overlapping_ranges(row->extra); + verify_ranges_are_sorted(row->extra); +} + +void +grid_row_curly_range_put(struct row *row, int col, struct curly_range_data data) +{ + ensure_row_has_extra_data(row); + + grid_row_range_put( + &row->extra->curly_ranges, col, + &(union range_data_for_insertion){.curly = data}, + ROW_RANGE_CURLY); + + verify_no_overlapping_ranges(row->extra); + verify_ranges_are_sorted(row->extra); } UNITTEST From 45f4eb48fb6b4b651df6b466936dd425d9647400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 25 Jun 2024 10:26:07 +0200 Subject: [PATCH 18/24] grid: refactor: remove union range_data_for_insertion This union is identical to row_range_data, except the URI char pointer is const. Let's ignore that, and re-use row_range_data, casting the URI pointer when necessary. Also remove uri_range_insert() and curly_range_insert(), and use the generic version of range_insert() everywhere. --- grid.c | 74 ++++++++++++++++++------------------------------------ terminal.h | 10 ++++++-- 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/grid.c b/grid.c index 30ef3b65..9aee69fb 100644 --- a/grid.c +++ b/grid.c @@ -176,17 +176,13 @@ range_ensure_size(struct row_ranges *ranges, int count_to_add) xassert(ranges->count + count_to_add <= ranges->size); } -union range_data_for_insertion { - struct { - uint64_t id; - const char *uri; - } uri; - struct curly_range_data curly; -}; - +/* + * Be careful! This function may xrealloc() the URI range vector, thus + * invalidating pointers into it. + */ static void range_insert(struct row_ranges *ranges, size_t idx, int start, int end, - enum row_range_type type, const union range_data_for_insertion *data) + enum row_range_type type, const union row_range_data *data) { range_ensure_size(ranges, 1); @@ -215,26 +211,6 @@ range_insert(struct row_ranges *ranges, size_t idx, int start, int end, } } -/* - * Be careful! This function may xrealloc() the URI range vector, thus - * invalidating pointers into it. - */ -static void -uri_range_insert(struct row_ranges *ranges, size_t idx, int start, int end, - uint64_t id, const char *uri) -{ - range_insert(ranges, idx, start, end, ROW_RANGE_URI, - &(union range_data_for_insertion){.uri = {.id = id, .uri = uri}}); -} - -static void -curly_range_insert(struct row_ranges *ranges, size_t idx, int start, int end, - struct curly_range_data data) -{ - range_insert(ranges, idx, start, end, ROW_RANGE_CURLY, - &(union range_data_for_insertion){.curly = data}); -} - static void uri_range_append_no_strdup(struct row_data *extra, int start, int end, uint64_t id, char *uri) @@ -1324,8 +1300,7 @@ ranges_match(const struct row_range *r1, const struct row_range *r2, } static bool -range_match_data(const struct row_range *r, - const union range_data_for_insertion *data, +range_match_data(const struct row_range *r, const union row_range_data *data, enum row_range_type type) { switch (type) { @@ -1344,8 +1319,7 @@ range_match_data(const struct row_range *r, static void grid_row_range_put(struct row_ranges *ranges, int col, - const union range_data_for_insertion *data, - enum row_range_type type) + const union row_range_data *data, enum row_range_type type) { size_t insert_idx = 0; bool replace = false; @@ -1393,9 +1367,13 @@ grid_row_range_put(struct row_ranges *ranges, int col, xassert(r->start < col); xassert(r->end > col); - range_insert( - ranges, i + 1, col + 1, r->end, type, - &(union range_data_for_insertion){.uri = {.id = r->uri.id, .uri = r->uri.uri}}); + union row_range_data insert_data; + switch (type) { + case ROW_RANGE_URI: insert_data.uri = r->uri; break; + case ROW_RANGE_CURLY: insert_data.curly = r->curly; break; + } + + range_insert(ranges, i + 1, col + 1, r->end, type, &insert_data); /* The insertion may xrealloc() the vector, making our * 'old' pointer invalid */ @@ -1453,7 +1431,7 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) grid_row_range_put( &row->extra->uri_ranges, col, - &(union range_data_for_insertion){.uri = {.id = id, .uri = uri}}, + &(union row_range_data){.uri = {.id = id, .uri = (char *)uri}}, ROW_RANGE_URI); verify_no_overlapping_ranges(row->extra); @@ -1467,7 +1445,7 @@ grid_row_curly_range_put(struct row *row, int col, struct curly_range_data data) grid_row_range_put( &row->extra->curly_ranges, col, - &(union range_data_for_insertion){.curly = data}, + &(union row_range_data){.curly = data}, ROW_RANGE_CURLY); verify_no_overlapping_ranges(row->extra); @@ -1561,17 +1539,15 @@ grid_row_range_erase(struct row_ranges *ranges, enum row_range_type type, } else if (start > old->start && end < old->end) { - /* Erase range erases a part in the middle of the URI */ - switch (type) { - case ROW_RANGE_URI: - uri_range_insert( - ranges, i + 1, end + 1, old->end, old->uri.id, old->uri.uri); - break; - - case ROW_RANGE_CURLY: - curly_range_insert(ranges, i + 1, end + 1, old->end, old->curly); - break; - } + /* + * Erase range erases a part in the middle of the URI + * + * Must copy, since range_insert() may xrealloc() (thus + * causing 'old' to be invalid) before it dereferences + * old->data + */ + union row_range_data data = old->data; + range_insert(ranges, i + 1, end + 1, old->end, type, &data); /* The insertion may xrealloc() the vector, making our * 'old' pointer invalid */ diff --git a/terminal.h b/terminal.h index 9fdd3dc0..21eb7cee 100644 --- a/terminal.h +++ b/terminal.h @@ -129,8 +129,14 @@ struct row_range { int end; union { - struct uri_range_data uri; - struct curly_range_data curly; + /* This is just an expanded union row_range_data, but + * anonymous, so that we don't have to write range->u.uri.id, + * but can instead do range->uri.id */ + union { + struct uri_range_data uri; + struct curly_range_data curly; + }; + union row_range_data data; }; }; From cb4a74e10ba97ad37df3d065ae95a8f8efa34420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 25 Jun 2024 20:01:47 +0200 Subject: [PATCH 19/24] grid: row_range_put(): use correct type in call to range_delete() --- grid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grid.c b/grid.c index 9aee69fb..93e9eaf8 100644 --- a/grid.c +++ b/grid.c @@ -1417,7 +1417,7 @@ grid_row_range_put(struct row_ranges *ranges, int col, if (ranges_match(r1, r2, type) && r1->end + 1 == r2->start) { r1->end = r2->end; - range_delete(ranges, ROW_RANGE_URI, i); + range_delete(ranges, type, i); i--; } } From 0d3f2f27e33bdee58dc1a0e0ff6a3cb39f53056f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 26 Jun 2024 19:01:54 +0200 Subject: [PATCH 20/24] grid: refactor: replace {uri,curly}_range_append() with range_append() Also add range_append_by_ref(), with replaces uri_range_append_no_strdup(). --- grid.c | 119 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 49 deletions(-) diff --git a/grid.c b/grid.c index 93e9eaf8..c9a0d8dc 100644 --- a/grid.c +++ b/grid.c @@ -194,55 +194,62 @@ range_insert(struct row_ranges *ranges, size_t idx, int start, int end, move_count * sizeof(ranges->v[0])); ranges->count++; - ranges->v[idx] = (struct row_range){ - .start = start, - .end = end, - }; + + struct row_range *r = &ranges->v[idx]; + r->start = start; + r->end = end; switch (type) { case ROW_RANGE_URI: - ranges->v[idx].uri.id = data->uri.id; - ranges->v[idx].uri.uri = xstrdup(data->uri.uri); + r->uri.id = data->uri.id; + r->uri.uri = xstrdup(data->uri.uri); break; case ROW_RANGE_CURLY: - ranges->v[idx].curly = data->curly; + r->curly = data->curly; break; } } static void -uri_range_append_no_strdup(struct row_data *extra, int start, int end, - uint64_t id, char *uri) +range_append_by_ref(struct row_ranges *ranges, int start, int end, + enum row_range_type type, const union row_range_data *data) { - range_ensure_size(&extra->uri_ranges, 1); - extra->uri_ranges.v[extra->uri_ranges.count++] = (struct row_range){ - .start = start, - .end = end, - .uri = { - .id = id, - .uri = uri, - }, - }; + range_ensure_size(ranges, 1); + + struct row_range *r = &ranges->v[ranges->count++]; + + r->start = start; + r->end = end; + + switch (type) { + case ROW_RANGE_URI: + r->uri.id = data->uri.id;; + r->uri.uri = data->uri.uri; + break; + + case ROW_RANGE_CURLY: + r->curly = data->curly; + break; + } } static void -uri_range_append(struct row_data *extra, int start, int end, uint64_t id, - const char *uri) +range_append(struct row_ranges *ranges, int start, int end, + enum row_range_type type, const union row_range_data *data) { - uri_range_append_no_strdup(extra, start, end, id, xstrdup(uri)); -} + switch (type) { + case ROW_RANGE_URI: + range_append_by_ref( + ranges, start, end, type, + &(union row_range_data){.uri = {.id = data->uri.id, + .uri = xstrdup(data->uri.uri)}}); + break; -static void -curly_range_append(struct row_data *extra, int start, int end, - struct curly_range_data data) -{ - range_ensure_size(&extra->curly_ranges, 1); - extra->curly_ranges.v[extra->curly_ranges.count++] = (struct row_range){ - .start = start, - .end = end, - .curly = data, - }; + case ROW_RANGE_CURLY: + range_append_by_ref(ranges, start, end, type, data); + break; + } } static void @@ -304,14 +311,16 @@ grid_snapshot(const struct grid *grid) for (int i = 0; i < extra->uri_ranges.count; i++) { const struct row_range *range = &extra->uri_ranges.v[i]; - uri_range_append( - clone_extra, - range->start, range->end, range->uri.id, range->uri.uri); + range_append( + &clone_extra->uri_ranges, + range->start, range->end, ROW_RANGE_URI, &range->data); } for (int i = 0; i < extra->curly_ranges.count; i++) { const struct row_range *range = &extra->curly_ranges.v[i]; - curly_range_append(clone_extra, range->start, range->end, range->curly); + range_append_by_ref( + &clone_extra->curly_ranges, range->start, range->end, + ROW_RANGE_CURLY, &range->data); } } else clone_row->extra = NULL; @@ -539,7 +548,7 @@ grid_resize_without_reflow( const int start = range->start; const int end = min(range->end, new_cols - 1); - uri_range_append(new_extra, start, end, range->uri.id, range->uri.uri); + range_append(&new_extra->uri_ranges, start, end, ROW_RANGE_URI, &range->data); } for (int i = 0; i < old_extra->curly_ranges.count; i++) { @@ -552,7 +561,7 @@ grid_resize_without_reflow( const int start = range->start; const int end = min(range->end, new_cols - 1); - curly_range_append(new_extra, start, end, range->curly); + range_append_by_ref(&new_extra->curly_ranges, start, end, ROW_RANGE_CURLY, &range->data); } } @@ -629,8 +638,11 @@ reflow_uri_range_start(struct row_range *range, struct row *new_row, int new_col_idx) { ensure_row_has_extra_data(new_row); - uri_range_append_no_strdup( - new_row->extra, new_col_idx, -1, range->uri.id, range->uri.uri); + range_append_by_ref( + &new_row->extra->uri_ranges, new_col_idx, -1, + ROW_RANGE_URI, &range->data); + + /* The reflowed range now owns the URI string */ range->uri.uri = NULL; } @@ -654,7 +666,8 @@ reflow_curly_range_start(struct row_range *range, struct row *new_row, int new_col_idx) { ensure_row_has_extra_data(new_row); - curly_range_append(new_row->extra, new_col_idx, -1, range->curly); + range_append_by_ref(&new_row->extra->curly_ranges, new_col_idx, -1, + ROW_RANGE_CURLY, &range->data); } @@ -732,7 +745,8 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, /* Open a new range on the new/current row */ ensure_row_has_extra_data(new_row); - uri_range_append(new_row->extra, 0, -1, range->uri.id, range->uri.uri); + range_append(&new_row->extra->uri_ranges, 0, -1, + ROW_RANGE_URI, &range->data); } } @@ -747,7 +761,8 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, /* Open a new range on the new/current row */ ensure_row_has_extra_data(new_row); - curly_range_append(new_row->extra, 0, -1, range->curly); + range_append(&new_row->extra->curly_ranges, 0, -1, + ROW_RANGE_CURLY, &range->data); } } @@ -1589,13 +1604,19 @@ UNITTEST { struct row_data row_data = {.uri_ranges = {0}}; struct row row = {.extra = &row_data}; + const union row_range_data data = { + .uri = { + .id = 0, + .uri = (char *)"dummy", + }, + }; /* Try erasing a row without any URIs */ grid_row_uri_range_erase(&row, 0, 200); xassert(row_data.uri_ranges.count == 0); - uri_range_append(&row_data, 1, 10, 0, "dummy"); - uri_range_append(&row_data, 11, 20, 0, "dummy"); + range_append(&row_data.uri_ranges, 1, 10, ROW_RANGE_URI, &data); + range_append(&row_data.uri_ranges, 11, 20, ROW_RANGE_URI, &data); 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); @@ -1610,8 +1631,8 @@ UNITTEST /* Two URIs, then erase second half of the first, first half of the second */ - uri_range_append(&row_data, 1, 10, 0, "dummy"); - uri_range_append(&row_data, 11, 20, 0, "dummy"); + range_append(&row_data.uri_ranges, 1, 10, ROW_RANGE_URI, &data); + range_append(&row_data.uri_ranges, 11, 20, ROW_RANGE_URI, &data); grid_row_uri_range_erase(&row, 5, 15); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[0].start == 1); @@ -1626,7 +1647,7 @@ UNITTEST row_data.uri_ranges.count = 0; /* One URI, erase middle part of it */ - uri_range_append(&row_data, 1, 10, 0, "dummy"); + range_append(&row_data.uri_ranges, 1, 10, ROW_RANGE_URI, &data); grid_row_uri_range_erase(&row, 5, 6); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[0].start == 1); @@ -1657,7 +1678,7 @@ UNITTEST 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"); + range_append(&row_data.uri_ranges, 1, 10, ROW_RANGE_URI, &data); xassert(row_data.uri_ranges.size == 1); grid_row_uri_range_erase(&row, 5, 7); From 19bf558e6cee456eabe6b87d9d51e8f220e20cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 27 Jun 2024 18:36:17 +0200 Subject: [PATCH 21/24] render: underlines: improve the appearance of the 'dotted' style Try to make the 'dotted' style appear more even, and less like each cell is rendered separately (even though they are). Algorithm: Each dot is a square; it's sides are that of the font's line thickness. The spacing (gaps) between the dots is initially the same width as the dots themselves. This means the number of dots per cell is the cell width divided by the dots' length/width, divided by two. At this point, there may be "left-over" pixels.I.e. the widths of the dots and the gaps between them may not add up to the width of the cell. These pixels are evenly (as possible) across the gaps. There are still visual inaccuracies at small font sizes. This is impossible to fix without changing the way underlines are rendered, to render an entire line in one go. This is not something we want to do, since it'll make styled underlines, for a specific cell/character, look differently, depending on the surrounding context. --- render.c | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/render.c b/render.c index d66bce65..65fd1739 100644 --- a/render.c +++ b/render.c @@ -432,17 +432,43 @@ draw_styled_underline(const struct terminal *term, pixman_image_t *pix, PIXMAN_OP_SRC, pix, color, 2, rects); break; } - case CURLY_DOTTED: { - const int ceil_w = cols * term->cell_width; - const int nrects = min(ceil_w / thickness / 2, 16); - pixman_rectangle16_t rects[16] = {0}; - for (int i = 0; i < nrects; i++) { + case CURLY_DOTTED: { + /* Number of dots per cell */ + int per_cell = (term->cell_width / thickness) / 2; + if (per_cell == 0) + per_cell = 1; + + xassert(per_cell >= 1); + + /* Spacing between dots; start with the same width as the dots + themselves, then widen them if necessary, to consume unused + pixels */ + int spacing[per_cell]; + for (int i = 0; i < per_cell; i++) + spacing[i] = thickness; + + /* Pixels remaining at the end of the cell */ + int remaining = term->cell_width - (per_cell * 2) * thickness; + + /* Spread out the left-over pixels across the spacing between + the dots */ + for (int i = 0; remaining > 0; i = (i + 1) % per_cell, remaining--) + spacing[i]++; + + xassert(remaining <= 0); + + pixman_rectangle16_t rects[per_cell]; + int dot_x = x; + for (int i = 0; i < per_cell; i++) { rects[i] = (pixman_rectangle16_t){ - x + i * thickness * 2, y + y_ofs, thickness, thickness}; + dot_x, y + y_ofs, thickness, thickness + }; + + dot_x += thickness + spacing[i]; } - pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, nrects, rects); + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, per_cell, rects); break; } From 0c7725217a706f6df1b543c4c85f54872aaf2f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 27 Jun 2024 18:54:46 +0200 Subject: [PATCH 22/24] render: draw_styled_underline(): respect main.underline-thickness Also refactor a bit, and break out early to draw_underline() for legacy underlines. --- render.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/render.c b/render.c index 65fd1739..35b7c6ae 100644 --- a/render.c +++ b/render.c @@ -392,7 +392,15 @@ draw_styled_underline(const struct terminal *term, pixman_image_t *pix, { xassert(style != CURLY_NONE); - const int thickness = font->underline.thickness; + if (style == CURLY_SINGLE) { + draw_underline(term, pix, font, color, x, y, cols); + return; + } + + const int thickness = term->conf->underline_thickness.px >= 0 + ? term_pt_or_px_as_pixels( + term, &term->conf->underline_thickness) + : font->underline.thickness; int y_ofs; @@ -404,10 +412,16 @@ draw_styled_underline(const struct terminal *term, pixman_image_t *pix, term->cell_height - thickness * 3); break; - default: + case CURLY_DASHED: + case CURLY_DOTTED: y_ofs = min(underline_offset(term, font), term->cell_height - thickness); break; + + case CURLY_NONE: + case CURLY_SINGLE: + BUG("underline styles not supposed to be handled here"); + break; } const int ceil_w = cols * term->cell_width; @@ -518,8 +532,9 @@ draw_styled_underline(const struct terminal *term, pixman_image_t *pix, break; } - default: - draw_underline(term, pix, font, color, x, y, cols); + case CURLY_NONE: + case CURLY_SINGLE: + BUG("underline styles not supposed to be handled here"); break; } } From 73f6982cbeb17501b097ea6ed6ca3342d46b6c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 27 Jun 2024 19:06:40 +0200 Subject: [PATCH 23/24] grid: merge reflow_{uri,curly}_range_start(), and reflow_{uri,curly}_range_end() --- grid.c | 90 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/grid.c b/grid.c index c9a0d8dc..1db71b5c 100644 --- a/grid.c +++ b/grid.c @@ -634,58 +634,60 @@ grid_resize_without_reflow( } static void -reflow_uri_range_start(struct row_range *range, struct row *new_row, - int new_col_idx) +reflow_range_start(struct row_range *range, enum row_range_type type, + struct row *new_row, int new_col_idx) { ensure_row_has_extra_data(new_row); - range_append_by_ref( - &new_row->extra->uri_ranges, new_col_idx, -1, - ROW_RANGE_URI, &range->data); - /* The reflowed range now owns the URI string */ - range->uri.uri = NULL; + struct row_ranges *new_ranges = NULL; + switch (type) { + case ROW_RANGE_URI: new_ranges = &new_row->extra->uri_ranges; break; + case ROW_RANGE_CURLY: new_ranges = &new_row->extra->curly_ranges; break; + } + + if (new_ranges == NULL) + BUG("unhandled range type"); + + range_append_by_ref(new_ranges, new_col_idx, -1, type, &range->data); + + switch (type) { + case ROW_RANGE_URI: range->uri.uri = NULL; break; /* Owned by new_ranges */ + case ROW_RANGE_CURLY: break; + } } static void -reflow_uri_range_end(struct row_range *range, struct row *new_row, - int new_col_idx) +reflow_range_end(struct row_range *range, enum row_range_type type, + struct row *new_row, int new_col_idx) { struct row_data *extra = new_row->extra; - xassert(extra->uri_ranges.count > 0); + struct row_ranges *ranges = NULL; - struct row_range *new_range = - &extra->uri_ranges.v[extra->uri_ranges.count - 1]; + switch (type) { + case ROW_RANGE_URI: ranges = &extra->uri_ranges; break; + case ROW_RANGE_CURLY: ranges = &extra->curly_ranges; break; + } - xassert(new_range->uri.id == range->uri.id); + if (ranges == NULL) + BUG("unhandled range type"); + + xassert(ranges->count > 0); + + struct row_range *new_range = &ranges->v[ranges->count - 1]; xassert(new_range->end < 0); - new_range->end = new_col_idx; -} -static void -reflow_curly_range_start(struct row_range *range, struct row *new_row, - int new_col_idx) -{ - ensure_row_has_extra_data(new_row); - range_append_by_ref(&new_row->extra->curly_ranges, new_col_idx, -1, - ROW_RANGE_CURLY, &range->data); -} + switch (type) { + case ROW_RANGE_URI: + xassert(new_range->uri.id == range->uri.id); + break; + case ROW_RANGE_CURLY: + xassert(new_range->curly.style == range->curly.style); + xassert(new_range->curly.color_src == range->curly.color_src); + xassert(new_range->curly.color == range->curly.color); + break; + } -static void -reflow_curly_range_end(struct row_range *range, struct row *new_row, - int new_col_idx) -{ - struct row_data *extra = new_row->extra; - xassert(extra->curly_ranges.count > 0); - - struct row_range *new_range = - &extra->curly_ranges.v[extra->curly_ranges.count - 1]; - - xassert(new_range->curly.style == range->curly.style); - xassert(new_range->curly.color_src == range->curly.color_src); - xassert(new_range->curly.color == range->curly.color); - - xassert(new_range->end < 0); new_range->end = new_col_idx; } @@ -1115,10 +1117,12 @@ grid_resize_and_reflow( xassert(uri_range != NULL); if (uri_range->start == end - 1) - reflow_uri_range_start(uri_range, new_row, new_col_idx - 1); + reflow_range_start( + uri_range, ROW_RANGE_URI, new_row, new_col_idx - 1); if (uri_range->end == end - 1) { - reflow_uri_range_end(uri_range, new_row, new_col_idx - 1); + reflow_range_end( + uri_range, ROW_RANGE_URI, new_row, new_col_idx - 1); grid_row_uri_range_destroy(uri_range); uri_range++; } @@ -1128,10 +1132,12 @@ grid_resize_and_reflow( xassert(curly_range != NULL); if (curly_range->start == end - 1) - reflow_curly_range_start(curly_range, new_row, new_col_idx - 1); + reflow_range_start( + curly_range, ROW_RANGE_CURLY, new_row, new_col_idx - 1); if (curly_range->end == end - 1) { - reflow_curly_range_end(curly_range, new_row, new_col_idx - 1); + reflow_range_end( + curly_range, ROW_RANGE_CURLY, new_row, new_col_idx - 1); grid_row_curly_range_destroy(curly_range); curly_range++; } From b503c0d6d96dad6cb09db2dea76ecfe857423552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 27 Jun 2024 19:28:04 +0200 Subject: [PATCH 24/24] reaadme: add styled and colored underlines to the feature list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c188f76c..3cb5834b 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator. * IME (via `text-input-v3`) * Multi-seat * True Color (24bpp) +* [Styled and colored underlines](https://sw.kovidgoyal.net/kitty/underlines/) * [Synchronized Updates](https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/2) support * [Sixel image support](https://en.wikipedia.org/wiki/Sixel)