diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
| commit | 4384435941b8c7658856b381d071aee2e0035e89 (patch) | |
| tree | 5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /src/osc/link.c | |
term: move the term core to subprojects/term
The core now builds, tests, and benchmarks alone, as a Meson subproject
with no dependency on the main tree.
- include/term/ holds term.h and the header-only unicode/. src/ holds
the core and its private headers. The main tree gets only
include/term through term_dep, so it cannot reach a private header.
- The term tests, the term benchmarks, the Kitty parser suite, and
tools/amalgamate.py and tools/generate_unicode.py move with it. The
tests run as term:<name>.
- The main build passes its -march (arch_flag) and b_ndebug to the
subproject. A directory configured before this commit needs
meson configure -Dterm:arch_flag=-march=... once.
- The term tests compile with -UNDEBUG. They do work inside assert.
- The subproject exports term_test_dep with term_api.h, for the main
tree's render, thumbnail, color, and selection tests.
- The Kitty bridge used struct vector2i and has not compiled since the
public API redesign. It builds again. 2 of its 14 tests fail: REP
after a tab, and a cursor report after a resize.
- dirgraph.py drops the term and unicode layers. Its self-test uses pty.
- The symbols test ignores the __odr_asan symbols that ASan adds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/osc/link.c')
| -rw-r--r-- | src/osc/link.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/osc/link.c b/src/osc/link.c new file mode 100644 index 0000000..f31a750 --- /dev/null +++ b/src/osc/link.c @@ -0,0 +1,260 @@ +#include "osc.h" +#include "../internal.h" +#include "../screen.h" +#include <stdlib.h> +#include <string.h> + +#define TERM_LINK_MAX 8192 +#define TERM_LINK_URI_MAX 4096 + +struct link_hash_slot { + uint64_t hash; + uint16_t id; +}; + +struct term_links { + char **uri; + uint16_t *free_ids; + struct link_hash_slot *hash_table; + uint32_t hash_cap; + uint16_t capacity; + uint16_t free_count; + uint16_t next; +}; + +static inline uint64_t hash_uri(const char *s) +{ + uint64_t h = HASH64_BASIS; + while (*s) + h = hash64_step(h, (uint8_t)*s++); + h = hash64_finish(h); + return h ? h : 1; +} + +static void links_insert_hash(struct term_links *l, uint64_t h, uint16_t id) +{ + if (!l->hash_table || !l->hash_cap) + return; + uint32_t mask = l->hash_cap - 1; + uint32_t idx = (uint32_t)h & mask; + for (uint32_t probe = 0; probe <= mask; probe++) { + struct link_hash_slot *slot = &l->hash_table[idx]; + if (slot->hash == 0) { + slot->hash = h; + slot->id = id; + return; + } + idx = (idx + 1) & mask; + } +} + +static bool links_grow(struct term_links *l) +{ + uint16_t new_cap = l->capacity == 0 ? 64 : l->capacity < 4096 ? l->capacity * 2 : TERM_LINK_MAX; + if (new_cap <= l->capacity) + return false; + char **new_uri = realloc(l->uri, sizeof(char *) * new_cap); + if (!new_uri) return false; + uint16_t *new_free = malloc(sizeof(uint16_t) * new_cap); + if (!new_free) { + l->uri = new_uri; + return false; + } + uint32_t new_hash_cap = (uint32_t)new_cap * 2; + struct link_hash_slot *new_hash = calloc(new_hash_cap, sizeof(*new_hash)); + if (!new_hash) { + free(new_free); + l->uri = new_uri; + return false; + } + uint16_t *old_free = l->free_ids; + l->free_ids = new_free; + if (l->capacity) + memcpy(new_free, old_free, sizeof(uint16_t) * l->capacity); + free(old_free); + memset(new_uri + l->capacity, 0, sizeof(char *) * (new_cap - l->capacity)); + l->uri = new_uri; + l->capacity = new_cap; + + free(l->hash_table); + l->hash_table = new_hash; + l->hash_cap = new_hash_cap; + + for (int i = 0; i + 1 < l->next; i++) { + if (l->uri[i]) + links_insert_hash(l, hash_uri(l->uri[i]), (uint16_t)(i + 1)); + } + return true; +} + +static void links_mark_row(const struct cell *row, int len, uint64_t *mark, uint16_t cap) +{ + if (len > TERM_MAX_COLS) len = TERM_MAX_COLS; + for (int i = 0; i < len; i++) { + uint16_t id = row[i].pad; + if (id && id <= cap) + mark[(id - 1) >> 6] |= 1ull << ((id - 1) & 63); + } +} + +static void links_mark_id(uint16_t id, uint64_t *mark, uint16_t cap) +{ + if (id && id <= cap) + mark[(id - 1) >> 6] |= 1ull << ((id - 1) & 63); +} + +struct links_mark { + uint64_t *mark; + uint16_t cap; +}; + +static void links_mark_hist(void *ud, uint16_t id) +{ + struct links_mark *m = ud; + links_mark_id(id, m->mark, m->cap); +} + +static void links_mark_screen(const struct screen *s, struct links_mark *m) +{ + for (int y = 0; y < s->rows; y++) { + const struct srow *row = screen_get_row(s, y); + if (row->len == 0) + continue; + if (row->kind == ROW_CELLS) + links_mark_row(row->cells, row->len, m->mark, m->cap); + else + term_hist_text_mark_links(&row->text, links_mark_hist, m); + } +} + +static void links_gc(struct term *t, struct term_links *l) +{ + uint64_t mark[TERM_LINK_MAX / 64] = { 0 }; + struct links_mark m = { mark, l->capacity }; + links_mark_id(t->cur_link, mark, l->capacity); + links_mark_screen(&t->scr, &m); + if (t->alternate && t->alt_allocated) + links_mark_screen(&t->alt_scr, &m); + term_hist_mark_links(&t->hist, links_mark_hist, &m); + l->free_count = 0; + for (int i = 0; i + 1 < l->next; i++) { + if (l->uri[i] && !(mark[i >> 6] & (1ull << (i & 63)))) { + free(l->uri[i]); + l->uri[i] = NULL; + } + if (!l->uri[i]) + l->free_ids[l->free_count++] = (uint16_t)(i + 1); + } + if (l->hash_table && l->hash_cap) { + memset(l->hash_table, 0, sizeof(*l->hash_table) * l->hash_cap); + for (int i = 0; i + 1 < l->next; i++) { + if (l->uri[i]) + links_insert_hash(l, hash_uri(l->uri[i]), (uint16_t)(i + 1)); + } + } +} + +static uint16_t links_find(const struct term_links *l, const char *uri, uint64_t h) +{ + if (!l->hash_table || !l->hash_cap) + return 0; + uint32_t mask = l->hash_cap - 1; + uint32_t idx = (uint32_t)h & mask; + for (uint32_t probe = 0; probe <= mask; probe++) { + const struct link_hash_slot *slot = &l->hash_table[idx]; + if (slot->hash == 0) + return 0; + if (slot->hash == h) { + uint16_t id = slot->id; + if (id && id <= l->capacity && l->uri[id - 1] && !strcmp(l->uri[id - 1], uri)) + return id; + } + idx = (idx + 1) & mask; + } + return 0; +} + +static uint16_t links_alloc(struct term *t, struct term_links *l) +{ + if (l->free_count) + return l->free_ids[--l->free_count]; + if (l->next <= l->capacity || links_grow(l)) + return l->next++; + links_gc(t, l); + if (l->free_count) + return l->free_ids[--l->free_count]; + return 0; +} + +void term_osc8(struct term *t, const char *params) +{ + const char *sep = strchr(params, ';'); + if (!sep) + return; + const char *uri = sep + 1; + if (!*uri) { + t->cur_link = 0; + return; + } + if (strlen(uri) > TERM_LINK_URI_MAX) { + t->cur_link = 0; + return; + } + struct term_links *l = t->links; + if (!l) { + l = calloc(1, sizeof(*l)); + if (!l) { + t->cur_link = 0; + return; + } + l->next = 1; + t->links = l; + } + uint64_t h = hash_uri(uri); + uint16_t id = links_find(l, uri, h); + if (!id) { + id = links_alloc(t, l); + if (!id) { + t->cur_link = 0; + return; + } + l->uri[id - 1] = term_strdup(uri); + if (!l->uri[id - 1]) { + l->free_ids[l->free_count++] = id; + t->cur_link = 0; + return; + } + links_insert_hash(l, h, id); + } + t->cur_link = id; +} + +const char *term_osc8_url_at(const struct term *t, int col, int row) +{ + const struct term_links *l = t->links; + if (!l || col < 0 || col >= t->cols) + return NULL; + const struct cell *cells = term_screen_view_row(t, row); + if (!cells) + return NULL; + uint16_t id = cells[col].pad; + if (!id || id > l->capacity || + !(cells[col].flags & CELL_LINK)) + return NULL; + return l->uri[id - 1]; +} + +void term_osc8_cleanup(struct term *t) +{ + struct term_links *l = t->links; + if (!l) + return; + for (uint16_t i = 0; i < l->capacity; i++) + free(l->uri[i]); + free(l->uri); + free(l->free_ids); + free(l->hash_table); + free(l); + t->links = NULL; + t->cur_link = 0; +} |
