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;