mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing OSC-8 escape (i.e. with an empty URI). But, this meant that cursor movements while the OSC-8 escape was in effect wasn’t handled correctly, since we’d add a range that spanned the cursor movements. Attempts were made to handle this in the cursor movement functions, by closing and re-opening the URI. However, there are too many corner cases to make this a viable approach. Scrolling is one such example, line-wrapping another. This patch takes a different approach; emit, or update the URI range when we print to the grid. This models the intended behavior much more closely, where an active OSC-8 URI act like any other SGR attribute - it is applied to all cells printed to, but otherwise have no effect. To avoid killing performance, this is only done in the “generic” printer. This means OSC-8 open/close calls must now “switch” the ASCII printer. Note that the “fast” printer still needs to *erase* pre-existing OSC-8 URIs. Closes #816
This commit is contained in:
parent
82219713cc
commit
8c50a7afd4
5 changed files with 143 additions and 116 deletions
|
|
@ -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
|
||||
|
|
|
|||
152
grid.c
152
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);
|
||||
}
|
||||
|
|
|
|||
2
grid.h
2
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);
|
||||
|
||||
|
|
|
|||
101
terminal.c
101
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,7 +185,6 @@ struct vt {
|
|||
struct {
|
||||
uint64_t id;
|
||||
char *uri;
|
||||
struct coord begin;
|
||||
} osc8;
|
||||
|
||||
struct {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue