diff --git a/CHANGELOG.md b/CHANGELOG.md index b506aafa..c185dd72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ * An ongoing mouse selection is now finalized on a pointer leave event (for example by switching workspace while doing a mouse selection). +* OSC-8 URIs in the last column +* OSC-8 URIs sometimes being applied to too many, and seemingly + unrelated cells (https://codeberg.org/dnkl/foot/issues/816). ### Security diff --git a/grid.c b/grid.c index 459fa369..2dcbaa55 100644 --- a/grid.c +++ b/grid.c @@ -844,6 +844,117 @@ ensure_row_has_extra_data(struct row *row) row->extra = xcalloc(1, sizeof(*row->extra)); } +static void +verify_no_overlapping_uris(const struct row *row) +{ +#if defined(_DEBUG) + const struct row_data *extra = row->extra; + tll_foreach(extra->uri_ranges, it1) { + const struct row_uri_range *r1 = &it1->item; + + tll_foreach(extra->uri_ranges, it2) { + const struct row_uri_range *r2 = &it2->item; + + if (r1 == r2) + continue; + + 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); + } + } + } +#endif +} + +void +grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) +{ + ensure_row_has_extra_data(row); + + struct row_data *extra = row->extra; + tll_rforeach(extra->uri_ranges, it) { + struct row_uri_range *r = &it->item; + + if (r->end + 1 < col) { + /* Ranges are sorted, and this means all the remaining + * URIs *end* before ‘col’ */ + break; + } + + if (r->start <= col && r->end >= col) { + /* + * ‘col’ is *inside* an existing range + */ + + if (r->id == id) { + /* ‘col’ is already inside a matching URI range */ + goto out; + } + + /* Splice old range */ + if (r->start == r->end) + tll_remove(extra->uri_ranges, it); + else if (r->start == col) + r->start++; + else if (r->end == col) + r->end--; + else { + xassert(r->start < col); + xassert(r->end > col); + + struct row_uri_range new_tail = { + .start = col + 1, + .end = r->end, + .id = r->id, + .uri = xstrdup(r->uri), + }; + + r->end = col - 1; + + xassert(r->start <= r->end); + xassert(new_tail.start <= new_tail.end); + + tll_insert_after(extra->uri_ranges, it, new_tail); + } + + break; /* Break out add and a new range for ‘col’ */ + } + + else if (r->id != id) + continue; + +#if 0 /* Trust the URI ID, for now... */ + if (strcmp(r->uri, uri) != 0) + continue; +#endif + + else if (likely(r->end + 1 == col)) { + r->end = col; + goto out; + } + + else if (col + 1 == r->start) { + r->start = col; + goto out; + } + } + + struct row_uri_range new_range = { + .start = col, + .end = col, + .id = id, + .uri = xstrdup(uri), + }; + grid_row_uri_range_add(row, new_range); + +out: + verify_no_overlapping_uris(row); +} + void grid_row_uri_range_add(struct row *row, struct row_uri_range range) { @@ -858,20 +969,7 @@ grid_row_uri_range_add(struct row *row, struct row_uri_range range) tll_push_front(row->extra->uri_ranges, range); out: - ; -#if defined(_DEBUG) - tll_foreach(row->extra->uri_ranges, it1) { - tll_foreach(row->extra->uri_ranges, it2) { - if (&it1->item == &it2->item) - continue; - - xassert(it1->item.start != it2->item.start); - xassert(it1->item.start != it2->item.end); - xassert(it1->item.end != it2->item.start); - xassert(it1->item.end != it2->item.end); - } - } -#endif + verify_no_overlapping_uris(row); } void @@ -929,36 +1027,22 @@ UNITTEST struct row_data row_data = {.uri_ranges = tll_init()}; struct row row = {.extra = &row_data}; -#define row_has_no_overlapping_uris(row) \ - do { \ - tll_foreach((row)->extra->uri_ranges, it1) { \ - tll_foreach((row)->extra->uri_ranges, it2) { \ - if (&it1->item == &it2->item) \ - continue; \ - xassert(it1->item.start != it2->item.start); \ - xassert(it1->item.start != it2->item.end); \ - xassert(it1->item.end != it2->item.start); \ - xassert(it1->item.end != it2->item.end); \ - } \ - } \ - } while (0) - grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); xassert(tll_length(row_data.uri_ranges) == 1); xassert(tll_front(row_data.uri_ranges).start == 1); xassert(tll_front(row_data.uri_ranges).end == 10); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); grid_row_uri_range_add(&row, (struct row_uri_range){11, 20}); xassert(tll_length(row_data.uri_ranges) == 2); xassert(tll_back(row_data.uri_ranges).start == 11); xassert(tll_back(row_data.uri_ranges).end == 20); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); /* Erase both URis */ grid_row_uri_range_erase(&row, 1, 20); xassert(tll_length(row_data.uri_ranges) == 0); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); /* Two URIs, then erase second half of the first, first half of the second */ @@ -970,7 +1054,7 @@ UNITTEST xassert(tll_front(row_data.uri_ranges).end == 4); xassert(tll_back(row_data.uri_ranges).start == 16); xassert(tll_back(row_data.uri_ranges).end == 20); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); tll_pop_back(row_data.uri_ranges); tll_pop_back(row_data.uri_ranges); @@ -984,9 +1068,7 @@ UNITTEST xassert(tll_front(row_data.uri_ranges).end == 4); xassert(tll_back(row_data.uri_ranges).start == 7); xassert(tll_back(row_data.uri_ranges).end == 10); - row_has_no_overlapping_uris(&row); - -#undef row_has_no_overlapping_uris + verify_no_overlapping_uris(&row); tll_free(row_data.uri_ranges); } diff --git a/grid.h b/grid.h index e2cd3a63..8ede4773 100644 --- a/grid.h +++ b/grid.h @@ -74,6 +74,8 @@ grid_row_in_view(struct grid *grid, int row_no) return row; } +void grid_row_uri_range_put( + struct row *row, int col, const char *uri, uint64_t id); void grid_row_uri_range_add(struct row *row, struct row_uri_range range); void grid_row_uri_range_erase(struct row *row, int start, int end); diff --git a/terminal.c b/terminal.c index 4cc9ed09..03ff1898 100644 --- a/terminal.c +++ b/terminal.c @@ -1134,9 +1134,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .blink = {.fd = -1}, .vt = { .state = 0, /* STATE_GROUND */ - .osc8 = { - .begin = {-1, -1}, - }, }, .colors = { .fg = conf->colors.fg, @@ -1826,7 +1823,6 @@ term_reset(struct terminal *term, bool hard) term->vt = (struct vt){ .state = 0, /* STATE_GROUND */ - .osc8 = {.begin = (struct coord){-1, -1}}, }; if (term->grid == &term->alt) { @@ -3151,7 +3147,8 @@ print_insert(struct terminal *term, int width) static void print_spacer(struct terminal *term, int col, int remaining) { - struct row *row = term->grid->cur_row; + struct grid *grid = term->grid; + struct row *row = grid->cur_row; struct cell *cell = &row->cells[col]; cell->wc = CELL_SPACER + remaining; @@ -3210,7 +3207,19 @@ term_print(struct terminal *term, wchar_t wc, int width) cell->wc = term->vt.last_printed = wc; cell->attrs = term->vt.attrs; - const int uri_start = col; + if (term->vt.osc8.uri != NULL) { + grid_row_uri_range_put( + row, col, term->vt.osc8.uri, term->vt.osc8.id); + + switch (term->conf->url.osc8_underline) { + case OSC8_UNDERLINE_ALWAYS: + cell->attrs.url = true; + break; + + case OSC8_UNDERLINE_URL_MODE: + break; + } + } /* Advance cursor the 'additional' columns while dirty:ing the cells */ for (int i = 1; i < width && col < term->cols - 1; i++) { @@ -3226,9 +3235,6 @@ term_print(struct terminal *term, wchar_t wc, int width) xassert(!grid->cursor.lcf); grid->cursor.point.col = col; - - if (unlikely(row->extra != NULL)) - grid_row_uri_range_erase(row, uri_start, uri_start + width - 1); } static void @@ -3260,7 +3266,6 @@ ascii_printer_fast(struct terminal *term, wchar_t wc) cell->wc = term->vt.last_printed = wc; cell->attrs = term->vt.attrs; - /* Advance cursor */ if (unlikely(++col >= term->cols)) { grid->cursor.lcf = true; @@ -3287,6 +3292,7 @@ term_update_ascii_printer(struct terminal *term) { void (*new_printer)(struct terminal *term, wchar_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 @@ -3294,7 +3300,7 @@ term_update_ascii_printer(struct terminal *term) #if defined(_DEBUG) && LOG_ENABLE_DBG if (term->ascii_printer != new_printer) { - LOG_DBG("switching ASCII printer %s -> %s", + LOG_DBG("§switching ASCII printer %s -> %s", term->ascii_printer == &ascii_printer_fast ? "fast" : "generic", new_printer == &ascii_printer_fast ? "fast" : "generic"); } @@ -3491,84 +3497,19 @@ term_ime_set_cursor_rect(struct terminal *term, int x, int y, int width, void term_osc8_open(struct terminal *term, uint64_t id, const char *uri) { - if (unlikely(term->vt.osc8.begin.row >= 0)) { - /* It’s valid to switch from one URI to another without - * closing the first one */ - term_osc8_close(term); - } - + term_osc8_close(term); xassert(term->vt.osc8.uri == NULL); - term->vt.osc8.begin = (struct coord){ - .col = term->grid->cursor.point.col, - .row = grid_row_absolute(term->grid, term->grid->cursor.point.row), - }; term->vt.osc8.id = id; term->vt.osc8.uri = xstrdup(uri); + term_update_ascii_printer(term); } void term_osc8_close(struct terminal *term) { - if (term->vt.osc8.begin.row < 0) - return; - - if (term->vt.osc8.uri[0] == '\0') - goto done; - - struct coord start = term->vt.osc8.begin; - struct coord end = (struct coord){ - .col = term->grid->cursor.point.col, - .row = grid_row_absolute(term->grid, term->grid->cursor.point.row), - }; - - if (start.row == end.row && start.col == end.col) { - /* Zero-length URL, e.g: \E]8;;http://foo\E\\\E]8;;\E\\ */ - goto done; - } - - /* end is *inclusive */ - if (--end.col < 0) { - end.row--; - end.col = term->cols - 1; - } - - int r = start.row; - int start_col = start.col; - while (true) { - int end_col = r == end.row ? end.col : term->cols - 1; - - struct row *row = term->grid->rows[r]; - - switch (term->conf->url.osc8_underline) { - case OSC8_UNDERLINE_ALWAYS: - for (int c = start_col; c <= end_col; c++) - row->cells[c].attrs.url = true; - break; - - case OSC8_UNDERLINE_URL_MODE: - break; - } - - struct row_uri_range range = { - .start = start_col, - .end = end_col, - .id = term->vt.osc8.id, - .uri = xstrdup(term->vt.osc8.uri), - }; - grid_row_uri_range_add(row, range); - start_col = 0; - - if (r == end.row) - break; - - r++; - r &= term->grid->num_rows - 1; - } - -done: free(term->vt.osc8.uri); - term->vt.osc8.id = 0; term->vt.osc8.uri = NULL; - term->vt.osc8.begin = (struct coord){-1, -1}; + term->vt.osc8.id = 0; + term_update_ascii_printer(term); } diff --git a/terminal.h b/terminal.h index b0526803..1f3913ae 100644 --- a/terminal.h +++ b/terminal.h @@ -185,7 +185,6 @@ struct vt { struct { uint64_t id; char *uri; - struct coord begin; } osc8; struct {