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 | |
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')
| -rw-r--r-- | src/osc/clipboard.c | 76 | ||||
| -rw-r--r-- | src/osc/dispatch.c | 227 | ||||
| -rw-r--r-- | src/osc/link.c | 260 | ||||
| -rw-r--r-- | src/osc/meson.build | 6 | ||||
| -rw-r--r-- | src/osc/misc.c | 69 | ||||
| -rw-r--r-- | src/osc/osc.h | 30 |
6 files changed, 668 insertions, 0 deletions
diff --git a/src/osc/clipboard.c b/src/osc/clipboard.c new file mode 100644 index 0000000..a5ac59d --- /dev/null +++ b/src/osc/clipboard.c @@ -0,0 +1,76 @@ +#include "osc.h" +#include "../internal.h" +#include <stdlib.h> +#include <string.h> + +static int b64_val(char c) +{ + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; + return -1; +} + +static bool b64_decode(const char *s, size_t n, char *out, size_t *out_len) +{ + size_t pad = 0; + while (pad < n && s[n - pad - 1] == '=') + pad++; + n -= pad; + if (pad > 2 || (pad && n % 4 + pad != 4)) + return false; + size_t rem = n % 4; + if (rem == 1) + return false; + size_t i = 0, o = 0; + for (; i + 4 <= n; i += 4) { + int a = b64_val(s[i]), b = b64_val(s[i + 1]); + int c = b64_val(s[i + 2]), d = b64_val(s[i + 3]); + if ((a | b | c | d) < 0) + return false; + out[o++] = (char)(a << 2 | b >> 4); + out[o++] = (char)(b << 4 | c >> 2); + out[o++] = (char)(c << 6 | d); + } + if (rem >= 2) { + int a = b64_val(s[i]), b = b64_val(s[i + 1]); + if ((a | b) < 0) + return false; + out[o++] = (char)(a << 2 | b >> 4); + if (rem == 3) { + int c = b64_val(s[i + 2]); + if (c < 0) + return false; + out[o++] = (char)(b << 4 | c >> 2); + } + } + *out_len = o; + return true; +} + +void term_osc52(struct term *t, const char *params) +{ + const char *sep = strchr(params, ';'); + if (!sep || sep == params) + return; + const char *pd = sep + 1; + if (!strcmp(pd, "?") || !t->cb.clipboard) + return; + size_t sel_len = (size_t)(sep - params); + char sel[64]; + if (sel_len >= sizeof(sel)) + return; + memcpy(sel, params, sel_len); + sel[sel_len] = '\0'; + if (!*pd) { + t->cb.clipboard(t->cb.ud, sel, "", 0); + return; + } + size_t enc_len = strlen(pd); + char *dec = (char *)pd; + size_t dec_len; + if (b64_decode(pd, enc_len, dec, &dec_len)) + t->cb.clipboard(t->cb.ud, sel, dec, dec_len); +} diff --git a/src/osc/dispatch.c b/src/osc/dispatch.c new file mode 100644 index 0000000..4cf986e --- /dev/null +++ b/src/osc/dispatch.c @@ -0,0 +1,227 @@ +#include "osc.h" +#include "../internal.h" +#include "../palette.h" +#include "../screen.h" +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#define OSC_HEAP_INIT 4096 +#define OSC_HEAP_KEEP (1u << 20) + +size_t term_osc_room(const struct term *t) +{ + size_t cap = t->osc_heap ? t->osc_heap_cap : sizeof(t->osc_buf); + return cap - 1 - (size_t)t->osc_len; +} + +char *term_osc_tail(struct term *t) +{ + return (t->osc_heap ? t->osc_heap : t->osc_buf) + t->osc_len; +} + +void term_osc_commit(struct term *t, size_t n) +{ + t->osc_len += (int)n; +} + +char *term_osc_data(struct term *t) +{ + return t->osc_heap ? t->osc_heap : t->osc_buf; +} + +bool term_osc_grow(struct term *t) +{ + if (!t->osc_heap) { + char *p = malloc(OSC_HEAP_INIT); + if (!p) return false; + memcpy(p, t->osc_buf, (size_t)t->osc_len); + t->osc_heap = p; + t->osc_heap_cap = OSC_HEAP_INIT; + return true; + } + if (t->osc_heap_cap >= TERM_OSC_MAX) return false; + size_t cap = t->osc_heap_cap * 2; + if (cap > TERM_OSC_MAX) cap = TERM_OSC_MAX; + char *p = realloc(t->osc_heap, cap); + if (!p) return false; + t->osc_heap = p; + t->osc_heap_cap = cap; + return true; +} + +void term_osc_cleanup(struct term *t) +{ + free(t->osc_heap); + t->osc_heap = NULL; + t->osc_heap_cap = 0; + free(t->dcs_buf); + t->dcs_buf = NULL; + t->dcs_cap = 0; + term_osc8_cleanup(t); + free(t->cwd); + t->cwd = NULL; + free(t->pointer_shape); + t->pointer_shape = NULL; +} + +/* Reply to a color query. An OSC 4 reply names the index it answers for; + * index is negative for OSC 10, 11, and 12. */ +static void send_color_query_reply(struct term *t, int code, int index, uint32_t argb) +{ + char resp[64], prefix[16] = ""; + uint8_t r = (argb >> 16) & 0xff, g = (argb >> 8) & 0xff, b = argb & 0xff; + if (index >= 0) + snprintf(prefix, sizeof(prefix), "%d;", index); + snprintf(resp, sizeof(resp), + "\x1b]%d;%srgb:%02x%02x/%02x%02x/%02x%02x\x1b\\", + code, prefix, r, r, g, g, b, b); + send_response(t, resp); +} + +static void osc_color_set_or_query(struct term *t, int code, const char *params, int slot) +{ + if (!strcmp(params, "?")) { + send_color_query_reply(t, code, -1, t->palette.colors[slot]); + return; + } + uint32_t argb; + if (term_color_parse(params, &argb)) + term_palette_set(&t->palette, slot, argb); +} + +static long osc_next_index(const char **pp) +{ + char *end; + long idx = strtol(*pp, &end, 10); + if (end == *pp || (*end != ';' && *end != '\0')) + return -1; + *pp = end; + if (**pp == ';') (*pp)++; + return idx; +} + +static void osc4_handle(struct term *t, const char *params) +{ + const char *p = params; + while (*p) { + long idx = osc_next_index(&p); + if (idx < 0) + return; + + const char *spec = p; + const char *spec_end = spec; + while (*spec_end && *spec_end != ';') + spec_end++; + size_t spec_len = (size_t)(spec_end - spec); + if (spec_len == 1 && spec[0] == '?') { + if (idx <= 255) + send_color_query_reply(t, 4, (int)idx, t->palette.colors[idx]); + } else { + char buf[128]; + if (idx <= 255 && spec_len < sizeof(buf)) { + memcpy(buf, spec, spec_len); + buf[spec_len] = '\0'; + uint32_t argb; + if (term_color_parse(buf, &argb)) + term_palette_set(&t->palette, (int)idx, argb); + } + } + p = spec_end; + if (*p == ';') p++; + } +} + +static void osc104_handle(struct term *t, const char *params) +{ + if (!*params) { + for (int i = 0; i < 256; i++) + term_palette_reset(&t->palette, i); + return; + } + const char *p = params; + while (*p) { + long idx = osc_next_index(&p); + if (idx < 0) + return; + if (idx <= 255) + term_palette_reset(&t->palette, (int)idx); + } +} + +void term_osc_finish(struct term *t) +{ + if (!term_str_was_truncated(t)) { + char *buf = term_osc_data(t); + buf[t->osc_len] = '\0'; + + if (!t->alternate && t->osc_len >= 5 && !strncmp(buf, "133;", 4) && + (buf[5] == ';' || buf[5] == '\0')) { + if (buf[4] == 'A') { + t->prompt_y = t->cursor_y; + t->has_prompt = true; + } + if (buf[4] == 'C' || buf[4] == 'D') { + t->prompt_y = -1; + t->has_prompt = false; + } + } + + char *rest; + long code = strtol(buf, &rest, 10); + bool no_code = rest == buf; + bool legacy_title = no_code && !strchr(buf, ';'); + if (no_code) { + code = legacy_title || *buf == ';' ? 0 : -1; + rest = buf; + } + if (code >= 0 && (legacy_title || *rest == ';' || *rest == '\0' || code <= 2)) { + const char *params = *rest == ';' ? rest + 1 : rest; + switch (code) { + case 0: case 1: case 2: + if (t->cb.title) + t->cb.title(t->cb.ud, params); + break; + case 4: + osc4_handle(t, params); + break; + case 8: + term_osc8(t, params); + break; + case 10: + osc_color_set_or_query(t, 10, params, TERM_PALETTE_FG); + break; + case 11: + osc_color_set_or_query(t, 11, params, TERM_PALETTE_BG); + break; + case 12: + osc_color_set_or_query(t, 12, params, TERM_PALETTE_CURSOR); + break; + case 52: + term_osc52(t, params); + break; + case 104: + osc104_handle(t, params); + break; + case 110: + term_palette_reset(&t->palette, TERM_PALETTE_FG); + break; + case 111: + term_palette_reset(&t->palette, TERM_PALETTE_BG); + break; + case 112: + term_palette_reset(&t->palette, TERM_PALETTE_CURSOR); + break; + default: + term_osc_misc_dispatch(t, code, params); + break; + } + } + } + t->osc_len = 0; + if (t->osc_heap && t->osc_heap_cap > OSC_HEAP_KEEP) { + free(t->osc_heap); + t->osc_heap = NULL; + t->osc_heap_cap = 0; + } +} 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; +} diff --git a/src/osc/meson.build b/src/osc/meson.build new file mode 100644 index 0000000..a3b9efa --- /dev/null +++ b/src/osc/meson.build @@ -0,0 +1,6 @@ +term_osc_src = files( + 'clipboard.c', + 'dispatch.c', + 'link.c', + 'misc.c', +) diff --git a/src/osc/misc.c b/src/osc/misc.c new file mode 100644 index 0000000..5d6b506 --- /dev/null +++ b/src/osc/misc.c @@ -0,0 +1,69 @@ +#include "osc.h" +#include "../internal.h" +#include <stdlib.h> +#include <string.h> + +static void osc7_cwd(struct term *t, const char *uri) +{ + if (!term_has_prefix_ci(uri, "file://")) + return; + const char *p = uri + 7; + const char *slash = strchr(p, '/'); + const char *path = slash ? slash : p; + char *cwd = malloc(strlen(path) + 1); + if (!cwd) + return; + char *d = cwd; + while (*path) { + int hi, lo; + if (*path == '%' && (hi = term_hex_nib(path[1])) >= 0 && + (lo = term_hex_nib(path[2])) >= 0) { + *d++ = (char)(hi * 16 + lo); + path += 3; + } else { + *d++ = *path++; + } + } + *d = '\0'; + free(t->cwd); + t->cwd = cwd; +} + +static void osc777_notify(struct term *t, const char *params) +{ + if (strncmp(params, "notify;", 7)) + return; + const char *title = params + 7; + const char *sep = strchr(title, ';'); + if (!sep) + return; + char *wsep = (char *)sep; + *wsep = '\0'; + t->cb.notify(t->cb.ud, title, sep + 1); + *wsep = ';'; +} + +void term_osc_misc_dispatch(struct term *t, long code, const char *params) +{ + if (!params) + return; + switch (code) { + case 7: + osc7_cwd(t, params); + break; + case 9: + if (t->cb.notify && strncmp(params, "4;", 2)) + t->cb.notify(t->cb.ud, "wsap_term", params); + break; + case 22: + free(t->pointer_shape); + t->pointer_shape = *params ? term_strdup(params) : NULL; + break; + case 777: + if (t->cb.notify) + osc777_notify(t, params); + break; + default: + break; + } +} diff --git a/src/osc/osc.h b/src/osc/osc.h new file mode 100644 index 0000000..1398534 --- /dev/null +++ b/src/osc/osc.h @@ -0,0 +1,30 @@ +#ifndef TERM_OSC_H +#define TERM_OSC_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +struct term; + +#define TERM_OSC_MAX (16u << 20) + +size_t term_osc_room(const struct term *t); +char *term_osc_tail(struct term *t); +void term_osc_commit(struct term *t, size_t n); +bool term_osc_grow(struct term *t); + +char *term_osc_data(struct term *t); + +void term_osc_finish(struct term *t); + +void term_osc_cleanup(struct term *t); + +void term_osc8(struct term *t, const char *params); +void term_osc52(struct term *t, const char *params); +void term_osc_misc_dispatch(struct term *t, long code, const char *params); + +const char *term_osc8_url_at(const struct term *t, int col, int row); +void term_osc8_cleanup(struct term *t); + +#endif |
