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 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) diff --git a/csi.c b/csi.c index be470e70..10f525ef 100644 --- a/csi.c +++ b/csi.c @@ -32,7 +32,12 @@ static void sgr_reset(struct terminal *term) { - memset(&term->vt.attrs, 0, sizeof(term->vt.attrs)); + 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; + term_update_ascii_printer(term); } static const char * @@ -88,7 +93,36 @@ 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; + term->bits_affecting_ascii_printer.curly_style = false; + break; + + case CURLY_SINGLE: + case CURLY_DOUBLE: + case CURLY_CURLY: + case CURLY_DOTTED: + case CURLY_DASHED: + term->vt.curly.style = style; + term->bits_affecting_ascii_printer.curly_style = + style > CURLY_SINGLE; + break; + } + + term_update_ascii_printer(term); + } + 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,13 @@ 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->bits_affecting_ascii_printer.curly_style = false; + 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 +159,8 @@ csi_sgr(struct terminal *term) break; case 38: - case 48: { + case 48: + case 58: { uint32_t color; enum color_source src; @@ -194,7 +235,12 @@ 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->bits_affecting_ascii_printer.curly_color = true; + term_update_ascii_printer(term); + } else if (param == 38) { term->vt.attrs.fg_src = src; term->vt.attrs.fg = color; } else { @@ -226,6 +272,13 @@ 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->bits_affecting_ascii_printer.curly_color = false; + term_update_ascii_printer(term); + break; + /* Bright foreground colors */ case 90: case 91: @@ -478,6 +531,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; @@ -1116,6 +1172,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/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 diff --git a/foot.info b/foot.info index cf5c7b82..b87df007 100644 --- a/foot.info +++ b/foot.info @@ -13,6 +13,7 @@ @default_terminfo@+base|foot base fragment, AX, + Su, Tc, XF, XT, @@ -40,6 +41,8 @@ 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%;, TS=\E]2;, diff --git a/grid.c b/grid.c index 85e6183f..1db71b5c 100644 --- a/grid.c +++ b/grid.c @@ -85,22 +85,32 @@ 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; + + case ROW_RANGE_CURLY: + BUG("curly underline overlap: %d-%d, %d-%d", + r1->start, r1->end, r2->start, r2->end); + break; + } } } } @@ -108,20 +118,38 @@ 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); + verify_no_overlapping_ranges_of_type(&extra->curly_ranges, ROW_RANGE_CURLY); +} + +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; + + 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; + } } } @@ -131,16 +159,21 @@ verify_uris_are_sorted(const struct row_data *extra) } static void -uri_range_ensure_size(struct row_data *extra, uint32_t count_to_add) +verify_ranges_are_sorted(const struct row_data *extra) { - 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])); + 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 +range_ensure_size(struct row_ranges *ranges, int count_to_add) +{ + 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); } /* @@ -148,58 +181,88 @@ 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, - uint64_t id, const char *uri) +range_insert(struct row_ranges *ranges, size_t idx, int start, int end, + enum row_range_type type, const union row_range_data *data) { - uri_range_ensure_size(extra, 1); + range_ensure_size(ranges, 1); - xassert(idx <= extra->uri_ranges.count); + xassert(idx <= 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])); + const size_t move_count = ranges->count - idx; + memmove(&ranges->v[idx + 1], + &ranges->v[idx], + move_count * sizeof(ranges->v[0])); - extra->uri_ranges.count++; - extra->uri_ranges.v[idx] = (struct row_uri_range){ - .start = start, - .end = end, - .id = id, - .uri = xstrdup(uri), - }; + ranges->count++; + + struct row_range *r = &ranges->v[idx]; + r->start = start; + r->end = end; + + switch (type) { + case ROW_RANGE_URI: + r->uri.id = data->uri.id; + r->uri.uri = xstrdup(data->uri.uri); + break; + + case ROW_RANGE_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) { - uri_range_ensure_size(extra, 1); - extra->uri_ranges.v[extra->uri_ranges.count++] = (struct row_uri_range){ - .start = start, - .end = end, - .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; + + case ROW_RANGE_CURLY: + range_append_by_ref(ranges, start, end, type, data); + break; + } } 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 * @@ -243,13 +306,21 @@ 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++) { - const struct row_uri_range *range = &extra->uri_ranges.v[i]; - uri_range_append( - clone_extra, - range->start, range->end, range->id, range->uri); + for (int i = 0; i < extra->uri_ranges.count; i++) { + const struct row_range *range = &extra->uri_ranges.v[i]; + 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]; + range_append_by_ref( + &clone_extra->curly_ranges, range->start, range->end, + ROW_RANGE_CURLY, &range->data); } } else clone_row->extra = NULL; @@ -464,10 +535,11 @@ 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++) { - const struct row_uri_range *range = &old_extra->uri_ranges.v[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) { /* The whole range is truncated */ @@ -476,9 +548,22 @@ 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); + range_append(&new_extra->uri_ranges, start, end, ROW_RANGE_URI, &range->data); } - } + + 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); + range_append_by_ref(&new_extra->curly_ranges, start, end, ROW_RANGE_CURLY, &range->data); + } +} /* Clear "new" lines */ for (int r = min(old_screen_rows, new_screen_rows); r < new_screen_rows; r++) { @@ -498,8 +583,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,27 +634,60 @@ grid_resize_without_reflow( } static void -reflow_uri_range_start(struct row_uri_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); - uri_range_append_no_strdup - (new_row->extra, new_col_idx, -1, range->id, range->uri); - range->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_uri_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_uri_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->id == range->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); + + 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; + } + new_range->end = new_col_idx; } @@ -619,7 +737,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 +747,24 @@ _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); + range_append(&new_row->extra->uri_ranges, 0, -1, + ROW_RANGE_URI, &range->data); + } + } + + 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); + range_append(&new_row->extra->curly_ranges, 0, -1, + ROW_RANGE_CURLY, &range->data); } } @@ -817,31 +952,46 @@ grid_resize_and_reflow( tp = NULL; /* Does this row have any URIs? */ - struct row_uri_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 */ - 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 - 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 @@ -850,9 +1000,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; } @@ -963,15 +1114,32 @@ 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_range_start( + uri_range, ROW_RANGE_URI, 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_range_end( + uri_range, ROW_RANGE_URI, 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_range_start( + curly_range, ROW_RANGE_CURLY, new_row, new_col_idx - 1); + + if (curly_range->end == end - 1) { + reflow_range_end( + curly_range, ROW_RANGE_CURLY, new_row, new_col_idx - 1); + grid_row_curly_range_destroy(curly_range); + curly_range++; } } @@ -997,20 +1165,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); + + } } } @@ -1042,9 +1216,11 @@ 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_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 */ @@ -1125,25 +1301,60 @@ 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; + 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; + } + + BUG("invalid range type"); + return false; +} + +static bool +range_match_data(const struct row_range *r, const union row_range_data *data, + enum row_range_type type) +{ + 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 row_range_data *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 (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { - struct row_uri_range *r = &extra->uri_ranges.v[i]; + for (int i = ranges->count - 1; i >= 0; i--) { + struct row_range *r = &ranges->v[i]; - const bool matching_id = r->id == id; + const bool matching = range_match_data(r, data, type); - if (matching_id && r->end + 1 == col) { - /* Extend existing URI's tail */ + if (matching && r->end + 1 == col) { + /* Extend existing range tail */ r->end++; - goto out; + return; } else if (r->end < col) { @@ -1158,8 +1369,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); - if (matching_id) - goto out; + if (matching) + return; if (r->start == r->end) { replace = true; @@ -1177,11 +1388,17 @@ 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); + 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 */ - r = &extra->uri_ranges.v[i]; + r = &ranges->v[i]; r->end = col - 1; xassert(r->start <= r->end); @@ -1192,35 +1409,68 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) } } - xassert(insert_idx <= extra->uri_ranges.count); + xassert(insert_idx <= ranges->count); if (replace) { - grid_row_uri_range_destroy(&extra->uri_ranges.v[insert_idx]); - extra->uri_ranges.v[insert_idx] = (struct row_uri_range){ + grid_row_range_destroy(&ranges->v[insert_idx], type); + ranges->v[insert_idx] = (struct row_range){ .start = col, .end = col, - .id = id, - .uri = xstrdup(uri), }; + + 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 - uri_range_insert(extra, insert_idx, col, col, id, uri); + range_insert(ranges, insert_idx, col, col, type, data); 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]; + 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->id == r2->id && r1->end + 1 == r2->start) { + if (ranges_match(r1, r2, type) && r1->end + 1 == r2->start) { r1->end = r2->end; - uri_range_delete(extra, i); + range_delete(ranges, type, i); i--; } } } +} -out: - verify_no_overlapping_uris(extra); - verify_uris_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 row_range_data){.uri = {.id = id, .uri = (char *)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 row_range_data){.curly = data}, + ROW_RANGE_CURLY); + + verify_no_overlapping_ranges(row->extra); + verify_ranges_are_sorted(row->extra); } UNITTEST @@ -1233,7 +1483,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); @@ -1288,17 +1538,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_uri_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; @@ -1308,17 +1556,23 @@ 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->id, old->uri); + /* + * 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 */ - 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 */ } @@ -1338,59 +1592,79 @@ 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}}; 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); - 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 */ - 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); 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 */ - 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); 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; /* @@ -1410,13 +1684,12 @@ 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); 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..c5c2b60c 100644 --- a/grid.h +++ b/grid.h @@ -88,10 +88,36 @@ 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_uri_range *range) +grid_row_uri_range_destroy(struct row_range *range) { - free(range->uri); + 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; + } +} + +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 @@ -102,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..35b7c6ae 100644 --- a/render.c +++ b/render.c @@ -384,6 +384,161 @@ 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); + + 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; + + /* 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; + + 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; + + 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: { + /* 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){ + dot_x, y + y_ofs, thickness, thickness + }; + + dot_x += thickness + spacing[i]; + } + + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, per_cell, 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; + } + + case CURLY_NONE: + case CURLY_SINGLE: + BUG("underline styles not supposed to be handled here"); + break; + } +} + static void draw_strikeout(const struct terminal *term, pixman_image_t *pix, const struct fcft_font *font, @@ -847,8 +1002,49 @@ 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; + enum curly_style underline_style = CURLY_SINGLE; + + /* 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) + break; + + 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; + } + + underline_style = range->curly.style; + break; + } + } + } + + draw_styled_underline( + term, pix, font, &underline_color, underline_style, x, y, cell_cols); + + } if (cell->attrs.strikethrough) draw_strikeout(term, pix, font, &fg, x, y, cell_cols); @@ -4243,6 +4439,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; 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 55c387c1..83cbb42b 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 @@ -2021,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) @@ -3019,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); } @@ -3621,7 +3627,8 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, cell->wc = data; cell->attrs = attrs; - if (unlikely(term->vt.osc8.uri != NULL)) { + /* TODO: why do we print the URI here, and then erase it below? */ + 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) { @@ -3633,10 +3640,27 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, break; } } + + 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); + } } - if (unlikely(row->extra != NULL)) - grid_row_uri_range_erase(row, c, c + count - 1); + if (unlikely(row->extra != NULL)) { + 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); + } + } } void @@ -3706,6 +3730,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 +3794,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 @@ -3772,19 +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->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) { @@ -4077,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); } @@ -4086,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 5033c550..21eb7cee 100644 --- a/terminal.h +++ b/terminal.h @@ -99,19 +99,58 @@ struct damage { uint16_t lines; }; -struct row_uri_range { - int start; - int end; +struct uri_range_data { uint64_t id; 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 { + /* 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; + }; +}; + +struct row_ranges { + struct row_range *v; + int size; + int count; +}; + +enum row_range_type {ROW_RANGE_URI, ROW_RANGE_CURLY}; + struct row_data { - struct { - struct row_uri_range *v; - uint32_t size; - uint32_t count; - } uri_ranges; + struct row_ranges uri_ranges; + struct row_ranges curly_ranges; }; struct row { @@ -260,6 +299,8 @@ struct vt { char *uri; } osc8; + struct curly_range_data curly; + struct { uint8_t *data; size_t size; @@ -358,6 +399,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/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, 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; }