summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 13:36:27 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 13:36:27 +0300
commit4741f7a6dfa144ce65cf3bbdd4a1bc6bee6df2e5 (patch)
tree97932e58eee98623b56a9d9181a3b4289b6ecfc1
parenta0fefd3856a4f03be0abbc51f7a9a0416db69eb5 (diff)
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
-rw-r--r--src/compat.h77
-rw-r--r--src/escape.c179
-rw-r--r--src/gstore.c5
-rw-r--r--src/hist.c3
-rw-r--r--src/input.c228
-rw-r--r--src/palette.c213
-rw-r--r--src/palette.h2
-rw-r--r--src/pen.h12
-rw-r--r--src/screen.c4
-rw-r--r--src/selection.c10
-rw-r--r--src/snapshot.c16
-rw-r--r--tests/input_test.c13
-rw-r--r--tests/osc_test.c48
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 <stdbool.h>
#include <stdint.h>
@@ -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;
}