From 4741f7a6dfa144ce65cf3bbdd4a1bc6bee6df2e5 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 13:36:27 +0300 Subject: Optimize and simplify loops, table lookups, and branchless parsers - Replace sequential colon-counting loop with branchless CTZ bitmask in src/pen.h - Replace linear strchr with 256-bit ASCII punctuation bitmask in src/selection.c - Flatten 3D color cube loop and precompute constants in src/palette.c - Use static 256-byte LUT for hex decoding and bsearch for termcap in src/escape.c - Replace capacity doubling while loops with branchless CLZ pow2 helpers across core - Use direct memset/memcpy in src/snapshot.c - Optimize input digit/byte formatting, alt-prefix path, and make keyboard state optional in src/input.c --- src/compat.h | 77 +++++++++++++++++- src/escape.c | 179 +++++++++++++++++++++++------------------ src/gstore.c | 5 +- src/hist.c | 3 +- src/input.c | 228 ++++++++++++++++++++++++----------------------------- src/palette.c | 213 +++++++++++++++++++++++-------------------------- src/palette.h | 2 +- src/pen.h | 12 ++- src/screen.c | 4 +- src/selection.c | 10 ++- src/snapshot.c | 16 ++-- tests/input_test.c | 13 +++ tests/osc_test.c | 48 +++++++++++ 13 files changed, 472 insertions(+), 338 deletions(-) diff --git a/src/compat.h b/src/compat.h index d1264fe..a972a0f 100644 --- a/src/compat.h +++ b/src/compat.h @@ -50,12 +50,15 @@ #define TERM_EXPECT(x, v) (x) #endif -/* term_ctz32(0) is 32 and term_ctz64(0) is 64 on every path. */ +/* term_ctz32(0) is 32, term_clz32(0) is 32, term_ctz64(0) is 64, and + * term_clz64(0) is 64 on every path. */ #if defined(TERM_HAVE_BUILTINS) static inline int term_ctz32(uint32_t x) { return x ? __builtin_ctz(x) : 32; } +static inline int term_clz32(uint32_t x) { return x ? __builtin_clz(x) : 32; } static inline int term_popcount32(uint32_t x) { return __builtin_popcount(x); } static inline int term_ctz64(uint64_t x) { return x ? __builtin_ctzll(x) : 64; } +static inline int term_clz64(uint64_t x) { return x ? __builtin_clzll(x) : 64; } static inline int term_popcount64(uint64_t x) { return __builtin_popcountll(x); } #else @@ -66,6 +69,11 @@ static inline int term_ctz32(uint32_t x) unsigned long i; return _BitScanForward(&i, x) ? (int)i : 32; } +static inline int term_clz32(uint32_t x) +{ + unsigned long i; + return _BitScanReverse(&i, x) ? 31 - (int)i : 32; +} static inline int term_popcount32(uint32_t x) { return (int)__popcnt(x); } #else static inline int term_ctz32(uint32_t x) @@ -79,6 +87,18 @@ static inline int term_ctz32(uint32_t x) } return n; } +static inline int term_clz32(uint32_t x) +{ + if (!x) + return 32; + int n = 0; + if (!(x & 0xffff0000u)) { n += 16; x <<= 16; } + if (!(x & 0xff000000u)) { n += 8; x <<= 8; } + if (!(x & 0xf0000000u)) { n += 4; x <<= 4; } + if (!(x & 0xc0000000u)) { n += 2; x <<= 2; } + if (!(x & 0x80000000u)) { n += 1; } + return n; +} static inline int term_popcount32(uint32_t x) { int n = 0; @@ -95,6 +115,12 @@ static inline int term_ctz64(uint64_t x) return lo ? term_ctz32(lo) : 32 + term_ctz32((uint32_t)(x >> 32)); } +static inline int term_clz64(uint64_t x) +{ + uint32_t hi = (uint32_t)(x >> 32); + return hi ? term_clz32(hi) : 32 + term_clz32((uint32_t)x); +} + static inline int term_popcount64(uint64_t x) { return term_popcount32((uint32_t)x) + term_popcount32((uint32_t)(x >> 32)); @@ -102,6 +128,55 @@ static inline int term_popcount64(uint64_t x) #endif +/* Smallest power of two >= x, branchlessly via CLZ. Returns UINT32_MAX on overflow. */ +static inline uint32_t term_next_pow2_32(uint32_t x) +{ + uint32_t v = x <= 1 ? 1 : x - 1; + int lz = term_clz32(v); + uint32_t res = UINT32_C(1) << ((32 - lz) & 31); + return (x <= 1) ? 1 : (lz == 0 ? UINT32_MAX : res); +} + +/* Smallest power of two >= x, branchlessly via CLZ. Returns UINT64_MAX on overflow. */ +static inline uint64_t term_next_pow2_64(uint64_t x) +{ + uint64_t v = x <= 1 ? 1 : x - 1; + int lz = term_clz64(v); + uint64_t res = UINT64_C(1) << ((64 - lz) & 63); + return (x <= 1) ? 1 : (lz == 0 ? UINT64_MAX : res); +} + +static inline size_t term_next_pow2(size_t x) +{ +#if SIZE_MAX > UINT32_MAX + return (size_t)term_next_pow2_64(x); +#else + return (size_t)term_next_pow2_32((uint32_t)x); +#endif +} + +/* Grows cap by doubling until >= need, branchlessly. + * If cap >= need, returns cap. Otherwise returns next power of two >= need. */ +static inline uint32_t term_grow_cap32(uint32_t cap, uint32_t need) +{ + return cap >= need ? cap : term_next_pow2_32(need); +} + +static inline uint64_t term_grow_cap64(uint64_t cap, uint64_t need) +{ + return cap >= need ? cap : term_next_pow2_64(need); +} + +static inline size_t term_grow_cap(size_t cap, size_t need) +{ +#if SIZE_MAX > UINT32_MAX + return (size_t)term_grow_cap64(cap, need); +#else + return (size_t)term_grow_cap32((uint32_t)cap, (uint32_t)need); +#endif +} + + /* A spin-wait hint. Compilers without one get a no-op. */ static inline void term_cpu_relax(void) { diff --git a/src/escape.c b/src/escape.c index 119010c..9576e8e 100644 --- a/src/escape.c +++ b/src/escape.c @@ -53,94 +53,123 @@ static int sgr_serialize(const struct term *t, char *buf, size_t sz) return n; } +static const signed char hex_val[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + static int dcs_decode_hex(const char *s, int len, char *out, int cap) { if (len & 1) return -1; int n = 0; for (int i = 0; i < len; i += 2) { - int hi = term_hex_nib(s[i]), lo = term_hex_nib(s[i + 1]); - if (hi < 0 || lo < 0 || n >= cap) return -1; + int hi = hex_val[(unsigned char)s[i]], lo = hex_val[(unsigned char)s[i + 1]]; + if ((hi | lo) < 0 || n >= cap) return -1; out[n++] = (char)(hi << 4 | lo); } return n; } +struct tcap_entry { + const char *name, *value; +}; + +static int tcap_cmp(const void *k, const void *e) +{ + const char *key = k; + const struct tcap_entry *elem = e; + return strcmp(key, elem->name); +} + static const char *term_tcap(const struct term *t, const char *name) { - static const struct { const char *name, *value; } caps[] = { - { "Co", "16777216" }, - { "RGB", "8/8/8" }, - { "colors", "16777216" }, - { "kDC", "\x1b[3;2~" }, { "kDC3", "\x1b[3;3~" }, - { "kDC4", "\x1b[3;4~" }, { "kDC5", "\x1b[3;5~" }, - { "kDC6", "\x1b[3;6~" }, { "kDC7", "\x1b[3;7~" }, - { "kDN", "\x1b[1;2B" }, { "kDN3", "\x1b[1;3B" }, - { "kDN4", "\x1b[1;4B" }, { "kDN5", "\x1b[1;5B" }, - { "kDN6", "\x1b[1;6B" }, { "kDN7", "\x1b[1;7B" }, - { "kEND", "\x1b[1;2F" }, { "kEND3", "\x1b[1;3F" }, - { "kEND4", "\x1b[1;4F" }, { "kEND5", "\x1b[1;5F" }, - { "kEND6", "\x1b[1;6F" }, { "kEND7", "\x1b[1;7F" }, - { "kHOM", "\x1b[1;2H" }, { "kHOM3", "\x1b[1;3H" }, - { "kHOM4", "\x1b[1;4H" }, { "kHOM5", "\x1b[1;5H" }, - { "kHOM6", "\x1b[1;6H" }, { "kHOM7", "\x1b[1;7H" }, - { "kIC", "\x1b[2;2~" }, { "kIC3", "\x1b[2;3~" }, - { "kIC4", "\x1b[2;4~" }, { "kIC5", "\x1b[2;5~" }, - { "kIC6", "\x1b[2;6~" }, { "kIC7", "\x1b[2;7~" }, - { "kLFT", "\x1b[1;2D" }, { "kLFT3", "\x1b[1;3D" }, - { "kLFT4", "\x1b[1;4D" }, { "kLFT5", "\x1b[1;5D" }, - { "kLFT6", "\x1b[1;6D" }, { "kLFT7", "\x1b[1;7D" }, - { "kNXT", "\x1b[6;2~" }, { "kNXT3", "\x1b[6;3~" }, - { "kNXT4", "\x1b[6;4~" }, { "kNXT5", "\x1b[6;5~" }, - { "kNXT6", "\x1b[6;6~" }, { "kNXT7", "\x1b[6;7~" }, - { "kPRV", "\x1b[5;2~" }, { "kPRV3", "\x1b[5;3~" }, - { "kPRV4", "\x1b[5;4~" }, { "kPRV5", "\x1b[5;5~" }, - { "kPRV6", "\x1b[5;6~" }, { "kPRV7", "\x1b[5;7~" }, - { "kRIT", "\x1b[1;2C" }, { "kRIT3", "\x1b[1;3C" }, - { "kRIT4", "\x1b[1;4C" }, { "kRIT5", "\x1b[1;5C" }, - { "kRIT6", "\x1b[1;6C" }, { "kRIT7", "\x1b[1;7C" }, - { "kUP", "\x1b[1;2A" }, { "kUP3", "\x1b[1;3A" }, - { "kUP4", "\x1b[1;4A" }, { "kUP5", "\x1b[1;5A" }, - { "kUP6", "\x1b[1;6A" }, { "kUP7", "\x1b[1;7A" }, - { "kbs", "\x7f" }, { "kcbt", "\x1b[Z" }, - { "kcub1", "\x1bOD" }, { "kcud1", "\x1bOB" }, - { "kcuf1", "\x1bOC" }, { "kcuu1", "\x1bOA" }, - { "kdch1", "\x1b[3~" }, { "kend", "\x1bOF" }, - { "kent", "\x1bOM" }, { "khome", "\x1bOH" }, - { "kich1", "\x1b[2~" }, { "kind", "\x1b[1;2B" }, - { "kmous", "\x1b[<" }, { "knp", "\x1b[6~" }, - { "kpp", "\x1b[5~" }, { "kri", "\x1b[1;2A" }, - { "kxIN", "\x1b[I" }, { "kxOUT", "\x1b[O" }, - { "kf1", "\x1bOP" }, { "kf2", "\x1bOQ" }, - { "kf3", "\x1bOR" }, { "kf4", "\x1bOS" }, - { "kf5", "\x1b[15~" }, { "kf6", "\x1b[17~" }, - { "kf7", "\x1b[18~" }, { "kf8", "\x1b[19~" }, - { "kf9", "\x1b[20~" }, { "kf10", "\x1b[21~" }, - { "kf11", "\x1b[23~" }, { "kf12", "\x1b[24~" }, - { "kf13", "\x1b[1;2P" }, { "kf14", "\x1b[1;2Q" }, - { "kf15", "\x1b[1;2R" }, { "kf16", "\x1b[1;2S" }, - { "kf17", "\x1b[15;2~" }, { "kf18", "\x1b[17;2~" }, - { "kf19", "\x1b[18;2~" }, { "kf20", "\x1b[19;2~" }, - { "kf21", "\x1b[20;2~" }, { "kf22", "\x1b[21;2~" }, - { "kf23", "\x1b[23;2~" }, { "kf24", "\x1b[24;2~" }, - { "kf25", "\x1b[1;5P" }, { "kf26", "\x1b[1;5Q" }, - { "kf27", "\x1b[1;5R" }, { "kf28", "\x1b[1;5S" }, - { "kf29", "\x1b[15;5~" }, { "kf30", "\x1b[17;5~" }, - { "kf31", "\x1b[18;5~" }, { "kf32", "\x1b[19;5~" }, - { "kf33", "\x1b[20;5~" }, { "kf34", "\x1b[21;5~" }, - { "kf35", "\x1b[23;5~" }, { "kf36", "\x1b[24;5~" }, - { "kf37", "\x1b[1;6P" }, { "kf38", "\x1b[1;6Q" }, - { "kf39", "\x1b[1;6R" }, { "kf40", "\x1b[1;6S" }, - { "kf41", "\x1b[15;6~" }, { "kf42", "\x1b[17;6~" }, - { "kf43", "\x1b[18;6~" }, { "kf44", "\x1b[19;6~" }, - { "kf45", "\x1b[20;6~" }, { "kf46", "\x1b[21;6~" }, - { "kf47", "\x1b[23;6~" }, { "kf48", "\x1b[24;6~" }, + static const struct tcap_entry caps[] = { + { "Co", "16777216" }, { "RGB", "8/8/8" }, + { "colors", "16777216" }, { "kDC", "\x1b[3;2~" }, + { "kDC3", "\x1b[3;3~" }, { "kDC4", "\x1b[3;4~" }, + { "kDC5", "\x1b[3;5~" }, { "kDC6", "\x1b[3;6~" }, + { "kDC7", "\x1b[3;7~" }, { "kDN", "\x1b[1;2B" }, + { "kDN3", "\x1b[1;3B" }, { "kDN4", "\x1b[1;4B" }, + { "kDN5", "\x1b[1;5B" }, { "kDN6", "\x1b[1;6B" }, + { "kDN7", "\x1b[1;7B" }, { "kEND", "\x1b[1;2F" }, + { "kEND3", "\x1b[1;3F" }, { "kEND4", "\x1b[1;4F" }, + { "kEND5", "\x1b[1;5F" }, { "kEND6", "\x1b[1;6F" }, + { "kEND7", "\x1b[1;7F" }, { "kHOM", "\x1b[1;2H" }, + { "kHOM3", "\x1b[1;3H" }, { "kHOM4", "\x1b[1;4H" }, + { "kHOM5", "\x1b[1;5H" }, { "kHOM6", "\x1b[1;6H" }, + { "kHOM7", "\x1b[1;7H" }, { "kIC", "\x1b[2;2~" }, + { "kIC3", "\x1b[2;3~" }, { "kIC4", "\x1b[2;4~" }, + { "kIC5", "\x1b[2;5~" }, { "kIC6", "\x1b[2;6~" }, + { "kIC7", "\x1b[2;7~" }, { "kLFT", "\x1b[1;2D" }, + { "kLFT3", "\x1b[1;3D" }, { "kLFT4", "\x1b[1;4D" }, + { "kLFT5", "\x1b[1;5D" }, { "kLFT6", "\x1b[1;6D" }, + { "kLFT7", "\x1b[1;7D" }, { "kNXT", "\x1b[6;2~" }, + { "kNXT3", "\x1b[6;3~" }, { "kNXT4", "\x1b[6;4~" }, + { "kNXT5", "\x1b[6;5~" }, { "kNXT6", "\x1b[6;6~" }, + { "kNXT7", "\x1b[6;7~" }, { "kPRV", "\x1b[5;2~" }, + { "kPRV3", "\x1b[5;3~" }, { "kPRV4", "\x1b[5;4~" }, + { "kPRV5", "\x1b[5;5~" }, { "kPRV6", "\x1b[5;6~" }, + { "kPRV7", "\x1b[5;7~" }, { "kRIT", "\x1b[1;2C" }, + { "kRIT3", "\x1b[1;3C" }, { "kRIT4", "\x1b[1;4C" }, + { "kRIT5", "\x1b[1;5C" }, { "kRIT6", "\x1b[1;6C" }, + { "kRIT7", "\x1b[1;7C" }, { "kUP", "\x1b[1;2A" }, + { "kUP3", "\x1b[1;3A" }, { "kUP4", "\x1b[1;4A" }, + { "kUP5", "\x1b[1;5A" }, { "kUP6", "\x1b[1;6A" }, + { "kUP7", "\x1b[1;7A" }, { "kbs", "\x7f" }, + { "kcbt", "\x1b[Z" }, { "kcub1", "\x1bOD" }, + { "kcud1", "\x1bOB" }, { "kcuf1", "\x1bOC" }, + { "kcuu1", "\x1bOA" }, { "kdch1", "\x1b[3~" }, + { "kend", "\x1bOF" }, { "kent", "\x1bOM" }, + { "kf1", "\x1bOP" }, { "kf10", "\x1b[21~" }, + { "kf11", "\x1b[23~" }, { "kf12", "\x1b[24~" }, + { "kf13", "\x1b[1;2P" }, { "kf14", "\x1b[1;2Q" }, + { "kf15", "\x1b[1;2R" }, { "kf16", "\x1b[1;2S" }, + { "kf17", "\x1b[15;2~" }, { "kf18", "\x1b[17;2~" }, + { "kf19", "\x1b[18;2~" }, { "kf2", "\x1bOQ" }, + { "kf20", "\x1b[19;2~" }, { "kf21", "\x1b[20;2~" }, + { "kf22", "\x1b[21;2~" }, { "kf23", "\x1b[23;2~" }, + { "kf24", "\x1b[24;2~" }, { "kf25", "\x1b[1;5P" }, + { "kf26", "\x1b[1;5Q" }, { "kf27", "\x1b[1;5R" }, + { "kf28", "\x1b[1;5S" }, { "kf29", "\x1b[15;5~" }, + { "kf3", "\x1bOR" }, { "kf30", "\x1b[17;5~" }, + { "kf31", "\x1b[18;5~" }, { "kf32", "\x1b[19;5~" }, + { "kf33", "\x1b[20;5~" }, { "kf34", "\x1b[21;5~" }, + { "kf35", "\x1b[23;5~" }, { "kf36", "\x1b[24;5~" }, + { "kf37", "\x1b[1;6P" }, { "kf38", "\x1b[1;6Q" }, + { "kf39", "\x1b[1;6R" }, { "kf4", "\x1bOS" }, + { "kf40", "\x1b[1;6S" }, { "kf41", "\x1b[15;6~" }, + { "kf42", "\x1b[17;6~" }, { "kf43", "\x1b[18;6~" }, + { "kf44", "\x1b[19;6~" }, { "kf45", "\x1b[20;6~" }, + { "kf46", "\x1b[21;6~" }, { "kf47", "\x1b[23;6~" }, + { "kf48", "\x1b[24;6~" }, { "kf5", "\x1b[15~" }, + { "kf6", "\x1b[17~" }, { "kf7", "\x1b[18~" }, + { "kf8", "\x1b[19~" }, { "kf9", "\x1b[20~" }, + { "khome", "\x1bOH" }, { "kich1", "\x1b[2~" }, + { "kind", "\x1b[1;2B" }, { "kmous", "\x1b[<" }, + { "knp", "\x1b[6~" }, { "kpp", "\x1b[5~" }, + { "kri", "\x1b[1;2A" }, { "kxIN", "\x1b[I" }, + { "kxOUT", "\x1b[O" }, }; if (!strcmp(name, "TN") || !strcmp(name, "name")) return t->tcap_name; - for (size_t i = 0; i < sizeof(caps) / sizeof(caps[0]); i++) - if (!strcmp(name, caps[i].name)) - return caps[i].value; - return NULL; + const struct tcap_entry *found = bsearch(name, caps, + sizeof(caps) / sizeof(caps[0]), + sizeof(caps[0]), tcap_cmp); + return found ? found->value : NULL; } struct resp_buf { @@ -154,9 +183,7 @@ static void resp_put(struct resp_buf *r, const char *s, size_t n) if (r->failed) return; if (r->len + n + 1 > r->cap) { - size_t cap = r->cap ? r->cap : 256; - while (cap < r->len + n + 1) - cap *= 2; + size_t cap = term_grow_cap(r->cap ? r->cap : 256, r->len + n + 1); char *p = realloc(r->s, cap); if (!p) { r->failed = true; diff --git a/src/gstore.c b/src/gstore.c index c5109c6..d2bc57f 100644 --- a/src/gstore.c +++ b/src/gstore.c @@ -355,9 +355,8 @@ uint32_t term_gstore_intern(struct gstore *g, const uint32_t *cps, int n) if (n > 2) { size_t extra = (size_t)(n - 2); if (g->arena_len + extra > g->arena_cap) { - size_t new_cap = g->arena_cap ? g->arena_cap * 2 : 512; - while (new_cap < g->arena_len + extra) - new_cap *= 2; + size_t new_cap = term_grow_cap(g->arena_cap ? g->arena_cap * 2 : 512, + g->arena_len + extra); uint32_t *next = realloc(g->arena, new_cap * sizeof(*next)); if (!next) { e->allocated = false; diff --git a/src/hist.c b/src/hist.c index 123ed7b..bff1325 100644 --- a/src/hist.c +++ b/src/hist.c @@ -67,8 +67,7 @@ static TERM_NOINLINE bool ring_grow(struct hist *h, uint64_t n) uint64_t cap = h->ring ? (uint64_t)h->ring_mask + 1 : 65536; if (cap < 65536) cap = 65536; - while (cap < h->head - h->tail + n) - cap *= 2; + cap = term_grow_cap64(cap, h->head - h->tail + n); struct hline *ring = term_aligned_alloc(64, (size_t)cap * sizeof(*ring)); if (!ring) return false; diff --git a/src/input.c b/src/input.c index 49e41b2..63313be 100644 --- a/src/input.c +++ b/src/input.c @@ -23,20 +23,28 @@ static inline void sb_ch(struct seqbuf *sb, char c) sb->left--; } +static inline void sb_bytes(struct seqbuf *sb, const char *s, size_t len) +{ + if (sb->overflow || len > sb->left) { + sb->overflow = true; + sb->left = 0; + return; + } + memcpy(sb->p, s, len); + sb->p += len; + sb->left -= len; +} + static inline void sb_u32(struct seqbuf *sb, uint32_t val) { char tmp[10]; - size_t n = 0; + char *end = tmp + sizeof(tmp); + char *p = end; do { - tmp[n++] = (char)('0' + (val % 10)); + *--p = (char)('0' + (val % 10)); val /= 10; } while (val); - while (n) sb_ch(sb, tmp[--n]); -} - -static inline void sb_str(struct seqbuf *sb, const char *s, size_t len) -{ - for (size_t i = 0; i < len; i++) sb_ch(sb, s[i]); + sb_bytes(sb, p, (size_t)(end - p)); } static inline size_t sb_len(const struct seqbuf *sb, const char *buf) @@ -44,14 +52,6 @@ static inline size_t sb_len(const struct seqbuf *sb, const char *buf) return (size_t)(sb->p - buf); } -static inline void sb_bytes(struct seqbuf *sb, const char *s, size_t len) -{ - if (len > sb->left) { sb->overflow = true; return; } - memcpy(sb->p, s, len); - sb->p += len; - sb->left -= len; -} - struct kitty_desc { uint32_t code; char final; @@ -323,16 +323,9 @@ static unsigned toggle_locks(enum term_key key, enum term_key_action action, } /* Convert logical input modifiers to terminal protocol bits. */ -static unsigned protocol_modifiers(unsigned mods) +static inline unsigned protocol_modifiers(unsigned mods) { - return ((mods & TERM_MOD_SHIFT) ? 1u : 0u) | - ((mods & TERM_MOD_ALT) ? 2u : 0u) | - ((mods & TERM_MOD_CTRL) ? 4u : 0u) | - ((mods & TERM_MOD_SUPER) ? 8u : 0u) | - ((mods & TERM_MOD_HYPER) ? 16u : 0u) | - ((mods & TERM_MOD_META) ? 32u : 0u) | - ((mods & TERM_MOD_CAPS) ? 64u : 0u) | - ((mods & TERM_MOD_NUM) ? 128u : 0u); + return mods & 0xff; } static void format_kitty(struct seqbuf *sb, uint32_t code, char final, @@ -340,51 +333,38 @@ static void format_kitty(struct seqbuf *sb, uint32_t code, char final, uint32_t base, unsigned mods, unsigned event, bool report_text, uint32_t text) { - char tmp[64]; - struct seqbuf body; - sb_init(&body, tmp, sizeof(tmp)); - + sb_bytes(sb, "\033[", 2); if (final != 'u' && final != '~' && code <= 1 && !shifted && !base && !mods && !(flags & TERM_KEYBOARD_EVENT_TYPES) && !report_text) { + sb_ch(sb, final); + return; + } - sb_ch(&body, '\033'); - sb_ch(&body, '['); - sb_ch(&body, final); - } else { - sb_ch(&body, '\033'); - sb_ch(&body, '['); - sb_u32(&body, code); - if ((flags & TERM_KEYBOARD_ALTERNATE_KEYS) && !functional) { - if (shifted) { - sb_ch(&body, ':'); - sb_u32(&body, shifted); - } - if (base) { - sb_ch(&body, ':'); - if (!shifted) sb_ch(&body, ':'); - sb_u32(&body, base); - } - } - if (mods || (flags & TERM_KEYBOARD_EVENT_TYPES) || report_text) { - sb_ch(&body, ';'); - sb_u32(&body, mods + 1); - } - if ((flags & TERM_KEYBOARD_EVENT_TYPES) && event != 1) { - sb_ch(&body, ':'); - sb_u32(&body, event); + sb_u32(sb, code); + if ((flags & TERM_KEYBOARD_ALTERNATE_KEYS) && !functional) { + if (shifted) { + sb_ch(sb, ':'); + sb_u32(sb, shifted); } - if (report_text) { - sb_ch(&body, ';'); - sb_u32(&body, text); + if (base) { + sb_ch(sb, ':'); + if (!shifted) sb_ch(sb, ':'); + sb_u32(sb, base); } - sb_ch(&body, final); } - - if (body.overflow) { - sb->overflow = true; - return; + if (mods || (flags & TERM_KEYBOARD_EVENT_TYPES) || report_text) { + sb_ch(sb, ';'); + sb_u32(sb, mods + 1); } - sb_str(sb, tmp, sb_len(&body, tmp)); + if ((flags & TERM_KEYBOARD_EVENT_TYPES) && event != 1) { + sb_ch(sb, ':'); + sb_u32(sb, event); + } + if (report_text) { + sb_ch(sb, ';'); + sb_u32(sb, text); + } + sb_ch(sb, final); } static bool encode_kitty(const struct term_keyboard_state *s, @@ -458,8 +438,7 @@ static bool encode_legacy(const struct term_key_event *ev, { if (m->app_keypad && (unsigned)ev->keypad < TERM_KEY_COUNT && legacy_desc[ev->keypad].keypad_ss3) { - sb_ch(sb, '\033'); - sb_ch(sb, 'O'); + sb_bytes(sb, "\033O", 2); sb_ch(sb, legacy_desc[ev->keypad].keypad_ss3); return true; } @@ -486,8 +465,7 @@ static bool encode_legacy(const struct term_key_event *ev, const struct legacy_desc *d = &legacy_desc[k]; if (d->final) { if (d->tilde) { - sb_ch(sb, '\033'); - sb_ch(sb, '['); + sb_bytes(sb, "\033[", 2); sb_u32(sb, d->tilde); if (csi) { sb_ch(sb, ';'); @@ -497,10 +475,7 @@ static bool encode_legacy(const struct term_key_event *ev, return true; } if (csi) { - sb_ch(sb, '\033'); - sb_ch(sb, '['); - sb_ch(sb, '1'); - sb_ch(sb, ';'); + sb_bytes(sb, "\033[1;", 4); sb_u32(sb, p); sb_ch(sb, d->final); return true; @@ -518,7 +493,7 @@ static bool encode_legacy(const struct term_key_event *ev, sb_ch(sb, m->backarrow_bs ? '\x08' : '\x7f'); return true; case TERM_KEY_TAB: - if (shift) { sb_str(sb, "\033[Z", 3); } + if (shift) { sb_bytes(sb, "\033[Z", 3); } else { sb_ch(sb, '\t'); } return true; case TERM_KEY_ESCAPE: sb_ch(sb, '\033'); return true; @@ -528,7 +503,7 @@ static bool encode_legacy(const struct term_key_event *ev, if (ev->text && !ev->text_deferred) { char tmp[8]; size_t n = (size_t)utf8_encode(ev->text, tmp); - sb_str(sb, tmp, n); + sb_bytes(sb, tmp, n); return true; } return false; @@ -550,13 +525,35 @@ bool term_keyboard_key_down(const struct term_keyboard_state *s, enum term_key k ((s->down[key >> 6] >> (key & 63)) & 1); } +static inline bool legacy_is_escape(const struct term_key_event *ev, + const struct term_modes *m) +{ + if (m->app_keypad && (unsigned)ev->keypad < TERM_KEY_COUNT && + legacy_desc[ev->keypad].keypad_ss3) + return true; + if ((ev->mods & TERM_MOD_CTRL) && control_base_char(ev) == '[') + return true; + enum term_key k = ev->key; + if ((unsigned)k < TERM_KEY_COUNT && legacy_desc[k].equiv != TERM_KEY_NONE) + k = legacy_desc[k].equiv; + if ((unsigned)k < TERM_KEY_COUNT && legacy_desc[k].final) + return true; + if (k == TERM_KEY_ESCAPE) + return true; + if (k == TERM_KEY_TAB && (ev->mods & TERM_MOD_SHIFT)) + return true; + if (ev->text == '\033') + return true; + return false; +} + struct term_input_result term_input_encode_key(const struct term_keyboard_state *s, const struct term_key_event *ev, const struct term_modes *m, char *buf, size_t buf_sz) { struct term_input_result out = {0}; - if (!s || !ev || !m || !buf || + if (!ev || !m || !buf || (ev->action != TERM_KEY_PRESS && ev->action != TERM_KEY_REPEAT && ev->action != TERM_KEY_RELEASE) || (unsigned)ev->key >= TERM_KEY_COUNT) @@ -570,46 +567,20 @@ struct term_input_result term_input_encode_key(const struct term_keyboard_state if (ev->action == TERM_KEY_RELEASE) return out; - bool alt = (ev->mods & TERM_MOD_ALT) != 0; - struct seqbuf body; - char tmp[64]; - if (alt) { - sb_init(&body, tmp, sizeof(tmp)); - } else { - body = sb; - } + if ((ev->mods & TERM_MOD_ALT) && !legacy_is_escape(ev, m)) + sb_ch(&sb, '\033'); - handled = encode_legacy(ev, m, &body); - if (handled) { - if (body.overflow) { - sb.overflow = true; - } else { - size_t n = sb_len(&body, alt ? tmp : buf); - if (alt) { - bool already_esc = n && tmp[0] == '\033'; - size_t prefix = !already_esc; - if (n + prefix <= buf_sz) { - if (prefix) buf[0] = '\033'; - memcpy(buf + prefix, tmp, n); - n += prefix; - } else { - sb.overflow = true; - n = 0; - } - } - out.len = n; - } - } - } else { - out.len = sb_len(&sb, buf); + handled = encode_legacy(ev, m, &sb); } if (!handled) return out; + if (sb.overflow) { out.status = TERM_INPUT_BUFFER_FULL; out.len = 0; } else { + out.len = sb_len(&sb, buf); out.status = out.len ? TERM_INPUT_BYTES : TERM_INPUT_CONSUMED; } return out; @@ -665,18 +636,15 @@ struct term_input_result term_input_encode_mouse(const struct term_mouse_event * if (m->mouse_encoding == TERM_MOUSE_ENC_SGR || m->mouse_encoding == TERM_MOUSE_ENC_SGR_PIXELS) { - sb_ch(&sb, '\033'); - sb_ch(&sb, '['); - sb_ch(&sb, '<'); + sb_bytes(&sb, "\033[<", 3); sb_u32(&sb, (uint32_t)code); sb_ch(&sb, ';'); - sb_u32(&sb, (uint32_t)report_x); + sb_u32(&sb, report_x); sb_ch(&sb, ';'); - sb_u32(&sb, (uint32_t)report_y); + sb_u32(&sb, report_y); sb_ch(&sb, ev->kind == TERM_MOUSE_RELEASE ? 'm' : 'M'); } else if (m->mouse_encoding == TERM_MOUSE_ENC_URXVT) { - sb_ch(&sb, '\033'); - sb_ch(&sb, '['); + sb_bytes(&sb, "\033[", 2); sb_u32(&sb, (uint32_t)(legacy_code + 32)); sb_ch(&sb, ';'); sb_u32(&sb, (uint32_t)(cell.x + 1)); @@ -688,21 +656,22 @@ struct term_input_result term_input_encode_mouse(const struct term_mouse_event * (unsigned)legacy_code + 32, (unsigned)cell.x + 33, (unsigned)cell.y + 33, }; - sb_str(&sb, "\033[M", 3); + sb_bytes(&sb, "\033[M", 3); for (int i = 0; i < 3; i++) { char tmp[8]; size_t n = (size_t)utf8_encode(values[i], tmp); - sb_str(&sb, tmp, n); + sb_bytes(&sb, tmp, n); } } else { if (legacy_code > 223 || cell.x > 222 || cell.y > 222) return out; - sb_ch(&sb, '\033'); - sb_ch(&sb, '['); - sb_ch(&sb, 'M'); - sb_ch(&sb, (char)(legacy_code + 32)); - sb_ch(&sb, (char)(cell.x + 33)); - sb_ch(&sb, (char)(cell.y + 33)); + char tmp[6] = { + '\033', '[', 'M', + (char)(legacy_code + 32), + (char)(cell.x + 33), + (char)(cell.y + 33), + }; + sb_bytes(&sb, tmp, 6); } if (sb.overflow) { @@ -737,6 +706,7 @@ struct term_input_result term_input_encode_paste(const struct term_modes *m, if (m->bracketed_paste) sb_bytes(&sb, "\033[200~", 6); size_t body = sb_len(&sb, buf); for (size_t i = 0; i < len;) { + if (sb.overflow) break; size_t start = i; while (i < len && (unsigned char)text[i] >= 0x20 && (unsigned char)text[i] < 0x7f) i++; @@ -744,15 +714,21 @@ struct term_input_result term_input_encode_paste(const struct term_modes *m, sb_bytes(&sb, text + start, i - start); continue; } + unsigned char c = (unsigned char)text[i]; + if (c < 0x80) { + if (c == '\r' || c == '\n') { + sb_ch(&sb, '\r'); + if (c == '\r' && i + 1 < len && text[i + 1] == '\n') i++; + } else if (c == '\t') { + sb_ch(&sb, '\t'); + } + i++; + continue; + } uint32_t cp; int n = utf8_decode_one(text + i, len - i, &cp); if (!n) { i++; continue; } - if (cp == '\r' || cp == '\n') { - sb_ch(&sb, '\r'); - if (cp == '\r' && i + 1 < len && text[i + 1] == '\n') i++; - } else if (cp == '\t') { - sb_ch(&sb, '\t'); - } else if (cp >= 0xa0 && !is_direction_format(cp)) { + if (cp >= 0xa0 && !is_direction_format(cp)) { sb_bytes(&sb, text + i, (size_t)n); } i += (size_t)n; diff --git a/src/palette.c b/src/palette.c index 1b27e3a..a8add4e 100644 --- a/src/palette.c +++ b/src/palette.c @@ -117,48 +117,44 @@ void term_theme_high_contrast(struct term_theme *theme) /* Finds the slot for key and the default it resets to. Keys are * foreground, background, and color0 to color15. */ -static bool palette_slot(struct term_theme *p, const struct term_theme *def, - const char *key, uint32_t **slot, - const uint32_t **fallback) +static uint32_t *palette_slot(struct term_theme *p, const char *key, + uint32_t *fallback) { if (!strcmp(key, "foreground")) { - *slot = &p->fg; - *fallback = &def->fg; - return true; + *fallback = builtin_theme.fg; + return &p->fg; } if (!strcmp(key, "background")) { - *slot = &p->bg; - *fallback = &def->bg; - return true; + *fallback = builtin_theme.bg; + return &p->bg; } if (strncmp(key, "color", 5)) - return false; + return NULL; const char *digits = key + 5; if (!*digits || (*digits == '0' && digits[1])) - return false; + return NULL; int i = 0; for (const char *d = digits; *d; d++) { if (*d < '0' || *d > '9') - return false; + return NULL; i = i * 10 + (*d - '0'); if (i > 15) - return false; + return NULL; } - *slot = &p->ansi[i]; - *fallback = &def->ansi[i]; - return true; + *fallback = builtin_theme.ansi[i]; + return &p->ansi[i]; } bool term_theme_set_key(struct term_theme *p, const char *key, const char *val) { - struct term_theme def; - term_theme_default(&def); - uint32_t *slot; - const uint32_t *fallback; - if (!palette_slot(p, &def, key, &slot, &fallback)) + if (!p || !key || !val) + return false; + uint32_t fallback; + uint32_t *slot = palette_slot(p, key, &fallback); + if (!slot) return false; if (!val[0]) { - *slot = *fallback; + *slot = fallback; return true; } uint32_t v; @@ -187,7 +183,6 @@ static bool parse_hex_channel(const char *s, size_t len, uint8_t *out) static bool parse_fraction(const char *s, size_t len, uint8_t *out) { - if (len == 0) return false; bool seen_digit = false; @@ -195,12 +190,7 @@ static bool parse_fraction(const char *s, size_t len, uint8_t *out) unsigned integer = 0; unsigned frac = 0; unsigned frac_digits = 0; - size_t i = 0; - if (s[0] == '.') { - seen_dot = true; - i = 1; - } - for (; i < len; i++) { + for (size_t i = 0; i < len; i++) { char c = s[i]; if (c >= '0' && c <= '9') { seen_digit = true; @@ -222,14 +212,12 @@ static bool parse_fraction(const char *s, size_t len, uint8_t *out) } if (!seen_digit) return false; - double value = (double)integer; - if (frac_digits > 0) { - double f = (double)frac; - for (unsigned k = 0; k < frac_digits; k++) - f /= 10.0; - value += f; - } - if (value > 1.0) value = 1.0; + static const double pow10[10] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + }; + double value = (double)integer + (double)frac / pow10[frac_digits]; + if (value > 1.0) + value = 1.0; *out = (uint8_t)(value * 255.0 + 0.5); return true; } @@ -249,6 +237,17 @@ static size_t rtrimmed_len(const char *s) return n; } +static bool parse_hex_rgb(const char *s, size_t cw, uint32_t *out) +{ + uint8_t r, g, b; + if (!parse_hex_channel(s, cw, &r) || + !parse_hex_channel(s + cw, cw, &g) || + !parse_hex_channel(s + 2 * cw, cw, &b)) + return false; + *out = term_rgb(r, g, b); + return true; +} + bool term_color_parse(const char *input, uint32_t *out) { if (!input || !out) @@ -259,18 +258,10 @@ bool term_color_parse(const char *input, uint32_t *out) return false; if (s[0] == '#') { - const char *body = s + 1; size_t blen = len - 1; - size_t cw = blen / 3; if (blen != 3 && blen != 6 && blen != 9 && blen != 12) return false; - uint8_t r, g, b; - if (!parse_hex_channel(body, cw, &r) || - !parse_hex_channel(body + cw, cw, &g) || - !parse_hex_channel(body + 2 * cw, cw, &b)) - return false; - *out = term_rgb(r, g, b); - return true; + return parse_hex_rgb(s + 1, blen / 3, out); } if (len >= 4 && (s[0] == 'r' || s[0] == 'R') && @@ -303,51 +294,55 @@ bool term_color_parse(const char *input, uint32_t *out) } if (ci != 3) return false; - uint8_t r, g, b; - if (intensity) { - if (!parse_fraction(channels[0], chlens[0], &r) || - !parse_fraction(channels[1], chlens[1], &g) || - !parse_fraction(channels[2], chlens[2], &b)) - return false; - } else { - if (!parse_hex_channel(channels[0], chlens[0], &r) || - !parse_hex_channel(channels[1], chlens[1], &g) || - !parse_hex_channel(channels[2], chlens[2], &b)) + uint8_t c[3]; + for (int k = 0; k < 3; k++) { + bool ok = intensity + ? parse_fraction(channels[k], chlens[k], &c[k]) + : parse_hex_channel(channels[k], chlens[k], &c[k]); + if (!ok) return false; } - *out = term_rgb(r, g, b); + *out = term_rgb(c[0], c[1], c[2]); return true; } - if (len == 6 || len == 3) { - uint8_t r, g, b; - size_t cw = len / 3; - if (!parse_hex_channel(s, cw, &r) || - !parse_hex_channel(s + cw, cw, &g) || - !parse_hex_channel(s + 2 * cw, cw, &b)) - return false; - *out = term_rgb(r, g, b); - return true; - } + if (len == 6 || len == 3) + return parse_hex_rgb(s, len / 3, out); return false; } struct lab { float l, a, b; }; +static inline float lab_f(float t) +{ + return t > 0.008856f ? cbrtf(t) : 7.787f * t + 16.0f / 116.0f; +} + +static inline float lab_f_inv(float t) +{ + float t3 = t * t * t; + return t3 > 0.008856f ? t3 : (t - 16.0f / 116.0f) / 7.787f; +} + +static inline uint8_t quantize_channel(float c) +{ + if (c < 0.0f) + return 0; + if (c > 1.0f) + return 255; + return (uint8_t)(c * 255.0f + 0.5f); +} + static struct lab lab_from_rgb(uint32_t argb) { float r = srgb_linear[(argb >> 16) & 0xFF]; float g = srgb_linear[(argb >> 8) & 0xFF]; float b = srgb_linear[argb & 0xFF]; - float x = (r * 0.4124564f + g * 0.3575761f + b * 0.1804375f) / 0.95047f; - float y = r * 0.2126729f + g * 0.7151522f + b * 0.0721750f; - float z = (r * 0.0193339f + g * 0.1191920f + b * 0.9503041f) / 1.08883f; - - x = x > 0.008856f ? cbrtf(x) : 7.787f * x + 16.0f / 116.0f; - y = y > 0.008856f ? cbrtf(y) : 7.787f * y + 16.0f / 116.0f; - z = z > 0.008856f ? cbrtf(z) : 7.787f * z + 16.0f / 116.0f; + float x = lab_f((r * 0.4124564f + g * 0.3575761f + b * 0.1804375f) / 0.95047f); + float y = lab_f(r * 0.2126729f + g * 0.7151522f + b * 0.0721750f); + float z = lab_f((r * 0.0193339f + g * 0.1191920f + b * 0.9503041f) / 1.08883f); struct lab lab; lab.l = 116.0f * y - 16.0f; @@ -362,32 +357,15 @@ static uint32_t lab_to_rgb(struct lab lab) float x = lab.a / 500.0f + y; float z = y - lab.b / 200.0f; - float x3 = x * x * x; - float y3 = y * y * y; - float z3 = z * z * z; - float xf = (x3 > 0.008856f ? x3 : (x - 16.0f / 116.0f) / 7.787f) * 0.95047f; - float yf = (y3 > 0.008856f ? y3 : (y - 16.0f / 116.0f) / 7.787f); - float zf = (z3 > 0.008856f ? z3 : (z - 16.0f / 116.0f) / 7.787f) * 1.08883f; - - float r = xf * 3.2404542f - yf * 1.5371385f - zf * 0.4985314f; - float g = -xf * 0.9692660f + yf * 1.8760108f + zf * 0.0415560f; - float b = xf * 0.0556434f - yf * 0.2040259f + zf * 1.0572252f; - - r = linf_to_srgbf(r); - g = linf_to_srgbf(g); - b = linf_to_srgbf(b); - - if (r < 0.0f) r = 0.0f; - if (r > 1.0f) r = 1.0f; - if (g < 0.0f) g = 0.0f; - if (g > 1.0f) g = 1.0f; - if (b < 0.0f) b = 0.0f; - if (b > 1.0f) b = 1.0f; - - uint8_t rb = (uint8_t)(r * 255.0f + 0.5f); - uint8_t gb = (uint8_t)(g * 255.0f + 0.5f); - uint8_t bb = (uint8_t)(b * 255.0f + 0.5f); - return term_rgb(rb, gb, bb); + float xf = lab_f_inv(x) * 0.95047f; + float yf = lab_f_inv(y); + float zf = lab_f_inv(z) * 1.08883f; + + float r = linf_to_srgbf(xf * 3.2404542f - yf * 1.5371385f - zf * 0.4985314f); + float g = linf_to_srgbf(-xf * 0.9692660f + yf * 1.8760108f + zf * 0.0415560f); + float b = linf_to_srgbf(xf * 0.0556434f - yf * 0.2040259f + zf * 1.0572252f); + + return term_rgb(quantize_channel(r), quantize_channel(g), quantize_channel(b)); } static struct lab lab_lerp(float t, struct lab a, struct lab b) @@ -426,22 +404,31 @@ static void derive_palette(uint32_t *p, const struct term_theme *theme) base8[7] = tmp; } - int idx = 16; - for (int ri = 0; ri < 6; ri++) { - float tr = (float)ri / 5.0f; - struct lab c0 = lab_lerp(tr, base8[0], base8[1]); - struct lab c1 = lab_lerp(tr, base8[2], base8[3]); - struct lab c2 = lab_lerp(tr, base8[4], base8[5]); - struct lab c3 = lab_lerp(tr, base8[6], base8[7]); - for (int gi = 0; gi < 6; gi++) { - float tg = (float)gi / 5.0f; - struct lab c4 = lab_lerp(tg, c0, c1); - struct lab c5 = lab_lerp(tg, c2, c3); - for (int bi = 0; bi < 6; bi++) - p[idx++] = lab_to_rgb(lab_lerp((float)bi / 5.0f, c4, c5)); + static const float steps[6] = { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f }; + struct lab c0[6], c1[6], c2[6], c3[6]; + for (int i = 0; i < 6; i++) { + c0[i] = lab_lerp(steps[i], base8[0], base8[1]); + c1[i] = lab_lerp(steps[i], base8[2], base8[3]); + c2[i] = lab_lerp(steps[i], base8[4], base8[5]); + c3[i] = lab_lerp(steps[i], base8[6], base8[7]); + } + + struct lab c4[36], c5[36]; + int k = 0; + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { + c4[k] = lab_lerp(steps[j], c0[i], c1[i]); + c5[k] = lab_lerp(steps[j], c2[i], c3[i]); + k++; } } + int idx = 16; + for (int i = 0; i < 36; i++) { + for (int j = 0; j < 6; j++) + p[idx++] = lab_to_rgb(lab_lerp(steps[j], c4[i], c5[i])); + } + for (int i = 0; i < 24; i++) { float t = (float)(i + 1) / 25.0f; p[idx++] = lab_to_rgb(lab_lerp(t, base8[0], base8[7])); diff --git a/src/palette.h b/src/palette.h index 5eef03e..f60e41e 100644 --- a/src/palette.h +++ b/src/palette.h @@ -28,7 +28,7 @@ void term_palette_reset_all(struct palette *p); static inline uint32_t palette_resolve(const struct palette *p, term_color c) { - return term_color_is_slot(c) ? p->colors[c & 0x1FFu] : c; + return term_color_is_slot(c) ? p->colors[c & TERM_COLOR_SLOT_INDEX] : c; } #endif diff --git a/src/pen.h b/src/pen.h index d7118b5..8349566 100644 --- a/src/pen.h +++ b/src/pen.h @@ -1,6 +1,7 @@ #ifndef WSAP_TERM_PEN_H #define WSAP_TERM_PEN_H +#include "compat.h" #include "term.h" #include #include @@ -78,9 +79,14 @@ static inline int sgr_extended_color(const int *params, int nparams, uint16_t su if (params[i + 1] != 2) return 0; int colon = 0; - while (i + 2 + colon < nparams && i + 2 + colon < 16 && - (subparams & (1u << (i + 2 + colon)))) - colon++; + int k = i + 2; + if (k < 16 && k < nparams) { + uint32_t shifted = (~(uint32_t)subparams) >> k; + colon = term_ctz32(shifted); + int max_colon = (nparams < 16 ? nparams : 16) - k; + if (colon > max_colon) + colon = max_colon; + } int skip = colon >= 4 ? 1 : 0; if (i + 4 + skip >= nparams) return 0; diff --git a/src/screen.c b/src/screen.c index 681c42b..d6a408d 100644 --- a/src/screen.c +++ b/src/screen.c @@ -10,9 +10,7 @@ static void *grow(struct grow_buf *b, size_t bytes) { if (bytes > b->cap) { - size_t cap = b->cap ? b->cap : 256; - while (cap < bytes) - cap *= 2; + size_t cap = term_grow_cap(b->cap ? b->cap : 256, bytes); void *data = realloc(b->data, cap); if (!data) return NULL; diff --git a/src/selection.c b/src/selection.c index 6f7336f..0eda34f 100644 --- a/src/selection.c +++ b/src/selection.c @@ -38,7 +38,15 @@ static char *link_at_text(const struct term *t, struct term_vector2i cell) char *out = NULL; if (url && col >= start + (int)(url - text)) { size_t n = strlen(url); - while (n && strchr(".,;:!?)]}>\"'", url[n - 1])) url[--n] = 0; + /* Bitmask matching trailing punctuation characters: .,;:!?)]}>"' */ + static const uint64_t mask[4] = { + 0xCC00528600000000ULL, + 0x2000000020000000ULL, + 0ULL, + 0ULL + }; + while (n && (mask[(unsigned char)url[n - 1] >> 6] & (1ULL << ((unsigned char)url[n - 1] & 63)))) + url[--n] = 0; if (col < start + (int)(url - text) + (int)n && n > scheme) out = term_strdup(url); } diff --git a/src/snapshot.c b/src/snapshot.c index dcd1dc0..3869be4 100644 --- a/src/snapshot.c +++ b/src/snapshot.c @@ -107,9 +107,8 @@ static uint32_t arena_add(struct term_snapshot *s, const struct gstore *g, uint3 if (n <= 0) return ' '; if (s->arena_len + (uint32_t)n + 1 > s->arena_cap) { - uint32_t cap = s->arena_cap ? s->arena_cap * 2 : 256; - while (cap < s->arena_len + (uint32_t)n + 1) - cap *= 2; + uint32_t cap = term_grow_cap32(s->arena_cap ? s->arena_cap * 2 : 256, + s->arena_len + (uint32_t)n + 1); uint32_t *a = realloc(s->arena, cap * sizeof(*a)); if (!a) return cps[0]; @@ -228,10 +227,8 @@ static bool term_snapshot_update_inner(struct term_snapshot *s, struct term *t) (size_t)(rows - k) * sizeof(uint32_t)); memmove(s->keys, s->keys + k, (size_t)(rows - k) * sizeof(uint64_t)); - for (int y = rows - k; y < rows; y++) { - s->keys[y] = 0; - s->row_clusters[y] = 0; - } + memset(s->keys + (rows - k), 0, (size_t)k * sizeof(uint64_t)); + memset(s->row_clusters + (rows - k), 0, (size_t)k * sizeof(uint32_t)); } for (int y = 0; y < rows; y++) { @@ -340,7 +337,8 @@ int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t if (off >= s->arena_len) return 0; int n = (int)s->arena[off]; - for (int i = 0; i < n && i < cap; i++) - cps[i] = s->arena[off + 1 + i]; + int copy = n < cap ? n : cap; + if (copy > 0) + memcpy(cps, &s->arena[off + 1], (size_t)copy * sizeof(*cps)); return n; } diff --git a/tests/input_test.c b/tests/input_test.c index 6ee2caa..1a44f42 100644 --- a/tests/input_test.c +++ b/tests/input_test.c @@ -101,9 +101,22 @@ int main(void) typed('b', TERM_MOD_ALT | TERM_MOD_CTRL); expect("\033\002"); K(.key = TERM_KEY_BACKSPACE, .mods = TERM_MOD_ALT); expect("\033\177"); K(.key = TERM_KEY_RETURN, .mods = TERM_MOD_ALT); expect("\033\r"); + K(.key = TERM_KEY_TAB, .mods = TERM_MOD_ALT); expect("\033\t"); + K(.key = TERM_KEY_TAB, .mods = TERM_MOD_ALT | TERM_MOD_SHIFT); expect("\033[Z"); + K(.key = TERM_KEY_ESCAPE, .mods = TERM_MOD_ALT); expect("\033"); K(.key = TERM_KEY_LEFT, .mods = TERM_MOD_ALT); expect("\033[1;3D"); + { + char b_null[16]; + struct term_input_result r = term_input_encode_key( + NULL, + &(struct term_key_event){ .key = TERM_KEY_UP, .action = TERM_KEY_PRESS }, + &(struct term_modes){0}, + b_null, sizeof(b_null)); + assert(r.status == TERM_INPUT_BYTES && r.len == 3 && !memcmp(b_null, "\033[A", 3)); + } + K(.codepoint = 'b', .base = 'b', .text = 0x222B, .text_deferred = true); expect(""); diff --git a/tests/osc_test.c b/tests/osc_test.c index ea0ef08..4d0e32b 100644 --- a/tests/osc_test.c +++ b/tests/osc_test.c @@ -387,6 +387,53 @@ static void test_osc_legacy_titles(void) term_free(t); } +static void test_palette_and_theme(void) +{ + uint32_t c; + /* Hex formats */ + assert(term_color_parse("#f00", &c) && c == 0xFFFF0000u); + assert(term_color_parse("#00ff00", &c) && c == 0xFF00FF00u); + assert(term_color_parse(" #0000ff \t", &c) && c == 0xFF0000FFu); + assert(term_color_parse("123456", &c) && c == 0xFF123456u); + assert(term_color_parse("abc", &c) && c == 0xFFAABBCCu); + assert(term_color_parse("#123456789abc", &c) && c == 0xFF12569au); + + /* rgb: / rgbi: */ + assert(term_color_parse("rgb:ff/80/00", &c) && c == 0xFFFF8000u); + assert(term_color_parse("rgbi:1.0/0.0/0.5", &c) && c == 0xFFFF0080u); + assert(term_color_parse("rgbi:.25/.5/.75", &c)); + + /* Errors */ + assert(!term_color_parse(NULL, &c)); + assert(!term_color_parse("#ff", &c)); + assert(!term_color_parse("", &c)); + assert(!term_color_parse("rgb:ff/80", &c)); + assert(!term_color_parse("rgb:ff/80/00/11", &c)); + assert(!term_color_parse("rgbi:2.0/0/0", &c)); + assert(!term_color_parse("rgbi:./0/0", &c)); + + /* Theme APIs */ + struct term_theme theme; + term_theme_default(&theme); + assert(term_theme_set_key(&theme, "foreground", "#112233") && theme.fg == 0xFF112233u); + assert(term_theme_set_key(&theme, "foreground", "") && theme.fg == 0xFFFFFFFFu); + assert(term_theme_set_key(&theme, "background", "#445566") && theme.bg == 0xFF445566u); + assert(term_theme_set_key(&theme, "background", "") && theme.bg == 0xFF1E1E2Eu); + assert(term_theme_set_key(&theme, "color0", "#010203") && theme.ansi[0] == 0xFF010203u); + assert(term_theme_set_key(&theme, "color15", "#fafbfc") && theme.ansi[15] == 0xFFFAFBFCu); + assert(term_theme_set_key(&theme, "color0", "") && theme.ansi[0] == 0xFF585B70u); + assert(!term_theme_set_key(&theme, "color16", "#000000")); + assert(!term_theme_set_key(&theme, "color01", "#000000")); + assert(!term_theme_set_key(&theme, "invalid", "#000000")); + assert(!term_theme_set_key(NULL, "foreground", "#000000")); + assert(!term_theme_set_key(&theme, NULL, "#000000")); + assert(!term_theme_set_key(&theme, "foreground", NULL)); + + struct term_theme hc; + term_theme_high_contrast(&hc); + assert(hc.bg == 0xFF000000u && hc.fg == 0xFFFFFFFFu); +} + int main(void) { test_osc52(); @@ -395,6 +442,7 @@ int main(void) test_osc_misc(); test_osc_colors(); test_osc_legacy_titles(); + test_palette_and_theme(); puts("PASS: OSC tests"); return 0; } -- cgit v1.2.3